Home | History | Annotate | Download | only in loader
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     20  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     22  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     23  */
     24 
     25 #include "config.h"
     26 #if ENABLE(FTPDIR)
     27 #include "FTPDirectoryDocument.h"
     28 
     29 #include "CharacterNames.h"
     30 #include "CString.h"
     31 #include "HTMLNames.h"
     32 #include "HTMLTableElement.h"
     33 #include "HTMLTokenizer.h"
     34 #include "LocalizedStrings.h"
     35 #include "Logging.h"
     36 #include "FTPDirectoryParser.h"
     37 #include "SegmentedString.h"
     38 #include "Settings.h"
     39 #include "SharedBuffer.h"
     40 #include "Text.h"
     41 
     42 #include <wtf/CurrentTime.h>
     43 #include <wtf/StdLibExtras.h>
     44 
     45 using namespace std;
     46 
     47 namespace WebCore {
     48 
     49 using namespace HTMLNames;
     50 
     51 class FTPDirectoryTokenizer : public HTMLTokenizer {
     52 public:
     53     FTPDirectoryTokenizer(HTMLDocument*);
     54 
     55     virtual void write(const SegmentedString&, bool appendData);
     56     virtual void finish();
     57 
     58     virtual bool isWaitingForScripts() const { return false; }
     59 
     60     inline void checkBuffer(int len = 10)
     61     {
     62         if ((m_dest - m_buffer) > m_size - len) {
     63             // Enlarge buffer
     64             int newSize = max(m_size * 2, m_size + len);
     65             int oldOffset = m_dest - m_buffer;
     66             m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar)));
     67             m_dest = m_buffer + oldOffset;
     68             m_size = newSize;
     69         }
     70     }
     71 
     72 private:
     73     // The tokenizer will attempt to load the document template specified via the preference
     74     // Failing that, it will fall back and create the basic document which will have a minimal
     75     // table for presenting the FTP directory in a useful manner
     76     bool loadDocumentTemplate();
     77     void createBasicDocument();
     78 
     79     void parseAndAppendOneLine(const String&);
     80     void appendEntry(const String& name, const String& size, const String& date, bool isDirectory);
     81     PassRefPtr<Element> createTDForFilename(const String&);
     82 
     83     Document* m_doc;
     84     RefPtr<HTMLTableElement> m_tableElement;
     85 
     86     bool m_skipLF;
     87     bool m_parsedTemplate;
     88 
     89     int m_size;
     90     UChar* m_buffer;
     91     UChar* m_dest;
     92     String m_carryOver;
     93 
     94     ListState m_listState;
     95 };
     96 
     97 FTPDirectoryTokenizer::FTPDirectoryTokenizer(HTMLDocument* doc)
     98     : HTMLTokenizer(doc, false)
     99     , m_doc(doc)
    100     , m_skipLF(false)
    101     , m_parsedTemplate(false)
    102     , m_size(254)
    103     , m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)))
    104     , m_dest(m_buffer)
    105 {
    106 }
    107 
    108 void FTPDirectoryTokenizer::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory)
    109 {
    110     ExceptionCode ec;
    111 
    112     RefPtr<Element> rowElement = m_tableElement->insertRow(-1, ec);
    113     rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec);
    114 
    115     RefPtr<Element> element = m_doc->createElement(tdTag, false);
    116     element->appendChild(Text::create(m_doc, String(&noBreakSpace, 1)), ec);
    117     if (isDirectory)
    118         element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeDirectory", ec);
    119     else
    120         element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeFile", ec);
    121     rowElement->appendChild(element, ec);
    122 
    123     element = createTDForFilename(filename);
    124     element->setAttribute("class", "ftpDirectoryFileName", ec);
    125     rowElement->appendChild(element, ec);
    126 
    127     element = m_doc->createElement(tdTag, false);
    128     element->appendChild(Text::create(m_doc, date), ec);
    129     element->setAttribute("class", "ftpDirectoryFileDate", ec);
    130     rowElement->appendChild(element, ec);
    131 
    132     element = m_doc->createElement(tdTag, false);
    133     element->appendChild(Text::create(m_doc, size), ec);
    134     element->setAttribute("class", "ftpDirectoryFileSize", ec);
    135     rowElement->appendChild(element, ec);
    136 }
    137 
    138 PassRefPtr<Element> FTPDirectoryTokenizer::createTDForFilename(const String& filename)
    139 {
    140     ExceptionCode ec;
    141 
    142     String fullURL = m_doc->baseURL().string();
    143     if (fullURL[fullURL.length() - 1] == '/')
    144         fullURL.append(filename);
    145     else
    146         fullURL.append("/" + filename);
    147 
    148     RefPtr<Element> anchorElement = m_doc->createElement(aTag, false);
    149     anchorElement->setAttribute("href", fullURL, ec);
    150     anchorElement->appendChild(Text::create(m_doc, filename), ec);
    151 
    152     RefPtr<Element> tdElement = m_doc->createElement(tdTag, false);
    153     tdElement->appendChild(anchorElement, ec);
    154 
    155     return tdElement.release();
    156 }
    157 
    158 static String processFilesizeString(const String& size, bool isDirectory)
    159 {
    160     if (isDirectory)
    161         return "--";
    162 
    163     bool valid;
    164     int64_t bytes = size.toUInt64(&valid);
    165     if (!valid)
    166         return unknownFileSizeText();
    167 
    168     if (bytes < 1000000)
    169         return String::format("%.2f KB", static_cast<float>(bytes)/1000);
    170 
    171     if (bytes < 1000000000)
    172         return String::format("%.2f MB", static_cast<float>(bytes)/1000000);
    173 
    174     return String::format("%.2f GB", static_cast<float>(bytes)/1000000000);
    175 }
    176 
    177 static bool wasLastDayOfMonth(int year, int month, int day)
    178 {
    179     static int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    180     if (month < 0 || month > 11)
    181         return false;
    182 
    183     if (month == 2) {
    184         if (year % 4 == 0 && (year % 100 || year % 400 == 0)) {
    185             if (day == 29)
    186                 return true;
    187             return false;
    188         }
    189 
    190         if (day == 28)
    191             return true;
    192         return false;
    193     }
    194 
    195     return lastDays[month] == day;
    196 }
    197 
    198 static String processFileDateString(const FTPTime& fileTime)
    199 {
    200     // FIXME: Need to localize this string?
    201 
    202     String timeOfDay;
    203 
    204     if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) {
    205         int hour = fileTime.tm_hour;
    206         ASSERT(hour >= 0 && hour < 24);
    207 
    208         if (hour < 12) {
    209             if (hour == 0)
    210                 hour = 12;
    211             timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min);
    212         } else {
    213             hour = hour - 12;
    214             if (hour == 0)
    215                 hour = 12;
    216             timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min);
    217         }
    218     }
    219 
    220     // If it was today or yesterday, lets just do that - but we have to compare to the current time
    221     struct tm now;
    222     time_t now_t = time(NULL);
    223     getLocalTime(&now_t, &now);
    224 
    225     // localtime does "year = current year - 1900", compensate for that for readability and comparison purposes
    226     now.tm_year += 1900;
    227 
    228     if (fileTime.tm_year == now.tm_year) {
    229         if (fileTime.tm_mon == now.tm_mon) {
    230             if (fileTime.tm_mday == now.tm_mday)
    231                 return "Today" + timeOfDay;
    232             if (fileTime.tm_mday == now.tm_mday - 1)
    233                 return "Yesterday" + timeOfDay;
    234         }
    235 
    236         if (now.tm_mday == 1 && (now.tm_mon == fileTime.tm_mon + 1 || (now.tm_mon == 0 && fileTime.tm_mon == 11)) &&
    237             wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday))
    238                 return "Yesterday" + timeOfDay;
    239     }
    240 
    241     if (fileTime.tm_year == now.tm_year - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.tm_mon == 1 && now.tm_mday == 1)
    242         return "Yesterday" + timeOfDay;
    243 
    244     static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
    245 
    246     int month = fileTime.tm_mon;
    247     if (month < 0 || month > 11)
    248         month = 12;
    249 
    250     String dateString;
    251 
    252     if (fileTime.tm_year > -1)
    253         dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year);
    254     else
    255         dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year);
    256 
    257     return dateString + timeOfDay;
    258 }
    259 
    260 void FTPDirectoryTokenizer::parseAndAppendOneLine(const String& inputLine)
    261 {
    262     ListResult result;
    263     CString latin1Input = inputLine.latin1();
    264 
    265     FTPEntryType typeResult = parseOneFTPLine(latin1Input.data(), m_listState, result);
    266 
    267     // FTPMiscEntry is a comment or usage statistic which we don't care about, and junk is invalid data - bail in these 2 cases
    268     if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry)
    269         return;
    270 
    271     String filename(result.filename, result.filenameLength);
    272     if (result.type == FTPDirectoryEntry) {
    273         filename.append("/");
    274 
    275         // We have no interest in linking to "current directory"
    276         if (filename == "./")
    277             return;
    278     }
    279 
    280     LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data());
    281 
    282     appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry);
    283 }
    284 
    285 static inline PassRefPtr<SharedBuffer> createTemplateDocumentData(Settings* settings)
    286 {
    287     RefPtr<SharedBuffer> buffer = 0;
    288     if (settings)
    289         buffer = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath());
    290     if (buffer)
    291         LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", buffer->size());
    292     return buffer.release();
    293 }
    294 
    295 bool FTPDirectoryTokenizer::loadDocumentTemplate()
    296 {
    297     DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, templateDocumentData, (createTemplateDocumentData(m_doc->settings())));
    298     // FIXME: Instead of storing the data, we'd rather actually parse the template data into the template Document once,
    299     // store that document, then "copy" it whenever we get an FTP directory listing.  There are complexities with this
    300     // approach that make it worth putting this off.
    301 
    302     if (!templateDocumentData) {
    303         LOG_ERROR("Could not load templateData");
    304         return false;
    305     }
    306 
    307     // Tokenize the template as an HTML document synchronously
    308     setForceSynchronous(true);
    309     HTMLTokenizer::write(String(templateDocumentData->data(), templateDocumentData->size()), true);
    310     setForceSynchronous(false);
    311 
    312     RefPtr<Element> tableElement = m_doc->getElementById("ftpDirectoryTable");
    313     if (!tableElement)
    314         LOG_ERROR("Unable to find element by id \"ftpDirectoryTable\" in the template document.");
    315     else if (!tableElement->hasTagName(tableTag))
    316         LOG_ERROR("Element of id \"ftpDirectoryTable\" is not a table element");
    317     else
    318         m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
    319 
    320     // Bail if we found the table element
    321     if (m_tableElement)
    322         return true;
    323 
    324     // Otherwise create one manually
    325     tableElement = m_doc->createElement(tableTag, false);
    326     m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
    327     ExceptionCode ec;
    328     m_tableElement->setAttribute("id", "ftpDirectoryTable", ec);
    329 
    330     // If we didn't find the table element, lets try to append our own to the body
    331     // If that fails for some reason, cram it on the end of the document as a last
    332     // ditch effort
    333     if (Element* body = m_doc->body())
    334         body->appendChild(m_tableElement, ec);
    335     else
    336         m_doc->appendChild(m_tableElement, ec);
    337 
    338     return true;
    339 }
    340 
    341 void FTPDirectoryTokenizer::createBasicDocument()
    342 {
    343     LOG(FTP, "Creating a basic FTP document structure as no template was loaded");
    344 
    345     // FIXME: Make this "basic document" more acceptable
    346 
    347 
    348     RefPtr<Element> bodyElement = m_doc->createElement(bodyTag, false);
    349 
    350     ExceptionCode ec;
    351     m_doc->appendChild(bodyElement, ec);
    352 
    353     RefPtr<Element> tableElement = m_doc->createElement(tableTag, false);
    354     m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
    355     m_tableElement->setAttribute("id", "ftpDirectoryTable", ec);
    356 
    357     bodyElement->appendChild(m_tableElement, ec);
    358 }
    359 
    360 void FTPDirectoryTokenizer::write(const SegmentedString& s, bool /*appendData*/)
    361 {
    362     // Make sure we have the table element to append to by loading the template set in the pref, or
    363     // creating a very basic document with the appropriate table
    364     if (!m_tableElement) {
    365         if (!loadDocumentTemplate())
    366             createBasicDocument();
    367         ASSERT(m_tableElement);
    368     }
    369 
    370     bool foundNewLine = false;
    371 
    372     m_dest = m_buffer;
    373     SegmentedString str = s;
    374     while (!str.isEmpty()) {
    375         UChar c = *str;
    376 
    377         if (c == '\r') {
    378             *m_dest++ = '\n';
    379             foundNewLine = true;
    380             // possibly skip an LF in the case of an CRLF sequence
    381             m_skipLF = true;
    382         } else if (c == '\n') {
    383             if (!m_skipLF)
    384                 *m_dest++ = c;
    385             else
    386                 m_skipLF = false;
    387         } else {
    388             *m_dest++ = c;
    389             m_skipLF = false;
    390         }
    391 
    392         str.advance();
    393 
    394         // Maybe enlarge the buffer
    395         checkBuffer();
    396     }
    397 
    398     if (!foundNewLine) {
    399         m_dest = m_buffer;
    400         return;
    401     }
    402 
    403     UChar* start = m_buffer;
    404     UChar* cursor = start;
    405 
    406     while (cursor < m_dest) {
    407         if (*cursor == '\n') {
    408             m_carryOver.append(String(start, cursor - start));
    409             LOG(FTP, "%s", m_carryOver.ascii().data());
    410             parseAndAppendOneLine(m_carryOver);
    411             m_carryOver = String();
    412 
    413             start = ++cursor;
    414         } else
    415             cursor++;
    416     }
    417 
    418     // Copy the partial line we have left to the carryover buffer
    419     if (cursor - start > 1)
    420         m_carryOver.append(String(start, cursor - start - 1));
    421 }
    422 
    423 void FTPDirectoryTokenizer::finish()
    424 {
    425     // Possible the last line in the listing had no newline, so try to parse it now
    426     if (!m_carryOver.isEmpty()) {
    427         parseAndAppendOneLine(m_carryOver);
    428         m_carryOver = String();
    429     }
    430 
    431     m_tableElement = 0;
    432     fastFree(m_buffer);
    433 
    434     HTMLTokenizer::finish();
    435 }
    436 
    437 FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame)
    438     : HTMLDocument(frame)
    439 {
    440 #ifndef NDEBUG
    441     LogFTP.state = WTFLogChannelOn;
    442 #endif
    443 }
    444 
    445 Tokenizer* FTPDirectoryDocument::createTokenizer()
    446 {
    447     return new FTPDirectoryTokenizer(this);
    448 }
    449 
    450 }
    451 
    452 #endif // ENABLE(FTPDIR)
    453