pion  5.0.6
message.hpp
1 // ---------------------------------------------------------------------
2 // pion: a Boost C++ framework for building lightweight HTTP interfaces
3 // ---------------------------------------------------------------------
4 // Copyright (C) 2007-2014 Splunk Inc. (https://github.com/splunk/pion)
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See http://www.boost.org/LICENSE_1_0.txt
8 //
9 
10 #ifndef __PION_HTTP_MESSAGE_HEADER__
11 #define __PION_HTTP_MESSAGE_HEADER__
12 
13 #include <iosfwd>
14 #include <vector>
15 #include <cstring>
16 #include <boost/cstdint.hpp>
17 #include <boost/asio.hpp>
18 #include <boost/scoped_array.hpp>
19 #include <boost/lexical_cast.hpp>
20 #include <boost/algorithm/string/trim.hpp>
21 #include <boost/regex.hpp>
22 #include <pion/config.hpp>
23 #include <pion/http/types.hpp>
24 
25 #ifndef BOOST_SYSTEM_NOEXCEPT
26  // if 'BOOST_NOEXCEPT' is not defined, as with some older versions of
27  // boost, setting it to nothing should be harmless.
28  #ifndef BOOST_NOEXCEPT
29  #define BOOST_SYSTEM_NOEXCEPT
30  #else
31  #define BOOST_SYSTEM_NOEXCEPT BOOST_NOEXCEPT
32  #endif
33 #endif
34 
35 
36 namespace pion { // begin namespace pion
37 
38 
39 namespace tcp {
40  // forward declaration for class used by send() and receive()
41  class connection;
42 }
43 
44 
45 namespace http { // begin namespace http
46 
47 
48 // forward declaration of parser class
49 class parser;
50 
51 
55 class PION_API message
56  : public http::types
57 {
58 public:
59 
61  typedef std::vector<boost::asio::const_buffer> write_buffers_t;
62 
64  typedef std::vector<char> chunk_cache_t;
65 
68  : public boost::system::error_category
69  {
70  virtual ~receive_error_t() {}
71  virtual inline const char *name() const BOOST_SYSTEM_NOEXCEPT { return "receive_error_t"; }
72  virtual inline std::string message(int ev) const {
73  std::string result;
74  switch(ev) {
75  case 1:
76  result = "HTTP message parsing error";
77  break;
78  default:
79  result = "Unknown receive error";
80  break;
81  }
82  return result;
83  }
84  };
85 
88  {
89  STATUS_NONE, // no data received (i.e. all lost)
90  STATUS_TRUNCATED, // one or more missing packets at the end
91  STATUS_PARTIAL, // one or more missing packets but NOT at the end
92  STATUS_OK // no missing packets
93  };
94 
96  message(void)
97  : m_is_valid(false), m_is_chunked(false), m_chunks_supported(false),
98  m_do_not_send_content_length(false),
99  m_version_major(1), m_version_minor(1), m_content_length(0), m_content_buf(),
100  m_status(STATUS_NONE), m_has_missing_packets(false), m_has_data_after_missing(false)
101  {}
102 
104  message(const message& http_msg)
105  : m_first_line(http_msg.m_first_line),
106  m_is_valid(http_msg.m_is_valid),
107  m_is_chunked(http_msg.m_is_chunked),
108  m_chunks_supported(http_msg.m_chunks_supported),
109  m_do_not_send_content_length(http_msg.m_do_not_send_content_length),
110  m_remote_ip(http_msg.m_remote_ip),
111  m_version_major(http_msg.m_version_major),
112  m_version_minor(http_msg.m_version_minor),
113  m_content_length(http_msg.m_content_length),
114  m_content_buf(http_msg.m_content_buf),
115  m_chunk_cache(http_msg.m_chunk_cache),
116  m_headers(http_msg.m_headers),
117  m_status(http_msg.m_status),
118  m_has_missing_packets(http_msg.m_has_missing_packets),
119  m_has_data_after_missing(http_msg.m_has_data_after_missing)
120  {}
121 
123  inline message& operator=(const message& http_msg) {
124  m_first_line = http_msg.m_first_line;
125  m_is_valid = http_msg.m_is_valid;
126  m_is_chunked = http_msg.m_is_chunked;
127  m_chunks_supported = http_msg.m_chunks_supported;
128  m_do_not_send_content_length = http_msg.m_do_not_send_content_length;
129  m_remote_ip = http_msg.m_remote_ip;
130  m_version_major = http_msg.m_version_major;
131  m_version_minor = http_msg.m_version_minor;
132  m_content_length = http_msg.m_content_length;
133  m_content_buf = http_msg.m_content_buf;
134  m_chunk_cache = http_msg.m_chunk_cache;
135  m_headers = http_msg.m_headers;
136  m_status = http_msg.m_status;
137  m_has_missing_packets = http_msg.m_has_missing_packets;
138  m_has_data_after_missing = http_msg.m_has_data_after_missing;
139  return *this;
140  }
141 
143  virtual ~message() {}
144 
146  virtual void clear(void) {
147  clear_first_line();
148  m_is_valid = m_is_chunked = m_chunks_supported
149  = m_do_not_send_content_length = false;
150  m_remote_ip = boost::asio::ip::address_v4(0);
151  m_version_major = m_version_minor = 1;
152  m_content_length = 0;
153  m_content_buf.clear();
154  m_chunk_cache.clear();
155  m_headers.clear();
156  m_cookie_params.clear();
157  m_status = STATUS_NONE;
158  m_has_missing_packets = false;
159  m_has_data_after_missing = false;
160  }
161 
163  virtual bool is_content_length_implied(void) const = 0;
164 
166  inline bool is_valid(void) const { return m_is_valid; }
167 
169  inline bool get_chunks_supported(void) const { return m_chunks_supported; }
170 
172  inline boost::asio::ip::address& get_remote_ip(void) {
173  return m_remote_ip;
174  }
175 
177  inline boost::uint16_t get_version_major(void) const { return m_version_major; }
178 
180  inline boost::uint16_t get_version_minor(void) const { return m_version_minor; }
181 
183  inline std::string get_version_string(void) const {
184  std::string http_version(STRING_HTTP_VERSION);
185  http_version += boost::lexical_cast<std::string>(get_version_major());
186  http_version += '.';
187  http_version += boost::lexical_cast<std::string>(get_version_minor());
188  return http_version;
189  }
190 
192  inline size_t get_content_length(void) const { return m_content_length; }
193 
195  inline bool is_chunked(void) const { return m_is_chunked; }
196 
198  bool is_content_buffer_allocated() const { return !m_content_buf.is_empty(); }
199 
201  inline std::size_t get_content_buffer_size() const { return m_content_buf.size(); }
202 
204  inline char *get_content(void) { return m_content_buf.get(); }
205 
207  inline const char *get_content(void) const { return m_content_buf.get(); }
208 
210  inline chunk_cache_t& get_chunk_cache(void) { return m_chunk_cache; }
211 
213  inline const std::string& get_header(const std::string& key) const {
214  return get_value(m_headers, key);
215  }
216 
218  inline ihash_multimap& get_headers(void) {
219  return m_headers;
220  }
221 
223  inline bool has_header(const std::string& key) const {
224  return(m_headers.find(key) != m_headers.end());
225  }
226 
229  inline const std::string& get_cookie(const std::string& key) const {
230  return get_value(m_cookie_params, key);
231  }
232 
234  inline ihash_multimap& get_cookies(void) {
235  return m_cookie_params;
236  }
237 
240  inline bool has_cookie(const std::string& key) const {
241  return(m_cookie_params.find(key) != m_cookie_params.end());
242  }
243 
246  inline void add_cookie(const std::string& key, const std::string& value) {
247  m_cookie_params.insert(std::make_pair(key, value));
248  }
249 
252  inline void change_cookie(const std::string& key, const std::string& value) {
253  change_value(m_cookie_params, key, value);
254  }
255 
258  inline void delete_cookie(const std::string& key) {
259  delete_value(m_cookie_params, key);
260  }
261 
263  inline const std::string& get_first_line(void) const {
264  if (m_first_line.empty())
265  update_first_line();
266  return m_first_line;
267  }
268 
270  inline bool has_missing_packets() const { return m_has_missing_packets; }
271 
273  inline void set_missing_packets(bool newVal) { m_has_missing_packets = newVal; }
274 
276  inline bool has_data_after_missing_packets() const { return m_has_data_after_missing; }
277 
278  inline void set_data_after_missing_packet(bool newVal) { m_has_data_after_missing = newVal; }
279 
281  inline void set_is_valid(bool b = true) { m_is_valid = b; }
282 
284  inline void set_chunks_supported(bool b) { m_chunks_supported = b; }
285 
287  inline void set_remote_ip(const boost::asio::ip::address& ip) { m_remote_ip = ip; }
288 
290  inline void set_version_major(const boost::uint16_t n) {
291  m_version_major = n;
292  clear_first_line();
293  }
294 
296  inline void set_version_minor(const boost::uint16_t n) {
297  m_version_minor = n;
298  clear_first_line();
299  }
300 
302  inline void set_content_length(size_t n) { m_content_length = n; }
303 
305  inline void set_do_not_send_content_length(void) { m_do_not_send_content_length = true; }
306 
308  inline data_status_t get_status() const { return m_status; }
309 
311  inline void set_status(data_status_t newVal) { m_status = newVal; }
312 
315  ihash_multimap::const_iterator i = m_headers.find(HEADER_CONTENT_LENGTH);
316  if (i == m_headers.end()) {
317  m_content_length = 0;
318  } else {
319  std::string trimmed_length(i->second);
320  boost::algorithm::trim(trimmed_length);
321  m_content_length = boost::lexical_cast<size_t>(trimmed_length);
322  }
323  }
324 
327  m_is_chunked = false;
328  ihash_multimap::const_iterator i = m_headers.find(HEADER_TRANSFER_ENCODING);
329  if (i != m_headers.end()) {
330  // From RFC 2616, sec 3.6: All transfer-coding values are case-insensitive.
331  m_is_chunked = boost::regex_match(i->second, REGEX_ICASE_CHUNKED);
332  // ignoring other possible values for now
333  }
334  }
335 
338  inline char *create_content_buffer(void) {
339  m_content_buf.resize(m_content_length);
340  return m_content_buf.get();
341  }
342 
344  inline void set_content(const std::string& content) {
345  set_content_length(content.size());
346  create_content_buffer();
347  memcpy(m_content_buf.get(), content.c_str(), content.size());
348  }
349 
351  inline void clear_content(void) {
352  set_content_length(0);
353  create_content_buffer();
354  delete_value(m_headers, HEADER_CONTENT_TYPE);
355  }
356 
358  inline void set_content_type(const std::string& type) {
359  change_value(m_headers, HEADER_CONTENT_TYPE, type);
360  }
361 
363  inline void add_header(const std::string& key, const std::string& value) {
364  m_headers.insert(std::make_pair(key, value));
365  }
366 
368  inline void change_header(const std::string& key, const std::string& value) {
369  change_value(m_headers, key, value);
370  }
371 
373  inline void delete_header(const std::string& key) {
374  delete_value(m_headers, key);
375  }
376 
378  inline bool check_keep_alive(void) const {
379  return (get_header(HEADER_CONNECTION) != "close"
380  && (get_version_major() > 1
381  || (get_version_major() >= 1 && get_version_minor() >= 1)) );
382  }
383 
391  inline void prepare_buffers_for_send(write_buffers_t& write_buffers,
392  const bool keep_alive,
393  const bool using_chunks)
394  {
395  // update message headers
396  prepare_headers_for_send(keep_alive, using_chunks);
397  // add first message line
398  write_buffers.push_back(boost::asio::buffer(get_first_line()));
399  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
400  // append cookie headers (if any)
401  append_cookie_headers();
402  // append HTTP headers
403  append_headers(write_buffers);
404  }
405 
406 
416  std::size_t send(tcp::connection& tcp_conn,
417  boost::system::error_code& ec,
418  bool headers_only = false);
419 
429  std::size_t receive(tcp::connection& tcp_conn,
430  boost::system::error_code& ec,
431  parser& http_parser);
432 
443  std::size_t receive(tcp::connection& tcp_conn,
444  boost::system::error_code& ec,
445  bool headers_only = false,
446  std::size_t max_content_length = static_cast<size_t>(-1));
447 
457  std::size_t write(std::ostream& out,
458  boost::system::error_code& ec,
459  bool headers_only = false);
460 
470  std::size_t read(std::istream& in,
471  boost::system::error_code& ec,
472  parser& http_parser);
473 
484  std::size_t read(std::istream& in,
485  boost::system::error_code& ec,
486  bool headers_only = false,
487  std::size_t max_content_length = static_cast<size_t>(-1));
488 
492  void concatenate_chunks(void);
493 
494 
495 protected:
496 
499  public:
502 
504  content_buffer_t() : m_buf(), m_len(0), m_empty(0), m_ptr(&m_empty) {}
505 
508  : m_buf(), m_len(0), m_empty(0), m_ptr(&m_empty)
509  {
510  if (buf.size()) {
511  resize(buf.size());
512  memcpy(get(), buf.get(), buf.size());
513  }
514  }
515 
518  if (buf.size()) {
519  resize(buf.size());
520  memcpy(get(), buf.get(), buf.size());
521  } else {
522  clear();
523  }
524  return *this;
525  }
526 
528  inline bool is_empty() const { return m_len == 0; }
529 
531  inline std::size_t size() const { return m_len; }
532 
534  inline const char *get() const { return m_ptr; }
535 
537  inline char *get() { return m_ptr; }
538 
540  inline void resize(std::size_t len) {
541  m_len = len;
542  if (len == 0) {
543  m_buf.reset();
544  m_ptr = &m_empty;
545  } else {
546  m_buf.reset(new char[len+1]);
547  m_buf[len] = '\0';
548  m_ptr = m_buf.get();
549  }
550  }
551 
553  inline void clear() { resize(0); }
554 
555  private:
556  boost::scoped_array<char> m_buf;
557  std::size_t m_len;
558  char m_empty;
559  char *m_ptr;
560  };
561 
568  inline void prepare_headers_for_send(const bool keep_alive,
569  const bool using_chunks)
570  {
571  change_header(HEADER_CONNECTION, (keep_alive ? "Keep-Alive" : "close") );
572  if (using_chunks) {
573  if (get_chunks_supported())
574  change_header(HEADER_TRANSFER_ENCODING, "chunked");
575  } else if (! m_do_not_send_content_length) {
576  change_header(HEADER_CONTENT_LENGTH, boost::lexical_cast<std::string>(get_content_length()));
577  }
578  }
579 
585  inline void append_headers(write_buffers_t& write_buffers) {
586  // add HTTP headers
587  for (ihash_multimap::const_iterator i = m_headers.begin(); i != m_headers.end(); ++i) {
588  write_buffers.push_back(boost::asio::buffer(i->first));
589  write_buffers.push_back(boost::asio::buffer(HEADER_NAME_VALUE_DELIMITER));
590  write_buffers.push_back(boost::asio::buffer(i->second));
591  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
592  }
593  // add an extra CRLF to end HTTP headers
594  write_buffers.push_back(boost::asio::buffer(STRING_CRLF));
595  }
596 
598  virtual void append_cookie_headers(void) {}
599 
608  template <typename DictionaryType>
609  inline static const std::string& get_value(const DictionaryType& dict,
610  const std::string& key)
611  {
612  typename DictionaryType::const_iterator i = dict.find(key);
613  return ( (i==dict.end()) ? STRING_EMPTY : i->second );
614  }
615 
625  template <typename DictionaryType>
626  inline static void change_value(DictionaryType& dict,
627  const std::string& key, const std::string& value)
628 
629  {
630  // retrieve all current values for key
631  std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
632  result_pair = dict.equal_range(key);
633  if (result_pair.first == dict.end()) {
634  // no values exist -> add a new key
635  dict.insert(std::make_pair(key, value));
636  } else {
637  // set the first value found for the key to the new one
638  result_pair.first->second = value;
639  // remove any remaining values
640  typename DictionaryType::iterator i;
641  ++(result_pair.first);
642  while (result_pair.first != result_pair.second) {
643  i = result_pair.first;
644  ++(result_pair.first);
645  dict.erase(i);
646  }
647  }
648  }
649 
656  template <typename DictionaryType>
657  inline static void delete_value(DictionaryType& dict,
658  const std::string& key)
659  {
660  std::pair<typename DictionaryType::iterator, typename DictionaryType::iterator>
661  result_pair = dict.equal_range(key);
662  if (result_pair.first != dict.end())
663  dict.erase(result_pair.first, result_pair.second);
664  }
665 
668  inline void clear_first_line(void) const {
669  if (! m_first_line.empty())
670  m_first_line.clear();
671  }
672 
674  virtual void update_first_line(void) const = 0;
675 
678  mutable std::string m_first_line;
679 
680 
681 private:
682 
684  static const boost::regex REGEX_ICASE_CHUNKED;
685 
687  bool m_is_valid;
688 
690  bool m_is_chunked;
691 
693  bool m_chunks_supported;
694 
696  bool m_do_not_send_content_length;
697 
699  boost::asio::ip::address m_remote_ip;
700 
702  boost::uint16_t m_version_major;
703 
705  boost::uint16_t m_version_minor;
706 
708  size_t m_content_length;
709 
711  content_buffer_t m_content_buf;
712 
714  chunk_cache_t m_chunk_cache;
715 
717  ihash_multimap m_headers;
718 
720  ihash_multimap m_cookie_params;
721 
723  data_status_t m_status;
724 
726  bool m_has_missing_packets;
727 
729  bool m_has_data_after_missing;
730 };
731 
732 
733 } // end namespace http
734 } // end namespace pion
735 
736 #endif
bool check_keep_alive(void) const
returns true if the HTTP connection may be kept alive
Definition: message.hpp:378
void clear_first_line(void) const
Definition: message.hpp:668
bool get_chunks_supported(void) const
returns true if chunked transfer encodings are supported
Definition: message.hpp:169
void update_content_length_using_header(void)
sets the length of the payload content using the Content-Length header
Definition: message.hpp:314
virtual ~message()
virtual destructor
Definition: message.hpp:143
bool is_empty() const
returns true if buffer is empty
Definition: message.hpp:528
message & operator=(const message &http_msg)
assignment operator
Definition: message.hpp:123
void change_cookie(const std::string &key, const std::string &value)
Definition: message.hpp:252
void add_header(const std::string &key, const std::string &value)
adds a value for the HTTP header named key
Definition: message.hpp:363
void add_cookie(const std::string &key, const std::string &value)
Definition: message.hpp:246
virtual void clear(void)
clears all message data
Definition: message.hpp:146
boost::asio::ip::address & get_remote_ip(void)
returns IP address of the remote endpoint
Definition: message.hpp:172
void set_is_valid(bool b=true)
sets whether or not the message is valid
Definition: message.hpp:281
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
void set_version_minor(const boost::uint16_t n)
sets the minor HTTP version number
Definition: message.hpp:296
bool is_valid(void) const
returns true if the message is valid
Definition: message.hpp:166
void set_version_major(const boost::uint16_t n)
sets the major HTTP version number
Definition: message.hpp:290
void set_remote_ip(const boost::asio::ip::address &ip)
sets IP address of the remote endpoint
Definition: message.hpp:287
void set_missing_packets(bool newVal)
set to true when missing packets detected
Definition: message.hpp:273
void append_headers(write_buffers_t &write_buffers)
Definition: message.hpp:585
bool has_data_after_missing_packets() const
true if more data seen after the missing packets
Definition: message.hpp:276
void change_header(const std::string &key, const std::string &value)
changes the value for the HTTP header named key
Definition: message.hpp:368
static const std::string & get_value(const DictionaryType &dict, const std::string &key)
Definition: message.hpp:609
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
void set_chunks_supported(bool b)
set to true if chunked transfer encodings are supported
Definition: message.hpp:284
ihash_multimap & get_headers(void)
returns a reference to the HTTP headers
Definition: message.hpp:218
const char * get() const
returns const pointer to data
Definition: message.hpp:534
void set_content(const std::string &content)
resets payload content to match the value of a string
Definition: message.hpp:344
boost::uint16_t get_version_minor(void) const
returns the minor HTTP version number
Definition: message.hpp:180
void set_content_length(size_t n)
sets the length of the payload content (in bytes)
Definition: message.hpp:302
content_buffer_t & operator=(const content_buffer_t &buf)
assignment operator
Definition: message.hpp:517
const std::string & get_first_line(void) const
returns a string containing the first line for the HTTP message
Definition: message.hpp:263
void set_content_type(const std::string &type)
sets the content type for the message payload
Definition: message.hpp:358
std::vector< char > chunk_cache_t
used to cache chunked data
Definition: message.hpp:64
static void change_value(DictionaryType &dict, const std::string &key, const std::string &value)
Definition: message.hpp:626
message(const message &http_msg)
copy constructor
Definition: message.hpp:104
bool has_cookie(const std::string &key) const
Definition: message.hpp:240
~content_buffer_t()
simple destructor
Definition: message.hpp:501
content_buffer_t()
default constructor
Definition: message.hpp:504
a simple helper class used to manage a fixed-size payload content buffer
Definition: message.hpp:498
bool has_missing_packets() const
true if there were missing packets
Definition: message.hpp:270
std::string get_version_string(void) const
returns a string representation of the HTTP version (i.e. "HTTP/1.1")
Definition: message.hpp:183
void delete_header(const std::string &key)
removes all values for the HTTP header named key
Definition: message.hpp:373
message(void)
constructs a new HTTP message object
Definition: message.hpp:96
void prepare_headers_for_send(const bool keep_alive, const bool using_chunks)
Definition: message.hpp:568
std::string m_first_line
Definition: message.hpp:678
chunk_cache_t & get_chunk_cache(void)
returns a reference to the chunk cache
Definition: message.hpp:210
const std::string & get_cookie(const std::string &key) const
Definition: message.hpp:229
void resize(std::size_t len)
changes the size of the content buffer
Definition: message.hpp:540
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
size_t get_content_length(void) const
returns the length of the payload content (in bytes)
Definition: message.hpp:192
virtual void append_cookie_headers(void)
appends HTTP headers for any cookies defined by the http::message
Definition: message.hpp:598
content_buffer_t(const content_buffer_t &buf)
copy constructor
Definition: message.hpp:507
void delete_cookie(const std::string &key)
Definition: message.hpp:258
std::size_t get_content_buffer_size() const
returns size of allocated buffer
Definition: message.hpp:201
void update_transfer_encoding_using_header(void)
sets the transfer coding using the Transfer-Encoding header
Definition: message.hpp:326
data type for library errors returned during receive() operations
Definition: message.hpp:67
char * create_content_buffer(void)
Definition: message.hpp:338
static void delete_value(DictionaryType &dict, const std::string &key)
Definition: message.hpp:657
data_status_t
defines message data integrity status codes
Definition: message.hpp:87
void clear_content(void)
clears payload content buffer
Definition: message.hpp:351
char * get_content(void)
returns a pointer to the payload content, or empty string if there is none
Definition: message.hpp:204
bool is_chunked(void) const
returns true if the message content is chunked
Definition: message.hpp:195
bool is_content_buffer_allocated() const
returns true if buffer for content is allocated
Definition: message.hpp:198
boost::uint16_t get_version_major(void) const
returns the major HTTP version number
Definition: message.hpp:177
std::size_t size() const
returns size in bytes
Definition: message.hpp:531
void prepare_buffers_for_send(write_buffers_t &write_buffers, const bool keep_alive, const bool using_chunks)
Definition: message.hpp:391
data_status_t get_status() const
return the data receival status
Definition: message.hpp:308
void clear()
clears the content buffer
Definition: message.hpp:553
ihash_multimap & get_cookies(void)
returns the cookie parameters
Definition: message.hpp:234
bool has_header(const std::string &key) const
returns true if at least one value for the header is defined
Definition: message.hpp:223
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