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 
std::string AString
Definition: StringUtils.h:11
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.
void SetIsInHeaders(bool a_IsInHeaders)
Sets the IsInHeaders flag; used by cMultipartParser to simplify the parser initial conditions.
bool IsInHeaders(void) const
Returns true if more input is expected for the envelope header.
virtual void OnHeaderLine(const AString &a_Key, const AString &a_Value) override
Called when a full header line is parsed.
cCallbacks & m_Callbacks
The callbacks to call for various parsing events.
bool m_IsValid
True if the data parsed so far is valid; if false, further parsing is skipped.
cMultipartParser(const AString &a_ContentType, cCallbacks &a_Callbacks)
Creates the parser, expects to find the boundary in a_ContentType.
cEnvelopeParser m_EnvelopeParser
Parser for each part's envelope.
AString m_Boundary
The boundary, excluding both the initial "--" and the terminating CRLF.
void Parse(const char *a_Data, size_t a_Size)
Parses more incoming data.
AString m_IncomingData
Buffer for the incoming data until it is parsed.
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 OnPartStart(void)=0
Called when a new part starts.
virtual void OnPartEnd(void)=0
Called when the current part ends.
virtual void OnPartData(const char *a_Data, size_t a_Size)=0
Called when body for a part is received.
bool Finish(void)
Notifies the parser that no more data will be coming.
bool IsValid(void) const
Returns true if the data parsed so far was valid.