mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-26 02:20:26 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			589 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 | |
| 
 |