diff options
Diffstat (limited to 'WebCore/loader/FTPDirectoryDocument.cpp')
-rw-r--r-- | WebCore/loader/FTPDirectoryDocument.cpp | 493 |
1 files changed, 493 insertions, 0 deletions
diff --git a/WebCore/loader/FTPDirectoryDocument.cpp b/WebCore/loader/FTPDirectoryDocument.cpp new file mode 100644 index 0000000..0a7e91e --- /dev/null +++ b/WebCore/loader/FTPDirectoryDocument.cpp @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#if ENABLE(FTPDIR) +#include "FTPDirectoryDocument.h" + +#include "CharacterNames.h" +#include "CString.h" +#include "HTMLNames.h" +#include "HTMLTableElement.h" +#include "HTMLTokenizer.h" +#include "LocalizedStrings.h" +#include "Logging.h" +#include "FTPDirectoryParser.h" +#include "SegmentedString.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "Text.h" + +#if PLATFORM(QT) +#include <QDateTime> +// On Windows, use the threadsafe *_r functions provided by pthread. +#elif PLATFORM(WIN_OS) && (USE(PTHREADS) || HAVE(PTHREAD_H)) +#include <pthread.h> +#endif + +using namespace std; + +namespace WebCore { + +using namespace HTMLNames; + +class FTPDirectoryTokenizer : public HTMLTokenizer { +public: + FTPDirectoryTokenizer(HTMLDocument*); + + virtual bool write(const SegmentedString&, bool appendData); + virtual void finish(); + + virtual bool isWaitingForScripts() const { return false; } + + inline void checkBuffer(int len = 10) + { + if ((m_dest - m_buffer) > m_size - len) { + // Enlarge buffer + int newSize = max(m_size * 2, m_size + len); + int oldOffset = m_dest - m_buffer; + m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar))); + m_dest = m_buffer + oldOffset; + m_size = newSize; + } + } + +private: + // The tokenizer will attempt to load the document template specified via the preference + // Failing that, it will fall back and create the basic document which will have a minimal + // table for presenting the FTP directory in a useful manner + bool loadDocumentTemplate(); + void createBasicDocument(); + + void parseAndAppendOneLine(const String&); + void appendEntry(const String& name, const String& size, const String& date, bool isDirectory); + PassRefPtr<Element> createTDForFilename(const String&); + + Document* m_doc; + RefPtr<HTMLTableElement> m_tableElement; + + bool m_skipLF; + bool m_parsedTemplate; + + int m_size; + UChar* m_buffer; + UChar* m_dest; + String m_carryOver; + + ListState m_listState; +}; + +FTPDirectoryTokenizer::FTPDirectoryTokenizer(HTMLDocument* doc) + : HTMLTokenizer(doc, false) + , m_doc(doc) + , m_skipLF(false) + , m_parsedTemplate(false) + , m_size(254) + , m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size))) + , m_dest(m_buffer) +{ +} + +void FTPDirectoryTokenizer::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory) +{ + ExceptionCode ec; + + RefPtr<Element> rowElement = m_tableElement->insertRow(-1, ec); + rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec); + + RefPtr<Element> element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, String(&noBreakSpace, 1)), ec); + if (isDirectory) + element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeDirectory", ec); + else + element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeFile", ec); + rowElement->appendChild(element, ec); + + element = createTDForFilename(filename); + element->setAttribute("class", "ftpDirectoryFileName", ec); + rowElement->appendChild(element, ec); + + element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, date), ec); + element->setAttribute("class", "ftpDirectoryFileDate", ec); + rowElement->appendChild(element, ec); + + element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + element->appendChild(new Text(m_doc, size), ec); + element->setAttribute("class", "ftpDirectoryFileSize", ec); + rowElement->appendChild(element, ec); +} + +PassRefPtr<Element> FTPDirectoryTokenizer::createTDForFilename(const String& filename) +{ + ExceptionCode ec; + + String fullURL = m_doc->baseURL().string(); + if (fullURL[fullURL.length() - 1] == '/') + fullURL.append(filename); + else + fullURL.append("/" + filename); + + RefPtr<Element> anchorElement = m_doc->createElementNS(xhtmlNamespaceURI, "a", ec); + anchorElement->setAttribute("href", fullURL, ec); + anchorElement->appendChild(new Text(m_doc, filename), ec); + + RefPtr<Element> tdElement = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec); + tdElement->appendChild(anchorElement, ec); + + return tdElement.release(); +} + +static String processFilesizeString(const String& size, bool isDirectory) +{ + if (isDirectory) + return "--"; + + bool valid; + int64_t bytes = size.toUInt64(&valid); + if (!valid) + return unknownFileSizeText(); + + if (bytes < 1000000) + return String::format("%.2f KB", static_cast<float>(bytes)/1000); + + if (bytes < 1000000000) + return String::format("%.2f MB", static_cast<float>(bytes)/1000000); + + return String::format("%.2f GB", static_cast<float>(bytes)/1000000000); +} + +static bool wasLastDayOfMonth(int year, int month, int day) +{ + static int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if (month < 0 || month > 11) + return false; + + if (month == 2) { + if (year % 4 == 0 && (year % 100 || year % 400 == 0)) { + if (day == 29) + return true; + return false; + } + + if (day == 28) + return true; + return false; + } + + return lastDays[month] == day; +} + +#if PLATFORM(QT) + +/*! + Replacement for localtime_r() which is not available on MinGW. + + We use this on all of Qt's platforms for portability. + */ +struct tm gmtimeQt(const QDateTime &input) +{ + tm result; + + const QDate date(input.date()); + result.tm_year = date.year() - 1900; + result.tm_mon = date.month(); + result.tm_mday = date.day(); + result.tm_wday = date.dayOfWeek(); + result.tm_yday = date.dayOfYear(); + + const QTime time(input.time()); + result.tm_sec = time.second(); + result.tm_min = time.minute(); + result.tm_hour = time.hour(); + + return result; +} + +static struct tm *localTimeQt(const time_t *const timep, struct tm *result) +{ + const QDateTime dt(QDateTime::fromTime_t(*timep)); + *result = WebCore::gmtimeQt(dt.toLocalTime()); + return result; +} + +#define localtime_r(x, y) localTimeQt(x, y) +#elif PLATFORM(WIN_OS) && !defined(localtime_r) +#define localtime_r(x, y) localtime_s((y), (x)) +#endif + +static String processFileDateString(const FTPTime& fileTime) +{ + // FIXME: Need to localize this string? + + String timeOfDay; + + if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) { + int hour = fileTime.tm_hour; + ASSERT(hour >= 0 && hour < 24); + + if (hour < 12) { + if (hour == 0) + hour = 12; + timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min); + } else { + hour = hour - 12; + if (hour == 0) + hour = 12; + timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min); + } + } + + // If it was today or yesterday, lets just do that - but we have to compare to the current time + struct tm now; + time_t now_t = time(NULL); + localtime_r(&now_t, &now); + + // localtime does "year = current year - 1900", compensate for that for readability and comparison purposes + now.tm_year += 1900; + + if (fileTime.tm_year == now.tm_year) { + if (fileTime.tm_mon == now.tm_mon) { + if (fileTime.tm_mday == now.tm_mday) + return "Today" + timeOfDay; + if (fileTime.tm_mday == now.tm_mday - 1) + return "Yesterday" + timeOfDay; + } + + if (now.tm_mday == 1 && (now.tm_mon == fileTime.tm_mon + 1 || now.tm_mon == 0 && fileTime.tm_mon == 11) && + wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday)) + return "Yesterday" + timeOfDay; + } + + if (fileTime.tm_year == now.tm_year - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.tm_mon == 1 && now.tm_mday == 1) + return "Yesterday" + timeOfDay; + + static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" }; + + int month = fileTime.tm_mon; + if (month < 0 || month > 11) + month = 12; + + String dateString; + + if (fileTime.tm_year > -1) + dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year); + else + dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year); + + return dateString + timeOfDay; +} + +void FTPDirectoryTokenizer::parseAndAppendOneLine(const String& inputLine) +{ + ListResult result; + + FTPEntryType typeResult = parseOneFTPLine(inputLine.latin1().data(), m_listState, result); + + // FTPMiscEntry is a comment or usage statistic which we don't care about, and junk is invalid data - bail in these 2 cases + if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry) + return; + + String filename(result.filename, result.filenameLength); + if (result.type == FTPDirectoryEntry) { + filename.append("/"); + + // We have no interest in linking to "current directory" + if (filename == "./") + return; + } + + LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data()); + + appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry); +} + +bool FTPDirectoryTokenizer::loadDocumentTemplate() +{ + static RefPtr<SharedBuffer> templateDocumentData; + // FIXME: Instead of storing the data, we'd rather actually parse the template data into the template Document once, + // store that document, then "copy" it whenever we get an FTP directory listing. There are complexities with this + // approach that make it worth putting this off. + + if (!templateDocumentData) { + Settings* settings = m_doc->settings(); + if (settings) + templateDocumentData = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath()); + if (templateDocumentData) + LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", templateDocumentData->size()); + } + + if (!templateDocumentData) { + LOG_ERROR("Could not load templateData"); + return false; + } + + // Tokenize the template as an HTML document synchronously + setForceSynchronous(true); + HTMLTokenizer::write(String(templateDocumentData->data(), templateDocumentData->size()), true); + setForceSynchronous(false); + + RefPtr<Element> tableElement = m_doc->getElementById("ftpDirectoryTable"); + if (!tableElement) + LOG_ERROR("Unable to find element by id \"ftpDirectoryTable\" in the template document."); + else if (!tableElement->hasTagName(tableTag)) + LOG_ERROR("Element of id \"ftpDirectoryTable\" is not a table element"); + else + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + + // Bail if we found the table element + if (m_tableElement) + return true; + + // Otherwise create one manually + ExceptionCode ec; + tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec); + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + m_tableElement->setAttribute("id", "ftpDirectoryTable", ec); + + // If we didn't find the table element, lets try to append our own to the body + // If that fails for some reason, cram it on the end of the document as a last + // ditch effort + if (Element* body = m_doc->body()) + body->appendChild(m_tableElement, ec); + else + m_doc->appendChild(m_tableElement, ec); + + return true; +} + +void FTPDirectoryTokenizer::createBasicDocument() +{ + LOG(FTP, "Creating a basic FTP document structure as no template was loaded"); + + // FIXME: Make this "basic document" more acceptable + + ExceptionCode ec; + + RefPtr<Element> bodyElement = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec); + + m_doc->appendChild(bodyElement, ec); + + RefPtr<Element> tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec); + m_tableElement = static_cast<HTMLTableElement*>(tableElement.get()); + m_tableElement->setAttribute("id", "ftpDirectoryTable", ec); + + bodyElement->appendChild(m_tableElement, ec); +} + +bool FTPDirectoryTokenizer::write(const SegmentedString& s, bool appendData) +{ + // Make sure we have the table element to append to by loading the template set in the pref, or + // creating a very basic document with the appropriate table + if (!m_tableElement) { + if (!loadDocumentTemplate()) + createBasicDocument(); + ASSERT(m_tableElement); + } + + bool foundNewLine = false; + + m_dest = m_buffer; + SegmentedString str = s; + while (!str.isEmpty()) { + UChar c = *str; + + if (c == '\r') { + *m_dest++ = '\n'; + foundNewLine = true; + // possibly skip an LF in the case of an CRLF sequence + m_skipLF = true; + } else if (c == '\n') { + if (!m_skipLF) + *m_dest++ = c; + else + m_skipLF = false; + } else { + *m_dest++ = c; + m_skipLF = false; + } + + str.advance(); + + // Maybe enlarge the buffer + checkBuffer(); + } + + if (!foundNewLine) { + m_dest = m_buffer; + return false; + } + + UChar* start = m_buffer; + UChar* cursor = start; + + while (cursor < m_dest) { + if (*cursor == '\n') { + m_carryOver.append(String(start, cursor - start)); + LOG(FTP, "%s", m_carryOver.ascii().data()); + parseAndAppendOneLine(m_carryOver); + m_carryOver = String(); + + start = ++cursor; + } else + cursor++; + } + + // Copy the partial line we have left to the carryover buffer + if (cursor - start > 1) + m_carryOver.append(String(start, cursor - start - 1)); + + return false; +} + +void FTPDirectoryTokenizer::finish() +{ + // Possible the last line in the listing had no newline, so try to parse it now + if (!m_carryOver.isEmpty()) { + parseAndAppendOneLine(m_carryOver); + m_carryOver = String(); + } + + m_tableElement = 0; + fastFree(m_buffer); + + HTMLTokenizer::finish(); +} + +FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame) + : HTMLDocument(frame) +{ +#ifndef NDEBUG + LogFTP.state = WTFLogChannelOn; +#endif +} + +Tokenizer* FTPDirectoryDocument::createTokenizer() +{ + return new FTPDirectoryTokenizer(this); +} + +} + +#endif // ENABLE(FTPDIR) |