blob: 47090e9d2a56578ec37690045b602b6a6a230cff [file] [log] [blame]
Chris Sosa45d9f102014-03-24 11:18:54 -07001// 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
16using namespace chromeos;
17using namespace chromeos::http::curl;
18
19Transport::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
25Transport::~Transport() {
26 Close();
27}
28
29void 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
37void Transport::AddRange(uint64_t fromByte, uint64_t toByte) {
38 ranges_.emplace_back(fromByte, toByte);
39}
40
41std::string Transport::GetAccept() const {
42 return accept_;
43}
44
45std::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
73void Transport::AddHeader(char const* header, char const* value) {
74 headers_[header] = value;
75}
76
77void Transport::RemoveHeader(char const* header) {
78 AddHeader(header, "");
79}
80
81bool 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
95bool 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
192int 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
200std::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
206void Transport::Close() {
207 if (curl_handle_) {
208 curl_easy_cleanup(curl_handle_);
209 curl_handle_ = nullptr;
210 }
211 stage_ = Stage::closed;
212}
213
214size_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
222size_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
239size_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}