2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "FTPDirectoryDocument.h"
29 #include "CharacterNames.h"
31 #include "HTMLNames.h"
32 #include "HTMLTableElement.h"
33 #include "HTMLTokenizer.h"
34 #include "LocalizedStrings.h"
36 #include "FTPDirectoryParser.h"
37 #include "SegmentedString.h"
39 #include "SharedBuffer.h"
42 // On Win, the threadsafe *_r functions need to be gotten from pthreads.
43 #if COMPILER(MSVC) && USE(PTHREADS)
55 using namespace HTMLNames;
57 class FTPDirectoryTokenizer : public HTMLTokenizer {
59 FTPDirectoryTokenizer(HTMLDocument*);
61 virtual bool write(const SegmentedString&, bool appendData);
62 virtual void finish();
64 virtual bool isWaitingForScripts() const { return false; }
66 inline void checkBuffer(int len = 10)
68 if ((m_dest - m_buffer) > m_size - len) {
70 int newSize = max(m_size * 2, m_size + len);
71 int oldOffset = m_dest - m_buffer;
72 m_buffer = static_cast<UChar*>(fastRealloc(m_buffer, newSize * sizeof(UChar)));
73 m_dest = m_buffer + oldOffset;
79 // The tokenizer will attempt to load the document template specified via the preference
80 // Failing that, it will fall back and create the basic document which will have a minimal
81 // table for presenting the FTP directory in a useful manner
82 bool loadDocumentTemplate();
83 void createBasicDocument();
85 void parseAndAppendOneLine(const String&);
86 void appendEntry(const String& name, const String& size, const String& date, bool isDirectory);
87 PassRefPtr<Element> createTDForFilename(const String&);
90 RefPtr<HTMLTableElement> m_tableElement;
93 bool m_parsedTemplate;
100 ListState m_listState;
103 FTPDirectoryTokenizer::FTPDirectoryTokenizer(HTMLDocument* doc)
104 : HTMLTokenizer(doc, false)
107 , m_parsedTemplate(false)
109 , m_buffer(static_cast<UChar*>(fastMalloc(sizeof(UChar) * m_size)))
114 void FTPDirectoryTokenizer::appendEntry(const String& filename, const String& size, const String& date, bool isDirectory)
118 RefPtr<Element> rowElement = m_tableElement->insertRow(-1, ec);
119 rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec);
121 RefPtr<Element> element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec);
122 element->appendChild(new Text(m_doc, String(&noBreakSpace, 1)), ec);
124 element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeDirectory", ec);
126 element->setAttribute("class", "ftpDirectoryIcon ftpDirectoryTypeFile", ec);
127 rowElement->appendChild(element, ec);
129 element = createTDForFilename(filename);
130 element->setAttribute("class", "ftpDirectoryFileName", ec);
131 rowElement->appendChild(element, ec);
133 element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec);
134 element->appendChild(new Text(m_doc, date), ec);
135 element->setAttribute("class", "ftpDirectoryFileDate", ec);
136 rowElement->appendChild(element, ec);
138 element = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec);
139 element->appendChild(new Text(m_doc, size), ec);
140 element->setAttribute("class", "ftpDirectoryFileSize", ec);
141 rowElement->appendChild(element, ec);
144 PassRefPtr<Element> FTPDirectoryTokenizer::createTDForFilename(const String& filename)
148 String fullURL = m_doc->baseURL().string();
149 if (fullURL[fullURL.length() - 1] == '/')
150 fullURL.append(filename);
152 fullURL.append("/" + filename);
154 RefPtr<Element> anchorElement = m_doc->createElementNS(xhtmlNamespaceURI, "a", ec);
155 anchorElement->setAttribute("href", fullURL, ec);
156 anchorElement->appendChild(new Text(m_doc, filename), ec);
158 RefPtr<Element> tdElement = m_doc->createElementNS(xhtmlNamespaceURI, "td", ec);
159 tdElement->appendChild(anchorElement, ec);
161 return tdElement.release();
164 static String processFilesizeString(const String& size, bool isDirectory)
170 int64_t bytes = size.toUInt64(&valid);
172 return unknownFileSizeText();
175 return String::format("%.2f KB", static_cast<float>(bytes)/1000);
177 if (bytes < 1000000000)
178 return String::format("%.2f MB", static_cast<float>(bytes)/1000000);
180 return String::format("%.2f GB", static_cast<float>(bytes)/1000000000);
183 static bool wasLastDayOfMonth(int year, int month, int day)
185 static int lastDays[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
186 if (month < 0 || month > 11)
190 if (year % 4 == 0 && (year % 100 || year % 400 == 0)) {
201 return lastDays[month] == day;
207 Replacement for localtime_r() which is not available on MinGW.
209 We use this on all of Qt's platforms for portability.
211 struct tm gmtimeQt(const QDateTime &input)
215 const QDate date(input.date());
216 result.tm_year = date.year() - 1900;
217 result.tm_mon = date.month();
218 result.tm_mday = date.day();
219 result.tm_wday = date.dayOfWeek();
220 result.tm_yday = date.dayOfYear();
222 const QTime time(input.time());
223 result.tm_sec = time.second();
224 result.tm_min = time.minute();
225 result.tm_hour = time.hour();
230 static struct tm *localTimeQt(const time_t *const timep, struct tm *result)
232 const QDateTime dt(QDateTime::fromTime_t(*timep));
233 *result = WebCore::gmtimeQt(dt.toLocalTime());
237 #define localtime_r(x, y) localTimeQt(x, y)
238 #elif PLATFORM(WIN_OS) && !defined(localtime_r)
239 #define localtime_r(x, y) localtime_s((y), (x))
242 static String processFileDateString(const FTPTime& fileTime)
244 // FIXME: Need to localize this string?
248 if (!(fileTime.tm_hour == 0 && fileTime.tm_min == 0 && fileTime.tm_sec == 0)) {
249 int hour = fileTime.tm_hour;
250 ASSERT(hour >= 0 && hour < 24);
255 timeOfDay = String::format(", %i:%02i AM", hour, fileTime.tm_min);
260 timeOfDay = String::format(", %i:%02i PM", hour, fileTime.tm_min);
264 // If it was today or yesterday, lets just do that - but we have to compare to the current time
266 time_t now_t = time(NULL);
267 localtime_r(&now_t, &now);
269 // localtime does "year = current year - 1900", compensate for that for readability and comparison purposes
272 if (fileTime.tm_year == now.tm_year) {
273 if (fileTime.tm_mon == now.tm_mon) {
274 if (fileTime.tm_mday == now.tm_mday)
275 return "Today" + timeOfDay;
276 if (fileTime.tm_mday == now.tm_mday - 1)
277 return "Yesterday" + timeOfDay;
280 if (now.tm_mday == 1 && (now.tm_mon == fileTime.tm_mon + 1 || now.tm_mon == 0 && fileTime.tm_mon == 11) &&
281 wasLastDayOfMonth(fileTime.tm_year, fileTime.tm_mon, fileTime.tm_mday))
282 return "Yesterday" + timeOfDay;
285 if (fileTime.tm_year == now.tm_year - 1 && fileTime.tm_mon == 12 && fileTime.tm_mday == 31 && now.tm_mon == 1 && now.tm_mday == 1)
286 return "Yesterday" + timeOfDay;
288 static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" };
290 int month = fileTime.tm_mon;
291 if (month < 0 || month > 11)
296 if (fileTime.tm_year > -1)
297 dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, fileTime.tm_year);
299 dateString = String::format("%s %i, %i", months[month], fileTime.tm_mday, now.tm_year);
301 return dateString + timeOfDay;
304 void FTPDirectoryTokenizer::parseAndAppendOneLine(const String& inputLine)
308 FTPEntryType typeResult = parseOneFTPLine(inputLine.latin1().data(), m_listState, result);
310 // FTPMiscEntry is a comment or usage statistic which we don't care about, and junk is invalid data - bail in these 2 cases
311 if (typeResult == FTPMiscEntry || typeResult == FTPJunkEntry)
314 String filename(result.filename, result.filenameLength);
315 if (result.type == FTPDirectoryEntry) {
316 filename.append("/");
318 // We have no interest in linking to "current directory"
319 if (filename == "./")
323 LOG(FTP, "Appending entry - %s, %s", filename.ascii().data(), result.fileSize.ascii().data());
325 appendEntry(filename, processFilesizeString(result.fileSize, result.type == FTPDirectoryEntry), processFileDateString(result.modifiedTime), result.type == FTPDirectoryEntry);
328 bool FTPDirectoryTokenizer::loadDocumentTemplate()
330 static RefPtr<SharedBuffer> templateDocumentData;
331 // FIXME: Instead of storing the data, we'd rather actually parse the template data into the template Document once,
332 // store that document, then "copy" it whenever we get an FTP directory listing. There are complexities with this
333 // approach that make it worth putting this off.
335 if (!templateDocumentData) {
336 Settings* settings = m_doc->settings();
338 templateDocumentData = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath());
339 if (templateDocumentData)
340 LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", templateDocumentData->size());
343 if (!templateDocumentData) {
344 LOG_ERROR("Could not load templateData");
348 // Tokenize the template as an HTML document synchronously
349 setForceSynchronous(true);
350 HTMLTokenizer::write(String(templateDocumentData->data(), templateDocumentData->size()), true);
351 setForceSynchronous(false);
353 RefPtr<Element> tableElement = m_doc->getElementById("ftpDirectoryTable");
355 LOG_ERROR("Unable to find element by id \"ftpDirectoryTable\" in the template document.");
356 else if (!tableElement->hasTagName(tableTag))
357 LOG_ERROR("Element of id \"ftpDirectoryTable\" is not a table element");
359 m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
361 // Bail if we found the table element
365 // Otherwise create one manually
367 tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec);
368 m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
369 m_tableElement->setAttribute("id", "ftpDirectoryTable", ec);
371 // If we didn't find the table element, lets try to append our own to the body
372 // If that fails for some reason, cram it on the end of the document as a last
374 if (Element* body = m_doc->body())
375 body->appendChild(m_tableElement, ec);
377 m_doc->appendChild(m_tableElement, ec);
382 void FTPDirectoryTokenizer::createBasicDocument()
384 LOG(FTP, "Creating a basic FTP document structure as no template was loaded");
386 // FIXME: Make this "basic document" more acceptable
390 RefPtr<Element> bodyElement = m_doc->createElementNS(xhtmlNamespaceURI, "body", ec);
392 m_doc->appendChild(bodyElement, ec);
394 RefPtr<Element> tableElement = m_doc->createElementNS(xhtmlNamespaceURI, "table", ec);
395 m_tableElement = static_cast<HTMLTableElement*>(tableElement.get());
396 m_tableElement->setAttribute("id", "ftpDirectoryTable", ec);
398 bodyElement->appendChild(m_tableElement, ec);
401 bool FTPDirectoryTokenizer::write(const SegmentedString& s, bool appendData)
403 // Make sure we have the table element to append to by loading the template set in the pref, or
404 // creating a very basic document with the appropriate table
405 if (!m_tableElement) {
406 if (!loadDocumentTemplate())
407 createBasicDocument();
408 ASSERT(m_tableElement);
411 bool foundNewLine = false;
414 SegmentedString str = s;
415 while (!str.isEmpty()) {
421 // possibly skip an LF in the case of an CRLF sequence
423 } else if (c == '\n') {
435 // Maybe enlarge the buffer
444 UChar* start = m_buffer;
445 UChar* cursor = start;
447 while (cursor < m_dest) {
448 if (*cursor == '\n') {
449 m_carryOver.append(String(start, cursor - start));
450 LOG(FTP, "%s", m_carryOver.ascii().data());
451 parseAndAppendOneLine(m_carryOver);
452 m_carryOver = String();
459 // Copy the partial line we have left to the carryover buffer
460 if (cursor - start > 1)
461 m_carryOver.append(String(start, cursor - start - 1));
466 void FTPDirectoryTokenizer::finish()
468 // Possible the last line in the listing had no newline, so try to parse it now
469 if (!m_carryOver.isEmpty()) {
470 parseAndAppendOneLine(m_carryOver);
471 m_carryOver = String();
477 HTMLTokenizer::finish();
480 FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame)
481 : HTMLDocument(frame)
484 LogFTP.state = WTFLogChannelOn;
488 Tokenizer* FTPDirectoryDocument::createTokenizer()
490 return new FTPDirectoryTokenizer(this);
495 #endif // ENABLE(FTPDIR)