Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 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 "buffet/http_connection_curl.h" |
| 6 | |
| 7 | #include <base/logging.h> |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 8 | #include <chromeos/string_utils.h> |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 9 | |
| 10 | #include "buffet/http_request.h" |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 11 | #include "buffet/http_transport_curl.h" |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 12 | |
Alex Vakulenko | af23b32 | 2014-05-08 16:25:45 -0700 | [diff] [blame] | 13 | namespace buffet { |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 14 | namespace http { |
| 15 | namespace curl { |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 16 | |
| 17 | static int curl_trace(CURL *handle, curl_infotype type, |
| 18 | char *data, size_t size, void *userp) { |
| 19 | std::string msg(data, size); |
| 20 | |
| 21 | switch (type) { |
| 22 | case CURLINFO_TEXT: |
| 23 | VLOG(3) << "== Info: " << msg; |
| 24 | break; |
| 25 | case CURLINFO_HEADER_OUT: |
| 26 | VLOG(3) << "=> Send headers:\n" << msg; |
| 27 | break; |
| 28 | case CURLINFO_DATA_OUT: |
| 29 | VLOG(3) << "=> Send data:\n" << msg; |
| 30 | break; |
| 31 | case CURLINFO_SSL_DATA_OUT: |
| 32 | VLOG(3) << "=> Send SSL data" << msg; |
| 33 | break; |
| 34 | case CURLINFO_HEADER_IN: |
| 35 | VLOG(3) << "<= Recv header: " << msg; |
| 36 | break; |
| 37 | case CURLINFO_DATA_IN: |
| 38 | VLOG(3) << "<= Recv data:\n" << msg; |
| 39 | break; |
| 40 | case CURLINFO_SSL_DATA_IN: |
| 41 | VLOG(3) << "<= Recv SSL data" << msg; |
| 42 | break; |
| 43 | default: |
| 44 | break; |
| 45 | } |
| 46 | return 0; |
| 47 | } |
| 48 | |
| 49 | Connection::Connection(CURL* curl_handle, const std::string& method, |
| 50 | std::shared_ptr<http::Transport> transport) : |
| 51 | http::Connection(transport), method_(method), curl_handle_(curl_handle) { |
| 52 | VLOG(1) << "curl::Connection created: " << method_; |
| 53 | } |
| 54 | |
| 55 | Connection::~Connection() { |
| 56 | VLOG(1) << "curl::Connection destroyed"; |
| 57 | } |
| 58 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 59 | bool Connection::SendHeaders(const HeaderList& headers, |
| 60 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 61 | headers_.insert(headers.begin(), headers.end()); |
| 62 | return true; |
| 63 | } |
| 64 | |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 65 | bool Connection::WriteRequestData(const void* data, size_t size, |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 66 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 67 | if (size > 0) { |
| 68 | auto data_ptr = reinterpret_cast<const unsigned char*>(data); |
| 69 | request_data_.insert(request_data_.end(), data_ptr, data_ptr + size); |
| 70 | } |
| 71 | return true; |
| 72 | } |
| 73 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 74 | bool Connection::FinishRequest(chromeos::ErrorPtr* error) { |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 75 | if (VLOG_IS_ON(3)) { |
| 76 | curl_easy_setopt(curl_handle_, CURLOPT_DEBUGFUNCTION, curl_trace); |
| 77 | curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L); |
| 78 | } |
| 79 | |
| 80 | // Set up HTTP request data. |
| 81 | if (method_ == request_type::kPut) { |
| 82 | curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE, |
| 83 | curl_off_t(request_data_.size())); |
| 84 | } else { |
| 85 | curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, |
| 86 | curl_off_t(request_data_.size())); |
| 87 | } |
| 88 | if (!request_data_.empty()) { |
| 89 | curl_easy_setopt(curl_handle_, |
| 90 | CURLOPT_READFUNCTION, &Connection::read_callback); |
| 91 | curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); |
| 92 | VLOG(2) << "Raw request data: " |
| 93 | << std::string(reinterpret_cast<const char*>(request_data_.data()), |
| 94 | request_data_.size()); |
| 95 | } |
| 96 | |
| 97 | curl_slist* header_list = nullptr; |
| 98 | if (!headers_.empty()) { |
| 99 | for (auto pair : headers_) { |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 100 | std::string header = chromeos::string_utils::Join(": ", |
| 101 | pair.first, |
| 102 | pair.second); |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 103 | VLOG(2) << "Request header: " << header; |
| 104 | header_list = curl_slist_append(header_list, header.c_str()); |
| 105 | } |
| 106 | curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list); |
| 107 | } |
| 108 | |
| 109 | headers_.clear(); |
| 110 | |
| 111 | // Set up HTTP response data. |
| 112 | if (method_ != request_type::kHead) { |
| 113 | curl_easy_setopt(curl_handle_, |
| 114 | CURLOPT_WRITEFUNCTION, &Connection::write_callback); |
| 115 | curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this); |
| 116 | } |
| 117 | |
| 118 | // HTTP response headers |
| 119 | curl_easy_setopt(curl_handle_, |
| 120 | CURLOPT_HEADERFUNCTION, &Connection::header_callback); |
| 121 | curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this); |
| 122 | |
| 123 | CURLcode ret = curl_easy_perform(curl_handle_); |
| 124 | if (header_list) |
| 125 | curl_slist_free_all(header_list); |
| 126 | if (ret != CURLE_OK) { |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 127 | chromeos::Error::AddTo(error, http::curl::kErrorDomain, |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 128 | chromeos::string_utils::ToString(ret), |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 129 | curl_easy_strerror(ret)); |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 130 | } else { |
| 131 | LOG(INFO) << "Response: " << GetResponseStatusCode() << " (" |
| 132 | << GetResponseStatusText() << ")"; |
| 133 | VLOG(2) << "Response data (" << response_data_.size() << "): " |
| 134 | << std::string(reinterpret_cast<const char*>(response_data_.data()), |
| 135 | response_data_.size()); |
| 136 | } |
| 137 | return (ret == CURLE_OK); |
| 138 | } |
| 139 | |
| 140 | int Connection::GetResponseStatusCode() const { |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 141 | long status_code = 0; // NOLINT(runtime/int) - curl expects a long here. |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 142 | curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); |
| 143 | return status_code; |
| 144 | } |
| 145 | |
| 146 | std::string Connection::GetResponseStatusText() const { |
| 147 | return status_text_; |
| 148 | } |
| 149 | |
| 150 | std::string Connection::GetProtocolVersion() const { |
| 151 | return protocol_version_; |
| 152 | } |
| 153 | |
| 154 | std::string Connection::GetResponseHeader( |
| 155 | const std::string& header_name) const { |
| 156 | auto p = headers_.find(header_name); |
| 157 | return p != headers_.end() ? p->second : std::string(); |
| 158 | } |
| 159 | |
| 160 | uint64_t Connection::GetResponseDataSize() const { |
| 161 | return response_data_.size(); |
| 162 | } |
| 163 | |
Alex Vakulenko | 5f47206 | 2014-08-14 17:54:04 -0700 | [diff] [blame] | 164 | bool Connection::ReadResponseData(void* data, |
| 165 | size_t buffer_size, |
| 166 | size_t* size_read, |
| 167 | chromeos::ErrorPtr* error) { |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 168 | size_t size_to_read = response_data_.size() - response_data_ptr_; |
| 169 | if (size_to_read > buffer_size) |
| 170 | size_to_read = buffer_size; |
| 171 | memcpy(data, response_data_.data() + response_data_ptr_, size_to_read); |
| 172 | if (size_read) |
| 173 | *size_read = size_to_read; |
| 174 | response_data_ptr_ += size_to_read; |
| 175 | return true; |
| 176 | } |
| 177 | |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 178 | size_t Connection::write_callback(char* ptr, size_t size, |
| 179 | size_t num, void* data) { |
| 180 | Connection* me = reinterpret_cast<Connection*>(data); |
| 181 | size_t data_len = size * num; |
| 182 | me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len); |
| 183 | return data_len; |
| 184 | } |
| 185 | |
| 186 | size_t Connection::read_callback(char* ptr, size_t size, |
| 187 | size_t num, void* data) { |
| 188 | Connection* me = reinterpret_cast<Connection*>(data); |
| 189 | size_t data_len = size * num; |
| 190 | |
| 191 | if (me->request_data_ptr_ >= me->request_data_.size()) |
| 192 | return 0; |
| 193 | |
| 194 | if (me->request_data_ptr_ + data_len > me->request_data_.size()) |
| 195 | data_len = me->request_data_.size() - me->request_data_ptr_; |
| 196 | |
| 197 | memcpy(ptr, me->request_data_.data() + me->request_data_ptr_, data_len); |
| 198 | me->request_data_ptr_ += data_len; |
| 199 | |
| 200 | return data_len; |
| 201 | } |
| 202 | |
| 203 | size_t Connection::header_callback(char* ptr, size_t size, |
| 204 | size_t num, void* data) { |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 205 | using chromeos::string_utils::SplitAtFirst; |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 206 | Connection* me = reinterpret_cast<Connection*>(data); |
| 207 | size_t hdr_len = size * num; |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 208 | std::string header(ptr, hdr_len); |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 209 | // Remove newlines at the end of header line. |
| 210 | while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) { |
| 211 | header.pop_back(); |
| 212 | } |
| 213 | |
| 214 | VLOG(2) << "Response header: " << header; |
| 215 | |
| 216 | if (!me->status_text_set_) { |
| 217 | // First header - response code as "HTTP/1.1 200 OK". |
| 218 | // Need to extract the OK part |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 219 | auto pair = SplitAtFirst(header, ' '); |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 220 | me->protocol_version_ = pair.first; |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 221 | me->status_text_ = SplitAtFirst(pair.second, ' ').second; |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 222 | me->status_text_set_ = true; |
| 223 | } else { |
Alex Vakulenko | b8fc1df | 2014-08-20 15:38:07 -0700 | [diff] [blame] | 224 | auto pair = SplitAtFirst(header, ':'); |
Alex Vakulenko | a3062c5 | 2014-04-21 17:05:51 -0700 | [diff] [blame] | 225 | if (!pair.second.empty()) |
| 226 | me->headers_.insert(pair); |
| 227 | } |
| 228 | return hdr_len; |
| 229 | } |
Alex Vakulenko | b3aac25 | 2014-05-07 17:35:24 -0700 | [diff] [blame] | 230 | |
| 231 | } // namespace curl |
| 232 | } // namespace http |
Alex Vakulenko | af23b32 | 2014-05-08 16:25:45 -0700 | [diff] [blame] | 233 | } // namespace buffet |