| #include "quiche/binary_http/binary_http_message.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <functional> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/attributes.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/ascii.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/common/quiche_callbacks.h" |
| #include "quiche/common/quiche_data_reader.h" |
| #include "quiche/common/quiche_data_writer.h" |
| |
| namespace quiche { |
| namespace { |
| |
| constexpr uint64_t kKnownLengthRequestFraming = 0; |
| constexpr uint64_t kKnownLengthResponseFraming = 1; |
| constexpr uint64_t kIndeterminateLengthRequestFraming = 2; |
| constexpr uint64_t kIndeterminateLengthResponseFraming = 3; |
| constexpr uint64_t kContentTerminator = 0; |
| |
| bool ReadStringValue(quiche::QuicheDataReader& reader, std::string& data) { |
| absl::string_view data_view; |
| if (!reader.ReadStringPieceVarInt62(&data_view)) { |
| return false; |
| } |
| data = std::string(data_view); |
| return true; |
| } |
| |
| bool IsValidPadding(absl::string_view data) { |
| return std::all_of(data.begin(), data.end(), |
| [](char c) { return c == '\0'; }); |
| } |
| |
| absl::StatusOr<BinaryHttpRequest::ControlData> DecodeControlData( |
| quiche::QuicheDataReader& reader) { |
| BinaryHttpRequest::ControlData control_data; |
| if (!ReadStringValue(reader, control_data.method)) { |
| return absl::InvalidArgumentError("Failed to read method."); |
| } |
| if (!ReadStringValue(reader, control_data.scheme)) { |
| return absl::InvalidArgumentError("Failed to read scheme."); |
| } |
| if (!ReadStringValue(reader, control_data.authority)) { |
| return absl::InvalidArgumentError("Failed to read authority."); |
| } |
| if (!ReadStringValue(reader, control_data.path)) { |
| return absl::InvalidArgumentError("Failed to read path."); |
| } |
| return control_data; |
| } |
| |
| // Decodes a header/trailer name and value. This takes a length which represents |
| // only the name length. |
| absl::StatusOr<BinaryHttpMessage::FieldView> DecodeField( |
| QuicheDataReader& reader, uint64_t name_length) { |
| absl::string_view name; |
| if (!reader.ReadStringPiece(&name, name_length)) { |
| return absl::OutOfRangeError("Not enough data to read field name."); |
| } |
| absl::string_view value; |
| if (!reader.ReadStringPieceVarInt62(&value)) { |
| return absl::OutOfRangeError("Not enough data to read field value."); |
| } |
| return BinaryHttpMessage::FieldView{name, value}; |
| } |
| |
| absl::Status DecodeFields(quiche::QuicheDataReader& reader, |
| quiche::UnretainedCallback<void( |
| absl::string_view name, absl::string_view value)> |
| callback) { |
| absl::string_view fields; |
| if (!reader.ReadStringPieceVarInt62(&fields)) { |
| return absl::InvalidArgumentError("Failed to read fields."); |
| } |
| quiche::QuicheDataReader fields_reader(fields); |
| while (!fields_reader.IsDoneReading()) { |
| absl::string_view name; |
| if (!fields_reader.ReadStringPieceVarInt62(&name)) { |
| return absl::InvalidArgumentError("Failed to read field name."); |
| } |
| absl::string_view value; |
| if (!fields_reader.ReadStringPieceVarInt62(&value)) { |
| return absl::InvalidArgumentError("Failed to read field value."); |
| } |
| callback(name, value); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status DecodeFieldsAndBody(quiche::QuicheDataReader& reader, |
| BinaryHttpMessage& message) { |
| if (const absl::Status status = DecodeFields( |
| reader, |
| [&message](absl::string_view name, absl::string_view value) { |
| message.AddHeaderField({std::string(name), std::string(value)}); |
| }); |
| !status.ok()) { |
| return status; |
| } |
| // Exit early if message has been truncated. |
| // https://www.rfc-editor.org/rfc/rfc9292#section-3.8 |
| if (reader.IsDoneReading()) { |
| return absl::OkStatus(); |
| } |
| |
| absl::string_view body; |
| if (!reader.ReadStringPieceVarInt62(&body)) { |
| return absl::InvalidArgumentError("Failed to read body."); |
| } |
| message.set_body(std::string(body)); |
| // TODO(bschneider): Check for / read-in any trailer-fields |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<BinaryHttpRequest> DecodeKnownLengthRequest( |
| quiche::QuicheDataReader& reader) { |
| const auto control_data = DecodeControlData(reader); |
| if (!control_data.ok()) { |
| return control_data.status(); |
| } |
| BinaryHttpRequest request(std::move(*control_data)); |
| if (reader.IsDoneReading()) { |
| // Per RFC 9292, Section 3.8, "Decoders MUST treat missing truncated fields |
| // as equivalent to having been sent with the length field set to zero." |
| // If we've run out of payload, stop parsing and return the request. |
| return request; |
| } |
| if (const absl::Status status = DecodeFieldsAndBody(reader, request); |
| !status.ok()) { |
| return status; |
| } |
| if (!IsValidPadding(reader.PeekRemainingPayload())) { |
| return absl::InvalidArgumentError("Non-zero padding."); |
| } |
| request.set_num_padding_bytes(reader.BytesRemaining()); |
| return request; |
| } |
| |
| absl::StatusOr<BinaryHttpResponse> DecodeKnownLengthResponse( |
| quiche::QuicheDataReader& reader) { |
| std::vector<std::pair<uint16_t, std::vector<BinaryHttpMessage::Field>>> |
| informational_responses; |
| uint64_t status_code; |
| bool reading_response_control_data = true; |
| while (reading_response_control_data) { |
| if (!reader.ReadVarInt62(&status_code)) { |
| return absl::InvalidArgumentError("Failed to read status code."); |
| } |
| if (status_code >= 100 && status_code <= 199) { |
| std::vector<BinaryHttpMessage::Field> fields; |
| if (const absl::Status status = DecodeFields( |
| reader, |
| [&fields](absl::string_view name, absl::string_view value) { |
| fields.push_back({std::string(name), std::string(value)}); |
| }); |
| !status.ok()) { |
| return status; |
| } |
| informational_responses.emplace_back(status_code, std::move(fields)); |
| } else { |
| reading_response_control_data = false; |
| } |
| } |
| BinaryHttpResponse response(status_code); |
| for (const auto& informational_response : informational_responses) { |
| if (const absl::Status status = response.AddInformationalResponse( |
| informational_response.first, |
| std::move(informational_response.second)); |
| !status.ok()) { |
| return status; |
| } |
| } |
| if (const absl::Status status = DecodeFieldsAndBody(reader, response); |
| !status.ok()) { |
| return status; |
| } |
| if (!IsValidPadding(reader.PeekRemainingPayload())) { |
| return absl::InvalidArgumentError("Non-zero padding."); |
| } |
| response.set_num_padding_bytes(reader.BytesRemaining()); |
| return response; |
| } |
| |
| uint64_t StringPieceVarInt62Len(absl::string_view s) { |
| return quiche::QuicheDataWriter::GetVarInt62Len(s.length()) + s.length(); |
| } |
| } // namespace |
| |
| void BinaryHttpMessage::Fields::AddField(BinaryHttpMessage::Field field) { |
| fields_.push_back(std::move(field)); |
| } |
| |
| // Encode fields in the order they were initially inserted. |
| // Updates do not change order. |
| absl::Status BinaryHttpMessage::Fields::Encode( |
| quiche::QuicheDataWriter& writer) const { |
| if (!writer.WriteVarInt62(EncodedFieldsSize())) { |
| return absl::InvalidArgumentError("Failed to write encoded field size."); |
| } |
| for (const BinaryHttpMessage::Field& field : fields_) { |
| if (!writer.WriteStringPieceVarInt62(field.name)) { |
| return absl::InvalidArgumentError("Failed to write field name."); |
| } |
| if (!writer.WriteStringPieceVarInt62(field.value)) { |
| return absl::InvalidArgumentError("Failed to write field value."); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| size_t BinaryHttpMessage::Fields::EncodedSize() const { |
| const size_t size = EncodedFieldsSize(); |
| return size + quiche::QuicheDataWriter::GetVarInt62Len(size); |
| } |
| |
| size_t BinaryHttpMessage::Fields::EncodedFieldsSize() const { |
| size_t size = 0; |
| for (const BinaryHttpMessage::Field& field : fields_) { |
| size += StringPieceVarInt62Len(field.name) + |
| StringPieceVarInt62Len(field.value); |
| } |
| return size; |
| } |
| |
| BinaryHttpMessage* BinaryHttpMessage::AddHeaderField( |
| BinaryHttpMessage::Field field) { |
| const std::string lower_name = absl::AsciiStrToLower(field.name); |
| if (lower_name == "host") { |
| has_host_ = true; |
| } |
| header_fields_.AddField({std::move(lower_name), std::move(field.value)}); |
| return this; |
| } |
| |
| // Appends the encoded fields and body to data. |
| absl::Status BinaryHttpMessage::EncodeKnownLengthFieldsAndBody( |
| quiche::QuicheDataWriter& writer) const { |
| if (const absl::Status status = header_fields_.Encode(writer); !status.ok()) { |
| return status; |
| } |
| if (!writer.WriteStringPieceVarInt62(body_)) { |
| return absl::InvalidArgumentError("Failed to encode body."); |
| } |
| // TODO(bschneider): Consider support for trailer fields on known-length |
| // requests. Trailers are atypical for a known-length request. |
| return absl::OkStatus(); |
| } |
| |
| size_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const { |
| return header_fields_.EncodedSize() + StringPieceVarInt62Len(body_); |
| } |
| |
| absl::Status BinaryHttpResponse::AddInformationalResponse( |
| uint16_t status_code, std::vector<Field> header_fields) { |
| if (status_code < 100) { |
| return absl::InvalidArgumentError("status code < 100"); |
| } |
| if (status_code > 199) { |
| return absl::InvalidArgumentError("status code > 199"); |
| } |
| InformationalResponse data(status_code); |
| for (Field& header : header_fields) { |
| data.AddField(header.name, std::move(header.value)); |
| } |
| informational_response_control_data_.push_back(std::move(data)); |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<std::string> BinaryHttpResponse::Serialize() const { |
| // Only supporting known length requests so far. |
| return EncodeAsKnownLength(); |
| } |
| |
| absl::StatusOr<std::string> BinaryHttpResponse::EncodeAsKnownLength() const { |
| std::string data; |
| data.resize(EncodedSize()); |
| quiche::QuicheDataWriter writer(data.size(), data.data()); |
| if (!writer.WriteVarInt62(kKnownLengthResponseFraming)) { |
| return absl::InvalidArgumentError("Failed to write framing indicator"); |
| } |
| // Informational response |
| for (const auto& informational : informational_response_control_data_) { |
| if (const absl::Status status = informational.Encode(writer); |
| !status.ok()) { |
| return status; |
| } |
| } |
| if (!writer.WriteVarInt62(status_code_)) { |
| return absl::InvalidArgumentError("Failed to write status code"); |
| } |
| if (const absl::Status status = EncodeKnownLengthFieldsAndBody(writer); |
| !status.ok()) { |
| return status; |
| } |
| QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes()); |
| writer.WritePadding(); |
| return data; |
| } |
| |
| size_t BinaryHttpResponse::EncodedSize() const { |
| size_t size = QuicheDataWriter::GetVarInt62Len(kKnownLengthResponseFraming); |
| for (const auto& informational : informational_response_control_data_) { |
| size += informational.EncodedSize(); |
| } |
| return size + quiche::QuicheDataWriter::GetVarInt62Len(status_code_) + |
| EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes(); |
| } |
| |
| void BinaryHttpResponse::InformationalResponse::AddField(absl::string_view name, |
| std::string value) { |
| fields_.AddField({absl::AsciiStrToLower(name), std::move(value)}); |
| } |
| |
| // Appends the encoded fields and body to data. |
| absl::Status BinaryHttpResponse::InformationalResponse::Encode( |
| quiche::QuicheDataWriter& writer) const { |
| writer.WriteVarInt62(status_code_); |
| return fields_.Encode(writer); |
| } |
| |
| size_t BinaryHttpResponse::InformationalResponse::EncodedSize() const { |
| return quiche::QuicheDataWriter::GetVarInt62Len(status_code_) + |
| fields_.EncodedSize(); |
| } |
| |
| absl::StatusOr<std::string> BinaryHttpRequest::Serialize() const { |
| // Only supporting known length requests so far. |
| return EncodeAsKnownLength(); |
| } |
| |
| // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-request-control-data |
| absl::Status BinaryHttpRequest::EncodeControlData( |
| quiche::QuicheDataWriter& writer) const { |
| if (!writer.WriteStringPieceVarInt62(control_data_.method)) { |
| return absl::InvalidArgumentError("Failed to encode method."); |
| } |
| if (!writer.WriteStringPieceVarInt62(control_data_.scheme)) { |
| return absl::InvalidArgumentError("Failed to encode scheme."); |
| } |
| // the Host header field is not replicated in the :authority field, as is |
| // required for ensuring that the request is reproduced accurately; see |
| // Section 8.1.2.3 of [H2]. |
| if (!has_host()) { |
| if (!writer.WriteStringPieceVarInt62(control_data_.authority)) { |
| return absl::InvalidArgumentError("Failed to encode authority."); |
| } |
| } else { |
| if (!writer.WriteStringPieceVarInt62("")) { |
| return absl::InvalidArgumentError("Failed to encode authority."); |
| } |
| } |
| if (!writer.WriteStringPieceVarInt62(control_data_.path)) { |
| return absl::InvalidArgumentError("Failed to encode path."); |
| } |
| return absl::OkStatus(); |
| } |
| |
| size_t BinaryHttpRequest::EncodedControlDataSize() const { |
| size_t size = StringPieceVarInt62Len(control_data_.method) + |
| StringPieceVarInt62Len(control_data_.scheme) + |
| StringPieceVarInt62Len(control_data_.path); |
| if (!has_host()) { |
| size += StringPieceVarInt62Len(control_data_.authority); |
| } else { |
| size += StringPieceVarInt62Len(""); |
| } |
| return size; |
| } |
| |
| size_t BinaryHttpRequest::EncodedSize() const { |
| return QuicheDataWriter::GetVarInt62Len(kKnownLengthRequestFraming) + |
| EncodedControlDataSize() + EncodedKnownLengthFieldsAndBodySize() + |
| num_padding_bytes(); |
| } |
| |
| // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-known-length-messages |
| absl::StatusOr<std::string> BinaryHttpRequest::EncodeAsKnownLength() const { |
| std::string data; |
| data.resize(EncodedSize()); |
| quiche::QuicheDataWriter writer(data.size(), data.data()); |
| if (!writer.WriteVarInt62(kKnownLengthRequestFraming)) { |
| return absl::InvalidArgumentError("Failed to encode framing indicator."); |
| } |
| if (const absl::Status status = EncodeControlData(writer); !status.ok()) { |
| return status; |
| } |
| if (const absl::Status status = EncodeKnownLengthFieldsAndBody(writer); |
| !status.ok()) { |
| return status; |
| } |
| QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes()); |
| writer.WritePadding(); |
| return data; |
| } |
| |
| absl::StatusOr<BinaryHttpRequest> BinaryHttpRequest::Create( |
| absl::string_view data) { |
| quiche::QuicheDataReader reader(data); |
| uint64_t framing; |
| if (!reader.ReadVarInt62(&framing)) { |
| return absl::InvalidArgumentError("Missing framing indicator."); |
| } |
| if (framing == kKnownLengthRequestFraming) { |
| return DecodeKnownLengthRequest(reader); |
| } |
| return absl::UnimplementedError( |
| absl::StrCat("Unsupported framing type ", framing)); |
| } |
| |
| absl::Status |
| BinaryHttpRequest::IndeterminateLengthDecoder::DecodeContentTerminatedSection( |
| QuicheDataReader& reader) { |
| uint64_t length_or_content_terminator; |
| do { |
| if (!reader.ReadVarInt62(&length_or_content_terminator)) { |
| return absl::OutOfRangeError("Not enough data to read section."); |
| } |
| if (length_or_content_terminator != kContentTerminator) { |
| switch (current_section_) { |
| case MessageSection::kHeader: { |
| const absl::StatusOr<FieldView> field = |
| DecodeField(reader, length_or_content_terminator); |
| if (!field.ok()) { |
| return field.status(); |
| } |
| const absl::Status section_status = |
| message_section_handler_.OnHeader(field->name, field->value); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat("Failed to handle header: ", |
| section_status.message())); |
| } |
| break; |
| } |
| case MessageSection::kBody: { |
| absl::string_view body_chunk; |
| if (!reader.ReadStringPiece(&body_chunk, |
| length_or_content_terminator)) { |
| return absl::OutOfRangeError("Failed to read body chunk."); |
| } |
| const absl::Status section_status = |
| message_section_handler_.OnBodyChunk(body_chunk); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle body chunk: ", section_status.message())); |
| } |
| break; |
| } |
| case MessageSection::kTrailer: { |
| const absl::StatusOr<FieldView> field = |
| DecodeField(reader, length_or_content_terminator); |
| if (!field.ok()) { |
| return field.status(); |
| } |
| const absl::Status section_status = |
| message_section_handler_.OnTrailer(field->name, field->value); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle trailer: ", section_status.message())); |
| } |
| break; |
| } |
| default: |
| return absl::InternalError( |
| "Unexpected section in DecodeContentTerminatedSection."); |
| } |
| } |
| // Either a section was successfully decoded or a content terminator was |
| // encountered, save the checkpoint. |
| SaveCheckpoint(reader); |
| } while (length_or_content_terminator != kContentTerminator); |
| return absl::OkStatus(); |
| } |
| |
| // Returns Ok status only if the decoding processes the Padding section |
| // successfully or if the message is truncated properly. All other points of |
| // return are errors. |
| absl::Status |
| BinaryHttpRequest::IndeterminateLengthDecoder::DecodeCheckpointData( |
| bool end_stream) { |
| QuicheDataReader reader(checkpoint_view_); |
| switch (current_section_) { |
| case MessageSection::kEnd: |
| return absl::InternalError("Decoder is invalid."); |
| case MessageSection::kControlData: { |
| uint64_t framing; |
| if (!reader.ReadVarInt62(&framing)) { |
| return absl::OutOfRangeError("Failed to read framing."); |
| } |
| if (framing != kIndeterminateLengthRequestFraming) { |
| return absl::InvalidArgumentError( |
| absl::StrFormat("Unsupported framing type: 0x%02x", framing)); |
| } |
| |
| const absl::StatusOr<BinaryHttpRequest::ControlData> control_data = |
| DecodeControlData(reader); |
| // Only fails if there is not enough data to read the entire control data. |
| if (!control_data.ok()) { |
| return absl::OutOfRangeError("Failed to read control data."); |
| } |
| |
| const absl::Status section_status = |
| message_section_handler_.OnControlData(control_data.value()); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle control data: ", section_status.message())); |
| } |
| SaveCheckpoint(reader); |
| current_section_ = MessageSection::kHeader; |
| } |
| ABSL_FALLTHROUGH_INTENDED; |
| case MessageSection::kHeader: { |
| const absl::Status status = DecodeContentTerminatedSection(reader); |
| if (!status.ok()) { |
| return status; |
| } |
| const absl::Status section_status = |
| message_section_handler_.OnHeadersDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle headers done: ", section_status.message())); |
| } |
| current_section_ = MessageSection::kBody; |
| } |
| ABSL_FALLTHROUGH_INTENDED; |
| case MessageSection::kBody: { |
| if (!reader.IsDoneReading()) { |
| maybe_truncated_ = false; |
| } |
| // Body and trailers truncation is valid only if: |
| // 1. There is no data to read after the headers section. |
| // 2. This is signaled as the last piece of data (end_stream). |
| if (maybe_truncated_ && end_stream) { |
| absl::Status section_status = |
| message_section_handler_.OnBodyChunksDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle body chunks done: ", section_status.message())); |
| } |
| section_status = message_section_handler_.OnTrailersDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle trailers done: ", section_status.message())); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status section_status = DecodeContentTerminatedSection(reader); |
| if (!section_status.ok()) { |
| return section_status; |
| } |
| section_status = message_section_handler_.OnBodyChunksDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle body chunks done: ", section_status.message())); |
| } |
| current_section_ = MessageSection::kTrailer; |
| // Reset the truncation flag before entering the trailers section. |
| maybe_truncated_ = true; |
| } |
| ABSL_FALLTHROUGH_INTENDED; |
| case MessageSection::kTrailer: { |
| if (!reader.IsDoneReading()) { |
| maybe_truncated_ = false; |
| } |
| // Trailers truncation is valid only if: |
| // 1. There is no data to read after the body section. |
| // 2. This is signaled as the last piece of data (end_stream). |
| if (maybe_truncated_ && end_stream) { |
| const absl::Status section_status = |
| message_section_handler_.OnTrailersDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle trailers done: ", section_status.message())); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status section_status = DecodeContentTerminatedSection(reader); |
| if (!section_status.ok()) { |
| return section_status; |
| } |
| section_status = message_section_handler_.OnTrailersDone(); |
| if (!section_status.ok()) { |
| return absl::InternalError(absl::StrCat( |
| "Failed to handle trailers done: ", section_status.message())); |
| } |
| current_section_ = MessageSection::kPadding; |
| } |
| ABSL_FALLTHROUGH_INTENDED; |
| case MessageSection::kPadding: { |
| if (!IsValidPadding(reader.PeekRemainingPayload())) { |
| return absl::InvalidArgumentError("Non-zero padding."); |
| } |
| return absl::OkStatus(); |
| } |
| } |
| } |
| |
| void BinaryHttpRequest::IndeterminateLengthDecoder::InitializeCheckpoint( |
| absl::string_view data) { |
| checkpoint_view_ = data; |
| // Prepend buffered data if present. This is the data from a previous call to |
| // Decode that could not finish because it needed this new data. |
| if (!buffer_.empty()) { |
| absl::StrAppend(&buffer_, data); |
| checkpoint_view_ = buffer_; |
| } |
| } |
| |
| absl::Status BinaryHttpRequest::IndeterminateLengthDecoder::Decode( |
| absl::string_view data, bool end_stream) { |
| if (current_section_ == MessageSection::kEnd) { |
| return absl::InternalError("Decoder is invalid."); |
| } |
| |
| InitializeCheckpoint(data); |
| absl::Status status = DecodeCheckpointData(end_stream); |
| if (end_stream) { |
| current_section_ = MessageSection::kEnd; |
| buffer_.clear(); |
| // Out of range errors shall be treated as invalid argument errors when the |
| // stream is ending. |
| if (absl::IsOutOfRange(status)) { |
| return absl::InvalidArgumentError(status.message()); |
| } |
| return status; |
| } |
| if (absl::IsOutOfRange(status)) { |
| BufferCheckpoint(); |
| return absl::OkStatus(); |
| } |
| if (!status.ok()) { |
| current_section_ = MessageSection::kEnd; |
| } |
| |
| buffer_.clear(); |
| return status; |
| } |
| |
| absl::StatusOr<BinaryHttpResponse> BinaryHttpResponse::Create( |
| absl::string_view data) { |
| quiche::QuicheDataReader reader(data); |
| uint64_t framing; |
| if (!reader.ReadVarInt62(&framing)) { |
| return absl::InvalidArgumentError("Missing framing indicator."); |
| } |
| if (framing == kKnownLengthResponseFraming) { |
| return DecodeKnownLengthResponse(reader); |
| } |
| return absl::UnimplementedError( |
| absl::StrCat("Unsupported framing type ", framing)); |
| } |
| |
| absl::StatusOr<std::string> |
| BinaryHttpResponse::IndeterminateLengthEncoder::EncodeFieldSection( |
| std::optional<uint16_t> status_code, absl::Span<FieldView> fields) { |
| uint64_t total_length = 0; |
| if (!framing_indicator_encoded_) { |
| total_length += quiche::QuicheDataWriter::GetVarInt62Len( |
| kIndeterminateLengthResponseFraming); |
| } |
| if (status_code.has_value()) { |
| total_length += QuicheDataWriter::GetVarInt62Len(*status_code); |
| } |
| for (const auto& field : fields) { |
| uint8_t length = QuicheDataWriter::GetVarInt62Len(field.name.size()); |
| if (length == 0) { |
| return absl::InvalidArgumentError("Field name exceeds maximum length."); |
| } |
| total_length += length + field.name.size(); |
| |
| length = QuicheDataWriter::GetVarInt62Len(field.value.size()); |
| if (length == 0) { |
| return absl::InvalidArgumentError("Field value exceeds maximum length."); |
| } |
| total_length += length + field.value.size(); |
| } |
| total_length += quiche::QuicheDataWriter::GetVarInt62Len(kContentTerminator); |
| |
| std::string data(total_length, '\0'); |
| QuicheDataWriter writer(total_length, data.data()); |
| |
| if (!framing_indicator_encoded_) { |
| if (!writer.WriteVarInt62(kIndeterminateLengthResponseFraming)) { |
| return absl::InternalError("Failed to write framing indicator."); |
| } |
| framing_indicator_encoded_ = true; |
| } |
| if (status_code.has_value() && !writer.WriteVarInt62(*status_code)) { |
| return absl::InternalError("Failed to write status code."); |
| } |
| for (const auto& field : fields) { |
| if (!writer.WriteStringPieceVarInt62(absl::AsciiStrToLower(field.name))) { |
| return absl::InternalError("Failed to write field name."); |
| } |
| if (!writer.WriteStringPieceVarInt62(field.value)) { |
| return absl::InternalError("Failed to write field value."); |
| } |
| } |
| if (!writer.WriteVarInt62(kContentTerminator)) { |
| return absl::InternalError("Failed to write content terminator."); |
| } |
| if (writer.remaining() != 0) { |
| return absl::InternalError("Failed to write all data."); |
| } |
| return data; |
| } |
| |
| std::string |
| BinaryHttpResponse::IndeterminateLengthEncoder::GetMessageSectionString( |
| MessageSection section) const { |
| switch (section) { |
| case MessageSection::kInformationalResponseOrHeader: |
| return "InformationalResponseOrHeader"; |
| case MessageSection::kBody: |
| return "Body"; |
| case MessageSection::kTrailer: |
| return "Trailer"; |
| case MessageSection::kEnd: |
| return "End"; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| absl::StatusOr<std::string> |
| BinaryHttpResponse::IndeterminateLengthEncoder::EncodeInformationalResponse( |
| uint16_t status_code, absl::Span<FieldView> fields) { |
| if (current_section_ != MessageSection::kInformationalResponseOrHeader) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError(absl::StrCat( |
| "EncodeInformationalResponse called in incorrect section: ", |
| GetMessageSectionString(current_section_))); |
| } |
| if (status_code < 100 || status_code > 199) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError(absl::StrCat( |
| "Invalid informational response status code: ", status_code)); |
| } |
| |
| absl::StatusOr<std::string> data = EncodeFieldSection(status_code, fields); |
| if (!data.ok()) { |
| current_section_ = MessageSection::kEnd; |
| } |
| |
| return data; |
| } |
| |
| absl::StatusOr<std::string> |
| BinaryHttpResponse::IndeterminateLengthEncoder::EncodeHeaders( |
| uint16_t status_code, absl::Span<FieldView> headers) { |
| if (current_section_ != MessageSection::kInformationalResponseOrHeader) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError( |
| absl::StrCat("EncodeHeaders called in incorrect section: ", |
| GetMessageSectionString(current_section_))); |
| } |
| if (status_code < 200 || status_code > 599) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError( |
| absl::StrCat("Invalid response status code: ", status_code)); |
| } |
| |
| absl::StatusOr<std::string> data = EncodeFieldSection(status_code, headers); |
| if (!data.ok()) { |
| current_section_ = MessageSection::kEnd; |
| return data; |
| } |
| current_section_ = MessageSection::kBody; |
| return data; |
| } |
| |
| absl::StatusOr<std::string> |
| BinaryHttpResponse::IndeterminateLengthEncoder::EncodeBodyChunks( |
| absl::Span<absl::string_view> body_chunks, bool body_chunks_done) { |
| if (current_section_ != MessageSection::kBody) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError( |
| absl::StrCat("EncodeBodyChunks called in incorrect section: ", |
| GetMessageSectionString(current_section_))); |
| } |
| uint64_t total_length = 0; |
| for (const auto& body_chunk : body_chunks) { |
| uint8_t body_chunk_var_int_length = |
| QuicheDataWriter::GetVarInt62Len(body_chunk.size()); |
| if (body_chunk_var_int_length == 0) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError( |
| "Body chunk size exceeds maximum length."); |
| } |
| total_length += body_chunk_var_int_length + body_chunk.size(); |
| } |
| if (body_chunks_done) { |
| total_length += |
| quiche::QuicheDataWriter::GetVarInt62Len(kContentTerminator); |
| } |
| |
| std::string data(total_length, '\0'); |
| QuicheDataWriter writer(total_length, data.data()); |
| |
| for (const auto& body_chunk : body_chunks) { |
| if (!writer.WriteStringPieceVarInt62(body_chunk)) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InternalError("Failed to write body chunk."); |
| } |
| } |
| if (body_chunks_done) { |
| if (!writer.WriteVarInt62(kContentTerminator)) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InternalError("Failed to write content terminator."); |
| } |
| current_section_ = MessageSection::kTrailer; |
| } |
| |
| if (writer.remaining() != 0) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InternalError("Failed to write all data."); |
| } |
| return data; |
| } |
| |
| absl::StatusOr<std::string> |
| BinaryHttpResponse::IndeterminateLengthEncoder::EncodeTrailers( |
| absl::Span<FieldView> trailers) { |
| if (current_section_ != MessageSection::kTrailer) { |
| current_section_ = MessageSection::kEnd; |
| return absl::InvalidArgumentError( |
| absl::StrCat("EncodeTrailers called in incorrect section: ", |
| GetMessageSectionString(current_section_))); |
| } |
| |
| absl::StatusOr<std::string> data = |
| EncodeFieldSection(/*status_code=*/std::nullopt, trailers); |
| |
| current_section_ = MessageSection::kEnd; |
| return data; |
| } |
| |
| std::string BinaryHttpMessage::DebugString() const { |
| std::vector<std::string> headers; |
| for (const auto& field : GetHeaderFields()) { |
| headers.emplace_back(field.DebugString()); |
| } |
| return absl::StrCat("BinaryHttpMessage{Headers{", absl::StrJoin(headers, ";"), |
| "}Body{", body(), "}}"); |
| } |
| |
| std::string BinaryHttpMessage::Field::DebugString() const { |
| return absl::StrCat("Field{", name, "=", value, "}"); |
| } |
| |
| std::string BinaryHttpResponse::InformationalResponse::DebugString() const { |
| std::vector<std::string> fs; |
| for (const auto& field : fields()) { |
| fs.emplace_back(field.DebugString()); |
| } |
| return absl::StrCat("InformationalResponse{", absl::StrJoin(fs, ";"), "}"); |
| } |
| |
| std::string BinaryHttpResponse::DebugString() const { |
| std::vector<std::string> irs; |
| for (const auto& ir : informational_responses()) { |
| irs.emplace_back(ir.DebugString()); |
| } |
| return absl::StrCat("BinaryHttpResponse(", status_code_, "){", |
| BinaryHttpMessage::DebugString(), absl::StrJoin(irs, ";"), |
| "}"); |
| } |
| |
| std::string BinaryHttpRequest::DebugString() const { |
| return absl::StrCat("BinaryHttpRequest{", BinaryHttpMessage::DebugString(), |
| "}"); |
| } |
| |
| void PrintTo(const BinaryHttpRequest& msg, std::ostream* os) { |
| *os << msg.DebugString(); |
| } |
| |
| void PrintTo(const BinaryHttpResponse& msg, std::ostream* os) { |
| *os << msg.DebugString(); |
| } |
| |
| void PrintTo(const BinaryHttpMessage::Field& msg, std::ostream* os) { |
| *os << msg.DebugString(); |
| } |
| |
| } // namespace quiche |