Cuberite
A lightweight, fast and extensible game server for Minecraft
TransferEncodingParser.cpp
Go to the documentation of this file.
1 
2 // TransferEncodingParser.cpp
3 
4 // Implements the cTransferEncodingParser class and its descendants representing the parsers for the various transfer encodings (chunked etc.)
5 
6 #include "Globals.h"
8 #include "EnvelopeParser.h"
9 
10 
11 
12 
13 
15 // cChunkedTEParser:
16 
20 {
22 
23 public:
24 
26  Super(a_Callbacks),
29  m_TrailerParser(*this)
30  {
31  }
32 
33 
34 protected:
35  enum eState
36  {
45  };
46 
49 
53 
56 
57 
59  void Error(const AString & a_ErrorMsg)
60  {
62  m_Callbacks.OnError(a_ErrorMsg);
63  }
64 
65 
69  size_t ParseChunkLength(const char * a_Data, size_t a_Size)
70  {
71  // Expected input: <hexnumber>[;<trailer>]<CR><LF>
72  // Only the hexnumber is parsed into m_ChunkDataLengthLeft, the rest is postponed into psChunkLengthTrailer or psChunkLengthLF
73  for (size_t i = 0; i < a_Size; i++)
74  {
75  switch (a_Data[i])
76  {
77  case '0':
78  case '1':
79  case '2':
80  case '3':
81  case '4':
82  case '5':
83  case '6':
84  case '7':
85  case '8':
86  case '9':
87  {
88  m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - '0');
89  break;
90  }
91  case 'a':
92  case 'b':
93  case 'c':
94  case 'd':
95  case 'e':
96  case 'f':
97  {
98  m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'a' + 10);
99  break;
100  }
101  case 'A':
102  case 'B':
103  case 'C':
104  case 'D':
105  case 'E':
106  case 'F':
107  {
108  m_ChunkDataLengthLeft = m_ChunkDataLengthLeft * 16 + static_cast<decltype(m_ChunkDataLengthLeft)>(a_Data[i] - 'A' + 10);
109  break;
110  }
111  case '\r':
112  {
114  return i + 1;
115  }
116  case ';':
117  {
119  return i + 1;
120  }
121  default:
122  {
123  Error(fmt::format(FMT_STRING("Invalid character in chunk length line: 0x{:02x}"), a_Data[i]));
124  return AString::npos;
125  }
126  } // switch (a_Data[i])
127  } // for i - a_Data[]
128  return a_Size;
129  }
130 
131 
135  size_t ParseChunkLengthTrailer(const char * a_Data, size_t a_Size)
136  {
137  // Expected input: <trailer><CR><LF>
138  // The LF itself is not parsed, it is instead postponed into psChunkLengthLF
139  for (size_t i = 0; i < a_Size; i++)
140  {
141  switch (a_Data[i])
142  {
143  case '\r':
144  {
146  return i;
147  }
148  default:
149  {
150  if (a_Data[i] < 32)
151  {
152  // Only printable characters are allowed in the trailer
153  Error(fmt::format(FMT_STRING("Invalid character in chunk length line: 0x{:02x}"), a_Data[i]));
154  return AString::npos;
155  }
156  }
157  } // switch (a_Data[i])
158  } // for i - a_Data[]
159  return a_Size;
160  }
161 
162 
167  size_t ParseChunkLengthLF(const char * a_Data, size_t a_Size)
168  {
169  // Expected input: <LF>
170  if (a_Size == 0)
171  {
172  return 0;
173  }
174  if (a_Data[0] == '\n')
175  {
176  if (m_ChunkDataLengthLeft == 0)
177  {
178  m_State = psTrailer;
179  }
180  else
181  {
183  }
184  return 1;
185  }
186  Error(fmt::format(FMT_STRING("Invalid character past chunk length's CR: 0x{:02x}"), a_Data[0]));
187  return AString::npos;
188  }
189 
190 
193  size_t ParseChunkData(const char * a_Data, size_t a_Size)
194  {
196  auto bytes = std::min(a_Size, m_ChunkDataLengthLeft);
197  m_ChunkDataLengthLeft -= bytes;
198  m_Callbacks.OnBodyData(a_Data, bytes);
199  if (m_ChunkDataLengthLeft == 0)
200  {
202  }
203  return bytes;
204  }
205 
206 
210  size_t ParseChunkDataCR(const char * a_Data, size_t a_Size)
211  {
212  // Expected input: <CR>
213  if (a_Size == 0)
214  {
215  return 0;
216  }
217  if (a_Data[0] == '\r')
218  {
220  return 1;
221  }
222  Error(fmt::format(FMT_STRING("Invalid character past chunk data: 0x{:02x}"), a_Data[0]));
223  return AString::npos;
224  }
225 
226 
227 
228 
232  size_t ParseChunkDataLF(const char * a_Data, size_t a_Size)
233  {
234  // Expected input: <LF>
235  if (a_Size == 0)
236  {
237  return 0;
238  }
239  if (a_Data[0] == '\n')
240  {
242  return 1;
243  }
244  Error(fmt::format(FMT_STRING("Invalid character past chunk data's CR: 0x{:02x}"), a_Data[0]));
245  return AString::npos;
246  }
247 
248 
252  size_t ParseTrailer(const char * a_Data, size_t a_Size)
253  {
254  auto res = m_TrailerParser.Parse(a_Data, a_Size);
255  if (res == AString::npos)
256  {
257  Error("Error while parsing the trailer");
258  }
259  if ((res < a_Size) || !m_TrailerParser.IsInHeaders())
260  {
263  }
264  return res;
265  }
266 
267 
268  // cTransferEncodingParser overrides:
269  virtual size_t Parse(const char * a_Data, size_t a_Size) override
270  {
271  while ((a_Size > 0) && (m_State != psFinished))
272  {
273  size_t consumed = 0;
274  switch (m_State)
275  {
276  case psChunkLength: consumed = ParseChunkLength (a_Data, a_Size); break;
277  case psChunkLengthTrailer: consumed = ParseChunkLengthTrailer(a_Data, a_Size); break;
278  case psChunkLengthLF: consumed = ParseChunkLengthLF (a_Data, a_Size); break;
279  case psChunkData: consumed = ParseChunkData (a_Data, a_Size); break;
280  case psChunkDataCR: consumed = ParseChunkDataCR (a_Data, a_Size); break;
281  case psChunkDataLF: consumed = ParseChunkDataLF (a_Data, a_Size); break;
282  case psTrailer: consumed = ParseTrailer (a_Data, a_Size); break;
283  case psFinished: consumed = 0; break; // Not supposed to happen, but Clang complains without it
284  }
285  if (consumed == AString::npos)
286  {
287  return AString::npos;
288  }
289  a_Data += consumed;
290  a_Size -= consumed;
291  }
292  return a_Size;
293  }
294 
295  virtual void Finish() override
296  {
297  if (m_State != psFinished)
298  {
299  Error(fmt::format(FMT_STRING("ChunkedTransferEncoding: Finish signal received before the data stream ended (state: {})"), m_State));
300  }
302  }
303 
304 
305  // cEnvelopeParser::cCallbacks overrides:
306  virtual void OnHeaderLine(const AString & a_Key, const AString & a_Value) override
307  {
308  // Ignored
309  }
310 };
311 
312 
313 
314 
315 
317 // cIdentityTEParser:
318 
321 {
323 
324 public:
325 
326  cIdentityTEParser(cCallbacks & a_Callbacks, size_t a_ContentLength):
327  Super(a_Callbacks),
328  m_BytesLeft(a_ContentLength)
329  {
330  }
331 
332 
333 protected:
335  size_t m_BytesLeft;
336 
337  // cTransferEncodingParser overrides:
338  virtual size_t Parse(const char * a_Data, size_t a_Size) override
339  {
340  auto size = std::min(a_Size, m_BytesLeft);
341  if (size > 0)
342  {
343  m_Callbacks.OnBodyData(a_Data, size);
344  }
345  m_BytesLeft -= size;
346  if (m_BytesLeft == 0)
347  {
349  }
350  return a_Size - size;
351  }
352 
353  virtual void Finish(void) override
354  {
355  if (m_BytesLeft > 0)
356  {
357  m_Callbacks.OnError("IdentityTransferEncoding: body was truncated");
358  }
359  else
360  {
361  // BodyFinished has already been called, just bail out
362  }
363  }
364 };
365 
366 
367 
368 
369 
371 // cTransferEncodingParser:
372 
374  cCallbacks & a_Callbacks,
375  const AString & a_TransferEncoding,
376  size_t a_ContentLength
377 )
378 {
379  if (a_TransferEncoding == "chunked")
380  {
381  return std::make_shared<cChunkedTEParser>(a_Callbacks);
382  }
383  if (a_TransferEncoding == "identity")
384  {
385  return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
386  }
387  if (a_TransferEncoding.empty())
388  {
389  return std::make_shared<cIdentityTEParser>(a_Callbacks, a_ContentLength);
390  }
391  return nullptr;
392 }
393 
394 
395 
396 
#define ASSERT(x)
Definition: Globals.h:276
std::shared_ptr< cTransferEncodingParser > cTransferEncodingParserPtr
std::string AString
Definition: StringUtils.h:11
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.
cEnvelopeParser m_TrailerParser
The parser used for the last (empty) chunk's trailer data.
void Error(const AString &a_ErrorMsg)
Calls the OnError callback and sets parser state to finished.
@ psChunkDataCR
Skipping the extra CR character after chunk data.
@ psChunkDataLF
Skipping the extra LF character after chunk data.
@ psChunkData
Relaying chunk data.
@ psChunkLengthLF
The LF character after the CR character terminating the chunk length.
@ psTrailer
Received an empty chunk, parsing the trailer (through the envelope parser)
@ psFinished
The parser has finished parsing, either successfully or with an error.
@ psChunkLengthTrailer
Any trailer (chunk extension) specified after the chunk length.
@ psChunkLength
Parsing the chunk length hex number.
size_t ParseTrailer(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkDataCR.
size_t ParseChunkLengthTrailer(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkLengthTrailer.
eState m_State
The current state of the parser (parsing chunk length / chunk data).
size_t ParseChunkDataLF(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkDataCR.
size_t ParseChunkLength(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkLength.
size_t m_ChunkDataLengthLeft
Number of bytes that still belong to the chunk currently being parsed.
size_t ParseChunkLengthLF(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkLengthLF.
cChunkedTEParser(Super::cCallbacks &a_Callbacks)
size_t ParseChunkDataCR(const char *a_Data, size_t a_Size)
Parses the incoming data, the current state is psChunkDataCR.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a full header line is parsed.
virtual void Finish() override
To be called when the stream is terminated from the source (connection closed).
virtual size_t Parse(const char *a_Data, size_t a_Size) override
Parses the incoming data and calls the appropriate callbacks.
size_t ParseChunkData(const char *a_Data, size_t a_Size)
Consumes as much chunk data from the input as possible.
virtual void Finish(void) override
To be called when the stream is terminated from the source (connection closed).
size_t m_BytesLeft
How many bytes of content are left before the message ends.
virtual size_t Parse(const char *a_Data, size_t a_Size) override
Parses the incoming data and calls the appropriate callbacks.
cIdentityTEParser(cCallbacks &a_Callbacks, size_t a_ContentLength)
Used as both the interface that all the parsers share and the (static) factory creating such parsers.
cCallbacks & m_Callbacks
The callbacks used to report progress.
cTransferEncodingParser(cCallbacks &a_Callbacks)
static cTransferEncodingParserPtr Create(cCallbacks &a_Callbacks, const AString &a_TransferEncoding, size_t a_ContentLength)
Creates a new parser for the specified encoding.
virtual void OnError(const AString &a_ErrorDescription)=0
Called when an error has occured while parsing.
virtual void OnBodyData(const void *a_Data, size_t a_Size)=0
Called for each chunk of the incoming body data.
virtual void OnBodyFinished(void)=0
Called when the entire body has been reported by OnBodyData().