├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── callback.rs ├── error.rs ├── flags.rs ├── http_method.rs ├── http_version.rs ├── lib.rs ├── parser.rs └── state.rs └── tests ├── helper.rs ├── test_content_length_overflow.rs ├── test_first_line.rs ├── test_header.rs ├── test_interface.rs ├── test_long_body.rs ├── test_requests.rs └── test_responses.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | # executable created for debug purpose 14 | lib 15 | Cargo.lock 16 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # The Rust HTTP Parser Changelog 2 | 3 | ## v0.0.2 4 | * Remove unnecessary fields from unit tests 5 | * Add documentation for public interfaces 6 | * No significant changes since v0.0.1 7 | 8 | ## v0.0.1 9 | * First release of this package 10 | * Finish porting the HTTP parser. Unit tests are ported as well and passed. 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "http_parser" 4 | description = "Http request/response parser for rust" 5 | version = "0.0.2" 6 | authors = ["Minjie Zha "] 7 | license = "MIT" 8 | 9 | documentation = "http://magic003.github.io/http-parser-rs/doc/http_parser/" 10 | homepage = "http://magic003.github.io/http-parser-rs/doc/http_parser/" 11 | repository = "https://github.com/magic003/http-parser-rs" 12 | 13 | [profile.dev] 14 | opt-level = 0 15 | debug = true 16 | 17 | [profile.test] 18 | opt-level = 0 19 | debug = true 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Minjie Zha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Rust HTTP Parser 2 | 3 | The Rust HTTP Parser provides the functionality to parse both HTTP requests and responses. It is ported from [joyent/http-parser](https://github.com/joyent/http-parser) written in C. 4 | 5 | ## Usage 6 | 7 | Please refer to the [documentation](http://magic003.github.io/http-parser-rs/doc/http_parser/). 8 | 9 | ## Status 10 | 11 | The parser is ported as well as unit tests. For the moment, all the test cases are passed. Besides that, I didn't do other tests around it and it was not used by other project either. So it is ready to be played around but far away to be used in production. Also, the API is unstable and may be changed. 12 | -------------------------------------------------------------------------------- /src/callback.rs: -------------------------------------------------------------------------------- 1 | use parser::HttpParser; 2 | 3 | /// `ParseAction` defines the potential actions that could be returned by any callback function. 4 | /// The parser uses it to determine consequent behavior. 5 | #[derive(Clone)] 6 | pub enum ParseAction { 7 | /// No special actions. Keep the normal execution. 8 | None, 9 | /// Skip body 10 | SkipBody, 11 | } 12 | 13 | /// Result of a callback function. 14 | pub type CallbackResult = Result; 15 | 16 | /// It defines the callback functions that would be called by parser. 17 | /// 18 | /// # Example 19 | /// 20 | /// ``` 21 | /// # use http_parser::*; 22 | /// # 23 | /// struct Callback; 24 | /// 25 | /// impl HttpParserCallback for Callback { 26 | /// fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 27 | /// println!("Message begin"); 28 | /// Ok(ParseAction::None) 29 | /// } 30 | /// 31 | /// // Override other functions as you wish 32 | /// } 33 | /// 34 | /// let mut cb = Callback; 35 | /// ``` 36 | pub trait HttpParserCallback { 37 | /// Function called when starting parsing a new HTTP request or response. 38 | #[allow(unused_variables)] 39 | fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 40 | Ok(ParseAction::None) 41 | } 42 | 43 | /// Function called when a URL is parsed. 44 | #[allow(unused_variables)] 45 | fn on_url(&mut self, parser: &mut HttpParser, data: &[u8],) -> CallbackResult { 46 | Ok(ParseAction::None) 47 | } 48 | 49 | /// Function called when a status is parsed. 50 | #[allow(unused_variables)] 51 | fn on_status(&mut self, parser: &mut HttpParser, data: &[u8]) -> CallbackResult { 52 | Ok(ParseAction::None) 53 | } 54 | 55 | /// Function called when a header field is parsed. 56 | #[allow(unused_variables)] 57 | fn on_header_field(&mut self, parser: &mut HttpParser, data: &[u8]) -> CallbackResult { 58 | Ok(ParseAction::None) 59 | } 60 | 61 | /// Function called when a header value is parsed. 62 | #[allow(unused_variables)] 63 | fn on_header_value(&mut self, parser: &mut HttpParser, data: &[u8]) -> CallbackResult { 64 | Ok(ParseAction::None) 65 | } 66 | 67 | /// Function called when all headers are parsed. 68 | #[allow(unused_variables)] 69 | fn on_headers_complete(&mut self, parser: &mut HttpParser) -> CallbackResult { 70 | Ok(ParseAction::None) 71 | } 72 | 73 | /// Function called when the body is parsed. 74 | #[allow(unused_variables)] 75 | fn on_body(&mut self, parser: &mut HttpParser, data: &[u8]) -> CallbackResult { 76 | Ok(ParseAction::None) 77 | } 78 | 79 | /// Function called when finishing parsing a HTTP request or response. 80 | #[allow(unused_variables)] 81 | fn on_message_complete(&mut self, parser: &mut HttpParser) -> CallbackResult { 82 | Ok(ParseAction::None) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// `HttpErrno` defines the encountered error during parsing. 4 | #[derive(PartialEq, Eq, Copy, Clone)] 5 | pub enum HttpErrno { 6 | // Callback-related errors 7 | /// Error happened in message begin callback 8 | CBMessageBegin, 9 | /// Error happened in url callback 10 | CBUrl, 11 | /// Error happened in header field callback 12 | CBHeaderField, 13 | /// Error happened in header value callback 14 | CBHeaderValue, 15 | /// Error happened in headers complete callback 16 | CBHeadersComplete, 17 | /// Error happened in body callback 18 | CBBody, 19 | /// Error happened in message complete callback 20 | CBMessageComplete, 21 | /// Error happened in status callback 22 | CBStatus, 23 | 24 | // Parsing-related errors 25 | /// Invalid EOF state 26 | InvalidEofState, 27 | /// Header size is overflowed 28 | HeaderOverflow, 29 | /// Connection is closed 30 | ClosedConnection, 31 | /// Invalid HTTP version 32 | InvalidVersion, 33 | /// Invalid HTTP status 34 | InvalidStatus, 35 | /// Invalid HTTP method 36 | InvalidMethod, 37 | /// Invalid URL 38 | InvalidUrl, 39 | /// Invalid host 40 | InvalidHost, 41 | /// Invalid port 42 | InvalidPort, 43 | /// Invalid path 44 | InvalidPath, 45 | /// Invalid query string 46 | InvalidQueryString, 47 | /// Invalid fragment 48 | InvalidFragment, 49 | /// Line feed is expected 50 | LFExpected, 51 | /// Invalid header token 52 | InvalidHeaderToken, 53 | /// Invalid content length 54 | InvalidContentLength, 55 | /// Invalid chunk size 56 | InvalidChunkSize, 57 | /// Invalid constant 58 | InvalidConstant, 59 | /// Invalid internal state 60 | InvalidInternalState, 61 | /// Error happened in strict mode 62 | Strict, 63 | /// Error happened when the parser is paused 64 | Paused, 65 | /// Unkown error 66 | Unknown, 67 | } 68 | 69 | impl fmt::Display for HttpErrno { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | match *self { 72 | HttpErrno::CBMessageBegin => write!(f, "the on_message_begin callback failed"), 73 | HttpErrno::CBUrl => write!(f, "the on_url callback failed"), 74 | HttpErrno::CBHeaderField => write!(f, "the on_header_field callback failed"), 75 | HttpErrno::CBHeaderValue => write!(f, "the on_header_value callback failed"), 76 | HttpErrno::CBHeadersComplete => write!(f, "the on_headers_complete callback failed"), 77 | HttpErrno::CBBody => write!(f, "the on_body callback failed"), 78 | HttpErrno::CBMessageComplete => write!(f, "the on_message_complete callback failed"), 79 | HttpErrno::CBStatus => write!(f, "the on_status callback failed"), 80 | 81 | HttpErrno::InvalidEofState => write!(f, "stream ended at an unexpected time"), 82 | HttpErrno::HeaderOverflow => write!(f, "too many header bytes seen; overflow detected"), 83 | HttpErrno::ClosedConnection => write!(f, "data received after completed connection: close message"), 84 | HttpErrno::InvalidVersion => write!(f, "invalid HTTP version"), 85 | HttpErrno::InvalidStatus => write!(f, "invalid HTTP status code"), 86 | HttpErrno::InvalidMethod => write!(f, "invalid HTTP method"), 87 | HttpErrno::InvalidUrl => write!(f, "invalid URL"), 88 | HttpErrno::InvalidHost => write!(f, "invalid host"), 89 | HttpErrno::InvalidPort => write!(f, "invalid port"), 90 | HttpErrno::InvalidPath => write!(f, "invalid path"), 91 | HttpErrno::InvalidQueryString => write!(f, "invalid query string"), 92 | HttpErrno::InvalidFragment => write!(f, "invalid fragment"), 93 | HttpErrno::LFExpected => write!(f, "LF character expected"), 94 | HttpErrno::InvalidHeaderToken => write!(f, "invalid charater in header"), 95 | HttpErrno::InvalidContentLength => write!(f, "invalid character in content-length header"), 96 | HttpErrno::InvalidChunkSize => write!(f, "invalid character in chunk size header"), 97 | HttpErrno::InvalidConstant => write!(f, "invalid constant string"), 98 | HttpErrno::InvalidInternalState => write!(f, "encountered unexpected internal state"), 99 | HttpErrno::Strict => write!(f, "strict mode assertion failed"), 100 | HttpErrno::Paused => write!(f, "parser is parsed"), 101 | HttpErrno::Unknown => write!(f, "an unknown error occurred"), 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/flags.rs: -------------------------------------------------------------------------------- 1 | pub enum Flags { 2 | Chunked = 1isize << 0, 3 | ConnectionKeepAlive = 1isize << 1, 4 | ConnectionClose = 1isize << 2, 5 | Trailing = 1isize << 3, 6 | Upgrade = 1isize << 4, 7 | SkipBody = 1isize << 5, 8 | } 9 | 10 | impl Flags { 11 | pub fn as_u8(self) -> u8 { 12 | self as u8 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/http_method.rs: -------------------------------------------------------------------------------- 1 | /// `HttpMethod` defines supported HTTP methods. 2 | #[derive(PartialEq, Eq, Clone, Copy)] 3 | pub enum HttpMethod { 4 | Delete, 5 | Get, 6 | Head, 7 | Post, 8 | Put, 9 | // pathological 10 | Connect, 11 | Options, 12 | Trace, 13 | // webdav 14 | Copy, 15 | Lock, 16 | MKCol, 17 | Move, 18 | PropFind, 19 | PropPatch, 20 | Search, 21 | Unlock, 22 | // subversion 23 | Report, 24 | MKActivity, 25 | Checkout, 26 | Merge, 27 | // upnp 28 | MSearch, 29 | Notify, 30 | Subscribe, 31 | Unsubscribe, 32 | // RFC-5789 33 | Patch, 34 | Purge, 35 | // CalDAV 36 | MKCalendar, 37 | } 38 | 39 | impl ToString for HttpMethod { 40 | fn to_string(&self) -> String { 41 | match *self { 42 | HttpMethod::Delete => "DELETE".to_string(), 43 | HttpMethod::Get => "GET".to_string(), 44 | HttpMethod::Head => "HEAD".to_string(), 45 | HttpMethod::Post => "POST".to_string(), 46 | HttpMethod::Put => "Put".to_string(), 47 | HttpMethod::Connect => "CONNECT".to_string(), 48 | HttpMethod::Options => "OPTIONS".to_string(), 49 | HttpMethod::Trace => "TRACE".to_string(), 50 | HttpMethod::Copy => "COPY".to_string(), 51 | HttpMethod::Lock => "LOCK".to_string(), 52 | HttpMethod::MKCol => "MKCOL".to_string(), 53 | HttpMethod::Move => "MOVE".to_string(), 54 | HttpMethod::PropFind => "PROPFIND".to_string(), 55 | HttpMethod::PropPatch => "PROPPATCH".to_string(), 56 | HttpMethod::Search => "SEARCH".to_string(), 57 | HttpMethod::Unlock => "UNLOCK".to_string(), 58 | HttpMethod::Report => "REPORT".to_string(), 59 | HttpMethod::MKActivity => "MKACTIVITY".to_string(), 60 | HttpMethod::Checkout => "CHECKOUT".to_string(), 61 | HttpMethod::Merge => "MERGE".to_string(), 62 | HttpMethod::MSearch => "M-SEARCH".to_string(), 63 | HttpMethod::Notify => "NOTIFY".to_string(), 64 | HttpMethod::Subscribe => "SUBSCRIBE".to_string(), 65 | HttpMethod::Unsubscribe => "UNSUBSCRIBE".to_string(), 66 | HttpMethod::Patch => "PATCH".to_string(), 67 | HttpMethod::Purge => "PURGE".to_string(), 68 | HttpMethod::MKCalendar => "MKCALENDAR".to_string(), 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/http_version.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | /// HTTP protocol version. 4 | #[derive(PartialEq, Eq, Clone, Copy, Debug)] 5 | pub struct HttpVersion { 6 | /// Major version 7 | pub major: u8, 8 | /// Minor version 9 | pub minor: u8, 10 | } 11 | 12 | impl fmt::Display for HttpVersion { 13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 14 | write!(f, "{}.{}", self.major, self.minor) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # The Rust HTTP Parser 2 | //! 3 | //! The Rust HTTP Parser provides the functionality to parse both HTTP requests and responses. 4 | //! 5 | //! It is ported from [joyent/http-parser](https://github.com/joyent/http-parser) written in C. 6 | //! 7 | //! # Install 8 | //! 9 | //! Add `http_parser` to `Cargo.toml`: 10 | //! 11 | //! ```toml 12 | //! [dependencies] 13 | //! http_parser = "0.0.2" 14 | //! ``` 15 | //! 16 | //! # Usage 17 | //! 18 | //! Define a callback struct: 19 | //! 20 | //! ``` 21 | //! # use http_parser::*; 22 | //! struct Callback; 23 | //! 24 | //! impl HttpParserCallback for Callback { 25 | //! fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 26 | //! println!("Message begin"); 27 | //! Ok(ParseAction::None) 28 | //! } 29 | //! 30 | //! // Override other functions as you wish 31 | //! } 32 | //! ``` 33 | //! 34 | //! Create an instance of `HttpParser` for requests: 35 | //! 36 | //! ``` 37 | //! # use http_parser::*; 38 | //! let mut parser = HttpParser::new(HttpParserType::Request); 39 | //! ``` 40 | //! 41 | //! Create an instance of `Callback` struct: 42 | //! 43 | //! ``` 44 | //! # use http_parser::*; 45 | //! # struct Callback; 46 | //! # 47 | //! # impl HttpParserCallback for Callback { 48 | //! # fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 49 | //! # println!("Message begin"); 50 | //! # Ok(ParseAction::None) 51 | //! # } 52 | //! # 53 | //! # // Override other functions as you wish 54 | //! # } 55 | //! let mut cb = Callback; 56 | //! ``` 57 | //! 58 | //! Execute the parser by providing a HTTP request: 59 | //! 60 | //! ``` 61 | //! # use http_parser::*; 62 | //! # struct Callback; 63 | //! # 64 | //! # impl HttpParserCallback for Callback { 65 | //! # fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 66 | //! # println!("Message begin"); 67 | //! # Ok(ParseAction::None) 68 | //! # } 69 | //! # 70 | //! # // Override other functions as you wish 71 | //! # } 72 | //! # let mut cb = Callback; 73 | //! # let mut parser = HttpParser::new(HttpParserType::Request); 74 | //! let line: &str = "GET / HTTP/1.1\r\n"; 75 | //! parser.execute(&mut cb, line.as_bytes()); 76 | //! ``` 77 | 78 | #![crate_name = "http_parser"] 79 | 80 | pub use self::parser::{HttpParser, HttpParserType}; 81 | pub use self::http_version::HttpVersion; 82 | pub use self::error::HttpErrno; 83 | pub use self::http_method::HttpMethod; 84 | pub use self::callback::{HttpParserCallback, CallbackResult, ParseAction}; 85 | 86 | mod parser; 87 | mod http_version; 88 | mod error; 89 | mod state; 90 | mod flags; 91 | mod http_method; 92 | mod callback; 93 | 94 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | //! The parser that parse requests and responses. 2 | 3 | use std::u64; 4 | use std::cmp; 5 | 6 | use state::{State, HeaderState}; 7 | use flags::Flags; 8 | use error::HttpErrno; 9 | use http_method::HttpMethod; 10 | use http_version::HttpVersion; 11 | use callback::{HttpParserCallback, ParseAction}; 12 | 13 | /// `HttpParserType` is a type specifies whether the parser is going to parse a HTTP request, 14 | /// response or both. 15 | #[derive(PartialEq, Eq, Clone, Copy)] 16 | pub enum HttpParserType { 17 | /// Parse request 18 | Request, 19 | /// Parse response 20 | Response, 21 | /// Parse both 22 | Both 23 | } 24 | 25 | /// The HTTP parser that parses requests and responses. 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// # use http_parser::*; 31 | /// # 32 | /// struct Callback; 33 | /// 34 | /// impl HttpParserCallback for Callback { 35 | /// fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 36 | /// println!("Message begin"); 37 | /// Ok(ParseAction::None) 38 | /// } 39 | /// 40 | /// // Override other functions as you wish 41 | /// } 42 | /// 43 | /// let mut parser = HttpParser::new(HttpParserType::Request); 44 | /// 45 | /// let mut cb = Callback; 46 | /// 47 | /// let line: &str = "GET / HTTP/1.1\r\n"; 48 | /// parser.execute(&mut cb, line.as_bytes()); 49 | /// ``` 50 | pub struct HttpParser { 51 | /// HTTP version of the request or response 52 | pub http_version: HttpVersion, 53 | /// Error number of there is an error in parsing 54 | pub errno: Option, 55 | /// Status code of the response 56 | pub status_code: Option, // response only 57 | /// HTTP method of the request 58 | pub method: Option, // request only 59 | 60 | /// whether the protocol is upgraded 61 | pub upgrade: bool, 62 | 63 | // TODO make it as a constructor parameter? 64 | /// whether using strict parsing mode 65 | pub strict: bool, // parsing using strict rules 66 | 67 | // private 68 | tp: HttpParserType, 69 | state: State, 70 | header_state: HeaderState, 71 | flags: u8, 72 | index: usize, // index into current matcher 73 | 74 | nread: usize, // bytes read in various scenarios 75 | content_length: u64, // bytes in body (0 if no Content-Length header) 76 | } 77 | 78 | //============== End of public interfaces =================== 79 | 80 | /// A macro that makes callback calls and check the returned value 81 | macro_rules! callback( 82 | ($parser:ident, $cb:expr, $err:expr, $idx:expr) => ( 83 | assert!($parser.errno.is_none()); 84 | match $cb { 85 | Err(..) => $parser.errno = Option::Some($err), 86 | _ => (), 87 | } 88 | 89 | if $parser.errno.is_some() { 90 | return $idx; 91 | } 92 | ); 93 | ); 94 | 95 | /// A macro that returns parsing error if it is in strict mode and the condition is not met. 96 | macro_rules! strict_check( 97 | ($parser:ident, $cond:expr, $idx:expr) => ( 98 | if $parser.strict && $cond { 99 | $parser.errno = Option::Some(HttpErrno::Strict); 100 | return $idx; 101 | } 102 | ); 103 | ); 104 | 105 | /// A macro that marks the index for any marker 106 | macro_rules! mark( 107 | ($mark:ident, $idx:expr) => ( 108 | if $mark.is_none() { 109 | $mark = Option::Some($idx); 110 | } 111 | ); 112 | ); 113 | 114 | const HTTP_MAX_HEADER_SIZE: usize = 80*1024; 115 | const ULLONG_MAX: u64 = u64::MAX; 116 | 117 | const CR: u8 = b'\r'; 118 | const LF: u8 = b'\n'; 119 | 120 | const PROXY_CONNECTION: &'static str = "proxy-connection"; 121 | const CONNECTION: &'static str = "connection"; 122 | const CONTENT_LENGTH: &'static str = "content-length"; 123 | const TRANSFER_ENCODING: &'static str = "transfer-encoding"; 124 | const UPGRADE: &'static str = "upgrade"; 125 | const CHUNKED: &'static str = "chunked"; 126 | const KEEP_ALIVE: &'static str = "keep-alive"; 127 | const CLOSE: &'static str = "close"; 128 | 129 | fn is_normal_header_char(ch: u8) -> bool { 130 | ch == b'!' || (ch >= b'#' && ch <= b'\'') /* #, $, %, &, ' */|| 131 | ch == b'*' || ch == b'+' || ch == b'-' || ch == b'.' || 132 | (ch >= b'0' && ch <= b'9') /* 0-9 */ || (ch >= b'A' && ch <= b'Z') /* A-Z */ || 133 | (ch >= b'^' && ch <= b'z') /* ^, _, `, a-z */ || ch == b'|' || ch == b'~' 134 | } 135 | 136 | fn is_header_char(strict: bool, ch: u8) -> bool { 137 | if strict { 138 | is_normal_header_char(ch) 139 | } else { 140 | ch == b' ' || is_normal_header_char(ch) 141 | } 142 | } 143 | 144 | fn is_normal_url_char(ch: u8) -> bool { 145 | // refer to http_parser.c or ascii table for characters 146 | ch == b'!' || ch == b'"' || (ch >= b'$' && ch <= b'>') || (ch >= b'@' && ch <= b'~') 147 | } 148 | 149 | fn is_url_char(strict: bool, ch: u8) -> bool { 150 | is_normal_url_char(ch) || (!strict && ((ch & 0x80) > 0 || ch == 9 || ch == 12)) 151 | } 152 | 153 | fn unhex_value(ch: u8) -> Option { 154 | if ch >= b'0' && ch <= b'9' { 155 | Option::Some((ch - b'0') as i32) 156 | } else if ch >= b'a' && ch <= b'f' { 157 | Option::Some((10 + ch - b'a') as i32) 158 | } else if ch >= b'A' && ch <= b'F' { 159 | Option::Some((10 + ch - b'A') as i32) 160 | } else { 161 | Option::None 162 | } 163 | } 164 | 165 | fn lower(ch: u8) -> u8 { 166 | ch | 0x20 167 | } 168 | 169 | fn is_num(ch: u8) -> bool { 170 | ch >= b'0' && ch <= b'9' 171 | } 172 | 173 | fn is_alpha(ch: u8) -> bool { 174 | (ch >= b'a' && ch <= b'z') || (ch >= b'A' && ch <= b'Z') 175 | } 176 | 177 | fn is_alphanum(ch: u8) -> bool { 178 | is_num(ch) || is_alpha(ch) 179 | } 180 | 181 | fn is_mark(ch: u8) -> bool { 182 | ch == b'-' || ch == b'_' || ch == b'.' || ch == b'!' || ch == b'~' || 183 | ch == b'*' || ch == b'\'' || ch == b'(' || ch == b')' 184 | } 185 | 186 | fn is_userinfo_char(ch: u8) -> bool { 187 | is_alphanum(ch) || is_mark(ch) || ch == b'%' || 188 | ch == b';' || ch == b':' || ch == b'&' || ch == b'=' || 189 | ch == b'+' || ch == b'$' || ch == b',' 190 | } 191 | 192 | impl HttpParser { 193 | /// Creates a parser of the specified type. 194 | /// 195 | /// # Example 196 | /// 197 | /// ``` 198 | /// # use http_parser::*; 199 | /// let mut parser = HttpParser::new(HttpParserType::Request); 200 | /// ``` 201 | pub fn new(tp: HttpParserType) -> HttpParser { 202 | HttpParser { 203 | tp: tp, 204 | state: match tp { 205 | HttpParserType::Request => State::StartReq, 206 | HttpParserType::Response => State::StartRes, 207 | HttpParserType::Both => State::StartReqOrRes, 208 | }, 209 | header_state: HeaderState::General, 210 | flags: 0, 211 | index: 0, 212 | nread: 0, 213 | content_length: ULLONG_MAX, 214 | http_version: HttpVersion { major: 1, minor: 0 }, 215 | errno: Option::None, 216 | status_code: Option::None, 217 | method: Option::None, 218 | upgrade: false, 219 | strict: true, 220 | } 221 | } 222 | 223 | /// Parses the HTTP requests or responses, specified in `data` as an array of bytes. 224 | /// 225 | /// # Example 226 | /// 227 | /// ``` 228 | /// # use http_parser::*; 229 | /// # struct Callback; 230 | /// # 231 | /// # impl HttpParserCallback for Callback { 232 | /// # fn on_message_begin(&mut self, parser: &mut HttpParser) -> CallbackResult { 233 | /// # println!("Message begin"); 234 | /// # Ok(ParseAction::None) 235 | /// # } 236 | /// # 237 | /// # // Override other functions as you wish 238 | /// # } 239 | /// let mut parser = HttpParser::new(HttpParserType::Request); 240 | /// 241 | /// let mut cb = Callback; 242 | /// 243 | /// let line: &str = "GET / HTTP/1.1\r\n"; 244 | /// parser.execute(&mut cb, line.as_bytes()); 245 | /// ``` 246 | pub fn execute(&mut self, cb: &mut T, data: &[u8]) -> usize { 247 | let len: usize = data.len(); 248 | let mut index: usize = 0; 249 | let mut header_field_mark: Option = Option::None; 250 | let mut header_value_mark: Option = Option::None; 251 | let mut url_mark: Option = Option::None; 252 | let mut body_mark: Option = Option::None; 253 | let mut status_mark: Option = Option::None; 254 | 255 | if self.errno.is_some() { 256 | return 0; 257 | } 258 | 259 | if len == 0 { // mean EOF 260 | match self.state { 261 | State::BodyIdentityEof => { 262 | callback!(self, cb.on_message_complete(self), 263 | HttpErrno::CBMessageComplete, index); 264 | return 0; 265 | }, 266 | State::Dead | 267 | State::StartReqOrRes | 268 | State::StartReq | 269 | State::StartRes => { 270 | return 0; 271 | }, 272 | _ => { 273 | self.errno = Option::Some(HttpErrno::InvalidEofState); 274 | // This is from parser.c, but it doesn't make sense to me. 275 | // return 1; 276 | return 0; 277 | } 278 | } 279 | } 280 | 281 | if self.state == State::HeaderField { 282 | header_field_mark = Option::Some(0); 283 | } 284 | if self.state == State::HeaderValue { 285 | header_value_mark = Option::Some(0); 286 | } 287 | match self.state { 288 | State::ReqPath | 289 | State::ReqSchema | 290 | State::ReqSchemaSlash | 291 | State::ReqSchemaSlashSlash | 292 | State::ReqServerStart | 293 | State::ReqServer | 294 | State::ReqServerWithAt | 295 | State::ReqQueryStringStart | 296 | State::ReqQueryString | 297 | State::ReqFragmentStart | 298 | State::ReqFragment => url_mark = Option::Some(0), 299 | State::ResStatus => status_mark = Option::Some(0), 300 | _ => (), 301 | } 302 | 303 | while index < len { 304 | let ch = data[index]; 305 | if self.state.is_header_state() { 306 | self.nread += 1; 307 | 308 | // Comments from http_parser.c: 309 | // Don't allow the total size of the HTTP headers (including the status 310 | // line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect 311 | // embedders against denial-of-service attacks where the attacker feeds 312 | // us a never-ending header that the embedder keeps buffering. 313 | // 314 | // This check is arguably the responsibility of embedders but we're doing 315 | // it on the embedder's behalf because most won't bother and this way we 316 | // make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger 317 | // than any reasonable request or response so this should never affect 318 | // day-to-day operation. 319 | if self.nread > HTTP_MAX_HEADER_SIZE { 320 | self.errno = Option::Some(HttpErrno::HeaderOverflow); 321 | return index; 322 | } 323 | } 324 | 325 | // using loop to simulate 'goto reexecute_byte' in http_parser.c 326 | loop { 327 | let mut retry = false; 328 | match self.state { 329 | State::Dead => { 330 | if ch != CR && ch != LF { 331 | self.errno = Option::Some(HttpErrno::ClosedConnection); 332 | return index; 333 | } 334 | }, 335 | State::StartReqOrRes => { 336 | if ch != CR && ch != LF { 337 | self.flags = 0; 338 | self.content_length = ULLONG_MAX; 339 | 340 | if ch == b'H' { 341 | self.state = State::ResOrRespH; 342 | callback!(self, cb.on_message_begin(self), HttpErrno::CBMessageBegin, index+1); 343 | } else { 344 | self.tp = HttpParserType::Request; 345 | self.state = State::StartReq; 346 | retry = true; 347 | } 348 | } 349 | }, 350 | State::ResOrRespH => { 351 | if ch == b'T' { 352 | self.tp = HttpParserType::Response; 353 | self.state = State::ResHT; 354 | } else { 355 | if ch != b'E' { 356 | self.errno = Option::Some(HttpErrno::InvalidConstant); 357 | return index; 358 | } 359 | 360 | self.tp = HttpParserType::Request; 361 | self.method = Option::Some(HttpMethod::Head); 362 | self.index = 2; 363 | self.state = State::ReqMethod; 364 | } 365 | }, 366 | State::StartRes => { 367 | self.flags = 0; 368 | self.content_length = ULLONG_MAX; 369 | 370 | match ch { 371 | b'H' => self.state = State::ResH, 372 | CR | LF => (), 373 | _ => { 374 | self.errno = Option::Some(HttpErrno::InvalidConstant); 375 | return index; 376 | }, 377 | } 378 | 379 | callback!(self, cb.on_message_begin(self), HttpErrno::CBMessageBegin, index+1); 380 | }, 381 | State::ResH => { 382 | strict_check!(self, ch != b'T', index); 383 | self.state = State::ResHT; 384 | }, 385 | State::ResHT => { 386 | strict_check!(self, ch != b'T', index); 387 | self.state = State::ResHTT; 388 | }, 389 | State::ResHTT => { 390 | strict_check!(self, ch != b'P', index); 391 | self.state = State::ResHTTP; 392 | }, 393 | State::ResHTTP => { 394 | strict_check!(self, ch != b'/', index); 395 | self.state = State::ResFirstHttpMajor; 396 | }, 397 | State::ResFirstHttpMajor => { 398 | if !is_num(ch) { 399 | self.errno = Option::Some(HttpErrno::InvalidVersion); 400 | return index; 401 | } 402 | self.http_version.major = ch - b'0'; 403 | self.state = State::ResHttpMajor; 404 | }, 405 | State::ResHttpMajor => { 406 | if ch == b'.' { 407 | self.state = State::ResFirstHttpMinor; 408 | } else { 409 | if !is_num(ch) { 410 | self.errno = Option::Some(HttpErrno::InvalidVersion); 411 | return index; 412 | } 413 | 414 | self.http_version.major *= 10; 415 | self.http_version.major += ch - b'0'; 416 | 417 | if self.http_version.major > 99 { 418 | self.errno = Option::Some(HttpErrno::InvalidVersion); 419 | return index; 420 | } 421 | } 422 | }, 423 | State::ResFirstHttpMinor => { 424 | if !is_num(ch) { 425 | self.errno = Option::Some(HttpErrno::InvalidVersion); 426 | return index; 427 | } 428 | 429 | self.http_version.minor = ch - b'0'; 430 | self.state = State::ResHttpMinor; 431 | }, 432 | // minor HTTP version or end of request line 433 | State::ResHttpMinor => { 434 | if ch == b' ' { 435 | self.state = State::ResFirstStatusCode; 436 | } else { 437 | if !is_num(ch) { 438 | self.errno = Option::Some(HttpErrno::InvalidVersion); 439 | return index; 440 | } 441 | 442 | self.http_version.minor *= 10; 443 | self.http_version.minor += ch - b'0'; 444 | 445 | if self.http_version.minor > 99 { 446 | self.errno = Option::Some(HttpErrno::InvalidVersion); 447 | return index; 448 | } 449 | } 450 | }, 451 | State::ResFirstStatusCode => { 452 | if !is_num(ch) { 453 | if ch != b' ' { 454 | self.errno = Option::Some(HttpErrno::InvalidStatus); 455 | return index; 456 | } 457 | } else { 458 | self.status_code = Option::Some((ch - b'0') as u16); 459 | self.state = State::ResStatusCode; 460 | } 461 | }, 462 | State::ResStatusCode => { 463 | if !is_num(ch) { 464 | match ch { 465 | b' ' => self.state = State::ResStatusStart, 466 | CR => self.state = State::ResLineAlmostDone, 467 | LF => self.state = State::HeaderFieldStart, 468 | _ => { 469 | self.errno = Option::Some(HttpErrno::InvalidStatus); 470 | return index; 471 | } 472 | } 473 | } else { 474 | let mut status_code = self.status_code.unwrap_or(0); 475 | status_code *= 10; 476 | status_code += (ch - b'0') as u16; 477 | self.status_code = Option::Some(status_code); 478 | 479 | if status_code > 999 { 480 | self.errno = Option::Some(HttpErrno::InvalidStatus); 481 | return index; 482 | } 483 | } 484 | }, 485 | State::ResStatusStart => { 486 | if ch == CR { 487 | self.state = State::ResLineAlmostDone; 488 | } else if ch == LF { 489 | self.state = State::HeaderFieldStart; 490 | } else { 491 | mark!(status_mark, index); 492 | self.state = State::ResStatus; 493 | self.index = 0; 494 | } 495 | }, 496 | State::ResStatus => { 497 | if ch == CR || ch == LF { 498 | self.state = if ch == CR { State::ResLineAlmostDone } else { State::HeaderFieldStart }; 499 | if status_mark.is_some() { 500 | callback!(self, 501 | cb.on_status(self, &data[status_mark.unwrap() .. index]), 502 | HttpErrno::CBStatus, index+1); 503 | status_mark = Option::None; 504 | } 505 | } 506 | }, 507 | State::ResLineAlmostDone => { 508 | strict_check!(self, ch != LF, index); 509 | self.state = State::HeaderFieldStart; 510 | }, 511 | State::StartReq => { 512 | if ch != CR && ch != LF { 513 | self.flags = 0; 514 | self.content_length = ULLONG_MAX; 515 | 516 | if !is_alpha(ch) { 517 | self.errno = Option::Some(HttpErrno::InvalidMethod); 518 | return index; 519 | } 520 | 521 | match ch { 522 | b'C' => self.method = Option::Some(HttpMethod::Connect), // or Copy, Checkout 523 | b'D' => self.method = Option::Some(HttpMethod::Delete), 524 | b'G' => self.method = Option::Some(HttpMethod::Get), 525 | b'H' => self.method = Option::Some(HttpMethod::Head), 526 | b'L' => self.method = Option::Some(HttpMethod::Lock), 527 | b'M' => self.method = Option::Some(HttpMethod::MKCol), // or Move, MKActivity, Merge, MSearch, MKCalendar 528 | b'N' => self.method = Option::Some(HttpMethod::Notify), 529 | b'O' => self.method = Option::Some(HttpMethod::Options), 530 | b'P' => self.method = Option::Some(HttpMethod::Post), // or PropFind|PropPatch|Put|Patch|Purge 531 | b'R' => self.method = Option::Some(HttpMethod::Report), 532 | b'S' => self.method = Option::Some(HttpMethod::Subscribe), // or Search 533 | b'T' => self.method = Option::Some(HttpMethod::Trace), 534 | b'U' => self.method = Option::Some(HttpMethod::Unlock), // or Unsubscribe 535 | _ => { 536 | self.errno = Option::Some(HttpErrno::InvalidMethod); 537 | return index; 538 | }, 539 | } 540 | self.index = 1; 541 | self.state = State::ReqMethod; 542 | 543 | callback!(self, cb.on_message_begin(self), 544 | HttpErrno::CBMessageBegin, index+1); 545 | } 546 | }, 547 | State::ReqMethod => { 548 | let matcher = self.method.unwrap().to_string(); 549 | if ch == b' ' && self.index == matcher.len() { 550 | self.state = State::ReqSpacesBeforeUrl; 551 | } else if self.index < matcher.len() && ch == (matcher[self.index ..].bytes().next().unwrap()) { 552 | ; 553 | } else if self.method == Option::Some(HttpMethod::Connect) { 554 | if self.index == 1 && ch == b'H' { 555 | self.method = Option::Some(HttpMethod::Checkout); 556 | } else if self.index == 2 && ch == b'P' { 557 | self.method = Option::Some(HttpMethod::Copy); 558 | } else { 559 | self.errno = Option::Some(HttpErrno::InvalidMethod); 560 | return index; 561 | } 562 | } else if self.method == Option::Some(HttpMethod::MKCol) { 563 | if self.index == 1 && ch == b'O' { 564 | self.method = Option::Some(HttpMethod::Move); 565 | } else if self.index == 1 && ch == b'E' { 566 | self.method = Option::Some(HttpMethod::Merge); 567 | } else if self.index == 1 && ch == b'-' { 568 | self.method = Option::Some(HttpMethod::MSearch); 569 | } else if self.index == 2 && ch == b'A' { 570 | self.method = Option::Some(HttpMethod::MKActivity); 571 | } else if self.index == 3 && ch == b'A' { 572 | self.method = Option::Some(HttpMethod::MKCalendar); 573 | } else { 574 | self.errno = Option::Some(HttpErrno::InvalidMethod); 575 | return index; 576 | } 577 | } else if self.method == Option::Some(HttpMethod::Subscribe) { 578 | if self.index == 1 && ch == b'E' { 579 | self.method = Option::Some(HttpMethod::Search); 580 | } else { 581 | self.errno = Option::Some(HttpErrno::InvalidMethod); 582 | return index; 583 | } 584 | } else if self.index == 1 && self.method == Option::Some(HttpMethod::Post) { 585 | if ch == b'R' { 586 | self.method = Option::Some(HttpMethod::PropFind); // or PropPatch 587 | } else if ch == b'U' { 588 | self.method = Option::Some(HttpMethod::Put); // or Purge 589 | } else if ch == b'A' { 590 | self.method = Option::Some(HttpMethod::Patch); 591 | } else { 592 | self.errno = Option::Some(HttpErrno::InvalidMethod); 593 | return index; 594 | } 595 | } else if self.index == 2 { 596 | if self.method == Option::Some(HttpMethod::Put) { 597 | if ch == b'R' { 598 | self.method = Option::Some(HttpMethod::Purge); 599 | } else { 600 | self.errno = Option::Some(HttpErrno::InvalidMethod); 601 | return index; 602 | } 603 | } else if self.method == Option::Some(HttpMethod::Unlock) { 604 | if ch == b'S' { 605 | self.method = Option::Some(HttpMethod::Unsubscribe); 606 | } else { 607 | self.errno = Option::Some(HttpErrno::InvalidMethod); 608 | return index; 609 | } 610 | } else { 611 | self.errno = Option::Some(HttpErrno::InvalidMethod); 612 | return index; 613 | } 614 | } else if self.index == 4 && self.method == Option::Some(HttpMethod::PropFind) && ch == b'P' { 615 | self.method = Option::Some(HttpMethod::PropPatch); 616 | } else { 617 | self.errno = Option::Some(HttpErrno::InvalidMethod); 618 | return index; 619 | } 620 | 621 | self.index += 1; 622 | }, 623 | State::ReqSpacesBeforeUrl => { 624 | if ch != b' ' { 625 | mark!(url_mark, index); 626 | if self.method == Option::Some(HttpMethod::Connect) { 627 | self.state = State::ReqServerStart; 628 | } 629 | 630 | self.state = HttpParser::parse_url_char(self, self.state, ch); 631 | if self.state == State::Dead { 632 | self.errno = Option::Some(HttpErrno::InvalidUrl); 633 | return index; 634 | } 635 | } 636 | }, 637 | State::ReqSchema | 638 | State::ReqSchemaSlash | 639 | State::ReqSchemaSlashSlash | 640 | State::ReqServerStart => { 641 | match ch { 642 | // No whitespace allowed here 643 | b' ' | CR | LF => { 644 | self.errno = Option::Some(HttpErrno::InvalidUrl); 645 | return index; 646 | }, 647 | _ => { 648 | self.state = HttpParser::parse_url_char(self, self.state, ch); 649 | if self.state == State::Dead { 650 | self.errno = Option::Some(HttpErrno::InvalidUrl); 651 | return index; 652 | } 653 | } 654 | } 655 | }, 656 | State::ReqServer | 657 | State::ReqServerWithAt | 658 | State::ReqPath | 659 | State::ReqQueryStringStart | 660 | State::ReqQueryString | 661 | State::ReqFragmentStart | 662 | State::ReqFragment => { 663 | match ch { 664 | b' ' => { 665 | self.state = State::ReqHttpStart; 666 | if url_mark.is_some() { 667 | callback!(self, 668 | cb.on_url(self, &data[url_mark.unwrap() .. index]), 669 | HttpErrno::CBUrl, index+1); 670 | url_mark = Option::None; 671 | } 672 | }, 673 | CR | LF => { 674 | self.http_version.major = 0; 675 | self.http_version.minor = 9; 676 | self.state = if ch == CR { 677 | State::ReqLineAlmostDone 678 | } else { 679 | State::HeaderFieldStart 680 | }; 681 | if url_mark.is_some() { 682 | callback!(self, 683 | cb.on_url(self, &data[url_mark.unwrap() .. index]), 684 | HttpErrno::CBUrl, index+1); 685 | url_mark = Option::None; 686 | } 687 | }, 688 | _ => { 689 | self.state = HttpParser::parse_url_char(self, self.state, ch); 690 | if self.state == State::Dead { 691 | self.errno = Option::Some(HttpErrno::InvalidUrl); 692 | return index; 693 | } 694 | } 695 | } 696 | }, 697 | State::ReqHttpStart => { 698 | match ch { 699 | b'H' => self.state = State::ReqHttpH, 700 | b' ' => (), 701 | _ => { 702 | self.errno = Option::Some(HttpErrno::InvalidConstant); 703 | return index; 704 | } 705 | } 706 | }, 707 | State::ReqHttpH => { 708 | strict_check!(self, ch != b'T', index); 709 | self.state = State::ReqHttpHT; 710 | }, 711 | State::ReqHttpHT => { 712 | strict_check!(self, ch != b'T', index); 713 | self.state = State::ReqHttpHTT; 714 | }, 715 | State::ReqHttpHTT => { 716 | strict_check!(self, ch != b'P', index); 717 | self.state = State::ReqHttpHTTP; 718 | }, 719 | State::ReqHttpHTTP => { 720 | strict_check!(self, ch != b'/', index); 721 | self.state = State::ReqFirstHttpMajor; 722 | }, 723 | // first digit of major HTTP version 724 | State::ReqFirstHttpMajor => { 725 | if ch < b'1' || ch > b'9' { 726 | self.errno = Option::Some(HttpErrno::InvalidVersion); 727 | return index; 728 | } 729 | 730 | self.http_version.major = ch - b'0'; 731 | self.state = State::ReqHttpMajor; 732 | }, 733 | // major HTTP version or dot 734 | State::ReqHttpMajor => { 735 | if ch == b'.' { 736 | self.state = State::ReqFirstHttpMinor; 737 | } else { 738 | if !is_num(ch) { 739 | self.errno = Option::Some(HttpErrno::InvalidVersion); 740 | return index; 741 | } 742 | 743 | self.http_version.major *= 10; 744 | self.http_version.major += ch - b'0'; 745 | 746 | if self.http_version.major > 99 { 747 | self.errno = Option::Some(HttpErrno::InvalidVersion); 748 | return index; 749 | } 750 | } 751 | }, 752 | // first digit of minor HTTP version 753 | State::ReqFirstHttpMinor => { 754 | if !is_num(ch) { 755 | self.errno = Option::Some(HttpErrno::InvalidVersion); 756 | return index; 757 | } 758 | 759 | self.http_version.minor = ch - b'0'; 760 | self.state = State::ReqHttpMinor; 761 | }, 762 | // minor HTTP version or end of request line 763 | State::ReqHttpMinor => { 764 | if ch == CR { 765 | self.state = State::ReqLineAlmostDone; 766 | } else if ch == LF { 767 | self.state = State::HeaderFieldStart; 768 | } else if is_num(ch) { 769 | self.http_version.minor *= 10; 770 | self.http_version.minor += ch - b'0'; 771 | 772 | if self.http_version.minor > 99 { 773 | self.errno = Option::Some(HttpErrno::InvalidVersion); 774 | return index; 775 | } 776 | } else { 777 | self.errno = Option::Some(HttpErrno::InvalidVersion); 778 | return index; 779 | } 780 | }, 781 | // end of request line 782 | State::ReqLineAlmostDone => { 783 | if ch != LF { 784 | self.errno = Option::Some(HttpErrno::LFExpected); 785 | return index; 786 | } 787 | 788 | self.state = State::HeaderFieldStart; 789 | }, 790 | State::HeaderFieldStart => { 791 | if ch == CR { 792 | self.state = State::HeadersAlmostDone; 793 | } else if ch == LF { 794 | // they might be just sending \n instead of \r\n, 795 | // so this would be the second \n to denote 796 | // the end of headers 797 | self.state = State::HeadersAlmostDone; 798 | retry = true; 799 | } else { 800 | if !is_header_char(self.strict, ch) { 801 | self.errno = Option::Some(HttpErrno::InvalidHeaderToken); 802 | return index; 803 | } 804 | 805 | mark!(header_field_mark, index); 806 | 807 | self.index = 0; 808 | self.state = State::HeaderField; 809 | 810 | match ch { 811 | b'c' | b'C' => self.header_state = HeaderState::C, 812 | b'p' | b'P' => self.header_state = HeaderState::MatchingProxyConnection, 813 | b't' | b'T' => self.header_state = HeaderState::MatchingTransferEncoding, 814 | b'u' | b'U' => self.header_state = HeaderState::MatchingUpgrade, 815 | _ => self.header_state = HeaderState::General, 816 | } 817 | } 818 | }, 819 | State::HeaderField => { 820 | if is_header_char(self.strict, ch) { 821 | let c : u8 = lower(ch); 822 | match self.header_state { 823 | HeaderState::General => (), 824 | HeaderState::C => { 825 | self.index += 1; 826 | self.header_state = if c == b'o'{ 827 | HeaderState::CO 828 | } else { 829 | HeaderState::General 830 | }; 831 | }, 832 | HeaderState::CO => { 833 | self.index += 1; 834 | self.header_state = if c == b'n' { 835 | HeaderState::CON 836 | } else { 837 | HeaderState::General 838 | }; 839 | }, 840 | HeaderState::CON => { 841 | self.index += 1; 842 | match c { 843 | b'n' => self.header_state = HeaderState::MatchingConnection, 844 | b't' => self.header_state = HeaderState::MatchingContentLength, 845 | _ => self.header_state = HeaderState::General, 846 | } 847 | }, 848 | // connection 849 | HeaderState::MatchingConnection => { 850 | self.index += 1; 851 | if self.index >= CONNECTION.len() || 852 | c != (CONNECTION[self.index ..].bytes().next().unwrap()) { 853 | self.header_state = HeaderState::General; 854 | } else if self.index == CONNECTION.len()-1 { 855 | self.header_state = HeaderState::Connection; 856 | } 857 | }, 858 | // proxy-connection 859 | HeaderState::MatchingProxyConnection => { 860 | self.index += 1; 861 | if self.index >= PROXY_CONNECTION.len() || 862 | c != (PROXY_CONNECTION[self.index ..].bytes().next().unwrap()) { 863 | self.header_state = HeaderState::General; 864 | } else if self.index == PROXY_CONNECTION.len()-1 { 865 | self.header_state = HeaderState::Connection; 866 | } 867 | }, 868 | // content-length 869 | HeaderState::MatchingContentLength => { 870 | self.index += 1; 871 | if self.index >= CONTENT_LENGTH.len() || 872 | c != (CONTENT_LENGTH[self.index ..].bytes().next().unwrap()) { 873 | self.header_state = HeaderState::General; 874 | } else if self.index == CONTENT_LENGTH.len()-1 { 875 | self.header_state = HeaderState::ContentLength; 876 | } 877 | }, 878 | // transfer-encoding 879 | HeaderState::MatchingTransferEncoding => { 880 | self.index += 1; 881 | if self.index >= TRANSFER_ENCODING.len() || 882 | c != (TRANSFER_ENCODING[self.index ..].bytes().next().unwrap()) { 883 | self.header_state = HeaderState::General; 884 | } else if self.index == TRANSFER_ENCODING.len()-1 { 885 | self.header_state = HeaderState::TransferEncoding; 886 | } 887 | }, 888 | // upgrade 889 | HeaderState::MatchingUpgrade => { 890 | self.index += 1; 891 | if self.index >= UPGRADE.len() || 892 | c != (UPGRADE[self.index ..].bytes().next().unwrap()) { 893 | self.header_state = HeaderState::General; 894 | } else if self.index == UPGRADE.len()-1 { 895 | self.header_state = HeaderState::Upgrade; 896 | } 897 | }, 898 | HeaderState::Connection | 899 | HeaderState::ContentLength | 900 | HeaderState::TransferEncoding | 901 | HeaderState::Upgrade => { 902 | if ch != b' ' { 903 | self.header_state = HeaderState::General; 904 | } 905 | }, 906 | _ => { 907 | panic!("Unknown header_state"); 908 | } 909 | } 910 | } else if ch == b':' { 911 | self.state = State::HeaderValueDiscardWs; 912 | if header_field_mark.is_some() { 913 | callback!(self, 914 | cb.on_header_field(self, &data[header_field_mark.unwrap() .. index]), 915 | HttpErrno::CBHeaderField, index+1); 916 | header_field_mark = Option::None; 917 | } 918 | } else { 919 | self.errno = Option::Some(HttpErrno::InvalidHeaderToken); 920 | return index; 921 | } 922 | }, 923 | State::HeaderValueDiscardWs if ch == b' ' || ch == b'\t' || 924 | ch == CR || ch == LF => { 925 | if ch == b' ' || ch == b'\t' { 926 | ; 927 | } else if ch == CR { 928 | self.state = State::HeaderValueDiscardWsAlmostDone; 929 | } else if ch == LF { 930 | self.state = State::HeaderValueDiscardLws; 931 | } 932 | }, 933 | State::HeaderValueDiscardWs | 934 | State::HeaderValueStart => { 935 | mark!(header_value_mark, index); 936 | 937 | self.state = State::HeaderValue; 938 | self.index = 0; 939 | 940 | let c: u8 = lower(ch); 941 | 942 | match self.header_state { 943 | HeaderState::Upgrade => { 944 | self.flags |= Flags::Upgrade.as_u8(); 945 | self.header_state = HeaderState::General; 946 | }, 947 | HeaderState::TransferEncoding => { 948 | // looking for 'Transfer-Encoding: chunked 949 | if c == b'c' { 950 | self.header_state = HeaderState::MatchingTransferEncodingChunked; 951 | } else { 952 | self.header_state = HeaderState::General; 953 | } 954 | }, 955 | HeaderState::ContentLength => { 956 | if !is_num(ch) { 957 | self.errno = Option::Some(HttpErrno::InvalidContentLength); 958 | return index; 959 | } 960 | 961 | self.content_length = (ch - b'0') as u64; 962 | }, 963 | HeaderState::Connection => { 964 | // looking for 'Connection: keep-alive 965 | if c == b'k' { 966 | self.header_state = HeaderState::MatchingConnectionKeepAlive; 967 | // looking for 'Connection: close 968 | } else if c == b'c' { 969 | self.header_state = HeaderState::MatchingConnectionClose; 970 | } else { 971 | self.header_state = HeaderState::General; 972 | } 973 | }, 974 | _ => self.header_state = HeaderState::General, 975 | } 976 | }, 977 | State::HeaderValue => { 978 | if ch == CR { 979 | self.state = State::HeaderAlmostDone; 980 | if header_value_mark.is_some() { 981 | callback!(self, 982 | cb.on_header_value(self, &data[header_value_mark.unwrap() .. index]), 983 | HttpErrno::CBHeaderValue, index+1); 984 | header_value_mark = Option::None; 985 | } 986 | } else if ch == LF { 987 | self.state = State::HeaderAlmostDone; 988 | if header_value_mark.is_some() { 989 | callback!(self, 990 | cb.on_header_value(self, &data[header_value_mark.unwrap() .. index]), 991 | HttpErrno::CBHeaderValue, index); 992 | header_value_mark = Option::None; 993 | } 994 | retry = true; 995 | } else { 996 | let c: u8 = lower(ch); 997 | 998 | match self.header_state { 999 | HeaderState::General => (), 1000 | HeaderState::Connection | HeaderState::TransferEncoding => { 1001 | panic!("Shouldn't get here."); 1002 | }, 1003 | HeaderState::ContentLength => { 1004 | if ch != b' ' { 1005 | if !is_num(ch) { 1006 | self.errno = Option::Some(HttpErrno::InvalidContentLength); 1007 | return index; 1008 | } 1009 | 1010 | // Overflow? Test against a conservative 1011 | // limit for simplicity 1012 | if (ULLONG_MAX - 10) / 10 < self.content_length { 1013 | self.errno = Option::Some(HttpErrno::InvalidContentLength); 1014 | return index; 1015 | } 1016 | 1017 | let mut t: u64 = self.content_length; 1018 | t *= 10; 1019 | t += (ch - b'0') as u64; 1020 | 1021 | self.content_length = t; 1022 | } 1023 | }, 1024 | // Transfer-Encoding: chunked 1025 | HeaderState::MatchingTransferEncodingChunked => { 1026 | self.index += 1; 1027 | if self.index >= CHUNKED.len() || 1028 | c != (CHUNKED[self.index ..].bytes().next().unwrap()) { 1029 | self.header_state = HeaderState::General; 1030 | } else if self.index == CHUNKED.len()-1 { 1031 | self.header_state = HeaderState::TransferEncodingChunked; 1032 | } 1033 | }, 1034 | // looking for 'Connection: keep-alive 1035 | HeaderState::MatchingConnectionKeepAlive => { 1036 | self.index += 1; 1037 | if self.index >= KEEP_ALIVE.len() || 1038 | c != (KEEP_ALIVE[self.index ..].bytes().next().unwrap()) { 1039 | self.header_state = HeaderState::General; 1040 | } else if self.index == KEEP_ALIVE.len()-1 { 1041 | self.header_state = HeaderState::ConnectionKeepAlive; 1042 | } 1043 | } 1044 | // looking for 'Connection: close 1045 | HeaderState::MatchingConnectionClose => { 1046 | self.index += 1; 1047 | if self.index >= CLOSE.len() || 1048 | c != (CLOSE[self.index ..].bytes().next().unwrap()) { 1049 | self.header_state = HeaderState::General; 1050 | } else if self.index == CLOSE.len()-1 { 1051 | self.header_state = HeaderState::ConnectionClose; 1052 | } 1053 | }, 1054 | HeaderState::TransferEncodingChunked | 1055 | HeaderState::ConnectionKeepAlive | 1056 | HeaderState::ConnectionClose => { 1057 | if ch != b' ' { 1058 | self.header_state = HeaderState::General; 1059 | } 1060 | }, 1061 | _ => { 1062 | self.state = State::HeaderValue; 1063 | self.header_state = HeaderState::General; 1064 | } 1065 | } 1066 | } 1067 | }, 1068 | State::HeaderAlmostDone => { 1069 | strict_check!(self, ch != LF, index); 1070 | self.state = State::HeaderValueLws; 1071 | }, 1072 | State::HeaderValueLws => { 1073 | if ch == b' ' || ch == b'\t' { 1074 | self.state = State::HeaderValueStart; 1075 | retry = true; 1076 | } else { 1077 | // finished the header 1078 | match self.header_state { 1079 | HeaderState::ConnectionKeepAlive => { 1080 | self.flags |= Flags::ConnectionKeepAlive.as_u8(); 1081 | }, 1082 | HeaderState::ConnectionClose => { 1083 | self.flags |= Flags::ConnectionClose.as_u8(); 1084 | }, 1085 | HeaderState::TransferEncodingChunked => { 1086 | self.flags |= Flags::Chunked.as_u8(); 1087 | }, 1088 | _ => (), 1089 | } 1090 | 1091 | self.state = State::HeaderFieldStart; 1092 | retry = true; 1093 | } 1094 | }, 1095 | State::HeaderValueDiscardWsAlmostDone => { 1096 | strict_check!(self, ch != LF, index); 1097 | self.state = State::HeaderValueDiscardLws; 1098 | }, 1099 | State::HeaderValueDiscardLws => { 1100 | if ch == b' ' || ch == b'\t' { 1101 | self.state = State::HeaderValueDiscardWs; 1102 | } else { 1103 | // header value was empty 1104 | mark!(header_value_mark, index); 1105 | self.state = State::HeaderFieldStart; 1106 | if header_value_mark.is_some() { 1107 | callback!(self, 1108 | cb.on_header_value(self, &data[header_value_mark.unwrap() .. index]), 1109 | HttpErrno::CBHeaderValue, index); 1110 | header_value_mark = Option::None; 1111 | } 1112 | retry = true; 1113 | } 1114 | }, 1115 | State::HeadersAlmostDone => { 1116 | strict_check!(self, ch != LF, index); 1117 | 1118 | if (self.flags & Flags::Trailing.as_u8()) > 0 { 1119 | // End of a chunked request 1120 | self.new_message(); 1121 | callback!(self, cb.on_message_complete(self), 1122 | HttpErrno::CBMessageComplete, index+1); 1123 | } else { 1124 | self.state = State::HeadersDone; 1125 | 1126 | // Set this here so that on_headers_complete() 1127 | // callbacks can see it 1128 | self.upgrade = (self.flags & Flags::Upgrade.as_u8() != 0) || 1129 | self.method == Option::Some(HttpMethod::Connect); 1130 | 1131 | match cb.on_headers_complete(self) { 1132 | Ok(ParseAction::None) => (), 1133 | Ok(ParseAction::SkipBody) => self.flags |= Flags::SkipBody.as_u8(), 1134 | _ => { 1135 | self.errno = Option::Some(HttpErrno::CBHeadersComplete); 1136 | return index; // Error 1137 | }, 1138 | } 1139 | 1140 | if self.errno.is_some() { 1141 | return index; 1142 | } 1143 | retry = true; 1144 | } 1145 | }, 1146 | State::HeadersDone => { 1147 | strict_check!(self, ch != LF, index); 1148 | self.nread = 0; 1149 | 1150 | // Exit, The rest of the connect is in a different protocol 1151 | if self.upgrade { 1152 | self.new_message(); 1153 | callback!(self, cb.on_message_complete(self), 1154 | HttpErrno::CBMessageComplete, index+1); 1155 | return index+1; 1156 | } 1157 | 1158 | if (self.flags & Flags::SkipBody.as_u8()) != 0 { 1159 | self.new_message(); 1160 | callback!(self, cb.on_message_complete(self), 1161 | HttpErrno::CBMessageComplete, index+1); 1162 | } else if (self.flags & Flags::Chunked.as_u8()) != 0 { 1163 | // chunked encoding - ignore Content-Length header 1164 | self.state = State::ChunkSizeStart; 1165 | } else { 1166 | if self.content_length == 0 { 1167 | // Content-Length header given but zero: Content-Length: 0\r\n 1168 | self.new_message(); 1169 | callback!(self, cb.on_message_complete(self), 1170 | HttpErrno::CBMessageComplete, index+1); 1171 | } else if self.content_length != ULLONG_MAX { 1172 | // Content-Length header given and non-zero 1173 | self.state = State::BodyIdentity; 1174 | } else { 1175 | if self.tp == HttpParserType::Request || 1176 | !self.http_message_needs_eof() { 1177 | // Assume content-length 0 - read the next 1178 | self.new_message(); 1179 | callback!(self, cb.on_message_complete(self), 1180 | HttpErrno::CBMessageComplete, index+1); 1181 | } else { 1182 | // Read body until EOF 1183 | self.state = State::BodyIdentityEof; 1184 | } 1185 | } 1186 | } 1187 | }, 1188 | State::BodyIdentity => { 1189 | let to_read: usize = cmp::min(self.content_length, 1190 | (len - index) as u64) as usize; 1191 | assert!(self.content_length != 0 && 1192 | self.content_length != ULLONG_MAX); 1193 | 1194 | mark!(body_mark, index); 1195 | self.content_length -= to_read as u64; 1196 | 1197 | index += to_read - 1; 1198 | 1199 | if self.content_length == 0 { 1200 | self.state = State::MessageDone; 1201 | 1202 | if body_mark.is_some() { 1203 | callback!(self, 1204 | cb.on_body(self, &data[body_mark.unwrap() .. (index + 1)]), 1205 | HttpErrno::CBBody, index); 1206 | body_mark = Option::None; 1207 | } 1208 | retry = true; 1209 | } 1210 | }, 1211 | // read until EOF 1212 | State::BodyIdentityEof => { 1213 | mark!(body_mark, index); 1214 | index = len - 1; 1215 | }, 1216 | State::MessageDone => { 1217 | self.new_message(); 1218 | callback!(self, cb.on_message_complete(self), 1219 | HttpErrno::CBMessageComplete, index+1); 1220 | }, 1221 | State::ChunkSizeStart => { 1222 | assert!(self.nread == 1); 1223 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1224 | 1225 | let unhex_val = unhex_value(ch); 1226 | if unhex_val.is_none() { 1227 | self.errno = Option::Some(HttpErrno::InvalidChunkSize); 1228 | return index; 1229 | } 1230 | 1231 | self.content_length = unhex_val.unwrap() as u64; 1232 | self.state = State::ChunkSize; 1233 | }, 1234 | State::ChunkSize => { 1235 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1236 | 1237 | if ch == CR { 1238 | self.state = State::ChunkSizeAlmostDone; 1239 | } else { 1240 | let unhex_val = unhex_value(ch); 1241 | if unhex_val.is_none() { 1242 | if ch == b';' || ch == b' ' { 1243 | self.state = State::ChunkParameters; 1244 | } else { 1245 | self.errno = Option::Some(HttpErrno::InvalidChunkSize); 1246 | return index; 1247 | } 1248 | } else { 1249 | // Overflow? Test against a conservative limit for simplicity 1250 | if (ULLONG_MAX - 16)/16 < self.content_length { 1251 | self.errno = Option::Some(HttpErrno::InvalidContentLength); 1252 | return index; 1253 | } 1254 | 1255 | let mut t: u64 = self.content_length; 1256 | t *= 16; 1257 | t += unhex_val.unwrap() as u64; 1258 | 1259 | self.content_length = t; 1260 | } 1261 | } 1262 | }, 1263 | State::ChunkParameters => { 1264 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1265 | // just ignore this shit. TODO check for overflow 1266 | if ch == CR { 1267 | self.state = State::ChunkSizeAlmostDone; 1268 | } 1269 | }, 1270 | State::ChunkSizeAlmostDone => { 1271 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1272 | strict_check!(self, ch != LF, index); 1273 | 1274 | self.nread = 0; 1275 | 1276 | if self.content_length == 0 { 1277 | self.flags |= Flags::Trailing.as_u8(); 1278 | self.state = State::HeaderFieldStart; 1279 | } else { 1280 | self.state = State::ChunkData; 1281 | } 1282 | }, 1283 | State::ChunkData => { 1284 | let to_read: usize = cmp::min(self.content_length, 1285 | (len - index) as u64) as usize; 1286 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1287 | assert!(self.content_length != 0 && 1288 | self.content_length != ULLONG_MAX); 1289 | 1290 | mark!(body_mark, index); 1291 | self.content_length -= to_read as u64; 1292 | index += to_read - 1; 1293 | 1294 | if self.content_length == 0 { 1295 | self.state = State::ChunkDataAlmostDone; 1296 | } 1297 | }, 1298 | State::ChunkDataAlmostDone => { 1299 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1300 | assert!(self.content_length == 0); 1301 | strict_check!(self, ch != CR, index); 1302 | self.state = State::ChunkDataDone; 1303 | 1304 | if body_mark.is_some() { 1305 | callback!(self, 1306 | cb.on_body(self, &data[body_mark.unwrap() .. index]), 1307 | HttpErrno::CBBody, index+1); 1308 | body_mark = Option::None; 1309 | } 1310 | }, 1311 | State::ChunkDataDone => { 1312 | assert!(self.flags & Flags::Chunked.as_u8() != 0); 1313 | strict_check!(self, ch != LF, index); 1314 | self.nread = 0; 1315 | self.state = State::ChunkSizeStart; 1316 | } 1317 | } 1318 | 1319 | if !retry { 1320 | break; 1321 | } 1322 | } 1323 | index += 1; 1324 | } 1325 | 1326 | // Run callbacks for any marks that we have leftover after we ran out of 1327 | // bytes. There should be at most one of these set, so it's OK to invoke 1328 | // them in series (unset marks will not result in callbacks). 1329 | // 1330 | assert!((if header_field_mark.is_some() { 1u8 } else { 0 }) + 1331 | (if header_value_mark.is_some() { 1 } else { 0 }) + 1332 | (if url_mark.is_some() { 1 } else { 0 }) + 1333 | (if body_mark.is_some() { 1 } else { 0 }) + 1334 | (if status_mark.is_some() { 1 } else { 0 }) <= 1); 1335 | 1336 | if header_field_mark.is_some() { 1337 | callback!(self, 1338 | cb.on_header_field(self, &data[header_field_mark.unwrap() .. index]), 1339 | HttpErrno::CBHeaderField, index); 1340 | } 1341 | if header_value_mark.is_some() { 1342 | callback!(self, 1343 | cb.on_header_value(self, &data[header_value_mark.unwrap() .. index]), 1344 | HttpErrno::CBHeaderValue, index); 1345 | } 1346 | if url_mark.is_some() { 1347 | callback!(self, 1348 | cb.on_url(self, &data[url_mark.unwrap() .. index]), 1349 | HttpErrno::CBUrl, index); 1350 | } 1351 | if body_mark.is_some() { 1352 | callback!(self, 1353 | cb.on_body(self, &data[body_mark.unwrap() .. index]), 1354 | HttpErrno::CBBody, index); 1355 | } 1356 | if status_mark.is_some() { 1357 | callback!(self, 1358 | cb.on_status(self, &data[status_mark.unwrap() .. index]), 1359 | HttpErrno::CBStatus, index); 1360 | } 1361 | len 1362 | } 1363 | 1364 | /// Returns true if the HTTP body is final. 1365 | pub fn http_body_is_final(&self) -> bool { 1366 | self.state == State::MessageDone 1367 | } 1368 | 1369 | /// Pauses the parser. 1370 | pub fn pause(&mut self, pause: bool) { 1371 | if self.errno.is_none() || self.errno == Option::Some(HttpErrno::Paused) { 1372 | self.errno = if pause { 1373 | Option::Some(HttpErrno::Paused) 1374 | } else { 1375 | Option::None 1376 | }; 1377 | } else { 1378 | panic!("Attempting to pause parser in error state"); 1379 | } 1380 | } 1381 | 1382 | /// Returns true if it needs to keep alive. 1383 | pub fn http_should_keep_alive(&self) -> bool { 1384 | if self.http_version.major > 0 && self.http_version.minor > 0 { 1385 | // HTTP/1.1 1386 | if (self.flags & Flags::ConnectionClose.as_u8()) != 0 { 1387 | return false 1388 | } 1389 | } else { 1390 | // HTTP/1.0 or earlier 1391 | if (self.flags & Flags::ConnectionKeepAlive.as_u8()) == 0 { 1392 | return false 1393 | } 1394 | } 1395 | 1396 | !self.http_message_needs_eof() 1397 | } 1398 | 1399 | // Our URL parser 1400 | fn parse_url_char(&self, s: State, ch: u8) -> State { 1401 | 1402 | if ch == b' ' || ch == b'\r' || ch == b'\n' || (self.strict && (ch == b'\t' || ch == b'\x0C')) { // '\x0C' = '\f' 1403 | return State::Dead; 1404 | } 1405 | 1406 | match s { 1407 | State::ReqSpacesBeforeUrl => { 1408 | // Proxied requests are followed by scheme of an absolute URI (alpha). 1409 | // All methods except CONNECT are followed by '/' or '*'. 1410 | 1411 | if ch == b'/' || ch == b'*' { 1412 | return State::ReqPath; 1413 | } 1414 | 1415 | if is_alpha(ch) { 1416 | return State::ReqSchema; 1417 | } 1418 | }, 1419 | State::ReqSchema => { 1420 | if is_alpha(ch) { 1421 | return s; 1422 | } 1423 | 1424 | if ch == b':' { 1425 | return State::ReqSchemaSlash; 1426 | } 1427 | }, 1428 | State::ReqSchemaSlash => { 1429 | if ch == b'/' { 1430 | return State::ReqSchemaSlashSlash; 1431 | } 1432 | }, 1433 | State::ReqSchemaSlashSlash => { 1434 | if ch == b'/' { 1435 | return State::ReqServerStart; 1436 | } 1437 | }, 1438 | State::ReqServerWithAt if ch == b'@' => return State::Dead, 1439 | State::ReqServerWithAt | State::ReqServerStart | State::ReqServer => { 1440 | if ch == b'/' { 1441 | return State::ReqPath; 1442 | } 1443 | 1444 | if ch == b'?' { 1445 | return State::ReqQueryStringStart; 1446 | } 1447 | 1448 | if ch == b'@' { 1449 | return State::ReqServerWithAt; 1450 | } 1451 | 1452 | if is_userinfo_char(ch) || ch == b'[' || ch == b']' { 1453 | return State::ReqServer; 1454 | } 1455 | }, 1456 | State::ReqPath => { 1457 | if is_url_char(self.strict, ch) { 1458 | return s; 1459 | } 1460 | 1461 | match ch { 1462 | b'?' => return State::ReqQueryStringStart, 1463 | b'#' => return State::ReqFragmentStart, 1464 | _ => (), 1465 | } 1466 | }, 1467 | State::ReqQueryStringStart | State::ReqQueryString => { 1468 | if is_url_char(self.strict, ch) { 1469 | return State::ReqQueryString; 1470 | } 1471 | 1472 | match ch { 1473 | b'?' => return State::ReqQueryString, // allow extra '?' in query string 1474 | b'#' => return State::ReqFragmentStart, 1475 | _ => (), 1476 | } 1477 | }, 1478 | State::ReqFragmentStart => { 1479 | if is_url_char(self.strict, ch) { 1480 | return State::ReqFragment; 1481 | } 1482 | 1483 | match ch { 1484 | b'?' => return State::ReqFragment, 1485 | b'#' => return s, 1486 | _ => (), 1487 | } 1488 | }, 1489 | State::ReqFragment => { 1490 | if is_url_char(self.strict, ch) { 1491 | return s; 1492 | } 1493 | 1494 | if ch == b'?' || ch == b'#' { 1495 | return s; 1496 | } 1497 | }, 1498 | _ => (), 1499 | } 1500 | 1501 | // We should never fall out of the switch above unless there's an error 1502 | return State::Dead; 1503 | } 1504 | 1505 | // Does the parser need to see an EOF to find the end of the message? 1506 | fn http_message_needs_eof(&self) -> bool { 1507 | if self.tp == HttpParserType::Request { 1508 | return false 1509 | } 1510 | 1511 | let status_code = self.status_code.unwrap_or(0); 1512 | // See RFC 2616 section 4.4 1513 | if status_code / 100 == 1 || // 1xx e.g. Continue 1514 | status_code == 204 || // No Content 1515 | status_code == 304 || // Not Modified 1516 | (self.flags & Flags::SkipBody.as_u8()) != 0 {// response to a HEAD request 1517 | return false 1518 | } 1519 | 1520 | if (self.flags & Flags::Chunked.as_u8() != 0) || 1521 | self.content_length != ULLONG_MAX { 1522 | return false 1523 | } 1524 | 1525 | true 1526 | } 1527 | 1528 | fn new_message(&mut self) { 1529 | let new_state = if self.tp == HttpParserType::Request { State::StartReq } else { State::StartRes }; 1530 | self.state = if self.strict { 1531 | if self.http_should_keep_alive() { 1532 | new_state 1533 | } else { 1534 | State::Dead 1535 | } 1536 | } else { 1537 | new_state 1538 | }; 1539 | } 1540 | } 1541 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 2 | pub enum State { 3 | Dead, 4 | 5 | StartReqOrRes, 6 | ResOrRespH, 7 | StartRes, 8 | ResH, 9 | ResHT, 10 | ResHTT, 11 | ResHTTP, 12 | ResFirstHttpMajor, 13 | ResHttpMajor, 14 | ResFirstHttpMinor, 15 | ResHttpMinor, 16 | ResFirstStatusCode, 17 | ResStatusCode, 18 | ResStatusStart, 19 | ResStatus, 20 | ResLineAlmostDone, 21 | 22 | StartReq, 23 | 24 | ReqMethod, 25 | ReqSpacesBeforeUrl, 26 | ReqSchema, 27 | ReqSchemaSlash, 28 | ReqSchemaSlashSlash, 29 | ReqServerStart, 30 | ReqServer, 31 | ReqServerWithAt, 32 | ReqPath, 33 | ReqQueryStringStart, 34 | ReqQueryString, 35 | ReqFragmentStart, 36 | ReqFragment, 37 | ReqHttpStart, 38 | ReqHttpH, 39 | ReqHttpHT, 40 | ReqHttpHTT, 41 | ReqHttpHTTP, 42 | ReqFirstHttpMajor, 43 | ReqHttpMajor, 44 | ReqFirstHttpMinor, 45 | ReqHttpMinor, 46 | ReqLineAlmostDone, 47 | 48 | HeaderFieldStart, 49 | HeaderField, 50 | HeaderValueDiscardWs, 51 | HeaderValueDiscardWsAlmostDone, 52 | HeaderValueDiscardLws, 53 | HeaderValueStart, 54 | HeaderValue, 55 | HeaderValueLws, 56 | 57 | HeaderAlmostDone, 58 | 59 | ChunkSizeStart, 60 | ChunkSize, 61 | ChunkParameters, 62 | ChunkSizeAlmostDone, 63 | 64 | HeadersAlmostDone, 65 | HeadersDone, 66 | 67 | ChunkData, 68 | ChunkDataAlmostDone, 69 | ChunkDataDone, 70 | 71 | BodyIdentity, 72 | BodyIdentityEof, 73 | 74 | MessageDone 75 | } 76 | 77 | impl State { 78 | pub fn is_header_state(self) -> bool { 79 | self <= State::HeadersDone 80 | } 81 | } 82 | 83 | pub enum HeaderState { 84 | General, 85 | C, 86 | CO, 87 | CON, 88 | 89 | MatchingConnection, 90 | MatchingProxyConnection, 91 | MatchingContentLength, 92 | MatchingTransferEncoding, 93 | MatchingUpgrade, 94 | 95 | Connection, 96 | ContentLength, 97 | TransferEncoding, 98 | Upgrade, 99 | 100 | MatchingTransferEncodingChunked, 101 | MatchingConnectionKeepAlive, 102 | MatchingConnectionClose, 103 | 104 | TransferEncodingChunked, 105 | ConnectionKeepAlive, 106 | ConnectionClose, 107 | } 108 | -------------------------------------------------------------------------------- /tests/helper.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use std::default::Default; 4 | use std::str; 5 | 6 | use self::http_parser::*; 7 | 8 | #[derive(PartialEq, Eq )] 9 | pub enum LastHeaderType { 10 | None, 11 | Field, 12 | Value, 13 | } 14 | 15 | pub struct Message { 16 | pub name: String, 17 | pub raw: String, 18 | pub tp: HttpParserType, 19 | pub strict: bool, 20 | pub method: Option, 21 | pub status_code: Option, 22 | pub response_status: Vec, 23 | pub request_path: String, 24 | pub request_url: Vec, 25 | pub fragment: String, 26 | pub query_string: String, 27 | pub body: String, 28 | pub body_size: usize, 29 | pub host: String, 30 | pub userinfo: String, 31 | pub port: u16, 32 | pub last_header_element: LastHeaderType, 33 | pub headers: Vec<[String; 2]>, 34 | pub should_keep_alive: bool, 35 | 36 | pub upgrade: Option, 37 | 38 | pub http_version: HttpVersion, 39 | 40 | pub message_begin_cb_called: bool, 41 | pub headers_complete_cb_called: bool, 42 | pub message_complete_cb_called: bool, 43 | pub message_complete_on_eof: bool, 44 | pub body_is_final: bool, 45 | } 46 | 47 | impl Default for Message { 48 | fn default() -> Message { 49 | Message { 50 | name: String::new() , 51 | raw: String::new(), 52 | tp: HttpParserType::Both, 53 | strict: true, 54 | method: None, 55 | status_code: None, 56 | response_status: vec![], 57 | request_path: String::new(), 58 | request_url: vec![], 59 | fragment: String::new(), 60 | query_string: String::new(), 61 | body: String::new(), 62 | body_size: 0, 63 | host: String::new(), 64 | userinfo: String::new(), 65 | port: 0, 66 | last_header_element: LastHeaderType::None, 67 | headers: vec![], 68 | should_keep_alive: false, 69 | 70 | upgrade: None, 71 | 72 | http_version: HttpVersion { major: 0, minor: 0 }, 73 | 74 | message_begin_cb_called: false, 75 | headers_complete_cb_called: false, 76 | message_complete_cb_called: false, 77 | message_complete_on_eof: false, 78 | body_is_final: false, 79 | } 80 | } 81 | } 82 | 83 | pub struct CallbackEmpty; 84 | 85 | impl HttpParserCallback for CallbackEmpty {} 86 | 87 | pub struct CallbackRegular { 88 | pub num_messages: usize, // maybe not necessary 89 | pub messages: Vec, 90 | pub currently_parsing_eof: bool, 91 | } 92 | 93 | impl Default for CallbackRegular { 94 | fn default() -> CallbackRegular { 95 | CallbackRegular { 96 | num_messages: 0, 97 | messages: Vec::new(), 98 | currently_parsing_eof: false, 99 | } 100 | } 101 | } 102 | 103 | impl HttpParserCallback for CallbackRegular { 104 | fn on_message_begin(&mut self, _ : &mut HttpParser) -> CallbackResult { 105 | self.messages[self.num_messages].message_begin_cb_called = true; 106 | Ok(ParseAction::None) 107 | } 108 | 109 | fn on_url(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 110 | for b in data { 111 | self.messages[self.num_messages].request_url.push(*b); 112 | } 113 | Ok(ParseAction::None) 114 | } 115 | 116 | fn on_status(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 117 | for b in data { 118 | self.messages[self.num_messages].response_status.push(*b); 119 | } 120 | Ok(ParseAction::None) 121 | } 122 | 123 | fn on_header_field(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 124 | let m : &mut Message = &mut self.messages[self.num_messages]; 125 | 126 | if m.last_header_element != LastHeaderType::Field { 127 | m.headers.push([String::new(), String::new()]); 128 | } 129 | 130 | match str::from_utf8(data) { 131 | Result::Ok(data_str) => { 132 | let i = m.headers.len()-1; 133 | m.headers[i][0].push_str(data_str); 134 | }, 135 | _ => panic!("on_header_field: data is not in utf8 encoding"), 136 | } 137 | 138 | m.last_header_element = LastHeaderType::Field; 139 | 140 | Ok(ParseAction::None) 141 | } 142 | 143 | fn on_header_value(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 144 | let m : &mut Message = &mut self.messages[self.num_messages]; 145 | 146 | match str::from_utf8(data) { 147 | Result::Ok(data_str) => { 148 | let i = m.headers.len()-1; 149 | m.headers[i][1].push_str(data_str); 150 | }, 151 | _ => panic!("on_header_value: data is not in utf8 encoding"), 152 | } 153 | 154 | m.last_header_element = LastHeaderType::Value; 155 | 156 | Ok(ParseAction::None) 157 | } 158 | 159 | fn on_headers_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 160 | let m : &mut Message = &mut self.messages[self.num_messages]; 161 | m.method = parser.method; 162 | m.status_code = parser.status_code; 163 | m.http_version = parser.http_version; 164 | m.headers_complete_cb_called = true; 165 | m.should_keep_alive = parser.http_should_keep_alive(); 166 | Ok(ParseAction::None) 167 | } 168 | 169 | fn on_body(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 170 | let m : &mut Message = &mut self.messages[self.num_messages]; 171 | 172 | match str::from_utf8(data) { 173 | Result::Ok(data_str) => { 174 | m.body.push_str(data_str); 175 | }, 176 | _ => panic!("on_body: data is not in utf8 encoding"), 177 | } 178 | m.body_size += data.len(); 179 | 180 | if m.body_is_final { 181 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 182 | on last on_body callback call \ 183 | but it doesn't! **\n\n"); 184 | } 185 | 186 | m.body_is_final = parser.http_body_is_final(); 187 | Ok(ParseAction::None) 188 | } 189 | 190 | fn on_message_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 191 | { 192 | let m : &mut Message = &mut self.messages[self.num_messages]; 193 | 194 | if m.should_keep_alive != parser.http_should_keep_alive() { 195 | panic!("\n\n ** Error http_should_keep_alive() should have same \ 196 | value in both on_message_complete and on_headers_complet \ 197 | but it doesn't! **\n\n"); 198 | } 199 | 200 | if m.body_size > 0 && parser.http_body_is_final() 201 | && !m.body_is_final { 202 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 203 | on last on_body callback call \ 204 | but it doesn't! **\n\n"); 205 | } 206 | 207 | m.message_complete_cb_called = true; 208 | m.message_complete_on_eof = self.currently_parsing_eof; 209 | } 210 | 211 | self.messages.push(Message{..Default::default()}); 212 | self.num_messages += 1; 213 | 214 | Ok(ParseAction::None) 215 | } 216 | } 217 | 218 | pub struct CallbackDontCall; 219 | 220 | impl HttpParserCallback for CallbackDontCall { 221 | fn on_message_begin(&mut self, _ : &mut HttpParser) -> CallbackResult { 222 | panic!("\n\n*** on_message_begin() called on paused parser ***\n\n"); 223 | } 224 | 225 | #[allow(unused_variables)] 226 | fn on_url(&mut self, _ : &mut HttpParser, data : &[u8],) -> CallbackResult { 227 | panic!("\n\n*** on_url() called on paused parser ***\n\n"); 228 | } 229 | 230 | #[allow(unused_variables)] 231 | fn on_status(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 232 | panic!("\n\n*** on_status() called on paused parser ***\n\n"); 233 | } 234 | 235 | #[allow(unused_variables)] 236 | fn on_header_field(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 237 | panic!("\n\n*** on_header_field() called on paused parser ***\n\n"); 238 | } 239 | 240 | #[allow(unused_variables)] 241 | fn on_header_value(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 242 | panic!("\n\n*** on_header_value() called on paused parser ***\n\n"); 243 | } 244 | 245 | fn on_headers_complete(&mut self, _ : &mut HttpParser) -> CallbackResult { 246 | panic!("\n\n*** on_headers_complete() called on paused parser ***\n\n"); 247 | } 248 | 249 | #[allow(unused_variables)] 250 | fn on_body(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 251 | panic!("\n\n*** on_body() called on paused parser ***\n\n"); 252 | } 253 | 254 | fn on_message_complete(&mut self, _ : &mut HttpParser) -> CallbackResult { 255 | panic!("\n\n*** on_message_complete() called on paused parser ***\n\n"); 256 | } 257 | } 258 | 259 | pub struct CallbackPause { 260 | pub num_messages: usize, // maybe not necessary 261 | pub messages: Vec, 262 | pub currently_parsing_eof: bool, 263 | 264 | pub paused: bool, 265 | dontcall: CallbackDontCall, 266 | } 267 | 268 | impl Default for CallbackPause { 269 | fn default() -> CallbackPause { 270 | CallbackPause { 271 | num_messages: 0, 272 | messages: Vec::new(), 273 | currently_parsing_eof: false, 274 | paused: false, 275 | dontcall: CallbackDontCall, 276 | } 277 | } 278 | } 279 | 280 | // TODO try to reuse code from CallbackRegular 281 | impl HttpParserCallback for CallbackPause { 282 | fn on_message_begin(&mut self, parser : &mut HttpParser) -> CallbackResult { 283 | if self.paused { 284 | self.dontcall.on_message_begin(parser) 285 | } else { 286 | parser.pause(true); 287 | self.paused = true; 288 | self.messages[self.num_messages].message_begin_cb_called = true; 289 | Ok(ParseAction::None) 290 | } 291 | } 292 | 293 | fn on_url(&mut self, parser : &mut HttpParser, data : &[u8],) -> CallbackResult { 294 | if self.paused { 295 | self.dontcall.on_url(parser, data) 296 | } else { 297 | parser.pause(true); 298 | self.paused = true; 299 | for b in data { 300 | self.messages[self.num_messages].request_url.push(*b); 301 | } 302 | Ok(ParseAction::None) 303 | } 304 | } 305 | 306 | fn on_status(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 307 | if self.paused { 308 | self.dontcall.on_status(parser, data) 309 | } else { 310 | parser.pause(true); 311 | self.paused = true; 312 | for b in data { 313 | self.messages[self.num_messages].response_status.push(*b); 314 | } 315 | Ok(ParseAction::None) 316 | } 317 | } 318 | 319 | fn on_header_field(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 320 | if self.paused { 321 | self.dontcall.on_header_field(parser, data) 322 | } else { 323 | parser.pause(true); 324 | self.paused = true; 325 | let m : &mut Message = &mut self.messages[self.num_messages]; 326 | 327 | if m.last_header_element != LastHeaderType::Field { 328 | m.headers.push([String::new(), String::new()]); 329 | } 330 | 331 | match str::from_utf8(data) { 332 | Result::Ok(data_str) => { 333 | let i = m.headers.len()-1; 334 | m.headers[i][0].push_str(data_str); 335 | }, 336 | _ => panic!("on_header_field: data is not in utf8 encoding"), 337 | } 338 | 339 | m.last_header_element = LastHeaderType::Field; 340 | 341 | Ok(ParseAction::None) 342 | } 343 | } 344 | 345 | fn on_header_value(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 346 | if self.paused { 347 | self.dontcall.on_header_value(parser, data) 348 | } else { 349 | parser.pause(true); 350 | self.paused = true; 351 | let m : &mut Message = &mut self.messages[self.num_messages]; 352 | 353 | match str::from_utf8(data) { 354 | Result::Ok(data_str) => { 355 | let i = m.headers.len()-1; 356 | m.headers[i][1].push_str(data_str); 357 | }, 358 | _ => panic!("on_header_value: data is not in utf8 encoding"), 359 | } 360 | 361 | m.last_header_element = LastHeaderType::Value; 362 | 363 | Ok(ParseAction::None) 364 | } 365 | } 366 | 367 | fn on_headers_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 368 | if self.paused { 369 | self.dontcall.on_headers_complete(parser) 370 | } else { 371 | parser.pause(true); 372 | self.paused = true; 373 | let m : &mut Message = &mut self.messages[self.num_messages]; 374 | m.method = parser.method; 375 | m.status_code = parser.status_code; 376 | m.http_version = parser.http_version; 377 | m.headers_complete_cb_called = true; 378 | m.should_keep_alive = parser.http_should_keep_alive(); 379 | Ok(ParseAction::None) 380 | } 381 | } 382 | 383 | fn on_body(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 384 | if self.paused { 385 | self.dontcall.on_body(parser, data) 386 | } else { 387 | parser.pause(true); 388 | self.paused = true; 389 | let m : &mut Message = &mut self.messages[self.num_messages]; 390 | 391 | match str::from_utf8(data) { 392 | Result::Ok(data_str) => { 393 | m.body.push_str(data_str); 394 | }, 395 | _ => panic!("on_body: data is not in utf8 encoding"), 396 | } 397 | m.body_size += data.len(); 398 | 399 | if m.body_is_final { 400 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 401 | on last on_body callback call \ 402 | but it doesn't! **\n\n"); 403 | } 404 | 405 | m.body_is_final = parser.http_body_is_final(); 406 | Ok(ParseAction::None) 407 | } 408 | } 409 | 410 | fn on_message_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 411 | if self.paused { 412 | self.dontcall.on_message_complete(parser) 413 | } else { 414 | parser.pause(true); 415 | self.paused = true; 416 | { 417 | let m : &mut Message = &mut self.messages[self.num_messages]; 418 | 419 | if m.should_keep_alive != parser.http_should_keep_alive() { 420 | panic!("\n\n ** Error http_should_keep_alive() should have same \ 421 | value in both on_message_complete and on_headers_complet \ 422 | but it doesn't! **\n\n"); 423 | } 424 | 425 | if m.body_size > 0 && parser.http_body_is_final() 426 | && !m.body_is_final { 427 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 428 | on last on_body callback call \ 429 | but it doesn't! **\n\n"); 430 | } 431 | 432 | m.message_complete_cb_called = true; 433 | m.message_complete_on_eof = self.currently_parsing_eof; 434 | } 435 | 436 | self.messages.push(Message{..Default::default()}); 437 | self.num_messages += 1; 438 | 439 | Ok(ParseAction::None) 440 | } 441 | } 442 | } 443 | 444 | pub struct CallbackCountBody { 445 | pub num_messages: usize, // maybe not necessary 446 | pub messages: Vec, 447 | pub currently_parsing_eof: bool, 448 | } 449 | 450 | impl Default for CallbackCountBody { 451 | fn default() -> CallbackCountBody { 452 | CallbackCountBody { 453 | num_messages: 0, 454 | messages: Vec::new(), 455 | currently_parsing_eof: false, 456 | } 457 | } 458 | } 459 | 460 | // find a way to reuse the code in CallbackRegular 461 | impl HttpParserCallback for CallbackCountBody { 462 | fn on_message_begin(&mut self, _ : &mut HttpParser) -> CallbackResult { 463 | self.messages[self.num_messages].message_begin_cb_called = true; 464 | Ok(ParseAction::None) 465 | } 466 | 467 | fn on_url(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 468 | for b in data { 469 | self.messages[self.num_messages].request_url.push(*b); 470 | } 471 | Ok(ParseAction::None) 472 | } 473 | 474 | fn on_status(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 475 | for b in data { 476 | self.messages[self.num_messages].response_status.push(*b); 477 | } 478 | Ok(ParseAction::None) 479 | } 480 | 481 | fn on_header_field(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 482 | let m : &mut Message = &mut self.messages[self.num_messages]; 483 | 484 | if m.last_header_element != LastHeaderType::Field { 485 | m.headers.push([String::new(), String::new()]); 486 | } 487 | 488 | match str::from_utf8(data) { 489 | Result::Ok(data_str) => { 490 | let i = m.headers.len()-1; 491 | m.headers[i][0].push_str(data_str); 492 | }, 493 | _ => panic!("on_header_field: data is not in utf8 encoding"), 494 | } 495 | 496 | m.last_header_element = LastHeaderType::Field; 497 | 498 | Ok(ParseAction::None) 499 | } 500 | 501 | fn on_header_value(&mut self, _ : &mut HttpParser, data : &[u8]) -> CallbackResult { 502 | let m : &mut Message = &mut self.messages[self.num_messages]; 503 | 504 | match str::from_utf8(data) { 505 | Result::Ok(data_str) => { 506 | let i = m.headers.len()-1; 507 | m.headers[i][1].push_str(data_str); 508 | }, 509 | _ => panic!("on_header_value: data is not in utf8 encoding"), 510 | } 511 | 512 | m.last_header_element = LastHeaderType::Value; 513 | 514 | Ok(ParseAction::None) 515 | } 516 | 517 | fn on_headers_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 518 | let m : &mut Message = &mut self.messages[self.num_messages]; 519 | m.method = parser.method; 520 | m.status_code = parser.status_code; 521 | m.http_version = parser.http_version; 522 | m.headers_complete_cb_called = true; 523 | m.should_keep_alive = parser.http_should_keep_alive(); 524 | Ok(ParseAction::None) 525 | } 526 | 527 | fn on_body(&mut self, parser : &mut HttpParser, data : &[u8]) -> CallbackResult { 528 | let m : &mut Message = &mut self.messages[self.num_messages]; 529 | 530 | m.body_size += data.len(); 531 | 532 | if m.body_is_final { 533 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 534 | on last on_body callback call \ 535 | but it doesn't! **\n\n"); 536 | } 537 | 538 | m.body_is_final = parser.http_body_is_final(); 539 | Ok(ParseAction::None) 540 | } 541 | 542 | fn on_message_complete(&mut self, parser : &mut HttpParser) -> CallbackResult { 543 | { 544 | let m : &mut Message = &mut self.messages[self.num_messages]; 545 | 546 | if m.should_keep_alive != parser.http_should_keep_alive() { 547 | panic!("\n\n ** Error http_should_keep_alive() should have same \ 548 | value in both on_message_complete and on_headers_complet \ 549 | but it doesn't! **\n\n"); 550 | } 551 | 552 | if m.body_size > 0 && parser.http_body_is_final() 553 | && !m.body_is_final { 554 | panic!("\n\n ** Error http_body_is_final() should return 1 \ 555 | on last on_body callback call \ 556 | but it doesn't! **\n\n"); 557 | } 558 | 559 | m.message_complete_cb_called = true; 560 | m.message_complete_on_eof = self.currently_parsing_eof; 561 | } 562 | 563 | self.messages.push(Message{..Default::default()}); 564 | self.num_messages += 1; 565 | 566 | Ok(ParseAction::None) 567 | } 568 | } 569 | pub fn print_error(errno: HttpErrno, raw: &[u8], error_location: usize) { 570 | println!("\n*** {} ***\n", errno); 571 | 572 | let len = raw.len(); 573 | let mut this_line = false; 574 | let mut char_len: u64; 575 | let mut error_location_line = 0; 576 | let mut eof = true; 577 | for i in (0..len ) { 578 | if i == (error_location as usize) { this_line = true; } 579 | match raw[i] { 580 | b'\r' => { 581 | char_len = 2; 582 | print!("\\r"); 583 | }, 584 | b'\n' => { 585 | println!("\\n"); 586 | 587 | if this_line { 588 | eof = false; 589 | break; 590 | } 591 | 592 | error_location_line = 0; 593 | continue; 594 | }, 595 | _ => { 596 | char_len = 1; 597 | print!("{}", (raw[i] as char)); 598 | }, 599 | } 600 | if !this_line { error_location_line += char_len; } 601 | } 602 | 603 | if eof { 604 | println!("[eof]"); 605 | } 606 | 607 | for _ in (0..error_location_line) { 608 | print!(" "); 609 | } 610 | println!("^\n\nerror location: {}", error_location); 611 | } 612 | 613 | pub fn assert_eq_message(actual: &Message, expected: &Message) { 614 | assert_eq!(actual.http_version, expected.http_version); 615 | 616 | if expected.tp == HttpParserType::Request { 617 | assert!(actual.method == expected.method); 618 | } else { 619 | assert_eq!(actual.status_code, expected.status_code); 620 | assert_eq!(actual.response_status, expected.response_status); 621 | } 622 | 623 | assert_eq!(actual.should_keep_alive, expected.should_keep_alive); 624 | assert_eq!(actual.message_complete_on_eof, expected.message_complete_on_eof); 625 | 626 | assert!(actual.message_begin_cb_called); 627 | assert!(actual.headers_complete_cb_called); 628 | assert!(actual.message_complete_cb_called); 629 | 630 | assert_eq!(actual.request_url, expected.request_url); 631 | 632 | // Check URL components; we can't do this w/ CONNECT since it doesn't 633 | // send us a well-formed URL. 634 | // TODO add after implementing http_parser_parse_url() 635 | 636 | if expected.body_size > 0 { 637 | assert_eq!(actual.body_size, expected.body_size); 638 | } else { 639 | assert_eq!(actual.body, expected.body); 640 | } 641 | 642 | assert_eq!(actual.headers.len(), expected.headers.len()); 643 | 644 | for i in (0..actual.headers.len()) { 645 | assert_eq!(actual.headers[i as usize][0], expected.headers[i as usize][0]); 646 | assert_eq!(actual.headers[i as usize][1], expected.headers[i as usize][1]); 647 | } 648 | 649 | assert_eq!(actual.upgrade, expected.upgrade); 650 | } 651 | 652 | pub fn test_message(message: &Message) { 653 | let raw = &message.raw; 654 | let raw_len = raw.len(); 655 | for i in (0..raw_len) { 656 | let mut hp = HttpParser::new(message.tp); 657 | hp.strict = message.strict; 658 | 659 | let mut cb = CallbackRegular{..Default::default()}; 660 | cb.messages.push(Message{..Default::default()}); 661 | 662 | let mut read: usize; 663 | 664 | if i > 0 { 665 | read = hp.execute(&mut cb, &raw.as_bytes()[0 .. i]); 666 | 667 | if message.upgrade.is_some() && hp.upgrade { 668 | cb.messages[cb.num_messages - 1].upgrade = Some(raw[read..].to_string()); 669 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", message.name); 670 | assert_eq_message(&cb.messages[0], message); 671 | continue; 672 | } 673 | 674 | if read != i { 675 | print_error(hp.errno.unwrap(), raw.as_bytes(), read); 676 | panic!(); 677 | } 678 | } 679 | 680 | read = hp.execute(&mut cb, &raw.as_bytes()[i..]); 681 | 682 | if message.upgrade.is_some() && hp.upgrade { 683 | cb.messages[cb.num_messages - 1].upgrade = Some(raw[i+read..].to_string()); 684 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", message.name); 685 | assert_eq_message(&cb.messages[0], message); 686 | continue; 687 | } 688 | 689 | if read != (raw_len - i) { 690 | print_error(hp.errno.unwrap(), raw.as_bytes(), i + read); 691 | panic!(); 692 | } 693 | 694 | cb.currently_parsing_eof = true; 695 | read = hp.execute(&mut cb, &[]); 696 | 697 | if read != 0 { 698 | print_error(hp.errno.unwrap(), raw.as_bytes(), read); 699 | panic!(); 700 | } 701 | 702 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", message.name); 703 | assert_eq_message(&cb.messages[0], message); 704 | } 705 | } 706 | 707 | pub fn test_message_pause(msg: &Message) { 708 | let mut raw : &str = &msg.raw; 709 | 710 | let mut hp = HttpParser::new(msg.tp); 711 | hp.strict = msg.strict; 712 | 713 | let mut cb = CallbackPause{..Default::default()}; 714 | cb.messages.push(Message{..Default::default()}); 715 | 716 | while raw.len() > 0 { 717 | cb.paused = false; 718 | let read = hp.execute(&mut cb, raw.as_bytes()); 719 | 720 | if cb.messages[0].message_complete_cb_called && 721 | msg.upgrade.is_some() && hp.upgrade { 722 | cb.messages[0].upgrade = Some(raw[read..].to_string()); 723 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", msg.name); 724 | assert_eq_message(&cb.messages[0], msg); 725 | return; 726 | } 727 | 728 | if read < raw.len() { 729 | if hp.errno == Option::Some(HttpErrno::Strict) { 730 | return; 731 | } 732 | 733 | assert!(hp.errno == Option::Some(HttpErrno::Paused)); 734 | } 735 | 736 | raw = &raw[read..]; 737 | hp.pause(false); 738 | } 739 | 740 | cb.currently_parsing_eof = true; 741 | cb.paused = false; 742 | let read = hp.execute(&mut cb, &[]); 743 | assert_eq!(read, 0); 744 | 745 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", msg.name); 746 | assert_eq_message(&cb.messages[0], msg); 747 | } 748 | 749 | fn count_parsed_messages(messages: &[&Message]) -> usize { 750 | let mut i: usize = 0; 751 | let len = messages.len(); 752 | 753 | while i < len { 754 | let msg = messages[i]; 755 | i += 1; 756 | 757 | if msg.upgrade.is_some() { 758 | break; 759 | } 760 | } 761 | 762 | i 763 | } 764 | 765 | pub fn test_multiple3(r1: &Message, r2: &Message, r3: &Message) { 766 | let messages = [r1, r2, r3]; 767 | let message_count = count_parsed_messages(&messages); 768 | 769 | let mut total = String::new(); 770 | total.push_str(&r1.raw); 771 | total.push_str(&r2.raw); 772 | total.push_str(&r3.raw); 773 | 774 | let mut hp = HttpParser::new(r1.tp); 775 | hp.strict = r1.strict && r2.strict && r3.strict; 776 | 777 | let mut cb = CallbackRegular{..Default::default()}; 778 | cb.messages.push(Message{..Default::default()}); 779 | 780 | let mut read = hp.execute(&mut cb, total.as_bytes()); 781 | 782 | if hp.upgrade { 783 | upgrade_message_fix(&mut cb, &total, read, &messages); 784 | 785 | assert!(message_count == cb.num_messages, 786 | "\n\n*** Parser didn't see 3 messages only {} *** \n", cb.num_messages); 787 | assert_eq_message(&cb.messages[0], r1); 788 | if message_count > 1 { 789 | assert_eq_message(&cb.messages[1], r2); 790 | } 791 | if message_count > 2 { 792 | assert_eq_message(&cb.messages[2], r3); 793 | } 794 | return; 795 | } 796 | 797 | if read != total.len() { 798 | print_error(hp.errno.unwrap(), total.as_bytes(), read); 799 | panic!(); 800 | } 801 | 802 | cb.currently_parsing_eof = true; 803 | read = hp.execute(&mut cb, &[]); 804 | 805 | if read != 0 { 806 | print_error(hp.errno.unwrap(), total.as_bytes(), read); 807 | panic!(); 808 | } 809 | 810 | assert!(message_count == cb.num_messages, 811 | "\n\n*** Parser didn't see 3 messages only {} *** \n", cb.num_messages); 812 | assert_eq_message(&cb.messages[0], r1); 813 | if message_count > 1 { 814 | assert_eq_message(&cb.messages[1], r2); 815 | } 816 | if message_count > 2 { 817 | assert_eq_message(&cb.messages[2], r3); 818 | } 819 | } 820 | 821 | fn upgrade_message_fix(cb: &mut CallbackRegular, body: &str, read: usize, msgs: &[&Message]) { 822 | let mut off : usize = 0; 823 | 824 | for m in msgs.iter() { 825 | off += m.raw.len(); 826 | 827 | if m.upgrade.is_some() { 828 | let upgrade_len = m.upgrade.as_ref().unwrap().len(); 829 | 830 | off -= upgrade_len; 831 | 832 | assert_eq!(&body[off..], &body[read..]); 833 | 834 | cb.messages[cb.num_messages-1].upgrade = 835 | Some(body[read .. read+upgrade_len].to_string()); 836 | return; 837 | } 838 | } 839 | 840 | panic!("\n\n*** Error: expected a message with upgrade ***\n"); 841 | } 842 | 843 | fn print_test_scan_error(i: usize, j: usize, buf1: &[u8], buf2: &[u8], buf3: &[u8]) { 844 | print!("i={} j={}\n", i, j); 845 | unsafe { 846 | print!("buf1 ({}) {}\n\n", buf1.len(), str::from_utf8_unchecked(buf1)); 847 | print!("buf2 ({}) {}\n\n", buf2.len(), str::from_utf8_unchecked(buf2)); 848 | print!("buf3 ({}) {}\n\n", buf3.len(), str::from_utf8_unchecked(buf3)); 849 | } 850 | panic!(); 851 | } 852 | 853 | pub fn test_scan(r1: &Message, r2: &Message, r3: &Message) { 854 | let mut total = String::new(); 855 | total.push_str(&r1.raw); 856 | total.push_str(&r2.raw); 857 | total.push_str(&r3.raw); 858 | 859 | let total_len = total.len(); 860 | 861 | let message_count = count_parsed_messages(&[r1, r2, r3]); 862 | 863 | for &is_type_both in [false, true].iter() { 864 | for j in (2..total_len) { 865 | for i in (1..j) { 866 | let mut hp = HttpParser::new(if is_type_both { HttpParserType::Both } else { r1.tp }); 867 | hp.strict = r1.strict && r2.strict && r3.strict; 868 | 869 | let mut cb = CallbackRegular{..Default::default()}; 870 | cb.messages.push(Message{..Default::default()}); 871 | 872 | let mut done = false; 873 | 874 | let buf1 = &total.as_bytes()[0 .. i]; 875 | let buf2 = &total.as_bytes()[i .. j]; 876 | let buf3 = &total.as_bytes()[j .. total_len]; 877 | 878 | let mut read = hp.execute(&mut cb, buf1); 879 | 880 | if hp.upgrade { 881 | done = true; 882 | } else { 883 | if read != buf1.len() { 884 | print_error(hp.errno.unwrap(), buf1, read); 885 | print_test_scan_error(i, j, buf1, buf2, buf3); 886 | } 887 | } 888 | 889 | if !done { 890 | read += hp.execute(&mut cb, buf2); 891 | 892 | if hp.upgrade { 893 | done = true; 894 | } else { 895 | if read != (buf1.len() + buf2.len()) { 896 | print_error(hp.errno.unwrap(), buf2, read); 897 | print_test_scan_error(i, j, buf1, buf2, buf3); 898 | } 899 | } 900 | } 901 | 902 | if !done { 903 | read += hp.execute(&mut cb, buf3); 904 | 905 | if hp.upgrade { 906 | done = true; 907 | } else { 908 | if read != (buf1.len() + buf2.len() + buf3.len()) { 909 | print_error(hp.errno.unwrap(), buf3, read); 910 | print_test_scan_error(i, j, buf1, buf2, buf3); 911 | } 912 | } 913 | } 914 | 915 | if !done { 916 | cb.currently_parsing_eof = true; 917 | read = hp.execute(&mut cb, &[]); 918 | } 919 | 920 | // test 921 | 922 | if hp.upgrade { 923 | upgrade_message_fix(&mut cb, &total, read, &[r1, r2, r3]); 924 | } 925 | 926 | if message_count != cb.num_messages { 927 | print!("\n\nParser didn't see {} messages only {}\n", message_count, cb.num_messages); 928 | print_test_scan_error(i, j, buf1, buf2, buf3); 929 | } 930 | 931 | assert_eq_message(&cb.messages[0], r1); 932 | if message_count > 1 { 933 | assert_eq_message(&cb.messages[1], r2); 934 | } 935 | if message_count > 2 { 936 | assert_eq_message(&cb.messages[2], r3); 937 | } 938 | } 939 | } 940 | } 941 | } 942 | -------------------------------------------------------------------------------- /tests/test_content_length_overflow.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use http_parser::{HttpParser, HttpParserType, HttpErrno}; 4 | 5 | pub mod helper; 6 | 7 | macro_rules! content_length( 8 | ($len:expr) => ( 9 | format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", $len); 10 | ); 11 | ); 12 | 13 | macro_rules! chunk_content( 14 | ($len:expr) => ( 15 | format!("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n{}\r\n...", $len); 16 | ); 17 | ); 18 | 19 | #[test] 20 | fn test_header_content_length_overflow() { 21 | let a = content_length!("1844674407370955160"); // 2^64 / 10 - 1 22 | let b = content_length!("18446744073709551615"); // 2^64 - 1 23 | let c = content_length!("18446744073709551616"); // 2^64 24 | 25 | test_content_length_overflow(a.as_bytes(), true); 26 | test_content_length_overflow(b.as_bytes(), false); 27 | test_content_length_overflow(c.as_bytes(), false); 28 | } 29 | 30 | #[test] 31 | fn test_chunk_content_length_overflow() { 32 | let a = chunk_content!("FFFFFFFFFFFFFFE"); // 2^64 / 16 - 1 33 | let b = chunk_content!("FFFFFFFFFFFFFFFF"); // 2^64 - 1 34 | let c = chunk_content!("10000000000000000"); // 2^64 35 | 36 | test_content_length_overflow(a.as_bytes(), true); 37 | test_content_length_overflow(b.as_bytes(), false); 38 | test_content_length_overflow(c.as_bytes(), false); 39 | } 40 | 41 | fn test_content_length_overflow(data: &[u8], expect_ok: bool) { 42 | let mut hp = HttpParser::new(HttpParserType::Response); 43 | let mut cb = helper::CallbackEmpty; 44 | 45 | hp.execute(&mut cb, data); 46 | 47 | if expect_ok { 48 | assert!(hp.errno.is_none()); 49 | } else { 50 | assert!(hp.errno == Option::Some(HttpErrno::InvalidContentLength)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/test_first_line.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | 4 | use self::http_parser::{HttpParser, HttpParserType}; 5 | 6 | pub mod helper; 7 | 8 | #[test] 9 | fn test_request_line() { 10 | let line : &str = "GET / HTTP/1.1\r\n"; 11 | test_first_line(HttpParserType::Request, line.as_bytes()); 12 | } 13 | 14 | #[test] 15 | fn test_status_line() { 16 | let line : &str = "HTTP/1.0 200 OK\r\n"; 17 | test_first_line(HttpParserType::Response, line.as_bytes()); 18 | } 19 | 20 | fn test_first_line(tp : HttpParserType, data : &[u8]) { 21 | let mut hp : HttpParser = HttpParser::new(tp); 22 | let mut cb = helper::CallbackEmpty; 23 | let parsed = hp.execute(&mut cb, data); 24 | assert_eq!(parsed, data.len()); 25 | } 26 | -------------------------------------------------------------------------------- /tests/test_header.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use self::http_parser::{HttpParser, HttpParserCallback, HttpParserType, 4 | HttpErrno}; 5 | 6 | pub mod helper; 7 | 8 | const HEADER_LINE : &'static str = "header-key: header-value\r\n"; 9 | 10 | #[test] 11 | fn test_request_header() { 12 | test_header(HttpParserType::Request); 13 | } 14 | 15 | #[test] 16 | fn test_request_header_overflow() { 17 | test_header_overflow(HttpParserType::Request); 18 | } 19 | 20 | #[test] 21 | fn test_response_header() { 22 | test_header(HttpParserType::Response); 23 | } 24 | 25 | #[test] 26 | fn test_response_header_overflow() { 27 | test_header_overflow(HttpParserType::Response); 28 | } 29 | 30 | fn test_header(tp : HttpParserType) { 31 | let mut hp : HttpParser = HttpParser::new(tp); 32 | let mut cb = helper::CallbackEmpty; 33 | 34 | before(&mut hp, &mut cb, tp); 35 | 36 | let parsed: usize = hp.execute(&mut cb, HEADER_LINE.as_bytes()); 37 | assert_eq!(parsed, HEADER_LINE.len()); 38 | } 39 | 40 | fn test_header_overflow(tp: HttpParserType) { 41 | let mut hp : HttpParser = HttpParser::new(tp); 42 | let mut cb = helper::CallbackEmpty; 43 | 44 | before(&mut hp, &mut cb, tp); 45 | 46 | let len : usize = HEADER_LINE.len(); 47 | let mut done = false; 48 | 49 | while !done { 50 | let parsed = hp.execute(&mut cb, HEADER_LINE.as_bytes()); 51 | if parsed != len { 52 | assert!(hp.errno == Option::Some(HttpErrno::HeaderOverflow)); 53 | done = true; 54 | } 55 | } 56 | assert!(done); 57 | } 58 | 59 | fn before(hp : &mut HttpParser, cb : &mut CB, tp : HttpParserType) { 60 | let line = if tp == HttpParserType::Request { 61 | "GET / HTTP/1.1\r\n" 62 | } else { 63 | "HTTP/1.0 200 OK\r\n" 64 | }; 65 | let parsed : usize = hp.execute(cb, line.as_bytes()); 66 | assert_eq!(parsed, line.len()); 67 | } 68 | -------------------------------------------------------------------------------- /tests/test_interface.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use self::http_parser::*; 4 | 5 | #[test] 6 | fn test_interface() { 7 | let mut hp = HttpParser::new(HttpParserType::Both); 8 | 9 | struct Callback; 10 | 11 | impl HttpParserCallback for Callback { 12 | fn on_message_complete(&mut self, _ : &mut HttpParser) -> CallbackResult { 13 | Ok(ParseAction::None) 14 | } 15 | } 16 | 17 | let mut cb = Callback; 18 | hp.execute(&mut cb, &[b'a', b'b', b'c']); 19 | } 20 | -------------------------------------------------------------------------------- /tests/test_long_body.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use http_parser::{HttpParserType, HttpParser}; 4 | 5 | pub mod helper; 6 | 7 | #[test] 8 | fn test_no_overflow_long_body_request() { 9 | test_no_overflow_long_body(HttpParserType::Request, 1000); 10 | test_no_overflow_long_body(HttpParserType::Request, 100000); 11 | } 12 | 13 | #[test] 14 | fn test_no_overflow_long_body_response() { 15 | test_no_overflow_long_body(HttpParserType::Response, 1000); 16 | test_no_overflow_long_body(HttpParserType::Response, 100000); 17 | } 18 | 19 | fn test_no_overflow_long_body(tp: HttpParserType, length: u64) { 20 | let mut hp = HttpParser::new(tp); 21 | let mut cb = helper::CallbackEmpty; 22 | 23 | let line = if tp == HttpParserType::Request { 24 | "POST / HTTP/1.0" 25 | } else { 26 | "HTTP/1.0 200 OK" 27 | }; 28 | 29 | let headers = format!("{}\r\nConnection: Keep-Alive\r\nContent-Length: {}\r\n\r\n", 30 | line, length); 31 | 32 | let mut parsed = hp.execute(&mut cb, headers.as_bytes()); 33 | assert_eq!(parsed, headers.len()); 34 | 35 | for _ in (0..length) { 36 | parsed = hp.execute(&mut cb, &[b'a']); 37 | assert_eq!(parsed, 1); 38 | } 39 | 40 | parsed = hp.execute(&mut cb, headers.as_bytes()); 41 | assert_eq!(parsed, headers.len()); 42 | } 43 | -------------------------------------------------------------------------------- /tests/test_requests.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use std::default::Default; 4 | 5 | use http_parser::{HttpParser, HttpParserType, HttpErrno, HttpMethod, HttpVersion}; 6 | 7 | pub mod helper; 8 | 9 | #[test] 10 | fn test_requests() { 11 | test_simple("GET / HTP/1.1\r\n\r\n", Option::Some(HttpErrno::InvalidVersion)); 12 | 13 | // Well-formed but incomplete 14 | test_simple("GET / HTTP/1.1\r\n\ 15 | Content-Type: text/plain\r\n\ 16 | Content-Length: 6\r\n\ 17 | \r\n\ 18 | fooba", Option::None); 19 | 20 | let all_methods = [ 21 | "DELETE", 22 | "GET", 23 | "HEAD", 24 | "POST", 25 | "PUT", 26 | // "CONNECT", // CONNECT can't be tested like other methods, it's a tunnel 27 | "OPTIONS", 28 | "TRACE", 29 | "COYP", 30 | "LOCK", 31 | "MKCOL", 32 | "MOVE", 33 | "PROPFIND", 34 | "PROPPATCH", 35 | "UNLOCK", 36 | "REPORT", 37 | "MKACTIVITY", 38 | "CHECKOUT", 39 | "MERGE", 40 | "M-SEARCH", 41 | "NOTIFY", 42 | "SUBSCRIBE", 43 | "PATCH", 44 | ]; 45 | 46 | for &method in all_methods.iter() { 47 | let mut buf = String::new(); 48 | buf.push_str(method); 49 | buf.push_str(" / HTTP1.1\r\n\r\n"); 50 | 51 | test_simple(&buf, Option::None); 52 | } 53 | 54 | let bad_methods = [ 55 | "ASDF", 56 | "C******", 57 | "COLA", 58 | "GEM", 59 | "GETA", 60 | "M****", 61 | "MKCOLA", 62 | "PROPPATCHA", 63 | "PUN", 64 | "PX", 65 | "SA", 66 | "hello world", 67 | ]; 68 | for &method in bad_methods.iter() { 69 | let mut buf = String::new(); 70 | buf.push_str(method); 71 | buf.push_str(" / HTTP1.1\r\n\r\n"); 72 | 73 | test_simple(&buf, Option::Some(HttpErrno::InvalidMethod)); 74 | } 75 | 76 | // illegal header field name line folding 77 | test_simple("GET / HTTP/1.1\r\n\ 78 | name\r\n\ 79 | :value\r\n\ 80 | \r\n", Option::Some(HttpErrno::InvalidHeaderToken)); 81 | 82 | let dumbfuck2 = 83 | "GET / HTTP/1.1\r\n\ 84 | X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\ 85 | \tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\ 86 | \tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\ 87 | \tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\ 88 | \tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\ 89 | \tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\ 90 | \tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\ 91 | \tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\ 92 | \tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\ 93 | \tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\ 94 | \t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\ 95 | \tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\ 96 | \twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\ 97 | \tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\ 98 | \tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\ 99 | \tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\ 100 | \tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\ 101 | \taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\ 102 | \t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\ 103 | \tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\ 104 | \tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\ 105 | \tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\ 106 | \tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\ 107 | \tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\ 108 | \tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\ 109 | \tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\ 110 | \tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\ 111 | \thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\ 112 | \twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\ 113 | \tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\ 114 | \tRA==\r\n\ 115 | \t-----END CERTIFICATE-----\r\n\ 116 | \r\n"; 117 | test_simple(dumbfuck2, Option::None); 118 | 119 | // REQUESTS 120 | let requests = [ 121 | helper::Message { 122 | name: "curl get".to_string(), 123 | tp: HttpParserType::Request, 124 | raw: "GET /test HTTP/1.1\r\n\ 125 | User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n\ 126 | Host: 0.0.0.0=5000\r\n\ 127 | Accept: */*\r\n\ 128 | \r\n".to_string(), 129 | should_keep_alive: true, 130 | message_complete_on_eof: false, 131 | http_version: HttpVersion { major: 1, minor: 1 }, 132 | method: Some(HttpMethod::Get), 133 | query_string: "".to_string(), 134 | fragment: "".to_string(), 135 | request_path: "/test".to_string(), 136 | request_url: { 137 | let mut v: Vec = Vec::new(); 138 | for b in "/test".as_bytes() { 139 | v.push(*b); 140 | } 141 | v 142 | }, 143 | headers: vec![ 144 | [ "User-Agent".to_string(), "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1".to_string() ], 145 | [ "Host".to_string(), "0.0.0.0=5000".to_string() ], 146 | [ "Accept".to_string(), "*/*".to_string() ], 147 | ], 148 | body: "".to_string(), 149 | ..Default::default() 150 | }, 151 | helper::Message { 152 | name: "firefox get".to_string(), 153 | tp: HttpParserType::Request, 154 | raw: "GET /favicon.ico HTTP/1.1\r\n\ 155 | Host: 0.0.0.0=5000\r\n\ 156 | User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n\ 157 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\ 158 | Accept-Language: en-us,en;q=0.5\r\n\ 159 | Accept-Encoding: gzip,deflate\r\n\ 160 | Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n\ 161 | Keep-Alive: 300\r\n\ 162 | Connection: keep-alive\r\n\ 163 | \r\n".to_string(), 164 | should_keep_alive: true, 165 | message_complete_on_eof: false, 166 | http_version: HttpVersion { major: 1, minor: 1 }, 167 | method: Some(HttpMethod::Get), 168 | query_string: "".to_string(), 169 | fragment: "".to_string(), 170 | request_path: "/favicon.ico".to_string(), 171 | request_url: { 172 | let mut v: Vec = Vec::new(); 173 | for b in "/favicon.ico".as_bytes() { 174 | v.push(*b); 175 | } 176 | v 177 | }, 178 | headers: vec![ 179 | [ "Host".to_string(), "0.0.0.0=5000".to_string() ], 180 | [ "User-Agent".to_string(), "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0".to_string() ], 181 | [ "Accept".to_string(), "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8".to_string() ], 182 | [ "Accept-Language".to_string(), "en-us,en;q=0.5".to_string() ], 183 | [ "Accept-Encoding".to_string(), "gzip,deflate".to_string() ], 184 | [ "Accept-Charset".to_string(), "ISO-8859-1,utf-8;q=0.7,*;q=0.7".to_string() ], 185 | [ "Keep-Alive".to_string(), "300".to_string() ], 186 | [ "Connection".to_string(), "keep-alive".to_string() ], 187 | ], 188 | body: "".to_string(), 189 | ..Default::default() 190 | }, 191 | helper::Message { 192 | name: "dumbfuck".to_string(), 193 | tp: HttpParserType::Request, 194 | raw: "GET /dumbfuck HTTP/1.1\r\n\ 195 | aaaaaaaaaaaaa: ++++++++++\r\n\ 196 | \r\n".to_string(), 197 | should_keep_alive: true, 198 | message_complete_on_eof: false, 199 | http_version: HttpVersion { major: 1, minor: 1 }, 200 | method: Some(HttpMethod::Get), 201 | query_string: "".to_string(), 202 | fragment: "".to_string(), 203 | request_path: "/dumbfuck".to_string(), 204 | request_url: { 205 | let mut v: Vec = Vec::new(); 206 | for b in "/dumbfuck".as_bytes() { 207 | v.push(*b); 208 | } 209 | v 210 | }, 211 | headers: vec![ 212 | [ "aaaaaaaaaaaaa".to_string(), "++++++++++".to_string() ], 213 | ], 214 | body: "".to_string(), 215 | ..Default::default() 216 | }, 217 | helper::Message { 218 | name: "fragment in url".to_string(), 219 | tp: HttpParserType::Request, 220 | raw: "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\ 221 | \r\n".to_string(), 222 | should_keep_alive: true, 223 | message_complete_on_eof: false, 224 | http_version: HttpVersion { major: 1, minor: 1 }, 225 | method: Some(HttpMethod::Get), 226 | query_string: "page=1".to_string(), 227 | fragment: "posts-17408".to_string(), 228 | request_path: "/forums/1/topics/2375".to_string(), 229 | request_url: { 230 | let mut v: Vec = Vec::new(); 231 | for b in "/forums/1/topics/2375?page=1#posts-17408".as_bytes() { 232 | v.push(*b); 233 | } 234 | v 235 | }, 236 | body: "".to_string(), 237 | ..Default::default() 238 | }, 239 | helper::Message { 240 | name: "get no headers no body".to_string(), 241 | tp: HttpParserType::Request, 242 | raw: "GET /get_no_headers_no_body/world HTTP/1.1\r\n\ 243 | \r\n".to_string(), 244 | should_keep_alive: true, 245 | message_complete_on_eof: false, 246 | http_version: HttpVersion { major: 1, minor: 1 }, 247 | method: Some(HttpMethod::Get), 248 | query_string: "".to_string(), 249 | fragment: "".to_string(), 250 | request_path: "/get_no_headers_no_body/world".to_string(), 251 | request_url: { 252 | let mut v: Vec = Vec::new(); 253 | for b in "/get_no_headers_no_body/world".as_bytes() { 254 | v.push(*b); 255 | } 256 | v 257 | }, 258 | body: "".to_string(), 259 | ..Default::default() 260 | }, 261 | helper::Message { 262 | name: "get one header no body".to_string(), 263 | tp: HttpParserType::Request, 264 | raw: "GET /get_one_header_no_body/world HTTP/1.1\r\n\ 265 | Accept: */*\r\n\ 266 | \r\n".to_string(), 267 | should_keep_alive: true, 268 | message_complete_on_eof: false, 269 | http_version: HttpVersion { major: 1, minor: 1 }, 270 | method: Some(HttpMethod::Get), 271 | query_string: "".to_string(), 272 | fragment: "".to_string(), 273 | request_path: "/get_one_header_no_body/world".to_string(), 274 | request_url: { 275 | let mut v: Vec = Vec::new(); 276 | for b in "/get_one_header_no_body/world".as_bytes() { 277 | v.push(*b); 278 | } 279 | v 280 | }, 281 | headers: vec![ 282 | [ "Accept".to_string(), "*/*".to_string() ], 283 | ], 284 | body: "".to_string(), 285 | ..Default::default() 286 | }, 287 | helper::Message { 288 | name: "get funky content length body hello".to_string(), 289 | tp: HttpParserType::Request, 290 | raw: "GET /get_funky_content_length_body_hello HTTP/1.0\r\n\ 291 | conTENT-Length: 5\r\n\ 292 | \r\n\ 293 | HELLO".to_string(), 294 | should_keep_alive: false, 295 | message_complete_on_eof: false, 296 | http_version: HttpVersion { major: 1, minor: 0 }, 297 | method: Some(HttpMethod::Get), 298 | query_string: "".to_string(), 299 | fragment: "".to_string(), 300 | request_path: "/get_funky_content_length_body_hello".to_string(), 301 | request_url: { 302 | let mut v: Vec = Vec::new(); 303 | for b in "/get_funky_content_length_body_hello".as_bytes() { 304 | v.push(*b); 305 | } 306 | v 307 | }, 308 | headers: vec![ 309 | [ "conTENT-Length".to_string(), "5".to_string() ] 310 | ], 311 | body: "HELLO".to_string(), 312 | ..Default::default() 313 | }, 314 | helper::Message { 315 | name: "post - chunked body: all your base are belong to us".to_string(), 316 | tp: HttpParserType::Request, 317 | raw: "POST /post_chunked_all_your_base HTTP/1.1\r\n\ 318 | Transfer-Encoding: chunked\r\n\ 319 | \r\n\ 320 | 1e\r\nall your base are belong to us\r\n\ 321 | 0\r\n\ 322 | \r\n".to_string(), 323 | should_keep_alive: true, 324 | message_complete_on_eof: false, 325 | http_version: HttpVersion { major: 1, minor: 1 }, 326 | method: Some(HttpMethod::Post), 327 | query_string: "".to_string(), 328 | fragment: "".to_string(), 329 | request_path: "/post_chunked_all_your_base".to_string(), 330 | request_url: { 331 | let mut v: Vec = Vec::new(); 332 | for b in "/post_chunked_all_your_base".as_bytes() { 333 | v.push(*b); 334 | } 335 | v 336 | }, 337 | headers: vec![ 338 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ] 339 | ], 340 | body: "all your base are belong to us".to_string(), 341 | ..Default::default() 342 | }, 343 | helper::Message { 344 | name: "two chunks ; triple zero ending".to_string(), 345 | tp: HttpParserType::Request, 346 | raw: "POST /two_chunks_mult_zero_end HTTP/1.1\r\n\ 347 | Transfer-Encoding: chunked\r\n\ 348 | \r\n\ 349 | 5\r\nhello\r\n\ 350 | 6\r\n world\r\n\ 351 | 000\r\n\ 352 | \r\n".to_string(), 353 | should_keep_alive: true, 354 | message_complete_on_eof: false, 355 | http_version: HttpVersion { major: 1, minor: 1 }, 356 | method: Some(HttpMethod::Post), 357 | query_string: "".to_string(), 358 | fragment: "".to_string(), 359 | request_path: "/two_chunks_mult_zero_end".to_string(), 360 | request_url: { 361 | let mut v: Vec = Vec::new(); 362 | for b in "/two_chunks_mult_zero_end".as_bytes() { 363 | v.push(*b); 364 | } 365 | v 366 | }, 367 | headers: vec![ 368 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ] 369 | ], 370 | body: "hello world".to_string(), 371 | ..Default::default() 372 | }, 373 | helper::Message { 374 | name: "chunked with trailing headers. blech.".to_string(), 375 | tp: HttpParserType::Request, 376 | raw: "POST /chunked_w_trailing_headers HTTP/1.1\r\n\ 377 | Transfer-Encoding: chunked\r\n\ 378 | \r\n\ 379 | 5\r\nhello\r\n\ 380 | 6\r\n world\r\n\ 381 | 0\r\n\ 382 | Vary: *\r\n\ 383 | Content-Type: text/plain\r\n\ 384 | \r\n".to_string(), 385 | should_keep_alive: true, 386 | message_complete_on_eof: false, 387 | http_version: HttpVersion { major: 1, minor: 1 }, 388 | method: Some(HttpMethod::Post), 389 | query_string: "".to_string(), 390 | fragment: "".to_string(), 391 | request_path: "/chunked_w_trailing_headers".to_string(), 392 | request_url: { 393 | let mut v: Vec = Vec::new(); 394 | for b in "/chunked_w_trailing_headers".as_bytes() { 395 | v.push(*b); 396 | } 397 | v 398 | }, 399 | headers: vec![ 400 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 401 | [ "Vary".to_string(), "*".to_string() ], 402 | [ "Content-Type".to_string(), "text/plain".to_string() ], 403 | ], 404 | body: "hello world".to_string(), 405 | ..Default::default() 406 | }, 407 | helper::Message { 408 | name: "with bullshit after the length".to_string(), 409 | tp: HttpParserType::Request, 410 | raw: "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n\ 411 | Transfer-Encoding: chunked\r\n\ 412 | \r\n\ 413 | 5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n\ 414 | 6; blahblah; blah\r\n world\r\n\ 415 | 0\r\n\ 416 | \r\n".to_string(), 417 | should_keep_alive: true, 418 | message_complete_on_eof: false, 419 | http_version: HttpVersion { major: 1, minor: 1 }, 420 | method: Some(HttpMethod::Post), 421 | query_string: "".to_string(), 422 | fragment: "".to_string(), 423 | request_path: "/chunked_w_bullshit_after_length".to_string(), 424 | request_url: { 425 | let mut v: Vec = Vec::new(); 426 | for b in "/chunked_w_bullshit_after_length".as_bytes() { 427 | v.push(*b); 428 | } 429 | v 430 | }, 431 | headers: vec![ 432 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 433 | ], 434 | body: "hello world".to_string(), 435 | ..Default::default() 436 | }, 437 | helper::Message { 438 | name: "with quotes".to_string(), 439 | tp: HttpParserType::Request, 440 | raw: "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\ 441 | \r\n".to_string(), 442 | should_keep_alive: true, 443 | message_complete_on_eof: false, 444 | http_version: HttpVersion { major: 1, minor: 1 }, 445 | method: Some(HttpMethod::Get), 446 | query_string: "foo=\"bar\"".to_string(), 447 | fragment: "".to_string(), 448 | request_path: "/with_\"stupid\"_quotes".to_string(), 449 | request_url: { 450 | let mut v: Vec = Vec::new(); 451 | for b in "/with_\"stupid\"_quotes?foo=\"bar\"".as_bytes() { 452 | v.push(*b); 453 | } 454 | v 455 | }, 456 | body: "".to_string(), 457 | ..Default::default() 458 | }, 459 | helper::Message { 460 | name: "apachebench get".to_string(), 461 | tp: HttpParserType::Request, 462 | raw: "GET /test HTTP/1.0\r\n\ 463 | Host: 0.0.0.0:5000\r\n\ 464 | User-Agent: ApacheBench/2.3\r\n\ 465 | Accept: */*\r\n\ 466 | \r\n".to_string(), 467 | should_keep_alive: false, 468 | message_complete_on_eof: false, 469 | http_version: HttpVersion { major: 1, minor: 0 }, 470 | method: Some(HttpMethod::Get), 471 | query_string: "".to_string(), 472 | fragment: "".to_string(), 473 | request_path: "/test".to_string(), 474 | request_url: { 475 | let mut v: Vec = Vec::new(); 476 | for b in "/test".as_bytes() { 477 | v.push(*b); 478 | } 479 | v 480 | }, 481 | headers: vec![ 482 | [ "Host".to_string(), "0.0.0.0:5000".to_string() ], 483 | [ "User-Agent".to_string(), "ApacheBench/2.3".to_string() ], 484 | [ "Accept".to_string(), "*/*".to_string() ], 485 | ], 486 | body: "".to_string(), 487 | ..Default::default() 488 | }, 489 | helper::Message { 490 | name: "query url with question mark".to_string(), 491 | tp: HttpParserType::Request, 492 | raw: "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\ 493 | \r\n".to_string(), 494 | should_keep_alive: true, 495 | message_complete_on_eof: false, 496 | http_version: HttpVersion { major: 1, minor: 1 }, 497 | method: Some(HttpMethod::Get), 498 | query_string: "foo=bar?baz".to_string(), 499 | fragment: "".to_string(), 500 | request_path: "/test.cgi".to_string(), 501 | request_url: { 502 | let mut v: Vec = Vec::new(); 503 | for b in "/test.cgi?foo=bar?baz".as_bytes() { 504 | v.push(*b); 505 | } 506 | v 507 | }, 508 | body: "".to_string(), 509 | ..Default::default() 510 | }, 511 | helper::Message { 512 | name: "newline prefix get".to_string(), 513 | tp: HttpParserType::Request, 514 | raw: "\r\nGET /test HTTP/1.1\r\n\ 515 | \r\n".to_string(), 516 | should_keep_alive: true, 517 | message_complete_on_eof: false, 518 | http_version: HttpVersion { major: 1, minor: 1 }, 519 | method: Some(HttpMethod::Get), 520 | query_string: "".to_string(), 521 | fragment: "".to_string(), 522 | request_path: "/test".to_string(), 523 | request_url: { 524 | let mut v: Vec = Vec::new(); 525 | for b in "/test".as_bytes() { 526 | v.push(*b); 527 | } 528 | v 529 | }, 530 | body: "".to_string(), 531 | ..Default::default() 532 | }, 533 | helper::Message { 534 | name: "upgrade request".to_string(), 535 | tp: HttpParserType::Request, 536 | raw: "GET /demo HTTP/1.1\r\n\ 537 | Host: example.com\r\n\ 538 | Connection: Upgrade\r\n\ 539 | Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n\ 540 | Sec-WebSocket-Protocol: sample\r\n\ 541 | Upgrade: WebSocket\r\n\ 542 | Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n\ 543 | Origin: http://example.com\r\n\ 544 | \r\n\ 545 | Hot diggity dogg".to_string(), 546 | should_keep_alive: true, 547 | message_complete_on_eof: false, 548 | http_version: HttpVersion { major: 1, minor: 1 }, 549 | method: Some(HttpMethod::Get), 550 | query_string: "".to_string(), 551 | fragment: "".to_string(), 552 | request_path: "/demo".to_string(), 553 | request_url: { 554 | let mut v: Vec = Vec::new(); 555 | for b in "/demo".as_bytes() { 556 | v.push(*b); 557 | } 558 | v 559 | }, 560 | upgrade: Some("Hot diggity dogg".to_string()), 561 | headers: vec![ 562 | [ "Host".to_string(), "example.com".to_string() ], 563 | [ "Connection".to_string(), "Upgrade".to_string() ], 564 | [ "Sec-WebSocket-Key2".to_string(), "12998 5 Y3 1 .P00".to_string() ], 565 | [ "Sec-WebSocket-Protocol".to_string(), "sample".to_string() ], 566 | [ "Upgrade".to_string(), "WebSocket".to_string() ], 567 | [ "Sec-WebSocket-Key1".to_string(), "4 @1 46546xW%0l 1 5".to_string() ], 568 | [ "Origin".to_string(), "http://example.com".to_string() ], 569 | ], 570 | body: "".to_string(), 571 | ..Default::default() 572 | }, 573 | helper::Message { 574 | name: "connect request".to_string(), 575 | tp: HttpParserType::Request, 576 | raw: "CONNECT 0-home0.netscape.com:443 HTTP/1.0\r\n\ 577 | User-agent: Mozilla/1.1N\r\n\ 578 | Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n\ 579 | \r\n\ 580 | some data\r\n\ 581 | and yet even more data".to_string(), 582 | should_keep_alive: false, 583 | message_complete_on_eof: false, 584 | http_version: HttpVersion { major: 1, minor: 0 }, 585 | method: Some(HttpMethod::Connect), 586 | query_string: "".to_string(), 587 | fragment: "".to_string(), 588 | request_path: "".to_string(), 589 | request_url: { 590 | let mut v: Vec = Vec::new(); 591 | for b in "0-home0.netscape.com:443".as_bytes() { 592 | v.push(*b); 593 | } 594 | v 595 | }, 596 | upgrade: Some("some data\r\nand yet even more data".to_string()), 597 | headers: vec![ 598 | [ "User-agent".to_string(), "Mozilla/1.1N".to_string() ], 599 | [ "Proxy-authorization".to_string(), "basic aGVsbG86d29ybGQ=".to_string() ], 600 | ], 601 | body: "".to_string(), 602 | ..Default::default() 603 | }, 604 | helper::Message { 605 | name: "report request".to_string(), 606 | tp: HttpParserType::Request, 607 | raw: "REPORT /test HTTP/1.1\r\n\ 608 | \r\n".to_string(), 609 | should_keep_alive: true, 610 | message_complete_on_eof: false, 611 | http_version: HttpVersion { major: 1, minor: 1 }, 612 | method: Some(HttpMethod::Report), 613 | query_string: "".to_string(), 614 | fragment: "".to_string(), 615 | request_path: "/test".to_string(), 616 | request_url: { 617 | let mut v: Vec = Vec::new(); 618 | for b in "/test".as_bytes() { 619 | v.push(*b); 620 | } 621 | v 622 | }, 623 | body: "".to_string(), 624 | ..Default::default() 625 | }, 626 | helper::Message { 627 | name: "request with no http version".to_string(), 628 | tp: HttpParserType::Request, 629 | raw: "GET /\r\n\ 630 | \r\n".to_string(), 631 | should_keep_alive: false, 632 | message_complete_on_eof: false, 633 | http_version: HttpVersion { major: 0, minor: 9 }, 634 | method: Some(HttpMethod::Get), 635 | query_string: "".to_string(), 636 | fragment: "".to_string(), 637 | request_path: "/".to_string(), 638 | request_url: { 639 | let mut v: Vec = Vec::new(); 640 | v.push(b'/'); 641 | v 642 | }, 643 | body: "".to_string(), 644 | ..Default::default() 645 | }, 646 | helper::Message { 647 | name: "m-search request".to_string(), 648 | tp: HttpParserType::Request, 649 | raw: "M-SEARCH * HTTP/1.1\r\n\ 650 | HOST: 239.255.255.250:1900\r\n\ 651 | MAN: \"ssdp:discover\"\r\n\ 652 | ST: \"ssdp:all\"\r\n\ 653 | \r\n".to_string(), 654 | should_keep_alive: true, 655 | message_complete_on_eof: false, 656 | http_version: HttpVersion { major: 1, minor: 1 }, 657 | method: Some(HttpMethod::MSearch), 658 | query_string: "".to_string(), 659 | fragment: "".to_string(), 660 | request_path: "*".to_string(), 661 | request_url: { 662 | let mut v: Vec = Vec::new(); 663 | v.push(b'*'); 664 | v 665 | }, 666 | headers: vec![ 667 | [ "HOST".to_string(), "239.255.255.250:1900".to_string()], 668 | [ "MAN".to_string(), "\"ssdp:discover\"".to_string()], 669 | [ "ST".to_string(), "\"ssdp:all\"".to_string()], 670 | ], 671 | body: "".to_string(), 672 | ..Default::default() 673 | }, 674 | helper::Message { 675 | name: "line folding in header value".to_string(), 676 | tp: HttpParserType::Request, 677 | raw: "GET / HTTP/1.1\r\n\ 678 | Line1: abc\r\n\tdef\r\n ghi\r\n\t\tjkl\r\n mno \r\n\t \tqrs\r\n\ 679 | Line2: \t line2\t\r\n\ 680 | Line3:\r\n line3\r\n\ 681 | Line4: \r\n \r\n\ 682 | Connection:\r\n close\r\n\ 683 | \r\n".to_string(), 684 | should_keep_alive: false, 685 | message_complete_on_eof: false, 686 | http_version: HttpVersion { major: 1, minor: 1 }, 687 | method: Some(HttpMethod::Get), 688 | query_string: "".to_string(), 689 | fragment: "".to_string(), 690 | request_path: "/".to_string(), 691 | request_url: { 692 | let mut v: Vec = Vec::new(); 693 | v.push(b'/'); 694 | v 695 | }, 696 | headers: vec![ 697 | [ "Line1".to_string(), "abc\tdef ghi\t\tjkl mno \t \tqrs".to_string()], 698 | [ "Line2".to_string(), "line2\t".to_string()], 699 | [ "Line3".to_string(), "line3".to_string()], 700 | [ "Line4".to_string(), "".to_string()], 701 | [ "Connection".to_string(), "close".to_string()], 702 | ], 703 | body: "".to_string(), 704 | ..Default::default() 705 | }, 706 | helper::Message { 707 | name: "host terminated by a query string".to_string(), 708 | tp: HttpParserType::Request, 709 | raw: "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n\ 710 | \r\n".to_string(), 711 | should_keep_alive: true, 712 | message_complete_on_eof: false, 713 | http_version: HttpVersion { major: 1, minor: 1 }, 714 | method: Some(HttpMethod::Get), 715 | query_string: "hail=all".to_string(), 716 | fragment: "".to_string(), 717 | request_path: "".to_string(), 718 | request_url: { 719 | let mut v: Vec = Vec::new(); 720 | for b in "http://hypnotoad.org?hail=all".as_bytes() { 721 | v.push(*b); 722 | } 723 | v 724 | }, 725 | host: "hypnotoad.org".to_string(), 726 | body: "".to_string(), 727 | ..Default::default() 728 | }, 729 | helper::Message { 730 | name: "host:port terminated by a query string".to_string(), 731 | tp: HttpParserType::Request, 732 | raw: "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n\ 733 | \r\n".to_string(), 734 | should_keep_alive: true, 735 | message_complete_on_eof: false, 736 | http_version: HttpVersion { major: 1, minor: 1 }, 737 | method: Some(HttpMethod::Get), 738 | query_string: "hail=all".to_string(), 739 | fragment: "".to_string(), 740 | request_path: "".to_string(), 741 | request_url: { 742 | let mut v: Vec = Vec::new(); 743 | for b in "http://hypnotoad.org:1234?hail=all".as_bytes() { 744 | v.push(*b); 745 | } 746 | v 747 | }, 748 | host: "hypnotoad.org".to_string(), 749 | port: 1234, 750 | body: "".to_string(), 751 | ..Default::default() 752 | }, 753 | helper::Message { 754 | name: "host:port terminated by a space".to_string(), 755 | tp: HttpParserType::Request, 756 | raw: "GET http://hypnotoad.org:1234 HTTP/1.1\r\n\ 757 | \r\n".to_string(), 758 | should_keep_alive: true, 759 | message_complete_on_eof: false, 760 | http_version: HttpVersion { major: 1, minor: 1 }, 761 | method: Some(HttpMethod::Get), 762 | query_string: "".to_string(), 763 | fragment: "".to_string(), 764 | request_path: "".to_string(), 765 | request_url: { 766 | let mut v: Vec = Vec::new(); 767 | for b in "http://hypnotoad.org:1234".as_bytes() { 768 | v.push(*b); 769 | } 770 | v 771 | }, 772 | host: "hypnotoad.org".to_string(), 773 | port: 1234, 774 | body: "".to_string(), 775 | ..Default::default() 776 | }, 777 | helper::Message { 778 | name: "PATCH request".to_string(), 779 | tp: HttpParserType::Request, 780 | raw: "PATCH /file.txt HTTP/1.1\r\n\ 781 | Host: www.example.com\r\n\ 782 | Content-Type: application/example\r\n\ 783 | If-Match: \"e0023aa4e\"\r\n\ 784 | Content-Length: 10\r\n\ 785 | \r\n\ 786 | cccccccccc".to_string(), 787 | should_keep_alive: true, 788 | message_complete_on_eof: false, 789 | http_version: HttpVersion { major: 1, minor: 1 }, 790 | method: Some(HttpMethod::Patch), 791 | query_string: "".to_string(), 792 | fragment: "".to_string(), 793 | request_path: "/file.txt".to_string(), 794 | request_url: { 795 | let mut v: Vec = Vec::new(); 796 | for b in "/file.txt".as_bytes() { 797 | v.push(*b); 798 | } 799 | v 800 | }, 801 | headers: vec![ 802 | [ "Host".to_string(), "www.example.com".to_string() ], 803 | [ "Content-Type".to_string(), "application/example".to_string() ], 804 | [ "If-Match".to_string(), "\"e0023aa4e\"".to_string() ], 805 | [ "Content-Length".to_string(), "10".to_string() ], 806 | ], 807 | body: "cccccccccc".to_string(), 808 | ..Default::default() 809 | }, 810 | helper::Message { 811 | name: "connect caps request".to_string(), 812 | tp: HttpParserType::Request, 813 | raw: "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n\ 814 | User-agent: Mozilla/1.1N\r\n\ 815 | Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n\ 816 | \r\n".to_string(), 817 | should_keep_alive: false, 818 | message_complete_on_eof: false, 819 | http_version: HttpVersion { major: 1, minor: 0 }, 820 | method: Some(HttpMethod::Connect), 821 | query_string: "".to_string(), 822 | fragment: "".to_string(), 823 | request_path: "".to_string(), 824 | request_url: { 825 | let mut v: Vec = Vec::new(); 826 | for b in "HOME0.NETSCAPE.COM:443".as_bytes() { 827 | v.push(*b); 828 | } 829 | v 830 | }, 831 | upgrade: Some("".to_string()), 832 | headers: vec![ 833 | [ "User-agent".to_string(), "Mozilla/1.1N".to_string() ], 834 | [ "Proxy-authorization".to_string(), "basic aGVsbG86d29ybGQ=".to_string() ], 835 | ], 836 | body: "".to_string(), 837 | ..Default::default() 838 | }, 839 | helper::Message { 840 | name: "utf-8 path request".to_string(), 841 | tp: HttpParserType::Request, 842 | strict: false, 843 | raw: "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n\ 844 | Host: github.com\r\n\ 845 | \r\n".to_string(), 846 | should_keep_alive: true, 847 | message_complete_on_eof: false, 848 | http_version: HttpVersion { major: 1, minor: 1 }, 849 | method: Some(HttpMethod::Get), 850 | query_string: "q=1".to_string(), 851 | fragment: "narf".to_string(), 852 | request_path: "/δ¶/δt/pope".to_string(), 853 | request_url: { 854 | let mut v: Vec = Vec::new(); 855 | for b in "/δ¶/δt/pope?q=1#narf".as_bytes() { 856 | v.push(*b); 857 | } 858 | v 859 | }, 860 | headers: vec![ 861 | [ "Host".to_string(), "github.com".to_string() ], 862 | ], 863 | body: "".to_string(), 864 | ..Default::default() 865 | }, 866 | helper::Message { 867 | name: "hostname underscore".to_string(), 868 | tp: HttpParserType::Request, 869 | strict: false, 870 | raw: "CONNECT home_0.netscape.com:443 HTTP/1.0\r\n\ 871 | User-agent: Mozilla/1.1N\r\n\ 872 | Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n\ 873 | \r\n".to_string(), 874 | should_keep_alive: false, 875 | message_complete_on_eof: false, 876 | http_version: HttpVersion { major: 1, minor: 0 }, 877 | method: Some(HttpMethod::Connect), 878 | query_string: "".to_string(), 879 | fragment: "".to_string(), 880 | request_path: "".to_string(), 881 | request_url: { 882 | let mut v: Vec = Vec::new(); 883 | for b in "home_0.netscape.com:443".as_bytes() { 884 | v.push(*b); 885 | } 886 | v 887 | }, 888 | upgrade: Some(String::new()), 889 | headers: vec![ 890 | [ "User-agent".to_string(), "Mozilla/1.1N".to_string() ], 891 | [ "Proxy-authorization".to_string(), "basic aGVsbG86d29ybGQ=".to_string() ], 892 | ], 893 | body: "".to_string(), 894 | ..Default::default() 895 | }, 896 | helper::Message { 897 | name: "eat CRLF between requests, no \"Connection: close\" header".to_string(), 898 | raw: "POST / HTTP/1.1\r\n\ 899 | Host: www.example.com\r\n\ 900 | Content-Type: application/x-www-form-urlencoded\r\n\ 901 | Content-Length: 4\r\n\ 902 | \r\n\ 903 | q=42\r\n".to_string(), 904 | should_keep_alive: true, 905 | message_complete_on_eof: false, 906 | http_version: HttpVersion { major: 1, minor: 1 }, 907 | method: Some(HttpMethod::Post), 908 | query_string: "".to_string(), 909 | fragment: "".to_string(), 910 | request_path: "/".to_string(), 911 | request_url: { 912 | let mut v: Vec = Vec::new(); 913 | v.push(b'/'); 914 | v 915 | }, 916 | headers: vec![ 917 | [ "Host".to_string(), "www.example.com".to_string() ], 918 | [ "Content-Type".to_string(), "application/x-www-form-urlencoded".to_string() ], 919 | [ "Content-Length".to_string(), "4".to_string() ], 920 | ], 921 | body: "q=42".to_string(), 922 | ..Default::default() 923 | }, 924 | helper::Message { 925 | name: "eat CRLF between requests even if \"Connection: close\" is set".to_string(), 926 | raw: "POST / HTTP/1.1\r\n\ 927 | Host: www.example.com\r\n\ 928 | Content-Type: application/x-www-form-urlencoded\r\n\ 929 | Content-Length: 4\r\n\ 930 | Connection: close\r\n\ 931 | \r\n\ 932 | q=42\r\n".to_string(), 933 | should_keep_alive: false, 934 | message_complete_on_eof: false, 935 | http_version: HttpVersion { major: 1, minor: 1 }, 936 | method: Some(HttpMethod::Post), 937 | query_string: "".to_string(), 938 | fragment: "".to_string(), 939 | request_path: "/".to_string(), 940 | request_url: { 941 | let mut v: Vec = Vec::new(); 942 | v.push(b'/'); 943 | v 944 | }, 945 | headers: vec![ 946 | [ "Host".to_string(), "www.example.com".to_string() ], 947 | [ "Content-Type".to_string(), "application/x-www-form-urlencoded".to_string() ], 948 | [ "Content-Length".to_string(), "4".to_string() ], 949 | [ "Connection".to_string(), "close".to_string() ], 950 | ], 951 | body: "q=42".to_string(), 952 | ..Default::default() 953 | }, 954 | helper::Message { 955 | name: "PURGE request".to_string(), 956 | tp: HttpParserType::Request, 957 | raw: "PURGE /file.txt HTTP/1.1\r\n\ 958 | Host: www.example.com\r\n\ 959 | \r\n".to_string(), 960 | should_keep_alive: true, 961 | message_complete_on_eof: false, 962 | http_version: HttpVersion { major: 1, minor: 1 }, 963 | method: Some(HttpMethod::Purge), 964 | query_string: "".to_string(), 965 | fragment: "".to_string(), 966 | request_path: "/file.txt".to_string(), 967 | request_url: { 968 | let mut v: Vec = Vec::new(); 969 | for b in "/file.txt".as_bytes() { 970 | v.push(*b); 971 | } 972 | v 973 | }, 974 | headers: vec![ 975 | [ "Host".to_string(), "www.example.com".to_string() ], 976 | ], 977 | body: "".to_string(), 978 | ..Default::default() 979 | }, 980 | helper::Message { 981 | name: "SEARCH request".to_string(), 982 | tp: HttpParserType::Request, 983 | raw: "SEARCH / HTTP/1.1\r\n\ 984 | Host: www.example.com\r\n\ 985 | \r\n".to_string(), 986 | should_keep_alive: true, 987 | message_complete_on_eof: false, 988 | http_version: HttpVersion { major: 1, minor: 1 }, 989 | method: Some(HttpMethod::Search), 990 | query_string: "".to_string(), 991 | fragment: "".to_string(), 992 | request_path: "/".to_string(), 993 | request_url: { 994 | let mut v: Vec = Vec::new(); 995 | v.push(b'/'); 996 | v 997 | }, 998 | headers: vec![ 999 | [ "Host".to_string(), "www.example.com".to_string() ], 1000 | ], 1001 | body: "".to_string(), 1002 | ..Default::default() 1003 | }, 1004 | helper::Message { 1005 | name: "host:port and basic_auth".to_string(), 1006 | tp: HttpParserType::Request, 1007 | raw: "GET http://a%12:b!&*$@hypnotoad.org:1234/toto HTTP/1.1\r\n\ 1008 | \r\n".to_string(), 1009 | should_keep_alive: true, 1010 | message_complete_on_eof: false, 1011 | http_version: HttpVersion { major: 1, minor: 1 }, 1012 | method: Some(HttpMethod::Get), 1013 | query_string: "".to_string(), 1014 | fragment: "".to_string(), 1015 | request_path: "/toto".to_string(), 1016 | request_url: { 1017 | let mut v: Vec = Vec::new(); 1018 | for b in "http://a%12:b!&*$@hypnotoad.org:1234/toto".as_bytes() { 1019 | v.push(*b); 1020 | } 1021 | v 1022 | }, 1023 | host: "hypnotoad.org".to_string(), 1024 | userinfo: "a%12:b!&*$".to_string(), 1025 | port: 1234, 1026 | body: "".to_string(), 1027 | ..Default::default() 1028 | }, 1029 | helper::Message { 1030 | name: "line folding in header value".to_string(), 1031 | tp: HttpParserType::Request, 1032 | raw: "GET / HTTP/1.1\r\n\ 1033 | Line1: abc\n\tdef\n ghi\n\t\tjkl\n mno \n\t \tqrs\n\ 1034 | Line2: \t line2\t\n\ 1035 | Line3:\n line3\n\ 1036 | Line4: \n \n\ 1037 | Connection:\n close\n\ 1038 | \n".to_string(), 1039 | should_keep_alive: false, 1040 | message_complete_on_eof: false, 1041 | http_version: HttpVersion { major: 1, minor: 1 }, 1042 | method: Some(HttpMethod::Get), 1043 | query_string: "".to_string(), 1044 | fragment: "".to_string(), 1045 | request_path: "/".to_string(), 1046 | request_url: { 1047 | let mut v: Vec = Vec::new(); 1048 | v.push(b'/'); 1049 | v 1050 | }, 1051 | headers: vec![ 1052 | [ "Line1".to_string(), "abc\tdef ghi\t\tjkl mno \t \tqrs".to_string() ], 1053 | [ "Line2".to_string(), "line2\t".to_string() ], 1054 | [ "Line3".to_string(), "line3".to_string() ], 1055 | [ "Line4".to_string(), "".to_string() ], 1056 | [ "Connection".to_string(), "close".to_string() ], 1057 | ], 1058 | body: "".to_string(), 1059 | ..Default::default() 1060 | }, 1061 | helper::Message { 1062 | name: "multiple connection header values with folding".to_string(), 1063 | tp: HttpParserType::Request, 1064 | raw: "GET /demo HTTP/1.1\r\n\ 1065 | Host: example.com\r\n\ 1066 | Connection: Something,\r\n Upgrade, ,Keep-Alive\r\n\ 1067 | Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n\ 1068 | Sec-WebSocket-Protocol: sample\r\n\ 1069 | Upgrade: WebSocket\r\n\ 1070 | Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n\ 1071 | Origin: http://example.com\r\n\ 1072 | \r\n\ 1073 | Hot diggity dogg".to_string(), 1074 | should_keep_alive: true, 1075 | message_complete_on_eof: false, 1076 | http_version: HttpVersion { major: 1, minor: 1 }, 1077 | method: Some(HttpMethod::Get), 1078 | query_string: "".to_string(), 1079 | fragment: "".to_string(), 1080 | request_path: "/demo".to_string(), 1081 | request_url: { 1082 | let mut v = Vec::new(); 1083 | for b in "/demo".as_bytes() { 1084 | v.push(*b); 1085 | } 1086 | v 1087 | }, 1088 | upgrade: Some("Hot diggity dogg".to_string()), 1089 | headers: vec![ 1090 | [ "Host".to_string(), "example.com".to_string() ], 1091 | [ "Connection".to_string(), "Something, Upgrade, ,Keep-Alive".to_string() ], 1092 | [ "Sec-WebSocket-Key2".to_string(), "12998 5 Y3 1 .P00".to_string() ], 1093 | [ "Sec-WebSocket-Protocol".to_string(), "sample".to_string() ], 1094 | [ "Upgrade".to_string(), "WebSocket".to_string() ], 1095 | [ "Sec-WebSocket-Key1".to_string(), "4 @1 46546xW%0l 1 5".to_string() ], 1096 | [ "Origin".to_string(), "http://example.com".to_string() ], 1097 | ], 1098 | body: "".to_string(), 1099 | ..Default::default() 1100 | }, 1101 | ]; 1102 | 1103 | const GET_NO_HEADERS_NO_BODY : usize = 4; 1104 | const GET_ONE_HEADER_NO_BODY : usize = 5; 1105 | const GET_FUNKY_CONTENT_LENGTH : usize = 6; 1106 | const POST_IDENTITY_BODY_WORLD : usize = 7; 1107 | const POST_CHUNKED_ALL_YOUR_BASE : usize = 8; 1108 | const TWO_CHUNKS_MULT_ZERO_END : usize = 9; 1109 | const CHUNKED_W_TRAILING_HEADERS : usize = 10; 1110 | const CHUNKED_W_BULLSHIT_AFTER_LENGTH : usize = 11; 1111 | const QUERY_URL_WITH_QUESTION_MARK_GET : usize = 14; 1112 | const PREFIX_NEWLINE_GET : usize = 15; 1113 | const CONNECT_REQUEST : usize = 17; 1114 | 1115 | // End REQUESTS 1116 | 1117 | for m in requests.iter() { 1118 | helper::test_message(m); 1119 | } 1120 | for m in requests.iter() { 1121 | helper::test_message_pause(m); 1122 | } 1123 | 1124 | for r1 in requests.iter() { 1125 | if !r1.should_keep_alive { continue; } 1126 | for r2 in requests.iter() { 1127 | if !r2.should_keep_alive { continue; } 1128 | for r3 in requests.iter() { 1129 | helper::test_multiple3(r1, r2, r3); 1130 | } 1131 | } 1132 | } 1133 | 1134 | helper::test_scan(&requests[GET_NO_HEADERS_NO_BODY], 1135 | &requests[GET_ONE_HEADER_NO_BODY], 1136 | &requests[GET_NO_HEADERS_NO_BODY]); 1137 | 1138 | helper::test_scan(&requests[POST_CHUNKED_ALL_YOUR_BASE], 1139 | &requests[POST_IDENTITY_BODY_WORLD], 1140 | &requests[GET_FUNKY_CONTENT_LENGTH]); 1141 | 1142 | helper::test_scan(&requests[TWO_CHUNKS_MULT_ZERO_END], 1143 | &requests[CHUNKED_W_TRAILING_HEADERS], 1144 | &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]); 1145 | 1146 | helper::test_scan(&requests[QUERY_URL_WITH_QUESTION_MARK_GET], 1147 | &requests[PREFIX_NEWLINE_GET], 1148 | &requests[CONNECT_REQUEST]); 1149 | } 1150 | 1151 | fn test_simple(buf: &str, err_expected: Option) { 1152 | let mut hp = HttpParser::new(HttpParserType::Request); 1153 | 1154 | let mut cb = helper::CallbackRegular{..Default::default()}; 1155 | cb.messages.push(helper::Message{..Default::default()}); 1156 | 1157 | hp.execute(&mut cb, buf.as_bytes()); 1158 | let err = hp.errno; 1159 | cb.currently_parsing_eof = true; 1160 | hp.execute(&mut cb, &[]); 1161 | 1162 | assert!(err_expected == err || 1163 | (hp.strict && (err_expected.is_none() || err == Option::Some(HttpErrno::Strict))), 1164 | "\n*** test_simple expected {}, but saw {} ***\n\n{}\n", 1165 | err_expected.unwrap().to_string(), err.unwrap().to_string(), buf); 1166 | } 1167 | -------------------------------------------------------------------------------- /tests/test_responses.rs: -------------------------------------------------------------------------------- 1 | extern crate http_parser; 2 | 3 | use std::default::Default; 4 | 5 | use http_parser::{HttpParser, HttpParserType, HttpVersion}; 6 | 7 | pub mod helper; 8 | 9 | #[test] 10 | fn test_responses() { 11 | // RESPONSES 12 | let responses: [helper::Message; 22] = [ 13 | helper::Message { 14 | name: "google 301".to_string(), 15 | tp: HttpParserType::Response, 16 | raw: "HTTP/1.1 301 Moved Permanently\r\n\ 17 | Location: http://www.google.com/\r\n\ 18 | Content-Type: text/html; charset=UTF-8\r\n\ 19 | Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n\ 20 | Expires: Tue, 26 May 2009 11:11:49 GMT\r\n\ 21 | X-$PrototypeBI-Version: 1.6.0.3\r\n\ 22 | Cache-Control: public, max-age=2592000\r\n\ 23 | Server: gws\r\n\ 24 | Content-Length: 219 \r\n\ 25 | \r\n\ 26 | \n\ 27 | 301 Moved\n\ 28 |

301 Moved

\n\ 29 | The document has moved\n\ 30 | here.\r\n\ 31 | \r\n".to_string(), 32 | should_keep_alive: true, 33 | http_version: HttpVersion { major: 1, minor: 1 }, 34 | status_code: Some(301), 35 | response_status: { 36 | let mut v: Vec = Vec::new(); 37 | for b in "Moved Permanently".as_bytes() { 38 | v.push(*b); 39 | } 40 | v 41 | }, 42 | headers: vec![ 43 | [ "Location".to_string(), "http://www.google.com/".to_string() ], 44 | [ "Content-Type".to_string(), "text/html; charset=UTF-8".to_string() ], 45 | [ "Date".to_string(), "Sun, 26 Apr 2009 11:11:49 GMT".to_string() ], 46 | [ "Expires".to_string(), "Tue, 26 May 2009 11:11:49 GMT".to_string() ], 47 | [ "X-$PrototypeBI-Version".to_string(), "1.6.0.3".to_string() ], 48 | [ "Cache-Control".to_string(), "public, max-age=2592000".to_string() ], 49 | [ "Server".to_string(), "gws".to_string() ], 50 | [ "Content-Length".to_string(), "219 ".to_string() ], 51 | ], 52 | body: "\n\ 53 | 301 Moved\n\ 54 |

301 Moved

\n\ 55 | The document has moved\n\ 56 | here.\r\n\ 57 | \r\n".to_string(), 58 | ..Default::default() 59 | }, 60 | helper::Message { 61 | name: "no content-length response".to_string(), 62 | tp: HttpParserType::Response, 63 | raw: "HTTP/1.1 200 OK\r\n\ 64 | Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n\ 65 | Server: Apache\r\n\ 66 | X-Powered-By: Servlet/2.5 JSP/2.1\r\n\ 67 | Content-Type: text/xml; charset=utf-8\r\n\ 68 | Connection: close\r\n\ 69 | \r\n\ 70 | \n\ 71 | \n\ 72 | \n\ 73 | \n\ 74 | SOAP-ENV:Client\n\ 75 | Client Error\n\ 76 | \n\ 77 | \n\ 78 | ".to_string(), 79 | should_keep_alive: false, 80 | message_complete_on_eof: true, 81 | http_version: HttpVersion { major: 1, minor: 1 }, 82 | status_code: Some(200), 83 | response_status: { 84 | let mut v: Vec = Vec::new(); 85 | for b in "OK".as_bytes() { 86 | v.push(*b); 87 | } 88 | v 89 | }, 90 | headers: vec![ 91 | [ "Date".to_string(), "Tue, 04 Aug 2009 07:59:32 GMT".to_string() ], 92 | [ "Server".to_string(), "Apache".to_string() ], 93 | [ "X-Powered-By".to_string(), "Servlet/2.5 JSP/2.1".to_string() ], 94 | [ "Content-Type".to_string(), "text/xml; charset=utf-8".to_string() ], 95 | [ "Connection".to_string(), "close".to_string() ], 96 | ], 97 | body: "\n\ 98 | \n\ 99 | \n\ 100 | \n\ 101 | SOAP-ENV:Client\n\ 102 | Client Error\n\ 103 | \n\ 104 | \n\ 105 | ".to_string(), 106 | ..Default::default() 107 | }, 108 | helper::Message { 109 | name: "404 no headers no body".to_string(), 110 | tp: HttpParserType::Response, 111 | raw: "HTTP/1.1 404 Not Found\r\n\r\n".to_string(), 112 | should_keep_alive: false, 113 | message_complete_on_eof: true, 114 | http_version: HttpVersion { major: 1, minor: 1 }, 115 | status_code: Some(404), 116 | response_status: { 117 | let mut v: Vec = Vec::new(); 118 | for b in "Not Found".as_bytes() { 119 | v.push(*b); 120 | } 121 | v 122 | }, 123 | headers: vec![ ], 124 | body_size: 0, 125 | body: "".to_string(), 126 | ..Default::default() 127 | }, 128 | helper::Message { 129 | name: "301 no response phrase".to_string(), 130 | tp: HttpParserType::Response, 131 | raw: "HTTP/1.1 301\r\n\r\n".to_string(), 132 | should_keep_alive: false, 133 | message_complete_on_eof: true, 134 | http_version: HttpVersion { major: 1, minor: 1 }, 135 | status_code: Some(301), 136 | response_status: { 137 | let mut v: Vec = Vec::new(); 138 | // FIXME no need to push? 139 | for b in "".as_bytes() { 140 | v.push(*b); 141 | } 142 | v 143 | }, 144 | headers: vec![ ], 145 | body: "".to_string(), 146 | ..Default::default() 147 | }, 148 | helper::Message { 149 | name: "200 trailing space on chunked body".to_string(), 150 | tp: HttpParserType::Response, 151 | raw: "HTTP/1.1 200 OK\r\n\ 152 | Content-Type: text/plain\r\n\ 153 | Transfer-Encoding: chunked\r\n\ 154 | \r\n\ 155 | 25 \r\n\ 156 | This is the data in the first chunk\r\n\ 157 | \r\n\ 158 | 1C\r\n\ 159 | and this is the second one\r\n\ 160 | \r\n\ 161 | 0 \r\n\ 162 | \r\n".to_string(), 163 | should_keep_alive: true, 164 | message_complete_on_eof: false, 165 | http_version: HttpVersion { major: 1, minor: 1 }, 166 | status_code: Some(200), 167 | response_status: { 168 | let mut v: Vec = Vec::new(); 169 | for b in "OK".as_bytes() { 170 | v.push(*b); 171 | } 172 | v 173 | }, 174 | headers: vec![ 175 | [ "Content-Type".to_string(), "text/plain".to_string() ], 176 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 177 | ], 178 | body_size: 37+28, 179 | body: "This is the data in the first chunk\r\n\ 180 | and this is the second one\r\n".to_string(), 181 | ..Default::default() 182 | }, 183 | helper::Message { 184 | name: "no carriage ret".to_string(), 185 | tp: HttpParserType::Response, 186 | raw: "HTTP/1.1 200 OK\n\ 187 | Content-Type: text/html; charset=utf-8\n\ 188 | Connection: close\n\ 189 | \n\ 190 | these headers are from http://news.ycombinator.com/".to_string(), 191 | should_keep_alive: false, 192 | message_complete_on_eof: true, 193 | http_version: HttpVersion { major: 1, minor: 1 }, 194 | status_code: Some(200), 195 | response_status: { 196 | let mut v: Vec = Vec::new(); 197 | for b in "OK".as_bytes() { 198 | v.push(*b); 199 | } 200 | v 201 | }, 202 | headers: vec![ 203 | [ "Content-Type".to_string(), "text/html; charset=utf-8".to_string() ], 204 | [ "Connection".to_string(), "close".to_string() ], 205 | ], 206 | body: "these headers are from http://news.ycombinator.com/".to_string(), 207 | ..Default::default() 208 | }, 209 | helper::Message { 210 | name: "proxy connection".to_string(), 211 | tp: HttpParserType::Response, 212 | raw: "HTTP/1.1 200 OK\r\n\ 213 | Content-Type: text/html; charset=UTF-8\r\n\ 214 | Content-Length: 11\r\n\ 215 | Proxy-Connection: close\r\n\ 216 | Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n\ 217 | \r\n\ 218 | hello world".to_string(), 219 | should_keep_alive: false, 220 | message_complete_on_eof: false, 221 | http_version: HttpVersion { major: 1, minor: 1 }, 222 | status_code: Some(200), 223 | response_status: { 224 | let mut v: Vec = Vec::new(); 225 | for b in "OK".as_bytes() { 226 | v.push(*b); 227 | } 228 | v 229 | }, 230 | headers: vec![ 231 | [ "Content-Type".to_string(), "text/html; charset=UTF-8".to_string() ], 232 | [ "Content-Length".to_string(), "11".to_string() ], 233 | [ "Proxy-Connection".to_string(), "close".to_string() ], 234 | [ "Date".to_string(), "Thu, 31 Dec 2009 20:55:48 +0000".to_string() ], 235 | ], 236 | body: "hello world".to_string(), 237 | ..Default::default() 238 | }, 239 | helper::Message { 240 | name: "underscore header key".to_string(), 241 | tp: HttpParserType::Response, 242 | raw: "HTTP/1.1 200 OK\r\n\ 243 | Server: DCLK-AdSvr\r\n\ 244 | Content-Type: text/xml\r\n\ 245 | Content-Length: 0\r\n\ 246 | DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n".to_string(), 247 | should_keep_alive: true, 248 | message_complete_on_eof: false, 249 | http_version: HttpVersion { major: 1, minor: 1 }, 250 | status_code: Some(200), 251 | response_status: { 252 | let mut v: Vec = Vec::new(); 253 | for b in "OK".as_bytes() { 254 | v.push(*b); 255 | } 256 | v 257 | }, 258 | headers: vec![ 259 | [ "Server".to_string(), "DCLK-AdSvr".to_string() ], 260 | [ "Content-Type".to_string(), "text/xml".to_string() ], 261 | [ "Content-Length".to_string(), "0".to_string() ], 262 | [ "DCLK_imp".to_string(), "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o".to_string() ], 263 | ], 264 | body: "".to_string(), 265 | ..Default::default() 266 | }, 267 | helper::Message { 268 | name: "bonjourmadame.fr".to_string(), 269 | tp: HttpParserType::Response, 270 | raw: "HTTP/1.0 301 Moved Permanently\r\n\ 271 | Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n\ 272 | Server: Apache/2.2.3 (Red Hat)\r\n\ 273 | Cache-Control: public\r\n\ 274 | Pragma: \r\n\ 275 | Location: http://www.bonjourmadame.fr/\r\n\ 276 | Vary: Accept-Encoding\r\n\ 277 | Content-Length: 0\r\n\ 278 | Content-Type: text/html; charset=UTF-8\r\n\ 279 | Connection: keep-alive\r\n\ 280 | \r\n".to_string(), 281 | should_keep_alive: true, 282 | message_complete_on_eof: false, 283 | http_version: HttpVersion { major: 1, minor: 0 }, 284 | status_code: Some(301), 285 | response_status: { 286 | let mut v: Vec = Vec::new(); 287 | for b in "Moved Permanently".as_bytes() { 288 | v.push(*b); 289 | } 290 | v 291 | }, 292 | headers: vec![ 293 | [ "Date".to_string(), "Thu, 03 Jun 2010 09:56:32 GMT".to_string() ], 294 | [ "Server".to_string(), "Apache/2.2.3 (Red Hat)".to_string() ], 295 | [ "Cache-Control".to_string(), "public".to_string() ], 296 | [ "Pragma".to_string(), "".to_string() ], 297 | [ "Location".to_string(), "http://www.bonjourmadame.fr/".to_string() ], 298 | [ "Vary".to_string(), "Accept-Encoding".to_string() ], 299 | [ "Content-Length".to_string(), "0".to_string() ], 300 | [ "Content-Type".to_string(), "text/html; charset=UTF-8".to_string() ], 301 | [ "Connection".to_string(), "keep-alive".to_string() ], 302 | ], 303 | body: "".to_string(), 304 | ..Default::default() 305 | }, 306 | helper::Message { 307 | name: "field underscore".to_string(), 308 | tp: HttpParserType::Response, 309 | raw: "HTTP/1.1 200 OK\r\n\ 310 | Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n\ 311 | Server: Apache\r\n\ 312 | Cache-Control: no-cache, must-revalidate\r\n\ 313 | Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n\ 314 | .et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n\ 315 | Vary: Accept-Encoding\r\n\ 316 | _eep-Alive: timeout=45\r\n\ 317 | _onnection: Keep-Alive\r\n\ 318 | Transfer-Encoding: chunked\r\n\ 319 | Content-Type: text/html\r\n\ 320 | Connection: close\r\n\ 321 | \r\n\ 322 | 0\r\n\r\n".to_string(), 323 | should_keep_alive: false, 324 | message_complete_on_eof: false, 325 | http_version: HttpVersion { major: 1, minor: 1 }, 326 | status_code: Some(200), 327 | response_status: { 328 | let mut v: Vec = Vec::new(); 329 | for b in "OK".as_bytes() { 330 | v.push(*b); 331 | } 332 | v 333 | }, 334 | headers: vec![ 335 | [ "Date".to_string(), "Tue, 28 Sep 2010 01:14:13 GMT".to_string() ], 336 | [ "Server".to_string(), "Apache".to_string() ], 337 | [ "Cache-Control".to_string(), "no-cache, must-revalidate".to_string() ], 338 | [ "Expires".to_string(), "Mon, 26 Jul 1997 05:00:00 GMT".to_string() ], 339 | [ ".et-Cookie".to_string(), "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com".to_string() ], 340 | [ "Vary".to_string(), "Accept-Encoding".to_string() ], 341 | [ "_eep-Alive".to_string(), "timeout=45".to_string() ], 342 | [ "_onnection".to_string(), "Keep-Alive".to_string() ], 343 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 344 | [ "Content-Type".to_string(), "text/html".to_string() ], 345 | [ "Connection".to_string(), "close".to_string() ], 346 | ], 347 | body: "".to_string(), 348 | ..Default::default() 349 | }, 350 | helper::Message { 351 | name: "non-ASCII in status line".to_string(), 352 | tp: HttpParserType::Response, 353 | raw: "HTTP/1.1 500 Oriëntatieprobleem\r\n\ 354 | Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n\ 355 | Content-Length: 0\r\n\ 356 | Connection: close\r\n\ 357 | \r\n".to_string(), 358 | should_keep_alive: false, 359 | message_complete_on_eof: false, 360 | http_version: HttpVersion { major: 1, minor: 1 }, 361 | status_code: Some(500), 362 | response_status: { 363 | let mut v: Vec = Vec::new(); 364 | for b in "Oriëntatieprobleem".as_bytes() { 365 | v.push(*b); 366 | } 367 | v 368 | }, 369 | headers: vec![ 370 | [ "Date".to_string(), "Fri, 5 Nov 2010 23:07:12 GMT+2".to_string() ], 371 | [ "Content-Length".to_string(), "0".to_string() ], 372 | [ "Connection".to_string(), "close".to_string() ], 373 | ], 374 | body: "".to_string(), 375 | ..Default::default() 376 | }, 377 | helper::Message { 378 | name: "http version 0.9".to_string(), 379 | tp: HttpParserType::Response, 380 | raw: "HTTP/0.9 200 OK\r\n\ 381 | \r\n".to_string(), 382 | should_keep_alive: false, 383 | message_complete_on_eof: true, 384 | http_version: HttpVersion { major: 0, minor: 9 }, 385 | status_code: Some(200), 386 | response_status: { 387 | let mut v: Vec = Vec::new(); 388 | for b in "OK".as_bytes() { 389 | v.push(*b); 390 | } 391 | v 392 | }, 393 | headers: vec![ 394 | ], 395 | body: "".to_string(), 396 | ..Default::default() 397 | }, 398 | helper::Message { 399 | name: "neither content-length nor transfer-encoding response".to_string(), 400 | tp: HttpParserType::Response, 401 | raw: "HTTP/1.1 200 OK\r\n\ 402 | Content-Type: text/plain\r\n\ 403 | \r\n\ 404 | hello world".to_string(), 405 | should_keep_alive: false, 406 | message_complete_on_eof: true, 407 | http_version: HttpVersion { major: 1, minor: 1 }, 408 | status_code: Some(200), 409 | response_status: { 410 | let mut v: Vec = Vec::new(); 411 | for b in "OK".as_bytes() { 412 | v.push(*b); 413 | } 414 | v 415 | }, 416 | headers: vec![ 417 | [ "Content-Type".to_string(), "text/plain".to_string() ], 418 | ], 419 | body: "hello world".to_string(), 420 | ..Default::default() 421 | }, 422 | helper::Message { 423 | name: "HTTP/1.0 with keep-alive and EOF-terminated 200 status".to_string(), 424 | tp: HttpParserType::Response, 425 | raw: "HTTP/1.0 200 OK\r\n\ 426 | Connection: keep-alive\r\n\ 427 | \r\n".to_string(), 428 | should_keep_alive: false, 429 | message_complete_on_eof: true, 430 | http_version: HttpVersion { major: 1, minor: 0 }, 431 | status_code: Some(200), 432 | response_status: { 433 | let mut v: Vec = Vec::new(); 434 | for b in "OK".as_bytes() { 435 | v.push(*b); 436 | } 437 | v 438 | }, 439 | headers: vec![ 440 | [ "Connection".to_string(), "keep-alive".to_string() ], 441 | ], 442 | body_size: 0, 443 | body: "".to_string(), 444 | ..Default::default() 445 | }, 446 | helper::Message { 447 | name: "HTTP/1.0 with keep-alive and a 204 status".to_string(), 448 | tp: HttpParserType::Response, 449 | raw: "HTTP/1.0 204 No content\r\n\ 450 | Connection: keep-alive\r\n\ 451 | \r\n".to_string(), 452 | should_keep_alive: true, 453 | message_complete_on_eof: false, 454 | http_version: HttpVersion { major: 1, minor: 0 }, 455 | status_code: Some(204), 456 | response_status: { 457 | let mut v: Vec = Vec::new(); 458 | for b in "No content".as_bytes() { 459 | v.push(*b); 460 | } 461 | v 462 | }, 463 | headers: vec![ 464 | [ "Connection".to_string(), "keep-alive".to_string() ], 465 | ], 466 | body_size: 0, 467 | body: "".to_string(), 468 | ..Default::default() 469 | }, 470 | helper::Message { 471 | name: "HTTP/1.1 with an EOF-terminated 200 status".to_string(), 472 | tp: HttpParserType::Response, 473 | raw: "HTTP/1.1 200 OK\r\n\ 474 | \r\n".to_string(), 475 | should_keep_alive: false, 476 | message_complete_on_eof: true, 477 | http_version: HttpVersion { major: 1, minor: 1 }, 478 | status_code: Some(200), 479 | response_status: { 480 | let mut v: Vec = Vec::new(); 481 | for b in "OK".as_bytes() { 482 | v.push(*b); 483 | } 484 | v 485 | }, 486 | headers: vec![ 487 | ], 488 | body_size: 0, 489 | body: "".to_string(), 490 | ..Default::default() 491 | }, 492 | helper::Message { 493 | name: "HTTP/1.1 with a 204 status".to_string(), 494 | tp: HttpParserType::Response, 495 | raw: "HTTP/1.1 204 No content\r\n\ 496 | \r\n".to_string(), 497 | should_keep_alive: true, 498 | message_complete_on_eof: false, 499 | http_version: HttpVersion { major: 1, minor: 1 }, 500 | status_code: Some(204), 501 | response_status: { 502 | let mut v: Vec = Vec::new(); 503 | for b in "No content".as_bytes() { 504 | v.push(*b); 505 | } 506 | v 507 | }, 508 | headers: vec![ 509 | ], 510 | body_size: 0, 511 | body: "".to_string(), 512 | ..Default::default() 513 | }, 514 | helper::Message { 515 | name: "HTTP/1.1 with a 204 status and keep-alive disabled".to_string(), 516 | tp: HttpParserType::Response, 517 | raw: "HTTP/1.1 204 No content\r\n\ 518 | Connection: close\r\n\ 519 | \r\n".to_string(), 520 | should_keep_alive: false, 521 | message_complete_on_eof: false, 522 | http_version: HttpVersion { major: 1, minor: 1 }, 523 | status_code: Some(204), 524 | response_status: { 525 | let mut v: Vec = Vec::new(); 526 | for b in "No content".as_bytes() { 527 | v.push(*b); 528 | } 529 | v 530 | }, 531 | headers: vec![ 532 | [ "Connection".to_string(), "close".to_string() ], 533 | ], 534 | body_size: 0, 535 | body: "".to_string(), 536 | ..Default::default() 537 | }, 538 | helper::Message { 539 | name: "HTTP/1.1 with chunked encoding and a 200 response".to_string(), 540 | tp: HttpParserType::Response, 541 | raw: "HTTP/1.1 200 OK\r\n\ 542 | Transfer-Encoding: chunked\r\n\ 543 | \r\n\ 544 | 0\r\n\ 545 | \r\n".to_string(), 546 | should_keep_alive: true, 547 | message_complete_on_eof: false, 548 | http_version: HttpVersion { major: 1, minor: 1 }, 549 | status_code: Some(200), 550 | response_status: { 551 | let mut v: Vec = Vec::new(); 552 | for b in "OK".as_bytes() { 553 | v.push(*b); 554 | } 555 | v 556 | }, 557 | headers: vec![ 558 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 559 | ], 560 | body_size: 0, 561 | body: "".to_string(), 562 | ..Default::default() 563 | }, 564 | helper::Message { 565 | name: "field space".to_string(), 566 | tp: HttpParserType::Response, 567 | strict: false, 568 | raw: "HTTP/1.1 200 OK\r\n\ 569 | Server: Microsoft-IIS/6.0\r\n\ 570 | X-Powered-By: ASP.NET\r\n\ 571 | en-US Content-Type: text/xml\r\n\ 572 | Content-Type: text/xml\r\n\ 573 | Content-Length: 16\r\n\ 574 | Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n\ 575 | Connection: keep-alive\r\n\ 576 | \r\n\ 577 | hello".to_string(), 578 | should_keep_alive: true, 579 | message_complete_on_eof: false, 580 | http_version: HttpVersion { major: 1, minor: 1 }, 581 | status_code: Some(200), 582 | response_status: { 583 | let mut v: Vec = Vec::new(); 584 | for b in "OK".as_bytes() { 585 | v.push(*b); 586 | } 587 | v 588 | }, 589 | headers: vec![ 590 | [ "Server".to_string(), "Microsoft-IIS/6.0".to_string() ], 591 | [ "X-Powered-By".to_string(), "ASP.NET".to_string() ], 592 | [ "en-US Content-Type".to_string(), "text/xml".to_string() ], 593 | [ "Content-Type".to_string(), "text/xml".to_string() ], 594 | [ "Content-Length".to_string(), "16".to_string() ], 595 | [ "Date".to_string(), "Fri, 23 Jul 2010 18:45:38 GMT".to_string() ], 596 | [ "Connection".to_string(), "keep-alive".to_string() ], 597 | ], 598 | body: "hello".to_string(), 599 | ..Default::default() 600 | }, 601 | helper::Message { 602 | name: "amazon.com".to_string(), 603 | tp: HttpParserType::Response, 604 | strict: false, 605 | raw: "HTTP/1.1 301 MovedPermanently\r\n\ 606 | Date: Wed, 15 May 2013 17:06:33 GMT\r\n\ 607 | Server: Server\r\n\ 608 | x-amz-id-1: 0GPHKXSJQ826RK7GZEB2\r\n\ 609 | p3p: policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"\r\n\ 610 | x-amz-id-2: STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD\r\n\ 611 | Location: http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846\r\n\ 612 | Vary: Accept-Encoding,User-Agent\r\n\ 613 | Content-Type: text/html; charset=ISO-8859-1\r\n\ 614 | Transfer-Encoding: chunked\r\n\ 615 | \r\n\ 616 | 1\r\n\ 617 | \n\r\n\ 618 | 0\r\n\ 619 | \r\n".to_string(), 620 | should_keep_alive: true, 621 | message_complete_on_eof: false, 622 | http_version: HttpVersion { major: 1, minor: 1 }, 623 | status_code: Some(301), 624 | response_status: { 625 | let mut v: Vec = Vec::new(); 626 | for b in "MovedPermanently".as_bytes() { 627 | v.push(*b); 628 | } 629 | v 630 | }, 631 | headers: vec![ 632 | [ "Date".to_string(), "Wed, 15 May 2013 17:06:33 GMT".to_string() ], 633 | [ "Server".to_string(), "Server".to_string() ], 634 | [ "x-amz-id-1".to_string(), "0GPHKXSJQ826RK7GZEB2".to_string() ], 635 | [ "p3p".to_string(), "policyref=\"http://www.amazon.com/w3c/p3p.xml\",CP=\"CAO DSP LAW CUR ADM IVAo IVDo CONo OTPo OUR DELi PUBi OTRi BUS PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA HEA PRE LOC GOV OTC \"".to_string() ], 636 | [ "x-amz-id-2".to_string(), "STN69VZxIFSz9YJLbz1GDbxpbjG6Qjmmq5E3DxRhOUw+Et0p4hr7c/Q8qNcx4oAD".to_string() ], 637 | [ "Location".to_string(), "http://www.amazon.com/Dan-Brown/e/B000AP9DSU/ref=s9_pop_gw_al1?_encoding=UTF8&refinementId=618073011&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=center-2&pf_rd_r=0SHYY5BZXN3KR20BNFAY&pf_rd_t=101&pf_rd_p=1263340922&pf_rd_i=507846".to_string() ], 638 | [ "Vary".to_string(), "Accept-Encoding,User-Agent".to_string() ], 639 | [ "Content-Type".to_string(), "text/html; charset=ISO-8859-1".to_string() ], 640 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 641 | ], 642 | body: "\n".to_string(), 643 | ..Default::default() 644 | }, 645 | helper::Message { 646 | name: "empty reason phrase after space".to_string(), 647 | tp: HttpParserType::Response, 648 | raw: "HTTP/1.1 200 \r\n\ 649 | \r\n".to_string(), 650 | should_keep_alive: false, 651 | message_complete_on_eof: true, 652 | http_version: HttpVersion { major: 1, minor: 1 }, 653 | status_code: Some(200), 654 | response_status: { 655 | let mut v: Vec = Vec::new(); 656 | // FIXME no need to push? 657 | for b in "".as_bytes() { 658 | v.push(*b); 659 | } 660 | v 661 | }, 662 | headers: vec![ 663 | ], 664 | body: "".to_string(), 665 | ..Default::default() 666 | }, 667 | ]; 668 | 669 | const NO_HEADERS_NO_BODY_404 : usize = 2; 670 | const NO_REASON_PHRASE : usize = 3; 671 | const TRAILING_SPACE_ON_CHUNKED_BODY : usize = 4; 672 | const NO_CARRIAGE_RET : usize = 5; 673 | const UNDERSCORE_HEADER_KEY : usize = 7; 674 | const BONJOUR_MADAME_FR : usize = 8; 675 | const NO_BODY_HTTP10_KA_204 : usize = 14; 676 | 677 | // End of RESPONSES 678 | for m in responses.iter() { 679 | helper::test_message(m); 680 | } 681 | 682 | for m in responses.iter() { 683 | helper::test_message_pause(m); 684 | } 685 | 686 | for r1 in responses.iter() { 687 | if !r1.should_keep_alive { continue; } 688 | for r2 in responses.iter() { 689 | if !r2.should_keep_alive { continue; } 690 | for r3 in responses.iter() { 691 | helper::test_multiple3(r1, r2, r3); 692 | } 693 | } 694 | } 695 | 696 | test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]); 697 | test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]); 698 | 699 | // test very large chunked response 700 | { 701 | let large_chunked = helper::Message { 702 | name: "large chunked".to_string(), 703 | tp: HttpParserType::Response, 704 | raw: create_large_chunked_message(31337, 705 | "HTTP/1.0 200 OK\r\n\ 706 | Transfer-Encoding: chunked\r\n\ 707 | Content-Type: text/plain\r\n\ 708 | \r\n"), 709 | should_keep_alive: false, 710 | message_complete_on_eof: false, 711 | http_version: HttpVersion { major: 1, minor: 0 }, 712 | status_code: Some(200), 713 | response_status: { 714 | let mut v = Vec::new(); 715 | for b in "OK".as_bytes() { 716 | v.push(*b); 717 | } 718 | v 719 | }, 720 | headers: vec![ 721 | [ "Transfer-Encoding".to_string(), "chunked".to_string() ], 722 | [ "Content-Type".to_string(), "text/plain".to_string() ], 723 | ], 724 | body_size: 31337*1024, 725 | ..Default::default() 726 | }; 727 | test_message_count_body(&large_chunked); 728 | } 729 | 730 | // response scan 1/2 731 | helper::test_scan(&responses[TRAILING_SPACE_ON_CHUNKED_BODY], 732 | &responses[NO_BODY_HTTP10_KA_204], 733 | &responses[NO_REASON_PHRASE]); 734 | 735 | // response scan 2/2 736 | helper::test_scan(&responses[BONJOUR_MADAME_FR], 737 | &responses[UNDERSCORE_HEADER_KEY], 738 | &responses[NO_CARRIAGE_RET]); 739 | } 740 | 741 | fn test_message_count_body(msg: &helper::Message) { 742 | let mut hp = HttpParser::new(msg.tp); 743 | hp.strict = msg.strict; 744 | 745 | let mut cb = helper::CallbackCountBody{..Default::default()}; 746 | cb.messages.push(helper::Message{..Default::default()}); 747 | 748 | let len : usize = msg.raw.len(); 749 | let chunk : usize = 4024; 750 | 751 | let mut i : usize = 0; 752 | while i < len { 753 | let toread : usize = std::cmp::min(len-i, chunk); 754 | let read = hp.execute(&mut cb, &msg.raw.as_bytes()[i .. i + toread]); 755 | if read != toread { 756 | helper::print_error(hp.errno.unwrap(), msg.raw.as_bytes(), read); 757 | panic!(); 758 | } 759 | 760 | i += chunk; 761 | } 762 | 763 | cb.currently_parsing_eof = true; 764 | let read = hp.execute(&mut cb, &[]); 765 | if read != 0 { 766 | helper::print_error(hp.errno.unwrap(), msg.raw.as_bytes(), read); 767 | panic!(); 768 | } 769 | 770 | assert!(cb.num_messages == 1, "\n*** num_messages != 1 after testing '{}' ***\n\n", msg.name); 771 | helper::assert_eq_message(&cb.messages[0], msg); 772 | } 773 | 774 | fn create_large_chunked_message(body_size_in_kb: usize, headers: &str) -> String { 775 | let mut buf = headers.to_string(); 776 | 777 | for _ in (0..body_size_in_kb) { 778 | buf.push_str("400\r\n"); 779 | for _ in (0u32..1024u32) { 780 | buf.push('C'); 781 | } 782 | buf.push_str("\r\n"); 783 | } 784 | 785 | buf.push_str("0\r\n\r\n"); 786 | buf 787 | } 788 | 789 | --------------------------------------------------------------------------------