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
17using testing::DoAll;
18using testing::Invoke;
19using testing::Return;
20using testing::SaveArg;
21using testing::SetArgPointee;
22using testing::WithoutArgs;
23using testing::_;
24
25namespace brillo {
26namespace http {
27namespace curl {
28
29class HttpCurlTransportTest : public testing::Test {
30 public:
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
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
57TEST_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
81TEST_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
95TEST_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
109TEST_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
125TEST_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
145TEST_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
164class HttpCurlTransportAsyncTest : public testing::Test {
165 public:
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
188TEST_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
287TEST_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