parser.hpp
// ---------------------------------------------------------------------
// pion: a Boost C++ framework for building lightweight HTTP interfaces
// ---------------------------------------------------------------------
// Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
//
// Distributed under the Boost Software License, Version 1.0.
// See http://www.boost.org/LICENSE_1_0.txt
//
#ifndef __PION_HTTP_PARSER_HEADER__
#define __PION_HTTP_PARSER_HEADER__
#include <string>
#include <boost/noncopyable.hpp>
#include <boost/function/function2.hpp>
#include <boost/logic/tribool.hpp>
#include <boost/system/error_code.hpp>
#include <boost/thread/once.hpp>
#include <pion/config.hpp>
#include <pion/logger.hpp>
#include <pion/http/message.hpp>
#ifndef BOOST_SYSTEM_NOEXCEPT
#define BOOST_SYSTEM_NOEXCEPT BOOST_NOEXCEPT
#endif
namespace pion { // begin namespace pion
namespace http { // begin namespace http
// forward declarations used for finishing HTTP messages
class request;
class response;
class PION_API parser :
private boost::noncopyable
{
public:
static const std::size_t DEFAULT_CONTENT_MAX;
typedef boost::function2<void, const char *, std::size_t> payload_handler_t;
enum error_value_t {
ERROR_METHOD_CHAR = 1,
ERROR_METHOD_SIZE,
ERROR_URI_CHAR,
ERROR_URI_SIZE,
ERROR_QUERY_CHAR,
ERROR_QUERY_SIZE,
ERROR_VERSION_EMPTY,
ERROR_VERSION_CHAR,
ERROR_STATUS_EMPTY,
ERROR_STATUS_CHAR,
ERROR_HEADER_CHAR,
ERROR_HEADER_NAME_SIZE,
ERROR_HEADER_VALUE_SIZE,
ERROR_INVALID_CONTENT_LENGTH,
ERROR_CHUNK_CHAR,
ERROR_MISSING_CHUNK_DATA,
ERROR_MISSING_HEADER_DATA,
ERROR_MISSING_TOO_MUCH_CONTENT,
};
class error_category_t
: public boost::system::error_category
{
public:
const char *name() const BOOST_SYSTEM_NOEXCEPT { return "parser"; }
std::string message(int ev) const {
switch (ev) {
case ERROR_METHOD_CHAR:
return "invalid method character";
case ERROR_METHOD_SIZE:
return "method exceeds maximum size";
case ERROR_URI_CHAR:
return "invalid URI character";
case ERROR_URI_SIZE:
return "method exceeds maximum size";
case ERROR_QUERY_CHAR:
return "invalid query string character";
case ERROR_QUERY_SIZE:
return "query string exceeds maximum size";
case ERROR_VERSION_EMPTY:
return "HTTP version undefined";
case ERROR_VERSION_CHAR:
return "invalid version character";
case ERROR_STATUS_EMPTY:
return "HTTP status undefined";
case ERROR_STATUS_CHAR:
return "invalid status character";
case ERROR_HEADER_CHAR:
return "invalid header character";
case ERROR_HEADER_NAME_SIZE:
return "header name exceeds maximum size";
case ERROR_HEADER_VALUE_SIZE:
return "header value exceeds maximum size";
case ERROR_INVALID_CONTENT_LENGTH:
return "invalid Content-Length header";
case ERROR_CHUNK_CHAR:
return "invalid chunk character";
case ERROR_MISSING_HEADER_DATA:
return "missing header data";
case ERROR_MISSING_CHUNK_DATA:
return "missing chunk data";
case ERROR_MISSING_TOO_MUCH_CONTENT:
return "missing too much content";
}
return "parser error";
}
};
parser(const bool is_request, std::size_t max_content_length = DEFAULT_CONTENT_MAX)
: m_logger(PION_GET_LOGGER("pion.http.parser")), m_is_request(is_request),
m_read_ptr(NULL), m_read_end_ptr(NULL), m_message_parse_state(PARSE_START),
m_headers_parse_state(is_request ? PARSE_METHOD_START : PARSE_HTTP_VERSION_H),
m_chunked_content_parse_state(PARSE_CHUNK_SIZE_START), m_status_code(0),
m_bytes_content_remaining(0), m_bytes_content_read(0),
m_bytes_last_read(0), m_bytes_total_read(0),
m_max_content_length(max_content_length),
m_parse_headers_only(false), m_save_raw_headers(false)
{}
virtual ~parser() {}
boost::tribool parse(http::message& http_msg, boost::system::error_code& ec);
boost::tribool parse_missing_data(http::message& http_msg, std::size_t len,
boost::system::error_code& ec);
void finish(http::message& http_msg) const;
inline void set_read_buffer(const char *ptr, size_t len) {
m_read_ptr = ptr;
m_read_end_ptr = ptr + len;
}
inline void load_read_pos(const char *&read_ptr, const char *&read_end_ptr) const {
read_ptr = m_read_ptr;
read_end_ptr = m_read_end_ptr;
}
inline bool check_premature_eof(http::message& http_msg) {
if (m_message_parse_state != PARSE_CONTENT_NO_LENGTH)
return true;
m_message_parse_state = PARSE_END;
http_msg.concatenate_chunks();
finish(http_msg);
return false;
}
inline void parse_headers_only(bool b = true) { m_parse_headers_only = b; }
inline void skip_header_parsing(http::message& http_msg) {
boost::system::error_code ec;
finish_header_parsing(http_msg, ec);
}
inline void reset(void) {
m_message_parse_state = PARSE_START;
m_headers_parse_state = (m_is_request ? PARSE_METHOD_START : PARSE_HTTP_VERSION_H);
m_chunked_content_parse_state = PARSE_CHUNK_SIZE_START;
m_status_code = 0;
m_status_message.erase();
m_method.erase();
m_resource.erase();
m_query_string.erase();
m_raw_headers.erase();
m_bytes_content_read = m_bytes_last_read = m_bytes_total_read = 0;
}
inline bool eof(void) const { return m_read_ptr == NULL || m_read_ptr >= m_read_end_ptr; }
inline std::size_t bytes_available(void) const { return (eof() ? 0 : (std::size_t)(m_read_end_ptr - m_read_ptr)); }
inline std::size_t gcount(void) const { return m_bytes_last_read; }
inline std::size_t get_total_bytes_read(void) const { return m_bytes_total_read; }
inline std::size_t get_content_bytes_read(void) const { return m_bytes_content_read; }
inline std::size_t get_max_content_length(void) const { return m_max_content_length; }
inline const std::string& get_raw_headers(void) const { return m_raw_headers; }
inline bool get_save_raw_headers(void) const { return m_save_raw_headers; }
inline bool get_parse_headers_only(void) { return m_parse_headers_only; }
inline bool is_parsing_request(void) const { return m_is_request; }
inline bool is_parsing_response(void) const { return ! m_is_request; }
inline void set_payload_handler(payload_handler_t& h) { m_payload_handler = h; }
inline void set_max_content_length(std::size_t n) { m_max_content_length = n; }
inline void reset_max_content_length(void) { m_max_content_length = DEFAULT_CONTENT_MAX; }
inline void set_save_raw_headers(bool b) { m_save_raw_headers = b; }
inline void set_logger(logger log_ptr) { m_logger = log_ptr; }
inline logger get_logger(void) { return m_logger; }
static bool parse_uri(const std::string& uri, std::string& proto,
std::string& host, boost::uint16_t& port, std::string& path,
std::string& query);
static bool parse_url_encoded(ihash_multimap& dict,
const char *ptr, const std::size_t len);
static bool parse_multipart_form_data(ihash_multimap& dict,
const std::string& content_type,
const char *ptr, const std::size_t len);
static bool parse_cookie_header(ihash_multimap& dict,
const char *ptr, const std::size_t len,
bool set_cookie_header);
static inline bool parse_cookie_header(ihash_multimap& dict,
const std::string& cookie_header, bool set_cookie_header)
{
return parse_cookie_header(dict, cookie_header.c_str(), cookie_header.size(), set_cookie_header);
}
static inline bool parse_url_encoded(ihash_multimap& dict,
const std::string& query)
{
return parse_url_encoded(dict, query.c_str(), query.size());
}
static inline bool parse_multipart_form_data(ihash_multimap& dict,
const std::string& content_type,
const std::string& form_data)
{
return parse_multipart_form_data(dict, content_type, form_data.c_str(), form_data.size());
}
boost::tribool finish_header_parsing(http::message& http_msg,
boost::system::error_code& ec);
static bool parse_forwarded_for(const std::string& header, std::string& public_ip);
static inline error_category_t& get_error_category(void) {
boost::call_once(parser::create_error_category, m_instance_flag);
return *m_error_category_ptr;
}
protected:
virtual void finished_parsing_headers(const boost::system::error_code& ec) {}
boost::tribool parse_headers(http::message& http_msg, boost::system::error_code& ec);
void update_message_with_header_data(http::message& http_msg) const;
boost::tribool parse_chunks(http::message::chunk_cache_t& chunk_buffers,
boost::system::error_code& ec);
boost::tribool consume_content(http::message& http_msg,
boost::system::error_code& ec);
std::size_t consume_content_as_next_chunk(http::message::chunk_cache_t& chunk_buffers);
static void compute_msg_status(http::message& http_msg, bool msg_parsed_ok);
static inline void set_error(boost::system::error_code& ec, error_value_t ev) {
ec = boost::system::error_code(static_cast<int>(ev), get_error_category());
}
static void create_error_category(void);
// misc functions used by the parsing functions
inline static bool is_char(int c);
inline static bool is_control(int c);
inline static bool is_special(int c);
inline static bool is_digit(int c);
inline static bool is_hex_digit(int c);
inline static bool is_cookie_attribute(const std::string& name, bool set_cookie_header);
static const boost::uint32_t STATUS_MESSAGE_MAX;
static const boost::uint32_t METHOD_MAX;
static const boost::uint32_t RESOURCE_MAX;
static const boost::uint32_t QUERY_STRING_MAX;
static const boost::uint32_t HEADER_NAME_MAX;
static const boost::uint32_t HEADER_VALUE_MAX;
static const boost::uint32_t QUERY_NAME_MAX;
static const boost::uint32_t QUERY_VALUE_MAX;
static const boost::uint32_t COOKIE_NAME_MAX;
static const boost::uint32_t COOKIE_VALUE_MAX;
mutable logger m_logger;
const bool m_is_request;
const char * m_read_ptr;
const char * m_read_end_ptr;
private:
enum message_parse_state_t {
PARSE_START, PARSE_HEADERS, PARSE_FOOTERS, PARSE_CONTENT,
PARSE_CONTENT_NO_LENGTH, PARSE_CHUNKS, PARSE_END
};
enum header_parse_state_t {
PARSE_METHOD_START, PARSE_METHOD, PARSE_URI_STEM, PARSE_URI_QUERY,
PARSE_HTTP_VERSION_H, PARSE_HTTP_VERSION_T_1, PARSE_HTTP_VERSION_T_2,
PARSE_HTTP_VERSION_P, PARSE_HTTP_VERSION_SLASH,
PARSE_HTTP_VERSION_MAJOR_START, PARSE_HTTP_VERSION_MAJOR,
PARSE_HTTP_VERSION_MINOR_START, PARSE_HTTP_VERSION_MINOR,
PARSE_STATUS_CODE_START, PARSE_STATUS_CODE, PARSE_STATUS_MESSAGE,
PARSE_EXPECTING_NEWLINE, PARSE_EXPECTING_CR,
PARSE_HEADER_WHITESPACE, PARSE_HEADER_START, PARSE_HEADER_NAME,
PARSE_SPACE_BEFORE_HEADER_VALUE, PARSE_HEADER_VALUE,
PARSE_EXPECTING_FINAL_NEWLINE, PARSE_EXPECTING_FINAL_CR
};
enum chunk_parse_state_t {
PARSE_CHUNK_SIZE_START, PARSE_CHUNK_SIZE,
PARSE_EXPECTING_IGNORED_TEXT_AFTER_CHUNK_SIZE,
PARSE_EXPECTING_CR_AFTER_CHUNK_SIZE,
PARSE_EXPECTING_LF_AFTER_CHUNK_SIZE, PARSE_CHUNK,
PARSE_EXPECTING_CR_AFTER_CHUNK, PARSE_EXPECTING_LF_AFTER_CHUNK,
PARSE_EXPECTING_FINAL_CR_OR_FOOTERS_AFTER_LAST_CHUNK,
PARSE_EXPECTING_FINAL_LF_AFTER_LAST_CHUNK
};
message_parse_state_t m_message_parse_state;
header_parse_state_t m_headers_parse_state;
chunk_parse_state_t m_chunked_content_parse_state;
payload_handler_t m_payload_handler;
boost::uint16_t m_status_code;
std::string m_status_message;
std::string m_method;
std::string m_resource;
std::string m_query_string;
std::string m_raw_headers;
std::string m_header_name;
std::string m_header_value;
std::string m_chunk_size_str;
std::size_t m_size_of_current_chunk;
std::size_t m_bytes_read_in_current_chunk;
std::size_t m_bytes_content_remaining;
std::size_t m_bytes_content_read;
std::size_t m_bytes_last_read;
std::size_t m_bytes_total_read;
std::size_t m_max_content_length;
bool m_parse_headers_only;
bool m_save_raw_headers;
static error_category_t * m_error_category_ptr;
static boost::once_flag m_instance_flag;
};
// inline functions for parser
inline bool parser::is_char(int c)
{
return(c >= 0 && c <= 127);
}
inline bool parser::is_control(int c)
{
return( (c >= 0 && c <= 31) || c == 127);
}
inline bool parser::is_special(int c)
{
switch (c) {
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ': case '\t':
return true;
default:
return false;
}
}
inline bool parser::is_digit(int c)
{
return(c >= '0' && c <= '9');
}
inline bool parser::is_hex_digit(int c)
{
return((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
inline bool parser::is_cookie_attribute(const std::string& name, bool set_cookie_header)
{
return (name.empty() || name[0] == '$' || (set_cookie_header &&
(
// This is needed because of a very lenient determination in parse_cookie_header() of what
// qualifies as a cookie-pair in a Set-Cookie header.
// According to RFC 6265, everything after the first semicolon is a cookie attribute, but RFC 2109,
// which is obsolete, allowed multiple comma separated cookies.
// parse_cookie_header() is very conservatively assuming that any <name>=<value> pair in a
// Set-Cookie header is a cookie-pair unless <name> is a known cookie attribute.
boost::algorithm::iequals(name, "Comment")
|| boost::algorithm::iequals(name, "Domain")
|| boost::algorithm::iequals(name, "Max-Age")
|| boost::algorithm::iequals(name, "Path")
|| boost::algorithm::iequals(name, "Secure")
|| boost::algorithm::iequals(name, "Version")
|| boost::algorithm::iequals(name, "Expires")
|| boost::algorithm::iequals(name, "HttpOnly")
)
));
}
} // end namespace http
} // end namespace pion
#endif
pion::http::parser::COOKIE_NAME_MAX
static const boost::uint32_t COOKIE_NAME_MAX
maximum length for the name of a cookie name
Definition: parser.hpp:558
pion::http::parser::parse_url_encoded
static bool parse_url_encoded(ihash_multimap &dict, const std::string &query)
Definition: parser.hpp:384
pion::http::parser::create_error_category
static void create_error_category(void)
creates the unique parser error_category_t
Definition: http_parser.cpp:1568
pion::http::parser::DEFAULT_CONTENT_MAX
static const std::size_t DEFAULT_CONTENT_MAX
maximum length for HTTP payload content
Definition: parser.hpp:46
pion::http::parser::is_parsing_response
bool is_parsing_response(void) const
returns true if the parser is being used to parse an HTTP response
Definition: parser.hpp:279
pion::http::parser::QUERY_NAME_MAX
static const boost::uint32_t QUERY_NAME_MAX
maximum length for the name of a query string variable
Definition: parser.hpp:552
pion::http::parser::HEADER_VALUE_MAX
static const boost::uint32_t HEADER_VALUE_MAX
maximum length for an HTTP header value
Definition: parser.hpp:549
pion::http::parser::m_read_end_ptr
const char * m_read_end_ptr
points to the end of the read_buffer (last byte + 1)
Definition: parser.hpp:574
pion::http::parser::get_raw_headers
const std::string & get_raw_headers(void) const
returns the raw HTTP headers saved by the parser
Definition: parser.hpp:267
pion::http::parser::set_save_raw_headers
void set_save_raw_headers(bool b)
sets parameter for saving raw HTTP header content
Definition: parser.hpp:291
pion::http::parser::STATUS_MESSAGE_MAX
static const boost::uint32_t STATUS_MESSAGE_MAX
maximum length for response status message
Definition: parser.hpp:534
pion::http::parser::RESOURCE_MAX
static const boost::uint32_t RESOURCE_MAX
maximum length for the resource requested
Definition: parser.hpp:540
pion::http::parser::m_logger
logger m_logger
primary logging interface used by this class
Definition: parser.hpp:565
pion::http::parser::error_category_t
class-specific error category
Definition: parser.hpp:74
pion::http::parser::get_content_bytes_read
std::size_t get_content_bytes_read(void) const
returns the total number of bytes read while parsing the payload content
Definition: parser.hpp:261
pion::http::parser::set_max_content_length
void set_max_content_length(std::size_t n)
sets the maximum length for HTTP payload content
Definition: parser.hpp:285
pion::http::parser::parser
parser(const bool is_request, std::size_t max_content_length=DEFAULT_CONTENT_MAX)
Definition: parser.hpp:129
pion::http::parser::set_error
static void set_error(boost::system::error_code &ec, error_value_t ev)
Definition: parser.hpp:516
pion::http::message::concatenate_chunks
void concatenate_chunks(void)
Definition: http_message.cpp:230
pion::http::parser::eof
bool eof(void) const
returns true if there are no more bytes available in the read buffer
Definition: parser.hpp:249
pion::http::parser::error_value_t
error_value_t
class-specific error code values
Definition: parser.hpp:52
pion::http::parser::QUERY_STRING_MAX
static const boost::uint32_t QUERY_STRING_MAX
maximum length for the query string
Definition: parser.hpp:543
pion::http::parser::reset_max_content_length
void reset_max_content_length(void)
resets the maximum length for HTTP payload content to the default value
Definition: parser.hpp:288
pion::http::message::chunk_cache_t
std::vector< char > chunk_cache_t
used to cache chunked data
Definition: message.hpp:64
pion::http::parser::bytes_available
std::size_t bytes_available(void) const
returns the number of bytes available in the read buffer
Definition: parser.hpp:252
pion::http::parser::skip_header_parsing
void skip_header_parsing(http::message &http_msg)
Definition: parser.hpp:229
pion::http::parser::m_is_request
const bool m_is_request
true if the message is an HTTP request; false if it is an HTTP response
Definition: parser.hpp:568
pion::http::parser::get_parse_headers_only
bool get_parse_headers_only(void)
returns true if parsing headers only
Definition: parser.hpp:273
pion::http::parser::parse_cookie_header
static bool parse_cookie_header(ihash_multimap &dict, const std::string &cookie_header, bool set_cookie_header)
Definition: parser.hpp:369
pion::http::message
Definition: message.hpp:55
pion::http::parser::get_max_content_length
std::size_t get_max_content_length(void) const
returns the maximum length for HTTP payload content
Definition: parser.hpp:264
pion::http::parser::finished_parsing_headers
virtual void finished_parsing_headers(const boost::system::error_code &ec)
Called after we have finished parsing the HTTP message headers.
Definition: parser.hpp:443
pion::http::parser::COOKIE_VALUE_MAX
static const boost::uint32_t COOKIE_VALUE_MAX
maximum length for the value of a cookie; also used for path and domain
Definition: parser.hpp:561
pion::http::parser::gcount
std::size_t gcount(void) const
returns the number of bytes read during the last parse operation
Definition: parser.hpp:255
pion::http::parser::get_save_raw_headers
bool get_save_raw_headers(void) const
returns true if the parser is saving raw HTTP header contents
Definition: parser.hpp:270
pion::http::parser::is_parsing_request
bool is_parsing_request(void) const
returns true if the parser is being used to parse an HTTP request
Definition: parser.hpp:276
pion::logger
Definition: logger.hpp:238
pion::http::parser::m_read_ptr
const char * m_read_ptr
points to the next character to be consumed in the read_buffer
Definition: parser.hpp:571
pion::http::parser::get_error_category
static error_category_t & get_error_category(void)
returns an instance of parser::error_category_t
Definition: parser.hpp:434
pion::http::parser::get_total_bytes_read
std::size_t get_total_bytes_read(void) const
returns the total number of bytes read while parsing the HTTP message
Definition: parser.hpp:258
pion::http::parser::parse_multipart_form_data
static bool parse_multipart_form_data(ihash_multimap &dict, const std::string &content_type, const std::string &form_data)
Definition: parser.hpp:400
pion::http::parser::get_logger
logger get_logger(void)
returns the logger currently in use
Definition: parser.hpp:297
pion::http::parser::load_read_pos
void load_read_pos(const char *&read_ptr, const char *&read_end_ptr) const
Definition: parser.hpp:195
pion::http::parser::HEADER_NAME_MAX
static const boost::uint32_t HEADER_NAME_MAX
maximum length for an HTTP header name
Definition: parser.hpp:546
pion::http::parser::set_payload_handler
void set_payload_handler(payload_handler_t &h)
defines a callback function to be used for consuming payload content
Definition: parser.hpp:282
pion
Definition: admin_rights.hpp:19
pion::http::parser::QUERY_VALUE_MAX
static const boost::uint32_t QUERY_VALUE_MAX
maximum length for the value of a query string variable
Definition: parser.hpp:555
pion::http::parser
Definition: parser.hpp:39
pion::http::parser::payload_handler_t
boost::function2< void, const char *, std::size_t > payload_handler_t
callback type used to consume payload content
Definition: parser.hpp:49
pion::http::parser::set_logger
void set_logger(logger log_ptr)
sets the logger to be used
Definition: parser.hpp:294
pion::http::parser::~parser
virtual ~parser()
default destructor
Definition: parser.hpp:141
pion::http::parser::check_premature_eof
bool check_premature_eof(http::message &http_msg)
Definition: parser.hpp:208
pion::http::parser::parse_headers_only
void parse_headers_only(bool b=true)
Definition: parser.hpp:222
pion::http::parser::METHOD_MAX
static const boost::uint32_t METHOD_MAX
maximum length for the request method
Definition: parser.hpp:537
pion::http::parser::reset
void reset(void)
resets the parser to its initial state
Definition: parser.hpp:235
pion::http::parser::set_read_buffer
void set_read_buffer(const char *ptr, size_t len)
Definition: parser.hpp:184