diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 21:46:50 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-24 21:46:50 +0000 |
commit | b1c7cc42d026a073d1db2e7fa293be525e936738 (patch) | |
tree | ff24b7ee9676f5b6ab1f49106bddd329ef3da7e2 /net/ftp | |
parent | 63cdfb96a08d384963ee792c4a6687009b0dc10b (diff) | |
download | chromium_src-b1c7cc42d026a073d1db2e7fa293be525e936738.zip chromium_src-b1c7cc42d026a073d1db2e7fa293be525e936738.tar.gz chromium_src-b1c7cc42d026a073d1db2e7fa293be525e936738.tar.bz2 |
Correctly talk to VMS servers (translate UNIX paths to VMS and vice versa).
This way we get a native VMS listing and not a UNIX emulation mode, which is
slightly different than native UNIX and confuses the parser.
TEST=Covered by net_unittests.
BUG=22193
Review URL: http://codereview.chromium.org/215058
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27129 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/ftp')
-rw-r--r-- | net/ftp/ftp_network_transaction.cc | 77 | ||||
-rw-r--r-- | net/ftp/ftp_network_transaction.h | 20 | ||||
-rw-r--r-- | net/ftp/ftp_network_transaction_unittest.cc | 123 | ||||
-rw-r--r-- | net/ftp/ftp_util.cc | 107 | ||||
-rw-r--r-- | net/ftp/ftp_util.h | 26 | ||||
-rw-r--r-- | net/ftp/ftp_util_unittest.cc | 101 |
6 files changed, 442 insertions, 12 deletions
diff --git a/net/ftp/ftp_network_transaction.cc b/net/ftp/ftp_network_transaction.cc index 5a90885..79ad77e 100644 --- a/net/ftp/ftp_network_transaction.cc +++ b/net/ftp/ftp_network_transaction.cc @@ -13,6 +13,7 @@ #include "net/base/net_util.h" #include "net/ftp/ftp_network_session.h" #include "net/ftp/ftp_request_info.h" +#include "net/ftp/ftp_util.h" #include "net/socket/client_socket.h" #include "net/socket/client_socket_factory.h" @@ -60,6 +61,7 @@ FtpNetworkTransaction::FtpNetworkTransaction( file_data_len_(0), write_command_buf_written_(0), last_error_(OK), + system_type_(SYSTEM_TYPE_UNKNOWN), retr_failed_(false), data_connection_port_(0), socket_factory_(socket_factory), @@ -323,13 +325,28 @@ void FtpNetworkTransaction::OnIOComplete(int result) { DoCallback(rv); } -std::string FtpNetworkTransaction::GetRequestPathForFtpCommand() const { - std::string path = (request_->url.has_path() ? request_->url.path() : "/"); +std::string FtpNetworkTransaction::GetRequestPathForFtpCommand( + bool is_directory) const { + std::string path(current_remote_directory_); + if (request_->url.has_path()) + path.append(request_->url.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[path.length() - 1] == '/') + path.erase(path.length() - 1); UnescapeRule::Type unescape_rules = UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS; // This may unescape to non-ASCII characters, but we allow that. See the // comment for IsValidFTPCommandString. path = UnescapeURLComponent(path, unescape_rules); + + if (system_type_ == SYSTEM_TYPE_VMS) { + if (is_directory) + path = FtpUtil::UnixDirectoryPathToVMS(path); + else + path = FtpUtil::UnixFilePathToVMS(path); + } + DCHECK(IsValidFTPCommandString(path)); return path; } @@ -634,10 +651,32 @@ int FtpNetworkTransaction::ProcessResponseSYST( switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_INVALID_RESPONSE); - case ERROR_CLASS_OK: - // TODO(ibrar): Process SYST response properly. + 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 (IsStringASCII(line)) { + line = StringToLowerASCII(line); + // The "magic" strings we test for below have been gathered by an + // empirical study. + 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; + } else if (line.find("vms") != std::string::npos) { + system_type_ = SYSTEM_TYPE_VMS; + } + } next_state_ = STATE_CTRL_WRITE_PWD; break; + } case ERROR_CLASS_INFO_NEEDED: return Stop(ERR_INVALID_RESPONSE); case ERROR_CLASS_TRANSIENT_ERROR: @@ -664,9 +703,27 @@ int FtpNetworkTransaction::ProcessResponsePWD(const FtpCtrlResponse& response) { switch (GetErrorClass(response.status_code)) { case ERROR_CLASS_INITIATED: return Stop(ERR_INVALID_RESPONSE); - case ERROR_CLASS_OK: + 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[line.length() - 1] == '/') + line.erase(line.length() - 1); + 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: @@ -800,7 +857,7 @@ int FtpNetworkTransaction::ProcessResponsePASV( // SIZE command int FtpNetworkTransaction::DoCtrlWriteSIZE() { - std::string command = "SIZE " + GetRequestPathForFtpCommand(); + std::string command = "SIZE " + GetRequestPathForFtpCommand(false); next_state_ = STATE_CTRL_READ; return SendFtpCommand(command, COMMAND_SIZE); } @@ -834,7 +891,7 @@ int FtpNetworkTransaction::ProcessResponseSIZE( // RETR command int FtpNetworkTransaction::DoCtrlWriteRETR() { - std::string command = "RETR " + GetRequestPathForFtpCommand(); + std::string command = "RETR " + GetRequestPathForFtpCommand(false); next_state_ = STATE_CTRL_READ; return SendFtpCommand(command, COMMAND_RETR); } @@ -881,7 +938,7 @@ int FtpNetworkTransaction::ProcessResponseRETR( // MDMT command int FtpNetworkTransaction::DoCtrlWriteMDTM() { - std::string command = "MDTM " + GetRequestPathForFtpCommand(); + std::string command = "MDTM " + GetRequestPathForFtpCommand(false); next_state_ = STATE_CTRL_READ; return SendFtpCommand(command, COMMAND_MDTM); } @@ -911,7 +968,7 @@ int FtpNetworkTransaction::ProcessResponseMDTM( // CWD command int FtpNetworkTransaction::DoCtrlWriteCWD() { - std::string command = "CWD " + GetRequestPathForFtpCommand(); + std::string command = "CWD " + GetRequestPathForFtpCommand(true); next_state_ = STATE_CTRL_READ; return SendFtpCommand(command, COMMAND_CWD); } @@ -945,7 +1002,7 @@ int FtpNetworkTransaction::ProcessResponseCWD(const FtpCtrlResponse& response) { // LIST command int FtpNetworkTransaction::DoCtrlWriteLIST() { - std::string command = "LIST"; + std::string command(system_type_ == SYSTEM_TYPE_VMS ? "LIST *.*;0" : "LIST"); next_state_ = STATE_CTRL_READ; return SendFtpCommand(command, COMMAND_LIST); } diff --git a/net/ftp/ftp_network_transaction.h b/net/ftp/ftp_network_transaction.h index 3366c71..e621a9b 100644 --- a/net/ftp/ftp_network_transaction.h +++ b/net/ftp/ftp_network_transaction.h @@ -85,6 +85,15 @@ class FtpNetworkTransaction : public FtpTransaction { ERROR_CLASS_PERMANENT_ERROR, }; + // 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, + }; + // Resets the members of the transaction so it can be restarted. void ResetStateForRestart(); @@ -101,8 +110,9 @@ class FtpNetworkTransaction : public FtpTransaction { // code to be in range 100-599. static ErrorClass GetErrorClass(int response_code); - // Returns request path suitable to be included in an FTP command. - std::string GetRequestPathForFtpCommand() const; + // 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; // Runs the state transition loop. int DoLoop(int result); @@ -190,11 +200,17 @@ class FtpNetworkTransaction : public FtpTransaction { int last_error_; + SystemType system_type_; + // We get username and password as wstrings in RestartWithAuth, so they are // also kept as wstrings here. std::wstring username_; std::wstring password_; + // Current directory on the remote server, as returned by last PWD command, + // with any trailing slash removed. + std::string current_remote_directory_; + bool retr_failed_; int data_connection_port_; diff --git a/net/ftp/ftp_network_transaction_unittest.cc b/net/ftp/ftp_network_transaction_unittest.cc index 54b0501..ba765a7 100644 --- a/net/ftp/ftp_network_transaction_unittest.cc +++ b/net/ftp/ftp_network_transaction_unittest.cc @@ -186,6 +186,82 @@ class FtpMockControlSocketDirectoryListing : public FtpMockControlSocket { DISALLOW_COPY_AND_ASSIGN(FtpMockControlSocketDirectoryListing); }; +class FtpMockControlSocketVMSDirectoryListing : public FtpMockControlSocket { + public: + FtpMockControlSocketVMSDirectoryListing() { + } + + virtual MockWriteResult OnWrite(const std::string& data) { + if (InjectFault()) + return MockWriteResult(true, 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_SIZE: + return Verify("SIZE ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_MDTM, + "550 I can only retrieve regular files\r\n"); + case PRE_MDTM: + return Verify("MDTM ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_RETR, + "213 20070221112533\r\n"); + case PRE_RETR: + return Verify("RETR ANONYMOUS_ROOT:[000000]dir\r\n", data, PRE_CWD, + "550 Can't download directory\r\n"); + case PRE_CWD: + return Verify("CWD ANONYMOUS_ROOT:[dir]\r\n", data, PRE_LIST, + "200 OK\r\n"); + case PRE_LIST: + return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n"); + default: + return FtpMockControlSocket::OnWrite(data); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(FtpMockControlSocketVMSDirectoryListing); +}; + +class FtpMockControlSocketVMSDirectoryListingRootDirectory + : public FtpMockControlSocket { + public: + FtpMockControlSocketVMSDirectoryListingRootDirectory() { + } + + virtual MockWriteResult OnWrite(const std::string& data) { + if (InjectFault()) + return MockWriteResult(true, 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_SIZE: + return Verify("SIZE ANONYMOUS_ROOT\r\n", data, PRE_MDTM, + "550 I can only retrieve regular files\r\n"); + case PRE_MDTM: + return Verify("MDTM ANONYMOUS_ROOT\r\n", data, PRE_RETR, + "213 20070221112533\r\n"); + case PRE_RETR: + return Verify("RETR ANONYMOUS_ROOT\r\n", data, PRE_CWD, + "550 Can't download directory\r\n"); + case PRE_CWD: + return Verify("CWD ANONYMOUS_ROOT:[000000]\r\n", data, PRE_LIST, + "200 OK\r\n"); + case PRE_LIST: + return Verify("LIST *.*;0\r\n", data, PRE_QUIT, "200 OK\r\n"); + default: + return FtpMockControlSocket::OnWrite(data); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN( + FtpMockControlSocketVMSDirectoryListingRootDirectory); +}; + class FtpMockControlSocketFileDownload : public FtpMockControlSocket { public: FtpMockControlSocketFileDownload() { @@ -212,6 +288,38 @@ class FtpMockControlSocketFileDownload : public FtpMockControlSocket { DISALLOW_COPY_AND_ASSIGN(FtpMockControlSocketFileDownload); }; +class FtpMockControlSocketVMSFileDownload : public FtpMockControlSocket { + public: + FtpMockControlSocketVMSFileDownload() { + } + + virtual MockWriteResult OnWrite(const std::string& data) { + if (InjectFault()) + return MockWriteResult(true, 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_SIZE: + return Verify("SIZE ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_MDTM, + "213 18\r\n"); + case PRE_MDTM: + return Verify("MDTM ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_RETR, + "213 20070221112533\r\n"); + case PRE_RETR: + return Verify("RETR ANONYMOUS_ROOT:[000000]file\r\n", data, PRE_QUIT, + "200 OK\r\n"); + default: + return FtpMockControlSocket::OnWrite(data); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(FtpMockControlSocketVMSFileDownload); +}; + class FtpMockControlSocketEscaping : public FtpMockControlSocket { public: FtpMockControlSocketEscaping() { @@ -491,6 +599,16 @@ TEST_F(FtpNetworkTransactionTest, DirectoryTransactionMultilineWelcomeShort) { ExecuteTransaction(&ctrl_socket, "ftp://host", OK); } +TEST_F(FtpNetworkTransactionTest, DirectoryTransactionVMS) { + FtpMockControlSocketVMSDirectoryListing ctrl_socket; + ExecuteTransaction(&ctrl_socket, "ftp://host/dir", OK); +} + +TEST_F(FtpNetworkTransactionTest, DirectoryTransactionVMSRootDirectory) { + FtpMockControlSocketVMSDirectoryListingRootDirectory ctrl_socket; + ExecuteTransaction(&ctrl_socket, "ftp://host", OK); +} + TEST_F(FtpNetworkTransactionTest, DownloadTransaction) { FtpMockControlSocketFileDownload ctrl_socket; ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK); @@ -514,6 +632,11 @@ TEST_F(FtpNetworkTransactionTest, DownloadTransactionShortReads5) { ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK); } +TEST_F(FtpNetworkTransactionTest, DownloadTransactionVMS) { + FtpMockControlSocketVMSFileDownload ctrl_socket; + ExecuteTransaction(&ctrl_socket, "ftp://host/file", OK); +} + TEST_F(FtpNetworkTransactionTest, DownloadTransactionAcceptedDataConnection) { FtpMockControlSocketFileDownloadAcceptedDataConnection ctrl_socket; std::string mock_data("mock-data"); diff --git a/net/ftp/ftp_util.cc b/net/ftp/ftp_util.cc new file mode 100644 index 0000000..591f7b2 --- /dev/null +++ b/net/ftp/ftp_util.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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 <vector> + +#include "base/logging.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" + +// 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(); + + StringTokenizer tokenizer(unix_path, "/"); + std::vector<std::string> tokens; + while (tokenizer.GetNext()) + tokens.push_back(tokenizer.token()); + + if (unix_path[0] == '/') { + // It's an absolute path. + + if (tokens.empty()) { + DCHECK_EQ(1U, unix_path.length()); + return "[]"; + } + + if (tokens.size() == 1) + return unix_path.substr(1); // Drop the leading slash. + + std::string result(tokens[0] + ":["); + if (tokens.size() == 2) { + // Don't ask why, it just works that way on VMS. + result.append("000000"); + } else { + result.append(tokens[1]); + for (size_t i = 2; i < tokens.size() - 1; i++) + result.append("." + tokens[i]); + } + result.append("]" + tokens[tokens.size() - 1]); + return result; + } + + if (tokens.size() == 1) + return unix_path; + + std::string result("["); + for (size_t i = 0; i < tokens.size() - 1; i++) + result.append("." + tokens[i]); + result.append("]" + tokens[tokens.size() - 1]); + 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[path.length() - 1] != '/') + 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 == "[]") + return "/"; + + std::string result(vms_path); + if (vms_path[0] == '[') { + // It's a relative path. + ReplaceFirstSubstringAfterOffset(&result, 0, "[.", ""); + } else { + // It's an absolute path. + result.insert(0, "/"); + ReplaceSubstringsAfterOffset(&result, 0, ":[000000]", "/"); + 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[result.length() - 1] == '/') + result = result.substr(0, result.length() - 1); + + return result; +} + +} // namespace diff --git a/net/ftp/ftp_util.h b/net/ftp/ftp_util.h new file mode 100644 index 0000000..c71b919 --- /dev/null +++ b/net/ftp/ftp_util.h @@ -0,0 +1,26 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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> + +namespace net { + +class FtpUtil { + public: + // Convert Unix file path to VMS path (must be a file, and not a directory). + static std::string UnixFilePathToVMS(const std::string& unix_path); + + // Convert Unix directory path to VMS path (must be a directory). + static std::string UnixDirectoryPathToVMS(const std::string& unix_path); + + // Convert VMS path to Unix-style path. + static std::string VMSPathToUnix(const std::string& vms_path); +}; + +} // 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 0000000..2ccb75b --- /dev/null +++ b/net/ftp/ftp_util_unittest.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// 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/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" + +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" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) { + EXPECT_EQ(kTestCases[i].expected_output, + net::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]" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) { + EXPECT_EQ(kTestCases[i].expected_output, + net::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" }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); i++) { + EXPECT_EQ(kTestCases[i].expected_output, + net::FtpUtil::VMSPathToUnix(kTestCases[i].input)) + << kTestCases[i].input; + } +} + +} // namespace |