JavaScriptCore:
authorbeidson <beidson@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 14 Jul 2007 22:25:31 +0000 (22:25 +0000)
committerbeidson <beidson@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 14 Jul 2007 22:25:31 +0000 (22:25 +0000)
        Reviewed by Sam Weinig

        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser

        * wtf/Platform.h: Add ENABLE_FTPDIR feature to handle building on platforms that don't have the
          proper network-layer support

WebCore:

        Reviewed by Sam Weinig

        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser

        Development has taken place on both Leopard and Windows (windows enabling patch will follow shortly)
        Support will only exist on platforms whose network layers (ie, ResourceHandle) support piping the raw
        FTP Directory data into the engine.

        This is known to include Leopard, Windows, and CURL based platforms - probably others
        Known incompatible platforms include Tiger.

        The FTP listing page is built in HTML.  An external HTML document can be provided as a template for the
        directory listing.  This document is parsed, and expected to have a <table> with a certain class in it.
        As FTP directory listings come in, each entry is added to this table as a row.

        If no HTML template is provided to work with, a very basic empty document is hard coded that contains only
        this special table.

        Upgrades to the look and feel of the directory listings can take place largely in this HTML template which,
        admittedly, is conspicuously absent in this initial checkin

        * WebCore.exp: New Settings symbols for WebKit
        * WebCore.xcodeproj/project.pbxproj:

        * config.h: If BUILDING_ON_TIGER, disable FTP directory listing support

        * dom/DOMImplementation.cpp:
        (WebCore::DOMImplementation::createDocument): Create an FTPDirectoryDocument if the mime type is
          "application/x-ftp-directory"

        * loader/FTPDirectoryDocument.cpp: Added.
        (WebCore::FTPDirectoryTokenizer::isWaitingForScripts):
        (WebCore::FTPDirectoryTokenizer::checkBuffer):
        (WebCore::FTPDirectoryTokenizer::FTPDirectoryTokenizer): Building on HTML tokenizer, this facilitates
          parsing FTP Directory listings in the engine
        (WebCore::FTPDirectoryTokenizer::appendEntry): Add a table row for a directory listing to the document
        (WebCore::FTPDirectoryTokenizer::createTDForFilename): Create the TD for the filename with the anchor in it

        (WebCore::processFilesizeString): Prettify the filesize
        (WebCore::wasLastDayOfMonth):
        (WebCore::processFileDateString): Prettify the date
        (WebCore::FTPDirectoryTokenizer::parseAndAppendOneLine):
        (WebCore::FTPDirectoryTokenizer::loadDocumentTemplate): Loads the HTML template that FTP directory listings
          can build on top of
        (WebCore::FTPDirectoryTokenizer::createBasicDocument): Creates a most basic document (body and one table) to
          append the rows to in case the document template is not loaded and parsed
        (WebCore::FTPDirectoryTokenizer::write): Add the FTP listing to the buffer and parse entries out of it
        (WebCore::FTPDirectoryTokenizer::finish):

        (WebCore::FTPDirectoryDocument::FTPDirectoryDocument): Special document-type for FTP directory listings
        (WebCore::FTPDirectoryDocument::createTokenizer):
        * loader/FTPDirectoryDocument.h: Added.
        (WebCore::FTPDirectoryDocument::isImageDocument):

        * loader/FTPDirectoryParser.cpp: Added.
        (WebCore::parseOneFTPLine): Adapted from ParseFTPList.cpp from Firefox - parses most known
          FTP directory listing styles into discrete FTP directory entries
        * loader/FTPDirectoryParser.h: Added.
        (WebCore::ListState::ListState): Maintains FTP Parser state
        (WebCore::ListResult::ListResult): Represents one FTP directory listing
        (WebCore::ListResult::clear):

        * loader/FrameLoader.cpp:
        (WebCore::FrameLoader::checkContentPolicy): If the Settings say to bypass the policy check for
          "application/x-ftp-directory", skip it.  This will allow people to test this feature even if their
          browser/WebKit app would otherwise not allow it

        * page/Settings.cpp:
        (WebCore::Settings::Settings):
        (WebCore::Settings::setFTPDirectoryTemplatePath): Set the path to the FTP listing document template
        (WebCore::Settings::setForceFTPDirectoryListings): Set to bypass the policy delegate check for mime type
          "application/x-ftp-directory"
        * page/Settings.h:
        (WebCore::Settings::ftpDirectoryTemplatePath):
        (WebCore::Settings::forceFTPDirectoryListings):

        * platform/Logging.cpp: Add an "LogFTP" logging channel
        * platform/Logging.h:
        * platform/mac/LoggingMac.mm:
        (WebCore::InitializeLoggingChannelsIfNecessary):

        * platform/MimeTypeRegistry.cpp:
        (WebCore::initialiseSupportedNonImageMimeTypes): Add "application/x-ftp-directory"

        * platform/SharedBuffer.cpp:
        (WebCore::SharedBuffer::createWithContentsOfFile): Stub for now
        * platform/SharedBuffer.h:

        * platform/mac/SharedBufferMac.mm:
        (WebCore::SharedBuffer::createWithContentsOfFile): Load contents of the file into an NSData, then
          wrap that NSData

WebKit:

        Reviewed by Sam Weinig

        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser

        * WebView/WebPreferenceKeysPrivate.h: Added preference keys for the FTP template location, as well as to force
          FTP directory listings, bypassing the policy delegate.  This is necessary to test the new feature until browser
          policy delegate support is added.

        * WebView/WebPreferences.m:
        (-[WebPreferences _setFTPDirectoryTemplatePath:]):
        (-[WebPreferences _ftpDirectoryTemplatePath]):
        (-[WebPreferences _setForceFTPDirectoryListings:]):
        (-[WebPreferences _forceFTPDirectoryListings]):
        * WebView/WebPreferencesPrivate.h:
        * WebView/WebView.mm:
        (-[WebView _updateWebCoreSettingsFromPreferences:]):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@24289 268f45cc-cd09-0410-ab3c-d52691b4dbfc

26 files changed:
JavaScriptCore/ChangeLog
JavaScriptCore/wtf/Platform.h
WebCore/ChangeLog
WebCore/WebCore.exp
WebCore/WebCore.xcodeproj/project.pbxproj
WebCore/config.h
WebCore/dom/DOMImplementation.cpp
WebCore/loader/FTPDirectoryDocument.cpp [new file with mode: 0644]
WebCore/loader/FTPDirectoryDocument.h [new file with mode: 0644]
WebCore/loader/FTPDirectoryParser.cpp [new file with mode: 0644]
WebCore/loader/FTPDirectoryParser.h [new file with mode: 0644]
WebCore/loader/FrameLoader.cpp
WebCore/page/Settings.cpp
WebCore/page/Settings.h
WebCore/platform/Logging.cpp
WebCore/platform/Logging.h
WebCore/platform/MimeTypeRegistry.cpp
WebCore/platform/SharedBuffer.cpp
WebCore/platform/SharedBuffer.h
WebCore/platform/mac/LoggingMac.mm
WebCore/platform/mac/SharedBufferMac.mm
WebKit/ChangeLog
WebKit/WebView/WebPreferenceKeysPrivate.h
WebKit/WebView/WebPreferences.m
WebKit/WebView/WebPreferencesPrivate.h
WebKit/WebView/WebView.mm

index e63bc07d35007e6476024683e8974fdb1b14bfcf..4ceab00e6bd9847d1eb6c28b496d5504398bda76 100644 (file)
@@ -1,3 +1,12 @@
+2007-07-14  Brady Eidson  <beidson@apple.com>
+
+        Reviewed by Sam Weinig
+
+        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser
+
+        * wtf/Platform.h: Add ENABLE_FTPDIR feature to handle building on platforms that don't have the
+          proper network-layer support
+
 2007-07-14  Cameron Zwarich  <cwzwarich@uwaterloo.ca>
 
         Reviewed by Darin.
index 775f0b1326b53c49bde04bf161ce7c5665844079..cdc57061a5ee9c8318d8af36cf91a9d93b44da5b 100644 (file)
 #define ENABLE_ICONDATABASE 1
 #endif
 
+#if !defined(FTPDIR)
+#define ENABLE_FTPDIR 1
+#endif
+
 #endif /* WTF_Platform_h */
index c729b57280d6213eb3910b2a1d469dbb75be38c6..39566f2634234afe0367b0d0f33f0f432bdb659f 100644 (file)
@@ -1,3 +1,97 @@
+2007-07-14  Brady Eidson  <beidson@apple.com>
+
+        Reviewed by Sam Weinig
+
+        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser
+
+        Development has taken place on both Leopard and Windows (windows enabling patch will follow shortly)
+        Support will only exist on platforms whose network layers (ie, ResourceHandle) support piping the raw
+        FTP Directory data into the engine.  
+
+        This is known to include Leopard, Windows, and CURL based platforms - probably others
+        Known incompatible platforms include Tiger.
+
+        The FTP listing page is built in HTML.  An external HTML document can be provided as a template for the 
+        directory listing.  This document is parsed, and expected to have a <table> with a certain class in it.
+        As FTP directory listings come in, each entry is added to this table as a row.
+
+        If no HTML template is provided to work with, a very basic empty document is hard coded that contains only 
+        this special table.
+
+        Upgrades to the look and feel of the directory listings can take place largely in this HTML template which,
+        admittedly, is conspicuously absent in this initial checkin
+
+        * WebCore.exp: New Settings symbols for WebKit
+        * WebCore.xcodeproj/project.pbxproj:
+
+        * config.h: If BUILDING_ON_TIGER, disable FTP directory listing support
+
+        * dom/DOMImplementation.cpp:
+        (WebCore::DOMImplementation::createDocument): Create an FTPDirectoryDocument if the mime type is 
+          "application/x-ftp-directory"
+
+        * loader/FTPDirectoryDocument.cpp: Added.
+        (WebCore::FTPDirectoryTokenizer::isWaitingForScripts):
+        (WebCore::FTPDirectoryTokenizer::checkBuffer):
+        (WebCore::FTPDirectoryTokenizer::FTPDirectoryTokenizer): Building on HTML tokenizer, this facilitates 
+          parsing FTP Directory listings in the engine
+        (WebCore::FTPDirectoryTokenizer::appendEntry): Add a table row for a directory listing to the document
+        (WebCore::FTPDirectoryTokenizer::createTDForFilename): Create the TD for the filename with the anchor in it
+
+        (WebCore::processFilesizeString): Prettify the filesize
+        (WebCore::wasLastDayOfMonth):
+        (WebCore::processFileDateString): Prettify the date
+        (WebCore::FTPDirectoryTokenizer::parseAndAppendOneLine):
+        (WebCore::FTPDirectoryTokenizer::loadDocumentTemplate): Loads the HTML template that FTP directory listings
+          can build on top of
+        (WebCore::FTPDirectoryTokenizer::createBasicDocument): Creates a most basic document (body and one table) to
+          append the rows to in case the document template is not loaded and parsed
+        (WebCore::FTPDirectoryTokenizer::write): Add the FTP listing to the buffer and parse entries out of it
+        (WebCore::FTPDirectoryTokenizer::finish): 
+
+        (WebCore::FTPDirectoryDocument::FTPDirectoryDocument): Special document-type for FTP directory listings
+        (WebCore::FTPDirectoryDocument::createTokenizer):
+        * loader/FTPDirectoryDocument.h: Added.
+        (WebCore::FTPDirectoryDocument::isImageDocument):
+        
+        * loader/FTPDirectoryParser.cpp: Added.
+        (WebCore::parseOneFTPLine): Adapted from ParseFTPList.cpp from Firefox - parses most known
+          FTP directory listing styles into discrete FTP directory entries
+        * loader/FTPDirectoryParser.h: Added.
+        (WebCore::ListState::ListState): Maintains FTP Parser state
+        (WebCore::ListResult::ListResult): Represents one FTP directory listing
+        (WebCore::ListResult::clear):
+
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::checkContentPolicy): If the Settings say to bypass the policy check for 
+          "application/x-ftp-directory", skip it.  This will allow people to test this feature even if their 
+          browser/WebKit app would otherwise not allow it
+
+        * page/Settings.cpp:
+        (WebCore::Settings::Settings):
+        (WebCore::Settings::setFTPDirectoryTemplatePath): Set the path to the FTP listing document template
+        (WebCore::Settings::setForceFTPDirectoryListings): Set to bypass the policy delegate check for mime type
+          "application/x-ftp-directory"
+        * page/Settings.h:
+        (WebCore::Settings::ftpDirectoryTemplatePath):
+        (WebCore::Settings::forceFTPDirectoryListings):
+
+        * platform/Logging.cpp: Add an "LogFTP" logging channel
+        * platform/Logging.h:
+        * platform/mac/LoggingMac.mm:
+        (WebCore::InitializeLoggingChannelsIfNecessary):
+
+        * platform/MimeTypeRegistry.cpp:
+        (WebCore::initialiseSupportedNonImageMimeTypes): Add "application/x-ftp-directory"
+
+        * platform/SharedBuffer.cpp:
+        (WebCore::SharedBuffer::createWithContentsOfFile): Stub for now
+        * platform/SharedBuffer.h:
+
+        * platform/mac/SharedBufferMac.mm:
+        (WebCore::SharedBuffer::createWithContentsOfFile): Load contents of the file into an NSData, then
+          wrap that NSData
+
 2007-07-14  Alexey Proskuryakov  <ap@webkit.org>
 
         Reviewed by Darin.
index a53bcb8e14288a9053b9aaf4d5846a61506d4dc3..f3f3f5948f8ed7f0819a2f52887d83a388d19202 100644 (file)
@@ -452,7 +452,9 @@ __ZN7WebCore8Settings25setPrivateBrowsingEnabledEb
 __ZN7WebCore8Settings25setShouldPrintBackgroundsEb
 __ZN7WebCore8Settings25setUserStyleSheetLocationERKNS_4KURLE
 __ZN7WebCore8Settings26setDefaultTextEncodingNameERKNS_6StringE
+__ZN7WebCore8Settings27setFTPDirectoryTemplatePathERKNS_6StringE
 __ZN7WebCore8Settings27setLoadsImagesAutomaticallyEb
+__ZN7WebCore8Settings28setForceFTPDirectoryListingsEb
 __ZN7WebCore8Settings31setShrinksStandaloneImagesToFitEb
 __ZN7WebCore8Settings34setNeedsAcrobatFrameReloadingQuirkEb
 __ZN7WebCore8Settings40setJavaScriptCanOpenWindowsAutomaticallyEb
index 81be01fd14597992d452862f5f3838cd274cb989..73828d7246d51b86a70a283ab5362f9499a9d289 100644 (file)
                51741D120B07259A00ED442C /* HistoryItem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51741D0E0B07259A00ED442C /* HistoryItem.cpp */; };
                5186C0560A9C21470034FE94 /* IconDataCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5186C0550A9C21470034FE94 /* IconDataCache.cpp */; };
                51AA3F6F0BD5AA9E00892971 /* ResourceLoaderMac.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51AA3F6E0BD5AA9E00892971 /* ResourceLoaderMac.mm */; };
+               51C81B890C4422F70019ECE3 /* FTPDirectoryParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51C81B870C4422F70019ECE3 /* FTPDirectoryParser.cpp */; };
+               51C81B8A0C4422F70019ECE3 /* FTPDirectoryParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */; };
                51D3EA160A3D24D300BADA35 /* SQLDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51D3EA130A3D24D300BADA35 /* SQLDatabase.cpp */; };
                51D3EA170A3D24D300BADA35 /* SQLDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 51D3EA140A3D24D300BADA35 /* SQLDatabase.h */; settings = {ATTRIBUTES = (Private, ); }; };
                51D3EA180A3D24D300BADA35 /* SQLStatement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51D3EA150A3D24D300BADA35 /* SQLStatement.cpp */; };
                51DF6D7E0B92A16D00C2DC85 /* ThreadCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 51DF6D7D0B92A16D00C2DC85 /* ThreadCheck.h */; settings = {ATTRIBUTES = (Private, ); }; };
                51DF6D800B92A18E00C2DC85 /* ThreadCheck.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51DF6D7F0B92A18E00C2DC85 /* ThreadCheck.mm */; };
+               51E4ADB60C42B4CF0042BC55 /* FTPDirectoryDocument.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51E4ADB20C42B4CF0042BC55 /* FTPDirectoryDocument.cpp */; };
+               51E4ADB70C42B4CF0042BC55 /* FTPDirectoryDocument.h in Headers */ = {isa = PBXBuildFile; fileRef = 51E4ADB30C42B4CF0042BC55 /* FTPDirectoryDocument.h */; };
                51F11E150A48C2920034A24E /* SQLTransaction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51F11E140A48C2920034A24E /* SQLTransaction.cpp */; };
                550A0BC9085F6039007353D6 /* QualifiedName.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 550A0BC7085F6039007353D6 /* QualifiedName.cpp */; };
                550A0BCA085F6039007353D6 /* QualifiedName.h in Headers */ = {isa = PBXBuildFile; fileRef = 550A0BC8085F6039007353D6 /* QualifiedName.h */; settings = {ATTRIBUTES = (Private, ); }; };
                51741D0E0B07259A00ED442C /* HistoryItem.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = HistoryItem.cpp; sourceTree = "<group>"; };
                5186C0550A9C21470034FE94 /* IconDataCache.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = IconDataCache.cpp; sourceTree = "<group>"; };
                51AA3F6E0BD5AA9E00892971 /* ResourceLoaderMac.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ResourceLoaderMac.mm; sourceTree = "<group>"; };
+               51C81B870C4422F70019ECE3 /* FTPDirectoryParser.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FTPDirectoryParser.cpp; sourceTree = "<group>"; };
+               51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FTPDirectoryParser.h; sourceTree = "<group>"; };
                51D3EA130A3D24D300BADA35 /* SQLDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SQLDatabase.cpp; sourceTree = "<group>"; };
                51D3EA140A3D24D300BADA35 /* SQLDatabase.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SQLDatabase.h; sourceTree = "<group>"; };
                51D3EA150A3D24D300BADA35 /* SQLStatement.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SQLStatement.cpp; sourceTree = "<group>"; };
                51DF6D7D0B92A16D00C2DC85 /* ThreadCheck.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadCheck.h; sourceTree = "<group>"; };
                51DF6D7F0B92A18E00C2DC85 /* ThreadCheck.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ThreadCheck.mm; sourceTree = "<group>"; };
+               51E4ADB20C42B4CF0042BC55 /* FTPDirectoryDocument.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = FTPDirectoryDocument.cpp; sourceTree = "<group>"; };
+               51E4ADB30C42B4CF0042BC55 /* FTPDirectoryDocument.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = FTPDirectoryDocument.h; sourceTree = "<group>"; };
                51F11E140A48C2920034A24E /* SQLTransaction.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = SQLTransaction.cpp; sourceTree = "<group>"; };
                51F6A3D50663BF04004D2919 /* HTMLCanvasElement.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTMLCanvasElement.cpp; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
                51F6A3D60663BF04004D2919 /* HTMLCanvasElement.h */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = HTMLCanvasElement.h; sourceTree = "<group>"; tabWidth = 8; usesTabs = 0; };
                                656D37240ADBA5DE00A4554D /* FrameLoader.h */,
                                656D37260ADBA5DE00A4554D /* FrameLoaderClient.h */,
                                93B77A370ADD792500EA4B81 /* FrameLoaderTypes.h */,
+                               51E4ADB30C42B4CF0042BC55 /* FTPDirectoryDocument.h */,
+                               51E4ADB20C42B4CF0042BC55 /* FTPDirectoryDocument.cpp */,
+                               51C81B880C4422F70019ECE3 /* FTPDirectoryParser.h */,
+                               51C81B870C4422F70019ECE3 /* FTPDirectoryParser.cpp */,
                                1A820D8F0A13EBA600AF843C /* ImageDocument.cpp */,
                                1A820D900A13EBA600AF843C /* ImageDocument.h */,
                                BCB16C130979C3BD00467741 /* loader.cpp */,
                                BC073BAA0C399B1F000F5979 /* FloatConversion.h in Headers */,
                                BCA85A100C3AEAF4006F8308 /* DOMSVGNumberInternal.h in Headers */,
                                E1F1E8300C3C2BB9006DB391 /* XSLTExtensions.h in Headers */,
+                               51E4ADB70C42B4CF0042BC55 /* FTPDirectoryDocument.h in Headers */,
+                               51C81B8A0C4422F70019ECE3 /* FTPDirectoryParser.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                BC5A86840C33676000EEA649 /* DOMSelection.cpp in Sources */,
                                BC5A86B50C3367E800EEA649 /* JSDOMSelection.cpp in Sources */,
                                E1F1E82F0C3C2BB9006DB391 /* XSLTExtensions.cpp in Sources */,
+                               51E4ADB60C42B4CF0042BC55 /* FTPDirectoryDocument.cpp in Sources */,
+                               51C81B890C4422F70019ECE3 /* FTPDirectoryParser.cpp in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 94590b72a271907d1669c528529fcae2943b244d..596bdf90fea5cd2d22331d469432476bec1dcc40 100644 (file)
@@ -116,3 +116,7 @@ typedef float CGFloat;
 #endif
 #endif /* PLATFORM(CG) */
 
+#ifdef BUILDING_ON_TIGER
+#define ENABLE_FTPDIR 0
+#endif
+
index 359ec1b1b29dd05cacd3c9cfcb9259456e63a3d5..4c276c6b6b2143f1b57f6ec8958bd577665a3dc4 100644 (file)
@@ -31,6 +31,7 @@
 #include "Element.h"
 #include "ExceptionCode.h"
 #include "Frame.h"
+#include "FTPDirectoryDocument.h"
 #include "HTMLDocument.h"
 #include "HTMLViewSourceDocument.h"
 #include "Image.h"
@@ -356,6 +357,12 @@ PassRefPtr<Document> DOMImplementation::createDocument(const String& type, Frame
         return new HTMLDocument(this, frame);
     if (type == "application/xhtml+xml")
         return new Document(this, frame);
+        
+#if ENABLE(FTPDIR)
+    // Plugins cannot take FTP from us either
+    if (type == "application/x-ftp-directory")
+        return new FTPDirectoryDocument(this, frame);
+#endif
 
     // PDF is one image type for which a plugin can override built-in support.
     // We do not want QuickTime to take over all image types, obviously.
diff --git a/WebCore/loader/FTPDirectoryDocument.cpp b/WebCore/loader/FTPDirectoryDocument.cpp
new file mode 100644 (file)
index 0000000..48b62ff
--- /dev/null
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2007 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 "Element.h"
+#include "HTMLNames.h"
+#include "HTMLTableElement.h"
+#include "HTMLTableSectionElement.h"
+#include "HTMLTokenizer.h"
+#include "KURL.h"
+#include "Logging.h"
+#include "FTPDirectoryParser.h"
+#include "SegmentedString.h"
+#include "Settings.h"
+#include "SharedBuffer.h"
+#include "Text.h"
+#include "XMLTokenizer.h"
+
+using namespace std;
+
+namespace WebCore {
+
+const String UnknownFileSizeString = "unknown";
+
+using namespace HTMLNames;
+    
+class FTPDirectoryTokenizer : public HTMLTokenizer {
+public:
+    FTPDirectoryTokenizer(HTMLDocument* doc);
+
+    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;
+
+    static RefPtr<SharedBuffer> m_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.
+};
+
+RefPtr<SharedBuffer> FTPDirectoryTokenizer::m_templateDocumentData;
+
+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_doc->createElementNS(xhtmlNamespaceURI, "tr", ec);
+    rowElement->setAttribute("class", "ftpDirectoryEntryRow", ec);
+    m_tableElement->appendChild(rowElement, 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", "ftpDirectoryDirectoryRow", ec);
+    else
+        element->setAttribute("class", "ftpDirectoryFileRow", 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();
+    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 UnknownFileSizeString;
+     
+    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;
+}
+
+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;
+
+    DeprecatedString depString = inputLine.deprecatedString();
+    const char* line = depString.ascii();
+    
+    FTPEntryType typeResult = parseOneFTPLine(line, 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()
+{        
+    if (!m_templateDocumentData) {
+        Settings* settings = m_doc->settings();
+        if (settings)
+            m_templateDocumentData = SharedBuffer::createWithContentsOfFile(settings->ftpDirectoryTemplatePath());
+        if (m_templateDocumentData)
+            LOG(FTP, "Loaded FTPDirectoryTemplate of length %i\n", m_templateDocumentData->size());
+    }
+    
+    if (!m_templateDocumentData) {
+        LOG_ERROR("Could not load templateData");
+        return false;
+    }
+    
+    // Tokenize the template as an HTML document synchronously
+    setForceSynchronous(true);
+    HTMLTokenizer::write(String(m_templateDocumentData->data(), m_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(0);
+        
+        // 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(DOMImplementation* implementation, Frame* frame)
+    : HTMLDocument(implementation, frame)
+{
+#ifndef NDEBUG
+    LogFTP.state = WTFLogChannelOn;
+#endif
+}
+
+Tokenizer* FTPDirectoryDocument::createTokenizer()
+{
+    return new FTPDirectoryTokenizer(this);
+}
+
+}
+
+#endif // ENABLE(FTPDIR)
diff --git a/WebCore/loader/FTPDirectoryDocument.h b/WebCore/loader/FTPDirectoryDocument.h
new file mode 100644 (file)
index 0000000..bea228d
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007 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. 
+ */
+#ifndef FTPDirectoryDocument_h
+#define FTPDirectoryDocument_h
+
+#include "HTMLDocument.h"
+
+namespace WebCore {
+    
+class DOMImplementation;
+    
+class FTPDirectoryDocument : public HTMLDocument {
+public:
+    FTPDirectoryDocument(DOMImplementation*, Frame*);
+    
+    virtual bool isImageDocument() const { return false; }
+    virtual Tokenizer* createTokenizer();
+};
+    
+} // namespace WebCore
+
+#endif // FTPDirectoryDocument_h
diff --git a/WebCore/loader/FTPDirectoryParser.cpp b/WebCore/loader/FTPDirectoryParser.cpp
new file mode 100644 (file)
index 0000000..8a1d3af
--- /dev/null
@@ -0,0 +1,1601 @@
+/*
+ * Copyright (C) 2002 Cyrus Patel <cyp@fb14.uni-mainz.de>
+ *           (C) 2007 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+// This was originally Mozilla code, titled ParseFTPList.cpp
+// Original version of this file can currently be found at: http://mxr.mozilla.org/mozilla1.8/source/netwerk/streamconv/converters/ParseFTPList.cpp
+
+#include "config.h"
+#if ENABLE(FTPDIR)
+#include "FTPDirectoryParser.h"
+
+#include "DeprecatedString.h"
+
+namespace WebCore {
+
+FTPEntryType parseOneFTPLine(const char* line, ListState& state, ListResult& result)
+{
+  result.clear();
+    
+  if (!line)
+    return FTPJunkEntry;
+
+  state.numLines++;
+
+  /* carry buffer is only valid from one line to the next */
+  unsigned int carry_buf_len = state.carryBufferLength;
+  state.carryBufferLength = 0;
+
+  unsigned linelen = 0;
+
+  /* strip leading whitespace */
+  while (*line == ' ' || *line == '\t')
+    line++;
+  
+  /* line is terminated at first '\0' or '\n' */
+  const char* p = line;
+  while (*p && *p != '\n')
+    p++;
+  linelen = p - line;
+
+  if (linelen > 0 && *p == '\n' && *(p-1) == '\r')
+    linelen--;
+
+  /* DON'T strip trailing whitespace. */
+
+  if (linelen > 0)
+  {
+    static const char *month_names = "JanFebMarAprMayJunJulAugSepOctNovDec";
+    const char *tokens[16]; /* 16 is more than enough */
+    unsigned int toklen[(sizeof(tokens)/sizeof(tokens[0]))];
+    unsigned int linelen_sans_wsp;  // line length sans whitespace
+    unsigned int numtoks = 0;
+    unsigned int tokmarker = 0; /* extra info for lstyle handler */
+    unsigned int month_num = 0;
+    char tbuf[4];
+    int lstyle = 0;
+
+    if (carry_buf_len) /* VMS long filename carryover buffer */
+    {
+      tokens[0] = state.carryBuffer;
+      toklen[0] = carry_buf_len;
+      numtoks++;
+    }
+
+    unsigned int pos = 0;
+    while (pos < linelen && numtoks < (sizeof(tokens)/sizeof(tokens[0])) )
+    {
+      while (pos < linelen && 
+            (line[pos] == ' ' || line[pos] == '\t' || line[pos] == '\r'))
+        pos++;
+      if (pos < linelen)
+      {
+        tokens[numtoks] = &line[pos];
+        while (pos < linelen && 
+           (line[pos] != ' ' && line[pos] != '\t' && line[pos] != '\r'))
+          pos++;
+        if (tokens[numtoks] != &line[pos])
+        {
+          toklen[numtoks] = (&line[pos] - tokens[numtoks]);
+          numtoks++;  
+        }
+      }
+    }    
+
+    linelen_sans_wsp = &(tokens[numtoks-1][toklen[numtoks-1]]) - tokens[0];
+    if (numtoks == (sizeof(tokens)/sizeof(tokens[0])) )
+    {
+      pos = linelen;
+      while (pos > 0 && (line[pos-1] == ' ' || line[pos-1] == '\t'))
+        pos--;
+      linelen_sans_wsp = pos;
+    }
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_EPLF)
+    /* EPLF handling must come somewhere before /bin/dls handling. */
+    if (!lstyle && (!state.listStyle || state.listStyle == 'E'))
+    {
+      if (*line == '+' && linelen > 4 && numtoks >= 2)
+      {
+        pos = 1;
+        while (pos < (linelen-1))
+        {
+          p = &line[pos++];
+          if (*p == '/') 
+            result.type = FTPDirectoryEntry; /* its a dir */
+          else if (*p == 'r')
+            result.type = FTPFileEntry; /* its a file */
+          else if (*p == 'm')
+          {
+            if (isdigit(line[pos]))
+            {
+              while (pos < linelen && isdigit(line[pos]))
+                pos++;
+              if (pos < linelen && line[pos] == ',')
+              {
+                unsigned long long seconds = 0;
+                sscanf(p + 1, "%llu", &seconds);
+                time_t t = static_cast<time_t>(seconds);
+                
+                // FIXME: This code has the year 2038 bug
+                gmtime_r(&t, &result.modifiedTime);
+                result.modifiedTime.tm_year += 1900;
+              }
+            }
+          }
+          else if (*p == 's')
+          {
+            if (isdigit(line[pos]))
+            {
+              while (pos < linelen && isdigit(line[pos]))
+                pos++;
+              if (pos < linelen && line[pos] == ',')
+                result.fileSize = String(p + 1, &line[pos] - p + 1);
+            }
+          }
+          else if (isalpha(*p)) /* 'i'/'up' or unknown "fact" (property) */
+          {
+            while (pos < linelen && *++p != ',')
+              pos++;
+          }
+          else if (*p != '\t' || (p+1) != tokens[1])
+          {
+            break; /* its not EPLF after all */
+          }
+          else
+          {
+            state.parsedOne = true;
+            state.listStyle = lstyle = 'E';
+
+            p = &(line[linelen_sans_wsp]);
+            result.filename = tokens[1];
+            result.filenameLength = p - tokens[1];
+
+            if (!result.type) /* access denied */
+            {
+              result.type = FTPFileEntry; /* is assuming 'f'ile correct? */
+              return FTPJunkEntry;            /* NO! junk it. */
+            }
+            return result.type;
+          }
+          if (pos >= (linelen-1) || line[pos] != ',')
+            break;
+          pos++;
+        } /* while (pos < linelen) */
+        result.clear();
+      } /* if (*line == '+' && linelen > 4 && numtoks >= 2) */
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'E')) */
+#endif /* SUPPORT_EPLF */
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_VMS)
+    if (!lstyle && (!state.listStyle || state.listStyle == 'V'))
+    {                          /* try VMS Multinet/UCX/CMS server */
+      /*
+       * Legal characters in a VMS file/dir spec are [A-Z0-9$.-_~].
+       * '$' cannot begin a filename and `-' cannot be used as the first 
+       * or last character. '.' is only valid as a directory separator 
+       * and <file>.<type> separator. A canonical filename spec might look 
+       * like this: DISK$VOL:[DIR1.DIR2.DIR3]FILE.TYPE;123
+       * All VMS FTP servers LIST in uppercase.
+       *
+       * We need to be picky about this in order to support
+       * multi-line listings correctly.
+      */
+      if (!state.parsedOne &&
+          (numtoks == 1 || (numtoks == 2 && toklen[0] == 9 &&
+                            memcmp(tokens[0], "Directory", 9)==0 )))
+      {
+        /* If no dirstyle has been detected yet, and this line is a 
+         * VMS list's dirname, then turn on VMS dirstyle.
+         * eg "ACA:[ANONYMOUS]", "DISK$FTP:[ANONYMOUS]", "SYS$ANONFTP:" 
+        */
+        p = tokens[0];
+        pos = toklen[0];
+        if (numtoks == 2)
+        {
+          p = tokens[1];
+          pos = toklen[1];
+        }
+        pos--;
+        if (pos >= 3)
+        {
+          while (pos > 0 && p[pos] != '[')
+          {
+            pos--;
+            if (p[pos] == '-' || p[pos] == '$')
+            {
+              if (pos == 0 || p[pos-1] == '[' || p[pos-1] == '.' ||
+                  (p[pos] == '-' && (p[pos+1] == ']' || p[pos+1] == '.')))
+                break;
+            }
+            else if (p[pos] != '.' && p[pos] != '~' && 
+                     !isdigit(p[pos]) && !isalpha(p[pos]))
+              break;
+            else if (isalpha(p[pos]) && p[pos] != toupper(p[pos]))
+              break;
+          }
+          if (pos > 0)
+          {
+            pos--;
+            if (p[pos] != ':' || p[pos+1] != '[')
+              pos = 0;
+          }
+        }
+        if (pos > 0 && p[pos] == ':')
+        {
+          while (pos > 0)
+          {
+            pos--;
+            if (p[pos] != '$' && p[pos] != '_' && p[pos] != '-' &&
+                p[pos] != '~' && !isdigit(p[pos]) && !isalpha(p[pos]))
+              break;
+            else if (isalpha(p[pos]) && p[pos] != toupper(p[pos]))
+              break;
+          }
+          if (pos == 0)
+          {  
+            state.listStyle = 'V';
+            return FTPJunkEntry; /* its junk */
+          }
+        }
+        /* fallthrough */ 
+      }
+      else if ((tokens[0][toklen[0]-1]) != ';')
+      {
+        if (numtoks == 1 && (state.listStyle == 'V' && !carry_buf_len))
+          lstyle = 'V';
+        else if (numtoks < 4)
+          ;
+        else if (toklen[1] >= 10 && memcmp(tokens[1], "%RMS-E-PRV", 10) == 0)
+          lstyle = 'V';
+        else if ((&line[linelen] - tokens[1]) >= 22 &&
+                  memcmp(tokens[1], "insufficient privilege", 22) == 0)
+          lstyle = 'V';
+        else if (numtoks != 4 && numtoks != 6)
+          ;
+        else if (numtoks == 6 && (
+                 toklen[5] < 4 || *tokens[5] != '(' ||        /* perms */
+                           (tokens[5][toklen[5]-1]) != ')'  ))
+          ;
+        else if (  (toklen[2] == 10 || toklen[2] == 11) &&      
+                        (tokens[2][toklen[2]-5]) == '-' &&
+                        (tokens[2][toklen[2]-9]) == '-' &&
+        (((toklen[3]==4 || toklen[3]==5 || toklen[3]==7 || toklen[3]==8) &&
+                        (tokens[3][toklen[3]-3]) == ':' ) ||
+         ((toklen[3]==10 || toklen[3]==11 ) &&
+                        (tokens[3][toklen[3]-3]) == '.' )
+        ) &&  /* time in [H]H:MM[:SS[.CC]] format */
+                                    isdigit(*tokens[1]) && /* size */
+                                    isdigit(*tokens[2]) && /* date */
+                                    isdigit(*tokens[3])    /* time */
+                )
+        {
+          lstyle = 'V';
+        }
+        if (lstyle == 'V')
+        {
+          /* 
+          * MultiNet FTP:
+          *   LOGIN.COM;2                 1   4-NOV-1994 04:09 [ANONYMOUS] (RWE,RWE,,)
+          *   PUB.DIR;1                   1  27-JAN-1994 14:46 [ANONYMOUS] (RWE,RWE,RE,RWE)
+          *   README.FTP;1        %RMS-E-PRV, insufficient privilege or file protection violation
+          *   ROUSSOS.DIR;1               1  27-JAN-1994 14:48 [CS,ROUSSOS] (RWE,RWE,RE,R)
+          *   S67-50903.JPG;1           328  22-SEP-1998 16:19 [ANONYMOUS] (RWED,RWED,,)
+          * UCX FTP: 
+          *   CII-MANUAL.TEX;1  213/216  29-JAN-1996 03:33:12  [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+          * CMU/VMS-IP FTP
+          *   [VMSSERV.FILES]ALARM.DIR;1 1/3 5-MAR-1993 18:09
+          * TCPware FTP
+          *   FOO.BAR;1 4 5-MAR-1993 18:09:01.12
+          * Long filename example:
+          *   THIS-IS-A-LONG-VMS-FILENAME.AND-THIS-IS-A-LONG-VMS-FILETYPE\r\n
+          *                    213[/nnn]  29-JAN-1996 03:33[:nn]  [ANONYMOU,ANONYMOUS] (RWED,RWED,,)
+          */
+          tokmarker = 0;
+          p = tokens[0];
+          pos = 0;
+          if (*p == '[' && toklen[0] >= 4) /* CMU style */
+          {
+            if (p[1] != ']') 
+            {
+              p++;
+              pos++;
+            }
+            while (lstyle && pos < toklen[0] && *p != ']')
+            {
+              if (*p != '$' && *p != '.' && *p != '_' && *p != '-' &&
+                  *p != '~' && !isdigit(*p) && !isalpha(*p))              
+                lstyle = 0;
+              pos++;
+              p++;
+            }
+            if (lstyle && pos < (toklen[0]-1) && *p == ']')
+            {
+              pos++;
+              p++;
+              tokmarker = pos; /* length of leading "[DIR1.DIR2.etc]" */
+            }
+          }
+          while (lstyle && pos < toklen[0] && *p != ';')
+          {
+            if (*p != '$' && *p != '.' && *p != '_' && *p != '-' &&
+                *p != '~' && !isdigit(*p) && !isalpha(*p))
+              lstyle = 0;
+            else if (isalpha(*p) && *p != toupper(*p))
+              lstyle = 0;
+            p++;
+            pos++;
+          }
+          if (lstyle && *p == ';')
+          {
+            if (pos == 0 || pos == (toklen[0]-1))
+              lstyle = 0;
+            for (pos++;lstyle && pos < toklen[0];pos++)
+            {
+              if (!isdigit(tokens[0][pos]))
+                lstyle = 0;
+            }
+          }
+          pos = (p - tokens[0]); /* => fnlength sans ";####" */
+          pos -= tokmarker;      /* => fnlength sans "[DIR1.DIR2.etc]" */
+          p = &(tokens[0][tokmarker]); /* offset of basename */
+
+          if (!lstyle || pos > 80) /* VMS filenames can't be longer than that */
+          {
+            lstyle = 0;
+          }
+          else if (numtoks == 1)
+          { 
+            /* if VMS has been detected and there is only one token and that 
+             * token was a VMS filename then this is a multiline VMS LIST entry.
+            */
+            if (pos >= (sizeof(state.carryBuffer)-1))
+              pos = (sizeof(state.carryBuffer)-1); /* shouldn't happen */
+            memcpy( state.carryBuffer, p, pos );
+            state.carryBufferLength = pos;
+            return FTPJunkEntry; /* tell caller to treat as junk */
+          }
+          else if (isdigit(*tokens[1])) /* not no-privs message */
+          {
+            for (pos = 0; lstyle && pos < (toklen[1]); pos++)
+            {
+              if (!isdigit((tokens[1][pos])) && (tokens[1][pos]) != '/')
+                lstyle = 0;
+            }
+            if (lstyle && numtoks > 4) /* Multinet or UCX but not CMU */
+            {
+              for (pos = 1; lstyle && pos < (toklen[5]-1); pos++)
+              {
+                p = &(tokens[5][pos]);
+                if (*p!='R' && *p!='W' && *p!='E' && *p!='D' && *p!=',')
+                  lstyle = 0;
+              }
+            }
+          }
+        } /* passed initial tests */
+      } /* else if ((tokens[0][toklen[0]-1]) != ';') */    
+
+      if (lstyle == 'V')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        if (isdigit(*tokens[1]))  /* not permission denied etc */
+        {
+          /* strip leading directory name */
+          if (*tokens[0] == '[') /* CMU server */
+          {
+            pos = toklen[0]-1;
+            p = tokens[0]+1;
+            while (*p != ']')
+            {
+              p++;
+              pos--;
+            }
+            toklen[0] = --pos;
+            tokens[0] = ++p;
+          }
+          pos = 0;
+          while (pos < toklen[0] && (tokens[0][pos]) != ';')
+            pos++;
+       
+          result.caseSensitive = true;
+          result.type = FTPFileEntry;
+          result.filename = tokens[0];
+          result.filenameLength = pos;
+
+          if (pos > 4)
+          {
+            p = &(tokens[0][pos-4]);
+            if (p[0] == '.' && p[1] == 'D' && p[2] == 'I' && p[3] == 'R')
+            {
+              result.filenameLength -= 4;
+              result.type = FTPDirectoryEntry;
+            }
+          }
+
+          if (result.type != FTPDirectoryEntry)
+          {
+            /* #### or used/allocated form. If used/allocated form, then
+             * 'used' is the size in bytes if and only if 'used'<=allocated.
+             * If 'used' is size in bytes then it can be > 2^32
+             * If 'used' is not size in bytes then it is size in blocks.
+            */
+            pos = 0;
+            while (pos < toklen[1] && (tokens[1][pos]) != '/')
+              pos++;
+            
+/*
+ * I've never seen size come back in bytes, its always in blocks, and 
+ * the following test fails. So, always perform the "size in blocks".
+ * I'm leaving the "size in bytes" code if'd out in case we ever need
+ * to re-instate it.
+*/
+#if 0
+            if (pos < toklen[1] && ( (pos<<1) > (toklen[1]-1) ||
+                 (strtoul(tokens[1], (char **)0, 10) > 
+                  strtoul(tokens[1]+pos+1, (char **)0, 10))        ))
+            {                                   /* size is in bytes */
+              if (pos > (sizeof(result.fe_size)-1))
+                pos = sizeof(result.fe_size)-1;
+              memcpy( result.fe_size, tokens[1], pos );
+              result.fe_size[pos] = '\0';
+            }
+            else /* size is in blocks */
+#endif
+            {
+              /* size requires multiplication by blocksize. 
+               *
+               * We could assume blocksize is 512 (like Lynx does) and
+               * shift by 9, but that might not be right. Even if it 
+               * were, doing that wouldn't reflect what the file's 
+               * real size was. The sanest thing to do is not use the
+               * LISTing's filesize, so we won't (like ftpmirror).
+               *
+               * ulltoa(((unsigned long long)fsz)<<9, result.fe_size, 10);
+               *
+               * A block is always 512 bytes on OpenVMS, compute size.
+               * So its rounded up to the next block, so what, its better
+               * than not showing the size at all.
+               * A block is always 512 bytes on OpenVMS, compute size.
+               * So its rounded up to the next block, so what, its better
+               * than not showing the size at all.
+              */
+              uint64_t size = strtoul(tokens[1], NULL, 10) * 512;
+              result.fileSize = String::number(size);
+            } 
+
+          } /* if (result.type != FTPDirectoryEntry) */
+
+          p = tokens[2] + 2;
+          if (*p == '-')
+            p++;
+          tbuf[0] = p[0];
+          tbuf[1] = tolower(p[1]);
+          tbuf[2] = tolower(p[2]);
+          month_num = 0;
+          for (pos = 0; pos < (12*3); pos+=3)
+          {
+            if (tbuf[0] == month_names[pos+0] && 
+                tbuf[1] == month_names[pos+1] && 
+                tbuf[2] == month_names[pos+2])
+              break;
+            month_num++;
+          }
+          if (month_num >= 12)
+            month_num = 0;
+          result.modifiedTime.tm_mon = month_num;
+          result.modifiedTime.tm_mday = atoi(tokens[2]);
+          result.modifiedTime.tm_year = atoi(p+4); // NSPR wants year as XXXX
+
+          p = tokens[3] + 2;
+          if (*p == ':')
+            p++;
+          if (p[2] == ':')
+            result.modifiedTime.tm_sec = atoi(p+3);
+          result.modifiedTime.tm_hour = atoi(tokens[3]);
+          result.modifiedTime.tm_min  = atoi(p);
+      
+          return result.type;
+
+        } /* if (isdigit(*tokens[1])) */
+
+        return FTPJunkEntry; /* junk */
+
+      } /* if (lstyle == 'V') */
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'V')) */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_CMS)
+    /* Virtual Machine/Conversational Monitor System (IBM Mainframe) */
+    if (!lstyle && (!state.listStyle || state.listStyle == 'C'))  /* VM/CMS */
+    {
+      /* LISTing according to mirror.pl
+       * Filename FileType  Fm Format Lrecl  Records Blocks Date      Time
+       * LASTING  GLOBALV   A1 V      41     21     1       9/16/91   15:10:32
+       * J43401   NETLOG    A0 V      77     1      1       9/12/91   12:36:04
+       * PROFILE  EXEC      A1 V      17     3      1       9/12/91   12:39:07
+       * DIRUNIX  SCRIPT    A1 V      77     1216   17      1/04/93   20:30:47
+       * MAIL     PROFILE   A2 F      80     1      1       10/14/92  16:12:27
+       * BADY2K   TEXT      A0 V      1      1      1       1/03/102  10:11:12
+       * AUTHORS            A1 DIR    -      -      -       9/20/99   10:31:11
+       *
+       * LISTing from vm.marist.edu and vm.sc.edu
+       * 220-FTPSERVE IBM VM Level 420 at VM.MARIST.EDU, 04:58:12 EDT WEDNESDAY 2002-07-10
+       * AUTHORS           DIR        -          -          - 1999-09-20 10:31:11 -
+       * HARRINGTON        DIR        -          -          - 1997-02-12 15:33:28 -
+       * PICS              DIR        -          -          - 2000-10-12 15:43:23 -
+       * SYSFILE           DIR        -          -          - 2000-07-20 17:48:01 -
+       * WELCNVT  EXEC     V         72          9          1 1999-09-20 17:16:18 -
+       * WELCOME  EREADME  F         80         21          1 1999-12-27 16:19:00 -
+       * WELCOME  README   V         82         21          1 1999-12-27 16:19:04 -
+       * README   ANONYMOU V         71         26          1 1997-04-02 12:33:20 TCP291
+       * README   ANONYOLD V         71         15          1 1995-08-25 16:04:27 TCP291
+      */
+      if (numtoks >= 7 && (toklen[0]+toklen[1]) <= 16)
+      {
+        for (pos = 1; !lstyle && (pos+5) < numtoks; pos++)
+        {
+          p = tokens[pos];
+          if ((toklen[pos] == 1 && (*p == 'F' || *p == 'V')) ||
+              (toklen[pos] == 3 && *p == 'D' && p[1] == 'I' && p[2] == 'R'))
+          {
+            if (toklen[pos+5] == 8 && (tokens[pos+5][2]) == ':' &&
+                                      (tokens[pos+5][5]) == ':'   )
+            {
+              p = tokens[pos+4];
+              if ((toklen[pos+4] == 10 && p[4] == '-' && p[7] == '-') ||
+                  (toklen[pos+4] >= 7 && toklen[pos+4] <= 9 && 
+                            p[((p[1]!='/')?(2):(1))] == '/' && 
+                            p[((p[1]!='/')?(5):(4))] == '/'))
+               /* Y2K bugs possible ("7/06/102" or "13/02/101") */
+              {
+                if ( (*tokens[pos+1] == '-' &&
+                      *tokens[pos+2] == '-' &&
+                      *tokens[pos+3] == '-')  ||
+                      (isdigit(*tokens[pos+1]) &&
+                       isdigit(*tokens[pos+2]) &&
+                       isdigit(*tokens[pos+3])) )
+                {
+                  lstyle = 'C';
+                  tokmarker = pos;
+                }
+              }
+            }
+          }
+        } /* for (pos = 1; !lstyle && (pos+5) < numtoks; pos++) */
+      } /* if (numtoks >= 7) */
+
+      /* extra checking if first pass */
+      if (lstyle && !state.listStyle) 
+      {
+        for (pos = 0, p = tokens[0]; lstyle && pos < toklen[0]; pos++, p++)
+        {  
+          if (isalpha(*p) && toupper(*p) != *p)
+            lstyle = 0;
+        } 
+        for (pos = tokmarker+1; pos <= tokmarker+3; pos++)
+        {
+          if (!(toklen[pos] == 1 && *tokens[pos] == '-'))
+          {
+            for (p = tokens[pos]; lstyle && p<(tokens[pos]+toklen[pos]); p++)
+            {
+              if (!isdigit(*p))
+                lstyle = 0;
+            }
+          }
+        }
+        for (pos = 0, p = tokens[tokmarker+4]; 
+             lstyle && pos < toklen[tokmarker+4]; pos++, p++)
+        {
+          if (*p == '/')
+          { 
+            /* There may be Y2K bugs in the date. Don't simplify to
+             * pos != (len-3) && pos != (len-6) like time is done.
+            */             
+            if ((tokens[tokmarker+4][1]) == '/')
+            {
+              if (pos != 1 && pos != 4)
+                lstyle = 0;
+            }
+            else if (pos != 2 && pos != 5)
+              lstyle = 0;
+          }
+          else if (*p != '-' && !isdigit(*p))
+            lstyle = 0;
+          else if (*p == '-' && pos != 4 && pos != 7)
+            lstyle = 0;
+        }
+        for (pos = 0, p = tokens[tokmarker+5]; 
+             lstyle && pos < toklen[tokmarker+5]; pos++, p++)
+        {
+          if (*p != ':' && !isdigit(*p))
+            lstyle = 0;
+          else if (*p == ':' && pos != (toklen[tokmarker+5]-3)
+                             && pos != (toklen[tokmarker+5]-6))
+            lstyle = 0;
+        }
+      } /* initial if() */
+
+      if (lstyle == 'C')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        p = tokens[tokmarker+4];
+        if (toklen[tokmarker+4] == 10) /* newstyle: YYYY-MM-DD format */
+        {
+          result.modifiedTime.tm_year = atoi(p+0) - 1900;
+          result.modifiedTime.tm_mon  = atoi(p+5) - 1;
+          result.modifiedTime.tm_mday = atoi(p+8);
+        }
+        else /* oldstyle: [M]M/DD/YY format */
+        {
+          pos = toklen[tokmarker+4];
+          result.modifiedTime.tm_mon  = atoi(p) - 1;
+          result.modifiedTime.tm_mday = atoi((p+pos)-5);
+          result.modifiedTime.tm_year = atoi((p+pos)-2);
+          if (result.modifiedTime.tm_year < 70)
+            result.modifiedTime.tm_year += 100;
+        }
+
+        p = tokens[tokmarker+5];
+        pos = toklen[tokmarker+5];
+        result.modifiedTime.tm_hour  = atoi(p);
+        result.modifiedTime.tm_min = atoi((p+pos)-5);
+        result.modifiedTime.tm_sec = atoi((p+pos)-2);
+
+        result.caseSensitive = true;
+        result.filename = tokens[0];
+        result.filenameLength = toklen[0];
+        result.type  = FTPFileEntry;
+
+        p = tokens[tokmarker];
+        if (toklen[tokmarker] == 3 && *p=='D' && p[1]=='I' && p[2]=='R')
+          result.type  = FTPDirectoryEntry;
+
+        if ((/*newstyle*/ toklen[tokmarker+4] == 10 && tokmarker > 1) ||
+            (/*oldstyle*/ toklen[tokmarker+4] != 10 && tokmarker > 2))
+        {                            /* have a filetype column */
+          char *dot;
+          p = &(tokens[0][toklen[0]]);
+          memcpy( &dot, &p, sizeof(dot) ); /* NASTY! */
+          *dot++ = '.';
+          p = tokens[1];
+          for (pos = 0; pos < toklen[1]; pos++)
+            *dot++ = *p++;
+          result.filenameLength += 1 + toklen[1];
+        }
+
+        /* oldstyle LISTING: 
+         * files/dirs not on the 'A' minidisk are not RETRievable/CHDIRable 
+        if (toklen[tokmarker+4] != 10 && *tokens[tokmarker-1] != 'A')
+          return FTPJunkEntry;
+        */
+        
+        /* VM/CMS LISTings have no usable filesize field. 
+         * Have to use the 'SIZE' command for that.
+        */
+        return result.type;
+
+      } /* if (lstyle == 'C' && (!state.listStyle || state.listStyle == lstyle)) */
+    } /* VM/CMS */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_DOS) /* WinNT DOS dirstyle */
+    if (!lstyle && (!state.listStyle || state.listStyle == 'W'))
+    {
+      /*
+       * "10-23-00  01:27PM       <DIR>          veronist"
+       * "06-15-00  07:37AM       <DIR>          zoe"
+       * "07-14-00  01:35PM              2094926 canprankdesk.tif"
+       * "07-21-00  01:19PM                95077 Jon Kauffman Enjoys the Good Life.jpg"
+       * "07-21-00  01:19PM                52275 Name Plate.jpg"
+       * "07-14-00  01:38PM              2250540 Valentineoffprank-HiRes.jpg"
+      */
+      if ((numtoks >= 4) && toklen[0] == 8 && toklen[1] == 7 && 
+          (*tokens[2] == '<' || isdigit(*tokens[2])) )
+      {
+        p = tokens[0];
+        if ( isdigit(p[0]) && isdigit(p[1]) && p[2]=='-' && 
+             isdigit(p[3]) && isdigit(p[4]) && p[5]=='-' &&
+             isdigit(p[6]) && isdigit(p[7]) )
+        {
+          p = tokens[1];
+          if ( isdigit(p[0]) && isdigit(p[1]) && p[2]==':' && 
+               isdigit(p[3]) && isdigit(p[4]) && 
+               (p[5]=='A' || p[5]=='P') && p[6]=='M')
+          {
+            lstyle = 'W';
+            if (!state.listStyle)
+            {            
+              p = tokens[2];
+              /* <DIR> or <JUNCTION> */
+              if (*p != '<' || p[toklen[2]-1] != '>')
+              {
+                for (pos = 1; (lstyle && pos < toklen[2]); pos++)
+                {
+                  if (!isdigit(*++p))
+                    lstyle = 0;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      if (lstyle == 'W')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        p = &(line[linelen_sans_wsp]); /* line end sans wsp */
+        result.caseSensitive = true;
+        result.filename = tokens[3];
+        result.filenameLength = p - tokens[3];
+        result.type = FTPDirectoryEntry;
+
+        if (*tokens[2] != '<') /* not <DIR> or <JUNCTION> */
+        {
+          result.type = FTPFileEntry;
+          pos = toklen[2];
+          result.fileSize = String(tokens[2], pos);
+        }
+        else if ((tokens[2][1]) != 'D') /* not <DIR> */
+        {
+          result.type = FTPJunkEntry; /* unknown until junc for sure */
+          if (result.filenameLength > 4)
+          {
+            p = result.filename;
+            for (pos = result.filenameLength - 4; pos > 0; pos--)
+            {
+              if (p[0] == ' ' && p[3] == ' ' && p[2] == '>' &&
+                  (p[1] == '=' || p[1] == '-'))
+              {
+                result.type = FTPLinkEntry;
+                result.filenameLength = p - result.filename;
+                result.linkname = p + 4;
+                result.linknameLength = &(line[linelen_sans_wsp]) 
+                                   - result.linkname;
+                break;
+              }
+              p++;
+            }    
+          }
+        }
+      
+        result.modifiedTime.tm_mon = atoi(tokens[0]+0);
+        if (result.modifiedTime.tm_mon != 0)
+        {
+          result.modifiedTime.tm_mon--;
+          result.modifiedTime.tm_mday = atoi(tokens[0]+3);
+          result.modifiedTime.tm_year = atoi(tokens[0]+6);
+          if (result.modifiedTime.tm_year < 80)
+            result.modifiedTime.tm_year += 100;
+        }
+
+        result.modifiedTime.tm_hour = atoi(tokens[1]+0);
+        result.modifiedTime.tm_min = atoi(tokens[1]+3);
+        if ((tokens[1][5]) == 'P' && result.modifiedTime.tm_hour < 12)
+          result.modifiedTime.tm_hour += 12;
+
+        /* the caller should do this (if dropping "." and ".." is desired)
+        if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
+            (result.filenameLength == 1 || (result.filenameLength == 2 &&
+                                      result.filename[1] == '.')))
+          return FTPJunkEntry;
+        */
+
+        return result.type;  
+      } /* if (lstyle == 'W' && (!state.listStyle || state.listStyle == lstyle)) */
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'W')) */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_OS2)
+    if (!lstyle && (!state.listStyle || state.listStyle == 'O')) /* OS/2 test */
+    {
+      /* 220 server IBM TCP/IP for OS/2 - FTP Server ver 23:04:36 on Jan 15 1997 ready.
+      * fixed position, space padded columns. I have only a vague idea 
+      * of what the contents between col 18 and 34 might be: All I can infer
+      * is that there may be attribute flags in there and there may be 
+      * a " DIR" in there.
+      *
+      *          1         2         3         4         5         6
+      *0123456789012345678901234567890123456789012345678901234567890123456789
+      *----- size -------|??????????????? MM-DD-YY|  HH:MM| nnnnnnnnn....
+      *                 0  DIR            04-11-95   16:26  .
+      *                 0  DIR            04-11-95   16:26  ..
+      *                 0  DIR            04-11-95   16:26  ADDRESS
+      *               612  RHSA           07-28-95   16:45  air_tra1.bag
+      *               195  A              08-09-95   10:23  Alfa1.bag
+      *                 0  RHS   DIR      04-11-95   16:26  ATTACH
+      *               372  A              08-09-95   10:26  Aussie_1.bag
+      *            310992                 06-28-94   09:56  INSTALL.EXE
+      *                            1         2         3         4
+      *                  01234567890123456789012345678901234567890123456789
+      * dirlist from the mirror.pl project, col positions from Mozilla.
+      */
+      p = &(line[toklen[0]]);
+      /* \s(\d\d-\d\d-\d\d)\s+(\d\d:\d\d)\s */
+      if (numtoks >= 4 && toklen[0] <= 18 && isdigit(*tokens[0]) &&
+         (linelen - toklen[0]) >= (53-18)                        &&
+         p[18-18] == ' ' && p[34-18] == ' '                      &&
+         p[37-18] == '-' && p[40-18] == '-' && p[43-18] == ' '   &&
+         p[45-18] == ' ' && p[48-18] == ':' && p[51-18] == ' '   &&
+         isdigit(p[35-18]) && isdigit(p[36-18])                  &&
+         isdigit(p[38-18]) && isdigit(p[39-18])                  &&
+         isdigit(p[41-18]) && isdigit(p[42-18])                  &&
+         isdigit(p[46-18]) && isdigit(p[47-18])                  &&
+         isdigit(p[49-18]) && isdigit(p[50-18])
+      )
+      {
+        lstyle = 'O'; /* OS/2 */
+        if (!state.listStyle)
+        {            
+          for (pos = 1; lstyle && pos < toklen[0]; pos++)
+          {
+            if (!isdigit(tokens[0][pos]))
+              lstyle = 0;
+          }
+        }
+      }
+
+      if (lstyle == 'O')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        p = &(line[toklen[0]]);
+
+        result.caseSensitive = true;
+        result.filename = &p[53-18];
+        result.filenameLength = (&(line[linelen_sans_wsp]))
+                           - (result.filename);
+        result.type = FTPFileEntry;
+
+        /* I don't have a real listing to determine exact pos, so scan. */
+        for (pos = (18-18); pos < ((35-18)-4); pos++)
+        {
+          if (p[pos+0] == ' ' && p[pos+1] == 'D' && 
+              p[pos+2] == 'I' && p[pos+3] == 'R')
+          {
+            result.type = FTPDirectoryEntry;
+            break;
+          }
+        }
+    
+        if (result.type != FTPDirectoryEntry)
+        {
+          pos = toklen[0];
+          result.fileSize = String(tokens[0], pos);
+        }  
+    
+        result.modifiedTime.tm_mon = atoi(&p[35-18]) - 1;
+        result.modifiedTime.tm_mday = atoi(&p[38-18]);
+        result.modifiedTime.tm_year = atoi(&p[41-18]);
+        if (result.modifiedTime.tm_year < 80)
+          result.modifiedTime.tm_year += 100;
+        result.modifiedTime.tm_hour = atoi(&p[46-18]);
+        result.modifiedTime.tm_min = atoi(&p[49-18]);
+   
+        /* the caller should do this (if dropping "." and ".." is desired)
+        if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
+            (result.filenameLength == 1 || (result.filenameLength == 2 &&
+                                      result.filename[1] == '.')))
+          return FTPJunkEntry;
+        */
+
+        return result.type;
+      } /* if (lstyle == 'O') */
+
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'O')) */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+    
+#if defined(SUPPORT_LSL)
+    if (!lstyle && (!state.listStyle || state.listStyle == 'U')) /* /bin/ls & co. */
+    {
+      /* UNIX-style listing, without inum and without blocks
+       * "-rw-r--r--   1 root     other        531 Jan 29 03:26 README"
+       * "dr-xr-xr-x   2 root     other        512 Apr  8  1994 etc"
+       * "dr-xr-xr-x   2 root     512 Apr  8  1994 etc"
+       * "lrwxrwxrwx   1 root     other          7 Jan 25 00:17 bin -> usr/bin"
+       * Also produced by Microsoft's FTP servers for Windows:
+       * "----------   1 owner    group         1803128 Jul 10 10:18 ls-lR.Z"
+       * "d---------   1 owner    group               0 May  9 19:45 Softlib"
+       * Also WFTPD for MSDOS:
+       * "-rwxrwxrwx   1 noone    nogroup      322 Aug 19  1996 message.ftp"
+       * Hellsoft for NetWare:
+       * "d[RWCEMFA] supervisor            512       Jan 16 18:53    login"
+       * "-[RWCEMFA] rhesus             214059       Oct 20 15:27    cx.exe"
+       * Newer Hellsoft for NetWare: (netlab2.usu.edu)
+       * - [RWCEAFMS] NFAUUser               192 Apr 27 15:21 HEADER.html
+       * d [RWCEAFMS] jrd                    512 Jul 11 03:01 allupdates
+       * Also NetPresenz for the Mac:
+       * "-------r--         326  1391972  1392298 Nov 22  1995 MegaPhone.sit"
+       * "drwxrwxr-x               folder        2 May 10  1996 network"
+       * Protected directory:
+       * "drwx-wx-wt  2 root  wheel  512 Jul  1 02:15 incoming"
+       * uid/gid instead of username/groupname:
+       * "drwxr-xr-x  2 0  0  512 May 28 22:17 etc"
+      */
+    
+      if (numtoks >= 6)
+      {
+        /* there are two perm formats (Hellsoft/NetWare and *IX strmode(3)).
+         * Scan for size column only if the perm format is one or the other.
+         */
+        if (toklen[0] == 1 || (tokens[0][1]) == '[')
+        {
+          if (*tokens[0] == 'd' || *tokens[0] == '-')
+          {
+            pos = toklen[0]-1;
+            p = tokens[0] + 1;
+            if (pos == 0)
+            {
+              p = tokens[1];
+              pos = toklen[1];
+            }
+            if ((pos == 9 || pos == 10)        && 
+                (*p == '[' && p[pos-1] == ']') &&
+                (p[1] == 'R' || p[1] == '-')   &&
+                (p[2] == 'W' || p[2] == '-')   &&
+                (p[3] == 'C' || p[3] == '-')   &&
+                (p[4] == 'E' || p[4] == '-'))
+            {
+              /* rest is FMA[S] or AFM[S] */
+              lstyle = 'U'; /* very likely one of the NetWare servers */
+            }
+          }
+        }
+        else if ((toklen[0] == 10 || toklen[0] == 11) 
+                   && strchr("-bcdlpsw?DFam", *tokens[0]))
+        {
+          p = &(tokens[0][1]);
+          if ((p[0] == 'r' || p[0] == '-') &&
+              (p[1] == 'w' || p[1] == '-') &&
+              (p[3] == 'r' || p[3] == '-') &&
+              (p[4] == 'w' || p[4] == '-') &&
+              (p[6] == 'r' || p[6] == '-') &&
+              (p[7] == 'w' || p[7] == '-'))
+            /* 'x'/p[9] can be S|s|x|-|T|t or implementation specific */
+          {
+            lstyle = 'U'; /* very likely /bin/ls */
+          }
+        }
+      }
+      if (lstyle == 'U') /* first token checks out */
+      {
+        lstyle = 0;
+        for (pos = (numtoks-5); !lstyle && pos > 1; pos--)
+        {
+          /* scan for: (\d+)\s+([A-Z][a-z][a-z])\s+
+           *  (\d\d\d\d|\d\:\d\d|\d\d\:\d\d|\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d)
+           *  \s+(.+)$
+          */
+          if (isdigit(*tokens[pos]) /* size */
+              /* (\w\w\w) */
+           && toklen[pos+1] == 3 && isalpha(*tokens[pos+1]) &&
+              isalpha(tokens[pos+1][1]) && isalpha(tokens[pos+1][2])
+              /* (\d|\d\d) */
+           && isdigit(*tokens[pos+2]) &&
+                (toklen[pos+2] == 1 || 
+                  (toklen[pos+2] == 2 && isdigit(tokens[pos+2][1])))
+           && toklen[pos+3] >= 4 && isdigit(*tokens[pos+3]) 
+              /* (\d\:\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
+           && (toklen[pos+3] <= 5 || (
+               (toklen[pos+3] == 7 || toklen[pos+3] == 8) &&
+               (tokens[pos+3][toklen[pos+3]-3]) == ':'))
+           && isdigit(tokens[pos+3][toklen[pos+3]-2])
+           && isdigit(tokens[pos+3][toklen[pos+3]-1])
+           && (
+              /* (\d\d\d\d) */
+                 ((toklen[pos+3] == 4 || toklen[pos+3] == 5) &&
+                  isdigit(tokens[pos+3][1]) &&
+                  isdigit(tokens[pos+3][2])  )
+              /* (\d\:\d\d|\d\:\d\d\:\d\d) */
+              || ((toklen[pos+3] == 4 || toklen[pos+3] == 7) && 
+                  (tokens[pos+3][1]) == ':' &&
+                  isdigit(tokens[pos+3][2]) && isdigit(tokens[pos+3][3]))
+              /* (\d\d\:\d\d|\d\d\:\d\d\:\d\d) */
+              || ((toklen[pos+3] == 5 || toklen[pos+3] == 8) && 
+                  isdigit(tokens[pos+3][1]) && (tokens[pos+3][2]) == ':' &&
+                  isdigit(tokens[pos+3][3]) && isdigit(tokens[pos+3][4])) 
+              )
+           )
+          {
+            lstyle = 'U'; /* assume /bin/ls or variant format */
+            tokmarker = pos;
+
+            /* check that size is numeric */
+            p = tokens[tokmarker];
+            for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++)
+            {
+              if (!isdigit(*p++))
+                lstyle = 0;
+            }
+            if (lstyle)
+            {
+              month_num = 0;
+              p = tokens[tokmarker+1];
+              for (pos = 0;pos < (12*3); pos+=3)
+              {
+                if (p[0] == month_names[pos+0] && 
+                    p[1] == month_names[pos+1] && 
+                    p[2] == month_names[pos+2])
+                  break;
+                month_num++;
+              }
+              if (month_num >= 12)
+                lstyle = 0;
+            }
+          } /* relative position test */
+        } /* while (pos+5) < numtoks */
+      } /* if (numtoks >= 4) */
+
+      if (lstyle == 'U')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+    
+        result.caseSensitive = false;
+        result.type = FTPJunkEntry;
+        if (*tokens[0] == 'd' || *tokens[0] == 'D')
+          result.type = FTPDirectoryEntry;
+        else if (*tokens[0] == 'l')
+          result.type = FTPLinkEntry;
+        else if (*tokens[0] == '-' || *tokens[0] == 'F')
+          result.type = FTPFileEntry; /* (hopefully a regular file) */
+
+        if (result.type != FTPDirectoryEntry)
+        {
+          pos = toklen[tokmarker];
+          result.fileSize = String(tokens[tokmarker], pos);
+        }
+
+        result.modifiedTime.tm_mon  = month_num;
+        result.modifiedTime.tm_mday = atoi(tokens[tokmarker+2]);
+        if (result.modifiedTime.tm_mday == 0)
+          result.modifiedTime.tm_mday++;
+
+        p = tokens[tokmarker+3];
+        pos = (unsigned int)atoi(p);
+        if (p[1] == ':') /* one digit hour */
+          p--;
+        if (p[2] != ':') /* year */
+        {
+          result.modifiedTime.tm_year = pos;
+        }
+        else
+        {
+          result.modifiedTime.tm_hour = pos;
+          result.modifiedTime.tm_min  = atoi(p+3);
+          if (p[5] == ':')
+            result.modifiedTime.tm_sec = atoi(p+6);
+       
+          if (!state.now)
+          {
+            time_t now = time(NULL);
+            state.now = now * 1000000.0;
+
+            // FIXME: This code has the year 2038 bug
+            gmtime_r(&now, &state.nowFTPTime);
+            state.nowFTPTime.tm_year += 1900;
+          }
+
+          result.modifiedTime.tm_year = state.nowFTPTime.tm_year;
+          if ( (( state.nowFTPTime.tm_mon << 5) + state.nowFTPTime.tm_mday) <
+               ((result.modifiedTime.tm_mon << 5) + result.modifiedTime.tm_mday) )
+            result.modifiedTime.tm_year--;
+       
+        } /* time/year */
+        
+        result.filename = tokens[tokmarker+4];
+        result.filenameLength = (&(line[linelen_sans_wsp]))
+                           - (result.filename);
+
+        if (result.type == FTPLinkEntry && result.filenameLength > 4)
+        {
+          p = result.filename + 1;
+          for (pos = 1; pos < (result.filenameLength - 4); pos++)
+          {
+            if (*p == ' ' && p[1] == '-' && p[2] == '>' && p[3] == ' ')
+            {
+              result.linkname = p + 4;
+              result.linknameLength = (&(line[linelen_sans_wsp]))
+                               - (result.linkname);
+              result.filenameLength = pos;
+              break;
+            }
+            p++;
+          }
+        }
+
+#if defined(SUPPORT_LSLF) /* some (very rare) servers return ls -lF */
+        if (result.filenameLength > 1)
+        {
+          p = result.filename[result.filenameLength-1];
+          pos = result.type;
+          if (pos == 'd') { 
+             if (*p == '/') result.filenameLength--; /* directory */
+          } else if (pos == 'l') { 
+             if (*p == '@') result.filenameLength--; /* symlink */
+          } else if (pos == 'f') { 
+             if (*p == '*') result.filenameLength--; /* executable */
+          } else if (*p == '=' || *p == '%' || *p == '|') {
+            result.filenameLength--; /* socket, whiteout, fifo */
+          }
+        }
+#endif
+     
+        /* the caller should do this (if dropping "." and ".." is desired)
+        if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
+            (result.filenameLength == 1 || (result.filenameLength == 2 &&
+                                      result.filename[1] == '.')))
+          return FTPJunkEntry;
+        */
+
+        return result.type;  
+
+      } /* if (lstyle == 'U') */
+
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'U')) */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_W16) /* 16bit Windows */
+    if (!lstyle && (!state.listStyle || state.listStyle == 'w'))
+    {       /* old SuperTCP suite FTP server for Win3.1 */
+            /* old NetManage Chameleon TCP/IP suite FTP server for Win3.1 */
+      /*
+      * SuperTCP dirlist from the mirror.pl project
+      * mon/day/year separator may be '/' or '-'.
+      * .               <DIR>           11-16-94        17:16
+      * ..              <DIR>           11-16-94        17:16
+      * INSTALL         <DIR>           11-16-94        17:17
+      * CMT             <DIR>           11-21-94        10:17
+      * DESIGN1.DOC          11264      05-11-95        14:20
+      * README.TXT            1045      05-10-95        11:01
+      * WPKIT1.EXE          960338      06-21-95        17:01
+      * CMT.CSV                  0      07-06-95        14:56
+      *
+      * Chameleon dirlist guessed from lynx
+      * .               <DIR>      Nov 16 1994 17:16   
+      * ..              <DIR>      Nov 16 1994 17:16   
+      * INSTALL         <DIR>      Nov 16 1994 17:17
+      * CMT             <DIR>      Nov 21 1994 10:17
+      * DESIGN1.DOC     11264      May 11 1995 14:20   A
+      * README.TXT       1045      May 10 1995 11:01
+      * WPKIT1.EXE     960338      Jun 21 1995 17:01   R
+      * CMT.CSV             0      Jul 06 1995 14:56   RHA
+      */
+      if (numtoks >= 4 && toklen[0] < 13 && 
+          ((toklen[1] == 5 && *tokens[1] == '<') || isdigit(*tokens[1])) )
+      {
+        if (numtoks == 4
+         && (toklen[2] == 8 || toklen[2] == 9)
+         && (((tokens[2][2]) == '/' && (tokens[2][5]) == '/') ||
+             ((tokens[2][2]) == '-' && (tokens[2][5]) == '-'))
+         && (toklen[3] == 4 || toklen[3] == 5)
+         && (tokens[3][toklen[3]-3]) == ':'
+         && isdigit(tokens[2][0]) && isdigit(tokens[2][1])
+         && isdigit(tokens[2][3]) && isdigit(tokens[2][4])
+         && isdigit(tokens[2][6]) && isdigit(tokens[2][7])
+         && (toklen[2] < 9 || isdigit(tokens[2][8]))
+         && isdigit(tokens[3][toklen[3]-1]) && isdigit(tokens[3][toklen[3]-2])
+         && isdigit(tokens[3][toklen[3]-4]) && isdigit(*tokens[3]) 
+         )
+        {
+          lstyle = 'w';
+        }
+        else if ((numtoks == 6 || numtoks == 7)
+         && toklen[2] == 3 && toklen[3] == 2
+         && toklen[4] == 4 && toklen[5] == 5
+         && (tokens[5][2]) == ':'
+         && isalpha(tokens[2][0]) && isalpha(tokens[2][1])
+         &&                          isalpha(tokens[2][2])
+         && isdigit(tokens[3][0]) && isdigit(tokens[3][1])
+         && isdigit(tokens[4][0]) && isdigit(tokens[4][1])
+         && isdigit(tokens[4][2]) && isdigit(tokens[4][3])
+         && isdigit(tokens[5][0]) && isdigit(tokens[5][1])
+         && isdigit(tokens[5][3]) && isdigit(tokens[5][4])
+         /* could also check that (&(tokens[5][5]) - tokens[2]) == 17 */
+        )
+        {
+          lstyle = 'w';
+        }
+        if (lstyle && state.listStyle != lstyle) /* first time */
+        {
+          p = tokens[1];   
+          if (toklen[1] != 5 || p[0] != '<' || p[1] != 'D' || 
+                 p[2] != 'I' || p[3] != 'R' || p[4] != '>')
+          {
+            for (pos = 0; lstyle && pos < toklen[1]; pos++)
+            {
+              if (!isdigit(*p++))
+                lstyle = 0;
+            }
+          } /* not <DIR> */
+        } /* if (first time) */
+      } /* if (numtoks == ...) */
+
+      if (lstyle == 'w')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        result.caseSensitive = true;
+        result.filename = tokens[0];
+        result.filenameLength = toklen[0];
+        result.type = FTPDirectoryEntry;
+
+        p = tokens[1];
+        if (isdigit(*p))
+        {
+          result.type = FTPFileEntry;
+          pos = toklen[1];
+          result.fileSize = String(p, pos);
+        }
+
+        p = tokens[2];
+        if (toklen[2] == 3) /* Chameleon */
+        {
+          tbuf[0] = toupper(p[0]);
+          tbuf[1] = tolower(p[1]);
+          tbuf[2] = tolower(p[2]);
+          for (pos = 0; pos < (12*3); pos+=3)
+          {
+            if (tbuf[0] == month_names[pos+0] &&
+                tbuf[1] == month_names[pos+1] && 
+                tbuf[2] == month_names[pos+2])
+            {
+              result.modifiedTime.tm_mon = pos/3;
+              result.modifiedTime.tm_mday = atoi(tokens[3]);
+              result.modifiedTime.tm_year = atoi(tokens[4]) - 1900;
+              break;
+            }
+          }          
+          pos = 5; /* Chameleon toknum of date field */
+        }
+        else
+        {
+          result.modifiedTime.tm_mon = atoi(p+0)-1;
+          result.modifiedTime.tm_mday = atoi(p+3);
+          result.modifiedTime.tm_year = atoi(p+6);
+          if (result.modifiedTime.tm_year < 80) /* SuperTCP */
+            result.modifiedTime.tm_year += 100;
+
+          pos = 3; /* SuperTCP toknum of date field */
+        }
+
+        result.modifiedTime.tm_hour = atoi(tokens[pos]);
+        result.modifiedTime.tm_min = atoi(&(tokens[pos][toklen[pos]-2]));
+
+        /* the caller should do this (if dropping "." and ".." is desired)
+        if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
+            (result.filenameLength == 1 || (result.filenameLength == 2 &&
+                                      result.filename[1] == '.')))
+          return FTPJunkEntry;
+        */
+
+        return result.type;
+      } /* (lstyle == 'w') */
+
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'w'))  */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+#if defined(SUPPORT_DLS) /* dls -dtR */
+    if (!lstyle && 
+       (state.listStyle == 'D' || (!state.listStyle && state.numLines == 1)))
+       /* /bin/dls lines have to be immediately recognizable (first line) */
+    {
+      /* I haven't seen an FTP server that delivers a /bin/dls listing,
+       * but can infer the format from the lynx and mirror.pl projects.
+       * Both formats are supported.
+       *
+       * Lynx says:
+       * README              763  Information about this server\0
+       * bin/                  -  \0
+       * etc/                  =  \0
+       * ls-lR                 0  \0
+       * ls-lR.Z               3  \0
+       * pub/                  =  Public area\0
+       * usr/                  -  \0
+       * morgan               14  -> ../real/morgan\0
+       * TIMIT.mostlikely.Z\0
+       *                   79215  \0
+       *
+       * mirror.pl says:
+       * filename:  ^(\S*)\s+
+       * size:      (\-|\=|\d+)\s+
+       * month/day: ((\w\w\w\s+\d+|\d+\s+\w\w\w)\s+
+       * time/year: (\d+:\d+|\d\d\d\d))\s+
+       * rest:      (.+) 
+       *
+       * README              763  Jul 11 21:05  Information about this server
+       * bin/                  -  Apr 28  1994
+       * etc/                  =  11 Jul 21:04
+       * ls-lR                 0   6 Aug 17:14
+       * ls-lR.Z               3  05 Sep 1994
+       * pub/                  =  Jul 11 21:04  Public area
+       * usr/                  -  Sep  7 09:39
+       * morgan               14  Apr 18 09:39  -> ../real/morgan
+       * TIMIT.mostlikely.Z
+       *                   79215  Jul 11 21:04
+      */
+      if (!state.listStyle && line[linelen-1] == ':' && 
+          linelen >= 2 && toklen[numtoks-1] != 1)
+      { 
+        /* code in mirror.pl suggests that a listing may be preceded
+         * by a PWD line in the form "/some/dir/names/here:"
+         * but does not necessarily begin with '/'. *sigh*
+        */
+        pos = 0;
+        p = line;
+        while (pos < (linelen-1))
+        {
+          /* illegal (or extremely unusual) chars in a dirspec */
+          if (*p == '<' || *p == '|' || *p == '>' ||
+              *p == '?' || *p == '*' || *p == '\\')
+            break;
+          if (*p == '/' && pos < (linelen-2) && p[1] == '/')
+            break;
+          pos++;
+          p++;
+        }
+        if (pos == (linelen-1))
+        {
+          state.listStyle = 'D';
+          return FTPJunkEntry;
+        }
+      }
+
+      if (!lstyle && numtoks >= 2)
+      {
+        pos = 22; /* pos of (\d+|-|=) if this is not part of a multiline */
+        if (state.listStyle && carry_buf_len) /* first is from previous line */
+          pos = toklen[1]-1; /* and is 'as-is' (may contain whitespace) */
+
+        if (linelen > pos)
+        {
+          p = &line[pos];
+          if ((*p == '-' || *p == '=' || isdigit(*p)) &&
+              ((linelen == (pos+1)) || 
+               (linelen >= (pos+3) && p[1] == ' ' && p[2] == ' ')) )
+          {
+            tokmarker = 1;
+            if (!carry_buf_len)
+            {
+              pos = 1;
+              while (pos < numtoks && (tokens[pos]+toklen[pos]) < (&line[23]))
+                pos++;
+              tokmarker = 0;
+              if ((tokens[pos]+toklen[pos]) == (&line[23]))
+                tokmarker = pos;
+            }
+            if (tokmarker)  
+            {
+              lstyle = 'D';
+              if (*tokens[tokmarker] == '-' || *tokens[tokmarker] == '=')
+              {
+                if (toklen[tokmarker] != 1 ||
+                   (tokens[tokmarker-1][toklen[tokmarker-1]-1]) != '/')
+                  lstyle = 0;
+              }              
+              else
+              {
+                for (pos = 0; lstyle && pos < toklen[tokmarker]; pos++) 
+                {
+                  if (!isdigit(tokens[tokmarker][pos]))
+                    lstyle = 0; 
+                }
+              }
+              if (lstyle && !state.listStyle) /* first time */
+              {
+                /* scan for illegal (or incredibly unusual) chars in fname */
+                for (p = tokens[0]; lstyle &&
+                     p < &(tokens[tokmarker-1][toklen[tokmarker-1]]); p++)
+                {
+                  if (*p == '<' || *p == '|' || *p == '>' || 
+                      *p == '?' || *p == '*' || *p == '/' || *p == '\\')
+                    lstyle = 0;
+                }
+              }
+
+            } /* size token found */
+          } /* expected chars behind expected size token */
+        } /* if (linelen > pos) */
+      } /* if (!lstyle && numtoks >= 2) */
+
+      if (!lstyle && state.listStyle == 'D' && !carry_buf_len)
+      {
+        /* the filename of a multi-line entry can be identified
+         * correctly only if dls format had been previously established.
+         * This should always be true because there should be entries
+         * for '.' and/or '..' and/or CWD that precede the rest of the
+         * listing.
+        */
+        pos = linelen;
+        if (pos > (sizeof(state.carryBuffer)-1))
+          pos = sizeof(state.carryBuffer)-1;
+        memcpy( state.carryBuffer, line, pos );
+        state.carryBufferLength = pos;
+        return FTPJunkEntry;
+      }
+
+      if (lstyle == 'D')
+      {
+        state.parsedOne = true;
+        state.listStyle = lstyle;
+
+        p = &(tokens[tokmarker-1][toklen[tokmarker-1]]);
+        result.filename = tokens[0];
+        result.filenameLength = p - tokens[0];
+        result.type  = FTPFileEntry;
+
+        if (result.filename[result.filenameLength-1] == '/')
+        {
+          if (result.linknameLength == 1)
+            result.type = FTPJunkEntry;
+          else
+          {
+            result.filenameLength--;
+            result.type  = FTPDirectoryEntry;
+          }
+        }
+        else if (isdigit(*tokens[tokmarker]))
+        {
+          pos = toklen[tokmarker];
+          result.fileSize = String(tokens[tokmarker], pos);
+        }
+
+        if ((tokmarker+3) < numtoks && 
+              (&(tokens[numtoks-1][toklen[numtoks-1]]) - 
+               tokens[tokmarker+1]) >= (1+1+3+1+4) )
+        {
+          pos = (tokmarker+3);
+          p = tokens[pos];
+          pos = toklen[pos];
+
+          if ((pos == 4 || pos == 5)
+          &&  isdigit(*p) && isdigit(p[pos-1]) && isdigit(p[pos-2])
+          &&  ((pos == 5 && p[2] == ':') ||  
+               (pos == 4 && (isdigit(p[1]) || p[1] == ':')))
+             )
+          {
+            month_num = tokmarker+1; /* assumed position of month field */
+            pos = tokmarker+2;       /* assumed position of mday field */
+            if (isdigit(*tokens[month_num])) /* positions are reversed */
+            {
+              month_num++;
+              pos--;
+            }
+            p = tokens[month_num];
+            if (isdigit(*tokens[pos]) 
+            && (toklen[pos] == 1 || 
+                  (toklen[pos] == 2 && isdigit(tokens[pos][1])))
+            && toklen[month_num] == 3
+            && isalpha(*p) && isalpha(p[1]) && isalpha(p[2])  )
+            {
+              pos = atoi(tokens[pos]);
+              if (pos > 0 && pos <= 31)
+              {
+                result.modifiedTime.tm_mday = pos;
+                month_num = 1;
+                for (pos = 0; pos < (12*3); pos+=3)
+                {
+                  if (p[0] == month_names[pos+0] &&
+                      p[1] == month_names[pos+1] &&
+                      p[2] == month_names[pos+2])
+                    break;
+                  month_num++;
+                }
+                if (month_num > 12)
+                  result.modifiedTime.tm_mday = 0;
+                else
+                  result.modifiedTime.tm_mon = month_num - 1;
+              }
+            }
+            if (result.modifiedTime.tm_mday)
+            {
+              tokmarker += 3; /* skip mday/mon/yrtime (to find " -> ") */
+              p = tokens[tokmarker];
+
+              pos = atoi(p);
+              if (pos > 24)
+                result.modifiedTime.tm_year = pos-1900;
+              else
+              {
+                if (p[1] == ':')
+                  p--;
+                result.modifiedTime.tm_hour = pos;
+                result.modifiedTime.tm_min = atoi(p+3);
+                if (!state.now)
+                {
+                  time_t now = time(NULL);
+                  state.now = now * 1000000.0;
+                  
+                  // FIXME: This code has the year 2038 bug
+                  gmtime_r(&now, &state.nowFTPTime);
+                  state.nowFTPTime.tm_year += 1900;
+                }
+                result.modifiedTime.tm_year = state.nowFTPTime.tm_year;
+                if ( (( state.nowFTPTime.tm_mon  << 4) + state.nowFTPTime.tm_mday) <
+                     ((result.modifiedTime.tm_mon << 4) + result.modifiedTime.tm_mday) )
+                  result.modifiedTime.tm_year--;
+              } /* got year or time */
+            } /* got month/mday */
+          } /* may have year or time */
+        } /* enough remaining to possibly have date/time */
+
+        if (numtoks > (tokmarker+2))
+        {
+          pos = tokmarker+1;
+          p = tokens[pos];
+          if (toklen[pos] == 2 && *p == '-' && p[1] == '>')
+          {
+            p = &(tokens[numtoks-1][toklen[numtoks-1]]);
+            result.type  = FTPLinkEntry;
+            result.linkname = tokens[pos+1];
+            result.linknameLength = p - result.linkname;
+            if (result.linknameLength > 1 &&
+                result.linkname[result.linknameLength-1] == '/')
+              result.linknameLength--;
+          }
+        } /* if (numtoks > (tokmarker+2)) */
+
+        /* the caller should do this (if dropping "." and ".." is desired)
+        if (result.type == FTPDirectoryEntry && result.filename[0] == '.' &&
+            (result.filenameLength == 1 || (result.filenameLength == 2 &&
+                                      result.filename[1] == '.')))
+          return FTPJunkEntry;
+        */
+
+        return result.type;
+
+      } /* if (lstyle == 'D') */
+    } /* if (!lstyle && (!state.listStyle || state.listStyle == 'D')) */
+#endif
+
+    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
+
+  } /* if (linelen > 0) */
+
+  if (state.parsedOne || state.listStyle) /* junk if we fail to parse */
+    return FTPJunkEntry;      /* this time but had previously parsed sucessfully */
+  return FTPMiscEntry;        /* its part of a comment or error message */
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(FTPDIR)
+
diff --git a/WebCore/loader/FTPDirectoryParser.h b/WebCore/loader/FTPDirectoryParser.h
new file mode 100644 (file)
index 0000000..a7d711c
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2002 Cyrus Patel <cyp@fb14.uni-mainz.de>
+ *           (C) 2007 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* ParseFTPList() parses lines from an FTP LIST command.
+**
+** Written July 2002 by Cyrus Patel <cyp@fb14.uni-mainz.de>
+** with acknowledgements to squid, lynx, wget and ftpmirror.
+**
+** Arguments:
+**   'line':       line of FTP data connection output. The line is assumed
+**                 to end at the first '\0' or '\n' or '\r\n'. 
+**   'state':      a structure used internally to track state between 
+**                 lines. Needs to be bzero()'d at LIST begin.
+**   'result':     where ParseFTPList will store the results of the parse
+**                 if 'line' is not a comment and is not junk.
+**
+** Returns one of the following:
+**    'd' - LIST line is a directory entry ('result' is valid)
+**    'f' - LIST line is a file's entry ('result' is valid)
+**    'l' - LIST line is a symlink's entry ('result' is valid)
+**    '?' - LIST line is junk. (cwd, non-file/dir/link, etc)
+**    '"' - its not a LIST line (its a "comment")
+**
+** It may be advisable to let the end-user see "comments" (particularly when 
+** the listing results in ONLY such lines) because such a listing may be:
+** - an unknown LIST format (NLST or "custom" format for example)
+** - an error msg (EPERM,ENOENT,ENFILE,EMFILE,ENOTDIR,ENOTBLK,EEXDEV etc).
+** - an empty directory and the 'comment' is a "total 0" line or similar.
+**   (warning: a "total 0" can also mean the total size is unknown).
+**
+** ParseFTPList() supports all known FTP LISTing formats:
+** - '/bin/ls -l' and all variants (including Hellsoft FTP for NetWare); 
+** - EPLF (Easily Parsable List Format); 
+** - Windows NT's default "DOS-dirstyle";
+** - OS/2 basic server format LIST format;  
+** - VMS (MultiNet, UCX, and CMU) LIST format (including multi-line format);
+** - IBM VM/CMS, VM/ESA LIST format (two known variants);  
+** - SuperTCP FTP Server for Win16 LIST format;  
+** - NetManage Chameleon (NEWT) for Win16 LIST format;  
+** - '/bin/dls' (two known variants, plus multi-line) LIST format;
+** If there are others, then I'd like to hear about them (send me a sample).
+**
+** NLSTings are not supported explicitely because they cannot be machine 
+** parsed consistantly: NLSTings do not have unique characteristics - even 
+** the assumption that there won't be whitespace on the line does not hold
+** because some nlistings have more than one filename per line and/or
+** may have filenames that have spaces in them. Moreover, distinguishing
+** between an error message and an NLST line would require ParseList() to
+** recognize all the possible strerror() messages in the world.
+*/
+
+// This was originally Mozilla code, titled ParseFTPList.h
+// Original version of this file can currently be found at: http://mxr.mozilla.org/mozilla1.8/source/netwerk/streamconv/converters/ParseFTPList.h
+
+#ifndef FTPDirectoryParser_h
+#define FTPDirectoryParser_h
+
+#include "PlatformString.h"
+
+#include <time.h>
+
+#define SUPPORT_LSL  /* Support for /bin/ls -l and dozens of variations therof */
+#define SUPPORT_DLS  /* Support for /bin/dls format (very, Very, VERY rare) */
+#define SUPPORT_EPLF /* Support for Extraordinarily Pathetic List Format */
+#define SUPPORT_DOS  /* Support for WinNT server in 'site dirstyle' dos */
+#define SUPPORT_VMS  /* Support for VMS (all: MultiNet, UCX, CMU-IP) */
+#define SUPPORT_CMS  /* Support for IBM VM/CMS,VM/ESA (z/VM and LISTING forms) */
+#define SUPPORT_OS2  /* Support for IBM TCP/IP for OS/2 - FTP Server */
+#define SUPPORT_W16  /* Support for win16 hosts: SuperTCP or NetManage Chameleon */
+
+namespace WebCore {
+
+typedef struct tm FTPTime;
+
+struct ListState {    
+    ListState()
+        : now(0)
+        , listStyle(0)
+        , parsedOne(false)
+        , carryBufferLength(0)
+        , numLines(0)
+    { 
+        memset(&nowFTPTime, 0, sizeof(FTPTime));
+    }
+    
+    double      now;               /* needed for year determination */
+    FTPTime     nowFTPTime;
+    char        listStyle;         /* LISTing style */
+    bool        parsedOne;         /* returned anything yet? */
+    char        carryBuffer[84];   /* for VMS multiline */
+    int         carryBufferLength; /* length of name in carry_buf */
+    int64_t     numLines;          /* number of lines seen */
+};
+
+enum FTPEntryType {
+    FTPDirectoryEntry,
+    FTPFileEntry,
+    FTPLinkEntry,
+    FTPMiscEntry,
+    FTPJunkEntry
+};
+
+struct ListResult
+{
+    ListResult()
+    { 
+        clear();
+    }
+    
+    void clear()
+    {
+        valid = false;
+        type = FTPJunkEntry;
+        filename = 0;
+        filenameLength = 0;
+        linkname = 0;
+        linknameLength = 0;
+        fileSize.truncate(0);
+        caseSensitive = false;
+        memset(&modifiedTime, 0, sizeof(FTPTime));
+    }
+    
+    bool valid;
+    FTPEntryType type;        
+    
+    const char* filename;
+    uint32_t filenameLength;
+    
+    const char* linkname;
+    uint32_t linknameLength;
+    
+    String fileSize;      
+    FTPTime modifiedTime; 
+    bool caseSensitive; // file system is definitely case insensitive
+};
+
+FTPEntryType parseOneFTPLine(const char* inputLine, ListState&, ListResult&);
+                 
+} // namespace WebCore
+
+#endif // FTPDirectoryParser_h
index c3956bb32851c16bb92f1c711b812d90cc97ae33..59f51fd2d017e08d4a6e4e43d23bb238e198917f 100644 (file)
@@ -2089,6 +2089,15 @@ void FrameLoader::checkContentPolicy(const String& MIMEType, ContentPolicyDecisi
         return;
     }
 
+#if ENABLE(FTPDIR)
+    // Respect the hidden FTP Directory Listing pref so it can be tested even if the policy delegate might otherwise disallow it
+    Settings* settings = m_frame->settings();
+    if (settings && settings->forceFTPDirectoryListings() && MIMEType == "application/x-ftp-directory") {
+        function(argument, PolicyUse);
+        return;
+    }
+#endif
+
     m_policyCheck.set(function, argument);
     m_client->dispatchDecidePolicyForMIMEType(&FrameLoader::continueAfterContentPolicy,
         MIMEType, activeDocumentLoader()->request());
index d0d57ff7a59075278ea6785758a713eafe641096..f7dddd335f3628d6428d14f7280b14def5c2a65b 100644 (file)
@@ -61,6 +61,7 @@ Settings::Settings(Page* page)
     , m_shrinksStandaloneImagesToFit(true)
     , m_usesPageCache(false)
     , m_showsURLsInToolTips(false)
+    , m_forceFTPDirectoryListings(false)
 {
     // A Frame may not have been created yet, so we initialize the AtomicString 
     // hash before trying to use it.
@@ -261,4 +262,14 @@ void Settings::setShowsURLsInToolTips(bool showsURLsInToolTips)
     m_showsURLsInToolTips = showsURLsInToolTips;
 }
 
+void Settings::setFTPDirectoryTemplatePath(const String& path)
+{
+    m_ftpDirectoryTemplatePath = path;
+}
+
+void Settings::setForceFTPDirectoryListings(bool force)
+{
+    m_forceFTPDirectoryListings = force;
+}
+
 } // namespace WebCore
index 8689bbf32effed18bcfdcd54a907749a0654ae68..cb0786c665e7707b5af9f1884c31191054933b10 100644 (file)
@@ -128,10 +128,17 @@ namespace WebCore {
         void setShowsURLsInToolTips(bool);
         bool showsURLsInToolTips() const { return m_showsURLsInToolTips; }
         
+        void setFTPDirectoryTemplatePath(const String&);
+        const String& ftpDirectoryTemplatePath() const { return m_ftpDirectoryTemplatePath; }
+        
+        void setForceFTPDirectoryListings(bool);
+        bool forceFTPDirectoryListings() const { return m_forceFTPDirectoryListings; }
+        
     private:
         Page* m_page;
         
         String m_defaultTextEncodingName;
+        String m_ftpDirectoryTemplatePath;
         KURL m_userStyleSheetLocation;
         AtomicString m_standardFontFamily;
         AtomicString m_fixedFontFamily;
@@ -158,6 +165,7 @@ namespace WebCore {
         bool m_shrinksStandaloneImagesToFit : 1;
         bool m_usesPageCache: 1;
         bool m_showsURLsInToolTips : 1;
+        bool m_forceFTPDirectoryListings : 1;
     };
 
 } // namespace WebCore
index 89f7feecc9b8bc35bcff175d11d917e694cfbdb3..776f9a7b6e09d59eef5e58e037b54b849753bf27 100644 (file)
@@ -50,5 +50,6 @@ WTFLogChannel LogPageCache =         { 0x00008000, "WebCoreLogLevel", WTFLogChan
 WTFLogChannel LogPlatformLeaks =     { 0x00010000, "WebCoreLogLevel", WTFLogChannelOff };
 
 WTFLogChannel LogNetwork =           { 0x00100000, "WebCoreLogLevel", WTFLogChannelOff };
+WTFLogChannel LogFTP =               { 0x00200000, "WebCoreLogLevel", WTFLogChannelOff };
 
 }
index a243a77a9ade75a81aa0abd2ccb004e1aa7c1091..0f433fc547325a564f480fd002b329908d32338f 100644 (file)
@@ -49,6 +49,7 @@ namespace WebCore {
     extern WTFLogChannel LogPageCache;
     extern WTFLogChannel LogPlatformLeaks;
     extern WTFLogChannel LogNetwork;
+    extern WTFLogChannel LogFTP;
 
     void InitializeLoggingChannelsIfNecessary();
 }
index 40203561f5421770ac53383281a2d7e71ecb780b..f6dbefdbeb3dac72e5f0346c36ab1d5f52b7dc90 100644 (file)
@@ -121,9 +121,12 @@ static void initialiseSupportedNonImageMimeTypes()
 #if PLATFORM(MAC)
       "application/x-webarchive",
 #endif
-      "multipart/x-mixed-replace",
+      "multipart/x-mixed-replace"
 #if ENABLE(SVG)
-      "image/svg+xml"
+      , "image/svg+xml"
+#endif
+#if ENABLE(FTPDIR)
+      , "application/x-ftp-directory"
 #endif
     };
     for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); ++i)
index 9c33e7ffea7c89f39eecdcc1a9a5002d80195750..b539f8b97ea9d6b1c0f787971e556072fe475972 100644 (file)
@@ -94,6 +94,12 @@ inline unsigned SharedBuffer::platformDataSize() const
     
     return 0;
 }
+
+PassRefPtr<SharedBuffer> SharedBuffer::createWithContentsOfFile(const String& filePath)
+{
+    return 0;
+}
+
 #endif
 
 }
index 625370428e62b602f15bc6e128fb35af37b0ade5..122ed166050077a4cb5212d77b22e43cf0073af9 100644 (file)
@@ -25,6 +25,7 @@
 #ifndef SharedBuffer_h
 #define SharedBuffer_h
 
+#include "PlatformString.h"
 #include "Shared.h"
 #include <wtf/Forward.h>
 #include <wtf/Vector.h>
@@ -46,6 +47,9 @@ class SharedBuffer : public Shared<SharedBuffer> {
 public:
     SharedBuffer();
     SharedBuffer(const char*, int);
+
+    static PassRefPtr<SharedBuffer> createWithContentsOfFile(const String& filePath);
+    
 #if PLATFORM(MAC)
     NSData *createNSData();
     CFDataRef createCFData();
index ef71eb1f3801e323218d9e0c6e82d5e05418c996..3c008e57ee85e5efcf64424f5a7ddbcb0374cb2c 100644 (file)
@@ -62,6 +62,7 @@ void InitializeLoggingChannelsIfNecessary()
     initializeWithUserDefault(LogHistory);
     initializeWithUserDefault(LogPageCache);
     initializeWithUserDefault(LogNetwork);
+    initializeWithUserDefault(LogFTP);
 }
 
 }
index 325eac39ffd4f1fb80e810985abe087f3fb1e782..bb041f7da112ce994f5403eb192ac0af5020dc02 100644 (file)
@@ -141,4 +141,13 @@ void SharedBuffer::clearPlatformData()
     m_nsData = 0;
 }
 
+PassRefPtr<SharedBuffer> SharedBuffer::createWithContentsOfFile(const String& filePath)
+{
+    NSData *resourceData = [NSData dataWithContentsOfFile:filePath];
+    if (resourceData) 
+        return SharedBuffer::wrapNSData(resourceData);
+    return 0;
+}
+
 }
+
index 912b8c25a9e0ae356c924cdd36579b009a621c01..ccc7d9106d8bfb5f8c4963f45d23fe9cc2db1069 100644 (file)
@@ -1,3 +1,22 @@
+2007-07-14  Brady Eidson  <beidson@apple.com>
+
+        Reviewed by Sam Weinig
+
+        Initial check-in for <rdar://problem/3154486> - Supporting FTP directory listings in the browser
+
+        * WebView/WebPreferenceKeysPrivate.h: Added preference keys for the FTP template location, as well as to force 
+          FTP directory listings, bypassing the policy delegate.  This is necessary to test the new feature until browser
+          policy delegate support is added.
+
+        * WebView/WebPreferences.m:
+        (-[WebPreferences _setFTPDirectoryTemplatePath:]):
+        (-[WebPreferences _ftpDirectoryTemplatePath]):
+        (-[WebPreferences _setForceFTPDirectoryListings:]):
+        (-[WebPreferences _forceFTPDirectoryListings]):
+        * WebView/WebPreferencesPrivate.h:
+        * WebView/WebView.mm:
+        (-[WebView _updateWebCoreSettingsFromPreferences:]):
+
 2007-07-13  Anders Carlsson  <andersca@apple.com>
 
         Reviewed by Maciej.
index e5d092cf89dfeedb1f24d323803198e7eda9d095..d7f568b563085e29cc57717a10493089ad9476b1 100644 (file)
@@ -62,6 +62,8 @@
 #define WebGrammarCheckingEnabled @"WebGrammarCheckingEnabled"
 #define WebKitDOMPasteAllowedPreferenceKey @"WebKitDOMPasteAllowedPreferenceKey"
 #define WebKitUsesPageCachePreferenceKey @"WebKitUsesPageCachePreferenceKey"
+#define WebKitFTPDirectoryTemplatePath @"WebKitFTPDirectoryTemplatePath"
+#define WebKitForceFTPDirectoryListings @"WebKitForceFTPDirectoryListings"
 
 // These are private both because callers should be using the cover methods and because the
 // cover methods themselves are private.
index c030b476dcbd4eb3fe5d505bc17a6c2d69c934b4..203e57f837a5c1a8a088089cac72a52c4244a129 100644 (file)
@@ -826,6 +826,26 @@ static NSString *classIBCreatorID = nil;
     [self _setBoolValue:DOMPasteAllowed forKey:WebKitDOMPasteAllowedPreferenceKey];
 }
 
+- (void)_setFTPDirectoryTemplatePath:(NSString *)path
+{
+    [self _setStringValue:path forKey:WebKitFTPDirectoryTemplatePath];
+}
+
+- (NSString *)_ftpDirectoryTemplatePath
+{
+    return [self _stringValueForKey:WebKitFTPDirectoryTemplatePath];
+}
+
+- (void)_setForceFTPDirectoryListings:(BOOL)force
+{
+    [self _setBoolValue:force forKey:WebKitForceFTPDirectoryListings];
+}
+
+- (BOOL)_forceFTPDirectoryListings
+{
+    return [self _boolValueForKey:WebKitForceFTPDirectoryListings];
+}
+
 @end
 
 @implementation WebPreferences (WebInternal)
index 0cf3caa5cfd252fce94220e823ffa95e32d378be..2f3ae0fb90be0650295134609444fe6e3f29a5cc 100644 (file)
@@ -78,6 +78,11 @@ typedef enum {
 - (BOOL)isDOMPasteAllowed;
 - (void)setDOMPasteAllowed:(BOOL)DOMPasteAllowed;
 
+- (NSString *)_ftpDirectoryTemplatePath;
+- (void)_setFTPDirectoryTemplatePath:(NSString *)path;
+- (void)_setForceFTPDirectoryListings:(BOOL)force;
+- (BOOL)_forceFTPDirectoryListings;
+
 // Other private methods
 - (size_t)_pageCacheSize;
 - (size_t)_objectCacheSize;
index cfcc5af3cb87038a22c8ee63ea78f7eec68f099d..213459b3df2f47a193975eb8f93c77db7606f1b8 100644 (file)
@@ -861,6 +861,8 @@ static bool debugWidget = true;
     settings->setDefaultTextEncodingName([preferences defaultTextEncodingName]);
     settings->setFantasyFontFamily([preferences fantasyFontFamily]);
     settings->setFixedFontFamily([preferences fixedFontFamily]);
+    settings->setForceFTPDirectoryListings([preferences _forceFTPDirectoryListings]);
+    settings->setFTPDirectoryTemplatePath([preferences _ftpDirectoryTemplatePath]);
     settings->setJavaEnabled([preferences isJavaEnabled]);
     settings->setJavaScriptEnabled([preferences isJavaScriptEnabled]);
     settings->setJavaScriptCanOpenWindowsAutomatically([preferences javaScriptCanOpenWindowsAutomatically]);