// Copyright (c) 2013 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 <stdio.h> #include <locale> #include <string> #include <vector> #include "base/at_exit.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.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/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/threading/thread_local.h" #include "chrome/test/chromedriver/logging.h" #include "chrome/test/chromedriver/net/port_server.h" #include "chrome/test/chromedriver/server/http_handler.h" #include "chrome/test/chromedriver/version.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/server/http_server.h" #include "net/server/http_server_request_info.h" #include "net/server/http_server_response_info.h" #include "net/socket/tcp_listen_socket.h" namespace { const char* kLocalHostAddress = "127.0.0.1"; typedef base::Callback< void(const net::HttpServerRequestInfo&, const HttpResponseSenderFunc&)> HttpRequestHandlerFunc; class HttpServer : public net::HttpServer::Delegate { public: explicit HttpServer(const HttpRequestHandlerFunc& handle_request_func) : handle_request_func_(handle_request_func), weak_factory_(this) {} virtual ~HttpServer() {} bool Start(int port, bool allow_remote) { std::string binding_ip = kLocalHostAddress; if (allow_remote) binding_ip = "0.0.0.0"; server_ = new net::HttpServer( net::TCPListenSocketFactory(binding_ip, port), this); net::IPEndPoint address; return server_->GetLocalAddress(&address) == net::OK; } // Overridden from net::HttpServer::Delegate: virtual void OnHttpRequest(int connection_id, const net::HttpServerRequestInfo& info) OVERRIDE { handle_request_func_.Run( info, base::Bind(&HttpServer::OnResponse, weak_factory_.GetWeakPtr(), connection_id)); } virtual void OnWebSocketRequest( int connection_id, const net::HttpServerRequestInfo& info) OVERRIDE {} virtual void OnWebSocketMessage(int connection_id, const std::string& data) OVERRIDE {} virtual void OnClose(int connection_id) OVERRIDE {} private: void OnResponse(int connection_id, scoped_ptr<net::HttpServerResponseInfo> response) { // Don't support keep-alive, since there's no way to detect if the // client is HTTP/1.0. In such cases, the client may hang waiting for // the connection to close (e.g., python 2.7 urllib). response->AddHeader("Connection", "close"); server_->SendResponse(connection_id, *response); server_->Close(connection_id); } HttpRequestHandlerFunc handle_request_func_; scoped_refptr<net::HttpServer> server_; base::WeakPtrFactory<HttpServer> weak_factory_; // Should be last. }; void SendResponseOnCmdThread( const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, const HttpResponseSenderFunc& send_response_on_io_func, scoped_ptr<net::HttpServerResponseInfo> response) { io_task_runner->PostTask( FROM_HERE, base::Bind(send_response_on_io_func, base::Passed(&response))); } void HandleRequestOnCmdThread( HttpHandler* handler, const std::vector<std::string>& whitelisted_ips, const net::HttpServerRequestInfo& request, const HttpResponseSenderFunc& send_response_func) { if (!whitelisted_ips.empty()) { std::string peer_address = request.peer.ToStringWithoutPort(); if (peer_address != kLocalHostAddress && std::find(whitelisted_ips.begin(), whitelisted_ips.end(), peer_address) == whitelisted_ips.end()) { LOG(WARNING) << "unauthorized access from " << request.peer.ToString(); scoped_ptr<net::HttpServerResponseInfo> response( new net::HttpServerResponseInfo(net::HTTP_UNAUTHORIZED)); response->SetBody("Unauthorized access", "text/plain"); send_response_func.Run(response.Pass()); return; } } handler->Handle(request, send_response_func); } void HandleRequestOnIOThread( const scoped_refptr<base::SingleThreadTaskRunner>& cmd_task_runner, const HttpRequestHandlerFunc& handle_request_on_cmd_func, const net::HttpServerRequestInfo& request, const HttpResponseSenderFunc& send_response_func) { cmd_task_runner->PostTask( FROM_HERE, base::Bind(handle_request_on_cmd_func, request, base::Bind(&SendResponseOnCmdThread, base::MessageLoopProxy::current(), send_response_func))); } base::LazyInstance<base::ThreadLocalPointer<HttpServer> > lazy_tls_server = LAZY_INSTANCE_INITIALIZER; void StopServerOnIOThread() { // Note, |server| may be NULL. HttpServer* server = lazy_tls_server.Pointer()->Get(); lazy_tls_server.Pointer()->Set(NULL); delete server; } void StartServerOnIOThread(int port, bool allow_remote, const HttpRequestHandlerFunc& handle_request_func) { scoped_ptr<HttpServer> temp_server(new HttpServer(handle_request_func)); if (!temp_server->Start(port, allow_remote)) { printf("Port not available. Exiting...\n"); exit(1); } lazy_tls_server.Pointer()->Set(temp_server.release()); } void RunServer(int port, bool allow_remote, const std::vector<std::string>& whitelisted_ips, const std::string& url_base, int adb_port, scoped_ptr<PortServer> port_server) { base::Thread io_thread("ChromeDriver IO"); CHECK(io_thread.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0))); base::MessageLoop cmd_loop; base::RunLoop cmd_run_loop; HttpHandler handler(cmd_run_loop.QuitClosure(), io_thread.message_loop_proxy(), url_base, adb_port, port_server.Pass()); HttpRequestHandlerFunc handle_request_func = base::Bind(&HandleRequestOnCmdThread, &handler, whitelisted_ips); io_thread.message_loop() ->PostTask(FROM_HERE, base::Bind(&StartServerOnIOThread, port, allow_remote, base::Bind(&HandleRequestOnIOThread, cmd_loop.message_loop_proxy(), handle_request_func))); // Run the command loop. This loop is quit after the response for a shutdown // request is posted to the IO loop. After the command loop quits, a task // is posted to the IO loop to stop the server. Lastly, the IO thread is // destroyed, which waits until all pending tasks have been completed. // This assumes the response is sent synchronously as part of the IO task. cmd_run_loop.Run(); io_thread.message_loop() ->PostTask(FROM_HERE, base::Bind(&StopServerOnIOThread)); } } // namespace int main(int argc, char *argv[]) { CommandLine::Init(argc, argv); base::AtExitManager at_exit; CommandLine* cmd_line = CommandLine::ForCurrentProcess(); #if defined(OS_LINUX) // Select the locale from the environment by passing an empty string instead // of the default "C" locale. This is particularly needed for the keycode // conversion code to work. setlocale(LC_ALL, ""); #endif // Parse command line flags. int port = 9515; int adb_port = 5037; bool allow_remote = false; std::vector<std::string> whitelisted_ips; std::string url_base; scoped_ptr<PortServer> port_server; if (cmd_line->HasSwitch("h") || cmd_line->HasSwitch("help")) { std::string options; const char* kOptionAndDescriptions[] = { "port=PORT", "port to listen on", "adb-port=PORT", "adb server port", "log-path=FILE", "write server log to file instead of stderr, " "increases log level to INFO", "verbose", "log verbosely", "version", "print the version number and exit", "silent", "log nothing", "url-base", "base URL path prefix for commands, e.g. wd/url", "port-server", "address of server to contact for reserving a port", "whitelisted-ips", "comma-separated whitelist of remote IPv4 addresses " "which are allowed to connect to ChromeDriver", }; for (size_t i = 0; i < arraysize(kOptionAndDescriptions) - 1; i += 2) { options += base::StringPrintf( " --%-30s%s\n", kOptionAndDescriptions[i], kOptionAndDescriptions[i + 1]); } printf("Usage: %s [OPTIONS]\n\nOptions\n%s", argv[0], options.c_str()); return 0; } if (cmd_line->HasSwitch("v") || cmd_line->HasSwitch("version")) { printf("ChromeDriver %s\n", kChromeDriverVersion); return 0; } if (cmd_line->HasSwitch("port")) { if (!base::StringToInt(cmd_line->GetSwitchValueASCII("port"), &port)) { printf("Invalid port. Exiting...\n"); return 1; } } if (cmd_line->HasSwitch("adb-port")) { if (!base::StringToInt(cmd_line->GetSwitchValueASCII("adb-port"), &adb_port)) { printf("Invalid adb-port. Exiting...\n"); return 1; } } if (cmd_line->HasSwitch("port-server")) { #if defined(OS_LINUX) std::string address = cmd_line->GetSwitchValueASCII("port-server"); if (address.empty() || address[0] != '@') { printf("Invalid port-server. Exiting...\n"); return 1; } std::string path; // First character of path is \0 to use Linux's abstract namespace. path.push_back(0); path += address.substr(1); port_server.reset(new PortServer(path)); #else printf("Warning: port-server not implemented for this platform.\n"); #endif } if (cmd_line->HasSwitch("url-base")) url_base = cmd_line->GetSwitchValueASCII("url-base"); if (url_base.empty() || url_base[0] != '/') url_base = "/" + url_base; if (url_base[url_base.length() - 1] != '/') url_base = url_base + "/"; if (cmd_line->HasSwitch("whitelisted-ips")) { allow_remote = true; std::string whitelist = cmd_line->GetSwitchValueASCII("whitelisted-ips"); base::SplitString(whitelist, ',', &whitelisted_ips); } if (!cmd_line->HasSwitch("silent")) { printf( "Starting ChromeDriver (v%s) on port %d\n", kChromeDriverVersion, port); if (!allow_remote) { printf("Only local connections are allowed.\n"); } else if (!whitelisted_ips.empty()) { printf("Remote connections are allowed by a whitelist (%s).\n", cmd_line->GetSwitchValueASCII("whitelisted-ips").c_str()); } else { printf("All remote connections are allowed. Use a whitelist instead!\n"); } fflush(stdout); } if (!InitLogging()) { printf("Unable to initialize logging. Exiting...\n"); return 1; } RunServer(port, allow_remote, whitelisted_ips, url_base, adb_port, port_server.Pass()); return 0; }