1 #ifndef QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_ 2 #define QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_ 3 4 #include <cstddef> 5 #include <cstdint> 6 #include <functional> 7 #include <memory> 8 #include <ostream> 9 #include <string> 10 #include <utility> 11 #include <vector> 12 13 #include "absl/container/flat_hash_map.h" 14 #include "absl/status/statusor.h" 15 #include "absl/strings/string_view.h" 16 #include "quiche/common/platform/api/quiche_export.h" 17 #include "quiche/common/quiche_data_writer.h" 18 19 namespace quiche { 20 21 // Supports encoding and decoding Binary Http messages. 22 // Currently limited to known-length messages. 23 // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html 24 class QUICHE_EXPORT BinaryHttpMessage { 25 public: 26 // Name value pair of either a header or trailer field. 27 struct QUICHE_EXPORT Field { 28 std::string name; 29 std::string value; 30 bool operator==(const BinaryHttpMessage::Field& rhs) const { 31 return name == rhs.name && value == rhs.value; 32 } 33 34 bool operator!=(const BinaryHttpMessage::Field& rhs) const { 35 return !(*this == rhs); 36 } 37 38 std::string DebugString() const; 39 }; 40 virtual ~BinaryHttpMessage() = default; 41 42 // TODO(bschneider): Switch to use existing Http2HeaderBlock 43 BinaryHttpMessage* AddHeaderField(Field header_field); 44 GetHeaderFields()45 const std::vector<Field>& GetHeaderFields() const { 46 return header_fields_.fields(); 47 } 48 set_body(std::string body)49 BinaryHttpMessage* set_body(std::string body) { 50 body_ = std::move(body); 51 return this; 52 } 53 swap_body(std::string & body)54 void swap_body(std::string& body) { body_.swap(body); } set_num_padding_bytes(size_t num_padding_bytes)55 void set_num_padding_bytes(size_t num_padding_bytes) { 56 num_padding_bytes_ = num_padding_bytes; 57 } num_padding_bytes()58 size_t num_padding_bytes() const { return num_padding_bytes_; } 59 body()60 absl::string_view body() const { return body_; } 61 62 // Returns the number of bytes `Serialize` will return, including padding. 63 virtual size_t EncodedSize() const = 0; 64 65 // Returns the Binary Http formatted message. 66 virtual absl::StatusOr<std::string> Serialize() const = 0; 67 // TODO(bschneider): Add AddTrailerField for chunked messages 68 // TODO(bschneider): Add SetBodyCallback() for chunked messages 69 70 virtual std::string DebugString() const; 71 72 protected: 73 class Fields { 74 public: 75 // Appends `field` to list of fields. Can contain duplicates. 76 void AddField(BinaryHttpMessage::Field field); 77 fields()78 const std::vector<BinaryHttpMessage::Field>& fields() const { 79 return fields_; 80 } 81 82 bool operator==(const BinaryHttpMessage::Fields& rhs) const { 83 return fields_ == rhs.fields_; 84 } 85 86 // Encode fields in insertion order. 87 // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-header-and-trailer-field-li 88 absl::Status Encode(quiche::QuicheDataWriter& writer) const; 89 90 // The number of returned by EncodedFieldsSize 91 // plus the number of bytes used in the varint holding that value. 92 size_t EncodedSize() const; 93 94 private: 95 // Number of bytes of just the set of fields. 96 size_t EncodedFieldsSize() const; 97 98 // Fields in insertion order. 99 std::vector<BinaryHttpMessage::Field> fields_; 100 }; 101 102 // Checks equality excluding padding. IsPayloadEqual(const BinaryHttpMessage & rhs)103 bool IsPayloadEqual(const BinaryHttpMessage& rhs) const { 104 // `has_host_` is derived from `header_fields_` so it doesn't need to be 105 // tested directly. 106 return body_ == rhs.body_ && header_fields_ == rhs.header_fields_; 107 } 108 109 absl::Status EncodeKnownLengthFieldsAndBody( 110 quiche::QuicheDataWriter& writer) const; 111 size_t EncodedKnownLengthFieldsAndBodySize() const; has_host()112 bool has_host() const { return has_host_; } 113 114 private: 115 std::string body_; 116 Fields header_fields_; 117 bool has_host_ = false; 118 size_t num_padding_bytes_ = 0; 119 }; 120 121 void QUICHE_EXPORT PrintTo(const BinaryHttpMessage::Field& msg, 122 std::ostream* os); 123 124 class QUICHE_EXPORT BinaryHttpRequest : public BinaryHttpMessage { 125 public: 126 // HTTP request must have method, scheme, and path fields. 127 // The `authority` field is required unless a `host` header field is added. 128 // If a `host` header field is added, `authority` is serialized as the empty 129 // string. 130 // Some examples are: 131 // scheme: HTTP 132 // authority: www.example.com 133 // path: /index.html 134 struct QUICHE_EXPORT ControlData { 135 std::string method; 136 std::string scheme; 137 std::string authority; 138 std::string path; 139 bool operator==(const BinaryHttpRequest::ControlData& rhs) const { 140 return method == rhs.method && scheme == rhs.scheme && 141 authority == rhs.authority && path == rhs.path; 142 } 143 bool operator!=(const BinaryHttpRequest::ControlData& rhs) const { 144 return !(*this == rhs); 145 } 146 }; BinaryHttpRequest(ControlData control_data)147 explicit BinaryHttpRequest(ControlData control_data) 148 : control_data_(std::move(control_data)) {} 149 150 // Deserialize 151 static absl::StatusOr<BinaryHttpRequest> Create(absl::string_view data); 152 153 size_t EncodedSize() const override; 154 absl::StatusOr<std::string> Serialize() const override; control_data()155 const ControlData& control_data() const { return control_data_; } 156 157 virtual std::string DebugString() const override; 158 159 // Returns true if the contents of the requests are equal, excluding padding. IsPayloadEqual(const BinaryHttpRequest & rhs)160 bool IsPayloadEqual(const BinaryHttpRequest& rhs) const { 161 return control_data_ == rhs.control_data_ && 162 BinaryHttpMessage::IsPayloadEqual(rhs); 163 } 164 165 bool operator==(const BinaryHttpRequest& rhs) const { 166 return IsPayloadEqual(rhs) && 167 num_padding_bytes() == rhs.num_padding_bytes(); 168 } 169 170 bool operator!=(const BinaryHttpRequest& rhs) const { 171 return !(*this == rhs); 172 } 173 174 private: 175 absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const; 176 177 size_t EncodedControlDataSize() const; 178 179 // Returns Binary Http known length request formatted request. 180 absl::StatusOr<std::string> EncodeAsKnownLength() const; 181 182 const ControlData control_data_; 183 }; 184 185 void QUICHE_EXPORT PrintTo(const BinaryHttpRequest& msg, std::ostream* os); 186 187 class QUICHE_EXPORT BinaryHttpResponse : public BinaryHttpMessage { 188 public: 189 // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-response-control-data 190 // A response can contain 0 to N informational responses. Each informational 191 // response contains a status code followed by a header field. Valid status 192 // codes are [100,199]. 193 class QUICHE_EXPORT InformationalResponse { 194 public: InformationalResponse(uint16_t status_code)195 explicit InformationalResponse(uint16_t status_code) 196 : status_code_(status_code) {} InformationalResponse(uint16_t status_code,const std::vector<BinaryHttpMessage::Field> & fields)197 InformationalResponse(uint16_t status_code, 198 const std::vector<BinaryHttpMessage::Field>& fields) 199 : status_code_(status_code) { 200 for (const BinaryHttpMessage::Field& field : fields) { 201 AddField(field.name, field.value); 202 } 203 } 204 205 bool operator==( 206 const BinaryHttpResponse::InformationalResponse& rhs) const { 207 return status_code_ == rhs.status_code_ && fields_ == rhs.fields_; 208 } 209 210 bool operator!=( 211 const BinaryHttpResponse::InformationalResponse& rhs) const { 212 return !(*this == rhs); 213 } 214 215 // Adds a field with the provided name, converted to lower case. 216 // Fields are in the order they are added. 217 void AddField(absl::string_view name, std::string value); 218 fields()219 const std::vector<BinaryHttpMessage::Field>& fields() const { 220 return fields_.fields(); 221 } 222 status_code()223 uint16_t status_code() const { return status_code_; } 224 225 std::string DebugString() const; 226 227 private: 228 // Give BinaryHttpResponse access to Encoding functionality. 229 friend class BinaryHttpResponse; 230 231 size_t EncodedSize() const; 232 233 // Appends the encoded fields and body to `writer`. 234 absl::Status Encode(quiche::QuicheDataWriter& writer) const; 235 236 const uint16_t status_code_; 237 BinaryHttpMessage::Fields fields_; 238 }; 239 BinaryHttpResponse(uint16_t status_code)240 explicit BinaryHttpResponse(uint16_t status_code) 241 : status_code_(status_code) {} 242 243 // Deserialize 244 static absl::StatusOr<BinaryHttpResponse> Create(absl::string_view data); 245 246 size_t EncodedSize() const override; 247 absl::StatusOr<std::string> Serialize() const override; 248 249 // Informational status codes must be between 100 and 199 inclusive. 250 absl::Status AddInformationalResponse(uint16_t status_code, 251 std::vector<Field> header_fields); 252 status_code()253 uint16_t status_code() const { return status_code_; } 254 255 // References in the returned `ResponseControlData` are invalidated on 256 // `BinaryHttpResponse` object mutations. informational_responses()257 const std::vector<InformationalResponse>& informational_responses() const { 258 return informational_response_control_data_; 259 } 260 261 virtual std::string DebugString() const override; 262 263 // Returns true if the contents of the requests are equal, excluding padding. IsPayloadEqual(const BinaryHttpResponse & rhs)264 bool IsPayloadEqual(const BinaryHttpResponse& rhs) const { 265 return informational_response_control_data_ == 266 rhs.informational_response_control_data_ && 267 status_code_ == rhs.status_code_ && 268 BinaryHttpMessage::IsPayloadEqual(rhs); 269 } 270 271 bool operator==(const BinaryHttpResponse& rhs) const { 272 return IsPayloadEqual(rhs) && 273 num_padding_bytes() == rhs.num_padding_bytes(); 274 } 275 276 bool operator!=(const BinaryHttpResponse& rhs) const { 277 return !(*this == rhs); 278 } 279 280 private: 281 // Returns Binary Http known length request formatted response. 282 absl::StatusOr<std::string> EncodeAsKnownLength() const; 283 284 std::vector<InformationalResponse> informational_response_control_data_; 285 const uint16_t status_code_; 286 }; 287 288 void QUICHE_EXPORT PrintTo(const BinaryHttpResponse& msg, std::ostream* os); 289 } // namespace quiche 290 291 #endif // QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_ 292