QtWebApp
httprequest.cpp
Go to the documentation of this file.
1 
6 #include "httprequest.h"
7 #include <QList>
8 #include <QDir>
9 #include "httpcookie.h"
10 
11 using namespace stefanfrings;
12 
13 HttpRequest::HttpRequest(const QSettings* settings)
14 {
15  status=waitForRequest;
16  currentSize=0;
17  expectedBodySize=0;
18  maxSize=settings->value("maxRequestSize","16000").toInt();
19  maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
20  tempFile=nullptr;
21 }
22 
23 
24 void HttpRequest::readRequest(QTcpSocket* socket)
25 {
26  #ifdef SUPERVERBOSE
27  qDebug("HttpRequest: read request");
28  #endif
29  int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
30  QByteArray dataRead = socket->readLine(toRead);
31  currentSize += dataRead.size();
32  lineBuffer.append(dataRead);
33  if (!lineBuffer.contains("\r\n"))
34  {
35  #ifdef SUPERVERBOSE
36  qDebug("HttpRequest: collecting more parts until line break");
37  #endif
38  return;
39  }
40  QByteArray newData=lineBuffer.trimmed();
41  lineBuffer.clear();
42  if (!newData.isEmpty())
43  {
44  qDebug("HttpRequest: from %s: %s",qPrintable(socket->peerAddress().toString()),newData.data());
45  QList<QByteArray> list=newData.split(' ');
46  if (list.count()!=3 || !list.at(2).contains("HTTP"))
47  {
48  qWarning("HttpRequest: received broken HTTP request, invalid first line");
49  status=abort;
50  }
51  else
52  {
53  method=list.at(0).trimmed();
54  path=list.at(1);
55  version=list.at(2);
56  peerAddress = socket->peerAddress();
57  status=waitForHeader;
58  }
59  }
60 }
61 
62 void HttpRequest::readHeader(QTcpSocket* socket)
63 {
64  int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
65  QByteArray dataRead = socket->readLine(toRead);
66  currentSize += dataRead.size();
67  lineBuffer.append(dataRead);
68  if (!lineBuffer.contains("\r\n"))
69  {
70  #ifdef SUPERVERBOSE
71  qDebug("HttpRequest: collecting more parts until line break");
72  #endif
73  return;
74  }
75  QByteArray newData=lineBuffer.trimmed();
76  lineBuffer.clear();
77  int colon=newData.indexOf(':');
78  if (colon>0)
79  {
80  // Received a line with a colon - a header
81  currentHeader=newData.left(colon).toLower();
82  QByteArray value=newData.mid(colon+1).trimmed();
83  headers.insert(currentHeader,value);
84  #ifdef SUPERVERBOSE
85  qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
86  #endif
87  }
88  else if (!newData.isEmpty())
89  {
90  // received another line - belongs to the previous header
91  #ifdef SUPERVERBOSE
92  qDebug("HttpRequest: read additional line of header");
93  #endif
94  // Received additional line of previous header
95  if (headers.contains(currentHeader)) {
96  headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
97  }
98  }
99  else
100  {
101  // received an empty line - end of headers reached
102  #ifdef SUPERVERBOSE
103  qDebug("HttpRequest: headers completed");
104  #endif
105  // Empty line received, that means all headers have been received
106  // Check for multipart/form-data
107  QByteArray contentType=headers.value("content-type");
108  if (contentType.startsWith("multipart/form-data"))
109  {
110  int posi=contentType.indexOf("boundary=");
111  if (posi>=0) {
112  boundary=contentType.mid(posi+9);
113  if (boundary.startsWith('"') && boundary.endsWith('"'))
114  {
115  boundary = boundary.mid(1,boundary.length()-2);
116  }
117  }
118  }
119  QByteArray contentLength=headers.value("content-length");
120  if (!contentLength.isEmpty())
121  {
122  expectedBodySize=contentLength.toInt();
123  }
124  if (expectedBodySize==0)
125  {
126  #ifdef SUPERVERBOSE
127  qDebug("HttpRequest: expect no body");
128  #endif
129  status=complete;
130  }
131  else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
132  {
133  qWarning("HttpRequest: expected body is too large");
134  status=abort;
135  }
136  else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
137  {
138  qWarning("HttpRequest: expected multipart body is too large");
139  status=abort;
140  }
141  else {
142  #ifdef SUPERVERBOSE
143  qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
144  #endif
145  status=waitForBody;
146  }
147  }
148 }
149 
150 void HttpRequest::readBody(QTcpSocket* socket)
151 {
152  Q_ASSERT(expectedBodySize!=0);
153  if (boundary.isEmpty())
154  {
155  // normal body, no multipart
156  #ifdef SUPERVERBOSE
157  qDebug("HttpRequest: receive body");
158  #endif
159  int toRead=expectedBodySize-bodyData.size();
160  QByteArray newData=socket->read(toRead);
161  currentSize+=newData.size();
162  bodyData.append(newData);
163  if (bodyData.size()>=expectedBodySize)
164  {
165  status=complete;
166  }
167  }
168  else
169  {
170  // multipart body, store into temp file
171  #ifdef SUPERVERBOSE
172  qDebug("HttpRequest: receiving multipart body");
173  #endif
174  // Create an object for the temporary file, if not already present
175  if (tempFile == nullptr)
176  {
177  tempFile = new QTemporaryFile;
178  }
179  if (!tempFile->isOpen())
180  {
181  tempFile->open();
182  }
183  // Transfer data in 64kb blocks
184  qint64 fileSize=tempFile->size();
185  qint64 toRead=expectedBodySize-fileSize;
186  if (toRead>65536)
187  {
188  toRead=65536;
189  }
190  fileSize+=tempFile->write(socket->read(toRead));
191  if (fileSize>=maxMultiPartSize)
192  {
193  qWarning("HttpRequest: received too many multipart bytes");
194  status=abort;
195  }
196  else if (fileSize>=expectedBodySize)
197  {
198  #ifdef SUPERVERBOSE
199  qDebug("HttpRequest: received whole multipart body");
200  #endif
201  tempFile->flush();
202  if (tempFile->error())
203  {
204  qCritical("HttpRequest: Error writing temp file for multipart body");
205  }
206  parseMultiPartFile();
207  tempFile->close();
208  status=complete;
209  }
210  }
211 }
212 
213 void HttpRequest::decodeRequestParams()
214 {
215  #ifdef SUPERVERBOSE
216  qDebug("HttpRequest: extract and decode request parameters");
217  #endif
218  // Get URL parameters
219  QByteArray rawParameters;
220  int questionMark=path.indexOf('?');
221  if (questionMark>=0)
222  {
223  rawParameters=path.mid(questionMark+1);
224  path=path.left(questionMark);
225  }
226  // Get request body parameters
227  QByteArray contentType=headers.value("content-type");
228  if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded")))
229  {
230  if (!rawParameters.isEmpty())
231  {
232  rawParameters.append('&');
233  rawParameters.append(bodyData);
234  }
235  else
236  {
237  rawParameters=bodyData;
238  }
239  }
240  // Split the parameters into pairs of value and name
241  QList<QByteArray> list=rawParameters.split('&');
242  foreach (QByteArray part, list)
243  {
244  int equalsChar=part.indexOf('=');
245  if (equalsChar>=0)
246  {
247  QByteArray name=part.left(equalsChar).trimmed();
248  QByteArray value=part.mid(equalsChar+1).trimmed();
249  parameters.insert(urlDecode(name),urlDecode(value));
250  }
251  else if (!part.isEmpty())
252  {
253  // Name without value
254  parameters.insert(urlDecode(part),"");
255  }
256  }
257 }
258 
259 void HttpRequest::extractCookies()
260 {
261  #ifdef SUPERVERBOSE
262  qDebug("HttpRequest: extract cookies");
263  #endif
264  foreach(QByteArray cookieStr, headers.values("cookie"))
265  {
266  QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
267  foreach(QByteArray part, list)
268  {
269  #ifdef SUPERVERBOSE
270  qDebug("HttpRequest: found cookie %s",part.data());
271  #endif // Split the part into name and value
272  QByteArray name;
273  QByteArray value;
274  int posi=part.indexOf('=');
275  if (posi)
276  {
277  name=part.left(posi).trimmed();
278  value=part.mid(posi+1).trimmed();
279  }
280  else
281  {
282  name=part.trimmed();
283  value="";
284  }
285  cookies.insert(name,value);
286  }
287  }
288  headers.remove("cookie");
289 }
290 
291 void HttpRequest::readFromSocket(QTcpSocket* socket)
292 {
293  Q_ASSERT(status!=complete);
294  if (status==waitForRequest)
295  {
296  readRequest(socket);
297  }
298  else if (status==waitForHeader)
299  {
300  readHeader(socket);
301  }
302  else if (status==waitForBody)
303  {
304  readBody(socket);
305  }
306  if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
307  {
308  qWarning("HttpRequest: received too many bytes");
309  status=abort;
310  }
311  if (status==complete)
312  {
313  // Extract and decode request parameters from url and body
314  decodeRequestParams();
315  // Extract cookies from headers
316  extractCookies();
317  }
318 }
319 
320 
322 {
323  return status;
324 }
325 
326 
327 QByteArray HttpRequest::getMethod() const
328 {
329  return method;
330 }
331 
332 
333 QByteArray HttpRequest::getPath() const
334 {
335  return urlDecode(path);
336 }
337 
338 
339 const QByteArray& HttpRequest::getRawPath() const
340 {
341  return path;
342 }
343 
344 
345 QByteArray HttpRequest::getVersion() const
346 {
347  return version;
348 }
349 
350 
351 QByteArray HttpRequest::getHeader(const QByteArray& name) const
352 {
353  return headers.value(name.toLower());
354 }
355 
356 QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
357 {
358  return headers.values(name.toLower());
359 }
360 
361 QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const
362 {
363  return headers;
364 }
365 
366 QByteArray HttpRequest::getParameter(const QByteArray& name) const
367 {
368  return parameters.value(name);
369 }
370 
371 QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
372 {
373  return parameters.values(name);
374 }
375 
376 QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const
377 {
378  return parameters;
379 }
380 
381 QByteArray HttpRequest::getBody() const
382 {
383  return bodyData;
384 }
385 
386 QByteArray HttpRequest::urlDecode(const QByteArray source)
387 {
388  QByteArray buffer(source);
389  buffer.replace('+',' ');
390  int percentChar=buffer.indexOf('%');
391  while (percentChar>=0)
392  {
393  bool ok;
394  int hexCode=buffer.mid(percentChar+1,2).toInt(&ok,16);
395  if (ok)
396  {
397  char c=char(hexCode);
398  buffer.replace(percentChar,3,&c,1);
399  }
400  percentChar=buffer.indexOf('%',percentChar+1);
401  }
402  return buffer;
403 }
404 
405 
406 void HttpRequest::parseMultiPartFile()
407 {
408  qDebug("HttpRequest: parsing multipart temp file");
409  tempFile->seek(0);
410  bool finished=false;
411  while (!tempFile->atEnd() && !finished && !tempFile->error())
412  {
413  #ifdef SUPERVERBOSE
414  qDebug("HttpRequest: reading multpart headers");
415  #endif
416  QByteArray fieldName;
417  QByteArray fileName;
418  while (!tempFile->atEnd() && !finished && !tempFile->error())
419  {
420  QByteArray line=tempFile->readLine(65536).trimmed();
421  if (line.startsWith("Content-Disposition:"))
422  {
423  if (line.contains("form-data"))
424  {
425  int start=line.indexOf(" name=\"");
426  int end=line.indexOf("\"",start+7);
427  if (start>=0 && end>=start)
428  {
429  fieldName=line.mid(start+7,end-start-7);
430  }
431  start=line.indexOf(" filename=\"");
432  end=line.indexOf("\"",start+11);
433  if (start>=0 && end>=start)
434  {
435  fileName=line.mid(start+11,end-start-11);
436  }
437  #ifdef SUPERVERBOSE
438  qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
439  #endif
440  }
441  else
442  {
443  qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
444  }
445  }
446  else if (line.isEmpty())
447  {
448  break;
449  }
450  }
451 
452  #ifdef SUPERVERBOSE
453  qDebug("HttpRequest: reading multpart data");
454  #endif
455  QTemporaryFile* uploadedFile=nullptr;
456  QByteArray fieldValue;
457  while (!tempFile->atEnd() && !finished && !tempFile->error())
458  {
459  QByteArray line=tempFile->readLine(65536);
460  if (line.startsWith("--"+boundary))
461  {
462  // Boundary found. Until now we have collected 2 bytes too much,
463  // so remove them from the last result
464  if (fileName.isEmpty() && !fieldName.isEmpty())
465  {
466  // last field was a form field
467  fieldValue.remove(fieldValue.size()-2,2);
468  parameters.insert(fieldName,fieldValue);
469  qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
470  }
471  else if (!fileName.isEmpty() && !fieldName.isEmpty())
472  {
473  // last field was a file
474  if (uploadedFile)
475  {
476  #ifdef SUPERVERBOSE
477  qDebug("HttpRequest: finishing writing to uploaded file");
478  #endif
479  uploadedFile->resize(uploadedFile->size()-2);
480  uploadedFile->flush();
481  uploadedFile->seek(0);
482  parameters.insert(fieldName,fileName);
483  qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
484  uploadedFiles.insert(fieldName,uploadedFile);
485  long int fileSize=(long int) uploadedFile->size();
486  qDebug("HttpRequest: uploaded file size is %li",fileSize);
487  }
488  else
489  {
490  qWarning("HttpRequest: format error, unexpected end of file data");
491  }
492  }
493  if (line.contains(boundary+"--"))
494  {
495  finished=true;
496  }
497  break;
498  }
499  else
500  {
501  if (fileName.isEmpty() && !fieldName.isEmpty())
502  {
503  // this is a form field.
504  currentSize+=line.size();
505  fieldValue.append(line);
506  }
507  else if (!fileName.isEmpty() && !fieldName.isEmpty())
508  {
509  // this is a file
510  if (!uploadedFile)
511  {
512  uploadedFile=new QTemporaryFile();
513  uploadedFile->open();
514  }
515  uploadedFile->write(line);
516  if (uploadedFile->error())
517  {
518  qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
519  }
520  }
521  }
522  }
523  }
524  if (tempFile->error())
525  {
526  qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile->errorString()));
527  }
528  #ifdef SUPERVERBOSE
529  qDebug("HttpRequest: finished parsing multipart temp file");
530  #endif
531 }
532 
534 {
535  foreach(QByteArray key, uploadedFiles.keys())
536  {
537  QTemporaryFile* file=uploadedFiles.value(key);
538  if (file->isOpen())
539  {
540  file->close();
541  }
542  delete file;
543  }
544  if (tempFile != nullptr)
545  {
546  if (tempFile->isOpen())
547  {
548  tempFile->close();
549  }
550  delete tempFile;
551  }
552 }
553 
554 QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
555 {
556  return uploadedFiles.value(fieldName);
557 }
558 
559 QByteArray HttpRequest::getCookie(const QByteArray& name) const
560 {
561  return cookies.value(name);
562 }
563 
565 QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap()
566 {
567  return cookies;
568 }
569 
575 QHostAddress HttpRequest::getPeerAddress() const
576 {
577  return peerAddress;
578 }
579 
static QList< QByteArray > splitCSV(const QByteArray source)
Split a string list into parts, where each part is delimited by semicolon.
Definition: httpcookie.cpp:240
QList< QByteArray > getParameters(const QByteArray &name) const
Get the values of a HTTP request parameter.
QMultiMap< QByteArray, QByteArray > getHeaderMap() const
Get all HTTP request headers.
RequestStatus getStatus() const
Get the status of this reqeust.
QMultiMap< QByteArray, QByteArray > getParameterMap() const
Get all HTTP request parameters.
HttpRequest(const QSettings *settings)
Constructor.
Definition: httprequest.cpp:13
QMap< QByteArray, QByteArray > & getCookieMap()
Get all cookies.
RequestStatus
Values for getStatus()
Definition: httprequest.h:44
QByteArray getHeader(const QByteArray &name) const
Get the value of a HTTP request header.
static QByteArray urlDecode(const QByteArray source)
Decode an URL parameter.
void readFromSocket(QTcpSocket *socket)
Read the HTTP request from a socket.
QByteArray getCookie(const QByteArray &name) const
Get the value of a cookie.
QByteArray getPath() const
Get the decoded path of the HTPP request (e.g.
QByteArray getBody() const
Get the HTTP request body.
QList< QByteArray > getHeaders(const QByteArray &name) const
Get the values of a HTTP request header.
QTemporaryFile * getUploadedFile(const QByteArray fieldName) const
Get an uploaded file.
QByteArray getParameter(const QByteArray &name) const
Get the value of a HTTP request parameter.
const QByteArray & getRawPath() const
Get the raw path of the HTTP request (e.g.
virtual ~HttpRequest()
Destructor.
QByteArray getVersion() const
Get the version of the HTPP request (e.g.
QByteArray getMethod() const
Get the method of the HTTP request (e.g.
QHostAddress getPeerAddress() const
Get the address of the connected client.