| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/tools/balsa/balsa_frame.h" |
| |
| // Visual C++ defines _M_IX86_FP as 2 if the /arch:SSE2 compiler option is |
| // specified. |
| #if !defined(__SSE2__) && _M_IX86_FP == 2 |
| #define __SSE2__ 1 |
| #endif |
| |
| #include <assert.h> |
| #if __SSE2__ |
| #include <emmintrin.h> |
| #endif // __SSE2__ |
| |
| #include <limits> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/port.h" |
| #include "base/strings/string_piece.h" |
| #include "net/tools/balsa/balsa_enums.h" |
| #include "net/tools/balsa/balsa_headers.h" |
| #include "net/tools/balsa/balsa_visitor_interface.h" |
| #include "net/tools/balsa/buffer_interface.h" |
| #include "net/tools/balsa/simple_buffer.h" |
| #include "net/tools/balsa/split.h" |
| #include "net/tools/balsa/string_piece_utils.h" |
| |
| #if defined(COMPILER_MSVC) |
| #include <intrin.h> |
| #include <string.h> |
| |
| #pragma intrinsic(_BitScanForward) |
| |
| static int ffs(int i) { |
| unsigned long index; |
| return _BitScanForward(&index, i) ? index + 1 : 0; |
| } |
| |
| #define strncasecmp _strnicmp |
| #else |
| #include <strings.h> |
| #endif |
| |
| namespace net { |
| |
| // Constants holding some header names for headers which can affect the way the |
| // HTTP message is framed, and so must be processed specially: |
| static const char kContentLength[] = "content-length"; |
| static const size_t kContentLengthSize = sizeof(kContentLength) - 1; |
| static const char kTransferEncoding[] = "transfer-encoding"; |
| static const size_t kTransferEncodingSize = sizeof(kTransferEncoding) - 1; |
| |
| BalsaFrame::BalsaFrame() |
| : last_char_was_slash_r_(false), |
| saw_non_newline_char_(false), |
| start_was_space_(true), |
| chunk_length_character_extracted_(false), |
| is_request_(true), |
| request_was_head_(false), |
| max_header_length_(16 * 1024), |
| max_request_uri_length_(2048), |
| visitor_(&do_nothing_visitor_), |
| chunk_length_remaining_(0), |
| content_length_remaining_(0), |
| last_slash_n_loc_(NULL), |
| last_recorded_slash_n_loc_(NULL), |
| last_slash_n_idx_(0), |
| term_chars_(0), |
| parse_state_(BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE), |
| last_error_(BalsaFrameEnums::NO_ERROR), |
| headers_(NULL) { |
| } |
| |
| BalsaFrame::~BalsaFrame() {} |
| |
| void BalsaFrame::Reset() { |
| last_char_was_slash_r_ = false; |
| saw_non_newline_char_ = false; |
| start_was_space_ = true; |
| chunk_length_character_extracted_ = false; |
| // is_request_ = true; // not reset between messages. |
| // request_was_head_ = false; // not reset between messages. |
| // max_header_length_ = 4096; // not reset between messages. |
| // max_request_uri_length_ = 2048; // not reset between messages. |
| // visitor_ = &do_nothing_visitor_; // not reset between messages. |
| chunk_length_remaining_ = 0; |
| content_length_remaining_ = 0; |
| last_slash_n_loc_ = NULL; |
| last_recorded_slash_n_loc_ = NULL; |
| last_slash_n_idx_ = 0; |
| term_chars_ = 0; |
| parse_state_ = BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE; |
| last_error_ = BalsaFrameEnums::NO_ERROR; |
| lines_.clear(); |
| if (headers_ != NULL) { |
| headers_->Clear(); |
| } |
| } |
| |
| const char* BalsaFrameEnums::ParseStateToString( |
| BalsaFrameEnums::ParseState error_code) { |
| switch (error_code) { |
| case PARSE_ERROR: |
| return "PARSE_ERROR"; |
| case READING_HEADER_AND_FIRSTLINE: |
| return "READING_HEADER_AND_FIRSTLINE"; |
| case READING_CHUNK_LENGTH: |
| return "READING_CHUNK_LENGTH"; |
| case READING_CHUNK_EXTENSION: |
| return "READING_CHUNK_EXTENSION"; |
| case READING_CHUNK_DATA: |
| return "READING_CHUNK_DATA"; |
| case READING_CHUNK_TERM: |
| return "READING_CHUNK_TERM"; |
| case READING_LAST_CHUNK_TERM: |
| return "READING_LAST_CHUNK_TERM"; |
| case READING_TRAILER: |
| return "READING_TRAILER"; |
| case READING_UNTIL_CLOSE: |
| return "READING_UNTIL_CLOSE"; |
| case READING_CONTENT: |
| return "READING_CONTENT"; |
| case MESSAGE_FULLY_READ: |
| return "MESSAGE_FULLY_READ"; |
| case NUM_STATES: |
| return "UNKNOWN_STATE"; |
| } |
| return "UNKNOWN_STATE"; |
| } |
| |
| const char* BalsaFrameEnums::ErrorCodeToString( |
| BalsaFrameEnums::ErrorCode error_code) { |
| switch (error_code) { |
| case NO_ERROR: |
| return "NO_ERROR"; |
| case NO_STATUS_LINE_IN_RESPONSE: |
| return "NO_STATUS_LINE_IN_RESPONSE"; |
| case NO_REQUEST_LINE_IN_REQUEST: |
| return "NO_REQUEST_LINE_IN_REQUEST"; |
| case FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION: |
| return "FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION"; |
| case FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD: |
| return "FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD"; |
| case FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE: |
| return "FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE"; |
| case FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI: |
| return "FAILED_TO_FIND_WS_AFTER_REQUEST_REQUEST_URI"; |
| case FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE: |
| return "FAILED_TO_FIND_NL_AFTER_RESPONSE_REASON_PHRASE"; |
| case FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION: |
| return "FAILED_TO_FIND_NL_AFTER_REQUEST_HTTP_VERSION"; |
| case FAILED_CONVERTING_STATUS_CODE_TO_INT: |
| return "FAILED_CONVERTING_STATUS_CODE_TO_INT"; |
| case REQUEST_URI_TOO_LONG: |
| return "REQUEST_URI_TOO_LONG"; |
| case HEADERS_TOO_LONG: |
| return "HEADERS_TOO_LONG"; |
| case UNPARSABLE_CONTENT_LENGTH: |
| return "UNPARSABLE_CONTENT_LENGTH"; |
| case MAYBE_BODY_BUT_NO_CONTENT_LENGTH: |
| return "MAYBE_BODY_BUT_NO_CONTENT_LENGTH"; |
| case REQUIRED_BODY_BUT_NO_CONTENT_LENGTH: |
| return "REQUIRED_BODY_BUT_NO_CONTENT_LENGTH"; |
| case HEADER_MISSING_COLON: |
| return "HEADER_MISSING_COLON"; |
| case INVALID_CHUNK_LENGTH: |
| return "INVALID_CHUNK_LENGTH"; |
| case CHUNK_LENGTH_OVERFLOW: |
| return "CHUNK_LENGTH_OVERFLOW"; |
| case CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO: |
| return "CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO"; |
| case CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT: |
| return "CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT"; |
| case MULTIPLE_CONTENT_LENGTH_KEYS: |
| return "MULTIPLE_CONTENT_LENGTH_KEYS"; |
| case MULTIPLE_TRANSFER_ENCODING_KEYS: |
| return "MULTIPLE_TRANSFER_ENCODING_KEYS"; |
| case UNKNOWN_TRANSFER_ENCODING: |
| return "UNKNOWN_TRANSFER_ENCODING"; |
| case INVALID_HEADER_FORMAT: |
| return "INVALID_HEADER_FORMAT"; |
| case INTERNAL_LOGIC_ERROR: |
| return "INTERNAL_LOGIC_ERROR"; |
| case NUM_ERROR_CODES: |
| return "UNKNOWN_ERROR"; |
| } |
| return "UNKNOWN_ERROR"; |
| } |
| |
| // Summary: |
| // Parses the first line of either a request or response. |
| // Note that in the case of a detected warning, error_code will be set |
| // but the function will not return false. |
| // Exactly zero or one warning or error (but not both) may be detected |
| // by this function. |
| // Note that this function will not write the data of the first-line |
| // into the header's buffer (that should already have been done elsewhere). |
| // |
| // Pre-conditions: |
| // begin != end |
| // *begin should be a character which is > ' '. This implies that there |
| // is at least one non-whitespace characters between [begin, end). |
| // headers is a valid pointer to a BalsaHeaders class. |
| // error_code is a valid pointer to a BalsaFrameEnums::ErrorCode value. |
| // Entire first line must exist between [begin, end) |
| // Exactly zero or one newlines -may- exist between [begin, end) |
| // [begin, end) should exist in the header's buffer. |
| // |
| // Side-effects: |
| // headers will be modified |
| // error_code may be modified if either a warning or error is detected |
| // |
| // Returns: |
| // True if no error (as opposed to warning) is detected. |
| // False if an error (as opposed to warning) is detected. |
| |
| // |
| // If there is indeed non-whitespace in the line, then the following |
| // will take care of this for you: |
| // while (*begin <= ' ') ++begin; |
| // ProcessFirstLine(begin, end, is_request, &headers, &error_code); |
| // |
| bool ParseHTTPFirstLine(const char* begin, |
| const char* end, |
| bool is_request, |
| size_t max_request_uri_length, |
| BalsaHeaders* headers, |
| BalsaFrameEnums::ErrorCode* error_code) { |
| const char* current = begin; |
| // HTTP firstlines all have the following structure: |
| // LWS NONWS LWS NONWS LWS NONWS NOTCRLF CRLF |
| // [\t \r\n]+ [^\t ]+ [\t ]+ [^\t ]+ [\t ]+ [^\t ]+ [^\r\n]+ "\r\n" |
| // ws1 nws1 ws2 nws2 ws3 nws3 ws4 |
| // | [-------) [-------) [----------------) |
| // REQ: method request_uri version |
| // RESP: version statuscode reason |
| // |
| // The first NONWS->LWS component we'll call firstline_a. |
| // The second firstline_b, and the third firstline_c. |
| // |
| // firstline_a goes from nws1 to (but not including) ws2 |
| // firstline_b goes from nws2 to (but not including) ws3 |
| // firstline_c goes from nws3 to (but not including) ws4 |
| // |
| // In the code: |
| // ws1 == whitespace_1_idx_ |
| // nws1 == non_whitespace_1_idx_ |
| // ws2 == whitespace_2_idx_ |
| // nws2 == non_whitespace_2_idx_ |
| // ws3 == whitespace_3_idx_ |
| // nws3 == non_whitespace_3_idx_ |
| // ws4 == whitespace_4_idx_ |
| |
| // Kill all whitespace (including '\r\n') at the end of the line. |
| --end; |
| if (*end != '\n') { |
| *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR; |
| LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n" |
| << headers->OriginalHeadersForDebugging(); |
| return false; |
| } |
| while (begin < end && *end <= ' ') { |
| --end; |
| } |
| DCHECK(*end != '\n'); |
| if (*end == '\n') { |
| *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR; |
| LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n" |
| << headers->OriginalHeadersForDebugging(); |
| return false; |
| } |
| ++end; |
| |
| // The two following statements should not be possible. |
| if (end == begin) { |
| *error_code = BalsaFrameEnums::INTERNAL_LOGIC_ERROR; |
| LOG(DFATAL) << "INTERNAL_LOGIC_ERROR Headers: \n" |
| << headers->OriginalHeadersForDebugging(); |
| return false; |
| } |
| |
| // whitespace_1_idx_ |
| headers->whitespace_1_idx_ = current - begin; |
| // This loop is commented out as it is never used in current code. This is |
| // true only because we don't begin parsing the headers at all until we've |
| // encountered a non whitespace character at the beginning of the stream, at |
| // which point we begin our demarcation of header-start. If we did -not- do |
| // this (for instance, only looked for [\r\n] instead of (< ' ')), this loop |
| // would be necessary for the proper functioning of this parsing. |
| // This is left here as this function may (in the future) be refactored out |
| // of the BalsaFrame class so that it may be shared between code in |
| // BalsaFrame and BalsaHeaders (where it would be used in some variant of the |
| // set_first_line() function (at which point it would be necessary). |
| #if 0 |
| while (*current <= ' ') { |
| ++current; |
| } |
| #endif |
| // non_whitespace_1_idx_ |
| headers->non_whitespace_1_idx_ = current - begin; |
| do { |
| // The first time through, we're guaranteed that the current character |
| // won't be a whitespace (else the loop above wouldn't have terminated). |
| // That implies that we're guaranteed to get at least one non-whitespace |
| // character if we get into this loop at all. |
| ++current; |
| if (current == end) { |
| headers->whitespace_2_idx_ = current - begin; |
| headers->non_whitespace_2_idx_ = current - begin; |
| headers->whitespace_3_idx_ = current - begin; |
| headers->non_whitespace_3_idx_ = current - begin; |
| headers->whitespace_4_idx_ = current - begin; |
| // FAILED_TO_FIND_WS_AFTER_REQUEST_METHOD for request |
| // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION for response |
| *error_code = |
| static_cast<BalsaFrameEnums::ErrorCode>( |
| BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION + |
| is_request); |
| if (!is_request) { // FAILED_TO_FIND_WS_AFTER_RESPONSE_VERSION |
| return false; |
| } |
| goto output_exhausted; |
| } |
| } while (*current > ' '); |
| // whitespace_2_idx_ |
| headers->whitespace_2_idx_ = current - begin; |
| do { |
| ++current; |
| // Note that due to the loop which consumes all of the whitespace |
| // at the end of the line, current can never == end while in this function. |
| } while (*current <= ' '); |
| // non_whitespace_2_idx_ |
| headers->non_whitespace_2_idx_ = current - begin; |
| do { |
| ++current; |
| if (current == end) { |
| headers->whitespace_3_idx_ = current - begin; |
| headers->non_whitespace_3_idx_ = current - begin; |
| headers->whitespace_4_idx_ = current - begin; |
| // FAILED_TO_FIND_START_OF_REQUEST_REQUEST_URI for request |
| // FAILED_TO_FIND_START_OF_RESPONSE_STATUSCODE for response |
| *error_code = |
| static_cast<BalsaFrameEnums::ErrorCode>( |
| BalsaFrameEnums::FAILED_TO_FIND_WS_AFTER_RESPONSE_STATUSCODE |
| + is_request); |
| goto output_exhausted; |
| } |
| } while (*current > ' '); |
| // whitespace_3_idx_ |
| headers->whitespace_3_idx_ = current - begin; |
| do { |
| ++current; |
| // Note that due to the loop which consumes all of the whitespace |
| // at the end of the line, current can never == end while in this function. |
| } while (*current <= ' '); |
| // non_whitespace_3_idx_ |
| headers->non_whitespace_3_idx_ = current - begin; |
| headers->whitespace_4_idx_ = end - begin; |
| |
| output_exhausted: |
| // Note that we don't fail the parse immediately when parsing of the |
| // firstline fails. Depending on the protocol type, we may want to accept |
| // a firstline with only one or two elements, e.g., for HTTP/0.9: |
| // GET\r\n |
| // or |
| // GET /\r\n |
| // should be parsed without issue (though the visitor should know that |
| // parsing the entire line was not exactly as it should be). |
| // |
| // Eventually, these errors may be removed alltogether, as the visitor can |
| // detect them on its own by examining the size of the various fields. |
| // headers->set_first_line(non_whitespace_1_idx_, current); |
| |
| if (is_request) { |
| if ((headers->whitespace_3_idx_ - headers->non_whitespace_2_idx_) > |
| max_request_uri_length) { |
| // For requests, we need at least the method. We could assume that a |
| // blank URI means "/". If version isn't stated, it should be assumed |
| // to be HTTP/0.9 by the visitor. |
| *error_code = BalsaFrameEnums::REQUEST_URI_TOO_LONG; |
| return false; |
| } |
| } else { |
| headers->parsed_response_code_ = 0; |
| { |
| const char* parsed_response_code_current = |
| begin + headers->non_whitespace_2_idx_; |
| const char* parsed_response_code_end = begin + headers->whitespace_3_idx_; |
| const size_t kMaxDiv10 = std::numeric_limits<size_t>::max() / 10; |
| |
| // Convert a string of [0-9]* into an int. |
| // Note that this allows for the conversion of response codes which |
| // are outside the bounds of normal HTTP response codes (no checking |
| // is done to ensure that these are valid-- they're merely parsed)! |
| while (parsed_response_code_current < parsed_response_code_end) { |
| if (*parsed_response_code_current < '0' || |
| *parsed_response_code_current > '9') { |
| *error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT; |
| return false; |
| } |
| size_t status_code_x_10 = headers->parsed_response_code_ * 10; |
| uint8 c = *parsed_response_code_current - '0'; |
| if ((headers->parsed_response_code_ > kMaxDiv10) || |
| (std::numeric_limits<size_t>::max() - status_code_x_10) < c) { |
| // overflow. |
| *error_code = BalsaFrameEnums::FAILED_CONVERTING_STATUS_CODE_TO_INT; |
| return false; |
| } |
| headers->parsed_response_code_ = status_code_x_10 + c; |
| ++parsed_response_code_current; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // begin - beginning of the firstline |
| // end - end of the firstline |
| // |
| // A precondition for this function is that there is non-whitespace between |
| // [begin, end). If this precondition is not met, the function will not perform |
| // as expected (and bad things may happen, and it will eat your first, second, |
| // and third unborn children!). |
| // |
| // Another precondition for this function is that [begin, end) includes |
| // at most one newline, which must be at the end of the line. |
| void BalsaFrame::ProcessFirstLine(const char* begin, const char* end) { |
| BalsaFrameEnums::ErrorCode previous_error = last_error_; |
| if (!ParseHTTPFirstLine(begin, |
| end, |
| is_request_, |
| max_request_uri_length_, |
| headers_, |
| &last_error_)) { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleHeaderError(this); |
| return; |
| } |
| if (previous_error != last_error_) { |
| visitor_->HandleHeaderWarning(this); |
| } |
| |
| if (is_request_) { |
| size_t version_length = |
| headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_; |
| visitor_->ProcessRequestFirstLine( |
| begin + headers_->non_whitespace_1_idx_, |
| headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_, |
| begin + headers_->non_whitespace_1_idx_, |
| headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_, |
| begin + headers_->non_whitespace_2_idx_, |
| headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_, |
| begin + headers_->non_whitespace_3_idx_, |
| version_length); |
| if (version_length == 0) |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| } else { |
| visitor_->ProcessResponseFirstLine( |
| begin + headers_->non_whitespace_1_idx_, |
| headers_->whitespace_4_idx_ - headers_->non_whitespace_1_idx_, |
| begin + headers_->non_whitespace_1_idx_, |
| headers_->whitespace_2_idx_ - headers_->non_whitespace_1_idx_, |
| begin + headers_->non_whitespace_2_idx_, |
| headers_->whitespace_3_idx_ - headers_->non_whitespace_2_idx_, |
| begin + headers_->non_whitespace_3_idx_, |
| headers_->whitespace_4_idx_ - headers_->non_whitespace_3_idx_); |
| } |
| } |
| |
| // 'stream_begin' points to the first character of the headers buffer. |
| // 'line_begin' points to the first character of the line. |
| // 'current' points to a char which is ':'. |
| // 'line_end' points to the position of '\n' + 1. |
| // 'line_begin' points to the position of first character of line. |
| void BalsaFrame::CleanUpKeyValueWhitespace( |
| const char* stream_begin, |
| const char* line_begin, |
| const char* current, |
| const char* line_end, |
| HeaderLineDescription* current_header_line) { |
| const char* colon_loc = current; |
| DCHECK_LT(colon_loc, line_end); |
| DCHECK_EQ(':', *colon_loc); |
| DCHECK_EQ(':', *current); |
| DCHECK_GE(' ', *line_end) |
| << "\"" << std::string(line_begin, line_end) << "\""; |
| |
| // TODO(fenix): Investigate whether or not the bounds tests in the |
| // while loops here are redundant, and if so, remove them. |
| --current; |
| while (current > line_begin && *current <= ' ') --current; |
| current += (current != colon_loc); |
| current_header_line->key_end_idx = current - stream_begin; |
| |
| current = colon_loc; |
| DCHECK_EQ(':', *current); |
| ++current; |
| while (current < line_end && *current <= ' ') ++current; |
| current_header_line->value_begin_idx = current - stream_begin; |
| |
| DCHECK_GE(current_header_line->key_end_idx, |
| current_header_line->first_char_idx); |
| DCHECK_GE(current_header_line->value_begin_idx, |
| current_header_line->key_end_idx); |
| DCHECK_GE(current_header_line->last_char_idx, |
| current_header_line->value_begin_idx); |
| } |
| |
| inline void BalsaFrame::FindColonsAndParseIntoKeyValue() { |
| DCHECK(!lines_.empty()); |
| const char* stream_begin = headers_->OriginalHeaderStreamBegin(); |
| // The last line is always just a newline (and is uninteresting). |
| const Lines::size_type lines_size_m1 = lines_.size() - 1; |
| #if __SSE2__ |
| const __m128i colons = _mm_set1_epi8(':'); |
| const char* header_lines_end_m16 = headers_->OriginalHeaderStreamEnd() - 16; |
| #endif // __SSE2__ |
| const char* current = stream_begin + lines_[1].first; |
| // This code is a bit more subtle than it may appear at first glance. |
| // This code looks for a colon in the current line... but it also looks |
| // beyond the current line. If there is no colon in the current line, then |
| // for each subsequent line (until the colon which -has- been found is |
| // associated with a line), no searching for a colon will be performed. In |
| // this way, we minimize the amount of bytes we have scanned for a colon. |
| for (Lines::size_type i = 1; i < lines_size_m1;) { |
| const char* line_begin = stream_begin + lines_[i].first; |
| |
| // Here we handle possible continuations. Note that we do not replace |
| // the '\n' in the line before a continuation (at least, as of now), |
| // which implies that any code which looks for a value must deal with |
| // "\r\n", etc -within- the line (and not just at the end of it). |
| for (++i; i < lines_size_m1; ++i) { |
| const char c = *(stream_begin + lines_[i].first); |
| if (c > ' ') { |
| // Not a continuation, so stop. Note that if the 'original' i = 1, |
| // and the next line is not a continuation, we'll end up with i = 2 |
| // when we break. This handles the incrementing of i for the outer |
| // loop. |
| break; |
| } |
| } |
| const char* line_end = stream_begin + lines_[i - 1].second; |
| DCHECK_LT(line_begin - stream_begin, line_end - stream_begin); |
| |
| // We cleanup the whitespace at the end of the line before doing anything |
| // else of interest as it allows us to do nothing when irregularly formatted |
| // headers are parsed (e.g. those with only keys, only values, or no colon). |
| // |
| // We're guaranteed to have *line_end > ' ' while line_end >= line_begin. |
| --line_end; |
| DCHECK_EQ('\n', *line_end) |
| << "\"" << std::string(line_begin, line_end) << "\""; |
| while (*line_end <= ' ' && line_end > line_begin) { |
| --line_end; |
| } |
| ++line_end; |
| DCHECK_GE(' ', *line_end); |
| DCHECK_LT(line_begin, line_end); |
| |
| // We use '0' for the block idx, because we're always writing to the first |
| // block from the framer (we do this because the framer requires that the |
| // entire header sequence be in a contiguous buffer). |
| headers_->header_lines_.push_back( |
| HeaderLineDescription(line_begin - stream_begin, |
| line_end - stream_begin, |
| line_end - stream_begin, |
| line_end - stream_begin, |
| 0)); |
| if (current >= line_end) { |
| last_error_ = BalsaFrameEnums::HEADER_MISSING_COLON; |
| visitor_->HandleHeaderWarning(this); |
| // Then the next colon will not be found within this header line-- time |
| // to try again with another header-line. |
| continue; |
| } else if (current < line_begin) { |
| // When this condition is true, the last detected colon was part of a |
| // previous line. We reset to the beginning of the line as we don't care |
| // about the presence of any colon before the beginning of the current |
| // line. |
| current = line_begin; |
| } |
| #if __SSE2__ |
| while (current < header_lines_end_m16) { |
| __m128i header_bytes = |
| _mm_loadu_si128(reinterpret_cast<const __m128i *>(current)); |
| __m128i colon_cmp = _mm_cmpeq_epi8(header_bytes, colons); |
| int colon_msk = _mm_movemask_epi8(colon_cmp); |
| if (colon_msk == 0) { |
| current += 16; |
| continue; |
| } |
| current += (ffs(colon_msk) - 1); |
| if (current > line_end) { |
| break; |
| } |
| goto found_colon; |
| } |
| #endif // __SSE2__ |
| for (; current < line_end; ++current) { |
| if (*current != ':') { |
| continue; |
| } |
| goto found_colon; |
| } |
| // If we've gotten to here, then there was no colon |
| // in the line. The arguments we passed into the construction |
| // for the HeaderLineDescription object should be OK-- it assumes |
| // that the entire content is 'key' by default (which is true, as |
| // there was no colon, there can be no value). Note that this is a |
| // construct which is technically not allowed by the spec. |
| last_error_ = BalsaFrameEnums::HEADER_MISSING_COLON; |
| visitor_->HandleHeaderWarning(this); |
| continue; |
| found_colon: |
| DCHECK_EQ(*current, ':'); |
| DCHECK_LE(current - stream_begin, line_end - stream_begin); |
| DCHECK_LE(stream_begin - stream_begin, current - stream_begin); |
| |
| HeaderLineDescription& current_header_line = headers_->header_lines_.back(); |
| current_header_line.key_end_idx = current - stream_begin; |
| current_header_line.value_begin_idx = current_header_line.key_end_idx; |
| if (current < line_end) { |
| ++current_header_line.key_end_idx; |
| |
| CleanUpKeyValueWhitespace(stream_begin, |
| line_begin, |
| current, |
| line_end, |
| ¤t_header_line); |
| } |
| } |
| } |
| |
| void BalsaFrame::ProcessContentLengthLine( |
| HeaderLines::size_type line_idx, |
| BalsaHeadersEnums::ContentLengthStatus* status, |
| size_t* length) { |
| const HeaderLineDescription& header_line = headers_->header_lines_[line_idx]; |
| const char* stream_begin = headers_->OriginalHeaderStreamBegin(); |
| const char* line_end = stream_begin + header_line.last_char_idx; |
| const char* value_begin = (stream_begin + header_line.value_begin_idx); |
| |
| if (value_begin >= line_end) { |
| // There is no non-whitespace value data. |
| #if DEBUGFRAMER |
| LOG(INFO) << "invalid content-length -- no non-whitespace value data"; |
| #endif |
| *status = BalsaHeadersEnums::INVALID_CONTENT_LENGTH; |
| return; |
| } |
| |
| *length = 0; |
| while (value_begin < line_end) { |
| if (*value_begin < '0' || *value_begin > '9') { |
| // bad! content-length found, and couldn't parse all of it! |
| *status = BalsaHeadersEnums::INVALID_CONTENT_LENGTH; |
| #if DEBUGFRAMER |
| LOG(INFO) << "invalid content-length - non numeric character detected"; |
| #endif // DEBUGFRAMER |
| return; |
| } |
| const size_t kMaxDiv10 = std::numeric_limits<size_t>::max() / 10; |
| size_t length_x_10 = *length * 10; |
| const unsigned char c = *value_begin - '0'; |
| if (*length > kMaxDiv10 || |
| (std::numeric_limits<size_t>::max() - length_x_10) < c) { |
| *status = BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW; |
| #if DEBUGFRAMER |
| LOG(INFO) << "content-length overflow"; |
| #endif // DEBUGFRAMER |
| return; |
| } |
| *length = length_x_10 + c; |
| ++value_begin; |
| } |
| #if DEBUGFRAMER |
| LOG(INFO) << "content_length parsed: " << *length; |
| #endif // DEBUGFRAMER |
| *status = BalsaHeadersEnums::VALID_CONTENT_LENGTH; |
| } |
| |
| void BalsaFrame::ProcessTransferEncodingLine(HeaderLines::size_type line_idx) { |
| const HeaderLineDescription& header_line = headers_->header_lines_[line_idx]; |
| const char* stream_begin = headers_->OriginalHeaderStreamBegin(); |
| const char* line_end = stream_begin + header_line.last_char_idx; |
| const char* value_begin = stream_begin + header_line.value_begin_idx; |
| size_t value_length = line_end - value_begin; |
| |
| if ((value_length == 7) && |
| !strncasecmp(value_begin, "chunked", 7)) { |
| headers_->transfer_encoding_is_chunked_ = true; |
| } else if ((value_length == 8) && |
| !strncasecmp(value_begin, "identity", 8)) { |
| headers_->transfer_encoding_is_chunked_ = false; |
| } else { |
| last_error_ = BalsaFrameEnums::UNKNOWN_TRANSFER_ENCODING; |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleHeaderError(this); |
| return; |
| } |
| } |
| |
| namespace { |
| bool SplitStringPiece(base::StringPiece original, char delim, |
| base::StringPiece* before, base::StringPiece* after) { |
| const char* p = original.data(); |
| const char* end = p + original.size(); |
| |
| while (p != end) { |
| if (*p == delim) { |
| ++p; |
| } else { |
| const char* start = p; |
| while (++p != end && *p != delim) { |
| // Skip to the next occurence of the delimiter. |
| } |
| *before = base::StringPiece(start, p - start); |
| if (p != end) |
| *after = base::StringPiece(p + 1, end - (p + 1)); |
| else |
| *after = base::StringPiece(""); |
| StringPieceUtils::RemoveWhitespaceContext(before); |
| StringPieceUtils::RemoveWhitespaceContext(after); |
| return true; |
| } |
| } |
| |
| *before = original; |
| *after = ""; |
| return false; |
| } |
| |
| // TODO(phython): Fix this function to properly deal with quoted values. |
| // E.g. ";;foo", "\";;\"", or \"aa; |
| // The last example, the semi-colon is a separator between extensions. |
| void ProcessChunkExtensionsManual(base::StringPiece all_extensions, |
| BalsaHeaders* extensions) { |
| base::StringPiece extension; |
| base::StringPiece remaining; |
| StringPieceUtils::RemoveWhitespaceContext(&all_extensions); |
| SplitStringPiece(all_extensions, ';', &extension, &remaining); |
| while (!extension.empty()) { |
| base::StringPiece key; |
| base::StringPiece value; |
| SplitStringPiece(extension, '=', &key, &value); |
| if (!value.empty()) { |
| // Strip quotation marks if they exist. |
| if (!value.empty() && value[0] == '"') |
| value.remove_prefix(1); |
| if (!value.empty() && value[value.length() - 1] == '"') |
| value.remove_suffix(1); |
| } |
| |
| extensions->AppendHeader(key, value); |
| |
| StringPieceUtils::RemoveWhitespaceContext(&remaining); |
| SplitStringPiece(remaining, ';', &extension, &remaining); |
| } |
| } |
| |
| } // anonymous namespace |
| |
| void BalsaFrame::ProcessChunkExtensions(const char* input, size_t size, |
| BalsaHeaders* extensions) { |
| ProcessChunkExtensionsManual(base::StringPiece(input, size), extensions); |
| } |
| |
| void BalsaFrame::ProcessHeaderLines() { |
| HeaderLines::size_type content_length_idx = 0; |
| HeaderLines::size_type transfer_encoding_idx = 0; |
| |
| DCHECK(!lines_.empty()); |
| #if DEBUGFRAMER |
| LOG(INFO) << "******@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@**********\n"; |
| #endif // DEBUGFRAMER |
| |
| // There is no need to attempt to process headers if no header lines exist. |
| // There are at least two lines in the message which are not header lines. |
| // These two non-header lines are the first line of the message, and the |
| // last line of the message (which is an empty line). |
| // Thus, we test to see if we have more than two lines total before attempting |
| // to parse any header lines. |
| if (lines_.size() > 2) { |
| const char* stream_begin = headers_->OriginalHeaderStreamBegin(); |
| |
| // Then, for the rest of the header data, we parse these into key-value |
| // pairs. |
| FindColonsAndParseIntoKeyValue(); |
| // At this point, we've parsed all of the headers. Time to look for those |
| // headers which we require for framing. |
| const HeaderLines::size_type |
| header_lines_size = headers_->header_lines_.size(); |
| for (HeaderLines::size_type i = 0; i < header_lines_size; ++i) { |
| const HeaderLineDescription& current_header_line = |
| headers_->header_lines_[i]; |
| const char* key_begin = |
| (stream_begin + current_header_line.first_char_idx); |
| const char* key_end = (stream_begin + current_header_line.key_end_idx); |
| const size_t key_len = key_end - key_begin; |
| const char c = *key_begin; |
| #if DEBUGFRAMER |
| LOG(INFO) << "[" << i << "]: " << std::string(key_begin, key_len) |
| << " c: '" << c << "' key_len: " << key_len; |
| #endif // DEBUGFRAMER |
| // If a header begins with either lowercase or uppercase 'c' or 't', then |
| // the header may be one of content-length, connection, content-encoding |
| // or transfer-encoding. These headers are special, as they change the way |
| // that the message is framed, and so the framer is required to search |
| // for them. |
| |
| |
| if (c == 'c' || c == 'C') { |
| if ((key_len == kContentLengthSize) && |
| 0 == strncasecmp(key_begin, kContentLength, kContentLengthSize)) { |
| BalsaHeadersEnums::ContentLengthStatus content_length_status = |
| BalsaHeadersEnums::NO_CONTENT_LENGTH; |
| size_t length = 0; |
| ProcessContentLengthLine(i, &content_length_status, &length); |
| if (content_length_idx != 0) { // then we've already seen one! |
| if ((headers_->content_length_status_ != content_length_status) || |
| ((headers_->content_length_status_ == |
| BalsaHeadersEnums::VALID_CONTENT_LENGTH) && |
| length != headers_->content_length_)) { |
| last_error_ = BalsaFrameEnums::MULTIPLE_CONTENT_LENGTH_KEYS; |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleHeaderError(this); |
| return; |
| } |
| continue; |
| } else { |
| content_length_idx = i + 1; |
| headers_->content_length_status_ = content_length_status; |
| headers_->content_length_ = length; |
| content_length_remaining_ = length; |
| } |
| |
| } |
| } else if (c == 't' || c == 'T') { |
| if ((key_len == kTransferEncodingSize) && |
| 0 == strncasecmp(key_begin, kTransferEncoding, |
| kTransferEncodingSize)) { |
| if (transfer_encoding_idx != 0) { |
| last_error_ = BalsaFrameEnums::MULTIPLE_TRANSFER_ENCODING_KEYS; |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleHeaderError(this); |
| return; |
| } |
| transfer_encoding_idx = i + 1; |
| } |
| } else if (i == 0 && (key_len == 0 || c == ' ')) { |
| last_error_ = BalsaFrameEnums::INVALID_HEADER_FORMAT; |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleHeaderError(this); |
| return; |
| } |
| } |
| if (headers_->transfer_encoding_is_chunked_) { |
| headers_->content_length_ = 0; |
| headers_->content_length_status_ = BalsaHeadersEnums::NO_CONTENT_LENGTH; |
| content_length_remaining_ = 0; |
| } |
| if (transfer_encoding_idx != 0) { |
| ProcessTransferEncodingLine(transfer_encoding_idx - 1); |
| } |
| } |
| } |
| |
| void BalsaFrame::AssignParseStateAfterHeadersHaveBeenParsed() { |
| // For responses, can't have a body if the request was a HEAD, or if it is |
| // one of these response-codes. rfc2616 section 4.3 |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| if (is_request_ || |
| !(request_was_head_ || |
| (headers_->parsed_response_code_ >= 100 && |
| headers_->parsed_response_code_ < 200) || |
| (headers_->parsed_response_code_ == 204) || |
| (headers_->parsed_response_code_ == 304))) { |
| // Then we can have a body. |
| if (headers_->transfer_encoding_is_chunked_) { |
| // Note that |
| // if ( Transfer-Encoding: chunked && Content-length: ) |
| // then Transfer-Encoding: chunked trumps. |
| // This is as specified in the spec. |
| // rfc2616 section 4.4.3 |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH; |
| } else { |
| // Errors parsing content-length definitely can cause |
| // protocol errors/warnings |
| switch (headers_->content_length_status_) { |
| // If we have a content-length, and it is parsed |
| // properly, there are two options. |
| // 1) zero content, in which case the message is done, and |
| // 2) nonzero content, in which case we have to |
| // consume the body. |
| case BalsaHeadersEnums::VALID_CONTENT_LENGTH: |
| if (headers_->content_length_ == 0) { |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| } else { |
| parse_state_ = BalsaFrameEnums::READING_CONTENT; |
| } |
| break; |
| case BalsaHeadersEnums::CONTENT_LENGTH_OVERFLOW: |
| case BalsaHeadersEnums::INVALID_CONTENT_LENGTH: |
| // If there were characters left-over after parsing the |
| // content length, we should flag an error and stop. |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::UNPARSABLE_CONTENT_LENGTH; |
| visitor_->HandleHeaderError(this); |
| break; |
| // We can have: no transfer-encoding, no content length, and no |
| // connection: close... |
| // Unfortunately, this case doesn't seem to be covered in the spec. |
| // We'll assume that the safest thing to do here is what the google |
| // binaries before 2008 already do, which is to assume that |
| // everything until the connection is closed is body. |
| case BalsaHeadersEnums::NO_CONTENT_LENGTH: |
| if (is_request_) { |
| base::StringPiece method = headers_->request_method(); |
| // POSTs and PUTs should have a detectable body length. If they |
| // do not we consider it an error. |
| if ((method.size() == 4 && |
| strncmp(method.data(), "POST", 4) == 0) || |
| (method.size() == 3 && |
| strncmp(method.data(), "PUT", 3) == 0)) { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = |
| BalsaFrameEnums::REQUIRED_BODY_BUT_NO_CONTENT_LENGTH; |
| visitor_->HandleHeaderError(this); |
| break; |
| } |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| } else { |
| parse_state_ = BalsaFrameEnums::READING_UNTIL_CLOSE; |
| last_error_ = BalsaFrameEnums::MAYBE_BODY_BUT_NO_CONTENT_LENGTH; |
| visitor_->HandleHeaderWarning(this); |
| } |
| break; |
| // The COV_NF_... statements here provide hints to the apparatus |
| // which computes coverage reports/ratios that this code is never |
| // intended to be executed, and should technically be impossible. |
| // COV_NF_START |
| default: |
| LOG(FATAL) << "Saw a content_length_status: " |
| << headers_->content_length_status_ << " which is unknown."; |
| // COV_NF_END |
| } |
| } |
| } |
| } |
| |
| size_t BalsaFrame::ProcessHeaders(const char* message_start, |
| size_t message_length) { |
| const char* const original_message_start = message_start; |
| const char* const message_end = message_start + message_length; |
| const char* message_current = message_start; |
| const char* checkpoint = message_start; |
| |
| if (message_length == 0) { |
| goto bottom; |
| } |
| |
| while (message_current < message_end) { |
| size_t base_idx = headers_->GetReadableBytesFromHeaderStream(); |
| |
| // Yes, we could use strchr (assuming null termination), or |
| // memchr, but as it turns out that is slower than this tight loop |
| // for the input that we see. |
| if (!saw_non_newline_char_) { |
| do { |
| const char c = *message_current; |
| if (c != '\r' && c != '\n') { |
| if (c <= ' ') { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::NO_REQUEST_LINE_IN_REQUEST; |
| visitor_->HandleHeaderError(this); |
| goto bottom; |
| } else { |
| saw_non_newline_char_ = true; |
| checkpoint = message_start = message_current; |
| goto read_real_message; |
| } |
| } |
| ++message_current; |
| } while (message_current < message_end); |
| goto bottom; // this is necessary to skip 'last_char_was_slash_r' checks |
| } else { |
| read_real_message: |
| // Note that SSE2 can be enabled on certain piii platforms. |
| #if __SSE2__ |
| { |
| const char* const message_end_m16 = message_end - 16; |
| __m128i newlines = _mm_set1_epi8('\n'); |
| while (message_current < message_end_m16) { |
| // What this does (using compiler intrinsics): |
| // |
| // Load 16 '\n's into an xmm register |
| // Load 16 bytes of currennt message into an xmm register |
| // Do byte-wise equals on those two xmm registers |
| // Take the first bit of each byte, and put that into the first |
| // 16 bits of a mask |
| // If the mask is zero, no '\n' found. increment by 16 and try again |
| // Else scan forward to find the first set bit. |
| // Increment current by the index of the first set bit |
| // (ffs returns index of first set bit + 1) |
| __m128i msg_bytes = |
| _mm_loadu_si128(const_cast<__m128i *>( |
| reinterpret_cast<const __m128i *>(message_current))); |
| __m128i newline_cmp = _mm_cmpeq_epi8(msg_bytes, newlines); |
| int newline_msk = _mm_movemask_epi8(newline_cmp); |
| if (newline_msk == 0) { |
| message_current += 16; |
| continue; |
| } |
| message_current += (ffs(newline_msk) - 1); |
| const size_t relative_idx = message_current - message_start; |
| const size_t message_current_idx = 1 + base_idx + relative_idx; |
| lines_.push_back(std::make_pair(last_slash_n_idx_, |
| message_current_idx)); |
| if (lines_.size() == 1) { |
| headers_->WriteFromFramer(checkpoint, |
| 1 + message_current - checkpoint); |
| checkpoint = message_current + 1; |
| const char* begin = headers_->OriginalHeaderStreamBegin(); |
| #if DEBUGFRAMER |
| LOG(INFO) << "First line " << std::string(begin, lines_[0].second); |
| LOG(INFO) << "is_request_: " << is_request_; |
| #endif |
| ProcessFirstLine(begin, begin + lines_[0].second); |
| if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) |
| goto process_lines; |
| else if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) |
| goto bottom; |
| } |
| const size_t chars_since_last_slash_n = (message_current_idx - |
| last_slash_n_idx_); |
| last_slash_n_idx_ = message_current_idx; |
| if (chars_since_last_slash_n > 2) { |
| // We have a slash-n, but the last slash n was |
| // more than 2 characters away from this. Thus, we know |
| // that this cannot be an end-of-header. |
| ++message_current; |
| continue; |
| } |
| if ((chars_since_last_slash_n == 1) || |
| (((message_current > message_start) && |
| (*(message_current - 1) == '\r')) || |
| (last_char_was_slash_r_))) { |
| goto process_lines; |
| } |
| ++message_current; |
| } |
| } |
| #endif // __SSE2__ |
| while (message_current < message_end) { |
| if (*message_current != '\n') { |
| ++message_current; |
| continue; |
| } |
| const size_t relative_idx = message_current - message_start; |
| const size_t message_current_idx = 1 + base_idx + relative_idx; |
| lines_.push_back(std::make_pair(last_slash_n_idx_, |
| message_current_idx)); |
| if (lines_.size() == 1) { |
| headers_->WriteFromFramer(checkpoint, |
| 1 + message_current - checkpoint); |
| checkpoint = message_current + 1; |
| const char* begin = headers_->OriginalHeaderStreamBegin(); |
| #if DEBUGFRAMER |
| LOG(INFO) << "First line " << std::string(begin, lines_[0].second); |
| LOG(INFO) << "is_request_: " << is_request_; |
| #endif |
| ProcessFirstLine(begin, begin + lines_[0].second); |
| if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) |
| goto process_lines; |
| else if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) |
| goto bottom; |
| } |
| const size_t chars_since_last_slash_n = (message_current_idx - |
| last_slash_n_idx_); |
| last_slash_n_idx_ = message_current_idx; |
| if (chars_since_last_slash_n > 2) { |
| // false positive. |
| ++message_current; |
| continue; |
| } |
| if ((chars_since_last_slash_n == 1) || |
| (((message_current > message_start) && |
| (*(message_current - 1) == '\r')) || |
| (last_char_was_slash_r_))) { |
| goto process_lines; |
| } |
| ++message_current; |
| } |
| } |
| continue; |
| process_lines: |
| ++message_current; |
| DCHECK(message_current >= message_start); |
| if (message_current > message_start) { |
| headers_->WriteFromFramer(checkpoint, message_current - checkpoint); |
| } |
| |
| // Check if we have exceeded maximum headers length |
| // Although we check for this limit before and after we call this function |
| // we check it here as well to make sure that in case the visitor changed |
| // the max_header_length_ (for example after processing the first line) |
| // we handle it gracefully. |
| if (headers_->GetReadableBytesFromHeaderStream() > max_header_length_) { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG; |
| visitor_->HandleHeaderError(this); |
| goto bottom; |
| } |
| |
| // Since we know that we won't be writing any more bytes of the header, |
| // we tell that to the headers object. The headers object may make |
| // more efficient allocation decisions when this is signaled. |
| headers_->DoneWritingFromFramer(); |
| { |
| const char* readable_ptr = NULL; |
| size_t readable_size = 0; |
| headers_->GetReadablePtrFromHeaderStream(&readable_ptr, &readable_size); |
| visitor_->ProcessHeaderInput(readable_ptr, readable_size); |
| } |
| |
| // Ok, now that we've written everything into our header buffer, it is |
| // time to process the header lines (extract proper values for headers |
| // which are important for framing). |
| ProcessHeaderLines(); |
| if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) { |
| goto bottom; |
| } |
| AssignParseStateAfterHeadersHaveBeenParsed(); |
| if (parse_state_ == BalsaFrameEnums::PARSE_ERROR) { |
| goto bottom; |
| } |
| visitor_->ProcessHeaders(*headers_); |
| visitor_->HeaderDone(); |
| if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ) { |
| visitor_->MessageDone(); |
| } |
| goto bottom; |
| } |
| // If we've gotten to here, it means that we've consumed all of the |
| // available input. We need to record whether or not the last character we |
| // saw was a '\r' so that a subsequent call to ProcessInput correctly finds |
| // a header framing that is split across the two calls. |
| last_char_was_slash_r_ = (*(message_end - 1) == '\r'); |
| DCHECK(message_current >= message_start); |
| if (message_current > message_start) { |
| headers_->WriteFromFramer(checkpoint, message_current - checkpoint); |
| } |
| bottom: |
| return message_current - original_message_start; |
| } |
| |
| |
| size_t BalsaFrame::BytesSafeToSplice() const { |
| switch (parse_state_) { |
| case BalsaFrameEnums::READING_CHUNK_DATA: |
| return chunk_length_remaining_; |
| case BalsaFrameEnums::READING_UNTIL_CLOSE: |
| return std::numeric_limits<size_t>::max(); |
| case BalsaFrameEnums::READING_CONTENT: |
| return content_length_remaining_; |
| default: |
| return 0; |
| } |
| } |
| |
| void BalsaFrame::BytesSpliced(size_t bytes_spliced) { |
| switch (parse_state_) { |
| case BalsaFrameEnums::READING_CHUNK_DATA: |
| if (chunk_length_remaining_ >= bytes_spliced) { |
| chunk_length_remaining_ -= bytes_spliced; |
| if (chunk_length_remaining_ == 0) { |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM; |
| } |
| return; |
| } else { |
| last_error_ = |
| BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT; |
| goto error_exit; |
| } |
| |
| case BalsaFrameEnums::READING_UNTIL_CLOSE: |
| return; |
| |
| case BalsaFrameEnums::READING_CONTENT: |
| if (content_length_remaining_ >= bytes_spliced) { |
| content_length_remaining_ -= bytes_spliced; |
| if (content_length_remaining_ == 0) { |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| visitor_->MessageDone(); |
| } |
| return; |
| } else { |
| last_error_ = |
| BalsaFrameEnums::CALLED_BYTES_SPLICED_AND_EXCEEDED_SAFE_SPLICE_AMOUNT; |
| goto error_exit; |
| } |
| |
| default: |
| last_error_ = BalsaFrameEnums::CALLED_BYTES_SPLICED_WHEN_UNSAFE_TO_DO_SO; |
| goto error_exit; |
| } |
| |
| error_exit: |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| visitor_->HandleBodyError(this); |
| }; |
| |
| // You may note that the state-machine contained within this function has both |
| // switch and goto labels for nearly the same thing. For instance, the |
| // following two labels refer to the same code block: |
| // label_reading_chunk_data: |
| // case BalsaFrameEnums::READING_CHUNK_DATA: |
| // The 'case' statement is required for the switch statement which occurs when |
| // ProcessInput is invoked. The goto label is required as the state-machine |
| // does not use a computed goto in any subsequent operations. |
| // |
| // Since several states exit the state machine for various reasons, there is |
| // also one label at the bottom of the function. When it is appropriate to |
| // return from the function, that part of the state machine instead issues a |
| // goto bottom; This results in less code duplication, and makes debugging |
| // easier (as you can add a statement to a section of code which is guaranteed |
| // to be invoked when the function is exiting. |
| size_t BalsaFrame::ProcessInput(const char* input, size_t size) { |
| const char* current = input; |
| const char* on_entry = current; |
| const char* end = current + size; |
| #if DEBUGFRAMER |
| LOG(INFO) << "\n==============" |
| << BalsaFrameEnums::ParseStateToString(parse_state_) |
| << "===============\n"; |
| #endif // DEBUGFRAMER |
| |
| DCHECK(headers_ != NULL); |
| if (headers_ == NULL) return 0; |
| |
| if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) { |
| const size_t header_length = headers_->GetReadableBytesFromHeaderStream(); |
| // Yes, we still have to check this here as the user can change the |
| // max_header_length amount! |
| // Also it is possible that we have reached the maximum allowed header size, |
| // and we have more to consume (remember we are still inside |
| // READING_HEADER_AND_FIRSTLINE) in which case we directly declare an error. |
| if (header_length > max_header_length_ || |
| (header_length == max_header_length_ && size > 0)) { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG; |
| visitor_->HandleHeaderError(this); |
| goto bottom; |
| } |
| size_t bytes_to_process = max_header_length_ - header_length; |
| if (bytes_to_process > size) { |
| bytes_to_process = size; |
| } |
| current += ProcessHeaders(input, bytes_to_process); |
| // If we are still reading headers check if we have crossed the headers |
| // limit. Note that we check for >= as opposed to >. This is because if |
| // header_length_after equals max_header_length_ and we are still in the |
| // parse_state_ BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE we know for |
| // sure that the headers limit will be crossed later on |
| if (parse_state_ == BalsaFrameEnums::READING_HEADER_AND_FIRSTLINE) { |
| // Note that headers_ is valid only if we are still reading headers. |
| const size_t header_length_after = |
| headers_->GetReadableBytesFromHeaderStream(); |
| if (header_length_after >= max_header_length_) { |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::HEADERS_TOO_LONG; |
| visitor_->HandleHeaderError(this); |
| } |
| } |
| goto bottom; |
| } else if (parse_state_ == BalsaFrameEnums::MESSAGE_FULLY_READ || |
| parse_state_ == BalsaFrameEnums::PARSE_ERROR) { |
| // Can do nothing more 'till we're reset. |
| goto bottom; |
| } |
| |
| while (current < end) { |
| switch (parse_state_) { |
| label_reading_chunk_length: |
| case BalsaFrameEnums::READING_CHUNK_LENGTH: |
| // In this state we read the chunk length. |
| // Note that once we hit a character which is not in: |
| // [0-9;A-Fa-f\n], we transition to a different state. |
| // |
| { |
| // If we used strtol, etc, we'd have to buffer this line. |
| // This is more annoying than simply doing the conversion |
| // here. This code accounts for overflow. |
| static const signed char buf[] = { |
| // %0 %1 %2 %3 %4 %5 %6 %7 %8 \t \n %b %c \r %e %f |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, |
| // %10 %11 %12 %13 %14 %15 %16 %17 %18 %19 %1a %1b %1c %1d %1e %1f |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| // ' ' %21 %22 %23 %24 %25 %26 %27 %28 %29 %2a %2b %2c %2d %2e %2f |
| -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| // %30 %31 %32 %33 %34 %35 %36 %37 %38 %39 %3a ';' %3c %3d %3e %3f |
| 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -2, -1, -1, -1, -1, |
| // %40 'A' 'B' 'C' 'D' 'E' 'F' %47 %48 %49 %4a %4b %4c %4d %4e %4f |
| -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| // %50 %51 %52 %53 %54 %55 %56 %57 %58 %59 %5a %5b %5c %5d %5e %5f |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| // %60 'a' 'b' 'c' 'd' 'e' 'f' %67 %68 %69 %6a %6b %6c %6d %6e %6f |
| -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| // %70 %71 %72 %73 %74 %75 %76 %77 %78 %79 %7a %7b %7c %7d %7e %7f |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| }; |
| // valid cases: |
| // "09123\n" // -> 09123 |
| // "09123\r\n" // -> 09123 |
| // "09123 \n" // -> 09123 |
| // "09123 \r\n" // -> 09123 |
| // "09123 12312\n" // -> 09123 |
| // "09123 12312\r\n" // -> 09123 |
| // "09123; foo=bar\n" // -> 09123 |
| // "09123; foo=bar\r\n" // -> 09123 |
| // "FFFFFFFFFFFFFFFF\r\n" // -> FFFFFFFFFFFFFFFF |
| // "FFFFFFFFFFFFFFFF 22\r\n" // -> FFFFFFFFFFFFFFFF |
| // invalid cases: |
| // "[ \t]+[^\n]*\n" |
| // "FFFFFFFFFFFFFFFFF\r\n" (would overflow) |
| // "\r\n" |
| // "\n" |
| while (current < end) { |
| const char c = *current; |
| ++current; |
| const signed char addition = buf[static_cast<int>(c)]; |
| if (addition >= 0) { |
| chunk_length_character_extracted_ = true; |
| size_t length_x_16 = chunk_length_remaining_ * 16; |
| const size_t kMaxDiv16 = std::numeric_limits<size_t>::max() / 16; |
| if ((chunk_length_remaining_ > kMaxDiv16) || |
| ((std::numeric_limits<size_t>::max() - length_x_16) < |
| static_cast<size_t>(addition))) { |
| // overflow -- asked for a chunk-length greater than 2^64 - 1!! |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::CHUNK_LENGTH_OVERFLOW; |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| visitor_->HandleChunkingError(this); |
| goto bottom; |
| } |
| chunk_length_remaining_ = length_x_16 + addition; |
| continue; |
| } |
| |
| if (!chunk_length_character_extracted_ || addition == -1) { |
| // ^[0-9;A-Fa-f][ \t\n] -- was not matched, either because no |
| // characters were converted, or an unexpected character was |
| // seen. |
| parse_state_ = BalsaFrameEnums::PARSE_ERROR; |
| last_error_ = BalsaFrameEnums::INVALID_CHUNK_LENGTH; |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| visitor_->HandleChunkingError(this); |
| goto bottom; |
| } |
| |
| --current; |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_EXTENSION; |
| visitor_->ProcessChunkLength(chunk_length_remaining_); |
| goto label_reading_chunk_extension; |
| } |
| } |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| goto bottom; // case BalsaFrameEnums::READING_CHUNK_LENGTH |
| |
| label_reading_chunk_extension: |
| case BalsaFrameEnums::READING_CHUNK_EXTENSION: |
| { |
| // TODO(phython): Convert this scanning to be 16 bytes at a time if |
| // there is data to be read. |
| const char* extensions_start = current; |
| size_t extensions_length = 0; |
| while (current < end) { |
| const char c = *current; |
| if (c == '\r' || c == '\n') { |
| extensions_length = |
| (extensions_start == current) ? |
| 0 : |
| current - extensions_start - 1; |
| } |
| |
| ++current; |
| if (c == '\n') { |
| chunk_length_character_extracted_ = false; |
| visitor_->ProcessChunkExtensions( |
| extensions_start, extensions_length); |
| if (chunk_length_remaining_ != 0) { |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_DATA; |
| goto label_reading_chunk_data; |
| } |
| HeaderFramingFound('\n'); |
| parse_state_ = BalsaFrameEnums::READING_LAST_CHUNK_TERM; |
| goto label_reading_last_chunk_term; |
| } |
| } |
| visitor_->ProcessChunkExtensions( |
| extensions_start, extensions_length); |
| } |
| |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| goto bottom; // case BalsaFrameEnums::READING_CHUNK_EXTENSION |
| |
| label_reading_chunk_data: |
| case BalsaFrameEnums::READING_CHUNK_DATA: |
| while (current < end) { |
| if (chunk_length_remaining_ == 0) { |
| break; |
| } |
| // read in the chunk |
| size_t bytes_remaining = end - current; |
| size_t consumed_bytes = (chunk_length_remaining_ < bytes_remaining) ? |
| chunk_length_remaining_ : bytes_remaining; |
| const char* tmp_current = current + consumed_bytes; |
| visitor_->ProcessBodyInput(on_entry, tmp_current - on_entry); |
| visitor_->ProcessBodyData(current, consumed_bytes); |
| on_entry = current = tmp_current; |
| chunk_length_remaining_ -= consumed_bytes; |
| } |
| if (chunk_length_remaining_ == 0) { |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_TERM; |
| goto label_reading_chunk_term; |
| } |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| goto bottom; // case BalsaFrameEnums::READING_CHUNK_DATA |
| |
| label_reading_chunk_term: |
| case BalsaFrameEnums::READING_CHUNK_TERM: |
| while (current < end) { |
| const char c = *current; |
| ++current; |
| |
| if (c == '\n') { |
| parse_state_ = BalsaFrameEnums::READING_CHUNK_LENGTH; |
| goto label_reading_chunk_length; |
| } |
| } |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| goto bottom; // case BalsaFrameEnums::READING_CHUNK_TERM |
| |
| label_reading_last_chunk_term: |
| case BalsaFrameEnums::READING_LAST_CHUNK_TERM: |
| while (current < end) { |
| const char c = *current; |
| |
| if (!HeaderFramingFound(c)) { |
| // If not, however, since the spec only suggests that the |
| // client SHOULD indicate the presence of trailers, we get to |
| // *test* that they did or didn't. |
| // If all of the bytes we've seen since: |
| // OPTIONAL_WS 0 OPTIONAL_STUFF CRLF |
| // are either '\r', or '\n', then we can assume that we don't yet |
| // know if we need to parse headers, or if the next byte will make |
| // the HeaderFramingFound condition (above) true. |
| if (HeaderFramingMayBeFound()) { |
| // If true, then we have seen only characters '\r' or '\n'. |
| ++current; |
| |
| // Lets try again! There is no state change here. |
| continue; |
| } else { |
| // If (!HeaderFramingMayBeFound()), then we know that we must be |
| // reading the first non CRLF character of a trailer. |
| parse_state_ = BalsaFrameEnums::READING_TRAILER; |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| on_entry = current; |
| goto label_reading_trailer; |
| } |
| } else { |
| // If we've found a "\r\n\r\n", then the message |
| // is done. |
| ++current; |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| visitor_->MessageDone(); |
| goto bottom; |
| } |
| break; // from while loop |
| } |
| visitor_->ProcessBodyInput(on_entry, current - on_entry); |
| goto bottom; // case BalsaFrameEnums::READING_LAST_CHUNK_TERM |
| |
| label_reading_trailer: |
| case BalsaFrameEnums::READING_TRAILER: |
| while (current < end) { |
| const char c = *current; |
| ++current; |
| // TODO(fenix): If we ever care about trailers as part of framing, |
| // deal with them here (see below for part of the 'solution') |
| // if (LineFramingFound(c)) { |
| // trailer_lines_.push_back(make_pair(start_of_line_, |
| // trailer_length_ - 1)); |
| // start_of_line_ = trailer_length_; |
| // } |
| if (HeaderFramingFound(c)) { |
| // ProcessTrailers(visitor_, &trailers_); |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| visitor_->ProcessTrailerInput(on_entry, current - on_entry); |
| visitor_->MessageDone(); |
| goto bottom; |
| } |
| } |
| visitor_->ProcessTrailerInput(on_entry, current - on_entry); |
| break; // case BalsaFrameEnums::READING_TRAILER |
| |
| // Note that there is no label: |
| // 'label_reading_until_close' |
| // here. This is because the state-machine exists immediately after |
| // reading the headers instead of transitioning here (as it would |
| // do if it was consuming all the data it could, all the time). |
| case BalsaFrameEnums::READING_UNTIL_CLOSE: |
| { |
| const size_t bytes_remaining = end - current; |
| if (bytes_remaining > 0) { |
| visitor_->ProcessBodyInput(current, bytes_remaining); |
| visitor_->ProcessBodyData(current, bytes_remaining); |
| current += bytes_remaining; |
| } |
| } |
| goto bottom; // case BalsaFrameEnums::READING_UNTIL_CLOSE |
| |
| // label_reading_content: |
| case BalsaFrameEnums::READING_CONTENT: |
| #if DEBUGFRAMER |
| LOG(INFO) << "ReadingContent: " << content_length_remaining_; |
| #endif // DEBUGFRAMER |
| while (content_length_remaining_ && current < end) { |
| // read in the content |
| const size_t bytes_remaining = end - current; |
| const size_t consumed_bytes = |
| (content_length_remaining_ < bytes_remaining) ? |
| content_length_remaining_ : bytes_remaining; |
| visitor_->ProcessBodyInput(current, consumed_bytes); |
| visitor_->ProcessBodyData(current, consumed_bytes); |
| current += consumed_bytes; |
| content_length_remaining_ -= consumed_bytes; |
| } |
| if (content_length_remaining_ == 0) { |
| parse_state_ = BalsaFrameEnums::MESSAGE_FULLY_READ; |
| visitor_->MessageDone(); |
| } |
| goto bottom; // case BalsaFrameEnums::READING_CONTENT |
| |
| default: |
| // The state-machine should never be in a state that isn't handled |
| // above. This is a glaring logic error, and we should do something |
| // drastic to ensure that this gets looked-at and fixed. |
| LOG(FATAL) << "Unknown state: " << parse_state_ // COV_NF_LINE |
| << " memory corruption?!"; // COV_NF_LINE |
| } |
| } |
| bottom: |
| #if DEBUGFRAMER |
| LOG(INFO) << "\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n" |
| << std::string(input, current) |
| << "\n$$$$$$$$$$$$$$" |
| << BalsaFrameEnums::ParseStateToString(parse_state_) |
| << "$$$$$$$$$$$$$$$" |
| << " consumed: " << (current - input); |
| if (Error()) { |
| LOG(INFO) << BalsaFrameEnums::ErrorCodeToString(ErrorCode()); |
| } |
| #endif // DEBUGFRAMER |
| return current - input; |
| } |
| |
| } // namespace net |