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(Printf("Unknown transfer encoding: %s", m_TransferEncoding.c_str()));
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(Printf("Invalid content length header value: \"%s\"", a_Value.c_str()));
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 
virtual void OnError(const AString &a_ErrorDescription) override
Called when an error has occured while parsing.
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.
cCallbacks & m_Callbacks
The callbacks used for reporting.
AString m_TransferEncoding
The transfer encoding to be used by the parser.
virtual void OnBodyData(const void *a_Data, size_t a_Size)=0
Called for each chunk of the incoming body data.
bool StringToInteger(const AString &a_str, T &a_Num)
Parses any integer type.
Definition: StringUtils.h:161
bool m_IsFinished
True if the response has been fully parsed.
AString m_Buffer
Buffer for the incoming data until the status line is parsed.
virtual void OnBodyFinished(void) override
Called when the entire body has been reported by OnBodyData().
void Reset(void)
Resets the parser to the initial state, so that a new request can be parsed.
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data.
bool m_HasHadError
Set to true if an error has been encountered by the parser.
cHTTPMessageParser(cCallbacks &a_Callbacks)
Creates a new parser instance that will use the specified callbacks for reporting.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a full header line is parsed.
size_t ParseBody(const char *a_Data, size_t a_Size)
Parses the message body.
size_t ParseFirstLine(void)
Parses the first line out of m_Buffer.
bool IsInHeaders(void) const
Returns true if more input is expected for the envelope header.
cEnvelopeParser m_EnvelopeParser
Parser for the envelope data (headers)
AString & Printf(AString &str, const char *format, fmt::ArgList args)
Output the formatted text into the string.
Definition: StringUtils.cpp:55
static cTransferEncodingParserPtr Create(cCallbacks &a_Callbacks, const AString &a_TransferEncoding, size_t a_ContentLength)
Creates a new parser for the specified encoding.
#define ASSERT(x)
Definition: Globals.h:335
virtual void OnBodyData(const void *a_Data, size_t a_Size) override
Called for each chunk of the incoming body data.
virtual void OnFirstLine(const AString &a_FirstLine)=0
Called when the first line of the request or response is fully parsed.
std::string AString
Definition: StringUtils.h:13
virtual void OnBodyFinished(void)=0
Called when the entire body has been reported by OnBodyData().
cTransferEncodingParserPtr m_TransferEncodingParser
The specific parser for the transfer encoding used by this response.
AString m_FirstLine
The complete first line of the response.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value)=0
Called when a single header line is parsed.
void Reset(void)
Makes the parser forget everything parsed so far, so that it can be reused for parsing another datast...
virtual void OnError(const AString &a_ErrorDescription)=0
Called when an error has occured while parsing.
AString StrToLower(const AString &s)
Returns a lower-cased copy of the string.
virtual void OnHeadersFinished(void)=0
Called when all the headers have been parsed.
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data and calls the appropriate callbacks.