dodo  0.0.1
A C++ library to create containerized Linux services
httprequest.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the dodo library (https://github.com/jmspit/dodo).
3  * Copyright (c) 2019 Jan-Marten Spit.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 
19 /**
20  * @file httprequest.cpp
21  * Implements the dodo::network::protocolo::http::HTTPRequest class.
22  */
23 
25 
26 #include <common/util.hpp>
27 
28 namespace dodo {
29 
30  namespace network::protocol::http {
31 
32  std::string HTTPRequest::methodAsString( Method method ) {
33  switch ( method ) {
34  case meGET : return "GET";
35  case meHEAD : return "HEAD";
36  case mePOST : return "POST";
37  case mePUT : return "PUT";
38  case mePATCH : return "PATCH";
39  case meDELETE : return "DELETE";
40  case meCONNECT : return "CONNECT";
41  case meOPTIONS : return "OPTIONS";
42  case meTRACE : return "TRACE";
43  default : return "INVALID_METHOD";
44  }
45  }
46 
48  if ( s == "GET" ) return meGET;
49  else if ( s == "HEAD" ) return meHEAD;
50  else if ( s == "POST" ) return mePOST;
51  else if ( s == "PUT" ) return mePUT;
52  else if ( s == "PATCH" ) return mePATCH;
53  else if ( s == "DELETE" ) return meDELETE;
54  else if ( s == "CONNECT" ) return meCONNECT;
55  else if ( s == "OPTIONS" ) return meOPTIONS;
56  else if ( s == "TRACE" ) return meTRACE;
57  else return meINVALID;
58  }
59 
61  switch ( method ) {
62  case meGET:
63  case meHEAD:
64  case meDELETE:
65  case meCONNECT:
66  case meTRACE:
67  return false;
68  case mePOST:
69  case mePUT:
70  case mePATCH:
71  case meOPTIONS:
72  return true;
73  default : return false;
74  }
75  }
76 
78  std::stringstream ss;
80  ss << request_uri_ << charSP;
81  ss << http_version_.asString();
83  return ss.str();
84  }
85 
86  std::string HTTPRequest::asString() const {
87  std::stringstream ss;
88  ss << request_line_.asString();
89  for ( auto header : headers_ ) {
90  ss << header.first << ": " << header.second << HTTPMessage::charCR << HTTPMessage::charLF;
91  }
94  ss << body_.asString();
95  }
96  return ss.str();
97  }
98 
100  std::stringstream ss;
101  ss << request_line_.asString();
102  for ( auto header : headers_ ) {
103  ss << header.first << ": " << header.second << HTTPMessage::charCR << HTTPMessage::charLF;
104  }
106  common::SystemError rc = socket->send( ss.str().c_str(), ss.str().length() );
107  if ( !rc.ok() ) return rc;
109  rc = socket->send( body_.getArray(), body_.getSize(), false );
110  }
111  return rc;
112  }
113 
115  ParseResult parseResult = request_line_.parse( data );
116  if ( parseResult.ok() ) {
117  if ( data.get() == charCR ) {
118  parseResult.setSystemError( data.next() );
119  if ( parseResult.ok() && data.get() == charLF ) {
120  parseResult.setSystemError( data.next() );
121  if ( parseResult.ok() ) return parseBody( data ); else return parseResult;
122  } else return { peOk, common::SystemError::ecOK };
123  } else {
124  // these are headers
125  parseResult = parseHeaders( data );
126  if ( !parseResult.ok() ) return parseResult;
127  if ( data.get() == charLF ) {
128  parseResult.setSystemError( data.next() );
129  if ( parseResult.ok() ) {
130  return parseBody( data );
131  } else if ( parseResult.eof() ) return { peOk, common::SystemError::ecOK };
132  else return parseResult;
133  } else return { peOk, common::SystemError::ecOK };
134  }
135  }
136  return parseResult;
137  }
138 
140  ParseResult parseResult;
141  size_t content_length;
142  if ( getHeaderValue( "content-length", content_length ) ) {
143  for ( size_t i = 0; i < content_length; i++ ) {
144  body_.append( data.get() );
145  if ( i < content_length -1 ) {
146  parseResult.setSystemError( data.next() );
147  if ( ! parseResult.ok() ) return parseResult;
148  }
149  }
150  } else {
151  std::string transfer_encoding;
152  if ( getHeaderValue( "transfer-encoding", transfer_encoding ) ) {
153  if ( transfer_encoding == "chunked" ) {
154  parseResult = parseChunkedBody( data );
155  if ( ! parseResult.ok() ) return parseResult;
157  } else return { peUnexpectedBody , common::SystemError::ecOK };
158  }
159  return parseResult;
160  }
161 
163  enum ParseState {
164  psMethodStart,
165  psMethodEnd,
166  psRequestURIStart,
167  psRequestURIEnd,
168  psHTTPVersionCR,
169  psHTTPVersionLF,
170  psHTTPVersionEnd,
171  psError,
172  psDone,
173  };
174  ParseState state = psMethodStart;
175  std::string method_string = "";
176  std::string version_string = "";
177  request_uri_ = "";
178  ParseResult parseResult;
179  while ( state != psDone && state != psError && parseResult.ok() ) {
180  switch ( state ) {
181 
182  case psMethodStart:
183  if ( std::isspace( data.get() ) ) {
184  state = psMethodEnd;
185  } else {
186  method_string += data.get();
187  parseResult.setSystemError( data.next() );
188  if ( ! parseResult.ok() ) return parseResult;
189  };
190  break;
191 
192  case psMethodEnd:
193  parseResult.setSystemError( eatSpace( data ) );
194  if ( ! parseResult.ok() ) return parseResult;
195  state = psRequestURIStart;
196  break;
197 
198  case psRequestURIStart:
199  if ( std::isspace( data.get() ) ) {
200  state = psRequestURIEnd;
201  } else {
202  request_uri_ += data.get();
203  parseResult.setSystemError( data.next() );
204  if ( ! parseResult.ok() ) return parseResult;
205  }
206  break;
207 
208  case psRequestURIEnd:
209  parseResult = http_version_.parse( data );
210  if ( parseResult.ok() ) {
211  state = psHTTPVersionCR;
212  parseResult.setSystemError( eatSpace( data ) );
213  } else return parseResult;
214  break;
215 
216  case psHTTPVersionCR:
217  if ( data.get() == HTTPMessage::charCR ) {
218  state = psHTTPVersionLF;
219  parseResult.setSystemError( data.next() );
220  if ( !parseResult.ok() ) return parseResult;
221  } else {
223  }
224  break;
225 
226  case psHTTPVersionLF:
227  if ( data.get() == HTTPMessage::charLF ) {
228  state = psDone;
229  parseResult.setSystemError( data.next() );
230  if ( !parseResult.ok() ) return parseResult;
231  } else {
233  }
234  break;
235 
236  default: throw_SystemException( common::Puts() <<
237  "HTTPRequest::HTTPRequestLine::parse unhandled state " <<
238  state, 0 );
239 
240  }
241  }
242  if ( state == psDone ) {
243  method_ = methodFromString( method_string );
244  if ( method_ == meINVALID ) return { peInvalidMethod, common::SystemError::ecOK };
246  return parseResult;
247  }
248 
250  return socket->send( this->asString().c_str(), this->asString().length(), false );
251  }
252 
253  }
254 
255 }
dodo::network::protocol::http::HTTPRequest::parse
virtual ParseResult parse(VirtualReadBuffer &data)
Read the HTTPRequest from the socket.
Definition: httprequest.cpp:114
dodo::network::protocol::http::HTTPMessage::parseChunkedBody
ParseResult parseChunkedBody(VirtualReadBuffer &data)
Parse a chunked body (transfer-encoding: chunked) and resturn it as a single string.
Definition: httpmessage.cpp:224
dodo::network::protocol::http::HTTPRequest::meINVALID
@ meINVALID
The parser was presented an invalid method.
Definition: httprequest.hpp:58
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::method_
HTTPRequest::Method method_
The request method.
Definition: httprequest.hpp:125
dodo::network::protocol::http::HTTPMessage::charCR
static const char charCR
CR character.
Definition: httpmessage.hpp:47
dodo::common::Bytes::getArray
Octet * getArray() const
Return the array.
Definition: bytes.hpp:186
dodo::network::protocol::http::HTTPMessage::charLF
static const char charLF
LF character.
Definition: httpmessage.hpp:52
dodo::network::protocol::http::HTTPFragment::ParseResult::ok
bool ok() const
Test if parseError == peOk && systemError == common::SystemError::ecOK.
Definition: httpfragment.hpp:93
dodo::network::VirtualReadBuffer
Interface to read individual bytes whilst the implementation can read from an actual source (such as ...
Definition: socketreadbuffer.hpp:44
dodo::network::protocol::http::HTTPFragment::ParseResult::setSystemError
void setSystemError(common::SystemError se)
Set the systemError.
Definition: httpfragment.hpp:106
dodo::network::protocol::http::HTTPMessage::getHeaderValue
bool getHeaderValue(const std::string &key, std::string &value) const
Get a header value as a string into value.
Definition: httpmessage.cpp:74
dodo::network::protocol::http::HTTPMessage::headers_
std::map< std::string, std::string > headers_
The message headers.
Definition: httpmessage.hpp:262
dodo::network::protocol::http::HTTPRequest::methodFromString
static Method methodFromString(const std::string &s)
Translate an uppercase string ('GET', "POST', ..) into a Method enum.
Definition: httprequest.cpp:47
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::asString
virtual std::string asString() const
Convert the HTTPRequestLine to a HTTP string.
Definition: httprequest.cpp:77
dodo::network::protocol::http::HTTPVersion::asString
virtual std::string asString() const
Convert to string as 'HTTP/major_.minor_'.
Definition: httpversion.hpp:56
dodo::network::VirtualReadBuffer::next
virtual common::SystemError next()=0
Move to the next char from the VirtualReadBuffer.
dodo::network::VirtualReadBuffer::get
virtual char get() const =0
Get the current char from VirtualReadBuffer.
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::getMethod
HTTPRequest::Method getMethod() const
Get the HTTPRequest::Method.
Definition: httprequest.hpp:95
dodo::network::protocol::http::HTTPMessage::charSP
static const char charSP
Space character.
Definition: httpmessage.hpp:57
dodo::common::SystemError::ok
bool ok() const
Returns true when this->errorcode_ == ecOK.
Definition: systemerror.hpp:254
dodo::network::protocol::http::HTTPRequest::send
virtual common::SystemError send(BaseSocket *socket)
Send this HTTPMessage to the socket.
Definition: httprequest.cpp:99
dodo::network::protocol::http::HTTPRequest::Method
Method
The HTTP request method.
Definition: httprequest.hpp:48
dodo::common::Bytes::append
void append(const Bytes &src)
Append another Bytes.
Definition: bytes.cpp:65
dodo
A C++ platform interface to lean Linux services tailored for containerized deployment.
Definition: application.hpp:29
dodo::network::protocol::http::HTTPFragment::peInvalidRequestLine
@ peInvalidRequestLine
The request line is invalid.
Definition: httpfragment.hpp:62
dodo::network::protocol::http::HTTPFragment::peInvalidTransferEncoding
@ peInvalidTransferEncoding
The specified transfer-encoding header is invalid.
Definition: httpfragment.hpp:65
dodo::network::protocol::http::HTTPFragment::peUnexpectedBody
@ peUnexpectedBody
A message body is present but should not be there.
Definition: httpfragment.hpp:64
dodo::network::protocol::http::HTTPFragment::ParseResult
Used to convey parsing succces.
Definition: httpfragment.hpp:75
throw_SystemException
#define throw_SystemException(what, errno)
Throws an Exception with errno, passes FILE and LINE to constructor.
Definition: exception.hpp:188
dodo::network::protocol::http::HTTPRequest::methodAllowsBody
static bool methodAllowsBody(Method method)
Return true if the Method allows a body in the message.
Definition: httprequest.cpp:60
dodo::network::protocol::http::HTTPFragment::ParseResult::eof
bool eof() const
Test if systemError == common::SystemError::ecEAGAIN
Definition: httpfragment.hpp:99
dodo::common::SystemError::ecOK
@ ecOK
0 Not an error, success
Definition: systemerror.hpp:60
dodo::common::Bytes::asString
std::string asString() const
Convert to a std::string.
Definition: bytes.cpp:98
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::request_uri_
std::string request_uri_
The request 'uri'.
Definition: httprequest.hpp:137
dodo::network::protocol::http::HTTPRequest::methodAsString
static std::string methodAsString(Method method)
Translate a Method enum to a human readable string.
Definition: httprequest.cpp:32
dodo::network::protocol::http::HTTPMessage::eatSpace
static common::SystemError eatSpace(VirtualReadBuffer &data)
Call buffer.next() as long as buffer.get() is whitespace ( charSP or charHT).
Definition: httpmessage.cpp:56
dodo::network::protocol::http::HTTPFragment::peInvalidMethod
@ peInvalidMethod
An invalid method was specified in the request line.
Definition: httpfragment.hpp:60
dodo::network::protocol::http::HTTPFragment::peOk
@ peOk
Ok.
Definition: httpfragment.hpp:52
dodo::network::protocol::http::HTTPMessage::parseHeaders
ParseResult parseHeaders(VirtualReadBuffer &data)
Parse a header section and update headers_.
Definition: httpmessage.cpp:83
dodo::common::Puts
Helper class to write strings in stream format, eg.
Definition: puts.hpp:46
dodo::common::SystemError
Linux system error primitive to provide a consistent interface to Linux error codes.
Definition: systemerror.hpp:53
httprequest.hpp
dodo::network::protocol::http::HTTPFragment::peExpectCRLF
@ peExpectCRLF
A CR was not followed by an LF.
Definition: httpfragment.hpp:55
dodo::network::protocol::http::HTTPMessage::body_
common::Bytes body_
The message body (if any).
Definition: httpmessage.hpp:267
dodo::network::protocol::http::HTTPRequest::write
common::SystemError write(BaseSocket *socket) const
Write the HTTPRequest to the socket.
Definition: httprequest.cpp:249
dodo::network::protocol::http::HTTPRequest::request_line_
HTTPRequestLine request_line_
The HTTPRequestLine of the HTTPRequest.
Definition: httprequest.hpp:208
dodo::common::Bytes::getSize
size_t getSize() const
Return the array size.
Definition: bytes.hpp:192
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::http_version_
HTTPVersion http_version_
The HTTP version.
Definition: httprequest.hpp:142
dodo::network::protocol::http::HTTPRequest::asString
virtual std::string asString() const
Return the HTTPRequest as a string.
Definition: httprequest.cpp:86
dodo::network::protocol::http::HTTPRequest::HTTPRequestLine::parse
virtual ParseResult parse(VirtualReadBuffer &data)
Parses a HTTPMessage.
Definition: httprequest.cpp:162
util.hpp
dodo::network::protocol::http::HTTPRequest::parseBody
virtual ParseResult parseBody(VirtualReadBuffer &data)
Parse a body and resturn it as a single string.
Definition: httprequest.cpp:139
dodo::network::BaseSocket
Interface to and common implementation of concrete sockets (Socket, TLSSocket).
Definition: basesocket.hpp:36
dodo::network::BaseSocket::send
virtual common::SystemError send(const void *buf, ssize_t len, bool more=false)=0
Send bytes on the socket.