| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "http_url_loader_impl.h" |
| |
| #include <istream> |
| #include <memory> |
| #include <ostream> |
| #include <string> |
| #include <vector> |
| |
| #include "garnet/bin/http/http_adapters.h" |
| #include "garnet/bin/http/http_client.h" |
| #include "garnet/bin/http/http_errors.h" |
| #include "src/lib/fxl/logging.h" |
| #include "src/lib/url/gurl.h" |
| |
| namespace http { |
| |
| namespace oldhttp = ::fuchsia::net::oldhttp; |
| |
| namespace { |
| const size_t kMaxRedirects = 20; |
| } // namespace |
| |
| URLLoaderImpl::URLLoaderImpl(Coordinator* coordinator) |
| : coordinator_(coordinator) {} |
| |
| URLLoaderImpl::~URLLoaderImpl() {} |
| |
| void URLLoaderImpl::Start(oldhttp::URLRequest request, Callback callback) { |
| callback_ = std::move(callback); |
| coordinator_->RequestNetworkSlot( |
| [this, request = std::move(request)](fit::closure on_inactive) mutable { |
| StartInternal(std::move(request)); |
| on_inactive(); |
| }); |
| } |
| |
| void URLLoaderImpl::FollowRedirect(Callback callback) { |
| FXL_NOTIMPLEMENTED(); |
| callback_ = std::move(callback); |
| SendError(HTTP_ERR_NOT_IMPLEMENTED); |
| } |
| |
| void URLLoaderImpl::QueryStatus(QueryStatusCallback callback) { |
| oldhttp::URLLoaderStatus status; |
| FXL_NOTIMPLEMENTED(); |
| status.error = MakeHttpError(HTTP_ERR_NOT_IMPLEMENTED); |
| callback(std::move(status)); |
| } |
| |
| void URLLoaderImpl::SendError(int error_code) { |
| oldhttp::URLResponse response; |
| response.error = MakeHttpError(error_code); |
| if (current_url_.is_valid()) { |
| response.url = current_url_.spec(); |
| } |
| SendResponse(std::move(response)); |
| } |
| |
| void URLLoaderImpl::FollowRedirectInternal() { /* TODO(toshik) */ |
| } |
| |
| void URLLoaderImpl::SendResponse(oldhttp::URLResponse response) { |
| Callback callback; |
| std::swap(callback_, callback); |
| callback(std::move(response)); |
| } |
| |
| void URLLoaderImpl::StartInternal(oldhttp::URLRequest request) { |
| std::string url_str = request.url; |
| std::string method = request.method; |
| std::map<std::string, std::string> extra_headers; |
| std::unique_ptr<http::UploadElementReader> request_body_reader; |
| |
| if (request.headers) { |
| for (size_t i = 0; i < request.headers->size(); ++i) |
| extra_headers[request.headers->at(i).name] = request.headers->at(i).value; |
| } |
| |
| if (request.body) { |
| // TODO(kulakowski) Implement responses into a shared_buffer |
| if (request.body->is_stream()) { |
| request_body_reader = std::make_unique<http::SocketUploadElementReader>( |
| std::move(request.body->stream())); |
| } else { |
| FXL_DCHECK(request.body->is_buffer()); |
| request_body_reader = std::make_unique<http::VmoUploadElementReader>( |
| std::move(request.body->buffer().vmo), request.body->buffer().size); |
| } |
| } |
| |
| response_body_mode_ = request.response_body_mode; |
| |
| asio::io_service io_service; |
| size_t redirectsLeft = kMaxRedirects; |
| |
| current_url_ = url::GURL(url_str); |
| if (!current_url_.is_valid()) { |
| SendError(HTTP_ERR_INVALID_ARGUMENT); |
| return; |
| } |
| |
| do { |
| if (current_url_.SchemeIs("https")) { |
| #ifdef NETWORK_SERVICE_USE_HTTPS |
| asio::ssl::context ctx(asio::ssl::context::sslv23); |
| #ifndef NETWORK_SERVICE_DISABLE_CERT_VERIFY |
| ctx.set_default_verify_paths(); |
| #endif |
| HTTPClient<asio::ssl::stream<tcp::socket>> c(this, io_service, ctx); |
| zx_status_t result = c.CreateRequest( |
| current_url_.host(), |
| current_url_.path() + |
| (current_url_.has_query() ? "?" + current_url_.query() : ""), |
| method, extra_headers, std::move(request_body_reader)); |
| if (result != ZX_OK) { |
| SendError(HTTP_ERR_INVALID_ARGUMENT); |
| return; |
| } |
| c.Start(current_url_.host(), |
| current_url_.has_port() ? current_url_.port() : "https"); |
| io_service.run(); |
| |
| if (c.status_code_ == 301 || c.status_code_ == 302) { |
| current_url_ = url::GURL(c.redirect_location_); |
| if (!current_url_.is_valid()) { |
| SendError(HTTP_ERR_INVALID_RESPONSE); |
| return; |
| } |
| // Follow redirect |
| io_service.reset(); |
| continue; |
| } |
| #else |
| FXL_LOG(WARNING) << "https is not built-in. " |
| "please build with NETWORK_SERVICE_USE_HTTPS"; |
| SendError(HTTP_ERR_INVALID_ARGUMENT); |
| return; |
| #endif |
| } else if (current_url_.SchemeIs("http")) { |
| HTTPClient<tcp::socket> c(this, io_service); |
| zx_status_t result = c.CreateRequest( |
| current_url_.host(), |
| current_url_.path() + |
| (current_url_.has_query() ? "?" + current_url_.query() : ""), |
| method, extra_headers, std::move(request_body_reader)); |
| if (result != ZX_OK) { |
| SendError(HTTP_ERR_INVALID_ARGUMENT); |
| return; |
| } |
| c.Start(current_url_.host(), |
| current_url_.has_port() ? current_url_.port() : "http"); |
| io_service.run(); |
| |
| if (c.status_code_ == 301 || c.status_code_ == 302) { |
| current_url_ = url::GURL(c.redirect_location_); |
| if (!current_url_.is_valid()) { |
| SendError(HTTP_ERR_INVALID_RESPONSE); |
| return; |
| } |
| // Follow redirect |
| io_service.reset(); |
| continue; |
| } |
| } else { |
| // unknown protocol |
| SendError(HTTP_ERR_INVALID_ARGUMENT); |
| return; |
| } |
| // Success without redirect |
| return; |
| } while (--redirectsLeft); |
| SendError(HTTP_ERR_TOO_MANY_REDIRECTS); |
| } |
| |
| } // namespace http |