Support padding of binary http request and response messages.
PiperOrigin-RevId: 486635936
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
index 24feedf..6d333ac 100644
--- a/quiche/binary_http/binary_http_message.cc
+++ b/quiche/binary_http/binary_http_message.cc
@@ -33,6 +33,11 @@
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;
@@ -107,6 +112,10 @@
!status.ok()) {
return status;
}
+ if (!IsValidPadding(reader.PeekRemainingPayload())) {
+ return absl::InvalidArgumentError("Non-zero padding.");
+ }
+ request.set_num_padding_bytes(reader.BytesRemaining());
return request;
}
@@ -148,13 +157,16 @@
!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) {
@@ -179,13 +191,13 @@
return absl::OkStatus();
}
-uint64_t BinaryHttpMessage::Fields::EncodedSize() const {
- uint64_t size = EncodedFieldsSize();
+size_t BinaryHttpMessage::Fields::EncodedSize() const {
+ const size_t size = EncodedFieldsSize();
return size + quiche::QuicheDataWriter::GetVarInt62Len(size);
}
-uint64_t BinaryHttpMessage::Fields::EncodedFieldsSize() const {
- uint64_t size = 0;
+size_t BinaryHttpMessage::Fields::EncodedFieldsSize() const {
+ size_t size = 0;
for (const BinaryHttpMessage::Field& field : fields_) {
size += StringPieceVarInt62Len(field.name) +
StringPieceVarInt62Len(field.value);
@@ -217,7 +229,7 @@
return absl::OkStatus();
}
-uint64_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const {
+size_t BinaryHttpMessage::EncodedKnownLengthFieldsAndBodySize() const {
return header_fields_.EncodedSize() + StringPieceVarInt62Len(body_);
}
@@ -263,17 +275,18 @@
!status.ok()) {
return status;
}
- QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+ QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes());
+ writer.WritePadding();
return data;
}
-uint64_t BinaryHttpResponse::EncodedSize() const {
- uint64_t size = sizeof(kKnownLengthResponseFraming);
+size_t BinaryHttpResponse::EncodedSize() const {
+ size_t size = sizeof(kKnownLengthResponseFraming);
for (const auto& informational : informational_response_control_data_) {
size += informational.EncodedSize();
}
return size + quiche::QuicheDataWriter::GetVarInt62Len(status_code_) +
- EncodedKnownLengthFieldsAndBodySize();
+ EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes();
}
void BinaryHttpResponse::InformationalResponse::AddField(absl::string_view name,
@@ -288,7 +301,7 @@
return fields_.Encode(writer);
}
-uint64_t BinaryHttpResponse::InformationalResponse::EncodedSize() const {
+size_t BinaryHttpResponse::InformationalResponse::EncodedSize() const {
return quiche::QuicheDataWriter::GetVarInt62Len(status_code_) +
fields_.EncodedSize();
}
@@ -325,10 +338,10 @@
return absl::OkStatus();
}
-uint64_t BinaryHttpRequest::EncodedControlDataSize() const {
- uint64_t size = StringPieceVarInt62Len(control_data_.method) +
- StringPieceVarInt62Len(control_data_.scheme) +
- StringPieceVarInt62Len(control_data_.path);
+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 {
@@ -337,9 +350,9 @@
return size;
}
-uint64_t BinaryHttpRequest::EncodedSize() const {
+size_t BinaryHttpRequest::EncodedSize() const {
return sizeof(kKnownLengthRequestFraming) + EncodedControlDataSize() +
- EncodedKnownLengthFieldsAndBodySize();
+ EncodedKnownLengthFieldsAndBodySize() + num_padding_bytes();
}
// https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-known-length-messages
@@ -357,7 +370,8 @@
!status.ok()) {
return status;
}
- QUICHE_DCHECK_EQ(writer.remaining(), 0u);
+ QUICHE_DCHECK_EQ(writer.remaining(), num_padding_bytes());
+ writer.WritePadding();
return data;
}
diff --git a/quiche/binary_http/binary_http_message.h b/quiche/binary_http/binary_http_message.h
index 70b8106..8f1f8f8 100644
--- a/quiche/binary_http/binary_http_message.h
+++ b/quiche/binary_http/binary_http_message.h
@@ -1,6 +1,7 @@
#ifndef QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
#define QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
+#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
@@ -51,9 +52,16 @@
}
void swap_body(std::string& body) { body_.swap(body); }
+ void set_num_padding_bytes(size_t num_padding_bytes) {
+ num_padding_bytes_ = num_padding_bytes;
+ }
+ size_t num_padding_bytes() const { return num_padding_bytes_; }
absl::string_view body() const { return body_; }
+ // Returns the number of bytes `Serialize` will return, including padding.
+ virtual size_t EncodedSize() const = 0;
+
// Returns the Binary Http formatted message.
virtual absl::StatusOr<std::string> Serialize() const = 0;
// TODO(bschneider): Add AddTrailerField for chunked messages
@@ -81,17 +89,18 @@
// The number of returned by EncodedFieldsSize
// plus the number of bytes used in the varint holding that value.
- uint64_t EncodedSize() const;
+ size_t EncodedSize() const;
private:
// Number of bytes of just the set of fields.
- uint64_t EncodedFieldsSize() const;
+ size_t EncodedFieldsSize() const;
// Fields in insertion order.
std::vector<BinaryHttpMessage::Field> fields_;
};
- bool operator==(const BinaryHttpMessage& rhs) const {
+ // Checks equality excluding padding.
+ bool IsPayloadEqual(const BinaryHttpMessage& rhs) const {
// `has_host_` is derived from `header_fields_` so it doesn't need to be
// tested directly.
return body_ == rhs.body_ && header_fields_ == rhs.header_fields_;
@@ -99,13 +108,14 @@
absl::Status EncodeKnownLengthFieldsAndBody(
quiche::QuicheDataWriter& writer) const;
- uint64_t EncodedKnownLengthFieldsAndBodySize() const;
+ size_t EncodedKnownLengthFieldsAndBodySize() const;
bool has_host() const { return has_host_; }
private:
std::string body_;
Fields header_fields_;
bool has_host_ = false;
+ size_t num_padding_bytes_ = 0;
};
void QUICHE_EXPORT PrintTo(const BinaryHttpMessage::Field& msg,
@@ -113,7 +123,10 @@
class QUICHE_EXPORT BinaryHttpRequest : public BinaryHttpMessage {
public:
- // HTTP request must have all of the following fields.
+ // HTTP request must have method, scheme, and path fields.
+ // The `authority` field is required unless a `host` header field is added.
+ // If a `host` header field is added, `authority` is serialized as the empty
+ // string.
// Some examples are:
// scheme: HTTP
// authority: www.example.com
@@ -137,14 +150,21 @@
// Deserialize
static absl::StatusOr<BinaryHttpRequest> Create(absl::string_view data);
+ size_t EncodedSize() const override;
absl::StatusOr<std::string> Serialize() const override;
const ControlData& control_data() const { return control_data_; }
virtual std::string DebugString() const override;
- bool operator==(const BinaryHttpRequest& rhs) const {
+ // Returns true if the contents of the requests are equal, excluding padding.
+ bool IsPayloadEqual(const BinaryHttpRequest& rhs) const {
return control_data_ == rhs.control_data_ &&
- BinaryHttpMessage::operator==(rhs);
+ BinaryHttpMessage::IsPayloadEqual(rhs);
+ }
+
+ bool operator==(const BinaryHttpRequest& rhs) const {
+ return IsPayloadEqual(rhs) &&
+ num_padding_bytes() == rhs.num_padding_bytes();
}
bool operator!=(const BinaryHttpRequest& rhs) const {
@@ -154,9 +174,7 @@
private:
absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const;
- uint64_t EncodedControlDataSize() const;
-
- uint64_t EncodedSize() const;
+ size_t EncodedControlDataSize() const;
// Returns Binary Http known length request formatted request.
absl::StatusOr<std::string> EncodeAsKnownLength() const;
@@ -210,7 +228,7 @@
// Give BinaryHttpResponse access to Encoding functionality.
friend class BinaryHttpResponse;
- uint64_t EncodedSize() const;
+ size_t EncodedSize() const;
// Appends the encoded fields and body to `writer`.
absl::Status Encode(quiche::QuicheDataWriter& writer) const;
@@ -225,6 +243,7 @@
// Deserialize
static absl::StatusOr<BinaryHttpResponse> Create(absl::string_view data);
+ size_t EncodedSize() const override;
absl::StatusOr<std::string> Serialize() const override;
// Informational status codes must be between 100 and 199 inclusive.
@@ -241,12 +260,19 @@
virtual std::string DebugString() const override;
- bool operator==(const BinaryHttpResponse& rhs) const {
+ // Returns true if the contents of the requests are equal, excluding padding.
+ bool IsPayloadEqual(const BinaryHttpResponse& rhs) const {
return informational_response_control_data_ ==
rhs.informational_response_control_data_ &&
status_code_ == rhs.status_code_ &&
- BinaryHttpMessage::operator==(rhs);
+ BinaryHttpMessage::IsPayloadEqual(rhs);
}
+
+ bool operator==(const BinaryHttpResponse& rhs) const {
+ return IsPayloadEqual(rhs) &&
+ num_padding_bytes() == rhs.num_padding_bytes();
+ }
+
bool operator!=(const BinaryHttpResponse& rhs) const {
return !(*this == rhs);
}
@@ -255,8 +281,6 @@
// Returns Binary Http known length request formatted response.
absl::StatusOr<std::string> EncodeAsKnownLength() const;
- uint64_t EncodedSize() const;
-
std::vector<InformationalResponse> informational_response_control_data_;
const uint16_t status_code_;
};
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
index 1ecd9f9..df8c036 100644
--- a/quiche/binary_http/binary_http_message_test.cc
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -707,4 +707,80 @@
EXPECT_NE(response, no_informational);
}
+MATCHER_P(HasEqPayload, value, "Payloads of messages are equivalent.") {
+ return arg.IsPayloadEqual(value);
+}
+
+template <typename T>
+void TestPadding(T& message) {
+ const auto data_so = message.Serialize();
+ ASSERT_TRUE(data_so.ok());
+ auto data = *data_so;
+ ASSERT_EQ(data.size(), message.EncodedSize());
+
+ message.set_num_padding_bytes(10);
+ const auto padded_data_so = message.Serialize();
+ ASSERT_TRUE(padded_data_so.ok());
+ const auto padded_data = *padded_data_so;
+ ASSERT_EQ(padded_data.size(), message.EncodedSize());
+
+ // Check padding size output.
+ ASSERT_EQ(data.size() + 10, padded_data.size());
+ // Check for valid null byte padding output
+ data.resize(data.size() + 10);
+ ASSERT_EQ(data, padded_data);
+
+ // Deserialize padded and not padded, and verify they are the same.
+ const auto deserialized_padded_message_so = T::Create(data);
+ ASSERT_TRUE(deserialized_padded_message_so.ok());
+ const auto deserialized_padded_message = *deserialized_padded_message_so;
+ ASSERT_EQ(deserialized_padded_message, message);
+ ASSERT_EQ(deserialized_padded_message.num_padding_bytes(), size_t(10));
+
+ // Invalid padding
+ data[data.size() - 1] = 'a';
+ const auto bad_so = T::Create(data);
+ ASSERT_FALSE(bad_so.ok());
+
+ // Check that padding does not impact equality.
+ data.resize(data.size() - 10);
+ const auto deserialized_message_so = T::Create(data);
+ ASSERT_TRUE(deserialized_message_so.ok());
+ const auto deserialized_message = *deserialized_message_so;
+ ASSERT_EQ(deserialized_message.num_padding_bytes(), size_t(0));
+ // Confirm that the message payloads are equal, but not fully equivalent due
+ // to padding.
+ ASSERT_THAT(deserialized_message, HasEqPayload(deserialized_padded_message));
+ ASSERT_NE(deserialized_message, deserialized_padded_message);
+}
+
+TEST(BinaryHttpRequest, Padding) {
+ /*
+ GET /hello.txt HTTP/1.1
+ User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3
+ Host: www.example.com
+ Accept-Language: en, mi
+ */
+ BinaryHttpRequest request({"GET", "https", "", "/hello.txt"});
+ request
+ .AddHeaderField({"User-Agent",
+ "curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3"})
+ ->AddHeaderField({"Host", "www.example.com"})
+ ->AddHeaderField({"Accept-Language", "en, mi"});
+ TestPadding(request);
+}
+
+TEST(BinaryHttpResponse, Padding) {
+ /*
+ HTTP/1.1 200 OK
+ Server: Apache
+
+ Hello, world!
+ */
+ BinaryHttpResponse response(200);
+ response.AddHeaderField({"Server", "Apache"});
+ response.set_body("Hello, world!\r\n");
+ TestPadding(response);
+}
+
} // namespace quiche