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*. 45 CURL* GetCurlHandle() { return reinterpret_cast<CURL*>(this); } 46 47 // Callback to be invoked when mocking out curl_easy_perform() method. 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. 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. 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. 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: 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 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 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 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