summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/chrome_tests.gypi4
-rw-r--r--chrome/test/webdriver/dispatch.cc221
-rw-r--r--chrome/test/webdriver/dispatch.h13
-rw-r--r--chrome/test/webdriver/dispatch_unittest.cc154
-rw-r--r--chrome/test/webdriver/http_response.cc135
-rw-r--r--chrome/test/webdriver/http_response.h80
-rw-r--r--chrome/test/webdriver/http_response_unittest.cc89
7 files changed, 563 insertions, 133 deletions
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index fc7eea0..4d8f57d 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -733,6 +733,8 @@
'test/webdriver/dispatch.h',
'test/webdriver/dispatch.cc',
'test/webdriver/error_codes.h',
+ 'test/webdriver/http_response.h',
+ 'test/webdriver/http_response.cc',
'test/webdriver/keymap.h',
'test/webdriver/keymap.cc',
'test/webdriver/session.h',
@@ -851,6 +853,8 @@
],
'sources': [
'../base/test/run_all_unittests.cc',
+ 'test/webdriver/dispatch_unittest.cc',
+ 'test/webdriver/http_response_unittest.cc',
'test/webdriver/utility_functions_unittest.cc',
'test/webdriver/webdriver_key_converter_unittest.cc',
],
diff --git a/chrome/test/webdriver/dispatch.cc b/chrome/test/webdriver/dispatch.cc
index aa83a2b..28a94da 100644
--- a/chrome/test/webdriver/dispatch.cc
+++ b/chrome/test/webdriver/dispatch.cc
@@ -14,6 +14,7 @@
#include "base/string_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
+#include "chrome/test/webdriver/http_response.h"
#include "chrome/test/webdriver/commands/command.h"
#include "chrome/test/webdriver/session_manager.h"
#include "chrome/test/webdriver/utility_functions.h"
@@ -22,119 +23,12 @@ namespace webdriver {
namespace {
-// The standard HTTP Status codes are implemented below. Chrome uses
-// OK, See Other, Not Found, Method Not Allowed, and Internal Error.
-// Internal Error, HTTP 500, is used as a catch all for any issue
-// not covered in the JSON protocol.
-void SendHttpOk(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const std::string json = response.ToJSON();
- std::ostringstream out;
- out << "HTTP/1.1 200 OK\r\n"
- << "Content-Length: " << strlen(json.c_str()) << "\r\n"
- << "Content-Type: application/json; charset=UTF-8\r\n"
- << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n"
- << "Accept-Ranges: bytes\r\n"
- << "Connection: close\r\n\r\n";
- if (strcmp(request_info->request_method, "HEAD") != 0)
- out << json << "\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
-}
-
-void SendHttpSeeOther(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const Value* value = response.value();
- CheckValueType(Value::TYPE_STRING, value);
- std::string location;
- if (!value->GetAsString(&location)) {
- NOTREACHED();
- }
-
- std::ostringstream out;
- out << "HTTP/1.1 303 See Other\r\n"
- << "Location: " << location << "\r\n"
- << "Content-Type: text/html\r\n"
- << "Content-Length: 0\r\n\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
-}
-
-void SendHttpBadRequest(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const std::string json = response.ToJSON();
- std::ostringstream out;
- out << "HTTP/1.1 400 Bad Request\r\n"
- << "Content-Length: " << strlen(json.c_str()) << "\r\n"
- << "Content-Type: application/json; charset=UTF-8\r\n"
- << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n"
- << "Accept-Ranges: bytes\r\n"
- << "Connection: close\r\n\r\n";
- if (strcmp(request_info->request_method, "HEAD") != 0)
- out << json << "\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
-}
-
-void SendHttpNotFound(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const std::string json = response.ToJSON();
- std::ostringstream out;
- out << "HTTP/1.1 404 Not Found\r\n"
- << "Content-Length: " << strlen(json.c_str()) << "\r\n"
- << "Content-Type: application/json; charset=UTF-8\r\n"
- << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n"
- << "Accept-Ranges: bytes\r\n"
- << "Connection: close\r\n\r\n";
- if (strcmp(request_info->request_method, "HEAD") != 0)
- out << json << "\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
-}
-
-void SendHttpMethodNotAllowed(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const Value* value = response.value();
- CheckValueType(Value::TYPE_LIST, value);
-
- std::vector<std::string> allowed_methods;
- const ListValue* list_value = static_cast<const ListValue*>(value);
- for (size_t i = 0; i < list_value->GetSize(); ++i) {
- std::string method;
- LOG_IF(WARNING, list_value->GetString(i, &method))
- << "Ignoring non-string value at index " << i;
- allowed_methods.push_back(method);
- }
-
- std::ostringstream out;
- out << "HTTP/1.1 405 Method Not Allowed\r\n"
- << "Content-Type: text/html\r\n"
- << "Content-Length: 0\r\n"
- << "Allow: " << JoinString(allowed_methods, ',') << "\r\n\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
-}
-
-void SendHttpInternalError(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- const std::string json = response.ToJSON();
- std::ostringstream out;
- out << "HTTP/1.1 500 Internal Server Error\r\n"
- << "Content-Length: " << strlen(json.c_str()) << "\r\n"
- << "Content-Type: application/json; charset=UTF-8\r\n"
- << "Vary: Accept-Charset, Accept-Encoding, Accept-Language, Accept\r\n"
- << "Accept-Ranges: bytes\r\n"
- << "Connection: close\r\n\r\n";
- if (strcmp(request_info->request_method, "HEAD") != 0)
- out << json << "\r\n";
- VLOG(1) << out.str();
- mg_printf(connection, "%s", out.str().c_str());
+bool ForbidsMessageBody(const std::string& request_method,
+ const HttpResponse& response) {
+ return request_method == "HEAD" ||
+ response.status() == HttpResponse::kNoContent ||
+ response.status() == HttpResponse::kNotModified ||
+ (response.status() >= 100 && response.status() < 200);
}
void DispatchCommand(Command* const command,
@@ -158,37 +52,102 @@ void DispatchCommand(Command* const command,
namespace internal {
-void SendResponse(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
- const Response& response) {
- switch (response.status()) {
+void PrepareHttpResponse(const Response& command_response,
+ HttpResponse* const http_response) {
+ switch (command_response.status()) {
case kSuccess:
- SendHttpOk(connection, request_info, response);
+ http_response->set_status(HttpResponse::kOk);
break;
- case kSeeOther:
- SendHttpSeeOther(connection, request_info, response);
+ // TODO(jleyba): kSeeOther, kBadRequest, kSessionNotFound,
+ // and kMethodNotAllowed should be detected before creating
+ // a command_response, and should thus not need conversion.
+ case kSeeOther: {
+ const Value* const value = command_response.value();
+ std::string location;
+ if (!value->GetAsString(&location)) {
+ // This should never happen.
+ http_response->set_status(HttpResponse::kInternalServerError);
+ http_response->SetBody("Unable to set 'Location' header: response "
+ "value is not a string: " +
+ command_response.ToJSON());
+ return;
+ }
+ http_response->AddHeader("Location", location);
+ http_response->set_status(HttpResponse::kSeeOther);
break;
+ }
case kBadRequest:
- SendHttpBadRequest(connection, request_info, response);
- break;
-
case kSessionNotFound:
- SendHttpNotFound(connection, request_info, response);
+ http_response->set_status(command_response.status());
break;
- case kMethodNotAllowed:
- SendHttpMethodNotAllowed(connection, request_info, response);
+ case kMethodNotAllowed: {
+ const Value* const value = command_response.value();
+ if (!value->IsType(Value::TYPE_LIST)) {
+ // This should never happen.
+ http_response->set_status(HttpResponse::kInternalServerError);
+ http_response->SetBody(
+ "Unable to set 'Allow' header: response value was "
+ "not a list of strings: " + command_response.ToJSON());
+ return;
+ }
+
+ const ListValue* const list_value =
+ static_cast<const ListValue* const>(value);
+ std::vector<std::string> allowed_methods;
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ std::string method;
+ if (list_value->GetString(i, &method)) {
+ allowed_methods.push_back(method);
+ } else {
+ // This should never happen.
+ http_response->set_status(HttpResponse::kInternalServerError);
+ http_response->SetBody(
+ "Unable to set 'Allow' header: response value was "
+ "not a list of strings: " + command_response.ToJSON());
+ return;
+ }
+ }
+ http_response->AddHeader("Allow", JoinString(allowed_methods, ','));
+ http_response->set_status(HttpResponse::kMethodNotAllowed);
break;
+ }
- // All other errors should be treated as generic 500s. The client will be
- // responsible for inspecting the message body for details.
- case kInternalServerError:
- default:
- SendHttpInternalError(connection, request_info, response);
+ // All other errors should be treated as generic 500s. The client
+ // will be responsible for inspecting the message body for details.
+ case kInternalServerError:
+ default:
+ http_response->set_status(HttpResponse::kInternalServerError);
break;
}
+
+ http_response->SetMimeType("application/json; charset=utf-8");
+ http_response->SetBody(command_response.ToJSON());
+}
+
+void SendResponse(struct mg_connection* const connection,
+ const std::string& request_method,
+ const Response& response) {
+ HttpResponse http_response;
+ PrepareHttpResponse(response, &http_response);
+
+ std::string message_header = base::StringPrintf("HTTP/1.1 %d %s\r\n",
+ http_response.status(), http_response.GetReasonPhrase().c_str());
+
+ typedef HttpResponse::HeaderMap::const_iterator HeaderIter;
+ for (HeaderIter header = http_response.headers()->begin();
+ header != http_response.headers()->end();
+ ++header) {
+ message_header.append(base::StringPrintf("%s:%s\r\n",
+ header->first.c_str(), header->second.c_str()));
+ }
+ message_header.append("\r\n");
+
+ mg_write(connection, message_header.data(), message_header.length());
+ if (!ForbidsMessageBody(request_method, http_response))
+ mg_write(connection, http_response.data(), http_response.length());
}
bool ParseRequestInfo(const struct mg_request_info* const request_info,
diff --git a/chrome/test/webdriver/dispatch.h b/chrome/test/webdriver/dispatch.h
index 817dd7c..c119e9c 100644
--- a/chrome/test/webdriver/dispatch.h
+++ b/chrome/test/webdriver/dispatch.h
@@ -16,14 +16,20 @@ class DictionaryValue;
namespace webdriver {
class Command;
+class HttpResponse;
namespace internal {
+// Converts a |Response| into a |HttpResponse| to be returned to the client.
+// This function is exposed for testing.
+void PrepareHttpResponse(const Response& command_response,
+ HttpResponse* const http_response);
+
// Sends a |response| to a WebDriver command back to the client.
// |connection| is the communication pipe to the HTTP server and
// |request_info| contains any data sent by the user.
void SendResponse(struct mg_connection* const connection,
- const struct mg_request_info* const request_info,
+ const std::string& request_method,
const Response& response);
// Parses the request info and returns whether parsing was successful. If not,
@@ -62,7 +68,10 @@ void Dispatch(struct mg_connection* connection,
method,
&response);
}
- internal::SendResponse(connection, request_info, response);
+
+ internal::SendResponse(connection,
+ request_info->request_method,
+ response);
}
} // namespace webdriver
diff --git a/chrome/test/webdriver/dispatch_unittest.cc b/chrome/test/webdriver/dispatch_unittest.cc
new file mode 100644
index 0000000..c724041
--- /dev/null
+++ b/chrome/test/webdriver/dispatch_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright (c) 2011 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 "base/format_macros.h"
+#include "base/scoped_ptr.h"
+#include "base/stringprintf.h"
+#include "base/values.h"
+#include "base/json/json_reader.h"
+#include "chrome/test/webdriver/dispatch.h"
+#include "chrome/test/webdriver/http_response.h"
+#include "chrome/test/webdriver/commands/response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webdriver {
+
+namespace {
+
+void ExpectHeaderValue(const HttpResponse& response, const std::string& name,
+ const std::string& expected_value) {
+ std::string actual_value;
+ EXPECT_TRUE(response.GetHeader(name, &actual_value));
+ EXPECT_EQ(expected_value, actual_value);
+}
+
+void ExpectHttpStatus(int expected_status,
+ const Response& command_response,
+ HttpResponse* const http_response) {
+ internal::PrepareHttpResponse(command_response, http_response);
+ EXPECT_EQ(expected_status, http_response->status());
+}
+
+void ExpectInternalError(ErrorCode command_status,
+ Response* command_response,
+ HttpResponse* const http_response) {
+ command_response->set_status(command_status);
+ http_response->set_status(HttpResponse::kOk); // Reset to detect changes.
+ ExpectHttpStatus(HttpResponse::kInternalServerError,
+ *command_response, http_response);
+}
+
+} // namespace
+
+TEST(DispatchTest, CorrectlyConvertsResponseCodesToHttpStatusCodes) {
+ HttpResponse http_response;
+
+ Response command_response;
+ command_response.set_value(Value::CreateStringValue("foobar"));
+
+ command_response.set_status(kSuccess);
+ ExpectHttpStatus(HttpResponse::kOk, command_response, &http_response);
+
+ command_response.set_status(kSeeOther);
+ ExpectHttpStatus(HttpResponse::kSeeOther, command_response, &http_response);
+ ExpectHeaderValue(http_response, "location", "foobar");
+ http_response.ClearHeaders();
+
+ command_response.set_status(kBadRequest);
+ ExpectHttpStatus(HttpResponse::kBadRequest, command_response,
+ &http_response);
+
+ command_response.set_status(kSessionNotFound);
+ ExpectHttpStatus(HttpResponse::kNotFound, command_response,
+ &http_response);
+
+ ListValue* methods = new ListValue;
+ methods->Append(Value::CreateStringValue("POST"));
+ methods->Append(Value::CreateStringValue("GET"));
+ command_response.set_value(methods);
+ command_response.set_status(kMethodNotAllowed);
+ ExpectHttpStatus(HttpResponse::kMethodNotAllowed, command_response,
+ &http_response);
+ ExpectHeaderValue(http_response, "allow", "POST,GET");
+ http_response.ClearHeaders();
+
+ ExpectInternalError(kNoSuchElement, &command_response, &http_response);
+ ExpectInternalError(kNoSuchFrame, &command_response, &http_response);
+ ExpectInternalError(kUnknownCommand, &command_response, &http_response);
+ ExpectInternalError(kStaleElementReference, &command_response,
+ &http_response);
+ ExpectInternalError(kInvalidElementState, &command_response, &http_response);
+ ExpectInternalError(kUnknownError, &command_response, &http_response);
+ ExpectInternalError(kElementNotSelectable, &command_response,
+ &http_response);
+ ExpectInternalError(kXPathLookupError, &command_response, &http_response);
+ ExpectInternalError(kNoSuchWindow, &command_response, &http_response);
+ ExpectInternalError(kInvalidCookieDomain, &command_response, &http_response);
+ ExpectInternalError(kUnableToSetCookie, &command_response, &http_response);
+ ExpectInternalError(kInternalServerError, &command_response, &http_response);
+}
+
+TEST(DispatchTest,
+ ReturnsAnErrorOnNonStringMethodsListedOnAMethodNotAllowedResponse) {
+ ListValue* methods = new ListValue;
+ methods->Append(Value::CreateStringValue("POST"));
+ methods->Append(new DictionaryValue);
+ methods->Append(Value::CreateStringValue("GET"));
+ methods->Append(new DictionaryValue);
+ methods->Append(Value::CreateStringValue("DELETE"));
+
+ Response command_response;
+ command_response.set_status(kMethodNotAllowed);
+ command_response.set_value(methods);
+
+ HttpResponse http_response;
+ ExpectHttpStatus(HttpResponse::kInternalServerError, command_response,
+ &http_response);
+}
+
+TEST(DispatchTest, ReturnsCommandResponseAsJson) {
+ const std::string kExpectedData = "{\"status\":0,\"value\":\"foobar\"}";
+
+ Response command_response;
+ command_response.set_status(kSuccess);
+ command_response.set_value(Value::CreateStringValue("foobar"));
+
+ HttpResponse http_response;
+ internal::PrepareHttpResponse(command_response, &http_response);
+ EXPECT_EQ(HttpResponse::kOk, http_response.status());
+ ExpectHeaderValue(http_response, "content-type",
+ "application/json; charset=utf-8");
+ ExpectHeaderValue(http_response, "content-length",
+ base::StringPrintf("%"PRIuS, kExpectedData.length()));
+
+ // We do not know whether the response status or value will be
+ // encoded first, so we have to parse the response body to
+ // verify it is correct.
+ std::string actual_data(http_response.data(),
+ http_response.length());
+
+ int error_code;
+ std::string error_message;
+ scoped_ptr<Value> parsed_response(base::JSONReader::ReadAndReturnError(
+ actual_data, false, &error_code, &error_message));
+
+ ASSERT_TRUE(parsed_response.get() != NULL) << error_message;
+ ASSERT_TRUE(parsed_response->IsType(Value::TYPE_DICTIONARY))
+ << "Response should be a dictionary: " << actual_data;
+
+ DictionaryValue* dict = static_cast<DictionaryValue*>(parsed_response.get());
+ EXPECT_EQ(2u, dict->size());
+ EXPECT_TRUE(dict->HasKey("status"));
+ EXPECT_TRUE(dict->HasKey("value"));
+
+ int status = -1;
+ EXPECT_TRUE(dict->GetInteger("status", &status));
+ EXPECT_EQ(kSuccess, static_cast<ErrorCode>(status));
+
+ std::string value;
+ EXPECT_TRUE(dict->GetStringASCII("value", &value));
+ EXPECT_EQ("foobar", value);
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/http_response.cc b/chrome/test/webdriver/http_response.cc
new file mode 100644
index 0000000..7757e52
--- /dev/null
+++ b/chrome/test/webdriver/http_response.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2011 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/webdriver/http_response.h"
+
+#include "base/format_macros.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+
+namespace webdriver {
+
+const int HttpResponse::kOk = 200;
+const int HttpResponse::kNoContent = 204;
+const int HttpResponse::kSeeOther = 303;
+const int HttpResponse::kNotModified = 304;
+const int HttpResponse::kBadRequest = 400;
+const int HttpResponse::kNotFound = 404;
+const int HttpResponse::kMethodNotAllowed = 405;
+const int HttpResponse::kInternalServerError = 500;
+
+HttpResponse::HttpResponse()
+ : status_(kOk) {
+}
+
+HttpResponse::~HttpResponse() {
+}
+
+void HttpResponse::AddHeader(const std::string& name,
+ const std::string& value) {
+ std::string lower_case_name(StringToLowerASCII(name));
+ HeaderMap::iterator header = headers_.find(lower_case_name);
+ if (header == headers_.end()) {
+ headers_[lower_case_name] = value;
+ } else {
+ header->second.append("," + value);
+ }
+}
+
+bool HttpResponse::GetHeader(const std::string& name,
+ std::string* value) const {
+ std::string lower_case_name(StringToLowerASCII(name));
+ HeaderMap::const_iterator header = headers_.find(lower_case_name);
+
+ if (header == headers_.end()) {
+ return false;
+ }
+
+ if (value) {
+ *value = header->second;
+ }
+
+ return true;
+}
+
+bool HttpResponse::RemoveHeader(const std::string& name) {
+ std::string lower_case_name(StringToLowerASCII(name));
+ HeaderMap::iterator header = headers_.find(lower_case_name);
+
+ if (header == headers_.end()) {
+ return false;
+ }
+
+ headers_.erase(header);
+ return true;
+}
+
+void HttpResponse::ClearHeaders() {
+ headers_.clear();
+}
+
+void HttpResponse::UpdateHeader(const std::string& name,
+ const std::string& new_value) {
+ RemoveHeader(name);
+ AddHeader(name, new_value);
+}
+
+void HttpResponse::SetMimeType(const std::string& mime_type) {
+ UpdateHeader("Content-Type", mime_type);
+}
+
+void HttpResponse::SetBody(const std::string& data) {
+ SetBody(data.data(), data.length());
+}
+
+void HttpResponse::SetBody(const char* const data, size_t length) {
+ data_ = std::string(data, length);
+ UpdateHeader("Content-Length",
+ base::StringPrintf("%"PRIuS"", data_.length()));
+}
+
+std::string HttpResponse::GetReasonPhrase() const {
+ switch (status_) {
+ case kOk:
+ return "OK";
+ case kNoContent:
+ return "No Content";
+ case kSeeOther:
+ return "See Other";
+ case kNotModified:
+ return "Not Modified";
+ case kBadRequest:
+ return "Bad Request";
+ case kNotFound:
+ return "Not Found";
+ case kMethodNotAllowed:
+ return "Method Not Allowed";
+ case kInternalServerError:
+ return "Internal Server Error";
+ default:
+ return "Unknown";
+ }
+}
+
+int HttpResponse::status() const {
+ return status_;
+}
+
+void HttpResponse::set_status(int status) {
+ status_ = status;
+}
+
+const HttpResponse::HeaderMap* const HttpResponse::headers() const {
+ return &headers_;
+}
+
+const char* const HttpResponse::data() const {
+ return data_.data();
+}
+
+size_t HttpResponse::length() const {
+ return data_.length();
+}
+
+} // namespace webdriver
diff --git a/chrome/test/webdriver/http_response.h b/chrome/test/webdriver/http_response.h
new file mode 100644
index 0000000..9139d92
--- /dev/null
+++ b/chrome/test/webdriver/http_response.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2011 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 CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_
+#define CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace webdriver {
+
+class HttpResponse {
+ public:
+ typedef std::map<std::string, std::string> HeaderMap;
+
+ // The supported HTTP response codes.
+ static const int kOk;
+ static const int kNoContent;
+ static const int kSeeOther;
+ static const int kNotModified;
+ static const int kBadRequest;
+ static const int kNotFound;
+ static const int kMethodNotAllowed;
+ static const int kInternalServerError;
+
+ HttpResponse();
+ ~HttpResponse();
+
+ // Sets a header in this response. If a header with the same |name| already
+ // exists, the |value| will be appended to the existing values. Since header
+ // names are case insensitive, the header will be stored in lowercase format.
+ void AddHeader(const std::string& name, const std::string& value);
+
+ // Retrieves the value of the specified header. If there is no such header,
+ // the output |value| will not be modified and false will be returned.
+ bool GetHeader(const std::string& name, std::string* value) const;
+
+ // Removes the header with the given |name|. Returns whether there was a
+ // matching header to remove.
+ bool RemoveHeader(const std::string& name);
+
+ // Removes all headers.
+ void ClearHeaders();
+
+ // Convenience function for setting the Content-Type header for this response.
+ void SetMimeType(const std::string& mime_type);
+
+ // Sets the message body for this response; will also set the "Content-Length"
+ // message header.
+ void SetBody(const std::string& data);
+ void SetBody(const char* const data, size_t length);
+
+ // Returns the status phrase recommended by RFC 2616 section 6.1.1 for this
+ // response's status code. If the status code is not recognized, the default
+ // "Unknown" status phrase will be used.
+ std::string GetReasonPhrase() const;
+
+ int status() const;
+ void set_status(int status);
+ const HeaderMap* const headers() const;
+ const char* const data() const;
+ size_t length() const;
+
+ private:
+ void UpdateHeader(const std::string& name, const std::string& new_value);
+
+ int status_;
+ HeaderMap headers_;
+ std::string data_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpResponse);
+};
+
+} // webdriver
+
+#endif // CHROME_TEST_WEBDRIVER_HTTP_RESPONSE_H_
diff --git a/chrome/test/webdriver/http_response_unittest.cc b/chrome/test/webdriver/http_response_unittest.cc
new file mode 100644
index 0000000..71be83f
--- /dev/null
+++ b/chrome/test/webdriver/http_response_unittest.cc
@@ -0,0 +1,89 @@
+// Copyright (c) 2011 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/webdriver/http_response.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webdriver {
+
+namespace {
+
+void ExpectHeaderValue(const HttpResponse& response, const std::string& name,
+ const std::string& expected_value) {
+ std::string actual_value;
+ EXPECT_TRUE(response.GetHeader(name, &actual_value));
+ EXPECT_EQ(expected_value, actual_value);
+}
+
+} // namespace
+
+TEST(HttpResponseTest, AddingHeaders) {
+ HttpResponse response;
+
+ response.AddHeader("FOO", "a");
+ ExpectHeaderValue(response, "foo", "a");
+
+ // Headers should be case insensitive.
+ response.AddHeader("fOo", "b,c");
+ response.AddHeader("FoO", "d");
+ ExpectHeaderValue(response, "foo", "a,b,c,d");
+}
+
+TEST(HttpResponseTest, RemovingHeaders) {
+ HttpResponse response;
+
+ ASSERT_FALSE(response.RemoveHeader("I-Am-Not-There"));
+
+ ASSERT_FALSE(response.GetHeader("foo", NULL));
+ response.AddHeader("foo", "bar");
+ ASSERT_TRUE(response.GetHeader("foo", NULL));
+ ASSERT_TRUE(response.RemoveHeader("foo"));
+ ASSERT_FALSE(response.GetHeader("foo", NULL));
+}
+
+TEST(HttpResponseTest, CanClearAllHeaders) {
+ HttpResponse response;
+ response.AddHeader("food", "cheese");
+ response.AddHeader("color", "red");
+
+ ExpectHeaderValue(response, "food", "cheese");
+ ExpectHeaderValue(response, "color", "red");
+
+ response.ClearHeaders();
+ EXPECT_FALSE(response.GetHeader("food", NULL));
+ EXPECT_FALSE(response.GetHeader("color", NULL));
+}
+
+TEST(HttpResponseTest, CanSetMimeType) {
+ HttpResponse response;
+
+ response.SetMimeType("application/json");
+ ExpectHeaderValue(response, "content-type", "application/json");
+
+ response.SetMimeType("text/html");
+ ExpectHeaderValue(response, "content-type", "text/html");
+}
+
+TEST(HttpResponseTest, SetBody) {
+ HttpResponse response;
+
+ std::string body("foo bar");
+ response.SetBody(body);
+ ASSERT_EQ(body.length(), response.length());
+ ASSERT_EQ(body, std::string(response.data(), response.length()));
+
+ // Grow the response size.
+ body.append(" baz");
+ response.SetBody(body);
+ ASSERT_EQ(body.length(), response.length());
+ ASSERT_EQ(body, std::string(response.data(), response.length()));
+
+ // Shrink the response size.
+ body = "small";
+ response.SetBody(body);
+ ASSERT_EQ(body.length(), response.length());
+ ASSERT_EQ(body, std::string(response.data(), response.length()));
+}
+
+} // namespace webdriver