message.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_MESSAGE_HEADER__
#define __PION_HTTP_MESSAGE_HEADER__
#include <iosfwd>
#include <vector>
#include <cstring>
#include <boost/cstdint.hpp>
#include <boost/asio.hpp>
#include <boost/scoped_array.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/regex.hpp>
#include <pion/config.hpp>
#include <pion/http/types.hpp>
#ifndef BOOST_SYSTEM_NOEXCEPT
// if 'BOOST_NOEXCEPT' is not defined, as with some older versions of
// boost, setting it to nothing should be harmless.
#ifndef BOOST_NOEXCEPT
#define BOOST_SYSTEM_NOEXCEPT
#else
#define BOOST_SYSTEM_NOEXCEPT BOOST_NOEXCEPT
#endif
#endif
namespace pion { // begin namespace pion
namespace tcp {
// forward declaration for class used by send() and receive()
class connection;
}
namespace http { // begin namespace http
// forward declaration of parser class
class parser;
class PION_API message
: public http::types
{
public:
typedef std::vector<boost::asio::const_buffer> write_buffers_t;
typedef std::vector<char> chunk_cache_t;
struct receive_error_t
: public boost::system::error_category
{
virtual ~receive_error_t() {}
virtual inline const char *name() const BOOST_SYSTEM_NOEXCEPT { return "receive_error_t"; }
virtual inline std::string message(int ev) const {
std::string result;
switch(ev) {
case 1:
result = "HTTP message parsing error";
break;
default:
result = "Unknown receive error";
break;
}
return result;
}
};
enum data_status_t
{
STATUS_NONE, // no data received (i.e. all lost)
STATUS_TRUNCATED, // one or more missing packets at the end
STATUS_PARTIAL, // one or more missing packets but NOT at the end
STATUS_OK // no missing packets
};
message(void)
: m_is_valid(false), m_is_chunked(false), m_chunks_supported(false),
m_do_not_send_content_length(false),
m_version_major(1), m_version_minor(1), m_content_length(0), m_content_buf(),
m_status(STATUS_NONE), m_has_missing_packets(false), m_has_data_after_missing(false)
{}
message(const message& http_msg)
: m_first_line(http_msg.m_first_line),
m_is_valid(http_msg.m_is_valid),
m_is_chunked(http_msg.m_is_chunked),
m_chunks_supported(http_msg.m_chunks_supported),
m_do_not_send_content_length(http_msg.m_do_not_send_content_length),
m_remote_ip(http_msg.m_remote_ip),
m_version_major(http_msg.m_version_major),
m_version_minor(http_msg.m_version_minor),
m_content_length(http_msg.m_content_length),
m_content_buf(http_msg.m_content_buf),
m_chunk_cache(http_msg.m_chunk_cache),
m_headers(http_msg.m_headers),
m_status(http_msg.m_status),
m_has_missing_packets(http_msg.m_has_missing_packets),
m_has_data_after_missing(http_msg.m_has_data_after_missing)
{}
inline message& operator=(const message& http_msg) {
m_first_line = http_msg.m_first_line;
m_is_valid = http_msg.m_is_valid;
m_is_chunked = http_msg.m_is_chunked;
m_chunks_supported = http_msg.m_chunks_supported;
m_do_not_send_content_length = http_msg.m_do_not_send_content_length;
m_remote_ip = http_msg.m_remote_ip;
m_version_major = http_msg.m_version_major;
m_version_minor = http_msg.m_version_minor;
m_content_length = http_msg.m_content_length;
m_content_buf = http_msg.m_content_buf;
m_chunk_cache = http_msg.m_chunk_cache;
m_headers = http_msg.m_headers;
m_status = http_msg.m_status;
m_has_missing_packets = http_msg.m_has_missing_packets;
m_has_data_after_missing = http_msg.m_has_data_after_missing;
return *this;
}
virtual ~message() {}
virtual void clear(void) {
clear_first_line();
m_is_valid = m_is_chunked = m_chunks_supported
= m_do_not_send_content_length = false;
m_remote_ip = boost::asio::ip::address_v4(0);
m_version_major = m_version_minor = 1;
m_content_length = 0;
m_content_buf.clear();
m_chunk_cache.clear();
m_headers.clear();
m_cookie_params.clear();
m_status = STATUS_NONE;
m_has_missing_packets = false;
m_has_data_after_missing = false;
}
virtual bool is_content_length_implied(void) const = 0;
inline bool is_valid(void) const { return m_is_valid; }
inline bool get_chunks_supported(void) const { return m_chunks_supported; }
inline boost::asio::ip::address& get_remote_ip(void) {
return m_remote_ip;
}
inline boost::uint16_t get_version_major(void) const { return m_version_major; }
inline boost::uint16_t get_version_minor(void) const { return m_version_minor; }
inline std::string get_version_string(void) const {
std::string http_version(STRING_HTTP_VERSION);
http_version += boost::lexical_cast<std::string>(get_version_major());
http_version += '.';
http_version += boost::lexical_cast<std::string>(get_version_minor());
return http_version;
}
inline size_t get_content_length(void) const { return m_content_length; }
inline bool is_chunked(void) const { return m_is_chunked; }
bool is_content_buffer_allocated() const { return !m_content_buf.is_empty(); }
inline std::size_t get_content_buffer_size() const { return m_content_buf.size(); }
inline char *get_content(void) { return m_content_buf.get(); }
inline const char *get_content(void) const { return m_content_buf.get(); }
inline chunk_cache_t& get_chunk_cache(void) { return m_chunk_cache; }
inline const std::string& get_header(const std::string& key) const {
return get_value(m_headers, key);
}
inline ihash_multimap& get_headers(void) {
return m_headers;
}
inline bool has_header(const std::string& key) const {
return(m_headers.find(key) != m_headers.end());
}
inline const std::string& get_cookie(const std::string& key) const {
return get_value(m_cookie_params, key);
}
inline ihash_multimap& get_cookies(void) {
return m_cookie_params;
}
inline bool has_cookie(const std::string& key) const {
return(m_cookie_params.find(key) != m_cookie_params.end());
}
inline void add_cookie(const std::string& key, const std::string& value) {
m_cookie_params.insert(std::make_pair(key, value));
}
inline void change_cookie(const std::string& key, const std::string& value) {
change_value(m_cookie_params, key, value);
}
inline void delete_cookie(const std::string& key) {
delete_value(m_cookie_params, key);
}
inline const std::string& get_first_line(void) const {
if (m_first_line.empty())
update_first_line();
return m_first_line;
}
inline bool has_missing_packets() const { return m_has_missing_packets; }
inline void set_missing_packets(bool newVal) { m_has_missing_packets = newVal; }
inline bool has_data_after_missing_packets() const { return m_has_data_after_missing; }
inline void set_data_after_missing_packet(bool newVal) { m_has_data_after_missing = newVal; }
inline void set_is_valid(bool b = true) { m_is_valid = b; }
inline void set_chunks_supported(bool b) { m_chunks_supported = b; }
inline void set_remote_ip(const boost::asio::ip::address& ip) { m_remote_ip = ip; }
inline void set_version_major(const boost::uint16_t n) {
m_version_major = n;
clear_first_line();
}
inline void set_version_minor(const boost::uint16_t n) {
m_version_minor = n;
clear_first_line();
}
inline void set_content_length(size_t n) { m_content_length = n; }
inline void set_do_not_send_content_length(void) { m_do_not_send_content_length = true; }
inline data_status_t get_status() const { return m_status; }
inline void set_status(data_status_t newVal) { m_status = newVal; }
inline void update_content_length_using_header(void) {
ihash_multimap::const_iterator i = m_headers.find(HEADER_CONTENT_LENGTH);
if (i == m_headers.end()) {
m_content_length = 0;
} else {
std::string trimmed_length(i->second);
boost::algorithm::trim(trimmed_length);
m_content_length = boost::lexical_cast<size_t>(trimmed_length);
}
}
inline void update_transfer_encoding_using_header(void) {
m_is_chunked = false;
ihash_multimap::const_iterator i = m_headers.find(HEADER_TRANSFER_ENCODING);
if (i != m_headers.end()) {
// From RFC 2616, sec 3.6: All transfer-coding values are case-insensitive.
m_is_chunked = boost::regex_match(i->second, REGEX_ICASE_CHUNKED);
// ignoring other possible values for now
}
}
inline char *create_content_buffer(void) {
m_content_buf.resize(m_content_length);
return m_content_buf.get();
}
inline void set_content(const std::string& content) {
set_content_length(content.size());
create_content_buffer();
memcpy(m_content_buf.get(), content.c_str(), content.size());
}
inline void clear_content(void) {
set_content_length(0);
create_content_buffer();
delete_value(m_headers, HEADER_CONTENT_TYPE);
}
inline void set_content_type(const std::string& type) {
change_value(m_headers, HEADER_CONTENT_TYPE, type);
}
inline void add_header(const std::string& key, const std::string& value) {
m_headers.insert(std::make_pair(key, value));
}
inline void change_header(const std::string& key, const std::string& value) {
change_value(m_headers, key, value);
}
inline void delete_header(const std::string& key) {
delete_value(m_headers, key);
}
inline bool check_keep_alive(void) const {
return (get_header(HEADER_CONNECTION) != "close"
&& (get_version_major() > 1
|| (get_version_major() >= 1 && get_version_minor() >= 1)) );
}
inline void prepare_buffers_for_send(write_buffers_t& write_buffers,
const bool keep_alive,
const bool using_chunks)
{
// update message headers
prepare_headers_for_send(keep_alive, using_chunks);
// add first message line
write_buffers.push_back(boost::asio::buffer(get_first_line()));
write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
// append cookie headers (if any)
append_cookie_headers();
// append HTTP headers
append_headers(write_buffers);
}
std::size_t send(tcp::connection& tcp_conn,
boost::system::error_code& ec,
bool headers_only = false);
std::size_t receive(tcp::connection& tcp_conn,
boost::system::error_code& ec,
parser& http_parser);
std::size_t receive(tcp::connection& tcp_conn,
boost::system::error_code& ec,
bool headers_only = false,
std::size_t max_content_length = static_cast<size_t>(-1));
std::size_t write(std::ostream& out,
boost::system::error_code& ec,
bool headers_only = false);
std::size_t read(std::istream& in,
boost::system::error_code& ec,
parser& http_parser);
std::size_t read(std::istream& in,
boost::system::error_code& ec,
bool headers_only = false,
std::size_t max_content_length = static_cast<size_t>(-1));
void concatenate_chunks(void);
protected:
class content_buffer_t {
public:
~content_buffer_t() {}
content_buffer_t() : m_buf(), m_len(0), m_empty(0), m_ptr(&m_empty) {}
content_buffer_t(const content_buffer_t& buf)
: m_buf(), m_len(0), m_empty(0), m_ptr(&m_empty)
{
if (buf.size()) {
resize(buf.size());
memcpy(get(), buf.get(), buf.size());
}
}
content_buffer_t& operator=(const content_buffer_t& buf) {
if (buf.size()) {
resize(buf.size());
memcpy(get(), buf.get(), buf.size());
} else {
clear();
}
return *this;
}
inline bool is_empty() const { return m_len == 0; }
inline std::size_t size() const { return m_len; }
inline const char *get() const { return m_ptr; }
inline char *get() { return m_ptr; }
inline void resize(std::size_t len) {
m_len = len;
if (len == 0) {
m_buf.reset();
m_ptr = &m_empty;
} else {
m_buf.reset(new char[len+1]);
m_buf[len] = '\0';
m_ptr = m_buf.get();
}
}
inline void clear() { resize(0); }
private:
boost::scoped_array<char> m_buf;
std::size_t m_len;
char m_empty;
char *m_ptr;
};
inline void prepare_headers_for_send(const bool keep_alive,
const bool using_chunks)
{
change_header(HEADER_CONNECTION, (keep_alive ? "Keep-Alive" : "close") );
if (using_chunks) {
if (get_chunks_supported())
change_header(HEADER_TRANSFER_ENCODING, "chunked");
} else if (! m_do_not_send_content_length) {
change_header(HEADER_CONTENT_LENGTH, boost::lexical_cast<std::string>(get_content_length()));
}
}
inline void append_headers(write_buffers_t& write_buffers) {
// add HTTP headers
for (ihash_multimap::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i) {
write_buffers.push_back(boost::asio::buffer(i->first));
write_buffers.push_back(boost::asio::buffer(HEADER_NAME_VALUE_DELIMITER));
write_buffers.push_back(boost::asio::buffer(i->second));
write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
}
// add an extra CRLF to end HTTP headers
write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
}
virtual void append_cookie_headers(void) {}
template <typename DictionaryType>
inline static const std::string& get_value(const DictionaryType& dict,
const std::string& key)
{
typename DictionaryType::const_iterator i = dict.find(key);
return ( (i==dict.end()) ? STRING_EMPTY : i->second );
}
template <typename DictionaryType>
inline static void change_value(DictionaryType& dict,
const std::string& key, const std::string& value)
{
// retrieve all current values for key
std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
result_pair = dict.equal_range(key);
if (result_pair.first == dict.end()) {
// no values exist -> add a new key
dict.insert(std::make_pair(key, value));
} else {
// set the first value found for the key to the new one
result_pair.first->second = value;
// remove any remaining values
typename DictionaryType::iterator i;
++(result_pair.first);
while (result_pair.first != result_pair.second) {
i = result_pair.first;
++(result_pair.first);
dict.erase(i);
}
}
}
template <typename DictionaryType>
inline static void delete_value(DictionaryType& dict,
const std::string& key)
{
std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
result_pair = dict.equal_range(key);
if (result_pair.first != dict.end())
dict.erase(result_pair.first, result_pair.second);
}
inline void clear_first_line(void) const {
if (! m_first_line.empty())
m_first_line.clear();
}
virtual void update_first_line(void) const = 0;
mutable std::string m_first_line;
private:
static const boost::regex REGEX_ICASE_CHUNKED;
bool m_is_valid;
bool m_is_chunked;
bool m_chunks_supported;
bool m_do_not_send_content_length;
boost::asio::ip::address m_remote_ip;
boost::uint16_t m_version_major;
boost::uint16_t m_version_minor;
size_t m_content_length;
content_buffer_t m_content_buf;
chunk_cache_t m_chunk_cache;
ihash_multimap m_headers;
ihash_multimap m_cookie_params;
data_status_t m_status;
bool m_has_missing_packets;
bool m_has_data_after_missing;
};
} // end namespace http
} // end namespace pion
#endif
pion::http::message::check_keep_alive
bool check_keep_alive(void) const
returns true if the HTTP connection may be kept alive
Definition: message.hpp:378
pion::http::message::clear_first_line
void clear_first_line(void) const
Definition: message.hpp:668
pion::http::message::get_chunks_supported
bool get_chunks_supported(void) const
returns true if chunked transfer encodings are supported
Definition: message.hpp:169
pion::http::message::update_content_length_using_header
void update_content_length_using_header(void)
sets the length of the payload content using the Content-Length header
Definition: message.hpp:314
pion::http::message::~message
virtual ~message()
virtual destructor
Definition: message.hpp:143
pion::http::message::content_buffer_t::is_empty
bool is_empty() const
returns true if buffer is empty
Definition: message.hpp:528
pion::http::message::operator=
message & operator=(const message &http_msg)
assignment operator
Definition: message.hpp:123
pion::http::message::change_cookie
void change_cookie(const std::string &key, const std::string &value)
Definition: message.hpp:252
pion::http::message::add_header
void add_header(const std::string &key, const std::string &value)
adds a value for the HTTP header named key
Definition: message.hpp:363
pion::http::message::add_cookie
void add_cookie(const std::string &key, const std::string &value)
Definition: message.hpp:246
pion::http::message::clear
virtual void clear(void)
clears all message data
Definition: message.hpp:146
pion::tcp::connection
Definition: connection.hpp:40
pion::http::message::get_remote_ip
boost::asio::ip::address & get_remote_ip(void)
returns IP address of the remote endpoint
Definition: message.hpp:172
pion::http::message::set_is_valid
void set_is_valid(bool b=true)
sets whether or not the message is valid
Definition: message.hpp:281
pion::http::message::get_content
const char * get_content(void) const
returns a const pointer to the payload content, or empty string if there is none
Definition: message.hpp:207
pion::http::message::set_version_minor
void set_version_minor(const boost::uint16_t n)
sets the minor HTTP version number
Definition: message.hpp:296
pion::http::message::is_valid
bool is_valid(void) const
returns true if the message is valid
Definition: message.hpp:166
pion::http::message::set_version_major
void set_version_major(const boost::uint16_t n)
sets the major HTTP version number
Definition: message.hpp:290
pion::http::message::set_remote_ip
void set_remote_ip(const boost::asio::ip::address &ip)
sets IP address of the remote endpoint
Definition: message.hpp:287
pion::http::message::set_missing_packets
void set_missing_packets(bool newVal)
set to true when missing packets detected
Definition: message.hpp:273
pion::http::message::append_headers
void append_headers(write_buffers_t &write_buffers)
Definition: message.hpp:585
pion::http::message::has_data_after_missing_packets
bool has_data_after_missing_packets() const
true if more data seen after the missing packets
Definition: message.hpp:276
pion::http::message::change_header
void change_header(const std::string &key, const std::string &value)
changes the value for the HTTP header named key
Definition: message.hpp:368
pion::http::message::get_value
static const std::string & get_value(const DictionaryType &dict, const std::string &key)
Definition: message.hpp:609
pion::http::message::get_header
const std::string & get_header(const std::string &key) const
returns a value for the header if any are defined; otherwise, an empty string
Definition: message.hpp:213
pion::http::message::set_chunks_supported
void set_chunks_supported(bool b)
set to true if chunked transfer encodings are supported
Definition: message.hpp:284
pion::http::message::get_headers
ihash_multimap & get_headers(void)
returns a reference to the HTTP headers
Definition: message.hpp:218
pion::http::message::content_buffer_t::get
const char * get() const
returns const pointer to data
Definition: message.hpp:534
pion::http::message::set_content
void set_content(const std::string &content)
resets payload content to match the value of a string
Definition: message.hpp:344
pion::http::message::get_version_minor
boost::uint16_t get_version_minor(void) const
returns the minor HTTP version number
Definition: message.hpp:180
pion::http::message::set_content_length
void set_content_length(size_t n)
sets the length of the payload content (in bytes)
Definition: message.hpp:302
pion::http::message::content_buffer_t::operator=
content_buffer_t & operator=(const content_buffer_t &buf)
assignment operator
Definition: message.hpp:517
pion::http::message::get_first_line
const std::string & get_first_line(void) const
returns a string containing the first line for the HTTP message
Definition: message.hpp:263
pion::http::message::set_content_type
void set_content_type(const std::string &type)
sets the content type for the message payload
Definition: message.hpp:358
pion::http::message::chunk_cache_t
std::vector< char > chunk_cache_t
used to cache chunked data
Definition: message.hpp:64
pion::http::message::change_value
static void change_value(DictionaryType &dict, const std::string &key, const std::string &value)
Definition: message.hpp:626
pion::http::message::message
message(const message &http_msg)
copy constructor
Definition: message.hpp:104
pion::http::message::has_cookie
bool has_cookie(const std::string &key) const
Definition: message.hpp:240
pion::http::message::content_buffer_t::~content_buffer_t
~content_buffer_t()
simple destructor
Definition: message.hpp:501
pion::http::message::content_buffer_t::content_buffer_t
content_buffer_t()
default constructor
Definition: message.hpp:504
pion::http::message::content_buffer_t
a simple helper class used to manage a fixed-size payload content buffer
Definition: message.hpp:498
pion::http::message::has_missing_packets
bool has_missing_packets() const
true if there were missing packets
Definition: message.hpp:270
pion::http::message::get_version_string
std::string get_version_string(void) const
returns a string representation of the HTTP version (i.e. "HTTP/1.1")
Definition: message.hpp:183
pion::http::message::delete_header
void delete_header(const std::string &key)
removes all values for the HTTP header named key
Definition: message.hpp:373
pion::http::message
Definition: message.hpp:55
pion::http::message::message
message(void)
constructs a new HTTP message object
Definition: message.hpp:96
pion::http::message::prepare_headers_for_send
void prepare_headers_for_send(const bool keep_alive, const bool using_chunks)
Definition: message.hpp:568
pion::http::message::m_first_line
std::string m_first_line
Definition: message.hpp:678
pion::http::message::get_chunk_cache
chunk_cache_t & get_chunk_cache(void)
returns a reference to the chunk cache
Definition: message.hpp:210
pion::http::message::get_cookie
const std::string & get_cookie(const std::string &key) const
Definition: message.hpp:229
pion::http::message::content_buffer_t::resize
void resize(std::size_t len)
changes the size of the content buffer
Definition: message.hpp:540
pion::http::message::set_do_not_send_content_length
void set_do_not_send_content_length(void)
if called, the content-length will not be sent in the HTTP headers
Definition: message.hpp:305
pion::http::message::get_content_length
size_t get_content_length(void) const
returns the length of the payload content (in bytes)
Definition: message.hpp:192
pion::http::message::append_cookie_headers
virtual void append_cookie_headers(void)
appends HTTP headers for any cookies defined by the http::message
Definition: message.hpp:598
pion::http::message::content_buffer_t::content_buffer_t
content_buffer_t(const content_buffer_t &buf)
copy constructor
Definition: message.hpp:507
pion::http::message::delete_cookie
void delete_cookie(const std::string &key)
Definition: message.hpp:258
pion::http::message::get_content_buffer_size
std::size_t get_content_buffer_size() const
returns size of allocated buffer
Definition: message.hpp:201
pion::http::message::update_transfer_encoding_using_header
void update_transfer_encoding_using_header(void)
sets the transfer coding using the Transfer-Encoding header
Definition: message.hpp:326
pion::http::message::receive_error_t
data type for library errors returned during receive() operations
Definition: message.hpp:67
pion
Definition: admin_rights.hpp:19
pion::http::message::create_content_buffer
char * create_content_buffer(void)
Definition: message.hpp:338
pion::http::message::delete_value
static void delete_value(DictionaryType &dict, const std::string &key)
Definition: message.hpp:657
pion::http::message::data_status_t
data_status_t
defines message data integrity status codes
Definition: message.hpp:87
pion::http::message::clear_content
void clear_content(void)
clears payload content buffer
Definition: message.hpp:351
pion::http::message::get_content
char * get_content(void)
returns a pointer to the payload content, or empty string if there is none
Definition: message.hpp:204
pion::http::parser
Definition: parser.hpp:39
pion::http::types
Definition: types.hpp:25
pion::http::message::is_chunked
bool is_chunked(void) const
returns true if the message content is chunked
Definition: message.hpp:195
pion::http::message::is_content_buffer_allocated
bool is_content_buffer_allocated() const
returns true if buffer for content is allocated
Definition: message.hpp:198
pion::http::message::get_version_major
boost::uint16_t get_version_major(void) const
returns the major HTTP version number
Definition: message.hpp:177
pion::http::message::content_buffer_t::size
std::size_t size() const
returns size in bytes
Definition: message.hpp:531
pion::http::message::prepare_buffers_for_send
void prepare_buffers_for_send(write_buffers_t &write_buffers, const bool keep_alive, const bool using_chunks)
Definition: message.hpp:391
pion::http::message::get_status
data_status_t get_status() const
return the data receival status
Definition: message.hpp:308
pion::http::message::content_buffer_t::clear
void clear()
clears the content buffer
Definition: message.hpp:553
pion::http::message::get_cookies
ihash_multimap & get_cookies(void)
returns the cookie parameters
Definition: message.hpp:234
pion::http::message::has_header
bool has_header(const std::string &key) const
returns true if at least one value for the header is defined
Definition: message.hpp:223
pion::http::message::write_buffers_t
std::vector< boost::asio::const_buffer > write_buffers_t
data type for I/O write buffers (these wrap existing data to be sent)
Definition: message.hpp:61