sdrangel/httpserver/httprequest.cpp

589 lines
17 KiB
C++

/**
@file
@author Stefan Frings
*/
#include "httprequest.h"
#include "httplistenersettings.h"
#include <QList>
#include <QDir>
#include "httpcookie.h"
using namespace qtwebapp;
HttpRequest::HttpRequest(QSettings* settings) :
useQtSettings(true)
{
Q_ASSERT(settings != 0);
status=waitForRequest;
currentSize=0;
expectedBodySize=0;
maxSize=settings->value("maxRequestSize","16000").toInt();
maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
tempFile=0;
}
HttpRequest::HttpRequest(const HttpListenerSettings* settings) :
useQtSettings(false)
{
Q_ASSERT(settings != 0);
status=waitForRequest;
currentSize=0;
expectedBodySize=0;
maxSize=settings->maxRequestSize;
maxMultiPartSize=settings->maxMultiPartSize;
tempFile=0;
}
void HttpRequest::readRequest(QTcpSocket* socket)
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readRequest: read request");
#endif
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
lineBuffer.append(socket->readLine(toRead));
currentSize+=lineBuffer.size();
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readRequest: collecting more parts until line break");
#endif
return;
}
QByteArray newData=lineBuffer.trimmed();
lineBuffer.clear();
if (!newData.isEmpty())
{
QList<QByteArray> list=newData.split(' ');
if (list.count()!=3 || !list.at(2).contains("HTTP"))
{
qWarning("HttpRequest::readRequest: received broken HTTP request, invalid first line");
status=abort;
}
else {
method=list.at(0).trimmed();
path=list.at(1);
version=list.at(2);
peerAddress = socket->peerAddress();
status=waitForHeader;
}
}
}
void HttpRequest::readHeader(QTcpSocket* socket)
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readHeader");
#endif
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
lineBuffer.append(socket->readLine(toRead));
currentSize+=lineBuffer.size();
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readHeader: 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::readHeader: 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::readHeader: 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::readHeader: 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::readHeader: expect no body");
#endif
status=complete;
}
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
{
qWarning("HttpRequest::readHeader: expected body is too large");
status=abort;
}
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
{
qWarning("HttpRequest::readHeader: expected multipart body is too large");
status=abort;
}
else {
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readHeader: 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::readBody: 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::readBody: receiving multipart body");
#endif
// Create an object for the temporary file, if not already present
if (tempFile == NULL)
{
tempFile = new QTemporaryFile;
}
if (!tempFile->isOpen())
{
tempFile->open();
}
// Transfer data in 64kb blocks
int fileSize=tempFile->size();
int toRead=expectedBodySize-fileSize;
if (toRead>65536)
{
toRead=65536;
}
fileSize+=tempFile->write(socket->read(toRead));
if (fileSize>=maxMultiPartSize)
{
qWarning("HttpRequest::readBody: received too many multipart bytes");
status=abort;
}
else if (fileSize>=expectedBodySize)
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::readBody: received whole multipart body");
#endif
tempFile->flush();
if (tempFile->error())
{
qCritical("HttpRequest::readBody: Error writing temp file for multipart body");
}
parseMultiPartFile();
tempFile->close();
status=complete;
}
}
}
void HttpRequest::decodeRequestParams()
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::decodeRequestParams: 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::extractCookies");
#endif
foreach(QByteArray cookieStr, headers.values("cookie"))
{
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
foreach(QByteArray part, list)
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::extractCookies: 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::readFromSocket: received too many bytes");
status=abort;
}
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;
}
QByteArray HttpRequest::getMethod() const
{
return method;
}
QByteArray HttpRequest::getPath() const
{
return urlDecode(path);
}
const QByteArray& HttpRequest::getRawPath() const
{
return path;
}
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());
}
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);
}
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const
{
return parameters;
}
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;
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
if (ok)
{
buffer.replace(percentChar,3,(char*)&byte,1);
}
percentChar=buffer.indexOf('%',percentChar+1);
}
return buffer;
}
void HttpRequest::parseMultiPartFile()
{
qDebug("HttpRequest::parseMultiPartFile: parsing multipart temp file");
tempFile->seek(0);
bool finished=false;
while (!tempFile->atEnd() && !finished && !tempFile->error())
{
#ifdef SUPERVERBOSE
qDebug("HttpRequest::parseMultiPartFile: 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::parseMultiPartFile: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
#endif
}
else
{
qDebug("HttpRequest::parseMultiPartFile: ignoring unsupported content part %s",line.data());
}
}
else if (line.isEmpty())
{
break;
}
}
#ifdef SUPERVERBOSE
qDebug("HttpRequest::parseMultiPartFile: reading multpart data");
#endif
QTemporaryFile* uploadedFile=0;
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
#ifdef SUPERVERBOSE
qDebug("HttpRequest::parseMultiPartFile: finishing writing to uploaded file");
#endif
parameters.insert(fieldName,fileName);
if (uploadedFile)
{
uploadedFile->resize(uploadedFile->size()-2);
uploadedFile->flush();
uploadedFile->seek(0);
qDebug("HttpRequest::parseMultiPartFile: set parameter %s=%s",fieldName.data(),fileName.data());
uploadedFiles.insert(fieldName,uploadedFile);
qDebug("HttpRequest::parseMultiPartFile: uploaded file size is %i",(int) uploadedFile->size());
}
}
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::parseMultiPartFile: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
}
}
}
}
}
if (tempFile->error())
{
qCritical("HttpRequest::parseMultiPartFile: cannot read temp file, %s",qPrintable(tempFile->errorString()));
}
#ifdef SUPERVERBOSE
qDebug("HttpRequest::parseMultiPartFile: 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 != NULL)
{
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 */
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap()
{
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).
*/
QHostAddress HttpRequest::getPeerAddress() const
{
return peerAddress;
}