dodo  0.0.1
A C++ library to create containerized Linux services
httpresponse.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 httpresponse.cpp
21  * Implements the dodo::network::protocolo::http::HTTPResponse class.
22  */
23 
25 
26 #include <common/util.hpp>
27 
28 namespace dodo {
29 
30  namespace network::protocol::http {
31 
33  ParseResult parseResult = response_line_.parse( data );
34  if ( parseResult.ok() ) {
35  if ( data.get() == charCR ) {
36  parseResult.setSystemError( data.next() );
37  if ( parseResult.ok() && data.get() == charLF ) {
38  parseResult.setSystemError( data.next() );
39  if ( parseResult.ok() ) return parseBody( data ); else return parseResult;
40  } else return { peOk, common::SystemError::ecOK };
41  } else {
42  // these are headers
43  parseResult = parseHeaders( data );
44  if ( !parseResult.ok() ) return parseResult;
45  if ( data.get() == charLF ) {
46  if ( hasBody() ) {
47  parseResult.setSystemError( data.next() );
48  if ( parseResult.ok() ) {
49  return parseBody( data );
50  } else if ( parseResult.eof() ) return { peOk, common::SystemError::ecOK };
51  } return { peOk, common::SystemError::ecOK };
52  } else return { peOk, common::SystemError::ecOK };
53  }
54  }
55  return parseResult;
56  }
57 
58  bool HTTPResponse::hasBody() const {
59  bool result = false;
60  unsigned long content_length = 0;
61  if ( getHeaderValue( "content-length", content_length ) ) {
62  if ( content_length > 0 ) return true;
63  }
64  std::string transfer_encoding;
65  std::string connection;
66  if ( getHeaderValue( "transfer-encoding", transfer_encoding ) ) if ( transfer_encoding == "chunked" ) return true;
67  if ( getHeaderValue( "connection", connection ) ) if ( connection == "close" ) return true;
68  return result;
69  }
70 
71  std::string HTTPResponse::asString() const {
72  std::stringstream ss;
73  ss << response_line_.asString();
74  for ( auto header : headers_ ) {
75  ss << header.first << ": " << header.second << HTTPMessage::charCR << HTTPMessage::charLF;
76  }
78  if ( body_.getSize() > 0 ) {
79  ss << body_.asString();
80  }
81  return ss.str();
82  }
83 
85  std::stringstream ss;
86  ss << response_line_.asString();
87  for ( auto header : headers_ ) {
88  ss << header.first << ": " << header.second << HTTPMessage::charCR << HTTPMessage::charLF;
89  }
91  common::SystemError rc = socket->send( ss.str().c_str(), ss.str().length() );
92  if ( !rc.ok() ) return rc;
93  if ( body_.getSize() > 0 ) {
94  rc = socket->send( body_.getArray(), body_.getSize(), false );
95  }
96  return rc;
97  }
98 
100  std::stringstream ss;
101  ss << version_.asString();
102  ss << charSP;
103  ss << std::fixed << std::dec << http_code_;
104  ss << charSP;
105  ss << HTTPCodeAsString( http_code_ );
106  ss << charCR;
107  ss << charLF;
108  return ss.str();
109  }
110 
112  ParseResult parseResult;
113  parseResult = version_.parse( data );
114  if ( parseResult.ok()) {
115  parseResult.setSystemError( eatSpace( data ) );
116  if ( !parseResult.ok() ) return parseResult;
117  unsigned int i;
118  parseResult = parseUInt( data, i );
119  if ( parseResult.ok() ) {
120  http_code_ = static_cast<HTTPCode>(i);
121  // ignore the error string (we already have the HTTP code) but we do expect CRLF.
122  while ( parseResult.ok() && data.get() != charCR ) {
123  parseResult.setSystemError( data.next() );
124  if ( ! parseResult.ok() ) return parseResult;
125  }
126  if ( data.get() != charCR )
128  else {
129  parseResult.setSystemError( data.next() );
130  if ( ! parseResult.ok() ) return parseResult;
131  }
132  if ( data.get() != charLF )
134  else {
135  parseResult.setSystemError( data.next() );
136  if ( parseResult.ok() ) return parseResult;
137  }
138  }
139  }
140  return parseResult;
141  }
142 
144  ParseResult parseResult;
145  size_t content_length;
146  if ( getHeaderValue( "content-length", content_length ) ) {
147  for ( size_t i = 0; i < content_length; i++ ) {
148  body_.append( data.get() );
149  if ( i < content_length -1 ) {
150  parseResult.setSystemError( data.next() );
151  if ( ! parseResult.ok() ) return parseResult;
152  }
153  }
154  } else {
155  std::string transfer_encoding;
156  std::string connection_close;
157  if ( getHeaderValue( "transfer-encoding", transfer_encoding ) ) {
158  if ( transfer_encoding == "chunked" ) {
159  parseResult = parseChunkedBody( data );
160  if ( ! parseResult.ok() ) return parseResult;
162  } else if ( getHeaderValue( "connection", connection_close ) ) {
163  if ( connection_close == "close" ) {
164  // read anything we can get and ignore all errors
165  while ( parseResult.ok() ) {
166  body_.append( data.get() );
167  parseResult.setSystemError( data.next() );
168  }
169  return { peOk, common::SystemError::ecOK };
170  } else return { peUnexpectedBody, common::SystemError::ecOK };
171  }
172  }
173  return parseResult;
174  }
175 
177  switch ( code ) {
178 
179  case hcContinue : return "CONTINUE";
180  case hcSwitchingProtocols : return "SWITCHING PROTOCOLS";
181  case hcProcessing : return "PROCESSING";
182  case hcEarlyHints : return "EARLY HINTS";
183 
184  case hcOK : return "OK";
185  case hcCreated : return "CREATED";
186  case hcAccepted : return "ACCEPTED";
187  case hcNonAuthoritiveInformation : return "NON-AUTHORITATIVE INFORMATION";
188  case hcNoContent : return "NO CONTENT";
189  case hcResetContent : return "RESET CONTENT";
190  case hcPartialContent : return "PARTIAL CONTENT";
191  case hcMultiStatus : return "MULTI-STATUS";
192  case hcAlreadyReported : return "ALREADY REPORTED";
193  case hcIMUsed : return "IM USED";
194 
195  case hcMultipleChoices : return "MULTIPLE CHOICES";
196  case hcMovedPermanently : return "MOVED PERMANENTLY";
197  case hcFound : return "FOUND";
198  case hcSeeOther : return "SEE OTHER";
199  case hcNotModified : return "NOT MODIFIED";
200  case hcUseProxy : return "USE PROXY";
201  case hcSwitchProxy : return "SWITCH PROXY";
202  case hcTemporaryRedirect : return "TEMPORARY REDIRECT";
203  case hcPermanentRedirect : return "PERMANENT REDIRECT";
204 
205  case hcBadRequest : return "BAD REQUEST";
206  case hcUnAuthorized : return "UNAUTHORIZED";
207  case hcPaymentRequired : return "PAYMENT REQUIRED";
208  case hcForbidden : return "FORBIDDEN";
209  case hcNotFound : return "NOT FOUND";
210  case hcMethodNotAllowed : return "METHOD NOT ALLOWED";
211  case hcNotAcceptable : return "NOT ACCEPTABLE";
212  case hcProxyAuthenticationRequired : return "PROXY AUTHENTICATION REQUIRED";
213  case hcRequestTimeout : return "REQUEST TIMEOUT";
214  case hcConflict : return "CONFLICT";
215  case hcGone : return "GONE";
216  case hcLengthRequired : return "LENGTH REQUIRED";
217  case hcPreconditionFailed : return "PRECONDITION FAILED";
218  case hcPayloadTooLarge : return "PAYLOAD TOO LARGE";
219  case hcURITooLong : return "URI TOO LONG";
220  case hcUnsupportedMediaType : return "UNSUPPORTED MEDIA TYPE";
221  case hcRangeNotSatisfiable : return "RANGE NOT SATISFIABLE";
222  case hcExpectationFailed : return "EXPECTATION FAILED";
223  case hcIAmATeapot : return "I'M A TEAPOT";
224  case hcMisdirectRequest : return "MISDIRECTED REQUEST";
225  case hcUnporessableEntity : return "UNPROCESSABLE ENTITY";
226  case hcLocked : return "LOCKED";
227  case hcFailedDependency : return "FAILED DEPENDENCY";
228  case hcUpgradeRequired : return "UPGRADE REQUIRED";
229  case hcPreconditionRequired : return "PRECONDITION REQUIRED";
230  case hcTooManyRequests : return "TOO MANY REQUESTS";
231  case hcRequestHeaderFieldsTooLarge : return "REQUEST HEADER FIELDS TOO LARGE";
232  case hcUnavailableForLegalReasons : return "UNAVAILABLE FOR LEGAL REASONS";
233 
234  case hcInternalServerError : return "INTERNAL SERVER ERROR";
235  case hcNotImplemented : return "NOT IMPLEMENTED";
236  case hcBadGateway : return "BAD GATEWAY";
237  case hcServiceUnavailable : return "SERVICE UNAVAILABLE";
238  case hcGatewayTimeout : return "GATEWAY TIMEOUT";
239  case hcHTTPVersionNotSupported : return "HTTP VERSION NOT SUPPORTED";
240  case hcVariantAlsoNegotiates : return "VARIANT ALSO NEGOTIATES";
241  case hcInsufficientStorage : return "INSUFFICIENT STORAGE";
242  case hcLoopDetected : return "LOOP DETECTED";
243  case hcNotExtended : return "NOT EXTENDED";
244  case hcNetworkAuthenticationRequired : return "NETWORK AUTHENTICATION REQUIRED";
245 
246  case hcThisIsFine : return "THIS IS FINE";
247  case hcPageExpired : return "PAGE EXPIRED";
248  case hcMethodFailure : return "METHOD FAILURE";
249  case hcBlockedByWindowsParentalControls : return "BLOCKED BY WINDOWS PARENTAL CONTROLS";
250  case hcInvalidToken : return "INVALID TOKEN";
251  //case hcTokenRequired : return "Token Required";
252  case hcBandwidthLimitExceeded : return "BANDWIDTH LIMIT EXCEEDED";
253  case hcInvalidSSLCertificate : return "INVALID SSL CERTIFICATE";
254  //case hcSiteIsFrozen : return "Site is frozen";
255  case hcNetworkReadTimeoutError : return "NETWORK READ TIMEOUT ERROR";
256 
257  case hcLoginTimeout : return "LOGIN TIME-OUT";
258  case hcRetryWith : return "RETRY WITH";
259 
260  case hcNoRepsonse : return "NO RESPONSE";
261  case hcRequestHeaderTooLarge : return "REQUEST HEADER TOO LARGE";
262  case hcSSLCertificateError : return "SSL CERTIFICATE ERROR";
263  case hcSSLCertificateRequired : return "SSL CERTIFICATE REQUIRED";
264  case hcHHTPRequestSentToHTTPSPort : return "HTTP REQUEST SENT TO HTTPS PORT";
265  case hcClientClosedrequest : return "CLIENT CLOSED REQUEST";
266 
267  case hcUnknownError : return "UNKNOWN ERROR";
268  case hcWebServerIsDown : return "WEB SERVER IS DOWN";
269  case hcConnectionTimedOut : return "CONNECTION TIMED OUT";
270  case hcOriginIsUnreachable : return "ORIGIN IS UNREACHABLE";
271  case hcATimeoutOccured : return "A TIMEOUT OCCURRED";
272  case hcSSLHandshakeFailed : return "SSL HANDSHAKE FAILED";
273  case hcRailgunError : return "RAILGUN ERROR";
274  case hcOriginDNSError : return "ORIGIN DNS ERROR";
275 
276  default : return "UNKNOWN HTTP CODE";
277  }
278  }
279 
280  }
281 
282 }
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::HTTPResponse::HTTPResponseLine::asString
virtual std::string asString() const
Convert the HTTPResponseLine to a HTTP string.
Definition: httpresponse.cpp:99
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::HTTPResponse::HTTPResponseLine::http_code_
HTTPCode http_code_
The http_code.
Definition: httpresponse.hpp:200
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::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::common::SystemError::ok
bool ok() const
Returns true when this->errorcode_ == ecOK.
Definition: systemerror.hpp:254
dodo::network::protocol::http::HTTPResponse::HTTPResponseLine::version_
HTTPVersion version_
The HTTPVersion.
Definition: httpresponse.hpp:194
dodo::network::protocol::http::HTTPResponse::parse
virtual ParseResult parse(VirtualReadBuffer &buffer)
Read a complete HTTPFragment from a VirtualReadBuffer.
Definition: httpresponse.cpp:32
dodo::common::Bytes::append
void append(const Bytes &src)
Append another Bytes.
Definition: bytes.cpp:65
dodo::network::protocol::http::HTTPResponse::HTTPCode
HTTPCode
Definition: httpresponse.hpp:46
dodo
A C++ platform interface to lean Linux services tailored for containerized deployment.
Definition: application.hpp:29
dodo::network::protocol::http::HTTPResponse::response_line_
HTTPResponseLine response_line_
The HTTPResponseLIne of the HTTPResponse.
Definition: httpresponse.hpp:236
httpresponse.hpp
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::HTTPResponse::HTTPCodeAsString
static std::string HTTPCodeAsString(HTTPCode code)
Return the (upper case) string representation of the http error code.
Definition: httpresponse.cpp:176
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::HTTPResponse::asString
virtual std::string asString() const
Return the HTTPResponse as a string.
Definition: httpresponse.cpp:71
dodo::network::protocol::http::HTTPResponse::parseBody
virtual ParseResult parseBody(VirtualReadBuffer &data)
Parse a body and resturn it as a single string.
Definition: httpresponse.cpp:143
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::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::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::network::protocol::http::HTTPResponse::send
virtual common::SystemError send(BaseSocket *socket)
Send this HTTPMessage to the socket.
Definition: httpresponse.cpp:84
dodo::common::Bytes::getSize
size_t getSize() const
Return the array size.
Definition: bytes.hpp:192
dodo::network::protocol::http::HTTPResponse::HTTPResponseLine::parse
virtual ParseResult parse(VirtualReadBuffer &data)
Read a complete HTTPFragment from a VirtualReadBuffer.
Definition: httpresponse.cpp:111
dodo::network::protocol::http::HTTPResponse::hasBody
bool hasBody() const
Return true when the response has a body.
Definition: httpresponse.cpp:58
util.hpp
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.