Cuberite
A lightweight, fast and extensible game server for Minecraft
MultipartParser.cpp
Go to the documentation of this file.
1 
2 // MultipartParser.cpp
3 
4 // Implements the cMultipartParser class that parses messages in "multipart/*" encoding into the separate parts
5 
6 #include "Globals.h"
7 #include "MultipartParser.h"
8 #include "NameValueParser.h"
9 
10 
11 
12 
13 
14 // Disable MSVC warnings:
15 #if defined(_MSC_VER)
16  #pragma warning(push)
17  #pragma warning(disable:4355) // 'this' : used in base member initializer list
18 #endif
19 
20 
21 
22 
23 
25 // self-test:
26 
27 #if 0
28 
29 class cMultipartParserTest :
31 {
32 public:
33  cMultipartParserTest(void)
34  {
35  cMultipartParser Parser("multipart/mixed; boundary=\"MyBoundaryString\"; foo=bar", *this);
36  const char Data[] =
37 "ThisIsIgnoredPrologue\r\n\
38 --MyBoundaryString\r\n\
39 \r\n\
40 Body with confusing strings\r\n\
41 --NotABoundary\r\n\
42 --MyBoundaryStringWithPostfix\r\n\
43 --\r\n\
44 --MyBoundaryString\r\n\
45 content-disposition: inline\r\n\
46 \r\n\
47 This is body\r\n\
48 --MyBoundaryString\r\n\
49 \r\n\
50 Headerless body with trailing CRLF\r\n\
51 \r\n\
52 --MyBoundaryString--\r\n\
53 ThisIsIgnoredEpilogue";
54  printf("Multipart parsing test commencing.\n");
55  Parser.Parse(Data, sizeof(Data) - 1);
56  // DEBUG: Check if the onscreen output corresponds with the data above
57  printf("Multipart parsing test finished\n");
58  }
59 
60  virtual void OnPartStart(void) override
61  {
62  printf("Starting a new part\n");
63  }
64 
65 
66  virtual void OnPartHeader(const AString & a_Key, const AString & a_Value) override
67  {
68  printf(" Hdr: \"%s\"=\"%s\"\n", a_Key.c_str(), a_Value.c_str());
69  }
70 
71 
72  virtual void OnPartData(const char * a_Data, int a_Size) override
73  {
74  printf(" Data: %d bytes, \"%.*s\"\n", a_Size, a_Size, a_Data);
75  }
76 
77 
78  virtual void OnPartEnd(void) override
79  {
80  printf("Part end\n");
81  }
82 } g_Test;
83 
84 #endif
85 
86 
87 
88 
89 
91 // cMultipartParser:
92 
93 
94 cMultipartParser::cMultipartParser(const AString & a_ContentType, cCallbacks & a_Callbacks) :
95  m_Callbacks(a_Callbacks),
96  m_IsValid(true),
97  m_EnvelopeParser(*this),
98  m_HasHadData(false)
99 {
100  // Check that the content type is multipart:
101  AString ContentType(a_ContentType);
102  if (strncmp(ContentType.c_str(), "multipart/", 10) != 0)
103  {
104  m_IsValid = false;
105  return;
106  }
107  size_t idxSC = ContentType.find(';', 10);
108  if (idxSC == AString::npos)
109  {
110  m_IsValid = false;
111  return;
112  }
113 
114  // Find the multipart boundary:
115  ContentType.erase(0, idxSC + 1);
116  cNameValueParser CTParser(ContentType.c_str(), ContentType.size());
117  CTParser.Finish();
118  if (!CTParser.IsValid())
119  {
120  m_IsValid = false;
121  return;
122  }
123  m_Boundary = CTParser["boundary"];
124  m_IsValid = !m_Boundary.empty();
125  if (!m_IsValid)
126  {
127  return;
128  }
129 
130  // Set the envelope parser for parsing the body, so that our Parse() function parses the ignored prefix data as a body
132 
133  // Append an initial CRLF to the incoming data, so that a body starting with the boundary line will get caught
134  m_IncomingData.assign("\r\n");
135 
136  /*
137  m_Boundary = AString("\r\n--") + m_Boundary
138  m_BoundaryEnd = m_Boundary + "--\r\n";
139  m_Boundary = m_Boundary + "\r\n";
140  */
141 }
142 
143 
144 
145 
146 
147 void cMultipartParser::Parse(const char * a_Data, size_t a_Size)
148 {
149  // Skip parsing if invalid
150  if (!m_IsValid)
151  {
152  return;
153  }
154 
155  // Append to buffer, then parse it:
156  m_IncomingData.append(a_Data, a_Size);
157  for (;;)
158  {
160  {
161  size_t BytesConsumed = m_EnvelopeParser.Parse(m_IncomingData.data(), m_IncomingData.size());
162  if (BytesConsumed == AString::npos)
163  {
164  m_IsValid = false;
165  return;
166  }
167  if ((BytesConsumed == a_Size) && m_EnvelopeParser.IsInHeaders())
168  {
169  // All the incoming data has been consumed and still waiting for more
170  return;
171  }
172  m_IncomingData.erase(0, BytesConsumed);
173  }
174 
175  // Search for boundary / boundary end:
176  size_t idxBoundary = m_IncomingData.find("\r\n--");
177  if (idxBoundary == AString::npos)
178  {
179  // Boundary string start not present, present as much data to the part callback as possible
180  if (m_IncomingData.size() > m_Boundary.size() + 8)
181  {
182  size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
183  m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
184  m_IncomingData.erase(0, BytesToReport);
185  }
186  return;
187  }
188  if (idxBoundary > 0)
189  {
190  m_Callbacks.OnPartData(m_IncomingData.data(), idxBoundary);
191  m_IncomingData.erase(0, idxBoundary);
192  }
193  idxBoundary = 4;
194  size_t LineEnd = m_IncomingData.find("\r\n", idxBoundary);
195  if (LineEnd == AString::npos)
196  {
197  // Not a complete line yet, present as much data to the part callback as possible
198  if (m_IncomingData.size() > m_Boundary.size() + 8)
199  {
200  size_t BytesToReport = m_IncomingData.size() - m_Boundary.size() - 8;
201  m_Callbacks.OnPartData(m_IncomingData.data(), BytesToReport);
202  m_IncomingData.erase(0, BytesToReport);
203  }
204  return;
205  }
206  if (
207  (LineEnd - idxBoundary != m_Boundary.size()) && // Line length not equal to boundary
208  (LineEnd - idxBoundary != m_Boundary.size() + 2) // Line length not equal to boundary end
209  )
210  {
211  // Got a line, but it's not a boundary, report it as data:
212  m_Callbacks.OnPartData(m_IncomingData.data(), LineEnd);
213  m_IncomingData.erase(0, LineEnd);
214  continue;
215  }
216 
217  if (strncmp(m_IncomingData.c_str() + idxBoundary, m_Boundary.c_str(), m_Boundary.size()) == 0)
218  {
219  // Boundary or BoundaryEnd found:
221  size_t idxSlash = idxBoundary + m_Boundary.size();
222  if ((m_IncomingData[idxSlash] == '-') && (m_IncomingData[idxSlash + 1] == '-'))
223  {
224  // This was the last part
225  m_Callbacks.OnPartData(m_IncomingData.data() + idxSlash + 4, m_IncomingData.size() - idxSlash - 4);
226  m_IncomingData.clear();
227  return;
228  }
230  m_IncomingData.erase(0, LineEnd + 2);
231 
232  // Keep parsing for the headers that may have come with this data:
234  continue;
235  }
236 
237  // It's a line, but not a boundary. It can be fully sent to the data receiver, since a boundary cannot cross lines
238  m_Callbacks.OnPartData(m_IncomingData.c_str(), LineEnd);
239  m_IncomingData.erase(0, LineEnd);
240  } // while (true)
241 }
242 
243 
244 
245 
246 
247 void cMultipartParser::OnHeaderLine(const AString & a_Key, const AString & a_Value)
248 {
249  m_Callbacks.OnPartHeader(a_Key, a_Value);
250 }
251 
252 
253 
254 
size_t Parse(const char *a_Data, size_t a_Size)
Parses the incoming data.
cCallbacks & m_Callbacks
The callbacks to call for various parsing events.
virtual void OnPartStart(void)=0
Called when a new part starts.
bool Finish(void)
Notifies the parser that no more data will be coming.
bool IsInHeaders(void) const
Returns true if more input is expected for the envelope header.
void SetIsInHeaders(bool a_IsInHeaders)
Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions...
AString m_IncomingData
Buffer for the incoming data until it is parsed.
cEnvelopeParser m_EnvelopeParser
Parser for each part's envelope.
std::string AString
Definition: StringUtils.h:13
virtual void OnPartEnd(void)=0
Called when the current part ends.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a full 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...
cMultipartParser(const AString &a_ContentType, cCallbacks &a_Callbacks)
Creates the parser, expects to find the boundary in a_ContentType.
void Parse(const char *a_Data, size_t a_Size)
Parses more incoming data.
bool m_IsValid
True if the data parsed so far is valid; if false, further parsing is skipped.
AString m_Boundary
The boundary, excluding both the initial "--" and the terminating CRLF.
virtual void OnPartHeader(const AString &a_Key, const AString &a_Value)=0
Called when a complete header line is received for a part.
virtual void OnPartData(const char *a_Data, size_t a_Size)=0
Called when body for a part is received.