Cuberite
A lightweight, fast and extensible game server for Minecraft
HTTPMessageParser.cpp
Go to the documentation of this file.
1 
2 // HTTPMessageParser.cpp
3 
4 // Implements the cHTTPMessageParser class that parses HTTP messages (request or response) being pushed into the parser,
5 // and reports the individual parts via callbacks
6 
7 #include "Globals.h"
8 #include "HTTPMessageParser.h"
9 
10 
11 
12 
13 
15  m_Callbacks(a_Callbacks),
16  m_EnvelopeParser(*this)
17 {
18  Reset();
19 }
20 
21 
22 
23 
24 
25 size_t cHTTPMessageParser::Parse(const char * a_Data, size_t a_Size)
26 {
27  // If parsing already finished or errorred, let the caller keep all the data:
29  {
30  return 0;
31  }
32 
33  // If still waiting for the status line, add to buffer and try parsing it:
34  auto inBufferSoFar = m_Buffer.size();
35  if (m_FirstLine.empty())
36  {
37  m_Buffer.append(a_Data, a_Size);
38  auto bytesConsumedFirstLine = ParseFirstLine();
39  ASSERT(bytesConsumedFirstLine <= inBufferSoFar + a_Size); // Haven't consumed more data than there is in the buffer
40  ASSERT(bytesConsumedFirstLine > inBufferSoFar); // Have consumed at least the previous buffer contents
41  if (m_FirstLine.empty())
42  {
43  // All data used, but not a complete status line yet.
44  return a_Size;
45  }
46  if (m_HasHadError)
47  {
48  return AString::npos;
49  }
50  // Status line completed, feed the rest of the buffer into the envelope parser:
51  auto bytesConsumedEnvelope = m_EnvelopeParser.Parse(m_Buffer.data(), m_Buffer.size());
52  if (bytesConsumedEnvelope == AString::npos)
53  {
54  m_HasHadError = true;
55  m_Callbacks.OnError("Failed to parse the envelope");
56  return AString::npos;
57  }
58  ASSERT(bytesConsumedEnvelope <= bytesConsumedFirstLine + a_Size); // Haven't consumed more data than there was in the buffer
59  m_Buffer.erase(0, bytesConsumedEnvelope);
61  {
63  // Process any data still left in the buffer as message body:
64  auto bytesConsumedBody = ParseBody(m_Buffer.data(), m_Buffer.size());
65  if (bytesConsumedBody == AString::npos)
66  {
67  // Error has already been reported by ParseBody, just bail out:
68  return AString::npos;
69  }
70  return bytesConsumedBody + bytesConsumedEnvelope + bytesConsumedFirstLine - inBufferSoFar;
71  }
72  return a_Size;
73  } // if (m_FirstLine.empty())
74 
75  // If still parsing headers, send them to the envelope parser:
77  {
78  auto bytesConsumed = m_EnvelopeParser.Parse(a_Data, a_Size);
79  if (bytesConsumed == AString::npos)
80  {
81  m_HasHadError = true;
82  m_Callbacks.OnError("Failed to parse the envelope");
83  return AString::npos;
84  }
86  {
88  // Process any data still left as message body:
89  auto bytesConsumedBody = ParseBody(a_Data + bytesConsumed, a_Size - bytesConsumed);
90  if (bytesConsumedBody == AString::npos)
91  {
92  // Error has already been reported by ParseBody, just bail out:
93  return AString::npos;
94  }
95  }
96  return a_Size;
97  }
98 
99  // Already parsing the body
100  return ParseBody(a_Data, a_Size);
101 }
102 
103 
104 
105 
106 
108 {
109  m_HasHadError = false;
110  m_IsFinished = false;
111  m_FirstLine.clear();
112  m_Buffer.clear();
114  m_TransferEncodingParser.reset();
115  m_TransferEncoding.clear();
116  m_ContentLength = 0;
117 }
118 
119 
120 
121 
122 
124 {
125  auto idxLineEnd = m_Buffer.find("\r\n");
126  if (idxLineEnd == AString::npos)
127  {
128  // Not a complete line yet
129  return m_Buffer.size();
130  }
131  m_FirstLine = m_Buffer.substr(0, idxLineEnd);
132  m_Buffer.erase(0, idxLineEnd + 2);
134  return idxLineEnd + 2;
135 }
136 
137 
138 
139 
140 
141 size_t cHTTPMessageParser::ParseBody(const char * a_Data, size_t a_Size)
142 {
143  if (m_TransferEncodingParser == nullptr)
144  {
145  // We have no Transfer-encoding parser assigned. This should have happened when finishing the envelope
146  OnError("No transfer encoding parser");
147  return AString::npos;
148  }
149 
150  // Parse the body using the transfer encoding parser:
151  // (Note that TE parser returns the number of bytes left, while we return the number of bytes consumed)
152  return a_Size - m_TransferEncodingParser->Parse(a_Data, a_Size);
153 }
154 
155 
156 
157 
158 
160 {
163  if (m_TransferEncodingParser == nullptr)
164  {
165  OnError(fmt::format(FMT_STRING("Unknown transfer encoding: {}"), m_TransferEncoding));
166  return;
167  }
168 }
169 
170 
171 
172 
173 
174 void cHTTPMessageParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
175 {
176  m_Callbacks.OnHeaderLine(a_Key, a_Value);
177  auto Key = StrToLower(a_Key);
178  if (Key == "content-length")
179  {
180  if (!StringToInteger(a_Value, m_ContentLength))
181  {
182  OnError(fmt::format(FMT_STRING("Invalid content length header value: \"{}\""), a_Value));
183  }
184  return;
185  }
186  if (Key == "transfer-encoding")
187  {
188  m_TransferEncoding = a_Value;
189  return;
190  }
191 }
192 
193 
194 
195 
196 
197 void cHTTPMessageParser::OnError(const AString & a_ErrorDescription)
198 {
199  m_HasHadError = true;
200  m_Callbacks.OnError(a_ErrorDescription);
201 }
202 
203 
204 
205 
206 
207 void cHTTPMessageParser::OnBodyData(const void * a_Data, size_t a_Size)
208 {
209  m_Callbacks.OnBodyData(a_Data, a_Size);
210 }
211 
212 
213 
214 
215 
217 {
218  m_IsFinished = true;
220 }
221 
222 
223 
224 
#define ASSERT(x)
Definition: Globals.h:276
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
std::string AString
Definition: StringUtils.h:11
bool StringToInteger(const AString &a_str, T &a_Num)
Parses any integer type.
Definition: StringUtils.h:143
void Reset(void)
Makes the parser forget everything parsed so far, so that it can be reused for parsing another datast...
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data.
bool IsInHeaders(void) const
Returns true if more input is expected for the envelope header.
void Reset(void)
Resets the parser to the initial state, so that a new request can be parsed.
virtual void OnBodyData(const void *a_Data, size_t a_Size) override
Called for each chunk of the incoming body data.
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data and calls the appropriate callbacks.
cCallbacks & m_Callbacks
The callbacks used for reporting.
virtual void OnBodyFinished(void) override
Called when the entire body has been reported by OnBodyData().
virtual void OnError(const AString &a_ErrorDescription) override
Called when an error has occured while parsing.
bool m_IsFinished
True if the response has been fully parsed.
cEnvelopeParser m_EnvelopeParser
Parser for the envelope data (headers)
cHTTPMessageParser(cCallbacks &a_Callbacks)
Creates a new parser instance that will use the specified callbacks for reporting.
void HeadersFinished(void)
Called internally when the headers-parsing has just finished.
size_t m_ContentLength
The content length, parsed from the headers, if available.
AString m_FirstLine
The complete first line of the response.
size_t ParseBody(const char *a_Data, size_t a_Size)
Parses the message body.
AString m_TransferEncoding
The transfer encoding to be used by the parser.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a full header line is parsed.
size_t ParseFirstLine(void)
Parses the first line out of m_Buffer.
cTransferEncodingParserPtr m_TransferEncodingParser
The specific parser for the transfer encoding used by this response.
bool m_HasHadError
Set to true if an error has been encountered by the parser.
AString m_Buffer
Buffer for the incoming data until the status line is parsed.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value)=0
Called when a single header line is parsed.
virtual void OnFirstLine(const AString &a_FirstLine)=0
Called when the first line of the request or response is fully parsed.
virtual void OnBodyFinished(void)=0
Called when the entire body has been reported by OnBodyData().
virtual void OnBodyData(const void *a_Data, size_t a_Size)=0
Called for each chunk of the incoming body data.
virtual void OnError(const AString &a_ErrorDescription)=0
Called when an error has occured while parsing.
virtual void OnHeadersFinished(void)=0
Called when all the headers have been parsed.
static cTransferEncodingParserPtr Create(cCallbacks &a_Callbacks, const AString &a_TransferEncoding, size_t a_ContentLength)
Creates a new parser for the specified encoding.