http_transport_fake.cc revision 9ed0cab99f18acb3570a35e9408f24355f6b8324
1// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <brillo/http/http_transport_fake.h>
6
7#include <utility>
8
9#include <base/json/json_reader.h>
10#include <base/json/json_writer.h>
11#include <base/logging.h>
12#include <brillo/bind_lambda.h>
13#include <brillo/http/http_connection_fake.h>
14#include <brillo/http/http_request.h>
15#include <brillo/mime_utils.h>
16#include <brillo/streams/memory_stream.h>
17#include <brillo/strings/string_utils.h>
18#include <brillo/url_utils.h>
19
20namespace brillo {
21
22using http::fake::Transport;
23using http::fake::ServerRequestResponseBase;
24using http::fake::ServerRequest;
25using http::fake::ServerResponse;
26
27Transport::Transport() {
28  VLOG(1) << "fake::Transport created";
29}
30
31Transport::~Transport() {
32  VLOG(1) << "fake::Transport destroyed";
33}
34
35std::shared_ptr<http::Connection> Transport::CreateConnection(
36    const std::string& url,
37    const std::string& method,
38    const HeaderList& headers,
39    const std::string& user_agent,
40    const std::string& referer,
41    brillo::ErrorPtr* error) {
42  std::shared_ptr<http::Connection> connection;
43  if (create_connection_error_) {
44    if (error)
45      *error = std::move(create_connection_error_);
46    return connection;
47  }
48  HeaderList headers_copy = headers;
49  if (!user_agent.empty()) {
50    headers_copy.push_back(
51        std::make_pair(http::request_header::kUserAgent, user_agent));
52  }
53  if (!referer.empty()) {
54    headers_copy.push_back(
55        std::make_pair(http::request_header::kReferer, referer));
56  }
57  connection =
58      std::make_shared<http::fake::Connection>(url, method, shared_from_this());
59  CHECK(connection) << "Unable to create Connection object";
60  if (!connection->SendHeaders(headers_copy, error))
61    connection.reset();
62  request_count_++;
63  return connection;
64}
65
66void Transport::RunCallbackAsync(const tracked_objects::Location& from_here,
67                                 const base::Closure& callback) {
68  if (!async_) {
69    callback.Run();
70    return;
71  }
72  async_callback_queue_.push(callback);
73}
74
75bool Transport::HandleOneAsyncRequest() {
76  if (async_callback_queue_.empty())
77    return false;
78
79  base::Closure callback = async_callback_queue_.front();
80  async_callback_queue_.pop();
81  callback.Run();
82  return true;
83}
84
85void Transport::HandleAllAsyncRequests() {
86  while (!async_callback_queue_.empty())
87    HandleOneAsyncRequest();
88}
89
90http::RequestID Transport::StartAsyncTransfer(
91    http::Connection* connection,
92    const SuccessCallback& success_callback,
93    const ErrorCallback& error_callback) {
94  // Fake transport doesn't use this method.
95  LOG(FATAL) << "This method should not be called on fake transport";
96  return 0;
97}
98
99bool Transport::CancelRequest(RequestID request_id) {
100  return false;
101}
102
103void Transport::SetDefaultTimeout(base::TimeDelta timeout) {
104}
105
106static inline std::string GetHandlerMapKey(const std::string& url,
107                                           const std::string& method) {
108  return method + ":" + url;
109}
110
111void Transport::AddHandler(const std::string& url,
112                           const std::string& method,
113                           const HandlerCallback& handler) {
114  // Make sure we can override/replace existing handlers.
115  handlers_[GetHandlerMapKey(url, method)] = handler;
116}
117
118void Transport::AddSimpleReplyHandler(const std::string& url,
119                                      const std::string& method,
120                                      int status_code,
121                                      const std::string& reply_text,
122                                      const std::string& mime_type) {
123  auto handler = [status_code, reply_text, mime_type](
124      const ServerRequest& request, ServerResponse* response) {
125    response->ReplyText(status_code, reply_text, mime_type);
126  };
127  AddHandler(url, method, base::Bind(handler));
128}
129
130Transport::HandlerCallback Transport::GetHandler(
131    const std::string& url,
132    const std::string& method) const {
133  // First try the exact combination of URL/Method
134  auto p = handlers_.find(GetHandlerMapKey(url, method));
135  if (p != handlers_.end())
136    return p->second;
137  // If not found, try URL/*
138  p = handlers_.find(GetHandlerMapKey(url, "*"));
139  if (p != handlers_.end())
140    return p->second;
141  // If still not found, try */method
142  p = handlers_.find(GetHandlerMapKey("*", method));
143  if (p != handlers_.end())
144    return p->second;
145  // Finally, try */*
146  p = handlers_.find(GetHandlerMapKey("*", "*"));
147  return (p != handlers_.end()) ? p->second : HandlerCallback();
148}
149
150void ServerRequestResponseBase::SetData(StreamPtr stream) {
151  data_.clear();
152  if (stream) {
153    uint8_t buffer[1024];
154    size_t size = 0;
155    if (stream->CanGetSize())
156      data_.reserve(stream->GetRemainingSize());
157
158    do {
159      CHECK(stream->ReadBlocking(buffer, sizeof(buffer), &size, nullptr));
160      data_.insert(data_.end(), buffer, buffer + size);
161    } while (size > 0);
162  }
163}
164
165std::string ServerRequestResponseBase::GetDataAsString() const {
166  if (data_.empty())
167    return std::string();
168  auto chars = reinterpret_cast<const char*>(data_.data());
169  return std::string(chars, data_.size());
170}
171
172std::unique_ptr<base::DictionaryValue>
173ServerRequestResponseBase::GetDataAsJson() const {
174  if (brillo::mime::RemoveParameters(
175          GetHeader(request_header::kContentType)) ==
176      brillo::mime::application::kJson) {
177    auto value = base::JSONReader::Read(GetDataAsString());
178    if (value) {
179      base::DictionaryValue* dict = nullptr;
180      if (value->GetAsDictionary(&dict)) {
181        // |value| is now owned by |dict|.
182        base::IgnoreResult(value.release());
183        return std::unique_ptr<base::DictionaryValue>(dict);
184      }
185    }
186  }
187  return std::unique_ptr<base::DictionaryValue>();
188}
189
190std::string ServerRequestResponseBase::GetDataAsNormalizedJsonString() const {
191  std::string value;
192  // Make sure we serialize the JSON back without any pretty print so
193  // the string comparison works correctly.
194  auto json = GetDataAsJson();
195  if (json)
196    base::JSONWriter::Write(*json, &value);
197  return value;
198}
199
200void ServerRequestResponseBase::AddHeaders(const HeaderList& headers) {
201  for (const auto& pair : headers) {
202    if (pair.second.empty())
203      headers_.erase(pair.first);
204    else
205      headers_.insert(pair);
206  }
207}
208
209std::string ServerRequestResponseBase::GetHeader(
210    const std::string& header_name) const {
211  auto p = headers_.find(header_name);
212  return p != headers_.end() ? p->second : std::string();
213}
214
215ServerRequest::ServerRequest(const std::string& url, const std::string& method)
216    : method_(method) {
217  auto params = brillo::url::GetQueryStringParameters(url);
218  url_ = brillo::url::RemoveQueryString(url, true);
219  form_fields_.insert(params.begin(), params.end());
220}
221
222std::string ServerRequest::GetFormField(const std::string& field_name) const {
223  if (!form_fields_parsed_) {
224    std::string mime_type = brillo::mime::RemoveParameters(
225        GetHeader(request_header::kContentType));
226    if (mime_type == brillo::mime::application::kWwwFormUrlEncoded &&
227        !GetData().empty()) {
228      auto fields = brillo::data_encoding::WebParamsDecode(GetDataAsString());
229      form_fields_.insert(fields.begin(), fields.end());
230    }
231    form_fields_parsed_ = true;
232  }
233  auto p = form_fields_.find(field_name);
234  return p != form_fields_.end() ? p->second : std::string();
235}
236
237void ServerResponse::Reply(int status_code,
238                           const void* data,
239                           size_t data_size,
240                           const std::string& mime_type) {
241  data_.clear();
242  status_code_ = status_code;
243  SetData(MemoryStream::OpenCopyOf(data, data_size, nullptr));
244  AddHeaders({{response_header::kContentLength,
245               brillo::string_utils::ToString(data_size)},
246              {response_header::kContentType, mime_type}});
247}
248
249void ServerResponse::ReplyText(int status_code,
250                               const std::string& text,
251                               const std::string& mime_type) {
252  Reply(status_code, text.data(), text.size(), mime_type);
253}
254
255void ServerResponse::ReplyJson(int status_code, const base::Value* json) {
256  std::string text;
257  base::JSONWriter::WriteWithOptions(
258      *json, base::JSONWriter::OPTIONS_PRETTY_PRINT, &text);
259  std::string mime_type =
260      brillo::mime::AppendParameter(brillo::mime::application::kJson,
261                                      brillo::mime::parameters::kCharset,
262                                      "utf-8");
263  ReplyText(status_code, text, mime_type);
264}
265
266void ServerResponse::ReplyJson(int status_code,
267                               const http::FormFieldList& fields) {
268  base::DictionaryValue json;
269  for (const auto& pair : fields) {
270    json.SetString(pair.first, pair.second);
271  }
272  ReplyJson(status_code, &json);
273}
274
275std::string ServerResponse::GetStatusText() const {
276  static std::vector<std::pair<int, const char*>> status_text_map = {
277      {100, "Continue"},
278      {101, "Switching Protocols"},
279      {102, "Processing"},
280      {200, "OK"},
281      {201, "Created"},
282      {202, "Accepted"},
283      {203, "Non-Authoritative Information"},
284      {204, "No Content"},
285      {205, "Reset Content"},
286      {206, "Partial Content"},
287      {207, "Multi-Status"},
288      {208, "Already Reported"},
289      {226, "IM Used"},
290      {300, "Multiple Choices"},
291      {301, "Moved Permanently"},
292      {302, "Found"},
293      {303, "See Other"},
294      {304, "Not Modified"},
295      {305, "Use Proxy"},
296      {306, "Switch Proxy"},
297      {307, "Temporary Redirect"},
298      {308, "Permanent Redirect"},
299      {400, "Bad Request"},
300      {401, "Unauthorized"},
301      {402, "Payment Required"},
302      {403, "Forbidden"},
303      {404, "Not Found"},
304      {405, "Method Not Allowed"},
305      {406, "Not Acceptable"},
306      {407, "Proxy Authentication Required"},
307      {408, "Request Timeout"},
308      {409, "Conflict"},
309      {410, "Gone"},
310      {411, "Length Required"},
311      {412, "Precondition Failed"},
312      {413, "Request Entity Too Large"},
313      {414, "Request - URI Too Long"},
314      {415, "Unsupported Media Type"},
315      {429, "Too Many Requests"},
316      {431, "Request Header Fields Too Large"},
317      {500, "Internal Server Error"},
318      {501, "Not Implemented"},
319      {502, "Bad Gateway"},
320      {503, "Service Unavailable"},
321      {504, "Gateway Timeout"},
322      {505, "HTTP Version Not Supported"},
323  };
324
325  for (const auto& pair : status_text_map) {
326    if (pair.first == status_code_)
327      return pair.second;
328  }
329  return std::string();
330}
331
332}  // namespace brillo
333