websocketpp  0.4.0
C++/Boost Asio based websocket client/server library
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Pages
response.hpp
1 /*
2  * Copyright (c) 2014, Peter Thorson. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  * * Redistributions of source code must retain the above copyright
7  * notice, this list of conditions and the following disclaimer.
8  * * Redistributions in binary form must reproduce the above copyright
9  * notice, this list of conditions and the following disclaimer in the
10  * documentation and/or other materials provided with the distribution.
11  * * Neither the name of the WebSocket++ Project nor the
12  * names of its contributors may be used to endorse or promote products
13  * derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  */
27 
28 #ifndef HTTP_PARSER_RESPONSE_IMPL_HPP
29 #define HTTP_PARSER_RESPONSE_IMPL_HPP
30 
31 #include <algorithm>
32 #include <sstream>
33 
34 #include <websocketpp/http/parser.hpp>
35 
36 namespace websocketpp {
37 namespace http {
38 namespace parser {
39 
40 inline size_t response::consume(const char *buf, size_t len) {
41  if (m_state == DONE) {return 0;}
42 
43  if (m_state == BODY) {
44  return this->process_body(buf,len);
45  }
46 
47  if (m_read + len > max_header_size) {
48  // exceeded max header size
49  throw exception("Maximum header size exceeded.",
50  status_code::request_header_fields_too_large);
51  }
52 
53  // copy new header bytes into buffer
54  m_buf->append(buf,len);
55 
56  // Search for delimiter in buf. If found read until then. If not read all
57  std::string::iterator begin = m_buf->begin();
58  std::string::iterator end = begin;
59 
60 
61  for (;;) {
62  // search for delimiter
63  end = std::search(
64  begin,
65  m_buf->end(),
68  );
69 
70  if (end == m_buf->end()) {
71  // we are out of bytes. Discard the processed bytes and copy the
72  // remaining unprecessed bytes to the beginning of the buffer
73  std::copy(begin,end,m_buf->begin());
74  m_buf->resize(static_cast<std::string::size_type>(end-begin));
75 
76  m_read +=len;
77 
78  return len;
79  }
80 
81  //the range [begin,end) now represents a line to be processed.
82 
83  if (end-begin == 0) {
84  // we got a blank line
85  if (m_state == RESPONSE_LINE) {
86  throw exception("Incomplete Request",status_code::bad_request);
87  }
88 
89  // TODO: grab content-length
90  std::string length = get_header("Content-Length");
91 
92  if (length == "") {
93  // no content length found, read indefinitely
94  m_read = 0;
95  } else {
96  std::istringstream ss(length);
97 
98  if ((ss >> m_read).fail()) {
99  throw exception("Unable to parse Content-Length header",
100  status_code::bad_request);
101  }
102  }
103 
104  m_state = BODY;
105 
106  // calc header bytes processed (starting bytes - bytes left)
107  size_t read = (
108  len - static_cast<std::string::size_type>(m_buf->end() - end)
109  + sizeof(header_delimiter) - 1
110  );
111 
112  // if there were bytes left process them as body bytes
113  if (read < len) {
114  read += this->process_body(buf+read,(len-read));
115  }
116 
117  // frees memory used temporarily during header parsing
118  m_buf.reset();
119 
120  return read;
121  } else {
122  if (m_state == RESPONSE_LINE) {
123  this->process(begin,end);
124  m_state = HEADERS;
125  } else {
126  this->process_header(begin,end);
127  }
128  }
129 
130  begin = end+(sizeof(header_delimiter) - 1);
131  }
132 }
133 
134 inline size_t response::consume(std::istream & s) {
135  char buf[istream_buffer];
136  size_t bytes_read;
137  size_t bytes_processed;
138  size_t total = 0;
139 
140  while (s.good()) {
141  s.getline(buf,istream_buffer);
142  bytes_read = static_cast<size_t>(s.gcount());
143 
144  if (s.fail() || s.eof()) {
145  bytes_processed = this->consume(buf,bytes_read);
146  total += bytes_processed;
147 
148  if (bytes_processed != bytes_read) {
149  // problem
150  break;
151  }
152  } else if (s.bad()) {
153  // problem
154  break;
155  } else {
156  // the delimiting newline was found. Replace the trailing null with
157  // the newline that was discarded, since our raw consume function
158  // expects the newline to be be there.
159  buf[bytes_read-1] = '\n';
160  bytes_processed = this->consume(buf,bytes_read);
161  total += bytes_processed;
162 
163  if (bytes_processed != bytes_read) {
164  // problem
165  break;
166  }
167  }
168  }
169 
170  return total;
171 }
172 
173 inline bool response::parse_complete(std::istream& s) {
174  // parse a complete header (ie \r\n\r\n MUST be in the input stream)
175  std::string line;
176 
177  // get status line
178  std::getline(s, line);
179 
180  if (line[line.size()-1] == '\r') {
181  line.erase(line.end()-1);
182 
183  std::stringstream ss(line);
184  std::string str_val;
185  int int_val;
186  char char_val[256];
187 
188  ss >> str_val;
189  set_version(str_val);
190 
191  ss >> int_val;
192  ss.getline(char_val,256);
193  set_status(status_code::value(int_val),std::string(char_val));
194  } else {
195  return false;
196  }
197 
198  return parse_headers(s);
199 }
200 
201 inline std::string response::raw() const {
202  // TODO: validation. Make sure all required fields have been set?
203 
204  std::stringstream ret;
205 
206  ret << get_version() << " " << m_status_code << " " << m_status_msg;
207  ret << "\r\n" << raw_headers() << "\r\n";
208 
209  ret << m_body;
210 
211  return ret.str();
212 }
213 
214 inline void response::set_status(status_code::value code) {
215  // TODO: validation?
216  m_status_code = code;
217  m_status_msg = get_string(code);
218 }
219 
220 inline void response::set_status(status_code::value code, const std::string&
221  msg)
222 {
223  // TODO: validation?
224  m_status_code = code;
225  m_status_msg = msg;
226 }
227 
228 inline void response::process(std::string::iterator begin,
229  std::string::iterator end)
230 {
231  std::string::iterator cursor_start = begin;
232  std::string::iterator cursor_end = std::find(begin,end,' ');
233 
234  if (cursor_end == end) {
235  throw exception("Invalid response line",status_code::bad_request);
236  }
237 
238  set_version(std::string(cursor_start,cursor_end));
239 
240  cursor_start = cursor_end+1;
241  cursor_end = std::find(cursor_start,end,' ');
242 
243  if (cursor_end == end) {
244  throw exception("Invalid request line",status_code::bad_request);
245  }
246 
247  int code;
248 
249  std::istringstream ss(std::string(cursor_start,cursor_end));
250 
251  if ((ss >> code).fail()) {
252  throw exception("Unable to parse response code",status_code::bad_request);
253  }
254 
255  set_status(status_code::value(code),std::string(cursor_end+1,end));
256 }
257 
258 inline size_t response::process_body(const char *buf, size_t len) {
259  // If no content length was set then we read forever and never set m_ready
260  if (m_read == 0) {
261  //m_body.append(buf,len);
262  //return len;
263  m_state = DONE;
264  return 0;
265  }
266 
267  // Otherwise m_read is the number of bytes left.
268  size_t to_read;
269 
270  if (len >= m_read) {
271  // if we have more bytes than we need read, read only the amount needed
272  // then set done state
273  to_read = m_read;
274  m_state = DONE;
275  } else {
276  // we need more bytes than are available, read them all
277  to_read = len;
278  }
279 
280  m_body.append(buf,to_read);
281  m_read -= to_read;
282  return to_read;
283 }
284 
285 } // namespace parser
286 } // namespace http
287 } // namespace websocketpp
288 
289 #endif // HTTP_PARSER_RESPONSE_IMPL_HPP
bool parse_complete(std::istream &s)
DEPRECATED parse a complete response from a pre-delimited istream.
Definition: response.hpp:173
static char const header_delimiter[]
Literal value of the HTTP header delimiter.
Definition: constants.hpp:54
void process_header(std::string::iterator begin, std::string::iterator end)
Process a header line.
Definition: parser.hpp:136
bool parse_headers(std::istream &s)
Parse headers from an istream.
Definition: parser.hpp:114
std::string const & get_header(std::string const &key) const
Get the value of an HTTP header.
Definition: parser.hpp:43
std::string const & get_version() const
Get the HTTP version string.
Definition: parser.hpp:391
size_t consume(const char *buf, size_t len)
Process bytes in the input buffer.
Definition: response.hpp:40
size_t const istream_buffer
Number of bytes to use for temporary istream read buffers.
Definition: constants.hpp:66
Namespace for the WebSocket++ project.
Definition: base64.hpp:41
size_t const max_header_size
Maximum size in bytes before rejecting an HTTP header as too big.
Definition: constants.hpp:63
void set_status(status_code::value code)
Set response status code and message.
Definition: response.hpp:214
std::string raw_headers() const
Generate and return the HTTP headers as a string.
Definition: parser.hpp:154
std::string get_string(value code)
Return a human readable interpretation of a WebSocket close code.
Definition: close.hpp:212
void set_version(std::string const &version)
Set HTTP parser Version.
Definition: parser.hpp:39
std::string raw() const
Returns the full raw response.
Definition: response.hpp:201