blob: 574f9d8dfef25129fdc95eb865178bebb500d103 [file] [log] [blame]
// Copyright 2025 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 "quiche/quic/masque/masque_ohttp_client.h"
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/cleanup/cleanup.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "openssl/base.h"
#include "quiche/quic/core/io/quic_default_event_loop.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/quic_default_clock.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/masque/masque_connection_pool.h"
#include "quiche/quic/tools/quic_url.h"
#include "quiche/binary_http/binary_http_message.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_system_event_loop.h"
#include "quiche/common/quiche_status_utils.h"
#include "quiche/common/quiche_text_utils.h"
#include "quiche/oblivious_http/buffers/oblivious_http_request.h"
#include "quiche/oblivious_http/buffers/oblivious_http_response.h"
#include "quiche/oblivious_http/common/oblivious_http_header_key_config.h"
#include "quiche/oblivious_http/oblivious_http_client.h"
namespace quic {
using ::quic::MasqueConnectionPool;
using ::quic::QuicUrl;
using ::quiche::BinaryHttpRequest;
using ::quiche::BinaryHttpResponse;
using ::quiche::ChunkedObliviousHttpClient;
using ::quiche::ObliviousHttpClient;
using ::quiche::ObliviousHttpHeaderKeyConfig;
using ::quiche::ObliviousHttpKeyConfigs;
using ::quiche::ObliviousHttpRequest;
using ::quiche::ObliviousHttpResponse;
using RequestId = ::quic::MasqueConnectionPool::RequestId;
using Message = ::quic::MasqueConnectionPool::Message;
namespace {
absl::StatusOr<std::string> FormatPrivateToken(
const std::string& private_token) {
// Private tokens require padded base64url and we allow any encoding for
// convenience, so we need to unescape and re-escape.
// https://www.rfc-editor.org/rfc/rfc9577#section-2.2.2
std::string formatted_token;
if (!absl::Base64Unescape(private_token, &formatted_token) &&
!absl::WebSafeBase64Unescape(private_token, &formatted_token)) {
return absl::InvalidArgumentError(absl::StrCat(
"Invalid base64 encoding in private token: \"", private_token, "\""));
}
formatted_token = absl::Base64Escape(formatted_token);
absl::StrReplaceAll({{"+", "-"}, {"/", "_"}}, &formatted_token);
return absl::StrCat("PrivateToken token=\"", formatted_token, "\"");
}
} // namespace
absl::Status MasqueOhttpClient::Config::ConfigureKeyFetchClientCert(
const std::string& client_cert_file,
const std::string& client_cert_key_file) {
QUICHE_ASSIGN_OR_RETURN(key_fetch_ssl_ctx_,
MasqueConnectionPool::CreateSslCtx(
client_cert_file, client_cert_key_file));
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::Config::ConfigureKeyFetchClientCertFromData(
const std::string& client_cert_pem_data,
const std::string& client_cert_key_data) {
QUICHE_ASSIGN_OR_RETURN(key_fetch_ssl_ctx_,
MasqueConnectionPool::CreateSslCtxFromData(
client_cert_pem_data, client_cert_key_data));
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::Config::ConfigureOhttpMtls(
const std::string& client_cert_file,
const std::string& client_cert_key_file) {
QUICHE_ASSIGN_OR_RETURN(
ohttp_ssl_ctx_, MasqueConnectionPool::CreateSslCtx(client_cert_file,
client_cert_key_file));
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::Config::ConfigureOhttpMtlsFromData(
const std::string& client_cert_pem_data,
const std::string& client_cert_key_data) {
QUICHE_ASSIGN_OR_RETURN(ohttp_ssl_ctx_,
MasqueConnectionPool::CreateSslCtxFromData(
client_cert_pem_data, client_cert_key_data));
return absl::OkStatus();
}
MasqueOhttpClient::MasqueOhttpClient(Config config,
quic::QuicEventLoop* event_loop)
: config_(std::move(config)),
connection_pool_(event_loop, config_.key_fetch_ssl_ctx(),
config_.disable_certificate_verification(),
config_.dns_config(), this) {
connection_pool_.SetMtlsSslCtx(config_.ohttp_ssl_ctx());
}
// static
absl::Status MasqueOhttpClient::Run(Config config) {
if (config.per_request_configs().empty()) {
return absl::InvalidArgumentError("No OHTTP URLs to request");
}
if (config.key_fetch_ssl_ctx() == nullptr) {
QUICHE_RETURN_IF_ERROR(config.ConfigureKeyFetchClientCert("", ""));
}
if (config.ohttp_ssl_ctx() == nullptr) {
QUICHE_RETURN_IF_ERROR(config.ConfigureOhttpMtls("", ""));
}
quiche::QuicheSystemEventLoop system_event_loop("masque_ohttp_client");
std::unique_ptr<QuicEventLoop> event_loop =
GetDefaultEventLoop()->Create(QuicDefaultClock::Get());
MasqueOhttpClient ohttp_client(std::move(config), event_loop.get());
QUICHE_RETURN_IF_ERROR(ohttp_client.Start());
while (!ohttp_client.IsDone()) {
ohttp_client.connection_pool_.event_loop()->RunEventLoopOnce(
quic::QuicTime::Delta::FromMilliseconds(50));
}
return ohttp_client.status_;
}
absl::Status MasqueOhttpClient::Start() {
absl::Status status = StartKeyFetch(config_.key_fetch_url());
if (!status.ok()) {
Abort(status);
return status;
}
return absl::OkStatus();
}
bool MasqueOhttpClient::IsDone() {
if (!status_.ok()) {
return true;
}
if (!ohttp_client_.has_value()) {
// Key fetch request is still pending.
return false;
}
return pending_ohttp_requests_.empty();
}
void MasqueOhttpClient::Abort(absl::Status status) {
QUICHE_CHECK(!status.ok());
if (!status_.ok()) {
QUICHE_LOG(ERROR)
<< "MasqueOhttpClient already aborted, ignoring new error: "
<< status.message();
return;
}
status_ = status;
QUICHE_LOG(ERROR) << "Aborting MasqueOhttpClient: " << status_.message();
}
absl::StatusOr<QuicUrl> ParseUrl(const std::string& url_string) {
QuicUrl url(url_string, "https");
if (url.host().empty() && !absl::StrContains(url_string, "://")) {
url = QuicUrl(absl::StrCat("https://", url_string));
}
if (url.host().empty()) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to parse OHTTP key URL ", url_string));
}
return url;
}
absl::Status MasqueOhttpClient::StartKeyFetch(const std::string& url_string) {
QuicUrl url(url_string, "https");
if (url.host().empty() && !absl::StrContains(url_string, "://")) {
url = QuicUrl(absl::StrCat("https://", url_string));
}
if (url.host().empty()) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to parse OHTTP key URL \"", url_string, "\""));
}
Message request;
request.headers[":method"] = "GET";
request.headers[":scheme"] = url.scheme();
request.headers[":authority"] = url.HostPort();
request.headers[":path"] = url.PathParamsQuery();
request.headers["accept"] = "application/ohttp-keys";
absl::StatusOr<RequestId> request_id =
connection_pool_.SendRequest(request, /*mtls=*/false);
if (!request_id.ok()) {
return absl::FailedPreconditionError(
absl::StrCat("Failed to send OHTTP key fetch request: ",
request_id.status().message()));
}
key_fetch_request_id_ = *request_id;
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::CheckStatusAndContentType(
const Message& response, const std::string& content_type,
std::optional<uint16_t> expected_status_code) {
auto status_it = response.headers.find(":status");
if (status_it == response.headers.end()) {
return absl::InvalidArgumentError(
absl::StrCat("No :status header in ", content_type, " response."));
}
int status_code;
if (!absl::SimpleAtoi(status_it->second, &status_code)) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to parse ", content_type, " status code."));
}
if (expected_status_code.has_value()) {
if (status_code != *expected_status_code) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected status in ", content_type, " response: ", status_code,
" (expected ", *expected_status_code, ")"));
}
if (*expected_status_code < 200 || *expected_status_code >= 300) {
// If we expect a failure status code, skip the content-type check.
return absl::OkStatus();
}
} else {
if (status_code < 200 || status_code >= 300) {
return absl::InvalidArgumentError(
absl::StrCat("Unexpected status in ", content_type,
" response: ", status_it->second));
}
}
auto content_type_it = response.headers.find("content-type");
if (content_type_it == response.headers.end()) {
return absl::InvalidArgumentError(
absl::StrCat("No content-type header in ", content_type, " response."));
}
std::vector<absl::string_view> content_type_split =
absl::StrSplit(content_type_it->second, absl::MaxSplits(';', 1));
absl::string_view content_type_without_params = content_type_split[0];
quiche::QuicheTextUtils::RemoveLeadingAndTrailingWhitespace(
&content_type_without_params);
if (content_type_without_params != content_type) {
return absl::InvalidArgumentError(
absl::StrCat("Unexpected content-type in ", content_type,
" response: ", content_type_it->second));
}
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::HandleKeyResponse(
const absl::StatusOr<Message>& response) {
key_fetch_request_id_ = std::nullopt;
if (!response.ok()) {
return absl::FailedPreconditionError(absl::StrCat(
"Failed to fetch OHTTP keys: ", response.status().message()));
}
QUICHE_LOG(INFO) << "Received OHTTP keys response: "
<< response->headers.DebugString();
QUICHE_RETURN_IF_ERROR(CheckStatusAndContentType(
*response, "application/ohttp-keys", std::nullopt));
absl::StatusOr<ObliviousHttpKeyConfigs> key_configs =
ObliviousHttpKeyConfigs::ParseConcatenatedKeys(response->body);
if (!key_configs.ok()) {
return absl::FailedPreconditionError(absl::StrCat(
"Failed to parse OHTTP keys: ", key_configs.status().message()));
}
QUICHE_LOG(INFO) << "Successfully got " << key_configs->NumKeys()
<< " OHTTP keys: " << std::endl
<< key_configs->DebugString();
if (config_.per_request_configs().empty()) {
return absl::InvalidArgumentError("No OHTTP URLs to request, exiting.");
}
relay_url_ = QuicUrl(config_.relay_url(), "https");
if (relay_url_.host().empty() &&
!absl::StrContains(config_.relay_url(), "://")) {
relay_url_ = QuicUrl(absl::StrCat("https://", config_.relay_url()));
}
QUICHE_LOG(INFO) << "Using relay URL: " << relay_url_.ToString();
ObliviousHttpHeaderKeyConfig key_config = key_configs->PreferredConfig();
absl::StatusOr<absl::string_view> public_key =
key_configs->GetPublicKeyForId(key_config.GetKeyId());
if (!public_key.ok()) {
return absl::InternalError(
absl::StrCat("Failed to get OHTTP public key for key ID ",
static_cast<int>(key_config.GetKeyId()), ": ",
public_key.status().message()));
}
absl::StatusOr<ObliviousHttpClient> ohttp_client =
ObliviousHttpClient::Create(*public_key, key_config);
if (!ohttp_client.ok()) {
return absl::InternalError(absl::StrCat("Failed to create OHTTP client: ",
ohttp_client.status().message()));
}
ohttp_client_.emplace(std::move(*ohttp_client));
for (const auto& per_request_config : config_.per_request_configs()) {
QUICHE_RETURN_IF_ERROR(SendOhttpRequest(per_request_config));
}
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::SendOhttpRequest(
const Config::PerRequestConfig& per_request_config) {
QuicUrl url(per_request_config.url(), "https");
if (url.host().empty() &&
!absl::StrContains(per_request_config.url(), "://")) {
url = QuicUrl(absl::StrCat("https://", per_request_config.url()));
}
if (url.host().empty()) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to parse URL ", per_request_config.url()));
}
BinaryHttpRequest::ControlData control_data;
std::string post_data = per_request_config.post_data();
control_data.method = post_data.empty() ? "GET" : "POST";
control_data.scheme = url.scheme();
control_data.authority = url.HostPort();
control_data.path = url.PathParamsQuery();
std::string encrypted_data;
PendingRequest pending_request(per_request_config);
std::string formatted_token;
if (!per_request_config.private_token().empty()) {
QUICHE_ASSIGN_OR_RETURN(
formatted_token,
FormatPrivateToken(per_request_config.private_token()));
}
if (!ohttp_client_.has_value()) {
QUICHE_LOG(FATAL) << "Cannot send OHTTP request without OHTTP client";
return absl::InternalError(
"Cannot send OHTTP request without OHTTP client");
}
if (pending_request.per_request_config.use_chunked_ohttp()) {
pending_request.chunk_handler = std::make_unique<ChunkHandler>();
absl::StatusOr<ChunkedObliviousHttpClient> chunked_client =
ChunkedObliviousHttpClient::Create(ohttp_client_->GetPublicKey(),
ohttp_client_->GetKeyConfig(),
pending_request.chunk_handler.get());
if (!chunked_client.ok()) {
return absl::InternalError(
absl::StrCat("Failed to create chunked OHTTP client: ",
chunked_client.status().message()));
}
BinaryHttpRequest::IndeterminateLengthEncoder encoder;
QUICHE_ASSIGN_OR_RETURN(std::string encoded_data,
encoder.EncodeControlData(control_data));
std::vector<quiche::BinaryHttpMessage::FieldView> headers;
if (!formatted_token.empty()) {
headers.push_back({"authorization", formatted_token});
}
QUICHE_ASSIGN_OR_RETURN(std::string encoded_headers,
encoder.EncodeHeaders(absl::MakeSpan(headers)));
encoded_data += encoded_headers;
if (!post_data.empty()) {
absl::string_view body = post_data;
std::vector<absl::string_view> body_chunks;
if (body.size() > 1) {
// Intentionally split the data into two chunks to test body chunking.
body_chunks.push_back(body.substr(0, body.size() / 2));
body_chunks.push_back(body.substr(body.size() / 2));
} else {
body_chunks.push_back(body);
}
QUICHE_ASSIGN_OR_RETURN(
std::string encoded_body,
encoder.EncodeBodyChunks(absl::MakeSpan(body_chunks),
/*body_chunks_done=*/false));
encoded_data += encoded_body;
}
std::vector<absl::string_view> empty_body_chunks;
QUICHE_ASSIGN_OR_RETURN(
std::string encoded_final_chunk,
encoder.EncodeBodyChunks(absl::MakeSpan(empty_body_chunks),
/*body_chunks_done=*/true));
encoded_data += encoded_final_chunk;
std::vector<quiche::BinaryHttpMessage::FieldView> trailers;
QUICHE_ASSIGN_OR_RETURN(std::string encoded_trailers,
encoder.EncodeTrailers(absl::MakeSpan(trailers)));
encoded_data += encoded_trailers;
// Intentionally split the data into two chunks to test encryption chunking.
QUICHE_ASSIGN_OR_RETURN(encrypted_data,
chunked_client->EncryptRequestChunk(
absl::string_view(encoded_data).substr(0, 1),
/*is_final_chunk=*/false));
QUICHE_ASSIGN_OR_RETURN(std::string encrypted_data2,
chunked_client->EncryptRequestChunk(
absl::string_view(encoded_data).substr(1),
/*is_final_chunk=*/true));
encrypted_data += encrypted_data2;
pending_request.chunk_handler->SetChunkedClient(std::move(*chunked_client));
} else {
BinaryHttpRequest binary_request(control_data);
binary_request.set_body(post_data);
if (!formatted_token.empty()) {
binary_request.AddHeaderField({"authorization", formatted_token});
}
absl::StatusOr<std::string> encoded_request = binary_request.Serialize();
if (!encoded_request.ok()) {
return absl::InternalError(
absl::StrCat("Failed to serialize OHTTP request: ",
encoded_request.status().message()));
}
absl::StatusOr<ObliviousHttpRequest> ohttp_request =
ohttp_client_->CreateObliviousHttpRequest(*encoded_request);
if (!ohttp_request.ok()) {
return absl::InternalError(
absl::StrCat("Failed to create OHTTP request: ",
ohttp_request.status().message()));
}
encrypted_data = ohttp_request->EncapsulateAndSerialize();
pending_request.context.emplace(std::move(*ohttp_request).ReleaseContext());
}
Message request;
request.headers[":method"] = "POST";
request.headers[":scheme"] = relay_url_.scheme();
request.headers[":authority"] = relay_url_.HostPort();
request.headers[":path"] = relay_url_.PathParamsQuery();
request.headers["content-type"] =
pending_request.per_request_config.use_chunked_ohttp()
? "message/ohttp-chunked-req"
: "message/ohttp-req";
request.body = encrypted_data;
absl::StatusOr<RequestId> request_id =
connection_pool_.SendRequest(request, /*mtls=*/true);
if (!request_id.ok()) {
return absl::InternalError(absl::StrCat("Failed to send request: ",
request_id.status().message()));
}
QUICHE_LOG(INFO) << "Sent OHTTP request for " << per_request_config.url();
pending_ohttp_requests_.insert({*request_id, std::move(pending_request)});
return absl::OkStatus();
}
absl::StatusOr<Message> MasqueOhttpClient::TryExtractEncapsulatedResponse(
const RequestId request_id, quiche::ObliviousHttpRequest::Context& context,
const Message& response) {
if (!ohttp_client_.has_value()) {
QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP client";
return absl::InternalError("Received OHTTP response without OHTTP client");
}
QUICHE_ASSIGN_OR_RETURN(
ObliviousHttpResponse ohttp_response,
ohttp_client_->DecryptObliviousHttpResponse(response.body, context));
QUICHE_LOG(INFO) << "Received OHTTP response for " << request_id;
absl::StatusOr<BinaryHttpResponse> binary_response =
BinaryHttpResponse::Create(ohttp_response.GetPlaintextData());
QUICHE_RETURN_IF_ERROR(binary_response.status());
Message encapsulated_response;
encapsulated_response.headers[":status"] =
absl::StrCat(binary_response->status_code());
for (const quiche::BinaryHttpMessage::Field& field :
binary_response->GetHeaderFields()) {
encapsulated_response.headers[field.name] = field.value;
}
encapsulated_response.body = binary_response->body();
return encapsulated_response;
}
absl::Status MasqueOhttpClient::ProcessOhttpResponse(
RequestId request_id, const absl::StatusOr<Message>& response) {
auto it = pending_ohttp_requests_.find(request_id);
if (it == pending_ohttp_requests_.end()) {
return absl::InternalError(absl::StrCat(
"Received unexpected response for unknown request ", request_id));
}
auto cleanup =
absl::MakeCleanup([this, it]() { pending_ohttp_requests_.erase(it); });
if (!response.ok()) {
if (it->second.per_request_config.expected_gateway_error().has_value() &&
absl::StrContains(
response.status().message(),
*it->second.per_request_config.expected_gateway_error())) {
return absl::OkStatus();
}
return response.status();
}
int16_t gateway_status_code = MasqueConnectionPool::GetStatusCode(*response);
if (it->second.per_request_config.expected_gateway_status_code()
.has_value()) {
if (gateway_status_code !=
*it->second.per_request_config.expected_gateway_status_code()) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected gateway status code: ", gateway_status_code, " != ",
*it->second.per_request_config.expected_gateway_status_code()));
}
} else if (gateway_status_code < 200 || gateway_status_code >= 300) {
return absl::InvalidArgumentError(
absl::StrCat("Bad gateway status code: ", gateway_status_code));
}
std::string content_type = it->second.per_request_config.use_chunked_ohttp()
? "message/ohttp-chunked-res"
: "message/ohttp-res";
std::optional<uint16_t> expected_gateway_status_code =
it->second.per_request_config.expected_gateway_status_code();
absl::Status status = CheckStatusAndContentType(*response, content_type,
expected_gateway_status_code);
if (!status.ok()) {
if (!response->body.empty()) {
QUICHE_LOG(ERROR) << "Bad " << content_type << " with body:" << std::endl
<< response->body;
} else {
QUICHE_LOG(ERROR) << "Bad " << content_type << " with empty body";
}
return status;
}
if (expected_gateway_status_code.has_value() &&
(*expected_gateway_status_code < 200 ||
*expected_gateway_status_code >= 300)) {
// If we expect a failure status code, skip decapsulation.
return absl::OkStatus();
}
std::optional<Message> encapsulated_response;
if (it->second.per_request_config.use_chunked_ohttp()) {
QUICHE_ASSIGN_OR_RETURN(
encapsulated_response,
it->second.chunk_handler->DecryptFullResponse(response->body));
} else {
if (!it->second.context.has_value()) {
QUICHE_LOG(FATAL) << "Received OHTTP response without OHTTP context";
return absl::InternalError(
"Received OHTTP response without OHTTP context");
}
QUICHE_ASSIGN_OR_RETURN(encapsulated_response,
TryExtractEncapsulatedResponse(
request_id, *it->second.context, *response));
}
QUICHE_LOG(INFO) << "Successfully decapsulated response for request ID "
<< request_id << ". Headers:"
<< encapsulated_response->headers.DebugString()
<< (encapsulated_response->body.empty()
? "Empty body"
: absl::StrCat("Body: \n",
encapsulated_response->body));
int16_t encapsulated_status_code =
MasqueConnectionPool::GetStatusCode(*encapsulated_response);
if (it->second.per_request_config.expected_encapsulated_status_code()
.has_value()) {
if (encapsulated_status_code !=
*it->second.per_request_config.expected_encapsulated_status_code()) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected encapsulated status code: ", encapsulated_status_code,
" != ",
*it->second.per_request_config.expected_encapsulated_status_code()));
}
} else if (encapsulated_status_code < 200 ||
encapsulated_status_code >= 300) {
return absl::InvalidArgumentError(absl::StrCat(
"Bad encapsulated status code: ", encapsulated_status_code));
}
if (it->second.per_request_config.expected_encapsulated_response_body()
.has_value() &&
encapsulated_response->body !=
*it->second.per_request_config
.expected_encapsulated_response_body()) {
return absl::InvalidArgumentError(absl::StrCat(
"Unexpected encapsulated response body: \"",
encapsulated_response->body, "\" != \"",
*it->second.per_request_config.expected_encapsulated_response_body(),
"\""));
}
return absl::OkStatus();
}
void MasqueOhttpClient::OnPoolResponse(MasqueConnectionPool* /*pool*/,
RequestId request_id,
absl::StatusOr<Message>&& response) {
if (key_fetch_request_id_.has_value() &&
*key_fetch_request_id_ == request_id) {
absl::Status status = HandleKeyResponse(response);
if (!status.ok()) {
Abort(status);
}
} else {
absl::Status status = ProcessOhttpResponse(request_id, response);
if (!status.ok()) {
Abort(status);
}
}
}
MasqueOhttpClient::ChunkHandler::ChunkHandler() : decoder_(this) {}
absl::StatusOr<Message> MasqueOhttpClient::ChunkHandler::DecryptFullResponse(
absl::string_view encrypted_response) {
if (!chunked_client_.has_value()) {
QUICHE_LOG(FATAL) << "DecryptFullResponse called without a chunked client";
return absl::InternalError(
"DecryptFullResponse called without a chunked client");
}
QUICHE_RETURN_IF_ERROR(chunked_client_->DecryptResponse(encrypted_response,
/*end_stream=*/true));
return std::move(response_);
}
absl::Status MasqueOhttpClient::ChunkHandler::OnDecryptedChunk(
absl::string_view decrypted_chunk) {
return decoder_.Decode(decrypted_chunk, /*end_stream=*/false);
}
absl::Status MasqueOhttpClient::ChunkHandler::OnChunksDone() {
return decoder_.Decode("", /*end_stream=*/true);
}
absl::Status MasqueOhttpClient::ChunkHandler::OnInformationalResponseStatusCode(
uint16_t status_code) {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnInformationalResponseHeader(
absl::string_view name, absl::string_view value) {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnInformationalResponseDone() {
return absl::OkStatus();
}
absl::Status
MasqueOhttpClient::ChunkHandler::OnInformationalResponsesSectionDone() {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnFinalResponseStatusCode(
uint16_t status_code) {
response_.headers[":status"] = absl::StrCat(status_code);
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnFinalResponseHeader(
absl::string_view name, absl::string_view value) {
response_.headers[name] = value;
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnFinalResponseHeadersDone() {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnBodyChunk(
absl::string_view body_chunk) {
response_.body += body_chunk;
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnBodyChunksDone() {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnTrailer(
absl::string_view name, absl::string_view value) {
return absl::OkStatus();
}
absl::Status MasqueOhttpClient::ChunkHandler::OnTrailersDone() {
return absl::OkStatus();
}
} // namespace quic