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