Home | History | Annotate | Download | only in http
      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