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 <base/logging.h> 8 #include <brillo/http/http_request.h> 9 #include <brillo/http/http_transport_curl.h> 10 #include <brillo/streams/memory_stream.h> 11 #include <brillo/streams/stream_utils.h> 12 #include <brillo/strings/string_utils.h> 13 14 namespace brillo { 15 namespace http { 16 namespace curl { 17 18 static int curl_trace(CURL* /* handle */, 19 curl_infotype type, 20 char* data, 21 size_t size, 22 void* /* userp */) { 23 std::string msg(data, size); 24 25 switch (type) { 26 case CURLINFO_TEXT: 27 VLOG(3) << "== Info: " << msg; 28 break; 29 case CURLINFO_HEADER_OUT: 30 VLOG(3) << "=> Send headers:\n" << msg; 31 break; 32 case CURLINFO_DATA_OUT: 33 VLOG(3) << "=> Send data:\n" << msg; 34 break; 35 case CURLINFO_SSL_DATA_OUT: 36 VLOG(3) << "=> Send SSL data" << msg; 37 break; 38 case CURLINFO_HEADER_IN: 39 VLOG(3) << "<= Recv header: " << msg; 40 break; 41 case CURLINFO_DATA_IN: 42 VLOG(3) << "<= Recv data:\n" << msg; 43 break; 44 case CURLINFO_SSL_DATA_IN: 45 VLOG(3) << "<= Recv SSL data" << msg; 46 break; 47 default: 48 break; 49 } 50 return 0; 51 } 52 53 Connection::Connection(CURL* curl_handle, 54 const std::string& method, 55 const std::shared_ptr<CurlInterface>& curl_interface, 56 const std::shared_ptr<http::Transport>& transport) 57 : http::Connection(transport), 58 method_(method), 59 curl_handle_(curl_handle), 60 curl_interface_(curl_interface) { 61 // Store the connection pointer inside the CURL handle so we can easily 62 // retrieve it when doing asynchronous I/O. 63 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this); 64 VLOG(2) << "curl::Connection created: " << method_; 65 } 66 67 Connection::~Connection() { 68 if (header_list_) 69 curl_slist_free_all(header_list_); 70 curl_interface_->EasyCleanup(curl_handle_); 71 VLOG(2) << "curl::Connection destroyed"; 72 } 73 74 bool Connection::SendHeaders(const HeaderList& headers, 75 brillo::ErrorPtr* /* error */) { 76 headers_.insert(headers.begin(), headers.end()); 77 return true; 78 } 79 80 bool Connection::SetRequestData(StreamPtr stream, 81 brillo::ErrorPtr* /* error */) { 82 request_data_stream_ = std::move(stream); 83 return true; 84 } 85 86 void Connection::SetResponseData(StreamPtr stream) { 87 response_data_stream_ = std::move(stream); 88 } 89 90 void Connection::PrepareRequest() { 91 if (VLOG_IS_ON(3)) { 92 curl_interface_->EasySetOptCallback( 93 curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace); 94 curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1); 95 } 96 97 if (method_ != request_type::kGet) { 98 // Set up HTTP request data. 99 uint64_t data_size = 0; 100 if (request_data_stream_ && request_data_stream_->CanGetSize()) 101 data_size = request_data_stream_->GetRemainingSize(); 102 103 if (!request_data_stream_ || request_data_stream_->CanGetSize()) { 104 // Data size is known (either no data, or data size is available). 105 if (method_ == request_type::kPut) { 106 curl_interface_->EasySetOptOffT( 107 curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size); 108 } else { 109 curl_interface_->EasySetOptOffT( 110 curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size); 111 } 112 } else { 113 // Data size is unknown, so use chunked upload. 114 headers_.emplace(http::request_header::kTransferEncoding, "chunked"); 115 } 116 117 if (request_data_stream_) { 118 curl_interface_->EasySetOptCallback( 119 curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback); 120 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this); 121 } 122 } 123 124 if (!headers_.empty()) { 125 CHECK(header_list_ == nullptr); 126 for (auto pair : headers_) { 127 std::string header = 128 brillo::string_utils::Join(": ", pair.first, pair.second); 129 VLOG(2) << "Request header: " << header; 130 header_list_ = curl_slist_append(header_list_, header.c_str()); 131 } 132 curl_interface_->EasySetOptPtr( 133 curl_handle_, CURLOPT_HTTPHEADER, header_list_); 134 } 135 136 headers_.clear(); 137 138 // Set up HTTP response data. 139 if (!response_data_stream_) 140 response_data_stream_ = MemoryStream::Create(nullptr); 141 if (method_ != request_type::kHead) { 142 curl_interface_->EasySetOptCallback( 143 curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback); 144 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this); 145 } 146 147 // HTTP response headers 148 curl_interface_->EasySetOptCallback( 149 curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback); 150 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this); 151 } 152 153 bool Connection::FinishRequest(brillo::ErrorPtr* error) { 154 PrepareRequest(); 155 CURLcode ret = curl_interface_->EasyPerform(curl_handle_); 156 if (ret != CURLE_OK) { 157 Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get()); 158 } else { 159 // Rewind our data stream to the beginning so that it can be read back. 160 if (response_data_stream_->CanSeek() && 161 !response_data_stream_->SetPosition(0, error)) 162 return false; 163 LOG(INFO) << "Response: " << GetResponseStatusCode() << " (" 164 << GetResponseStatusText() << ")"; 165 } 166 return (ret == CURLE_OK); 167 } 168 169 RequestID Connection::FinishRequestAsync( 170 const SuccessCallback& success_callback, 171 const ErrorCallback& error_callback) { 172 PrepareRequest(); 173 return transport_->StartAsyncTransfer(this, success_callback, error_callback); 174 } 175 176 int Connection::GetResponseStatusCode() const { 177 int status_code = 0; 178 curl_interface_->EasyGetInfoInt( 179 curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); 180 return status_code; 181 } 182 183 std::string Connection::GetResponseStatusText() const { 184 return status_text_; 185 } 186 187 std::string Connection::GetProtocolVersion() const { 188 return protocol_version_; 189 } 190 191 std::string Connection::GetResponseHeader( 192 const std::string& header_name) const { 193 auto p = headers_.find(header_name); 194 return p != headers_.end() ? p->second : std::string(); 195 } 196 197 StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) { 198 if (!response_data_stream_) { 199 stream_utils::ErrorStreamClosed(FROM_HERE, error); 200 } 201 return std::move(response_data_stream_); 202 } 203 204 size_t Connection::write_callback(char* ptr, 205 size_t size, 206 size_t num, 207 void* data) { 208 Connection* me = reinterpret_cast<Connection*>(data); 209 size_t data_len = size * num; 210 VLOG(1) << "Response data (" << data_len << "): " 211 << std::string{ptr, data_len}; 212 // TODO(nathanbullock): Currently we are relying on the stream not blocking, 213 // but if the stream is representing a pipe or some other construct that might 214 // block then this code will behave badly. 215 if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) { 216 LOG(ERROR) << "Failed to write response data"; 217 data_len = 0; 218 } 219 return data_len; 220 } 221 222 size_t Connection::read_callback(char* ptr, 223 size_t size, 224 size_t num, 225 void* data) { 226 Connection* me = reinterpret_cast<Connection*>(data); 227 size_t data_len = size * num; 228 229 size_t read_size = 0; 230 bool success = me->request_data_stream_->ReadBlocking(ptr, data_len, 231 &read_size, nullptr); 232 VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size}; 233 return success ? read_size : CURL_READFUNC_ABORT; 234 } 235 236 size_t Connection::header_callback(char* ptr, 237 size_t size, 238 size_t num, 239 void* data) { 240 using brillo::string_utils::SplitAtFirst; 241 Connection* me = reinterpret_cast<Connection*>(data); 242 size_t hdr_len = size * num; 243 std::string header(ptr, hdr_len); 244 // Remove newlines at the end of header line. 245 while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { 246 header.pop_back(); 247 } 248 249 VLOG(2) << "Response header: " << header; 250 251 if (!me->status_text_set_) { 252 // First header - response code as "HTTP/1.1 200 OK". 253 // Need to extract the OK part 254 auto pair = SplitAtFirst(header, " "); 255 me->protocol_version_ = pair.first; 256 me->status_text_ = SplitAtFirst(pair.second, " ").second; 257 me->status_text_set_ = true; 258 } else { 259 auto pair = SplitAtFirst(header, ":"); 260 if (!pair.second.empty()) 261 me->headers_.insert(pair); 262 } 263 return hdr_len; 264 } 265 266 } // namespace curl 267 } // namespace http 268 } // namespace brillo 269