Skip to content

Commit

Permalink
implement HTTP request body parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
zaphoyd committed Nov 19, 2014
1 parent 8cbe222 commit 12700f2
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 9 deletions.
77 changes: 77 additions & 0 deletions test/http/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions websocketpp/http/constants.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
46 changes: 46 additions & 0 deletions websocketpp/http/impl/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#define HTTP_PARSER_IMPL_HPP

#include <algorithm>
#include <cstdlib>
#include <istream>
#include <sstream>
#include <string>
Expand Down Expand Up @@ -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());
Expand All @@ -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)
{
Expand Down
31 changes: 27 additions & 4 deletions websocketpp/http/impl/request.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -81,18 +91,31 @@ 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
);

// 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);
Expand Down
91 changes: 86 additions & 5 deletions websocketpp/http/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
Expand Down

0 comments on commit 12700f2

Please sign in to comment.