implement HTTP request body parsing

This commit is contained in:
Peter Thorson
2014-11-18 22:00:31 -05:00
parent 8cbe22266d
commit 12700f2bc2
5 changed files with 239 additions and 9 deletions
+77
View File
@@ -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;
+3
View File
@@ -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;
+46
View File
@@ -29,6 +29,7 @@
#define HTTP_PARSER_IMPL_HPP
#include <algorithm>
#include <cstdlib>
#include <istream>
#include <sstream>
#include <string>
@@ -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)
{
+27 -4
View File
@@ -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<std::string::size_type>(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);
+86 -5
View File
@@ -49,6 +49,14 @@ namespace state {
};
}
namespace body_encoding {
enum value {
unknown,
plain,
chunked
};
}
typedef std::map<std::string, std::string, utility::ci_less > 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 @@ public:
*/
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 @@ public:
*/
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 @@ protected:
*/
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 @@ protected:
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