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
11using namespace stefanfrings;
12
13HttpRequest::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
24void 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_broken;
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
62void 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_size;
135 }
136 else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
137 {
138 qWarning("HttpRequest: expected multipart body is too large");
139 status=abort_size;
140 }
141 else {
142 #ifdef SUPERVERBOSE
143 qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
144 #endif
145 status=waitForBody;
146 }
147 }
148}
149
150void 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_size;
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
213void 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
259void 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
291void 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_size;
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
327const QByteArray& HttpRequest::getMethod() const
328{
329 return method;
330}
331
332
333QByteArray HttpRequest::getPath() const
334{
335 return urlDecode(path);
336}
337
338
339const QByteArray& HttpRequest::getRawPath() const
340{
341 return path;
342}
343
344
345const QByteArray& HttpRequest::getVersion() const
346{
347 return version;
348}
349
350
351QByteArray HttpRequest::getHeader(const QByteArray& name) const
352{
353 return headers.value(name.toLower());
354}
355
356QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
357{
358 return headers.values(name.toLower());
359}
360
361const QMultiMap<QByteArray,QByteArray>& HttpRequest::getHeaderMap() const
362{
363 return headers;
364}
365
366QByteArray HttpRequest::getParameter(const QByteArray& name) const
367{
368 return parameters.value(name);
369}
370
371QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
372{
373 return parameters.values(name);
374}
375
376const QMultiMap<QByteArray,QByteArray>& HttpRequest::getParameterMap() const
377{
378 return parameters;
379}
380
381const QByteArray &HttpRequest::getBody() const
382{
383 return bodyData;
384}
385
386QByteArray 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
406void 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
554QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
555{
556 return uploadedFiles.value(fieldName);
557}
558
559QByteArray HttpRequest::getCookie(const QByteArray& name) const
560{
561 return cookies.value(name);
562}
563
565const QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() const
566{
567 return cookies;
568}
569
575const 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
const QMultiMap< QByteArray, QByteArray > & getHeaderMap() const
Get all HTTP request headers.
QList< QByteArray > getParameters(const QByteArray &name) const
Get the values of a HTTP request parameter.
RequestStatus getStatus() const
Get the status of this reqeust.
const QByteArray & getMethod() const
Get the method of the HTTP request (e.g.
RequestStatus
Values for getStatus()
Definition: httprequest.h:44
QByteArray getHeader(const QByteArray &name) const
Get the value of a HTTP request header.
const QMap< QByteArray, QByteArray > & getCookieMap() const
Get all cookies.
const QHostAddress & getPeerAddress() const
Get the address of the connected client.
const QMultiMap< QByteArray, QByteArray > & getParameterMap() const
Get all HTTP request parameters.
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.
const QByteArray & getBody() const
Get the HTTP request body.
QByteArray getPath() const
Get the decoded path of the HTPP request (e.g.
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.
const QByteArray & getVersion() const
Get the version of the HTPP request (e.g.