普通文本  |  744行  |  30.12 KB

// 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 "chrome/test/chromedriver/server/http_handler.h"

#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"  // For CHECK macros.
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "chrome/test/chromedriver/alert_commands.h"
#include "chrome/test/chromedriver/chrome/adb_impl.h"
#include "chrome/test/chromedriver/chrome/device_manager.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/net/port_server.h"
#include "chrome/test/chromedriver/net/url_request_context_getter.h"
#include "chrome/test/chromedriver/session.h"
#include "chrome/test/chromedriver/session_thread_map.h"
#include "chrome/test/chromedriver/util.h"
#include "chrome/test/chromedriver/version.h"
#include "net/server/http_server_request_info.h"
#include "net/server/http_server_response_info.h"

#if defined(OS_MACOSX)
#include "base/mac/scoped_nsautorelease_pool.h"
#endif

namespace {

const char kLocalStorage[] = "localStorage";
const char kSessionStorage[] = "sessionStorage";
const char kShutdownPath[] = "shutdown";

void UnimplementedCommand(
    const base::DictionaryValue& params,
    const std::string& session_id,
    const CommandCallback& callback) {
  callback.Run(Status(kUnknownCommand), scoped_ptr<base::Value>(), session_id);
}

}  // namespace

CommandMapping::CommandMapping(HttpMethod method,
                               const std::string& path_pattern,
                               const Command& command)
    : method(method), path_pattern(path_pattern), command(command) {}

CommandMapping::~CommandMapping() {}

HttpHandler::HttpHandler(const std::string& url_base)
    : url_base_(url_base),
      received_shutdown_(false),
      command_map_(new CommandMap()),
      weak_ptr_factory_(this) {}

HttpHandler::HttpHandler(
    const base::Closure& quit_func,
    const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
    const std::string& url_base,
    int adb_port,
    scoped_ptr<PortServer> port_server)
    : quit_func_(quit_func),
      url_base_(url_base),
      received_shutdown_(false),
      weak_ptr_factory_(this) {
#if defined(OS_MACOSX)
  base::mac::ScopedNSAutoreleasePool autorelease_pool;
#endif
  context_getter_ = new URLRequestContextGetter(io_task_runner);
  socket_factory_ = CreateSyncWebSocketFactory(context_getter_.get());
  adb_.reset(new AdbImpl(io_task_runner, adb_port));
  device_manager_.reset(new DeviceManager(adb_.get()));
  port_server_ = port_server.Pass();
  port_manager_.reset(new PortManager(12000, 13000));

  CommandMapping commands[] = {
      CommandMapping(
          kPost,
          internal::kNewSessionPathPattern,
          base::Bind(&ExecuteCreateSession,
                     &session_thread_map_,
                     WrapToCommand(
                         "InitSession",
                         base::Bind(&ExecuteInitSession,
                                    InitSessionParams(context_getter_,
                                                      socket_factory_,
                                                      device_manager_.get(),
                                                      port_server_.get(),
                                                      port_manager_.get()))))),
      CommandMapping(kGet,
                     "session/:sessionId",
                     WrapToCommand("GetSessionCapabilities",
                                   base::Bind(&ExecuteGetSessionCapabilities))),
      CommandMapping(kDelete,
                     "session/:sessionId",
                     base::Bind(&ExecuteSessionCommand,
                                &session_thread_map_,
                                "Quit",
                                base::Bind(&ExecuteQuit, false),
                                true)),
      CommandMapping(kGet,
                     "session/:sessionId/window_handle",
                     WrapToCommand("GetWindow",
                                   base::Bind(&ExecuteGetCurrentWindowHandle))),
      CommandMapping(
          kGet,
          "session/:sessionId/window_handles",
          WrapToCommand("GetWindows", base::Bind(&ExecuteGetWindowHandles))),
      CommandMapping(kPost,
                     "session/:sessionId/url",
                     WrapToCommand("Navigate", base::Bind(&ExecuteGet))),
      CommandMapping(kPost,
                     "session/:sessionId/chromium/launch_app",
                     WrapToCommand("LaunchApp", base::Bind(&ExecuteLaunchApp))),
      CommandMapping(kGet,
                     "session/:sessionId/alert",
                     WrapToCommand("IsAlertOpen",
                                   base::Bind(&ExecuteAlertCommand,
                                              base::Bind(&ExecuteGetAlert)))),
      CommandMapping(
          kPost,
          "session/:sessionId/dismiss_alert",
          WrapToCommand("DismissAlert",
                        base::Bind(&ExecuteAlertCommand,
                                   base::Bind(&ExecuteDismissAlert)))),
      CommandMapping(
          kPost,
          "session/:sessionId/accept_alert",
          WrapToCommand("AcceptAlert",
                        base::Bind(&ExecuteAlertCommand,
                                   base::Bind(&ExecuteAcceptAlert)))),
      CommandMapping(
          kGet,
          "session/:sessionId/alert_text",
          WrapToCommand("GetAlertMessage",
                        base::Bind(&ExecuteAlertCommand,
                                   base::Bind(&ExecuteGetAlertText)))),
      CommandMapping(
          kPost,
          "session/:sessionId/alert_text",
          WrapToCommand("SetAlertPrompt",
                        base::Bind(&ExecuteAlertCommand,
                                   base::Bind(&ExecuteSetAlertValue)))),
      CommandMapping(kPost,
                     "session/:sessionId/forward",
                     WrapToCommand("GoForward", base::Bind(&ExecuteGoForward))),
      CommandMapping(kPost,
                     "session/:sessionId/back",
                     WrapToCommand("GoBack", base::Bind(&ExecuteGoBack))),
      CommandMapping(kPost,
                     "session/:sessionId/refresh",
                     WrapToCommand("Refresh", base::Bind(&ExecuteRefresh))),
      CommandMapping(
          kPost,
          "session/:sessionId/execute",
          WrapToCommand("ExecuteScript", base::Bind(&ExecuteExecuteScript))),
      CommandMapping(kPost,
                     "session/:sessionId/execute_async",
                     WrapToCommand("ExecuteAsyncScript",
                                   base::Bind(&ExecuteExecuteAsyncScript))),
      CommandMapping(
          kGet,
          "session/:sessionId/url",
          WrapToCommand("GetUrl", base::Bind(&ExecuteGetCurrentUrl))),
      CommandMapping(kGet,
                     "session/:sessionId/title",
                     WrapToCommand("GetTitle", base::Bind(&ExecuteGetTitle))),
      CommandMapping(
          kGet,
          "session/:sessionId/source",
          WrapToCommand("GetSource", base::Bind(&ExecuteGetPageSource))),
      CommandMapping(
          kGet,
          "session/:sessionId/screenshot",
          WrapToCommand("Screenshot", base::Bind(&ExecuteScreenshot))),
      CommandMapping(
          kGet,
          "session/:sessionId/chromium/heap_snapshot",
          WrapToCommand("HeapSnapshot", base::Bind(&ExecuteTakeHeapSnapshot))),
      CommandMapping(kPost,
                     "session/:sessionId/visible",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kGet,
                     "session/:sessionId/visible",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(
          kPost,
          "session/:sessionId/element",
          WrapToCommand("FindElement", base::Bind(&ExecuteFindElement, 50))),
      CommandMapping(
          kPost,
          "session/:sessionId/elements",
          WrapToCommand("FindElements", base::Bind(&ExecuteFindElements, 50))),
      CommandMapping(kPost,
                     "session/:sessionId/element/active",
                     WrapToCommand("GetActiveElement",
                                   base::Bind(&ExecuteGetActiveElement))),
      CommandMapping(kPost,
                     "session/:sessionId/element/:id/element",
                     WrapToCommand("FindChildElement",
                                   base::Bind(&ExecuteFindChildElement, 50))),
      CommandMapping(kPost,
                     "session/:sessionId/element/:id/elements",
                     WrapToCommand("FindChildElements",
                                   base::Bind(&ExecuteFindChildElements, 50))),
      CommandMapping(
          kPost,
          "session/:sessionId/element/:id/click",
          WrapToCommand("ClickElement", base::Bind(&ExecuteClickElement))),
      CommandMapping(
          kPost,
          "session/:sessionId/element/:id/clear",
          WrapToCommand("ClearElement", base::Bind(&ExecuteClearElement))),
      CommandMapping(
          kPost,
          "session/:sessionId/element/:id/submit",
          WrapToCommand("SubmitElement", base::Bind(&ExecuteSubmitElement))),
      CommandMapping(
          kGet,
          "session/:sessionId/element/:id/text",
          WrapToCommand("GetElementText", base::Bind(&ExecuteGetElementText))),
      CommandMapping(
          kPost,
          "session/:sessionId/element/:id/value",
          WrapToCommand("TypeElement", base::Bind(&ExecuteSendKeysToElement))),
      CommandMapping(
          kPost,
          "session/:sessionId/file",
          WrapToCommand("UploadFile", base::Bind(&ExecuteUploadFile))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/value",
                     WrapToCommand("GetElementValue",
                                   base::Bind(&ExecuteGetElementValue))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/name",
                     WrapToCommand("GetElementTagName",
                                   base::Bind(&ExecuteGetElementTagName))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/selected",
                     WrapToCommand("IsElementSelected",
                                   base::Bind(&ExecuteIsElementSelected))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/enabled",
                     WrapToCommand("IsElementEnabled",
                                   base::Bind(&ExecuteIsElementEnabled))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/displayed",
                     WrapToCommand("IsElementDisplayed",
                                   base::Bind(&ExecuteIsElementDisplayed))),
      CommandMapping(
          kPost,
          "session/:sessionId/element/:id/hover",
          WrapToCommand("HoverElement", base::Bind(&ExecuteHoverOverElement))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/location",
                     WrapToCommand("GetElementLocation",
                                   base::Bind(&ExecuteGetElementLocation))),
      CommandMapping(
          kGet,
          "session/:sessionId/element/:id/location_in_view",
          WrapToCommand(
              "GetElementLocationInView",
              base::Bind(&ExecuteGetElementLocationOnceScrolledIntoView))),
      CommandMapping(
          kGet,
          "session/:sessionId/element/:id/size",
          WrapToCommand("GetElementSize", base::Bind(&ExecuteGetElementSize))),
      CommandMapping(kGet,
                     "session/:sessionId/element/:id/attribute/:name",
                     WrapToCommand("GetElementAttribute",
                                   base::Bind(&ExecuteGetElementAttribute))),
      CommandMapping(
          kGet,
          "session/:sessionId/element/:id/equals/:other",
          WrapToCommand("IsElementEqual", base::Bind(&ExecuteElementEquals))),
      CommandMapping(
          kGet,
          "session/:sessionId/cookie",
          WrapToCommand("GetCookies", base::Bind(&ExecuteGetCookies))),
      CommandMapping(kPost,
                     "session/:sessionId/cookie",
                     WrapToCommand("AddCookie", base::Bind(&ExecuteAddCookie))),
      CommandMapping(kDelete,
                     "session/:sessionId/cookie",
                     WrapToCommand("DeleteAllCookies",
                                   base::Bind(&ExecuteDeleteAllCookies))),
      CommandMapping(
          kDelete,
          "session/:sessionId/cookie/:name",
          WrapToCommand("DeleteCookie", base::Bind(&ExecuteDeleteCookie))),
      CommandMapping(
          kPost,
          "session/:sessionId/frame",
          WrapToCommand("SwitchToFrame", base::Bind(&ExecuteSwitchToFrame))),
      CommandMapping(
          kPost,
          "session/:sessionId/frame/parent",
          WrapToCommand("SwitchToParentFrame",
                        base::Bind(&ExecuteSwitchToParentFrame))),
      CommandMapping(
          kPost,
          "session/:sessionId/window",
          WrapToCommand("SwitchToWindow", base::Bind(&ExecuteSwitchToWindow))),
      CommandMapping(
          kGet,
          "session/:sessionId/window/:windowHandle/size",
          WrapToCommand("GetWindowSize", base::Bind(&ExecuteGetWindowSize))),
      CommandMapping(kGet,
                     "session/:sessionId/window/:windowHandle/position",
                     WrapToCommand("GetWindowPosition",
                                   base::Bind(&ExecuteGetWindowPosition))),
      CommandMapping(
          kPost,
          "session/:sessionId/window/:windowHandle/size",
          WrapToCommand("SetWindowSize", base::Bind(&ExecuteSetWindowSize))),
      CommandMapping(kPost,
                     "session/:sessionId/window/:windowHandle/position",
                     WrapToCommand("SetWindowPosition",
                                   base::Bind(&ExecuteSetWindowPosition))),
      CommandMapping(
          kPost,
          "session/:sessionId/window/:windowHandle/maximize",
          WrapToCommand("MaximizeWindow", base::Bind(&ExecuteMaximizeWindow))),
      CommandMapping(kDelete,
                     "session/:sessionId/window",
                     WrapToCommand("CloseWindow", base::Bind(&ExecuteClose))),
      CommandMapping(kPost,
                     "session/:sessionId/element/:id/drag",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(
          kGet,
          "session/:sessionId/element/:id/css/:propertyName",
          WrapToCommand("GetElementCSSProperty",
                        base::Bind(&ExecuteGetElementValueOfCSSProperty))),
      CommandMapping(
          kPost,
          "session/:sessionId/timeouts/implicit_wait",
          WrapToCommand("SetImplicitWait", base::Bind(&ExecuteImplicitlyWait))),
      CommandMapping(kPost,
                     "session/:sessionId/timeouts/async_script",
                     WrapToCommand("SetScriptTimeout",
                                   base::Bind(&ExecuteSetScriptTimeout))),
      CommandMapping(
          kPost,
          "session/:sessionId/timeouts",
          WrapToCommand("SetTimeout", base::Bind(&ExecuteSetTimeout))),
      CommandMapping(kPost,
                     "session/:sessionId/execute_sql",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(
          kGet,
          "session/:sessionId/location",
          WrapToCommand("GetGeolocation", base::Bind(&ExecuteGetLocation))),
      CommandMapping(
          kPost,
          "session/:sessionId/location",
          WrapToCommand("SetGeolocation", base::Bind(&ExecuteSetLocation))),
      CommandMapping(kGet,
                     "session/:sessionId/application_cache/status",
                     base::Bind(&ExecuteGetStatus)),
      CommandMapping(kGet,
                     "session/:sessionId/browser_connection",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/browser_connection",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(
          kGet,
          "session/:sessionId/local_storage/key/:key",
          WrapToCommand("GetLocalStorageItem",
                        base::Bind(&ExecuteGetStorageItem, kLocalStorage))),
      CommandMapping(
          kDelete,
          "session/:sessionId/local_storage/key/:key",
          WrapToCommand("RemoveLocalStorageItem",
                        base::Bind(&ExecuteRemoveStorageItem, kLocalStorage))),
      CommandMapping(
          kGet,
          "session/:sessionId/local_storage",
          WrapToCommand("GetLocalStorageKeys",
                        base::Bind(&ExecuteGetStorageKeys, kLocalStorage))),
      CommandMapping(
          kPost,
          "session/:sessionId/local_storage",
          WrapToCommand("SetLocalStorageKeys",
                        base::Bind(&ExecuteSetStorageItem, kLocalStorage))),
      CommandMapping(
          kDelete,
          "session/:sessionId/local_storage",
          WrapToCommand("ClearLocalStorage",
                        base::Bind(&ExecuteClearStorage, kLocalStorage))),
      CommandMapping(
          kGet,
          "session/:sessionId/local_storage/size",
          WrapToCommand("GetLocalStorageSize",
                        base::Bind(&ExecuteGetStorageSize, kLocalStorage))),
      CommandMapping(
          kGet,
          "session/:sessionId/session_storage/key/:key",
          WrapToCommand("GetSessionStorageItem",
                        base::Bind(&ExecuteGetStorageItem, kSessionStorage))),
      CommandMapping(kDelete,
                     "session/:sessionId/session_storage/key/:key",
                     WrapToCommand("RemoveSessionStorageItem",
                                   base::Bind(&ExecuteRemoveStorageItem,
                                              kSessionStorage))),
      CommandMapping(
          kGet,
          "session/:sessionId/session_storage",
          WrapToCommand("GetSessionStorageKeys",
                        base::Bind(&ExecuteGetStorageKeys, kSessionStorage))),
      CommandMapping(
          kPost,
          "session/:sessionId/session_storage",
          WrapToCommand("SetSessionStorageItem",
                        base::Bind(&ExecuteSetStorageItem, kSessionStorage))),
      CommandMapping(
          kDelete,
          "session/:sessionId/session_storage",
          WrapToCommand("ClearSessionStorage",
                        base::Bind(&ExecuteClearStorage, kSessionStorage))),
      CommandMapping(
          kGet,
          "session/:sessionId/session_storage/size",
          WrapToCommand("GetSessionStorageSize",
                        base::Bind(&ExecuteGetStorageSize, kSessionStorage))),
      CommandMapping(kGet,
                     "session/:sessionId/orientation",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/orientation",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/click",
                     WrapToCommand("Click", base::Bind(&ExecuteMouseClick))),
      CommandMapping(
          kPost,
          "session/:sessionId/doubleclick",
          WrapToCommand("DoubleClick", base::Bind(&ExecuteMouseDoubleClick))),
      CommandMapping(
          kPost,
          "session/:sessionId/buttondown",
          WrapToCommand("MouseDown", base::Bind(&ExecuteMouseButtonDown))),
      CommandMapping(
          kPost,
          "session/:sessionId/buttonup",
          WrapToCommand("MouseUp", base::Bind(&ExecuteMouseButtonUp))),
      CommandMapping(
          kPost,
          "session/:sessionId/moveto",
          WrapToCommand("MouseMove", base::Bind(&ExecuteMouseMoveTo))),
      CommandMapping(
          kPost,
          "session/:sessionId/keys",
          WrapToCommand("Type", base::Bind(&ExecuteSendKeysToActiveElement))),
      CommandMapping(kGet,
                     "session/:sessionId/ime/available_engines",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kGet,
                     "session/:sessionId/ime/active_engine",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kGet,
                     "session/:sessionId/ime/activated",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/ime/deactivate",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/ime/activate",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/touch/click",
                     WrapToCommand("Tap", base::Bind(&ExecuteTouchSingleTap))),
      CommandMapping(kPost,
                     "session/:sessionId/touch/down",
                     WrapToCommand("TouchDown", base::Bind(&ExecuteTouchDown))),
      CommandMapping(kPost,
                     "session/:sessionId/touch/up",
                     WrapToCommand("TouchUp", base::Bind(&ExecuteTouchUp))),
      CommandMapping(kPost,
                     "session/:sessionId/touch/move",
                     WrapToCommand("TouchMove", base::Bind(&ExecuteTouchMove))),
      CommandMapping(kPost,
                     "session/:sessionId/touch/scroll",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/touch/doubleclick",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/touch/longclick",
                     base::Bind(&UnimplementedCommand)),
      CommandMapping(kPost,
                     "session/:sessionId/touch/flick",
                     WrapToCommand("TouchFlick", base::Bind(&ExecuteFlick))),
      CommandMapping(kPost,
                     "session/:sessionId/log",
                     WrapToCommand("GetLog", base::Bind(&ExecuteGetLog))),
      CommandMapping(kGet,
                     "session/:sessionId/log/types",
                     WrapToCommand("GetLogTypes",
                                   base::Bind(&ExecuteGetAvailableLogTypes))),
      CommandMapping(kPost, "logs", base::Bind(&UnimplementedCommand)),
      CommandMapping(kGet, "status", base::Bind(&ExecuteGetStatus)),

      // Custom Chrome commands:
      // Allow quit all to be called with GET or POST.
      CommandMapping(
          kGet,
          kShutdownPath,
          base::Bind(&ExecuteQuitAll,
                     WrapToCommand("QuitAll", base::Bind(&ExecuteQuit, true)),
                     &session_thread_map_)),
      CommandMapping(
          kPost,
          kShutdownPath,
          base::Bind(&ExecuteQuitAll,
                     WrapToCommand("QuitAll", base::Bind(&ExecuteQuit, true)),
                     &session_thread_map_)),
      CommandMapping(kGet,
                     "session/:sessionId/is_loading",
                     WrapToCommand("IsLoading", base::Bind(&ExecuteIsLoading))),
      CommandMapping(kGet,
                     "session/:sessionId/autoreport",
                     WrapToCommand("IsAutoReporting",
                                   base::Bind(&ExecuteIsAutoReporting))),
      CommandMapping(kPost,
                     "session/:sessionId/autoreport",
                     WrapToCommand(
                         "SetAutoReporting",
                         base::Bind(&ExecuteSetAutoReporting))),
  };
  command_map_.reset(
      new CommandMap(commands, commands + arraysize(commands)));
}

HttpHandler::~HttpHandler() {}

void HttpHandler::Handle(const net::HttpServerRequestInfo& request,
                         const HttpResponseSenderFunc& send_response_func) {
  CHECK(thread_checker_.CalledOnValidThread());

  if (received_shutdown_)
    return;

  std::string path = request.path;
  if (!StartsWithASCII(path, url_base_, true)) {
    scoped_ptr<net::HttpServerResponseInfo> response(
        new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
    response->SetBody("unhandled request", "text/plain");
    send_response_func.Run(response.Pass());
    return;
  }

  path.erase(0, url_base_.length());

  HandleCommand(request, path, send_response_func);

  if (path == kShutdownPath)
    received_shutdown_ = true;
}

Command HttpHandler::WrapToCommand(
    const char* name,
    const SessionCommand& session_command) {
  return base::Bind(&ExecuteSessionCommand,
                    &session_thread_map_,
                    name,
                    session_command,
                    false);
}

Command HttpHandler::WrapToCommand(
    const char* name,
    const WindowCommand& window_command) {
  return WrapToCommand(name, base::Bind(&ExecuteWindowCommand, window_command));
}

Command HttpHandler::WrapToCommand(
    const char* name,
    const ElementCommand& element_command) {
  return WrapToCommand(name,
                       base::Bind(&ExecuteElementCommand, element_command));
}

void HttpHandler::HandleCommand(
    const net::HttpServerRequestInfo& request,
    const std::string& trimmed_path,
    const HttpResponseSenderFunc& send_response_func) {
  base::DictionaryValue params;
  std::string session_id;
  CommandMap::const_iterator iter = command_map_->begin();
  while (true) {
    if (iter == command_map_->end()) {
      scoped_ptr<net::HttpServerResponseInfo> response(
          new net::HttpServerResponseInfo(net::HTTP_NOT_FOUND));
      response->SetBody("unknown command: " + trimmed_path, "text/plain");
      send_response_func.Run(response.Pass());
      return;
    }
    if (internal::MatchesCommand(
            request.method, trimmed_path, *iter, &session_id, &params)) {
      break;
    }
    ++iter;
  }

  if (request.data.length()) {
    base::DictionaryValue* body_params;
    scoped_ptr<base::Value> parsed_body(base::JSONReader::Read(request.data));
    if (!parsed_body || !parsed_body->GetAsDictionary(&body_params)) {
      scoped_ptr<net::HttpServerResponseInfo> response(
          new net::HttpServerResponseInfo(net::HTTP_BAD_REQUEST));
      response->SetBody("missing command parameters", "text/plain");
      send_response_func.Run(response.Pass());
      return;
    }
    params.MergeDictionary(body_params);
  }

  iter->command.Run(params,
                    session_id,
                    base::Bind(&HttpHandler::PrepareResponse,
                               weak_ptr_factory_.GetWeakPtr(),
                               trimmed_path,
                               send_response_func));
}

void HttpHandler::PrepareResponse(
    const std::string& trimmed_path,
    const HttpResponseSenderFunc& send_response_func,
    const Status& status,
    scoped_ptr<base::Value> value,
    const std::string& session_id) {
  CHECK(thread_checker_.CalledOnValidThread());
  scoped_ptr<net::HttpServerResponseInfo> response =
      PrepareResponseHelper(trimmed_path, status, value.Pass(), session_id);
  send_response_func.Run(response.Pass());
  if (trimmed_path == kShutdownPath)
    quit_func_.Run();
}

scoped_ptr<net::HttpServerResponseInfo> HttpHandler::PrepareResponseHelper(
    const std::string& trimmed_path,
    const Status& status,
    scoped_ptr<base::Value> value,
    const std::string& session_id) {
  if (status.code() == kUnknownCommand) {
    scoped_ptr<net::HttpServerResponseInfo> response(
        new net::HttpServerResponseInfo(net::HTTP_NOT_IMPLEMENTED));
    response->SetBody("unimplemented command: " + trimmed_path, "text/plain");
    return response.Pass();
  }

  if (status.IsError()) {
    Status full_status(status);
    full_status.AddDetails(base::StringPrintf(
        "Driver info: chromedriver=%s,platform=%s %s %s",
        kChromeDriverVersion,
        base::SysInfo::OperatingSystemName().c_str(),
        base::SysInfo::OperatingSystemVersion().c_str(),
        base::SysInfo::OperatingSystemArchitecture().c_str()));
    scoped_ptr<base::DictionaryValue> error(new base::DictionaryValue());
    error->SetString("message", full_status.message());
    value.reset(error.release());
  }
  if (!value)
    value.reset(base::Value::CreateNullValue());

  base::DictionaryValue body_params;
  body_params.SetInteger("status", status.code());
  body_params.Set("value", value.release());
  body_params.SetString("sessionId", session_id);
  std::string body;
  base::JSONWriter::WriteWithOptions(
      &body_params, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
      &body);
  scoped_ptr<net::HttpServerResponseInfo> response(
      new net::HttpServerResponseInfo(net::HTTP_OK));
  response->SetBody(body, "application/json; charset=utf-8");
  return response.Pass();
}

namespace internal {

const char kNewSessionPathPattern[] = "session";

bool MatchesMethod(HttpMethod command_method, const std::string& method) {
  std::string lower_method = StringToLowerASCII(method);
  switch (command_method) {
  case kGet:
    return lower_method == "get";
  case kPost:
    return lower_method == "post" || lower_method == "put";
  case kDelete:
    return lower_method == "delete";
  }
  return false;
}

bool MatchesCommand(const std::string& method,
                    const std::string& path,
                    const CommandMapping& command,
                    std::string* session_id,
                    base::DictionaryValue* out_params) {
  if (!MatchesMethod(command.method, method))
    return false;

  std::vector<std::string> path_parts;
  base::SplitString(path, '/', &path_parts);
  std::vector<std::string> command_path_parts;
  base::SplitString(command.path_pattern, '/', &command_path_parts);
  if (path_parts.size() != command_path_parts.size())
    return false;

  base::DictionaryValue params;
  for (size_t i = 0; i < path_parts.size(); ++i) {
    CHECK(command_path_parts[i].length());
    if (command_path_parts[i][0] == ':') {
      std::string name = command_path_parts[i];
      name.erase(0, 1);
      CHECK(name.length());
      if (name == "sessionId")
        *session_id = path_parts[i];
      else
        params.SetString(name, path_parts[i]);
    } else if (command_path_parts[i] != path_parts[i]) {
      return false;
    }
  }
  out_params->MergeDictionary(&params);
  return true;
}

}  // namespace internal