| // Copyright 2014 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <base/check.h> | |
| #include <brillo/http/http_request.h> | |
| #include <base/functional/bind.h> | |
| #include <base/functional/callback.h> | |
| #include <base/logging.h> | |
| #include <brillo/http/http_form_data.h> | |
| #include <brillo/map_utils.h> | |
| #include <brillo/mime_utils.h> | |
| #include <brillo/streams/memory_stream.h> | |
| #include <brillo/strings/string_utils.h> | |
| namespace brillo { | |
| namespace http { | |
| // request_type | |
| const char request_type::kOptions[] = "OPTIONS"; | |
| const char request_type::kGet[] = "GET"; | |
| const char request_type::kHead[] = "HEAD"; | |
| const char request_type::kPost[] = "POST"; | |
| const char request_type::kPut[] = "PUT"; | |
| const char request_type::kPatch[] = "PATCH"; | |
| const char request_type::kDelete[] = "DELETE"; | |
| const char request_type::kTrace[] = "TRACE"; | |
| const char request_type::kConnect[] = "CONNECT"; | |
| const char request_type::kCopy[] = "COPY"; | |
| const char request_type::kMove[] = "MOVE"; | |
| // request_header | |
| const char request_header::kAccept[] = "Accept"; | |
| const char request_header::kAcceptCharset[] = "Accept-Charset"; | |
| const char request_header::kAcceptEncoding[] = "Accept-Encoding"; | |
| const char request_header::kAcceptLanguage[] = "Accept-Language"; | |
| const char request_header::kAllow[] = "Allow"; | |
| const char request_header::kAuthorization[] = "Authorization"; | |
| const char request_header::kCacheControl[] = "Cache-Control"; | |
| const char request_header::kConnection[] = "Connection"; | |
| const char request_header::kContentEncoding[] = "Content-Encoding"; | |
| const char request_header::kContentLanguage[] = "Content-Language"; | |
| const char request_header::kContentLength[] = "Content-Length"; | |
| const char request_header::kContentLocation[] = "Content-Location"; | |
| const char request_header::kContentMd5[] = "Content-MD5"; | |
| const char request_header::kContentRange[] = "Content-Range"; | |
| const char request_header::kContentType[] = "Content-Type"; | |
| const char request_header::kCookie[] = "Cookie"; | |
| const char request_header::kDate[] = "Date"; | |
| const char request_header::kExpect[] = "Expect"; | |
| const char request_header::kExpires[] = "Expires"; | |
| const char request_header::kFrom[] = "From"; | |
| const char request_header::kHost[] = "Host"; | |
| const char request_header::kIfMatch[] = "If-Match"; | |
| const char request_header::kIfModifiedSince[] = "If-Modified-Since"; | |
| const char request_header::kIfNoneMatch[] = "If-None-Match"; | |
| const char request_header::kIfRange[] = "If-Range"; | |
| const char request_header::kIfUnmodifiedSince[] = "If-Unmodified-Since"; | |
| const char request_header::kLastModified[] = "Last-Modified"; | |
| const char request_header::kMaxForwards[] = "Max-Forwards"; | |
| const char request_header::kPragma[] = "Pragma"; | |
| const char request_header::kProxyAuthorization[] = "Proxy-Authorization"; | |
| const char request_header::kRange[] = "Range"; | |
| const char request_header::kReferer[] = "Referer"; | |
| const char request_header::kTE[] = "TE"; | |
| const char request_header::kTrailer[] = "Trailer"; | |
| const char request_header::kTransferEncoding[] = "Transfer-Encoding"; | |
| const char request_header::kUpgrade[] = "Upgrade"; | |
| const char request_header::kUserAgent[] = "User-Agent"; | |
| const char request_header::kVia[] = "Via"; | |
| const char request_header::kWarning[] = "Warning"; | |
| // response_header | |
| const char response_header::kAcceptRanges[] = "Accept-Ranges"; | |
| const char response_header::kAge[] = "Age"; | |
| const char response_header::kAllow[] = "Allow"; | |
| const char response_header::kCacheControl[] = "Cache-Control"; | |
| const char response_header::kConnection[] = "Connection"; | |
| const char response_header::kContentEncoding[] = "Content-Encoding"; | |
| const char response_header::kContentLanguage[] = "Content-Language"; | |
| const char response_header::kContentLength[] = "Content-Length"; | |
| const char response_header::kContentLocation[] = "Content-Location"; | |
| const char response_header::kContentMd5[] = "Content-MD5"; | |
| const char response_header::kContentRange[] = "Content-Range"; | |
| const char response_header::kContentType[] = "Content-Type"; | |
| const char response_header::kDate[] = "Date"; | |
| const char response_header::kETag[] = "ETag"; | |
| const char response_header::kExpires[] = "Expires"; | |
| const char response_header::kLastModified[] = "Last-Modified"; | |
| const char response_header::kLocation[] = "Location"; | |
| const char response_header::kPragma[] = "Pragma"; | |
| const char response_header::kProxyAuthenticate[] = "Proxy-Authenticate"; | |
| const char response_header::kRetryAfter[] = "Retry-After"; | |
| const char response_header::kServer[] = "Server"; | |
| const char response_header::kSetCookie[] = "Set-Cookie"; | |
| const char response_header::kTrailer[] = "Trailer"; | |
| const char response_header::kTransferEncoding[] = "Transfer-Encoding"; | |
| const char response_header::kUpgrade[] = "Upgrade"; | |
| const char response_header::kVary[] = "Vary"; | |
| const char response_header::kVia[] = "Via"; | |
| const char response_header::kWarning[] = "Warning"; | |
| const char response_header::kWwwAuthenticate[] = "WWW-Authenticate"; | |
| // *********************************************************** | |
| // ********************** Request Class ********************** | |
| // *********************************************************** | |
| Request::Request(const std::string& url, | |
| const std::string& method, | |
| std::shared_ptr<Transport> transport) | |
| : transport_(transport), request_url_(url), method_(method) { | |
| VLOG(1) << "http::Request created"; | |
| if (!transport_) | |
| transport_ = http::Transport::CreateDefault(); | |
| } | |
| Request::~Request() { | |
| VLOG(1) << "http::Request destroyed"; | |
| } | |
| void Request::AddRange(int64_t bytes) { | |
| DCHECK(transport_) << "Request already sent"; | |
| if (bytes < 0) { | |
| ranges_.emplace_back(Request::range_value_omitted, -bytes); | |
| } else { | |
| ranges_.emplace_back(bytes, Request::range_value_omitted); | |
| } | |
| } | |
| void Request::AddRange(uint64_t from_byte, uint64_t to_byte) { | |
| DCHECK(transport_) << "Request already sent"; | |
| ranges_.emplace_back(from_byte, to_byte); | |
| } | |
| std::unique_ptr<Response> Request::GetResponseAndBlock( | |
| brillo::ErrorPtr* error) { | |
| if (!SendRequestIfNeeded(error) || !connection_->FinishRequest(error)) | |
| return std::unique_ptr<Response>(); | |
| std::unique_ptr<Response> response(new Response(connection_)); | |
| connection_.reset(); | |
| transport_.reset(); // Indicate that the response has been received | |
| return response; | |
| } | |
| RequestID Request::GetResponse(SuccessCallback success_callback, | |
| ErrorCallback error_callback) { | |
| ErrorPtr error; | |
| if (!SendRequestIfNeeded(&error)) { | |
| transport_->RunCallbackAsync(FROM_HERE, | |
| base::BindOnce(std::move(error_callback), 0, | |
| base::Owned(error.release()))); | |
| return 0; | |
| } | |
| RequestID id = connection_->FinishRequestAsync(std::move(success_callback), | |
| std::move(error_callback)); | |
| connection_.reset(); | |
| transport_.reset(); // Indicate that the request has been dispatched. | |
| return id; | |
| } | |
| void Request::SetAccept(const std::string& accept_mime_types) { | |
| DCHECK(transport_) << "Request already sent"; | |
| accept_ = accept_mime_types; | |
| } | |
| const std::string& Request::GetAccept() const { | |
| return accept_; | |
| } | |
| void Request::SetContentType(const std::string& contentType) { | |
| DCHECK(transport_) << "Request already sent"; | |
| content_type_ = contentType; | |
| } | |
| const std::string& Request::GetContentType() const { | |
| return content_type_; | |
| } | |
| void Request::AddHeader(const std::string& header, const std::string& value) { | |
| DCHECK(transport_) << "Request already sent"; | |
| headers_.emplace(header, value); | |
| } | |
| void Request::AddHeaders(const HeaderList& headers) { | |
| DCHECK(transport_) << "Request already sent"; | |
| headers_.insert(headers.begin(), headers.end()); | |
| } | |
| bool Request::AddRequestBody(const void* data, | |
| size_t size, | |
| brillo::ErrorPtr* error) { | |
| if (!SendRequestIfNeeded(error)) | |
| return false; | |
| StreamPtr stream = MemoryStream::OpenCopyOf(data, size, error); | |
| return stream && connection_->SetRequestData(std::move(stream), error); | |
| } | |
| bool Request::AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error) { | |
| return SendRequestIfNeeded(error) && | |
| connection_->SetRequestData(std::move(stream), error); | |
| } | |
| bool Request::AddRequestBodyAsFormData(std::unique_ptr<FormData> form_data, | |
| brillo::ErrorPtr* error) { | |
| AddHeader(request_header::kContentType, form_data->GetContentType()); | |
| if (!SendRequestIfNeeded(error)) | |
| return false; | |
| return connection_->SetRequestData(form_data->ExtractDataStream(), error); | |
| } | |
| bool Request::AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error) { | |
| if (!SendRequestIfNeeded(error)) | |
| return false; | |
| connection_->SetResponseData(std::move(stream)); | |
| return true; | |
| } | |
| const std::string& Request::GetRequestURL() const { | |
| return request_url_; | |
| } | |
| const std::string& Request::GetRequestMethod() const { | |
| return method_; | |
| } | |
| void Request::SetReferer(const std::string& referer) { | |
| DCHECK(transport_) << "Request already sent"; | |
| referer_ = referer; | |
| } | |
| const std::string& Request::GetReferer() const { | |
| return referer_; | |
| } | |
| void Request::SetUserAgent(const std::string& user_agent) { | |
| DCHECK(transport_) << "Request already sent"; | |
| user_agent_ = user_agent; | |
| } | |
| const std::string& Request::GetUserAgent() const { | |
| return user_agent_; | |
| } | |
| bool Request::SendRequestIfNeeded(brillo::ErrorPtr* error) { | |
| if (transport_) { | |
| if (!connection_) { | |
| http::HeaderList headers = brillo::MapToVector(headers_); | |
| std::vector<std::string> ranges; | |
| if (method_ != request_type::kHead) { | |
| ranges.reserve(ranges_.size()); | |
| for (auto p : ranges_) { | |
| if (p.first != range_value_omitted || | |
| p.second != range_value_omitted) { | |
| std::string range; | |
| if (p.first != range_value_omitted) { | |
| range = brillo::string_utils::ToString(p.first); | |
| } | |
| range += '-'; | |
| if (p.second != range_value_omitted) { | |
| range += brillo::string_utils::ToString(p.second); | |
| } | |
| ranges.push_back(range); | |
| } | |
| } | |
| } | |
| if (!ranges.empty()) | |
| headers.emplace_back( | |
| request_header::kRange, | |
| "bytes=" + brillo::string_utils::Join(",", ranges)); | |
| headers.emplace_back(request_header::kAccept, GetAccept()); | |
| if (method_ != request_type::kGet && method_ != request_type::kHead) { | |
| if (!content_type_.empty()) | |
| headers.emplace_back(request_header::kContentType, content_type_); | |
| } | |
| connection_ = transport_->CreateConnection(request_url_, method_, headers, | |
| user_agent_, referer_, error); | |
| } | |
| if (connection_) | |
| return true; | |
| } else { | |
| brillo::Error::AddTo(error, FROM_HERE, http::kErrorDomain, | |
| "response_already_received", | |
| "HTTP response already received"); | |
| } | |
| return false; | |
| } | |
| // ************************************************************ | |
| // ********************** Response Class ********************** | |
| // ************************************************************ | |
| Response::Response(const std::shared_ptr<Connection>& connection) | |
| : connection_{connection} { | |
| VLOG(1) << "http::Response created"; | |
| } | |
| Response::~Response() { | |
| VLOG(1) << "http::Response destroyed"; | |
| } | |
| bool Response::IsSuccessful() const { | |
| int code = GetStatusCode(); | |
| return code >= status_code::Continue && code < status_code::BadRequest; | |
| } | |
| int Response::GetStatusCode() const { | |
| if (!connection_) | |
| return -1; | |
| return connection_->GetResponseStatusCode(); | |
| } | |
| std::string Response::GetStatusText() const { | |
| if (!connection_) | |
| return std::string(); | |
| return connection_->GetResponseStatusText(); | |
| } | |
| std::string Response::GetContentType() const { | |
| return GetHeader(response_header::kContentType); | |
| } | |
| StreamPtr Response::ExtractDataStream(ErrorPtr* error) { | |
| return connection_->ExtractDataStream(error); | |
| } | |
| std::vector<uint8_t> Response::ExtractData() { | |
| std::vector<uint8_t> data; | |
| StreamPtr src_stream = connection_->ExtractDataStream(nullptr); | |
| StreamPtr dest_stream = MemoryStream::CreateRef(&data, nullptr); | |
| if (src_stream && dest_stream) { | |
| char buffer[1024]; | |
| size_t read = 0; | |
| while (src_stream->ReadBlocking(buffer, sizeof(buffer), &read, nullptr) && | |
| read > 0) { | |
| CHECK(dest_stream->WriteAllBlocking(buffer, read, nullptr)); | |
| } | |
| } | |
| return data; | |
| } | |
| std::string Response::ExtractDataAsString() { | |
| std::vector<uint8_t> data = ExtractData(); | |
| return std::string{data.begin(), data.end()}; | |
| } | |
| std::string Response::GetHeader(const std::string& header_name) const { | |
| if (connection_) | |
| return connection_->GetResponseHeader(header_name); | |
| return std::string(); | |
| } | |
| } // namespace http | |
| } // namespace brillo |