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