diff options
Diffstat (limited to 'net/test')
-rw-r--r-- | net/test/test_server.cc | 446 | ||||
-rw-r--r-- | net/test/test_server.h | 349 | ||||
-rw-r--r-- | net/test/test_server_posix.cc | 64 | ||||
-rw-r--r-- | net/test/test_server_win.cc | 165 |
4 files changed, 451 insertions, 573 deletions
diff --git a/net/test/test_server.cc b/net/test/test_server.cc index 7a2df0b..e133345 100644 --- a/net/test/test_server.cc +++ b/net/test/test_server.cc @@ -10,10 +10,7 @@ #include "build/build_config.h" -#if defined(OS_WIN) -#include <windows.h> -#include <wincrypt.h> -#elif defined(OS_MACOSX) +#if defined(OS_MACOSX) #include "net/base/x509_certificate.h" #endif @@ -23,17 +20,15 @@ #include "base/path_service.h" #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" +#include "googleurl/src/gurl.h" #include "net/base/cert_test_util.h" +#include "net/base/host_port_pair.h" #include "net/base/host_resolver.h" #include "net/base/test_completion_callback.h" #include "net/socket/tcp_client_socket.h" #include "net/socket/tcp_pinger.h" #include "testing/platform_test.h" -#if defined(OS_WIN) -#pragma comment(lib, "crypt32.lib") -#endif - namespace { // Number of connection attempts for tests. @@ -42,6 +37,33 @@ const int kServerConnectionAttempts = 10; // Connection timeout in milliseconds for tests. const int kServerConnectionTimeoutMs = 1000; +int GetPort(net::TestServer::Type type) { + switch (type) { + case net::TestServer::TYPE_FTP: + return 1338; + case net::TestServer::TYPE_HTTP: + return 1337; + case net::TestServer::TYPE_HTTPS: + case net::TestServer::TYPE_HTTPS_CLIENT_AUTH: + case net::TestServer::TYPE_HTTPS_EXPIRED_CERTIFICATE: + return 9443; + case net::TestServer::TYPE_HTTPS_MISMATCHED_HOSTNAME: + return 9666; + default: + NOTREACHED(); + } + return -1; +} + +std::string GetHostname(net::TestServer::Type type) { + if (type == net::TestServer::TYPE_HTTPS_MISMATCHED_HOSTNAME) { + // Return a different hostname string that resolves to the same hostname. + return "localhost"; + } + + return "127.0.0.1"; +} + } // namespace namespace net { @@ -50,258 +72,192 @@ namespace net { void SetMacTestCertificate(X509Certificate* cert); #endif -// static -const char TestServerLauncher::kHostName[] = "127.0.0.1"; -const char TestServerLauncher::kMismatchedHostName[] = "localhost"; -const int TestServerLauncher::kOKHTTPSPort = 9443; -const int TestServerLauncher::kBadHTTPSPort = 9666; +TestServer::TestServer(Type type, const FilePath& document_root) + : host_port_pair_(GetHostname(type), GetPort(type)), + process_handle_(base::kNullProcessHandle), + type_(type) { + FilePath src_dir; + PathService::Get(base::DIR_SOURCE_ROOT, &src_dir); -// The issuer name of the cert that should be trusted for the test to work. -const wchar_t TestServerLauncher::kCertIssuerName[] = L"Test CA"; - -TestServerLauncher::TestServerLauncher() - : process_handle_(base::kNullProcessHandle), - ssl_client_auth_(false) { - InitCertPath(); -} + document_root_ = src_dir.Append(document_root); -void TestServerLauncher::InitCertPath() { - PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_); - cert_dir_ = cert_dir_.Append(FILE_PATH_LITERAL("net")) + certificates_dir_ = src_dir.Append(FILE_PATH_LITERAL("net")) .Append(FILE_PATH_LITERAL("data")) .Append(FILE_PATH_LITERAL("ssl")) .Append(FILE_PATH_LITERAL("certificates")); } -namespace { - -void AppendToPythonPath(const FilePath& dir) { - // Do nothing if dir already on path. - -#if defined(OS_WIN) - const wchar_t kPythonPath[] = L"PYTHONPATH"; - // TODO(dkegel): handle longer PYTHONPATH variables - wchar_t oldpath[4096]; - if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) { - SetEnvironmentVariableW(kPythonPath, dir.value().c_str()); - } else if (!wcsstr(oldpath, dir.value().c_str())) { - std::wstring newpath(oldpath); - newpath.append(L";"); - newpath.append(dir.value()); - SetEnvironmentVariableW(kPythonPath, newpath.c_str()); - } -#elif defined(OS_POSIX) - const char kPythonPath[] = "PYTHONPATH"; - const char* oldpath = getenv(kPythonPath); - // setenv() leaks memory intentionally on Mac - if (!oldpath) { - setenv(kPythonPath, dir.value().c_str(), 1); - } else if (!strstr(oldpath, dir.value().c_str())) { - std::string newpath(oldpath); - newpath.append(":"); - newpath.append(dir.value()); - setenv(kPythonPath, newpath.c_str(), 1); - } +TestServer::~TestServer() { +#if defined(OS_MACOSX) + SetMacTestCertificate(NULL); #endif + Stop(); } -} // end namespace - -void TestServerLauncher::SetPythonPath() { - FilePath third_party_dir; - CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)); - third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party")); - - AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite"))); - AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib"))); - - // Locate the Python code generated by the protocol buffers compiler. - FilePath generated_code_dir; - CHECK(PathService::Get(base::DIR_EXE, &generated_code_dir)); - generated_code_dir = generated_code_dir.Append(FILE_PATH_LITERAL("pyproto")); - AppendToPythonPath(generated_code_dir); - AppendToPythonPath(generated_code_dir.Append(FILE_PATH_LITERAL("sync_pb"))); -} - -bool TestServerLauncher::Start(Protocol protocol, - const std::string& host_name, int port, - const FilePath& document_root, - const FilePath& cert_path, - const std::wstring& file_root_url) { - if (!cert_path.value().empty()) { +bool TestServer::Start() { + if (GetScheme() == "https") { if (!LoadTestRootCert()) return false; if (!CheckCATrusted()) return false; } - std::string port_str = base::IntToString(port); - // Get path to python server script FilePath testserver_path; - if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) + if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) { + LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; return false; + } testserver_path = testserver_path .Append(FILE_PATH_LITERAL("net")) .Append(FILE_PATH_LITERAL("tools")) .Append(FILE_PATH_LITERAL("testserver")) .Append(FILE_PATH_LITERAL("testserver.py")); - PathService::Get(base::DIR_SOURCE_ROOT, &document_root_dir_); - document_root_dir_ = document_root_dir_.Append(document_root); - - SetPythonPath(); - -#if defined(OS_WIN) - // Get path to python interpreter - FilePath python_exe; - if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_exe)) + if (!SetPythonPath()) return false; - python_exe = python_exe - .Append(FILE_PATH_LITERAL("third_party")) - .Append(FILE_PATH_LITERAL("python_24")) - .Append(FILE_PATH_LITERAL("python.exe")); - - std::wstring command_line = - L"\"" + python_exe.ToWStringHack() + L"\" " + - L"\"" + testserver_path.ToWStringHack() + - L"\" --port=" + UTF8ToWide(port_str) + - L" --data-dir=\"" + document_root_dir_.ToWStringHack() + L"\""; - if (protocol == ProtoFTP) - command_line.append(L" -f"); - if (!cert_path.value().empty()) { - command_line.append(L" --https=\""); - command_line.append(cert_path.ToWStringHack()); - command_line.append(L"\""); - } - if (!file_root_url.empty()) { - command_line.append(L" --file-root-url=\""); - command_line.append(file_root_url); - command_line.append(L"\""); - } - if (ssl_client_auth_) - command_line.append(L" --ssl-client-auth"); - - if (!LaunchTestServerAsJob(command_line, - true, - &process_handle_, - &job_handle_)) { - LOG(ERROR) << "Failed to launch " << command_line; - return false; - } -#elif defined(OS_POSIX) - std::vector<std::string> command_line; - command_line.push_back("python"); - command_line.push_back(testserver_path.value()); - command_line.push_back("--port=" + port_str); - command_line.push_back("--data-dir=" + document_root_dir_.value()); - if (protocol == ProtoFTP) - command_line.push_back("-f"); - if (!cert_path.value().empty()) - command_line.push_back("--https=" + cert_path.value()); - if (ssl_client_auth_) - command_line.push_back("--ssl-client-auth"); - - base::file_handle_mapping_vector no_mappings; - LOG(INFO) << "Trying to launch " << command_line[0] << " ..."; - if (!base::LaunchApp(command_line, no_mappings, false, &process_handle_)) { - LOG(ERROR) << "Failed to launch " << command_line[0] << " ..."; + + if (!LaunchPython(testserver_path)) return false; - } -#endif - // Let the server start, then verify that it's up. - // Our server is Python, and takes about 500ms to start - // up the first time, and about 200ms after that. - if (!WaitToStart(host_name, port)) { - LOG(ERROR) << "Failed to connect to server"; + if (!WaitToStart()) { Stop(); return false; } - LOG(INFO) << "Started on port " << port_str; return true; } -bool TestServerLauncher::WaitToStart(const std::string& host_name, int port) { - // Verify that the webserver is actually started. - // Otherwise tests can fail if they run faster than Python can start. - net::AddressList addr; - scoped_refptr<net::HostResolver> resolver( - net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism)); - net::HostResolver::RequestInfo info(host_name, port); - int rv = resolver->Resolve(info, &addr, NULL, NULL, BoundNetLog()); - if (rv != net::OK) - return false; - - net::TCPPinger pinger(addr); - rv = pinger.Ping( - base::TimeDelta::FromMilliseconds(kServerConnectionTimeoutMs), - kServerConnectionAttempts); - return rv == net::OK; -} - -bool TestServerLauncher::WaitToFinish(int timeout_ms) { +bool TestServer::Stop() { if (!process_handle_) return true; - bool ret = base::WaitForSingleProcess(process_handle_, timeout_ms); + // First check if the process has already terminated. + bool ret = base::WaitForSingleProcess(process_handle_, 0); + if (!ret) + ret = base::KillProcess(process_handle_, 1, true); + if (ret) { base::CloseProcessHandle(process_handle_); process_handle_ = base::kNullProcessHandle; - LOG(INFO) << "Finished."; } else { - LOG(INFO) << "Timed out."; + LOG(INFO) << "Kill failed?"; } + return ret; } -bool TestServerLauncher::Stop() { +bool TestServer::WaitToFinish(int timeout_ms) { if (!process_handle_) return true; - // First check if the process has already terminated. - bool ret = base::WaitForSingleProcess(process_handle_, 0); - if (!ret) - ret = base::KillProcess(process_handle_, 1, true); - + bool ret = base::WaitForSingleProcess(process_handle_, timeout_ms); if (ret) { base::CloseProcessHandle(process_handle_); process_handle_ = base::kNullProcessHandle; - LOG(INFO) << "Stopped."; } else { - LOG(INFO) << "Kill failed?"; + LOG(ERROR) << "Timed out."; } - return ret; } -TestServerLauncher::~TestServerLauncher() { -#if defined(OS_MACOSX) - SetMacTestCertificate(NULL); -#endif - Stop(); +std::string TestServer::GetScheme() const { + switch (type_) { + case TYPE_FTP: + return "ftp"; + case TYPE_HTTP: + return "http"; + case TYPE_HTTPS: + case TYPE_HTTPS_CLIENT_AUTH: + case TYPE_HTTPS_MISMATCHED_HOSTNAME: + case TYPE_HTTPS_EXPIRED_CERTIFICATE: + return "https"; + default: + NOTREACHED(); + } + return std::string(); +} + +bool TestServer::GetAddressList(AddressList* address_list) const { + DCHECK(address_list); + + scoped_refptr<HostResolver> resolver( + CreateSystemHostResolver(HostResolver::kDefaultParallelism)); + HostResolver::RequestInfo info(host_port_pair_.host(), + host_port_pair_.port()); + int rv = resolver->Resolve(info, address_list, NULL, NULL, BoundNetLog()); + if (rv != net::OK) { + LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host(); + return false; + } + return true; +} + +GURL TestServer::GetURL(const std::string& path) { + return GURL(GetScheme() + "://" + host_port_pair_.ToString() + + "/" + path); } -FilePath TestServerLauncher::GetRootCertPath() { - FilePath path(cert_dir_); - path = path.AppendASCII("root_ca_cert.crt"); - return path; +GURL TestServer::GetURLWithUser(const std::string& path, + const std::string& user) { + return GURL(GetScheme() + "://" + user + "@" + + host_port_pair_.ToString() + + "/" + path); +} + +GURL TestServer::GetURLWithUserAndPassword(const std::string& path, + const std::string& user, + const std::string& password) { + return GURL(GetScheme() + "://" + user + ":" + password + + "@" + host_port_pair_.ToString() + + "/" + path); +} + +bool TestServer::SetPythonPath() { + FilePath third_party_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) { + LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; + return false; + } + third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party")); + + AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite"))); + AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib"))); + + // Locate the Python code generated by the protocol buffers compiler. + FilePath generated_code_dir; + if (!PathService::Get(base::DIR_EXE, &generated_code_dir)) { + LOG(ERROR) << "Failed to get DIR_EXE"; + return false; + } + generated_code_dir = generated_code_dir.Append(FILE_PATH_LITERAL("pyproto")); + AppendToPythonPath(generated_code_dir); + AppendToPythonPath(generated_code_dir.Append(FILE_PATH_LITERAL("sync_pb"))); + + return true; } -FilePath TestServerLauncher::GetOKCertPath() { - FilePath path(cert_dir_); - path = path.AppendASCII("ok_cert.pem"); - return path; +bool TestServer::WaitToStart() { + net::AddressList addr; + if (!GetAddressList(&addr)) + return false; + + net::TCPPinger pinger(addr); + int rv = pinger.Ping( + base::TimeDelta::FromMilliseconds(kServerConnectionTimeoutMs), + kServerConnectionAttempts); + bool result = (rv == net::OK); + if (!result) { + LOG(ERROR) << "Failed to connect to server"; + } + return result; } -FilePath TestServerLauncher::GetExpiredCertPath() { - FilePath path(cert_dir_); - path = path.AppendASCII("expired_cert.pem"); - return path; +FilePath TestServer::GetRootCertificatePath() { + return certificates_dir_.AppendASCII("root_ca_cert.crt"); } -bool TestServerLauncher::LoadTestRootCert() { +bool TestServer::LoadTestRootCert() { #if defined(USE_NSS) if (cert_) return true; @@ -312,11 +268,10 @@ bool TestServerLauncher::LoadTestRootCert() { // TODO(dkegel): fix the leak and remove the entry in // tools/valgrind/memcheck/suppressions.txt ANNOTATE_SCOPED_MEMORY_LEAK; // Tell heap checker about the leak. - cert_ = LoadTemporaryRootCert(GetRootCertPath()); - DCHECK(cert_); + cert_ = LoadTemporaryRootCert(GetRootCertificatePath()); return (cert_ != NULL); #elif defined(OS_MACOSX) - X509Certificate* cert = LoadTemporaryRootCert(GetRootCertPath()); + X509Certificate* cert = LoadTemporaryRootCert(GetRootCertificatePath()); if (!cert) return false; SetMacTestCertificate(cert); @@ -326,88 +281,21 @@ bool TestServerLauncher::LoadTestRootCert() { #endif } -bool TestServerLauncher::CheckCATrusted() { -#if defined(OS_WIN) - HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT"); - if (!cert_store) { - LOG(ERROR) << " could not open trusted root CA store"; - return false; - } - PCCERT_CONTEXT cert = - CertFindCertificateInStore(cert_store, - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - 0, - CERT_FIND_ISSUER_STR, - kCertIssuerName, - NULL); - if (cert) - CertFreeCertificateContext(cert); - CertCloseStore(cert_store, 0); - - if (!cert) { - LOG(ERROR) << " TEST CONFIGURATION ERROR: you need to import the test ca " - "certificate to your trusted roots for this test to work. " - "For more info visit:\n" - "http://dev.chromium.org/developers/testing\n"; - return false; +FilePath TestServer::GetCertificatePath() { + switch (type_) { + case TYPE_FTP: + case TYPE_HTTP: + return FilePath(); + case TYPE_HTTPS: + case TYPE_HTTPS_CLIENT_AUTH: + case TYPE_HTTPS_MISMATCHED_HOSTNAME: + return certificates_dir_.AppendASCII("ok_cert.pem"); + case TYPE_HTTPS_EXPIRED_CERTIFICATE: + return certificates_dir_.AppendASCII("expired_cert.pem"); + default: + NOTREACHED(); } -#endif - return true; -} - -#if defined(OS_WIN) -bool LaunchTestServerAsJob(const std::wstring& cmdline, - bool start_hidden, - base::ProcessHandle* process_handle, - ScopedHandle* job_handle) { - // Launch test server process. - STARTUPINFO startup_info = {0}; - startup_info.cb = sizeof(startup_info); - startup_info.dwFlags = STARTF_USESHOWWINDOW; - startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW; - PROCESS_INFORMATION process_info; - - // If this code is run under a debugger, the test server process is - // automatically associated with a job object created by the debugger. - // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. - if (!CreateProcess(NULL, - const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, - FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, - &startup_info, &process_info)) { - LOG(ERROR) << "Could not create process."; - return false; - } - CloseHandle(process_info.hThread); - - // If the caller wants the process handle, we won't close it. - if (process_handle) { - *process_handle = process_info.hProcess; - } else { - CloseHandle(process_info.hProcess); - } - - // Create a JobObject and associate the test server process with it. - job_handle->Set(CreateJobObject(NULL, NULL)); - if (!job_handle->IsValid()) { - LOG(ERROR) << "Could not create JobObject."; - return false; - } else { - JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; - limit_info.BasicLimitInformation.LimitFlags = - JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; - if (0 == SetInformationJobObject(job_handle->Get(), - JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info))) { - LOG(ERROR) << "Could not SetInformationJobObject."; - return false; - } - if (0 == AssignProcessToJobObject(job_handle->Get(), - process_info.hProcess)) { - LOG(ERROR) << "Could not AssignProcessToObject."; - return false; - } - } - return true; + return FilePath(); } -#endif } // namespace net diff --git a/net/test/test_server.h b/net/test/test_server.h index 989e38d..5f51571 100644 --- a/net/test/test_server.h +++ b/net/test/test_server.h @@ -13,52 +13,40 @@ #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/process_util.h" -#include "base/ref_counted.h" -#include "base/string_number_conversions.h" -//#include "base/string_util.h" -#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" #if defined(OS_WIN) #include "base/scoped_handle_win.h" #endif #if defined(USE_NSS) +#include "base/ref_counted.h" #include "net/base/x509_certificate.h" #endif -namespace net { +class GURL; -const int kHTTPDefaultPort = 1337; -const int kFTPDefaultPort = 1338; +namespace net { -const char kDefaultHostName[] = "localhost"; +class AddressList; -// This object bounds the lifetime of an external python-based HTTP/HTTPS/FTP -// server that can provide various responses useful for testing. -class TestServerLauncher { +// This object bounds the lifetime of an external python-based HTTP/FTP server +// that can provide various responses useful for testing. +class TestServer { public: - TestServerLauncher(); - virtual ~TestServerLauncher(); - - enum Protocol { - ProtoHTTP, ProtoFTP + enum Type { + TYPE_FTP, + TYPE_HTTP, + TYPE_HTTPS, + TYPE_HTTPS_CLIENT_AUTH, + TYPE_HTTPS_MISMATCHED_HOSTNAME, + TYPE_HTTPS_EXPIRED_CERTIFICATE, }; - // Load the test root cert, if it hasn't been loaded yet. - bool LoadTestRootCert() WARN_UNUSED_RESULT; + TestServer(Type type, const FilePath& document_root); + ~TestServer(); - // Start src/net/tools/testserver/testserver.py and - // ask it to serve the given protocol. - // If protocol is HTTP, and cert_path is not empty, serves HTTPS. - // file_root_url specifies the root url on the server that documents will be - // served out of. This is /files/ by default. - // Returns true on success, false if files not found or root cert - // not trusted. - bool Start(net::TestServerLauncher::Protocol protocol, - const std::string& host_name, int port, - const FilePath& document_root, - const FilePath& cert_path, - const std::wstring& file_root_url) WARN_UNUSED_RESULT; + bool Start() WARN_UNUSED_RESULT; // Stop the server started by Start(). bool Stop(); @@ -69,56 +57,56 @@ class TestServerLauncher { // It returns true if the server exited cleanly. bool WaitToFinish(int milliseconds) WARN_UNUSED_RESULT; - // Paths to a good, an expired, and an invalid server certificate - // (use as arguments to Start()). - FilePath GetOKCertPath(); - FilePath GetExpiredCertPath(); - - FilePath GetDocumentRootPath() { return document_root_dir_; } - - // When Start is called, if protocol is HTTPS and ssl_client_auth_ is true, - // the server will request a client certificate on each connection. Must be - // called before Start to take effect. - void set_ssl_client_auth(bool ssl_client_auth) { - ssl_client_auth_ = ssl_client_auth; - } + const FilePath& document_root() const { return document_root_; } + const HostPortPair& host_port_pair() const { return host_port_pair_; } + std::string GetScheme() const; + bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT; - // Issuer name of the root cert that should be trusted for the test to work. - static const wchar_t kCertIssuerName[]; + GURL GetURL(const std::string& path); - // Hostname to use for test server - static const char kHostName[]; + GURL GetURLWithUser(const std::string& path, + const std::string& user); - // Different hostname to use for test server (that still resolves to same IP) - static const char kMismatchedHostName[]; + GURL GetURLWithUserAndPassword(const std::string& path, + const std::string& user, + const std::string& password); - // Port to use for test server - static const int kOKHTTPSPort; + private: + // Appends |dir| to PYTHONPATH. + static void AppendToPythonPath(const FilePath& dir); - // Port to use for bad test server - static const int kBadHTTPSPort; + // Modify PYTHONPATH to contain libraries we need. + bool SetPythonPath() WARN_UNUSED_RESULT; - private: - // Wait a while for the server to start, return whether - // we were able to make a connection to it. - bool WaitToStart(const std::string& host_name, int port) WARN_UNUSED_RESULT; + // Launches the Python test server. Returns true on success. + bool LaunchPython(const FilePath& testserver_path) WARN_UNUSED_RESULT; - // Append to PYTHONPATH so Python can find pyftpdlib and tlslite. - void SetPythonPath(); + // Waits for the server to start. Returns true on success. + bool WaitToStart() WARN_UNUSED_RESULT; - // Path to our test root certificate. - FilePath GetRootCertPath(); + // Returns path to the root certificate. + FilePath GetRootCertificatePath(); // Returns false if our test root certificate is not trusted. bool CheckCATrusted() WARN_UNUSED_RESULT; - // Initilize the certificate path. - void InitCertPath(); + // Load the test root cert, if it hasn't been loaded yet. + bool LoadTestRootCert() WARN_UNUSED_RESULT; + + // Returns path to the SSL certificate we should use, or empty path + // if not applicable. + FilePath GetCertificatePath(); - FilePath document_root_dir_; + // Document root of the test server. + FilePath document_root_; - FilePath cert_dir_; + // Directory that contains the SSL certificates. + FilePath certificates_dir_; + // Address the test server listens on. + HostPortPair host_port_pair_; + + // Handle of the Python process running the test server. base::ProcessHandle process_handle_; #if defined(OS_WIN) @@ -130,238 +118,11 @@ class TestServerLauncher { scoped_refptr<X509Certificate> cert_; #endif - bool ssl_client_auth_; - - DISALLOW_COPY_AND_ASSIGN(TestServerLauncher); -}; - -#if defined(OS_WIN) -// Launch test server as a job so that it is not orphaned if the test case is -// abnormally terminated. -bool LaunchTestServerAsJob(const std::wstring& cmdline, - bool start_hidden, - base::ProcessHandle* process_handle, - ScopedHandle* job_handle); -#endif - -// This object bounds the lifetime of an external python-based HTTP/FTP server -// that can provide various responses useful for testing. -class BaseTestServer : public base::RefCounted<BaseTestServer> { - protected: - BaseTestServer() {} + Type type_; - public: - bool WaitToFinish(int milliseconds) { - return launcher_.WaitToFinish(milliseconds); - } - - bool Stop() { - return launcher_.Stop(); - } - - GURL TestServerPage(const std::string& base_address, - const std::string& path) { - return GURL(base_address + path); - } - - GURL TestServerPage(const std::string& path) { - // TODO(phajdan.jr): Check for problems with IPv6. - return GURL(scheme_ + "://" + host_name_ + ":" + port_str_ + "/" + path); - } - - GURL TestServerPage(const std::string& path, - const std::string& user, - const std::string& password) { - // TODO(phajdan.jr): Check for problems with IPv6. - - if (password.empty()) - return GURL(scheme_ + "://" + user + "@" + - host_name_ + ":" + port_str_ + "/" + path); - - return GURL(scheme_ + "://" + user + ":" + password + - "@" + host_name_ + ":" + port_str_ + "/" + path); - } - - FilePath GetDataDirectory() { - return launcher_.GetDocumentRootPath(); - } - - protected: - friend class base::RefCounted<BaseTestServer>; - virtual ~BaseTestServer() { } - - bool Start(net::TestServerLauncher::Protocol protocol, - const std::string& host_name, int port, - const FilePath& document_root, - const FilePath& cert_path, - const std::wstring& file_root_url) { - if (!launcher_.Start(protocol, - host_name, port, document_root, cert_path, file_root_url)) - return false; - - if (protocol == net::TestServerLauncher::ProtoFTP) - scheme_ = "ftp"; - else - scheme_ = "http"; - if (!cert_path.empty()) - scheme_.push_back('s'); - - host_name_ = host_name; - port_str_ = base::IntToString(port); - return true; - } - - net::TestServerLauncher launcher_; - std::string scheme_; - std::string host_name_; - std::string port_str_; + DISALLOW_COPY_AND_ASSIGN(TestServer); }; -class HTTPTestServer : public BaseTestServer { - protected: - HTTPTestServer() {} - - public: - // Creates and returns a new HTTPTestServer. - static scoped_refptr<HTTPTestServer> CreateServer( - const std::wstring& document_root) { - return CreateServerWithFileRootURL(document_root, std::wstring()); - } - - static scoped_refptr<HTTPTestServer> CreateServerWithFileRootURL( - const std::wstring& document_root, - const std::wstring& file_root_url) { - scoped_refptr<HTTPTestServer> test_server(new HTTPTestServer()); - FilePath no_cert; - FilePath docroot = FilePath::FromWStringHack(document_root); - if (!StartTestServer(test_server.get(), docroot, no_cert, file_root_url)) - return NULL; - return test_server; - } - - static bool StartTestServer(HTTPTestServer* server, - const FilePath& document_root, - const FilePath& cert_path, - const std::wstring& file_root_url) { - return server->Start(net::TestServerLauncher::ProtoHTTP, kDefaultHostName, - kHTTPDefaultPort, document_root, cert_path, - file_root_url); - } -}; - -class HTTPSTestServer : public HTTPTestServer { - protected: - HTTPSTestServer() {} - - public: - // Create a server with a valid certificate - // TODO(dkegel): HTTPSTestServer should not require an instance to specify - // stock test certificates - static scoped_refptr<HTTPSTestServer> CreateGoodServer( - const std::wstring& document_root) { - scoped_refptr<HTTPSTestServer> test_server = new HTTPSTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath certpath = test_server->launcher_.GetOKCertPath(); - if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, - net::TestServerLauncher::kHostName, - net::TestServerLauncher::kOKHTTPSPort, - docroot, certpath, std::wstring())) { - return NULL; - } - return test_server; - } - - // Create a server which requests SSL client auth - static scoped_refptr<HTTPSTestServer> CreateClientAuthServer( - const std::wstring& document_root) { - scoped_refptr<HTTPSTestServer> test_server = new HTTPSTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath certpath = test_server->launcher_.GetOKCertPath(); - test_server->launcher_.set_ssl_client_auth(true); - if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, - net::TestServerLauncher::kHostName, - net::TestServerLauncher::kOKHTTPSPort, - docroot, certpath, std::wstring())) { - return NULL; - } - return test_server; - } - - // Create a server with an up to date certificate for the wrong hostname - // for this host - static scoped_refptr<HTTPSTestServer> CreateMismatchedServer( - const std::wstring& document_root) { - scoped_refptr<HTTPSTestServer> test_server = new HTTPSTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath certpath = test_server->launcher_.GetOKCertPath(); - if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, - net::TestServerLauncher::kMismatchedHostName, - net::TestServerLauncher::kOKHTTPSPort, - docroot, certpath, std::wstring())) { - return NULL; - } - return test_server; - } - - // Create a server with an expired certificate - static scoped_refptr<HTTPSTestServer> CreateExpiredServer( - const std::wstring& document_root) { - scoped_refptr<HTTPSTestServer> test_server = new HTTPSTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath certpath = test_server->launcher_.GetExpiredCertPath(); - if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, - net::TestServerLauncher::kHostName, - net::TestServerLauncher::kBadHTTPSPort, - docroot, certpath, std::wstring())) { - return NULL; - } - return test_server; - } - - // Create a server with an arbitrary certificate - static scoped_refptr<HTTPSTestServer> CreateServer( - const std::string& host_name, int port, - const std::wstring& document_root, - const std::wstring& cert_path) { - scoped_refptr<HTTPSTestServer> test_server = new HTTPSTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath certpath = FilePath::FromWStringHack(cert_path); - if (!test_server->Start(net::TestServerLauncher::ProtoHTTP, - host_name, port, docroot, certpath, std::wstring())) { - return NULL; - } - return test_server; - } - - protected: - std::wstring cert_path_; - - private: - virtual ~HTTPSTestServer() {} -}; - -class FTPTestServer : public BaseTestServer { - public: - FTPTestServer() { - } - - static scoped_refptr<FTPTestServer> CreateServer( - const std::wstring& document_root) { - scoped_refptr<FTPTestServer> test_server = new FTPTestServer(); - FilePath docroot = FilePath::FromWStringHack(document_root); - FilePath no_cert; - if (!test_server->Start(net::TestServerLauncher::ProtoFTP, - kDefaultHostName, kFTPDefaultPort, docroot, no_cert, std::wstring())) { - return NULL; - } - return test_server; - } - - private: - ~FTPTestServer() {} -}; - - } // namespace net #endif // NET_TEST_TEST_SERVER_H_ diff --git a/net/test/test_server_posix.cc b/net/test/test_server_posix.cc new file mode 100644 index 0000000..783bdc3 --- /dev/null +++ b/net/test/test_server_posix.cc @@ -0,0 +1,64 @@ +// Copyright (c) 2010 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/test/test_server.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_number_conversions.h" + +namespace net { + +// static +void TestServer::AppendToPythonPath(const FilePath& dir) { + const char kPythonPath[] = "PYTHONPATH"; + const char* oldpath = getenv(kPythonPath); + // setenv() leaks memory intentionally on Mac + if (!oldpath) { + setenv(kPythonPath, dir.value().c_str(), 1); + } else if (!strstr(oldpath, dir.value().c_str())) { + std::string newpath(oldpath); + newpath.append(":"); + newpath.append(dir.value()); + setenv(kPythonPath, newpath.c_str(), 1); + } +} + +bool TestServer::LaunchPython(const FilePath& testserver_path) { + std::vector<std::string> command_line; + command_line.push_back("python"); + command_line.push_back(testserver_path.value()); + command_line.push_back("--port=" + base::IntToString(host_port_pair_.port())); + command_line.push_back("--data-dir=" + document_root_.value()); + + if (type_ == TYPE_FTP) + command_line.push_back("-f"); + + FilePath certificate_path(GetCertificatePath()); + if (!certificate_path.value().empty()) { + if (!file_util::PathExists(certificate_path)) { + LOG(ERROR) << "Certificate path " << certificate_path.value() + << " doesn't exist. Can't launch https server."; + return false; + } + command_line.push_back("--https=" + certificate_path.value()); + } + + if (type_ == TYPE_HTTPS_CLIENT_AUTH) + command_line.push_back("--ssl-client-auth"); + + base::file_handle_mapping_vector no_mappings; + if (!base::LaunchApp(command_line, no_mappings, false, &process_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line[0] << " ..."; + return false; + } + + return true; +} + +bool TestServer::CheckCATrusted() { + return true; +} + +} // namespace net diff --git a/net/test/test_server_win.cc b/net/test/test_server_win.cc new file mode 100644 index 0000000..b806cba --- /dev/null +++ b/net/test/test_server_win.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2010 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/test/test_server.h" + +#include <windows.h> +#include <wincrypt.h> + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" + +#pragma comment(lib, "crypt32.lib") + +namespace { + +bool LaunchTestServerAsJob(const std::wstring& cmdline, + bool start_hidden, + base::ProcessHandle* process_handle, + ScopedHandle* job_handle) { + // Launch test server process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW; + PROCESS_INFORMATION process_info; + + // If this code is run under a debugger, the test server process is + // automatically associated with a job object created by the debugger. + // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, + &startup_info, &process_info)) { + LOG(ERROR) << "Could not create process."; + return false; + } + CloseHandle(process_info.hThread); + + // If the caller wants the process handle, we won't close it. + if (process_handle) { + *process_handle = process_info.hProcess; + } else { + CloseHandle(process_info.hProcess); + } + + // Create a JobObject and associate the test server process with it. + job_handle->Set(CreateJobObject(NULL, NULL)); + if (!job_handle->IsValid()) { + LOG(ERROR) << "Could not create JobObject."; + return false; + } else { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (0 == SetInformationJobObject(job_handle->Get(), + JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info))) { + LOG(ERROR) << "Could not SetInformationJobObject."; + return false; + } + if (0 == AssignProcessToJobObject(job_handle->Get(), + process_info.hProcess)) { + LOG(ERROR) << "Could not AssignProcessToObject."; + return false; + } + } + return true; +} + +} // namespace + +namespace net { + +// static +void TestServer::AppendToPythonPath(const FilePath& dir) { + const wchar_t kPythonPath[] = L"PYTHONPATH"; + // TODO(dkegel): handle longer PYTHONPATH variables + wchar_t oldpath[4096]; + if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) { + SetEnvironmentVariableW(kPythonPath, dir.value().c_str()); + } else if (!wcsstr(oldpath, dir.value().c_str())) { + std::wstring newpath(oldpath); + newpath.append(L";"); + newpath.append(dir.value()); + SetEnvironmentVariableW(kPythonPath, newpath.c_str()); + } +} + +bool TestServer::LaunchPython(const FilePath& testserver_path) { + FilePath python_exe; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_exe)) + return false; + python_exe = python_exe + .Append(FILE_PATH_LITERAL("third_party")) + .Append(FILE_PATH_LITERAL("python_24")) + .Append(FILE_PATH_LITERAL("python.exe")); + + std::wstring command_line = + L"\"" + python_exe.value() + L"\" " + + L"\"" + testserver_path.value() + + L"\" --port=" + ASCIIToWide(base::IntToString(host_port_pair_.port())) + + L" --data-dir=\"" + document_root_.value() + L"\""; + + if (type_ == TYPE_FTP) + command_line.append(L" -f"); + + FilePath certificate_path(GetCertificatePath()); + if (!certificate_path.value().empty()) { + if (!file_util::PathExists(certificate_path)) { + LOG(ERROR) << "Certificate path " << certificate_path.value() + << " doesn't exist. Can't launch https server."; + return false; + } + command_line.append(L" --https=\""); + command_line.append(certificate_path.value()); + command_line.append(L"\""); + } + + if (type_ == TYPE_HTTPS_CLIENT_AUTH) + command_line.append(L" --ssl-client-auth"); + + if (!LaunchTestServerAsJob(command_line, + true, + &process_handle_, + &job_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line; + return false; + } + + return true; +} + +bool TestServer::CheckCATrusted() { + HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT"); + if (!cert_store) { + LOG(ERROR) << " could not open trusted root CA store"; + return false; + } + PCCERT_CONTEXT cert = + CertFindCertificateInStore(cert_store, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_ISSUER_STR, + L"Test CA", + NULL); + if (cert) + CertFreeCertificateContext(cert); + CertCloseStore(cert_store, 0); + + if (!cert) { + LOG(ERROR) << " TEST CONFIGURATION ERROR: you need to import the test ca " + "certificate to your trusted roots for this test to work. " + "For more info visit:\n" + "http://dev.chromium.org/developers/testing\n"; + return false; + } + + return true; +} + +} // namespace net |