dodo  0.0.1
A C++ library to create containerized Linux services
httpmessage.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 httpmessage.cpp
21  * Implements the dodo::network::protocolo::http::HTTPMessage class.
22  */
23 
25 
26 #include <common/util.hpp>
27 
28 namespace dodo {
29 
30  namespace network::protocol::http {
31 
32  const char HTTPMessage::charCR = char(13);
33  const char HTTPMessage::charLF = char(10);
34  const char HTTPMessage::charSP = char(32);
35  const char HTTPMessage::charHT = char(9);
36 
37  void HTTPMessage::addHeader( const std::string &key, const std::string &value ) {
38  std::map<std::string,std::string>::const_iterator i = headers_.find( key );
39  if ( i == headers_.end() ) {
40  headers_[key] = value;
41  } else {
42  headers_[key] += "," + value;
43  }
44  }
45 
47  ParseResult parseResult;
48  if ( buffer.get() != charCR ) return parseResult;
49  parseResult.setSystemError( buffer.next() );
50  if ( ! parseResult.ok() ) return parseResult;
51  if ( buffer.get() != charLF ) return { peExpectCRLF, common::SystemError::ecOK };
52  parseResult.setSystemError( buffer.next() );
53  return parseResult;
54  }
55 
58  while ( ( buffer.get() == charSP || buffer.get() == charHT ) && error == common::SystemError::ecOK ) {
59  error = buffer.next();
60  }
61  return error;
62  }
63 
64  bool HTTPMessage::getHeaderValue( const std::string &key, unsigned long &value ) const {
65  std::map<std::string,std::string>::const_iterator i = headers_.find( key );
66  value = 0;
67  if ( i != headers_.end() ) {
68  size_t pos = 0;
69  value = stoul( i->second, &pos );
70  return pos==i->second.size();
71  } else return false;
72  }
73 
74  bool HTTPMessage::getHeaderValue( const std::string &key, std::string &value ) const {
75  std::map<std::string,std::string>::const_iterator i = headers_.find( key );
76  value = "";
77  if ( i != headers_.end() ) {
78  value = i->second;
79  return true;
80  } else return false;
81  }
82 
84  ParseResult parseResult;
85  parseResult.setSystemError( eatSpace( data ) );
86  std::string header_key = "";
87  std::string header_value = "";
88  do {
89  parseResult = parseToken( data, header_key );
90  if ( parseResult.ok() ) {
91  std::for_each( header_key.begin(), header_key.end(), [](char & c) {
92  c = (char)std::tolower(c);
93  });
94  parseResult.setSystemError( eatSpace( data ) );
95  if ( !parseResult.ok() ) return parseResult;
96  if ( data.get() != ':' ) return { peExpectingHeaderColon, common::SystemError::ecOK };
97  parseResult.setSystemError( eatSpace( data ) );
98  if ( ! parseResult.ok() ) return parseResult;
99  parseResult.setSystemError( data.next() );
100  if ( ! parseResult.ok() ) return parseResult;
101  parseResult = parseFieldValue( data, header_value );
102  if ( parseResult.ok() || parseResult.eof() ) {
103  addHeader( header_key, header_value );
104  parseResult.setSystemError( eatSpace( data ) );
105  if ( ! parseResult.ok() ) return parseResult;
106  } else return parseResult;
107  } else return parseResult;
108  } while ( data.get() != charCR );
109  parseResult.setSystemError( data.next() );
110  if ( ! parseResult.ok() ) return parseResult;
111  if ( data.get() != charLF ) return { peInvalidHeaderListEnd, common::SystemError::ecOK };
112  return parseResult;
113  }
114 
116  ParseResult parseResult;
117  if ( buffer.get() != charCR ) return { peExpectCRLF, common::SystemError::ecOK };
118  parseResult.setSystemError( buffer.next() );
119  if ( ! parseResult.ok() ) return parseResult;
120  if ( buffer.get() != charLF ) return { peExpectCRLF, common::SystemError::ecOK };
121  parseResult.setSystemError( buffer.next() );
122  return parseResult;
123  }
124 
126  ParseResult parseResult;
127  std::stringstream ss;
128  while ( true ) {
129  if ( std::isxdigit( buffer.get() ) ) {
130  ss << buffer.get();
131  parseResult.setSystemError( buffer.next() );
132  if ( !parseResult.ok() ) return parseResult;
133  } else break;
134  }
135  if ( ss.str().size() == 0 ) return { peInvalidChunkHex, common::SystemError::ecOK };
136  ss >> std::hex >> value;
137  return parseCRLF( buffer );
138  }
139 
140 
142  ParseResult parseResult;
143  const int psStart = 0;
144  const int psReadValue = 1;
145  const int psCR = 2;
146  const int psSP = 3;
147  const int psDone = 101;
148  value = "";
149  int state = psStart;
150  while ( parseResult.ok() && state != psDone ) {
151  switch( state ) {
152  case psStart:
153  if ( !isSP( data.get() ) ) {
154  state = psReadValue;
155  } else parseResult.setSystemError( data.next() );
156  break;
157  case psReadValue:
158  if ( data.get() == charCR ) {
159  state = psCR;
160  parseResult.setSystemError( data.next() );
161  } else if ( isSP( data.get() ) ) {
162  state = psSP;
163  parseResult.setSystemError( data.next() );
164  } else {
165  value += data.get();
166  parseResult.setSystemError( data.next() );
167  }
168  break;
169  case psCR:
170  if ( data.get() == charLF ) {
171  parseResult.setSystemError( data.next() );
172  if ( parseResult.ok() && isSP( data.get() ) ) {
173  state = psSP;
174  parseResult.setSystemError( data.next() );
175  } else {
176  state = psDone;
177  }
178  } else {
180  }
181  break;
182  case psSP:
183  if ( isSP( data.get() ) ) {
184  parseResult.setSystemError( data.next() );
185  } else {
186  value += charSP;
187  state = psReadValue;
188  }
189  break;
190  }
191  }
192  if ( state != psDone ) return { peInvalidHeaderFieldValue, common::SystemError::ecOK };
193  return parseResult;
194  }
195 
197  ParseResult parseResult;
198  token = "";
199  std::stringstream stoken;
200  while ( !isSeparator( buffer.get() ) && !isCTL( buffer.get() ) ) {
201  stoken << buffer.get();
202  parseResult.setSystemError( buffer.next() );
203  if ( ! parseResult.ok() ) return parseResult;
204  }
205  if ( isSeparator( buffer.get() ) || isCTL( buffer.get() ) ) {
206  token = stoken.str();
207  return { peOk, common::SystemError::ecOK };
208  } else return { peUnFinishedToken, common::SystemError::ecOK };
209  }
210 
212  ParseResult parseResult;
213  std::stringstream ss;
214  value = 0;
215  while ( parseResult.ok() && isdigit( data.get() ) ) {
216  ss << data.get();
217  parseResult.setSystemError( data.next() );
218  }
219  if ( !ss.str().length() ) return { peExpectingUnsignedInt, common::SystemError::ecOK };
220  ss >> value;
221  return parseResult;
222  }
223 
225  ParseResult parseResult;
226  size_t chunk_size = 0;
227  while ( parseResult.ok() ) {
228  parseResult = parseChunkHex( data, chunk_size );
229  if ( parseResult.ok() ) {
230  if ( chunk_size == 0 ) break;
231  for ( size_t i = 0; i < chunk_size; i++ ) {
232  body_.append( data.get() );
233  parseResult.setSystemError( data.next() );
234  if ( ! parseResult.ok() ) return parseResult;
235  }
236  parseResult = parseCRLF( data );
237  if ( ! parseResult.ok() ) return parseResult;
238  } else return parseResult;
239  }
240  if ( chunk_size != 0 ) return { peInvalidLastChunk, common::SystemError::ecOK };
241  if ( parseResult.eof() ) return { peOk, common::SystemError::ecOK }; else return parseResult;
242  }
243 
244  void HTTPMessage::replaceHeader( const std::string &key, const std::string &value ) {
245  headers_[key] = value;
246  }
247 
248  void HTTPMessage::setBody( const std::string& body ) {
249  body_ = body;
250  replaceHeader( "content-length", common::Puts() << common::Puts::fixed() << body_.getSize() );
251  }
252 
253  }
254 
255 }
dodo::network::protocol::http::HTTPFragment::peExpectingUnsignedInt
@ peExpectingUnsignedInt
An unsigned int was expected.
Definition: httpfragment.hpp:68
dodo::network::protocol::http::HTTPMessage::addHeader
void addHeader(const std::string &key, const std::string &value)
Add a header to the HTTPMessage.
Definition: httpmessage.cpp:37
dodo::network::protocol::http::HTTPFragment::peExpectingHeaderColon
@ peExpectingHeaderColon
A header field name was read, but no colon found after it.
Definition: httpfragment.hpp:57
dodo::network::protocol::http::HTTPMessage::parseFieldValue
ParseResult parseFieldValue(VirtualReadBuffer &data, std::string &value)
Parse a header field value.
Definition: httpmessage.cpp:141
dodo::network::protocol::http::HTTPMessage::isCTL
static bool isCTL(char c)
Check is the char is a CTL character.
Definition: httpmessage.hpp:164
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
httpmessage.hpp
dodo::network::protocol::http::HTTPMessage::charCR
static const char charCR
CR character.
Definition: httpmessage.hpp:47
dodo::network::protocol::http::HTTPMessage::charLF
static const char charLF
LF character.
Definition: httpmessage.hpp:52
dodo::network::protocol::http::HTTPFragment::peUnFinishedToken
@ peUnFinishedToken
A token is being parsed but is ending erroneously.
Definition: httpfragment.hpp:56
dodo::network::protocol::http::HTTPMessage::parseChunkHex
ParseResult parseChunkHex(VirtualReadBuffer &data, unsigned long &value)
Parse a hexadecimal chunk size (a hex value followed by CR LF).
Definition: httpmessage.cpp:125
dodo::network::protocol::http::HTTPFragment::peInvalidHeaderListEnd
@ peInvalidHeaderListEnd
A header list is not ending properly.
Definition: httpfragment.hpp:59
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::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::common::Puts::fixed
Put the stream in floating point fixed-format mode.
Definition: puts.hpp:59
dodo::network::protocol::http::HTTPMessage::charHT
static const char charHT
Horizontal tab character.
Definition: httpmessage.hpp:62
dodo::network::protocol::http::HTTPMessage::charSP
static const char charSP
Space character.
Definition: httpmessage.hpp:57
dodo::network::protocol::http::HTTPMessage::parseUInt
static ParseResult parseUInt(VirtualReadBuffer &data, unsigned int &value)
Parse an unsigned integer.
Definition: httpmessage.cpp:211
dodo::network::protocol::http::HTTPFragment::peInvalidChunkHex
@ peInvalidChunkHex
The hex chunk size is invalid.
Definition: httpfragment.hpp:66
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::HTTPMessage::isSP
static bool isSP(char c)
Return true if the char is whitespace character (charSP or charHT)
Definition: httpmessage.hpp:255
dodo::network::protocol::http::HTTPMessage::replaceHeader
void replaceHeader(const std::string &key, const std::string &value)
Replace the value of a header.
Definition: httpmessage.cpp:244
dodo::network::protocol::http::HTTPMessage::isSeparator
static bool isSeparator(char c)
Return true if the char is a separator.
Definition: httpmessage.hpp:242
dodo::network::protocol::http::HTTPFragment::ParseResult
Used to convey parsing succces.
Definition: httpfragment.hpp:75
dodo::network::protocol::http::HTTPFragment::ParseResult::eof
bool eof() const
Test if systemError == common::SystemError::ecEAGAIN
Definition: httpfragment.hpp:99
dodo::network::protocol::http::HTTPFragment::peInvalidLastChunk
@ peInvalidLastChunk
The last chunk does not have size 0.
Definition: httpfragment.hpp:67
dodo::common::SystemError::ecOK
@ ecOK
0 Not an error, success
Definition: systemerror.hpp:60
dodo::network::protocol::http::HTTPMessage::setBody
void setBody(const std::string &body)
Set the body.
Definition: httpmessage.cpp:248
dodo::network::protocol::http::HTTPMessage::parseToken
ParseResult parseToken(VirtualReadBuffer &buffer, std::string &token)
Parse a token (such as a header field name).
Definition: httpmessage.cpp:196
dodo::network::protocol::http::HTTPFragment::peInvalidHeaderFieldValue
@ peInvalidHeaderFieldValue
A header field value was being read, but it is invalid.
Definition: httpfragment.hpp:58
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::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
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::common::Bytes::getSize
size_t getSize() const
Return the array size.
Definition: bytes.hpp:192
dodo::network::protocol::http::HTTPMessage::eatCRLF
ParseResult eatCRLF(VirtualReadBuffer &data)
Consume a CR LF sequence only when it is there.
Definition: httpmessage.cpp:46
dodo::network::protocol::http::HTTPMessage::parseCRLF
ParseResult parseCRLF(VirtualReadBuffer &data)
Consume a CR LF sequence.
Definition: httpmessage.cpp:115
util.hpp