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_transport_curl.h>
6
7 #include <base/at_exit.h>
8 #include <base/bind.h>
9 #include <base/message_loop/message_loop.h>
10 #include <base/run_loop.h>
11 #include <base/threading/thread_task_runner_handle.h>
12 #include <brillo/http/http_connection_curl.h>
13 #include <brillo/http/http_request.h>
14 #include <brillo/http/mock_curl_api.h>
15 #include <gmock/gmock.h>
16 #include <gtest/gtest.h>
17
18 using testing::DoAll;
19 using testing::Invoke;
20 using testing::Return;
21 using testing::SaveArg;
22 using testing::SetArgPointee;
23 using testing::WithoutArgs;
24 using testing::_;
25
26 namespace brillo {
27 namespace http {
28 namespace curl {
29
30 class HttpCurlTransportTest : public testing::Test {
31 public:
SetUp()32 void SetUp() override {
33 curl_api_ = std::make_shared<MockCurlInterface>();
34 transport_ = std::make_shared<Transport>(curl_api_);
35 handle_ = reinterpret_cast<CURL*>(100); // Mock handle value.
36 EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
37 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_CAINFO, _))
38 .WillOnce(Return(CURLE_OK));
39 EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
40 .WillOnce(Return(CURLE_OK));
41 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
42 .WillOnce(Return(CURLE_OK));
43 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
44 .WillOnce(Return(CURLE_OK));
45 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
46 .WillRepeatedly(Return(CURLE_OK));
47 }
48
TearDown()49 void TearDown() override {
50 transport_.reset();
51 curl_api_.reset();
52 }
53
54 protected:
55 std::shared_ptr<MockCurlInterface> curl_api_;
56 std::shared_ptr<Transport> transport_;
57 CURL* handle_{nullptr};
58 };
59
TEST_F(HttpCurlTransportTest,RequestGet)60 TEST_F(HttpCurlTransportTest, RequestGet) {
61 EXPECT_CALL(*curl_api_,
62 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
63 .WillOnce(Return(CURLE_OK));
64 EXPECT_CALL(*curl_api_,
65 EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent"))
66 .WillOnce(Return(CURLE_OK));
67 EXPECT_CALL(*curl_api_,
68 EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz"))
69 .WillOnce(Return(CURLE_OK));
70 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
71 .WillOnce(Return(CURLE_OK));
72 auto connection = transport_->CreateConnection("http://foo.bar/get",
73 request_type::kGet,
74 {},
75 "User Agent",
76 "http://foo.bar/baz",
77 nullptr);
78 EXPECT_NE(nullptr, connection.get());
79
80 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
81 connection.reset();
82 }
83
TEST_F(HttpCurlTransportTest,RequestGetWithProxy)84 TEST_F(HttpCurlTransportTest, RequestGetWithProxy) {
85 EXPECT_CALL(*curl_api_,
86 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
87 .WillOnce(Return(CURLE_OK));
88 EXPECT_CALL(*curl_api_,
89 EasySetOptStr(handle_, CURLOPT_USERAGENT, "User Agent"))
90 .WillOnce(Return(CURLE_OK));
91 EXPECT_CALL(*curl_api_,
92 EasySetOptStr(handle_, CURLOPT_REFERER, "http://foo.bar/baz"))
93 .WillOnce(Return(CURLE_OK));
94 EXPECT_CALL(*curl_api_,
95 EasySetOptStr(handle_, CURLOPT_PROXY, "http://proxy.server"))
96 .WillOnce(Return(CURLE_OK));
97 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
98 .WillOnce(Return(CURLE_OK));
99 std::shared_ptr<Transport> proxy_transport =
100 std::make_shared<Transport>(curl_api_, "http://proxy.server");
101
102 auto connection = proxy_transport->CreateConnection("http://foo.bar/get",
103 request_type::kGet,
104 {},
105 "User Agent",
106 "http://foo.bar/baz",
107 nullptr);
108 EXPECT_NE(nullptr, connection.get());
109
110 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
111 connection.reset();
112 }
113
TEST_F(HttpCurlTransportTest,RequestHead)114 TEST_F(HttpCurlTransportTest, RequestHead) {
115 EXPECT_CALL(*curl_api_,
116 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/head"))
117 .WillOnce(Return(CURLE_OK));
118 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_NOBODY, 1))
119 .WillOnce(Return(CURLE_OK));
120 auto connection = transport_->CreateConnection(
121 "http://foo.bar/head", request_type::kHead, {}, "", "", nullptr);
122 EXPECT_NE(nullptr, connection.get());
123
124 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
125 connection.reset();
126 }
127
TEST_F(HttpCurlTransportTest,RequestPut)128 TEST_F(HttpCurlTransportTest, RequestPut) {
129 EXPECT_CALL(*curl_api_,
130 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/put"))
131 .WillOnce(Return(CURLE_OK));
132 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_UPLOAD, 1))
133 .WillOnce(Return(CURLE_OK));
134 auto connection = transport_->CreateConnection(
135 "http://foo.bar/put", request_type::kPut, {}, "", "", nullptr);
136 EXPECT_NE(nullptr, connection.get());
137
138 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
139 connection.reset();
140 }
141
TEST_F(HttpCurlTransportTest,RequestPost)142 TEST_F(HttpCurlTransportTest, RequestPost) {
143 EXPECT_CALL(*curl_api_,
144 EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/post"))
145 .WillOnce(Return(CURLE_OK));
146 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
147 .WillOnce(Return(CURLE_OK));
148 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
149 .WillOnce(Return(CURLE_OK));
150 auto connection = transport_->CreateConnection(
151 "http://www.foo.bar/post", request_type::kPost, {}, "", "", nullptr);
152 EXPECT_NE(nullptr, connection.get());
153
154 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
155 connection.reset();
156 }
157
TEST_F(HttpCurlTransportTest,RequestPatch)158 TEST_F(HttpCurlTransportTest, RequestPatch) {
159 EXPECT_CALL(*curl_api_,
160 EasySetOptStr(handle_, CURLOPT_URL, "http://www.foo.bar/patch"))
161 .WillOnce(Return(CURLE_OK));
162 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_POST, 1))
163 .WillOnce(Return(CURLE_OK));
164 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_POSTFIELDS, nullptr))
165 .WillOnce(Return(CURLE_OK));
166 EXPECT_CALL(
167 *curl_api_,
168 EasySetOptStr(handle_, CURLOPT_CUSTOMREQUEST, request_type::kPatch))
169 .WillOnce(Return(CURLE_OK));
170 auto connection = transport_->CreateConnection(
171 "http://www.foo.bar/patch", request_type::kPatch, {}, "", "", nullptr);
172 EXPECT_NE(nullptr, connection.get());
173
174 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
175 connection.reset();
176 }
177
TEST_F(HttpCurlTransportTest,CurlFailure)178 TEST_F(HttpCurlTransportTest, CurlFailure) {
179 EXPECT_CALL(*curl_api_,
180 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
181 .WillOnce(Return(CURLE_OK));
182 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
183 .WillOnce(Return(CURLE_OUT_OF_MEMORY));
184 EXPECT_CALL(*curl_api_, EasyStrError(CURLE_OUT_OF_MEMORY))
185 .WillOnce(Return("Out of Memory"));
186 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
187 ErrorPtr error;
188 auto connection = transport_->CreateConnection(
189 "http://foo.bar/get", request_type::kGet, {}, "", "", &error);
190
191 EXPECT_EQ(nullptr, connection.get());
192 EXPECT_EQ("curl_easy_error", error->GetDomain());
193 EXPECT_EQ(std::to_string(CURLE_OUT_OF_MEMORY), error->GetCode());
194 EXPECT_EQ("Out of Memory", error->GetMessage());
195 }
196
197 class HttpCurlTransportAsyncTest : public testing::Test {
198 public:
SetUp()199 void SetUp() override {
200 curl_api_ = std::make_shared<MockCurlInterface>();
201 transport_ = std::make_shared<Transport>(curl_api_);
202 EXPECT_CALL(*curl_api_, EasyInit()).WillOnce(Return(handle_));
203 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_CAINFO, _))
204 .WillOnce(Return(CURLE_OK));
205 EXPECT_CALL(*curl_api_, EasySetOptStr(handle_, CURLOPT_CAPATH, _))
206 .WillOnce(Return(CURLE_OK));
207 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYPEER, 1))
208 .WillOnce(Return(CURLE_OK));
209 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_SSL_VERIFYHOST, 2))
210 .WillOnce(Return(CURLE_OK));
211 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_PRIVATE, _))
212 .WillOnce(Return(CURLE_OK));
213 }
214
215 protected:
216 std::shared_ptr<MockCurlInterface> curl_api_;
217 std::shared_ptr<Transport> transport_;
218 CURL* handle_{reinterpret_cast<CURL*>(123)}; // Mock handle value.
219 CURLM* multi_handle_{reinterpret_cast<CURLM*>(456)}; // Mock handle value.
220 curl_socket_t dummy_socket_{789};
221 };
222
TEST_F(HttpCurlTransportAsyncTest,StartAsyncTransfer)223 TEST_F(HttpCurlTransportAsyncTest, StartAsyncTransfer) {
224 // This test is a bit tricky because it deals with asynchronous I/O which
225 // relies on a message loop to run all the async tasks.
226 // For this, create a temporary I/O message loop and run it ourselves for the
227 // duration of the test.
228 base::MessageLoopForIO message_loop;
229 base::RunLoop run_loop;
230
231 // Initial expectations for creating a CURL connection.
232 EXPECT_CALL(*curl_api_,
233 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
234 .WillOnce(Return(CURLE_OK));
235 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
236 .WillOnce(Return(CURLE_OK));
237 auto connection = transport_->CreateConnection(
238 "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
239 ASSERT_NE(nullptr, connection.get());
240
241 // Success/error callback needed to report the result of an async operation.
242 int success_call_count = 0;
243 auto success_callback = base::Bind([](
244 int* success_call_count, const base::Closure& quit_closure,
245 RequestID /* request_id */, std::unique_ptr<http::Response> /* resp */) {
246 base::ThreadTaskRunnerHandle::Get()->PostTask(
247 FROM_HERE, quit_closure);
248 (*success_call_count)++;
249 }, &success_call_count, run_loop.QuitClosure());
250
251 auto error_callback = [](RequestID /* request_id */,
252 const Error* /* error */) {
253 FAIL() << "This callback shouldn't have been called";
254 };
255
256 EXPECT_CALL(*curl_api_, MultiInit()).WillOnce(Return(multi_handle_));
257 EXPECT_CALL(*curl_api_, EasyGetInfoInt(handle_, CURLINFO_RESPONSE_CODE, _))
258 .WillRepeatedly(DoAll(SetArgPointee<2>(200), Return(CURLE_OK)));
259
260 curl_socket_callback socket_callback = nullptr;
261 EXPECT_CALL(*curl_api_,
262 MultiSetSocketCallback(multi_handle_, _, transport_.get()))
263 .WillOnce(DoAll(SaveArg<1>(&socket_callback), Return(CURLM_OK)));
264
265 curl_multi_timer_callback timer_callback = nullptr;
266 EXPECT_CALL(*curl_api_,
267 MultiSetTimerCallback(multi_handle_, _, transport_.get()))
268 .WillOnce(DoAll(SaveArg<1>(&timer_callback), Return(CURLM_OK)));
269
270 EXPECT_CALL(*curl_api_, MultiAddHandle(multi_handle_, handle_))
271 .WillOnce(Return(CURLM_OK));
272
273 EXPECT_EQ(1, transport_->StartAsyncTransfer(connection.get(),
274 success_callback,
275 base::Bind(error_callback)));
276 EXPECT_EQ(0, success_call_count);
277
278 timer_callback(multi_handle_, 1, transport_.get());
279
280 auto do_socket_action = [&socket_callback, this] {
281 EXPECT_CALL(*curl_api_, MultiAssign(multi_handle_, dummy_socket_, _))
282 .Times(2).WillRepeatedly(Return(CURLM_OK));
283 EXPECT_EQ(0, socket_callback(handle_, dummy_socket_, CURL_POLL_REMOVE,
284 transport_.get(), nullptr));
285 };
286
287 EXPECT_CALL(*curl_api_,
288 MultiSocketAction(multi_handle_, CURL_SOCKET_TIMEOUT, 0, _))
289 .WillOnce(DoAll(SetArgPointee<3>(1),
290 WithoutArgs(Invoke(do_socket_action)),
291 Return(CURLM_OK)))
292 .WillRepeatedly(DoAll(SetArgPointee<3>(0), Return(CURLM_OK)));
293
294 CURLMsg msg = {};
295 msg.msg = CURLMSG_DONE;
296 msg.easy_handle = handle_;
297 msg.data.result = CURLE_OK;
298
299 EXPECT_CALL(*curl_api_, MultiInfoRead(multi_handle_, _))
300 .WillOnce(DoAll(SetArgPointee<1>(0), Return(&msg)))
301 .WillRepeatedly(DoAll(SetArgPointee<1>(0), Return(nullptr)));
302 EXPECT_CALL(*curl_api_, EasyGetInfoPtr(handle_, CURLINFO_PRIVATE, _))
303 .WillRepeatedly(DoAll(SetArgPointee<2>(connection.get()),
304 Return(CURLE_OK)));
305
306 EXPECT_CALL(*curl_api_, MultiRemoveHandle(multi_handle_, handle_))
307 .WillOnce(Return(CURLM_OK));
308
309 // Just in case something goes wrong and |success_callback| isn't called,
310 // post a time-out quit closure to abort the message loop after 1 second.
311 message_loop.task_runner()->PostDelayedTask(
312 FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromSeconds(1));
313 run_loop.Run();
314 EXPECT_EQ(1, success_call_count);
315
316 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
317 connection.reset();
318
319 EXPECT_CALL(*curl_api_, MultiCleanup(multi_handle_))
320 .WillOnce(Return(CURLM_OK));
321 transport_.reset();
322 }
323
TEST_F(HttpCurlTransportTest,RequestGetTimeout)324 TEST_F(HttpCurlTransportTest, RequestGetTimeout) {
325 transport_->SetDefaultTimeout(base::TimeDelta::FromMilliseconds(2000));
326 EXPECT_CALL(*curl_api_,
327 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
328 .WillOnce(Return(CURLE_OK));
329 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_TIMEOUT_MS, 2000))
330 .WillOnce(Return(CURLE_OK));
331 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
332 .WillOnce(Return(CURLE_OK));
333 auto connection = transport_->CreateConnection(
334 "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
335 EXPECT_NE(nullptr, connection.get());
336
337 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
338 connection.reset();
339 }
340
TEST_F(HttpCurlTransportTest,RequestGetResolveHost)341 TEST_F(HttpCurlTransportTest, RequestGetResolveHost) {
342 transport_->ResolveHostToIp("foo.bar", 80, "127.0.0.1");
343 EXPECT_CALL(*curl_api_,
344 EasySetOptStr(handle_, CURLOPT_URL, "http://foo.bar/get"))
345 .WillOnce(Return(CURLE_OK));
346 EXPECT_CALL(*curl_api_, EasySetOptPtr(handle_, CURLOPT_RESOLVE, _))
347 .WillOnce(Return(CURLE_OK));
348 EXPECT_CALL(*curl_api_, EasySetOptInt(handle_, CURLOPT_HTTPGET, 1))
349 .WillOnce(Return(CURLE_OK));
350 auto connection = transport_->CreateConnection(
351 "http://foo.bar/get", request_type::kGet, {}, "", "", nullptr);
352 EXPECT_NE(nullptr, connection.get());
353
354 EXPECT_CALL(*curl_api_, EasyCleanup(handle_)).Times(1);
355 connection.reset();
356 }
357
358 } // namespace curl
359 } // namespace http
360 } // namespace brillo
361