• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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