Chris Sosa | 45d9f10 | 2014-03-24 11:18:54 -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_transport_curl.h" |
| 6 | |
| 7 | #define __STDC_FORMAT_MACROS |
| 8 | #include <inttypes.h> |
| 9 | #include <string.h> |
| 10 | #include <base/logging.h> |
| 11 | |
| 12 | #include "buffet/mime_utils.h" |
| 13 | #include "buffet/string_utils.h" |
| 14 | #include "buffet/map_utils.h" |
| 15 | |
| 16 | using namespace chromeos; |
| 17 | using namespace chromeos::http::curl; |
| 18 | |
| 19 | Transport::Transport(std::string const& url, char const* method) : |
| 20 | request_url_(url), |
| 21 | method_(method ? method : request_type::kGet) { |
| 22 | stage_ = Stage::initialized; |
| 23 | } |
| 24 | |
| 25 | Transport::~Transport() { |
| 26 | Close(); |
| 27 | } |
| 28 | |
| 29 | void Transport::AddRange(int64_t bytes) { |
| 30 | if (bytes < 0) { |
| 31 | ranges_.emplace_back(Transport::range_value_omitted, -bytes); |
| 32 | } else { |
| 33 | ranges_.emplace_back(bytes, Transport::range_value_omitted); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | void Transport::AddRange(uint64_t fromByte, uint64_t toByte) { |
| 38 | ranges_.emplace_back(fromByte, toByte); |
| 39 | } |
| 40 | |
| 41 | std::string Transport::GetAccept() const { |
| 42 | return accept_; |
| 43 | } |
| 44 | |
| 45 | std::vector<std::pair<std::string, std::string>> Transport::GetHeaders() const { |
| 46 | auto headers = MapToVector(headers_); |
| 47 | std::vector<std::string> ranges; |
| 48 | if (method_ != request_type::kHead) { |
| 49 | ranges.reserve(ranges_.size()); |
| 50 | for (auto p : ranges_) { |
| 51 | if (p.first != range_value_omitted || p.second != range_value_omitted) { |
| 52 | std::string range; |
| 53 | if (p.first != range_value_omitted) { |
| 54 | range = std::to_string(p.first); |
| 55 | } |
| 56 | range += '-'; |
| 57 | if (p.second != range_value_omitted) { |
| 58 | range += std::to_string(p.second); |
| 59 | } |
| 60 | ranges.push_back(range); |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | if (!ranges.empty()) |
| 65 | headers.emplace_back(request_header::kRange, |
| 66 | "bytes=" + string_utils::Join(',', ranges)); |
| 67 | |
| 68 | headers.emplace_back(request_header::kAccept, GetAccept()); |
| 69 | |
| 70 | return headers; |
| 71 | } |
| 72 | |
| 73 | void Transport::AddHeader(char const* header, char const* value) { |
| 74 | headers_[header] = value; |
| 75 | } |
| 76 | |
| 77 | void Transport::RemoveHeader(char const* header) { |
| 78 | AddHeader(header, ""); |
| 79 | } |
| 80 | |
| 81 | bool Transport::AddRequestBody(void const* data, size_t size) { |
| 82 | if (size == 0) |
| 83 | return true; |
| 84 | |
| 85 | if (data == nullptr) { |
| 86 | LOG(ERROR) << "Invalid request body data pointer"; |
| 87 | return false; |
| 88 | } |
| 89 | |
| 90 | unsigned char const* data_ptr = reinterpret_cast<unsigned char const*>(data); |
| 91 | request_data_.insert(request_data_.end(), data_ptr, data_ptr + size); |
| 92 | return true; |
| 93 | } |
| 94 | |
| 95 | bool Transport::Perform() { |
| 96 | if (stage_ != Stage::initialized) { |
| 97 | LOG(ERROR) << "Cannot call Perform() on unintialized transport object"; |
| 98 | return false; |
| 99 | } |
| 100 | |
| 101 | curl_handle_ = curl_easy_init(); |
| 102 | if (!curl_handle_) { |
| 103 | LOG(ERROR) << "Failed to initialize CURL"; |
| 104 | return false; |
| 105 | } |
| 106 | |
| 107 | curl_easy_setopt(curl_handle_, CURLOPT_URL, request_url_.c_str()); |
| 108 | |
| 109 | if (!user_agent_.empty()) { |
| 110 | curl_easy_setopt(curl_handle_, |
| 111 | CURLOPT_USERAGENT, user_agent_.c_str()); |
| 112 | } |
| 113 | |
| 114 | if (!referer_.empty()) { |
| 115 | curl_easy_setopt(curl_handle_, |
| 116 | CURLOPT_REFERER, referer_.c_str()); |
| 117 | } |
| 118 | |
| 119 | // Setup HTTP request method and optional request body. |
| 120 | if (method_ == request_type::kGet) { |
| 121 | curl_easy_setopt(curl_handle_, CURLOPT_HTTPGET, 1L); |
| 122 | } else if (method_ == request_type::kHead) { |
| 123 | curl_easy_setopt(curl_handle_, CURLOPT_NOBODY, 1L); |
| 124 | } else if (method_ == request_type::kPost) { |
| 125 | curl_easy_setopt(curl_handle_, CURLOPT_POST, 1L); |
| 126 | curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS, nullptr); |
| 127 | if (!request_data_.empty()) { |
| 128 | curl_easy_setopt(curl_handle_, |
| 129 | CURLOPT_READFUNCTION, &Transport::read_callback); |
| 130 | curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); |
| 131 | } |
| 132 | curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, |
| 133 | curl_off_t(request_data_.size())); |
| 134 | } else if (method_ == request_type::kPut) { |
| 135 | curl_easy_setopt(curl_handle_, CURLOPT_UPLOAD, 1L); |
| 136 | curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE, |
| 137 | curl_off_t(request_data_.size())); |
| 138 | curl_easy_setopt(curl_handle_, |
| 139 | CURLOPT_READFUNCTION, &Transport::read_callback); |
| 140 | curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); |
| 141 | } else { |
| 142 | curl_easy_setopt(curl_handle_, CURLOPT_CUSTOMREQUEST, method_.c_str()); |
| 143 | if (!request_data_.empty()) { |
| 144 | curl_easy_setopt(curl_handle_, |
| 145 | CURLOPT_READFUNCTION, &Transport::read_callback); |
| 146 | curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // Setup HTTP response data. |
| 151 | if (method_ != request_type::kHead) { |
| 152 | curl_easy_setopt(curl_handle_, |
| 153 | CURLOPT_WRITEFUNCTION, &Transport::write_callback); |
| 154 | curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this); |
| 155 | } |
| 156 | |
| 157 | // HTTP request headers |
| 158 | auto headers = GetHeaders(); |
| 159 | if (method_ != request_type::kGet && method_ != request_type::kHead) { |
| 160 | if (!content_type_.empty()) |
| 161 | headers.emplace_back(request_header::kContentType, content_type_); |
| 162 | } |
| 163 | |
| 164 | curl_slist* header_list = nullptr; |
| 165 | if (!headers.empty()) { |
| 166 | for (auto pair : headers) { |
| 167 | std::string header = string_utils::Join(": ", pair.first, pair.second); |
| 168 | header_list = curl_slist_append(header_list, header.c_str()); |
| 169 | } |
| 170 | curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list); |
| 171 | } |
| 172 | |
| 173 | headers.clear(); |
| 174 | |
| 175 | // HTTP response headers |
| 176 | curl_easy_setopt(curl_handle_, |
| 177 | CURLOPT_HEADERFUNCTION, &Transport::header_callback); |
| 178 | curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this); |
| 179 | |
| 180 | CURLcode ret = curl_easy_perform(curl_handle_); |
| 181 | if (ret != CURLE_OK) { |
| 182 | error_ = curl_easy_strerror(ret); |
| 183 | stage_ = Stage::failed; |
| 184 | LOG(ERROR) << "CURL request failed: " << error_; |
| 185 | } else { |
| 186 | stage_ = Stage::response_received; |
| 187 | } |
| 188 | curl_slist_free_all(header_list); |
| 189 | return (ret == CURLE_OK); |
| 190 | } |
| 191 | |
| 192 | int Transport::GetResponseStatusCode() const { |
| 193 | if (stage_ != Stage::response_received) |
| 194 | return 0; |
| 195 | long status_code = 0; |
| 196 | curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code); |
| 197 | return status_code; |
| 198 | } |
| 199 | |
| 200 | std::string Transport::GetResponseHeader(char const* headerName) const { |
| 201 | auto p = headers_.find(headerName); |
| 202 | return p != headers_.end() ? p->second : std::string(); |
| 203 | } |
| 204 | |
| 205 | |
| 206 | void Transport::Close() { |
| 207 | if (curl_handle_) { |
| 208 | curl_easy_cleanup(curl_handle_); |
| 209 | curl_handle_ = nullptr; |
| 210 | } |
| 211 | stage_ = Stage::closed; |
| 212 | } |
| 213 | |
| 214 | size_t Transport::write_callback(char* ptr, size_t size, |
| 215 | size_t num, void* data) { |
| 216 | Transport* me = reinterpret_cast<Transport*>(data); |
| 217 | size_t data_len = size * num; |
| 218 | me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len); |
| 219 | return data_len; |
| 220 | } |
| 221 | |
| 222 | size_t Transport::read_callback(char* ptr, size_t size, |
| 223 | size_t num, void* data) { |
| 224 | Transport* me = reinterpret_cast<Transport*>(data); |
| 225 | size_t data_len = size * num; |
| 226 | |
| 227 | if (me->request_data_ptr_ >= me->request_data_.size()) |
| 228 | return 0; |
| 229 | |
| 230 | if (me->request_data_ptr_ + data_len > me->request_data_.size()) |
| 231 | data_len = me->request_data_.size() - me->request_data_ptr_; |
| 232 | |
| 233 | memcpy(ptr, me->request_data_.data() + me->request_data_ptr_, data_len); |
| 234 | me->request_data_ptr_ += data_len; |
| 235 | |
| 236 | return data_len; |
| 237 | } |
| 238 | |
| 239 | size_t Transport::header_callback(char* ptr, size_t size, |
| 240 | size_t num, void* data) { |
| 241 | Transport* me = reinterpret_cast<Transport*>(data); |
| 242 | size_t hdr_len = size * num; |
| 243 | std::string header(ptr, int(hdr_len)); |
| 244 | if (!me->status_text_set_) { |
| 245 | // First header - response code as "HTTP/1.1 200 OK". |
| 246 | // Need to extract the OK part |
| 247 | size_t pos = header.find(' '); |
| 248 | if(pos != std::string::npos) |
| 249 | pos = header.find(' ', pos + 1); |
| 250 | if (pos != std::string::npos) |
| 251 | me->status_text_ = header.substr(pos + 1); |
| 252 | me->status_text_set_ = true; |
| 253 | } else { |
| 254 | auto pair = string_utils::SplitAtFirst(header, ':'); |
| 255 | if (!pair.second.empty()) |
| 256 | me->headers_.insert(pair); |
| 257 | } |
| 258 | return hdr_len; |
| 259 | } |