| // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <brillo/http/http_connection_curl.h> |
| |
| #include <base/logging.h> |
| #include <brillo/http/http_request.h> |
| #include <brillo/http/http_transport_curl.h> |
| #include <brillo/streams/memory_stream.h> |
| #include <brillo/streams/stream_utils.h> |
| #include <brillo/strings/string_utils.h> |
| |
| namespace brillo { |
| namespace http { |
| namespace curl { |
| |
| static int curl_trace(CURL* /* handle */, |
| curl_infotype type, |
| char* data, |
| size_t size, |
| void* /* userp */) { |
| std::string msg(data, size); |
| |
| switch (type) { |
| case CURLINFO_TEXT: |
| VLOG(3) << "== Info: " << msg; |
| break; |
| case CURLINFO_HEADER_OUT: |
| VLOG(3) << "=> Send headers:\n" << msg; |
| break; |
| case CURLINFO_DATA_OUT: |
| VLOG(3) << "=> Send data:\n" << msg; |
| break; |
| case CURLINFO_SSL_DATA_OUT: |
| VLOG(3) << "=> Send SSL data" << msg; |
| break; |
| case CURLINFO_HEADER_IN: |
| VLOG(3) << "<= Recv header: " << msg; |
| break; |
| case CURLINFO_DATA_IN: |
| VLOG(3) << "<= Recv data:\n" << msg; |
| break; |
| case CURLINFO_SSL_DATA_IN: |
| VLOG(3) << "<= Recv SSL data" << msg; |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| Connection::Connection(CURL* curl_handle, |
| const std::string& method, |
| const std::shared_ptr<CurlInterface>& curl_interface, |
| const std::shared_ptr<http::Transport>& transport) |
| : http::Connection(transport), |
| method_(method), |
| curl_handle_(curl_handle), |
| curl_interface_(curl_interface) { |
| // Store the connection pointer inside the CURL handle so we can easily |
| // retrieve it when doing asynchronous I/O. |
| curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this); |
| VLOG(2) << "curl::Connection created: " << method_; |
| } |
| |
| Connection::~Connection() { |
| if (header_list_) |
| curl_slist_free_all(header_list_); |
| curl_interface_->EasyCleanup(curl_handle_); |
| VLOG(2) << "curl::Connection destroyed"; |
| } |
| |
| bool Connection::SendHeaders(const HeaderList& headers, |
| brillo::ErrorPtr* /* error */) { |
| headers_.insert(headers.begin(), headers.end()); |
| return true; |
| } |
| |
| bool Connection::SetRequestData(StreamPtr stream, |
| brillo::ErrorPtr* /* error */) { |
| request_data_stream_ = std::move(stream); |
| return true; |
| } |
| |
| void Connection::SetResponseData(StreamPtr stream) { |
| response_data_stream_ = std::move(stream); |
| } |
| |
| void Connection::PrepareRequest() { |
| if (VLOG_IS_ON(3)) { |
| curl_interface_->EasySetOptCallback( |
| curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace); |
| curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1); |
| } |
| |
| if (method_ != request_type::kGet) { |
| // Set up HTTP request data. |
| uint64_t data_size = 0; |
| if (request_data_stream_ && request_data_stream_->CanGetSize()) |
| data_size = request_data_stream_->GetRemainingSize(); |
| |
| if (!request_data_stream_ || request_data_stream_->CanGetSize()) { |
| // Data size is known (either no data, or data size is available). |
| if (method_ == request_type::kPut) { |
| curl_interface_->EasySetOptOffT( |
| curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size); |
| } else { |
| curl_interface_->EasySetOptOffT( |
| curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size); |
| } |
| } else { |
| // Data size is unknown, so use chunked upload. |
| headers_.emplace(http::request_header::kTransferEncoding, "chunked"); |
| } |
| |
| if (request_data_stream_) { |
| curl_interface_->EasySetOptCallback( |
| curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback); |
| curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this); |
| } |
| } |
| |
| if (!headers_.empty()) { |
| CHECK(header_list_ == nullptr); |
| for (auto pair : headers_) { |
| std::string header = |
| brillo::string_utils::Join(": ", pair.first, pair.second); |
| VLOG(2) << "Request header: " << header; |
| header_list_ = curl_slist_append(header_list_, header.c_str()); |
| } |
| curl_interface_->EasySetOptPtr( |
| curl_handle_, CURLOPT_HTTPHEADER, header_list_); |
| } |
| |
| headers_.clear(); |
| |
| // Set up HTTP response data. |
| if (!response_data_stream_) |
| response_data_stream_ = MemoryStream::Create(nullptr); |
| if (method_ != request_type::kHead) { |
| curl_interface_->EasySetOptCallback( |
| curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback); |
| curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this); |
| } |
| |
| // HTTP response headers |
| curl_interface_->EasySetOptCallback( |
| curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback); |
| curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this); |
| } |
| |
| bool Connection::FinishRequest(brillo::ErrorPtr* error) { |
| PrepareRequest(); |
| CURLcode ret = curl_interface_->EasyPerform(curl_handle_); |
| if (ret != CURLE_OK) { |
| Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get()); |
| } else { |
| // Rewind our data stream to the beginning so that it can be read back. |
| if (response_data_stream_->CanSeek() && |
| !response_data_stream_->SetPosition(0, error)) |
| return false; |
| LOG(INFO) << "Response: " << GetResponseStatusCode() << " (" |
| << GetResponseStatusText() << ")"; |
| } |
| return (ret == CURLE_OK); |
| } |
| |
| RequestID Connection::FinishRequestAsync( |
| const SuccessCallback& success_callback, |
| const ErrorCallback& error_callback) { |
| PrepareRequest(); |
| return transport_->StartAsyncTransfer(this, success_callback, error_callback); |
| } |
| |
| int Connection::GetResponseStatusCode() const { |
| int status_code = 0; |
| curl_interface_->EasyGetInfoInt( |
| curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); |
| return status_code; |
| } |
| |
| std::string Connection::GetResponseStatusText() const { |
| return status_text_; |
| } |
| |
| std::string Connection::GetProtocolVersion() const { |
| return protocol_version_; |
| } |
| |
| std::string Connection::GetResponseHeader( |
| const std::string& header_name) const { |
| auto p = headers_.find(header_name); |
| return p != headers_.end() ? p->second : std::string(); |
| } |
| |
| StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) { |
| if (!response_data_stream_) { |
| stream_utils::ErrorStreamClosed(FROM_HERE, error); |
| } |
| return std::move(response_data_stream_); |
| } |
| |
| size_t Connection::write_callback(char* ptr, |
| size_t size, |
| size_t num, |
| void* data) { |
| Connection* me = reinterpret_cast<Connection*>(data); |
| size_t data_len = size * num; |
| VLOG(1) << "Response data (" << data_len << "): " |
| << std::string{ptr, data_len}; |
| // TODO(nathanbullock): Currently we are relying on the stream not blocking, |
| // but if the stream is representing a pipe or some other construct that might |
| // block then this code will behave badly. |
| if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) { |
| LOG(ERROR) << "Failed to write response data"; |
| data_len = 0; |
| } |
| return data_len; |
| } |
| |
| size_t Connection::read_callback(char* ptr, |
| size_t size, |
| size_t num, |
| void* data) { |
| Connection* me = reinterpret_cast<Connection*>(data); |
| size_t data_len = size * num; |
| |
| size_t read_size = 0; |
| bool success = me->request_data_stream_->ReadBlocking(ptr, data_len, |
| &read_size, nullptr); |
| VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size}; |
| return success ? read_size : CURL_READFUNC_ABORT; |
| } |
| |
| size_t Connection::header_callback(char* ptr, |
| size_t size, |
| size_t num, |
| void* data) { |
| using brillo::string_utils::SplitAtFirst; |
| Connection* me = reinterpret_cast<Connection*>(data); |
| size_t hdr_len = size * num; |
| std::string header(ptr, hdr_len); |
| // Remove newlines at the end of header line. |
| while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { |
| header.pop_back(); |
| } |
| |
| VLOG(2) << "Response header: " << header; |
| |
| if (!me->status_text_set_) { |
| // First header - response code as "HTTP/1.1 200 OK". |
| // Need to extract the OK part |
| auto pair = SplitAtFirst(header, " "); |
| me->protocol_version_ = pair.first; |
| me->status_text_ = SplitAtFirst(pair.second, " ").second; |
| me->status_text_set_ = true; |
| } else { |
| auto pair = SplitAtFirst(header, ":"); |
| if (!pair.second.empty()) |
| me->headers_.insert(pair); |
| } |
| return hdr_len; |
| } |
| |
| } // namespace curl |
| } // namespace http |
| } // namespace brillo |