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_utils.h>
6
7#include <algorithm>
8
9#include <base/bind.h>
10#include <base/json/json_reader.h>
11#include <base/json/json_writer.h>
12#include <base/values.h>
13#include <brillo/data_encoding.h>
14#include <brillo/errors/error_codes.h>
15#include <brillo/mime_utils.h>
16#include <brillo/streams/memory_stream.h>
17
18using brillo::mime::AppendParameter;
19using brillo::mime::RemoveParameters;
20
21namespace brillo {
22namespace http {
23
24std::unique_ptr<Response> GetAndBlock(const std::string& url,
25                                      const HeaderList& headers,
26                                      std::shared_ptr<Transport> transport,
27                                      brillo::ErrorPtr* error) {
28  return SendRequestWithNoDataAndBlock(
29      request_type::kGet, url, headers, transport, error);
30}
31
32RequestID Get(const std::string& url,
33              const HeaderList& headers,
34              std::shared_ptr<Transport> transport,
35              const SuccessCallback& success_callback,
36              const ErrorCallback& error_callback) {
37  return SendRequestWithNoData(request_type::kGet,
38                               url,
39                               headers,
40                               transport,
41                               success_callback,
42                               error_callback);
43}
44
45std::unique_ptr<Response> HeadAndBlock(const std::string& url,
46                                       std::shared_ptr<Transport> transport,
47                                       brillo::ErrorPtr* error) {
48  return SendRequestWithNoDataAndBlock(
49      request_type::kHead, url, {}, transport, error);
50}
51
52RequestID Head(const std::string& url,
53               std::shared_ptr<Transport> transport,
54               const SuccessCallback& success_callback,
55               const ErrorCallback& error_callback) {
56  return SendRequestWithNoData(request_type::kHead,
57                               url,
58                               {},
59                               transport,
60                               success_callback,
61                               error_callback);
62}
63
64std::unique_ptr<Response> PostTextAndBlock(const std::string& url,
65                                           const std::string& data,
66                                           const std::string& mime_type,
67                                           const HeaderList& headers,
68                                           std::shared_ptr<Transport> transport,
69                                           brillo::ErrorPtr* error) {
70  return PostBinaryAndBlock(
71      url, data.data(), data.size(), mime_type, headers, transport, error);
72}
73
74RequestID PostText(const std::string& url,
75                   const std::string& data,
76                   const std::string& mime_type,
77                   const HeaderList& headers,
78                   std::shared_ptr<Transport> transport,
79                   const SuccessCallback& success_callback,
80                   const ErrorCallback& error_callback) {
81  return PostBinary(url,
82                    data.data(),
83                    data.size(),
84                    mime_type,
85                    headers,
86                    transport,
87                    success_callback,
88                    error_callback);
89}
90
91std::unique_ptr<Response> SendRequestAndBlock(
92    const std::string& method,
93    const std::string& url,
94    const void* data,
95    size_t data_size,
96    const std::string& mime_type,
97    const HeaderList& headers,
98    std::shared_ptr<Transport> transport,
99    brillo::ErrorPtr* error) {
100  Request request(url, method, transport);
101  request.AddHeaders(headers);
102  if (data_size > 0) {
103    CHECK(!mime_type.empty()) << "MIME type must be specified if request body "
104                                 "message is provided";
105    request.SetContentType(mime_type);
106    if (!request.AddRequestBody(data, data_size, error))
107      return std::unique_ptr<Response>();
108  }
109  return request.GetResponseAndBlock(error);
110}
111
112std::unique_ptr<Response> SendRequestWithNoDataAndBlock(
113    const std::string& method,
114    const std::string& url,
115    const HeaderList& headers,
116    std::shared_ptr<Transport> transport,
117    brillo::ErrorPtr* error) {
118  return SendRequestAndBlock(
119      method, url, nullptr, 0, {}, headers, transport, error);
120}
121
122RequestID SendRequest(const std::string& method,
123                      const std::string& url,
124                      StreamPtr stream,
125                      const std::string& mime_type,
126                      const HeaderList& headers,
127                      std::shared_ptr<Transport> transport,
128                      const SuccessCallback& success_callback,
129                      const ErrorCallback& error_callback) {
130  Request request(url, method, transport);
131  request.AddHeaders(headers);
132  if (stream && (!stream->CanGetSize() || stream->GetRemainingSize() > 0)) {
133    CHECK(!mime_type.empty()) << "MIME type must be specified if request body "
134                                 "message is provided";
135    request.SetContentType(mime_type);
136    brillo::ErrorPtr error;
137    if (!request.AddRequestBody(std::move(stream), &error)) {
138      transport->RunCallbackAsync(
139          FROM_HERE, base::Bind(error_callback,
140                                0, base::Owned(error.release())));
141      return 0;
142    }
143  }
144  return request.GetResponse(success_callback, error_callback);
145}
146
147RequestID SendRequest(const std::string& method,
148                      const std::string& url,
149                      const void* data,
150                      size_t data_size,
151                      const std::string& mime_type,
152                      const HeaderList& headers,
153                      std::shared_ptr<Transport> transport,
154                      const SuccessCallback& success_callback,
155                      const ErrorCallback& error_callback) {
156  return SendRequest(method,
157                     url,
158                     MemoryStream::OpenCopyOf(data, data_size, nullptr),
159                     mime_type,
160                     headers,
161                     transport,
162                     success_callback,
163                     error_callback);
164}
165
166RequestID SendRequestWithNoData(const std::string& method,
167                                const std::string& url,
168                                const HeaderList& headers,
169                                std::shared_ptr<Transport> transport,
170                                const SuccessCallback& success_callback,
171                                const ErrorCallback& error_callback) {
172  return SendRequest(method,
173                     url,
174                     {},
175                     {},
176                     headers,
177                     transport,
178                     success_callback,
179                     error_callback);
180}
181
182std::unique_ptr<Response> PostBinaryAndBlock(
183    const std::string& url,
184    const void* data,
185    size_t data_size,
186    const std::string& mime_type,
187    const HeaderList& headers,
188    std::shared_ptr<Transport> transport,
189    brillo::ErrorPtr* error) {
190  return SendRequestAndBlock(request_type::kPost,
191                             url,
192                             data,
193                             data_size,
194                             mime_type,
195                             headers,
196                             transport,
197                             error);
198}
199
200RequestID PostBinary(const std::string& url,
201                     StreamPtr stream,
202                     const std::string& mime_type,
203                     const HeaderList& headers,
204                     std::shared_ptr<Transport> transport,
205                     const SuccessCallback& success_callback,
206                     const ErrorCallback& error_callback) {
207  return SendRequest(request_type::kPost,
208                     url,
209                     std::move(stream),
210                     mime_type,
211                     headers,
212                     transport,
213                     success_callback,
214                     error_callback);
215}
216
217RequestID PostBinary(const std::string& url,
218                     const void* data,
219                     size_t data_size,
220                     const std::string& mime_type,
221                     const HeaderList& headers,
222                     std::shared_ptr<Transport> transport,
223                     const SuccessCallback& success_callback,
224                     const ErrorCallback& error_callback) {
225  return SendRequest(request_type::kPost,
226                     url,
227                     data,
228                     data_size,
229                     mime_type,
230                     headers,
231                     transport,
232                     success_callback,
233                     error_callback);
234}
235
236std::unique_ptr<Response> PostFormDataAndBlock(
237    const std::string& url,
238    const FormFieldList& data,
239    const HeaderList& headers,
240    std::shared_ptr<Transport> transport,
241    brillo::ErrorPtr* error) {
242  std::string encoded_data = brillo::data_encoding::WebParamsEncode(data);
243  return PostBinaryAndBlock(url,
244                            encoded_data.c_str(),
245                            encoded_data.size(),
246                            brillo::mime::application::kWwwFormUrlEncoded,
247                            headers,
248                            transport,
249                            error);
250}
251
252std::unique_ptr<Response> PostFormDataAndBlock(
253    const std::string& url,
254    std::unique_ptr<FormData> form_data,
255    const HeaderList& headers,
256    std::shared_ptr<Transport> transport,
257    brillo::ErrorPtr* error) {
258  Request request(url, request_type::kPost, transport);
259  request.AddHeaders(headers);
260  if (!request.AddRequestBodyAsFormData(std::move(form_data), error))
261    return std::unique_ptr<Response>();
262  return request.GetResponseAndBlock(error);
263}
264
265RequestID PostFormData(const std::string& url,
266                       const FormFieldList& data,
267                       const HeaderList& headers,
268                       std::shared_ptr<Transport> transport,
269                       const SuccessCallback& success_callback,
270                       const ErrorCallback& error_callback) {
271  std::string encoded_data = brillo::data_encoding::WebParamsEncode(data);
272  return PostBinary(url,
273                    encoded_data.c_str(),
274                    encoded_data.size(),
275                    brillo::mime::application::kWwwFormUrlEncoded,
276                    headers,
277                    transport,
278                    success_callback,
279                    error_callback);
280}
281
282RequestID PostFormData(const std::string& url,
283                       std::unique_ptr<FormData> form_data,
284                       const HeaderList& headers,
285                       std::shared_ptr<Transport> transport,
286                       const SuccessCallback& success_callback,
287                       const ErrorCallback& error_callback) {
288  Request request(url, request_type::kPost, transport);
289  request.AddHeaders(headers);
290  brillo::ErrorPtr error;
291  if (!request.AddRequestBodyAsFormData(std::move(form_data), &error)) {
292    transport->RunCallbackAsync(
293        FROM_HERE, base::Bind(error_callback, 0, base::Owned(error.release())));
294    return 0;
295  }
296  return request.GetResponse(success_callback, error_callback);
297}
298
299std::unique_ptr<Response> PostJsonAndBlock(const std::string& url,
300                                           const base::Value* json,
301                                           const HeaderList& headers,
302                                           std::shared_ptr<Transport> transport,
303                                           brillo::ErrorPtr* error) {
304  std::string data;
305  if (json)
306    base::JSONWriter::Write(*json, &data);
307  std::string mime_type = AppendParameter(brillo::mime::application::kJson,
308                                          brillo::mime::parameters::kCharset,
309                                          "utf-8");
310  return PostBinaryAndBlock(
311      url, data.c_str(), data.size(), mime_type, headers, transport, error);
312}
313
314RequestID PostJson(const std::string& url,
315                   std::unique_ptr<base::Value> json,
316                   const HeaderList& headers,
317                   std::shared_ptr<Transport> transport,
318                   const SuccessCallback& success_callback,
319                   const ErrorCallback& error_callback) {
320  std::string data;
321  if (json)
322    base::JSONWriter::Write(*json, &data);
323  std::string mime_type = AppendParameter(brillo::mime::application::kJson,
324                                          brillo::mime::parameters::kCharset,
325                                          "utf-8");
326  return PostBinary(url,
327                    data.c_str(),
328                    data.size(),
329                    mime_type,
330                    headers,
331                    transport,
332                    success_callback,
333                    error_callback);
334}
335
336std::unique_ptr<Response> PatchJsonAndBlock(
337    const std::string& url,
338    const base::Value* json,
339    const HeaderList& headers,
340    std::shared_ptr<Transport> transport,
341    brillo::ErrorPtr* error) {
342  std::string data;
343  if (json)
344    base::JSONWriter::Write(*json, &data);
345  std::string mime_type = AppendParameter(brillo::mime::application::kJson,
346                                          brillo::mime::parameters::kCharset,
347                                          "utf-8");
348  return SendRequestAndBlock(request_type::kPatch,
349                             url,
350                             data.c_str(),
351                             data.size(),
352                             mime_type,
353                             headers,
354                             transport,
355                             error);
356}
357
358RequestID PatchJson(const std::string& url,
359                    std::unique_ptr<base::Value> json,
360                    const HeaderList& headers,
361                    std::shared_ptr<Transport> transport,
362                    const SuccessCallback& success_callback,
363                    const ErrorCallback& error_callback) {
364  std::string data;
365  if (json)
366    base::JSONWriter::Write(*json, &data);
367  std::string mime_type =
368      AppendParameter(brillo::mime::application::kJson,
369                      brillo::mime::parameters::kCharset, "utf-8");
370  return SendRequest(request_type::kPatch, url, data.c_str(), data.size(),
371                     mime_type, headers, transport, success_callback,
372                     error_callback);
373}
374
375std::unique_ptr<base::DictionaryValue> ParseJsonResponse(
376    Response* response,
377    int* status_code,
378    brillo::ErrorPtr* error) {
379  if (!response)
380    return std::unique_ptr<base::DictionaryValue>();
381
382  if (status_code)
383    *status_code = response->GetStatusCode();
384
385  // Make sure we have a correct content type. Do not try to parse
386  // binary files, or HTML output. Limit to application/json and text/plain.
387  auto content_type = RemoveParameters(response->GetContentType());
388  if (content_type != brillo::mime::application::kJson &&
389      content_type != brillo::mime::text::kPlain) {
390    brillo::Error::AddTo(error, FROM_HERE, brillo::errors::json::kDomain,
391                         "non_json_content_type",
392                         "Unexpected response content type: " + content_type);
393    return std::unique_ptr<base::DictionaryValue>();
394  }
395
396  std::string json = response->ExtractDataAsString();
397  std::string error_message;
398  auto value = base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC,
399                                                    nullptr, &error_message);
400  if (!value) {
401    brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain,
402                               brillo::errors::json::kParseError,
403                               "Error '%s' occurred parsing JSON string '%s'",
404                               error_message.c_str(), json.c_str());
405    return std::unique_ptr<base::DictionaryValue>();
406  }
407  base::DictionaryValue* dict_value = nullptr;
408  if (!value->GetAsDictionary(&dict_value)) {
409    brillo::Error::AddToPrintf(error, FROM_HERE, brillo::errors::json::kDomain,
410                               brillo::errors::json::kObjectExpected,
411                               "Response is not a valid JSON object: '%s'",
412                               json.c_str());
413    return std::unique_ptr<base::DictionaryValue>();
414  } else {
415    // |value| is now owned by |dict_value|, so release the scoped_ptr now.
416    base::IgnoreResult(value.release());
417  }
418  return std::unique_ptr<base::DictionaryValue>(dict_value);
419}
420
421std::string GetCanonicalHeaderName(const std::string& name) {
422  std::string canonical_name = name;
423  bool word_begin = true;
424  for (char& c : canonical_name) {
425    if (c == '-') {
426      word_begin = true;
427    } else {
428      if (word_begin) {
429        c = toupper(c);
430      } else {
431        c = tolower(c);
432      }
433      word_begin = false;
434    }
435  }
436  return canonical_name;
437}
438
439}  // namespace http
440}  // namespace brillo
441