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 <brillo/http/http_connection_curl.h>
      6 
      7 #include <algorithm>
      8 #include <set>
      9 
     10 #include <base/callback.h>
     11 #include <brillo/http/http_request.h>
     12 #include <brillo/http/http_transport.h>
     13 #include <brillo/http/mock_curl_api.h>
     14 #include <brillo/http/mock_transport.h>
     15 #include <brillo/streams/memory_stream.h>
     16 #include <brillo/streams/mock_stream.h>
     17 #include <brillo/strings/string_utils.h>
     18 #include <brillo/mime_utils.h>
     19 #include <gmock/gmock.h>
     20 #include <gtest/gtest.h>
     21 
     22 using testing::DoAll;
     23 using testing::Invoke;
     24 using testing::Return;
     25 using testing::SetArgPointee;
     26 using testing::_;
     27 
     28 namespace brillo {
     29 namespace http {
     30 namespace curl {
     31 
     32 namespace {
     33 
     34 using ReadWriteCallback =
     35     size_t(char* ptr, size_t size, size_t num, void* data);
     36 
     37 // A helper class to simulate curl_easy_perform action. It invokes the
     38 // read callbacks to obtain the request data from the Connection and then
     39 // calls the header and write callbacks to "send" the response header and body.
     40 class CurlPerformer {
     41  public:
     42   // During the tests, use the address of |this| as the CURL* handle.
     43   // This allows the static Perform() method to obtain the instance pointer
     44   // having only CURL*.
     45   CURL* GetCurlHandle() { return reinterpret_cast<CURL*>(this); }
     46 
     47   // Callback to be invoked when mocking out curl_easy_perform() method.
     48   static CURLcode Perform(CURL* curl) {
     49     CurlPerformer* me = reinterpret_cast<CurlPerformer*>(curl);
     50     return me->DoPerform();
     51   }
     52 
     53   // CURL callback functions and |connection| pointer needed to invoke the
     54   // callbacks from the Connection class.
     55   Connection* connection{nullptr};
     56   ReadWriteCallback* write_callback{nullptr};
     57   ReadWriteCallback* read_callback{nullptr};
     58   ReadWriteCallback* header_callback{nullptr};
     59 
     60   // Request body read from the connection.
     61   std::string request_body;
     62 
     63   // Response data to be sent back to connection.
     64   std::string status_line;
     65   HeaderList response_headers;
     66   std::string response_body;
     67 
     68  private:
     69   // The actual implementation of curl_easy_perform() fake.
     70   CURLcode DoPerform() {
     71     // Read request body.
     72     char buffer[1024];
     73     for (;;) {
     74       size_t size_read = read_callback(buffer, sizeof(buffer), 1, connection);
     75       if (size_read == CURL_READFUNC_ABORT)
     76         return CURLE_ABORTED_BY_CALLBACK;
     77       if (size_read == CURL_READFUNC_PAUSE)
     78         return CURLE_READ_ERROR;  // Shouldn't happen.
     79       if (size_read == 0)
     80         break;
     81       request_body.append(buffer, size_read);
     82     }
     83 
     84     // Send the response headers.
     85     std::vector<std::string> header_lines;
     86     header_lines.push_back(status_line + "\r\n");
     87     for (const auto& pair : response_headers) {
     88       header_lines.push_back(string_utils::Join(": ", pair.first, pair.second) +
     89                              "\r\n");
     90     }
     91 
     92     for (const std::string& line : header_lines) {
     93       CURLcode code = WriteString(header_callback, line);
     94       if (code != CURLE_OK)
     95         return code;
     96     }
     97 
     98     // Send response body.
     99     return WriteString(write_callback, response_body);
    100   }
    101 
    102   // Helper method to send a string to a write callback. Keeps calling
    103   // the callback until all the data is written.
    104   CURLcode WriteString(ReadWriteCallback* callback, const std::string& str) {
    105     size_t pos = 0;
    106     size_t size_remaining = str.size();
    107     while (size_remaining) {
    108       size_t size_written = callback(
    109           const_cast<char*>(str.data() + pos), size_remaining, 1, connection);
    110       if (size_written == CURL_WRITEFUNC_PAUSE)
    111         return CURLE_WRITE_ERROR;  // Shouldn't happen.
    112       CHECK(size_written <= size_remaining) << "Unexpected size returned";
    113       size_remaining -= size_written;
    114       pos += size_written;
    115     }
    116     return CURLE_OK;
    117   }
    118 };
    119 
    120 // Custom matcher to validate the parameter of CURLOPT_HTTPHEADER CURL option
    121 // which contains the request headers as curl_slist* chain.
    122 MATCHER_P(HeadersMatch, headers, "") {
    123   std::multiset<std::string> test_headers;
    124   for (const auto& pair : headers)
    125     test_headers.insert(string_utils::Join(": ", pair.first, pair.second));
    126 
    127   std::multiset<std::string> src_headers;
    128   const curl_slist* head = static_cast<const curl_slist*>(arg);
    129   while (head) {
    130     src_headers.insert(head->data);
    131     head = head->next;
    132   }
    133 
    134   std::vector<std::string> difference;
    135   std::set_symmetric_difference(src_headers.begin(), src_headers.end(),
    136                                 test_headers.begin(), test_headers.end(),
    137                                 std::back_inserter(difference));
    138   return difference.empty();
    139 }
    140 
    141 // Custom action to save a CURL callback pointer into a member of CurlPerformer.
    142 ACTION_TEMPLATE(SaveCallback,
    143                 HAS_1_TEMPLATE_PARAMS(int, k),
    144                 AND_2_VALUE_PARAMS(performer, mem_ptr)) {
    145   performer->*mem_ptr = reinterpret_cast<ReadWriteCallback*>(std::get<k>(args));
    146 }
    147 
    148 }  // anonymous namespace
    149 
    150 class HttpCurlConnectionTest : public testing::Test {
    151  public:
    152   void SetUp() override {
    153     curl_api_ = std::make_shared<MockCurlInterface>();
    154     transport_ = std::make_shared<MockTransport>();
    155     EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
    156         .WillOnce(Return(CURLE_OK));
    157     connection_ = std::make_shared<Connection>(
    158         handle_, request_type::kPost, curl_api_, transport_);
    159     performer_.connection = connection_.get();
    160   }
    161 
    162   void TearDown() override {
    163     EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
    164     connection_.reset();
    165     transport_.reset();
    166     curl_api_.reset();
    167   }
    168 
    169  protected:
    170   std::shared_ptr<MockCurlInterface> curl_api_;
    171   std::shared_ptr<MockTransport> transport_;
    172   CurlPerformer performer_;
    173   CURL* handle_{performer_.GetCurlHandle()};
    174   std::shared_ptr<Connection> connection_;
    175 };
    176 
    177 TEST_F(HttpCurlConnectionTest, FinishRequestAsync) {
    178   std::string request_data{"Foo Bar Baz"};
    179   StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr);
    180   EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr));
    181   EXPECT_TRUE(connection_->SendHeaders({{"X-Foo", "bar"}}, nullptr));
    182 
    183   if (VLOG_IS_ON(3)) {
    184     EXPECT_CALL(*curl_api_,
    185                 EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
    186         .WillOnce(Return(CURLE_OK));
    187     EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
    188         .WillOnce(Return(CURLE_OK));
    189   }
    190 
    191   EXPECT_CALL(
    192       *curl_api_,
    193       EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size()))
    194       .WillOnce(Return(CURLE_OK));
    195 
    196   EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
    197       .WillOnce(Return(CURLE_OK));
    198   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
    199       .WillOnce(Return(CURLE_OK));
    200 
    201   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, _))
    202       .WillOnce(Return(CURLE_OK));
    203 
    204   EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
    205       .WillOnce(Return(CURLE_OK));
    206   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
    207       .WillOnce(Return(CURLE_OK));
    208 
    209   EXPECT_CALL(*curl_api_,
    210               EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
    211       .WillOnce(Return(CURLE_OK));
    212   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
    213       .WillOnce(Return(CURLE_OK));
    214 
    215   EXPECT_CALL(*transport_, StartAsyncTransfer(connection_.get(), _, _))
    216       .Times(1);
    217   connection_->FinishRequestAsync({}, {});
    218 }
    219 
    220 MATCHER_P(MatchStringBuffer, data, "") {
    221   return data.compare(static_cast<const char*>(arg)) == 0;
    222 }
    223 
    224 TEST_F(HttpCurlConnectionTest, FinishRequest) {
    225   std::string request_data{"Foo Bar Baz"};
    226   std::string response_data{"<html><body>OK</body></html>"};
    227   StreamPtr stream = MemoryStream::OpenRef(request_data, nullptr);
    228   HeaderList headers{
    229       {request_header::kAccept, "*/*"},
    230       {request_header::kContentType, mime::text::kPlain},
    231       {request_header::kContentLength, std::to_string(request_data.size())},
    232       {"X-Foo", "bar"},
    233   };
    234   std::unique_ptr<MockStream> response_stream(new MockStream);
    235   EXPECT_CALL(*response_stream,
    236               WriteAllBlocking(MatchStringBuffer(response_data),
    237                                response_data.size(), _))
    238       .WillOnce(Return(true));
    239   EXPECT_CALL(*response_stream, CanSeek())
    240       .WillOnce(Return(false));
    241   connection_->SetResponseData(std::move(response_stream));
    242   EXPECT_TRUE(connection_->SetRequestData(std::move(stream), nullptr));
    243   EXPECT_TRUE(connection_->SendHeaders(headers, nullptr));
    244 
    245   // Expectations for Connection::FinishRequest() call.
    246   if (VLOG_IS_ON(3)) {
    247     EXPECT_CALL(*curl_api_,
    248                 EasySetOptCallback(handle_, CURLOPT_DEBUGFUNCTION, _))
    249         .WillOnce(Return(CURLE_OK));
    250     EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_VERBOSE, 1))
    251         .WillOnce(Return(CURLE_OK));
    252   }
    253 
    254   EXPECT_CALL(
    255       *curl_api_,
    256       EasySetOptOffT(handle_, CURLOPT_POSTFIELDSIZE_LARGE, request_data.size()))
    257       .WillOnce(Return(CURLE_OK));
    258 
    259   EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_READFUNCTION, _))
    260       .WillOnce(
    261           DoAll(SaveCallback<2>(&performer_, &CurlPerformer::read_callback),
    262                 Return(CURLE_OK)));
    263   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_READDATA, _))
    264       .WillOnce(Return(CURLE_OK));
    265 
    266   EXPECT_CALL(*curl_api_,
    267               EasySetOptPtr(handle_, CURLOPT_HTTPHEADER, HeadersMatch(headers)))
    268       .WillOnce(Return(CURLE_OK));
    269 
    270   EXPECT_CALL(*curl_api_, EasySetOptCallback(handle_, CURLOPT_WRITEFUNCTION, _))
    271       .WillOnce(
    272           DoAll(SaveCallback<2>(&performer_, &CurlPerformer::write_callback),
    273                 Return(CURLE_OK)));
    274   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_WRITEDATA, _))
    275       .WillOnce(Return(CURLE_OK));
    276 
    277   EXPECT_CALL(*curl_api_,
    278               EasySetOptCallback(handle_, CURLOPT_HEADERFUNCTION, _))
    279       .WillOnce(
    280           DoAll(SaveCallback<2>(&performer_, &CurlPerformer::header_callback),
    281                 Return(CURLE_OK)));
    282   EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_HEADERDATA, _))
    283       .WillOnce(Return(CURLE_OK));
    284 
    285   EXPECT_CALL(*curl_api_, EasyPerform(handle_))
    286       .WillOnce(Invoke(&CurlPerformer::Perform));
    287 
    288   EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
    289       .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
    290 
    291   // Set up the CurlPerformer with the response data expected to be received.
    292   HeaderList response_headers{
    293       {response_header::kContentLength, std::to_string(response_data.size())},
    294       {response_header::kContentType, mime::text::kHtml},
    295       {"X-Foo", "baz"},
    296   };
    297   performer_.status_line = "HTTP/1.1 200 OK";
    298   performer_.response_body = response_data;
    299   performer_.response_headers = response_headers;
    300 
    301   // Perform the request.
    302   EXPECT_TRUE(connection_->FinishRequest(nullptr));
    303 
    304   // Make sure we sent out the request body correctly.
    305   EXPECT_EQ(request_data, performer_.request_body);
    306 
    307   // Validate the parsed response data.
    308   EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
    309       .WillOnce(DoAll(SetArgPointee<2>(status_code::Ok), Return(CURLE_OK)));
    310   EXPECT_EQ(status_code::Ok, connection_->GetResponseStatusCode());
    311   EXPECT_EQ("HTTP/1.1", connection_->GetProtocolVersion());
    312   EXPECT_EQ("OK", connection_->GetResponseStatusText());
    313   EXPECT_EQ(std::to_string(response_data.size()),
    314             connection_->GetResponseHeader(response_header::kContentLength));
    315   EXPECT_EQ(mime::text::kHtml,
    316             connection_->GetResponseHeader(response_header::kContentType));
    317   EXPECT_EQ("baz", connection_->GetResponseHeader("X-Foo"));
    318   auto data_stream = connection_->ExtractDataStream(nullptr);
    319   ASSERT_NE(nullptr, data_stream.get());
    320 }
    321 
    322 }  // namespace curl
    323 }  // namespace http
    324 }  // namespace brillo
    325