• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
isWaitingForScripts() const58     virtual bool isWaitingForScripts() const { return false; }
59 
checkBuffer(int len=10)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 
FTPDirectoryTokenizer(HTMLDocument * doc)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 
appendEntry(const String & filename,const String & size,const String & date,bool isDirectory)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 
createTDForFilename(const String & filename)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 
processFilesizeString(const String & size,bool isDirectory)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 
wasLastDayOfMonth(int year,int month,int day)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 
processFileDateString(const FTPTime & fileTime)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 
parseAndAppendOneLine(const String & inputLine)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 
createTemplateDocumentData(Settings * settings)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 
loadDocumentTemplate()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 
createBasicDocument()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 
write(const SegmentedString & s,bool)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 
finish()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 
FTPDirectoryDocument(Frame * frame)437 FTPDirectoryDocument::FTPDirectoryDocument(Frame* frame)
438     : HTMLDocument(frame)
439 {
440 #ifndef NDEBUG
441     LogFTP.state = WTFLogChannelOn;
442 #endif
443 }
444 
createTokenizer()445 Tokenizer* FTPDirectoryDocument::createTokenizer()
446 {
447     return new FTPDirectoryTokenizer(this);
448 }
449 
450 }
451 
452 #endif // ENABLE(FTPDIR)
453