diff --git a/test/http/parser.cpp b/test/http/parser.cpp index 0200a1aa797..73e109e67d0 100644 --- a/test/http/parser.cpp +++ b/test/http/parser.cpp @@ -480,6 +480,62 @@ BOOST_AUTO_TEST_CASE( basic_request ) { BOOST_CHECK( r.get_header("Host") == "www.example.com" ); } +BOOST_AUTO_TEST_CASE( basic_request_with_body ) { + websocketpp::http::parser::request r; + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 5\r\n\r\nabcdef"; + + bool exception = false; + size_t pos = 0; + + try { + pos = r.consume(raw.c_str(),raw.size()); + } catch (std::exception &e) { + exception = true; + std::cout << e.what() << std::endl; + } + + BOOST_CHECK( exception == false ); + BOOST_CHECK_EQUAL( pos, 65 ); + BOOST_CHECK( r.ready() == true ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_header("Content-Length"), "5" ); + BOOST_CHECK_EQUAL( r.get_body(), "abcde" ); +} + +BOOST_AUTO_TEST_CASE( basic_request_with_body_split ) { + websocketpp::http::parser::request r; + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 6\r\n\r\nabc"; + std::string raw2 = "def"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(raw.c_str(),raw.size()); + pos += r.consume(raw2.c_str(),raw2.size()); + } catch (std::exception &e) { + exception = true; + std::cout << e.what() << std::endl; + } + + BOOST_CHECK( exception == false ); + BOOST_CHECK_EQUAL( pos, 66 ); + BOOST_CHECK( r.ready() == true ); + BOOST_CHECK_EQUAL( r.get_version(), "HTTP/1.1" ); + BOOST_CHECK_EQUAL( r.get_method(), "GET" ); + BOOST_CHECK_EQUAL( r.get_uri(), "/" ); + BOOST_CHECK_EQUAL( r.get_header("Host"), "www.example.com" ); + BOOST_CHECK_EQUAL( r.get_header("Content-Length"), "6" ); + BOOST_CHECK_EQUAL( r.get_body(), "abcdef" ); +} + + + BOOST_AUTO_TEST_CASE( trailing_body_characters ) { websocketpp::http::parser::request r; @@ -595,6 +651,27 @@ BOOST_AUTO_TEST_CASE( max_header_len_split ) { BOOST_CHECK( exception == true ); } +BOOST_AUTO_TEST_CASE( max_body_len ) { + websocketpp::http::parser::request r; + + r.set_max_body_size(5); + + std::string raw = "GET / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 6\r\n\r\nabcdef"; + + bool exception = false; + size_t pos = 0; + + try { + pos += r.consume(raw.c_str(),raw.size()); + } catch (websocketpp::http::exception const & e) { + exception = true; + BOOST_CHECK_EQUAL(e.m_error_code,websocketpp::http::status_code::request_entity_too_large); + } + + BOOST_CHECK_EQUAL(r.get_max_body_size(),5); + BOOST_CHECK( exception == true ); +} + BOOST_AUTO_TEST_CASE( firefox_full_request ) { websocketpp::http::parser::request r; diff --git a/websocketpp/http/constants.hpp b/websocketpp/http/constants.hpp index 6d50c2d3357..10e726d0b7e 100644 --- a/websocketpp/http/constants.hpp +++ b/websocketpp/http/constants.hpp @@ -63,6 +63,9 @@ namespace http { /// Maximum size in bytes before rejecting an HTTP header as too big. size_t const max_header_size = 16000; + + /// Default Maximum size in bytes for HTTP message bodies. + size_t const max_body_size = 32000000; /// Number of bytes to use for temporary istream read buffers size_t const istream_buffer = 512; diff --git a/websocketpp/http/impl/parser.hpp b/websocketpp/http/impl/parser.hpp index 25fba342320..92799a4069d 100644 --- a/websocketpp/http/impl/parser.hpp +++ b/websocketpp/http/impl/parser.hpp @@ -29,6 +29,7 @@ #define HTTP_PARSER_IMPL_HPP #include +#include #include #include #include @@ -94,6 +95,9 @@ inline void parser::set_body(std::string const & value) { return; } + // TODO: should this method respect the max size? If so how should errors + // be indicated? + std::stringstream len; len << value.size(); replace_header("Content-Length", len.str()); @@ -112,6 +116,48 @@ inline bool parser::parse_parameter_list(std::string const & in, return (it == in.begin()); } +inline bool parser::prepare_body() { + if (get_header("Content-Length") != "") { + std::string const & cl_header = get_header("Content-Length"); + char * end; + + // TODO: not 100% sure what the compatibility of this method is. Also, + // I believe this will only work up to 32bit sizes. Is there a need for + // > 4GiB HTTP payloads? + m_body_bytes_needed = std::strtoul(cl_header.c_str(),&end,10); + + if (m_body_bytes_needed > m_body_bytes_max) { + throw exception("HTTP message body too large", + status_code::request_entity_too_large); + } + + m_body_encoding = body_encoding::plain; + return true; + } else if (get_header("Transfer-Encoding") == "chunked") { + // TODO + //m_body_encoding = body_encoding::chunked; + return false; + } else { + return false; + } +} + +inline size_t parser::process_body(char const * buf, size_t len) { + if (m_body_encoding == body_encoding::plain) { + size_t processed = (std::min)(m_body_bytes_needed,len); + m_body.append(buf,processed); + m_body_bytes_needed -= processed; + return processed; + } else if (m_body_encoding == body_encoding::chunked) { + // TODO: + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } else { + throw exception("Unexpected body encoding", + status_code::internal_server_error); + } +} + inline void parser::process_header(std::string::iterator begin, std::string::iterator end) { diff --git a/websocketpp/http/impl/request.hpp b/websocketpp/http/impl/request.hpp index ce827c680e9..fe7091a7d6e 100644 --- a/websocketpp/http/impl/request.hpp +++ b/websocketpp/http/impl/request.hpp @@ -39,7 +39,17 @@ namespace http { namespace parser { inline size_t request::consume(char const * buf, size_t len) { + size_t bytes_processed; + if (m_ready) {return 0;} + + if (m_body_bytes_needed > 0) { + bytes_processed = process_body(buf,len); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } if (m_buf->size() + len > max_header_size) { // exceeded max header size @@ -81,9 +91,8 @@ inline size_t request::consume(char const * buf, size_t len) { if (m_method.empty() || get_header("Host") == "") { throw exception("Incomplete Request",status_code::bad_request); } - m_ready = true; - size_t bytes_processed = ( + bytes_processed = ( len - static_cast(m_buf->end()-end) + sizeof(header_delimiter) - 1 ); @@ -91,8 +100,22 @@ inline size_t request::consume(char const * buf, size_t len) { // frees memory used temporarily during request parsing m_buf.reset(); - // return number of bytes processed (starting bytes - bytes left) - return bytes_processed; + // if this was not an upgrade request and has a content length + // continue capturing content-length bytes and expose them as a + // request body. + + if (prepare_body()) { + bytes_processed += process_body(buf+bytes_processed,len-bytes_processed); + if (body_ready()) { + m_ready = true; + } + return bytes_processed; + } else { + m_ready = true; + + // return number of bytes processed (starting bytes - bytes left) + return bytes_processed; + } } else { if (m_method.empty()) { this->process(begin,end); diff --git a/websocketpp/http/parser.hpp b/websocketpp/http/parser.hpp index 185c7bbb06d..eb7ac144226 100644 --- a/websocketpp/http/parser.hpp +++ b/websocketpp/http/parser.hpp @@ -49,6 +49,14 @@ namespace state { }; } +namespace body_encoding { + enum value { + unknown, + plain, + chunked + }; +} + typedef std::map header_list; /// Read and return the next token in the stream @@ -385,6 +393,11 @@ inline std::string strip_lws(std::string const & input) { */ class parser { public: + parser() + : m_body_bytes_needed(0) + , m_body_bytes_max(max_body_size) + , m_body_encoding(body_encoding::unknown) {} + /// Get the HTTP version string /** * @return The version string for this parser @@ -468,12 +481,11 @@ class parser { */ void remove_header(std::string const & key); - /// Set HTTP body + /// Get HTTP body /** - * Sets the body of the HTTP object and fills in the appropriate content - * length header. + * Gets the body of the HTTP object * - * @param [in] value The value to set the body to. + * @return The body of the HTTP message. */ std::string const & get_body() const { return m_body; @@ -490,6 +502,32 @@ class parser { */ void set_body(std::string const & value); + /// Get body size limit + /** + * Retrieves the maximum number of bytes to parse & buffer before canceling + * a request. + * + * @since 0.5.0 + * + * @return The maximum length of a message body. + */ + size_t get_max_body_size() const { + return m_body_bytes_max; + } + + /// Set body size limit + /** + * Set the maximum number of bytes to parse and buffer before canceling a + * request. + * + * @since 0.5.0 + * + * @param value The size to set the max body length to. + */ + void set_max_body_size(size_t value) { + m_body_bytes_max = value; + } + /// Extract an HTTP parameter list from a string. /** * @param [in] in The input string. @@ -508,6 +546,45 @@ class parser { */ void process_header(std::string::iterator begin, std::string::iterator end); + /// Prepare the parser to begin parsing body data + /** + * Inspects headers to determine if the message has a body that needs to be + * read. If so, sets up the necessary state, otherwise returns false. If + * this method returns true and loading the message body is desired call + * `process_body` until it returns zero bytes or an error. + * + * Must not be called until after all headers have been processed. + * + * @since 0.5.0 + * + * @return True if more bytes are needed to load the body, false otherwise. + */ + bool prepare_body(); + + /// Process body data + /** + * Parses body data. + * + * @since 0.5.0 + * + * @param [in] begin An iterator to the beginning of the sequence. + * @param [in] end An iterator to the end of the sequence. + * @return The number of bytes processed + */ + size_t process_body(char const * buf, size_t len); + + /// Check if the parser is done parsing the body + /** + * Behavior before a call to `prepare_body` is undefined. + * + * @since 0.5.0 + * + * @return True if the message body has been completed loaded. + */ + bool body_ready() const { + return (m_body_bytes_needed == 0); + } + /// Generate and return the HTTP headers as a string /** * Each headers will be followed by the \r\n sequence including the last one. @@ -519,7 +596,11 @@ class parser { std::string m_version; header_list m_headers; - std::string m_body; + + std::string m_body; + size_t m_body_bytes_needed; + size_t m_body_bytes_max; + body_encoding::value m_body_encoding; }; } // namespace parser