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