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