blob: 419ce5babad6623d6c659d0b8a040ed6d5ae2d95 [file] [log] [blame]
// Copyright 2018 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 "base/strings/string_number_conversions.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/cert/cert_status_flags.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_info.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request_context.h"
#include "net/tools/quic/quic_http_proxy_backend_stream.h"
namespace net {
// This is the Size of the buffer that consumes the response from the Backend
// The response is consumed upto 64KB at a time to avoid a large response
// from hogging resources from smaller responses.
const int QuicHttpProxyBackendStream::kBufferSize = 64000;
/*502 Bad Gateway
The server was acting as a gateway or proxy and received an
invalid response from the upstream server.*/
const int QuicHttpProxyBackendStream::kProxyHttpBackendError = 502;
// Hop-by-hop headers (small-caps). These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
// not Trailers per URL above;
// http://www.rfc-editor.org/errata_search.php?eid=4522
const std::set<std::string> QuicHttpProxyBackendStream::kHopHeaders = {
"connection",
"proxy-connection", // non-standard but still sent by libcurl and rejected
// by e.g. google
"keep-alive", "proxy-authenticate", "proxy-authorization",
"te", // canonicalized version of "TE"
"trailer", // not Trailers per URL above;
// http://www.rfc-editor.org/errata_search.php?eid=4522
"transfer-encoding", "upgrade",
};
const std::string QuicHttpProxyBackendStream::kDefaultQuicPeerIP = "Unknown";
QuicHttpProxyBackendStream::QuicHttpProxyBackendStream(
QuicHttpProxyBackend* proxy_context)
: proxy_context_(proxy_context),
delegate_(nullptr),
quic_peer_ip_(kDefaultQuicPeerIP),
url_request_(nullptr),
buf_(new IOBuffer(kBufferSize)),
response_completed_(false),
headers_set_(false),
quic_response_(new quic::QuicBackendResponse()),
weak_factory_(this) {}
QuicHttpProxyBackendStream::~QuicHttpProxyBackendStream() {}
void QuicHttpProxyBackendStream::Initialize(
quic::QuicConnectionId quic_connection_id,
quic::QuicStreamId quic_stream_id,
std::string quic_peer_ip) {
quic_connection_id_ = quic_connection_id;
quic_stream_id_ = quic_stream_id;
quic_peer_ip_ = quic_peer_ip;
if (!quic_proxy_task_runner_.get()) {
quic_proxy_task_runner_ = proxy_context_->GetProxyTaskRunner();
} else {
DCHECK_EQ(quic_proxy_task_runner_, proxy_context_->GetProxyTaskRunner());
}
quic_response_->set_response_type(
quic::QuicBackendResponse::BACKEND_ERR_RESPONSE);
}
void QuicHttpProxyBackendStream::set_delegate(
quic::QuicSimpleServerBackend::RequestHandler* delegate) {
delegate_ = delegate;
delegate_task_runner_ = base::SequencedTaskRunnerHandle::Get();
}
bool QuicHttpProxyBackendStream::SendRequestToBackend(
const spdy::SpdyHeaderBlock* incoming_request_headers,
const std::string& incoming_body) {
DCHECK(proxy_context_->IsBackendInitialized())
<< " The quic-backend-proxy-context should be initialized";
// Get Path From the Incoming Header Block
spdy::SpdyHeaderBlock::const_iterator it =
incoming_request_headers->find(":path");
GURL url = proxy_context_->backend_url();
std::string backend_spec = url.spec();
if (it != incoming_request_headers->end()) {
if (url.path().compare("/") == 0) {
backend_spec.pop_back();
}
backend_spec.append(it->second.as_string());
}
url_ = GURL(backend_spec.c_str());
if (!url_.is_valid()) {
LOG(ERROR) << "Invalid URL received from QUIC client " << backend_spec;
return false;
}
LOG(INFO) << "QUIC Proxy Making a request to the Backed URL: " + url_.spec();
// Set the Method From the Incoming Header Block
std::string method = "";
it = incoming_request_headers->find(":method");
if (it != incoming_request_headers->end()) {
method.append(it->second.as_string());
}
if (ValidateHttpMethod(method) != true) {
LOG(INFO) << "Unknown Request Type received from QUIC client " << method;
return false;
}
CopyHeaders(incoming_request_headers);
if (method_type_ == "POST" || method_type_ == "PUT" ||
method_type_ == "PATCH") {
// Upload content must be set
if (!incoming_body.empty()) {
std::unique_ptr<UploadElementReader> reader(new UploadBytesElementReader(
incoming_body.data(), incoming_body.size()));
SetUpload(
ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
}
}
// Start the request on the backend thread
bool posted = quic_proxy_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&QuicHttpProxyBackendStream::SendRequestOnBackendThread,
weak_factory_.GetWeakPtr()));
return posted;
}
void QuicHttpProxyBackendStream::CopyHeaders(
const spdy::SpdyHeaderBlock* incoming_request_headers) {
// Set all the request headers
// Add or append the X-Forwarded-For Header and X-Real-IP
for (spdy::SpdyHeaderBlock::const_iterator it =
incoming_request_headers->begin();
it != incoming_request_headers->end(); ++it) {
std::string key = it->first.as_string();
std::string value = it->second.as_string();
// Ignore the spdy headers
if (!key.empty() && key[0] != ':') {
// Remove hop-by-hop headers
if (base::ContainsKey(kHopHeaders, key)) {
LOG(INFO) << "QUIC Proxy Ignoring Hop-by-hop Request Header: " << key
<< ":" << value;
} else {
LOG(INFO) << "QUIC Proxy Copying to backend Request Header: " << key
<< ":" << value;
AddRequestHeader(key, value);
}
}
}
// ToDo append proxy ip when x_forwarded_for header already present
AddRequestHeader("X-Forwarded-For", quic_peer_ip_);
}
bool QuicHttpProxyBackendStream::ValidateHttpMethod(std::string method) {
// Http method is a token, just as header name.
if (!net::HttpUtil::IsValidHeaderName(method))
return false;
method_type_ = method;
return true;
}
bool QuicHttpProxyBackendStream::AddRequestHeader(std::string name,
std::string value) {
if (!net::HttpUtil::IsValidHeaderName(name) ||
!net::HttpUtil::IsValidHeaderValue(value)) {
return false;
}
request_headers_.SetHeader(name, value);
return true;
}
void QuicHttpProxyBackendStream::SetUpload(
std::unique_ptr<net::UploadDataStream> upload) {
DCHECK(!upload_);
upload_ = std::move(upload);
}
void QuicHttpProxyBackendStream::SendRequestOnBackendThread() {
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
url_request_ = proxy_context_->GetURLRequestContext()->CreateRequest(
url_, net::DEFAULT_PRIORITY, this);
url_request_->set_method(method_type_);
url_request_->SetExtraRequestHeaders(request_headers_);
if (upload_) {
url_request_->set_upload(std::move(upload_));
}
url_request_->Start();
VLOG(1) << "Quic Proxy Sending Request to Backend for quic_conn_id: "
<< quic_connection_id_ << " quic_stream_id: " << quic_stream_id_
<< " backend_req_id: " << url_request_->identifier()
<< " url: " << url_;
}
void QuicHttpProxyBackendStream::OnReceivedRedirect(
net::URLRequest* request,
const net::RedirectInfo& redirect_info,
bool* defer_redirect) {
DCHECK_EQ(request, url_request_.get());
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
// Do not defer redirect, retry again from the proxy with the new url
*defer_redirect = false;
LOG(ERROR) << "Received Redirect from Backend "
<< " BackendReqId: " << request->identifier() << " redirectUrl: "
<< redirect_info.new_url.possibly_invalid_spec().c_str()
<< " RespCode " << request->GetResponseCode();
}
void QuicHttpProxyBackendStream::OnCertificateRequested(
net::URLRequest* request,
net::SSLCertRequestInfo* cert_request_info) {
DCHECK_EQ(request, url_request_.get());
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
// Continue the SSL handshake without a client certificate.
request->ContinueWithCertificate(nullptr, nullptr);
}
void QuicHttpProxyBackendStream::OnSSLCertificateError(
net::URLRequest* request,
const net::SSLInfo& ssl_info,
bool fatal) {
request->Cancel();
OnResponseCompleted();
}
void QuicHttpProxyBackendStream::OnResponseStarted(net::URLRequest* request,
int net_error) {
DCHECK_EQ(request, url_request_.get());
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
// It doesn't make sense for the request to have IO pending at this point.
DCHECK_NE(net::ERR_IO_PENDING, net_error);
if (net_error != net::OK) {
LOG(ERROR) << "OnResponseStarted Error from Backend "
<< url_request_->identifier() << " url: "
<< url_request_->url().possibly_invalid_spec().c_str()
<< " RespError " << net::ErrorToString(net_error);
OnResponseCompleted();
return;
}
// Initialite the first read
ReadOnceTask();
}
void QuicHttpProxyBackendStream::ReadOnceTask() {
// Initiate a read for a max of kBufferSize
// This avoids a request with a large response from starving
// requests with smaller responses
int bytes_read = url_request_->Read(buf_.get(), kBufferSize);
OnReadCompleted(url_request_.get(), bytes_read);
}
// In the case of net::ERR_IO_PENDING,
// OnReadCompleted callback will be called by URLRequest
void QuicHttpProxyBackendStream::OnReadCompleted(net::URLRequest* unused,
int bytes_read) {
DCHECK_EQ(url_request_.get(), unused);
LOG(INFO) << "OnReadCompleted Backend with"
<< " ReqId: " << url_request_->identifier() << " RespCode "
<< url_request_->GetResponseCode() << " RcvdBytesCount "
<< bytes_read << " RcvdTotalBytes " << data_received_.size();
if (bytes_read > 0) {
data_received_.append(buf_->data(), bytes_read);
// More data to be read, send a task to self
quic_proxy_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&QuicHttpProxyBackendStream::ReadOnceTask,
weak_factory_.GetWeakPtr()));
} else if (bytes_read != net::ERR_IO_PENDING) {
quic_response_->set_response_type(
quic::QuicBackendResponse::REGULAR_RESPONSE);
OnResponseCompleted();
}
}
/* Response from Backend complete, send the last chunk of data with fin=true to
* the corresponding quic stream */
void QuicHttpProxyBackendStream::OnResponseCompleted() {
DCHECK(!response_completed_);
LOG(INFO) << "Quic Proxy Received Response from Backend for quic_conn_id: "
<< quic_connection_id_ << " quic_stream_id: " << quic_stream_id_
<< " backend_req_id: " << url_request_->identifier()
<< " url: " << url_;
// ToDo Stream the response
spdy::SpdyHeaderBlock response_headers;
if (quic_response_->response_type() !=
quic::QuicBackendResponse::BACKEND_ERR_RESPONSE) {
response_headers = getAsQuicHeaders(url_request_->response_headers(),
url_request_->GetResponseCode(),
data_received_.size());
quic_response_->set_headers(std::move(response_headers));
quic_response_->set_body(std::move(data_received_));
} else {
response_headers =
getAsQuicHeaders(url_request_->response_headers(),
kProxyHttpBackendError, data_received_.size());
quic_response_->set_headers(std::move(response_headers));
}
response_completed_ = true;
ReleaseRequest();
// Send the response back to the quic client on the quic/main thread
if (delegate_ != nullptr) {
delegate_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&QuicHttpProxyBackendStream::SendResponseOnDelegateThread,
base::Unretained(this)));
}
}
void QuicHttpProxyBackendStream::SendResponseOnDelegateThread() {
DCHECK(delegate_ != nullptr);
// Proxy currently does not support push resources
std::list<quic::QuicBackendResponse::ServerPushInfo> empty_resources;
delegate_->OnResponseBackendComplete(quic_response_.get(), empty_resources);
}
void QuicHttpProxyBackendStream::CancelRequest() {
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
if (quic_proxy_task_runner_.get())
DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
delegate_ = nullptr;
if (url_request_.get()) {
url_request_->CancelWithError(ERR_ABORTED);
ReleaseRequest();
}
}
void QuicHttpProxyBackendStream::ReleaseRequest() {
url_request_.reset();
buf_ = nullptr;
}
quic::QuicBackendResponse* QuicHttpProxyBackendStream::GetBackendResponse()
const {
return quic_response_.get();
}
// Copy Backend Response headers to Quic response headers
spdy::SpdyHeaderBlock QuicHttpProxyBackendStream::getAsQuicHeaders(
net::HttpResponseHeaders* resp_headers,
int response_code,
uint64_t response_decoded_body_size) {
DCHECK(!headers_set_);
bool response_body_encoded = false;
spdy::SpdyHeaderBlock quic_response_headers;
// Add spdy headers: Status, version need : before the header
quic_response_headers[":status"] = base::NumberToString(response_code);
headers_set_ = true;
// Returns an empty array if |headers| is nullptr.
if (resp_headers != nullptr) {
size_t iter = 0;
std::string header_name;
std::string header_value;
while (resp_headers->EnumerateHeaderLines(&iter, &header_name,
&header_value)) {
header_name = base::ToLowerASCII(header_name);
// Do not copy status again since status needs a ":" before the header
// name
if (header_name.compare("status") != 0) {
if (header_name.compare("content-encoding") != 0) {
// Remove hop-by-hop headers
if (base::ContainsKey(kHopHeaders, header_name)) {
LOG(INFO) << "Quic Proxy Ignoring Hop-by-hop Response Header: "
<< header_name << ":" << header_value;
} else {
LOG(INFO) << " Quic Proxy Copying Response Header: " << header_name
<< ":" << header_value;
quic_response_headers.AppendValueOrAddHeader(header_name,
header_value);
}
} else {
response_body_encoded = true;
}
}
} // while
// Currently URLRequest class does not support ability to disable decoding,
// response body (gzip, deflate etc. )
// Instead of re-encoding the body, we send decode body to the quic client
// and re-write the content length to the original body size
if (response_body_encoded) {
LOG(INFO) << " Quic Proxy Rewriting the Content-Length Header since "
"the response was encoded : "
<< response_decoded_body_size;
quic_response_headers["content-length"] =
base::NumberToString(response_decoded_body_size);
}
}
return quic_response_headers;
}
} // namespace net