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*.
GetCurlHandle()45   CURL* GetCurlHandle() { return reinterpret_cast<CURL*>(this); }
46 
47   // Callback to be invoked when mocking out curl_easy_perform() method.
Perform(CURL * curl)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.
DoPerform()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.
WriteString(ReadWriteCallback * callback,const std::string & str)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.
ACTION_TEMPLATE(SaveCallback,HAS_1_TEMPLATE_PARAMS (int,k),AND_2_VALUE_PARAMS (performer,mem_ptr))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:
SetUp()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 
TearDown()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 
TEST_F(HttpCurlConnectionTest,FinishRequestAsync)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 
TEST_F(HttpCurlConnectionTest,FinishRequest)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