Home | History | Annotate | Download | only in http
      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.
     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.
     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 ///////////////////////////////////////////////////////////////////////////////
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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