Home | History | Annotate | Download | only in html
      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 "HTMLDocumentParser.h"
     30 #include "HTMLNames.h"
     31 #include "HTMLTableElement.h"
     32 #include "LocalizedStrings.h"
     33 #include "Logging.h"
     34 #include "FTPDirectoryParser.h"
     35 #include "SegmentedString.h"
     36 #include "Settings.h"
     37 #include "SharedBuffer.h"
     38 #include "Text.h"
     39 #include <wtf/text/CString.h>
     40 #include <wtf/text/StringConcatenate.h>
     41 #include <wtf/CurrentTime.h>
     42 #include <wtf/StdLibExtras.h>
     43 #include <wtf/unicode/CharacterNames.h>
     44 
     45 using namespace std;
     46 
     47 namespace WebCore {
     48 
     49 using namespace HTMLNames;
     50 
     51 class FTPDirectoryDocumentParser : public HTMLDocumentParser {
     52 public:
     53     static PassRefPtr<FTPDirectoryDocumentParser> create(HTMLDocument* document)
     54     {
     55         return adoptRef(new FTPDirectoryDocumentParser(document));
     56     }
     57 
     58     virtual void append(const SegmentedString&);
     59     virtual void finish();
     60 
     61     virtual bool isWaitingForScripts() const { return false; }
     62 
     63     inline void checkBuffer(int len = 10)
     64     {
     65         if ((m_dest - m_buffer) > m_size - len) {
     66             // Enlarge buffer
     67             int newSize = max(m_size * 2, m_size + len);
     68             int oldOffset = m_dest - m_buffer;
     69             m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar)));
     70             m_dest = m_buffer + oldOffset;
     71             m_size = newSize;
     72         }
     73     }
     74 
     75 private:
     76     FTPDirectoryDocumentParser(HTMLDocument*);
     77 
     78     // The parser will attempt to load the document template specified via the preference
     79     // Failing that, it will fall back and create the basic document which will have a minimal
     80     // table for presenting the FTP directory in a useful manner
     81     bool loadDocumentTemplate();
     82     void createBasicDocument();
     83 
     84     void parseAndAppendOneLine(const String&);
     85     void appendEntry(const String& name, const String& size, const String& date, bool isDirectory);
     86     PassRefPtr<Element> createTDForFilename(const String&);
     87 
     88     RefPtr<HTMLTableElement> m_tableElement;
     89 
     90     bool m_skipLF;
     91     bool m_parsedTemplate;
     92 
     93     int m_size;
     94     UChar* m_buffer;
     95     UChar* m_dest;
     96     String m_carryOver;
     97 
     98     ListState m_listState;
     99 };
    100 
    101 FTPDirectoryDocumentParser::FTPDirectoryDocumentParser(HTMLDocument* document)
    102     : HTMLDocumentParser(document, false)
    103     , m_skipLF(false)
    104     , m_parsedTemplate(false)
    105     , m_size(254)
    106     , m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)))
    107     , m_dest(m_buffer)
    108 {
    109 }
    110 
    111 void FTPDirectoryDocumentParser::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory)
    112 {
    113     ExceptionCode ec;
    114 
    115     RefPtr<Element> rowElement = m_tableElement->insertRow(-1, ec);
    116     rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec);
    117 
    118     RefPtr<Element> element = document()->createElement(tdTag, false);
    119     element->appendChild(Text::create(document(), String(&noBreakSpace, 1)), ec);
    120     if (isDirectory)
    121         element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeDirectory", ec);
    122     else
    123         element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeFile", ec);
    124     rowElement->appendChild(element, ec);
    125 
    126     element = createTDForFilename(filename);
    127     element->setAttribute("class", "ftpDirectoryFileName", ec);
    128     rowElement->appendChild(element, ec);
    129 
    130     element = document()->createElement(tdTag, false);
    131     element->appendChild(Text::create(document(), date), ec);
    132     element->setAttribute("class", "ftpDirectoryFileDate", ec);
    133     rowElement->appendChild(element, ec);
    134 
    135     element = document()->createElement(tdTag, false);
    136     element->appendChild(Text::create(document(), size), ec);
    137     element->setAttribute("class", "ftpDirectoryFileSize", ec);
    138     rowElement->appendChild(element, ec);
    139 }
    140 
    141 PassRefPtr<Element> FTPDirectoryDocumentParser::createTDForFilename(const String& filename)
    142 {
    143     ExceptionCode ec;
    144 
    145     String fullURL = document()->baseURL().string();
    146     if (fullURL[fullURL.length() - 1] == '/')
    147         fullURL.append(filename);
    148     else
    149         fullURL.append("/" + filename);
    150 
    151     RefPtr<Element> anchorElement = document()->createElement(aTag, false);
    152     anchorElement->setAttribute("href", fullURL, ec);
    153     anchorElement->appendChild(Text::create(document(), filename), ec);
    154 
    155     RefPtr<Element> tdElement = document()->createElement(tdTag, false);
    156     tdElement->appendChild(anchorElement, ec);
    157 
    158     return tdElement.release();
    159 }
    160 
    161 static String processFilesizeString(const String& size, bool isDirectory)
    162 {
    163     if (isDirectory)
    164         return "--";
    165 
    166     bool valid;
    167     int64_t bytes = size.toUInt64(&valid);
    168     if (!valid)
    169         return unknownFileSizeText();
    170 
    171     if (bytes < 1000000)
    172         return String::format("%.2f KB", static_cast<float>(bytes)/1000);
    173 
    174     if (bytes < 1000000000)
    175         return String::format("%.2f MB", static_cast<float>(bytes)/1000000);
    176 
    177     return String::format("%.2f GB", static_cast<float>(bytes)/1000000000);
    178 }
    179 
    180 static bool wasLastDayOfMonth(int year, int month, int day)
    181 {
    182     static int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    183     if (month < 0 || month > 11)
    184         return false;
    185 
    186     if (month == 2) {
    187         if (year % 4 == 0 && (year % 100 || year % 400 == 0)) {
    188             if (day == 29)
    189                 return true;
    190             return false;
    191         }
    192 
    193         if (day == 28)
    194             return true;
    195         return false;
    196     }
    197 
    198     return lastDays[month] == day;
    199 }
    200 
    201 static String processFileDateString(const FTPTime& fileTime)
    202 {
    203     // FIXME: Need to localize this string?
    204 
    205     String timeOfDay;
    206 
    207     if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) {
    208         int hour = fileTime.tm_hour;
    209         ASSERT(hour >= 0 && hour < 24);
    210 
    211         if (hour < 12) {
    212             if (hour == 0)
    213                 hour = 12;
    214             timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min);
    215         } else {
    216             hour = hour - 12;
    217             if (hour == 0)
    218                 hour = 12;
    219             timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min);
    220         }
    221     }
    222 
    223     // If it was today or yesterday, lets just do that - but we have to compare to the current time
    224     struct tm now;
    225     time_t now_t = time(NULL);
    226     getLocalTime(&now_t, &now);
    227 
    228     // localtime does "year = current year - 1900", compensate for that for readability and comparison purposes
    229     now.tm_year += 1900;
    230 
    231     if (fileTime.tm_year == now.tm_year) {
    232         if (fileTime.tm_mon == now.tm_mon) {
    233             if (fileTime.tm_mday == now.tm_mday)
    234                 return "Today" + timeOfDay;
    235             if (fileTime.tm_mday == now.tm_mday - 1)
    236                 return "Yesterday" + timeOfDay;
    237         }
    238 
    239         if (now.tm_mday == 1 && (now.tm_mon == fileTime.tm_mon + 1 || (now.tm_mon == 0 && fileTime.tm_mon == 11)) &&
    240             wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday))
    241                 return "Yesterday" + timeOfDay;
    242     }
    243 
    244     if (fileTime.tm_year == now.tm_year - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.tm_mon == 1 && now.tm_mday == 1)
    245         return "Yesterday" + timeOfDay;
    246 
    247     static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
    248 
    249     int month = fileTime.tm_mon;
    250     if (month < 0 || month > 11)
    251         month = 12;
    252 
    253     String dateString;
    254 
    255     if (fileTime.tm_year > -1)
    256         dateString = makeString(months[month], ' ', String::number(fileTime.tm_mday), ", ", String::number(fileTime.tm_year));
    257     else
    258         dateString = makeString(months[month], ' ', String::number(fileTime.tm_mday), ", ", String::number(now.tm_year));
    259 
    260     return dateString + timeOfDay;
    261 }
    262 
    263 void FTPDirectoryDocumentParser::parseAndAppendOneLine(const String& inputLine)
    264 {
    265     ListResult result;
    266     CString latin1Input = inputLine.latin1();
    267 
    268     FTPEntryType typeResult = parseOneFTPLine(latin1Input.data(), m_listState, result);
    269 
    270     // FTPMiscEntry is a comment or usage statistic which we don't care about, and junk is invalid data - bail in these 2 cases
    271     if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry)
    272         return;
    273 
    274     String filename(result.filename, result.filenameLength);
    275     if (result.type == FTPDirectoryEntry) {
    276         filename.append("/");
    277 
    278         // We have no interest in linking to "current directory"
    279         if (filename == "./")
    280             return;
    281     }
    282 
    283     LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data());
    284 
    285     appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry);
    286 }
    287 
    288 static inline PassRefPtr<SharedBuffer> createTemplateDocumentData(Settings* settings)
    289 {
    290     RefPtr<SharedBuffer> buffer = 0;
    291     if (settings)
    292         buffer = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath());
    293     if (buffer)
    294         LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", buffer->size());
    295     return buffer.release();
    296 }
    297 
    298 bool FTPDirectoryDocumentParser::loadDocumentTemplate()
    299 {
    300     DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, templateDocumentData, (createTemplateDocumentData(document()->settings())));
    301     // FIXME: Instead of storing the data, we'd rather actually parse the template data into the template Document once,
    302     // store that document, then "copy" it whenever we get an FTP directory listing.  There are complexities with this
    303     // approach that make it worth putting this off.
    304 
    305     if (!templateDocumentData) {
    306         LOG_ERROR("Could not load templateData");
    307         return false;
    308     }
    309 
    310     HTMLDocumentParser::insert(String(templateDocumentData->data(), templateDocumentData->size()));
    311 
    312     RefPtr<Element> tableElement = document()->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 = document()->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 = document()->body())
    334         body->appendChild(m_tableElement, ec);
    335     else
    336         document()->appendChild(m_tableElement, ec);
    337 
    338     return true;
    339 }
    340 
    341 void FTPDirectoryDocumentParser::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     RefPtr<Element> bodyElement = document()->createElement(bodyTag, false);
    348 
    349     ExceptionCode ec;
    350     document()->appendChild(bodyElement, ec);
    351 
    352     RefPtr<Element> tableElement = document()->createElement(tableTag, false);
    353     m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
    354     m_tableElement->setAttribute("id", "ftpDirectoryTable", ec);
    355 
    356     bodyElement->appendChild(m_tableElement, ec);
    357 }
    358 
    359 void FTPDirectoryDocumentParser::append(const SegmentedString& source)
    360 {
    361     // Make sure we have the table element to append to by loading the template set in the pref, or
    362     // creating a very basic document with the appropriate table
    363     if (!m_tableElement) {
    364         if (!loadDocumentTemplate())
    365             createBasicDocument();
    366         ASSERT(m_tableElement);
    367     }
    368 
    369     bool foundNewLine = false;
    370 
    371     m_dest = m_buffer;
    372     SegmentedString str = source;
    373     while (!str.isEmpty()) {
    374         UChar c = *str;
    375 
    376         if (c == '\r') {
    377             *m_dest++ = '\n';
    378             foundNewLine = true;
    379             // possibly skip an LF in the case of an CRLF sequence
    380             m_skipLF = true;
    381         } else if (c == '\n') {
    382             if (!m_skipLF)
    383                 *m_dest++ = c;
    384             else
    385                 m_skipLF = false;
    386         } else {
    387             *m_dest++ = c;
    388             m_skipLF = false;
    389         }
    390 
    391         str.advance();
    392 
    393         // Maybe enlarge the buffer
    394         checkBuffer();
    395     }
    396 
    397     if (!foundNewLine) {
    398         m_dest = m_buffer;
    399         return;
    400     }
    401 
    402     UChar* start = m_buffer;
    403     UChar* cursor = start;
    404 
    405     while (cursor < m_dest) {
    406         if (*cursor == '\n') {
    407             m_carryOver.append(String(start, cursor - start));
    408             LOG(FTP, "%s", m_carryOver.ascii().data());
    409             parseAndAppendOneLine(m_carryOver);
    410             m_carryOver = String();
    411 
    412             start = ++cursor;
    413         } else
    414             cursor++;
    415     }
    416 
    417     // Copy the partial line we have left to the carryover buffer
    418     if (cursor - start > 1)
    419         m_carryOver.append(String(start, cursor - start - 1));
    420 }
    421 
    422 void FTPDirectoryDocumentParser::finish()
    423 {
    424     // Possible the last line in the listing had no newline, so try to parse it now
    425     if (!m_carryOver.isEmpty()) {
    426         parseAndAppendOneLine(m_carryOver);
    427         m_carryOver = String();
    428     }
    429 
    430     m_tableElement = 0;
    431     fastFree(m_buffer);
    432 
    433     HTMLDocumentParser::finish();
    434 }
    435 
    436 FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame, const KURL& url)
    437     : HTMLDocument(frame, url)
    438 {
    439 #ifndef NDEBUG
    440     LogFTP.state = WTFLogChannelOn;
    441 #endif
    442 }
    443 
    444 PassRefPtr<DocumentParser> FTPDirectoryDocument::createParser()
    445 {
    446     return FTPDirectoryDocumentParser::create(this);
    447 }
    448 
    449 }
    450 
    451 #endif // ENABLE(FTPDIR)
    452