24 #include <QScopedPointer> 26 #include <QTextStream> 27 #include <QStringList> 47 #if defined(Q_OS_LINUX) 48 #include <sys/sendfile.h> 49 #include <sys/errno.h> 50 #elif defined(Q_OS_MAC) 51 #include <sys/socket.h> 67 QRegExp
gRegExp = QRegExp(
"[ \r\n][ \r\n]*");
91 m_cacheTag(QStringLiteral(
"")),
104 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"NULL Reader"));
110 QStringList items = Method.split(
gRegExp, QString::SkipEmptyParts);
113 if (!items.isEmpty())
115 item = items.takeFirst();
118 if (item.startsWith(QStringLiteral(
"HTTP")))
126 if (!items.isEmpty())
137 if (!items.isEmpty())
140 QUrl url = QUrl::fromEncoded(items.takeFirst().toUtf8());
144 int index = m_path.lastIndexOf(
'/');
147 m_method = m_path.mid(index + 1).trimmed();
148 m_path = m_path.left(index + 1).trimmed();
153 QStringList pairs = url.query().split(
'&');
154 foreach (
const QString &pair, pairs)
156 int index = pair.indexOf(
'=');
157 QString key = pair.left(index);
158 QString val = pair.mid(index + 1);
165 if (!items.isEmpty())
173 QString connection =
m_headers.value(QStringLiteral(
"Connection")).toLower();
175 if (connection == QStringLiteral(
"keep-alive"))
177 else if (connection == QStringLiteral(
"close"))
180 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"HTTP request: path '%1' method '%2'").arg(
m_path,
m_method));
323 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"'%1' not found").arg(
m_fullUrl));
327 QTextStream stream(&result);
328 stream <<
"<html><head><title>" <<
TORC_REALM <<
"</title></head>";
329 stream <<
"<body><h1><a href='/'>" << TORC_REALM <<
"</a></h1>";
330 stream <<
"<p>File not found";
331 stream <<
"</body></html>";
343 if (types.size() == 1)
344 contenttype = types.first();
350 QByteArray contentheader = QStringLiteral(
"Content-Type: %1\r\n").arg(contenttype).toLatin1();
354 qint64 sendsize = totalsize;
355 bool multipart =
false;
356 static QByteArray seperator(
"\r\n--STaRT\r\n");
357 QList<QByteArray> partheaders;
376 QVector<QPair<quint64,quint64> >::const_iterator it =
m_ranges.constBegin();
377 for ( ; it !=
m_ranges.constEnd(); ++it)
379 QByteArray header = seperator + contentheader + QStringLiteral(
"Content-Range: bytes %1\r\n\r\n").arg(
RangeToString((*it), totalsize)).toLatin1();
380 partheaders << header;
381 sendsize += header.size();
388 QScopedPointer<QByteArray> headers(
new QByteArray);
389 QTextStream response(headers.data());
392 response <<
"Date: " << QDateTime::currentDateTimeUtc().toString(
DateFormat) <<
"\r\n";
395 response <<
"Accept-Ranges: bytes\r\n";
399 response <<
"Cache-Control: private, no-cache, no-store, must-revalidate\r\nExpires: 0\r\nPragma: no-cache\r\n";
405 response <<
"Cache-Control: public, max-age=3600\r\n";
407 response <<
"Cache-Control: public, max-age=31536000\r\n";
413 response << QStringLiteral(
"ETag: \"%1\"\r\n").arg(
m_cacheTag);
415 response << QStringLiteral(
"Last-Modified: %1\r\n").arg(
m_cacheTag);
426 m_headers.contains(QStringLiteral(
"Accept-Encoding")) &&
427 m_headers.value(QStringLiteral(
"Accept-Encoding")).contains(QStringLiteral(
"gzip"), Qt::CaseInsensitive))
440 response <<
"Content-Encoding: gzip\r\n";
444 response <<
"Content-Type: multipart/byteranges; boundary=STaRT\r\n";
446 response << contentheader;
450 response <<
"Content-Length: " << QString::number(sendsize) <<
"\r\n";
455 response <<
"Content-Range: bytes */" << QString::number(totalsize) <<
"\r\n";
463 response << it.key().toLatin1() <<
": " << it.value().toLatin1() <<
"\r\n";
469 qint64 headersize = headers.data()->size();
470 qint64 sent = Socket->write(headers.data()->constData(), headersize);
471 if (headersize != sent)
472 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Buffer size %1 - but sent %2").arg(headersize).arg(sent));
474 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 header bytes").arg(sent));
476 LOG(VB_NETWORK, LOG_DEBUG, QString(headers->data()));
483 QVector<QPair<quint64,quint64> >::const_iterator it =
m_ranges.constBegin();
484 QList<QByteArray>::const_iterator bit = partheaders.constBegin();
485 for ( ; it !=
m_ranges.constEnd(); ++it, ++bit)
487 qint64 sent = Socket->write((*bit).data(), (*bit).size());
488 if ((*bit).size() != sent)
489 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Buffer size %1 - but sent %2").arg((*bit).size()).arg(sent));
491 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 multipart header bytes").arg(sent));
493 quint64 start = (*it).first;
494 qint64 chunksize = (*it).second - start + 1;
496 if (chunksize != sent)
497 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Buffer size %1 - but sent %2").arg(chunksize).arg(sent));
499 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 content bytes").arg(sent));
504 qint64 size = sendsize;
508 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Buffer size %1 - but sent %2").arg(size).arg(sent));
510 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 content bytes").arg(sent));
516 file.open(QIODevice::ReadOnly);
522 QVector<QPair<quint64,quint64> >::const_iterator it =
m_ranges.constBegin();
523 QList<QByteArray>::const_iterator bit = partheaders.constBegin();
524 for ( ; it !=
m_ranges.constEnd(); ++it, ++bit)
526 off64_t sent = Socket->write((*bit).data(), (*bit).size());
527 if ((*bit).size() != sent)
528 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Buffer size %1 - but sent %2").arg((*bit).size()).arg(sent));
530 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 multipart header bytes").arg(sent));
533 off64_t offset = (*it).first;
534 off64_t size = (*it).second - offset + 1;
536 #if defined(Q_OS_LINUX) 545 off64_t send = sendfile64(Socket->socketDescriptor(), file.handle(), &offset, remaining);
549 if (errno == EAGAIN || errno == EWOULDBLOCK)
551 QThread::usleep(5000);
555 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
560 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 for %2").arg(send).arg(file.handle()));
567 #elif defined(Q_OS_MAC) 573 off64_t bytessent = 0;
574 off64_t off = offset;
579 if (sendfile(file.handle(), Socket->socketDescriptor(), off, &bytessent,
nullptr, 0) < 0)
583 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
587 QThread::usleep(5000);
591 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sent %1 for %2").arg(bytessent).arg(file.handle()));
607 qint64 read = file.read(buffer.data()->data(), remaining);
610 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error reading from '%1' (%2)").arg(file.fileName()).arg(file.errorString()));
614 qint64 send = Socket->write(buffer.data()->data(), read);
618 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1)").arg(Socket->errorString()));
627 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to send all data for '%1'").arg(file.fileName()));
635 off64_t size = sendsize;
638 #if defined(Q_OS_LINUX) 647 off64_t send = sendfile64(Socket->socketDescriptor(), file.handle(), &offset, remaining);
651 if (errno == EAGAIN || errno == EWOULDBLOCK)
653 QThread::usleep(5000);
657 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1 - '%2')").arg(errno).arg(strerror(errno)));
662 LOG(VB_NETWORK, LOG_DEBUG, QStringLiteral(
"Sent %1 for %2").arg(send).arg(file.handle()));
669 #elif defined(Q_OS_MAC) 682 if (sendfile(file.handle(), Socket->socketDescriptor(), off, &bytessent,
nullptr, 0) < 0)
686 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1) %2").arg(errno).arg(strerror(errno)));
690 QThread::usleep(5000);
694 LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral(
"Sent %1 for %2").arg(bytessent).arg(file.handle()));
711 qint64 read = file.read(buffer.data()->data(), remaining);
714 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error reading from '%1' (%2)").arg(file.fileName()).arg(file.errorString()));
718 qint64 send = Socket->write(buffer.data()->data(), read);
722 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error sending data (%1)").arg(Socket->errorString()));
731 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to send all data for '%1'").arg(file.fileName()));
740 Socket->disconnectFromHost();
752 if (Type == QStringLiteral(
"GET"))
return HTTPGet;
753 if (Type == QStringLiteral(
"HEAD"))
return HTTPHead;
754 if (Type == QStringLiteral(
"POST"))
return HTTPPost;
755 if (Type == QStringLiteral(
"PUT"))
return HTTPPut;
756 if (Type == QStringLiteral(
"OPTIONS"))
return HTTPOptions;
757 if (Type == QStringLiteral(
"DELETE"))
return HTTPDelete;
766 case HTTPHead:
return QStringLiteral(
"HEAD");
767 case HTTPGet:
return QStringLiteral(
"GET");
768 case HTTPPost:
return QStringLiteral(
"POST");
769 case HTTPPut:
return QStringLiteral(
"PUT");
770 case HTTPDelete:
return QStringLiteral(
"DELETE");
771 case HTTPOptions:
return QStringLiteral(
"OPTIONS");
777 return QStringLiteral(
"UNKNOWN");
782 if (Protocol.startsWith(QStringLiteral(
"HTTP")))
784 if (Protocol.endsWith(QStringLiteral(
"1.1")))
return HTTPOneDotOne;
785 if (Protocol.endsWith(QStringLiteral(
"1.0")))
return HTTPOneDotZero;
794 if (Status.startsWith(QStringLiteral(
"200")))
return HTTP_OK;
801 if (Status.startsWith(QStringLiteral(
"402")))
return HTTP_Forbidden;
802 if (Status.startsWith(QStringLiteral(
"404")))
return HTTP_NotFound;
821 return QStringLiteral(
"Error");
829 case HTTP_OK:
return QStringLiteral(
"200 OK");
847 return QStringLiteral(
"Error");
855 case HTTPResponseXML:
return QStringLiteral(
"text/xml; charset=\"UTF-8\"");
856 case HTTPResponseHTML:
return QStringLiteral(
"text/html; charset=\"UTF-8\"");
872 return QStringLiteral(
"text/plain");
879 if (Allowed &
HTTPHead) result << QStringLiteral(
"HEAD");
880 if (Allowed &
HTTPGet) result << QStringLiteral(
"GET");
881 if (Allowed &
HTTPPost) result << QStringLiteral(
"POST");
882 if (Allowed &
HTTPPut) result << QStringLiteral(
"PUT");
883 if (Allowed &
HTTPDelete) result << QStringLiteral(
"DELETE");
884 if (Allowed &
HTTPOptions) result << QStringLiteral(
"OPTIONS");
886 return result.join(QStringLiteral(
", "));
898 return QStringLiteral();
905 if (Allowed.contains(QStringLiteral(
"HEAD"), Qt::CaseInsensitive)) allowed +=
HTTPHead;
906 if (Allowed.contains(QStringLiteral(
"GET"), Qt::CaseInsensitive)) allowed +=
HTTPGet;
907 if (Allowed.contains(QStringLiteral(
"POST"), Qt::CaseInsensitive)) allowed +=
HTTPPost;
908 if (Allowed.contains(QStringLiteral(
"PUT"), Qt::CaseInsensitive)) allowed +=
HTTPPut;
909 if (Allowed.contains(QStringLiteral(
"DELETE"), Qt::CaseInsensitive)) allowed +=
HTTPDelete;
910 if (Allowed.contains(QStringLiteral(
"OPTIONS"), Qt::CaseInsensitive)) allowed +=
HTTPOptions;
911 if (Allowed.contains(QStringLiteral(
"AUTH"), Qt::CaseInsensitive)) allowed +=
HTTPAuth;
919 QVector<QPair<quint64,quint64> > results;
923 if (Ranges.contains(QStringLiteral(
"bytes"), Qt::CaseInsensitive))
925 QStringList newranges = Ranges.split(
'=');
926 if (newranges.size() == 2)
928 QStringList rangelist = newranges[1].split(
',', QString::SkipEmptyParts);
930 foreach (
const QString &range, rangelist)
932 QStringList parts = range.split(
'-');
934 if (parts.size() != 2)
940 bool havefirst = !parts[0].trimmed().isEmpty();
941 bool havesecond = !parts[1].trimmed().isEmpty();
943 if (havefirst && havesecond)
945 start = parts[0].toULongLong(&ok);
952 end = parts[1].toULongLong(&ok);
959 else if (havefirst && !havesecond)
961 start = parts[0].toULongLong(&ok);
964 if (ok && start >= Size)
969 else if (!havefirst && havesecond)
972 start = parts[1].toULongLong(&ok);
975 start = Size - start;
985 results << QPair<quint64,quint64>(start, end);
986 tosend += end - start + 1;
992 if (results.isEmpty())
995 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error parsing byte ranges ('%1')").arg(Ranges));
1002 while (results.size() > 1 && check)
1005 quint64 laststart = results[0].first;
1006 quint64 lastend = results[0].second;
1008 for (
int i = 1; i < results.size(); ++i)
1010 quint64 thisstart = results[i].first;
1011 quint64 thisend = results[i].second;
1013 if (thisstart > lastend)
1015 laststart = thisstart;
1020 tosend -= (lastend - laststart + 1);
1021 tosend -= (thisend - thisstart + 1);
1022 results[i - 1].first = qMin(laststart, thisstart);
1023 results[i - 1].second = qMax(lastend, thisend);
1024 tosend += results[i - 1].second - results[i - 1].first + 1;
1025 results.removeAt(i);
1032 SizeToSend = tosend;
1040 return QStringLiteral(
"%1-%2/%3").arg(Range.first).arg(Range.second).arg(Size);
1061 QDateTime since = QDateTime::fromString(
m_headers.value(QStringLiteral(
"If-Modified-Since")),
DateFormat);
1063 if (LastModified <= since)
void SetAllowed(int Allowed)
QString GetMethod(void) const
bool GetAllowCORS(void) const
QMap< QString, QString > m_queries
HTTPRequestType GetHTTPRequestType(void) const
QByteArray m_responseContent
static QString RequestTypeToString(HTTPRequestType Type)
static QStringList MimeTypeForFileName(const QString &FileName)
Return a list of possible MIME types for the file named Name.
QString GetCache(void) const
bool HasZlib(void)
Return true if zlib support is available.
void SetConnection(HTTPConnection Connection)
A convenience class to read HTTP requests from a QTcpSocket.
HTTPStatus m_responseStatus
HTTPStatus GetHTTPStatus(void) const
static QString MimeTypeForFileNameAndData(const QString &FileName, QIODevice *Device)
Return a MIME type for the media described by FileName and Device.
static QString ResponseTypeToString(HTTPResponseType Response)
static QString StatusToString(HTTPStatus Status)
void Initialise(const QString &Method)
HTTPRequestType m_requestType
void SetAllowCORS(bool Allowed)
HTTPAuthorisation IsAuthorised(void) const
bool Unmodified(void)
Check whether the resource is equivalent to the last seen version.
void Serialise(const QVariant &Data, const QString &Type)
static HTTPRequestType RequestTypeFromString(const QString &Type)
void Serialise(QByteArray &Dest, const QVariant &Data, const QString &Type=QString())
static TorcSerialiser * GetSerialiser(const QString &MimeType)
QString GetPath(void) const
const QMap< QString, QString > & Headers(void) const
void SetCache(int Cache, const QString &Tag=QStringLiteral(""))
Set the caching behaviour for this response.
void SetResponseContent(const QByteArray &Content)
QString GetMethod(void) const
void SetAllowGZip(bool Allowed)
Allow gzip compression for the contents of this request.
QMap< QString, QString > m_headers
virtual HTTPResponseType ResponseType(void)=0
TorcHTTPRequest(TorcHTTPReader *Reader)
void SetResponseHeader(const QString &Header, const QString &Value)
static QVector< QPair< quint64, quint64 > > StringToRanges(const QString &Ranges, qint64 Size, qint64 &SizeToSend)
HTTPAuthorisation m_authorised
static QString ProtocolToString(HTTPProtocol Protocol)
void Redirected(const QString &Redirected)
static int StringToAllowed(const QString &Allowed)
void SetSecure(bool Secure)
void SetStatus(HTTPStatus Status)
#define LOG(_MASK_, _LEVEL_, _STRING_)
static HTTPProtocol ProtocolFromString(const QString &Protocol)
static QString PlatformName(void)
void TakeRequest(QByteArray &Content, QMap< QString, QString > &Headers)
Take ownership of the contents and headers. New owner is responsible for deleting.
static QString RangeToString(QPair< quint64, quint64 > Range, qint64 Size)
void SetResponseFile(const QString &File)
void SetResponseType(HTTPResponseType Type)
QString GetUrl(void) const
QVector< QPair< quint64, quint64 > > m_ranges
static HTTPStatus StatusFromString(const QString &Status)
QByteArray GZipCompressFile(QFile &Source)
Compress the given file using GZip.
static QString ConnectionToString(HTTPConnection Connection)
HTTPProtocol GetHTTPProtocol(void) const
QByteArray GZipCompress(QByteArray &Source)
Compress the supplied data using GZip.
HTTPConnection m_connection
QMap< QString, QString > m_responseHeaders
HTTPType GetHTTPType(void) const
const QMap< QString, QString > & Queries(void) const
void Authorise(HTTPAuthorisation Authorisation)
static QString AllowedToString(int Allowed)
HTTPResponseType m_responseType
void Respond(QTcpSocket *Socket)