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