Cuberite
A lightweight, fast and extensible game server for Minecraft
HTTPFormParser.cpp
Go to the documentation of this file.
1 
2 // HTTPFormParser.cpp
3 
4 // Implements the cHTTPFormParser class representing a parser for forms sent over HTTP
5 
6 #include "Globals.h"
7 #include "HTTPFormParser.h"
8 #include "HTTPMessage.h"
9 #include "NameValueParser.h"
10 
11 
12 
13 
14 
16  m_Callbacks(a_Callbacks),
17  m_IsValid(true),
18  m_IsCurrentPartFile(false),
19  m_FileHasBeenAnnounced(false)
20 {
21  if (a_Request.GetMethod() == "GET")
22  {
23  m_Kind = fpkURL;
24 
25  // Directly parse the URL in the request:
26  const AString & URL = a_Request.GetURL();
27  size_t idxQM = URL.find('?');
28  if (idxQM != AString::npos)
29  {
30  Parse(URL.c_str() + idxQM + 1, URL.size() - idxQM - 1);
31  }
32  return;
33  }
34  if ((a_Request.GetMethod() == "POST") || (a_Request.GetMethod() == "PUT"))
35  {
36  if (strncmp(a_Request.GetContentType().c_str(), "application/x-www-form-urlencoded", 33) == 0)
37  {
39  return;
40  }
41  if (strncmp(a_Request.GetContentType().c_str(), "multipart/form-data", 19) == 0)
42  {
44  BeginMultipart(a_Request);
45  return;
46  }
47  }
48  // Invalid method / content type combination, this is not a HTTP form
49  m_IsValid = false;
50 }
51 
52 
53 
54 
55 
56 cHTTPFormParser::cHTTPFormParser(eKind a_Kind, const char * a_Data, size_t a_Size, cCallbacks & a_Callbacks) :
57  m_Callbacks(a_Callbacks),
58  m_Kind(a_Kind),
59  m_IsValid(true),
60  m_IsCurrentPartFile(false),
61  m_FileHasBeenAnnounced(false)
62 {
63  Parse(a_Data, a_Size);
64 }
65 
66 
67 
68 
69 
70 void cHTTPFormParser::Parse(const char * a_Data, size_t a_Size)
71 {
72  if (!m_IsValid)
73  {
74  return;
75  }
76 
77  switch (m_Kind)
78  {
79  case fpkURL:
80  case fpkFormUrlEncoded:
81  {
82  // This format is used for smaller forms (not file uploads), so we can delay parsing it until Finish()
83  m_IncomingData.append(a_Data, a_Size);
84  break;
85  }
86  case fpkMultipart:
87  {
88  ASSERT(m_MultipartParser.get() != nullptr);
89  m_MultipartParser->Parse(a_Data, a_Size);
90  break;
91  }
92  }
93 }
94 
95 
96 
97 
98 
100 {
101  switch (m_Kind)
102  {
103  case fpkURL:
104  case fpkFormUrlEncoded:
105  {
106  // m_IncomingData has all the form data, parse it now:
108  break;
109  }
110  case fpkMultipart:
111  {
112  // Nothing needed for other formats
113  break;
114  }
115  }
116  return (m_IsValid && m_IncomingData.empty());
117 }
118 
119 
120 
121 
122 
124 {
125  const AString & ContentType = a_Request.GetContentType();
126  return (
127  (ContentType == "application/x-www-form-urlencoded") ||
128  (strncmp(ContentType.c_str(), "multipart/form-data", 19) == 0) ||
129  (
130  (a_Request.GetMethod() == "GET") &&
131  (a_Request.GetURL().find('?') != AString::npos)
132  )
133  );
134 }
135 
136 
137 
138 
139 
141 {
142  ASSERT(m_MultipartParser.get() == nullptr);
143  m_MultipartParser.reset(new cMultipartParser(a_Request.GetContentType(), *this));
144 }
145 
146 
147 
148 
149 
151 {
152  // Parse m_IncomingData for all the variables; no more data is incoming, since this is called from Finish()
153  // This may not be the most performant version, but we don't care, the form data is small enough and we're not a full-fledged web server anyway
155  for (AStringVector::iterator itr = Lines.begin(), end = Lines.end(); itr != end; ++itr)
156  {
157  AStringVector Components = StringSplit(*itr, "=");
158  switch (Components.size())
159  {
160  default:
161  {
162  // Neither name nor value, or too many "="s, mark this as invalid form:
163  m_IsValid = false;
164  return;
165  }
166  case 1:
167  {
168  // Only name present
169  auto name = URLDecode(ReplaceAllCharOccurrences(Components[0], '+', ' '));
170  if (name.first)
171  {
172  (*this)[name.second] = "";
173  }
174  break;
175  }
176  case 2:
177  {
178  // name=value format:
179  auto name = URLDecode(Components[0]);
180  auto value = URLDecode(Components[1]);
181  if (name.first && value.first)
182  {
183  (*this)[name.second] = value.second;
184  }
185  break;
186  }
187  }
188  } // for itr - Lines[]
189  m_IncomingData.clear();
190 }
191 
192 
193 
194 
195 
197 {
198  m_CurrentPartFileName.clear();
199  m_CurrentPartName.clear();
200  m_IsCurrentPartFile = false;
201  m_FileHasBeenAnnounced = false;
202 }
203 
204 
205 
206 
207 
208 void cHTTPFormParser::OnPartHeader(const AString & a_Key, const AString & a_Value)
209 {
210  if (NoCaseCompare(a_Key, "Content-Disposition") == 0)
211  {
212  size_t len = a_Value.size();
213  size_t ParamsStart = AString::npos;
214  for (size_t i = 0; i < len; ++i)
215  {
216  if (a_Value[i] > ' ')
217  {
218  if (strncmp(a_Value.c_str() + i, "form-data", 9) != 0)
219  {
220  // Content disposition is not "form-data", mark the whole form invalid
221  m_IsValid = false;
222  return;
223  }
224  ParamsStart = a_Value.find(';', i + 9);
225  break;
226  }
227  }
228  if (ParamsStart == AString::npos)
229  {
230  // There is data missing in the Content-Disposition field, mark the whole form invalid:
231  m_IsValid = false;
232  return;
233  }
234 
235  // Parse the field name and optional filename from this header:
236  cNameValueParser Parser(a_Value.data() + ParamsStart, a_Value.size() - ParamsStart);
237  Parser.Finish();
238  m_CurrentPartName = Parser["name"];
239  if (!Parser.IsValid() || m_CurrentPartName.empty())
240  {
241  // The required parameter "name" is missing, mark the whole form invalid:
242  m_IsValid = false;
243  return;
244  }
245  m_CurrentPartFileName = Parser["filename"];
246  }
247 }
248 
249 
250 
251 
252 
253 void cHTTPFormParser::OnPartData(const char * a_Data, size_t a_Size)
254 {
255  if (m_CurrentPartName.empty())
256  {
257  // Prologue, epilogue or invalid part
258  return;
259  }
260  if (m_CurrentPartFileName.empty())
261  {
262  // This is a variable, store it in the map
263  iterator itr = find(m_CurrentPartName);
264  if (itr == end())
265  {
266  (*this)[m_CurrentPartName] = AString(a_Data, a_Size);
267  }
268  else
269  {
270  itr->second.append(a_Data, a_Size);
271  }
272  }
273  else
274  {
275  // This is a file, pass it on through the callbacks
277  {
279  m_FileHasBeenAnnounced = true;
280  }
281  m_Callbacks.OnFileData(*this, a_Data, a_Size);
282  }
283 }
284 
285 
286 
287 
288 
290 {
292  {
293  m_Callbacks.OnFileEnd(*this);
294  }
295  m_CurrentPartName.clear();
296  m_CurrentPartFileName.clear();
297 }
298 
299 
300 
301 
#define ASSERT(x)
Definition: Globals.h:276
std::pair< bool, AString > URLDecode(const AString &a_Text)
URL-Decodes the given string.
AString ReplaceAllCharOccurrences(const AString &a_String, char a_From, char a_To)
Replaces all occurrences of char a_From inside a_String with char a_To.
AStringVector StringSplit(const AString &str, const AString &delim)
Split the string at any of the listed delimiters.
Definition: StringUtils.cpp:55
int NoCaseCompare(const AString &s1, const AString &s2)
Case-insensitive string comparison.
std::vector< AString > AStringVector
Definition: StringUtils.h:12
std::string AString
Definition: StringUtils.h:11
bool Finish(void)
Notifies that there's no more data incoming and the parser should finish its parsing.
void ParseFormUrlEncoded(void)
Parses m_IncomingData as form-urlencoded data (fpkURL or fpkFormUrlEncoded kinds)
std::unique_ptr< cMultipartParser > m_MultipartParser
The parser for the multipart data, if used.
eKind m_Kind
The kind of the parser (decided in the constructor, used in Parse()
virtual void OnPartHeader(const AString &a_Key, const AString &a_Value) override
Called when a complete header line is received for a part.
virtual void OnPartStart(void) override
Called when a new part starts.
bool m_IsCurrentPartFile
True if the currently parsed part in multipart data is a file.
AString m_CurrentPartFileName
Filename of the current parsed part in multipart data (for file uploads)
AString m_CurrentPartName
Name of the currently parsed part in multipart data.
virtual void OnPartEnd(void) override
Called when the current part ends.
cHTTPFormParser(const cHTTPIncomingRequest &a_Request, cCallbacks &a_Callbacks)
Creates a parser that is tied to a request and notifies of various events using a callback mechanism.
void BeginMultipart(const cHTTPIncomingRequest &a_Request)
Sets up the object for parsing a fpkMultipart request.
bool m_FileHasBeenAnnounced
Set to true after m_Callbacks.OnFileStart() has been called, reset to false on PartEnd.
bool m_IsValid
True if the information received so far is a valid form; set to false on first problem.
static bool HasFormData(const cHTTPIncomingRequest &a_Request)
Returns true if the headers suggest the request has form data parseable by this class.
@ fpkURL
The form has been transmitted as parameters to a GET request.
@ fpkFormUrlEncoded
The form has been POSTed or PUT, with Content-Type of "application/x-www-form-urlencoded".
@ fpkMultipart
The form has been POSTed or PUT, with Content-Type of "multipart/form-data".
virtual void OnPartData(const char *a_Data, size_t a_Size) override
Called when body for a part is received.
cCallbacks & m_Callbacks
The callbacks to call for incoming file data.
AString m_IncomingData
Buffer for the incoming data until it's parsed.
void Parse(const char *a_Data, size_t a_Size)
Adds more data into the parser, as the request body is received.
virtual void OnFileEnd(cHTTPFormParser &a_Parser)=0
Called when the current file part has ended in the form data.
virtual void OnFileData(cHTTPFormParser &a_Parser, const char *a_Data, size_t a_Size)=0
Called when more file data has come for the current file in the form data.
virtual void OnFileStart(cHTTPFormParser &a_Parser, const AString &a_FileName)=0
Called when a new file part is encountered in the form data.
const AString & GetContentType(void) const
Definition: HTTPMessage.h:42
Provides storage for an incoming HTTP request.
Definition: HTTPMessage.h:90
const AString & GetMethod(void) const
Returns the method used in the request.
Definition: HTTPMessage.h:109
const AString & GetURL(void) const
Returns the URL used in the request.
Definition: HTTPMessage.h:112
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.