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