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 <brillo/http/http_connection_curl.h>
6
7 #include <base/logging.h>
8 #include <brillo/http/http_request.h>
9 #include <brillo/http/http_transport_curl.h>
10 #include <brillo/streams/memory_stream.h>
11 #include <brillo/streams/stream_utils.h>
12 #include <brillo/strings/string_utils.h>
13
14 namespace brillo {
15 namespace http {
16 namespace curl {
17
curl_trace(CURL *,curl_infotype type,char * data,size_t size,void *)18 static int curl_trace(CURL* /* handle */,
19 curl_infotype type,
20 char* data,
21 size_t size,
22 void* /* userp */) {
23 std::string msg(data, size);
24
25 switch (type) {
26 case CURLINFO_TEXT:
27 VLOG(3) << "== Info: " << msg;
28 break;
29 case CURLINFO_HEADER_OUT:
30 VLOG(3) << "=> Send headers:\n" << msg;
31 break;
32 case CURLINFO_DATA_OUT:
33 VLOG(3) << "=> Send data:\n" << msg;
34 break;
35 case CURLINFO_SSL_DATA_OUT:
36 VLOG(3) << "=> Send SSL data" << msg;
37 break;
38 case CURLINFO_HEADER_IN:
39 VLOG(3) << "<= Recv header: " << msg;
40 break;
41 case CURLINFO_DATA_IN:
42 VLOG(3) << "<= Recv data:\n" << msg;
43 break;
44 case CURLINFO_SSL_DATA_IN:
45 VLOG(3) << "<= Recv SSL data" << msg;
46 break;
47 default:
48 break;
49 }
50 return 0;
51 }
52
Connection(CURL * curl_handle,const std::string & method,const std::shared_ptr<CurlInterface> & curl_interface,const std::shared_ptr<http::Transport> & transport)53 Connection::Connection(CURL* curl_handle,
54 const std::string& method,
55 const std::shared_ptr<CurlInterface>& curl_interface,
56 const std::shared_ptr<http::Transport>& transport)
57 : http::Connection(transport),
58 method_(method),
59 curl_handle_(curl_handle),
60 curl_interface_(curl_interface) {
61 // Store the connection pointer inside the CURL handle so we can easily
62 // retrieve it when doing asynchronous I/O.
63 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_PRIVATE, this);
64 VLOG(2) << "curl::Connection created: " << method_;
65 }
66
~Connection()67 Connection::~Connection() {
68 if (header_list_)
69 curl_slist_free_all(header_list_);
70 curl_interface_->EasyCleanup(curl_handle_);
71 VLOG(2) << "curl::Connection destroyed";
72 }
73
SendHeaders(const HeaderList & headers,brillo::ErrorPtr *)74 bool Connection::SendHeaders(const HeaderList& headers,
75 brillo::ErrorPtr* /* error */) {
76 headers_.insert(headers.begin(), headers.end());
77 return true;
78 }
79
SetRequestData(StreamPtr stream,brillo::ErrorPtr *)80 bool Connection::SetRequestData(StreamPtr stream,
81 brillo::ErrorPtr* /* error */) {
82 request_data_stream_ = std::move(stream);
83 return true;
84 }
85
SetResponseData(StreamPtr stream)86 void Connection::SetResponseData(StreamPtr stream) {
87 response_data_stream_ = std::move(stream);
88 }
89
PrepareRequest()90 void Connection::PrepareRequest() {
91 if (VLOG_IS_ON(3)) {
92 curl_interface_->EasySetOptCallback(
93 curl_handle_, CURLOPT_DEBUGFUNCTION, &curl_trace);
94 curl_interface_->EasySetOptInt(curl_handle_, CURLOPT_VERBOSE, 1);
95 }
96
97 if (method_ != request_type::kGet) {
98 // Set up HTTP request data.
99 uint64_t data_size = 0;
100 if (request_data_stream_ && request_data_stream_->CanGetSize())
101 data_size = request_data_stream_->GetRemainingSize();
102
103 if (!request_data_stream_ || request_data_stream_->CanGetSize()) {
104 // Data size is known (either no data, or data size is available).
105 if (method_ == request_type::kPut) {
106 curl_interface_->EasySetOptOffT(
107 curl_handle_, CURLOPT_INFILESIZE_LARGE, data_size);
108 } else {
109 curl_interface_->EasySetOptOffT(
110 curl_handle_, CURLOPT_POSTFIELDSIZE_LARGE, data_size);
111 }
112 } else {
113 // Data size is unknown, so use chunked upload.
114 headers_.emplace(http::request_header::kTransferEncoding, "chunked");
115 }
116
117 if (request_data_stream_) {
118 curl_interface_->EasySetOptCallback(
119 curl_handle_, CURLOPT_READFUNCTION, &Connection::read_callback);
120 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_READDATA, this);
121 }
122 }
123
124 if (!headers_.empty()) {
125 CHECK(header_list_ == nullptr);
126 for (auto pair : headers_) {
127 std::string header =
128 brillo::string_utils::Join(": ", pair.first, pair.second);
129 VLOG(2) << "Request header: " << header;
130 header_list_ = curl_slist_append(header_list_, header.c_str());
131 }
132 curl_interface_->EasySetOptPtr(
133 curl_handle_, CURLOPT_HTTPHEADER, header_list_);
134 }
135
136 headers_.clear();
137
138 // Set up HTTP response data.
139 if (!response_data_stream_)
140 response_data_stream_ = MemoryStream::Create(nullptr);
141 if (method_ != request_type::kHead) {
142 curl_interface_->EasySetOptCallback(
143 curl_handle_, CURLOPT_WRITEFUNCTION, &Connection::write_callback);
144 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_WRITEDATA, this);
145 }
146
147 // HTTP response headers
148 curl_interface_->EasySetOptCallback(
149 curl_handle_, CURLOPT_HEADERFUNCTION, &Connection::header_callback);
150 curl_interface_->EasySetOptPtr(curl_handle_, CURLOPT_HEADERDATA, this);
151 }
152
FinishRequest(brillo::ErrorPtr * error)153 bool Connection::FinishRequest(brillo::ErrorPtr* error) {
154 PrepareRequest();
155 CURLcode ret = curl_interface_->EasyPerform(curl_handle_);
156 if (ret != CURLE_OK) {
157 Transport::AddEasyCurlError(error, FROM_HERE, ret, curl_interface_.get());
158 } else {
159 // Rewind our data stream to the beginning so that it can be read back.
160 if (response_data_stream_->CanSeek() &&
161 !response_data_stream_->SetPosition(0, error))
162 return false;
163 LOG(INFO) << "Response: " << GetResponseStatusCode() << " ("
164 << GetResponseStatusText() << ")";
165 }
166 return (ret == CURLE_OK);
167 }
168
FinishRequestAsync(const SuccessCallback & success_callback,const ErrorCallback & error_callback)169 RequestID Connection::FinishRequestAsync(
170 const SuccessCallback& success_callback,
171 const ErrorCallback& error_callback) {
172 PrepareRequest();
173 return transport_->StartAsyncTransfer(this, success_callback, error_callback);
174 }
175
GetResponseStatusCode() const176 int Connection::GetResponseStatusCode() const {
177 int status_code = 0;
178 curl_interface_->EasyGetInfoInt(
179 curl_handle_, CURLINFO_RESPONSE_CODE, &status_code);
180 return status_code;
181 }
182
GetResponseStatusText() const183 std::string Connection::GetResponseStatusText() const {
184 return status_text_;
185 }
186
GetProtocolVersion() const187 std::string Connection::GetProtocolVersion() const {
188 return protocol_version_;
189 }
190
GetResponseHeader(const std::string & header_name) const191 std::string Connection::GetResponseHeader(
192 const std::string& header_name) const {
193 auto p = headers_.find(header_name);
194 return p != headers_.end() ? p->second : std::string();
195 }
196
ExtractDataStream(brillo::ErrorPtr * error)197 StreamPtr Connection::ExtractDataStream(brillo::ErrorPtr* error) {
198 if (!response_data_stream_) {
199 stream_utils::ErrorStreamClosed(FROM_HERE, error);
200 }
201 return std::move(response_data_stream_);
202 }
203
write_callback(char * ptr,size_t size,size_t num,void * data)204 size_t Connection::write_callback(char* ptr,
205 size_t size,
206 size_t num,
207 void* data) {
208 Connection* me = reinterpret_cast<Connection*>(data);
209 size_t data_len = size * num;
210 VLOG(1) << "Response data (" << data_len << "): "
211 << std::string{ptr, data_len};
212 // TODO(nathanbullock): Currently we are relying on the stream not blocking,
213 // but if the stream is representing a pipe or some other construct that might
214 // block then this code will behave badly.
215 if (!me->response_data_stream_->WriteAllBlocking(ptr, data_len, nullptr)) {
216 LOG(ERROR) << "Failed to write response data";
217 data_len = 0;
218 }
219 return data_len;
220 }
221
read_callback(char * ptr,size_t size,size_t num,void * data)222 size_t Connection::read_callback(char* ptr,
223 size_t size,
224 size_t num,
225 void* data) {
226 Connection* me = reinterpret_cast<Connection*>(data);
227 size_t data_len = size * num;
228
229 size_t read_size = 0;
230 bool success = me->request_data_stream_->ReadBlocking(ptr, data_len,
231 &read_size, nullptr);
232 VLOG_IF(3, success) << "Sending data: " << std::string{ptr, read_size};
233 return success ? read_size : CURL_READFUNC_ABORT;
234 }
235
header_callback(char * ptr,size_t size,size_t num,void * data)236 size_t Connection::header_callback(char* ptr,
237 size_t size,
238 size_t num,
239 void* data) {
240 using brillo::string_utils::SplitAtFirst;
241 Connection* me = reinterpret_cast<Connection*>(data);
242 size_t hdr_len = size * num;
243 std::string header(ptr, hdr_len);
244 // Remove newlines at the end of header line.
245 while (!header.empty() && (header.back() == '\r' || header.back() == '\n')) {
246 header.pop_back();
247 }
248
249 VLOG(2) << "Response header: " << header;
250
251 if (!me->status_text_set_) {
252 // First header - response code as "HTTP/1.1 200 OK".
253 // Need to extract the OK part
254 auto pair = SplitAtFirst(header, " ");
255 me->protocol_version_ = pair.first;
256 me->status_text_ = SplitAtFirst(pair.second, " ").second;
257 me->status_text_set_ = true;
258 } else {
259 auto pair = SplitAtFirst(header, ":");
260 if (!pair.second.empty())
261 me->headers_.insert(pair);
262 }
263 return hdr_len;
264 }
265
266 } // namespace curl
267 } // namespace http
268 } // namespace brillo
269