11using namespace stefanfrings;
13HttpRequest::HttpRequest(
const QSettings* settings)
15 status=waitForRequest;
18 maxSize=settings->value(
"maxRequestSize",
"16000").toInt();
19 maxMultiPartSize=settings->value(
"maxMultiPartSize",
"1000000").toInt();
24void HttpRequest::readRequest(QTcpSocket* socket)
27 qDebug(
"HttpRequest: read request");
29 int toRead=maxSize-currentSize+1;
30 QByteArray dataRead = socket->readLine(toRead);
31 currentSize += dataRead.size();
32 lineBuffer.append(dataRead);
33 if (!lineBuffer.contains(
"\r\n"))
36 qDebug(
"HttpRequest: collecting more parts until line break");
40 QByteArray newData=lineBuffer.trimmed();
42 if (!newData.isEmpty())
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"))
48 qWarning(
"HttpRequest: received broken HTTP request, invalid first line");
53 method=list.at(0).trimmed();
56 peerAddress = socket->peerAddress();
62void HttpRequest::readHeader(QTcpSocket* socket)
64 int toRead=maxSize-currentSize+1;
65 QByteArray dataRead = socket->readLine(toRead);
66 currentSize += dataRead.size();
67 lineBuffer.append(dataRead);
68 if (!lineBuffer.contains(
"\r\n"))
71 qDebug(
"HttpRequest: collecting more parts until line break");
75 QByteArray newData=lineBuffer.trimmed();
77 int colon=newData.indexOf(
':');
81 currentHeader=newData.left(colon).toLower();
82 QByteArray value=newData.mid(colon+1).trimmed();
83 headers.insert(currentHeader,value);
85 qDebug(
"HttpRequest: received header %s: %s",currentHeader.data(),value.data());
88 else if (!newData.isEmpty())
92 qDebug(
"HttpRequest: read additional line of header");
95 if (headers.contains(currentHeader)) {
96 headers.insert(currentHeader,headers.value(currentHeader)+
" "+newData);
103 qDebug(
"HttpRequest: headers completed");
107 QByteArray contentType=headers.value(
"content-type");
108 if (contentType.startsWith(
"multipart/form-data"))
110 int posi=contentType.indexOf(
"boundary=");
112 boundary=contentType.mid(posi+9);
113 if (boundary.startsWith(
'"') && boundary.endsWith(
'"'))
115 boundary = boundary.mid(1,boundary.length()-2);
119 QByteArray contentLength=headers.value(
"content-length");
120 if (!contentLength.isEmpty())
122 expectedBodySize=contentLength.toInt();
124 if (expectedBodySize==0)
127 qDebug(
"HttpRequest: expect no body");
131 else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
133 qWarning(
"HttpRequest: expected body is too large");
136 else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
138 qWarning(
"HttpRequest: expected multipart body is too large");
143 qDebug(
"HttpRequest: expect %i bytes body",expectedBodySize);
150void HttpRequest::readBody(QTcpSocket* socket)
152 Q_ASSERT(expectedBodySize!=0);
153 if (boundary.isEmpty())
157 qDebug(
"HttpRequest: receive body");
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)
172 qDebug(
"HttpRequest: receiving multipart body");
175 if (tempFile ==
nullptr)
177 tempFile =
new QTemporaryFile;
179 if (!tempFile->isOpen())
184 qint64 fileSize=tempFile->size();
185 qint64 toRead=expectedBodySize-fileSize;
190 fileSize+=tempFile->write(socket->read(toRead));
191 if (fileSize>=maxMultiPartSize)
193 qWarning(
"HttpRequest: received too many multipart bytes");
196 else if (fileSize>=expectedBodySize)
199 qDebug(
"HttpRequest: received whole multipart body");
202 if (tempFile->error())
204 qCritical(
"HttpRequest: Error writing temp file for multipart body");
206 parseMultiPartFile();
213void HttpRequest::decodeRequestParams()
216 qDebug(
"HttpRequest: extract and decode request parameters");
219 QByteArray rawParameters;
220 int questionMark=path.indexOf(
'?');
223 rawParameters=path.mid(questionMark+1);
224 path=path.left(questionMark);
227 QByteArray contentType=headers.value(
"content-type");
228 if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith(
"application/x-www-form-urlencoded")))
230 if (!rawParameters.isEmpty())
232 rawParameters.append(
'&');
233 rawParameters.append(bodyData);
237 rawParameters=bodyData;
241 QList<QByteArray> list=rawParameters.split(
'&');
242 foreach (QByteArray part, list)
244 int equalsChar=part.indexOf(
'=');
247 QByteArray name=part.left(equalsChar).trimmed();
248 QByteArray value=part.mid(equalsChar+1).trimmed();
251 else if (!part.isEmpty())
259void HttpRequest::extractCookies()
262 qDebug(
"HttpRequest: extract cookies");
264 foreach(QByteArray cookieStr, headers.values(
"cookie"))
267 foreach(QByteArray part, list)
270 qDebug(
"HttpRequest: found cookie %s",part.data());
274 int posi=part.indexOf(
'=');
277 name=part.left(posi).trimmed();
278 value=part.mid(posi+1).trimmed();
285 cookies.insert(name,value);
288 headers.remove(
"cookie");
293 Q_ASSERT(status!=complete);
294 if (status==waitForRequest)
298 else if (status==waitForHeader)
302 else if (status==waitForBody)
306 if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
308 qWarning(
"HttpRequest: received too many bytes");
311 if (status==complete)
314 decodeRequestParams();
353 return headers.value(name.toLower());
358 return headers.values(name.toLower());
368 return parameters.value(name);
373 return parameters.values(name);
388 QByteArray buffer(source);
389 buffer.replace(
'+',
' ');
390 int percentChar=buffer.indexOf(
'%');
391 while (percentChar>=0)
394 int hexCode=buffer.mid(percentChar+1,2).toInt(&ok,16);
397 char c=char(hexCode);
398 buffer.replace(percentChar,3,&c,1);
400 percentChar=buffer.indexOf(
'%',percentChar+1);
406void HttpRequest::parseMultiPartFile()
408 qDebug(
"HttpRequest: parsing multipart temp file");
411 while (!tempFile->atEnd() && !finished && !tempFile->error())
414 qDebug(
"HttpRequest: reading multpart headers");
416 QByteArray fieldName;
418 while (!tempFile->atEnd() && !finished && !tempFile->error())
420 QByteArray line=tempFile->readLine(65536).trimmed();
421 if (line.startsWith(
"Content-Disposition:"))
423 if (line.contains(
"form-data"))
425 int start=line.indexOf(
" name=\"");
426 int end=line.indexOf(
"\"",start+7);
427 if (start>=0 && end>=start)
429 fieldName=line.mid(start+7,end-start-7);
431 start=line.indexOf(
" filename=\"");
432 end=line.indexOf(
"\"",start+11);
433 if (start>=0 && end>=start)
435 fileName=line.mid(start+11,end-start-11);
438 qDebug(
"HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
443 qDebug(
"HttpRequest: ignoring unsupported content part %s",line.data());
446 else if (line.isEmpty())
453 qDebug(
"HttpRequest: reading multpart data");
455 QTemporaryFile* uploadedFile=
nullptr;
456 QByteArray fieldValue;
457 while (!tempFile->atEnd() && !finished && !tempFile->error())
459 QByteArray line=tempFile->readLine(65536);
460 if (line.startsWith(
"--"+boundary))
464 if (fileName.isEmpty() && !fieldName.isEmpty())
467 fieldValue.remove(fieldValue.size()-2,2);
468 parameters.insert(fieldName,fieldValue);
469 qDebug(
"HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
471 else if (!fileName.isEmpty() && !fieldName.isEmpty())
477 qDebug(
"HttpRequest: finishing writing to uploaded file");
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);
490 qWarning(
"HttpRequest: format error, unexpected end of file data");
493 if (line.contains(boundary+
"--"))
501 if (fileName.isEmpty() && !fieldName.isEmpty())
504 currentSize+=line.size();
505 fieldValue.append(line);
507 else if (!fileName.isEmpty() && !fieldName.isEmpty())
512 uploadedFile=
new QTemporaryFile();
513 uploadedFile->open();
515 uploadedFile->write(line);
516 if (uploadedFile->error())
518 qCritical(
"HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
524 if (tempFile->error())
526 qCritical(
"HttpRequest: cannot read temp file, %s",qPrintable(tempFile->errorString()));
529 qDebug(
"HttpRequest: finished parsing multipart temp file");
535 foreach(QByteArray key, uploadedFiles.keys())
537 QTemporaryFile* file=uploadedFiles.value(key);
544 if (tempFile !=
nullptr)
546 if (tempFile->isOpen())
556 return uploadedFiles.value(fieldName);
561 return cookies.value(name);
static QList< QByteArray > splitCSV(const QByteArray source)
Split a string list into parts, where each part is delimited by semicolon.
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()
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.