Files
cantata/web/http/httprequest.cpp
Craig Drummond 605af484f7 Initial import of *VERY* incomplete, and not fully functional Cantata
webapp. Mainly used to test SQLite backend for storing MPD db.
2015-06-01 22:57:49 +01:00

338 lines
10 KiB
C++

/**
* @file
*
* @author Stefan Frings
* @author Petr Bravenec petr.bravenec@hobrasoft.cz
* @author Craig Drummond
*/
#include "httprequest.h"
#include "httpconnection.h"
#include "basichttpserver.h"
#include <QList>
#include <QDir>
#include <QSettings>
#include <QDebug>
#define DBUG if (BasicHttpServer::debugEnabled()) qWarning() << "HttpRequest" << (void *)this << __FUNCTION__
static int maxRequestSize=16384;
static int maxMultiPartSize=1048576;
void HttpRequest::init(const QSettings *settings)
{
maxRequestSize=settings->value("http/maxRequestSize", maxRequestSize).toInt();
maxMultiPartSize=settings->value("http/maxMultiPartSize", maxMultiPartSize).toInt();
}
void HttpRequest::reset()
{
stat=StatusWaitForRequest;
currentSize=0;
expectedBodySize=0;
methd=Method_Unknown;
}
void HttpRequest::readFromSocket(QTcpSocket *socket)
{
switch(stat) {
case StatusComplete:
return;
break;
case StatusAbort:
return;
break;
case StatusWaitForRequest:
readRequest(socket);
break;
case StatusWaitForHeader:
readHeader(socket);
break;
case StatusWaitForBody:
readBody(socket);
break;
}
if (currentSize > maxRequestSize) {
stat = StatusAbort;
}
if (StatusComplete==stat) {
decodeRequestParams();
extractCookies();
}
}
void HttpRequest::readRequest(QTcpSocket *socket)
{
int toRead = maxRequestSize - currentSize + 1;
QByteArray newdata = socket->readLine(toRead).trimmed();
currentSize += newdata.size();
if (newdata.isEmpty()) {
return;
}
QList<QByteArray> list = newdata.split(' ');
if (list.size() != 3 || !list[2].contains("HTTP")) {
DBUG << "received broken HTTP request, invalid first line";
stat = StatusAbort;
return;
}
QByteArray m=list[0];
if ("GET"==m) {
methd=Method_Get;
} else if ("POST"==m) {
methd=Method_Post;
} else if ("PUT"==m) {
methd=Method_Put;
} else if ("DELETE"==m) {
methd=Method_Delete;
}
pth = urlDecode(list[1]);
stat = StatusWaitForHeader;
}
void HttpRequest::readHeader(QTcpSocket *socket)
{
int toRead = maxRequestSize - currentSize + 1;
QByteArray newdata = socket->readLine(toRead).trimmed();
currentSize += newdata.size();
int colonpos = newdata.indexOf(':');
if (colonpos > 0) {
currentHeader = newdata.left(colonpos);
QByteArray value = newdata.mid(colonpos+1).trimmed();
hdrs.insert(currentHeader, value);
return;
}
if (colonpos <= 0 && !newdata.isEmpty()) {
hdrs.insert(currentHeader, hdrs.value(currentHeader)+" "+newdata);
return;
}
if (newdata.isEmpty()) {
QByteArray contentType = hdrs.value("Content-Type");
if (contentType.startsWith("multipart/form-data")) {
int pos = contentType.indexOf("boundary=");
if (pos >= 0) {
boundary = contentType.mid(pos+9);
}
}
expectedBodySize = hdrs.value("Content-Length").toInt();
if (expectedBodySize <= 0) {
stat = StatusComplete;
return;
}
if (boundary.isEmpty() && expectedBodySize + currentSize > maxRequestSize) {
DBUG << "expected body is too large";
stat = StatusAbort;
return;
}
if (!boundary.isEmpty() && expectedBodySize > maxMultiPartSize) {
DBUG << "expected multipart body is too large";
stat = StatusAbort;
return;
}
stat = StatusWaitForBody;
}
}
void HttpRequest::readBody(QTcpSocket *socket)
{
// normal body, no multipart
if (boundary.isEmpty()) {
int toRead = expectedBodySize - bodyData.size();
QByteArray newdata = socket->read(toRead);
currentSize += newdata.size();
bodyData.append(newdata);
if (bodyData.size() >= expectedBodySize) {
stat = StatusComplete;
}
return;
}
if (!boundary.isEmpty()) {
if (!tempFile.isOpen()) {
tempFile.open();
}
int filesize = tempFile.size();
int toRead = expectedBodySize - filesize;
if (toRead > 65536) {
toRead = 65536;
}
filesize += tempFile.write(socket->read(toRead));
if (filesize >= maxMultiPartSize) {
DBUG << "received too many multipart bytes";
stat = StatusAbort;
return;
}
if (filesize >= expectedBodySize) {
tempFile.flush();
parseMultiPartFile();
tempFile.close();
stat = StatusComplete;
}
}
}
void HttpRequest::decodeRequestParams()
{
QByteArray rawParameters;
if (hdrs.value("Content-Type") == "application/x-www-form-urlencoded") {
rawParameters = bodyData;
} else {
int questionmarkpos = pth.indexOf('?');
if (questionmarkpos >= 0) {
rawParameters = pth.mid(questionmarkpos+1);
pth = pth.left(questionmarkpos);
}
}
QList<QByteArray> list = rawParameters.split('&');
foreach (const QByteArray &part, list) {
int eqpos = part.indexOf('=');
if (eqpos >= 0) {
QByteArray name = urlDecode(part.left(eqpos).trimmed());
QByteArray value = urlDecode(part.mid(eqpos+1).trimmed());
params.insert(name, value);
continue;
}
if (!part.isEmpty()) {
QByteArray name = urlDecode(part);
params.insert(name, "");
}
}
}
void HttpRequest::extractCookies()
{
QList<QByteArray> cookieList = hdrs.values("Cookie");
foreach (const QByteArray &cook, cookieList) {
QList<QByteArray> parts = cook.split(';');
foreach (const QByteArray &part, parts) {
int eqpos = part.indexOf('=');
if (eqpos >= 0) {
QByteArray name = part.left(eqpos).trimmed();
if (name.startsWith('$')) {
continue;
}
cookies.insert(name, part.mid(eqpos+1).trimmed());
continue;
}
if (eqpos < 0) {
cookies.insert(part, "");
continue;
}
}
}
hdrs.remove("Cookie");
}
QByteArray HttpRequest::urlDecode(const QByteArray &text)
{
QByteArray buffer(text);
buffer.replace('+', ' ');
int percentPos=buffer.indexOf('%');
while (percentPos>=0) {
bool ok;
char byte=buffer.mid(percentPos+1, 2).toInt(&ok, 16);
if (ok) {
buffer.replace(percentPos, 3, (char*)&byte, 1);
}
percentPos=buffer.indexOf('%', percentPos+1);
}
return buffer;
}
void HttpRequest::parseMultiPartFile()
{
DBUG << "parsing multipart temp file";
tempFile.seek(0);
bool finished=false;
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
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);
}
} else {
DBUG << "ignoring unsupported content part" << line.data();
}
} else if (line.isEmpty()) {
break;
}
}
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);
params.insert(fieldName,fieldValue);
DBUG << "set parameter" << fieldName.data() << fieldValue.data();
}
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
// last field was a file
uploadedFile->resize(uploadedFile->size()-2);
uploadedFile->flush();
uploadedFile->seek(0);
params.insert(fieldName,fileName);
DBUG << "set parameter" << fieldName.data() << fileName.data();
files.insert(fieldName,uploadedFile);
DBUG << "uploaded file size is" << 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: error writing temp file, %s", qPrintable(uploadedFile->errorString()));
}
}
}
}
}
if (tempFile.error()) {
qCritical("HttpRequest: cannot read temp file, %s", qPrintable(tempFile.errorString()));
}
}