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 <numeric>
6#include <string>
7#include <vector>
8
9#include <base/values.h>
10#include <brillo/bind_lambda.h>
11#include <brillo/http/http_transport_fake.h>
12#include <brillo/http/http_utils.h>
13#include <brillo/mime_utils.h>
14#include <brillo/strings/string_utils.h>
15#include <brillo/url_utils.h>
16#include <gtest/gtest.h>
17
18namespace brillo {
19namespace http {
20
21static const char kFakeUrl[] = "http://localhost";
22static const char kEchoUrl[] = "http://localhost/echo";
23static const char kMethodEchoUrl[] = "http://localhost/echo/method";
24
25///////////////////// Generic helper request handlers /////////////////////////
26// Returns the request data back with the same content type.
27static void EchoDataHandler(const fake::ServerRequest& request,
28                            fake::ServerResponse* response) {
29  response->Reply(status_code::Ok,
30                  request.GetData(),
31                  request.GetHeader(request_header::kContentType));
32}
33
34// Returns the request method as a plain text response.
35static void EchoMethodHandler(const fake::ServerRequest& request,
36                              fake::ServerResponse* response) {
37  response->ReplyText(
38      status_code::Ok, request.GetMethod(), brillo::mime::text::kPlain);
39}
40
41///////////////////////////////////////////////////////////////////////////////
42TEST(HttpUtils, SendRequest_BinaryData) {
43  std::shared_ptr<fake::Transport> transport(new fake::Transport);
44  transport->AddHandler(
45      kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler));
46
47  // Test binary data round-tripping.
48  std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
49  auto response =
50      http::SendRequestAndBlock(request_type::kPost,
51                                kEchoUrl,
52                                custom_data.data(),
53                                custom_data.size(),
54                                brillo::mime::application::kOctet_stream,
55                                {},
56                                transport,
57                                nullptr);
58  EXPECT_TRUE(response->IsSuccessful());
59  EXPECT_EQ(brillo::mime::application::kOctet_stream,
60            response->GetContentType());
61  EXPECT_EQ(custom_data, response->ExtractData());
62}
63
64TEST(HttpUtils, SendRequestAsync_BinaryData) {
65  std::shared_ptr<fake::Transport> transport(new fake::Transport);
66  transport->AddHandler(
67      kEchoUrl, request_type::kPost, base::Bind(EchoDataHandler));
68
69  // Test binary data round-tripping.
70  std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
71  auto success_callback = [](const std::vector<uint8_t>& custom_data,
72                             RequestID /* id */,
73                             std::unique_ptr<http::Response> response) {
74    EXPECT_TRUE(response->IsSuccessful());
75    EXPECT_EQ(brillo::mime::application::kOctet_stream,
76              response->GetContentType());
77    EXPECT_EQ(custom_data, response->ExtractData());
78  };
79  auto error_callback = [](RequestID /* id */, const Error* /* error */) {
80    FAIL() << "This callback shouldn't have been called";
81  };
82  http::SendRequest(request_type::kPost,
83                    kEchoUrl,
84                    custom_data.data(),
85                    custom_data.size(),
86                    brillo::mime::application::kOctet_stream,
87                    {},
88                    transport,
89                    base::Bind(success_callback, custom_data),
90                    base::Bind(error_callback));
91}
92
93TEST(HttpUtils, SendRequest_Post) {
94  std::shared_ptr<fake::Transport> transport(new fake::Transport);
95  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
96
97  // Test binary data round-tripping.
98  std::vector<uint8_t> custom_data{0xFF, 0x00, 0x80, 0x40, 0xC0, 0x7F};
99
100  // Check the correct HTTP method used.
101  auto response =
102      http::SendRequestAndBlock(request_type::kPost,
103                                kMethodEchoUrl,
104                                custom_data.data(),
105                                custom_data.size(),
106                                brillo::mime::application::kOctet_stream,
107                                {},
108                                transport,
109                                nullptr);
110  EXPECT_TRUE(response->IsSuccessful());
111  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
112  EXPECT_EQ(request_type::kPost, response->ExtractDataAsString());
113}
114
115TEST(HttpUtils, SendRequest_Get) {
116  std::shared_ptr<fake::Transport> transport(new fake::Transport);
117  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
118
119  auto response = http::SendRequestAndBlock(request_type::kGet,
120                                            kMethodEchoUrl,
121                                            nullptr,
122                                            0,
123                                            std::string{},
124                                            {},
125                                            transport,
126                                            nullptr);
127  EXPECT_TRUE(response->IsSuccessful());
128  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
129  EXPECT_EQ(request_type::kGet, response->ExtractDataAsString());
130}
131
132TEST(HttpUtils, SendRequest_Put) {
133  std::shared_ptr<fake::Transport> transport(new fake::Transport);
134  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
135
136  auto response = http::SendRequestAndBlock(request_type::kPut,
137                                            kMethodEchoUrl,
138                                            nullptr,
139                                            0,
140                                            std::string{},
141                                            {},
142                                            transport,
143                                            nullptr);
144  EXPECT_TRUE(response->IsSuccessful());
145  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
146  EXPECT_EQ(request_type::kPut, response->ExtractDataAsString());
147}
148
149TEST(HttpUtils, SendRequest_NotFound) {
150  std::shared_ptr<fake::Transport> transport(new fake::Transport);
151  // Test failed response (URL not found).
152  auto response = http::SendRequestWithNoDataAndBlock(
153      request_type::kGet, "http://blah.com", {}, transport, nullptr);
154  EXPECT_FALSE(response->IsSuccessful());
155  EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
156}
157
158TEST(HttpUtils, SendRequestAsync_NotFound) {
159  std::shared_ptr<fake::Transport> transport(new fake::Transport);
160  // Test failed response (URL not found).
161  auto success_callback =
162      [](RequestID /* request_id */, std::unique_ptr<http::Response> response) {
163    EXPECT_FALSE(response->IsSuccessful());
164    EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
165  };
166  auto error_callback = [](RequestID /* request_id */,
167                           const Error* /* error */) {
168    FAIL() << "This callback shouldn't have been called";
169  };
170  http::SendRequestWithNoData(request_type::kGet,
171                              "http://blah.com",
172                              {},
173                              transport,
174                              base::Bind(success_callback),
175                              base::Bind(error_callback));
176}
177
178TEST(HttpUtils, SendRequest_Headers) {
179  std::shared_ptr<fake::Transport> transport(new fake::Transport);
180
181  static const char json_echo_url[] = "http://localhost/echo/json";
182  auto JsonEchoHandler =
183      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
184    base::DictionaryValue json;
185    json.SetString("method", request.GetMethod());
186    json.SetString("data", request.GetDataAsString());
187    for (const auto& pair : request.GetHeaders()) {
188      json.SetString("header." + pair.first, pair.second);
189    }
190    response->ReplyJson(status_code::Ok, &json);
191  };
192  transport->AddHandler(json_echo_url, "*", base::Bind(JsonEchoHandler));
193  auto response = http::SendRequestAndBlock(
194      request_type::kPost, json_echo_url, "abcd", 4,
195      brillo::mime::application::kOctet_stream, {
196        {request_header::kCookie, "flavor=vanilla"},
197        {request_header::kIfMatch, "*"},
198      }, transport, nullptr);
199  EXPECT_TRUE(response->IsSuccessful());
200  EXPECT_EQ(brillo::mime::application::kJson,
201            brillo::mime::RemoveParameters(response->GetContentType()));
202  auto json = ParseJsonResponse(response.get(), nullptr, nullptr);
203  std::string value;
204  EXPECT_TRUE(json->GetString("method", &value));
205  EXPECT_EQ(request_type::kPost, value);
206  EXPECT_TRUE(json->GetString("data", &value));
207  EXPECT_EQ("abcd", value);
208  EXPECT_TRUE(json->GetString("header.Cookie", &value));
209  EXPECT_EQ("flavor=vanilla", value);
210  EXPECT_TRUE(json->GetString("header.Content-Type", &value));
211  EXPECT_EQ(brillo::mime::application::kOctet_stream, value);
212  EXPECT_TRUE(json->GetString("header.Content-Length", &value));
213  EXPECT_EQ("4", value);
214  EXPECT_TRUE(json->GetString("header.If-Match", &value));
215  EXPECT_EQ("*", value);
216}
217
218TEST(HttpUtils, Get) {
219  // Sends back the "?test=..." portion of URL.
220  // So if we do GET "http://localhost?test=blah", this handler responds
221  // with "blah" as text/plain.
222  auto GetHandler =
223      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
224    EXPECT_EQ(request_type::kGet, request.GetMethod());
225    EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
226    EXPECT_EQ("", request.GetHeader(request_header::kContentType));
227    response->ReplyText(status_code::Ok,
228                        request.GetFormField("test"),
229                        brillo::mime::text::kPlain);
230  };
231
232  std::shared_ptr<fake::Transport> transport(new fake::Transport);
233  transport->AddHandler(kFakeUrl, request_type::kGet, base::Bind(GetHandler));
234  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
235
236  // Make sure Get() actually does the GET request
237  auto response = http::GetAndBlock(kMethodEchoUrl, {}, transport, nullptr);
238  EXPECT_TRUE(response->IsSuccessful());
239  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
240  EXPECT_EQ(request_type::kGet, response->ExtractDataAsString());
241
242  for (std::string data : {"blah", "some data", ""}) {
243    std::string url = brillo::url::AppendQueryParam(kFakeUrl, "test", data);
244    response = http::GetAndBlock(url, {}, transport, nullptr);
245    EXPECT_EQ(data, response->ExtractDataAsString());
246  }
247}
248
249TEST(HttpUtils, Head) {
250  auto HeadHandler =
251      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
252    EXPECT_EQ(request_type::kHead, request.GetMethod());
253    EXPECT_EQ("0", request.GetHeader(request_header::kContentLength));
254    EXPECT_EQ("", request.GetHeader(request_header::kContentType));
255    response->ReplyText(status_code::Ok, "blah", brillo::mime::text::kPlain);
256  };
257
258  std::shared_ptr<fake::Transport> transport(new fake::Transport);
259  transport->AddHandler(kFakeUrl, request_type::kHead, base::Bind(HeadHandler));
260
261  auto response = http::HeadAndBlock(kFakeUrl, transport, nullptr);
262  EXPECT_TRUE(response->IsSuccessful());
263  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
264  EXPECT_EQ("", response->ExtractDataAsString());  // Must not have actual body.
265  EXPECT_EQ("4", response->GetHeader(request_header::kContentLength));
266}
267
268TEST(HttpUtils, PostBinary) {
269  auto Handler =
270      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
271    EXPECT_EQ(request_type::kPost, request.GetMethod());
272    EXPECT_EQ("256", request.GetHeader(request_header::kContentLength));
273    EXPECT_EQ(brillo::mime::application::kOctet_stream,
274              request.GetHeader(request_header::kContentType));
275    const auto& data = request.GetData();
276    EXPECT_EQ(256, data.size());
277
278    // Sum up all the bytes.
279    int sum = std::accumulate(data.begin(), data.end(), 0);
280    EXPECT_EQ(32640, sum);  // sum(i, i => [0, 255]) = 32640.
281    response->ReplyText(status_code::Ok, "", brillo::mime::text::kPlain);
282  };
283
284  std::shared_ptr<fake::Transport> transport(new fake::Transport);
285  transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(Handler));
286
287  /// Fill the data buffer with bytes from 0x00 to 0xFF.
288  std::vector<uint8_t> data(256);
289  std::iota(data.begin(), data.end(), 0);
290
291  auto response = http::PostBinaryAndBlock(kFakeUrl,
292                                           data.data(),
293                                           data.size(),
294                                           mime::application::kOctet_stream,
295                                           {},
296                                           transport,
297                                           nullptr);
298  EXPECT_TRUE(response->IsSuccessful());
299}
300
301TEST(HttpUtils, PostText) {
302  std::string fake_data = "Some data";
303  auto PostHandler = [](const std::string& fake_data,
304                        const fake::ServerRequest& request,
305                        fake::ServerResponse* response) {
306    EXPECT_EQ(request_type::kPost, request.GetMethod());
307    EXPECT_EQ(fake_data.size(),
308              std::stoul(request.GetHeader(request_header::kContentLength)));
309    EXPECT_EQ(brillo::mime::text::kPlain,
310              request.GetHeader(request_header::kContentType));
311    response->ReplyText(status_code::Ok,
312                        request.GetDataAsString(),
313                        brillo::mime::text::kPlain);
314  };
315
316  std::shared_ptr<fake::Transport> transport(new fake::Transport);
317  transport->AddHandler(
318      kFakeUrl, request_type::kPost, base::Bind(PostHandler, fake_data));
319
320  auto response = http::PostTextAndBlock(kFakeUrl,
321                                         fake_data,
322                                         brillo::mime::text::kPlain,
323                                         {},
324                                         transport,
325                                         nullptr);
326  EXPECT_TRUE(response->IsSuccessful());
327  EXPECT_EQ(brillo::mime::text::kPlain, response->GetContentType());
328  EXPECT_EQ(fake_data, response->ExtractDataAsString());
329}
330
331TEST(HttpUtils, PostFormData) {
332  std::shared_ptr<fake::Transport> transport(new fake::Transport);
333  transport->AddHandler(
334      kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler));
335
336  auto response = http::PostFormDataAndBlock(
337      kFakeUrl, {
338          {"key", "value"},
339          {"field", "field value"},
340      }, {}, transport, nullptr);
341  EXPECT_TRUE(response->IsSuccessful());
342  EXPECT_EQ(brillo::mime::application::kWwwFormUrlEncoded,
343            response->GetContentType());
344  EXPECT_EQ("key=value&field=field+value", response->ExtractDataAsString());
345}
346
347TEST(HttpUtils, PostMultipartFormData) {
348  std::shared_ptr<fake::Transport> transport(new fake::Transport);
349  transport->AddHandler(
350      kFakeUrl, request_type::kPost, base::Bind(EchoDataHandler));
351
352  std::unique_ptr<FormData> form_data{new FormData{"boundary123"}};
353  form_data->AddTextField("key1", "value1");
354  form_data->AddTextField("key2", "value2");
355  std::string expected_content_type = form_data->GetContentType();
356  auto response = http::PostFormDataAndBlock(
357      kFakeUrl, std::move(form_data), {}, transport, nullptr);
358  EXPECT_TRUE(response->IsSuccessful());
359  EXPECT_EQ(expected_content_type, response->GetContentType());
360  const char expected_value[] =
361      "--boundary123\r\n"
362      "Content-Disposition: form-data; name=\"key1\"\r\n"
363      "\r\n"
364      "value1\r\n"
365      "--boundary123\r\n"
366      "Content-Disposition: form-data; name=\"key2\"\r\n"
367      "\r\n"
368      "value2\r\n"
369      "--boundary123--";
370  EXPECT_EQ(expected_value, response->ExtractDataAsString());
371}
372
373TEST(HttpUtils, PostPatchJson) {
374  auto JsonHandler =
375      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
376    auto mime_type = brillo::mime::RemoveParameters(
377        request.GetHeader(request_header::kContentType));
378    EXPECT_EQ(brillo::mime::application::kJson, mime_type);
379    response->ReplyJson(
380        status_code::Ok,
381        {
382          {"method", request.GetMethod()}, {"data", request.GetDataAsString()},
383        });
384  };
385  std::shared_ptr<fake::Transport> transport(new fake::Transport);
386  transport->AddHandler(kFakeUrl, "*", base::Bind(JsonHandler));
387
388  base::DictionaryValue json;
389  json.SetString("key1", "val1");
390  json.SetString("key2", "val2");
391  std::string value;
392
393  // Test POST
394  auto response =
395      http::PostJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr);
396  auto resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
397  EXPECT_NE(nullptr, resp_json.get());
398  EXPECT_TRUE(resp_json->GetString("method", &value));
399  EXPECT_EQ(request_type::kPost, value);
400  EXPECT_TRUE(resp_json->GetString("data", &value));
401  EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
402
403  // Test PATCH
404  response = http::PatchJsonAndBlock(kFakeUrl, &json, {}, transport, nullptr);
405  resp_json = http::ParseJsonResponse(response.get(), nullptr, nullptr);
406  EXPECT_NE(nullptr, resp_json.get());
407  EXPECT_TRUE(resp_json->GetString("method", &value));
408  EXPECT_EQ(request_type::kPatch, value);
409  EXPECT_TRUE(resp_json->GetString("data", &value));
410  EXPECT_EQ("{\"key1\":\"val1\",\"key2\":\"val2\"}", value);
411}
412
413TEST(HttpUtils, ParseJsonResponse) {
414  auto JsonHandler =
415      [](const fake::ServerRequest& request, fake::ServerResponse* response) {
416    int status_code = std::stoi(request.GetFormField("code"));
417    response->ReplyJson(status_code, {{"data", request.GetFormField("value")}});
418  };
419  std::shared_ptr<fake::Transport> transport(new fake::Transport);
420  transport->AddHandler(kFakeUrl, request_type::kPost, base::Bind(JsonHandler));
421
422  // Test valid JSON responses (with success or error codes).
423  for (auto item : {"200;data", "400;wrong", "500;Internal Server error"}) {
424    auto pair = brillo::string_utils::SplitAtFirst(item, ";");
425    auto response = http::PostFormDataAndBlock(
426        kFakeUrl, {
427          {"code", pair.first},
428          {"value", pair.second},
429        }, {}, transport, nullptr);
430    int code = 0;
431    auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
432    EXPECT_NE(nullptr, json.get());
433    std::string value;
434    EXPECT_TRUE(json->GetString("data", &value));
435    EXPECT_EQ(pair.first, brillo::string_utils::ToString(code));
436    EXPECT_EQ(pair.second, value);
437  }
438
439  // Test invalid (non-JSON) response.
440  auto response = http::GetAndBlock("http://bad.url", {}, transport, nullptr);
441  EXPECT_EQ(status_code::NotFound, response->GetStatusCode());
442  EXPECT_EQ(brillo::mime::text::kHtml, response->GetContentType());
443  int code = 0;
444  auto json = http::ParseJsonResponse(response.get(), &code, nullptr);
445  EXPECT_EQ(nullptr, json.get());
446  EXPECT_EQ(status_code::NotFound, code);
447}
448
449TEST(HttpUtils, SendRequest_Failure) {
450  std::shared_ptr<fake::Transport> transport(new fake::Transport);
451  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
452  ErrorPtr error;
453  Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
454  transport->SetCreateConnectionError(std::move(error));
455  error.reset();  // Just to make sure it is empty...
456  auto response = http::SendRequestWithNoDataAndBlock(
457      request_type::kGet, "http://blah.com", {}, transport, &error);
458  EXPECT_EQ(nullptr, response.get());
459  EXPECT_EQ("test_domain", error->GetDomain());
460  EXPECT_EQ("test_code", error->GetCode());
461  EXPECT_EQ("Test message", error->GetMessage());
462}
463
464TEST(HttpUtils, SendRequestAsync_Failure) {
465  std::shared_ptr<fake::Transport> transport(new fake::Transport);
466  transport->AddHandler(kMethodEchoUrl, "*", base::Bind(EchoMethodHandler));
467  ErrorPtr error;
468  Error::AddTo(&error, FROM_HERE, "test_domain", "test_code", "Test message");
469  transport->SetCreateConnectionError(std::move(error));
470  auto success_callback =
471      [](RequestID /* request_id */,
472         std::unique_ptr<http::Response> /* response */) {
473    FAIL() << "This callback shouldn't have been called";
474  };
475  auto error_callback = [](RequestID /* request_id */, const Error* error) {
476    EXPECT_EQ("test_domain", error->GetDomain());
477    EXPECT_EQ("test_code", error->GetCode());
478    EXPECT_EQ("Test message", error->GetMessage());
479  };
480  http::SendRequestWithNoData(request_type::kGet,
481                              "http://blah.com",
482                              {},
483                              transport,
484                              base::Bind(success_callback),
485                              base::Bind(error_callback));
486}
487
488TEST(HttpUtils, GetCanonicalHeaderName) {
489  EXPECT_EQ("Foo", GetCanonicalHeaderName("foo"));
490  EXPECT_EQ("Bar", GetCanonicalHeaderName("BaR"));
491  EXPECT_EQ("Baz", GetCanonicalHeaderName("BAZ"));
492  EXPECT_EQ("Foo-Bar", GetCanonicalHeaderName("foo-bar"));
493  EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("foo-Bar-BAZ"));
494  EXPECT_EQ("Foo-Bar-Baz", GetCanonicalHeaderName("FOO-BAR-BAZ"));
495  EXPECT_EQ("Foo-Bar-", GetCanonicalHeaderName("fOO-bAR-"));
496  EXPECT_EQ("-Bar", GetCanonicalHeaderName("-bAR"));
497  EXPECT_EQ("", GetCanonicalHeaderName(""));
498  EXPECT_EQ("A-B-C", GetCanonicalHeaderName("a-B-c"));
499}
500
501}  // namespace http
502}  // namespace brillo
503