mirror of
https://github.com/Alex313031/thorium.git
synced 2025-01-09 03:07:48 -03:00
14183 lines
421 KiB
Diff
14183 lines
421 KiB
Diff
diff --git a/android_webview/browser/aw_browser_context.cc b/android_webview/browser/aw_browser_context.cc
|
||
index 6167ef5ed42af..6af9e477c4c8a 100644
|
||
--- a/android_webview/browser/aw_browser_context.cc
|
||
+++ b/android_webview/browser/aw_browser_context.cc
|
||
@@ -574,6 +574,9 @@ void AwBrowserContext::ConfigureNetworkContextParams(
|
||
// (http://crbug.com/921750).
|
||
context_params->enforce_chrome_ct_policy = false;
|
||
|
||
+ // WebView does not support ftp yet.
|
||
+ context_params->enable_ftp_url_support = false;
|
||
+
|
||
context_params->enable_brotli = true;
|
||
context_params->enable_zstd =
|
||
base::FeatureList::IsEnabled(net::features::kZstdContentEncoding);
|
||
diff --git a/chrome/app/app-Info.plist b/chrome/app/app-Info.plist
|
||
index 5654e5c9d5858..08d00c2005d7b 100644
|
||
--- a/chrome/app/app-Info.plist
|
||
+++ b/chrome/app/app-Info.plist
|
||
@@ -233,6 +233,14 @@
|
||
<string>https</string>
|
||
</array>
|
||
</dict>
|
||
+ <dict>
|
||
+ <key>CFBundleURLName</key>
|
||
+ <string>FTP site URL</string>
|
||
+ <key>CFBundleURLSchemes</key>
|
||
+ <array>
|
||
+ <string>ftp</string>
|
||
+ </array>
|
||
+ </dict>
|
||
<dict>
|
||
<key>CFBundleURLName</key>
|
||
<string>Local file URL</string>
|
||
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
|
||
index ceabbced0f67e..c298005d46bc3 100644
|
||
--- a/chrome/app/generated_resources.grd
|
||
+++ b/chrome/app/generated_resources.grd
|
||
@@ -13574,6 +13574,9 @@ This can include information about installed software, files, your browser, and
|
||
<message name="IDS_DIRECTORY_LISTING_DATE_MODIFIED" desc="When viewing a local directory, this is the text for the column above the last modified dates.">
|
||
Date Modified
|
||
</message>
|
||
+ <message name="IDS_DIRECTORY_LISTING_PARSING_ERROR_BOX_TEXT" desc="Text to show in a box when we failed to parse FTP directory listing.">
|
||
+ Oh, no! This server is sending data <ph name="PRODUCT_NAME">$1<ex>Thorium</ex></ph> can't understand. Please <ph name="BEGIN_LINK"><a href="https://issues.chromium.org/issues/new"></ph>report a bug<ph name="END_LINK"></a><ex></a></ex></ph>, and include the <ph name="BEGIN2_LINK"><a href="LOCATION"></ph>raw listing<ph name="END2_LINK"></a></ph>.
|
||
+ </message>
|
||
|
||
<!-- Saving Page-->
|
||
<message name="IDS_SAVE_PAGE_DESC_HTML_ONLY" desc="In the Save Page dialog, the description of saving only the HTML of a webpage.">
|
||
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
|
||
index c9e74052c7afb..d271ccc0dca71 100644
|
||
--- a/chrome/browser/about_flags.cc
|
||
+++ b/chrome/browser/about_flags.cc
|
||
@@ -8542,6 +8542,12 @@ const FeatureEntry kFeatureEntries[] = {
|
||
FEATURE_VALUE_TYPE(
|
||
heavy_ad_intervention::features::kHeavyAdPrivacyMitigations)},
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ {"enable-ftp", flag_descriptions::kEnableFtpName,
|
||
+ flag_descriptions::kEnableFtpDescription, kOsAll,
|
||
+ FEATURE_VALUE_TYPE(network::features::kFtpProtocol)},
|
||
+#endif
|
||
+
|
||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||
{"crostini-container-install",
|
||
flag_descriptions::kCrostiniContainerInstallName,
|
||
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
|
||
index e2a95fa3aadaa..5b608e9272838 100644
|
||
--- a/chrome/browser/flag_descriptions.cc
|
||
+++ b/chrome/browser/flag_descriptions.cc
|
||
@@ -355,6 +355,13 @@ const char kEnableDrDcDescription[] =
|
||
"(raster, webgl, video) "
|
||
" continues using the gpu main thread.";
|
||
|
||
+const char kEnableFtpName[] = "Enable support for FTP URLs. Enabled by default. Thorium Flag.";
|
||
+const char kEnableFtpDescription[] =
|
||
+ "When enabled, the browser will handle navigations to ftp:// URLs by "
|
||
+ "either showing a directory listing or downloading the resource over FTP. "
|
||
+ "When disabled, the browser has no special handling for ftp:// URLs and "
|
||
+ "by default defers handling of the URL to the underlying platform.";
|
||
+
|
||
const char kTextBasedAudioDescriptionName[] = "Enable audio descriptions.";
|
||
const char kTextBasedAudioDescriptionDescription[] =
|
||
"When enabled, HTML5 video elements with a 'descriptions' WebVTT track "
|
||
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
|
||
index 4ef2b219f1d74..69f9c4f7d9e1a 100644
|
||
--- a/chrome/browser/flag_descriptions.h
|
||
+++ b/chrome/browser/flag_descriptions.h
|
||
@@ -255,6 +255,9 @@ extern const char
|
||
extern const char
|
||
kEnableExtensionsPermissionsForSupervisedUsersOnDesktopDescription[];
|
||
|
||
+extern const char kEnableFtpName[];
|
||
+extern const char kEnableFtpDescription[];
|
||
+
|
||
extern const char
|
||
kEnableSupervisedUserSkipParentApprovalToInstallExtensionsName[];
|
||
extern const char
|
||
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
|
||
index e50641649a959..0f0ef6bd2b0fd 100644
|
||
--- a/chrome/browser/net/profile_network_context_service.cc
|
||
+++ b/chrome/browser/net/profile_network_context_service.cc
|
||
@@ -1190,6 +1190,15 @@ void ProfileNetworkContextService::ConfigureNetworkContextParamsInternal(
|
||
network_context_params->hsts_policy_bypass_list.push_back(*string_value);
|
||
}
|
||
|
||
+ // NOTE(mmenke): Keep these protocol handlers and
|
||
+ // ProfileIOData::SetUpJobFactoryDefaultsForBuilder in sync with
|
||
+ // ProfileIOData::IsHandledProtocol().
|
||
+ // TODO(mmenke): Find a better way of handling tracking supported schemes.
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ network_context_params->enable_ftp_url_support =
|
||
+ base::FeatureList::IsEnabled(network::features::kFtpProtocol);
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
proxy_config_monitor_.AddToNetworkContextParams(network_context_params);
|
||
|
||
network_context_params->enable_certificate_reporting = true;
|
||
diff --git a/chrome/browser/net/system_network_context_manager.cc b/chrome/browser/net/system_network_context_manager.cc
|
||
index 22a62ad698fdb..ec30208a38bf0 100644
|
||
--- a/chrome/browser/net/system_network_context_manager.cc
|
||
+++ b/chrome/browser/net/system_network_context_manager.cc
|
||
@@ -1042,6 +1042,12 @@ SystemNetworkContextManager::CreateNetworkContextParams() {
|
||
|
||
network_context_params->http_cache_enabled = false;
|
||
|
||
+ // These are needed for PAC scripts that use FTP URLs.
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ network_context_params->enable_ftp_url_support =
|
||
+ base::FeatureList::IsEnabled(network::features::kFtpProtocol);
|
||
+#endif
|
||
+
|
||
proxy_config_monitor_.AddToNetworkContextParams(network_context_params.get());
|
||
|
||
return network_context_params;
|
||
diff --git a/chrome/browser/profiles/profile_io_data.cc b/chrome/browser/profiles/profile_io_data.cc
|
||
index 39f96f45bca3a..123e400761e0f 100644
|
||
--- a/chrome/browser/profiles/profile_io_data.cc
|
||
+++ b/chrome/browser/profiles/profile_io_data.cc
|
||
@@ -15,6 +15,7 @@
|
||
#include "components/dom_distiller/core/url_constants.h"
|
||
#include "extensions/buildflags/buildflags.h"
|
||
#include "net/net_buildflags.h"
|
||
+#include "services/network/public/cpp/features.h"
|
||
#include "url/gurl.h"
|
||
|
||
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
|
||
@@ -54,6 +55,9 @@ bool ProfileIOData::IsHandledProtocol(const std::string& scheme) {
|
||
#if !BUILDFLAG(IS_ANDROID)
|
||
chrome::kIsolatedAppScheme,
|
||
#endif // !BUILDFLAG(IS_ANDROID)
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ url::kFtpScheme,
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
});
|
||
|
||
return kProtocolList.contains(scheme);
|
||
diff --git a/chrome/browser/ui/login/login_tab_helper.cc b/chrome/browser/ui/login/login_tab_helper.cc
|
||
index f6702ac512f8d..a54956a7ec00a 100644
|
||
--- a/chrome/browser/ui/login/login_tab_helper.cc
|
||
+++ b/chrome/browser/ui/login/login_tab_helper.cc
|
||
@@ -74,13 +74,16 @@ void LoginTabHelper::DidFinishNavigation(
|
||
return;
|
||
}
|
||
|
||
- // Show a login prompt with the navigation's AuthChallengeInfo on HTTP 401/407
|
||
- // responses.
|
||
- int response_code = navigation_handle->GetResponseHeaders()->response_code();
|
||
- if (response_code !=
|
||
- net::HttpStatusCode::HTTP_PROXY_AUTHENTICATION_REQUIRED &&
|
||
- response_code != net::HttpStatusCode::HTTP_UNAUTHORIZED) {
|
||
- return;
|
||
+ // Show a login prompt with the navigation's AuthChallengeInfo on FTP
|
||
+ // navigations and on HTTP 401/407 responses.
|
||
+ if (!navigation_handle->GetURL().SchemeIs(url::kFtpScheme)) {
|
||
+ int response_code =
|
||
+ navigation_handle->GetResponseHeaders()->response_code();
|
||
+ if (response_code !=
|
||
+ net::HttpStatusCode::HTTP_PROXY_AUTHENTICATION_REQUIRED &&
|
||
+ response_code != net::HttpStatusCode::HTTP_UNAUTHORIZED) {
|
||
+ return;
|
||
+ }
|
||
}
|
||
|
||
challenge_ = navigation_handle->GetAuthChallengeInfo().value();
|
||
diff --git a/chrome/common/net/net_resource_provider.cc b/chrome/common/net/net_resource_provider.cc
|
||
index 88d43505881b5..92a452fc5f9e9 100644
|
||
--- a/chrome/common/net/net_resource_provider.cc
|
||
+++ b/chrome/common/net/net_resource_provider.cc
|
||
@@ -35,6 +35,8 @@ struct LazyDirectoryListerCacher {
|
||
l10n_util::GetStringUTF8(IDS_DIRECTORY_LISTING_DATE_MODIFIED));
|
||
value.Set("language",
|
||
l10n_util::GetLanguage(base::i18n::GetConfiguredLocale()));
|
||
+ value.Set("listingParsingErrorBoxText",
|
||
+ l10n_util::GetStringUTF8(IDS_DIRECTORY_LISTING_PARSING_ERROR_BOX_TEXT));
|
||
value.Set("textdirection", base::i18n::IsRTL() ? "rtl" : "ltr");
|
||
std::string str = webui::GetI18nTemplateHtml(
|
||
ui::ResourceBundle::GetSharedInstance().LoadDataResourceString(
|
||
diff --git a/chrome/installer/util/shell_util.cc b/chrome/installer/util/shell_util.cc
|
||
index c087d1c02bca4..aa5d721c5ea93 100644
|
||
--- a/chrome/installer/util/shell_util.cc
|
||
+++ b/chrome/installer/util/shell_util.cc
|
||
@@ -1,4 +1,4 @@
|
||
-// Copyright 2012 The Chromium Authors
|
||
+// Copyright 2024 The Chromium Authors and Alex313031
|
||
// Use of this source code is governed by a BSD-style license that can be
|
||
// found in the LICENSE file.
|
||
//
|
||
@@ -1545,10 +1545,10 @@ const wchar_t* ShellUtil::kDefaultFileAssociations[] = {
|
||
const wchar_t* ShellUtil::kPotentialFileAssociations[] = {
|
||
L".htm", L".html", L".mhtml", L".pdf", L".shtml",
|
||
L".svg", L".xht", L".xhtml", L".webp", nullptr};
|
||
-const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"http", L"https",
|
||
+const wchar_t* ShellUtil::kBrowserProtocolAssociations[] = {L"ftp", L"http", L"https",
|
||
nullptr};
|
||
const wchar_t* ShellUtil::kPotentialProtocolAssociations[] = {
|
||
- L"http", L"https", L"irc", L"mailto", L"mms", L"news", L"nntp",
|
||
+ L"ftp", L"http", L"https", L"irc", L"mailto", L"mms", L"news", L"nntp",
|
||
L"sms", L"smsto", L"snews", L"tel", L"urn", L"webcal", nullptr};
|
||
const wchar_t* ShellUtil::kRegUrlProtocol = L"URL Protocol";
|
||
const wchar_t* ShellUtil::kRegApplication = L"\\Application";
|
||
diff --git a/chrome/test/data/ftp/dir1/test.html b/chrome/test/data/ftp/dir1/test.html
|
||
new file mode 100644
|
||
index 0000000000000..ce0c4c91818cd
|
||
--- /dev/null
|
||
+++ b/chrome/test/data/ftp/dir1/test.html
|
||
@@ -0,0 +1,5 @@
|
||
+<html>
|
||
+<head>
|
||
+<title>PASS</title>
|
||
+</head>
|
||
+</html>
|
||
diff --git a/components/safe_browsing/content/browser/safe_browsing_network_context.cc b/components/safe_browsing/content/browser/safe_browsing_network_context.cc
|
||
index 37852c47b90d4..8be654c440fc9 100644
|
||
--- a/components/safe_browsing/content/browser/safe_browsing_network_context.cc
|
||
+++ b/components/safe_browsing/content/browser/safe_browsing_network_context.cc
|
||
@@ -133,6 +133,10 @@ class SafeBrowsingNetworkContext::SharedURLLoaderFactory
|
||
network_context_params->file_paths->trigger_migration = trigger_migration_;
|
||
network_context_params->file_paths->cookie_database_name = base::FilePath(
|
||
base::FilePath::StringType(kSafeBrowsingBaseFilename) + kCookiesFile);
|
||
+ // These are needed for PAC scripts that use FTP URLs.
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ network_context_params->enable_ftp_url_support = true;
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
network_context_params->enable_encrypted_cookies = false;
|
||
|
||
return network_context_params;
|
||
diff --git a/content/browser/browser_url_handler_impl.cc b/content/browser/browser_url_handler_impl.cc
|
||
index 3158ee1460b5b..8f62b65b2ee3e 100644
|
||
--- a/content/browser/browser_url_handler_impl.cc
|
||
+++ b/content/browser/browser_url_handler_impl.cc
|
||
@@ -34,6 +34,7 @@ static bool HandleViewSource(GURL* url, BrowserContext* browser_context) {
|
||
std::vector<std::string> all_allowed_sub_schemes({
|
||
url::kHttpScheme,
|
||
url::kHttpsScheme,
|
||
+ url::kFtpScheme,
|
||
kChromeUIScheme,
|
||
url::kFileScheme,
|
||
url::kFileSystemScheme,
|
||
diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc
|
||
index 30f63bfc1750f..a38a1839d7207 100644
|
||
--- a/content/public/common/content_switches.cc
|
||
+++ b/content/public/common/content_switches.cc
|
||
@@ -329,6 +329,9 @@ const char kEnableFakeNoAllocDirectCallForTesting[] =
|
||
// status:"experimental", which are enabled when running web tests.
|
||
const char kEnableBlinkTestFeatures[] = "enable-blink-test-features";
|
||
|
||
+// Enables support for FTP URLs. See https://crbug.com/333943.
|
||
+const char kEnableFtp[] = "enable-ftp";
|
||
+
|
||
// Disables all RuntimeEnabledFeatures that can be enabled via OriginTrials.
|
||
const char kDisableOriginTrialControlledBlinkFeatures[] =
|
||
"disable-origin-trial-controlled-blink-features";
|
||
diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h
|
||
index b6f3b21a2b1ff..d01af10dcec28 100644
|
||
--- a/content/public/common/content_switches.h
|
||
+++ b/content/public/common/content_switches.h
|
||
@@ -106,6 +106,7 @@ CONTENT_EXPORT extern const char kEnableExperimentalWebAssemblyFeatures[];
|
||
CONTENT_EXPORT extern const char kEnableExperimentalWebAssemblyFeatures[];
|
||
CONTENT_EXPORT extern const char kEnableExperimentalWebPlatformFeatures[];
|
||
CONTENT_EXPORT extern const char kEnableBlinkTestFeatures[];
|
||
+CONTENT_EXPORT extern const char kEnableFtp[];
|
||
CONTENT_EXPORT extern const char kEnableGpuMemoryBufferVideoFrames[];
|
||
CONTENT_EXPORT extern const char kEnableIsolatedWebAppsInRenderer[];
|
||
CONTENT_EXPORT extern const char kEnableLCDText[];
|
||
diff --git a/content/public/renderer/render_frame.h b/content/public/renderer/render_frame.h
|
||
index c7aa04a3f3edb..61b37176a24ab 100644
|
||
--- a/content/public/renderer/render_frame.h
|
||
+++ b/content/public/renderer/render_frame.h
|
||
@@ -179,6 +179,9 @@ class CONTENT_EXPORT RenderFrame :
|
||
virtual blink::AssociatedInterfaceProvider*
|
||
GetRemoteAssociatedInterfaces() = 0;
|
||
|
||
+ // Returns true if this frame is a FTP directory listing.
|
||
+ virtual bool IsFTPDirectoryListing() = 0;
|
||
+
|
||
// Notifies the browser of text selection changes made.
|
||
virtual void SetSelectedText(const std::u16string& selection_text,
|
||
size_t offset,
|
||
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
|
||
index f362ee7c23241..d34f9fbe475b2 100644
|
||
--- a/content/renderer/render_frame_impl.cc
|
||
+++ b/content/renderer/render_frame_impl.cc
|
||
@@ -2500,6 +2500,10 @@ RenderFrameImpl::GetRemoteAssociatedInterfaces() {
|
||
return remote_associated_interfaces_.get();
|
||
}
|
||
|
||
+bool RenderFrameImpl::IsFTPDirectoryListing() {
|
||
+ return frame_->GetDocumentLoader()->IsListingFtpDirectory();
|
||
+}
|
||
+
|
||
void RenderFrameImpl::SetSelectedText(const std::u16string& selection_text,
|
||
size_t offset,
|
||
const gfx::Range& range) {
|
||
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
|
||
index b32fbb5887cb2..626bbdd3b1e5d 100644
|
||
--- a/content/renderer/render_frame_impl.h
|
||
+++ b/content/renderer/render_frame_impl.h
|
||
@@ -400,6 +400,7 @@ class CONTENT_EXPORT RenderFrameImpl
|
||
mojo::ScopedMessagePipeHandle interface_pipe) override;
|
||
blink::AssociatedInterfaceRegistry* GetAssociatedInterfaceRegistry() override;
|
||
blink::AssociatedInterfaceProvider* GetRemoteAssociatedInterfaces() override;
|
||
+ bool IsFTPDirectoryListing() override;
|
||
void SetSelectedText(const std::u16string& selection_text,
|
||
size_t offset,
|
||
const gfx::Range& range) override;
|
||
diff --git a/ios/chrome/browser/profile/model/off_the_record_profile_ios_io_data.mm b/ios/chrome/browser/profile/model/off_the_record_profile_ios_io_data.mm
|
||
index 00516bebc6fa3..62b2eceb8e48a 100644
|
||
--- a/ios/chrome/browser/profile/model/off_the_record_profile_ios_io_data.mm
|
||
+++ b/ios/chrome/browser/profile/model/off_the_record_profile_ios_io_data.mm
|
||
@@ -26,6 +26,7 @@
|
||
#import "ios/web/public/thread/web_thread.h"
|
||
#import "net/cookies/cookie_store.h"
|
||
#import "net/disk_cache/disk_cache.h"
|
||
+#import "net/ftp/ftp_network_layer.h"
|
||
#import "net/http/http_cache.h"
|
||
#import "net/http/http_network_session.h"
|
||
#import "net/http/http_server_properties.h"
|
||
index e229ec4c97bb6..dd33fcf48c550 100644
|
||
--- a/net/BUILD.gn
|
||
+++ b/net/BUILD.gn
|
||
@@ -95,6 +95,7 @@ buildflag_header("buildflags") {
|
||
flags = [
|
||
"POSIX_BYPASS_MMAP=$posix_bypass_mmap",
|
||
"DISABLE_FILE_SUPPORT=$disable_file_support",
|
||
+ "DISABLE_FTP_SUPPORT=$disable_ftp_support",
|
||
"ENABLE_MDNS=$enable_mdns",
|
||
"ENABLE_REPORTING=$enable_reporting",
|
||
"ENABLE_WEBSOCKETS=$enable_websockets",
|
||
@@ -1587,6 +1588,41 @@ component("net") {
|
||
]
|
||
}
|
||
|
||
+ if (!disable_ftp_support) {
|
||
+ sources += [
|
||
+ "ftp/ftp_auth_cache.cc",
|
||
+ "ftp/ftp_auth_cache.h",
|
||
+ "ftp/ftp_ctrl_response_buffer.cc",
|
||
+ "ftp/ftp_ctrl_response_buffer.h",
|
||
+ "ftp/ftp_directory_listing_parser.cc",
|
||
+ "ftp/ftp_directory_listing_parser.h",
|
||
+ "ftp/ftp_directory_listing_parser_ls.cc",
|
||
+ "ftp/ftp_directory_listing_parser_ls.h",
|
||
+ "ftp/ftp_directory_listing_parser_vms.cc",
|
||
+ "ftp/ftp_directory_listing_parser_vms.h",
|
||
+ "ftp/ftp_directory_listing_parser_windows.cc",
|
||
+ "ftp/ftp_directory_listing_parser_windows.h",
|
||
+ "ftp/ftp_network_layer.cc",
|
||
+ "ftp/ftp_network_layer.h",
|
||
+ "ftp/ftp_network_session.cc",
|
||
+ "ftp/ftp_network_session.h",
|
||
+ "ftp/ftp_network_transaction.cc",
|
||
+ "ftp/ftp_network_transaction.h",
|
||
+ "ftp/ftp_request_info.h",
|
||
+ "ftp/ftp_response_info.cc",
|
||
+ "ftp/ftp_response_info.h",
|
||
+ "ftp/ftp_server_type.h",
|
||
+ "ftp/ftp_transaction.h",
|
||
+ "ftp/ftp_transaction_factory.h",
|
||
+ "ftp/ftp_util.cc",
|
||
+ "ftp/ftp_util.h",
|
||
+ "url_request/ftp_protocol_handler.cc",
|
||
+ "url_request/ftp_protocol_handler.h",
|
||
+ "url_request/url_request_ftp_job.cc",
|
||
+ "url_request/url_request_ftp_job.h",
|
||
+ ]
|
||
+ }
|
||
+
|
||
if (enable_websockets) {
|
||
sources += [
|
||
"server/http_connection.cc",
|
||
@@ -3203,6 +3239,21 @@ target(_test_target_type, "net_unittests") {
|
||
]
|
||
}
|
||
|
||
+ if (!disable_ftp_support) {
|
||
+ sources += [
|
||
+ "ftp/ftp_auth_cache_unittest.cc",
|
||
+ "ftp/ftp_ctrl_response_buffer_unittest.cc",
|
||
+ "ftp/ftp_directory_listing_parser_ls_unittest.cc",
|
||
+ "ftp/ftp_directory_listing_parser_unittest.cc",
|
||
+ "ftp/ftp_directory_listing_parser_unittest.h",
|
||
+ "ftp/ftp_directory_listing_parser_vms_unittest.cc",
|
||
+ "ftp/ftp_directory_listing_parser_windows_unittest.cc",
|
||
+ "ftp/ftp_network_transaction_unittest.cc",
|
||
+ "ftp/ftp_util_unittest.cc",
|
||
+ "url_request/url_request_ftp_job_unittest.cc",
|
||
+ ]
|
||
+ }
|
||
+
|
||
if (enable_built_in_dns) {
|
||
sources += [ "url_request/http_with_dns_over_https_unittest.cc" ]
|
||
}
|
||
@@ -3720,6 +3771,46 @@ fuzzer_test("net_spdy_headers_to_http_response_headers_fuzzer") {
|
||
libfuzzer_options = [ "max_len = 512" ]
|
||
}
|
||
|
||
+if (!disable_ftp_support) {
|
||
+ fuzzer_test("net_ftp_ctrl_response_fuzzer") {
|
||
+ sources = [ "ftp/ftp_ctrl_response_fuzzer.cc" ]
|
||
+ deps = [
|
||
+ ":net_fuzzer_test_support",
|
||
+ "//base",
|
||
+ "//net",
|
||
+ ]
|
||
+ }
|
||
+
|
||
+ fuzzer_test("net_ftp_directory_listing_fuzzer") {
|
||
+ sources = [ "ftp/ftp_directory_listing_fuzzer.cc" ]
|
||
+ deps = [
|
||
+ ":net_fuzzer_test_support",
|
||
+ "//base",
|
||
+ "//net",
|
||
+ ]
|
||
+
|
||
+ # TODO(https://crbug.com/921297): Re-enable once source of timeout is
|
||
+ # understood (probably just needs to restrict maximum input size).
|
||
+ additional_configs = [ "//testing/libfuzzer:no_clusterfuzz" ]
|
||
+ }
|
||
+
|
||
+ fuzzer_test("net_url_request_ftp_fuzzer") {
|
||
+ sources = [ "url_request/url_request_ftp_fuzzer.cc" ]
|
||
+ deps = [
|
||
+ ":net_fuzzer_test_support",
|
||
+ ":test_support",
|
||
+ "//base",
|
||
+ "//net",
|
||
+ ]
|
||
+ dict = "data/fuzzer_dictionaries/net_url_request_ftp_fuzzer.dict"
|
||
+ seed_corpus = "data/fuzzer_data/net_url_request_ftp_fuzzer/"
|
||
+
|
||
+ # TODO(https://crbug.com/962087): Re-enable once source of timeout is
|
||
+ # understood (probably just needs to restrict maximum input size).
|
||
+ additional_configs = [ "//testing/libfuzzer:no_clusterfuzz" ]
|
||
+ }
|
||
+}
|
||
+
|
||
fuzzer_test("net_unescape_url_component_fuzzer") {
|
||
sources = [ "base/unescape_url_component_fuzzer.cc" ]
|
||
deps = [
|
||
diff --git a/net/DEPS b/net/DEPS
|
||
index 82956220ad000..d016bd9e7d052 100644
|
||
--- a/net/DEPS
|
||
+++ b/net/DEPS
|
||
@@ -45,6 +45,14 @@ specific_include_rules = {
|
||
"+base/i18n",
|
||
],
|
||
|
||
+ "ftp_util\.cc": [
|
||
+ "+base/i18n",
|
||
+ "+third_party/icu",
|
||
+ ],
|
||
+ "ftp_directory_listing_parser\.cc": [
|
||
+ "+base/i18n",
|
||
+ ],
|
||
+
|
||
"brotli_source_stream\.cc": [
|
||
"+third_party/brotli",
|
||
],
|
||
diff --git a/net/base/auth.h b/net/base/auth.h
|
||
index e80006ee9ac79..c5ddb3f652b4e 100644
|
||
--- a/net/base/auth.h
|
||
+++ b/net/base/auth.h
|
||
@@ -31,8 +31,8 @@ class NET_EXPORT AuthChallengeInfo {
|
||
// The service issuing the challenge.
|
||
url::SchemeHostPort challenger;
|
||
|
||
- // The authentication scheme used, such as "basic" or "digest". The encoding
|
||
- // is ASCII.
|
||
+ // The authentication scheme used, such as "basic" or "digest". If the
|
||
+ // |source| is FTP_SERVER, this is an empty string. The encoding is ASCII.
|
||
std::string scheme;
|
||
|
||
// The realm of the challenge. May be empty. The encoding is UTF-8.
|
||
diff --git a/net/base/dir_header.html b/net/base/dir_header.html
|
||
index a2421e94b4465..10539e9ee4a0a 100644
|
||
--- a/net/base/dir_header.html
|
||
+++ b/net/base/dir_header.html
|
||
@@ -81,6 +81,13 @@ function onHasParentDirectory() {
|
||
link.href = root + "..";
|
||
}
|
||
|
||
+function onListingParsingError() {
|
||
+ var box = document.getElementById("listingParsingErrorBox");
|
||
+ box.innerHTML = box.innerHTML.replace("LOCATION", encodeURI(document.location)
|
||
+ + "?raw");
|
||
+ box.style.display = "block";
|
||
+}
|
||
+
|
||
function sortTable(column) {
|
||
var theader = document.getElementById("theader");
|
||
var oldOrder = theader.cells[column].dataset.order || '1';
|
||
@@ -191,6 +198,13 @@ window.addEventListener('DOMContentLoaded', onLoad);
|
||
margin-bottom: 10px;
|
||
padding-bottom: 10px;
|
||
}
|
||
+
|
||
+ #listingParsingErrorBox {
|
||
+ border: 1px solid black;
|
||
+ background: #fae691;
|
||
+ padding: 10px;
|
||
+ display: none;
|
||
+ }
|
||
</style>
|
||
|
||
<title id="title"></title>
|
||
@@ -199,6 +213,8 @@ window.addEventListener('DOMContentLoaded', onLoad);
|
||
|
||
<body>
|
||
|
||
+<div id="listingParsingErrorBox">$i18nRaw{listingParsingErrorBoxText}</div>
|
||
+
|
||
<h1 id="header">$i18n{header}</h1>
|
||
|
||
<div id="parentDirLinkBox" style="display:none">
|
||
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
|
||
index dff37088d6c11..31fa5e6ad19a2 100644
|
||
--- a/net/base/net_error_list.h
|
||
+++ b/net/base/net_error_list.h
|
||
@@ -18,7 +18,7 @@
|
||
// 300-399 HTTP errors
|
||
// 400-499 Cache errors
|
||
// 500-599 ?
|
||
-// 600-699 <Obsolete: FTP errors>
|
||
+// 600-699 FTP errors
|
||
// 700-799 Certificate manager errors
|
||
// 800-899 DNS resolver errors
|
||
|
||
@@ -913,14 +913,36 @@ NET_ERROR(TRUST_TOKEN_OPERATION_FAILED, -506)
|
||
// to a local provider (for "platform-provided" issuance).
|
||
NET_ERROR(TRUST_TOKEN_OPERATION_SUCCESS_WITHOUT_SENDING_REQUEST, -507)
|
||
|
||
-// *** Code -600 is reserved (was FTP_PASV_COMMAND_FAILED). ***
|
||
-// *** Code -601 is reserved (was FTP_FAILED). ***
|
||
-// *** Code -602 is reserved (was FTP_SERVICE_UNAVAILABLE). ***
|
||
-// *** Code -603 is reserved (was FTP_TRANSFER_ABORTED). ***
|
||
-// *** Code -604 is reserved (was FTP_FILE_BUSY). ***
|
||
-// *** Code -605 is reserved (was FTP_SYNTAX_ERROR). ***
|
||
-// *** Code -606 is reserved (was FTP_COMMAND_NOT_SUPPORTED). ***
|
||
-// *** Code -607 is reserved (was FTP_BAD_COMMAND_SEQUENCE). ***
|
||
+// A generic error for failed FTP control connection command.
|
||
+// If possible, please use or add a more specific error code.
|
||
+NET_ERROR(FTP_FAILED, -601)
|
||
+
|
||
+// The server cannot fulfill the request at this point. This is a temporary
|
||
+// error.
|
||
+// FTP response code 421.
|
||
+NET_ERROR(FTP_SERVICE_UNAVAILABLE, -602)
|
||
+
|
||
+// The server has aborted the transfer.
|
||
+// FTP response code 426.
|
||
+NET_ERROR(FTP_TRANSFER_ABORTED, -603)
|
||
+
|
||
+// The file is busy, or some other temporary error condition on opening
|
||
+// the file.
|
||
+// FTP response code 450.
|
||
+NET_ERROR(FTP_FILE_BUSY, -604)
|
||
+
|
||
+// Server rejected our command because of syntax errors.
|
||
+// FTP response codes 500, 501.
|
||
+NET_ERROR(FTP_SYNTAX_ERROR, -605)
|
||
+
|
||
+// Server does not support the command we issued.
|
||
+// FTP response codes 502, 504.
|
||
+NET_ERROR(FTP_COMMAND_NOT_SUPPORTED, -606)
|
||
+
|
||
+// Server rejected our command because we didn't issue the commands in right
|
||
+// order.
|
||
+// FTP response code 503.
|
||
+NET_ERROR(FTP_BAD_COMMAND_SEQUENCE, -607)
|
||
|
||
// PKCS #12 import failed due to incorrect password.
|
||
NET_ERROR(PKCS12_IMPORT_BAD_PASSWORD, -701)
|
||
diff --git a/net/base/port_util.cc b/net/base/port_util.cc
|
||
index a70f9e9b21a33..5f3d1364d8c62 100644
|
||
--- a/net/base/port_util.cc
|
||
+++ b/net/base/port_util.cc
|
||
@@ -139,6 +139,11 @@ bool IsPortAllowedForScheme(int port, std::string_view url_scheme) {
|
||
if (g_explicitly_allowed_ports.Get().count(port) > 0)
|
||
return true;
|
||
|
||
+ // FTP requests are permitted to use port 21.
|
||
+ if ((base::ToLowerASCII(url_scheme) == url::kFtpScheme) && port == 21) {
|
||
+ return true;
|
||
+ }
|
||
+
|
||
// Finally check against the generic list of restricted ports for all
|
||
// schemes.
|
||
for (int restricted_port : kRestrictedPorts) {
|
||
diff --git a/net/data/ftp/dir-listing-ls-1 b/net/data/ftp/dir-listing-ls-1
|
||
new file mode 100644
|
||
index 0000000000000..e99088e773075
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-1
|
||
@@ -0,0 +1,6 @@
|
||
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 .
|
||
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 ..
|
||
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 .message
|
||
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README
|
||
+-rw-r--r-- 1 ftp ftp 560 Sep 28 2007 index.html
|
||
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-1-utf8 b/net/data/ftp/dir-listing-ls-1-utf8
|
||
new file mode 100644
|
||
index 0000000000000..7f9a33339aadd
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-1-utf8
|
||
@@ -0,0 +1,6 @@
|
||
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 .
|
||
+drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 ..
|
||
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 .message
|
||
+-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README!
|
||
+-rw-r--r-- 1 ftp ftp 560 Sep 28 2007 こんにちは.html
|
||
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-1-utf8.expected b/net/data/ftp/dir-listing-ls-1-utf8.expected
|
||
new file mode 100644
|
||
index 0000000000000..8e4508f6e0a72
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-1-utf8.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+5
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+5
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+-
|
||
+.message
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+README!
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+こんにちは.html
|
||
+560
|
||
+2007
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+8
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-1.expected b/net/data/ftp/dir-listing-ls-1.expected
|
||
new file mode 100644
|
||
index 0000000000000..40375902c4c8a
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-1.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+5
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+5
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+-
|
||
+.message
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+README
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+index.html
|
||
+560
|
||
+2007
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+8
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-10 b/net/data/ftp/dir-listing-ls-10
|
||
new file mode 100644
|
||
index 0000000000000..deff68fc29264
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-10
|
||
@@ -0,0 +1,10 @@
|
||
+Gesamt 4352
|
||
+---------- 1 1 1 0 Okt 25 1999 .notar
|
||
+lrwxrwxrwx 1 1 1 7 Okt 23 2007 bin -> usr/bin
|
||
+d--x--x--x 1 2 2 512 Apr 23 2002 dev
|
||
+d--x--x--x 1 2 2 512 Apr 1 2004 etc
|
||
+drwx------ 1 7 root 1536 Aug 14 13:49 lost+found
|
||
+drwxr-xr-x 1 3 1 512 M<>r 10 2003 private
|
||
+drwxrwsr-x 1 25 1260 1024 Aug 10 2006 pub
|
||
+-rw------- 1 1 1 2211496 Okt 23 2007 restoresymtable
|
||
+d--x--x--x 1 6 2 512 Apr 23 2002 usr
|
||
diff --git a/net/data/ftp/dir-listing-ls-10.expected b/net/data/ftp/dir-listing-ls-10.expected
|
||
new file mode 100644
|
||
index 0000000000000..d8f7a60941813
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-10.expected
|
||
@@ -0,0 +1,80 @@
|
||
+-
|
||
+.notar
|
||
+0
|
||
+1999
|
||
+10
|
||
+25
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+bin
|
||
+-1
|
||
+2007
|
||
+10
|
||
+23
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dev
|
||
+-1
|
||
+2002
|
||
+4
|
||
+23
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+2004
|
||
+4
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+lost+found
|
||
+-1
|
||
+1994
|
||
+8
|
||
+14
|
||
+13
|
||
+49
|
||
+
|
||
+d
|
||
+private
|
||
+-1
|
||
+2003
|
||
+3
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2006
|
||
+8
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+restoresymtable
|
||
+2211496
|
||
+2007
|
||
+10
|
||
+23
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+usr
|
||
+-1
|
||
+2002
|
||
+4
|
||
+23
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-11 b/net/data/ftp/dir-listing-ls-11
|
||
new file mode 100644
|
||
index 0000000000000..f81fda61f383d
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-11
|
||
@@ -0,0 +1,8 @@
|
||
+total 14
|
||
+drwxr-xr-x 2 other 512 Feb 25 2009 beid
|
||
+lrwxrwxrwx 1 bin 9 May 1 2007 bin -> ./usr/bin
|
||
+d--x--x--x 2 sys 512 May 1 2007 dev
|
||
+d--x--x--x 5 sys 512 May 1 2007 etc
|
||
+drwxr-xr-x 2 sys 512 Mar 27 2009 pub
|
||
+drwxr-xr-x 3 other 512 Apr 11 2007 tigerd1
|
||
+d--x--x--x 6 sys 512 May 1 2007 usr
|
||
diff --git a/net/data/ftp/dir-listing-ls-11.expected b/net/data/ftp/dir-listing-ls-11.expected
|
||
new file mode 100644
|
||
index 0000000000000..82f1a9ddaa65c
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-11.expected
|
||
@@ -0,0 +1,62 @@
|
||
+d
|
||
+beid
|
||
+-1
|
||
+2009
|
||
+2
|
||
+25
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+bin
|
||
+-1
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dev
|
||
+-1
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2009
|
||
+3
|
||
+27
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+tigerd1
|
||
+-1
|
||
+2007
|
||
+4
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+usr
|
||
+-1
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-12 b/net/data/ftp/dir-listing-ls-12
|
||
new file mode 100644
|
||
index 0000000000000..7eee0bd1f242e
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-12
|
||
@@ -0,0 +1,8 @@
|
||
+total 14
|
||
+lrwxrwxrwx 1 other 7 Sep 1 2005 bin -> usr/bin
|
||
+dr-xr-xr-x 2 other 512 Aug 9 2004 dev
|
||
+dr-xr-xr-x 2 other 512 Sep 28 2006 etc
|
||
+drwxr-xr-x 2 other 512 Sep 28 2006 msgs
|
||
+drwxr-xr-x 53 other 1024 Jun 30 09:52 pub
|
||
+dr-xr-xr-x 5 other 512 Aug 9 2004 usr
|
||
+drwxr-xr-x 2 other 512 Aug 9 2004 var
|
||
diff --git a/net/data/ftp/dir-listing-ls-12.expected b/net/data/ftp/dir-listing-ls-12.expected
|
||
new file mode 100644
|
||
index 0000000000000..c2f323207b8f2
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-12.expected
|
||
@@ -0,0 +1,62 @@
|
||
+l
|
||
+bin
|
||
+-1
|
||
+2005
|
||
+9
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dev
|
||
+-1
|
||
+2004
|
||
+8
|
||
+9
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+2006
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+msgs
|
||
+-1
|
||
+2006
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+1994
|
||
+6
|
||
+30
|
||
+9
|
||
+52
|
||
+
|
||
+d
|
||
+usr
|
||
+-1
|
||
+2004
|
||
+8
|
||
+9
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+var
|
||
+-1
|
||
+2004
|
||
+8
|
||
+9
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-13 b/net/data/ftp/dir-listing-ls-13
|
||
new file mode 100644
|
||
index 0000000000000..b2b7bff5a24fd
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-13
|
||
@@ -0,0 +1,3 @@
|
||
+-r--r--r-- 1 ftp -A--- 93064 Dec 28 2007 test.jpg
|
||
+dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels
|
||
+-r--r--r-- 1 ftp -A--- 13274 Mar 1 2006 UpTime.exe
|
||
diff --git a/net/data/ftp/dir-listing-ls-13.expected b/net/data/ftp/dir-listing-ls-13.expected
|
||
new file mode 100644
|
||
index 0000000000000..049f3bf497179
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-13.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+test.jpg
|
||
+93064
|
||
+2007
|
||
+12
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+kernels
|
||
+-1
|
||
+1993
|
||
+11
|
||
+17
|
||
+17
|
||
+8
|
||
+
|
||
+-
|
||
+UpTime.exe
|
||
+13274
|
||
+2006
|
||
+3
|
||
+1
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-14 b/net/data/ftp/dir-listing-ls-14
|
||
new file mode 100644
|
||
index 0000000000000..84edee4ef3b44
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-14
|
||
@@ -0,0 +1,3 @@
|
||
+-rwxr-xr-x 0 214 214 Jun 30 2005 !readme
|
||
+drwxr-xr-x folder 0 Jul 17 2006 online
|
||
+-rwxr-xr-x 332 7 339 Feb 5 2004 welcome.txt
|
||
diff --git a/net/data/ftp/dir-listing-ls-14.expected b/net/data/ftp/dir-listing-ls-14.expected
|
||
new file mode 100644
|
||
index 0000000000000..05c039897fa61
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-14.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+!readme
|
||
+214
|
||
+2005
|
||
+6
|
||
+30
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+online
|
||
+-1
|
||
+2006
|
||
+7
|
||
+17
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+welcome.txt
|
||
+339
|
||
+2004
|
||
+2
|
||
+5
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-15 b/net/data/ftp/dir-listing-ls-15
|
||
new file mode 100644
|
||
index 0000000000000..ee0de6ff0b1e7
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-15
|
||
@@ -0,0 +1,5 @@
|
||
+total 30
|
||
+---------- 1 root other 0 Mar 26 2004 .notar
|
||
+d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming
|
||
+-r--r--r-- 2 ftp 989 7196 Aug 22 2007 incoming.README
|
||
+drwxr-xr-x+ 16 root sys 512 Sep 25 09:56 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-15.expected b/net/data/ftp/dir-listing-ls-15.expected
|
||
new file mode 100644
|
||
index 0000000000000..c521d00914429
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-15.expected
|
||
@@ -0,0 +1,35 @@
|
||
+-
|
||
+.notar
|
||
+0
|
||
+2004
|
||
+3
|
||
+26
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+incoming
|
||
+-1
|
||
+1993
|
||
+12
|
||
+8
|
||
+15
|
||
+54
|
||
+
|
||
+-
|
||
+incoming.README
|
||
+7196
|
||
+2007
|
||
+8
|
||
+22
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+1994
|
||
+9
|
||
+25
|
||
+9
|
||
+56
|
||
diff --git a/net/data/ftp/dir-listing-ls-16 b/net/data/ftp/dir-listing-ls-16
|
||
new file mode 100644
|
||
index 0000000000000..4c2f783c05435
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-16
|
||
@@ -0,0 +1,7 @@
|
||
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 documentales
|
||
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 dosier
|
||
+dr-xr-xr-x 1 owner group 0 Dec 1 2008 promos
|
||
+dr-xr-xr-x 1 owner group 0 Nov 28 2008 Sue<75>os_futbol
|
||
+dr-xr-xr-x 1 owner group 0 Nov 2 15:53 test
|
||
+dr-xr-xr-x 1 owner group 0 Nov 25 10:04 tmp
|
||
+-r-xr-xr-x 1 owner group 125 Oct 11 2007 Gastronom<6F>a.txt
|
||
diff --git a/net/data/ftp/dir-listing-ls-16.expected b/net/data/ftp/dir-listing-ls-16.expected
|
||
new file mode 100644
|
||
index 0000000000000..90e39e653a78c
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-16.expected
|
||
@@ -0,0 +1,62 @@
|
||
+d
|
||
+documentales
|
||
+-1
|
||
+2008
|
||
+11
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dosier
|
||
+-1
|
||
+2008
|
||
+11
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+promos
|
||
+-1
|
||
+2008
|
||
+12
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Sueños_futbol
|
||
+-1
|
||
+2008
|
||
+11
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+test
|
||
+-1
|
||
+1994
|
||
+11
|
||
+2
|
||
+15
|
||
+53
|
||
+
|
||
+d
|
||
+tmp
|
||
+-1
|
||
+1993
|
||
+11
|
||
+25
|
||
+10
|
||
+4
|
||
+
|
||
+-
|
||
+Gastronomía.txt
|
||
+125
|
||
+2007
|
||
+10
|
||
+11
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-17 b/net/data/ftp/dir-listing-ls-17
|
||
new file mode 100644
|
||
index 0000000000000..c07fbf3e34f1e
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-17
|
||
@@ -0,0 +1 @@
|
||
+ftpd-BSD: .: Permission denied
|
||
diff --git a/net/data/ftp/dir-listing-ls-17.expected b/net/data/ftp/dir-listing-ls-17.expected
|
||
new file mode 100644
|
||
index 0000000000000..e69de29bb2d1d
|
||
diff --git a/net/data/ftp/dir-listing-ls-18 b/net/data/ftp/dir-listing-ls-18
|
||
new file mode 100644
|
||
index 0000000000000..a504074577592
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-18
|
||
@@ -0,0 +1,3 @@
|
||
+-rw-r--r-- 1 ftpadmin ftpadmin125435904 Apr 9 2008 .pureftpd-upload.47fcbb3c.849.191b.cd40d08a
|
||
+-rw-r--r-- 1 ftpadmin ftpadmin153198592 Nov 20 2008 .pureftpd-upload.4925a00d.849.668d.9ea5b3ed
|
||
+-rw-r--r-- 1 ftpadmin ftpadmin 8760 Nov 24 2008 .pureftpd-upload.492b0a03.849.12d.bf5d2bc6
|
||
diff --git a/net/data/ftp/dir-listing-ls-18.expected b/net/data/ftp/dir-listing-ls-18.expected
|
||
new file mode 100644
|
||
index 0000000000000..1f54f350fb0d4
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-18.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+.pureftpd-upload.47fcbb3c.849.191b.cd40d08a
|
||
+-1
|
||
+2008
|
||
+4
|
||
+9
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+.pureftpd-upload.4925a00d.849.668d.9ea5b3ed
|
||
+-1
|
||
+2008
|
||
+11
|
||
+20
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+.pureftpd-upload.492b0a03.849.12d.bf5d2bc6
|
||
+8760
|
||
+2008
|
||
+11
|
||
+24
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-19 b/net/data/ftp/dir-listing-ls-19
|
||
new file mode 100644
|
||
index 0000000000000..ac4c907efce4d
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-19
|
||
@@ -0,0 +1,2 @@
|
||
+drwxr-xr-x 2 0 0 4096 Mar 18 2007
|
||
+-rw-r--r-- 1 48 0 4327486 Jun 16 2006 junorelease.zip
|
||
diff --git a/net/data/ftp/dir-listing-ls-19.expected b/net/data/ftp/dir-listing-ls-19.expected
|
||
new file mode 100644
|
||
index 0000000000000..9f0f29744f5b1
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-19.expected
|
||
@@ -0,0 +1,8 @@
|
||
+-
|
||
+junorelease.zip
|
||
+4327486
|
||
+2006
|
||
+6
|
||
+16
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-2 b/net/data/ftp/dir-listing-ls-2
|
||
new file mode 100644
|
||
index 0000000000000..052d63da4c393
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-2
|
||
@@ -0,0 +1,7 @@
|
||
+drwxr-xr-x 3 0 0 4096 Sep 18 2008 .
|
||
+drwxr-xr-x 3 0 0 4096 Sep 18 2008 ..
|
||
+lrwxrwxrwx 1 0 509 1 Nov 08 2007 ftp -> .
|
||
+lrwxrwxrwx 1 0 0 3 Oct 12 2007 mirror -> pub
|
||
+lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub
|
||
+lrwxrwxrwx 1 0 0 27 Oct 16 2007 site -> vol/1/.CLUSTER/var_ftp/site
|
||
+drwxr-xr-x 7 0 0 4096 Jul 02 2008 vol
|
||
diff --git a/net/data/ftp/dir-listing-ls-2.expected b/net/data/ftp/dir-listing-ls-2.expected
|
||
new file mode 100644
|
||
index 0000000000000..315f0a55fe2b1
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-2.expected
|
||
@@ -0,0 +1,62 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+2008
|
||
+9
|
||
+18
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+2008
|
||
+9
|
||
+18
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+ftp
|
||
+-1
|
||
+2007
|
||
+11
|
||
+8
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+mirror
|
||
+-1
|
||
+2007
|
||
+10
|
||
+12
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+9
|
||
+18
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+site
|
||
+-1
|
||
+2007
|
||
+10
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+vol
|
||
+-1
|
||
+2008
|
||
+7
|
||
+2
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-20 b/net/data/ftp/dir-listing-ls-20
|
||
new file mode 100644
|
||
index 0000000000000..b8cb80b29c728
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-20
|
||
@@ -0,0 +1,18 @@
|
||
+drwxrwxr-x 17 ftp ftp 4096 Nov 01 16:27 .
|
||
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
|
||
+drwxrwxrwx 5 ftp ftp 4096 May 26 15:51 2012_-_2012-(2009)-[1080p]_[BD]
|
||
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:24 _READ_ME.txt
|
||
+drwxrwxrwx 5 ftp ftp 4096 Nov 01 16:27 <20><><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>_-_Face_Off-(1997)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jan 22 2010 <20><><EFBFBD><EFBFBD><EFBFBD>_-_Up-(2009)-[1080p]_[BD]
|
||
+drwxrwxrwx 5 ftp ftp 4096 May 27 18:12 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD>_-_Total_Recall-(1990)-[1080p]_[BD]
|
||
+drwxrwxrwx 3 ftp ftp 4096 May 21 14:28 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Law_Abiding_Citizen_[Unrated_Edition]-(2009)-[1080p]_[BD_Remux]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jan 21 2010 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Cloverfield-(2008)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 May 26 13:43 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Pandorum-(2009)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 May 27 14:18 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Last_Samurai-(2003)-[1080p]_[BD]
|
||
+drwxrwxrwx 6 ftp ftp 4096 Jan 27 2010 <20><><EFBFBD><EFBFBD><EFBFBD>_9_-_District_9-(2009)-[1080p]_[BD]
|
||
+drwxrwxrwx 7 ftp ftp 4096 Jan 01 2010 <20><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Rocky_The_Undisputed_Collection-(1976_1979_1982_1985_1990)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 May 31 12:57 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Surrogates-(2009)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jan 28 2010 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_-_The_Fast_and_the_Furious-Tokyo_Drift-(2006)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jan 08 2010 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Fast_and_the_Furious-(2001)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jan 06 2010 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_2_-_2_Fast_2_Furious-(2003)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jun 08 15:26 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_4_-_Fast_&_Furious-(2009)-[1080p]_[BD]
|
||
diff --git a/net/data/ftp/dir-listing-ls-20.expected b/net/data/ftp/dir-listing-ls-20.expected
|
||
new file mode 100644
|
||
index 0000000000000..2b51bd72abe55
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-20.expected
|
||
@@ -0,0 +1,161 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+11
|
||
+1
|
||
+16
|
||
+27
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+2010
|
||
+4
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+2012_-_2012-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+26
|
||
+15
|
||
+51
|
||
+
|
||
+-
|
||
+_READ_ME.txt
|
||
+4931
|
||
+1994
|
||
+6
|
||
+8
|
||
+15
|
||
+24
|
||
+
|
||
+d
|
||
+Без_лица_-_Face_Off-(1997)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+11
|
||
+1
|
||
+16
|
||
+27
|
||
+
|
||
+d
|
||
+Вверх_-_Up-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+22
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Вспомнить_все_-_Total_Recall-(1990)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+27
|
||
+18
|
||
+12
|
||
+
|
||
+d
|
||
+Законопослушный_гражданин_[Расширенная_версия]_-_Law_Abiding_Citizen_[Unrated_Edition]-(2009)-[1080p]_[BD_Remux]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+21
|
||
+14
|
||
+28
|
||
+
|
||
+d
|
||
+Монстро_-_Cloverfield-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+21
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Пандорум_-_Pandorum-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+26
|
||
+13
|
||
+43
|
||
+
|
||
+d
|
||
+Последний_самурай_-_The_Last_Samurai-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+27
|
||
+14
|
||
+18
|
||
+
|
||
+d
|
||
+Район_9_-_District_9-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+27
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Рокки_Антология_-_Rocky_The_Undisputed_Collection-(1976_1979_1982_1985_1990)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Суррогаты_-_Surrogates-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+31
|
||
+12
|
||
+57
|
||
+
|
||
+d
|
||
+Тройной_Форсаж-Токийский_Дрифт_-_The_Fast_and_the_Furious-Tokyo_Drift-(2006)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Форсаж_-_The_Fast_and_the_Furious-(2001)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+8
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Форсаж_2_-_2_Fast_2_Furious-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+2010
|
||
+1
|
||
+6
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Форсаж_4_-_Fast_&_Furious-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+6
|
||
+8
|
||
+15
|
||
+26
|
||
diff --git a/net/data/ftp/dir-listing-ls-21 b/net/data/ftp/dir-listing-ls-21
|
||
new file mode 100644
|
||
index 0000000000000..197177df70ace
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-21
|
||
@@ -0,0 +1,27 @@
|
||
+drwxrwxr-x 26 ftp ftp 4096 Jul 15 2009 .
|
||
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
|
||
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:24 _READ_ME.txt
|
||
+drwxr-xr-x 5 ftp ftp 4096 Apr 27 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Avalon-(2001)-[1080p]_[BD_Remux]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jun 15 2009 <20><><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Bruce_Almighty-(2003)-[1080p]_[BD]
|
||
+drwxr-xr-x 4 ftp ftp 4096 Apr 15 2009 <20><><EFBFBD><EFBFBD>-<2D>_-_WALL-E-(2008)-[1080p]_[BD]
|
||
+drwxr-xr-x 4 ftp ftp 4096 Apr 28 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>_007-<2D><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_James_Bond_007-Quantum_of_Solace-(2008)-[1080p]_[BD]
|
||
+drwxr-xr-x 4 ftp ftp 4096 Apr 15 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_c<5F><63><EFBFBD><EFBFBD><EFBFBD>_-_Dead_Space-Downfall-(2008)-[1080p]_[BD]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_1_-_Madagascar_1-(2005)-[1080p]_[BD]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_2_-_Madagascar-Escape_2_Africa-(2008)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jun 13 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Matrix-Reloaded-(2003)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jun 14 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Matrix-Revolutions-(2003)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jun 12 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Matrix-(1999)-[1080p]_[BD]
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jul 02 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD>_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD]
|
||
+drwxr-xr-x 3 ftp ftp 4096 May 01 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Island-(2005)-[1080p]_[BD]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jul 03 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_3_-_Transporter_3-(2008)-[1080p]_[BD]
|
||
+drwxrwxr-x 5 ftp ftp 4096 May 02 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>-<2D><>_<EFBFBD><5F><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_-_Pirates_of_the_Caribbean-At_World's_End-(2007)-[1080p]_[BD]
|
||
+drwxrwxr-x 5 ftp ftp 4096 May 03 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Pirates_of_the_Caribbean-The_Curse_of_the_Black_Pearl-(2003)-[1080p]_[BD]
|
||
+drwxrwxr-x 5 ftp ftp 4096 May 02 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Pirates_of_the_Caribbean-Dead_Man's_Chest-(2006)-[1080p]_[BD]
|
||
+drwxrwxr-x 3 ftp ftp 4096 May 01 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Ghost_Rider-(2007)-[1080p]_[BD]
|
||
+drwxr-xr-x 5 ftp ftp 4096 Apr 29 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Princess_Bride-(1987)-[1080p]_[BD]
|
||
+drwxrwxrwx 5 ftp ftp 4096 Jun 08 2009 <20><><EFBFBD><EFBFBD>_<EFBFBD>_101_<31><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Sex_and_Death_101-(2007)-[1080p]_[BD]
|
||
+drwxr-xr-x 4 ftp ftp 4096 May 01 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>_-_Transformers-Bonus_Disk-(2007)-[1080p]_[BD]
|
||
+drwxr-xr-x 4 ftp ftp 4096 Apr 30 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Transformers-(2007)-[1080p]_[BD]
|
||
+drwxrwxrwx 6 ftp ftp 4096 Jun 07 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>_-_The_Thirteenth_Floor-(1999)-[1080p]_[BD]
|
||
+drwxrwxr-x 3 ftp ftp 4096 May 04 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD>_-_Street_Fighter-(1994)-[1080p]_[BD_Remux]
|
||
+drwxr-xr-x 5 ftp ftp 4096 Mar 15 2009 <20><><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_What_Woman_Want-(2000)-[1080p]_[BD]
|
||
diff --git a/net/data/ftp/dir-listing-ls-21.expected b/net/data/ftp/dir-listing-ls-21.expected
|
||
new file mode 100644
|
||
index 0000000000000..d86a006012fa4
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-21.expected
|
||
@@ -0,0 +1,242 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+2009
|
||
+7
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+2010
|
||
+4
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+_READ_ME.txt
|
||
+4931
|
||
+1994
|
||
+6
|
||
+8
|
||
+15
|
||
+24
|
||
+
|
||
+d
|
||
+Авалон_-_Avalon-(2001)-[1080p]_[BD_Remux]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+27
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Брюс_всемогущий_-_Bruce_Almighty-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+ВАЛЛ-И_-_WALL-E-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Джеймс_Бонд_007-Квант_милосердия_-_James_Bond_007-Quantum_of_Solace-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Космос-Территория_cмерти_-_Dead_Space-Downfall-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Мадагаскар_1_-_Madagascar_1-(2005)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+7
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Мадагаскар_2_-_Madagascar-Escape_2_Africa-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+7
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Матрица-Перезагрузка_-_The_Matrix-Reloaded-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+13
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Матрица-Революция_-_The_Matrix-Revolutions-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+14
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Матрица_-_The_Matrix-(1999)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+12
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Обитель_зла_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+7
|
||
+2
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Остров_-_The_Island-(2005)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Перевозчик_3_-_Transporter_3-(2008)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+7
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Пираты_Карибского_моря-На_краю_Света_-_Pirates_of_the_Caribbean-At_World's_End-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+2
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Пираты_Карибского_моря-Проклятие_Черной_Жемчужины_-_Pirates_of_the_Caribbean-The_Curse_of_the_Black_Pearl-(2003)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Пираты_Карибского_моря-Сундук_мертвеца_-_Pirates_of_the_Caribbean-Dead_Man's_Chest-(2006)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+2
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Призрачный_гонщик_-_Ghost_Rider-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Принцесса-невеста_-_The_Princess_Bride-(1987)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+29
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Секс_и_101_смерть_-_Sex_and_Death_101-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+8
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Трансформеры-Бонус_диск_-_Transformers-Bonus_Disk-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Трансформеры_-_Transformers-(2007)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+4
|
||
+30
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Тринадцатый_этаж_-_The_Thirteenth_Floor-(1999)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+6
|
||
+7
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Уличный_боец_-_Street_Fighter-(1994)-[1080p]_[BD_Remux]
|
||
+-1
|
||
+2009
|
||
+5
|
||
+4
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Чего_хотят_женщины_-_What_Woman_Want-(2000)-[1080p]_[BD]
|
||
+-1
|
||
+2009
|
||
+3
|
||
+15
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-22 b/net/data/ftp/dir-listing-ls-22
|
||
new file mode 100644
|
||
index 0000000000000..d8a01fb6b5a8e
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-22
|
||
@@ -0,0 +1,32 @@
|
||
+drwxrwxr-x 5 ftp ftp 12288 Oct 20 17:04 .
|
||
+drwxr-xr-x 7 ftp ftp 4096 Apr 03 2010 ..
|
||
+-rw-rw-rw- 1 ftp ftp 4931 Jun 08 15:23 _READ_ME.txt
|
||
+drwxrwxrwx 4 ftp ftp 4096 May 31 16:26 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Avatar-(2009)-[1080p]_[BD]
|
||
+-rwxrwxr-x 1 ftp ftp 17577705968 Mar 08 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_1_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_American_Pie_1_[Unrated_Edition]-(1999)-[1080p]_[BD_remux].ts
|
||
+-rwxrwxr-x 1 ftp ftp 15512934868 Mar 16 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD>_-_Snatch-(2000)-[1080i]_[HDTV].ts
|
||
+drwxrwxrwx 2 ftp ftp 4096 Jun 03 19:07 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD>_-_Snatch-(2000)-[1080p]_[BD_Remux]
|
||
+-rwxrwxr-x 1 ftp ftp 8900589105 Mar 24 2009 <20><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_-_War_of_the_Worlds-(2005)-[720p]_[HDTV].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 27728321654 Mar 09 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_American_Gangster-(2007)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 31731782861 Mar 09 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_American_Gangster_[Unrated_Edition]-(2007)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 5009104014 Mar 24 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Road_Trip-(2000)-[720p]_[HDTV_Rip].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 21410583980 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_2-<2D><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Star_Wars-Episode_2-Attack_of_the_Clones-(2002)-[1080i]_[HDTV].ts
|
||
+-rwxrwxr-x 1 ftp ftp 19858181688 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_3-<2D><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Star_Wars-Episode_3-Revenge_of_the_Sith-(2005)-[1080i]_[HDTV].ts
|
||
+-rwxrwxr-x 1 ftp ftp 29026065728 Mar 16 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Starship_Troopers-(1997)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 22169179449 Mar 16 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Mirrors_[Unrated_Edition]-(2008)-[1080p]_[BD_remux].mkv
|
||
+drwxrwxrwx 4 ftp ftp 4096 Jun 15 14:56 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Ninja_Assassin-(2009)-[1080p]_[BD]
|
||
+-rwxrwxr-x 1 ftp ftp 19717173247 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD>_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 18660904388 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_Pathology-(2008)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 16476154520 Mar 05 2009 <20><><EFBFBD><EFBFBD>_1_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_I_[Director's_Cut]-(2004)-[1080p]_[HDDVD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 19917510515 Mar 05 2009 <20><><EFBFBD><EFBFBD>_2_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_II_[Director's_Cut]-(2005)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 18085592265 Mar 05 2009 <20><><EFBFBD><EFBFBD>_3_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_III_[Director's_Cut]-(2006)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 3473582701 Mar 05 2009 <20><><EFBFBD><EFBFBD>_4_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].flac
|
||
+-rwxrwxr-x 1 ftp ftp 15263958421 Mar 05 2009 <20><><EFBFBD><EFBFBD>_4_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 19944605507 Mar 16 2009 <20><><EFBFBD><EFBFBD>_5_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Saw_V_[Director's_Cut]-(2008)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 3024333064 Mar 24 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>!_-_The_Madagascar_Penguins_in_A_Christmas_Caper-(2005)-[1080p]_[BD_remux].ts
|
||
+-rwxrwxr-x 1 ftp ftp 125961 Mar 05 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].srt
|
||
+-rwxrwxr-x 1 ftp ftp 19908695408 Mar 05 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].ts
|
||
+-rwxrwxr-x 1 ftp ftp 23185439267 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Shawshank_Redemption-(1994)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 19567287274 Mar 16 2009 <20><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>_<EFBFBD><5F><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Dumb_and_Dumber_[Unrated_Edition]-(1994)-[1080p]_[BD_remux].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 14773061093 Mar 16 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_-_The_Hurricane-(1999)-[1080p]_[HDDVD_Rip].mkv
|
||
+-rwxrwxr-x 1 ftp ftp 22411268500 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_2_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Hostel_2_[Director's_Cut]-(2007)-[1080p]_[BD_remux].ts
|
||
+-rwxrwxr-x 1 ftp ftp 23712519861 Mar 11 2009 <20><><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]_-_Alien_vs_Predator_[Unrated_Edition]-(2004)-[1080p]_[BD_remux].mkv
|
||
diff --git a/net/data/ftp/dir-listing-ls-22.expected b/net/data/ftp/dir-listing-ls-22.expected
|
||
new file mode 100644
|
||
index 0000000000000..2bf724fb94b6b
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-22.expected
|
||
@@ -0,0 +1,287 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+10
|
||
+20
|
||
+17
|
||
+4
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+2010
|
||
+4
|
||
+3
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+_READ_ME.txt
|
||
+4931
|
||
+1994
|
||
+6
|
||
+8
|
||
+15
|
||
+23
|
||
+
|
||
+d
|
||
+Аватар_-_Avatar-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+5
|
||
+31
|
||
+16
|
||
+26
|
||
+
|
||
+-
|
||
+Американский_пирог_1_[Расширенная_версия]_-_American_Pie_1_[Unrated_Edition]-(1999)-[1080p]_[BD_remux].ts
|
||
+17577705968
|
||
+2009
|
||
+3
|
||
+8
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Большой_куш_-_Snatch-(2000)-[1080i]_[HDTV].ts
|
||
+15512934868
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Большой_куш_-_Snatch-(2000)-[1080p]_[BD_Remux]
|
||
+-1
|
||
+1994
|
||
+6
|
||
+3
|
||
+19
|
||
+7
|
||
+
|
||
+-
|
||
+Война_миров_-_War_of_the_Worlds-(2005)-[720p]_[HDTV].mkv
|
||
+8900589105
|
||
+2009
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Гангстер_-_American_Gangster-(2007)-[1080p]_[BD_remux].mkv
|
||
+27728321654
|
||
+2009
|
||
+3
|
||
+9
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Гангстер_[Расширенная_версия]_-_American_Gangster_[Unrated_Edition]-(2007)-[1080p]_[BD_remux].mkv
|
||
+31731782861
|
||
+2009
|
||
+3
|
||
+9
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Дорожное_приключение_-_Road_Trip-(2000)-[720p]_[HDTV_Rip].mkv
|
||
+5009104014
|
||
+2009
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Звёздные_войны-Эпизод_2-Атака_клонов_-_Star_Wars-Episode_2-Attack_of_the_Clones-(2002)-[1080i]_[HDTV].ts
|
||
+21410583980
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Звёздные_войны-Эпизод_3-Месть_Ситхов_-_Star_Wars-Episode_3-Revenge_of_the_Sith-(2005)-[1080i]_[HDTV].ts
|
||
+19858181688
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Звёздный_десант_-_Starship_Troopers-(1997)-[1080p]_[BD_remux].mkv
|
||
+29026065728
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Зеркала_[Расширенная_версия]_-_Mirrors_[Unrated_Edition]-(2008)-[1080p]_[BD_remux].mkv
|
||
+22169179449
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Ниндзя-убийца_-_Ninja_Assassin-(2009)-[1080p]_[BD]
|
||
+-1
|
||
+1994
|
||
+6
|
||
+15
|
||
+14
|
||
+56
|
||
+
|
||
+-
|
||
+Обитель_зла_3_-_Resident_Evil-Extinction-(2007)-[1080p]_[BD_remux].mkv
|
||
+19717173247
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Патология_-_Pathology-(2008)-[1080p]_[BD_remux].mkv
|
||
+18660904388
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_1_[Режиссёрская_версия]_-_Saw_I_[Director's_Cut]-(2004)-[1080p]_[HDDVD_remux].mkv
|
||
+16476154520
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_2_[Режиссёрская_версия]_-_Saw_II_[Director's_Cut]-(2005)-[1080p]_[BD_remux].mkv
|
||
+19917510515
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_3_[Режиссёрская_версия]_-_Saw_III_[Director's_Cut]-(2006)-[1080p]_[BD_remux].mkv
|
||
+18085592265
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_4_[Режиссёрская_версия]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].flac
|
||
+3473582701
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_4_[Режиссёрская_версия]_-_Saw_IV_[Director's_Cut]-(2007)-[1080p]_[BD_remux].mkv
|
||
+15263958421
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пила_5_[Режиссёрская_версия]_-_Saw_V_[Director's_Cut]-(2008)-[1080p]_[BD_remux].mkv
|
||
+19944605507
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Пингвины_из_Мадагаскара-Операция_С_Новым_Годом!_-_The_Madagascar_Penguins_in_A_Christmas_Caper-(2005)-[1080p]_[BD_remux].ts
|
||
+3024333064
|
||
+2009
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Плохой_Санта_[Расширенная_версия]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].srt
|
||
+125961
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Плохой_Санта_[Расширенная_версия]_-_Bad_Santa_[Unrated_Edition]-(2003)-[1080p]_[BD_remux].ts
|
||
+19908695408
|
||
+2009
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Побег_из_Шоушенка_-_The_Shawshank_Redemption-(1994)-[1080p]_[BD_remux].mkv
|
||
+23185439267
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Тупой_и_еще_тупее_[Расширенная_версия]_-_Dumb_and_Dumber_[Unrated_Edition]-(1994)-[1080p]_[BD_remux].mkv
|
||
+19567287274
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Ураган_-_The_Hurricane-(1999)-[1080p]_[HDDVD_Rip].mkv
|
||
+14773061093
|
||
+2009
|
||
+3
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Хостел_2_[Режиссёрская_версия]_-_Hostel_2_[Director's_Cut]-(2007)-[1080p]_[BD_remux].ts
|
||
+22411268500
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Чужой_против_Хищника_[Расширенная_версия]_-_Alien_vs_Predator_[Unrated_Edition]-(2004)-[1080p]_[BD_remux].mkv
|
||
+23712519861
|
||
+2009
|
||
+3
|
||
+11
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-23 b/net/data/ftp/dir-listing-ls-23
|
||
new file mode 100644
|
||
index 0000000000000..2b8c49483d0c6
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-23
|
||
@@ -0,0 +1,2 @@
|
||
+total 0
|
||
+ftpd: .: Permission denied
|
||
diff --git a/net/data/ftp/dir-listing-ls-23.expected b/net/data/ftp/dir-listing-ls-23.expected
|
||
new file mode 100644
|
||
index 0000000000000..e69de29bb2d1d
|
||
diff --git a/net/data/ftp/dir-listing-ls-24 b/net/data/ftp/dir-listing-ls-24
|
||
new file mode 100644
|
||
index 0000000000000..855559757c53a
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-24
|
||
@@ -0,0 +1 @@
|
||
+drwxr-xr-x 33 ftp ftp 4096 Aug 12 2008 note_empty_line_below
|
||
diff --git a/net/data/ftp/dir-listing-ls-24.expected b/net/data/ftp/dir-listing-ls-24.expected
|
||
new file mode 100644
|
||
index 0000000000000..c46afa27dd5a6
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-24.expected
|
||
@@ -0,0 +1,8 @@
|
||
+d
|
||
+note_empty_line_below
|
||
+-1
|
||
+2008
|
||
+8
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-25 b/net/data/ftp/dir-listing-ls-25
|
||
new file mode 100644
|
||
index 0000000000000..47e0487b3cf85
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-25
|
||
@@ -0,0 +1,6 @@
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 апр 18:11 .
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 июл 18:11 ..
|
||
+-rw-r--r-- 1 ftp ftp 528 01 май 2007 .message
|
||
+-rw-r--r-- 1 ftp ftp 528 01 ноя 2007 README
|
||
+-rw-r--r-- 1 ftp ftp 560 28 сен 2007 index.html
|
||
+drwxr-xr-x 33 ftp ftp 4096 12 фев 2008 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-25.expected b/net/data/ftp/dir-listing-ls-25.expected
|
||
new file mode 100644
|
||
index 0000000000000..3405f86d27901
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-25.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+4
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+7
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+-
|
||
+.message
|
||
+528
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+README
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+index.html
|
||
+560
|
||
+2007
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+2
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-26 b/net/data/ftp/dir-listing-ls-26
|
||
new file mode 100644
|
||
index 0000000000000..45d7e8a8d22af
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-26
|
||
@@ -0,0 +1,6 @@
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 <20><><EFBFBD> 18:11 .
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 <20><><EFBFBD> 18:11 ..
|
||
+-rw-r--r-- 1 ftp ftp 528 01 <20><><EFBFBD> 2007 .message
|
||
+-rw-r--r-- 1 ftp ftp 528 01 <20><><EFBFBD> 2007 README
|
||
+-rw-r--r-- 1 ftp ftp 560 28 <20><><EFBFBD> 2007 index.html
|
||
+drwxr-xr-x 33 ftp ftp 4096 12 <20><><EFBFBD> 2008 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-26.expected b/net/data/ftp/dir-listing-ls-26.expected
|
||
new file mode 100644
|
||
index 0000000000000..3405f86d27901
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-26.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+4
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+7
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+-
|
||
+.message
|
||
+528
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+README
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+index.html
|
||
+560
|
||
+2007
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+2
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-27 b/net/data/ftp/dir-listing-ls-27
|
||
new file mode 100644
|
||
index 0000000000000..3c0a3040e17a7
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-27
|
||
@@ -0,0 +1,6 @@
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 <20><><EFBFBD> 18:11 .
|
||
+drwxr-xr-x 3 ftp ftp 4096 15 <20><><EFBFBD> 18:11 ..
|
||
+-rw-r--r-- 1 ftp ftp 528 01 <20><><EFBFBD> 2007 .message
|
||
+-rw-r--r-- 1 ftp ftp 528 01 <20><><EFBFBD> 2007 README
|
||
+-rw-r--r-- 1 ftp ftp 560 28 <20><><EFBFBD> 2007 index.html
|
||
+drwxr-xr-x 33 ftp ftp 4096 12 <20><><EFBFBD> 2008 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-27.expected b/net/data/ftp/dir-listing-ls-27.expected
|
||
new file mode 100644
|
||
index 0000000000000..3405f86d27901
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-27.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+4
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+7
|
||
+15
|
||
+18
|
||
+11
|
||
+
|
||
+-
|
||
+.message
|
||
+528
|
||
+2007
|
||
+5
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+README
|
||
+528
|
||
+2007
|
||
+11
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+index.html
|
||
+560
|
||
+2007
|
||
+9
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2008
|
||
+2
|
||
+12
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-28 b/net/data/ftp/dir-listing-ls-28
|
||
new file mode 100644
|
||
index 0000000000000..85b387f57025e
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-28
|
||
@@ -0,0 +1,5 @@
|
||
+drwxr-x 2 10 4096 Dec 10 14:32 pollq
|
||
+drwxr-x 4 0 4096 Jul 28 01:44 etc
|
||
+drwxrwx 2 10 4096 Jul 28 02:41 tmp
|
||
+drwxr-x 2 10 4096 Jul 28 02:00 status
|
||
+drwxr-x 2 0 4096 Jul 27 23:21 bin
|
||
diff --git a/net/data/ftp/dir-listing-ls-28.expected b/net/data/ftp/dir-listing-ls-28.expected
|
||
new file mode 100644
|
||
index 0000000000000..d3e4d46f5d6e6
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-28.expected
|
||
@@ -0,0 +1,44 @@
|
||
+d
|
||
+pollq
|
||
+-1
|
||
+1993
|
||
+12
|
||
+10
|
||
+14
|
||
+32
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+1994
|
||
+7
|
||
+28
|
||
+1
|
||
+44
|
||
+
|
||
+d
|
||
+tmp
|
||
+-1
|
||
+1994
|
||
+7
|
||
+28
|
||
+2
|
||
+41
|
||
+
|
||
+d
|
||
+status
|
||
+-1
|
||
+1994
|
||
+7
|
||
+28
|
||
+2
|
||
+0
|
||
+
|
||
+d
|
||
+bin
|
||
+-1
|
||
+1994
|
||
+7
|
||
+27
|
||
+23
|
||
+21
|
||
diff --git a/net/data/ftp/dir-listing-ls-29 b/net/data/ftp/dir-listing-ls-29
|
||
new file mode 100644
|
||
index 0000000000000..6c82909f19544
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-29
|
||
@@ -0,0 +1,9 @@
|
||
+.:
|
||
+total 1204984
|
||
+drwxrwxrwx 2 sunle sunle 2048 Apr 6 00:36 .
|
||
+drwxrwxrwx 9406 sunle sunle 245760 Apr 10 18:00 ..
|
||
+-rw-rw---- 1 ftp sunle 110583757 Apr 5 13:43 GRBRATLIN64_4_77_ia64_4.77_S0@874222@03717.rpm.Z
|
||
+-rw-rw---- 1 ftp sunle 121591811 Apr 5 13:43 GRBRATLIN_4_77_ia32_4.77_S0@874222@20701.rpm.Z
|
||
+-rw-rw---- 1 ftp sunle 110965625 Apr 5 13:43 XENRATLIN64_4_77_ia64_4.77_S0@874222@17785.rpm.Z
|
||
+-rw-rw---- 1 ftp sunle 121986153 Apr 5 13:44 XENRATLIN_4_77_ia32_4.77_S0@874222@09635.rpm.Z
|
||
+-rw-rw---- 1 ftp sunle 151525255 Apr 5 13:44 XENRATS10_4_77_sun4_10_4.77_S0@874222@21358.dstream.Z
|
||
diff --git a/net/data/ftp/dir-listing-ls-29.expected b/net/data/ftp/dir-listing-ls-29.expected
|
||
new file mode 100644
|
||
index 0000000000000..f90e8b163e2db
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-29.expected
|
||
@@ -0,0 +1,62 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+1994
|
||
+4
|
||
+6
|
||
+0
|
||
+36
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+1994
|
||
+4
|
||
+10
|
||
+18
|
||
+0
|
||
+
|
||
+-
|
||
+GRBRATLIN64_4_77_ia64_4.77_S0@874222@03717.rpm.Z
|
||
+110583757
|
||
+1994
|
||
+4
|
||
+5
|
||
+13
|
||
+43
|
||
+
|
||
+-
|
||
+GRBRATLIN_4_77_ia32_4.77_S0@874222@20701.rpm.Z
|
||
+121591811
|
||
+1994
|
||
+4
|
||
+5
|
||
+13
|
||
+43
|
||
+
|
||
+-
|
||
+XENRATLIN64_4_77_ia64_4.77_S0@874222@17785.rpm.Z
|
||
+110965625
|
||
+1994
|
||
+4
|
||
+5
|
||
+13
|
||
+43
|
||
+
|
||
+-
|
||
+XENRATLIN_4_77_ia32_4.77_S0@874222@09635.rpm.Z
|
||
+121986153
|
||
+1994
|
||
+4
|
||
+5
|
||
+13
|
||
+44
|
||
+
|
||
+-
|
||
+XENRATS10_4_77_sun4_10_4.77_S0@874222@21358.dstream.Z
|
||
+151525255
|
||
+1994
|
||
+4
|
||
+5
|
||
+13
|
||
+44
|
||
diff --git a/net/data/ftp/dir-listing-ls-3 b/net/data/ftp/dir-listing-ls-3
|
||
new file mode 100644
|
||
index 0000000000000..8720c06572b06
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-3
|
||
@@ -0,0 +1,7 @@
|
||
+
|
||
+-r-xr-xr-x 1 anonymou 245 90 Mar 1 2003 .welcome
|
||
+drwxr-xr-x 1 system 1 512 Feb 26 2005 decus
|
||
+dr-xr-xr-x 1 system 1 3072 Dec 2 2006 gnv
|
||
+-r-xr-xr-x 1 anonymou 245 158208 Apr 10 2003 unzip.alpha_exe
|
||
+-r-xr-xr-x 1 anonymou 245 102400 Apr 10 2003 unzip.vax_exe
|
||
+dr-xr-xr-x 1 anonymou 245 1024 Mar 1 2003 vms-freeware
|
||
diff --git a/net/data/ftp/dir-listing-ls-3.expected b/net/data/ftp/dir-listing-ls-3.expected
|
||
new file mode 100644
|
||
index 0000000000000..37bbbafa1b058
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-3.expected
|
||
@@ -0,0 +1,53 @@
|
||
+-
|
||
+.welcome
|
||
+90
|
||
+2003
|
||
+3
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+decus
|
||
+-1
|
||
+2005
|
||
+2
|
||
+26
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+gnv
|
||
+-1
|
||
+2006
|
||
+12
|
||
+2
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+unzip.alpha_exe
|
||
+158208
|
||
+2003
|
||
+4
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+unzip.vax_exe
|
||
+102400
|
||
+2003
|
||
+4
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+vms-freeware
|
||
+-1
|
||
+2003
|
||
+3
|
||
+1
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-30 b/net/data/ftp/dir-listing-ls-30
|
||
new file mode 100644
|
||
index 0000000000000..6c095ccd554b8
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-30
|
||
@@ -0,0 +1,2 @@
|
||
+total 395136991232
|
||
+drwx-wx-wx 10 ftpadmin ftpusers 256 May 27 2010 downloads
|
||
diff --git a/net/data/ftp/dir-listing-ls-30.expected b/net/data/ftp/dir-listing-ls-30.expected
|
||
new file mode 100644
|
||
index 0000000000000..19dcc75bdaf28
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-30.expected
|
||
@@ -0,0 +1,8 @@
|
||
+d
|
||
+downloads
|
||
+-1
|
||
+2010
|
||
+5
|
||
+27
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-31 b/net/data/ftp/dir-listing-ls-31
|
||
new file mode 100644
|
||
index 0000000000000..95f48ac3e9bb9
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-31
|
||
@@ -0,0 +1,15 @@
|
||
+drwxrwxrwx 6 1000 1000 4096 Jul 23 2011 Alfresco
|
||
+drwxrwxrwx 2 1000 1000 4096 Mar 18 2010 DIRECTUM
|
||
+-rwxrwxrwx 1 1000 1000 222510 Mar 29 2010 Featurelist 6.30.pdf
|
||
+drwxrwxrwx 2 1000 1000 4096 Jul 23 2011 NauDoc_v4.4
|
||
+-rwxrwxrwx 1 1000 1000 1564767 Apr 06 2010 RUS_v_01_00_МЕТОД СБОРА И ДОКУМЕНТИРОВАНИЯ
|
||
+ ТРЕБОВАНИЙ К ПОРТАЛУ
|
||
+.pdf
|
||
+drwxrwxrwx 4 1000 1000 4096 Jul 22 2011 Videoconferencing
|
||
+drwxrwxrwx 3 1000 1000 4096 Apr 15 2010 Virtualization
|
||
+-rwxrwxrwx 1 1000 1000 111726333 Jan 10 2010 electr_docoborot_2010.flv
|
||
+-rwxrwxrwx 1 1000 1000 4224387 Mar 31 2010 millenniumbsa.pdf
|
||
+drwxrwxrwx 5 1000 1000 4096 Apr 16 2010 Бизнес План
|
||
+-rwxrwxrwx 1 1000 1000 138217 Apr 16 2010 Мониторинг в инфраструктуре распределенных приложений .NET.rar
|
||
+-rwxrwxrwx 1 1000 1000 4131 Feb 25 2010 О законе О персональных данных.txt
|
||
+-rwxrwxrwx 1 1000 1000 3627173 Feb 21 2010 Шеер А.В. -- Бизнес-процессы. Основные понятия..djvu
|
||
diff --git a/net/data/ftp/dir-listing-ls-31.expected b/net/data/ftp/dir-listing-ls-31.expected
|
||
new file mode 100644
|
||
index 0000000000000..99a8474b46a20
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-31.expected
|
||
@@ -0,0 +1,118 @@
|
||
+d
|
||
+Alfresco
|
||
+-1
|
||
+2011
|
||
+7
|
||
+23
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+DIRECTUM
|
||
+-1
|
||
+2010
|
||
+3
|
||
+18
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Featurelist 6.30.pdf
|
||
+222510
|
||
+2010
|
||
+3
|
||
+29
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+NauDoc_v4.4
|
||
+-1
|
||
+2011
|
||
+7
|
||
+23
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+RUS_v_01_00_МЕТОД СБОРА И ДОКУМЕНТИРОВАНИЯ
|
||
+ ТРЕБОВАНИЙ К ПОРТАЛУ
|
||
+.pdf
|
||
+1564767
|
||
+2010
|
||
+4
|
||
+6
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Videoconferencing
|
||
+-1
|
||
+2011
|
||
+7
|
||
+22
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Virtualization
|
||
+-1
|
||
+2010
|
||
+4
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+electr_docoborot_2010.flv
|
||
+111726333
|
||
+2010
|
||
+1
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+millenniumbsa.pdf
|
||
+4224387
|
||
+2010
|
||
+3
|
||
+31
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Бизнес План
|
||
+-1
|
||
+2010
|
||
+4
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Мониторинг в инфраструктуре распределенных приложений .NET.rar
|
||
+138217
|
||
+2010
|
||
+4
|
||
+16
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+О законе О персональных данных.txt
|
||
+4131
|
||
+2010
|
||
+2
|
||
+25
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Шеер А.В. -- Бизнес-процессы. Основные понятия..djvu
|
||
+3627173
|
||
+2010
|
||
+2
|
||
+21
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-32 b/net/data/ftp/dir-listing-ls-32
|
||
new file mode 100644
|
||
index 0000000000000..aa21f24570c45
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-32
|
||
@@ -0,0 +1,13 @@
|
||
+
|
||
+total 1
|
||
+drwxrwxr-x 1 500 244 660 Jan 1 00:0 bin
|
||
+drwxr-xr-x 1 0 0 0 Jan 1 00:0 dev
|
||
+drwxrwxr-x 1 500 244 272 Jan 1 00:0 etc
|
||
+drwxrwxr-x 1 500 244 16 Jan 1 00:0 mnt
|
||
+drwxrwxr-x 1 500 244 0 Jan 1 00:0 nfs
|
||
+dr-xr-xr-x 50 0 0 0 Jan 1 00:0 proc
|
||
+drwxrwxrwx 1 0 0 0 Jan 1 00:0 ram
|
||
+drwxrwxr-x 1 500 244 296 Jan 1 00:0 sbin
|
||
+lrwxrwxrwx 1 500 244 7 Jan 1 00:0 tmp -> ram/tmp
|
||
+drwxrwxr-x 1 500 244 16 Jan 1 00:0 usr
|
||
+lrwxrwxrwx 1 500 244 7 Jan 1 00:0 var -> ram/var
|
||
diff --git a/net/data/ftp/dir-listing-ls-32.expected b/net/data/ftp/dir-listing-ls-32.expected
|
||
new file mode 100644
|
||
index 0000000000000..4626652da423c
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-32.expected
|
||
@@ -0,0 +1,98 @@
|
||
+d
|
||
+bin
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dev
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+mnt
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+nfs
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+proc
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+ram
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+sbin
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+tmp
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+usr
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
+
|
||
+l
|
||
+var
|
||
+-1
|
||
+1994
|
||
+1
|
||
+1
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-33 b/net/data/ftp/dir-listing-ls-33
|
||
new file mode 100644
|
||
index 0000000000000..2a2e6a28f2dab
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-33
|
||
@@ -0,0 +1,4 @@
|
||
+total -9560322322989056
|
||
+-rw-r--r-- 1 stadmin stgroup 4310450 Nov 17 13:12 01643.001.862.TestPermission-DUP0001.zip
|
||
+-rw-r--r-- 1 stadmin stgroup 2496430080 Nov 13 14:46 I_Base_01_RSE_R720.iso
|
||
+-rw-r--r-- 1 stadmin stgroup 478576612 Nov 13 08:11 I_Base_01_RSE_R720.zip
|
||
diff --git a/net/data/ftp/dir-listing-ls-33.expected b/net/data/ftp/dir-listing-ls-33.expected
|
||
new file mode 100644
|
||
index 0000000000000..42ae5f932ec6d
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-33.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+01643.001.862.TestPermission-DUP0001.zip
|
||
+4310450
|
||
+1993
|
||
+11
|
||
+17
|
||
+13
|
||
+12
|
||
+
|
||
+-
|
||
+I_Base_01_RSE_R720.iso
|
||
+2496430080
|
||
+1994
|
||
+11
|
||
+13
|
||
+14
|
||
+46
|
||
+
|
||
+-
|
||
+I_Base_01_RSE_R720.zip
|
||
+478576612
|
||
+1994
|
||
+11
|
||
+13
|
||
+8
|
||
+11
|
||
diff --git a/net/data/ftp/dir-listing-ls-34 b/net/data/ftp/dir-listing-ls-34
|
||
new file mode 100644
|
||
index 0000000000000..6286ed186f4d5
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-34
|
||
@@ -0,0 +1,2 @@
|
||
+-rw-rw-r-- 1 ftpuser ftpusers 1761280 Dec 20 2002 controle_embarqu<71>_avec_labview_real_time_et_compactfieldpoint.ppt
|
||
+-rw-rw-r-- 1 ftpuser ftpusers 329216 Dec 20 2002 optimisez_l'acquisition_de_donn<6E>es_sous_labview.ppt
|
||
diff --git a/net/data/ftp/dir-listing-ls-34.expected b/net/data/ftp/dir-listing-ls-34.expected
|
||
new file mode 100644
|
||
index 0000000000000..d197cb362c805
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-34.expected
|
||
@@ -0,0 +1,17 @@
|
||
+-
|
||
+controle_embarqu<71>_avec_labview_real_time_et_compactfieldpoint.ppt
|
||
+1761280
|
||
+2002
|
||
+12
|
||
+20
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+optimisez_l'acquisition_de_donnFs_sous_labview.ppt
|
||
+329216
|
||
+2002
|
||
+12
|
||
+20
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-4 b/net/data/ftp/dir-listing-ls-4
|
||
new file mode 100644
|
||
index 0000000000000..078542f0976dc
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-4
|
||
@@ -0,0 +1,10 @@
|
||
+
|
||
+-rwx---r-x 1 archives 0 472 Jun 28 2004 .welcome
|
||
+drwxr-xr-x 1 archives 0 512 Mar 5 1998 contributed-software
|
||
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 customer_support
|
||
+drwxr-xr-x 1 archives 0 512 Dec 30 1998 docs
|
||
+drwxr-xr-x 1 archives 0 512 May 8 1998 faq
|
||
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 mail_archives
|
||
+drwxr-xr-x 1 archives 0 1024 Nov 11 1997 patches
|
||
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 tech-tips
|
||
+drwxr-xr-x 1 archives 0 512 Nov 11 1997 white_papers
|
||
diff --git a/net/data/ftp/dir-listing-ls-4.expected b/net/data/ftp/dir-listing-ls-4.expected
|
||
new file mode 100644
|
||
index 0000000000000..53397030473f2
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-4.expected
|
||
@@ -0,0 +1,80 @@
|
||
+-
|
||
+.welcome
|
||
+472
|
||
+2004
|
||
+6
|
||
+28
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+contributed-software
|
||
+-1
|
||
+1998
|
||
+3
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+customer_support
|
||
+-1
|
||
+1997
|
||
+11
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+docs
|
||
+-1
|
||
+1998
|
||
+12
|
||
+30
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+faq
|
||
+-1
|
||
+1998
|
||
+5
|
||
+8
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+mail_archives
|
||
+-1
|
||
+1997
|
||
+11
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+patches
|
||
+-1
|
||
+1997
|
||
+11
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+tech-tips
|
||
+-1
|
||
+1997
|
||
+11
|
||
+11
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+white_papers
|
||
+-1
|
||
+1997
|
||
+11
|
||
+11
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-5 b/net/data/ftp/dir-listing-ls-5
|
||
new file mode 100644
|
||
index 0000000000000..87c42665ffd51
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-5
|
||
@@ -0,0 +1 @@
|
||
+drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub
|
||
diff --git a/net/data/ftp/dir-listing-ls-5.expected b/net/data/ftp/dir-listing-ls-5.expected
|
||
new file mode 100644
|
||
index 0000000000000..3acbcf39b2137
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-5.expected
|
||
@@ -0,0 +1,8 @@
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2007
|
||
+2
|
||
+20
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-6 b/net/data/ftp/dir-listing-ls-6
|
||
new file mode 100644
|
||
index 0000000000000..5cc802c8822a4
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-6
|
||
@@ -0,0 +1,7 @@
|
||
+total 14
|
||
+dr-xr-xr-x 7 0 1 512 Jun 19 2006 .
|
||
+dr-xr-xr-x 7 0 1 512 Jun 19 2006 ..
|
||
+dr-xr-xr-x 2 0 1 512 Mar 24 2003 bin
|
||
+dr-xr-xr-x 2 0 1 512 Mar 24 2003 etc
|
||
+dr-xr-xr-x 12 0 0 512 Apr 7 2009 pub
|
||
+dr-xr-xr-x 3 0 1 512 Mar 24 2003 usr
|
||
diff --git a/net/data/ftp/dir-listing-ls-6.expected b/net/data/ftp/dir-listing-ls-6.expected
|
||
new file mode 100644
|
||
index 0000000000000..80b61dd391fe3
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-6.expected
|
||
@@ -0,0 +1,53 @@
|
||
+d
|
||
+.
|
||
+-1
|
||
+2006
|
||
+6
|
||
+19
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+..
|
||
+-1
|
||
+2006
|
||
+6
|
||
+19
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+bin
|
||
+-1
|
||
+2003
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+etc
|
||
+-1
|
||
+2003
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2009
|
||
+4
|
||
+7
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+usr
|
||
+-1
|
||
+2003
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-7 b/net/data/ftp/dir-listing-ls-7
|
||
new file mode 100644
|
||
index 0000000000000..93dd8045ca341
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-7
|
||
@@ -0,0 +1,6 @@
|
||
+-rw-r--r-- 1 0 100 3108 Mar 07 2001 00readme.html
|
||
+drwxr-xr-x 3 1164 100 4096 Oct 19 13:45 OCU
|
||
+lrwxrwxrwx 1 203 1 10 Jun 15 2006 a<>o2000 -> ./urte2000
|
||
+drwxr-xr-x 2 0 0 4096 Mar 07 2001 bin
|
||
+drwxr-xr-x 2 0 100 4096 Mar 07 2001 dev
|
||
+drwxr-xr-x 3 0 100 4096 Apr 20 2005 doc
|
||
diff --git a/net/data/ftp/dir-listing-ls-7.expected b/net/data/ftp/dir-listing-ls-7.expected
|
||
new file mode 100644
|
||
index 0000000000000..90c13511e6250
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-7.expected
|
||
@@ -0,0 +1,53 @@
|
||
+-
|
||
+00readme.html
|
||
+3108
|
||
+2001
|
||
+3
|
||
+7
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+OCU
|
||
+-1
|
||
+1994
|
||
+10
|
||
+19
|
||
+13
|
||
+45
|
||
+
|
||
+l
|
||
+año2000
|
||
+-1
|
||
+2006
|
||
+6
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+bin
|
||
+-1
|
||
+2001
|
||
+3
|
||
+7
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+dev
|
||
+-1
|
||
+2001
|
||
+3
|
||
+7
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+doc
|
||
+-1
|
||
+2005
|
||
+4
|
||
+20
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-ls-8 b/net/data/ftp/dir-listing-ls-8
|
||
new file mode 100644
|
||
index 0000000000000..92811047f08ec
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-8
|
||
@@ -0,0 +1 @@
|
||
+total 0
|
||
diff --git a/net/data/ftp/dir-listing-ls-8.expected b/net/data/ftp/dir-listing-ls-8.expected
|
||
new file mode 100644
|
||
index 0000000000000..e69de29bb2d1d
|
||
diff --git a/net/data/ftp/dir-listing-ls-9 b/net/data/ftp/dir-listing-ls-9
|
||
new file mode 100644
|
||
index 0000000000000..89df17cb8ef6f
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-9
|
||
@@ -0,0 +1,4 @@
|
||
+total 510528
|
||
+-rw-r--r-- 1 nobody nogroup 174680068 Jun 4 23:20 Akademia Teatralna spot.mpg
|
||
+-rw-r--r-- 1 nobody nogroup 3447432 May 18 2009 Foo - Instrukcja_Obsługi.pdf
|
||
+-rw-r--r-- 1 nobody nogroup 23197684 Jun 9 13:36 Zdjecia.zip
|
||
diff --git a/net/data/ftp/dir-listing-ls-9.expected b/net/data/ftp/dir-listing-ls-9.expected
|
||
new file mode 100644
|
||
index 0000000000000..78ecd3f21c34f
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-ls-9.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+Akademia Teatralna spot.mpg
|
||
+174680068
|
||
+1994
|
||
+6
|
||
+4
|
||
+23
|
||
+20
|
||
+
|
||
+-
|
||
+Foo - Instrukcja_Obsługi.pdf
|
||
+3447432
|
||
+2009
|
||
+5
|
||
+18
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+Zdjecia.zip
|
||
+23197684
|
||
+1994
|
||
+6
|
||
+9
|
||
+13
|
||
+36
|
||
diff --git a/net/data/ftp/dir-listing-netware-1 b/net/data/ftp/dir-listing-netware-1
|
||
new file mode 100644
|
||
index 0000000000000..02a5167699291
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-1
|
||
@@ -0,0 +1,3 @@
|
||
+total 0
|
||
+d [RWCEAFMS] ftpadmin 512 Jun 25 2007 pandora
|
||
+d [RWCEAFMS] ftpadmin 512 Jan 29 2004 pub
|
||
diff --git a/net/data/ftp/dir-listing-netware-1.expected b/net/data/ftp/dir-listing-netware-1.expected
|
||
new file mode 100644
|
||
index 0000000000000..be3f9b8b20bfc
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-1.expected
|
||
@@ -0,0 +1,17 @@
|
||
+d
|
||
+pandora
|
||
+-1
|
||
+2007
|
||
+6
|
||
+25
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2004
|
||
+1
|
||
+29
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-netware-2 b/net/data/ftp/dir-listing-netware-2
|
||
new file mode 100644
|
||
index 0000000000000..13af27b729257
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-2
|
||
@@ -0,0 +1,4 @@
|
||
+total 0
|
||
+- [RWCEAFMS] AK101850 1328 Dec 27 2007 rootcert.der
|
||
+d [RWCEAFMS] Admin 512 Nov 13 07:51 Driver
|
||
+d [RWCEAFMS] AK101850 512 Nov 16 15:40 temp
|
||
diff --git a/net/data/ftp/dir-listing-netware-2.expected b/net/data/ftp/dir-listing-netware-2.expected
|
||
new file mode 100644
|
||
index 0000000000000..3c78ff0094c84
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-2.expected
|
||
@@ -0,0 +1,26 @@
|
||
+-
|
||
+rootcert.der
|
||
+1328
|
||
+2007
|
||
+12
|
||
+27
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Driver
|
||
+-1
|
||
+1994
|
||
+11
|
||
+13
|
||
+7
|
||
+51
|
||
+
|
||
+d
|
||
+temp
|
||
+-1
|
||
+1993
|
||
+11
|
||
+16
|
||
+15
|
||
+40
|
||
diff --git a/net/data/ftp/dir-listing-netware-3 b/net/data/ftp/dir-listing-netware-3
|
||
new file mode 100644
|
||
index 0000000000000..1d24bccd574b3
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-3
|
||
@@ -0,0 +1,22 @@
|
||
+total 0
|
||
+- [RWCEAFMS] bodi 1588 Oct 28 22:21 leltar.as
|
||
+- [RWCEAFMS] bodi 19440 Oct 28 22:21 leltarVizellatas.as
|
||
+- [RWCEAFMS] bodi 38795 Oct 28 22:21 leltarVizelvezetes.as
|
||
+- [RWCEAFMS] bodi 227 Jan 06 2010 Olvass_el.txt
|
||
+- [RWCEAFMS] dora 19300888 Jan 15 2011 vegleges.pdf
|
||
+d [RWCEAFMS] Fulop 512 Nov 10 2011 DHI
|
||
+d [RWCEAFMS] bodi 512 Aug 02 11:48 English
|
||
+d [RWCEAFMS] clement 512 Jun 24 2011 Floodrisk
|
||
+d [RWCEAFMS] bodi 512 Mar 10 14:53 HC_MIR_FD
|
||
+d [RWCEAFMS] bodi 512 Oct 08 15:59 HCWP614-11
|
||
+d [RWCEAFMS] knolmar 512 Mar 26 15:10 Mike
|
||
+d [RWCEAFMS] daniel 512 May 17 2010 NVP anyagok
|
||
+d [RWCEAFMS] bodi 512 Sep 15 2010 Oktatas
|
||
+d [RWCEAFMS] buzas 512 Oct 09 12:47 processing_modflow
|
||
+d [RWCEAFMS] peterbud 512 Mar 24 2010 Prospektushoz
|
||
+d [RWCEAFMS] bodi 512 Sep 05 2011 sewcad
|
||
+d [RWCEAFMS] bodi 512 May 09 08:50 szakmernok
|
||
+d [RWCEAFMS] daniel 512 Aug 03 18:53 tomi
|
||
+d [RWCEAFMS] bodi 512 Sep 20 21:10 Virtualis-GEP
|
||
+d [RWCEAFMS] clement 512 Sep 26 12:31 Vizrajzi evkonyvek
|
||
+d [RWCEAFMS] darabos 512 Jul 09 2011 VKKI-Tanulmanyok
|
||
diff --git a/net/data/ftp/dir-listing-netware-3.expected b/net/data/ftp/dir-listing-netware-3.expected
|
||
new file mode 100644
|
||
index 0000000000000..fe809e38f6fb8
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-netware-3.expected
|
||
@@ -0,0 +1,188 @@
|
||
+-
|
||
+leltar.as
|
||
+1588
|
||
+1994
|
||
+10
|
||
+28
|
||
+22
|
||
+21
|
||
+
|
||
+-
|
||
+leltarVizellatas.as
|
||
+19440
|
||
+1994
|
||
+10
|
||
+28
|
||
+22
|
||
+21
|
||
+
|
||
+-
|
||
+leltarVizelvezetes.as
|
||
+38795
|
||
+1994
|
||
+10
|
||
+28
|
||
+22
|
||
+21
|
||
+
|
||
+-
|
||
+Olvass_el.txt
|
||
+227
|
||
+2010
|
||
+1
|
||
+6
|
||
+0
|
||
+0
|
||
+
|
||
+-
|
||
+vegleges.pdf
|
||
+19300888
|
||
+2011
|
||
+1
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+DHI
|
||
+-1
|
||
+2011
|
||
+11
|
||
+10
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+English
|
||
+-1
|
||
+1994
|
||
+8
|
||
+2
|
||
+11
|
||
+48
|
||
+
|
||
+d
|
||
+Floodrisk
|
||
+-1
|
||
+2011
|
||
+6
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+HC_MIR_FD
|
||
+-1
|
||
+1994
|
||
+3
|
||
+10
|
||
+14
|
||
+53
|
||
+
|
||
+d
|
||
+HCWP614-11
|
||
+-1
|
||
+1994
|
||
+10
|
||
+8
|
||
+15
|
||
+59
|
||
+
|
||
+d
|
||
+Mike
|
||
+-1
|
||
+1994
|
||
+3
|
||
+26
|
||
+15
|
||
+10
|
||
+
|
||
+d
|
||
+NVP anyagok
|
||
+-1
|
||
+2010
|
||
+5
|
||
+17
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+Oktatas
|
||
+-1
|
||
+2010
|
||
+9
|
||
+15
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+processing_modflow
|
||
+-1
|
||
+1994
|
||
+10
|
||
+9
|
||
+12
|
||
+47
|
||
+
|
||
+d
|
||
+Prospektushoz
|
||
+-1
|
||
+2010
|
||
+3
|
||
+24
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+sewcad
|
||
+-1
|
||
+2011
|
||
+9
|
||
+5
|
||
+0
|
||
+0
|
||
+
|
||
+d
|
||
+szakmernok
|
||
+-1
|
||
+1994
|
||
+5
|
||
+9
|
||
+8
|
||
+50
|
||
+
|
||
+d
|
||
+tomi
|
||
+-1
|
||
+1994
|
||
+8
|
||
+3
|
||
+18
|
||
+53
|
||
+
|
||
+d
|
||
+Virtualis-GEP
|
||
+-1
|
||
+1994
|
||
+9
|
||
+20
|
||
+21
|
||
+10
|
||
+
|
||
+d
|
||
+Vizrajzi evkonyvek
|
||
+-1
|
||
+1994
|
||
+9
|
||
+26
|
||
+12
|
||
+31
|
||
+
|
||
+d
|
||
+VKKI-Tanulmanyok
|
||
+-1
|
||
+2011
|
||
+7
|
||
+9
|
||
+0
|
||
+0
|
||
diff --git a/net/data/ftp/dir-listing-os2-1 b/net/data/ftp/dir-listing-os2-1
|
||
new file mode 100644
|
||
index 0000000000000..e286ce1681b17
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-os2-1
|
||
@@ -0,0 +1,9 @@
|
||
+ 0 DIR 08-08-11 01:47 Archive
|
||
+ 0 DIR 01-13-11 10:18 BootCD
|
||
+ 0 DIR 10-29-10 23:20 BootUSB512
|
||
+ 65201 A 08-04-11 10:24 csort.exe
|
||
+ 0 DIR 08-04-11 16:07 misc
|
||
+ 0 DIR 06-24-11 01:18 moveton
|
||
+ 1031106 A 08-08-11 01:47 os2krnlSVN3370_unoff.zip
|
||
+ 603 A 01-08-11 14:18 SSE_TEST.EXE
|
||
+ 91018 A 04-07-11 18:26 TETRIS.ZIP
|
||
diff --git a/net/data/ftp/dir-listing-os2-1.expected b/net/data/ftp/dir-listing-os2-1.expected
|
||
new file mode 100644
|
||
index 0000000000000..72c37a897c64b
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-os2-1.expected
|
||
@@ -0,0 +1,80 @@
|
||
+d
|
||
+Archive
|
||
+-1
|
||
+2011
|
||
+8
|
||
+8
|
||
+1
|
||
+47
|
||
+
|
||
+d
|
||
+BootCD
|
||
+-1
|
||
+2011
|
||
+1
|
||
+13
|
||
+10
|
||
+18
|
||
+
|
||
+d
|
||
+BootUSB512
|
||
+-1
|
||
+2010
|
||
+10
|
||
+29
|
||
+23
|
||
+20
|
||
+
|
||
+-
|
||
+csort.exe
|
||
+65201
|
||
+2011
|
||
+8
|
||
+4
|
||
+10
|
||
+24
|
||
+
|
||
+d
|
||
+misc
|
||
+-1
|
||
+2011
|
||
+8
|
||
+4
|
||
+16
|
||
+7
|
||
+
|
||
+d
|
||
+moveton
|
||
+-1
|
||
+2011
|
||
+6
|
||
+24
|
||
+1
|
||
+18
|
||
+
|
||
+-
|
||
+os2krnlSVN3370_unoff.zip
|
||
+1031106
|
||
+2011
|
||
+8
|
||
+8
|
||
+1
|
||
+47
|
||
+
|
||
+-
|
||
+SSE_TEST.EXE
|
||
+603
|
||
+2011
|
||
+1
|
||
+8
|
||
+14
|
||
+18
|
||
+
|
||
+-
|
||
+TETRIS.ZIP
|
||
+91018
|
||
+2011
|
||
+4
|
||
+7
|
||
+18
|
||
+26
|
||
diff --git a/net/data/ftp/dir-listing-vms-1 b/net/data/ftp/dir-listing-vms-1
|
||
new file mode 100644
|
||
index 0000000000000..8df617efba86d
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-1
|
||
@@ -0,0 +1,19 @@
|
||
+
|
||
+
|
||
+
|
||
+Directory ANONYMOUS_ROOT:[000000]
|
||
+
|
||
+.WELCOME;1 2 13-FEB-2002 23:32:40.47
|
||
+DECUS.DIR;1 1 9-MAY-2001 22:18:51.69
|
||
+INFORMATION.DIR;1 1 9-MAY-2001 22:23:42.78
|
||
+MADGOAT.DIR;1 2 9-MAY-2001 22:23:44.85
|
||
+MAIL_ARCHIVES.DIR;1
|
||
+ 1 13-DEC-2005 08:45:27.56
|
||
+MOZILLA.DIR;1 1 21-JUN-2001 14:57:51.38
|
||
+README.TXT;4 2 18-APR-2000 10:40:39.90
|
||
+SSH.DIR;1 1 22-JUN-2002 15:11:12.71
|
||
+SUPPORT.DIR;1 3 9-MAY-2001 22:29:45.02
|
||
+TCPWARE.DIR;1 1 9-MAY-2001 23:34:10.92
|
||
+VMS-FREEWARE.DIR;1 2 9-MAY-2001 23:58:31.39
|
||
+
|
||
+Total of 11 files, 17 blocks.
|
||
diff --git a/net/data/ftp/dir-listing-vms-1.expected b/net/data/ftp/dir-listing-vms-1.expected
|
||
new file mode 100644
|
||
index 0000000000000..5bc2ab5b21a2b
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-1.expected
|
||
@@ -0,0 +1,98 @@
|
||
+-
|
||
+.welcome
|
||
+1024
|
||
+2002
|
||
+2
|
||
+13
|
||
+23
|
||
+32
|
||
+
|
||
+d
|
||
+decus
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+22
|
||
+18
|
||
+
|
||
+d
|
||
+information
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+22
|
||
+23
|
||
+
|
||
+d
|
||
+madgoat
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+22
|
||
+23
|
||
+
|
||
+d
|
||
+mail_archives
|
||
+-1
|
||
+2005
|
||
+12
|
||
+13
|
||
+8
|
||
+45
|
||
+
|
||
+d
|
||
+mozilla
|
||
+-1
|
||
+2001
|
||
+6
|
||
+21
|
||
+14
|
||
+57
|
||
+
|
||
+-
|
||
+readme.txt
|
||
+1024
|
||
+2000
|
||
+4
|
||
+18
|
||
+10
|
||
+40
|
||
+
|
||
+d
|
||
+ssh
|
||
+-1
|
||
+2002
|
||
+6
|
||
+22
|
||
+15
|
||
+11
|
||
+
|
||
+d
|
||
+support
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+22
|
||
+29
|
||
+
|
||
+d
|
||
+tcpware
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+23
|
||
+34
|
||
+
|
||
+d
|
||
+vms-freeware
|
||
+-1
|
||
+2001
|
||
+5
|
||
+9
|
||
+23
|
||
+58
|
||
diff --git a/net/data/ftp/dir-listing-vms-2 b/net/data/ftp/dir-listing-vms-2
|
||
new file mode 100644
|
||
index 0000000000000..7fc20a93f9c89
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-2
|
||
@@ -0,0 +1,35 @@
|
||
+
|
||
+Directory SYS$SYSDEVICE:[ANONYMOUS]
|
||
+
|
||
+ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)
|
||
+BOINC.DIR;1 1/16 29-DEC-2005 21:33:21 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+BZIP2.DIR;1 1/16 27-SEP-2005 19:45:39 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+CDRTOOLS.DIR;1 3/16 10-MAR-2005 17:31:44 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+DIFFUTILS.DIR;1 1/16 23-JUN-2007 23:04:21 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+DTSS_NTP.DIR;1 2/16 25-SEP-2000 21:03:28 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+FIXREC.DIR;1 1/16 20-DEC-2003 10:57:22 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+GNUPG.DIR;1 1/16 9-AUG-2006 02:11:51 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+GZIP.DIR;1 1/16 5-JUL-2006 21:59:45 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+INFO-ZIP.DIR;1 15/16 20-SEP-2004 21:27:27 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+INPUT.DIR;1 1/16 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] (RWE,RWE,RWE,RWE)
|
||
+KERMIT.DIR;1 1/16 25-FEB-2006 12:22:34 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+LOGIN.COM;2 1/16 28-SEP-2006 09:20:32 [SYSTEM] (RWED,RWED,RE,RE)
|
||
+MISC.DIR;1 6/16 12-DEC-1999 17:31:56 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+MMK.DIR;1 1/16 30-SEP-2009 08:06:26 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+MOZ_TEST.DIR;1 1/16 8-APR-2008 17:12:53 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+MPACK.DIR;1 1/16 21-AUG-2009 10:28:57 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+MTOOLS.DIR;1 1/16 14-MAR-2006 15:05:01 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+OPENSSL.DIR;1 1/16 12-JAN-2009 08:42:56 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+PGP.DIR;1 1/16 19-SEP-1999 16:39:04 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+PICS.DIR;1 No privilege for attempted operation
|
||
+QREADCD.DIR;1 1/16 29-SEP-2004 20:32:38 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+RZSPINUP.DIR;1 1/16 24-JUL-2004 21:34:12 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+TEST.DIR;1 1/16 5-NOV-2008 21:59:10 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+VIM.DIR;1 1/16 30-APR-2005 16:32:56 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+VMSTAR.DIR;1 1/16 7-JUN-2007 09:36:04 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+WELCOME.TXT;3 1/16 12-MAR-2005 08:45:28 [SYSTEM] (RWED,RWED,RE,RE)
|
||
+WGET.DIR;1 3/16 17-AUG-1999 20:41:54 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+WGET_TEST.DIR;1 1/16 13-JUN-2006 21:29:27 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+WPUT.DIR;1 1/16 9-DEC-2004 20:16:46 [SYSTEM] (RWE,RWE,RE,RE)
|
||
+
|
||
+Total of 30 files, 53/464 blocks
|
||
diff --git a/net/data/ftp/dir-listing-vms-2.expected b/net/data/ftp/dir-listing-vms-2.expected
|
||
new file mode 100644
|
||
index 0000000000000..6b3ca660232d1
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-2.expected
|
||
@@ -0,0 +1,260 @@
|
||
+-
|
||
+announce.txt
|
||
+512
|
||
+2005
|
||
+3
|
||
+12
|
||
+8
|
||
+44
|
||
+
|
||
+d
|
||
+boinc
|
||
+-1
|
||
+2005
|
||
+12
|
||
+29
|
||
+21
|
||
+33
|
||
+
|
||
+d
|
||
+bzip2
|
||
+-1
|
||
+2005
|
||
+9
|
||
+27
|
||
+19
|
||
+45
|
||
+
|
||
+d
|
||
+cdrtools
|
||
+-1
|
||
+2005
|
||
+3
|
||
+10
|
||
+17
|
||
+31
|
||
+
|
||
+d
|
||
+diffutils
|
||
+-1
|
||
+2007
|
||
+6
|
||
+23
|
||
+23
|
||
+4
|
||
+
|
||
+d
|
||
+dtss_ntp
|
||
+-1
|
||
+2000
|
||
+9
|
||
+25
|
||
+21
|
||
+3
|
||
+
|
||
+d
|
||
+fixrec
|
||
+-1
|
||
+2003
|
||
+12
|
||
+20
|
||
+10
|
||
+57
|
||
+
|
||
+d
|
||
+gnupg
|
||
+-1
|
||
+2006
|
||
+8
|
||
+9
|
||
+2
|
||
+11
|
||
+
|
||
+d
|
||
+gzip
|
||
+-1
|
||
+2006
|
||
+7
|
||
+5
|
||
+21
|
||
+59
|
||
+
|
||
+d
|
||
+info-zip
|
||
+-1
|
||
+2004
|
||
+9
|
||
+20
|
||
+21
|
||
+27
|
||
+
|
||
+d
|
||
+input
|
||
+-1
|
||
+1999
|
||
+3
|
||
+4
|
||
+22
|
||
+14
|
||
+
|
||
+d
|
||
+kermit
|
||
+-1
|
||
+2006
|
||
+2
|
||
+25
|
||
+12
|
||
+22
|
||
+
|
||
+-
|
||
+login.com
|
||
+512
|
||
+2006
|
||
+9
|
||
+28
|
||
+9
|
||
+20
|
||
+
|
||
+d
|
||
+misc
|
||
+-1
|
||
+1999
|
||
+12
|
||
+12
|
||
+17
|
||
+31
|
||
+
|
||
+d
|
||
+mmk
|
||
+-1
|
||
+2009
|
||
+9
|
||
+30
|
||
+8
|
||
+6
|
||
+
|
||
+d
|
||
+moz_test
|
||
+-1
|
||
+2008
|
||
+4
|
||
+8
|
||
+17
|
||
+12
|
||
+
|
||
+d
|
||
+mpack
|
||
+-1
|
||
+2009
|
||
+8
|
||
+21
|
||
+10
|
||
+28
|
||
+
|
||
+d
|
||
+mtools
|
||
+-1
|
||
+2006
|
||
+3
|
||
+14
|
||
+15
|
||
+5
|
||
+
|
||
+d
|
||
+openssl
|
||
+-1
|
||
+2009
|
||
+1
|
||
+12
|
||
+8
|
||
+42
|
||
+
|
||
+d
|
||
+pgp
|
||
+-1
|
||
+1999
|
||
+9
|
||
+19
|
||
+16
|
||
+39
|
||
+
|
||
+d
|
||
+qreadcd
|
||
+-1
|
||
+2004
|
||
+9
|
||
+29
|
||
+20
|
||
+32
|
||
+
|
||
+d
|
||
+rzspinup
|
||
+-1
|
||
+2004
|
||
+7
|
||
+24
|
||
+21
|
||
+34
|
||
+
|
||
+d
|
||
+test
|
||
+-1
|
||
+2008
|
||
+11
|
||
+5
|
||
+21
|
||
+59
|
||
+
|
||
+d
|
||
+vim
|
||
+-1
|
||
+2005
|
||
+4
|
||
+30
|
||
+16
|
||
+32
|
||
+
|
||
+d
|
||
+vmstar
|
||
+-1
|
||
+2007
|
||
+6
|
||
+7
|
||
+9
|
||
+36
|
||
+
|
||
+-
|
||
+welcome.txt
|
||
+512
|
||
+2005
|
||
+3
|
||
+12
|
||
+8
|
||
+45
|
||
+
|
||
+d
|
||
+wget
|
||
+-1
|
||
+1999
|
||
+8
|
||
+17
|
||
+20
|
||
+41
|
||
+
|
||
+d
|
||
+wget_test
|
||
+-1
|
||
+2006
|
||
+6
|
||
+13
|
||
+21
|
||
+29
|
||
+
|
||
+d
|
||
+wput
|
||
+-1
|
||
+2004
|
||
+12
|
||
+9
|
||
+20
|
||
+16
|
||
diff --git a/net/data/ftp/dir-listing-vms-3 b/net/data/ftp/dir-listing-vms-3
|
||
new file mode 100644
|
||
index 0000000000000..14d31f1031743
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-3
|
||
@@ -0,0 +1,3 @@
|
||
+
|
||
+
|
||
+Total of 0 blocks in 0 files in 0 directories.
|
||
diff --git a/net/data/ftp/dir-listing-vms-3.expected b/net/data/ftp/dir-listing-vms-3.expected
|
||
new file mode 100644
|
||
index 0000000000000..e69de29bb2d1d
|
||
diff --git a/net/data/ftp/dir-listing-vms-4 b/net/data/ftp/dir-listing-vms-4
|
||
new file mode 100644
|
||
index 0000000000000..0954441c229b9
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-4
|
||
@@ -0,0 +1,15 @@
|
||
+
|
||
+EISNER$DRA3:[DECUSERVE_USER.JOHNDOE]
|
||
+
|
||
+$MAIN.TPU$JOURNAL;1 1 4-NOV-2009 05:59 [JOHNDOE] (RWED,RWED,,)
|
||
+.WELCOME;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
|
||
+EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)
|
||
+FILE.;1 1 4-NOV-2009 08:59 [JOHNDOE] (RWED,RWED,,)
|
||
+FTP_SERVER.LOG;12 0 4-NOV-2009 09:12 [JOHNDOE] (RWED,RWED,,)
|
||
+LOGIN.COM;1 2 4-NOV-2009 05:58 [JOHNDOE] (RWED,RWED,,)
|
||
+NOTES$NOTEBOOK.NOTE;1
|
||
+ 36 4-NOV-2009 05:55 [DECUSERVE] (RWE,RWE,,)
|
||
+TEST.DIR;1 1 4-NOV-2009 08:15 [JOHNDOE] (RWE,RWE,,)
|
||
+
|
||
+
|
||
+Total of 43 blocks in 8 files.
|
||
diff --git a/net/data/ftp/dir-listing-vms-4.expected b/net/data/ftp/dir-listing-vms-4.expected
|
||
new file mode 100644
|
||
index 0000000000000..3c89235e27a0f
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-4.expected
|
||
@@ -0,0 +1,71 @@
|
||
+-
|
||
+$main.tpu$journal
|
||
+512
|
||
+2009
|
||
+11
|
||
+4
|
||
+5
|
||
+59
|
||
+
|
||
+-
|
||
+.welcome
|
||
+512
|
||
+2009
|
||
+11
|
||
+4
|
||
+6
|
||
+2
|
||
+
|
||
+-
|
||
+example.txt
|
||
+512
|
||
+2009
|
||
+11
|
||
+4
|
||
+6
|
||
+2
|
||
+
|
||
+-
|
||
+file.
|
||
+512
|
||
+2009
|
||
+11
|
||
+4
|
||
+8
|
||
+59
|
||
+
|
||
+-
|
||
+ftp_server.log
|
||
+0
|
||
+2009
|
||
+11
|
||
+4
|
||
+9
|
||
+12
|
||
+
|
||
+-
|
||
+login.com
|
||
+1024
|
||
+2009
|
||
+11
|
||
+4
|
||
+5
|
||
+58
|
||
+
|
||
+-
|
||
+notes$notebook.note
|
||
+18432
|
||
+2009
|
||
+11
|
||
+4
|
||
+5
|
||
+55
|
||
+
|
||
+d
|
||
+test
|
||
+-1
|
||
+2009
|
||
+11
|
||
+4
|
||
+8
|
||
+15
|
||
diff --git a/net/data/ftp/dir-listing-vms-5 b/net/data/ftp/dir-listing-vms-5
|
||
new file mode 100644
|
||
index 0000000000000..d769993de8ed2
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-5
|
||
@@ -0,0 +1,12 @@
|
||
+
|
||
+SYS:[ANON_LOGS]
|
||
+
|
||
+FTP_SERVER.LOG;1682 0 27-NOV-2009 14:35 [NET,ANONYMOUS] (RWED,RWED,,)
|
||
+FTP_SERVER_LOG.KEEP;11400
|
||
+ 2 19-DEC-1994 15:40 [NET,ANONYMOUS] (RWED,RWED,,)
|
||
+FTP_SERVER_LOG.SEARCH;1
|
||
+ 274 7-DEC-1993 15:54 [NET,ANONYMOUS] (RWED,RWED,,)
|
||
+TESTLOG.DAT;1 0 27-APR-1995 13:18 [NET,ANONYMOUS] (RWED,RWED,,)
|
||
+
|
||
+
|
||
+Total of 276 blocks in 4 files.
|
||
diff --git a/net/data/ftp/dir-listing-vms-5.expected b/net/data/ftp/dir-listing-vms-5.expected
|
||
new file mode 100644
|
||
index 0000000000000..7f12b60984116
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-5.expected
|
||
@@ -0,0 +1,35 @@
|
||
+-
|
||
+ftp_server.log
|
||
+0
|
||
+2009
|
||
+11
|
||
+27
|
||
+14
|
||
+35
|
||
+
|
||
+-
|
||
+ftp_server_log.keep
|
||
+1024
|
||
+1994
|
||
+12
|
||
+19
|
||
+15
|
||
+40
|
||
+
|
||
+-
|
||
+ftp_server_log.search
|
||
+140288
|
||
+1993
|
||
+12
|
||
+7
|
||
+15
|
||
+54
|
||
+
|
||
+-
|
||
+testlog.dat
|
||
+0
|
||
+1995
|
||
+4
|
||
+27
|
||
+13
|
||
+18
|
||
diff --git a/net/data/ftp/dir-listing-vms-6 b/net/data/ftp/dir-listing-vms-6
|
||
new file mode 100644
|
||
index 0000000000000..6143c82e14a3a
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-6
|
||
@@ -0,0 +1,13 @@
|
||
+
|
||
+ANONYMOUS:[000000]
|
||
+
|
||
+ANON_FTP_LOG.LOG;23
|
||
+ <%SYSTEM-F-NOPRIV, insufficient privilege or object protection violation>
|
||
+EK-SMCPR-UG-A01.PDF;1
|
||
+ <%SYSTEM-F-NOPRIV, insufficient privilege or object protection violation>
|
||
+INCOMING.DIR;1 1 16-FEB-2009 00:49 [ANONY,ANONYMOUS] (RWE,RWE,RE,RE)
|
||
+INPUT.DIR;1 1 25-JAN-2004 07:11 [ANONY,ANONYMOUS] (RWED,RWE,R,R)
|
||
+LOGIN.COM;1 8 25-NOV-2003 20:01 [ANONY,ANONYMOUS] (RWED,RE,RE,RE)
|
||
+PUB.DIR;1 2 25-JAN-2004 07:11 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+
|
||
+Total of 6 Files, 12 Blocks.
|
||
diff --git a/net/data/ftp/dir-listing-vms-6.expected b/net/data/ftp/dir-listing-vms-6.expected
|
||
new file mode 100644
|
||
index 0000000000000..34fd885a2e370
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-6.expected
|
||
@@ -0,0 +1,35 @@
|
||
+d
|
||
+incoming
|
||
+-1
|
||
+2009
|
||
+2
|
||
+16
|
||
+0
|
||
+49
|
||
+
|
||
+d
|
||
+input
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+11
|
||
+
|
||
+-
|
||
+login.com
|
||
+4096
|
||
+2003
|
||
+11
|
||
+25
|
||
+20
|
||
+1
|
||
+
|
||
+d
|
||
+pub
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+11
|
||
diff --git a/net/data/ftp/dir-listing-vms-7 b/net/data/ftp/dir-listing-vms-7
|
||
new file mode 100644
|
||
index 0000000000000..d9ad20ba97bc0
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-7
|
||
@@ -0,0 +1,4 @@
|
||
+
|
||
+ANONYMOUS:[INCOMING]
|
||
+
|
||
+*.*;0 <%RMS-E-FNF, file not found>
|
||
diff --git a/net/data/ftp/dir-listing-vms-7.expected b/net/data/ftp/dir-listing-vms-7.expected
|
||
new file mode 100644
|
||
index 0000000000000..e69de29bb2d1d
|
||
diff --git a/net/data/ftp/dir-listing-vms-8 b/net/data/ftp/dir-listing-vms-8
|
||
new file mode 100644
|
||
index 0000000000000..d805ab50b7258
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-8
|
||
@@ -0,0 +1,37 @@
|
||
+
|
||
+ANONYMOUS:[PUB]
|
||
+
|
||
+ALPHA0721.ISO-GZ;1 383594 14-MAY-2008 08:52 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
|
||
+AXP.DIR;1 1 25-JAN-2004 07:11 [SYSTEM] (RWE,RE,RE,RE)
|
||
+BENCHMARKS.DIR;1 1 10-JUN-2009 19:32 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+EULER.DIR;1 1 10-JUN-2009 20:43 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+GAMES.DIR;1 1 5-FEB-2009 18:43 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+HOUSE.DIR;1 1 27-MAR-2008 08:02 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+HTML.DIR;1 1 25-JAN-2004 07:12 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+I64.DIR;1 1 22-JUN-2008 20:07 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+ITANIUM.DIR;1 1 12-JAN-2008 06:56 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+J2VMS.DIR;1 1 17-NOV-2008 23:26 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+MISC.DIR;1 1 25-JAN-2004 07:12 [SYSTEM] (RWE,RE,RE,RE)
|
||
+OPENSOURCE.DIR;1 1 8-SEP-2009 19:29 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+PHONE.DIR;1 2 15-DEC-2008 21:03 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+PLIDOCS.DIR;1 1 25-JAN-2004 07:12 [SYSTEM] (RWE,RE,RE,RE)
|
||
+PLI_HTML_DOCS-BCK.ZIP;1
|
||
+ 2992 25-MAR-2009 18:27 [ANONY,ANONYMOUS] (RWED,RWED,RE,RE)
|
||
+PLI_HTML_DOCS.BCK;2
|
||
+ 9639 25-MAR-2009 18:23 [VMSUSER,TSNEDDON] (RE,RWED,RE,RE)
|
||
+RESET_BACKUP_SAVESET_ATTRIBUTES.COM;1
|
||
+ 3 14-JUN-2009 22:46 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
|
||
+RESET_BACKUP_SAVESET_ATTRIBUTES.TXT;1
|
||
+ 3 28-AUG-2001 07:54 [SYSTEM] (RE,RE,RE,RE)
|
||
+rexx.DIR;1 1 11-AUG-2009 18:06 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+SDLEXT.DIR;1 1 19-DEC-2008 08:35 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+TOOLS.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
|
||
+TRU64.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
|
||
+VAX.DIR;1 1 25-JAN-2004 07:13 [SYSTEM] (RWE,RE,RE,RE)
|
||
+VMS721.ISO;2 ****** 6-MAY-2008 09:29 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
|
||
+WINDOWS.DIR;1 1 26-AUG-2011 05:53 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+XMLRTL.DIR;1 1 3-JUN-2009 17:24 [ANONY,ANONYMOUS] (RWE,RE,RE,RE)
|
||
+_SITE.CSS;8 5 19-MAR-2009 23:44 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
|
||
+_VWCMS.CSS;5 14 19-MAR-2009 23:38 [ANONY,ANONYMOUS] (RE,RWED,RE,RE)
|
||
+
|
||
+Total of 28 Files, 1557541 Blocks.
|
||
diff --git a/net/data/ftp/dir-listing-vms-8.expected b/net/data/ftp/dir-listing-vms-8.expected
|
||
new file mode 100644
|
||
index 0000000000000..75676be74b39a
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-vms-8.expected
|
||
@@ -0,0 +1,251 @@
|
||
+-
|
||
+alpha0721.iso-gz
|
||
+196400128
|
||
+2008
|
||
+5
|
||
+14
|
||
+8
|
||
+52
|
||
+
|
||
+d
|
||
+axp
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+11
|
||
+
|
||
+d
|
||
+benchmarks
|
||
+-1
|
||
+2009
|
||
+6
|
||
+10
|
||
+19
|
||
+32
|
||
+
|
||
+d
|
||
+euler
|
||
+-1
|
||
+2009
|
||
+6
|
||
+10
|
||
+20
|
||
+43
|
||
+
|
||
+d
|
||
+games
|
||
+-1
|
||
+2009
|
||
+2
|
||
+5
|
||
+18
|
||
+43
|
||
+
|
||
+d
|
||
+house
|
||
+-1
|
||
+2008
|
||
+3
|
||
+27
|
||
+8
|
||
+2
|
||
+
|
||
+d
|
||
+html
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+12
|
||
+
|
||
+d
|
||
+i64
|
||
+-1
|
||
+2008
|
||
+6
|
||
+22
|
||
+20
|
||
+7
|
||
+
|
||
+d
|
||
+itanium
|
||
+-1
|
||
+2008
|
||
+1
|
||
+12
|
||
+6
|
||
+56
|
||
+
|
||
+d
|
||
+j2vms
|
||
+-1
|
||
+2008
|
||
+11
|
||
+17
|
||
+23
|
||
+26
|
||
+
|
||
+d
|
||
+misc
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+12
|
||
+
|
||
+d
|
||
+opensource
|
||
+-1
|
||
+2009
|
||
+9
|
||
+8
|
||
+19
|
||
+29
|
||
+
|
||
+d
|
||
+phone
|
||
+-1
|
||
+2008
|
||
+12
|
||
+15
|
||
+21
|
||
+3
|
||
+
|
||
+d
|
||
+plidocs
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+12
|
||
+
|
||
+-
|
||
+pli_html_docs-bck.zip
|
||
+1531904
|
||
+2009
|
||
+3
|
||
+25
|
||
+18
|
||
+27
|
||
+
|
||
+-
|
||
+pli_html_docs.bck
|
||
+4935168
|
||
+2009
|
||
+3
|
||
+25
|
||
+18
|
||
+23
|
||
+
|
||
+-
|
||
+reset_backup_saveset_attributes.com
|
||
+1536
|
||
+2009
|
||
+6
|
||
+14
|
||
+22
|
||
+46
|
||
+
|
||
+-
|
||
+reset_backup_saveset_attributes.txt
|
||
+1536
|
||
+2001
|
||
+8
|
||
+28
|
||
+7
|
||
+54
|
||
+
|
||
+d
|
||
+rexx
|
||
+-1
|
||
+2009
|
||
+8
|
||
+11
|
||
+18
|
||
+6
|
||
+
|
||
+d
|
||
+sdlext
|
||
+-1
|
||
+2008
|
||
+12
|
||
+19
|
||
+8
|
||
+35
|
||
+
|
||
+d
|
||
+tools
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+13
|
||
+
|
||
+d
|
||
+tru64
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+13
|
||
+
|
||
+d
|
||
+vax
|
||
+-1
|
||
+2004
|
||
+1
|
||
+25
|
||
+7
|
||
+13
|
||
+
|
||
+-
|
||
+vms721.iso
|
||
+-1
|
||
+2008
|
||
+5
|
||
+6
|
||
+9
|
||
+29
|
||
+
|
||
+d
|
||
+windows
|
||
+-1
|
||
+2011
|
||
+8
|
||
+26
|
||
+5
|
||
+53
|
||
+
|
||
+d
|
||
+xmlrtl
|
||
+-1
|
||
+2009
|
||
+6
|
||
+3
|
||
+17
|
||
+24
|
||
+
|
||
+-
|
||
+_site.css
|
||
+2560
|
||
+2009
|
||
+3
|
||
+19
|
||
+23
|
||
+44
|
||
+
|
||
+-
|
||
+_vwcms.css
|
||
+7168
|
||
+2009
|
||
+3
|
||
+19
|
||
+23
|
||
+38
|
||
diff --git a/net/data/ftp/dir-listing-windows-1 b/net/data/ftp/dir-listing-windows-1
|
||
new file mode 100644
|
||
index 0000000000000..a8ff16d73a49b
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-windows-1
|
||
@@ -0,0 +1,2 @@
|
||
+11-02-09 05:32PM <DIR> NT
|
||
+01-06-09 02:42PM 458 Readme.txt
|
||
diff --git a/net/data/ftp/dir-listing-windows-1.expected b/net/data/ftp/dir-listing-windows-1.expected
|
||
new file mode 100644
|
||
index 0000000000000..8fbbb48fcd621
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-windows-1.expected
|
||
@@ -0,0 +1,17 @@
|
||
+d
|
||
+NT
|
||
+-1
|
||
+2009
|
||
+11
|
||
+2
|
||
+17
|
||
+32
|
||
+
|
||
+-
|
||
+Readme.txt
|
||
+458
|
||
+2009
|
||
+1
|
||
+6
|
||
+14
|
||
+42
|
||
diff --git a/net/data/ftp/dir-listing-windows-2 b/net/data/ftp/dir-listing-windows-2
|
||
new file mode 100644
|
||
index 0000000000000..f62fea49959e3
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-windows-2
|
||
@@ -0,0 +1,16 @@
|
||
+05-18-09 11:07AM <DIR> beta
|
||
+01-06-09 04:25PM <DIR> cdrom
|
||
+01-06-09 02:38PM 129 checkdownload.html
|
||
+08-19-09 11:23AM <DIR> Digital_Media_Player
|
||
+12-29-08 10:27PM <DIR> LiveUpdate
|
||
+08-20-09 04:34PM <DIR> mb
|
||
+01-06-09 02:38PM 83933 Mb.eng
|
||
+11-04-09 03:42PM <DIR> misc
|
||
+06-12-09 04:20PM 462 Path.idx
|
||
+12-30-08 07:41AM <DIR> PDA
|
||
+01-06-09 02:38PM 2625 Platform.idx
|
||
+12-30-08 07:41AM <DIR> print
|
||
+01-06-09 02:42PM 458 Readme.txt
|
||
+10-28-09 02:27PM <DIR> server
|
||
+12-30-08 08:59AM <DIR> vga
|
||
+02-03-09 04:42PM 1951823 VH203_FR.pdf
|
||
diff --git a/net/data/ftp/dir-listing-windows-2.expected b/net/data/ftp/dir-listing-windows-2.expected
|
||
new file mode 100644
|
||
index 0000000000000..81388eef34514
|
||
--- /dev/null
|
||
+++ b/net/data/ftp/dir-listing-windows-2.expected
|
||
@@ -0,0 +1,143 @@
|
||
+d
|
||
+beta
|
||
+-1
|
||
+2009
|
||
+5
|
||
+18
|
||
+11
|
||
+7
|
||
+
|
||
+d
|
||
+cdrom
|
||
+-1
|
||
+2009
|
||
+1
|
||
+6
|
||
+16
|
||
+25
|
||
+
|
||
+-
|
||
+checkdownload.html
|
||
+129
|
||
+2009
|
||
+1
|
||
+6
|
||
+14
|
||
+38
|
||
+
|
||
+d
|
||
+Digital_Media_Player
|
||
+-1
|
||
+2009
|
||
+8
|
||
+19
|
||
+11
|
||
+23
|
||
+
|
||
+d
|
||
+LiveUpdate
|
||
+-1
|
||
+2008
|
||
+12
|
||
+29
|
||
+22
|
||
+27
|
||
+
|
||
+d
|
||
+mb
|
||
+-1
|
||
+2009
|
||
+8
|
||
+20
|
||
+16
|
||
+34
|
||
+
|
||
+-
|
||
+Mb.eng
|
||
+83933
|
||
+2009
|
||
+1
|
||
+6
|
||
+14
|
||
+38
|
||
+
|
||
+d
|
||
+misc
|
||
+-1
|
||
+2009
|
||
+11
|
||
+4
|
||
+15
|
||
+42
|
||
+
|
||
+-
|
||
+Path.idx
|
||
+462
|
||
+2009
|
||
+6
|
||
+12
|
||
+16
|
||
+20
|
||
+
|
||
+d
|
||
+PDA
|
||
+-1
|
||
+2008
|
||
+12
|
||
+30
|
||
+7
|
||
+41
|
||
+
|
||
+-
|
||
+Platform.idx
|
||
+2625
|
||
+2009
|
||
+1
|
||
+6
|
||
+14
|
||
+38
|
||
+
|
||
+d
|
||
+print
|
||
+-1
|
||
+2008
|
||
+12
|
||
+30
|
||
+7
|
||
+41
|
||
+
|
||
+-
|
||
+Readme.txt
|
||
+458
|
||
+2009
|
||
+1
|
||
+6
|
||
+14
|
||
+42
|
||
+
|
||
+d
|
||
+server
|
||
+-1
|
||
+2009
|
||
+10
|
||
+28
|
||
+14
|
||
+27
|
||
+
|
||
+d
|
||
+vga
|
||
+-1
|
||
+2008
|
||
+12
|
||
+30
|
||
+8
|
||
+59
|
||
+
|
||
+-
|
||
+VH203_FR.pdf
|
||
+1951823
|
||
+2009
|
||
+2
|
||
+3
|
||
+16
|
||
+42
|
||
diff --git a/net/data/fuzzer_dictionaries/net_url_request_ftp_fuzzer.dict b/net/data/fuzzer_dictionaries/net_url_request_ftp_fuzzer.dict
|
||
new file mode 100644
|
||
index 0000000000000..eb4acff145d95
|
||
--- /dev/null
|
||
+++ b/net/data/fuzzer_dictionaries/net_url_request_ftp_fuzzer.dict
|
||
@@ -0,0 +1,59 @@
|
||
+# Copyright 2016 The Chromium Authors
|
||
+# Use of this source code is governed by a BSD-style license that can be
|
||
+# found in the LICENSE file.
|
||
+
|
||
+# Fuzzer dictionary targetting FTP responses.
|
||
+
|
||
+# Each response is on its own line, so CRLF is generally useful.
|
||
+"\x0D\x0A"
|
||
+
|
||
+# End of string marker, used by FuzzedDataProvider.
|
||
+"\\ "
|
||
+
|
||
+# Suffixes for file types.
|
||
+";type=a"
|
||
+";type=i"
|
||
+";type=d"
|
||
+
|
||
+# Dashes indicate multi-line responses.
|
||
+"-"
|
||
+
|
||
+# Generic success.
|
||
+"200 OK\x0D\x0A\\ "
|
||
+
|
||
+# Greeting
|
||
+"230 Welcome\x0D\x0A\\ "
|
||
+
|
||
+# SIZE response
|
||
+"213 18\x0D\x0A\\ "
|
||
+
|
||
+# All important PASV/EPSV responses
|
||
+"227 Entering PASV mode (1,1,1,1,50,50)\x0D\x0A\\ "
|
||
+"227 Entering Passive Mode 127,0,0,1,123,456\x0D\x0A\\ "
|
||
+"227 Entering Extended Passive Mode (|||31744|)\x0D\x0A\\ "
|
||
+"227 Entering The Twilight Zone\x0D\x0A\\ "
|
||
+
|
||
+# RETR/LIST response.
|
||
+"125-Data connection already open.\x0D\x0A125 Transfer starting.\x0D\x0A226 Transfer complete.\x0D\x0A\\ "
|
||
+"125-Data connection already open.\x0D\x0A\\ "
|
||
+"125 Transfer starting.\x0D\x0A\\ "
|
||
+"226 Transfer complete.\x0D\x0A\\ "
|
||
+
|
||
+# Some specific success messages, taken from unittests.
|
||
+"215 UNIX\x0D\x0A\\ "
|
||
+"215 VMS\x0D\x0A\\ "
|
||
+"220 host TestFTPd\x0D\x0A\\ "
|
||
+"221 Goodbye!\x0D\x0A\\ "
|
||
+"257 \"/\" is your current location\x0D\x0A\\ "
|
||
+"257 \"ANONYMOUS_ROOT:[000000]\"\x0D\x0A\\ "
|
||
+
|
||
+# Error messages, taken from unittests.
|
||
+"331 Password needed\x0D\x0A\\ "
|
||
+"331 User okay, send password\x0D\x0A\\ "
|
||
+"451 not a directory\x0D\x0A\\ "
|
||
+"500 EPSV command unknown\x0D\x0A\\ "
|
||
+"503 Bad sequence of commands\x0D\x0A\\ "
|
||
+"530 Login authentication failed\x0D\x0A\\ "
|
||
+"550 I can only retrieve regular files\x0D\x0A\\ "
|
||
+"550 Not a directory\x0D\x0A\\ "
|
||
+"599 I'm sorry, Dave, I'm afraid I can't do that.\x0D\x0A\\ "
|
||
diff --git a/net/docs/proxy.md b/net/docs/proxy.md
|
||
index a0ea9f52d1779..589b499e2d4c4 100644
|
||
--- a/net/docs/proxy.md
|
||
+++ b/net/docs/proxy.md
|
||
@@ -109,6 +109,7 @@ about an HTTP proxy.
|
||
|
||
When using an HTTP proxy in Chrome, name resolution is always deferred to the
|
||
proxy. HTTP proxies can proxy `http://`, `https://`, `ws://` and `wss://` URLs.
|
||
+(Chromium's FTP support is deprecated, and HTTP proxies cannot proxy `ftp://` anymore)
|
||
|
||
Communication to HTTP proxy servers is insecure, meaning proxied `http://`
|
||
requests are sent in the clear. When proxying `https://` requests through an
|
||
diff --git a/net/features.gni b/net/features.gni
|
||
index 026a38c0baab1..1d7d13a7c409f 100644
|
||
--- a/net/features.gni
|
||
+++ b/net/features.gni
|
||
@@ -14,6 +14,9 @@ declare_args() {
|
||
# and are optional in cronet.
|
||
enable_websockets = use_blink
|
||
|
||
+ # Disable FTP support.
|
||
+ disable_ftp_support = is_ios
|
||
+
|
||
# Enable Kerberos authentication. It is disabled by default on iOS, Fuchsia
|
||
# and Chromecast, at least for now. This feature needs configuration
|
||
# (krb5.conf and so on).
|
||
diff --git a/net/ftp/DIR_METADATA b/net/ftp/DIR_METADATA
|
||
new file mode 100644
|
||
index 0000000000000..c611fb72b48b1
|
||
--- /dev/null
|
||
+++ b/net/ftp/DIR_METADATA
|
||
@@ -0,0 +1,11 @@
|
||
+# Metadata information for this directory.
|
||
+#
|
||
+# For more information on DIR_METADATA files, see:
|
||
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
|
||
+#
|
||
+# For the schema of this file, see Metadata message:
|
||
+# https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
|
||
+
|
||
+monorail {
|
||
+ component: "Internals>Network>FTP"
|
||
+}
|
||
\ No newline at end of file
|
||
diff --git a/net/ftp/OWNERS b/net/ftp/OWNERS
|
||
new file mode 100644
|
||
index 0000000000000..53e68eb7787a3
|
||
--- /dev/null
|
||
+++ b/net/ftp/OWNERS
|
||
@@ -0,0 +1 @@
|
||
+mmenke@chromium.org
|
||
diff --git a/net/ftp/ftp_auth_cache.cc b/net/ftp/ftp_auth_cache.cc
|
||
new file mode 100644
|
||
index 0000000000000..359d5460db859
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_auth_cache.cc
|
||
@@ -0,0 +1,62 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+
|
||
+#include "base/check_op.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+// static
|
||
+const size_t FtpAuthCache::kMaxEntries = 10;
|
||
+
|
||
+FtpAuthCache::Entry::Entry(const GURL& origin,
|
||
+ const AuthCredentials& credentials)
|
||
+ : origin(origin),
|
||
+ credentials(credentials) {
|
||
+}
|
||
+
|
||
+FtpAuthCache::Entry::~Entry() = default;
|
||
+
|
||
+FtpAuthCache::FtpAuthCache() = default;
|
||
+
|
||
+FtpAuthCache::~FtpAuthCache() = default;
|
||
+
|
||
+FtpAuthCache::Entry* FtpAuthCache::Lookup(const GURL& origin) {
|
||
+ for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||
+ if (it->origin == origin)
|
||
+ return &(*it);
|
||
+ }
|
||
+ return nullptr;
|
||
+}
|
||
+
|
||
+void FtpAuthCache::Add(const GURL& origin, const AuthCredentials& credentials) {
|
||
+ DCHECK(origin.SchemeIs("ftp"));
|
||
+ DCHECK_EQ(origin.DeprecatedGetOriginAsURL(), origin);
|
||
+
|
||
+ Entry* entry = Lookup(origin);
|
||
+ if (entry) {
|
||
+ entry->credentials = credentials;
|
||
+ } else {
|
||
+ entries_.push_front(Entry(origin, credentials));
|
||
+
|
||
+ // Prevent unbound memory growth of the cache.
|
||
+ if (entries_.size() > kMaxEntries)
|
||
+ entries_.pop_back();
|
||
+ }
|
||
+}
|
||
+
|
||
+void FtpAuthCache::Remove(const GURL& origin,
|
||
+ const AuthCredentials& credentials) {
|
||
+ for (auto it = entries_.begin(); it != entries_.end(); ++it) {
|
||
+ if (it->origin == origin && it->credentials.Equals(credentials)) {
|
||
+ entries_.erase(it);
|
||
+ DCHECK(!Lookup(origin));
|
||
+ return;
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_auth_cache.h b/net/ftp/ftp_auth_cache.h
|
||
new file mode 100644
|
||
index 0000000000000..e7f3057e153bf
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_auth_cache.h
|
||
@@ -0,0 +1,63 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_AUTH_CACHE_H_
|
||
+#define NET_FTP_FTP_AUTH_CACHE_H_
|
||
+
|
||
+#include <stddef.h>
|
||
+
|
||
+#include <list>
|
||
+
|
||
+#include "net/base/auth.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+// The FtpAuthCache class is a simple cache structure to store authentication
|
||
+// information for ftp. Provides lookup, insertion, and deletion of entries.
|
||
+// The parameter for doing lookups, insertions, and deletions is a GURL of the
|
||
+// server's address (not a full URL with path, since FTP auth isn't per path).
|
||
+// For example:
|
||
+// GURL("ftp://myserver") -- OK (implied port of 21)
|
||
+// GURL("ftp://myserver:21") -- OK
|
||
+// GURL("ftp://myserver/PATH") -- WRONG, paths not allowed
|
||
+class NET_EXPORT_PRIVATE FtpAuthCache {
|
||
+ public:
|
||
+ // Maximum number of entries we allow in the cache.
|
||
+ static const size_t kMaxEntries;
|
||
+
|
||
+ struct Entry {
|
||
+ Entry(const GURL& origin, const AuthCredentials& credentials);
|
||
+ ~Entry();
|
||
+
|
||
+ GURL origin;
|
||
+ AuthCredentials credentials;
|
||
+ };
|
||
+
|
||
+ FtpAuthCache();
|
||
+ ~FtpAuthCache();
|
||
+
|
||
+ // Return Entry corresponding to given |origin| or NULL if not found.
|
||
+ Entry* Lookup(const GURL& origin);
|
||
+
|
||
+ // Add an entry for |origin| to the cache using |credentials|. If there is
|
||
+ // already an entry for |origin|, it will be overwritten.
|
||
+ void Add(const GURL& origin, const AuthCredentials& credentials);
|
||
+
|
||
+ // Remove the entry for |origin| from the cache, if one exists and matches
|
||
+ // |credentials|.
|
||
+ void Remove(const GURL& origin, const AuthCredentials& credentials);
|
||
+
|
||
+ private:
|
||
+ typedef std::list<Entry> EntryList;
|
||
+
|
||
+ // Internal representation of cache, an STL list. This makes lookups O(n),
|
||
+ // but we expect n to be very low.
|
||
+ EntryList entries_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_AUTH_CACHE_H_
|
||
diff --git a/net/ftp/ftp_auth_cache_unittest.cc b/net/ftp/ftp_auth_cache_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..9c5dea5303895
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_auth_cache_unittest.cc
|
||
@@ -0,0 +1,161 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/base/auth.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+using base::ASCIIToUTF16;
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+const std::u16string kBogus(u"bogus");
|
||
+const std::u16string kOthername(u"othername");
|
||
+const std::u16string kOtherword(u"otherword");
|
||
+const std::u16string kPassword(u"password");
|
||
+const std::u16string kPassword1(u"password1");
|
||
+const std::u16string kPassword2(u"password2");
|
||
+const std::u16string kPassword3(u"password3");
|
||
+const std::u16string kUsername(u"username");
|
||
+const std::u16string kUsername1(u"username1");
|
||
+const std::u16string kUsername2(u"username2");
|
||
+const std::u16string kUsername3(u"username3");
|
||
+
|
||
+} // namespace
|
||
+
|
||
+TEST(FtpAuthCacheTest, LookupAddRemove) {
|
||
+ FtpAuthCache cache;
|
||
+
|
||
+ GURL origin1("ftp://foo1");
|
||
+ GURL origin2("ftp://foo2");
|
||
+
|
||
+ // Lookup non-existent entry.
|
||
+ EXPECT_TRUE(cache.Lookup(origin1) == nullptr);
|
||
+
|
||
+ // Add entry for origin1.
|
||
+ cache.Add(origin1, AuthCredentials(kUsername1, kPassword1));
|
||
+ FtpAuthCache::Entry* entry1 = cache.Lookup(origin1);
|
||
+ ASSERT_TRUE(entry1);
|
||
+ EXPECT_EQ(origin1, entry1->origin);
|
||
+ EXPECT_EQ(kUsername1, entry1->credentials.username());
|
||
+ EXPECT_EQ(kPassword1, entry1->credentials.password());
|
||
+
|
||
+ // Add an entry for origin2.
|
||
+ cache.Add(origin2, AuthCredentials(kUsername2, kPassword2));
|
||
+ FtpAuthCache::Entry* entry2 = cache.Lookup(origin2);
|
||
+ ASSERT_TRUE(entry2);
|
||
+ EXPECT_EQ(origin2, entry2->origin);
|
||
+ EXPECT_EQ(kUsername2, entry2->credentials.username());
|
||
+ EXPECT_EQ(kPassword2, entry2->credentials.password());
|
||
+
|
||
+ // The original entry1 should still be there.
|
||
+ EXPECT_EQ(entry1, cache.Lookup(origin1));
|
||
+
|
||
+ // Overwrite the entry for origin1.
|
||
+ cache.Add(origin1, AuthCredentials(kUsername3, kPassword3));
|
||
+ FtpAuthCache::Entry* entry3 = cache.Lookup(origin1);
|
||
+ ASSERT_TRUE(entry3);
|
||
+ EXPECT_EQ(origin1, entry3->origin);
|
||
+ EXPECT_EQ(kUsername3, entry3->credentials.username());
|
||
+ EXPECT_EQ(kPassword3, entry3->credentials.password());
|
||
+
|
||
+ // Remove entry of origin1.
|
||
+ cache.Remove(origin1, AuthCredentials(kUsername3, kPassword3));
|
||
+ EXPECT_TRUE(cache.Lookup(origin1) == nullptr);
|
||
+
|
||
+ // Remove non-existent entry.
|
||
+ cache.Remove(origin1, AuthCredentials(kUsername3, kPassword3));
|
||
+ EXPECT_TRUE(cache.Lookup(origin1) == nullptr);
|
||
+}
|
||
+
|
||
+// Check that if the origin differs only by port number, it is considered
|
||
+// a separate origin.
|
||
+TEST(FtpAuthCacheTest, LookupWithPort) {
|
||
+ FtpAuthCache cache;
|
||
+
|
||
+ GURL origin1("ftp://foo:80");
|
||
+ GURL origin2("ftp://foo:21");
|
||
+
|
||
+ cache.Add(origin1, AuthCredentials(kUsername, kPassword));
|
||
+ cache.Add(origin2, AuthCredentials(kUsername, kPassword));
|
||
+
|
||
+ EXPECT_NE(cache.Lookup(origin1), cache.Lookup(origin2));
|
||
+}
|
||
+
|
||
+TEST(FtpAuthCacheTest, NormalizedKey) {
|
||
+ // GURL is automatically canonicalized. Hence the following variations in
|
||
+ // url format should all map to the same entry (case insensitive host,
|
||
+ // default port of 21).
|
||
+
|
||
+ FtpAuthCache cache;
|
||
+
|
||
+ // Add.
|
||
+ cache.Add(GURL("ftp://HoSt:21"), AuthCredentials(kUsername, kPassword));
|
||
+
|
||
+ // Lookup.
|
||
+ FtpAuthCache::Entry* entry1 = cache.Lookup(GURL("ftp://HoSt:21"));
|
||
+ ASSERT_TRUE(entry1);
|
||
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host:21")));
|
||
+ EXPECT_EQ(entry1, cache.Lookup(GURL("ftp://host")));
|
||
+
|
||
+ // Overwrite.
|
||
+ cache.Add(GURL("ftp://host"), AuthCredentials(kOthername, kOtherword));
|
||
+ FtpAuthCache::Entry* entry2 = cache.Lookup(GURL("ftp://HoSt:21"));
|
||
+ ASSERT_TRUE(entry2);
|
||
+ EXPECT_EQ(GURL("ftp://host"), entry2->origin);
|
||
+ EXPECT_EQ(kOthername, entry2->credentials.username());
|
||
+ EXPECT_EQ(kOtherword, entry2->credentials.password());
|
||
+
|
||
+ // Remove
|
||
+ cache.Remove(GURL("ftp://HOsT"), AuthCredentials(kOthername, kOtherword));
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == nullptr);
|
||
+}
|
||
+
|
||
+TEST(FtpAuthCacheTest, OnlyRemoveMatching) {
|
||
+ FtpAuthCache cache;
|
||
+
|
||
+ cache.Add(GURL("ftp://host"), AuthCredentials(kUsername, kPassword));
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
|
||
+
|
||
+ // Auth data doesn't match, shouldn't remove.
|
||
+ cache.Remove(GURL("ftp://host"), AuthCredentials(kBogus, kBogus));
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")));
|
||
+
|
||
+ // Auth data matches, should remove.
|
||
+ cache.Remove(GURL("ftp://host"), AuthCredentials(kUsername, kPassword));
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host")) == nullptr);
|
||
+}
|
||
+
|
||
+TEST(FtpAuthCacheTest, EvictOldEntries) {
|
||
+ FtpAuthCache cache;
|
||
+
|
||
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
|
||
+ cache.Add(GURL("ftp://host" + base::NumberToString(i)),
|
||
+ AuthCredentials(kUsername, kPassword));
|
||
+ }
|
||
+
|
||
+ // No entries should be evicted before reaching the limit.
|
||
+ for (size_t i = 0; i < FtpAuthCache::kMaxEntries; i++) {
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::NumberToString(i))));
|
||
+ }
|
||
+
|
||
+ // Adding one entry should cause eviction of the first entry.
|
||
+ cache.Add(GURL("ftp://last_host"), AuthCredentials(kUsername, kPassword));
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host0")) == nullptr);
|
||
+
|
||
+ // Remaining entries should not get evicted.
|
||
+ for (size_t i = 1; i < FtpAuthCache::kMaxEntries; i++) {
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://host" + base::NumberToString(i))));
|
||
+ }
|
||
+ EXPECT_TRUE(cache.Lookup(GURL("ftp://last_host")));
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_ctrl_response_buffer.cc b/net/ftp/ftp_ctrl_response_buffer.cc
|
||
new file mode 100644
|
||
index 0000000000000..9bd4059d5fbe4
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_ctrl_response_buffer.cc
|
||
@@ -0,0 +1,159 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_ctrl_response_buffer.h"
|
||
+
|
||
+#include <utility>
|
||
+
|
||
+#include "base/check_op.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/values.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/base/parse_number.h"
|
||
+#include "net/log/net_log_event_type.h"
|
||
+#include "net/log/net_log_values.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+// static
|
||
+const int FtpCtrlResponse::kInvalidStatusCode = -1;
|
||
+
|
||
+FtpCtrlResponse::FtpCtrlResponse() : status_code(kInvalidStatusCode) {}
|
||
+
|
||
+FtpCtrlResponse::FtpCtrlResponse(const FtpCtrlResponse& other) = default;
|
||
+
|
||
+FtpCtrlResponse::~FtpCtrlResponse() = default;
|
||
+
|
||
+FtpCtrlResponseBuffer::FtpCtrlResponseBuffer(const NetLogWithSource& net_log)
|
||
+ : multiline_(false), net_log_(net_log) {}
|
||
+
|
||
+FtpCtrlResponseBuffer::~FtpCtrlResponseBuffer() = default;
|
||
+
|
||
+int FtpCtrlResponseBuffer::ConsumeData(const char* data, int data_length) {
|
||
+ buffer_.append(data, data_length);
|
||
+ ExtractFullLinesFromBuffer();
|
||
+
|
||
+ while (!lines_.empty()) {
|
||
+ ParsedLine line = lines_.front();
|
||
+ lines_.pop();
|
||
+
|
||
+ if (multiline_) {
|
||
+ if (!line.is_complete || line.status_code != response_buf_.status_code) {
|
||
+ line_buf_.append(line.raw_text);
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ response_buf_.lines.push_back(line_buf_);
|
||
+
|
||
+ line_buf_ = line.status_text;
|
||
+ DCHECK_EQ(line.status_code, response_buf_.status_code);
|
||
+
|
||
+ if (!line.is_multiline) {
|
||
+ response_buf_.lines.push_back(line_buf_);
|
||
+ responses_.push(response_buf_);
|
||
+
|
||
+ // Prepare to handle following lines.
|
||
+ response_buf_ = FtpCtrlResponse();
|
||
+ line_buf_.clear();
|
||
+ multiline_ = false;
|
||
+ }
|
||
+ } else {
|
||
+ if (!line.is_complete)
|
||
+ return ERR_INVALID_RESPONSE;
|
||
+
|
||
+ response_buf_.status_code = line.status_code;
|
||
+ if (line.is_multiline) {
|
||
+ line_buf_ = line.status_text;
|
||
+ multiline_ = true;
|
||
+ } else {
|
||
+ response_buf_.lines.push_back(line.status_text);
|
||
+ responses_.push(response_buf_);
|
||
+
|
||
+ // Prepare to handle following lines.
|
||
+ response_buf_ = FtpCtrlResponse();
|
||
+ line_buf_.clear();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+namespace {
|
||
+
|
||
+base::Value::Dict NetLogFtpCtrlResponseParams(const FtpCtrlResponse* response) {
|
||
+ base::Value::List lines;
|
||
+ for (const auto& line : response->lines)
|
||
+ lines.Append(NetLogStringValue(line));
|
||
+
|
||
+ base::Value::Dict dict;
|
||
+ dict.Set("status_code", response->status_code);
|
||
+ dict.Set("lines", std::move(lines));
|
||
+ return dict;
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+FtpCtrlResponse FtpCtrlResponseBuffer::PopResponse() {
|
||
+ FtpCtrlResponse result = responses_.front();
|
||
+ responses_.pop();
|
||
+
|
||
+ net_log_.AddEvent(NetLogEventType::FTP_CONTROL_RESPONSE,
|
||
+ [&] { return NetLogFtpCtrlResponseParams(&result); });
|
||
+
|
||
+ return result;
|
||
+}
|
||
+
|
||
+FtpCtrlResponseBuffer::ParsedLine::ParsedLine()
|
||
+ : has_status_code(false),
|
||
+ is_multiline(false),
|
||
+ is_complete(false),
|
||
+ status_code(FtpCtrlResponse::kInvalidStatusCode) {
|
||
+}
|
||
+
|
||
+FtpCtrlResponseBuffer::ParsedLine::ParsedLine(const ParsedLine& other) =
|
||
+ default;
|
||
+
|
||
+// static
|
||
+FtpCtrlResponseBuffer::ParsedLine FtpCtrlResponseBuffer::ParseLine(
|
||
+ const std::string& line) {
|
||
+ ParsedLine result;
|
||
+
|
||
+ if (line.length() >= 3) {
|
||
+ if (ParseInt32(base::MakeStringPiece(line.begin(), line.begin() + 3),
|
||
+ ParseIntFormat::NON_NEGATIVE, &result.status_code)) {
|
||
+ result.has_status_code =
|
||
+ (100 <= result.status_code && result.status_code <= 599);
|
||
+ }
|
||
+ if (result.has_status_code && line.length() >= 4 && line[3] == ' ') {
|
||
+ result.is_complete = true;
|
||
+ } else if (result.has_status_code && line.length() >= 4 && line[3] == '-') {
|
||
+ result.is_complete = true;
|
||
+ result.is_multiline = true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (result.is_complete) {
|
||
+ result.status_text = line.substr(4);
|
||
+ } else {
|
||
+ result.status_text = line;
|
||
+ }
|
||
+
|
||
+ result.raw_text = line;
|
||
+
|
||
+ return result;
|
||
+}
|
||
+
|
||
+void FtpCtrlResponseBuffer::ExtractFullLinesFromBuffer() {
|
||
+ int cut_pos = 0;
|
||
+ for (size_t i = 0; i < buffer_.length(); i++) {
|
||
+ if (i >= 1 && buffer_[i - 1] == '\r' && buffer_[i] == '\n') {
|
||
+ lines_.push(ParseLine(buffer_.substr(cut_pos, i - cut_pos - 1)));
|
||
+ cut_pos = i + 1;
|
||
+ }
|
||
+ }
|
||
+ buffer_.erase(0, cut_pos);
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_ctrl_response_buffer.h b/net/ftp/ftp_ctrl_response_buffer.h
|
||
new file mode 100644
|
||
index 0000000000000..149097185dafc
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_ctrl_response_buffer.h
|
||
@@ -0,0 +1,101 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
|
||
+#define NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "base/containers/queue.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/log/net_log_with_source.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+struct NET_EXPORT_PRIVATE FtpCtrlResponse {
|
||
+ static const int kInvalidStatusCode;
|
||
+
|
||
+ FtpCtrlResponse();
|
||
+ FtpCtrlResponse(const FtpCtrlResponse& other);
|
||
+ ~FtpCtrlResponse();
|
||
+
|
||
+ int status_code; // Three-digit status code.
|
||
+ std::vector<std::string> lines; // Response lines, without CRLFs.
|
||
+};
|
||
+
|
||
+class NET_EXPORT_PRIVATE FtpCtrlResponseBuffer {
|
||
+ public:
|
||
+ FtpCtrlResponseBuffer(const NetLogWithSource& net_log);
|
||
+ ~FtpCtrlResponseBuffer();
|
||
+ FtpCtrlResponseBuffer(const FtpCtrlResponseBuffer&) = delete;
|
||
+ FtpCtrlResponseBuffer& operator=(const FtpCtrlResponseBuffer&) =
|
||
+ delete;
|
||
+ // Called when data is received from the control socket. Returns error code.
|
||
+ int ConsumeData(const char* data, int data_length);
|
||
+
|
||
+ bool ResponseAvailable() const {
|
||
+ return !responses_.empty();
|
||
+ }
|
||
+
|
||
+ // Returns the next response. It is an error to call this function
|
||
+ // unless ResponseAvailable returns true.
|
||
+ FtpCtrlResponse PopResponse();
|
||
+
|
||
+ private:
|
||
+ struct ParsedLine {
|
||
+ ParsedLine();
|
||
+ ParsedLine(const ParsedLine& other);
|
||
+
|
||
+ // Indicates that this line begins with a valid 3-digit status code.
|
||
+ bool has_status_code;
|
||
+
|
||
+ // Indicates that this line has the dash (-) after the code, which
|
||
+ // means a multiline response.
|
||
+ bool is_multiline;
|
||
+
|
||
+ // Indicates that this line could be parsed as a complete and valid
|
||
+ // response line, without taking into account preceding lines (which
|
||
+ // may change its meaning into a continuation of the previous line).
|
||
+ bool is_complete;
|
||
+
|
||
+ // Part of response parsed as status code.
|
||
+ int status_code;
|
||
+
|
||
+ // Part of response parsed as status text.
|
||
+ std::string status_text;
|
||
+
|
||
+ // Text before parsing, without terminating CRLF.
|
||
+ std::string raw_text;
|
||
+ };
|
||
+
|
||
+ static ParsedLine ParseLine(const std::string& line);
|
||
+
|
||
+ void ExtractFullLinesFromBuffer();
|
||
+
|
||
+ // We keep not-yet-parsed data in a string buffer.
|
||
+ std::string buffer_;
|
||
+
|
||
+ base::queue<ParsedLine> lines_;
|
||
+
|
||
+ // True if we are in the middle of parsing a multi-line response.
|
||
+ bool multiline_;
|
||
+
|
||
+ // When parsing a multiline response, we don't know beforehand if a line
|
||
+ // will have a continuation. So always store last line of multiline response
|
||
+ // so we can append the continuation to it.
|
||
+ std::string line_buf_;
|
||
+
|
||
+ // Keep the response data while we add all lines to it.
|
||
+ FtpCtrlResponse response_buf_;
|
||
+
|
||
+ // As we read full responses (possibly multiline), we add them to the queue.
|
||
+ base::queue<FtpCtrlResponse> responses_;
|
||
+
|
||
+ NetLogWithSource net_log_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_CTRL_RESPONSE_BUFFER_H_
|
||
diff --git a/net/ftp/ftp_ctrl_response_buffer_unittest.cc b/net/ftp/ftp_ctrl_response_buffer_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..a133aa441d7fc
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_ctrl_response_buffer_unittest.cc
|
||
@@ -0,0 +1,184 @@
|
||
+// Copyright (c) 2009 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_ctrl_response_buffer.h"
|
||
+
|
||
+#include <string.h>
|
||
+
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/test/gtest_util.h"
|
||
+#include "testing/gmock/include/gmock/gmock.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+
|
||
+using net::test::IsError;
|
||
+using net::test::IsOk;
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+class FtpCtrlResponseBufferTest : public testing::Test {
|
||
+ public:
|
||
+ FtpCtrlResponseBufferTest() : buffer_(NetLogWithSource()) {}
|
||
+
|
||
+ protected:
|
||
+ int PushDataToBuffer(const char* data) {
|
||
+ return buffer_.ConsumeData(data, strlen(data));
|
||
+ }
|
||
+
|
||
+ FtpCtrlResponseBuffer buffer_;
|
||
+};
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, Basic) {
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("200 Status Text\r\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(200, response.status_code);
|
||
+ ASSERT_EQ(1U, response.lines.size());
|
||
+ EXPECT_EQ("Status Text", response.lines[0]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, Chunks) {
|
||
+ EXPECT_THAT(PushDataToBuffer("20"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_THAT(PushDataToBuffer("0 Status"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_THAT(PushDataToBuffer(" Text"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_THAT(PushDataToBuffer("\r"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_THAT(PushDataToBuffer("\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(200, response.status_code);
|
||
+ ASSERT_EQ(1U, response.lines.size());
|
||
+ EXPECT_EQ("Status Text", response.lines[0]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, Continuation) {
|
||
+ EXPECT_THAT(PushDataToBuffer("230-FirstLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230-SecondLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230 LastLine\r\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(230, response.status_code);
|
||
+ ASSERT_EQ(3U, response.lines.size());
|
||
+ EXPECT_EQ("FirstLine", response.lines[0]);
|
||
+ EXPECT_EQ("SecondLine", response.lines[1]);
|
||
+ EXPECT_EQ("LastLine", response.lines[2]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, MultilineContinuation) {
|
||
+ EXPECT_THAT(PushDataToBuffer("230-FirstLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("Continued\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230-SecondLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("215 Continued\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230 LastLine\r\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(230, response.status_code);
|
||
+ ASSERT_EQ(3U, response.lines.size());
|
||
+ EXPECT_EQ("FirstLineContinued", response.lines[0]);
|
||
+ EXPECT_EQ("SecondLine215 Continued", response.lines[1]);
|
||
+ EXPECT_EQ("LastLine", response.lines[2]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, MultilineContinuationZeroLength) {
|
||
+ // For the corner case from bug 29322.
|
||
+ EXPECT_THAT(PushDataToBuffer("230-\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("example.com\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230 LastLine\r\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(230, response.status_code);
|
||
+ ASSERT_EQ(2U, response.lines.size());
|
||
+ EXPECT_EQ("example.com", response.lines[0]);
|
||
+ EXPECT_EQ("LastLine", response.lines[1]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, SimilarContinuation) {
|
||
+ EXPECT_THAT(PushDataToBuffer("230-FirstLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ // Notice the space at the start of the line. It should be recognized
|
||
+ // as a continuation, and not the last line.
|
||
+ EXPECT_THAT(PushDataToBuffer(" 230 Continued\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230 TrueLastLine\r\n"), IsOk());
|
||
+ EXPECT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(230, response.status_code);
|
||
+ ASSERT_EQ(2U, response.lines.size());
|
||
+ EXPECT_EQ("FirstLine 230 Continued", response.lines[0]);
|
||
+ EXPECT_EQ("TrueLastLine", response.lines[1]);
|
||
+}
|
||
+
|
||
+// The nesting of multi-line responses is not allowed.
|
||
+TEST_F(FtpCtrlResponseBufferTest, NoNesting) {
|
||
+ EXPECT_THAT(PushDataToBuffer("230-FirstLine\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("300-Continuation\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("300 Still continuation\r\n"), IsOk());
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+
|
||
+ EXPECT_THAT(PushDataToBuffer("230 Real End\r\n"), IsOk());
|
||
+ ASSERT_TRUE(buffer_.ResponseAvailable());
|
||
+
|
||
+ FtpCtrlResponse response = buffer_.PopResponse();
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+ EXPECT_EQ(230, response.status_code);
|
||
+ ASSERT_EQ(2U, response.lines.size());
|
||
+ EXPECT_EQ("FirstLine300-Continuation300 Still continuation",
|
||
+ response.lines[0]);
|
||
+ EXPECT_EQ("Real End", response.lines[1]);
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, NonNumericResponse) {
|
||
+ EXPECT_THAT(PushDataToBuffer("Non-numeric\r\n"),
|
||
+ IsError(ERR_INVALID_RESPONSE));
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+}
|
||
+
|
||
+TEST_F(FtpCtrlResponseBufferTest, OutOfRangeResponse) {
|
||
+ EXPECT_THAT(PushDataToBuffer("777 OK?\r\n"), IsError(ERR_INVALID_RESPONSE));
|
||
+ EXPECT_FALSE(buffer_.ResponseAvailable());
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_ctrl_response_fuzzer.cc b/net/ftp/ftp_ctrl_response_fuzzer.cc
|
||
new file mode 100644
|
||
index 0000000000000..f3a9ee3c6dfbb
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_ctrl_response_fuzzer.cc
|
||
@@ -0,0 +1,21 @@
|
||
+// Copyright 2015 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include <stddef.h>
|
||
+#include <stdint.h>
|
||
+
|
||
+#include "net/ftp/ftp_ctrl_response_buffer.h"
|
||
+
|
||
+// Entry point for LibFuzzer.
|
||
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||
+ const net::NetLogWithSource log;
|
||
+ net::FtpCtrlResponseBuffer buffer(log);
|
||
+ if (!buffer.ConsumeData(reinterpret_cast<const char*>(data), size)) {
|
||
+ return 0;
|
||
+ }
|
||
+ while (buffer.ResponseAvailable()) {
|
||
+ (void)buffer.PopResponse();
|
||
+ }
|
||
+ return 0;
|
||
+}
|
||
diff --git a/net/ftp/ftp_directory_listing_fuzzer.cc b/net/ftp/ftp_directory_listing_fuzzer.cc
|
||
new file mode 100644
|
||
index 0000000000000..459379e3eef89
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_fuzzer.cc
|
||
@@ -0,0 +1,20 @@
|
||
+// Copyright 2015 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include <stddef.h>
|
||
+#include <stdint.h>
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "base/time/time.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+
|
||
+// Entry point for LibFuzzer.
|
||
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||
+ std::string buffer(reinterpret_cast<const char*>(data), size);
|
||
+ std::vector<net::FtpDirectoryListingEntry> entries;
|
||
+ net::ParseFtpDirectoryListing(buffer, base::Time::Now(), &entries);
|
||
+ return 0;
|
||
+}
|
||
diff --git a/net/ftp/ftp_directory_listing_parser.cc b/net/ftp/ftp_directory_listing_parser.cc
|
||
new file mode 100644
|
||
index 0000000000000..57013a78c6e72
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser.cc
|
||
@@ -0,0 +1,131 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifdef UNSAFE_BUFFERS_BUILD
|
||
+// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
|
||
+#pragma allow_unsafe_buffers
|
||
+#endif
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+
|
||
+#include "base/functional/callback.h"
|
||
+#include "base/functional/bind.h"
|
||
+#include "base/i18n/encoding_detection.h"
|
||
+#include "base/i18n/icu_string_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
|
||
+#include "net/ftp/ftp_server_type.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+// Fills in |raw_name| for all |entries| using |encoding|. Returns network
|
||
+// error code.
|
||
+int FillInRawName(const std::string& encoding,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries) {
|
||
+ for (size_t i = 0; i < entries->size(); i++) {
|
||
+ if (!base::UTF16ToCodepage(entries->at(i).name, encoding.c_str(),
|
||
+ base::OnStringConversionError::SUBSTITUTE,
|
||
+ &entries->at(i).raw_name)) {
|
||
+ return ERR_ENCODING_CONVERSION_FAILED;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// Parses |text| as an FTP directory listing. Fills in |entries|
|
||
+// and |server_type| and returns network error code.
|
||
+int ParseListing(const std::u16string& text,
|
||
+ const std::u16string& newline_separator,
|
||
+ const std::string& encoding,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries,
|
||
+ FtpServerType* server_type) {
|
||
+ std::vector<std::u16string> lines = base::SplitStringUsingSubstr(
|
||
+ text, newline_separator, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+
|
||
+ struct {
|
||
+ base::OnceCallback<bool(void)> callback;
|
||
+ FtpServerType server_type;
|
||
+ } parsers[] = {
|
||
+ {
|
||
+ base::BindOnce(&ParseFtpDirectoryListingLs, lines, current_time, entries),
|
||
+ SERVER_LS
|
||
+ },
|
||
+ {
|
||
+ base::BindOnce(&ParseFtpDirectoryListingWindows, lines, entries),
|
||
+ SERVER_WINDOWS
|
||
+ },
|
||
+ {
|
||
+ base::BindOnce(&ParseFtpDirectoryListingVms, lines, entries),
|
||
+ SERVER_VMS
|
||
+ },
|
||
+ };
|
||
+
|
||
+ for (size_t i = 0; i < std::ranges::size(parsers); i++) {
|
||
+ entries->clear();
|
||
+ if (std::move(parsers[i].callback).Run()) {
|
||
+ *server_type = parsers[i].server_type;
|
||
+ return FillInRawName(encoding, entries);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ entries->clear();
|
||
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
|
||
+}
|
||
+
|
||
+// Detects encoding of |text| and parses it as an FTP directory listing.
|
||
+// Fills in |entries| and |server_type| and returns network error code.
|
||
+int DecodeAndParse(const std::string& text,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries,
|
||
+ FtpServerType* server_type) {
|
||
+ std::string encoding;
|
||
+ if (!base::DetectEncoding(text, &encoding))
|
||
+ return ERR_ENCODING_DETECTION_FAILED;
|
||
+ const char* encoding_name = encoding.c_str();
|
||
+
|
||
+ std::u16string converted_text;
|
||
+ if (base::CodepageToUTF16(text, encoding_name,
|
||
+ base::OnStringConversionError::SUBSTITUTE,
|
||
+ &converted_text)) {
|
||
+ const char* const kNewlineSeparators[] = {"\n", "\r\n"};
|
||
+
|
||
+ for (size_t j = 0; j < std::ranges::size(kNewlineSeparators); j++) {
|
||
+ int rv = ParseListing(converted_text,
|
||
+ base::ASCIIToUTF16(kNewlineSeparators[j]),
|
||
+ encoding_name, current_time, entries, server_type);
|
||
+ if (rv == OK)
|
||
+ return rv;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ entries->clear();
|
||
+ *server_type = SERVER_UNKNOWN;
|
||
+ return ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT;
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+FtpDirectoryListingEntry::FtpDirectoryListingEntry()
|
||
+ : type(UNKNOWN),
|
||
+ size(-1) {
|
||
+}
|
||
+
|
||
+int ParseFtpDirectoryListing(const std::string& text,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries) {
|
||
+ FtpServerType server_type = SERVER_UNKNOWN;
|
||
+ int rv = DecodeAndParse(text, current_time, entries, &server_type);
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser.h b/net/ftp/ftp_directory_listing_parser.h
|
||
new file mode 100644
|
||
index 0000000000000..f813bb04e5209
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser.h
|
||
@@ -0,0 +1,46 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
|
||
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
|
||
+
|
||
+#include <stdint.h>
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "base/time/time.h"
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+struct FtpDirectoryListingEntry {
|
||
+ enum Type {
|
||
+ UNKNOWN,
|
||
+ FILE,
|
||
+ DIRECTORY,
|
||
+ SYMLINK,
|
||
+ };
|
||
+
|
||
+ FtpDirectoryListingEntry();
|
||
+
|
||
+ Type type;
|
||
+ std::u16string name; // Name (UTF-16-encoded).
|
||
+ std::string raw_name; // Name in original character encoding.
|
||
+ int64_t size; // File size, in bytes. -1 if not applicable.
|
||
+
|
||
+ // Last modified time, in local time zone.
|
||
+ base::Time last_modified;
|
||
+};
|
||
+
|
||
+// Parses an FTP directory listing |text|. On success fills in |entries|.
|
||
+// Returns network error code.
|
||
+NET_EXPORT int ParseFtpDirectoryListing(
|
||
+ const std::string& text,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries);
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_H_
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_ls.cc b/net/ftp/ftp_directory_listing_parser_ls.cc
|
||
new file mode 100644
|
||
index 0000000000000..5388146beff3c
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_ls.cc
|
||
@@ -0,0 +1,227 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
|
||
+
|
||
+#include <vector>
|
||
+
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "net/ftp/ftp_util.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+bool TwoColumnDateListingToTime(const std::u16string& date,
|
||
+ const std::u16string& time,
|
||
+ base::Time* result) {
|
||
+ base::Time::Exploded time_exploded = { 0 };
|
||
+
|
||
+ // Date should be in format YYYY-MM-DD.
|
||
+ std::vector<std::u16string> date_parts = base::SplitString(
|
||
+ date, u"-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (date_parts.size() != 3)
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[0], &time_exploded.year))
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[1], &time_exploded.month))
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[2], &time_exploded.day_of_month))
|
||
+ return false;
|
||
+
|
||
+ // Time should be in format HH:MM
|
||
+ if (time.length() != 5)
|
||
+ return false;
|
||
+
|
||
+ std::vector<std::u16string> time_parts = base::SplitString(
|
||
+ time, u":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (time_parts.size() != 2)
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
|
||
+ return false;
|
||
+ if (!time_exploded.HasValidValues())
|
||
+ return false;
|
||
+
|
||
+ // We don't know the time zone of the server, so just use UTC.
|
||
+ return base::Time::FromUTCExploded(time_exploded, result);
|
||
+}
|
||
+
|
||
+// Returns the column index of the end of the date listing and detected
|
||
+// last modification time.
|
||
+bool DetectColumnOffsetSizeAndModificationTime(
|
||
+ const std::vector<std::u16string>& columns,
|
||
+ const base::Time& current_time,
|
||
+ size_t* offset,
|
||
+ std::u16string* size,
|
||
+ base::Time* modification_time) {
|
||
+ // The column offset can be arbitrarily large if some fields
|
||
+ // like owner or group name contain spaces. Try offsets from left to right
|
||
+ // and use the first one that matches a date listing.
|
||
+ //
|
||
+ // Here is how a listing line should look like. A star ("*") indicates
|
||
+ // a required field:
|
||
+ //
|
||
+ // * 1. permission listing
|
||
+ // 2. number of links (optional)
|
||
+ // * 3. owner name (may contain spaces)
|
||
+ // 4. group name (optional, may contain spaces)
|
||
+ // * 5. size in bytes
|
||
+ // * 6. month
|
||
+ // * 7. day of month
|
||
+ // * 8. year or time <-- column_offset will be the index of this column
|
||
+ // 9. file name (optional, may contain spaces)
|
||
+ for (size_t i = 5U; i < columns.size(); i++) {
|
||
+ if (FtpUtil::LsDateListingToTime(columns[i - 2], columns[i - 1], columns[i],
|
||
+ current_time, modification_time)) {
|
||
+ *size = columns[i - 3];
|
||
+ *offset = i;
|
||
+ return true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Some FTP listings have swapped the "month" and "day of month" columns
|
||
+ // (for example Russian listings). We try to recognize them only after making
|
||
+ // sure no column offset works above (this is a more strict way).
|
||
+ for (size_t i = 5U; i < columns.size(); i++) {
|
||
+ if (FtpUtil::LsDateListingToTime(columns[i - 1], columns[i - 2], columns[i],
|
||
+ current_time, modification_time)) {
|
||
+ *size = columns[i - 3];
|
||
+ *offset = i;
|
||
+ return true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Some FTP listings use a different date format.
|
||
+ for (size_t i = 5U; i < columns.size(); i++) {
|
||
+ if (TwoColumnDateListingToTime(columns[i - 1],
|
||
+ columns[i],
|
||
+ modification_time)) {
|
||
+ *size = columns[i - 2];
|
||
+ *offset = i;
|
||
+ return true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return false;
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+bool ParseFtpDirectoryListingLs(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries) {
|
||
+ // True after we have received a "total n" listing header, where n is an
|
||
+ // integer. Only one such header is allowed per listing.
|
||
+ bool received_total_line = false;
|
||
+
|
||
+ for (size_t i = 0; i < lines.size(); i++) {
|
||
+ if (lines[i].empty())
|
||
+ continue;
|
||
+
|
||
+ std::vector<std::u16string> columns =
|
||
+ base::SplitString(base::CollapseWhitespace(lines[i], false), u" ",
|
||
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+
|
||
+ // Some FTP servers put a "total n" line at the beginning of the listing
|
||
+ // (n is an integer). Allow such a line, but only once, and only if it's
|
||
+ // the first non-empty line. Do not match the word exactly, because it may
|
||
+ // be in different languages (at least English and German have been seen
|
||
+ // in the field).
|
||
+ if (columns.size() == 2 && !received_total_line) {
|
||
+ received_total_line = true;
|
||
+
|
||
+ // Some FTP servers incorrectly return a negative integer for "n". Since
|
||
+ // this value is ignored anyway, just check any valid integer was
|
||
+ // provided.
|
||
+ int64_t total_number;
|
||
+ if (!base::StringToInt64(columns[1], &total_number))
|
||
+ return false;
|
||
+
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ FtpDirectoryListingEntry entry;
|
||
+
|
||
+ size_t column_offset;
|
||
+ std::u16string size;
|
||
+ if (!DetectColumnOffsetSizeAndModificationTime(columns,
|
||
+ current_time,
|
||
+ &column_offset,
|
||
+ &size,
|
||
+ &entry.last_modified)) {
|
||
+ // Some servers send a message in one of the first few lines.
|
||
+ // All those messages have in common is the string ".:",
|
||
+ // where "." means the current directory, and ":" separates it
|
||
+ // from the rest of the message, which may be empty.
|
||
+ if (lines[i].find(u".:") != std::u16string::npos)
|
||
+ continue;
|
||
+
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ // Do not check "validity" of the permission listing. It's quirky,
|
||
+ // and some servers send garbage here while other parts of the line are OK.
|
||
+
|
||
+ if (!columns[0].empty() && columns[0][0] == 'l') {
|
||
+ entry.type = FtpDirectoryListingEntry::SYMLINK;
|
||
+ } else if (!columns[0].empty() && columns[0][0] == 'd') {
|
||
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
|
||
+ } else {
|
||
+ entry.type = FtpDirectoryListingEntry::FILE;
|
||
+ }
|
||
+
|
||
+ if (!base::StringToInt64(size, &entry.size)) {
|
||
+ // Some FTP servers do not separate owning group name from file size,
|
||
+ // like "group1234". We still want to display the file name for that
|
||
+ // entry, but can't really get the size (What if the group is named
|
||
+ // "group1", and the size is in fact 234? We can't distinguish between
|
||
+ // that and "group" with size 1234). Use a dummy value for the size.
|
||
+ entry.size = -1;
|
||
+ }
|
||
+ if (entry.size < 0) {
|
||
+ // Some FTP servers have bugs that cause them to display the file size
|
||
+ // as negative. They're most likely big files like DVD ISO images.
|
||
+ // We still want to display them, so just say the real file size
|
||
+ // is unknown.
|
||
+ entry.size = -1;
|
||
+ }
|
||
+ if (entry.type != FtpDirectoryListingEntry::FILE)
|
||
+ entry.size = -1;
|
||
+
|
||
+ if (column_offset == columns.size() - 1) {
|
||
+ // If the end of the date listing is the last column, there is no file
|
||
+ // name. Some FTP servers send listing entries with empty names.
|
||
+ // It's not obvious how to display such an entry, so we ignore them.
|
||
+ // We don't want to make the parsing fail at this point though.
|
||
+ // Other entries can still be useful.
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i],
|
||
+ column_offset + 1);
|
||
+
|
||
+ if (entry.type == FtpDirectoryListingEntry::SYMLINK) {
|
||
+ std::u16string::size_type pos = entry.name.rfind(u" -> ");
|
||
+
|
||
+ // We don't require the " -> " to be present. Some FTP servers don't send
|
||
+ // the symlink target, possibly for security reasons.
|
||
+ if (pos != std::u16string::npos)
|
||
+ entry.name = entry.name.substr(0, pos);
|
||
+ }
|
||
+
|
||
+ entries->push_back(entry);
|
||
+ }
|
||
+
|
||
+ return true;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_ls.h b/net/ftp/ftp_directory_listing_parser_ls.h
|
||
new file mode 100644
|
||
index 0000000000000..42ec4fc6a3a27
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_ls.h
|
||
@@ -0,0 +1,29 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
|
||
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace base {
|
||
+class Time;
|
||
+}
|
||
+
|
||
+namespace net {
|
||
+
|
||
+struct FtpDirectoryListingEntry;
|
||
+
|
||
+// Parses "ls -l" FTP directory listing. Returns true on success.
|
||
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingLs(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ const base::Time& current_time,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries);
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_LS_H_
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_ls_unittest.cc b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..d64a564b413e0
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_ls_unittest.cc
|
||
@@ -0,0 +1,222 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_unittest.h"
|
||
+
|
||
+#include "base/cxx17_backports.h"
|
||
+#include "base/format_macros.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_ls.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserLsTest;
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserLsTest, Good) {
|
||
+ const struct SingleLineTestData good_cases[] = {
|
||
+ { "-rw-r--r-- 1 ftp ftp 528 Nov 01 2007 README",
|
||
+ FtpDirectoryListingEntry::FILE, "README", 528,
|
||
+ 2007, 11, 1, 0, 0 },
|
||
+ { "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11 directory",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "directory", -1,
|
||
+ 1994, 5, 15, 18, 11 },
|
||
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
|
||
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
|
||
+ 2008, 9, 18, 0, 0 },
|
||
+ { "lrwxrwxrwx 1 0 0 3 Oct 12 13:37 mirror -> pub",
|
||
+ FtpDirectoryListingEntry::SYMLINK, "mirror", -1,
|
||
+ 1994, 10, 12, 13, 37 },
|
||
+ { "drwxrwsr-x 4 501 501 4096 Feb 20 2007 pub",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
|
||
+ 2007, 2, 20, 0, 0 },
|
||
+ { "drwxr-xr-x 4 (?) (?) 4096 Apr 8 2007 jigdo",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
|
||
+ 2007, 4, 8, 0, 0 },
|
||
+ { "drwx-wx-wt 2 root wheel 512 Jul 1 02:15 incoming",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
|
||
+ 1994, 7, 1, 2, 15 },
|
||
+ { "-rw-r--r-- 1 2 3 3447432 May 18 2009 Foo - Manual.pdf",
|
||
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
|
||
+ 2009, 5, 18, 0, 0 },
|
||
+ { "d-wx-wx-wt+ 4 ftp 989 512 Dec 8 15:54 incoming",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "incoming", -1,
|
||
+ 1993, 12, 8, 15, 54 },
|
||
+ { "drwxrwxrwx 1 owner group 1024 Sep 13 0:30 audio",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "audio", -1,
|
||
+ 1994, 9, 13, 0, 30 },
|
||
+ { "lrwxrwxrwx 1 0 0 26 Sep 18 2008 pub",
|
||
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
|
||
+ 2008, 9, 18, 0, 0 },
|
||
+ { "-rw-r--r-- 1 ftp ftp -528 Nov 01 2007 README",
|
||
+ FtpDirectoryListingEntry::FILE, "README", -1,
|
||
+ 2007, 11, 1, 0, 0 },
|
||
+
|
||
+ // Tests for the wu-ftpd variant:
|
||
+ { "drwxr-xr-x 2 sys 512 Mar 27 2009 pub",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "pub", -1,
|
||
+ 2009, 3, 27, 0, 0 },
|
||
+ { "lrwxrwxrwx 0 0 26 Sep 18 2008 pub -> vol/1/.CLUSTER/var_ftp/pub",
|
||
+ FtpDirectoryListingEntry::SYMLINK, "pub", -1,
|
||
+ 2008, 9, 18, 0, 0 },
|
||
+ { "drwxr-xr-x (?) (?) 4096 Apr 8 2007 jigdo",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "jigdo", -1,
|
||
+ 2007, 4, 8, 0, 0 },
|
||
+ { "-rw-r--r-- 2 3 3447432 May 18 2009 Foo - Manual.pdf",
|
||
+ FtpDirectoryListingEntry::FILE, "Foo - Manual.pdf", 3447432,
|
||
+ 2009, 5, 18, 0, 0 },
|
||
+
|
||
+ // Tests for "ls -l" style listings sent by an OS/2 server (FtpServer):
|
||
+ { "-r--r--r-- 1 ftp -A--- 13274 Mar 1 2006 UpTime.exe",
|
||
+ FtpDirectoryListingEntry::FILE, "UpTime.exe", 13274,
|
||
+ 2006, 3, 1, 0, 0 },
|
||
+ { "dr--r--r-- 1 ftp ----- 0 Nov 17 17:08 kernels",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "kernels", -1,
|
||
+ 1993, 11, 17, 17, 8 },
|
||
+
|
||
+ // Tests for "ls -l" style listing sent by Xplain FTP Server.
|
||
+ { "drwxr-xr-x folder 0 Jul 17 2006 online",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "online", -1,
|
||
+ 2006, 7, 17, 0, 0 },
|
||
+
|
||
+ // Tests for "ls -l" style listing with owning group name
|
||
+ // not separated from file size (http://crbug.com/58963).
|
||
+ { "-rw-r--r-- 1 ftpadmin ftpadmin125435904 Apr 9 2008 .pureftpd-upload",
|
||
+ FtpDirectoryListingEntry::FILE, ".pureftpd-upload", -1,
|
||
+ 2008, 4, 9, 0, 0 },
|
||
+
|
||
+ // Tests for "ls -l" style listing with number of links
|
||
+ // not separated from permission listing (http://crbug.com/70394).
|
||
+ { "drwxr-xr-x1732 266 111 90112 Jun 21 2001 .rda_2",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, ".rda_2", -1,
|
||
+ 2001, 6, 21, 0, 0 },
|
||
+
|
||
+ // Tests for "ls -l" style listing with group name containing spaces.
|
||
+ { "drwxrwxr-x 3 %%%% Domain Users 4096 Dec 9 2009 %%%%%",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "%%%%%", -1,
|
||
+ 2009, 12, 9, 0, 0 },
|
||
+
|
||
+ // Tests for "ls -l" style listing in Russian locale (note the swapped
|
||
+ // parts order: the day of month is the first, before month).
|
||
+ { "-rwxrwxr-x 1 ftp ftp 123 23 \xd0\xbc\xd0\xb0\xd0\xb9 2011 test",
|
||
+ FtpDirectoryListingEntry::FILE, "test", 123,
|
||
+ 2011, 5, 23, 0, 0 },
|
||
+ { "drwxrwxr-x 1 ftp ftp 4096 19 \xd0\xbe\xd0\xba\xd1\x82 2011 dir",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "dir", -1,
|
||
+ 2011, 10, 19, 0, 0 },
|
||
+
|
||
+ // Plan9 sends entry type "a" for append-only files.
|
||
+ { "ar-xr-xr-x 2 none none 512 Apr 26 17:52 plan9",
|
||
+ FtpDirectoryListingEntry::FILE, "plan9", 512,
|
||
+ 1994, 4, 26, 17, 52 },
|
||
+
|
||
+ // Hylafax sends a shorter permission listing.
|
||
+ { "drwxrwx 2 10 4096 Jul 28 02:41 tmp",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "tmp", -1,
|
||
+ 1994, 7, 28, 2, 41 },
|
||
+
|
||
+ // Completely different date format (YYYY-MM-DD).
|
||
+ { "drwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "notas_servico", -1,
|
||
+ 2012, 2, 7, 0, 31 },
|
||
+ { "-rwxrwxrwx 2 root root 4096 2012-02-07 00:31 notas_servico",
|
||
+ FtpDirectoryListingEntry::FILE, "notas_servico", 4096,
|
||
+ 2012, 2, 7, 0, 31 },
|
||
+
|
||
+ // Weird permission bits.
|
||
+ { "drwx--l--- 2 0 10 512 Dec 22 1994 swetzel",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "swetzel", -1,
|
||
+ 1994, 12, 22, 0, 0 },
|
||
+
|
||
+ { "drwxrwxr-x 1 500 244 660 Jan 1 00:0 bin",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "bin", -1,
|
||
+ 1994, 1, 1, 0, 0 },
|
||
+
|
||
+ // Garbage in date (but still parseable).
|
||
+ { "lrw-rw-rw- 1 user group 542 "
|
||
+ "/t11/member/incomingFeb 8 2007 "
|
||
+ "Shortcut to incoming.lnk -> /t11/member/incoming",
|
||
+ FtpDirectoryListingEntry::SYMLINK, "Shortcut to incoming.lnk", -1,
|
||
+ 2007, 2, 8, 0, 0 },
|
||
+
|
||
+ // Garbage in permissions (with no effect on other bits).
|
||
+ // Also test multiple "columns" resulting from the garbage.
|
||
+ { "garbage 1 ftp ftp 528 Nov 01 2007 README",
|
||
+ FtpDirectoryListingEntry::FILE, "README", 528,
|
||
+ 2007, 11, 1, 0, 0 },
|
||
+ { "gar bage 1 ftp ftp 528 Nov 01 2007 README",
|
||
+ FtpDirectoryListingEntry::FILE, "README", 528,
|
||
+ 2007, 11, 1, 0, 0 },
|
||
+ { "g a r b a g e 1 ftp ftp 528 Nov 01 2007 README",
|
||
+ FtpDirectoryListingEntry::FILE, "README", 528,
|
||
+ 2007, 11, 1, 0, 0 },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(good_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ good_cases[i].input));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
|
||
+ GetSingleLineTestCase(good_cases[i].input),
|
||
+ GetMockCurrentTime(),
|
||
+ &entries));
|
||
+ VerifySingleLineTestCase(good_cases[i], entries);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserLsTest, Ignored) {
|
||
+ const char* const ignored_cases[] = {
|
||
+ "drwxr-xr-x 2 0 0 4096 Mar 18 2007 ", // http://crbug.com/60065
|
||
+
|
||
+ "ftpd: .: Permission denied",
|
||
+ "ftpd-BSD: .: Permission denied",
|
||
+ "ls: .: EDC5111I Permission denied.",
|
||
+
|
||
+ // Tests important for security: verify that after we detect the column
|
||
+ // offset we don't try to access invalid memory on malformed input.
|
||
+ "drwxr-xr-x 3 ftp ftp 4096 May 15 18:11",
|
||
+ "drwxr-xr-x 3 ftp 4096 May 15 18:11",
|
||
+ "drwxr-xr-x folder 0 May 15 18:11",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(ignored_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ ignored_cases[i]));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingLs(
|
||
+ GetSingleLineTestCase(ignored_cases[i]),
|
||
+ GetMockCurrentTime(),
|
||
+ &entries));
|
||
+ EXPECT_EQ(0U, entries.size());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserLsTest, Bad) {
|
||
+ const char* const bad_cases[] = {
|
||
+ " foo",
|
||
+ "garbage",
|
||
+ "-rw-r--r-- ftp ftp",
|
||
+ "-rw-r--r-- ftp ftp 528 Foo 01 2007 README",
|
||
+ "-rw-r--r-- 1 ftp ftp",
|
||
+ "-rw-r--r-- 1 ftp ftp 528 Foo 01 2007 README",
|
||
+
|
||
+ // Invalid month value (30).
|
||
+ "drwxrwxrwx 2 root root 4096 2012-30-07 00:31 notas_servico",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(bad_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ bad_cases[i]));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingLs(GetSingleLineTestCase(bad_cases[i]),
|
||
+ GetMockCurrentTime(),
|
||
+ &entries));
|
||
+ }
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_unittest.cc b/net/ftp/ftp_directory_listing_parser_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..de08f99478562
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_unittest.cc
|
||
@@ -0,0 +1,186 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+
|
||
+#include "base/files/file_util.h"
|
||
+#include "base/format_macros.h"
|
||
+#include "base/path_service.h"
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+struct FtpTestParam {
|
||
+ const char* name;
|
||
+ Error expected_result;
|
||
+};
|
||
+
|
||
+std::string TestName(testing::TestParamInfo<FtpTestParam> info) {
|
||
+ std::string result;
|
||
+ base::ReplaceChars(info.param.name, "-", "_", &result);
|
||
+ return result;
|
||
+}
|
||
+
|
||
+class FtpDirectoryListingParserTest
|
||
+ : public testing::TestWithParam<FtpTestParam> {};
|
||
+
|
||
+TEST_P(FtpDirectoryListingParserTest, Parse) {
|
||
+ FtpTestParam param = GetParam();
|
||
+ base::FilePath test_dir;
|
||
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &test_dir);
|
||
+ test_dir = test_dir.AppendASCII("net");
|
||
+ test_dir = test_dir.AppendASCII("data");
|
||
+ test_dir = test_dir.AppendASCII("ftp");
|
||
+
|
||
+ base::Time::Exploded mock_current_time_exploded = { 0 };
|
||
+ mock_current_time_exploded.year = 1994;
|
||
+ mock_current_time_exploded.month = 11;
|
||
+ mock_current_time_exploded.day_of_month = 15;
|
||
+ mock_current_time_exploded.hour = 12;
|
||
+ mock_current_time_exploded.minute = 45;
|
||
+ base::Time mock_current_time;
|
||
+ EXPECT_TRUE(base::Time::FromUTCExploded(mock_current_time_exploded,
|
||
+ &mock_current_time));
|
||
+
|
||
+ SCOPED_TRACE(base::StringPrintf("Test case: %s", param.name));
|
||
+
|
||
+ std::string test_listing;
|
||
+ EXPECT_TRUE(
|
||
+ base::ReadFileToString(test_dir.AppendASCII(param.name), &test_listing));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_EQ(
|
||
+ param.expected_result,
|
||
+ ParseFtpDirectoryListing(test_listing, mock_current_time, &entries));
|
||
+ if (param.expected_result != OK)
|
||
+ return;
|
||
+
|
||
+ std::string expected_listing;
|
||
+ ASSERT_TRUE(base::ReadFileToString(
|
||
+ test_dir.AppendASCII(std::string(param.name) + ".expected"),
|
||
+ &expected_listing));
|
||
+
|
||
+ std::vector<std::string> lines = base::SplitStringUsingSubstr(
|
||
+ expected_listing, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+
|
||
+ // Special case for empty listings.
|
||
+ if (lines.size() == 1 && lines[0].empty())
|
||
+ lines.clear();
|
||
+
|
||
+ ASSERT_EQ(9 * entries.size(), lines.size());
|
||
+
|
||
+ for (size_t i = 0; i < lines.size() / 9; i++) {
|
||
+ std::string type(lines[9 * i]);
|
||
+ std::string name(lines[9 * i + 1]);
|
||
+ int64_t size;
|
||
+ base::StringToInt64(lines[9 * i + 2], &size);
|
||
+
|
||
+ SCOPED_TRACE(base::StringPrintf("Filename: %s", name.c_str()));
|
||
+
|
||
+ int year, month, day_of_month, hour, minute;
|
||
+ base::StringToInt(lines[9 * i + 3], &year);
|
||
+ base::StringToInt(lines[9 * i + 4], &month);
|
||
+ base::StringToInt(lines[9 * i + 5], &day_of_month);
|
||
+ base::StringToInt(lines[9 * i + 6], &hour);
|
||
+ base::StringToInt(lines[9 * i + 7], &minute);
|
||
+
|
||
+ const FtpDirectoryListingEntry& entry = entries[i];
|
||
+
|
||
+ if (type == "d") {
|
||
+ EXPECT_EQ(FtpDirectoryListingEntry::DIRECTORY, entry.type);
|
||
+ } else if (type == "-") {
|
||
+ EXPECT_EQ(FtpDirectoryListingEntry::FILE, entry.type);
|
||
+ } else if (type == "l") {
|
||
+ EXPECT_EQ(FtpDirectoryListingEntry::SYMLINK, entry.type);
|
||
+ } else {
|
||
+ ADD_FAILURE() << "invalid gold test data: " << type;
|
||
+ }
|
||
+
|
||
+ EXPECT_EQ(base::UTF8ToUTF16(name), entry.name);
|
||
+ EXPECT_EQ(size, entry.size);
|
||
+
|
||
+ base::Time::Exploded time_exploded;
|
||
+ entry.last_modified.UTCExplode(&time_exploded);
|
||
+ EXPECT_EQ(year, time_exploded.year);
|
||
+ EXPECT_EQ(month, time_exploded.month);
|
||
+ EXPECT_EQ(day_of_month, time_exploded.day_of_month);
|
||
+ EXPECT_EQ(hour, time_exploded.hour);
|
||
+ EXPECT_EQ(minute, time_exploded.minute);
|
||
+ }
|
||
+}
|
||
+
|
||
+const FtpTestParam kTestParams[] = {
|
||
+ {"dir-listing-ls-1", OK},
|
||
+ {"dir-listing-ls-1-utf8", OK},
|
||
+ {"dir-listing-ls-2", OK},
|
||
+ {"dir-listing-ls-3", OK},
|
||
+ {"dir-listing-ls-4", OK},
|
||
+ {"dir-listing-ls-5", OK},
|
||
+ {"dir-listing-ls-6", OK},
|
||
+ {"dir-listing-ls-7", OK},
|
||
+ {"dir-listing-ls-8", OK},
|
||
+ {"dir-listing-ls-9", OK},
|
||
+ {"dir-listing-ls-10", OK},
|
||
+ {"dir-listing-ls-11", OK},
|
||
+ {"dir-listing-ls-12", OK},
|
||
+ {"dir-listing-ls-13", OK},
|
||
+ {"dir-listing-ls-14", OK},
|
||
+ {"dir-listing-ls-15", OK},
|
||
+ {"dir-listing-ls-16", OK},
|
||
+ {"dir-listing-ls-17", OK},
|
||
+ {"dir-listing-ls-18", OK},
|
||
+ {"dir-listing-ls-19", OK},
|
||
+ {"dir-listing-ls-20", OK},
|
||
+ {"dir-listing-ls-21", OK},
|
||
+ {"dir-listing-ls-22", OK},
|
||
+ {"dir-listing-ls-23", OK},
|
||
+ {"dir-listing-ls-24", OK},
|
||
+
|
||
+ // Tests for Russian listings. The only difference between those
|
||
+ // files is character encoding:
|
||
+ {"dir-listing-ls-25", OK}, // UTF-8
|
||
+ {"dir-listing-ls-26", OK}, // KOI8-R
|
||
+ {"dir-listing-ls-27", OK}, // windows-1251
|
||
+
|
||
+ {"dir-listing-ls-28", OK}, // Hylafax FTP server
|
||
+ {"dir-listing-ls-29", OK},
|
||
+ {"dir-listing-ls-30", OK},
|
||
+ {"dir-listing-ls-31", OK},
|
||
+ {"dir-listing-ls-32", OK}, // busybox
|
||
+ {"dir-listing-ls-33", OK},
|
||
+ {"dir-listing-ls-34", OK}, // Broken encoding. Should not fail.
|
||
+
|
||
+ {"dir-listing-netware-1", OK},
|
||
+ {"dir-listing-netware-2", OK},
|
||
+ {"dir-listing-netware-3", OK},
|
||
+ {"dir-listing-os2-1", ERR_UNRECOGNIZED_FTP_DIRECTORY_LISTING_FORMAT},
|
||
+ {"dir-listing-vms-1", OK},
|
||
+ {"dir-listing-vms-2", OK},
|
||
+ {"dir-listing-vms-3", OK},
|
||
+ {"dir-listing-vms-4", OK},
|
||
+ {"dir-listing-vms-5", OK},
|
||
+ {"dir-listing-vms-6", OK},
|
||
+ {"dir-listing-vms-7", OK},
|
||
+ {"dir-listing-vms-8", OK},
|
||
+ {"dir-listing-windows-1", OK},
|
||
+ {"dir-listing-windows-2", OK},
|
||
+};
|
||
+
|
||
+INSTANTIATE_TEST_SUITE_P(All,
|
||
+ FtpDirectoryListingParserTest,
|
||
+ testing::ValuesIn(kTestParams),
|
||
+ TestName);
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_unittest.h b/net/ftp/ftp_directory_listing_parser_unittest.h
|
||
new file mode 100644
|
||
index 0000000000000..9dfc44acfd999
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_unittest.h
|
||
@@ -0,0 +1,81 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
|
||
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
|
||
+
|
||
+#include <stdint.h>
|
||
+
|
||
+#include <vector>
|
||
+
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpDirectoryListingParserTest : public testing::Test {
|
||
+ public:
|
||
+ struct SingleLineTestData {
|
||
+ const char* input;
|
||
+ FtpDirectoryListingEntry::Type type;
|
||
+ const char* filename;
|
||
+ int64_t size;
|
||
+ int year;
|
||
+ int month;
|
||
+ int day_of_month;
|
||
+ int hour;
|
||
+ int minute;
|
||
+ };
|
||
+
|
||
+ protected:
|
||
+ FtpDirectoryListingParserTest() {}
|
||
+
|
||
+ std::vector<std::u16string> GetSingleLineTestCase(const std::string& text) {
|
||
+ std::vector<std::u16string> lines;
|
||
+ lines.push_back(base::UTF8ToUTF16(text));
|
||
+ return lines;
|
||
+ }
|
||
+
|
||
+ void VerifySingleLineTestCase(
|
||
+ const SingleLineTestData& test_case,
|
||
+ const std::vector<FtpDirectoryListingEntry>& entries) {
|
||
+ ASSERT_FALSE(entries.empty());
|
||
+
|
||
+ FtpDirectoryListingEntry entry = entries[0];
|
||
+ EXPECT_EQ(test_case.type, entry.type);
|
||
+ EXPECT_EQ(base::UTF8ToUTF16(test_case.filename), entry.name);
|
||
+ EXPECT_EQ(test_case.size, entry.size);
|
||
+
|
||
+ base::Time::Exploded time_exploded;
|
||
+ entry.last_modified.UTCExplode(&time_exploded);
|
||
+
|
||
+ // Only test members displayed on the directory listing.
|
||
+ EXPECT_EQ(test_case.year, time_exploded.year);
|
||
+ EXPECT_EQ(test_case.month, time_exploded.month);
|
||
+ EXPECT_EQ(test_case.day_of_month, time_exploded.day_of_month);
|
||
+ EXPECT_EQ(test_case.hour, time_exploded.hour);
|
||
+ EXPECT_EQ(test_case.minute, time_exploded.minute);
|
||
+
|
||
+ EXPECT_EQ(1U, entries.size());
|
||
+ }
|
||
+
|
||
+ base::Time GetMockCurrentTime() {
|
||
+ base::Time::Exploded mock_current_time_exploded = { 0 };
|
||
+ mock_current_time_exploded.year = 1994;
|
||
+ mock_current_time_exploded.month = 11;
|
||
+ mock_current_time_exploded.day_of_month = 15;
|
||
+ mock_current_time_exploded.hour = 12;
|
||
+ mock_current_time_exploded.minute = 45;
|
||
+
|
||
+ base::Time out_time;
|
||
+ EXPECT_TRUE(
|
||
+ base::Time::FromUTCExploded(mock_current_time_exploded, &out_time));
|
||
+ return out_time;
|
||
+ }
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_UNITTEST_H_
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_vms.cc b/net/ftp/ftp_directory_listing_parser_vms.cc
|
||
new file mode 100644
|
||
index 0000000000000..7d0ca2a045f75
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_vms.cc
|
||
@@ -0,0 +1,313 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifdef UNSAFE_BUFFERS_BUILD
|
||
+// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
|
||
+#pragma allow_unsafe_buffers
|
||
+#endif
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
|
||
+
|
||
+#include <vector>
|
||
+
|
||
+#include "base/numerics/safe_math.h"
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "net/ftp/ftp_util.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+// Converts the filename component in listing to the filename we can display.
|
||
+// Returns true on success.
|
||
+bool ParseVmsFilename(const std::u16string& raw_filename,
|
||
+ std::u16string* parsed_filename,
|
||
+ FtpDirectoryListingEntry::Type* type) {
|
||
+ // On VMS, the files and directories are versioned. The version number is
|
||
+ // separated from the file name by a semicolon. Example: ANNOUNCE.TXT;2.
|
||
+ std::vector<std::u16string> listing_parts = base::SplitString(
|
||
+ raw_filename, u";", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (listing_parts.size() != 2)
|
||
+ return false;
|
||
+ int version_number;
|
||
+ if (!base::StringToInt(listing_parts[1], &version_number))
|
||
+ return false;
|
||
+ if (version_number < 0)
|
||
+ return false;
|
||
+
|
||
+ // Even directories have extensions in the listings. Don't display extensions
|
||
+ // for directories; it's awkward for non-VMS users. Also, VMS is
|
||
+ // case-insensitive, but generally uses uppercase characters. This may look
|
||
+ // awkward, so we convert them to lower case.
|
||
+ std::vector<std::u16string> filename_parts = base::SplitString(
|
||
+ listing_parts[0], u".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (filename_parts.size() != 2)
|
||
+ return false;
|
||
+ if (base::EqualsASCII(filename_parts[1], "DIR")) {
|
||
+ *parsed_filename = base::ToLowerASCII(filename_parts[0]);
|
||
+ *type = FtpDirectoryListingEntry::DIRECTORY;
|
||
+ } else {
|
||
+ *parsed_filename = base::ToLowerASCII(listing_parts[0]);
|
||
+ *type = FtpDirectoryListingEntry::FILE;
|
||
+ }
|
||
+ return true;
|
||
+}
|
||
+
|
||
+// VMS's directory listing gives file size in blocks. The exact file size is
|
||
+// unknown both because it is measured in blocks, but also because the block
|
||
+// size is unknown (but assumed to be 512 bytes).
|
||
+bool ApproximateFilesizeFromBlockCount(int64_t num_blocks, int64_t* out_size) {
|
||
+ if (num_blocks < 0)
|
||
+ return false;
|
||
+
|
||
+ const int kBlockSize = 512;
|
||
+ base::CheckedNumeric<int64_t> num_bytes = num_blocks;
|
||
+ num_bytes *= kBlockSize;
|
||
+
|
||
+ if (!num_bytes.IsValid())
|
||
+ return false; // Block count is too large.
|
||
+
|
||
+ *out_size = num_bytes.ValueOrDie();
|
||
+ return true;
|
||
+}
|
||
+
|
||
+bool ParseVmsFilesize(const std::u16string& input, int64_t* size) {
|
||
+ if (base::ContainsOnlyChars(input, u"*")) {
|
||
+ // Response consisting of asterisks means unknown size.
|
||
+ *size = -1;
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ int64_t num_blocks;
|
||
+ if (base::StringToInt64(input, &num_blocks))
|
||
+ return ApproximateFilesizeFromBlockCount(num_blocks, size);
|
||
+
|
||
+ std::vector<std::u16string_view> parts = base::SplitStringPiece(
|
||
+ input, u"/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (parts.size() != 2)
|
||
+ return false;
|
||
+
|
||
+ int64_t blocks_used, blocks_allocated;
|
||
+ if (!base::StringToInt64(parts[0], &blocks_used))
|
||
+ return false;
|
||
+ if (!base::StringToInt64(parts[1], &blocks_allocated))
|
||
+ return false;
|
||
+ if (blocks_used > blocks_allocated)
|
||
+ return false;
|
||
+ if (blocks_used < 0 || blocks_allocated < 0)
|
||
+ return false;
|
||
+
|
||
+ return ApproximateFilesizeFromBlockCount(blocks_used, size);
|
||
+}
|
||
+
|
||
+bool LooksLikeVmsFileProtectionListingPart(const std::u16string& input) {
|
||
+ if (input.length() > 4)
|
||
+ return false;
|
||
+
|
||
+ // On VMS there are four different permission bits: Read, Write, Execute,
|
||
+ // and Delete. They appear in that order in the permission listing.
|
||
+ std::string pattern("RWED");
|
||
+ std::u16string match(input);
|
||
+ while (!match.empty() && !pattern.empty()) {
|
||
+ if (match[0] == pattern[0])
|
||
+ match = match.substr(1);
|
||
+ pattern = pattern.substr(1);
|
||
+ }
|
||
+ return match.empty();
|
||
+}
|
||
+
|
||
+bool LooksLikeVmsFileProtectionListing(const std::u16string& input) {
|
||
+ if (input.length() < 2)
|
||
+ return false;
|
||
+ if (input.front() != '(' || input.back() != ')')
|
||
+ return false;
|
||
+
|
||
+ // We expect four parts of the file protection listing: for System, Owner,
|
||
+ // Group, and World.
|
||
+ std::vector<std::u16string> parts = base::SplitString(
|
||
+ std::u16string_view(input).substr(1, input.length() - 2), u",",
|
||
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (parts.size() != 4)
|
||
+ return false;
|
||
+
|
||
+ return LooksLikeVmsFileProtectionListingPart(parts[0]) &&
|
||
+ LooksLikeVmsFileProtectionListingPart(parts[1]) &&
|
||
+ LooksLikeVmsFileProtectionListingPart(parts[2]) &&
|
||
+ LooksLikeVmsFileProtectionListingPart(parts[3]);
|
||
+}
|
||
+
|
||
+bool LooksLikeVmsUserIdentificationCode(const std::u16string& input) {
|
||
+ if (input.length() < 2)
|
||
+ return false;
|
||
+ return input.front() == '[' && input.back() == ']';
|
||
+}
|
||
+
|
||
+bool LooksLikeVMSError(const std::u16string& text) {
|
||
+ static const char* const kPermissionDeniedMessages[] = {
|
||
+ "%RMS-E-FNF", // File not found.
|
||
+ "%RMS-E-PRV", // Access denied.
|
||
+ "%SYSTEM-F-NOPRIV",
|
||
+ "privilege",
|
||
+ };
|
||
+
|
||
+ for (size_t i = 0; i < std::ranges::size(kPermissionDeniedMessages); i++) {
|
||
+ if (text.find(base::ASCIIToUTF16(kPermissionDeniedMessages[i])) !=
|
||
+ std::u16string::npos)
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ return false;
|
||
+}
|
||
+
|
||
+bool VmsDateListingToTime(const std::vector<std::u16string>& columns,
|
||
+ base::Time* time) {
|
||
+ DCHECK_EQ(4U, columns.size());
|
||
+
|
||
+ base::Time::Exploded time_exploded = { 0 };
|
||
+
|
||
+ // Date should be in format DD-MMM-YYYY.
|
||
+ std::vector<std::u16string_view> date_parts = base::SplitStringPiece(
|
||
+ columns[2], u"-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (date_parts.size() != 3)
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[0], &time_exploded.day_of_month))
|
||
+ return false;
|
||
+ if (!FtpUtil::AbbreviatedMonthToNumber(std::u16string(date_parts[1]),
|
||
+ &time_exploded.month))
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
|
||
+ return false;
|
||
+
|
||
+ // Time can be in format HH:MM, HH:MM:SS, or HH:MM:SS.mm. Try to recognize the
|
||
+ // last type first. Do not parse the seconds, they will be ignored anyway.
|
||
+ std::u16string time_column(columns[3]);
|
||
+ if (time_column.length() == 11 && time_column[8] == '.')
|
||
+ time_column = time_column.substr(0, 8);
|
||
+ if (time_column.length() == 8 && time_column[5] == ':')
|
||
+ time_column = time_column.substr(0, 5);
|
||
+ if (time_column.length() != 5)
|
||
+ return false;
|
||
+ std::vector<std::u16string_view> time_parts = base::SplitStringPiece(
|
||
+ time_column, u":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (time_parts.size() != 2)
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
|
||
+ return false;
|
||
+
|
||
+ // We don't know the time zone of the server, so just use UTC.
|
||
+ return base::Time::FromUTCExploded(time_exploded, time);
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+bool ParseFtpDirectoryListingVms(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries) {
|
||
+ // The first non-empty line is the listing header. It often
|
||
+ // starts with "Directory ", but not always. We set a flag after
|
||
+ // seing the header.
|
||
+ bool seen_header = false;
|
||
+
|
||
+ // Sometimes the listing doesn't end with a "Total" line, but
|
||
+ // it's only okay when it contains some errors (it's needed
|
||
+ // to distinguish it from "ls -l" format).
|
||
+ bool seen_error = false;
|
||
+
|
||
+ std::u16string total_of = u"Total of ";
|
||
+ char16_t space[2] = {' ', 0};
|
||
+ for (size_t i = 0; i < lines.size(); i++) {
|
||
+ if (lines[i].empty())
|
||
+ continue;
|
||
+
|
||
+ if (base::StartsWith(lines[i], total_of, base::CompareCase::SENSITIVE)) {
|
||
+ // After the "total" line, all following lines must be empty.
|
||
+ for (size_t j = i + 1; j < lines.size(); j++)
|
||
+ if (!lines[j].empty())
|
||
+ return false;
|
||
+
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ if (!seen_header) {
|
||
+ seen_header = true;
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ if (LooksLikeVMSError(lines[i])) {
|
||
+ seen_error = true;
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ std::vector<std::u16string> columns =
|
||
+ base::SplitString(base::CollapseWhitespace(lines[i], false), space,
|
||
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+
|
||
+ if (columns.size() == 1) {
|
||
+ // There can be no continuation if the current line is the last one.
|
||
+ if (i == lines.size() - 1)
|
||
+ return false;
|
||
+
|
||
+ // Skip the next line.
|
||
+ i++;
|
||
+
|
||
+ // This refers to the continuation line.
|
||
+ if (LooksLikeVMSError(lines[i])) {
|
||
+ seen_error = true;
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ // Join the current and next line and split them into columns.
|
||
+ columns = base::SplitString(
|
||
+ base::CollapseWhitespace(
|
||
+ lines[i - 1] + space + lines[i], false),
|
||
+ space, base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ }
|
||
+
|
||
+ if (columns.empty())
|
||
+ return false;
|
||
+
|
||
+ FtpDirectoryListingEntry entry;
|
||
+ if (!ParseVmsFilename(columns[0], &entry.name, &entry.type))
|
||
+ return false;
|
||
+
|
||
+ // There are different variants of a VMS listing. Some display
|
||
+ // the protection listing and user identification code, some do not.
|
||
+ if (columns.size() == 6) {
|
||
+ if (!LooksLikeVmsFileProtectionListing(columns[5]))
|
||
+ return false;
|
||
+ if (!LooksLikeVmsUserIdentificationCode(columns[4]))
|
||
+ return false;
|
||
+
|
||
+ // Drop the unneeded data, so that the following code can always expect
|
||
+ // just four columns.
|
||
+ columns.resize(4);
|
||
+ }
|
||
+
|
||
+ if (columns.size() != 4)
|
||
+ return false;
|
||
+
|
||
+ if (!ParseVmsFilesize(columns[1], &entry.size))
|
||
+ return false;
|
||
+ if (entry.type != FtpDirectoryListingEntry::FILE)
|
||
+ entry.size = -1;
|
||
+ if (!VmsDateListingToTime(columns, &entry.last_modified))
|
||
+ return false;
|
||
+
|
||
+ entries->push_back(entry);
|
||
+ }
|
||
+
|
||
+ // The only place where we return true is after receiving the "Total" line,
|
||
+ // that should be present in every VMS listing. Alternatively, if the listing
|
||
+ // contains error messages, it's OK not to have the "Total" line.
|
||
+ return seen_error;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_vms.h b/net/ftp/ftp_directory_listing_parser_vms.h
|
||
new file mode 100644
|
||
index 0000000000000..e59a679303e23
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_vms.h
|
||
@@ -0,0 +1,24 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
|
||
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+struct FtpDirectoryListingEntry;
|
||
+
|
||
+// Parses VMS FTP directory listing. Returns true on success.
|
||
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingVms(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries);
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_VMS_H_
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_vms_unittest.cc b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..88deedd5e2d28
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_vms_unittest.cc
|
||
@@ -0,0 +1,197 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_unittest.h"
|
||
+
|
||
+#include "base/cxx17_backports.h"
|
||
+#include "base/format_macros.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_vms.h"
|
||
+
|
||
+using base::ASCIIToUTF16;
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserVmsTest;
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserVmsTest, Good) {
|
||
+ const struct SingleLineTestData good_cases[] = {
|
||
+ {"README.TXT;4 2 18-APR-2000 10:40:39.90",
|
||
+ FtpDirectoryListingEntry::FILE, "readme.txt", 1024, 2000, 4, 18, 10, 40},
|
||
+ {".WELCOME;1 2 13-FEB-2002 23:32:40.47",
|
||
+ FtpDirectoryListingEntry::FILE, ".welcome", 1024, 2002, 2, 13, 23, 32},
|
||
+ {"FILE.;1 2 13-FEB-2002 23:32:40.47", FtpDirectoryListingEntry::FILE,
|
||
+ "file.", 1024, 2002, 2, 13, 23, 32},
|
||
+ {"EXAMPLE.TXT;1 1 4-NOV-2009 06:02 [JOHNDOE] (RWED,RWED,,)",
|
||
+ FtpDirectoryListingEntry::FILE, "example.txt", 512, 2009, 11, 4, 6, 2},
|
||
+ {"ANNOUNCE.TXT;2 1/16 12-MAR-2005 08:44:57 [SYSTEM] (RWED,RWED,RE,RE)",
|
||
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512, 2005, 3, 12, 8, 44},
|
||
+ {"TEST.DIR;1 1 4-MAR-1999 22:14:34 [UCX$NOBO,ANONYMOUS] "
|
||
+ "(RWE,RWE,RWE,RWE)",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "test", -1, 1999, 3, 4, 22, 14},
|
||
+ {"ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (,,,)",
|
||
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512, 2005, 3, 12, 8, 44},
|
||
+ {"ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (R,RW,RWD,RE)",
|
||
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512, 2005, 3, 12, 8, 44},
|
||
+ {"ANNOUNCE.TXT;2 1 12-MAR-2005 08:44:57 [X] (ED,RED,WD,WED)",
|
||
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512, 2005, 3, 12, 8, 44},
|
||
+ {"VMS721.ISO;2 ****** 6-MAY-2008 09:29 [ANONY,ANONYMOUS] "
|
||
+ "(RE,RWED,RE,RE)",
|
||
+ FtpDirectoryListingEntry::FILE, "vms721.iso", -1, 2008, 5, 6, 9, 29},
|
||
+ // This has an unusually large allocated block size (INT64_MAX), but
|
||
+ // shouldn't matter as it is not used.
|
||
+ {"ANNOUNCE.TXT;2 1/9223372036854775807 12-MAR-2005 08:44:57 [SYSTEM] "
|
||
+ "(RWED,RWED,RE,RE)",
|
||
+ FtpDirectoryListingEntry::FILE, "announce.txt", 512, 2005, 3, 12, 8, 44},
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(good_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ good_cases[i].input));
|
||
+
|
||
+ std::vector<std::u16string> lines(
|
||
+ GetSingleLineTestCase(good_cases[i].input));
|
||
+
|
||
+ // The parser requires a directory header before accepting regular input.
|
||
+ lines.insert(lines.begin(), u"Directory ANONYMOUS_ROOT:[000000]");
|
||
+
|
||
+ // A valid listing must also have a "Total" line at the end.
|
||
+ lines.insert(lines.end(), u"Total of 1 file, 2 blocks.");
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
|
||
+ &entries));
|
||
+ VerifySingleLineTestCase(good_cases[i], entries);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserVmsTest, Bad) {
|
||
+ const char* const bad_cases[] = {
|
||
+ "garbage",
|
||
+
|
||
+ // Missing file version number.
|
||
+ "README.TXT 2 18-APR-2000 10:40:39",
|
||
+
|
||
+ // Missing extension.
|
||
+ "README;1 2 18-APR-2000 10:40:39",
|
||
+
|
||
+ // Malformed file size.
|
||
+ "README.TXT;1 garbage 18-APR-2000 10:40:39",
|
||
+ "README.TXT;1 -2 18-APR-2000 10:40:39",
|
||
+
|
||
+ // Malformed date.
|
||
+ "README.TXT;1 2 APR-2000 10:40:39",
|
||
+ "README.TXT;1 2 -18-APR-2000 10:40:39", "README.TXT;1 2 18-APR 10:40:39",
|
||
+ "README.TXT;1 2 18-APR-2000 10", "README.TXT;1 2 18-APR-2000 10:40.25",
|
||
+ "README.TXT;1 2 18-APR-2000 10:40.25.25",
|
||
+
|
||
+ // Malformed security information.
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (RWED,RWED,RE,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM]",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 (SYSTEM) (RWED,RWED,RE,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [SYSTEM] [RWED,RWED,RE,RE]",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,RE,RE,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWEDRWED,RE,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,DEWR,RE,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RWED,Q,RE)",
|
||
+ "X.TXT;2 1 12-MAR-2005 08:44:57 [X] (RWED,RRWWEEDD,RE,RE)",
|
||
+
|
||
+ // Block size (INT64_MAX) is too large -- will overflow when
|
||
+ // multiplying by 512 to calculate the file size in bytes.
|
||
+ "README.TXT;1 9223372036854775807 18-APR-2000 10:40:39.90",
|
||
+ "README.TXT;1 9223372036854775807/9223372036854775807 18-APR-2000 "
|
||
+ "10:40:39.90",
|
||
+
|
||
+ // Block size (larger than INT64_MAX) is too large -- will fail to
|
||
+ // parse to an int64_t
|
||
+ "README.TXT;1 19223372036854775807 18-APR-2000 10:40:39.90",
|
||
+ "README.TXT;1 19223372036854775807/19223372036854775807 18-APR-2000 "
|
||
+ "10:40:39.90",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(bad_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
|
||
+
|
||
+ std::vector<std::u16string> lines(GetSingleLineTestCase(bad_cases[i]));
|
||
+
|
||
+ // The parser requires a directory header before accepting regular input.
|
||
+ lines.insert(lines.begin(), u"Directory ANONYMOUS_ROOT:[000000]");
|
||
+
|
||
+ // A valid listing must also have a "Total" line at the end.
|
||
+ lines.insert(lines.end(), u"Total of 1 file, 2 blocks.");
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
|
||
+ &entries));
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserVmsTest, BadDataAfterFooter) {
|
||
+ const char* const bad_cases[] = {
|
||
+ "garbage",
|
||
+ "Total of 1 file, 2 blocks.",
|
||
+ "Directory ANYNYMOUS_ROOT:[000000]",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(bad_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i, bad_cases[i]));
|
||
+
|
||
+ std::vector<std::u16string> lines(
|
||
+ GetSingleLineTestCase("README.TXT;4 2 18-APR-2000 10:40:39.90"));
|
||
+
|
||
+ // The parser requires a directory header before accepting regular input.
|
||
+ lines.insert(lines.begin(), u"Directory ANONYMOUS_ROOT:[000000]");
|
||
+
|
||
+ // A valid listing must also have a "Total" line at the end.
|
||
+ lines.insert(lines.end(), u"Total of 1 file, 2 blocks.");
|
||
+
|
||
+ {
|
||
+ // Make sure the listing is valid before we add data after footer.
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingVms(lines,
|
||
+ &entries));
|
||
+ }
|
||
+
|
||
+ {
|
||
+ // Insert a line at the end of the listing that should make it invalid.
|
||
+ lines.insert(lines.end(),
|
||
+ ASCIIToUTF16(bad_cases[i]));
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines,
|
||
+ &entries));
|
||
+ }
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserVmsTest, EmptyColumnZero) {
|
||
+ std::vector<std::u16string> lines;
|
||
+
|
||
+ // The parser requires a directory header before accepting regular input.
|
||
+ lines.push_back(u"garbage");
|
||
+
|
||
+ char16_t data[] = {0x0};
|
||
+ lines.push_back(std::u16string(data, 1));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines, &entries));
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserVmsTest, EmptyColumnWhitespace) {
|
||
+ std::vector<std::u16string> lines;
|
||
+
|
||
+ // The parser requires a directory header before accepting regular input.
|
||
+ lines.push_back(u"garbage");
|
||
+
|
||
+ lines.push_back(u" ");
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingVms(lines, &entries));
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_windows.cc b/net/ftp/ftp_directory_listing_parser_windows.cc
|
||
new file mode 100644
|
||
index 0000000000000..0d7906f5d68db
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_windows.cc
|
||
@@ -0,0 +1,74 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
|
||
+
|
||
+#include <vector>
|
||
+
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "net/ftp/ftp_util.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+bool ParseFtpDirectoryListingWindows(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries) {
|
||
+ for (size_t i = 0; i < lines.size(); i++) {
|
||
+ if (lines[i].empty())
|
||
+ continue;
|
||
+
|
||
+ std::vector<std::u16string> columns =
|
||
+ base::SplitString(base::CollapseWhitespace(lines[i], false), u" ",
|
||
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+
|
||
+ // Every line of the listing consists of the following:
|
||
+ //
|
||
+ // 1. date
|
||
+ // 2. time
|
||
+ // 3. size in bytes (or "<DIR>" for directories)
|
||
+ // 4. filename (may be empty or contain spaces)
|
||
+ //
|
||
+ // For now, make sure we have 1-3, and handle 4 later.
|
||
+ if (columns.size() < 3)
|
||
+ return false;
|
||
+
|
||
+ FtpDirectoryListingEntry entry;
|
||
+ if (base::EqualsASCII(columns[2], "<DIR>")) {
|
||
+ entry.type = FtpDirectoryListingEntry::DIRECTORY;
|
||
+ entry.size = -1;
|
||
+ } else {
|
||
+ entry.type = FtpDirectoryListingEntry::FILE;
|
||
+ if (!base::StringToInt64(columns[2], &entry.size))
|
||
+ return false;
|
||
+ if (entry.size < 0)
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ if (!FtpUtil::WindowsDateListingToTime(columns[0],
|
||
+ columns[1],
|
||
+ &entry.last_modified)) {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ entry.name = FtpUtil::GetStringPartAfterColumns(lines[i], 3);
|
||
+ if (entry.name.empty()) {
|
||
+ // Some FTP servers send listing entries with empty names.
|
||
+ // It's not obvious how to display such an entry, so ignore them.
|
||
+ // We don't want to make the parsing fail at this point though.
|
||
+ // Other entries can still be useful.
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ entries->push_back(entry);
|
||
+ }
|
||
+
|
||
+ return true;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_windows.h b/net/ftp/ftp_directory_listing_parser_windows.h
|
||
new file mode 100644
|
||
index 0000000000000..39aefe2929328
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_windows.h
|
||
@@ -0,0 +1,24 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
|
||
+#define NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+struct FtpDirectoryListingEntry;
|
||
+
|
||
+// Parses Windows FTP directory listing. Returns true on success.
|
||
+NET_EXPORT_PRIVATE bool ParseFtpDirectoryListingWindows(
|
||
+ const std::vector<std::u16string>& lines,
|
||
+ std::vector<FtpDirectoryListingEntry>* entries);
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_DIRECTORY_LISTING_PARSER_WINDOWS_H_
|
||
diff --git a/net/ftp/ftp_directory_listing_parser_windows_unittest.cc b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..08b01b88f6017
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_directory_listing_parser_windows_unittest.cc
|
||
@@ -0,0 +1,128 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_directory_listing_parser_unittest.h"
|
||
+
|
||
+#include "base/cxx17_backports.h"
|
||
+#include "base/format_macros.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser_windows.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+typedef FtpDirectoryListingParserTest FtpDirectoryListingParserWindowsTest;
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserWindowsTest, Good) {
|
||
+ const struct SingleLineTestData good_cases[] = {
|
||
+ { "11-02-09 05:32PM <DIR> NT",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "NT", -1,
|
||
+ 2009, 11, 2, 17, 32 },
|
||
+ { "01-06-09 02:42PM 458 Readme.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
|
||
+ 2009, 1, 6, 14, 42 },
|
||
+ { "01-06-09 02:42AM 1 Readme.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 1,
|
||
+ 2009, 1, 6, 2, 42 },
|
||
+ { "01-06-01 02:42AM 458 Readme.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
|
||
+ 2001, 1, 6, 2, 42 },
|
||
+ { "01-06-00 02:42AM 458 Corner1.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Corner1.txt", 458,
|
||
+ 2000, 1, 6, 2, 42 },
|
||
+ { "01-06-99 02:42AM 458 Corner2.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Corner2.txt", 458,
|
||
+ 1999, 1, 6, 2, 42 },
|
||
+ { "01-06-80 02:42AM 458 Corner3.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Corner3.txt", 458,
|
||
+ 1980, 1, 6, 2, 42 },
|
||
+#if !defined(OS_LINUX) && !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
|
||
+ // TODO(phajdan.jr): https://crbug.com/28792: Re-enable when 2038-year
|
||
+ // problem is fixed on Linux.
|
||
+ { "01-06-79 02:42AM 458 Corner4",
|
||
+ FtpDirectoryListingEntry::FILE, "Corner4", 458,
|
||
+ 2079, 1, 6, 2, 42 },
|
||
+#endif // !defined (OS_LINUX) && !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
|
||
+ { "01-06-1979 02:42AM 458 Readme.txt",
|
||
+ FtpDirectoryListingEntry::FILE, "Readme.txt", 458,
|
||
+ 1979, 1, 6, 2, 42 },
|
||
+ { "11-02-09 05:32PM <DIR> My Directory",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "My Directory", -1,
|
||
+ 2009, 11, 2, 17, 32 },
|
||
+ { "12-25-10 12:00AM <DIR> Christmas Midnight",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midnight", -1,
|
||
+ 2010, 12, 25, 0, 0 },
|
||
+ { "12-25-10 12:00PM <DIR> Christmas Midday",
|
||
+ FtpDirectoryListingEntry::DIRECTORY, "Christmas Midday", -1,
|
||
+ 2010, 12, 25, 12, 0 },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(good_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ good_cases[i].input));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
|
||
+ GetSingleLineTestCase(good_cases[i].input),
|
||
+ &entries));
|
||
+ VerifySingleLineTestCase(good_cases[i], entries);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserWindowsTest, Ignored) {
|
||
+ const char* const ignored_cases[] = {
|
||
+ "12-07-10 12:05AM <DIR> ", // http://crbug.com/66097
|
||
+ "12-07-10 12:05AM 1234 ",
|
||
+ "11-02-09 05:32 <DIR>",
|
||
+ "11-02-09 05:32PM <DIR>",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(ignored_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ ignored_cases[i]));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_TRUE(ParseFtpDirectoryListingWindows(
|
||
+ GetSingleLineTestCase(ignored_cases[i]),
|
||
+ &entries));
|
||
+ EXPECT_EQ(0U, entries.size());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(FtpDirectoryListingParserWindowsTest, Bad) {
|
||
+ const char* const bad_cases[] = {
|
||
+ "garbage",
|
||
+ "11-02-09 05:32PM <GARBAGE>",
|
||
+ "11-02-09 05:32PM <GARBAGE> NT",
|
||
+ "11-FEB-09 05:32PM <DIR>",
|
||
+ "11-02 05:32PM <DIR>",
|
||
+ "11-02-09 05:32PM -1",
|
||
+ "11-FEB-09 05:32PM <DIR> NT",
|
||
+ "11-02 05:32PM <DIR> NT",
|
||
+ "11-02-09 05:32PM -1 NT",
|
||
+ "99-25-10 12:00AM 0",
|
||
+ "12-99-10 12:00AM 0",
|
||
+ "12-25-10 99:00AM 0",
|
||
+ "12-25-10 12:99AM 0",
|
||
+ "12-25-10 12:00ZM 0",
|
||
+ "99-25-10 12:00AM 0 months out of range",
|
||
+ "12-99-10 12:00AM 0 days out of range",
|
||
+ "12-25-10 99:00AM 0 hours out of range",
|
||
+ "12-25-10 12:99AM 0 minutes out of range",
|
||
+ "12-25-10 12:00ZM 0 what does ZM mean",
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(bad_cases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s", i,
|
||
+ bad_cases[i]));
|
||
+
|
||
+ std::vector<FtpDirectoryListingEntry> entries;
|
||
+ EXPECT_FALSE(ParseFtpDirectoryListingWindows(
|
||
+ GetSingleLineTestCase(bad_cases[i]),
|
||
+ &entries));
|
||
+ }
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_network_layer.cc b/net/ftp/ftp_network_layer.cc
|
||
new file mode 100644
|
||
index 0000000000000..8a467f8896487
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_layer.cc
|
||
@@ -0,0 +1,34 @@
|
||
+// Copyright (c) 2008 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_network_layer.h"
|
||
+
|
||
+#include "base/check.h"
|
||
+#include "net/ftp/ftp_network_session.h"
|
||
+#include "net/ftp/ftp_network_transaction.h"
|
||
+#include "net/socket/client_socket_factory.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+FtpNetworkLayer::FtpNetworkLayer(HostResolver* host_resolver)
|
||
+ : session_(new FtpNetworkSession(host_resolver)),
|
||
+ suspended_(false) {
|
||
+ DCHECK(host_resolver);
|
||
+}
|
||
+
|
||
+FtpNetworkLayer::~FtpNetworkLayer() = default;
|
||
+
|
||
+std::unique_ptr<FtpTransaction> FtpNetworkLayer::CreateTransaction() {
|
||
+ if (suspended_)
|
||
+ return nullptr;
|
||
+
|
||
+ return std::make_unique<FtpNetworkTransaction>(
|
||
+ session_->host_resolver(), ClientSocketFactory::GetDefaultFactory());
|
||
+}
|
||
+
|
||
+void FtpNetworkLayer::Suspend(bool suspend) {
|
||
+ suspended_ = suspend;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_network_layer.h b/net/ftp/ftp_network_layer.h
|
||
new file mode 100644
|
||
index 0000000000000..59c4d2b04bdf3
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_layer.h
|
||
@@ -0,0 +1,38 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_NETWORK_LAYER_H_
|
||
+#define NET_FTP_FTP_NETWORK_LAYER_H_
|
||
+
|
||
+#include <memory>
|
||
+
|
||
+#include "base/compiler_specific.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/ftp/ftp_transaction_factory.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpNetworkSession;
|
||
+class HostResolver;
|
||
+
|
||
+class NET_EXPORT FtpNetworkLayer : public FtpTransactionFactory {
|
||
+ public:
|
||
+ explicit FtpNetworkLayer(HostResolver* host_resolver);
|
||
+ ~FtpNetworkLayer() override;
|
||
+ FtpNetworkLayer(const FtpNetworkLayer&) = delete;
|
||
+ FtpNetworkLayer& operator=(const FtpNetworkLayer&) =
|
||
+ delete;
|
||
+
|
||
+ // FtpTransactionFactory methods:
|
||
+ std::unique_ptr<FtpTransaction> CreateTransaction() override;
|
||
+ void Suspend(bool suspend) override;
|
||
+
|
||
+ private:
|
||
+ std::unique_ptr<FtpNetworkSession> session_;
|
||
+ bool suspended_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_NETWORK_LAYER_H_
|
||
diff --git a/net/ftp/ftp_network_session.cc b/net/ftp/ftp_network_session.cc
|
||
new file mode 100644
|
||
index 0000000000000..9de9796b08bf3
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_session.cc
|
||
@@ -0,0 +1,14 @@
|
||
+// Copyright (c) 2010 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_network_session.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+FtpNetworkSession::FtpNetworkSession(HostResolver* host_resolver)
|
||
+ : host_resolver_(host_resolver) {}
|
||
+
|
||
+FtpNetworkSession::~FtpNetworkSession() = default;
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_network_session.h b/net/ftp/ftp_network_session.h
|
||
new file mode 100644
|
||
index 0000000000000..0ef5b781eec4e
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_session.h
|
||
@@ -0,0 +1,29 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_NETWORK_SESSION_H_
|
||
+#define NET_FTP_FTP_NETWORK_SESSION_H_
|
||
+
|
||
+#include "base/memory/raw_ptr.h"
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class HostResolver;
|
||
+
|
||
+// This class holds session objects used by FtpNetworkTransaction objects.
|
||
+class NET_EXPORT_PRIVATE FtpNetworkSession {
|
||
+ public:
|
||
+ explicit FtpNetworkSession(HostResolver* host_resolver);
|
||
+ virtual ~FtpNetworkSession();
|
||
+
|
||
+ HostResolver* host_resolver() { return host_resolver_; }
|
||
+
|
||
+ private:
|
||
+ raw_ptr<HostResolver> const host_resolver_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_NETWORK_SESSION_H_
|
||
diff --git a/net/ftp/ftp_network_transaction.cc b/net/ftp/ftp_network_transaction.cc
|
||
new file mode 100644
|
||
index 0000000000000..85053f403f224
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_transaction.cc
|
||
@@ -0,0 +1,1298 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifdef UNSAFE_BUFFERS_BUILD
|
||
+// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
|
||
+#pragma allow_unsafe_buffers
|
||
+#endif
|
||
+
|
||
+#include "net/ftp/ftp_network_transaction.h"
|
||
+
|
||
+#include <vector>
|
||
+
|
||
+#include "base/functional/bind.h"
|
||
+#include "base/functional/callback_helpers.h"
|
||
+#include "base/compiler_specific.h"
|
||
+#include "base/memory/scoped_refptr.h"
|
||
+#include "base/strings/escape.h"
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/values.h"
|
||
+#include "net/base/address_list.h"
|
||
+#include "net/base/completion_once_callback.h"
|
||
+#include "net/base/ip_endpoint.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/base/network_isolation_key.h"
|
||
+#include "net/base/parse_number.h"
|
||
+#include "net/base/port_util.h"
|
||
+#include "net/base/url_util.h"
|
||
+#include "net/ftp/ftp_request_info.h"
|
||
+#include "net/ftp/ftp_util.h"
|
||
+#include "net/log/net_log.h"
|
||
+#include "net/log/net_log_event_type.h"
|
||
+#include "net/log/net_log_source.h"
|
||
+#include "net/socket/client_socket_factory.h"
|
||
+#include "net/socket/stream_socket.h"
|
||
+#include "url/scheme_host_port.h"
|
||
+#include "url/url_constants.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+const char kCRLF[] = "\r\n";
|
||
+
|
||
+const int kCtrlBufLen = 1024;
|
||
+
|
||
+// Returns true if |input| can be safely used as a part of an FTP command.
|
||
+bool IsValidFTPCommandSubstring(const std::string& input) {
|
||
+ // RFC 959 only allows ASCII strings, but at least Firefox can send non-ASCII
|
||
+ // characters in the command if the request path contains them. To be
|
||
+ // compatible, we do the same and allow non-ASCII characters in a command.
|
||
+
|
||
+ // Protect agains newline injection attack.
|
||
+ if (input.find_first_of("\r\n") != std::string::npos)
|
||
+ return false;
|
||
+
|
||
+ return true;
|
||
+}
|
||
+
|
||
+enum ErrorClass {
|
||
+ // The requested action was initiated. The client should expect another
|
||
+ // reply before issuing the next command.
|
||
+ ERROR_CLASS_INITIATED,
|
||
+
|
||
+ // The requested action has been successfully completed.
|
||
+ ERROR_CLASS_OK,
|
||
+
|
||
+ // The command has been accepted, but to complete the operation, more
|
||
+ // information must be sent by the client.
|
||
+ ERROR_CLASS_INFO_NEEDED,
|
||
+
|
||
+ // The command was not accepted and the requested action did not take place.
|
||
+ // This condition is temporary, and the client is encouraged to restart the
|
||
+ // command sequence.
|
||
+ ERROR_CLASS_TRANSIENT_ERROR,
|
||
+
|
||
+ // The command was not accepted and the requested action did not take place.
|
||
+ // This condition is rather permanent, and the client is discouraged from
|
||
+ // repeating the exact request.
|
||
+ ERROR_CLASS_PERMANENT_ERROR,
|
||
+};
|
||
+
|
||
+// Returns the error class for given response code. Caller should ensure
|
||
+// that |response_code| is in range 100-599.
|
||
+ErrorClass GetErrorClass(int response_code) {
|
||
+ if (response_code >= 100 && response_code <= 199)
|
||
+ return ERROR_CLASS_INITIATED;
|
||
+
|
||
+ if (response_code >= 200 && response_code <= 299)
|
||
+ return ERROR_CLASS_OK;
|
||
+
|
||
+ if (response_code >= 300 && response_code <= 399)
|
||
+ return ERROR_CLASS_INFO_NEEDED;
|
||
+
|
||
+ if (response_code >= 400 && response_code <= 499)
|
||
+ return ERROR_CLASS_TRANSIENT_ERROR;
|
||
+
|
||
+ if (response_code >= 500 && response_code <= 599)
|
||
+ return ERROR_CLASS_PERMANENT_ERROR;
|
||
+
|
||
+ // We should not be called on invalid error codes.
|
||
+ NOTREACHED() << response_code;
|
||
+}
|
||
+
|
||
+// Returns network error code for received FTP |response_code|.
|
||
+int GetNetErrorCodeForFtpResponseCode(int response_code) {
|
||
+ switch (response_code) {
|
||
+ case 421:
|
||
+ return ERR_FTP_SERVICE_UNAVAILABLE;
|
||
+ case 426:
|
||
+ return ERR_FTP_TRANSFER_ABORTED;
|
||
+ case 450:
|
||
+ return ERR_FTP_FILE_BUSY;
|
||
+ case 500:
|
||
+ case 501:
|
||
+ return ERR_FTP_SYNTAX_ERROR;
|
||
+ case 502:
|
||
+ case 504:
|
||
+ return ERR_FTP_COMMAND_NOT_SUPPORTED;
|
||
+ case 503:
|
||
+ return ERR_FTP_BAD_COMMAND_SEQUENCE;
|
||
+ default:
|
||
+ return ERR_FTP_FAILED;
|
||
+ }
|
||
+}
|
||
+
|
||
+// From RFC 2428 Section 3:
|
||
+// The text returned in response to the EPSV command MUST be:
|
||
+// <some text> (<d><d><d><tcp-port><d>)
|
||
+// <d> is a delimiter character, ideally to be |
|
||
+bool ExtractPortFromEPSVResponse(const FtpCtrlResponse& response, int* port) {
|
||
+ if (response.lines.size() != 1)
|
||
+ return false;
|
||
+
|
||
+ std::string_view epsv_line(response.lines[0]);
|
||
+ size_t start = epsv_line.find('(');
|
||
+ // If the line doesn't have a '(' or doesn't have enough characters after the
|
||
+ // first '(', fail.
|
||
+ if (start == std::string_view::npos || epsv_line.length() - start < 7)
|
||
+ return false;
|
||
+
|
||
+ char separator = epsv_line[start + 1];
|
||
+
|
||
+ // Make sure we have "(<d><d><d>...", where <d> is not a number.
|
||
+ if ((separator >= '0' && separator <= '9') ||
|
||
+ epsv_line[start + 2] != separator || epsv_line[start + 3] != separator) {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ // Skip over those characters.
|
||
+ start += 4;
|
||
+
|
||
+ // Make sure there's a terminal <d>.
|
||
+ size_t end = epsv_line.find(separator, start);
|
||
+ if (end == std::string_view::npos)
|
||
+ return false;
|
||
+
|
||
+ return ParseInt32(epsv_line.substr(start, end - start),
|
||
+ ParseIntFormat::NON_NEGATIVE, port);
|
||
+}
|
||
+
|
||
+// There are two way we can receive IP address and port.
|
||
+// (127,0,0,1,23,21) IP address and port encapsulated in ().
|
||
+// 127,0,0,1,23,21 IP address and port without ().
|
||
+//
|
||
+// See RFC 959, Section 4.1.2
|
||
+bool ExtractPortFromPASVResponse(const FtpCtrlResponse& response, int* port) {
|
||
+ if (response.lines.size() != 1)
|
||
+ return false;
|
||
+
|
||
+ std::string line(response.lines[0]);
|
||
+ if (!base::IsStringASCII(line))
|
||
+ return false;
|
||
+ if (line.length() < 2)
|
||
+ return false;
|
||
+
|
||
+ size_t paren_pos = line.find('(');
|
||
+ if (paren_pos == std::string::npos) {
|
||
+ // Find the first comma and use it to locate the beginning
|
||
+ // of the response data.
|
||
+ size_t comma_pos = line.find(',');
|
||
+ if (comma_pos == std::string::npos)
|
||
+ return false;
|
||
+
|
||
+ size_t space_pos = line.rfind(' ', comma_pos);
|
||
+ if (space_pos != std::string::npos)
|
||
+ line = line.substr(space_pos + 1);
|
||
+ } else {
|
||
+ // Remove the parentheses and use the text inside them.
|
||
+ size_t closing_paren_pos = line.rfind(')');
|
||
+ if (closing_paren_pos == std::string::npos)
|
||
+ return false;
|
||
+ if (closing_paren_pos <= paren_pos)
|
||
+ return false;
|
||
+
|
||
+ line = line.substr(paren_pos + 1, closing_paren_pos - paren_pos - 1);
|
||
+ }
|
||
+
|
||
+ // Split the line into comma-separated pieces and extract
|
||
+ // the last two.
|
||
+ std::vector<std::string_view> pieces = base::SplitStringPiece(
|
||
+ line, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (pieces.size() != 6)
|
||
+ return false;
|
||
+
|
||
+ // Ignore the IP address supplied in the response. We are always going
|
||
+ // to connect back to the same server to prevent FTP PASV port scanning.
|
||
+ uint32_t p0, p1;
|
||
+ if (!ParseUint32(pieces[4], ParseIntFormat::NON_NEGATIVE, &p0))
|
||
+ return false;
|
||
+ if (!ParseUint32(pieces[5], ParseIntFormat::NON_NEGATIVE, &p1))
|
||
+ return false;
|
||
+ if (p0 > 0xFF || p1 > 0xFF)
|
||
+ return false;
|
||
+
|
||
+ *port = (p0 << 8) + p1;
|
||
+ return true;
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+FtpNetworkTransaction::FtpNetworkTransaction(
|
||
+ HostResolver* resolver,
|
||
+ ClientSocketFactory* socket_factory)
|
||
+ : command_sent_(COMMAND_NONE),
|
||
+ io_callback_(base::BindRepeating(&FtpNetworkTransaction::OnIOComplete,
|
||
+ base::Unretained(this))),
|
||
+ request_(nullptr),
|
||
+ resolver_(resolver),
|
||
+ read_ctrl_buf_(base::MakeRefCounted<IOBufferWithSize>(kCtrlBufLen)),
|
||
+ read_data_buf_len_(0),
|
||
+ last_error_(OK),
|
||
+ system_type_(SYSTEM_TYPE_UNKNOWN),
|
||
+ // Use image (binary) transfer by default. It should always work,
|
||
+ // whereas the ascii transfer may damage binary data.
|
||
+ data_type_(DATA_TYPE_IMAGE),
|
||
+ resource_type_(RESOURCE_TYPE_UNKNOWN),
|
||
+ use_epsv_(true),
|
||
+ data_connection_port_(0),
|
||
+ socket_factory_(socket_factory),
|
||
+ next_state_(STATE_NONE),
|
||
+ state_after_data_connect_complete_(STATE_NONE) {}
|
||
+
|
||
+FtpNetworkTransaction::~FtpNetworkTransaction() = default;
|
||
+
|
||
+int FtpNetworkTransaction::Stop(int error) {
|
||
+ if (command_sent_ == COMMAND_QUIT) {
|
||
+ if (error != ERR_EMPTY_RESPONSE)
|
||
+ return error;
|
||
+
|
||
+ // For empty responses, if this is propagating an error, then it will return
|
||
+ // the error. If the error occurred during a QUIT command, then this will
|
||
+ // return OK since there was no previous error. Some FTP servers are lazy
|
||
+ // and do not bother responding to QUIT commands.
|
||
+ // See https://crbug.com/633841
|
||
+ return last_error_;
|
||
+ }
|
||
+
|
||
+ next_state_ = STATE_CTRL_WRITE_QUIT;
|
||
+ last_error_ = error;
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::Start(
|
||
+ raw_ptr <FtpRequestInfo> request_info,
|
||
+ CompletionOnceCallback callback,
|
||
+ const NetLogWithSource& net_log,
|
||
+ const NetworkTrafficAnnotationTag& traffic_annotation) {
|
||
+ net_log_ = net_log;
|
||
+ request_ = request_info;
|
||
+ traffic_annotation_ = MutableNetworkTrafficAnnotationTag(traffic_annotation);
|
||
+
|
||
+ ctrl_response_buffer_ = std::make_unique<FtpCtrlResponseBuffer>(net_log_);
|
||
+
|
||
+ if (request_->url.has_username()) {
|
||
+ std::u16string username;
|
||
+ std::u16string password;
|
||
+ GetIdentityFromURL(request_->url, &username, &password);
|
||
+ credentials_.Set(username, password);
|
||
+ } else {
|
||
+ credentials_.Set(u"anonymous", u"chrome@example.com");
|
||
+ }
|
||
+
|
||
+ DetectTypecode();
|
||
+
|
||
+ if (request_->url.has_path()) {
|
||
+ std::string gurl_path(request_->url.path());
|
||
+
|
||
+ // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path.
|
||
+ std::string::size_type pos = gurl_path.rfind(';');
|
||
+ if (pos != std::string::npos)
|
||
+ gurl_path.resize(pos);
|
||
+
|
||
+ // This may unescape to non-ASCII characters, but we allow that. See the
|
||
+ // comment for IsValidFTPCommandSubstring.
|
||
+ if (!base::UnescapeBinaryURLComponentSafe(
|
||
+ gurl_path, true /* fail_on_path_separators*/, &unescaped_path_)) {
|
||
+ return ERR_INVALID_URL;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
|
||
+ int rv = DoLoop(OK);
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ user_callback_ = std::move(callback);
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials,
|
||
+ CompletionOnceCallback callback) {
|
||
+ ResetStateForRestart();
|
||
+
|
||
+ credentials_ = credentials;
|
||
+
|
||
+ next_state_ = STATE_CTRL_RESOLVE_HOST;
|
||
+ int rv = DoLoop(OK);
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ user_callback_ = std::move(callback);
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::Read(IOBuffer* buf,
|
||
+ int buf_len,
|
||
+ CompletionOnceCallback callback) {
|
||
+ DCHECK(buf);
|
||
+ DCHECK_GT(buf_len, 0);
|
||
+
|
||
+ read_data_buf_ = buf;
|
||
+ read_data_buf_len_ = buf_len;
|
||
+
|
||
+ next_state_ = STATE_DATA_READ;
|
||
+ int rv = DoLoop(OK);
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ user_callback_ = std::move(callback);
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+const FtpResponseInfo* FtpNetworkTransaction::GetResponseInfo() const {
|
||
+ return &response_;
|
||
+}
|
||
+
|
||
+LoadState FtpNetworkTransaction::GetLoadState() const {
|
||
+ if (next_state_ == STATE_CTRL_RESOLVE_HOST_COMPLETE)
|
||
+ return LOAD_STATE_RESOLVING_HOST;
|
||
+
|
||
+ if (next_state_ == STATE_CTRL_CONNECT_COMPLETE ||
|
||
+ next_state_ == STATE_DATA_CONNECT_COMPLETE)
|
||
+ return LOAD_STATE_CONNECTING;
|
||
+
|
||
+ if (next_state_ == STATE_DATA_READ_COMPLETE)
|
||
+ return LOAD_STATE_READING_RESPONSE;
|
||
+
|
||
+ if (command_sent_ == COMMAND_RETR && read_data_buf_.get())
|
||
+ return LOAD_STATE_READING_RESPONSE;
|
||
+
|
||
+ if (command_sent_ == COMMAND_QUIT)
|
||
+ return LOAD_STATE_IDLE;
|
||
+
|
||
+ if (command_sent_ != COMMAND_NONE)
|
||
+ return LOAD_STATE_SENDING_REQUEST;
|
||
+
|
||
+ return LOAD_STATE_IDLE;
|
||
+}
|
||
+
|
||
+uint64_t FtpNetworkTransaction::GetUploadProgress() const {
|
||
+ return 0;
|
||
+}
|
||
+
|
||
+void FtpNetworkTransaction::ResetStateForRestart() {
|
||
+ command_sent_ = COMMAND_NONE;
|
||
+ user_callback_.Reset();
|
||
+ response_ = FtpResponseInfo();
|
||
+ read_ctrl_buf_ = base::MakeRefCounted<IOBufferWithSize>(kCtrlBufLen);
|
||
+ ctrl_response_buffer_ = std::make_unique<FtpCtrlResponseBuffer>(net_log_);
|
||
+ read_data_buf_ = nullptr;
|
||
+ read_data_buf_len_ = 0;
|
||
+ if (write_buf_.get())
|
||
+ write_buf_->SetOffset(0);
|
||
+ last_error_ = OK;
|
||
+ data_connection_port_ = 0;
|
||
+ ctrl_socket_.reset();
|
||
+ data_socket_.reset();
|
||
+ next_state_ = STATE_NONE;
|
||
+ state_after_data_connect_complete_ = STATE_NONE;
|
||
+}
|
||
+
|
||
+void FtpNetworkTransaction::EstablishDataConnection(State state_after_connect) {
|
||
+ DCHECK(state_after_connect == STATE_CTRL_WRITE_RETR ||
|
||
+ state_after_connect == STATE_CTRL_WRITE_LIST);
|
||
+ state_after_data_connect_complete_ = state_after_connect;
|
||
+ next_state_ = use_epsv_ ? STATE_CTRL_WRITE_EPSV : STATE_CTRL_WRITE_PASV;
|
||
+}
|
||
+
|
||
+void FtpNetworkTransaction::DoCallback(int rv) {
|
||
+ DCHECK(rv != ERR_IO_PENDING);
|
||
+
|
||
+ std::move(user_callback_).Run(rv);
|
||
+}
|
||
+
|
||
+void FtpNetworkTransaction::OnIOComplete(int result) {
|
||
+ int rv = DoLoop(result);
|
||
+ if (rv != ERR_IO_PENDING)
|
||
+ DoCallback(rv);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessCtrlResponse() {
|
||
+ FtpCtrlResponse response = ctrl_response_buffer_->PopResponse();
|
||
+
|
||
+ int rv = OK;
|
||
+ switch (command_sent_) {
|
||
+ case COMMAND_NONE:
|
||
+ // TODO(phajdan.jr): https://crbug.com/526721: Check for errors in the
|
||
+ // welcome message.
|
||
+ next_state_ = STATE_CTRL_WRITE_USER;
|
||
+ break;
|
||
+ case COMMAND_USER:
|
||
+ rv = ProcessResponseUSER(response);
|
||
+ break;
|
||
+ case COMMAND_PASS:
|
||
+ rv = ProcessResponsePASS(response);
|
||
+ break;
|
||
+ case COMMAND_SYST:
|
||
+ rv = ProcessResponseSYST(response);
|
||
+ break;
|
||
+ case COMMAND_PWD:
|
||
+ rv = ProcessResponsePWD(response);
|
||
+ break;
|
||
+ case COMMAND_TYPE:
|
||
+ rv = ProcessResponseTYPE(response);
|
||
+ break;
|
||
+ case COMMAND_EPSV:
|
||
+ rv = ProcessResponseEPSV(response);
|
||
+ break;
|
||
+ case COMMAND_PASV:
|
||
+ rv = ProcessResponsePASV(response);
|
||
+ break;
|
||
+ case COMMAND_SIZE:
|
||
+ rv = ProcessResponseSIZE(response);
|
||
+ break;
|
||
+ case COMMAND_RETR:
|
||
+ rv = ProcessResponseRETR(response);
|
||
+ break;
|
||
+ case COMMAND_CWD:
|
||
+ rv = ProcessResponseCWD(response);
|
||
+ break;
|
||
+ case COMMAND_LIST:
|
||
+ rv = ProcessResponseLIST(response);
|
||
+ break;
|
||
+ case COMMAND_QUIT:
|
||
+ rv = ProcessResponseQUIT(response);
|
||
+ break;
|
||
+ default:
|
||
+ LOG(DFATAL) << "Unexpected value of command_sent_: " << command_sent_;
|
||
+ return ERR_UNEXPECTED;
|
||
+ }
|
||
+
|
||
+ // We may get multiple responses for some commands,
|
||
+ // see http://crbug.com/18036. Consume all responses, regardless of whether
|
||
+ // they make sense. On unexpected responses, SendFtpCommand expects all data
|
||
+ // to have already been consumed, even when sending the QUIT command.
|
||
+ while (ctrl_response_buffer_->ResponseAvailable() && rv == OK) {
|
||
+ response = ctrl_response_buffer_->PopResponse();
|
||
+
|
||
+ switch (command_sent_) {
|
||
+ case COMMAND_RETR:
|
||
+ rv = ProcessResponseRETR(response);
|
||
+ break;
|
||
+ case COMMAND_LIST:
|
||
+ rv = ProcessResponseLIST(response);
|
||
+ break;
|
||
+ default:
|
||
+ // Multiple responses for other commands are invalid.
|
||
+ rv = Stop(ERR_INVALID_RESPONSE);
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+// Used to prepare and send FTP command.
|
||
+int FtpNetworkTransaction::SendFtpCommand(const std::string& command,
|
||
+ const std::string& command_for_log,
|
||
+ Command cmd) {
|
||
+ // If we send a new command when we still have unprocessed responses
|
||
+ // for previous commands, the response receiving code will have no way to know
|
||
+ // which responses are for which command.
|
||
+ DCHECK(!ctrl_response_buffer_->ResponseAvailable());
|
||
+
|
||
+ DCHECK(!write_command_buf_.get());
|
||
+ DCHECK(!write_buf_.get());
|
||
+
|
||
+ if (!IsValidFTPCommandSubstring(command)) {
|
||
+ // Callers should validate the command themselves and return a more specific
|
||
+ // error code.
|
||
+ NOTREACHED();
|
||
+ }
|
||
+
|
||
+ command_sent_ = cmd;
|
||
+
|
||
+ write_command_buf_ =
|
||
+ base::MakeRefCounted<IOBufferWithSize>(command.length() + 2);
|
||
+ write_buf_ = base::MakeRefCounted<DrainableIOBuffer>(
|
||
+ write_command_buf_, write_command_buf_->size());
|
||
+ memcpy(write_command_buf_->data(), command.data(), command.length());
|
||
+ memcpy(write_command_buf_->data() + command.length(), kCRLF, 2);
|
||
+
|
||
+ net_log_.AddEventWithStringParams(NetLogEventType::FTP_COMMAND_SENT,
|
||
+ "command", command_for_log);
|
||
+
|
||
+ next_state_ = STATE_CTRL_WRITE;
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+std::string FtpNetworkTransaction::GetRequestPathForFtpCommand(
|
||
+ bool is_directory) const {
|
||
+ std::string path(current_remote_directory_ + unescaped_path_);
|
||
+
|
||
+ // Make sure that if the path is expected to be a file, it won't end
|
||
+ // with a trailing slash.
|
||
+ if (!is_directory && path.length() > 1 && path.back() == '/')
|
||
+ path.erase(path.length() - 1);
|
||
+
|
||
+ if (system_type_ == SYSTEM_TYPE_VMS) {
|
||
+ if (is_directory)
|
||
+ path = FtpUtil::UnixDirectoryPathToVMS(path);
|
||
+ else
|
||
+ path = FtpUtil::UnixFilePathToVMS(path);
|
||
+ }
|
||
+
|
||
+ DCHECK(IsValidFTPCommandSubstring(path));
|
||
+ return path;
|
||
+}
|
||
+
|
||
+void FtpNetworkTransaction::DetectTypecode() {
|
||
+ if (!request_->url.has_path())
|
||
+ return;
|
||
+ std::string gurl_path(request_->url.path());
|
||
+
|
||
+ // Extract the typecode, see RFC 1738 section 3.2.2. FTP url-path.
|
||
+ std::string::size_type pos = gurl_path.rfind(';');
|
||
+ if (pos == std::string::npos)
|
||
+ return;
|
||
+ std::string typecode_string(gurl_path.substr(pos));
|
||
+ if (typecode_string == ";type=a") {
|
||
+ data_type_ = DATA_TYPE_ASCII;
|
||
+ resource_type_ = RESOURCE_TYPE_FILE;
|
||
+ } else if (typecode_string == ";type=i") {
|
||
+ data_type_ = DATA_TYPE_IMAGE;
|
||
+ resource_type_ = RESOURCE_TYPE_FILE;
|
||
+ } else if (typecode_string == ";type=d") {
|
||
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
|
||
+ }
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoLoop(int result) {
|
||
+ DCHECK(next_state_ != STATE_NONE);
|
||
+
|
||
+ int rv = result;
|
||
+ do {
|
||
+ State state = next_state_;
|
||
+ next_state_ = STATE_NONE;
|
||
+ switch (state) {
|
||
+ case STATE_CTRL_RESOLVE_HOST:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlResolveHost();
|
||
+ break;
|
||
+ case STATE_CTRL_RESOLVE_HOST_COMPLETE:
|
||
+ rv = DoCtrlResolveHostComplete(rv);
|
||
+ break;
|
||
+ case STATE_CTRL_CONNECT:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlConnect();
|
||
+ break;
|
||
+ case STATE_CTRL_CONNECT_COMPLETE:
|
||
+ rv = DoCtrlConnectComplete(rv);
|
||
+ break;
|
||
+ case STATE_CTRL_READ:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlRead();
|
||
+ break;
|
||
+ case STATE_CTRL_READ_COMPLETE:
|
||
+ rv = DoCtrlReadComplete(rv);
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWrite();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_COMPLETE:
|
||
+ rv = DoCtrlWriteComplete(rv);
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_USER:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteUSER();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_PASS:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWritePASS();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_SYST:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteSYST();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_PWD:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWritePWD();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_TYPE:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteTYPE();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_EPSV:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteEPSV();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_PASV:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWritePASV();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_RETR:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteRETR();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_SIZE:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteSIZE();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_CWD:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteCWD();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_LIST:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteLIST();
|
||
+ break;
|
||
+ case STATE_CTRL_WRITE_QUIT:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoCtrlWriteQUIT();
|
||
+ break;
|
||
+ case STATE_DATA_CONNECT:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoDataConnect();
|
||
+ break;
|
||
+ case STATE_DATA_CONNECT_COMPLETE:
|
||
+ rv = DoDataConnectComplete(rv);
|
||
+ break;
|
||
+ case STATE_DATA_READ:
|
||
+ DCHECK(rv == OK);
|
||
+ rv = DoDataRead();
|
||
+ break;
|
||
+ case STATE_DATA_READ_COMPLETE:
|
||
+ rv = DoDataReadComplete(rv);
|
||
+ break;
|
||
+ default:
|
||
+ NOTREACHED() << "bad state";
|
||
+ }
|
||
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlResolveHost() {
|
||
+ next_state_ = STATE_CTRL_RESOLVE_HOST_COMPLETE;
|
||
+
|
||
+ resolve_request_ =
|
||
+ resolver_->CreateRequest(url::SchemeHostPort(request_->url),
|
||
+ NetworkAnonymizationKey(), net_log_, std::nullopt);
|
||
+ return resolve_request_->Start(base::BindOnce(
|
||
+ &FtpNetworkTransaction::OnIOComplete, base::Unretained(this)));
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlResolveHostComplete(int result) {
|
||
+ if (result == OK)
|
||
+ next_state_ = STATE_CTRL_CONNECT;
|
||
+ return result;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlConnect() {
|
||
+ next_state_ = STATE_CTRL_CONNECT_COMPLETE;
|
||
+ // TODO(https://crbug.com/1123197): Pass a non-null NetworkQualityEstimator.
|
||
+ NetworkQualityEstimator* network_quality_estimator = nullptr;
|
||
+
|
||
+ DCHECK(resolve_request_ && resolve_request_->GetAddressResults());
|
||
+ ctrl_socket_ = socket_factory_->CreateTransportClientSocket(
|
||
+ AddressList(*resolve_request_->GetAddressResults()), nullptr,
|
||
+ network_quality_estimator, net_log_.net_log(), net_log_.source());
|
||
+ net_log_.AddEventReferencingSource(NetLogEventType::FTP_CONTROL_CONNECTION,
|
||
+ ctrl_socket_->NetLog().source());
|
||
+ return ctrl_socket_->Connect(io_callback_);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlConnectComplete(int result) {
|
||
+ if (result == OK) {
|
||
+ // Put the peer's IP address and port into the response.
|
||
+ IPEndPoint ip_endpoint;
|
||
+ result = ctrl_socket_->GetPeerAddress(&ip_endpoint);
|
||
+ if (result == OK) {
|
||
+ response_.remote_endpoint = ip_endpoint;
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+
|
||
+ if (ip_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4) {
|
||
+ // Do not use EPSV for IPv4 connections. Some servers become confused
|
||
+ // and we time out while waiting to connect. PASV is perfectly fine for
|
||
+ // IPv4. Note that this blocks IPv4 not to use EPSV instead of allowing
|
||
+ // IPv6 to use it, to make the code more future-proof:
|
||
+ // all future protocols should just use EPSV.
|
||
+ use_epsv_ = false;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ return result;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlRead() {
|
||
+ next_state_ = STATE_CTRL_READ_COMPLETE;
|
||
+ return ctrl_socket_->Read(read_ctrl_buf_.get(), kCtrlBufLen, io_callback_);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlReadComplete(int result) {
|
||
+ if (result == 0) {
|
||
+ // Some servers (for example Pure-FTPd) apparently close the control
|
||
+ // connection when anonymous login is not permitted. For more details
|
||
+ // see http://crbug.com/25023.
|
||
+ if (command_sent_ == COMMAND_USER &&
|
||
+ credentials_.username() == u"anonymous") {
|
||
+ response_.needs_auth = true;
|
||
+ }
|
||
+ return Stop(ERR_EMPTY_RESPONSE);
|
||
+ }
|
||
+ if (result < 0)
|
||
+ return Stop(result);
|
||
+
|
||
+ ctrl_response_buffer_->ConsumeData(read_ctrl_buf_->data(), result);
|
||
+
|
||
+ if (!ctrl_response_buffer_->ResponseAvailable()) {
|
||
+ // Read more data from the control socket.
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return OK;
|
||
+ }
|
||
+
|
||
+ return ProcessCtrlResponse();
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlWrite() {
|
||
+ next_state_ = STATE_CTRL_WRITE_COMPLETE;
|
||
+
|
||
+ return ctrl_socket_->Write(write_buf_.get(), write_buf_->BytesRemaining(),
|
||
+ io_callback_,
|
||
+ NetworkTrafficAnnotationTag(traffic_annotation_));
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoCtrlWriteComplete(int result) {
|
||
+ if (result < 0)
|
||
+ return result;
|
||
+
|
||
+ write_buf_->DidConsume(result);
|
||
+ if (write_buf_->BytesRemaining() == 0) {
|
||
+ // Clear the write buffer.
|
||
+ write_buf_ = nullptr;
|
||
+ write_command_buf_ = nullptr;
|
||
+
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ } else {
|
||
+ next_state_ = STATE_CTRL_WRITE;
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// FTP Commands and responses
|
||
+
|
||
+// USER Command.
|
||
+int FtpNetworkTransaction::DoCtrlWriteUSER() {
|
||
+ std::string command = "USER " + base::UTF16ToUTF8(credentials_.username());
|
||
+
|
||
+ if (!IsValidFTPCommandSubstring(command))
|
||
+ return Stop(ERR_MALFORMED_IDENTITY);
|
||
+
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, "USER ***", COMMAND_USER);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseUSER(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK:
|
||
+ next_state_ = STATE_CTRL_WRITE_SYST;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ next_state_ = STATE_CTRL_WRITE_PASS;
|
||
+ break;
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ response_.needs_auth = true;
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// PASS command.
|
||
+int FtpNetworkTransaction::DoCtrlWritePASS() {
|
||
+ std::string command = "PASS " + base::UTF16ToUTF8(credentials_.password());
|
||
+
|
||
+ if (!IsValidFTPCommandSubstring(command))
|
||
+ return Stop(ERR_MALFORMED_IDENTITY);
|
||
+
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, "PASS ***", COMMAND_PASS);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponsePASS(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK:
|
||
+ next_state_ = STATE_CTRL_WRITE_SYST;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ response_.needs_auth = true;
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// SYST command.
|
||
+int FtpNetworkTransaction::DoCtrlWriteSYST() {
|
||
+ std::string command = "SYST";
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_SYST);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseSYST(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK: {
|
||
+ // All important info should be on the first line.
|
||
+ std::string line = response.lines[0];
|
||
+ // The response should be ASCII, which allows us to do case-insensitive
|
||
+ // comparisons easily. If it is not ASCII, we leave the system type
|
||
+ // as unknown.
|
||
+ if (base::IsStringASCII(line)) {
|
||
+ line = base::ToLowerASCII(line);
|
||
+
|
||
+ // Remove all whitespace, to correctly handle cases like fancy "V M S"
|
||
+ // response instead of "VMS".
|
||
+ base::RemoveChars(line, base::kWhitespaceASCII, &line);
|
||
+
|
||
+ // The "magic" strings we test for below have been gathered by an
|
||
+ // empirical study. VMS needs to come first because some VMS systems
|
||
+ // also respond with "UNIX emulation", which is not perfect. It is much
|
||
+ // more reliable to talk to these servers in their native language.
|
||
+ if (line.find("vms") != std::string::npos) {
|
||
+ system_type_ = SYSTEM_TYPE_VMS;
|
||
+ } else if (line.find("l8") != std::string::npos ||
|
||
+ line.find("unix") != std::string::npos ||
|
||
+ line.find("bsd") != std::string::npos) {
|
||
+ system_type_ = SYSTEM_TYPE_UNIX;
|
||
+ } else if (line.find("win32") != std::string::npos ||
|
||
+ line.find("windows") != std::string::npos) {
|
||
+ system_type_ = SYSTEM_TYPE_WINDOWS;
|
||
+ } else if (line.find("os/2") != std::string::npos) {
|
||
+ system_type_ = SYSTEM_TYPE_OS2;
|
||
+ }
|
||
+ }
|
||
+ next_state_ = STATE_CTRL_WRITE_PWD;
|
||
+ break;
|
||
+ }
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ // Server does not recognize the SYST command so proceed.
|
||
+ next_state_ = STATE_CTRL_WRITE_PWD;
|
||
+ break;
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// PWD command.
|
||
+int FtpNetworkTransaction::DoCtrlWritePWD() {
|
||
+ std::string command = "PWD";
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_PWD);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK: {
|
||
+ // The info we look for should be on the first line.
|
||
+ std::string line = response.lines[0];
|
||
+ if (line.empty())
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ std::string::size_type quote_pos = line.find('"');
|
||
+ if (quote_pos != std::string::npos) {
|
||
+ line = line.substr(quote_pos + 1);
|
||
+ quote_pos = line.find('"');
|
||
+ if (quote_pos == std::string::npos)
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ line = line.substr(0, quote_pos);
|
||
+ }
|
||
+ if (system_type_ == SYSTEM_TYPE_VMS)
|
||
+ line = FtpUtil::VMSPathToUnix(line);
|
||
+ if (!line.empty() && line.back() == '/')
|
||
+ line.erase(line.length() - 1);
|
||
+ // Fail if the "path" contains characters not allowed in commands.
|
||
+ // This does mean that files with CRs or LFs in their names aren't
|
||
+ // handled, but that's probably for the best.
|
||
+ if (!IsValidFTPCommandSubstring(line))
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ current_remote_directory_ = line;
|
||
+ next_state_ = STATE_CTRL_WRITE_TYPE;
|
||
+ break;
|
||
+ }
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// TYPE command.
|
||
+int FtpNetworkTransaction::DoCtrlWriteTYPE() {
|
||
+ std::string command = "TYPE ";
|
||
+ if (data_type_ == DATA_TYPE_ASCII) {
|
||
+ command += "A";
|
||
+ } else if (data_type_ == DATA_TYPE_IMAGE) {
|
||
+ command += "I";
|
||
+ } else {
|
||
+ NOTREACHED();
|
||
+ }
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_TYPE);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseTYPE(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK:
|
||
+ next_state_ = STATE_CTRL_WRITE_SIZE;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// EPSV command
|
||
+int FtpNetworkTransaction::DoCtrlWriteEPSV() {
|
||
+ const std::string command = "EPSV";
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_EPSV);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseEPSV(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK: {
|
||
+ int port;
|
||
+ if (!ExtractPortFromEPSVResponse(response, &port))
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ if (IsWellKnownPort(port) ||
|
||
+ !IsPortAllowedForScheme(port, url::kFtpScheme)) {
|
||
+ return Stop(ERR_UNSAFE_PORT);
|
||
+ }
|
||
+ data_connection_port_ = static_cast<uint16_t>(port);
|
||
+ next_state_ = STATE_DATA_CONNECT;
|
||
+ break;
|
||
+ }
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ use_epsv_ = false;
|
||
+ next_state_ = STATE_CTRL_WRITE_PASV;
|
||
+ return OK;
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// PASV command
|
||
+int FtpNetworkTransaction::DoCtrlWritePASV() {
|
||
+ std::string command = "PASV";
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_PASV);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponsePASV(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK: {
|
||
+ int port;
|
||
+ if (!ExtractPortFromPASVResponse(response, &port))
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ if (IsWellKnownPort(port) ||
|
||
+ !IsPortAllowedForScheme(port, url::kFtpScheme)) {
|
||
+ return Stop(ERR_UNSAFE_PORT);
|
||
+ }
|
||
+ data_connection_port_ = static_cast<uint16_t>(port);
|
||
+ next_state_ = STATE_DATA_CONNECT;
|
||
+ break;
|
||
+ }
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// RETR command
|
||
+int FtpNetworkTransaction::DoCtrlWriteRETR() {
|
||
+ std::string command = "RETR " + GetRequestPathForFtpCommand(false);
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_RETR);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseRETR(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ // Resource type should be either filled in by DetectTypecode() or
|
||
+ // detected with CWD. RETR is sent only when the resource is a file.
|
||
+ DCHECK_EQ(RESOURCE_TYPE_FILE, resource_type_);
|
||
+
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ // We want the client to start reading the response at this point.
|
||
+ // It got here either through Start or RestartWithAuth. We want that
|
||
+ // method to complete. Not setting next state here will make DoLoop exit
|
||
+ // and in turn make Start/RestartWithAuth complete.
|
||
+ break;
|
||
+ case ERROR_CLASS_OK:
|
||
+ next_state_ = STATE_CTRL_WRITE_QUIT;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// SIZE command
|
||
+int FtpNetworkTransaction::DoCtrlWriteSIZE() {
|
||
+ std::string command = "SIZE " + GetRequestPathForFtpCommand(false);
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_SIZE);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseSIZE(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ break;
|
||
+ case ERROR_CLASS_OK:
|
||
+ if (response.lines.size() != 1)
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ int64_t size;
|
||
+ if (!base::StringToInt64(response.lines[0], &size))
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ if (size < 0)
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+
|
||
+ // A successful response to SIZE does not mean the resource is a file.
|
||
+ // Some FTP servers (for example, the qnx one) send a SIZE even for
|
||
+ // directories.
|
||
+ response_.expected_content_size = size;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ break;
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ break;
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ // It's possible that SIZE failed because the path is a directory.
|
||
+ // TODO(xunjieli): https://crbug.com/526724: Add a test for this case.
|
||
+ if (resource_type_ == RESOURCE_TYPE_UNKNOWN &&
|
||
+ response.status_code != 550) {
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ break;
|
||
+ }
|
||
+
|
||
+ // If the resource is known beforehand to be a file, RETR should be issued,
|
||
+ // otherwise do CWD which will detect the resource type.
|
||
+ if (resource_type_ == RESOURCE_TYPE_FILE)
|
||
+ EstablishDataConnection(STATE_CTRL_WRITE_RETR);
|
||
+ else
|
||
+ next_state_ = STATE_CTRL_WRITE_CWD;
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// CWD command
|
||
+int FtpNetworkTransaction::DoCtrlWriteCWD() {
|
||
+ std::string command = "CWD " + GetRequestPathForFtpCommand(true);
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_CWD);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) {
|
||
+ // CWD should be invoked only when the resource is not a file.
|
||
+ DCHECK_NE(RESOURCE_TYPE_FILE, resource_type_);
|
||
+
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_OK:
|
||
+ resource_type_ = RESOURCE_TYPE_DIRECTORY;
|
||
+ EstablishDataConnection(STATE_CTRL_WRITE_LIST);
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ // Some FTP servers send response 451 (not a valid CWD response according
|
||
+ // to RFC 959) instead of 550.
|
||
+ if (response.status_code == 451)
|
||
+ return ProcessResponseCWDNotADirectory();
|
||
+
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ if (response.status_code == 550)
|
||
+ return ProcessResponseCWDNotADirectory();
|
||
+
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseCWDNotADirectory() {
|
||
+ if (resource_type_ == RESOURCE_TYPE_DIRECTORY) {
|
||
+ // We're assuming that the resource is a directory, but the server
|
||
+ // says it's not true. The most probable interpretation is that it
|
||
+ // doesn't exist (with FTP we can't be sure).
|
||
+ return Stop(ERR_FILE_NOT_FOUND);
|
||
+ }
|
||
+
|
||
+ // If it is not a directory, it is probably a file.
|
||
+ resource_type_ = RESOURCE_TYPE_FILE;
|
||
+
|
||
+ EstablishDataConnection(STATE_CTRL_WRITE_RETR);
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// LIST command
|
||
+int FtpNetworkTransaction::DoCtrlWriteLIST() {
|
||
+ // Use the -l option for mod_ftp configured in LISTIsNLST mode: the option
|
||
+ // forces LIST output instead of NLST (which would be ambiguous for us
|
||
+ // to parse).
|
||
+ std::string command("LIST -l");
|
||
+ if (system_type_ == SYSTEM_TYPE_VMS)
|
||
+ command = "LIST *.*;0";
|
||
+
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_LIST);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseLIST(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ // Resource type should be either filled in by DetectTypecode() or
|
||
+ // detected with CWD. LIST is sent only when the resource is a directory.
|
||
+ DCHECK_EQ(RESOURCE_TYPE_DIRECTORY, resource_type_);
|
||
+
|
||
+ switch (GetErrorClass(response.status_code)) {
|
||
+ case ERROR_CLASS_INITIATED:
|
||
+ // We want the client to start reading the response at this point.
|
||
+ // It got here either through Start or RestartWithAuth. We want that
|
||
+ // method to complete. Not setting next state here will make DoLoop exit
|
||
+ // and in turn make Start/RestartWithAuth complete.
|
||
+ response_.is_directory_listing = true;
|
||
+ break;
|
||
+ case ERROR_CLASS_OK:
|
||
+ response_.is_directory_listing = true;
|
||
+ next_state_ = STATE_CTRL_WRITE_QUIT;
|
||
+ break;
|
||
+ case ERROR_CLASS_INFO_NEEDED:
|
||
+ return Stop(ERR_INVALID_RESPONSE);
|
||
+ case ERROR_CLASS_TRANSIENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ case ERROR_CLASS_PERMANENT_ERROR:
|
||
+ return Stop(GetNetErrorCodeForFtpResponseCode(response.status_code));
|
||
+ }
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+// QUIT command
|
||
+int FtpNetworkTransaction::DoCtrlWriteQUIT() {
|
||
+ std::string command = "QUIT";
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return SendFtpCommand(command, command, COMMAND_QUIT);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::ProcessResponseQUIT(
|
||
+ const FtpCtrlResponse& response) {
|
||
+ ctrl_socket_->Disconnect();
|
||
+ return last_error_;
|
||
+}
|
||
+
|
||
+// Data Connection
|
||
+
|
||
+int FtpNetworkTransaction::DoDataConnect() {
|
||
+ next_state_ = STATE_DATA_CONNECT_COMPLETE;
|
||
+ IPEndPoint ip_endpoint;
|
||
+ AddressList data_address;
|
||
+ // Connect to the same host as the control socket to prevent PASV port
|
||
+ // scanning attacks.
|
||
+ int rv = ctrl_socket_->GetPeerAddress(&ip_endpoint);
|
||
+ if (rv != OK)
|
||
+ return Stop(rv);
|
||
+ data_address = AddressList::CreateFromIPAddress(
|
||
+ ip_endpoint.address(), data_connection_port_);
|
||
+ // TODO(https://crbug.com/1123197): Pass a non-null NetworkQualityEstimator.
|
||
+ NetworkQualityEstimator* network_quality_estimator = nullptr;
|
||
+
|
||
+ data_socket_ = socket_factory_->CreateTransportClientSocket(
|
||
+ data_address, nullptr, network_quality_estimator, net_log_.net_log(),
|
||
+ net_log_.source());
|
||
+ net_log_.AddEventReferencingSource(NetLogEventType::FTP_DATA_CONNECTION,
|
||
+ data_socket_->NetLog().source());
|
||
+ return data_socket_->Connect(io_callback_);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoDataConnectComplete(int result) {
|
||
+ if (result != OK && use_epsv_) {
|
||
+ // It's possible we hit a broken server, sadly. They can break in different
|
||
+ // ways. Some time out, some reset a connection. Fall back to PASV.
|
||
+ // TODO(phajdan.jr): https://crbug.com/526723: remember it for future
|
||
+ // transactions with this server.
|
||
+ use_epsv_ = false;
|
||
+ next_state_ = STATE_CTRL_WRITE_PASV;
|
||
+ return OK;
|
||
+ }
|
||
+
|
||
+ if (result != OK)
|
||
+ return Stop(result);
|
||
+
|
||
+ next_state_ = state_after_data_connect_complete_;
|
||
+ return OK;
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoDataRead() {
|
||
+ DCHECK(read_data_buf_.get());
|
||
+ DCHECK_GT(read_data_buf_len_, 0);
|
||
+
|
||
+ if (!data_socket_ || !data_socket_->IsConnected()) {
|
||
+ // If we don't destroy the data socket completely, some servers will wait
|
||
+ // for us (http://crbug.com/21127). The half-closed TCP connection needs
|
||
+ // to be closed on our side too.
|
||
+ data_socket_.reset();
|
||
+
|
||
+ if (ctrl_socket_->IsConnected()) {
|
||
+ // Wait for the server's response, we should get it before sending QUIT.
|
||
+ next_state_ = STATE_CTRL_READ;
|
||
+ return OK;
|
||
+ }
|
||
+
|
||
+ // We are no longer connected to the server, so just finish the transaction.
|
||
+ return Stop(OK);
|
||
+ }
|
||
+
|
||
+ next_state_ = STATE_DATA_READ_COMPLETE;
|
||
+ read_data_buf_->data()[0] = 0;
|
||
+ return data_socket_->Read(
|
||
+ read_data_buf_.get(), read_data_buf_len_, io_callback_);
|
||
+}
|
||
+
|
||
+int FtpNetworkTransaction::DoDataReadComplete(int result) {
|
||
+ return result;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_network_transaction.h b/net/ftp/ftp_network_transaction.h
|
||
new file mode 100644
|
||
index 0000000000000..c7cb8753f8acf
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_transaction.h
|
||
@@ -0,0 +1,268 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_NETWORK_TRANSACTION_H_
|
||
+#define NET_FTP_FTP_NETWORK_TRANSACTION_H_
|
||
+
|
||
+#include <stdint.h>
|
||
+
|
||
+#include <memory>
|
||
+#include <string>
|
||
+#include <utility>
|
||
+
|
||
+#include "base/compiler_specific.h"
|
||
+#include "base/gtest_prod_util.h"
|
||
+#include "base/memory/raw_ptr.h"
|
||
+#include "base/memory/ref_counted.h"
|
||
+#include "net/base/auth.h"
|
||
+#include "net/base/completion_once_callback.h"
|
||
+#include "net/base/completion_repeating_callback.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/dns/host_resolver.h"
|
||
+#include "net/ftp/ftp_ctrl_response_buffer.h"
|
||
+#include "net/ftp/ftp_response_info.h"
|
||
+#include "net/ftp/ftp_transaction.h"
|
||
+#include "net/log/net_log_with_source.h"
|
||
+#include "net/traffic_annotation/network_traffic_annotation.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class ClientSocketFactory;
|
||
+class StreamSocket;
|
||
+
|
||
+class NET_EXPORT_PRIVATE FtpNetworkTransaction : public FtpTransaction {
|
||
+ public:
|
||
+ FtpNetworkTransaction(HostResolver* resolver,
|
||
+ ClientSocketFactory* socket_factory);
|
||
+ ~FtpNetworkTransaction() override;
|
||
+
|
||
+ int Stop(int error);
|
||
+
|
||
+ // FtpTransaction methods:
|
||
+ int Start(raw_ptr <FtpRequestInfo> request_info,
|
||
+ CompletionOnceCallback callback,
|
||
+ const NetLogWithSource& net_log,
|
||
+ const NetworkTrafficAnnotationTag& traffic_annotation) override;
|
||
+ int RestartWithAuth(const AuthCredentials& credentials,
|
||
+ CompletionOnceCallback callback) override;
|
||
+ int Read(IOBuffer* buf,
|
||
+ int buf_len,
|
||
+ CompletionOnceCallback callback) override;
|
||
+ const FtpResponseInfo* GetResponseInfo() const override;
|
||
+ LoadState GetLoadState() const override;
|
||
+ uint64_t GetUploadProgress() const override;
|
||
+
|
||
+ private:
|
||
+ FRIEND_TEST_ALL_PREFIXES(FtpNetworkTransactionTest,
|
||
+ DownloadTransactionEvilPasvUnsafeHost);
|
||
+
|
||
+ enum Command {
|
||
+ COMMAND_NONE,
|
||
+ COMMAND_USER,
|
||
+ COMMAND_PASS,
|
||
+ COMMAND_SYST,
|
||
+ COMMAND_TYPE,
|
||
+ COMMAND_EPSV,
|
||
+ COMMAND_PASV,
|
||
+ COMMAND_PWD,
|
||
+ COMMAND_SIZE,
|
||
+ COMMAND_RETR,
|
||
+ COMMAND_CWD,
|
||
+ COMMAND_LIST,
|
||
+ COMMAND_QUIT,
|
||
+ };
|
||
+
|
||
+ // Major categories of remote system types, as returned by SYST command.
|
||
+ enum SystemType {
|
||
+ SYSTEM_TYPE_UNKNOWN,
|
||
+ SYSTEM_TYPE_UNIX,
|
||
+ SYSTEM_TYPE_WINDOWS,
|
||
+ SYSTEM_TYPE_OS2,
|
||
+ SYSTEM_TYPE_VMS,
|
||
+ };
|
||
+
|
||
+ // Data representation type, see RFC 959 section 3.1.1. Data Types.
|
||
+ // We only support the two most popular data types.
|
||
+ enum DataType {
|
||
+ DATA_TYPE_ASCII,
|
||
+ DATA_TYPE_IMAGE,
|
||
+ };
|
||
+
|
||
+ // In FTP we need to issue different commands depending on whether a resource
|
||
+ // is a file or directory. If we don't know that, we're going to autodetect
|
||
+ // it.
|
||
+ enum ResourceType {
|
||
+ RESOURCE_TYPE_UNKNOWN,
|
||
+ RESOURCE_TYPE_FILE,
|
||
+ RESOURCE_TYPE_DIRECTORY,
|
||
+ };
|
||
+
|
||
+ enum State {
|
||
+ // Control connection states:
|
||
+ STATE_CTRL_RESOLVE_HOST,
|
||
+ STATE_CTRL_RESOLVE_HOST_COMPLETE,
|
||
+ STATE_CTRL_CONNECT,
|
||
+ STATE_CTRL_CONNECT_COMPLETE,
|
||
+ STATE_CTRL_READ,
|
||
+ STATE_CTRL_READ_COMPLETE,
|
||
+ STATE_CTRL_WRITE,
|
||
+ STATE_CTRL_WRITE_COMPLETE,
|
||
+ STATE_CTRL_WRITE_USER,
|
||
+ STATE_CTRL_WRITE_PASS,
|
||
+ STATE_CTRL_WRITE_SYST,
|
||
+ STATE_CTRL_WRITE_TYPE,
|
||
+ STATE_CTRL_WRITE_EPSV,
|
||
+ STATE_CTRL_WRITE_PASV,
|
||
+ STATE_CTRL_WRITE_PWD,
|
||
+ STATE_CTRL_WRITE_RETR,
|
||
+ STATE_CTRL_WRITE_SIZE,
|
||
+ STATE_CTRL_WRITE_CWD,
|
||
+ STATE_CTRL_WRITE_LIST,
|
||
+ STATE_CTRL_WRITE_QUIT,
|
||
+ // Data connection states:
|
||
+ STATE_DATA_CONNECT,
|
||
+ STATE_DATA_CONNECT_COMPLETE,
|
||
+ STATE_DATA_READ,
|
||
+ STATE_DATA_READ_COMPLETE,
|
||
+ STATE_NONE
|
||
+ };
|
||
+
|
||
+ // Resets the members of the transaction so it can be restarted.
|
||
+ void ResetStateForRestart();
|
||
+
|
||
+ // Establishes the data connection and switches to |state_after_connect|.
|
||
+ // |state_after_connect| should only be RETR or LIST.
|
||
+ void EstablishDataConnection(State state_after_connect);
|
||
+
|
||
+ void DoCallback(int result);
|
||
+ void OnIOComplete(int result);
|
||
+
|
||
+ // Executes correct ProcessResponse + command_name function based on last
|
||
+ // issued command. Returns error code.
|
||
+ int ProcessCtrlResponse();
|
||
+
|
||
+ int SendFtpCommand(const std::string& command,
|
||
+ const std::string& command_for_log,
|
||
+ Command cmd);
|
||
+
|
||
+ // Returns request path suitable to be included in an FTP command. If the path
|
||
+ // will be used as a directory, |is_directory| should be true.
|
||
+ std::string GetRequestPathForFtpCommand(bool is_directory) const;
|
||
+
|
||
+ // See if the request URL contains a typecode and make us respect it.
|
||
+ void DetectTypecode();
|
||
+
|
||
+ // Runs the state transition loop.
|
||
+ int DoLoop(int result);
|
||
+
|
||
+ // Each of these methods corresponds to a State value. Those with an input
|
||
+ // argument receive the result from the previous state. If a method returns
|
||
+ // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the
|
||
+ // next state method as the result arg.
|
||
+ int DoCtrlResolveHost();
|
||
+ int DoCtrlResolveHostComplete(int result);
|
||
+ int DoCtrlConnect();
|
||
+ int DoCtrlConnectComplete(int result);
|
||
+ int DoCtrlRead();
|
||
+ int DoCtrlReadComplete(int result);
|
||
+ int DoCtrlWrite();
|
||
+ int DoCtrlWriteComplete(int result);
|
||
+ int DoCtrlWriteUSER();
|
||
+ int ProcessResponseUSER(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWritePASS();
|
||
+ int ProcessResponsePASS(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteSYST();
|
||
+ int ProcessResponseSYST(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWritePWD();
|
||
+ int ProcessResponsePWD(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteTYPE();
|
||
+ int ProcessResponseTYPE(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteEPSV();
|
||
+ int ProcessResponseEPSV(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWritePASV();
|
||
+ int ProcessResponsePASV(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteRETR();
|
||
+ int ProcessResponseRETR(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteSIZE();
|
||
+ int ProcessResponseSIZE(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteCWD();
|
||
+ int ProcessResponseCWD(const FtpCtrlResponse& response);
|
||
+ int ProcessResponseCWDNotADirectory();
|
||
+ int DoCtrlWriteLIST();
|
||
+ int ProcessResponseLIST(const FtpCtrlResponse& response);
|
||
+ int DoCtrlWriteQUIT();
|
||
+ int ProcessResponseQUIT(const FtpCtrlResponse& response);
|
||
+
|
||
+ int DoDataConnect();
|
||
+ int DoDataConnectComplete(int result);
|
||
+ int DoDataRead();
|
||
+ int DoDataReadComplete(int result);
|
||
+
|
||
+ Command command_sent_;
|
||
+
|
||
+ CompletionRepeatingCallback io_callback_;
|
||
+ CompletionOnceCallback user_callback_;
|
||
+
|
||
+ NetLogWithSource net_log_;
|
||
+ raw_ptr<FtpRequestInfo> request_;
|
||
+ MutableNetworkTrafficAnnotationTag traffic_annotation_;
|
||
+ FtpResponseInfo response_;
|
||
+
|
||
+ raw_ptr<HostResolver> resolver_;
|
||
+ // Cancels the outstanding request on destruction.
|
||
+ std::unique_ptr<HostResolver::ResolveHostRequest> resolve_request_;
|
||
+
|
||
+ // User buffer passed to the Read method for control socket.
|
||
+ scoped_refptr<IOBuffer> read_ctrl_buf_;
|
||
+
|
||
+ std::unique_ptr<FtpCtrlResponseBuffer> ctrl_response_buffer_;
|
||
+
|
||
+ scoped_refptr<IOBuffer> read_data_buf_;
|
||
+ int read_data_buf_len_;
|
||
+
|
||
+ // Buffer holding the command line to be written to the control socket.
|
||
+ scoped_refptr<IOBufferWithSize> write_command_buf_;
|
||
+
|
||
+ // Buffer passed to the Write method of control socket. It actually writes
|
||
+ // to the write_command_buf_ at correct offset.
|
||
+ scoped_refptr<DrainableIOBuffer> write_buf_;
|
||
+
|
||
+ int last_error_;
|
||
+
|
||
+ SystemType system_type_;
|
||
+
|
||
+ // Data type to be used for the TYPE command.
|
||
+ DataType data_type_;
|
||
+
|
||
+ // Detected resource type (file or directory).
|
||
+ ResourceType resource_type_;
|
||
+
|
||
+ // Initially we favour EPSV over PASV for transfers but should any
|
||
+ // EPSV fail, we fall back to PASV for the duration of connection.
|
||
+ bool use_epsv_;
|
||
+
|
||
+ AuthCredentials credentials_;
|
||
+
|
||
+ // Current directory on the remote server, as returned by last PWD command,
|
||
+ // with any trailing slash removed.
|
||
+ std::string current_remote_directory_;
|
||
+
|
||
+ uint16_t data_connection_port_;
|
||
+
|
||
+ raw_ptr<ClientSocketFactory> const socket_factory_;
|
||
+
|
||
+ std::string unescaped_path_;
|
||
+
|
||
+ std::unique_ptr<StreamSocket> ctrl_socket_;
|
||
+ std::unique_ptr<StreamSocket> data_socket_;
|
||
+
|
||
+ State next_state_;
|
||
+
|
||
+ // State to switch to after data connection is complete.
|
||
+ State state_after_data_connect_complete_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_NETWORK_TRANSACTION_H_
|
||
diff --git a/net/ftp/ftp_network_transaction_unittest.cc b/net/ftp/ftp_network_transaction_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..fb2facdd3c653
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_network_transaction_unittest.cc
|
||
@@ -0,0 +1,1722 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_network_transaction.h"
|
||
+
|
||
+#include "base/containers/circular_deque.h"
|
||
+#include "base/macros.h"
|
||
+#include "base/memory/ref_counted.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "net/base/io_buffer.h"
|
||
+#include "net/base/ip_endpoint.h"
|
||
+#include "net/base/sys_addrinfo.h"
|
||
+#include "net/base/test_completion_callback.h"
|
||
+#include "net/dns/mock_host_resolver.h"
|
||
+#include "net/ftp/ftp_request_info.h"
|
||
+#include "net/socket/socket_test_util.h"
|
||
+#include "net/test/gtest_util.h"
|
||
+#include "net/test/test_with_task_environment.h"
|
||
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
||
+#include "testing/gmock/include/gmock/gmock.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+#include "testing/platform_test.h"
|
||
+
|
||
+using net::test::IsError;
|
||
+using net::test::IsOk;
|
||
+
|
||
+namespace {
|
||
+
|
||
+// Size we use for IOBuffers used to receive data from the test data socket.
|
||
+const int kBufferSize = 128;
|
||
+
|
||
+} // namespace
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpSocketDataProvider : public SocketDataProvider {
|
||
+ public:
|
||
+ enum State {
|
||
+ NONE,
|
||
+ PRE_USER,
|
||
+ PRE_PASSWD,
|
||
+ PRE_SYST,
|
||
+ PRE_PWD,
|
||
+ PRE_TYPE,
|
||
+ PRE_SIZE,
|
||
+ PRE_LIST_EPSV,
|
||
+ PRE_LIST_PASV,
|
||
+ PRE_LIST,
|
||
+ PRE_RETR,
|
||
+ PRE_RETR_EPSV,
|
||
+ PRE_RETR_PASV,
|
||
+ PRE_CWD,
|
||
+ PRE_QUIT,
|
||
+ PRE_NOPASV,
|
||
+ QUIT
|
||
+ };
|
||
+
|
||
+ FtpSocketDataProvider()
|
||
+ : short_read_limit_(0),
|
||
+ allow_unconsumed_reads_(false),
|
||
+ failure_injection_state_(NONE),
|
||
+ multiline_welcome_(false),
|
||
+ use_epsv_(true),
|
||
+ data_type_('I') {
|
||
+ Init();
|
||
+ }
|
||
+ ~FtpSocketDataProvider() override = default;
|
||
+
|
||
+ // SocketDataProvider implementation.
|
||
+ MockRead OnRead() override {
|
||
+ if (reads_.empty())
|
||
+ return MockRead(SYNCHRONOUS, ERR_UNEXPECTED);
|
||
+ MockRead result = reads_.front();
|
||
+ if (short_read_limit_ == 0 || result.data_len <= short_read_limit_) {
|
||
+ reads_.pop_front();
|
||
+ } else {
|
||
+ result.data_len = short_read_limit_;
|
||
+ reads_.front().data += result.data_len;
|
||
+ reads_.front().data_len -= result.data_len;
|
||
+ }
|
||
+ return result;
|
||
+ }
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_USER:
|
||
+ return Verify("USER anonymous\r\n", data, PRE_PASSWD,
|
||
+ "331 Password needed\r\n");
|
||
+ case PRE_PASSWD:
|
||
+ {
|
||
+ static const char response_one[] = "230 Welcome\r\n";
|
||
+ static const char response_multi[] =
|
||
+ "230- One\r\n230- Two\r\n230 Three\r\n";
|
||
+ return Verify("PASS chrome@example.com\r\n", data, PRE_SYST,
|
||
+ multiline_welcome_ ? response_multi : response_one);
|
||
+ }
|
||
+ case PRE_SYST:
|
||
+ return Verify("SYST\r\n", data, PRE_PWD, "215 UNIX\r\n");
|
||
+ case PRE_PWD:
|
||
+ return Verify("PWD\r\n", data, PRE_TYPE,
|
||
+ "257 \"/\" is your current location\r\n");
|
||
+ case PRE_TYPE:
|
||
+ return Verify(std::string("TYPE ") + data_type_ + "\r\n", data,
|
||
+ PRE_SIZE, "200 TYPE set successfully\r\n");
|
||
+ case PRE_LIST_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_LIST,
|
||
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
|
||
+ case PRE_LIST_PASV:
|
||
+ return Verify("PASV\r\n", data, PRE_LIST,
|
||
+ "227 Entering Passive Mode 127,0,0,1,123,123\r\n");
|
||
+ case PRE_RETR_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_RETR,
|
||
+ "227 Entering Extended Passive Mode (|||31744|)\r\n");
|
||
+ case PRE_RETR_PASV:
|
||
+ return Verify("PASV\r\n", data, PRE_RETR,
|
||
+ "227 Entering Passive Mode 127,0,0,1,123,123\r\n");
|
||
+ case PRE_NOPASV:
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the
|
||
+ // generic ERR_FTP_FAILED bucket.
|
||
+ return Verify("PASV\r\n", data, PRE_QUIT,
|
||
+ "599 fail\r\n");
|
||
+ case PRE_QUIT:
|
||
+ return Verify("QUIT\r\n", data, QUIT, "221 Goodbye.\r\n");
|
||
+ default:
|
||
+ NOTREACHED() << "State not handled " << state();
|
||
+ return MockWriteResult(ASYNC, ERR_UNEXPECTED);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ void InjectFailure(State state, State next_state, const char* response) {
|
||
+ DCHECK_EQ(NONE, failure_injection_state_);
|
||
+ DCHECK_NE(NONE, state);
|
||
+ DCHECK_NE(NONE, next_state);
|
||
+ DCHECK_NE(state, next_state);
|
||
+ failure_injection_state_ = state;
|
||
+ failure_injection_next_state_ = next_state;
|
||
+ fault_response_ = response;
|
||
+ }
|
||
+
|
||
+ State state() const {
|
||
+ return state_;
|
||
+ }
|
||
+
|
||
+ void Reset() override {
|
||
+ reads_.clear();
|
||
+ Init();
|
||
+ }
|
||
+
|
||
+ bool AllReadDataConsumed() const override { return state_ == QUIT; }
|
||
+
|
||
+ bool AllWriteDataConsumed() const override { return state_ == QUIT; }
|
||
+
|
||
+ void set_multiline_welcome(bool multiline) { multiline_welcome_ = multiline; }
|
||
+
|
||
+ bool use_epsv() const { return use_epsv_; }
|
||
+ void set_use_epsv(bool use_epsv) { use_epsv_ = use_epsv; }
|
||
+
|
||
+ void set_data_type(char data_type) { data_type_ = data_type; }
|
||
+
|
||
+ int short_read_limit() const { return short_read_limit_; }
|
||
+ void set_short_read_limit(int limit) { short_read_limit_ = limit; }
|
||
+
|
||
+ void set_allow_unconsumed_reads(bool allow) {
|
||
+ allow_unconsumed_reads_ = allow;
|
||
+ }
|
||
+
|
||
+ protected:
|
||
+ void Init() {
|
||
+ state_ = PRE_USER;
|
||
+ SimulateRead("220 host TestFTPd\r\n");
|
||
+ }
|
||
+
|
||
+ // If protocol fault injection has been requested, adjusts state and mocked
|
||
+ // read and returns true.
|
||
+ bool InjectFault() {
|
||
+ if (state_ != failure_injection_state_)
|
||
+ return false;
|
||
+ SimulateRead(fault_response_);
|
||
+ state_ = failure_injection_next_state_;
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ MockWriteResult Verify(const std::string& expected,
|
||
+ const std::string& data,
|
||
+ State next_state,
|
||
+ const char* next_read,
|
||
+ const size_t next_read_length) {
|
||
+ EXPECT_EQ(expected, data);
|
||
+ if (expected == data) {
|
||
+ state_ = next_state;
|
||
+ SimulateRead(next_read, next_read_length);
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ }
|
||
+ return MockWriteResult(ASYNC, ERR_UNEXPECTED);
|
||
+ }
|
||
+
|
||
+ MockWriteResult Verify(const std::string& expected,
|
||
+ const std::string& data,
|
||
+ State next_state,
|
||
+ const char* next_read) {
|
||
+ return Verify(expected, data, next_state,
|
||
+ next_read, std::strlen(next_read));
|
||
+ }
|
||
+
|
||
+ // The next time there is a read from this socket, it will return |data|.
|
||
+ // Before calling SimulateRead next time, the previous data must be consumed.
|
||
+ void SimulateRead(const char* data, size_t length) {
|
||
+ if (!allow_unconsumed_reads_) {
|
||
+ EXPECT_TRUE(reads_.empty()) << "Unconsumed read: " << reads_.front().data;
|
||
+ }
|
||
+ reads_.push_back(MockRead(ASYNC, data, length));
|
||
+ }
|
||
+ void SimulateRead(const char* data) { SimulateRead(data, std::strlen(data)); }
|
||
+
|
||
+ private:
|
||
+ // List of reads to be consumed.
|
||
+ base::circular_deque<MockRead> reads_;
|
||
+
|
||
+ // Max number of bytes we will read at a time. 0 means no limit.
|
||
+ int short_read_limit_;
|
||
+
|
||
+ // If true, we'll not require the client to consume all data before we
|
||
+ // mock the next read.
|
||
+ bool allow_unconsumed_reads_;
|
||
+
|
||
+ State state_;
|
||
+ State failure_injection_state_;
|
||
+ State failure_injection_next_state_;
|
||
+ const char* fault_response_;
|
||
+
|
||
+ // If true, we will send multiple 230 lines as response after PASS.
|
||
+ bool multiline_welcome_;
|
||
+
|
||
+ // If true, we will use EPSV command.
|
||
+ bool use_epsv_;
|
||
+
|
||
+ // Data type to be used for TYPE command.
|
||
+ char data_type_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProvider);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderDirectoryListing : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderDirectoryListing() = default;
|
||
+ ~FtpSocketDataProviderDirectoryListing() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /\r\n", data, PRE_CWD,
|
||
+ "550 I can only retrieve regular files\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD /\r\n", data,
|
||
+ use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
|
||
+ case PRE_LIST:
|
||
+ return Verify("LIST -l\r\n", data, PRE_QUIT, "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderDirectoryListing);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderDirectoryListingWithPasvFallback
|
||
+ : public FtpSocketDataProviderDirectoryListing {
|
||
+ public:
|
||
+ FtpSocketDataProviderDirectoryListingWithPasvFallback() = default;
|
||
+ ~FtpSocketDataProviderDirectoryListingWithPasvFallback() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_LIST_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_LIST_PASV,
|
||
+ "500 no EPSV for you\r\n");
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /\r\n", data, PRE_CWD,
|
||
+ "550 I can only retrieve regular files\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderDirectoryListing::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(
|
||
+ FtpSocketDataProviderDirectoryListingWithPasvFallback);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderVMSDirectoryListing : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderVMSDirectoryListing() = default;
|
||
+ ~FtpSocketDataProviderVMSDirectoryListing() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SYST:
|
||
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
|
||
+ case PRE_PWD:
|
||
+ return Verify("PWD\r\n", data, PRE_TYPE,
|
||
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
|
||
+ case PRE_LIST_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_LIST_PASV,
|
||
+ "500 Invalid command\r\n");
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD,
|
||
+ "550 I can only retrieve regular files\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data,
|
||
+ use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
|
||
+ case PRE_LIST:
|
||
+ return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSDirectoryListing);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderVMSDirectoryListingRootDirectory
|
||
+ : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory() = default;
|
||
+ ~FtpSocketDataProviderVMSDirectoryListingRootDirectory() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SYST:
|
||
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
|
||
+ case PRE_PWD:
|
||
+ return Verify("PWD\r\n", data, PRE_TYPE,
|
||
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
|
||
+ case PRE_LIST_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_LIST_PASV,
|
||
+ "500 EPSV command unknown\r\n");
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_CWD,
|
||
+ "550 I can only retrieve regular files\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data,
|
||
+ use_epsv() ? PRE_LIST_EPSV : PRE_LIST_PASV, "200 OK\r\n");
|
||
+ case PRE_LIST:
|
||
+ return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(
|
||
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownloadWithFileTypecode
|
||
+ : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownloadWithFileTypecode() = default;
|
||
+ ~FtpSocketDataProviderFileDownloadWithFileTypecode() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /file\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, "213 18\r\n");
|
||
+ case PRE_RETR:
|
||
+ return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithFileTypecode);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownload : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownload() = default;
|
||
+ ~FtpSocketDataProviderFileDownload() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify(base::StringPrintf("SIZE %s\r\n", file_path_.c_str()),
|
||
+ data, PRE_CWD, "213 18\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify(base::StringPrintf("CWD %s\r\n", file_path_.c_str()),
|
||
+ data, use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "550 Not a directory\r\n");
|
||
+ case PRE_RETR:
|
||
+ return Verify(base::StringPrintf("RETR %s\r\n", file_path_.c_str()),
|
||
+ data, PRE_QUIT, "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ void set_file_path(const std::string& file_path) { file_path_ = file_path; }
|
||
+
|
||
+ private:
|
||
+ std::string file_path_ = "/file";
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownload);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileNotFound : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileNotFound() = default;
|
||
+ ~FtpSocketDataProviderFileNotFound() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /file\r\n", data, PRE_CWD,
|
||
+ "550 File Not Found\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD /file\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "550 File Not Found\r\n");
|
||
+ case PRE_RETR:
|
||
+ return Verify("RETR /file\r\n", data, PRE_QUIT,
|
||
+ "550 File Not Found\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileNotFound);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownloadWithPasvFallback
|
||
+ : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownloadWithPasvFallback() = default;
|
||
+ ~FtpSocketDataProviderFileDownloadWithPasvFallback() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_RETR_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_RETR_PASV, "500 No can do\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD /file\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "550 Not a directory\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadWithPasvFallback);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownloadZeroSize
|
||
+ : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownloadZeroSize() = default;
|
||
+ ~FtpSocketDataProviderFileDownloadZeroSize() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /file\r\n", data, PRE_CWD,
|
||
+ "213 0\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD /file\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "550 not a directory\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadZeroSize);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownloadCWD451
|
||
+ : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownloadCWD451() = default;
|
||
+ ~FtpSocketDataProviderFileDownloadCWD451() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD /file\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "451 not a directory\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadCWD451);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderVMSFileDownload : public FtpSocketDataProvider {
|
||
+ public:
|
||
+ FtpSocketDataProviderVMSFileDownload() = default;
|
||
+ ~FtpSocketDataProviderVMSFileDownload() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SYST:
|
||
+ return Verify("SYST\r\n", data, PRE_PWD, "215 VMS\r\n");
|
||
+ case PRE_PWD:
|
||
+ return Verify("PWD\r\n", data, PRE_TYPE,
|
||
+ "257 \"ANONYMOUS_ROOT:[000000]\"\r\n");
|
||
+ case PRE_LIST_EPSV:
|
||
+ return Verify("EPSV\r\n", data, PRE_LIST_PASV,
|
||
+ "500 EPSV command unknown\r\n");
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_CWD,
|
||
+ "213 18\r\n");
|
||
+ case PRE_CWD:
|
||
+ return Verify("CWD ANONYMOUS_ROOT:[file]\r\n", data,
|
||
+ use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV,
|
||
+ "550 Not a directory\r\n");
|
||
+ case PRE_RETR:
|
||
+ return Verify("RETR ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_QUIT,
|
||
+ "200 OK\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProvider::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSFileDownload);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderFileDownloadInvalidResponse
|
||
+ : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderFileDownloadInvalidResponse() = default;
|
||
+ ~FtpSocketDataProviderFileDownloadInvalidResponse() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the
|
||
+ // generic ERR_FTP_FAILED bucket.
|
||
+ return Verify("SIZE /file\r\n", data, PRE_QUIT,
|
||
+ "599 Evil Response\r\n"
|
||
+ "599 More Evil\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownloadInvalidResponse);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderEvilEpsv : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
|
||
+ State expected_state)
|
||
+ : epsv_response_(epsv_response),
|
||
+ epsv_response_length_(std::strlen(epsv_response)),
|
||
+ expected_state_(expected_state) {}
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv(const char* epsv_response,
|
||
+ size_t epsv_response_length,
|
||
+ State expected_state)
|
||
+ : epsv_response_(epsv_response),
|
||
+ epsv_response_length_(epsv_response_length),
|
||
+ expected_state_(expected_state) {}
|
||
+
|
||
+ ~FtpSocketDataProviderEvilEpsv() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_RETR_EPSV:
|
||
+ return Verify("EPSV\r\n", data, expected_state_,
|
||
+ epsv_response_, epsv_response_length_);
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ const char* const epsv_response_;
|
||
+ const size_t epsv_response_length_;
|
||
+ const State expected_state_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilEpsv);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderEvilPasv
|
||
+ : public FtpSocketDataProviderFileDownloadWithPasvFallback {
|
||
+ public:
|
||
+ FtpSocketDataProviderEvilPasv(const char* pasv_response, State expected_state)
|
||
+ : pasv_response_(pasv_response),
|
||
+ expected_state_(expected_state) {
|
||
+ }
|
||
+ ~FtpSocketDataProviderEvilPasv() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_RETR_PASV:
|
||
+ return Verify("PASV\r\n", data, expected_state_, pasv_response_);
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownloadWithPasvFallback::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ const char* const pasv_response_;
|
||
+ const State expected_state_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilPasv);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderEvilSize : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderEvilSize(const char* size_response, State expected_state)
|
||
+ : size_response_(size_response),
|
||
+ expected_state_(expected_state) {
|
||
+ }
|
||
+ ~FtpSocketDataProviderEvilSize() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_SIZE:
|
||
+ return Verify("SIZE /file\r\n", data, expected_state_, size_response_);
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ const char* const size_response_;
|
||
+ const State expected_state_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilSize);
|
||
+};
|
||
+
|
||
+class FtpSocketDataProviderEvilLogin
|
||
+ : public FtpSocketDataProviderFileDownload {
|
||
+ public:
|
||
+ FtpSocketDataProviderEvilLogin(const char* expected_user,
|
||
+ const char* expected_password)
|
||
+ : expected_user_(expected_user),
|
||
+ expected_password_(expected_password) {
|
||
+ }
|
||
+ ~FtpSocketDataProviderEvilLogin() override = default;
|
||
+
|
||
+ MockWriteResult OnWrite(const std::string& data) override {
|
||
+ if (InjectFault())
|
||
+ return MockWriteResult(ASYNC, data.length());
|
||
+ switch (state()) {
|
||
+ case PRE_USER:
|
||
+ return Verify(std::string("USER ") + expected_user_ + "\r\n", data,
|
||
+ PRE_PASSWD, "331 Password needed\r\n");
|
||
+ case PRE_PASSWD:
|
||
+ return Verify(std::string("PASS ") + expected_password_ + "\r\n", data,
|
||
+ PRE_SYST, "230 Welcome\r\n");
|
||
+ default:
|
||
+ return FtpSocketDataProviderFileDownload::OnWrite(data);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ const char* const expected_user_;
|
||
+ const char* const expected_password_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEvilLogin);
|
||
+};
|
||
+
|
||
+class FtpNetworkTransactionTest : public PlatformTest,
|
||
+ public ::testing::WithParamInterface<int>,
|
||
+ public WithTaskEnvironment {
|
||
+ public:
|
||
+ FtpNetworkTransactionTest() : host_resolver_(new MockHostResolver) {
|
||
+ SetUpTransaction();
|
||
+
|
||
+ scoped_refptr<RuleBasedHostResolverProc> rules(
|
||
+ new RuleBasedHostResolverProc(nullptr));
|
||
+ if (GetFamily() == AF_INET) {
|
||
+ rules->AddIPLiteralRule("*", "127.0.0.1", "127.0.0.1");
|
||
+ } else if (GetFamily() == AF_INET6) {
|
||
+ rules->AddIPLiteralRule("*", "::1", "::1");
|
||
+ } else {
|
||
+ NOTREACHED();
|
||
+ }
|
||
+ host_resolver_->set_rules(rules.get());
|
||
+ }
|
||
+ ~FtpNetworkTransactionTest() override = default;
|
||
+
|
||
+ // Sets up an FtpNetworkTransaction and MocketClientSocketFactory, replacing
|
||
+ // the default one. Only needs to be called if a test runs multiple
|
||
+ // transactions.
|
||
+ void SetUpTransaction() {
|
||
+ mock_socket_factory_ = std::make_unique<MockClientSocketFactory>();
|
||
+ transaction_ = std::make_unique<FtpNetworkTransaction>(
|
||
+ host_resolver_.get(), mock_socket_factory_.get());
|
||
+ }
|
||
+
|
||
+ protected:
|
||
+ // Accessor to make code refactoring-friendly, e.g. when we change the way
|
||
+ // parameters are passed (like more parameters).
|
||
+ int GetFamily() {
|
||
+ return GetParam();
|
||
+ }
|
||
+
|
||
+ FtpRequestInfo GetRequestInfo(const std::string& url) {
|
||
+ FtpRequestInfo info;
|
||
+ info.url = GURL(url);
|
||
+ return info;
|
||
+ }
|
||
+
|
||
+ void ExecuteTransaction(FtpSocketDataProvider* ctrl_socket,
|
||
+ const char* request,
|
||
+ int expected_result) {
|
||
+ // Expect EPSV usage for non-IPv4 control connections.
|
||
+ ctrl_socket->set_use_epsv((GetFamily() != AF_INET));
|
||
+
|
||
+ mock_socket_factory_->AddSocketDataProvider(ctrl_socket);
|
||
+
|
||
+ std::string mock_data("mock-data");
|
||
+ MockRead data_reads[] = {
|
||
+ // Usually FTP servers close the data connection after the entire data has
|
||
+ // been received.
|
||
+ MockRead(SYNCHRONOUS, ERR_TEST_PEER_CLOSE_AFTER_NEXT_MOCK_READ),
|
||
+ MockRead(mock_data.c_str()),
|
||
+ };
|
||
+
|
||
+ std::unique_ptr<StaticSocketDataProvider> data_socket =
|
||
+ std::make_unique<StaticSocketDataProvider>(data_reads,
|
||
+ base::span<MockWrite>());
|
||
+ mock_socket_factory_->AddSocketDataProvider(data_socket.get());
|
||
+ FtpRequestInfo request_info = GetRequestInfo(request);
|
||
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
|
||
+ ASSERT_EQ(
|
||
+ ERR_IO_PENDING,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ EXPECT_NE(LOAD_STATE_IDLE, transaction_->GetLoadState());
|
||
+ ASSERT_EQ(expected_result, callback_.WaitForResult());
|
||
+ if (expected_result == OK) {
|
||
+ scoped_refptr<IOBuffer> io_buffer =
|
||
+ base::MakeRefCounted<IOBuffer>(kBufferSize);
|
||
+ memset(io_buffer->data(), 0, kBufferSize);
|
||
+ ASSERT_EQ(ERR_IO_PENDING, transaction_->Read(io_buffer.get(), kBufferSize,
|
||
+ callback_.callback()));
|
||
+ ASSERT_EQ(static_cast<int>(mock_data.length()),
|
||
+ callback_.WaitForResult());
|
||
+ EXPECT_EQ(mock_data, std::string(io_buffer->data(), mock_data.length()));
|
||
+
|
||
+ // Do another Read to detect that the data socket is now closed.
|
||
+ int rv = transaction_->Read(io_buffer.get(), kBufferSize,
|
||
+ callback_.callback());
|
||
+ if (rv == ERR_IO_PENDING) {
|
||
+ EXPECT_EQ(0, callback_.WaitForResult());
|
||
+ } else {
|
||
+ EXPECT_EQ(0, rv);
|
||
+ }
|
||
+ }
|
||
+ EXPECT_EQ(FtpSocketDataProvider::QUIT, ctrl_socket->state());
|
||
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
|
||
+ }
|
||
+
|
||
+ void TransactionFailHelper(FtpSocketDataProvider* ctrl_socket,
|
||
+ const char* request,
|
||
+ FtpSocketDataProvider::State state,
|
||
+ FtpSocketDataProvider::State next_state,
|
||
+ const char* response,
|
||
+ int expected_result) {
|
||
+ ctrl_socket->InjectFailure(state, next_state, response);
|
||
+ ExecuteTransaction(ctrl_socket, request, expected_result);
|
||
+ }
|
||
+
|
||
+ std::unique_ptr<MockHostResolver> host_resolver_;
|
||
+ std::unique_ptr<MockClientSocketFactory> mock_socket_factory_;
|
||
+ std::unique_ptr<FtpNetworkTransaction> transaction_;
|
||
+ TestCompletionCallback callback_;
|
||
+};
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, FailedLookup) {
|
||
+ FtpRequestInfo request_info = GetRequestInfo("ftp://badhost");
|
||
+ scoped_refptr<RuleBasedHostResolverProc> rules(
|
||
+ new RuleBasedHostResolverProc(nullptr));
|
||
+ rules->AddSimulatedFailure("badhost");
|
||
+ host_resolver_->set_rules(rules.get());
|
||
+
|
||
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
|
||
+ ASSERT_EQ(
|
||
+ ERR_IO_PENDING,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_NAME_NOT_RESOLVED));
|
||
+ EXPECT_EQ(LOAD_STATE_IDLE, transaction_->GetLoadState());
|
||
+}
|
||
+
|
||
+// Check that when determining the host, the square brackets decorating IPv6
|
||
+// literals in URLs are stripped.
|
||
+TEST_P(FtpNetworkTransactionTest, StripBracketsFromIPv6Literals) {
|
||
+ // This test only makes sense for IPv6 connections.
|
||
+ if (GetFamily() != AF_INET6)
|
||
+ return;
|
||
+
|
||
+ host_resolver_->rules()->AddSimulatedFailure("[::1]");
|
||
+
|
||
+ // We start a transaction that is expected to fail with ERR_INVALID_RESPONSE.
|
||
+ // The important part of this test is to make sure that we don't fail with
|
||
+ // ERR_NAME_NOT_RESOLVED, since that would mean the decorated hostname
|
||
+ // was used.
|
||
+ FtpSocketDataProviderEvilSize ctrl_socket(
|
||
+ "213 99999999999999999999999999999999\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://[::1]/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransaction) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+
|
||
+ EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
|
||
+ EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
|
||
+ EXPECT_EQ(
|
||
+ (GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
|
||
+ transaction_->GetResponseInfo()->remote_endpoint.ToStringWithoutPort());
|
||
+ EXPECT_EQ(21, transaction_->GetResponseInfo()->remote_endpoint.port());
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithPasvFallback) {
|
||
+ FtpSocketDataProviderDirectoryListingWithPasvFallback ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+
|
||
+ EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
|
||
+ EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionWithTypecode) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/;type=d", OK);
|
||
+
|
||
+ EXPECT_TRUE(transaction_->GetResponseInfo()->is_directory_listing);
|
||
+ EXPECT_EQ(-1, transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcome) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.set_multiline_welcome(true);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads2) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.set_short_read_limit(2);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionShortReads5) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.set_short_read_limit(5);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcomeShort) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // The client will not consume all three 230 lines. That's good, we want to
|
||
+ // test that scenario.
|
||
+ ctrl_socket.set_allow_unconsumed_reads(true);
|
||
+ ctrl_socket.set_multiline_welcome(true);
|
||
+ ctrl_socket.set_short_read_limit(5);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+// Regression test for http://crbug.com/60555.
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionZeroSize) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_SIZE,
|
||
+ FtpSocketDataProvider::PRE_CWD, "213 0\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMS) {
|
||
+ FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/dir", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionVMSRootDirectory) {
|
||
+ FtpSocketDataProviderVMSDirectoryListingRootDirectory ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionTransferStarting) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_LIST,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "125-Data connection already open.\r\n"
|
||
+ "125 Transfer starting.\r\n"
|
||
+ "226 Transfer complete.\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransaction) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+
|
||
+ // We pass an artificial value of 18 as a response to the SIZE command.
|
||
+ EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
|
||
+ EXPECT_EQ(
|
||
+ (GetFamily() == AF_INET) ? "127.0.0.1" : "::1",
|
||
+ transaction_->GetResponseInfo()->remote_endpoint.ToStringWithoutPort());
|
||
+ EXPECT_EQ(21, transaction_->GetResponseInfo()->remote_endpoint.port());
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithPasvFallback) {
|
||
+ FtpSocketDataProviderFileDownloadWithPasvFallback ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+
|
||
+ // We pass an artificial value of 18 as a response to the SIZE command.
|
||
+ EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeA) {
|
||
+ FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
|
||
+ ctrl_socket.set_data_type('A');
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=a", OK);
|
||
+
|
||
+ // We pass an artificial value of 18 as a response to the SIZE command.
|
||
+ EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionWithTypecodeI) {
|
||
+ FtpSocketDataProviderFileDownloadWithFileTypecode ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file;type=i", OK);
|
||
+
|
||
+ // We pass an artificial value of 18 as a response to the SIZE command.
|
||
+ EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionMultilineWelcome) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.set_multiline_welcome(true);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads2) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.set_short_read_limit(2);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionShortReads5) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.set_short_read_limit(5);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionZeroSize) {
|
||
+ FtpSocketDataProviderFileDownloadZeroSize ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionCWD451) {
|
||
+ FtpSocketDataProviderFileDownloadCWD451 ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionVMS) {
|
||
+ FtpSocketDataProviderVMSFileDownload ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionTransferStarting) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_RETR,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "125-Data connection already open.\r\n"
|
||
+ "125 Transfer starting.\r\n"
|
||
+ "226 Transfer complete.\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionInvalidResponse) {
|
||
+ FtpSocketDataProviderFileDownloadInvalidResponse ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvReallyBadFormat) {
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort1) {
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,0,22)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort2) {
|
||
+ // Still unsafe. 1 * 256 + 2 = 258, which is < 1024.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,1,2)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort3) {
|
||
+ // Still unsafe. 3 * 256 + 4 = 772, which is < 1024.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,3,4)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafePort4) {
|
||
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket("227 Portscan (127,0,0,1,8,1)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort1) {
|
||
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket(
|
||
+ "227 Portscan (127,0,0,1,256,100)\r\n", FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort2) {
|
||
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket(
|
||
+ "227 Portscan (127,0,0,1,100,256)\r\n", FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort3) {
|
||
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket(
|
||
+ "227 Portscan (127,0,0,1,-100,100)\r\n", FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvInvalidPort4) {
|
||
+ // Unsafe. 8 * 256 + 1 = 2049, which is used by nfs.
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket(
|
||
+ "227 Portscan (127,0,0,1,100,-100)\r\n", FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilPasvUnsafeHost) {
|
||
+ FtpSocketDataProviderEvilPasv ctrl_socket(
|
||
+ "227 Portscan (10,1,2,3,123,123)\r\n", FtpSocketDataProvider::PRE_RETR);
|
||
+ ctrl_socket.set_use_epsv(GetFamily() != AF_INET);
|
||
+ std::string mock_data("mock-data");
|
||
+ MockRead data_reads[] = {
|
||
+ MockRead(mock_data.c_str()),
|
||
+ };
|
||
+ StaticSocketDataProvider data_socket1;
|
||
+ StaticSocketDataProvider data_socket2(data_reads, base::span<MockWrite>());
|
||
+ mock_socket_factory_->AddSocketDataProvider(&ctrl_socket);
|
||
+ mock_socket_factory_->AddSocketDataProvider(&data_socket1);
|
||
+ mock_socket_factory_->AddSocketDataProvider(&data_socket2);
|
||
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
|
||
+
|
||
+ // Start the transaction.
|
||
+ ASSERT_EQ(
|
||
+ ERR_IO_PENDING,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ ASSERT_THAT(callback_.WaitForResult(), IsOk());
|
||
+
|
||
+ // The transaction fires the callback when we can start reading data. That
|
||
+ // means that the data socket should be open.
|
||
+ MockTCPClientSocket* data_socket =
|
||
+ static_cast<MockTCPClientSocket*>(transaction_->data_socket_.get());
|
||
+ ASSERT_TRUE(data_socket);
|
||
+ ASSERT_TRUE(data_socket->IsConnected());
|
||
+
|
||
+ // Even if the PASV response specified some other address, we connect
|
||
+ // to the address we used for control connection (which could be 127.0.0.1
|
||
+ // or ::1 depending on whether we use IPv6).
|
||
+ for (auto it = data_socket->addresses().begin();
|
||
+ it != data_socket->addresses().end(); ++it) {
|
||
+ EXPECT_NE("10.1.2.3", it->ToStringWithoutPort());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat1) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat2) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat3) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat4) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (||||)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvReallyBadFormat5) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ // Breaking the string in the next line prevents MSVC warning C4125.
|
||
+ const char response[] = "227 Portscan (\0\0\031" "773\0)\r\n";
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket(response, sizeof(response)-1,
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort1) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||22|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort2) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||258|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort3) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||772|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvUnsafePort4) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||2049|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvInvalidPort) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|||4294973296|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvWeirdSep) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$31744$)\r\n",
|
||
+ FtpSocketDataProvider::PRE_RETR);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest,
|
||
+ DownloadTransactionEvilEpsvWeirdSepUnsafePort) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan ($$$317$)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_UNSAFE_PORT);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilEpsvIllegalHost) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderEvilEpsv ctrl_socket("227 Portscan (|2|::1|31744|)\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadUsername) {
|
||
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello%0Aworld", "test");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%0Aworld:test@host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilLoginBadPassword) {
|
||
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello%0Dworld");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%0Dworld@host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInLogin) {
|
||
+ FtpSocketDataProviderEvilLogin ctrl_socket("hello world", "test");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://hello%20world:test@host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionSpaceInPassword) {
|
||
+ FtpSocketDataProviderEvilLogin ctrl_socket("test", "hello world");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%20world@host/file", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, FailOnInvalidUrls) {
|
||
+ const char* const kBadUrls[]{
|
||
+ // Make sure FtpNetworkTransaction doesn't request paths like
|
||
+ // "/foo/../bar". Doing so wouldn't be a security issue, client side, but
|
||
+ // just doesn't seem like a good idea.
|
||
+ "ftp://host/foo%2f..%2fbar%5c",
|
||
+
|
||
+ // LF
|
||
+ "ftp://host/foo%10.txt",
|
||
+ // CR
|
||
+ "ftp://host/foo%13.txt",
|
||
+
|
||
+ "ftp://host/foo%00.txt",
|
||
+ };
|
||
+
|
||
+ for (const char* bad_url : kBadUrls) {
|
||
+ SCOPED_TRACE(bad_url);
|
||
+
|
||
+ SetUpTransaction();
|
||
+ FtpRequestInfo request_info = GetRequestInfo(bad_url);
|
||
+ ASSERT_EQ(
|
||
+ ERR_INVALID_URL,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, EvilRestartUser) {
|
||
+ FtpSocketDataProvider ctrl_socket1;
|
||
+ ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "530 Login authentication failed\r\n");
|
||
+ mock_socket_factory_->AddSocketDataProvider(&ctrl_socket1);
|
||
+
|
||
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
|
||
+
|
||
+ ASSERT_EQ(
|
||
+ ERR_IO_PENDING,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_FTP_FAILED));
|
||
+
|
||
+ MockRead ctrl_reads[] = {
|
||
+ MockRead("220 host TestFTPd\r\n"),
|
||
+ MockRead("221 Goodbye!\r\n"),
|
||
+ MockRead(SYNCHRONOUS, OK),
|
||
+ };
|
||
+ MockWrite ctrl_writes[] = {
|
||
+ MockWrite("QUIT\r\n"),
|
||
+ };
|
||
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
|
||
+ mock_socket_factory_->AddSocketDataProvider(&ctrl_socket2);
|
||
+ ASSERT_EQ(ERR_IO_PENDING, transaction_->RestartWithAuth(
|
||
+ AuthCredentials(u"foo\nownz0red", u"innocent"),
|
||
+ callback_.callback()));
|
||
+ EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_MALFORMED_IDENTITY));
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, EvilRestartPassword) {
|
||
+ FtpSocketDataProvider ctrl_socket1;
|
||
+ ctrl_socket1.InjectFailure(FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "530 Login authentication failed\r\n");
|
||
+ mock_socket_factory_->AddSocketDataProvider(&ctrl_socket1);
|
||
+
|
||
+ FtpRequestInfo request_info = GetRequestInfo("ftp://host/file");
|
||
+
|
||
+ ASSERT_EQ(
|
||
+ ERR_IO_PENDING,
|
||
+ transaction_->Start(&request_info, callback_.callback(),
|
||
+ NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ ASSERT_THAT(callback_.WaitForResult(), IsError(ERR_FTP_FAILED));
|
||
+
|
||
+ MockRead ctrl_reads[] = {
|
||
+ MockRead("220 host TestFTPd\r\n"),
|
||
+ MockRead("331 User okay, send password\r\n"),
|
||
+ MockRead("221 Goodbye!\r\n"),
|
||
+ MockRead(SYNCHRONOUS, OK),
|
||
+ };
|
||
+ MockWrite ctrl_writes[] = {
|
||
+ MockWrite("USER innocent\r\n"),
|
||
+ MockWrite("QUIT\r\n"),
|
||
+ };
|
||
+ StaticSocketDataProvider ctrl_socket2(ctrl_reads, ctrl_writes);
|
||
+ mock_socket_factory_->AddSocketDataProvider(&ctrl_socket2);
|
||
+ ASSERT_EQ(ERR_IO_PENDING, transaction_->RestartWithAuth(
|
||
+ AuthCredentials(u"innocent", u"foo\nownz0red"),
|
||
+ callback_.callback()));
|
||
+ EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_MALFORMED_IDENTITY));
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, Escaping) {
|
||
+ const struct TestCase {
|
||
+ const char* url;
|
||
+ const char* expected_path;
|
||
+ } kTestCases[] = {
|
||
+ {"ftp://host/%20%21%22%23%24%25%79%80%81", "/ !\"#$%y\200\201"},
|
||
+ // This is no allowed to be unescaped by UnescapeURLComponent, since it's
|
||
+ // a lock icon. But it has no special meaning or security concern in the
|
||
+ // context of making FTP requests.
|
||
+ {"ftp://host/%F0%9F%94%92", "/\xF0\x9F\x94\x92"},
|
||
+ // Invalid UTF-8 character, which again has no special meaning over FTP.
|
||
+ {"ftp://host/%81", "/\x81"},
|
||
+ };
|
||
+
|
||
+ for (const auto& test_case : kTestCases) {
|
||
+ SCOPED_TRACE(test_case.url);
|
||
+
|
||
+ SetUpTransaction();
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.set_file_path(test_case.expected_path);
|
||
+ ExecuteTransaction(&ctrl_socket, test_case.url, OK);
|
||
+ }
|
||
+}
|
||
+
|
||
+// Test for http://crbug.com/23794.
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionEvilSize) {
|
||
+ // Try to overflow int64_t in the response.
|
||
+ FtpSocketDataProviderEvilSize ctrl_socket(
|
||
+ "213 99999999999999999999999999999999\r\n",
|
||
+ FtpSocketDataProvider::PRE_QUIT);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+// Test for http://crbug.com/36360.
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionBigSize) {
|
||
+ // Pass a valid, but large file size. The transaction should not fail.
|
||
+ FtpSocketDataProviderEvilSize ctrl_socket(
|
||
+ "213 3204427776\r\n",
|
||
+ FtpSocketDataProvider::PRE_CWD);
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK);
|
||
+ EXPECT_EQ(3204427776LL,
|
||
+ transaction_->GetResponseInfo()->expected_content_size);
|
||
+}
|
||
+
|
||
+// Regression test for http://crbug.com/25023.
|
||
+TEST_P(FtpNetworkTransactionTest, CloseConnection) {
|
||
+ FtpSocketDataProvider ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_USER,
|
||
+ FtpSocketDataProvider::PRE_QUIT, "");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host", ERR_EMPTY_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailUser) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_USER,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "530 Login authentication failed\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+// Regression test for http://crbug.com/38707.
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPass503) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "503 Bad sequence of commands\r\n",
|
||
+ ERR_FTP_BAD_COMMAND_SEQUENCE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailSyst) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_SYST,
|
||
+ FtpSocketDataProvider::PRE_PWD,
|
||
+ "599 fail\r\n",
|
||
+ OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailPwd) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailType) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_TYPE,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailEpsv) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(
|
||
+ &ctrl_socket, "ftp://host", FtpSocketDataProvider::PRE_LIST_EPSV,
|
||
+ FtpSocketDataProvider::PRE_NOPASV, "599 fail\r\n", ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailCwd) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host",
|
||
+ FtpSocketDataProvider::PRE_CWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DirectoryTransactionFailList) {
|
||
+ FtpSocketDataProviderVMSDirectoryListing ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/dir",
|
||
+ FtpSocketDataProvider::PRE_LIST,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailUser) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_USER,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPass) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "530 Login authentication failed\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailSyst) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_SYST,
|
||
+ FtpSocketDataProvider::PRE_PWD,
|
||
+ "599 fail\r\n",
|
||
+ OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailPwd) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailType) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_TYPE,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailEpsv) {
|
||
+ // This test makes no sense for IPv4 connections (we don't use EPSV there).
|
||
+ if (GetFamily() == AF_INET)
|
||
+ return;
|
||
+
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(
|
||
+ &ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_RETR_EPSV,
|
||
+ FtpSocketDataProvider::PRE_NOPASV, "599 fail\r\n", ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, DownloadTransactionFailRetr) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ // Use unallocated 599 FTP error code to make sure it falls into the generic
|
||
+ // ERR_FTP_FAILED bucket.
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_RETR,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "599 fail\r\n",
|
||
+ ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, FileNotFound) {
|
||
+ FtpSocketDataProviderFileNotFound ctrl_socket;
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_FTP_FAILED);
|
||
+}
|
||
+
|
||
+// Test for http://crbug.com/38845.
|
||
+TEST_P(FtpNetworkTransactionTest, ZeroLengthDirInPWD) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ TransactionFailHelper(&ctrl_socket,
|
||
+ "ftp://host/file",
|
||
+ FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_TYPE,
|
||
+ "257 \"\"\r\n",
|
||
+ OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, UnexpectedInitiatedResponseForDirectory) {
|
||
+ // The states for a directory listing where an initiated response will cause
|
||
+ // an error. Includes all commands sent on the directory listing path, except
|
||
+ // CWD, SIZE, LIST, and QUIT commands.
|
||
+ FtpSocketDataProvider::State kFailingStates[] = {
|
||
+ FtpSocketDataProvider::PRE_USER, FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_SYST, FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_TYPE,
|
||
+ GetFamily() != AF_INET ? FtpSocketDataProvider::PRE_LIST_EPSV
|
||
+ : FtpSocketDataProvider::PRE_LIST_PASV,
|
||
+ FtpSocketDataProvider::PRE_CWD,
|
||
+ };
|
||
+
|
||
+ for (FtpSocketDataProvider::State state : kFailingStates) {
|
||
+ SetUpTransaction();
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(state, FtpSocketDataProvider::PRE_QUIT,
|
||
+ "157 Foo\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, UnexpectedInitiatedResponseForFile) {
|
||
+ // The states for a download where an initiated response will cause an error.
|
||
+ // Includes all commands sent on the file download path, except CWD, SIZE, and
|
||
+ // QUIT commands.
|
||
+ const FtpSocketDataProvider::State kFailingStates[] = {
|
||
+ FtpSocketDataProvider::PRE_USER, FtpSocketDataProvider::PRE_PASSWD,
|
||
+ FtpSocketDataProvider::PRE_SYST, FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_TYPE,
|
||
+ GetFamily() != AF_INET ? FtpSocketDataProvider::PRE_RETR_EPSV
|
||
+ : FtpSocketDataProvider::PRE_RETR_PASV,
|
||
+ FtpSocketDataProvider::PRE_CWD};
|
||
+
|
||
+ for (FtpSocketDataProvider::State state : kFailingStates) {
|
||
+ LOG(ERROR) << "??: " << state;
|
||
+ SetUpTransaction();
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(state, FtpSocketDataProvider::PRE_QUIT,
|
||
+ "157 Foo\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/file", ERR_INVALID_RESPONSE);
|
||
+ }
|
||
+}
|
||
+
|
||
+// Make sure that receiving extra unexpected responses correctly results in
|
||
+// sending a QUIT message, without triggering a DCHECK.
|
||
+TEST_P(FtpNetworkTransactionTest, ExtraResponses) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_TYPE,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "157 Foo\r\n"
|
||
+ "157 Bar\r\n"
|
||
+ "157 Trombones\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+// Make sure that receiving extra unexpected responses to a QUIT message
|
||
+// correctly results in ending the transaction with an error, without triggering
|
||
+// a DCHECK.
|
||
+TEST_P(FtpNetworkTransactionTest, ExtraQuitResponses) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_QUIT,
|
||
+ FtpSocketDataProvider::QUIT,
|
||
+ "221 Foo\r\n"
|
||
+ "221 Bar\r\n"
|
||
+ "221 Trombones\r\n");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+// Test case for https://crbug.com/633841 - similar to the ExtraQuitResponses
|
||
+// test case, but with an empty response.
|
||
+TEST_P(FtpNetworkTransactionTest, EmptyQuitResponse) {
|
||
+ FtpSocketDataProviderDirectoryListing ctrl_socket;
|
||
+ ctrl_socket.InjectFailure(FtpSocketDataProvider::PRE_QUIT,
|
||
+ FtpSocketDataProvider::QUIT, "");
|
||
+ ExecuteTransaction(&ctrl_socket, "ftp://host/", OK);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, InvalidRemoteDirectory) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ TransactionFailHelper(
|
||
+ &ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "257 \"foo\rbar\" is your current location\r\n", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+TEST_P(FtpNetworkTransactionTest, InvalidRemoteDirectory2) {
|
||
+ FtpSocketDataProviderFileDownload ctrl_socket;
|
||
+ TransactionFailHelper(
|
||
+ &ctrl_socket, "ftp://host/file", FtpSocketDataProvider::PRE_PWD,
|
||
+ FtpSocketDataProvider::PRE_QUIT,
|
||
+ "257 \"foo\nbar\" is your current location\r\n", ERR_INVALID_RESPONSE);
|
||
+}
|
||
+
|
||
+INSTANTIATE_TEST_SUITE_P(Ftp,
|
||
+ FtpNetworkTransactionTest,
|
||
+ ::testing::Values(AF_INET, AF_INET6));
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_request_info.h b/net/ftp/ftp_request_info.h
|
||
new file mode 100644
|
||
index 0000000000000..be7702fd3d9c0
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_request_info.h
|
||
@@ -0,0 +1,20 @@
|
||
+// Copyright (c) 2010 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_REQUEST_INFO_H_
|
||
+#define NET_FTP_FTP_REQUEST_INFO_H_
|
||
+
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpRequestInfo {
|
||
+ public:
|
||
+ // The requested URL.
|
||
+ GURL url;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_REQUEST_INFO_H_
|
||
diff --git a/net/ftp/ftp_response_info.cc b/net/ftp/ftp_response_info.cc
|
||
new file mode 100644
|
||
index 0000000000000..51b3c0e3aa367
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_response_info.cc
|
||
@@ -0,0 +1,17 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_response_info.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+FtpResponseInfo::FtpResponseInfo()
|
||
+ : needs_auth(false),
|
||
+ expected_content_size(-1),
|
||
+ is_directory_listing(false) {
|
||
+}
|
||
+
|
||
+FtpResponseInfo::~FtpResponseInfo() = default;
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_response_info.h b/net/ftp/ftp_response_info.h
|
||
new file mode 100644
|
||
index 0000000000000..576ef2501b465
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_response_info.h
|
||
@@ -0,0 +1,46 @@
|
||
+// Copyright (c) 2010 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_RESPONSE_INFO_H_
|
||
+#define NET_FTP_FTP_RESPONSE_INFO_H_
|
||
+
|
||
+#include <stdint.h>
|
||
+
|
||
+#include "base/time/time.h"
|
||
+#include "net/base/ip_endpoint.h"
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class NET_EXPORT_PRIVATE FtpResponseInfo {
|
||
+ public:
|
||
+ FtpResponseInfo();
|
||
+ ~FtpResponseInfo();
|
||
+
|
||
+ // True if authentication failed and valid authentication credentials are
|
||
+ // needed.
|
||
+ bool needs_auth;
|
||
+
|
||
+ // The time at which the request was made that resulted in this response.
|
||
+ // For cached responses, this time could be "far" in the past.
|
||
+ base::Time request_time;
|
||
+
|
||
+ // The time at which the response headers were received. For cached
|
||
+ // responses, this time could be "far" in the past.
|
||
+ base::Time response_time;
|
||
+
|
||
+ // Expected content size, in bytes, as reported by SIZE command. Only valid
|
||
+ // for file downloads. -1 means unknown size.
|
||
+ int64_t expected_content_size;
|
||
+
|
||
+ // True if the response data is of a directory listing.
|
||
+ bool is_directory_listing;
|
||
+
|
||
+ // Remote address of the socket which fetched this resource.
|
||
+ IPEndPoint remote_endpoint;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_RESPONSE_INFO_H_
|
||
diff --git a/net/ftp/ftp_server_type.h b/net/ftp/ftp_server_type.h
|
||
new file mode 100644
|
||
index 0000000000000..18b0595bd9500
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_server_type.h
|
||
@@ -0,0 +1,27 @@
|
||
+// Copyright (c) 2009 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_SERVER_TYPE_H_
|
||
+#define NET_FTP_FTP_SERVER_TYPE_H_
|
||
+
|
||
+namespace net {
|
||
+
|
||
+enum FtpServerType {
|
||
+ // Cases in which we couldn't parse the server's response. That means
|
||
+ // a server type we don't recognize, a security attack (when what we're
|
||
+ // connecting to isn't an FTP server), or a broken server.
|
||
+ SERVER_UNKNOWN = 0,
|
||
+
|
||
+ SERVER_LS = 1, // Server using /bin/ls -l listing style.
|
||
+ SERVER_WINDOWS = 2, // Server using Windows listing style.
|
||
+ SERVER_VMS = 3, // Server using VMS listing style.
|
||
+ SERVER_NETWARE = 4, // OBSOLETE. Server using Netware listing style.
|
||
+ SERVER_OS2 = 5, // OBSOLETE. Server using OS/2 listing style.
|
||
+
|
||
+ NUM_OF_SERVER_TYPES
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_SERVER_TYPE_H_
|
||
diff --git a/net/ftp/ftp_transaction.h b/net/ftp/ftp_transaction.h
|
||
new file mode 100644
|
||
index 0000000000000..dcb67243f9dae
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_transaction.h
|
||
@@ -0,0 +1,82 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_TRANSACTION_H_
|
||
+#define NET_FTP_FTP_TRANSACTION_H_
|
||
+
|
||
+#include <stdint.h>
|
||
+
|
||
+#include "net/base/completion_once_callback.h"
|
||
+#include "net/base/io_buffer.h"
|
||
+#include "net/base/load_states.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/traffic_annotation/network_traffic_annotation.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class AuthCredentials;
|
||
+class FtpResponseInfo;
|
||
+class FtpRequestInfo;
|
||
+class NetLogWithSource;
|
||
+
|
||
+// Represents a single FTP transaction.
|
||
+class NET_EXPORT_PRIVATE FtpTransaction {
|
||
+ public:
|
||
+ // Stops any pending IO and destroys the transaction object.
|
||
+ virtual ~FtpTransaction() {}
|
||
+
|
||
+ // Starts the FTP transaction (i.e., sends the FTP request).
|
||
+ //
|
||
+ // Returns OK if the transaction could be started synchronously, which means
|
||
+ // that the request was served from the cache (only supported for directory
|
||
+ // listings). ERR_IO_PENDING is returned to indicate that |callback| will be
|
||
+ // notified once response info is available or if an IO error occurs. Any
|
||
+ // other return value indicates that the transaction could not be started.
|
||
+ //
|
||
+ // Regardless of the return value, the caller is expected to keep the
|
||
+ // request_info object alive until Destroy is called on the transaction.
|
||
+ //
|
||
+ // NOTE: The transaction is not responsible for deleting the callback object.
|
||
+ //
|
||
+ // Profiling information for the request is saved to |net_log| if non-NULL.
|
||
+ virtual int Start(raw_ptr <FtpRequestInfo> request_info,
|
||
+ CompletionOnceCallback callback,
|
||
+ const NetLogWithSource& net_log,
|
||
+ const NetworkTrafficAnnotationTag& traffic_annotation) = 0;
|
||
+
|
||
+ // Restarts the FTP transaction with authentication credentials.
|
||
+ virtual int RestartWithAuth(const AuthCredentials& credentials,
|
||
+ CompletionOnceCallback callback) = 0;
|
||
+
|
||
+ // Once response info is available for the transaction, response data may be
|
||
+ // read by calling this method.
|
||
+ //
|
||
+ // Response data is copied into the given buffer and the number of bytes
|
||
+ // copied is returned. ERR_IO_PENDING is returned if response data is not yet
|
||
+ // available. |callback| is notified when the data copy completes, and it is
|
||
+ // passed the number of bytes that were successfully copied. Or, if a read
|
||
+ // error occurs, |callback| is notified of the error. Any other negative
|
||
+ // return value indicates that the transaction could not be read.
|
||
+ //
|
||
+ // NOTE: The transaction is not responsible for deleting the callback object.
|
||
+ //
|
||
+ virtual int Read(IOBuffer* buf,
|
||
+ int buf_len,
|
||
+ CompletionOnceCallback callback) = 0;
|
||
+
|
||
+ // Returns the response info for this transaction or NULL if the response
|
||
+ // info is not available.
|
||
+ virtual const FtpResponseInfo* GetResponseInfo() const = 0;
|
||
+
|
||
+ // Returns the load state for this transaction.
|
||
+ virtual LoadState GetLoadState() const = 0;
|
||
+
|
||
+ // Returns the upload progress in bytes. If there is no upload data,
|
||
+ // zero will be returned.
|
||
+ virtual uint64_t GetUploadProgress() const = 0;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_TRANSACTION_H_
|
||
diff --git a/net/ftp/ftp_transaction_factory.h b/net/ftp/ftp_transaction_factory.h
|
||
new file mode 100644
|
||
index 0000000000000..5f4270f25ca7d
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_transaction_factory.h
|
||
@@ -0,0 +1,31 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_TRANSACTION_FACTORY_H_
|
||
+#define NET_FTP_FTP_TRANSACTION_FACTORY_H_
|
||
+
|
||
+#include <memory>
|
||
+
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpTransaction;
|
||
+
|
||
+// An interface to a class that can create FtpTransaction objects.
|
||
+class NET_EXPORT FtpTransactionFactory {
|
||
+ public:
|
||
+ virtual ~FtpTransactionFactory() {}
|
||
+
|
||
+ // Creates a FtpTransaction object.
|
||
+ virtual std::unique_ptr<FtpTransaction> CreateTransaction() = 0;
|
||
+
|
||
+ // Suspends the creation of new transactions. If |suspend| is false, creation
|
||
+ // of new transactions is resumed.
|
||
+ virtual void Suspend(bool suspend) = 0;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_TRANSACTION_FACTORY_H_
|
||
diff --git a/net/ftp/ftp_util.cc b/net/ftp/ftp_util.cc
|
||
new file mode 100644
|
||
index 0000000000000..5d06d8d81d459
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_util.cc
|
||
@@ -0,0 +1,378 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifdef UNSAFE_BUFFERS_BUILD
|
||
+// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
|
||
+#pragma allow_unsafe_buffers
|
||
+#endif
|
||
+
|
||
+#include "net/ftp/ftp_util.h"
|
||
+
|
||
+#include <map>
|
||
+#include <vector>
|
||
+
|
||
+#include "base/check_op.h"
|
||
+#include "base/i18n/case_conversion.h"
|
||
+#include "base/i18n/char_iterator.h"
|
||
+#include "base/i18n/unicodestring.h"
|
||
+#include "base/memory/singleton.h"
|
||
+#include "base/strings/strcat.h"
|
||
+#include "base/strings/string_number_conversions.h"
|
||
+#include "base/strings/string_split.h"
|
||
+#include "base/strings/string_tokenizer.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "third_party/icu/source/common/unicode/uchar.h"
|
||
+#include "third_party/icu/source/i18n/unicode/datefmt.h"
|
||
+#include "third_party/icu/source/i18n/unicode/dtfmtsym.h"
|
||
+
|
||
+using base::ASCIIToUTF16;
|
||
+using std::u16string_view;
|
||
+
|
||
+// For examples of Unix<->VMS path conversions, see the unit test file. On VMS
|
||
+// a path looks differently depending on whether it's a file or directory.
|
||
+
|
||
+namespace net {
|
||
+
|
||
+// static
|
||
+std::string FtpUtil::UnixFilePathToVMS(const std::string& unix_path) {
|
||
+ if (unix_path.empty())
|
||
+ return std::string();
|
||
+
|
||
+ base::StringTokenizer tokenizer(unix_path, "/");
|
||
+ std::vector<std::string_view> tokens;
|
||
+ while (tokenizer.GetNext())
|
||
+ tokens.push_back(tokenizer.token_piece());
|
||
+
|
||
+ if (unix_path[0] == '/') {
|
||
+ // It's an absolute path.
|
||
+
|
||
+ if (tokens.empty()) {
|
||
+ // It's just "/" or a series of slashes, which all mean the same thing.
|
||
+ return "[]";
|
||
+ }
|
||
+
|
||
+ if (tokens.size() == 1)
|
||
+ return std::string(tokens.front()); // Return without leading slashes.
|
||
+
|
||
+ std::string result = base::StrCat({tokens[0], ":["});
|
||
+ if (tokens.size() == 2) {
|
||
+ // Don't ask why, it just works that way on VMS.
|
||
+ result.append("000000");
|
||
+ } else {
|
||
+ base::StrAppend(&result, {tokens[1]});
|
||
+ for (size_t i = 2; i < tokens.size() - 1; i++)
|
||
+ base::StrAppend(&result, {".", tokens[i]});
|
||
+ }
|
||
+ base::StrAppend(&result, {"]", tokens.back()});
|
||
+ return result;
|
||
+ }
|
||
+
|
||
+ if (tokens.size() == 1)
|
||
+ return unix_path;
|
||
+
|
||
+ std::string result("[");
|
||
+ for (size_t i = 0; i < tokens.size() - 1; i++)
|
||
+ base::StrAppend(&result, {".", tokens[i]});
|
||
+ base::StrAppend(&result, {"]", tokens.back()});
|
||
+ return result;
|
||
+}
|
||
+
|
||
+// static
|
||
+std::string FtpUtil::UnixDirectoryPathToVMS(const std::string& unix_path) {
|
||
+ if (unix_path.empty())
|
||
+ return std::string();
|
||
+
|
||
+ std::string path(unix_path);
|
||
+
|
||
+ if (path.back() != '/')
|
||
+ path.append("/");
|
||
+
|
||
+ // Reuse logic from UnixFilePathToVMS by appending a fake file name to the
|
||
+ // real path and removing it after conversion.
|
||
+ path.append("x");
|
||
+ path = UnixFilePathToVMS(path);
|
||
+ return path.substr(0, path.length() - 1);
|
||
+}
|
||
+
|
||
+// static
|
||
+std::string FtpUtil::VMSPathToUnix(const std::string& vms_path) {
|
||
+ if (vms_path.empty())
|
||
+ return ".";
|
||
+
|
||
+ if (vms_path[0] == '/') {
|
||
+ // This is not really a VMS path. Most likely the server is emulating UNIX.
|
||
+ // Return path as-is.
|
||
+ return vms_path;
|
||
+ }
|
||
+
|
||
+ if (vms_path == "[]")
|
||
+ return "/";
|
||
+
|
||
+ std::string result(vms_path);
|
||
+ if (vms_path[0] == '[') {
|
||
+ // It's a relative path.
|
||
+ base::ReplaceFirstSubstringAfterOffset(
|
||
+ &result, 0, "[.", std::string_view());
|
||
+ } else {
|
||
+ // It's an absolute path.
|
||
+ result.insert(0, "/");
|
||
+ base::ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/");
|
||
+ base::ReplaceSubstringsAfterOffset(&result, 0, ":[", "/");
|
||
+ }
|
||
+ std::replace(result.begin(), result.end(), '.', '/');
|
||
+ std::replace(result.begin(), result.end(), ']', '/');
|
||
+
|
||
+ // Make sure the result doesn't end with a slash.
|
||
+ if (!result.empty() && result.back() == '/')
|
||
+ result = result.substr(0, result.length() - 1);
|
||
+
|
||
+ return result;
|
||
+}
|
||
+
|
||
+namespace {
|
||
+
|
||
+// Lazy-initialized map of abbreviated month names.
|
||
+class AbbreviatedMonthsMap {
|
||
+ public:
|
||
+ static AbbreviatedMonthsMap* GetInstance() {
|
||
+ return base::Singleton<AbbreviatedMonthsMap>::get();
|
||
+ }
|
||
+
|
||
+ // Converts abbreviated month name |text| to its number (in range 1-12).
|
||
+ // On success returns true and puts the number in |number|.
|
||
+ bool GetMonthNumber(const std::u16string& text, int* number) {
|
||
+ // Ignore the case of the month names. The simplest way to handle that
|
||
+ // is to make everything lowercase.
|
||
+ std::u16string text_lower(base::i18n::ToLower(text));
|
||
+
|
||
+ if (map_.find(text_lower) == map_.end())
|
||
+ return false;
|
||
+
|
||
+ *number = map_[text_lower];
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ friend struct base::DefaultSingletonTraits<AbbreviatedMonthsMap>;
|
||
+
|
||
+ // Constructor, initializes the map based on ICU data. It is much faster
|
||
+ // to do that just once.
|
||
+ AbbreviatedMonthsMap() {
|
||
+ int32_t locales_count;
|
||
+ const icu::Locale* locales =
|
||
+ icu::DateFormat::getAvailableLocales(locales_count);
|
||
+
|
||
+ for (int32_t locale = 0; locale < locales_count; locale++) {
|
||
+ UErrorCode status(U_ZERO_ERROR);
|
||
+
|
||
+ icu::DateFormatSymbols format_symbols(locales[locale], status);
|
||
+
|
||
+ // If we cannot get format symbols for some locale, it's not a fatal
|
||
+ // error. Just try another one.
|
||
+ if (U_FAILURE(status))
|
||
+ continue;
|
||
+
|
||
+ int32_t months_count;
|
||
+ const icu::UnicodeString* months =
|
||
+ format_symbols.getShortMonths(months_count);
|
||
+
|
||
+ for (int32_t month = 0; month < months_count; month++) {
|
||
+ std::u16string month_name(
|
||
+ base::i18n::UnicodeStringToString16(months[month]));
|
||
+
|
||
+ // Ignore the case of the month names. The simplest way to handle that
|
||
+ // is to make everything lowercase.
|
||
+ month_name = base::i18n::ToLower(month_name);
|
||
+
|
||
+ map_[month_name] = month + 1;
|
||
+
|
||
+ // Sometimes ICU returns longer strings, but in FTP listings a shorter
|
||
+ // abbreviation is used (for example for the Russian locale). Make sure
|
||
+ // we always have a map entry for a three-letter abbreviation.
|
||
+ map_[month_name.substr(0, 3)] = month + 1;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Fail loudly if the data returned by ICU is obviously incomplete.
|
||
+ // This is intended to catch cases like http://crbug.com/177428
|
||
+ // much earlier. Note that the issue above turned out to be non-trivial
|
||
+ // to reproduce - crash data is much better indicator of a problem
|
||
+ // than incomplete bug reports.
|
||
+ CHECK_EQ(1, map_[u"jan"]);
|
||
+ CHECK_EQ(2, map_[u"feb"]);
|
||
+ CHECK_EQ(3, map_[u"mar"]);
|
||
+ CHECK_EQ(4, map_[u"apr"]);
|
||
+ CHECK_EQ(5, map_[u"may"]);
|
||
+ CHECK_EQ(6, map_[u"jun"]);
|
||
+ CHECK_EQ(7, map_[u"jul"]);
|
||
+ CHECK_EQ(8, map_[u"aug"]);
|
||
+ CHECK_EQ(9, map_[u"sep"]);
|
||
+ CHECK_EQ(10, map_[u"oct"]);
|
||
+ CHECK_EQ(11, map_[u"nov"]);
|
||
+ CHECK_EQ(12, map_[u"dec"]);
|
||
+ }
|
||
+ AbbreviatedMonthsMap(const AbbreviatedMonthsMap&) = delete;
|
||
+ AbbreviatedMonthsMap& operator=(const AbbreviatedMonthsMap&) =
|
||
+ delete;
|
||
+ // Maps lowercase month names to numbers in range 1-12.
|
||
+ std::map<std::u16string, int> map_;
|
||
+};
|
||
+
|
||
+} // namespace
|
||
+
|
||
+// static
|
||
+bool FtpUtil::AbbreviatedMonthToNumber(const std::u16string& text,
|
||
+ int* number) {
|
||
+ return AbbreviatedMonthsMap::GetInstance()->GetMonthNumber(text, number);
|
||
+}
|
||
+
|
||
+// static
|
||
+bool FtpUtil::LsDateListingToTime(const std::u16string& month,
|
||
+ const std::u16string& day,
|
||
+ const std::u16string& rest,
|
||
+ const base::Time& current_time,
|
||
+ base::Time* result) {
|
||
+ base::Time::Exploded time_exploded = { 0 };
|
||
+
|
||
+ if (!AbbreviatedMonthToNumber(month, &time_exploded.month)) {
|
||
+ // Work around garbage sent by some servers in the same column
|
||
+ // as the month. Take just last 3 characters of the string.
|
||
+ if (month.length() < 3 ||
|
||
+ !AbbreviatedMonthToNumber(month.substr(month.length() - 3),
|
||
+ &time_exploded.month)) {
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (!base::StringToInt(day, &time_exploded.day_of_month))
|
||
+ return false;
|
||
+ if (time_exploded.day_of_month > 31)
|
||
+ return false;
|
||
+
|
||
+ if (!base::StringToInt(rest, &time_exploded.year)) {
|
||
+ // Maybe it's time. Does it look like time? Note that it can be any of
|
||
+ // "HH:MM", "H:MM", "HH:M" or maybe even "H:M".
|
||
+ if (rest.length() > 5)
|
||
+ return false;
|
||
+
|
||
+ size_t colon_pos = rest.find(':');
|
||
+ if (colon_pos == std::u16string::npos)
|
||
+ return false;
|
||
+ if (colon_pos > 2)
|
||
+ return false;
|
||
+
|
||
+ if (!base::StringToInt(
|
||
+ base::MakeStringPiece16(rest.begin(), rest.begin() + colon_pos),
|
||
+ &time_exploded.hour)) {
|
||
+ return false;
|
||
+ }
|
||
+ if (!base::StringToInt(
|
||
+ base::MakeStringPiece16(rest.begin() + colon_pos + 1, rest.end()),
|
||
+ &time_exploded.minute)) {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ // Guess the year.
|
||
+ base::Time::Exploded current_exploded;
|
||
+ current_time.UTCExplode(¤t_exploded);
|
||
+
|
||
+ // If it's not possible for the parsed date to be in the current year,
|
||
+ // use the previous year.
|
||
+ if (time_exploded.month > current_exploded.month ||
|
||
+ (time_exploded.month == current_exploded.month &&
|
||
+ time_exploded.day_of_month > current_exploded.day_of_month)) {
|
||
+ time_exploded.year = current_exploded.year - 1;
|
||
+ } else {
|
||
+ time_exploded.year = current_exploded.year;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // We don't know the time zone of the listing, so just use UTC.
|
||
+ return base::Time::FromUTCExploded(time_exploded, result);
|
||
+}
|
||
+
|
||
+// static
|
||
+bool FtpUtil::WindowsDateListingToTime(const std::u16string& date,
|
||
+ const std::u16string& time,
|
||
+ base::Time* result) {
|
||
+ base::Time::Exploded time_exploded = { 0 };
|
||
+
|
||
+ // Date should be in format MM-DD-YY[YY].
|
||
+ std::vector<std::u16string_view> date_parts = base::SplitStringPiece(
|
||
+ date, u"-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (date_parts.size() != 3)
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[0], &time_exploded.month))
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[1], &time_exploded.day_of_month))
|
||
+ return false;
|
||
+ if (!base::StringToInt(date_parts[2], &time_exploded.year))
|
||
+ return false;
|
||
+ if (time_exploded.year < 0)
|
||
+ return false;
|
||
+ // If year has only two digits then assume that 00-79 is 2000-2079,
|
||
+ // and 80-99 is 1980-1999.
|
||
+ if (time_exploded.year < 80)
|
||
+ time_exploded.year += 2000;
|
||
+ else if (time_exploded.year < 100)
|
||
+ time_exploded.year += 1900;
|
||
+
|
||
+ // Time should be in format HH:MM[(AM|PM)]
|
||
+ if (time.length() < 5)
|
||
+ return false;
|
||
+
|
||
+ std::vector<std::u16string_view> time_parts =
|
||
+ base::SplitStringPiece(std::u16string_view(time).substr(0, 5), u":",
|
||
+ base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||
+ if (time_parts.size() != 2)
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[0], &time_exploded.hour))
|
||
+ return false;
|
||
+ if (!base::StringToInt(time_parts[1], &time_exploded.minute))
|
||
+ return false;
|
||
+ if (!time_exploded.HasValidValues())
|
||
+ return false;
|
||
+
|
||
+ if (time.length() > 5) {
|
||
+ if (time.length() != 7)
|
||
+ return false;
|
||
+ std::u16string am_or_pm(time.substr(5, 2));
|
||
+ if (base::EqualsASCII(am_or_pm, "PM")) {
|
||
+ if (time_exploded.hour < 12)
|
||
+ time_exploded.hour += 12;
|
||
+ } else if (base::EqualsASCII(am_or_pm, "AM")) {
|
||
+ if (time_exploded.hour == 12)
|
||
+ time_exploded.hour = 0;
|
||
+ } else {
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // We don't know the time zone of the server, so just use UTC.
|
||
+ return base::Time::FromUTCExploded(time_exploded, result);
|
||
+}
|
||
+
|
||
+// static
|
||
+std::u16string FtpUtil::GetStringPartAfterColumns(const std::u16string& text,
|
||
+ int columns) {
|
||
+ base::i18n::UTF16CharIterator iter(text);
|
||
+
|
||
+ for (int i = 0; i < columns; i++) {
|
||
+ // Skip the leading whitespace.
|
||
+ while (!iter.end() && u_isspace(iter.get()))
|
||
+ iter.Advance();
|
||
+
|
||
+ // Skip the actual text of i-th column.
|
||
+ while (!iter.end() && !u_isspace(iter.get()))
|
||
+ iter.Advance();
|
||
+ }
|
||
+
|
||
+ std::u16string result(text.substr(iter.array_pos()));
|
||
+ base::TrimWhitespace(result, base::TRIM_ALL, &result);
|
||
+ return result;
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/ftp/ftp_util.h b/net/ftp/ftp_util.h
|
||
new file mode 100644
|
||
index 0000000000000..6647df1569862
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_util.h
|
||
@@ -0,0 +1,57 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_FTP_FTP_UTIL_H_
|
||
+#define NET_FTP_FTP_UTIL_H_
|
||
+
|
||
+#include <string>
|
||
+
|
||
+#include "net/base/net_export.h"
|
||
+
|
||
+namespace base {
|
||
+class Time;
|
||
+}
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class NET_EXPORT_PRIVATE FtpUtil {
|
||
+ public:
|
||
+ // Converts Unix file path to VMS path (must be a file, and not a directory).
|
||
+ static std::string UnixFilePathToVMS(const std::string& unix_path);
|
||
+
|
||
+ // Converts Unix directory path to VMS path (must be a directory).
|
||
+ static std::string UnixDirectoryPathToVMS(const std::string& unix_path);
|
||
+
|
||
+ // Converts VMS path to Unix-style path.
|
||
+ static std::string VMSPathToUnix(const std::string& vms_path);
|
||
+
|
||
+ // Converts abbreviated month (like Nov) to its number (in range 1-12).
|
||
+ // Note: in some locales abbreviations are more than three letters long,
|
||
+ // and this function also handles them correctly.
|
||
+ static bool AbbreviatedMonthToNumber(const std::u16string& text, int* number);
|
||
+
|
||
+ // Converts a "ls -l" date listing to time. The listing comes in three
|
||
+ // columns. The first one contains month, the second one contains day
|
||
+ // of month. The third one is either a time (and then we guess the year based
|
||
+ // on |current_time|), or is a year (and then we don't know the time).
|
||
+ static bool LsDateListingToTime(const std::u16string& month,
|
||
+ const std::u16string& day,
|
||
+ const std::u16string& rest,
|
||
+ const base::Time& current_time,
|
||
+ base::Time* result);
|
||
+
|
||
+ // Converts a Windows date listing to time. Returns true on success.
|
||
+ static bool WindowsDateListingToTime(const std::u16string& date,
|
||
+ const std::u16string& time,
|
||
+ base::Time* result);
|
||
+
|
||
+ // Skips |columns| columns from |text| (whitespace-delimited), and returns the
|
||
+ // remaining part, without leading/trailing whitespace.
|
||
+ static std::u16string GetStringPartAfterColumns(const std::u16string& text,
|
||
+ int columns);
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_FTP_FTP_UTIL_H_
|
||
diff --git a/net/ftp/ftp_util_unittest.cc b/net/ftp/ftp_util_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..ffeaaa541d7d8
|
||
--- /dev/null
|
||
+++ b/net/ftp/ftp_util_unittest.cc
|
||
@@ -0,0 +1,268 @@
|
||
+// Copyright (c) 2011 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/ftp/ftp_util.h"
|
||
+
|
||
+#include "base/cxx17_backports.h"
|
||
+#include "base/format_macros.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/stringprintf.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+
|
||
+using base::ASCIIToUTF16;
|
||
+using base::UTF8ToUTF16;
|
||
+
|
||
+namespace net {
|
||
+
|
||
+namespace {
|
||
+
|
||
+TEST(FtpUtilTest, UnixFilePathToVMS) {
|
||
+ const struct {
|
||
+ const char* input;
|
||
+ const char* expected_output;
|
||
+ } kTestCases[] = {
|
||
+ { "", "" },
|
||
+ { "/", "[]" },
|
||
+ { "/a", "a" },
|
||
+ { "/a/b", "a:[000000]b" },
|
||
+ { "/a/b/c", "a:[b]c" },
|
||
+ { "/a/b/c/d", "a:[b.c]d" },
|
||
+ { "/a/b/c/d/e", "a:[b.c.d]e" },
|
||
+ { "a", "a" },
|
||
+ { "a/b", "[.a]b" },
|
||
+ { "a/b/c", "[.a.b]c" },
|
||
+ { "a/b/c/d", "[.a.b.c]d" },
|
||
+ // Extra slashes shouldn't matter.
|
||
+ { "/////", "[]" },
|
||
+ { "/////a", "a" },
|
||
+ { "//a//b///c", "a:[b]c" },
|
||
+ { "a//b///c", "[.a.b]c" },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ EXPECT_EQ(kTestCases[i].expected_output,
|
||
+ FtpUtil::UnixFilePathToVMS(kTestCases[i].input))
|
||
+ << kTestCases[i].input;
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST(FtpUtilTest, UnixDirectoryPathToVMS) {
|
||
+ const struct {
|
||
+ const char* input;
|
||
+ const char* expected_output;
|
||
+ } kTestCases[] = {
|
||
+ { "", "" },
|
||
+ { "/", "" },
|
||
+ { "/a", "a:[000000]" },
|
||
+ { "/a/", "a:[000000]" },
|
||
+ { "/a/b", "a:[b]" },
|
||
+ { "/a/b/", "a:[b]" },
|
||
+ { "/a/b/c", "a:[b.c]" },
|
||
+ { "/a/b/c/", "a:[b.c]" },
|
||
+ { "/a/b/c/d", "a:[b.c.d]" },
|
||
+ { "/a/b/c/d/", "a:[b.c.d]" },
|
||
+ { "/a/b/c/d/e", "a:[b.c.d.e]" },
|
||
+ { "/a/b/c/d/e/", "a:[b.c.d.e]" },
|
||
+ { "a", "[.a]" },
|
||
+ { "a/", "[.a]" },
|
||
+ { "a/b", "[.a.b]" },
|
||
+ { "a/b/", "[.a.b]" },
|
||
+ { "a/b/c", "[.a.b.c]" },
|
||
+ { "a/b/c/", "[.a.b.c]" },
|
||
+ { "a/b/c/d", "[.a.b.c.d]" },
|
||
+ { "a/b/c/d/", "[.a.b.c.d]" },
|
||
+ // Extra slashes shouldn't matter.
|
||
+ { "/////", "" },
|
||
+ { "//a//b///c//", "a:[b.c]" },
|
||
+ { "a//b///c//", "[.a.b.c]" },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ EXPECT_EQ(kTestCases[i].expected_output,
|
||
+ FtpUtil::UnixDirectoryPathToVMS(kTestCases[i].input))
|
||
+ << kTestCases[i].input;
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST(FtpUtilTest, VMSPathToUnix) {
|
||
+ const struct {
|
||
+ const char* input;
|
||
+ const char* expected_output;
|
||
+ } kTestCases[] = {
|
||
+ { "", "." },
|
||
+ { "[]", "/" },
|
||
+ { "a", "/a" },
|
||
+ { "a:[000000]", "/a" },
|
||
+ { "a:[000000]b", "/a/b" },
|
||
+ { "a:[b]", "/a/b" },
|
||
+ { "a:[b]c", "/a/b/c" },
|
||
+ { "a:[b.c]", "/a/b/c" },
|
||
+ { "a:[b.c]d", "/a/b/c/d" },
|
||
+ { "a:[b.c.d]", "/a/b/c/d" },
|
||
+ { "a:[b.c.d]e", "/a/b/c/d/e" },
|
||
+ { "a:[b.c.d.e]", "/a/b/c/d/e" },
|
||
+ { "[.a]", "a" },
|
||
+ { "[.a]b", "a/b" },
|
||
+ { "[.a.b]", "a/b" },
|
||
+ { "[.a.b]c", "a/b/c" },
|
||
+ { "[.a.b.c]", "a/b/c" },
|
||
+ { "[.a.b.c]d", "a/b/c/d" },
|
||
+ { "[.a.b.c.d]", "a/b/c/d" },
|
||
+ { "[.", "" },
|
||
+
|
||
+ // UNIX emulation:
|
||
+ { "/", "/" },
|
||
+ { "/a", "/a" },
|
||
+ { "/a/b", "/a/b" },
|
||
+ { "/a/b/c", "/a/b/c" },
|
||
+ { "/a/b/c/d", "/a/b/c/d" },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ EXPECT_EQ(kTestCases[i].expected_output,
|
||
+ FtpUtil::VMSPathToUnix(kTestCases[i].input))
|
||
+ << kTestCases[i].input;
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST(FtpUtilTest, LsDateListingToTime) {
|
||
+ base::Time mock_current_time;
|
||
+ ASSERT_TRUE(base::Time::FromString("Tue, 15 Nov 1994 12:45:26 GMT",
|
||
+ &mock_current_time));
|
||
+
|
||
+ const struct {
|
||
+ // Input.
|
||
+ const char* month;
|
||
+ const char* day;
|
||
+ const char* rest;
|
||
+
|
||
+ // Expected output.
|
||
+ int expected_year;
|
||
+ int expected_month;
|
||
+ int expected_day_of_month;
|
||
+ int expected_hour;
|
||
+ int expected_minute;
|
||
+ } kTestCases[] = {
|
||
+ { "Nov", "01", "2007", 2007, 11, 1, 0, 0 },
|
||
+ { "Jul", "25", "13:37", 1994, 7, 25, 13, 37 },
|
||
+
|
||
+ // Test date listings in German.
|
||
+ { "M\xc3\xa4r", "13", "2009", 2009, 3, 13, 0, 0 },
|
||
+ { "Mai", "1", "10:10", 1994, 5, 1, 10, 10 },
|
||
+ { "Okt", "14", "21:18", 1994, 10, 14, 21, 18 },
|
||
+ { "Dez", "25", "2008", 2008, 12, 25, 0, 0 },
|
||
+
|
||
+ // Test date listings in Russian.
|
||
+ { "\xd1\x8f\xd0\xbd\xd0\xb2", "1", "2011", 2011, 1, 1, 0, 0 },
|
||
+ { "\xd1\x84\xd0\xb5\xd0\xb2", "1", "2011", 2011, 2, 1, 0, 0 },
|
||
+ { "\xd0\xbc\xd0\xb0\xd1\x80", "1", "2011", 2011, 3, 1, 0, 0 },
|
||
+ { "\xd0\xb0\xd0\xbf\xd1\x80", "1", "2011", 2011, 4, 1, 0, 0 },
|
||
+ { "\xd0\xbc\xd0\xb0\xd0\xb9", "1", "2011", 2011, 5, 1, 0, 0 },
|
||
+ { "\xd0\xb8\xd1\x8e\xd0\xbd", "1", "2011", 2011, 6, 1, 0, 0 },
|
||
+ { "\xd0\xb8\xd1\x8e\xd0\xbb", "1", "2011", 2011, 7, 1, 0, 0 },
|
||
+ { "\xd0\xb0\xd0\xb2\xd0\xb3", "1", "2011", 2011, 8, 1, 0, 0 },
|
||
+ { "\xd1\x81\xd0\xb5\xd0\xbd", "1", "2011", 2011, 9, 1, 0, 0 },
|
||
+ { "\xd0\xbe\xd0\xba\xd1\x82", "1", "2011", 2011, 10, 1, 0, 0 },
|
||
+ { "\xd0\xbd\xd0\xbe\xd1\x8f", "1", "2011", 2011, 11, 1, 0, 0 },
|
||
+ { "\xd0\xb4\xd0\xb5\xd0\xba", "1", "2011", 2011, 12, 1, 0, 0 },
|
||
+
|
||
+ // Test current year detection.
|
||
+ { "Nov", "01", "12:00", 1994, 11, 1, 12, 0 },
|
||
+ { "Nov", "15", "12:00", 1994, 11, 15, 12, 0 },
|
||
+ { "Nov", "16", "12:00", 1993, 11, 16, 12, 0 },
|
||
+ { "Jan", "01", "08:30", 1994, 1, 1, 8, 30 },
|
||
+ { "Sep", "02", "09:00", 1994, 9, 2, 9, 0 },
|
||
+ { "Dec", "06", "21:00", 1993, 12, 6, 21, 0 },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s %s", i,
|
||
+ kTestCases[i].month, kTestCases[i].day,
|
||
+ kTestCases[i].rest));
|
||
+
|
||
+ base::Time time;
|
||
+ ASSERT_TRUE(FtpUtil::LsDateListingToTime(
|
||
+ UTF8ToUTF16(kTestCases[i].month), UTF8ToUTF16(kTestCases[i].day),
|
||
+ UTF8ToUTF16(kTestCases[i].rest), mock_current_time, &time));
|
||
+
|
||
+ base::Time::Exploded time_exploded;
|
||
+ time.UTCExplode(&time_exploded);
|
||
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
|
||
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
|
||
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
|
||
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
|
||
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
|
||
+ EXPECT_EQ(0, time_exploded.second);
|
||
+ EXPECT_EQ(0, time_exploded.millisecond);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST(FtpUtilTest, WindowsDateListingToTime) {
|
||
+ const struct {
|
||
+ // Input.
|
||
+ const char* date;
|
||
+ const char* time;
|
||
+
|
||
+ // Expected output.
|
||
+ int expected_year;
|
||
+ int expected_month;
|
||
+ int expected_day_of_month;
|
||
+ int expected_hour;
|
||
+ int expected_minute;
|
||
+ } kTestCases[] = {
|
||
+ { "11-01-07", "12:42", 2007, 11, 1, 12, 42 },
|
||
+ { "11-01-07", "12:42AM", 2007, 11, 1, 0, 42 },
|
||
+ { "11-01-07", "12:42PM", 2007, 11, 1, 12, 42 },
|
||
+
|
||
+ { "11-01-2007", "12:42", 2007, 11, 1, 12, 42 },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %s", i,
|
||
+ kTestCases[i].date, kTestCases[i].time));
|
||
+
|
||
+ base::Time time;
|
||
+ ASSERT_TRUE(FtpUtil::WindowsDateListingToTime(
|
||
+ UTF8ToUTF16(kTestCases[i].date), UTF8ToUTF16(kTestCases[i].time),
|
||
+ &time));
|
||
+
|
||
+ base::Time::Exploded time_exploded;
|
||
+ time.UTCExplode(&time_exploded);
|
||
+ EXPECT_EQ(kTestCases[i].expected_year, time_exploded.year);
|
||
+ EXPECT_EQ(kTestCases[i].expected_month, time_exploded.month);
|
||
+ EXPECT_EQ(kTestCases[i].expected_day_of_month, time_exploded.day_of_month);
|
||
+ EXPECT_EQ(kTestCases[i].expected_hour, time_exploded.hour);
|
||
+ EXPECT_EQ(kTestCases[i].expected_minute, time_exploded.minute);
|
||
+ EXPECT_EQ(0, time_exploded.second);
|
||
+ EXPECT_EQ(0, time_exploded.millisecond);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST(FtpUtilTest, GetStringPartAfterColumns) {
|
||
+ const struct {
|
||
+ const char* text;
|
||
+ int column;
|
||
+ const char* expected_result;
|
||
+ } kTestCases[] = {
|
||
+ { "", 0, "" },
|
||
+ { "", 1, "" },
|
||
+ { "foo abc", 0, "foo abc" },
|
||
+ { "foo abc", 1, "abc" },
|
||
+ { " foo abc", 0, "foo abc" },
|
||
+ { " foo abc", 1, "abc" },
|
||
+ { " foo abc", 2, "" },
|
||
+ { " foo abc ", 0, "foo abc" },
|
||
+ { " foo abc ", 1, "abc" },
|
||
+ { " foo abc ", 2, "" },
|
||
+ };
|
||
+ for (size_t i = 0; i < base::size(kTestCases); i++) {
|
||
+ SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]: %s %d", i,
|
||
+ kTestCases[i].text, kTestCases[i].column));
|
||
+
|
||
+ EXPECT_EQ(ASCIIToUTF16(kTestCases[i].expected_result),
|
||
+ FtpUtil::GetStringPartAfterColumns(
|
||
+ ASCIIToUTF16(kTestCases[i].text), kTestCases[i].column));
|
||
+ }
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/proxy_resolution/pac_file_fetcher_impl.cc b/net/proxy_resolution/pac_file_fetcher_impl.cc
|
||
index 02ef9a79878bb..ee62937fdecbd 100644
|
||
--- a/net/proxy_resolution/pac_file_fetcher_impl.cc
|
||
+++ b/net/proxy_resolution/pac_file_fetcher_impl.cc
|
||
@@ -324,8 +324,8 @@ PacFileFetcherImpl::PacFileFetcherImpl(URLRequestContext* url_request_context)
|
||
}
|
||
|
||
bool PacFileFetcherImpl::IsUrlSchemeAllowed(const GURL& url) const {
|
||
- // Always allow http://, https://, and data:.
|
||
- if (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data"))
|
||
+ // Always allow http://, https://, data:, and ftp://.
|
||
+ if (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("ftp") || url.SchemeIs("data"))
|
||
return true;
|
||
|
||
// Disallow any other URL scheme.
|
||
diff --git a/net/url_request/ftp_protocol_handler.cc b/net/url_request/ftp_protocol_handler.cc
|
||
new file mode 100644
|
||
index 0000000000000..03783e50cfc0b
|
||
--- /dev/null
|
||
+++ b/net/url_request/ftp_protocol_handler.cc
|
||
@@ -0,0 +1,58 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/url_request/ftp_protocol_handler.h"
|
||
+
|
||
+#include "base/check_op.h"
|
||
+#include "base/memory/ptr_util.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/base/port_util.h"
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#include "net/ftp/ftp_network_layer.h"
|
||
+#include "net/url_request/url_request.h"
|
||
+#include "net/url_request/url_request_error_job.h"
|
||
+#include "net/url_request/url_request_ftp_job.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+std::unique_ptr<FtpProtocolHandler> FtpProtocolHandler::Create(
|
||
+ HostResolver* host_resolver,
|
||
+ FtpAuthCache* auth_cache) {
|
||
+ DCHECK(auth_cache);
|
||
+ return base::WrapUnique(new FtpProtocolHandler(
|
||
+ base::WrapUnique(new FtpNetworkLayer(host_resolver)), auth_cache));
|
||
+}
|
||
+
|
||
+std::unique_ptr<FtpProtocolHandler> FtpProtocolHandler::CreateForTesting(
|
||
+ std::unique_ptr<FtpTransactionFactory> ftp_transaction_factory,
|
||
+ FtpAuthCache* auth_cache) {
|
||
+ return base::WrapUnique(
|
||
+ new FtpProtocolHandler(std::move(ftp_transaction_factory), auth_cache));
|
||
+}
|
||
+
|
||
+FtpProtocolHandler::~FtpProtocolHandler() = default;
|
||
+
|
||
+std::unique_ptr<URLRequestJob> FtpProtocolHandler::CreateJob(
|
||
+ URLRequest* request) const {
|
||
+ DCHECK_EQ("ftp", request->url().scheme());
|
||
+
|
||
+ if (!IsPortAllowedForScheme(request->url().EffectiveIntPort(),
|
||
+ request->url().scheme_piece())) {
|
||
+ return std::make_unique<URLRequestErrorJob>(request, ERR_UNSAFE_PORT);
|
||
+ }
|
||
+
|
||
+ return std::make_unique<URLRequestFtpJob>(
|
||
+ request, ftp_transaction_factory_.get(), ftp_auth_cache_);
|
||
+}
|
||
+
|
||
+FtpProtocolHandler::FtpProtocolHandler(
|
||
+ std::unique_ptr<FtpTransactionFactory> ftp_transaction_factory,
|
||
+ FtpAuthCache* auth_cache)
|
||
+ : ftp_transaction_factory_(std::move(ftp_transaction_factory)),
|
||
+ ftp_auth_cache_(auth_cache) {
|
||
+ DCHECK(ftp_transaction_factory_);
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/url_request/ftp_protocol_handler.h b/net/url_request/ftp_protocol_handler.h
|
||
new file mode 100644
|
||
index 0000000000000..3c7e0faebc2bb
|
||
--- /dev/null
|
||
+++ b/net/url_request/ftp_protocol_handler.h
|
||
@@ -0,0 +1,59 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
|
||
+#define NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
|
||
+
|
||
+#include <memory>
|
||
+
|
||
+#include "base/memory/raw_ptr.h"
|
||
+#include "base/compiler_specific.h"
|
||
+#include "base/memory/raw_ptr.h"
|
||
+#include "base/memory/ref_counted.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/url_request/url_request_job_factory.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class FtpAuthCache;
|
||
+class FtpTransactionFactory;
|
||
+class HostResolver;
|
||
+class URLRequestJob;
|
||
+
|
||
+// Implements a ProtocolHandler for FTP.
|
||
+class NET_EXPORT FtpProtocolHandler :
|
||
+ public URLRequestJobFactory::ProtocolHandler {
|
||
+ public:
|
||
+ ~FtpProtocolHandler() override;
|
||
+ FtpProtocolHandler(const FtpProtocolHandler&) = delete;
|
||
+ FtpProtocolHandler& operator=(const FtpProtocolHandler&) =
|
||
+ delete;
|
||
+
|
||
+ // Creates an FtpProtocolHandler using the specified HostResolver and
|
||
+ // FtpAuthCache. |auth_cache| cannot be null.
|
||
+ static std::unique_ptr<FtpProtocolHandler> Create(HostResolver* host_resolver,
|
||
+ FtpAuthCache* auth_cache);
|
||
+
|
||
+ // Creates an FtpProtocolHandler using the specified FtpTransactionFactory, to
|
||
+ // allow a mock to be used for testing.
|
||
+ static std::unique_ptr<FtpProtocolHandler> CreateForTesting(
|
||
+ std::unique_ptr<FtpTransactionFactory> ftp_transaction_factory,
|
||
+ FtpAuthCache* auth_cache);
|
||
+
|
||
+ std::unique_ptr<URLRequestJob> CreateJob(URLRequest* request) const override;
|
||
+
|
||
+ private:
|
||
+ friend class FtpTestURLRequestContext;
|
||
+
|
||
+ explicit FtpProtocolHandler(
|
||
+ std::unique_ptr<FtpTransactionFactory> ftp_transaction_factory,
|
||
+ FtpAuthCache* auth_cache);
|
||
+
|
||
+ std::unique_ptr<FtpTransactionFactory> ftp_transaction_factory_;
|
||
+ raw_ptr<FtpAuthCache> ftp_auth_cache_;
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
|
||
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
|
||
index 859dcb00e8153..d0791fdace3c5 100644
|
||
--- a/net/url_request/url_request.h
|
||
+++ b/net/url_request/url_request.h
|
||
@@ -749,7 +749,8 @@ class NET_EXPORT URLRequest : public base::SupportsUserData {
|
||
}
|
||
|
||
// The number of bytes in the raw response body (before any decompression,
|
||
- // etc.). This is only available after the final Read completes.
|
||
+ // etc.). This is only available after the final Read completes. Not available
|
||
+ // for FTP responses.
|
||
int64_t received_response_content_length() const {
|
||
return received_response_content_length_;
|
||
}
|
||
diff --git a/net/url_request/url_request_context.h b/net/url_request/url_request_context.h
|
||
index 5618d02062989..e71e085486f2f 100644
|
||
--- a/net/url_request/url_request_context.h
|
||
+++ b/net/url_request/url_request_context.h
|
||
@@ -55,6 +55,10 @@ class URLRequest;
|
||
class URLRequestJobFactory;
|
||
class URLRequestContextBuilder;
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+class FtpAuthCache;
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
#if BUILDFLAG(ENABLE_REPORTING)
|
||
class NetworkErrorLoggingService;
|
||
class PersistentReportingAndNelStore;
|
||
@@ -235,6 +239,13 @@ class NET_EXPORT URLRequestContext final {
|
||
// context has been bound to.
|
||
handles::NetworkHandle bound_network() const { return bound_network_; }
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ void set_ftp_auth_cache(FtpAuthCache* auth_cache) {
|
||
+ ftp_auth_cache_ = auth_cache;
|
||
+ }
|
||
+ FtpAuthCache* ftp_auth_cache() { return ftp_auth_cache_; }
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
void AssertCalledOnValidThread() {
|
||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||
}
|
||
@@ -363,6 +374,10 @@ class NET_EXPORT URLRequestContext final {
|
||
|
||
std::unique_ptr<TransportSecurityPersister> transport_security_persister_;
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ raw_ptr<FtpAuthCache> ftp_auth_cache_;
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
std::unique_ptr<std::set<raw_ptr<const URLRequest, SetExperimental>>>
|
||
url_requests_;
|
||
|
||
diff --git a/net/url_request/url_request_context_builder.cc b/net/url_request/url_request_context_builder.cc
|
||
index c4ba7328234bf..4f5bb37448ccd 100644
|
||
--- a/net/url_request/url_request_context_builder.cc
|
||
+++ b/net/url_request/url_request_context_builder.cc
|
||
@@ -53,6 +53,12 @@
|
||
#include "net/url_request/url_request_job_factory.h"
|
||
#include "url/url_constants.h"
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+#include "net/ftp/ftp_auth_cache.h" // nogncheck
|
||
+#include "net/ftp/ftp_network_layer.h" // nogncheck
|
||
+#include "net/url_request/ftp_protocol_handler.h" // nogncheck
|
||
+#endif
|
||
+
|
||
#if BUILDFLAG(ENABLE_REPORTING)
|
||
#include "net/network_error_logging/network_error_logging_service.h"
|
||
#include "net/network_error_logging/persistent_reporting_and_nel_store.h"
|
||
@@ -592,12 +598,23 @@ std::unique_ptr<URLRequestContext> URLRequestContextBuilder::Build() {
|
||
|
||
std::unique_ptr<URLRequestJobFactory> job_factory =
|
||
std::make_unique<URLRequestJobFactory>();
|
||
+ // Adds caller-provided protocol handlers first so that these handlers are
|
||
+ // used over the ftp handler below.
|
||
for (auto& scheme_handler : protocol_handlers_) {
|
||
job_factory->SetProtocolHandler(scheme_handler.first,
|
||
std::move(scheme_handler.second));
|
||
}
|
||
protocol_handlers_.clear();
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ if (ftp_enabled_) {
|
||
+ context->set_ftp_auth_cache(new FtpAuthCache());
|
||
+ job_factory->SetProtocolHandler(
|
||
+ url::kFtpScheme, FtpProtocolHandler::Create(context->host_resolver(),
|
||
+ context->ftp_auth_cache()));
|
||
+ }
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
context->set_job_factory(std::move(job_factory));
|
||
|
||
if (cookie_deprecation_label_.has_value()) {
|
||
diff --git a/net/url_request/url_request_context_builder.h b/net/url_request/url_request_context_builder.h
|
||
index 66fd979ebae17..996f83526a69c 100644
|
||
--- a/net/url_request/url_request_context_builder.h
|
||
+++ b/net/url_request/url_request_context_builder.h
|
||
@@ -217,6 +217,11 @@ class NET_EXPORT URLRequestContextBuilder {
|
||
void set_http_user_agent_settings(
|
||
std::unique_ptr<HttpUserAgentSettings> http_user_agent_settings);
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ // Control support for ftp:// requests. By default it's disabled.
|
||
+ void set_ftp_enabled(bool enable) { ftp_enabled_ = enable; }
|
||
+#endif
|
||
+
|
||
// Sets a valid ProtocolHandler for a scheme.
|
||
// A ProtocolHandler already exists for |scheme| will be overwritten.
|
||
void SetProtocolHandler(
|
||
@@ -441,6 +446,11 @@ class NET_EXPORT URLRequestContextBuilder {
|
||
|
||
std::optional<std::string> cookie_deprecation_label_;
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ // Include support for ftp:// requests.
|
||
+ bool ftp_enabled_ = false;
|
||
+#endif
|
||
+
|
||
bool http_cache_enabled_ = true;
|
||
bool cookie_store_set_by_client_ = false;
|
||
bool suppress_setting_socket_performance_watcher_factory_for_testing_ = false;
|
||
diff --git a/net/url_request/url_request_ftp_fuzzer.cc b/net/url_request/url_request_ftp_fuzzer.cc
|
||
new file mode 100644
|
||
index 0000000000000..e589091fb92e4
|
||
--- /dev/null
|
||
+++ b/net/url_request/url_request_ftp_fuzzer.cc
|
||
@@ -0,0 +1,92 @@
|
||
+// Copyright 2016 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include <stddef.h>
|
||
+#include <stdint.h>
|
||
+
|
||
+#include <fuzzer/FuzzedDataProvider.h>
|
||
+
|
||
+#include "base/macros.h"
|
||
+#include "base/run_loop.h"
|
||
+#include "net/base/request_priority.h"
|
||
+#include "net/dns/context_host_resolver.h"
|
||
+#include "net/dns/fuzzed_host_resolver_util.h"
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#include "net/ftp/ftp_network_transaction.h"
|
||
+#include "net/ftp/ftp_transaction_factory.h"
|
||
+#include "net/socket/client_socket_factory.h"
|
||
+#include "net/socket/fuzzed_socket_factory.h"
|
||
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
||
+#include "net/url_request/ftp_protocol_handler.h"
|
||
+#include "net/url_request/url_request.h"
|
||
+#include "net/url_request/url_request_context.h"
|
||
+#include "net/url_request/url_request_job_factory.h"
|
||
+#include "net/url_request/url_request_test_util.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace {
|
||
+
|
||
+// Returns FtpNetworkTransactions using the specified HostResolver
|
||
+// and ClientSocketFactory.
|
||
+class FuzzedFtpTransactionFactory : public net::FtpTransactionFactory {
|
||
+ public:
|
||
+ FuzzedFtpTransactionFactory(net::HostResolver* host_resolver,
|
||
+ net::ClientSocketFactory* client_socket_factory)
|
||
+ : host_resolver_(host_resolver),
|
||
+ client_socket_factory_(client_socket_factory) {}
|
||
+
|
||
+ // FtpTransactionFactory:
|
||
+ std::unique_ptr<net::FtpTransaction> CreateTransaction() override {
|
||
+ return std::make_unique<net::FtpNetworkTransaction>(host_resolver_,
|
||
+ client_socket_factory_);
|
||
+ }
|
||
+
|
||
+ void Suspend(bool suspend) override { NOTREACHED(); }
|
||
+
|
||
+ private:
|
||
+ net::HostResolver* host_resolver_;
|
||
+ net::ClientSocketFactory* client_socket_factory_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(FuzzedFtpTransactionFactory);
|
||
+};
|
||
+
|
||
+} // namespace
|
||
+
|
||
+// Integration fuzzer for URLRequestFtpJob.
|
||
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||
+ FuzzedDataProvider data_provider(data, size);
|
||
+ net::TestURLRequestContext url_request_context(true);
|
||
+ net::FuzzedSocketFactory fuzzed_socket_factory(&data_provider);
|
||
+ url_request_context.set_client_socket_factory(&fuzzed_socket_factory);
|
||
+
|
||
+ // Need to fuzz the HostResolver to select between IPv4 and IPv6.
|
||
+ std::unique_ptr<net::ContextHostResolver> host_resolver =
|
||
+ net::CreateFuzzedContextHostResolver(net::HostResolver::ManagerOptions(),
|
||
+ nullptr, &data_provider,
|
||
+ true /* enable_caching */);
|
||
+ url_request_context.set_host_resolver(host_resolver.get());
|
||
+
|
||
+ net::URLRequestJobFactory job_factory;
|
||
+ net::FtpAuthCache auth_cache;
|
||
+ job_factory.SetProtocolHandler(
|
||
+ "ftp", net::FtpProtocolHandler::CreateForTesting(
|
||
+ std::make_unique<FuzzedFtpTransactionFactory>(
|
||
+ host_resolver.get(), &fuzzed_socket_factory),
|
||
+ &auth_cache));
|
||
+ url_request_context.set_job_factory(&job_factory);
|
||
+
|
||
+ url_request_context.Init();
|
||
+
|
||
+ net::TestDelegate delegate;
|
||
+
|
||
+ std::unique_ptr<net::URLRequest> url_request(
|
||
+ url_request_context.CreateRequest(
|
||
+ GURL("ftp://foo/" + data_provider.ConsumeRandomLengthString(1000)),
|
||
+ net::DEFAULT_PRIORITY, &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ url_request->Start();
|
||
+ // TestDelegate quits the message loop on completion.
|
||
+ base::RunLoop().Run();
|
||
+
|
||
+ return 0;
|
||
+}
|
||
diff --git a/net/url_request/url_request_ftp_job.cc b/net/url_request/url_request_ftp_job.cc
|
||
new file mode 100644
|
||
index 0000000000000..1e8bacb3d9ac9
|
||
--- /dev/null
|
||
+++ b/net/url_request/url_request_ftp_job.cc
|
||
@@ -0,0 +1,324 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/url_request/url_request_ftp_job.h"
|
||
+
|
||
+#include "base/functional/bind.h"
|
||
+#include "base/compiler_specific.h"
|
||
+#include "base/location.h"
|
||
+#include "base/metrics/histogram_macros.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/task/single_thread_task_runner.h"
|
||
+#include "net/base/auth.h"
|
||
+#include "net/base/host_port_pair.h"
|
||
+#include "net/base/load_flags.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#include "net/ftp/ftp_network_transaction.h"
|
||
+#include "net/ftp/ftp_response_info.h"
|
||
+#include "net/ftp/ftp_transaction_factory.h"
|
||
+#include "net/http/http_response_headers.h"
|
||
+#include "net/http/http_transaction_factory.h"
|
||
+#include "net/proxy_resolution/proxy_resolution_request.h"
|
||
+#include "net/url_request/url_request.h"
|
||
+#include "net/url_request/url_request_context.h"
|
||
+#include "net/url_request/url_request_error_job.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+class URLRequestFtpJob::AuthData {
|
||
+ public:
|
||
+ AuthState state; // Whether we need, have, or gave up on authentication.
|
||
+ AuthCredentials credentials; // The credentials to use for auth.
|
||
+
|
||
+ AuthData();
|
||
+ ~AuthData();
|
||
+};
|
||
+
|
||
+URLRequestFtpJob::AuthData::AuthData() : state(AUTH_STATE_NEED_AUTH) {}
|
||
+
|
||
+URLRequestFtpJob::AuthData::~AuthData() = default;
|
||
+
|
||
+URLRequestFtpJob::URLRequestFtpJob(
|
||
+ URLRequest* request,
|
||
+ FtpTransactionFactory* ftp_transaction_factory,
|
||
+ FtpAuthCache* ftp_auth_cache)
|
||
+ : URLRequestJob(request),
|
||
+ proxy_resolution_service_(
|
||
+ request_->context()->proxy_resolution_service()),
|
||
+ read_in_progress_(false),
|
||
+ ftp_transaction_factory_(ftp_transaction_factory),
|
||
+ ftp_auth_cache_(ftp_auth_cache) {
|
||
+ DCHECK(proxy_resolution_service_);
|
||
+ DCHECK(ftp_transaction_factory);
|
||
+ DCHECK(ftp_auth_cache);
|
||
+}
|
||
+
|
||
+URLRequestFtpJob::~URLRequestFtpJob() {
|
||
+ Kill();
|
||
+}
|
||
+
|
||
+bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
|
||
+ // Disallow all redirects.
|
||
+ return false;
|
||
+}
|
||
+
|
||
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
|
||
+ // When auth has been cancelled, return a blank text/plain page instead of
|
||
+ // triggering a download.
|
||
+ if (auth_data_ && auth_data_->state == AUTH_STATE_CANCELED) {
|
||
+ *mime_type = "text/plain";
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ if (ftp_transaction_->GetResponseInfo()->is_directory_listing) {
|
||
+ *mime_type = "text/vnd.chromium.ftp-dir";
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ // FTP resources other than directory listings ought to be handled as raw
|
||
+ // binary data, not sniffed into HTML or etc.
|
||
+ *mime_type = "application/octet-stream";
|
||
+ return true;
|
||
+}
|
||
+
|
||
+IPEndPoint URLRequestFtpJob::GetResponseRemoteEndpoint() const {
|
||
+ if (!ftp_transaction_)
|
||
+ return IPEndPoint();
|
||
+ return ftp_transaction_->GetResponseInfo()->remote_endpoint;
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::Start() {
|
||
+ DCHECK(!proxy_resolve_request_);
|
||
+ DCHECK(!ftp_transaction_);
|
||
+
|
||
+ int rv = OK;
|
||
+ if (request_->load_flags() & LOAD_BYPASS_PROXY) {
|
||
+ proxy_info_.UseDirect();
|
||
+ } else {
|
||
+ DCHECK_EQ(request_->context()->proxy_resolution_service(),
|
||
+ proxy_resolution_service_);
|
||
+ rv = proxy_resolution_service_->ResolveProxy(
|
||
+ request_->url(), "GET", NetworkAnonymizationKey(), &proxy_info_,
|
||
+ base::BindOnce(&URLRequestFtpJob::OnResolveProxyComplete,
|
||
+ base::Unretained(this)),
|
||
+ &proxy_resolve_request_, request_->net_log());
|
||
+
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ return;
|
||
+ }
|
||
+ OnResolveProxyComplete(rv);
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::Kill() {
|
||
+ if (proxy_resolve_request_) {
|
||
+ proxy_resolve_request_.reset();
|
||
+ }
|
||
+ if (ftp_transaction_)
|
||
+ ftp_transaction_.reset();
|
||
+ URLRequestJob::Kill();
|
||
+ weak_factory_.InvalidateWeakPtrs();
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
|
||
+ // Don't expose the challenge if it has already been successfully
|
||
+ // authenticated.
|
||
+ if (!auth_data_ || auth_data_->state == AUTH_STATE_HAVE_AUTH)
|
||
+ return;
|
||
+
|
||
+ std::unique_ptr<AuthChallengeInfo> challenge = GetAuthChallengeInfo();
|
||
+ if (challenge)
|
||
+ info->auth_challenge = *challenge;
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::OnResolveProxyComplete(int result) {
|
||
+ proxy_resolve_request_ = nullptr;
|
||
+
|
||
+ if (result != OK) {
|
||
+ OnStartCompletedAsync(result);
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ // Remove unsupported proxies from the list.
|
||
+ proxy_info_.RemoveProxiesWithoutScheme(ProxyServer::SCHEME_QUIC);
|
||
+
|
||
+ if (proxy_info_.is_direct()) {
|
||
+ StartFtpTransaction();
|
||
+ } else {
|
||
+ OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES);
|
||
+ }
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::StartFtpTransaction() {
|
||
+ // Create a transaction.
|
||
+ DCHECK(!ftp_transaction_);
|
||
+
|
||
+ ftp_request_info_.url = request_->url();
|
||
+ ftp_transaction_ = ftp_transaction_factory_->CreateTransaction();
|
||
+
|
||
+ int rv;
|
||
+ if (ftp_transaction_) {
|
||
+ rv = ftp_transaction_->Start(
|
||
+ &ftp_request_info_,
|
||
+ base::BindOnce(&URLRequestFtpJob::OnStartCompleted,
|
||
+ base::Unretained(this)),
|
||
+ request_->net_log(), request_->traffic_annotation());
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ return;
|
||
+ } else {
|
||
+ rv = ERR_FAILED;
|
||
+ }
|
||
+ // The transaction started synchronously, but we need to notify the
|
||
+ // URLRequest delegate via the message loop.
|
||
+ OnStartCompletedAsync(rv);
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::OnStartCompleted(int result) {
|
||
+ if (result == OK) {
|
||
+ DCHECK(ftp_transaction_);
|
||
+
|
||
+ // FTP obviously doesn't have HTTP Content-Length header. We have to pass
|
||
+ // the content size information manually.
|
||
+ set_expected_content_size(
|
||
+ ftp_transaction_->GetResponseInfo()->expected_content_size);
|
||
+
|
||
+ if (auth_data_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) {
|
||
+ LogFtpStartResult(FTPStartResult::kSuccessAuth);
|
||
+ } else {
|
||
+ LogFtpStartResult(FTPStartResult::kSuccessNoAuth);
|
||
+ }
|
||
+
|
||
+ NotifyHeadersComplete();
|
||
+ } else if (ftp_transaction_ /* May be null if creation fails. */ &&
|
||
+ ftp_transaction_->GetResponseInfo()->needs_auth) {
|
||
+ HandleAuthNeededResponse();
|
||
+ } else {
|
||
+ LogFtpStartResult(FTPStartResult::kFailed);
|
||
+ NotifyStartError(result);
|
||
+ }
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::OnStartCompletedAsync(int result) {
|
||
+ base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
||
+ FROM_HERE, base::BindOnce(&URLRequestFtpJob::OnStartCompleted,
|
||
+ weak_factory_.GetWeakPtr(), result));
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::OnReadCompleted(int result) {
|
||
+ read_in_progress_ = false;
|
||
+ ReadRawDataComplete(result);
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::RestartTransactionWithAuth() {
|
||
+ DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH);
|
||
+
|
||
+ int rv = ftp_transaction_->RestartWithAuth(
|
||
+ auth_data_->credentials,
|
||
+ base::BindOnce(&URLRequestFtpJob::OnStartCompleted,
|
||
+ base::Unretained(this)));
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ return;
|
||
+
|
||
+ OnStartCompletedAsync(rv);
|
||
+}
|
||
+
|
||
+LoadState URLRequestFtpJob::GetLoadState() const {
|
||
+ if (proxy_resolve_request_)
|
||
+ return proxy_resolve_request_->GetLoadState();
|
||
+ return ftp_transaction_ ? ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE;
|
||
+}
|
||
+
|
||
+bool URLRequestFtpJob::NeedsAuth() {
|
||
+ return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
|
||
+}
|
||
+
|
||
+std::unique_ptr<AuthChallengeInfo> URLRequestFtpJob::GetAuthChallengeInfo() {
|
||
+ std::unique_ptr<AuthChallengeInfo> result =
|
||
+ std::make_unique<AuthChallengeInfo>();
|
||
+ result->is_proxy = false;
|
||
+ result->challenger = url::SchemeHostPort(request_->url());
|
||
+ // scheme, realm, path, and challenge are kept empty.
|
||
+ DCHECK(result->scheme.empty());
|
||
+ DCHECK(result->realm.empty());
|
||
+ DCHECK(result->challenge.empty());
|
||
+ DCHECK(result->path.empty());
|
||
+ return result;
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
|
||
+ DCHECK(ftp_transaction_);
|
||
+ DCHECK(NeedsAuth());
|
||
+
|
||
+ auth_data_->state = AUTH_STATE_HAVE_AUTH;
|
||
+ auth_data_->credentials = credentials;
|
||
+
|
||
+ ftp_auth_cache_->Add(request_->url().DeprecatedGetOriginAsURL(), auth_data_->credentials);
|
||
+
|
||
+ RestartTransactionWithAuth();
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::CancelAuth() {
|
||
+ DCHECK(ftp_transaction_);
|
||
+ DCHECK(NeedsAuth());
|
||
+
|
||
+ auth_data_->state = AUTH_STATE_CANCELED;
|
||
+
|
||
+ ftp_transaction_.reset();
|
||
+ NotifyHeadersComplete();
|
||
+}
|
||
+
|
||
+int URLRequestFtpJob::ReadRawData(IOBuffer* buf, int buf_size) {
|
||
+ DCHECK_NE(buf_size, 0);
|
||
+ DCHECK(!read_in_progress_);
|
||
+
|
||
+ if (!ftp_transaction_)
|
||
+ return 0;
|
||
+
|
||
+ int rv =
|
||
+ ftp_transaction_->Read(buf, buf_size,
|
||
+ base::BindOnce(&URLRequestFtpJob::OnReadCompleted,
|
||
+ base::Unretained(this)));
|
||
+
|
||
+ if (rv == ERR_IO_PENDING)
|
||
+ read_in_progress_ = true;
|
||
+ return rv;
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::HandleAuthNeededResponse() {
|
||
+ GURL origin = request_->url().DeprecatedGetOriginAsURL();
|
||
+
|
||
+ if (auth_data_.get()) {
|
||
+ if (auth_data_->state == AUTH_STATE_CANCELED) {
|
||
+ NotifyHeadersComplete();
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH) {
|
||
+ ftp_auth_cache_->Remove(origin, auth_data_->credentials);
|
||
+
|
||
+ // The user entered invalid auth
|
||
+ LogFtpStartResult(FTPStartResult::kFailed);
|
||
+ }
|
||
+ } else {
|
||
+ auth_data_ = std::make_unique<AuthData>();
|
||
+ }
|
||
+ auth_data_->state = AUTH_STATE_NEED_AUTH;
|
||
+
|
||
+ FtpAuthCache::Entry* cached_auth = nullptr;
|
||
+ if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
|
||
+ cached_auth = ftp_auth_cache_->Lookup(origin);
|
||
+ if (cached_auth) {
|
||
+ // Retry using cached auth data.
|
||
+ SetAuth(cached_auth->credentials);
|
||
+ } else {
|
||
+ // Prompt for a username/password.
|
||
+ NotifyHeadersComplete();
|
||
+ }
|
||
+}
|
||
+
|
||
+void URLRequestFtpJob::LogFtpStartResult(FTPStartResult result) {
|
||
+ UMA_HISTOGRAM_ENUMERATION("Net.FTP.StartResult", result);
|
||
+}
|
||
+
|
||
+} // namespace net
|
||
diff --git a/net/url_request/url_request_ftp_job.h b/net/url_request/url_request_ftp_job.h
|
||
new file mode 100644
|
||
index 0000000000000..fda29bf6cb24c
|
||
--- /dev/null
|
||
+++ b/net/url_request/url_request_ftp_job.h
|
||
@@ -0,0 +1,102 @@
|
||
+// Copyright (c) 2012 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
|
||
+#define NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
|
||
+
|
||
+#include <memory>
|
||
+#include <string>
|
||
+
|
||
+#include "base/memory/raw_ptr.h"
|
||
+#include "base/memory/weak_ptr.h"
|
||
+#include "net/base/auth.h"
|
||
+#include "net/base/ip_endpoint.h"
|
||
+#include "net/base/net_export.h"
|
||
+#include "net/ftp/ftp_request_info.h"
|
||
+#include "net/ftp/ftp_transaction.h"
|
||
+#include "net/proxy_resolution/proxy_info.h"
|
||
+#include "net/proxy_resolution/proxy_resolution_service.h"
|
||
+#include "net/url_request/url_request_job.h"
|
||
+
|
||
+namespace net {
|
||
+
|
||
+// These values are persisted to logs. Entries should not be renumbered and
|
||
+// numeric values should never be reused.
|
||
+enum class FTPStartResult : int {
|
||
+ kSuccessNoAuth = 0,
|
||
+ kSuccessAuth = 1,
|
||
+ kFailed = 2,
|
||
+ kMaxValue = kFailed
|
||
+};
|
||
+
|
||
+class FtpAuthCache;
|
||
+class FtpTransactionFactory;
|
||
+class ProxyResolutionRequest;
|
||
+
|
||
+// A URLRequestJob subclass that is built on top of FtpTransaction. It
|
||
+// provides an implementation for FTP.
|
||
+class NET_EXPORT_PRIVATE URLRequestFtpJob : public URLRequestJob {
|
||
+ public:
|
||
+ URLRequestFtpJob(URLRequest* request,
|
||
+ FtpTransactionFactory* ftp_transaction_factory,
|
||
+ FtpAuthCache* ftp_auth_cache);
|
||
+ ~URLRequestFtpJob() override;
|
||
+ void Start() override;
|
||
+ URLRequestFtpJob(const URLRequestFtpJob&) = delete;
|
||
+ URLRequestFtpJob& operator=(const URLRequestFtpJob&) =
|
||
+ delete;
|
||
+ protected:
|
||
+ // Overridden from URLRequestJob:
|
||
+ bool IsSafeRedirect(const GURL& location) override;
|
||
+ bool GetMimeType(std::string* mime_type) const override;
|
||
+ IPEndPoint GetResponseRemoteEndpoint() const override;
|
||
+ void Kill() override;
|
||
+ void GetResponseInfo(HttpResponseInfo* info) override;
|
||
+
|
||
+ private:
|
||
+ class AuthData;
|
||
+
|
||
+ void OnResolveProxyComplete(int result);
|
||
+
|
||
+ void StartFtpTransaction();
|
||
+
|
||
+ void OnStartCompleted(int result);
|
||
+ void OnStartCompletedAsync(int result);
|
||
+ void OnReadCompleted(int result);
|
||
+
|
||
+ void RestartTransactionWithAuth();
|
||
+
|
||
+ // Overridden from URLRequestJob:
|
||
+ LoadState GetLoadState() const override;
|
||
+ bool NeedsAuth() override;
|
||
+ std::unique_ptr<AuthChallengeInfo> GetAuthChallengeInfo() override;
|
||
+ void SetAuth(const AuthCredentials& credentials) override;
|
||
+ void CancelAuth() override;
|
||
+
|
||
+ int ReadRawData(IOBuffer* buf, int buf_size) override;
|
||
+
|
||
+ void HandleAuthNeededResponse();
|
||
+
|
||
+ void LogFtpStartResult(FTPStartResult result);
|
||
+
|
||
+ raw_ptr<ProxyResolutionService> proxy_resolution_service_;
|
||
+ ProxyInfo proxy_info_;
|
||
+ std::unique_ptr<ProxyResolutionRequest> proxy_resolve_request_;
|
||
+
|
||
+ FtpRequestInfo ftp_request_info_;
|
||
+ std::unique_ptr<FtpTransaction> ftp_transaction_;
|
||
+
|
||
+ bool read_in_progress_;
|
||
+
|
||
+ std::unique_ptr<AuthData> auth_data_;
|
||
+
|
||
+ raw_ptr<FtpTransactionFactory> ftp_transaction_factory_;
|
||
+ raw_ptr<FtpAuthCache> ftp_auth_cache_;
|
||
+
|
||
+ base::WeakPtrFactory<URLRequestFtpJob> weak_factory_{this};
|
||
+};
|
||
+
|
||
+} // namespace net
|
||
+
|
||
+#endif // NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
|
||
diff --git a/net/url_request/url_request_ftp_job_unittest.cc b/net/url_request/url_request_ftp_job_unittest.cc
|
||
new file mode 100644
|
||
index 0000000000000..62e4719adc72d
|
||
--- /dev/null
|
||
+++ b/net/url_request/url_request_ftp_job_unittest.cc
|
||
@@ -0,0 +1,245 @@
|
||
+// Copyright (c) 2019 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "net/url_request/url_request_ftp_job.h"
|
||
+
|
||
+#include <memory>
|
||
+
|
||
+#include "base/test/metrics/histogram_tester.h"
|
||
+#include "net/base/auth.h"
|
||
+#include "net/base/load_states.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/base/request_priority.h"
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#include "net/ftp/ftp_response_info.h"
|
||
+#include "net/ftp/ftp_transaction.h"
|
||
+#include "net/ftp/ftp_transaction_factory.h"
|
||
+#include "net/test/test_with_task_environment.h"
|
||
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
||
+#include "net/url_request/url_request_job_factory.h"
|
||
+#include "net/url_request/url_request_test_util.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+
|
||
+namespace net {
|
||
+namespace {
|
||
+
|
||
+class MockFtpTransaction : public FtpTransaction {
|
||
+ public:
|
||
+ MockFtpTransaction(int start_return_value,
|
||
+ int read_return_value,
|
||
+ bool needs_auth,
|
||
+ std::vector<int> restart_return_values)
|
||
+ : start_return_value_(start_return_value),
|
||
+ read_return_value_(read_return_value),
|
||
+ restart_return_values_(restart_return_values),
|
||
+ restart_index_(0) {
|
||
+ response_.needs_auth = needs_auth;
|
||
+ }
|
||
+ ~MockFtpTransaction() override {}
|
||
+
|
||
+ int Start(const FtpRequestInfo* request_info,
|
||
+ CompletionOnceCallback callback,
|
||
+ const NetLogWithSource& net_log,
|
||
+ const NetworkTrafficAnnotationTag& traffic_annotation) override {
|
||
+ return start_return_value_;
|
||
+ }
|
||
+
|
||
+ int RestartWithAuth(const AuthCredentials& credentials,
|
||
+ CompletionOnceCallback callback) override {
|
||
+ CHECK(restart_index_ < restart_return_values_.size());
|
||
+ return restart_return_values_[restart_index_++];
|
||
+ }
|
||
+
|
||
+ int Read(IOBuffer* buf,
|
||
+ int buf_len,
|
||
+ CompletionOnceCallback callback) override {
|
||
+ return read_return_value_;
|
||
+ }
|
||
+
|
||
+ const FtpResponseInfo* GetResponseInfo() const override { return &response_; }
|
||
+
|
||
+ LoadState GetLoadState() const override { return LOAD_STATE_IDLE; }
|
||
+
|
||
+ uint64_t GetUploadProgress() const override { return 0; }
|
||
+
|
||
+ private:
|
||
+ FtpResponseInfo response_;
|
||
+ int start_return_value_;
|
||
+ int read_return_value_;
|
||
+ std::vector<int> restart_return_values_;
|
||
+ unsigned int restart_index_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(MockFtpTransaction);
|
||
+};
|
||
+
|
||
+class MockFtpTransactionFactory : public FtpTransactionFactory {
|
||
+ public:
|
||
+ MockFtpTransactionFactory(int start_return_value,
|
||
+ int read_return_value,
|
||
+ bool needs_auth,
|
||
+ std::vector<int> restart_return_values)
|
||
+ : start_return_value_(start_return_value),
|
||
+ read_return_value_(read_return_value),
|
||
+ needs_auth_(needs_auth),
|
||
+ restart_return_values_(restart_return_values) {}
|
||
+
|
||
+ ~MockFtpTransactionFactory() override {}
|
||
+
|
||
+ std::unique_ptr<FtpTransaction> CreateTransaction() override {
|
||
+ return std::make_unique<MockFtpTransaction>(start_return_value_,
|
||
+ read_return_value_, needs_auth_,
|
||
+ restart_return_values_);
|
||
+ }
|
||
+
|
||
+ void Suspend(bool suspend) override {}
|
||
+
|
||
+ private:
|
||
+ int start_return_value_;
|
||
+ int read_return_value_;
|
||
+ bool needs_auth_;
|
||
+ std::vector<int> restart_return_values_;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(MockFtpTransactionFactory);
|
||
+};
|
||
+
|
||
+class MockURLRequestFtpJobFactory : public URLRequestJobFactory {
|
||
+ public:
|
||
+ MockURLRequestFtpJobFactory(int start_return_value,
|
||
+ int read_return_value,
|
||
+ bool needs_auth,
|
||
+ std::vector<int> restart_return_values)
|
||
+ : auth_cache(new FtpAuthCache()),
|
||
+ factory(new MockFtpTransactionFactory(start_return_value,
|
||
+ read_return_value,
|
||
+ needs_auth,
|
||
+ restart_return_values)) {}
|
||
+
|
||
+ ~MockURLRequestFtpJobFactory() override {
|
||
+ delete auth_cache;
|
||
+ delete factory;
|
||
+ }
|
||
+
|
||
+ std::unique_ptr<URLRequestJob> CreateJob(URLRequest* request) const override {
|
||
+ return std::make_unique<URLRequestFtpJob>(request, factory, auth_cache);
|
||
+ }
|
||
+
|
||
+ bool IsSafeRedirectTarget(const GURL& location) const override {
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ FtpAuthCache* auth_cache;
|
||
+ MockFtpTransactionFactory* factory;
|
||
+
|
||
+ DISALLOW_COPY_AND_ASSIGN(MockURLRequestFtpJobFactory);
|
||
+};
|
||
+
|
||
+using UrlRequestFtpJobTest = TestWithTaskEnvironment;
|
||
+
|
||
+TEST_F(UrlRequestFtpJobTest, HistogramLogSuccessNoAuth) {
|
||
+ base::HistogramTester histograms;
|
||
+ MockURLRequestFtpJobFactory url_request_ftp_job_factory(OK, OK, false, {OK});
|
||
+ TestNetworkDelegate network_delegate;
|
||
+ TestURLRequestContext context(true);
|
||
+ context.set_network_delegate(&network_delegate);
|
||
+ context.set_job_factory(&url_request_ftp_job_factory);
|
||
+ context.Init();
|
||
+
|
||
+ TestDelegate test_delegate;
|
||
+ std::unique_ptr<URLRequest> r(context.CreateRequest(
|
||
+ GURL("ftp://example.test/"), RequestPriority::DEFAULT_PRIORITY,
|
||
+ &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+
|
||
+ r->Start();
|
||
+ test_delegate.RunUntilComplete();
|
||
+
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessNoAuth, 1);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessAuth, 0);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult", FTPStartResult::kFailed,
|
||
+ 0);
|
||
+}
|
||
+
|
||
+TEST_F(UrlRequestFtpJobTest, HistogramLogSuccessAuth) {
|
||
+ base::HistogramTester histograms;
|
||
+ MockURLRequestFtpJobFactory url_request_ftp_job_factory(
|
||
+ ERR_FAILED, ERR_FAILED, true, {OK});
|
||
+ TestNetworkDelegate network_delegate;
|
||
+ TestURLRequestContext context(true);
|
||
+ context.set_network_delegate(&network_delegate);
|
||
+ context.set_job_factory(&url_request_ftp_job_factory);
|
||
+ context.Init();
|
||
+
|
||
+ TestDelegate test_delegate;
|
||
+ test_delegate.set_credentials(AuthCredentials(u"user", u"pass"));
|
||
+ std::unique_ptr<URLRequest> r(context.CreateRequest(
|
||
+ GURL("ftp://example.test/"), RequestPriority::DEFAULT_PRIORITY,
|
||
+ &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+
|
||
+ r->Start();
|
||
+ test_delegate.RunUntilComplete();
|
||
+
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessNoAuth, 0);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessAuth, 1);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult", FTPStartResult::kFailed,
|
||
+ 0);
|
||
+}
|
||
+
|
||
+TEST_F(UrlRequestFtpJobTest, HistogramLogFailed) {
|
||
+ base::HistogramTester histograms;
|
||
+ MockURLRequestFtpJobFactory url_request_ftp_job_factory(
|
||
+ ERR_FAILED, ERR_FAILED, false, {ERR_FAILED});
|
||
+ TestNetworkDelegate network_delegate;
|
||
+ TestURLRequestContext context(true);
|
||
+ context.set_network_delegate(&network_delegate);
|
||
+ context.set_job_factory(&url_request_ftp_job_factory);
|
||
+ context.Init();
|
||
+
|
||
+ TestDelegate test_delegate;
|
||
+ std::unique_ptr<URLRequest> r(context.CreateRequest(
|
||
+ GURL("ftp://example.test/"), RequestPriority::DEFAULT_PRIORITY,
|
||
+ &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+
|
||
+ r->Start();
|
||
+ test_delegate.RunUntilComplete();
|
||
+
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessNoAuth, 0);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessAuth, 0);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult", FTPStartResult::kFailed,
|
||
+ 1);
|
||
+}
|
||
+
|
||
+TEST_F(UrlRequestFtpJobTest, HistogramLogFailedInvalidAuthThenSucceed) {
|
||
+ base::HistogramTester histograms;
|
||
+ MockURLRequestFtpJobFactory url_request_ftp_job_factory(
|
||
+ ERR_FAILED, ERR_FAILED, true, {ERR_ACCESS_DENIED, OK});
|
||
+ TestNetworkDelegate network_delegate;
|
||
+ TestURLRequestContext context(true);
|
||
+ context.set_network_delegate(&network_delegate);
|
||
+ context.set_job_factory(&url_request_ftp_job_factory);
|
||
+ context.Init();
|
||
+
|
||
+ TestDelegate test_delegate;
|
||
+ test_delegate.set_credentials(AuthCredentials(u"user", u"pass"));
|
||
+ std::unique_ptr<URLRequest> r(context.CreateRequest(
|
||
+ GURL("ftp://example.test/"), RequestPriority::DEFAULT_PRIORITY,
|
||
+ &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+
|
||
+ r->Start();
|
||
+ test_delegate.RunUntilComplete();
|
||
+
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessNoAuth, 0);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult",
|
||
+ FTPStartResult::kSuccessAuth, 1);
|
||
+ histograms.ExpectBucketCount("Net.FTP.StartResult", FTPStartResult::kFailed,
|
||
+ 1);
|
||
+}
|
||
+} // namespace
|
||
+} // namespace net
|
||
diff --git a/net/url_request/url_request_job_factory.cc b/net/url_request/url_request_job_factory.cc
|
||
index 68b4dae4b2e71..32c72a06c53bf 100644
|
||
--- a/net/url_request/url_request_job_factory.cc
|
||
+++ b/net/url_request/url_request_job_factory.cc
|
||
@@ -20,9 +20,9 @@ namespace {
|
||
|
||
URLRequestInterceptor* g_interceptor_for_testing = nullptr;
|
||
|
||
-// TODO(mmenke): Look into removing this class and
|
||
-// URLRequestJobFactory::ProtocolHandlers completely. The only other subclass
|
||
-// is iOS-only.
|
||
+// TODO(mmenke): Once FTP support is removed, look into removing this class, and
|
||
+// URLRequestJobFactory::ProtocolHandlers completely. The only other subclass is
|
||
+// iOS-only.
|
||
class HttpProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
|
||
public:
|
||
// URLRequest::is_for_websockets() must match `is_for_websockets`, or requests
|
||
diff --git a/net/url_request/url_request_test_util.h b/net/url_request/url_request_test_util.h
|
||
index 575a510b7e188..2ada1ead4c8f8 100644
|
||
--- a/net/url_request/url_request_test_util.h
|
||
+++ b/net/url_request/url_request_test_util.h
|
||
@@ -35,6 +35,7 @@
|
||
#include "net/cookies/cookie_util.h"
|
||
#include "net/disk_cache/disk_cache.h"
|
||
#include "net/first_party_sets/first_party_set_metadata.h"
|
||
+#include "net/ftp/ftp_network_layer.h"
|
||
#include "net/http/http_auth_handler_factory.h"
|
||
#include "net/http/http_cache.h"
|
||
#include "net/http/http_network_layer.h"
|
||
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
|
||
index 18b16b9d46f01..24c0efb4f2cef 100644
|
||
--- a/net/url_request/url_request_unittest.cc
|
||
+++ b/net/url_request/url_request_unittest.cc
|
||
@@ -173,10 +173,15 @@
|
||
|
||
#include <shlobj.h>
|
||
#include <wrl/client.h>
|
||
-
|
||
#include "base/win/scoped_com_initializer.h"
|
||
#endif
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT) && !BUILDFLAG(IS_ANDROID)
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#include "net/ftp/ftp_network_layer.h"
|
||
+#include "net/url_request/ftp_protocol_handler.h"
|
||
+#endif
|
||
+
|
||
#if BUILDFLAG(IS_APPLE)
|
||
#include "base/mac/mac_util.h"
|
||
#endif
|
||
@@ -218,6 +223,12 @@ const std::u16string kUser(u"user");
|
||
const base::FilePath::CharType kTestFilePath[] =
|
||
FILE_PATH_LITERAL("net/data/url_request_unittest");
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT) && !defined(OS_ANDROID) && \
|
||
+ !defined(OS_FUCHSIA)
|
||
+// Test file used in most FTP tests.
|
||
+const char kFtpTestFile[] = "BullRunSpeech.txt";
|
||
+#endif
|
||
+
|
||
// Tests load timing information in the case a fresh connection was used, with
|
||
// no proxy.
|
||
void TestLoadTimingNotReused(const LoadTimingInfo& load_timing_info,
|
||
@@ -319,6 +330,29 @@ void TestLoadTimingCacheHitNoNetwork(const LoadTimingInfo& load_timing_info) {
|
||
EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
|
||
}
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT) && !defined(OS_ANDROID) && \
|
||
+ !defined(OS_FUCHSIA)
|
||
+// Tests load timing in the case that there is no HTTP response. This can be
|
||
+// used to test in the case of errors or non-HTTP requests.
|
||
+void TestLoadTimingNoHttpResponse(const LoadTimingInfo& load_timing_info) {
|
||
+ EXPECT_FALSE(load_timing_info.socket_reused);
|
||
+ EXPECT_EQ(NetLogSource::kInvalidId, load_timing_info.socket_log_id);
|
||
+
|
||
+ // Only the request times should be non-null.
|
||
+ EXPECT_FALSE(load_timing_info.request_start_time.is_null());
|
||
+ EXPECT_FALSE(load_timing_info.request_start.is_null());
|
||
+
|
||
+ ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing);
|
||
+
|
||
+ EXPECT_TRUE(load_timing_info.proxy_resolve_start.is_null());
|
||
+ EXPECT_TRUE(load_timing_info.proxy_resolve_end.is_null());
|
||
+ EXPECT_TRUE(load_timing_info.send_start.is_null());
|
||
+ EXPECT_TRUE(load_timing_info.send_end.is_null());
|
||
+ EXPECT_TRUE(load_timing_info.receive_headers_start.is_null());
|
||
+ EXPECT_TRUE(load_timing_info.receive_headers_end.is_null());
|
||
+}
|
||
+#endif
|
||
+
|
||
// Job that allows monitoring of its priority.
|
||
class PriorityMonitoringURLRequestJob : public URLRequestTestJob {
|
||
public:
|
||
@@ -11822,6 +11856,404 @@ TEST_F(HTTPSLocalCRLSetTest, InterceptionBlockedAllowOverrideOnHSTS) {
|
||
}
|
||
#endif // !BUILDFLAG(IS_IOS)
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT) && !defined(OS_ANDROID) && \
|
||
+ !defined(OS_FUCHSIA)
|
||
+// FTP uses a second TCP connection with the port number allocated dynamically
|
||
+// on the server side, so it would be hard to make RemoteTestServer proxy FTP
|
||
+// connections reliably. FTP tests are disabled on platforms that use
|
||
+// RemoteTestServer. See http://crbug.com/495220
|
||
+class URLRequestTestFTP : public URLRequestTest {
|
||
+ public:
|
||
+ URLRequestTestFTP()
|
||
+ : ftp_test_server_(SpawnedTestServer::TYPE_FTP,
|
||
+ base::FilePath(kTestFilePath)) {
|
||
+ // Can't use |default_context_|'s HostResolver to set up the
|
||
+ // FTPTransactionFactory because it hasn't been created yet.
|
||
+ default_context().set_host_resolver(&host_resolver_);
|
||
+ }
|
||
+
|
||
+ // URLRequestTest interface:
|
||
+ void SetUpFactory() override {
|
||
+ // Add FTP support to the default URLRequestContext.
|
||
+ job_factory_->SetProtocolHandler(
|
||
+ "ftp", FtpProtocolHandler::Create(&host_resolver_, &ftp_auth_cache_));
|
||
+ }
|
||
+
|
||
+ std::string GetTestFileContents() {
|
||
+ base::FilePath path;
|
||
+ EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &path));
|
||
+ path = path.Append(kTestFilePath);
|
||
+ path = path.AppendASCII(kFtpTestFile);
|
||
+ std::string contents;
|
||
+ EXPECT_TRUE(base::ReadFileToString(path, &contents));
|
||
+ return contents;
|
||
+ }
|
||
+
|
||
+ protected:
|
||
+ MockHostResolver host_resolver_;
|
||
+ FtpAuthCache ftp_auth_cache_;
|
||
+
|
||
+ SpawnedTestServer ftp_test_server_;
|
||
+};
|
||
+
|
||
+// Make sure an FTP request using an unsafe ports fails.
|
||
+TEST_F(URLRequestTestFTP, UnsafePort) {
|
||
+ GURL url("ftp://127.0.0.1:7");
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ url, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(ERR_UNSAFE_PORT, d.request_status());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPDirectoryListing) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL("/"), DEFAULT_PRIORITY, &d,
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_LT(0, d.bytes_received());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().host(),
|
||
+ r->GetResponseRemoteEndpoint().ToStringWithoutPort());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().port(),
|
||
+ r->GetResponseRemoteEndpoint().port());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPGetTestAnonymous) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL(kFtpTestFile), DEFAULT_PRIORITY, &d,
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d.data_received());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().host(),
|
||
+ r->GetResponseRemoteEndpoint().ToStringWithoutPort());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().port(),
|
||
+ r->GetResponseRemoteEndpoint().port());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPMimeType) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ struct {
|
||
+ const char* path;
|
||
+ const char* mime;
|
||
+ } test_cases[] = {
|
||
+ {"/", "text/vnd.chromium.ftp-dir"},
|
||
+ {kFtpTestFile, "application/octet-stream"},
|
||
+ };
|
||
+
|
||
+ for (const auto test : test_cases) {
|
||
+ TestDelegate d;
|
||
+
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL(test.path), DEFAULT_PRIORITY, &d,
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ std::string mime;
|
||
+ r->GetMimeType(&mime);
|
||
+ EXPECT_EQ(test.mime, mime);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPGetTest) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "chrome",
|
||
+ "chrome"),
|
||
+ DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d.data_received());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().host(),
|
||
+ r->GetResponseRemoteEndpoint().ToStringWithoutPort());
|
||
+ EXPECT_EQ(ftp_test_server_.host_port_pair().port(),
|
||
+ r->GetResponseRemoteEndpoint().port());
|
||
+
|
||
+ LoadTimingInfo load_timing_info;
|
||
+ r->GetLoadTimingInfo(&load_timing_info);
|
||
+ TestLoadTimingNoHttpResponse(load_timing_info);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCheckWrongPassword) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "chrome",
|
||
+ "wrong_password"),
|
||
+ DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(d.bytes_received(), 0);
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCheckWrongPasswordRestart) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ // Set correct login credentials. The delegate will be asked for them when
|
||
+ // the initial login with wrong credentials will fail.
|
||
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "chrome",
|
||
+ "wrong_password"),
|
||
+ DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d.data_received());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCheckWrongUser) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "wrong_user",
|
||
+ "chrome"),
|
||
+ DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(0, d.bytes_received());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCheckWrongUserRestart) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ // Set correct login credentials. The delegate will be asked for them when
|
||
+ // the initial login with wrong credentials will fail.
|
||
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "wrong_user",
|
||
+ "chrome"),
|
||
+ DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d.response_started_count());
|
||
+ EXPECT_FALSE(d.received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d.data_received());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCacheURLCredentials) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ std::unique_ptr<TestDelegate> d(new TestDelegate);
|
||
+ {
|
||
+ // Pass correct login identity in the URL.
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "chrome",
|
||
+ "chrome"),
|
||
+ DEFAULT_PRIORITY, d.get(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d->RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d->response_started_count());
|
||
+ EXPECT_FALSE(d->received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d->data_received());
|
||
+ }
|
||
+
|
||
+ d = std::make_unique<TestDelegate>();
|
||
+ {
|
||
+ // This request should use cached identity from previous request.
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL(kFtpTestFile), DEFAULT_PRIORITY, d.get(),
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d->RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d->response_started_count());
|
||
+ EXPECT_FALSE(d->received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d->data_received());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FTPCacheLoginBoxCredentials) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ std::unique_ptr<TestDelegate> d(new TestDelegate);
|
||
+ // Set correct login credentials. The delegate will be asked for them when
|
||
+ // the initial login with wrong credentials will fail.
|
||
+ d->set_credentials(AuthCredentials(kChrome, kChrome));
|
||
+ {
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURLWithUserAndPassword(kFtpTestFile, "chrome",
|
||
+ "wrong_password"),
|
||
+ DEFAULT_PRIORITY, d.get(), TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d->RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d->response_started_count());
|
||
+ EXPECT_FALSE(d->received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d->data_received());
|
||
+ }
|
||
+
|
||
+ // Use a new delegate without explicit credentials. The cached ones should be
|
||
+ // used.
|
||
+ d = std::make_unique<TestDelegate>();
|
||
+ {
|
||
+ // Don't pass wrong credentials in the URL, they would override valid cached
|
||
+ // ones.
|
||
+ std::unique_ptr<URLRequest> r(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL(kFtpTestFile), DEFAULT_PRIORITY, d.get(),
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ r->Start();
|
||
+ EXPECT_TRUE(r->is_pending());
|
||
+
|
||
+ d->RunUntilComplete();
|
||
+
|
||
+ EXPECT_FALSE(r->is_pending());
|
||
+ EXPECT_EQ(1, d->response_started_count());
|
||
+ EXPECT_FALSE(d->received_data_before_response());
|
||
+ EXPECT_EQ(GetTestFileContents(), d->data_received());
|
||
+ }
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, RawBodyBytes) {
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+
|
||
+ TestDelegate d;
|
||
+ std::unique_ptr<URLRequest> req(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL("simple.html"), DEFAULT_PRIORITY, &d,
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ req->Start();
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ EXPECT_EQ(6, req->GetRawBodyBytes());
|
||
+}
|
||
+
|
||
+TEST_F(URLRequestTestFTP, FtpAuthCancellation) {
|
||
+ ftp_test_server_.set_no_anonymous_ftp_user(true);
|
||
+ ASSERT_TRUE(ftp_test_server_.Start());
|
||
+ TestDelegate d;
|
||
+ std::unique_ptr<URLRequest> req(default_context().CreateRequest(
|
||
+ ftp_test_server_.GetURL("simple.html"), DEFAULT_PRIORITY, &d,
|
||
+ TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ req->Start();
|
||
+ d.RunUntilComplete();
|
||
+
|
||
+ ASSERT_TRUE(d.auth_required_called());
|
||
+ EXPECT_EQ(OK, d.request_status());
|
||
+ EXPECT_TRUE(req->auth_challenge_info());
|
||
+ std::string mime_type;
|
||
+ req->GetMimeType(&mime_type);
|
||
+ EXPECT_EQ("text/plain", mime_type);
|
||
+ EXPECT_EQ("", d.data_received());
|
||
+ EXPECT_EQ(-1, req->GetExpectedContentSize());
|
||
+}
|
||
+
|
||
+class URLRequestTestFTPOverHttpProxy : public URLRequestTestFTP {
|
||
+ public:
|
||
+ // Test interface:
|
||
+ void SetUp() override {
|
||
+ proxy_resolution_service_ = ConfiguredProxyResolutionService::CreateFixed(
|
||
+ "localhost", TRAFFIC_ANNOTATION_FOR_TESTS);
|
||
+ default_context_->set_proxy_resolution_service(
|
||
+ proxy_resolution_service_.get());
|
||
+ URLRequestTestFTP::SetUp();
|
||
+ }
|
||
+
|
||
+ private:
|
||
+ std::unique_ptr<ProxyResolutionService> proxy_resolution_service_;
|
||
+};
|
||
+
|
||
+// Check that FTP is not supported over an HTTP proxy.
|
||
+TEST_F(URLRequestTestFTPOverHttpProxy, Fails) {
|
||
+ TestDelegate delegate;
|
||
+ std::unique_ptr<URLRequest> request(
|
||
+ default_context_->CreateRequest(GURL("ftp://foo.test/"), DEFAULT_PRIORITY,
|
||
+ &delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
|
||
+ request->Start();
|
||
+ delegate.RunUntilComplete();
|
||
+
|
||
+ EXPECT_THAT(delegate.request_status(), IsError(ERR_NO_SUPPORTED_PROXIES));
|
||
+}
|
||
+
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
TEST_F(URLRequestTest, NetworkAccessedSetOnHostResolutionFailure) {
|
||
auto context_builder = CreateTestURLRequestContextBuilder();
|
||
auto host_resolver = std::make_unique<MockHostResolver>();
|
||
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
|
||
index 16e8cf435e4b0..2d06e601e9376 100644
|
||
--- a/services/network/network_context.cc
|
||
+++ b/services/network/network_context.cc
|
||
@@ -164,6 +164,10 @@
|
||
#include "services/network/sct_auditing/sct_auditing_handler.h"
|
||
#endif // BUILDFLAG(IS_CT_SUPPORTED)
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
#if BUILDFLAG(IS_CHROMEOS)
|
||
#include "services/network/cert_verifier_with_trust_anchors.h"
|
||
#endif // BUILDFLAG(IS_CHROMEOS)
|
||
@@ -2313,6 +2317,14 @@ void NetworkContext::AddAuthCacheEntry(
|
||
const net::NetworkAnonymizationKey& network_anonymization_key,
|
||
const net::AuthCredentials& credentials,
|
||
AddAuthCacheEntryCallback callback) {
|
||
+ if (challenge.challenger.scheme() == url::kFtpScheme) {
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ net::FtpAuthCache* auth_cache = url_request_context_->ftp_auth_cache();
|
||
+ auth_cache->Add(challenge.challenger.GetURL(), credentials);
|
||
+#else
|
||
+ NOTREACHED();
|
||
+#endif // BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ } else {
|
||
net::HttpAuthCache* http_auth_cache =
|
||
url_request_context_->http_transaction_factory()
|
||
->GetSession()
|
||
@@ -2324,6 +2336,7 @@ void NetworkContext::AddAuthCacheEntry(
|
||
net::HttpAuth::StringToScheme(challenge.scheme),
|
||
network_anonymization_key, challenge.challenge,
|
||
credentials, challenge.path);
|
||
+ }
|
||
std::move(callback).Run();
|
||
}
|
||
|
||
@@ -2734,6 +2747,12 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
|
||
}
|
||
builder.set_hsts_policy_bypass_list(params_->hsts_policy_bypass_list);
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ builder.set_ftp_enabled(params_->enable_ftp_url_support);
|
||
+#else // BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ DCHECK(!params_->enable_ftp_url_support);
|
||
+#endif
|
||
+
|
||
#if BUILDFLAG(ENABLE_REPORTING)
|
||
bool reporting_enabled = base::FeatureList::IsEnabled(features::kReporting);
|
||
if (reporting_enabled) {
|
||
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
|
||
index 6d3382b4de9ff..a4d5cdd555ad8 100644
|
||
--- a/services/network/network_context_unittest.cc
|
||
+++ b/services/network/network_context_unittest.cc
|
||
@@ -180,6 +180,10 @@
|
||
#include "url/scheme_host_port.h"
|
||
#include "url/url_constants.h"
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+#include "net/ftp/ftp_auth_cache.h"
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
#if BUILDFLAG(ENABLE_REPORTING)
|
||
#include "net/network_error_logging/network_error_logging_service.h"
|
||
#include "net/reporting/reporting_cache.h"
|
||
@@ -927,7 +931,8 @@ TEST_F(NetworkContextTest, UnhandedProtocols) {
|
||
GURL("file:///not/a/path/that/leads/anywhere/but/it/should/not/matter/"
|
||
"anyways"),
|
||
|
||
- // FTP is not supported natively by Chrome.
|
||
+ // FTP is handled by the network service on some platforms, but support
|
||
+ // for it is not enabled by default.
|
||
GURL("ftp://foo.test/"),
|
||
};
|
||
|
||
@@ -7438,6 +7443,37 @@ TEST_F(NetworkContextTest, BlockAllCookies) {
|
||
EXPECT_EQ("None", response_body);
|
||
}
|
||
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+TEST_F(NetworkContextTest, AddFtpAuthCacheEntry) {
|
||
+ GURL url("ftp://example.test/");
|
||
+ const char16_t kUsername[] = u"test_user";
|
||
+ const char16_t kPassword[] = u"test_pass";
|
||
+ mojom::NetworkContextParamsPtr params =
|
||
+ CreateNetworkContextParamsForTesting();
|
||
+ params->enable_ftp_url_support = true;
|
||
+ std::unique_ptr<NetworkContext> network_context =
|
||
+ CreateContextWithParams(std::move(params));
|
||
+ net::AuthChallengeInfo challenge;
|
||
+ challenge.is_proxy = false;
|
||
+ challenge.challenger = url::Origin::Create(url);
|
||
+
|
||
+ ASSERT_TRUE(network_context->url_request_context()->ftp_auth_cache());
|
||
+ ASSERT_FALSE(
|
||
+ network_context->url_request_context()->ftp_auth_cache()->Lookup(url));
|
||
+ base::RunLoop run_loop;
|
||
+ network_context->AddAuthCacheEntry(challenge, net::NetworkIsolationKey(),
|
||
+ net::AuthCredentials(kUsername, kPassword),
|
||
+ run_loop.QuitClosure());
|
||
+ run_loop.Run();
|
||
+ net::FtpAuthCache::Entry* entry =
|
||
+ network_context->url_request_context()->ftp_auth_cache()->Lookup(url);
|
||
+ ASSERT_TRUE(entry);
|
||
+ EXPECT_EQ(url, entry->origin);
|
||
+ EXPECT_EQ(kUsername, entry->credentials.username());
|
||
+ EXPECT_EQ(kPassword, entry->credentials.password());
|
||
+}
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
TEST_F(NetworkContextTest, AddHttpAuthCacheEntry) {
|
||
std::unique_ptr<NetworkContext> network_context =
|
||
CreateContextWithParams(CreateNetworkContextParamsForTesting());
|
||
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
|
||
index d38116aca078f..18b3daef09f8e 100644
|
||
--- a/services/network/public/cpp/features.cc
|
||
+++ b/services/network/public/cpp/features.cc
|
||
@@ -225,6 +225,11 @@ BASE_FEATURE(kAcceptCHFrame, "AcceptCHFrame", base::FEATURE_ENABLED_BY_DEFAULT);
|
||
BASE_FEATURE(kGetCookiesStringUma,
|
||
"GetCookiesStringUma",
|
||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||
+// Enables support for FTP URLs. When disabled FTP URLs will behave the same as
|
||
+// any other URL scheme that's unknown to the UA. See https://crbug.com/333943
|
||
+BASE_FEATURE(kFtpProtocol,
|
||
+ "FtpProtocol",
|
||
+ base::FEATURE_ENABLED_BY_DEFAULT);
|
||
|
||
namespace {
|
||
|
||
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
|
||
index 66221223e2c55..31dc4e14bfe10 100644
|
||
--- a/services/network/public/cpp/features.h
|
||
+++ b/services/network/public/cpp/features.h
|
||
@@ -92,6 +92,8 @@ extern uint32_t GetDataPipeDefaultAllocationSize(
|
||
COMPONENT_EXPORT(NETWORK_CPP)
|
||
extern size_t GetNetAdapterMaxBufSize();
|
||
|
||
+COMPONENT_EXPORT(NETWORK_CPP) BASE_DECLARE_FEATURE(kFtpProtocol);
|
||
+
|
||
COMPONENT_EXPORT(NETWORK_CPP)
|
||
extern size_t GetLoaderChunkSize();
|
||
|
||
diff --git a/services/network/public/cpp/simple_url_loader.cc b/services/network/public/cpp/simple_url_loader.cc
|
||
index 517777afcfeae..9b2469197b537 100644
|
||
--- a/services/network/public/cpp/simple_url_loader.cc
|
||
+++ b/services/network/public/cpp/simple_url_loader.cc
|
||
@@ -1848,7 +1848,7 @@ void SimpleURLLoaderImpl::OnReceiveResponse(
|
||
|
||
// Assume a 200 response unless headers were received indicating otherwise.
|
||
// No headers indicates this was not a real HTTP response (Could be a file
|
||
- // URL, chrome URL, response could have been provided by something else, etc).
|
||
+ // URL, chrome URL, FTP, response could have been provided by something else, etc).
|
||
int response_code = 200;
|
||
if (response_head->headers)
|
||
response_code = response_head->headers->response_code();
|
||
diff --git a/services/network/public/cpp/url_util.cc b/services/network/public/cpp/url_util.cc
|
||
index bb80e3ebd42f8..dbf99f662b65d 100644
|
||
--- a/services/network/public/cpp/url_util.cc
|
||
+++ b/services/network/public/cpp/url_util.cc
|
||
@@ -4,12 +4,25 @@
|
||
|
||
#include "services/network/public/cpp/url_util.h"
|
||
|
||
+#include "base/feature_list.h"
|
||
+#include "build/build_config.h"
|
||
+#include "net/net_buildflags.h"
|
||
+#include "services/network/public/cpp/features.h"
|
||
+#include "services/network/public/cpp/is_potentially_trustworthy.h"
|
||
#include "url/gurl.h"
|
||
+#include "url/url_constants.h"
|
||
|
||
namespace network {
|
||
|
||
bool IsURLHandledByNetworkService(const GURL& url) {
|
||
- return (url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS());
|
||
+ if (url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS())
|
||
+ return true;
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ if (url.SchemeIs(url::kFtpScheme) &&
|
||
+ base::FeatureList::IsEnabled(features::kFtpProtocol))
|
||
+ return true;
|
||
+#endif
|
||
+ return false;
|
||
}
|
||
|
||
} // namespace network
|
||
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
|
||
index c774dfca15ae1..e972bac69d2e5 100644
|
||
--- a/services/network/public/mojom/network_context.mojom
|
||
+++ b/services/network/public/mojom/network_context.mojom
|
||
@@ -374,6 +374,10 @@ struct NetworkContextParams {
|
||
// used to fetch PAC scripts. Note that currently data URLs are always enabled
|
||
// and file URLs are always disabled.
|
||
|
||
+ // True if ftp URLs should be supported.
|
||
+ // Must be false if built without FTP support.
|
||
+ bool enable_ftp_url_support = false;
|
||
+
|
||
// Whether or not to check the Android platform's cleartext policy for
|
||
// requests. Under some conditions, Android may advise us to block cleartext
|
||
// traffic.
|
||
@@ -1620,12 +1624,14 @@ interface NetworkContext {
|
||
LoadHttpAuthCacheProxyEntries(mojo_base.mojom.UnguessableToken cache_key)
|
||
=> ();
|
||
|
||
- // Adds an entry to the HttpAuthCache. `network_anonymization_key` is the
|
||
- // NetworkAnonymizationKey to restrict the credentials to, and is only
|
||
- // respected for server (not proxy) HTTP auth and only when the NetworkService
|
||
- // was configured to split the auth cache by NetworkAnonymizationKey.
|
||
- // `challenge` may not necessarily contain a stateful challenge that requires
|
||
- // a persistent connection, allowing the cache to be pre-populated.
|
||
+ // Adds an entry to the HttpAuthCache or FtpAuthCache (determined by whether
|
||
+ // the |challenger| field within |challenge| is an ftp:// URL).
|
||
+ // |network_isolation_key| is the NetworkIsolationKey to restrict the
|
||
+ // credentials to, and is only respected for server (not proxy) HTTP auth
|
||
+ // and only when the NetworkService was configured to split the auth cache by
|
||
+ // NetworkIsolationKey. |challenge| may not necessarily contain a stateful
|
||
+ // challenge that requires a persistent connection, allowing the cache to be
|
||
+ // pre-populated.
|
||
AddAuthCacheEntry(AuthChallengeInfo challenge,
|
||
NetworkAnonymizationKey network_anonymization_key,
|
||
AuthCredentials credentials) => ();
|
||
diff --git a/third_party/blink/public/web/web_document_loader.h b/third_party/blink/public/web/web_document_loader.h
|
||
index f9d3219990dcf..d2b95cce4c380 100644
|
||
--- a/third_party/blink/public/web/web_document_loader.h
|
||
+++ b/third_party/blink/public/web/web_document_loader.h
|
||
@@ -135,6 +135,9 @@ class BLINK_EXPORT WebDocumentLoader {
|
||
// committed in this WebDocumentLoader had transient activation.
|
||
virtual bool LastNavigationHadTransientUserActivation() const = 0;
|
||
|
||
+ // Returns true when the document is a FTP directory.
|
||
+ virtual bool IsListingFtpDirectory() const = 0;
|
||
+
|
||
// Sets the CodeCacheHosts for this loader.
|
||
virtual void SetCodeCacheHost(
|
||
CrossVariantMojoRemote<mojom::CodeCacheHostInterfaceBase> code_cache_host,
|
||
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
|
||
index 15c696731f42b..61659b43bc89a 100644
|
||
--- a/third_party/blink/renderer/core/loader/document_loader.cc
|
||
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
|
||
@@ -160,6 +160,7 @@
|
||
#include "third_party/blink/renderer/platform/loader/fetch/resource_timing_utils.h"
|
||
#include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
|
||
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/navigation_body_loader.h"
|
||
+#include "third_party/blink/renderer/platform/loader/ftp_directory_listing.h"
|
||
#include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
|
||
#include "third_party/blink/renderer/platform/mhtml/archive_resource.h"
|
||
#include "third_party/blink/renderer/platform/mhtml/mhtml_archive.h"
|
||
@@ -309,6 +310,7 @@ struct SameSizeAsDocumentLoader
|
||
bool is_same_origin_navigation;
|
||
bool has_text_fragment_token;
|
||
bool was_discarded;
|
||
+ bool listing_ftp_directory;
|
||
bool loading_main_document_from_mhtml_archive;
|
||
bool loading_srcdoc;
|
||
KURL fallback_base_url;
|
||
@@ -1279,7 +1281,7 @@ void DocumentLoader::BodyDataReceivedImpl(BodyData& data) {
|
||
DCHECK(!frame_->GetPage()->Paused());
|
||
time_of_last_data_received_ = clock_->NowTicks();
|
||
|
||
- if (loading_main_document_from_mhtml_archive_) {
|
||
+ if (listing_ftp_directory_ || loading_main_document_from_mhtml_archive_) {
|
||
// 1) Ftp directory listings accumulate data buffer and transform it later
|
||
// to the actual document content.
|
||
// 2) Mhtml archives accumulate data buffer and parse it as mhtml later
|
||
@@ -1402,6 +1404,12 @@ void DocumentLoader::FinishedLoading(base::TimeTicks finish_time) {
|
||
MainThreadDebugger::Instance(frame_->DomWindow()->GetIsolate())
|
||
->IsPaused());
|
||
|
||
+ if (listing_ftp_directory_) {
|
||
+ data_buffer_ = GenerateFtpDirectoryListingHtml(
|
||
+ response_.CurrentRequestUrl(), data_buffer_.get());
|
||
+ ProcessDataBuffer();
|
||
+ }
|
||
+
|
||
if (loading_main_document_from_mhtml_archive_ && state_ < kCommitted) {
|
||
// The browser process should block any navigation to an MHTML archive
|
||
// inside iframes. See NavigationRequest::OnResponseStarted().
|
||
@@ -1542,6 +1550,17 @@ DocumentPolicy::ParsedDocumentPolicy DocumentLoader::CreateDocumentPolicy() {
|
||
void DocumentLoader::HandleResponse() {
|
||
DCHECK(frame_);
|
||
|
||
+ if (response_.CurrentRequestUrl().ProtocolIs("ftp") &&
|
||
+ response_.MimeType() == "text/vnd.chromium.ftp-dir") {
|
||
+ if (response_.CurrentRequestUrl().Query() == "raw") {
|
||
+ // Interpret the FTP LIST command result as text.
|
||
+ response_.SetMimeType(AtomicString("text/plain"));
|
||
+ } else {
|
||
+ // FTP directory listing: Make up an HTML for the entries.
|
||
+ listing_ftp_directory_ = true;
|
||
+ response_.SetMimeType(AtomicString("text/html"));
|
||
+ }
|
||
+ }
|
||
if (response_.IsHTTP() &&
|
||
!network::IsSuccessfulStatus(response_.HttpStatusCode())) {
|
||
DCHECK(!IsA<HTMLObjectElement>(frame_->Owner()));
|
||
diff --git a/third_party/blink/renderer/core/loader/document_loader.h b/third_party/blink/renderer/core/loader/document_loader.h
|
||
index 4b0cc3793ecb9..337cd1eea6a70 100644
|
||
--- a/third_party/blink/renderer/core/loader/document_loader.h
|
||
+++ b/third_party/blink/renderer/core/loader/document_loader.h
|
||
@@ -359,6 +359,8 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
|
||
return devtools_navigation_token_;
|
||
}
|
||
|
||
+ bool IsListingFtpDirectory() const override { return listing_ftp_directory_; }
|
||
+
|
||
UseCounterImpl& GetUseCounter() { return use_counter_; }
|
||
|
||
PrefetchedSignedExchangeManager* GetPrefetchedSignedExchangeManager() const;
|
||
@@ -778,6 +780,8 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
|
||
// See WebNavigationParams for definition.
|
||
const bool was_discarded_ = false;
|
||
|
||
+ bool listing_ftp_directory_ = false;
|
||
+
|
||
// True when loading the main document from the MHTML archive. It implies an
|
||
// |archive_| to be created. Nested documents will also inherit from the same
|
||
// |archive_|, but won't have |loading_main_document_from_mhtml_archive_| set.
|
||
diff --git a/third_party/blink/renderer/platform/loader/BUILD.gn b/third_party/blink/renderer/platform/loader/BUILD.gn
|
||
index da5070066fd47..39fc54f713ec4 100644
|
||
--- a/third_party/blink/renderer/platform/loader/BUILD.gn
|
||
+++ b/third_party/blink/renderer/platform/loader/BUILD.gn
|
||
@@ -161,6 +161,8 @@ blink_platform_sources("loader") {
|
||
"fetch/worker_resource_timing_notifier.h",
|
||
"internet_disconnected_url_loader.cc",
|
||
"internet_disconnected_url_loader.h",
|
||
+ "ftp_directory_listing.cc",
|
||
+ "ftp_directory_listing.h",
|
||
"link_header.cc",
|
||
"link_header.h",
|
||
"mixed_content.cc",
|
||
@@ -244,6 +246,7 @@ source_set("unit_tests") {
|
||
"fetch/url_loader/sync_load_context_unittest.cc",
|
||
"fetch/url_loader/url_loader_unittest.cc",
|
||
"fetch/url_loader/worker_main_script_loader_unittest.cc",
|
||
+ "ftp_directory_listing_test.cc",
|
||
"link_header_test.cc",
|
||
"static_data_navigation_body_loader_test.cc",
|
||
"subresource_integrity_test.cc",
|
||
diff --git a/third_party/blink/renderer/platform/loader/ftp_directory_listing.cc b/third_party/blink/renderer/platform/loader/ftp_directory_listing.cc
|
||
new file mode 100644
|
||
index 0000000000000..fbee10b1875a7
|
||
--- /dev/null
|
||
+++ b/third_party/blink/renderer/platform/loader/ftp_directory_listing.cc
|
||
@@ -0,0 +1,109 @@
|
||
+// Copyright 2018 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "third_party/blink/renderer/platform/loader/ftp_directory_listing.h"
|
||
+
|
||
+#include <string>
|
||
+#include <vector>
|
||
+
|
||
+#include "base/i18n/encoding_detection.h"
|
||
+#include "base/i18n/icu_string_conversions.h"
|
||
+#include "base/strings/escape.h"
|
||
+#include "base/strings/string_util.h"
|
||
+#include "base/strings/sys_string_conversions.h"
|
||
+#include "base/strings/utf_string_conversions.h"
|
||
+#include "base/time/time.h"
|
||
+#include "net/base/directory_listing.h"
|
||
+#include "net/base/net_errors.h"
|
||
+#include "net/ftp/ftp_directory_listing_parser.h"
|
||
+#include "net/net_buildflags.h"
|
||
+#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
|
||
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
|
||
+#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
|
||
+#include "url/gurl.h"
|
||
+
|
||
+namespace blink {
|
||
+
|
||
+namespace {
|
||
+
|
||
+std::u16string ConvertPathToUTF16(const std::string& path) {
|
||
+ // Per RFC 2640, FTP servers should use UTF-8 or its proper subset ASCII,
|
||
+ // but many old FTP servers use legacy encodings. Try UTF-8 first.
|
||
+ if (base::IsStringUTF8(path))
|
||
+ return base::UTF8ToUTF16(path);
|
||
+
|
||
+ // Try detecting the encoding. The sample is rather small though, so it may
|
||
+ // fail.
|
||
+ std::string encoding;
|
||
+ if (base::DetectEncoding(path, &encoding) && encoding != "US-ASCII") {
|
||
+ std::u16string path_utf16;
|
||
+ if (base::CodepageToUTF16(path, encoding.c_str(),
|
||
+ base::OnStringConversionError::SUBSTITUTE,
|
||
+ &path_utf16)) {
|
||
+ return path_utf16;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Use system native encoding as the last resort.
|
||
+ return base::WideToUTF16(base::SysNativeMBToWide(path));
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+scoped_refptr<SharedBuffer> GenerateFtpDirectoryListingHtml(
|
||
+ const KURL& url,
|
||
+ const SharedBuffer* input) {
|
||
+ const GURL gurl = GURL(url);
|
||
+ scoped_refptr<SharedBuffer> output = SharedBuffer::Create();
|
||
+ base::UnescapeRule::Type unescape_rules =
|
||
+ base::UnescapeRule::SPACES |
|
||
+ base::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS;
|
||
+ std::string unescaped_path =
|
||
+ base::UnescapeURLComponent(gurl.path(), unescape_rules);
|
||
+ const std::string header =
|
||
+ net::GetDirectoryListingHeader(ConvertPathToUTF16(unescaped_path));
|
||
+ output->Append(header.c_str(), header.size());
|
||
+
|
||
+ // If this isn't top level directory (i.e. the path isn't "/",)
|
||
+ // add a link to the parent directory.
|
||
+ if (gurl.path().length() > 1) {
|
||
+ const std::string link = net::GetParentDirectoryLink();
|
||
+ output->Append(link.c_str(), link.size());
|
||
+ }
|
||
+
|
||
+ std::string flatten;
|
||
+ for (const auto& span : *input) {
|
||
+ flatten.append(span.data(), span.size());
|
||
+ }
|
||
+
|
||
+ std::vector<net::FtpDirectoryListingEntry> entries;
|
||
+ int rv = net::ERR_NOT_IMPLEMENTED;
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+ rv = net::ParseFtpDirectoryListing(flatten, base::Time::Now(), &entries);
|
||
+#endif
|
||
+ if (rv != net::OK) {
|
||
+ const std::string script = "<script>onListingParsingError();</script>\n";
|
||
+ output->Append(script.c_str(), script.size());
|
||
+ return output;
|
||
+ }
|
||
+ for (const net::FtpDirectoryListingEntry& entry : entries) {
|
||
+ // Skip the current and parent directory entries in the listing.
|
||
+ // net::GetParentDirectoryLink() takes care of them.
|
||
+ if (base::EqualsASCII(entry.name, ".") ||
|
||
+ base::EqualsASCII(entry.name, ".."))
|
||
+ continue;
|
||
+
|
||
+ bool is_directory =
|
||
+ (entry.type == net::FtpDirectoryListingEntry::DIRECTORY);
|
||
+ int64_t size =
|
||
+ entry.type == net::FtpDirectoryListingEntry::FILE ? entry.size : 0;
|
||
+ std::string entry_string = net::GetDirectoryListingEntry(
|
||
+ entry.name, entry.raw_name, is_directory, size, entry.last_modified);
|
||
+ output->Append(entry_string.c_str(), entry_string.size());
|
||
+ }
|
||
+
|
||
+ return output;
|
||
+}
|
||
+
|
||
+} // namespace blink
|
||
diff --git a/third_party/blink/renderer/platform/loader/ftp_directory_listing.h b/third_party/blink/renderer/platform/loader/ftp_directory_listing.h
|
||
new file mode 100644
|
||
index 0000000000000..c5f9bda28b5c0
|
||
--- /dev/null
|
||
+++ b/third_party/blink/renderer/platform/loader/ftp_directory_listing.h
|
||
@@ -0,0 +1,24 @@
|
||
+// Copyright 2018 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FTP_DIRECTORY_LISTING_H_
|
||
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FTP_DIRECTORY_LISTING_H_
|
||
+
|
||
+#include "base/memory/scoped_refptr.h"
|
||
+#include "third_party/blink/renderer/platform/platform_export.h"
|
||
+#include "third_party/blink/renderer/platform/wtf/forward.h"
|
||
+
|
||
+namespace blink {
|
||
+
|
||
+class KURL;
|
||
+
|
||
+// Translates |input|, an FTP LISTING result, to an HTML and returns it. When
|
||
+// an error happens that is written in the result HTML.
|
||
+PLATFORM_EXPORT scoped_refptr<SharedBuffer> GenerateFtpDirectoryListingHtml(
|
||
+ const KURL& url,
|
||
+ const SharedBuffer* input);
|
||
+
|
||
+} // namespace blink
|
||
+
|
||
+#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_LOADER_FTP_DIRECTORY_LISTING_H_
|
||
diff --git a/third_party/blink/renderer/platform/loader/ftp_directory_listing_test.cc b/third_party/blink/renderer/platform/loader/ftp_directory_listing_test.cc
|
||
new file mode 100644
|
||
index 0000000000000..9f52cba0c1222
|
||
--- /dev/null
|
||
+++ b/third_party/blink/renderer/platform/loader/ftp_directory_listing_test.cc
|
||
@@ -0,0 +1,114 @@
|
||
+// Copyright 2018 The Chromium Authors
|
||
+// Use of this source code is governed by a BSD-style license that can be
|
||
+// found in the LICENSE file.
|
||
+
|
||
+#include "third_party/blink/renderer/platform/loader/ftp_directory_listing.h"
|
||
+
|
||
+#include <string>
|
||
+
|
||
+#include "base/test/icu_test_util.h"
|
||
+#include "net/net_buildflags.h"
|
||
+#include "testing/gtest/include/gtest/gtest.h"
|
||
+#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
|
||
+#include "third_party/blink/renderer/platform/weborigin/kurl.h"
|
||
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
|
||
+#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
|
||
+#include "third_party/icu/source/i18n/unicode/timezone.h"
|
||
+
|
||
+namespace blink {
|
||
+namespace {
|
||
+
|
||
+class ScopedRestoreDefaultTimezone {
|
||
+ STACK_ALLOCATED();
|
||
+
|
||
+ public:
|
||
+ explicit ScopedRestoreDefaultTimezone(const char* zoneid) {
|
||
+ original_zone_.reset(icu::TimeZone::createDefault());
|
||
+ icu::TimeZone::adoptDefault(icu::TimeZone::createTimeZone(zoneid));
|
||
+ }
|
||
+ ~ScopedRestoreDefaultTimezone() {
|
||
+ icu::TimeZone::adoptDefault(original_zone_.release());
|
||
+ }
|
||
+
|
||
+ ScopedRestoreDefaultTimezone(const ScopedRestoreDefaultTimezone&) = delete;
|
||
+ ScopedRestoreDefaultTimezone& operator=(const ScopedRestoreDefaultTimezone&) =
|
||
+ delete;
|
||
+
|
||
+ private:
|
||
+ std::unique_ptr<icu::TimeZone> original_zone_;
|
||
+};
|
||
+
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+TEST(FtpDirectoryListingTest, Top) {
|
||
+ base::test::ScopedRestoreICUDefaultLocale locale("en_US");
|
||
+ ScopedRestoreDefaultTimezone timezone("Asia/Tokyo");
|
||
+
|
||
+ const KURL url("ftp://ftp.example.com/");
|
||
+
|
||
+ const std::string input = "drwxr-xr-x 1 ftp ftp 17 Feb 15 2016 top\r\n";
|
||
+ // Referring to code in net/base/dir_header.html, but the code itself
|
||
+ // is not included in the expectation due to unittest configuration.
|
||
+ std::string expected = R"JS(<script>start("/");</script>
|
||
+<script>addRow("top","top",1,0,"0 B",1455494400,"2/15/16, 9:00:00 AM");</script>
|
||
+)JS";
|
||
+ auto input_buffer = SharedBuffer::Create();
|
||
+ input_buffer->Append(input.data(), input.size());
|
||
+
|
||
+ auto output = GenerateFtpDirectoryListingHtml(url, input_buffer.get());
|
||
+ std::string flatten_output;
|
||
+ for (const auto span : *output) {
|
||
+ flatten_output.append(span.data(), span.size());
|
||
+ }
|
||
+
|
||
+ EXPECT_EQ(expected, flatten_output);
|
||
+}
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
+#if !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+TEST(FtpDirectoryListingTest, NonTop) {
|
||
+ base::test::ScopedRestoreICUDefaultLocale locale("en_US");
|
||
+ ScopedRestoreDefaultTimezone timezone("Asia/Tokyo");
|
||
+ const KURL url("ftp://ftp.example.com/foo/");
|
||
+
|
||
+ const std::string input = "drwxr-xr-x 1 ftp ftp 17 Feb 15 2016 dir\r\n";
|
||
+ // Referring to code in net/base/dir_header.html, but the code itself
|
||
+ // is not included in the expectation due to unittest configuration.
|
||
+ std::string expected = R"JS(<script>start("/foo/");</script>
|
||
+<script>onHasParentDirectory();</script>
|
||
+<script>addRow("dir","dir",1,0,"0 B",1455494400,"2/15/16, 9:00:00 AM");</script>
|
||
+)JS";
|
||
+
|
||
+ auto input_buffer = SharedBuffer::Create();
|
||
+ input_buffer->Append(input.data(), input.size());
|
||
+
|
||
+ auto output = GenerateFtpDirectoryListingHtml(url, input_buffer.get());
|
||
+ std::string flatten_output;
|
||
+ for (const auto span : *output) {
|
||
+ flatten_output.append(span.data(), span.size());
|
||
+ }
|
||
+
|
||
+ EXPECT_EQ(expected, flatten_output);
|
||
+}
|
||
+#endif // !BUILDFLAG(DISABLE_FTP_SUPPORT)
|
||
+
|
||
+TEST(FtpDirectoryListingTest, Fail) {
|
||
+ base::test::ScopedRestoreICUDefaultLocale locale("en_US");
|
||
+ ScopedRestoreDefaultTimezone timezone("Asia/Tokyo");
|
||
+ const KURL url("ftp://ftp.example.com/");
|
||
+ auto input = SharedBuffer::Create();
|
||
+ input->Append("bogus", 5u);
|
||
+ std::string expected = R"JS(<script>start("/");</script>
|
||
+<script>onListingParsingError();</script>
|
||
+)JS";
|
||
+ auto output = GenerateFtpDirectoryListingHtml(url, input.get());
|
||
+ std::string flatten_output;
|
||
+ for (const auto span : *output) {
|
||
+ flatten_output.append(span.data(), span.size());
|
||
+ }
|
||
+
|
||
+ EXPECT_EQ(expected, flatten_output);
|
||
+}
|
||
+
|
||
+} // namespace
|
||
+
|
||
+} // namespace blink
|