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