| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /**
- @file
- @author Stefan Frings
- */
- #include "httprequest.h"
- #include <QList>
- #include <QDir>
- #include "httpcookie.h"
- using namespace stefanfrings;
- HttpRequest::HttpRequest(const QSettings* settings)
- {
- status=waitForRequest;
- currentSize=0;
- expectedBodySize=0;
- maxSize=settings->value("maxRequestSize","16000").toInt();
- maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
- tempFile=nullptr;
- }
- void HttpRequest::readRequest(QTcpSocket* socket)
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: read request");
- #endif
- int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
- QByteArray dataRead = socket->readLine(toRead);
- currentSize += dataRead.size();
- lineBuffer.append(dataRead);
- if (!lineBuffer.contains("\r\n"))
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: collecting more parts until line break");
- #endif
- return;
- }
- QByteArray newData=lineBuffer.trimmed();
- lineBuffer.clear();
- if (!newData.isEmpty())
- {
- qDebug("HttpRequest: from %s: %s",qPrintable(socket->peerAddress().toString()),newData.data());
- QList<QByteArray> list=newData.split(' ');
- if (list.count()!=3 || !list.at(2).contains("HTTP"))
- {
- qWarning("HttpRequest: received broken HTTP request, invalid first line");
- status=abort_broken;
- }
- else
- {
- method=list.at(0).trimmed();
- path=list.at(1);
- version=list.at(2);
- peerAddress = socket->peerAddress();
- status=waitForHeader;
- }
- }
- }
- void HttpRequest::readHeader(QTcpSocket* socket)
- {
- int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
- QByteArray dataRead = socket->readLine(toRead);
- currentSize += dataRead.size();
- lineBuffer.append(dataRead);
- if (!lineBuffer.contains("\r\n"))
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: collecting more parts until line break");
- #endif
- return;
- }
- QByteArray newData=lineBuffer.trimmed();
- lineBuffer.clear();
- int colon=newData.indexOf(':');
- if (colon>0)
- {
- // Received a line with a colon - a header
- currentHeader=newData.left(colon).toLower();
- QByteArray value=newData.mid(colon+1).trimmed();
- headers.insert(currentHeader,value);
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
- #endif
- }
- else if (!newData.isEmpty())
- {
- // received another line - belongs to the previous header
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: read additional line of header");
- #endif
- // Received additional line of previous header
- if (headers.contains(currentHeader)) {
- headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
- }
- }
- else
- {
- // received an empty line - end of headers reached
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: headers completed");
- #endif
- // Empty line received, that means all headers have been received
- // Check for multipart/form-data
- QByteArray contentType=headers.value("content-type");
- if (contentType.startsWith("multipart/form-data"))
- {
- int posi=contentType.indexOf("boundary=");
- if (posi>=0) {
- boundary=contentType.mid(posi+9);
- if (boundary.startsWith('"') && boundary.endsWith('"'))
- {
- boundary = boundary.mid(1,boundary.length()-2);
- }
- }
- }
- QByteArray contentLength=headers.value("content-length");
- if (!contentLength.isEmpty())
- {
- expectedBodySize=contentLength.toInt();
- }
- if (expectedBodySize==0)
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: expect no body");
- #endif
- status=complete;
- }
- else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
- {
- qWarning("HttpRequest: expected body is too large");
- status=abort_size;
- }
- else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
- {
- qWarning("HttpRequest: expected multipart body is too large");
- status=abort_size;
- }
- else {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
- #endif
- status=waitForBody;
- }
- }
- }
- void HttpRequest::readBody(QTcpSocket* socket)
- {
- Q_ASSERT(expectedBodySize!=0);
- if (boundary.isEmpty())
- {
- // normal body, no multipart
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: receive body");
- #endif
- int toRead=expectedBodySize-bodyData.size();
- QByteArray newData=socket->read(toRead);
- currentSize+=newData.size();
- bodyData.append(newData);
- if (bodyData.size()>=expectedBodySize)
- {
- status=complete;
- }
- }
- else
- {
- // multipart body, store into temp file
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: receiving multipart body");
- #endif
- // Create an object for the temporary file, if not already present
- if (tempFile == nullptr)
- {
- tempFile = new QTemporaryFile;
- }
- if (!tempFile->isOpen())
- {
- tempFile->open();
- }
- // Transfer data in 64kb blocks
- qint64 fileSize=tempFile->size();
- qint64 toRead=expectedBodySize-fileSize;
- if (toRead>65536)
- {
- toRead=65536;
- }
- fileSize+=tempFile->write(socket->read(toRead));
- if (fileSize>=maxMultiPartSize)
- {
- qWarning("HttpRequest: received too many multipart bytes");
- status=abort_size;
- }
- else if (fileSize>=expectedBodySize)
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: received whole multipart body");
- #endif
- tempFile->flush();
- if (tempFile->error())
- {
- qCritical("HttpRequest: Error writing temp file for multipart body");
- }
- parseMultiPartFile();
- tempFile->close();
- status=complete;
- }
- }
- }
- void HttpRequest::decodeRequestParams()
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: extract and decode request parameters");
- #endif
- // Get URL parameters
- QByteArray rawParameters;
- int questionMark=path.indexOf('?');
- if (questionMark>=0)
- {
- rawParameters=path.mid(questionMark+1);
- path=path.left(questionMark);
- }
- // Get request body parameters
- QByteArray contentType=headers.value("content-type");
- if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded")))
- {
- if (!rawParameters.isEmpty())
- {
- rawParameters.append('&');
- rawParameters.append(bodyData);
- }
- else
- {
- rawParameters=bodyData;
- }
- }
- // Split the parameters into pairs of value and name
- QList<QByteArray> list=rawParameters.split('&');
- foreach (QByteArray part, list)
- {
- int equalsChar=part.indexOf('=');
- if (equalsChar>=0)
- {
- QByteArray name=part.left(equalsChar).trimmed();
- QByteArray value=part.mid(equalsChar+1).trimmed();
- parameters.insert(urlDecode(name),urlDecode(value));
- }
- else if (!part.isEmpty())
- {
- // Name without value
- parameters.insert(urlDecode(part),"");
- }
- }
- }
- void HttpRequest::extractCookies()
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: extract cookies");
- #endif
- foreach(QByteArray cookieStr, headers.values("cookie"))
- {
- QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
- foreach(QByteArray part, list)
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: found cookie %s",part.data());
- #endif // Split the part into name and value
- QByteArray name;
- QByteArray value;
- int posi=part.indexOf('=');
- if (posi)
- {
- name=part.left(posi).trimmed();
- value=part.mid(posi+1).trimmed();
- }
- else
- {
- name=part.trimmed();
- value="";
- }
- cookies.insert(name,value);
- }
- }
- headers.remove("cookie");
- }
- void HttpRequest::readFromSocket(QTcpSocket* socket)
- {
- Q_ASSERT(status!=complete);
- if (status==waitForRequest)
- {
- readRequest(socket);
- }
- else if (status==waitForHeader)
- {
- readHeader(socket);
- }
- else if (status==waitForBody)
- {
- readBody(socket);
- }
- if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
- {
- qWarning("HttpRequest: received too many bytes");
- status=abort_size;
- }
- if (status==complete)
- {
- // Extract and decode request parameters from url and body
- decodeRequestParams();
- // Extract cookies from headers
- extractCookies();
- }
- }
- HttpRequest::RequestStatus HttpRequest::getStatus() const
- {
- return status;
- }
- const QByteArray& HttpRequest::getMethod() const
- {
- return method;
- }
- QByteArray HttpRequest::getPath() const
- {
- return urlDecode(path);
- }
- const QByteArray& HttpRequest::getRawPath() const
- {
- return path;
- }
- const QByteArray& HttpRequest::getVersion() const
- {
- return version;
- }
- QByteArray HttpRequest::getHeader(const QByteArray& name) const
- {
- return headers.value(name.toLower());
- }
- QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
- {
- return headers.values(name.toLower());
- }
- const QMultiMap<QByteArray,QByteArray>& HttpRequest::getHeaderMap() const
- {
- return headers;
- }
- QByteArray HttpRequest::getParameter(const QByteArray& name) const
- {
- return parameters.value(name);
- }
- QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
- {
- return parameters.values(name);
- }
- const QMultiMap<QByteArray,QByteArray>& HttpRequest::getParameterMap() const
- {
- return parameters;
- }
- const QByteArray &HttpRequest::getBody() const
- {
- return bodyData;
- }
- QByteArray HttpRequest::urlDecode(const QByteArray source)
- {
- QByteArray buffer(source);
- buffer.replace('+',' ');
- int percentChar=buffer.indexOf('%');
- while (percentChar>=0)
- {
- bool ok;
- int hexCode=buffer.mid(percentChar+1,2).toInt(&ok,16);
- if (ok)
- {
- char c=char(hexCode);
- buffer.replace(percentChar,3,&c,1);
- }
- percentChar=buffer.indexOf('%',percentChar+1);
- }
- return buffer;
- }
- void HttpRequest::parseMultiPartFile()
- {
- qDebug("HttpRequest: parsing multipart temp file");
- tempFile->seek(0);
- bool finished=false;
- while (!tempFile->atEnd() && !finished && !tempFile->error())
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: reading multpart headers");
- #endif
- QByteArray fieldName;
- QByteArray fileName;
- while (!tempFile->atEnd() && !finished && !tempFile->error())
- {
- QByteArray line=tempFile->readLine(65536).trimmed();
- if (line.startsWith("Content-Disposition:"))
- {
- if (line.contains("form-data"))
- {
- int start=line.indexOf(" name=\"");
- int end=line.indexOf("\"",start+7);
- if (start>=0 && end>=start)
- {
- fieldName=line.mid(start+7,end-start-7);
- }
- start=line.indexOf(" filename=\"");
- end=line.indexOf("\"",start+11);
- if (start>=0 && end>=start)
- {
- fileName=line.mid(start+11,end-start-11);
- }
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
- #endif
- }
- else
- {
- qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
- }
- }
- else if (line.isEmpty())
- {
- break;
- }
- }
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: reading multpart data");
- #endif
- QTemporaryFile* uploadedFile=nullptr;
- QByteArray fieldValue;
- while (!tempFile->atEnd() && !finished && !tempFile->error())
- {
- QByteArray line=tempFile->readLine(65536);
- if (line.startsWith("--"+boundary))
- {
- // Boundary found. Until now we have collected 2 bytes too much,
- // so remove them from the last result
- if (fileName.isEmpty() && !fieldName.isEmpty())
- {
- // last field was a form field
- fieldValue.remove(fieldValue.size()-2,2);
- parameters.insert(fieldName,fieldValue);
- qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
- }
- else if (!fileName.isEmpty() && !fieldName.isEmpty())
- {
- // last field was a file
- if (uploadedFile)
- {
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: finishing writing to uploaded file");
- #endif
- uploadedFile->resize(uploadedFile->size()-2);
- uploadedFile->flush();
- uploadedFile->seek(0);
- parameters.insert(fieldName,fileName);
- qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
- uploadedFiles.insert(fieldName,uploadedFile);
- long int fileSize=(long int) uploadedFile->size();
- qDebug("HttpRequest: uploaded file size is %li",fileSize);
- }
- else
- {
- qWarning("HttpRequest: format error, unexpected end of file data");
- }
- }
- if (line.contains(boundary+"--"))
- {
- finished=true;
- }
- break;
- }
- else
- {
- if (fileName.isEmpty() && !fieldName.isEmpty())
- {
- // this is a form field.
- currentSize+=line.size();
- fieldValue.append(line);
- }
- else if (!fileName.isEmpty() && !fieldName.isEmpty())
- {
- // this is a file
- if (!uploadedFile)
- {
- uploadedFile=new QTemporaryFile();
- uploadedFile->open();
- }
- uploadedFile->write(line);
- if (uploadedFile->error())
- {
- qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
- }
- }
- }
- }
- }
- if (tempFile->error())
- {
- qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile->errorString()));
- }
- #ifdef SUPERVERBOSE
- qDebug("HttpRequest: finished parsing multipart temp file");
- #endif
- }
- HttpRequest::~HttpRequest()
- {
- foreach(QByteArray key, uploadedFiles.keys())
- {
- QTemporaryFile* file=uploadedFiles.value(key);
- if (file->isOpen())
- {
- file->close();
- }
- delete file;
- }
- if (tempFile != nullptr)
- {
- if (tempFile->isOpen())
- {
- tempFile->close();
- }
- delete tempFile;
- }
- }
- QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
- {
- return uploadedFiles.value(fieldName);
- }
- QByteArray HttpRequest::getCookie(const QByteArray& name) const
- {
- return cookies.value(name);
- }
- /** Get the map of cookies */
- const QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() const
- {
- return cookies;
- }
- /**
- Get the address of the connected client.
- Note that multiple clients may have the same IP address, if they
- share an internet connection (which is very common).
- */
- const QHostAddress& HttpRequest::getPeerAddress() const
- {
- return peerAddress;
- }
|