blob: 595e8a482fda2eb2781b901e59d2a55d3179332e [file] [log] [blame]
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/webdriver/dispatcher.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "cobalt/webdriver/protocol/response.h"
namespace cobalt {
namespace webdriver {
namespace {
const char kVariablePrefix = ':';
// CommandResultHandler implementation that sends the results of a
// WebDriver command to a WebDriverServer::ResponseHandler.
class CommandResultHandlerImpl
: public WebDriverDispatcher::CommandResultHandler {
public:
CommandResultHandlerImpl(
scoped_ptr<WebDriverServer::ResponseHandler> response_handler)
: response_handler_(response_handler.Pass()) {}
void SendResult(const base::optional<protocol::SessionId>& session_id,
protocol::Response::StatusCode status_code,
scoped_ptr<base::Value> webdriver_response_value) override {
scoped_ptr<base::Value> response = protocol::Response::CreateResponse(
session_id, status_code, webdriver_response_value.Pass());
if (status_code == protocol::Response::kSuccess) {
response_handler_->Success(response.Pass());
} else {
response_handler_->FailedCommand(response.Pass());
}
}
void SendInvalidRequestResponse(RequestError error,
const std::string& error_string) override {
switch (error) {
case kInvalidParameters:
response_handler_->MissingCommandParameters(error_string);
break;
case kInvalidPathVariable:
response_handler_->VariableResourceNotFound(error_string);
break;
}
}
private:
scoped_ptr<WebDriverServer::ResponseHandler> response_handler_;
};
// Helper function to get all supported methods for a given mapping of
// HttpMethod to DispatchCommandCallback.
typedef base::hash_map<WebDriverServer::HttpMethod,
WebDriverDispatcher::DispatchCommandCallback>
MethodToCommandMap;
std::vector<WebDriverServer::HttpMethod> GetAvailableMethods(
const MethodToCommandMap& method_lookup) {
std::vector<WebDriverServer::HttpMethod> available_methods;
for (MethodToCommandMap::const_iterator it = method_lookup.begin();
it != method_lookup.end(); ++it) {
available_methods.push_back(it->first);
}
return available_methods;
}
// Populate a PathVariableMapInternal mapping based on the components of a
// registered URL and a requested path.
// The key of the map will be the name of the variable component, and the value
// will be the actual value of the corresponding component in the request.
typedef base::hash_map<std::string, std::string> PathVariableMapInternal;
void PopulatePathVariableMap(PathVariableMapInternal* path_variable_map,
const std::vector<std::string>& request,
const std::vector<std::string>& path_components) {
DCHECK_EQ(path_components.size(), request.size());
for (size_t i = 0; i < request.size(); ++i) {
DCHECK(!path_components[i].empty());
// If the path component starts with a colon, it is a variable. Map the
// variable name (dropping the colon) to the actual value in the request.
if (path_components[i][0] == kVariablePrefix) {
(*path_variable_map)[path_components[i]] = request[i];
}
}
}
// Tokenize a URL path by component.
std::vector<std::string> TokenizePath(const std::string& path) {
std::vector<std::string> tokenized_path;
StringTokenizer tokenizer(path, "/");
while (tokenizer.GetNext()) {
tokenized_path.push_back(tokenizer.token());
}
return tokenized_path;
}
// Used to compare two path components. If match_exact is set to false,
// then if either path is a variable component (starts with ':'), they will
// trivially match.
class PathComponentsAreEqualPredicate {
public:
explicit PathComponentsAreEqualPredicate(bool match_exact)
: match_exact_(match_exact) {}
bool operator()(const std::string& lhs, const std::string& rhs) const {
if (!match_exact_ && (StartsWithColon(lhs) || StartsWithColon(rhs))) {
return true;
}
return lhs == rhs;
}
private:
bool StartsWithColon(const std::string& s) const {
return !s.empty() && s[0] == kVariablePrefix;
}
bool match_exact_;
};
} // namespace
void WebDriverDispatcher::RegisterCommand(
WebDriverServer::HttpMethod method, const std::string& path,
const DispatchCommandCallback& callback) {
// Break the registered path into components.
std::vector<std::string> tokenized_path = TokenizePath(path);
// Find the registered CommandMapping struct if any other methods have been
// registered for this path yet. Use exact matching so that we don't match
// any path variables as wildcards.
CommandMapping* mapping = GetMappingForPath(tokenized_path, kMatchExact);
if (!mapping) {
// No commands registered for this path yet, so create a new CommandMapping.
int tokenized_path_size = static_cast<int>(tokenized_path.size());
CommandMappingLookup::iterator it = command_lookup_.insert(
std::make_pair(tokenized_path_size, CommandMapping(tokenized_path)));
mapping = &it->second;
}
// Register this Http method for this CommandMapping.
CommandMapping::MethodToCommandMap& method_to_command_map =
mapping->command_map;
DCHECK(method_to_command_map.end() == method_to_command_map.find(method));
method_to_command_map[method] = callback;
}
WebDriverDispatcher::CommandMapping* WebDriverDispatcher::GetMappingForPath(
const std::vector<std::string>& components, MatchStrategy strategy) {
// Use reverse iterators for the match so we can early-out of a mismatch
// more quickly. Most requests start with '/session/:sessionId/', for
// example.
typedef std::vector<std::string>::const_reverse_iterator MismatchResult;
typedef std::pair<MismatchResult, MismatchResult> MismatchResultPair;
typedef std::pair<CommandMappingLookup::iterator,
CommandMappingLookup::iterator> EqualRangeResultPair;
PathComponentsAreEqualPredicate predicate(strategy == kMatchExact);
// First find all commands that have the same number of components.
EqualRangeResultPair pair_range =
command_lookup_.equal_range(components.size());
// For each command mapping that has the correct number of components,
// check if the components match the CommandMapping's components.
for (CommandMappingLookup::iterator it = pair_range.first;
it != pair_range.second; ++it) {
DCHECK_EQ(components.size(), it->second.path_components.size());
// mismatch will return a pair of iterators to the first item in each
// sequence that is not equal.
MismatchResultPair result_pair =
std::mismatch(components.rbegin(), components.rend(),
it->second.path_components.rbegin(), predicate);
if (result_pair.first == components.rend()) {
DCHECK(
result_pair.second ==
static_cast<MismatchResult>(it->second.path_components.rend()));
return &it->second;
}
}
return NULL;
}
void WebDriverDispatcher::HandleWebDriverServerRequest(
WebDriverServer::HttpMethod method, const std::string& path,
scoped_ptr<base::Value> request_value,
scoped_ptr<WebDriverServer::ResponseHandler> response_handler) {
// Tokenize the requested resource path and look up a CommandMapping for it,
// matching variables.
std::vector<std::string> tokenized_request = TokenizePath(path);
const CommandMapping* command_mapping =
GetMappingForPath(tokenized_request, kMatchVariables);
if (command_mapping == NULL) {
// No commands were registered to this path.
response_handler->UnknownCommand(path);
return;
}
CommandMapping::MethodToCommandMap::const_iterator command_it =
command_mapping->command_map.find(method);
if (command_it == command_mapping->command_map.end()) {
// The requested URL maps to a known command, but not with the requested
// method.
response_handler->InvalidCommandMethod(
method, GetAvailableMethods(command_mapping->command_map));
return;
}
// Convert any variable components of the path to a mapping.
PathVariableMapInternal path_variables;
PopulatePathVariableMap(&path_variables, tokenized_request,
command_mapping->path_components);
PathVariableMap path_variable_map(path_variables);
// Create a new CommandResultHandler that will be passed to the Command
// callback, and run the callback.
scoped_ptr<CommandResultHandler> result_handler(
new CommandResultHandlerImpl(response_handler.Pass()));
command_it->second.Run(request_value.get(), &path_variable_map,
result_handler.Pass());
}
} // namespace webdriver
} // namespace cobalt