#include "net/quic/quic_proxy_client_socket.h"
#include <cstdio>
#include <string_view>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_delegate.h"
#include "net/http/http_auth_controller.h"
#include "net/http/http_log_util.h"
#include "net/http/http_response_headers.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_source_type.h"
#include "net/quic/quic_http_utils.h"
#include "net/spdy/spdy_http_utils.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
namespace net {
QuicProxyClientSocket::QuicProxyClientSocket(
std::unique_ptr<QuicChromiumClientStream::Handle> stream,
std::unique_ptr<QuicChromiumClientSession::Handle> session,
const ProxyChain& proxy_chain,
size_t proxy_chain_index,
const std::string& user_agent,
const HostPortPair& endpoint,
const NetLogWithSource& net_log,
scoped_refptr<HttpAuthController> auth_controller,
ProxyDelegate* proxy_delegate)
: stream_(std::move(stream)),
session_(std::move(session)),
endpoint_(endpoint),
auth_(std::move(auth_controller)),
proxy_chain_(proxy_chain),
proxy_chain_index_(proxy_chain_index),
proxy_delegate_(proxy_delegate),
user_agent_(user_agent),
net_log_(net_log) {
DCHECK(stream_->IsOpen());
request_.method = "CONNECT";
request_.url = GURL("https://" + endpoint.ToString());
net_log_.BeginEventReferencingSource(NetLogEventType::SOCKET_ALIVE,
net_log_.source());
net_log_.AddEventReferencingSource(
NetLogEventType::HTTP2_PROXY_CLIENT_SESSION, stream_->net_log().source());
}
QuicProxyClientSocket::~QuicProxyClientSocket() {
Disconnect();
net_log_.EndEvent(NetLogEventType::SOCKET_ALIVE);
}
const HttpResponseInfo* QuicProxyClientSocket::GetConnectResponseInfo() const {
return response_.headers.get() ? &response_ : nullptr;
}
const scoped_refptr<HttpAuthController>&
QuicProxyClientSocket::GetAuthController() const {
return auth_;
}
int QuicProxyClientSocket::RestartWithAuth(CompletionOnceCallback callback) {
next_state_ = STATE_DISCONNECTED;
return ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
}
void QuicProxyClientSocket::SetStreamPriority(RequestPriority priority) {}
int QuicProxyClientSocket::Connect(CompletionOnceCallback callback) {
DCHECK(connect_callback_.is_null());
if (!stream_->IsOpen())
return ERR_CONNECTION_CLOSED;
DCHECK_EQ(STATE_DISCONNECTED, next_state_);
next_state_ = STATE_GENERATE_AUTH_TOKEN;
int rv = DoLoop(OK);
if (rv == ERR_IO_PENDING)
connect_callback_ = std::move(callback);
return rv;
}
void QuicProxyClientSocket::Disconnect() {
connect_callback_.Reset();
read_callback_.Reset();
read_buf_ = nullptr;
write_callback_.Reset();
write_buf_len_ = 0;
next_state_ = STATE_DISCONNECTED;
stream_->Reset(quic::QUIC_STREAM_CANCELLED);
}
bool QuicProxyClientSocket::IsConnected() const {
return next_state_ == STATE_CONNECT_COMPLETE && stream_->IsOpen();
}
bool QuicProxyClientSocket::IsConnectedAndIdle() const {
return IsConnected() && !stream_->HasBytesToRead();
}
const NetLogWithSource& QuicProxyClientSocket::NetLog() const {
return net_log_;
}
bool QuicProxyClientSocket::WasEverUsed() const {
return session_->WasEverUsed();
}
NextProto QuicProxyClientSocket::GetNegotiatedProtocol() const {
return NextProto::kProtoUnknown;
}
bool QuicProxyClientSocket::GetSSLInfo(SSLInfo* ssl_info) {
return false;
}
int64_t QuicProxyClientSocket::GetTotalReceivedBytes() const {
return stream_->NumBytesConsumed();
}
void QuicProxyClientSocket::ApplySocketTag(const SocketTag& tag) {
CHECK(tag == SocketTag());
}
int QuicProxyClientSocket::Read(IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback) {
DCHECK(connect_callback_.is_null());
DCHECK(read_callback_.is_null());
DCHECK(!read_buf_);
if (next_state_ == STATE_DISCONNECTED)
return ERR_SOCKET_NOT_CONNECTED;
if (!stream_->IsOpen()) {
return 0;
}
int rv =
stream_->ReadBody(buf, buf_len,
base::BindOnce(&QuicProxyClientSocket::OnReadComplete,
weak_factory_.GetWeakPtr()));
if (rv == ERR_IO_PENDING) {
read_callback_ = std::move(callback);
read_buf_ = buf;
} else if (rv == 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, 0,
nullptr);
} else if (rv > 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, rv,
buf->data());
}
return rv;
}
void QuicProxyClientSocket::OnReadComplete(int rv) {
if (!stream_->IsOpen())
rv = 0;
if (!read_callback_.is_null()) {
DCHECK(read_buf_);
if (rv >= 0) {
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_RECEIVED, rv,
read_buf_->data());
}
read_buf_ = nullptr;
std::move(read_callback_).Run(rv);
}
}
int QuicProxyClientSocket::Write(
IOBuffer* buf,
int buf_len,
CompletionOnceCallback callback,
const NetworkTrafficAnnotationTag& traffic_annotation) {
DCHECK(connect_callback_.is_null());
DCHECK(write_callback_.is_null());
if (next_state_ != STATE_CONNECT_COMPLETE)
return ERR_SOCKET_NOT_CONNECTED;
net_log_.AddByteTransferEvent(NetLogEventType::SOCKET_BYTES_SENT, buf_len,
buf->data());
int rv = stream_->WriteStreamData(
std::string_view(buf->data(), buf_len), false,
base::BindOnce(&QuicProxyClientSocket::OnWriteComplete,
weak_factory_.GetWeakPtr()));
if (rv == OK)
return buf_len;
if (rv == ERR_IO_PENDING) {
write_callback_ = std::move(callback);
write_buf_len_ = buf_len;
}
return rv;
}
void QuicProxyClientSocket::OnWriteComplete(int rv) {
if (!write_callback_.is_null()) {
if (rv == OK)
rv = write_buf_len_;
write_buf_len_ = 0;
std::move(write_callback_).Run(rv);
}
}
int QuicProxyClientSocket::SetReceiveBufferSize(int32_t size) {
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyClientSocket::SetSendBufferSize(int32_t size) {
return ERR_NOT_IMPLEMENTED;
}
int QuicProxyClientSocket::GetPeerAddress(IPEndPoint* address) const {
return IsConnected() ? session_->GetPeerAddress(address)
: ERR_SOCKET_NOT_CONNECTED;
}
int QuicProxyClientSocket::GetLocalAddress(IPEndPoint* address) const {
return IsConnected() ? session_->GetSelfAddress(address)
: ERR_SOCKET_NOT_CONNECTED;
}
void QuicProxyClientSocket::OnIOComplete(int result) {
DCHECK_NE(STATE_DISCONNECTED, next_state_);
int rv = DoLoop(result);
if (rv != ERR_IO_PENDING) {
DCHECK(!connect_callback_.is_null());
std::move(connect_callback_).Run(rv);
}
}
int QuicProxyClientSocket::DoLoop(int last_io_result) {
DCHECK_NE(next_state_, STATE_DISCONNECTED);
int rv = last_io_result;
do {
State state = next_state_;
next_state_ = STATE_DISCONNECTED;
switch (state) {
case STATE_GENERATE_AUTH_TOKEN:
DCHECK_EQ(OK, rv);
rv = DoGenerateAuthToken();
break;
case STATE_GENERATE_AUTH_TOKEN_COMPLETE:
rv = DoGenerateAuthTokenComplete(rv);
break;
case STATE_CALCULATE_HEADERS:
DCHECK_EQ(OK, rv);
rv = DoCalculateHeaders();
break;
case STATE_CALCULATE_HEADERS_COMPLETE:
rv = DoCalculateHeadersComplete(rv);
break;
case STATE_SEND_REQUEST:
DCHECK_EQ(OK, rv);
net_log_.BeginEvent(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST);
rv = DoSendRequest();
break;
case STATE_SEND_REQUEST_COMPLETE:
net_log_.EndEventWithNetErrorCode(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, rv);
rv = DoSendRequestComplete(rv);
break;
case STATE_READ_REPLY:
rv = DoReadReply();
break;
case STATE_READ_REPLY_COMPLETE:
rv = DoReadReplyComplete(rv);
net_log_.EndEventWithNetErrorCode(
NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv);
break;
case STATE_PROCESS_RESPONSE_HEADERS:
DCHECK_EQ(OK, rv);
rv = DoProcessResponseHeaders();
break;
case STATE_PROCESS_RESPONSE_HEADERS_COMPLETE:
rv = DoProcessResponseHeadersComplete(rv);
break;
case STATE_PROCESS_RESPONSE_CODE:
DCHECK_EQ(OK, rv);
rv = DoProcessResponseCode();
break;
default:
NOTREACHED() << "bad state";
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_DISCONNECTED &&
next_state_ != STATE_CONNECT_COMPLETE);
return rv;
}
int QuicProxyClientSocket::DoGenerateAuthToken() {
next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
return auth_->MaybeGenerateAuthToken(
&request_,
base::BindOnce(&QuicProxyClientSocket::OnIOComplete,
weak_factory_.GetWeakPtr()),
net_log_);
}
int QuicProxyClientSocket::DoGenerateAuthTokenComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (result == OK) {
next_state_ = STATE_CALCULATE_HEADERS;
}
return result;
}
int QuicProxyClientSocket::DoCalculateHeaders() {
next_state_ = STATE_CALCULATE_HEADERS_COMPLETE;
authorization_headers_.Clear();
proxy_delegate_headers_.Clear();
if (auth_->HaveAuth()) {
auth_->AddAuthorizationHeader(&authorization_headers_);
}
if (proxy_delegate_) {
ASSIGN_OR_RETURN(
proxy_delegate_headers_,
proxy_delegate_->OnBeforeTunnelRequest(
proxy_chain_, proxy_chain_index_,
base::BindOnce(
&QuicProxyClientSocket::OnBeforeTunnelRequestComplete,
weak_factory_.GetWeakPtr())),
[](const auto& e) {
CHECK_NE(OK, e);
return e;
});
}
return OK;
}
int QuicProxyClientSocket::DoCalculateHeadersComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (result != OK) {
return result;
}
next_state_ = STATE_SEND_REQUEST;
request_.extra_headers.MergeFrom(proxy_delegate_headers_);
return result;
}
int QuicProxyClientSocket::DoSendRequest() {
next_state_ = STATE_SEND_REQUEST_COMPLETE;
std::string request_line;
BuildTunnelRequest(endpoint_, authorization_headers_, user_agent_,
&request_line, &request_.extra_headers);
NetLogRequestHeaders(net_log_,
NetLogEventType::HTTP_TRANSACTION_SEND_TUNNEL_HEADERS,
request_line, &request_.extra_headers);
quiche::HttpHeaderBlock headers;
CreateSpdyHeadersFromHttpRequest(request_, std::nullopt,
request_.extra_headers, &headers);
return stream_->WriteHeaders(std::move(headers), false, nullptr);
}
int QuicProxyClientSocket::DoSendRequestComplete(int result) {
if (result >= 0) {
next_state_ = STATE_READ_REPLY;
result = OK;
}
if (result >= 0 || result == ERR_IO_PENDING) {
net_log_.BeginEvent(NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS);
}
return result;
}
int QuicProxyClientSocket::DoReadReply() {
next_state_ = STATE_READ_REPLY_COMPLETE;
int rv = stream_->ReadInitialHeaders(
&response_header_block_,
base::BindOnce(&QuicProxyClientSocket::OnReadResponseHeadersComplete,
weak_factory_.GetWeakPtr()));
if (rv == ERR_IO_PENDING)
return ERR_IO_PENDING;
if (rv < 0)
return rv;
return ProcessResponseHeaders(response_header_block_);
}
int QuicProxyClientSocket::DoReadReplyComplete(int result) {
if (result < 0)
return result;
if (response_.headers->GetHttpVersion() < HttpVersion(1, 0))
return ERR_TUNNEL_CONNECTION_FAILED;
next_state_ = STATE_PROCESS_RESPONSE_HEADERS;
NetLogResponseHeaders(
net_log_, NetLogEventType::HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS,
response_.headers.get());
return OK;
}
int QuicProxyClientSocket::DoProcessResponseHeaders() {
next_state_ = STATE_PROCESS_RESPONSE_HEADERS_COMPLETE;
if (proxy_delegate_) {
return proxy_delegate_->OnTunnelHeadersReceived(
proxy_chain_, proxy_chain_index_, *response_.headers,
base::BindOnce(&QuicProxyClientSocket::OnIOComplete,
weak_factory_.GetWeakPtr()));
}
return OK;
}
int QuicProxyClientSocket::DoProcessResponseHeadersComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
if (result != OK) {
return result;
}
next_state_ = STATE_PROCESS_RESPONSE_CODE;
return OK;
}
int QuicProxyClientSocket::DoProcessResponseCode() {
switch (response_.headers->response_code()) {
case 200:
next_state_ = STATE_CONNECT_COMPLETE;
return OK;
case 407:
next_state_ = STATE_CONNECT_COMPLETE;
SanitizeProxyAuth(response_);
return HandleProxyAuthChallenge(auth_.get(), &response_, net_log_);
default:
return ERR_TUNNEL_CONNECTION_FAILED;
}
}
void QuicProxyClientSocket::OnReadResponseHeadersComplete(int result) {
if (result > 0)
result = ProcessResponseHeaders(response_header_block_);
if (result != ERR_IO_PENDING)
OnIOComplete(result);
}
int QuicProxyClientSocket::ProcessResponseHeaders(
const quiche::HttpHeaderBlock& headers) {
if (SpdyHeadersToHttpResponse(headers, &response_) != OK) {
DLOG(WARNING) << "Invalid headers";
return ERR_QUIC_PROTOCOL_ERROR;
}
return OK;
}
void QuicProxyClientSocket::OnBeforeTunnelRequestComplete(
base::expected<HttpRequestHeaders, Error> result) {
if (result.has_value()) {
proxy_delegate_headers_ = std::move(result.value());
OnIOComplete(OK);
} else {
CHECK_NE(ERR_IO_PENDING, result.error());
CHECK_NE(OK, result.error());
OnIOComplete(result.error());
}
}
}