• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 namespace brillo {
19 namespace http {
20 
21 static const char kFakeUrl[] = "http://localhost";
22 static const char kEchoUrl[] = "http://localhost/echo";
23 static const char kMethodEchoUrl[] = "http://localhost/echo/method";
24 
25 ///////////////////// Generic helper request handlers /////////////////////////
26 // Returns the request data back with the same content type.
EchoDataHandler(const fake::ServerRequest & request,fake::ServerResponse * response)27 static 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.
EchoMethodHandler(const fake::ServerRequest & request,fake::ServerResponse * response)35 static 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 ///////////////////////////////////////////////////////////////////////////////
TEST(HttpUtils,SendRequest_BinaryData)42 TEST(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 
TEST(HttpUtils,SendRequestAsync_BinaryData)64 TEST(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 
TEST(HttpUtils,SendRequest_Post)93 TEST(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 
TEST(HttpUtils,SendRequest_Get)115 TEST(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 
TEST(HttpUtils,SendRequest_Put)132 TEST(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 
TEST(HttpUtils,SendRequest_NotFound)149 TEST(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 
TEST(HttpUtils,SendRequestAsync_NotFound)158 TEST(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 
TEST(HttpUtils,SendRequest_Headers)178 TEST(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 
TEST(HttpUtils,Get)218 TEST(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 
TEST(HttpUtils,Head)249 TEST(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 
TEST(HttpUtils,PostBinary)268 TEST(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 
TEST(HttpUtils,PostText)301 TEST(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 
TEST(HttpUtils,PostFormData)331 TEST(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 
TEST(HttpUtils,PostMultipartFormData)347 TEST(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 
TEST(HttpUtils,PostPatchJson)373 TEST(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 
TEST(HttpUtils,ParseJsonResponse)413 TEST(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 
TEST(HttpUtils,SendRequest_Failure)449 TEST(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 
TEST(HttpUtils,SendRequestAsync_Failure)464 TEST(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 
TEST(HttpUtils,GetCanonicalHeaderName)488 TEST(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