blob: 86715abb725c1ead62603acb06050341740d57be [file] [log] [blame]
Alex Vakulenkoa3062c52014-04-21 17:05:51 -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_connection_curl.h"
6
7#include <base/logging.h>
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -07008#include <chromeos/string_utils.h>
Alex Vakulenkoa3062c52014-04-21 17:05:51 -07009
10#include "buffet/http_request.h"
Alex Vakulenkob3aac252014-05-07 17:35:24 -070011#include "buffet/http_transport_curl.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070012
Alex Vakulenkoaf23b322014-05-08 16:25:45 -070013namespace buffet {
Alex Vakulenkob3aac252014-05-07 17:35:24 -070014namespace http {
15namespace curl {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070016
17static 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
49Connection::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
55Connection::~Connection() {
56 VLOG(1) << "curl::Connection destroyed";
57}
58
Alex Vakulenko5f472062014-08-14 17:54:04 -070059bool Connection::SendHeaders(const HeaderList& headers,
60 chromeos::ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070061 headers_.insert(headers.begin(), headers.end());
62 return true;
63}
64
Alex Vakulenkob3aac252014-05-07 17:35:24 -070065bool Connection::WriteRequestData(const void* data, size_t size,
Alex Vakulenko5f472062014-08-14 17:54:04 -070066 chromeos::ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070067 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 Vakulenko5f472062014-08-14 17:54:04 -070074bool Connection::FinishRequest(chromeos::ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070075 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 Vakulenkob8fc1df2014-08-20 15:38:07 -0700100 std::string header = chromeos::string_utils::Join(": ",
101 pair.first,
102 pair.second);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700103 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 Vakulenko5f472062014-08-14 17:54:04 -0700127 chromeos::Error::AddTo(error, http::curl::kErrorDomain,
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -0700128 chromeos::string_utils::ToString(ret),
Alex Vakulenko5f472062014-08-14 17:54:04 -0700129 curl_easy_strerror(ret));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700130 } 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
140int Connection::GetResponseStatusCode() const {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700141 long status_code = 0; // NOLINT(runtime/int) - curl expects a long here.
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700142 curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
143 return status_code;
144}
145
146std::string Connection::GetResponseStatusText() const {
147 return status_text_;
148}
149
150std::string Connection::GetProtocolVersion() const {
151 return protocol_version_;
152}
153
154std::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
160uint64_t Connection::GetResponseDataSize() const {
161 return response_data_.size();
162}
163
Alex Vakulenko5f472062014-08-14 17:54:04 -0700164bool Connection::ReadResponseData(void* data,
165 size_t buffer_size,
166 size_t* size_read,
167 chromeos::ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700168 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 Vakulenkoa3062c52014-04-21 17:05:51 -0700178size_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
186size_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
203size_t Connection::header_callback(char* ptr, size_t size,
204 size_t num, void* data) {
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -0700205 using chromeos::string_utils::SplitAtFirst;
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700206 Connection* me = reinterpret_cast<Connection*>(data);
207 size_t hdr_len = size * num;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700208 std::string header(ptr, hdr_len);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700209 // 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 Vakulenkob8fc1df2014-08-20 15:38:07 -0700219 auto pair = SplitAtFirst(header, ' ');
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700220 me->protocol_version_ = pair.first;
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -0700221 me->status_text_ = SplitAtFirst(pair.second, ' ').second;
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700222 me->status_text_set_ = true;
223 } else {
Alex Vakulenkob8fc1df2014-08-20 15:38:07 -0700224 auto pair = SplitAtFirst(header, ':');
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700225 if (!pair.second.empty())
226 me->headers_.insert(pair);
227 }
228 return hdr_len;
229}
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700230
231} // namespace curl
232} // namespace http
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700233} // namespace buffet