blob: ebdd3ccdac974210d2e1e66b63161e5cb45f0168 [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>
8
9#include "buffet/http_request.h"
Alex Vakulenkob3aac252014-05-07 17:35:24 -070010#include "buffet/http_transport_curl.h"
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070011#include "buffet/string_utils.h"
12
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 Vakulenkob3aac252014-05-07 17:35:24 -070059bool Connection::SendHeaders(const HeaderList& headers, ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070060 headers_.insert(headers.begin(), headers.end());
61 return true;
62}
63
Alex Vakulenkob3aac252014-05-07 17:35:24 -070064bool Connection::WriteRequestData(const void* data, size_t size,
65 ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070066 if (size > 0) {
67 auto data_ptr = reinterpret_cast<const unsigned char*>(data);
68 request_data_.insert(request_data_.end(), data_ptr, data_ptr + size);
69 }
70 return true;
71}
72
Alex Vakulenkob3aac252014-05-07 17:35:24 -070073bool Connection::FinishRequest(ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -070074 if (VLOG_IS_ON(3)) {
75 curl_easy_setopt(curl_handle_, CURLOPT_DEBUGFUNCTION, curl_trace);
76 curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L);
77 }
78
79 // Set up HTTP request data.
80 if (method_ == request_type::kPut) {
81 curl_easy_setopt(curl_handle_, CURLOPT_INFILESIZE_LARGE,
82 curl_off_t(request_data_.size()));
83 } else {
84 curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE,
85 curl_off_t(request_data_.size()));
86 }
87 if (!request_data_.empty()) {
88 curl_easy_setopt(curl_handle_,
89 CURLOPT_READFUNCTION, &Connection::read_callback);
90 curl_easy_setopt(curl_handle_, CURLOPT_READDATA, this);
91 VLOG(2) << "Raw request data: "
92 << std::string(reinterpret_cast<const char*>(request_data_.data()),
93 request_data_.size());
94 }
95
96 curl_slist* header_list = nullptr;
97 if (!headers_.empty()) {
98 for (auto pair : headers_) {
99 std::string header = string_utils::Join(": ", pair.first, pair.second);
100 VLOG(2) << "Request header: " << header;
101 header_list = curl_slist_append(header_list, header.c_str());
102 }
103 curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, header_list);
104 }
105
106 headers_.clear();
107
108 // Set up HTTP response data.
109 if (method_ != request_type::kHead) {
110 curl_easy_setopt(curl_handle_,
111 CURLOPT_WRITEFUNCTION, &Connection::write_callback);
112 curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this);
113 }
114
115 // HTTP response headers
116 curl_easy_setopt(curl_handle_,
117 CURLOPT_HEADERFUNCTION, &Connection::header_callback);
118 curl_easy_setopt(curl_handle_, CURLOPT_HEADERDATA, this);
119
120 CURLcode ret = curl_easy_perform(curl_handle_);
121 if (header_list)
122 curl_slist_free_all(header_list);
123 if (ret != CURLE_OK) {
Alex Vakulenko96c84d32014-06-06 11:07:32 -0700124 Error::AddTo(error, http::curl::kErrorDomain, string_utils::ToString(ret),
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700125 curl_easy_strerror(ret));
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700126 } else {
127 LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
128 << GetResponseStatusText() << ")";
129 VLOG(2) << "Response data (" << response_data_.size() << "): "
130 << std::string(reinterpret_cast<const char*>(response_data_.data()),
131 response_data_.size());
132 }
133 return (ret == CURLE_OK);
134}
135
136int Connection::GetResponseStatusCode() const {
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700137 long status_code = 0; // NOLINT(runtime/int) - curl expects a long here.
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700138 curl_easy_getinfo(curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
139 return status_code;
140}
141
142std::string Connection::GetResponseStatusText() const {
143 return status_text_;
144}
145
146std::string Connection::GetProtocolVersion() const {
147 return protocol_version_;
148}
149
150std::string Connection::GetResponseHeader(
151 const std::string& header_name) const {
152 auto p = headers_.find(header_name);
153 return p != headers_.end() ? p->second : std::string();
154}
155
156uint64_t Connection::GetResponseDataSize() const {
157 return response_data_.size();
158}
159
160bool Connection::ReadResponseData(void* data, size_t buffer_size,
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700161 size_t* size_read, ErrorPtr* error) {
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700162 size_t size_to_read = response_data_.size() - response_data_ptr_;
163 if (size_to_read > buffer_size)
164 size_to_read = buffer_size;
165 memcpy(data, response_data_.data() + response_data_ptr_, size_to_read);
166 if (size_read)
167 *size_read = size_to_read;
168 response_data_ptr_ += size_to_read;
169 return true;
170}
171
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700172size_t Connection::write_callback(char* ptr, size_t size,
173 size_t num, void* data) {
174 Connection* me = reinterpret_cast<Connection*>(data);
175 size_t data_len = size * num;
176 me->response_data_.insert(me->response_data_.end(), ptr, ptr + data_len);
177 return data_len;
178}
179
180size_t Connection::read_callback(char* ptr, size_t size,
181 size_t num, void* data) {
182 Connection* me = reinterpret_cast<Connection*>(data);
183 size_t data_len = size * num;
184
185 if (me->request_data_ptr_ >= me->request_data_.size())
186 return 0;
187
188 if (me->request_data_ptr_ + data_len > me->request_data_.size())
189 data_len = me->request_data_.size() - me->request_data_ptr_;
190
191 memcpy(ptr, me->request_data_.data() + me->request_data_ptr_, data_len);
192 me->request_data_ptr_ += data_len;
193
194 return data_len;
195}
196
197size_t Connection::header_callback(char* ptr, size_t size,
198 size_t num, void* data) {
199 Connection* me = reinterpret_cast<Connection*>(data);
200 size_t hdr_len = size * num;
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700201 std::string header(ptr, hdr_len);
Alex Vakulenkoa3062c52014-04-21 17:05:51 -0700202 // Remove newlines at the end of header line.
203 while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) {
204 header.pop_back();
205 }
206
207 VLOG(2) << "Response header: " << header;
208
209 if (!me->status_text_set_) {
210 // First header - response code as "HTTP/1.1 200 OK".
211 // Need to extract the OK part
212 auto pair = string_utils::SplitAtFirst(header, ' ');
213 me->protocol_version_ = pair.first;
214 me->status_text_ = string_utils::SplitAtFirst(pair.second, ' ').second;
215 me->status_text_set_ = true;
216 } else {
217 auto pair = string_utils::SplitAtFirst(header, ':');
218 if (!pair.second.empty())
219 me->headers_.insert(pair);
220 }
221 return hdr_len;
222}
Alex Vakulenkob3aac252014-05-07 17:35:24 -0700223
224} // namespace curl
225} // namespace http
Alex Vakulenkoaf23b322014-05-08 16:25:45 -0700226} // namespace buffet