1 // Copyright 2022 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef QUICHE_BALSA_BALSA_FRAME_H_ 6 #define QUICHE_BALSA_BALSA_FRAME_H_ 7 8 #include <cstddef> 9 #include <cstdint> 10 #include <utility> 11 #include <vector> 12 13 #include "absl/container/flat_hash_map.h" 14 #include "quiche/balsa/balsa_enums.h" 15 #include "quiche/balsa/balsa_headers.h" 16 #include "quiche/balsa/balsa_visitor_interface.h" 17 #include "quiche/balsa/framer_interface.h" 18 #include "quiche/balsa/http_validation_policy.h" 19 #include "quiche/balsa/noop_balsa_visitor.h" 20 #include "quiche/common/platform/api/quiche_bug_tracker.h" 21 #include "quiche/common/platform/api/quiche_export.h" 22 #include "quiche/common/platform/api/quiche_flag_utils.h" 23 24 namespace quiche { 25 26 namespace test { 27 class BalsaFrameTestPeer; 28 } // namespace test 29 30 // BalsaFrame is a lightweight HTTP framer. 31 class QUICHE_EXPORT BalsaFrame : public FramerInterface { 32 public: 33 typedef std::vector<std::pair<size_t, size_t> > Lines; 34 35 typedef BalsaHeaders::HeaderLineDescription HeaderLineDescription; 36 typedef BalsaHeaders::HeaderLines HeaderLines; 37 typedef BalsaHeaders::HeaderTokenList HeaderTokenList; 38 39 enum class InvalidCharsLevel { kOff, kWarning, kError }; 40 41 static constexpr int32_t kValidTerm1 = '\n' << 16 | '\r' << 8 | '\n'; 42 static constexpr int32_t kValidTerm1Mask = 0xFF << 16 | 0xFF << 8 | 0xFF; 43 static constexpr int32_t kValidTerm2 = '\n' << 8 | '\n'; 44 static constexpr int32_t kValidTerm2Mask = 0xFF << 8 | 0xFF; BalsaFrame()45 BalsaFrame() 46 : last_char_was_slash_r_(false), 47 saw_non_newline_char_(false), 48 start_was_space_(true), 49 chunk_length_character_extracted_(false), 50 is_request_(true), 51 allow_reading_until_close_for_request_(false), 52 request_was_head_(false), 53 max_header_length_(16 * 1024), 54 visitor_(&do_nothing_visitor_), 55 chunk_length_remaining_(0), 56 content_length_remaining_(0), 57 last_slash_n_loc_(nullptr), 58 last_recorded_slash_n_loc_(nullptr), 59 last_slash_n_idx_(0), 60 term_chars_(0), 61 parse_state_(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE), 62 last_error_(BalsaFrameEnums::BALSA_NO_ERROR), 63 continue_headers_(nullptr), 64 headers_(nullptr), 65 start_of_trailer_line_(0), 66 trailer_length_(0), 67 trailer_(nullptr), 68 invalid_chars_level_(InvalidCharsLevel::kOff), 69 use_interim_headers_callback_(false) {} 70 ~BalsaFrame()71 ~BalsaFrame() override {} 72 73 // Reset reinitializes all the member variables of the framer and clears the 74 // attached header object (but doesn't change the pointer value headers_). 75 void Reset(); 76 77 // The method set_balsa_headers clears the headers provided and attaches them 78 // to the framer. This is a required step before the framer will process any 79 // input message data. 80 // To detach the header object from the framer, use 81 // set_balsa_headers(nullptr). set_balsa_headers(BalsaHeaders * headers)82 void set_balsa_headers(BalsaHeaders* headers) { 83 if (headers_ != headers) { 84 headers_ = headers; 85 } 86 if (headers_ != nullptr) { 87 // Clear the headers if they are non-null, even if the new headers are 88 // the same as the old. 89 headers_->Clear(); 90 } 91 } 92 93 // If set to non-null, allow 100 Continue headers before the main headers. 94 // This method is a no-op if set_use_interim_headers_callback(true) is called. set_continue_headers(BalsaHeaders * continue_headers)95 void set_continue_headers(BalsaHeaders* continue_headers) { 96 if (continue_headers_ != continue_headers) { 97 continue_headers_ = continue_headers; 98 } 99 if (continue_headers_ != nullptr) { 100 // Clear the headers if they are non-null, even if the new headers are 101 // the same as the old. 102 continue_headers_->Clear(); 103 } 104 } 105 106 // The method set_balsa_trailer() clears `trailer` and attaches it to the 107 // framer. This is a required step before the framer will process any input 108 // message data. To detach the trailer object from the framer, use 109 // set_balsa_trailer(nullptr). set_balsa_trailer(BalsaHeaders * trailer)110 void set_balsa_trailer(BalsaHeaders* trailer) { 111 if (trailer != nullptr && is_request()) { 112 QUICHE_CODE_COUNT(balsa_trailer_in_request); 113 } 114 115 if (trailer_ != trailer) { 116 trailer_ = trailer; 117 } 118 if (trailer_ != nullptr) { 119 // Clear the trailer if it is non-null, even if the new trailer is 120 // the same as the old. 121 trailer_->Clear(); 122 } 123 } 124 set_balsa_visitor(BalsaVisitorInterface * visitor)125 void set_balsa_visitor(BalsaVisitorInterface* visitor) { 126 visitor_ = visitor; 127 if (visitor_ == nullptr) { 128 visitor_ = &do_nothing_visitor_; 129 } 130 } 131 set_invalid_chars_level(InvalidCharsLevel v)132 void set_invalid_chars_level(InvalidCharsLevel v) { 133 invalid_chars_level_ = v; 134 } 135 track_invalid_chars()136 bool track_invalid_chars() { 137 return invalid_chars_level_ != InvalidCharsLevel::kOff; 138 } 139 invalid_chars_error_enabled()140 bool invalid_chars_error_enabled() { 141 return invalid_chars_level_ == InvalidCharsLevel::kError; 142 } 143 set_http_validation_policy(const quiche::HttpValidationPolicy & policy)144 void set_http_validation_policy(const quiche::HttpValidationPolicy& policy) { 145 http_validation_policy_ = policy; 146 } http_validation_policy()147 const quiche::HttpValidationPolicy& http_validation_policy() const { 148 return http_validation_policy_; 149 } 150 set_is_request(bool is_request)151 void set_is_request(bool is_request) { is_request_ = is_request; } 152 is_request()153 bool is_request() const { return is_request_; } 154 set_request_was_head(bool request_was_head)155 void set_request_was_head(bool request_was_head) { 156 request_was_head_ = request_was_head; 157 } 158 set_max_header_length(size_t max_header_length)159 void set_max_header_length(size_t max_header_length) { 160 max_header_length_ = max_header_length; 161 } 162 max_header_length()163 size_t max_header_length() const { return max_header_length_; } 164 MessageFullyRead()165 bool MessageFullyRead() const { 166 return parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ; 167 } 168 ParseState()169 BalsaFrameEnums::ParseState ParseState() const { return parse_state_; } 170 Error()171 bool Error() const { return parse_state_ == BalsaFrameEnums::ERROR; } 172 ErrorCode()173 BalsaFrameEnums::ErrorCode ErrorCode() const { return last_error_; } 174 get_invalid_chars()175 const absl::flat_hash_map<char, int>& get_invalid_chars() const { 176 return invalid_chars_; 177 } 178 headers()179 const BalsaHeaders* headers() const { return headers_; } mutable_headers()180 BalsaHeaders* mutable_headers() { return headers_; } 181 trailer()182 const BalsaHeaders* trailer() const { return trailer_; } mutable_trailer()183 BalsaHeaders* mutable_trailer() { return trailer_; } 184 185 size_t BytesSafeToSplice() const; 186 void BytesSpliced(size_t bytes_spliced); 187 188 size_t ProcessInput(const char* input, size_t size) override; 189 set_allow_reading_until_close_for_request(bool set)190 void set_allow_reading_until_close_for_request(bool set) { 191 allow_reading_until_close_for_request_ = set; 192 } 193 194 // For websockets and possibly other uses, we suspend the usual expectations 195 // about when a message has a body and how long it should be. AllowArbitraryBody()196 void AllowArbitraryBody() { 197 parse_state_ = BalsaFrameEnums::READING_UNTIL_CLOSE; 198 } 199 200 // If enabled, calls BalsaVisitorInterface::OnInterimHeaders() when parsing 201 // interim headers. For 100 Continue, this callback will be invoked instead of 202 // ContinueHeaderDone(), even when set_continue_headers() is called. set_use_interim_headers_callback(bool set)203 void set_use_interim_headers_callback(bool set) { 204 use_interim_headers_callback_ = set; 205 } 206 207 protected: 208 inline BalsaHeadersEnums::ContentLengthStatus ProcessContentLengthLine( 209 size_t line_idx, size_t* length); 210 211 inline void ProcessTransferEncodingLine(size_t line_idx); 212 213 void ProcessFirstLine(const char* begin, const char* end); 214 215 void CleanUpKeyValueWhitespace(const char* stream_begin, 216 const char* line_begin, const char* current, 217 const char* line_end, 218 HeaderLineDescription* current_header_line); 219 220 void ProcessHeaderLines(const Lines& lines, bool is_trailer, 221 BalsaHeaders* headers); 222 223 // Returns true if there are invalid characters, false otherwise. 224 // Will also update counts per invalid character in invalid_chars_. 225 bool CheckHeaderLinesForInvalidChars(const Lines& lines, 226 const BalsaHeaders* headers); 227 228 inline size_t ProcessHeaders(const char* message_start, 229 size_t message_length); 230 231 void AssignParseStateAfterHeadersHaveBeenParsed(); 232 LineFramingFound(char current_char)233 inline bool LineFramingFound(char current_char) { 234 return current_char == '\n'; 235 } 236 237 // Return header framing pattern. Non-zero return value indicates found, 238 // which has two possible outcomes: kValidTerm1, which means \n\r\n 239 // or kValidTerm2, which means \n\n. Zero return value means not found. HeaderFramingFound(char current_char)240 inline int32_t HeaderFramingFound(char current_char) { 241 // Note that the 'if (current_char == '\n' ...)' test exists to ensure that 242 // the HeaderFramingMayBeFound test works properly. In benchmarking done on 243 // 2/13/2008, the 'if' actually speeds up performance of the function 244 // anyway.. 245 if (current_char == '\n' || current_char == '\r') { 246 term_chars_ <<= 8; 247 // This is necessary IFF architecture has > 8 bit char. Alas, I'm 248 // paranoid. 249 term_chars_ |= current_char & 0xFF; 250 251 if ((term_chars_ & kValidTerm1Mask) == kValidTerm1) { 252 term_chars_ = 0; 253 return kValidTerm1; 254 } 255 if ((term_chars_ & kValidTerm2Mask) == kValidTerm2) { 256 term_chars_ = 0; 257 return kValidTerm2; 258 } 259 } else { 260 term_chars_ = 0; 261 } 262 return 0; 263 } 264 HeaderFramingMayBeFound()265 inline bool HeaderFramingMayBeFound() const { return term_chars_ != 0; } 266 267 private: 268 friend class test::BalsaFrameTestPeer; 269 270 // Calls HandleError() and returns false on error. 271 bool FindColonsAndParseIntoKeyValue(const Lines& lines, bool is_trailer, 272 BalsaHeaders* headers); 273 274 void HandleError(BalsaFrameEnums::ErrorCode error_code); 275 void HandleWarning(BalsaFrameEnums::ErrorCode error_code); 276 277 bool last_char_was_slash_r_; 278 bool saw_non_newline_char_; 279 bool start_was_space_; 280 bool chunk_length_character_extracted_; 281 bool is_request_; // This is not reset in Reset() 282 // Generally, requests are not allowed to frame with connection: close. For 283 // protocols which do their own protocol-specific chunking, such as streamed 284 // stubby, we allow connection close semantics for requests. 285 bool allow_reading_until_close_for_request_; 286 bool request_was_head_; // This is not reset in Reset() 287 size_t max_header_length_; // This is not reset in Reset() 288 BalsaVisitorInterface* visitor_; 289 size_t chunk_length_remaining_; 290 size_t content_length_remaining_; 291 const char* last_slash_n_loc_; 292 const char* last_recorded_slash_n_loc_; 293 size_t last_slash_n_idx_; 294 uint32_t term_chars_; 295 BalsaFrameEnums::ParseState parse_state_; 296 BalsaFrameEnums::ErrorCode last_error_; 297 absl::flat_hash_map<char, int> invalid_chars_; 298 299 Lines lines_; 300 301 BalsaHeaders* continue_headers_; // This is not reset to nullptr in Reset(). 302 BalsaHeaders* headers_; // This is not reset to nullptr in Reset(). 303 NoOpBalsaVisitor do_nothing_visitor_; 304 305 Lines trailer_lines_; 306 size_t start_of_trailer_line_; 307 size_t trailer_length_; 308 BalsaHeaders* trailer_; // Does not own and is not reset to nullptr 309 // in Reset(). 310 InvalidCharsLevel invalid_chars_level_; // This is not reset in Reset(). 311 312 quiche::HttpValidationPolicy http_validation_policy_; 313 314 // This is not reset in Reset(). 315 // TODO(b/68801833): Default-enable and then deprecate this field, along with 316 // set_continue_headers(). 317 bool use_interim_headers_callback_; 318 }; 319 320 } // namespace quiche 321 322 #endif // QUICHE_BALSA_BALSA_FRAME_H_ 323