19 #include <QStringList>
20 #include <QCryptographicHash>
24 #include "mythconfig.h"
25 #if !( CONFIG_DARWIN || CONFIG_CYGWIN || defined(__FreeBSD__) || defined(_WIN32))
26 #define USE_SETSOCKOPT
27 #include <sys/sendfile.h>
33 #include <sys/types.h>
38 #include <netinet/tcp.h>
65 {
"gif" ,
"image/gif" },
66 {
"ico" ,
"image/x-icon" },
67 {
"jpeg",
"image/jpeg" },
68 {
"jpg" ,
"image/jpeg" },
69 {
"mng" ,
"image/x-mng" },
70 {
"png" ,
"image/png" },
71 {
"svg" ,
"image/svg+xml" },
72 {
"svgz",
"image/svg+xml" },
73 {
"tif" ,
"image/tiff" },
74 {
"tiff",
"image/tiff" },
76 {
"htm" ,
"text/html" },
77 {
"html",
"text/html" },
78 {
"qsp" ,
"text/html" },
79 {
"txt" ,
"text/plain" },
80 {
"xml" ,
"text/xml" },
81 {
"qxml",
"text/xml" },
82 {
"xslt",
"text/xml" },
83 {
"css" ,
"text/css" },
85 {
"crt" ,
"application/x-x509-ca-cert" },
86 {
"doc" ,
"application/vnd.ms-word" },
87 {
"gz" ,
"application/x-tar" },
88 {
"js" ,
"application/javascript" },
89 {
"m3u" ,
"application/x-mpegurl" },
90 {
"m3u8",
"application/x-mpegurl" },
91 {
"ogx" ,
"application/ogg" },
92 {
"pdf" ,
"application/pdf" },
93 {
"pem" ,
"application/x-x509-ca-cert" },
94 {
"qjs" ,
"application/javascript" },
95 {
"rm" ,
"application/vnd.rn-realmedia" },
96 {
"swf" ,
"application/x-shockwave-flash" },
97 {
"xls" ,
"application/vnd.ms-excel" },
98 {
"zip" ,
"application/x-tar" },
100 {
"aac" ,
"audio/mp4" },
101 {
"ac3" ,
"audio/vnd.dolby.dd-raw" },
102 {
"flac",
"audio/x-flac" },
103 {
"m4a" ,
"audio/x-m4a" },
104 {
"mid" ,
"audio/midi" },
105 {
"mka" ,
"audio/x-matroska" },
106 {
"mp3" ,
"audio/mpeg" },
107 {
"oga" ,
"audio/ogg" },
108 {
"ogg" ,
"audio/ogg" },
109 {
"wav" ,
"audio/wav" },
110 {
"wma" ,
"audio/x-ms-wma" },
112 {
"3gp" ,
"video/3gpp" },
113 {
"3g2" ,
"video/3gpp2" },
114 {
"asx" ,
"video/x-ms-asf" },
115 {
"asf" ,
"video/x-ms-asf" },
116 {
"avi" ,
"video/x-msvideo" },
117 {
"m2p" ,
"video/mp2p" },
118 {
"m4v" ,
"video/mp4" },
119 {
"mpeg",
"video/mp2p" },
120 {
"mpeg2",
"video/mp2p" },
121 {
"mpg" ,
"video/mp2p" },
122 {
"mpg2",
"video/mp2p" },
123 {
"mov" ,
"video/quicktime" },
124 {
"mp4" ,
"video/mp4" },
125 {
"mkv" ,
"video/x-matroska" },
126 {
"nuv" ,
"video/nupplevideo" },
127 {
"ogv" ,
"video/ogg" },
128 {
"ps" ,
"video/mp2p" },
129 {
"ts" ,
"video/mp2t" },
130 {
"vob" ,
"video/mpeg" },
131 {
"wmv" ,
"video/x-ms-wmv" },
133 {
"ttf" ,
"font/ttf" },
134 {
"woff" ,
"font/woff" },
135 {
"woff2",
"font/woff2" }
148 "<TITLE>Error %1</TITLE>"
149 "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
151 "<BODY><H1>%2.</H1></BODY>"
154 #ifdef USE_SETSOCKOPT
168 if (!values.isEmpty())
169 return values.last();
193 LOG(VB_HTTP, LOG_INFO,
194 QString(
"HTTPRequest::SentRequestType( %1 ) - returning Unknown.")
267 if (sTransferMode.isEmpty())
271 sTransferMode =
"Streaming";
273 sTransferMode =
"Interactive";
276 if (sTransferMode ==
"Streaming")
278 else if (sTransferMode ==
"Background")
280 else if (sTransferMode ==
"Interactive")
285 SetResponseHeader(
"contentFeatures.dlna.org",
"DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000");
289 for (
const auto & value : values)
292 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
295 QMap<QString, QString>::iterator it;
298 LOG(VB_HTTP, LOG_INFO, QString(
"(Response Header) %1: %2").
arg(it.key()).arg(it.value()));
321 LOG(VB_HTTP, LOG_INFO,
322 QString(
"HTTPRequest::SendResponse( None ) :%1 -> %2:")
336 if (
file.exists() &&
file.size() < (2 * 1024 * 1024) &&
337 file.open(QIODevice::ReadOnly | QIODevice::Text))
346 [[clang::fallthrough]];
348 LOG(VB_HTTP, LOG_INFO,
349 QString(
"HTTPRequest::SendResponse( File ) :%1 -> %2:")
358 LOG(VB_HTTP, LOG_INFO,
359 QString(
"HTTPRequest::SendResponse(xml/html) (%1) :%2 -> %3: %4")
367 #ifdef USE_SETSOCKOPT
388 LOG(VB_HTTP, LOG_INFO,
389 QString(
"HTTPRequest::SendResponse(%1) - Cached")
401 int nContentLen =
m_response.buffer().length();
407 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
408 std::cout <<
m_response.buffer().constData() << std::endl;
411 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Content Length: %1").
arg(nContentLen));
420 bool gzip_found = std::any_of(values.cbegin(), values.cend(),
421 [](
const auto & value)
422 {return value.contains(
"gzip" ); });
424 if (( nContentLen > 0 ) && gzip_found)
427 compBuffer.setData( compressed );
429 if (!compBuffer.buffer().isEmpty())
431 pBuffer = &compBuffer;
434 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Compressed Content Length: %1").
arg(compBuffer.buffer().length()));
442 nContentLen = pBuffer->buffer().length();
446 QByteArray sHeader = rHeader.toUtf8();
447 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").
arg(sHeader.length()));
448 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
450 if (nBytes < sHeader.length())
452 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponse(): "
453 "Incomplete write of header, "
455 .
arg(nBytes).
arg(sHeader.length()));
465 qint64 bytesWritten =
SendData( pBuffer, 0, nContentLen );
468 if (bytesWritten != nContentLen)
469 LOG(VB_HTTP, LOG_ERR,
"HttpRequest::SendResponse(): Error occurred while writing response body.");
471 nBytes += bytesWritten;
478 #ifdef USE_SETSOCKOPT
498 long long llSize = 0;
499 long long llStart = 0;
502 LOG(VB_HTTP, LOG_INFO, QString(
"SendResponseFile ( %1 )").
arg(sFileName));
511 #ifdef USE_SETSOCKOPT
523 QFile tmpFile( sFileName );
524 if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
533 llSize = llEnd = tmpFile.size( );
544 if (!sRange.isEmpty())
546 bRange =
ParseRange( sRange, llSize, &llStart, &llEnd );
553 if ((llSize > llStart) && (llSize > llEnd) && (llEnd > llStart))
562 llSize = (llEnd - llStart) + 1;
574 LOG(VB_HTTP, LOG_INFO,
575 QString(
"HTTPRequest::SendResponseFile(%1) - "
576 "invalid byte range %2-%3/%4")
577 .
arg(sFileName) .
arg(llStart) .
arg(llEnd)
595 LOG(VB_HTTP, LOG_INFO,
596 QString(
"HTTPRequest::SendResponseFile(%1) - cannot find file!")
609 QByteArray sHeader = rHeader.toUtf8();
610 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").
arg(sHeader.length()));
611 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
613 if (nBytes < sHeader.length())
615 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponseFile(): "
616 "Incomplete write of header, "
618 .
arg(nBytes).
arg(sHeader.length()));
626 LOG(VB_HTTP, LOG_DEBUG,
627 QString(
"SendResponseFile : size = %1, start = %2, end = %3")
632 long long sent =
SendFile( tmpFile, llStart, llSize );
636 LOG(VB_HTTP, LOG_INFO,
637 QString(
"SendResponseFile( %1 ) Error: %2 [%3]" )
638 .
arg(sFileName) .
arg(errno) .
arg(strerror(errno)));
648 #ifdef USE_SETSOCKOPT
669 #define SENDFILE_BUFFER_SIZE 65536
673 bool bShouldClose =
false;
676 if (!pDevice->isOpen())
678 pDevice->open( QIODevice::ReadOnly );
686 if ( !pDevice->seek( llStart ))
689 std::array<char,SENDFILE_BUFFER_SIZE> aBuffer {};
691 qint64 llBytesRemaining = llBytes;
692 qint64 llBytesToRead = 0;
693 qint64 llBytesRead = 0;
695 while ((sent < llBytes) && !pDevice->atEnd())
699 if (( llBytesRead = pDevice->read( aBuffer.data(), llBytesToRead )) != -1 )
701 if (
WriteBlock( aBuffer.data(), llBytesRead ) == -1)
707 llBytesRemaining -= llBytesRead;
723 qint64 sent =
SendData( (QIODevice *)(&
file), llStart, llBytes );
734 const QString &sFaultString,
735 const QString &sDetails )
742 stream << R
"(<?xml version="1.0" encoding="utf-8"?>)";
744 QString sWhere = ( bServerError ) ? "s:Server" :
"s:Client";
752 <<
"<faultcode>" << sWhere <<
"</faultcode>"
753 <<
"<faultstring>" << sFaultString <<
"</faultstring>";
756 if (!sDetails.isEmpty())
758 stream <<
"<detail>" << sDetails <<
"</detail>";
793 stream <<
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
800 <<
"<u:" <<
m_sMethod <<
"Response xmlns:u=\""
804 stream <<
"<" <<
m_sMethod <<
"Response>\r\n";
806 for (
const auto &
arg : qAsConst(
args))
808 stream <<
"<" <<
arg.m_sName;
810 if (
arg.m_pAttributes)
812 for (
const auto & attr : qAsConst(*
arg.m_pAttributes))
814 stream <<
" " << attr.m_sName <<
"='"
815 <<
Encode( attr.m_sValue ) <<
"'";
824 stream <<
arg.m_sValue;
826 stream <<
"</" <<
arg.m_sName <<
">\r\n";
831 stream <<
"</u:" <<
m_sMethod <<
"Response>\r\n"
835 stream <<
"</" <<
m_sMethod <<
"Response>\r\n";
867 ims.setTimeSpec(Qt::OffsetFromUTC);
868 if (ims.isValid() && ims <=
file.lastModified())
889 LOG(VB_HTTP, LOG_INFO,
890 QString(
"HTTPRequest::FormatFileResponse('%1') - cannot find file")
901 m_sProtocol = sLine.section(
'/', 0, 0 ).trimmed();
902 QString sVersion = sLine.section(
'/', 1 ).trimmed();
904 m_nMajor = sVersion.section(
'.', 0, 0 ).toInt();
905 m_nMinor = sVersion.section(
'.', 1 ).toInt();
937 return QString(
"HTTP/1.1");
946 if ((sType ==
"application/x-www-form-urlencoded" ) ||
947 (sType.startsWith(
"application/x-www-form-urlencoded;")))
950 if ((sType ==
"text/xml" ) ||
951 (sType.startsWith(
"text/xml;") ))
966 case 200:
return(
"200 OK" );
967 case 201:
return(
"201 Created" );
968 case 202:
return(
"202 Accepted" );
969 case 204:
return(
"204 No Content" );
970 case 205:
return(
"205 Reset Content" );
971 case 206:
return(
"206 Partial Content" );
972 case 300:
return(
"300 Multiple Choices" );
973 case 301:
return(
"301 Moved Permanently" );
974 case 302:
return(
"302 Found" );
975 case 303:
return(
"303 See Other" );
976 case 304:
return(
"304 Not Modified" );
977 case 305:
return(
"305 Use Proxy" );
978 case 307:
return(
"307 Temporary Redirect" );
979 case 308:
return(
"308 Permanent Redirect" );
980 case 400:
return(
"400 Bad Request" );
981 case 401:
return(
"401 Unauthorized" );
982 case 403:
return(
"403 Forbidden" );
983 case 404:
return(
"404 Not Found" );
984 case 405:
return(
"405 Method Not Allowed" );
985 case 406:
return(
"406 Not Acceptable" );
986 case 408:
return(
"408 Request Timeout" );
987 case 410:
return(
"410 Gone" );
988 case 411:
return(
"411 Length Required" );
989 case 412:
return(
"412 Precondition Failed" );
990 case 413:
return(
"413 Request Entity Too Large" );
991 case 414:
return(
"414 Request-URI Too Long" );
992 case 415:
return(
"415 Unsupported Media Type" );
993 case 416:
return(
"416 Requested Range Not Satisfiable" );
994 case 417:
return(
"417 Expectation Failed" );
996 case 428:
return(
"428 Precondition Required" );
997 case 429:
return(
"429 Too Many Requests" );
998 case 431:
return(
"431 Request Header Fields Too Large" );
999 case 500:
return(
"500 Internal Server Error" );
1000 case 501:
return(
"501 Not Implemented" );
1001 case 502:
return(
"502 Bad Gateway" );
1002 case 503:
return(
"503 Service Unavailable" );
1003 case 504:
return(
"504 Gateway Timeout" );
1004 case 505:
return(
"505 HTTP Version Not Supported" );
1005 case 510:
return(
"510 Not Extended" );
1006 case 511:
return(
"511 Network Authentication Required" );
1038 return(
"text/plain" );
1051 ext =
type.pszExtension;
1053 if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
1054 return(
type.pszType );
1057 return(
"text/plain" );
1066 QStringList mimeTypes;
1070 if (!mimeTypes.contains(
type.pszType ))
1071 mimeTypes.append(
type.pszType );
1083 QFileInfo info( sFileName );
1084 QString sLOC =
"HTTPRequest::TestMimeType(" + sFileName +
") - ";
1085 QString sSuffix = info.suffix().toLower();
1088 if ( sSuffix ==
"nuv" )
1091 QFile
file( sFileName );
1093 if (
file.open(QIODevice::ReadOnly | QIODevice::Text) )
1095 QByteArray head =
file.read(8);
1096 QString sHex = head.toHex();
1098 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"file starts with " + sHex);
1100 if ( sHex ==
"000001ba44000400" )
1101 sMIME =
"video/mp2p";
1103 if ( head ==
"MythTVVi" )
1106 head =
file.read(4);
1108 if ( head ==
"DIVX" )
1110 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"('MythTVVi...DIVXLAME')");
1111 sMIME =
"video/mp4";
1121 LOG(VB_GENERAL, LOG_ERR, sLOC +
"Could not read file");
1124 LOG(VB_HTTP, LOG_INFO, sLOC +
"type is " + sMIME);
1136 LOG(VB_HTTP, LOG_INFO, QString(
"sParams: '%1'").
arg(sParams));
1141 sParams.replace(
"&",
"&" );
1143 if (!sParams.isEmpty())
1145 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1146 QStringList params = sParams.split(
'&', QString::SkipEmptyParts);
1148 QStringList params = sParams.split(
'&', Qt::SkipEmptyParts);
1151 for (
const auto & param : qAsConst(params))
1153 QString sName = param.section(
'=', 0, 0 );
1154 QString sValue = param.section(
'=', 1 );
1155 sValue.replace(
"+",
" ");
1157 if (!sName.isEmpty())
1159 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1160 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1162 mapParams.insert( sName.trimmed(), sValue );
1178 QStringMap::iterator it =
m_mapHeaders.find( sKey.toLower() );
1199 sHeader += it.key() +
": ";
1200 sHeader += *it +
"\r\n";
1216 bool bKeepAlive =
true;
1225 QString sConnection =
GetRequestHeader(
"connection",
"default" ).toLower();
1227 QStringList sValueList = sConnection.split(
",");
1229 if ( sValueList.contains(
"close") )
1231 LOG(VB_HTTP, LOG_DEBUG,
"Client requested the connection be closed");
1234 else if (sValueList.contains(
"keep-alive"))
1246 QStringList sCookieList =
m_mapHeaders.values(
"cookie");
1248 QStringList::iterator it;
1249 for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1251 QString key = (*it).section(
'=', 0, 0);
1252 QString value = (*it).section(
'=', 1);
1264 bool bSuccess =
false;
1269 QString sRequestLine =
ReadLine( 2000 );
1271 if ( sRequestLine.isEmpty() )
1273 LOG(VB_GENERAL, LOG_ERR,
"Timeout reading first line of request." );
1305 while (( !sLine.isEmpty() ) && !bDone )
1307 if (sLine !=
"\r\n")
1309 QString sName = sLine.section(
':', 0, 0 ).trimmed();
1310 QString sValue = sLine.section(
':', 1 );
1312 sValue.truncate( sValue.length() - 2 );
1314 if (!sName.isEmpty() && !sValue.isEmpty())
1316 m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1330 LOG(VB_HTTP, LOG_INFO, QString(
"(Request Header) %1: %2")
1331 .
arg(it.key()).arg(*it));
1343 LOG(VB_GENERAL, LOG_INFO,
"Timeout waiting for request header." );
1397 long nPayloadSize =
GetLastHeader(
"content-length" ).toLong();
1399 if (nPayloadSize > 0)
1401 char *pszPayload =
new char[ nPayloadSize + 2 ];
1404 nBytes =
ReadBlock( pszPayload, nPayloadSize, 5000 );
1405 if (nBytes == nPayloadSize )
1407 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1415 LOG(VB_GENERAL, LOG_ERR,
1416 QString(
"Unable to read entire payload (read %1 of %2 bytes)")
1417 .
arg( nBytes ) .
arg( nPayloadSize ) );
1421 delete [] pszPayload;
1427 if (!sSOAPAction.isEmpty())
1434 LOG(VB_HTTP, LOG_DEBUG,
1435 QString(
"HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1436 "Method (%3) - Bytes in Socket Buffer (%4)")
1443 LOG(VB_GENERAL, LOG_WARNING,
1444 "Unexpected exception in HTTPRequest::ParseRequest" );
1458 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1459 QStringList tokens = sLine.split(
m_procReqLineExp, QString::SkipEmptyParts);
1463 int nCount = tokens.count();
1467 if ( sLine.startsWith( QString(
"HTTP/") ))
1491 m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1497 QString sQueryStr = tokens[1].section(
'?', 1, 1 );
1499 if (!sQueryStr.isEmpty())
1527 long long *pllStart,
1535 if (sRange.isEmpty())
1547 sRange.remove( 0, nIdx );
1553 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1554 QStringList ranges = sRange.split(
',', QString::SkipEmptyParts);
1556 QStringList ranges = sRange.split(
',', Qt::SkipEmptyParts);
1559 if (ranges.count() == 0)
1566 QStringList parts = ranges[0].split(
'-');
1568 if (parts.count() != 2)
1571 if (parts[0].isEmpty() && parts[1].isEmpty())
1578 bool conv_ok =
false;
1579 if (parts[0].isEmpty())
1585 long long llValue = parts[1].toLongLong(&conv_ok);
1586 if (!conv_ok)
return false;
1588 *pllStart = llSize - llValue;
1589 *pllEnd = llSize - 1;
1591 else if (parts[1].isEmpty())
1597 *pllStart = parts[0].toLongLong(&conv_ok);
1602 *pllEnd = llSize - 1;
1610 *pllStart = parts[0].toLongLong(&conv_ok);
1611 if (!conv_ok)
return false;
1612 *pllEnd = parts[1].toLongLong(&conv_ok);
1613 if (!conv_ok)
return false;
1615 if (*pllStart > *pllEnd)
1619 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 Range Requested %2 - %3")
1634 QRegularExpression re {
"^http[s]?://.*?/"};
1637 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1638 QStringList sList =
m_sBaseUrl.split(
'/', QString::SkipEmptyParts);
1640 QStringList sList =
m_sBaseUrl.split(
'/', Qt::SkipEmptyParts);
1645 if (!sList.isEmpty())
1652 LOG(VB_HTTP, LOG_INFO, QString(
"ExtractMethodFromURL(end) : %1 : %2")
1662 bool bSuccess =
false;
1668 LOG(VB_HTTP, LOG_INFO,
1669 QString(
"HTTPRequest::ProcessSOAPPayload : %1 : ").
arg(sSOAPAction));
1670 QDomDocument
doc (
"request" );
1676 if (!
doc.setContent(
m_sPayload,
true, &sErrMsg, &nErrLine, &nErrCol ))
1678 LOG(VB_GENERAL, LOG_ERR,
1679 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1680 .
arg(nErrLine) .
arg(nErrCol) .
arg(sErrMsg));
1690 if (sSOAPAction.contains(
'#' ))
1692 m_sNameSpace = sSOAPAction.section(
'#', 0, 0).remove( 0, 1);
1693 m_sMethod = sSOAPAction.section(
'#', 1 );
1698 if (sSOAPAction.contains(
'/' ))
1700 int nPos = sSOAPAction.lastIndexOf(
'/' );
1703 sSOAPAction.length() - nPos - 2);
1720 if (oNodeList.count() == 0)
1723 doc.elementsByTagNameNS(
"http://schemas.xmlsoap.org/soap/envelope/",
1727 if (oNodeList.count() > 0)
1729 QDomNode oMethod = oNodeList.item(0);
1731 if (!oMethod.isNull())
1735 for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1736 oNode = oNode.nextSibling() )
1738 QDomElement
e = oNode.toElement();
1742 QString sName =
e.tagName();
1743 QString sValue =
"";
1745 QDomText oText = oNode.firstChild().toText();
1747 if (!oText.isNull())
1748 sValue = oText.nodeValue();
1750 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1751 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1753 m_mapParams.insert( sName.trimmed().toLower(), sValue );
1781 if (sAccept.contains(
"application/json", Qt::CaseInsensitive ) ||
1782 sAccept.contains(
"text/javascript", Qt::CaseInsensitive ))
1787 else if (sAccept.contains(
"text/x-apple-plist+xml", Qt::CaseInsensitive ))
1795 if (pSerializer ==
nullptr)
1809 LOG(VB_HTTP, LOG_DEBUG,
1810 QString(
"HTTPRequest::Encode Input : %1").
arg(sStr));
1812 sStr.replace(
'&',
"&" );
1813 sStr.replace(
'<',
"<" );
1814 sStr.replace(
'>',
">" );
1815 sStr.replace(
'"',
""");
1816 sStr.replace(
"'",
"'");
1819 LOG(VB_HTTP, LOG_DEBUG,
1820 QString(
"HTTPRequest::Encode Output : %1").
arg(sStr));
1832 sStr.replace(
"&",
"&");
1833 sStr.replace(
"<",
"<");
1834 sStr.replace(
">",
">");
1835 sStr.replace(
""",
"\"");
1836 sStr.replace(
"'",
"'");
1847 QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1849 return (
"\"" + hash.toHex() +
"\"");
1860 QStringList oList = sProtected.split(
';' );
1862 for(
int nIdx = 0; nIdx < oList.count(); nIdx++)
1864 if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1880 QString realm =
"MythTV";
1888 QString stale = isStale ?
"true" :
"false";
1889 authHeader = QString(
"Digest realm=\"%1\",nonce=\"%2\","
1890 "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1891 .arg(realm).arg(nonce).arg(stale);
1895 authHeader = QString(
"Basic realm=\"%1\"").arg(realm);
1907 QString uniqueID = QString(
"%1:%2").arg(timeStamp).arg(
m_sPrivateToken);
1908 QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex();
1909 QString nonce = QString(
"%1%2").arg(timeStamp).arg(hash);
1919 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Basic Authentication");
1920 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
1924 LOG(VB_GENERAL, LOG_WARNING,
"Basic authentication is only allowed for HTTP 1.0");
1928 QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1930 oList = sCredentials.split(
':' );
1932 if (oList.count() < 2)
1934 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid number of tokens");
1938 QString sUsername = oList[0];
1939 QString sPassword = oList[1];
1941 if (sUsername ==
"nouser")
1947 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
1951 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
1957 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password");
1961 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
1978 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Digest Authentication");
1979 QString realm =
"MythTV";
1981 QString authMethod =
GetLastHeader(
"authorization" ).section(
' ', 0, 0).toLower();
1983 if (authMethod !=
"digest")
1985 LOG(VB_GENERAL, LOG_WARNING,
"Invalid method in Authorization header");
1989 QString parameterStr =
GetLastHeader(
"authorization" ).section(
' ', 1);
1991 QMap<QString, QString> paramMap;
1992 QStringList paramList = parameterStr.split(
',');
1993 QStringList::iterator it;
1994 for (it = paramList.begin(); it != paramList.end(); ++it)
1996 QString key = (*it).section(
'=', 0, 0).toLower().trimmed();
1998 QString value = (*it).section(
'=', 1).trimmed();
2001 paramMap[key] = value;
2004 if (paramMap.size() < 8)
2006 LOG(VB_GENERAL, LOG_WARNING,
"Invalid number of parameters in Authorization header");
2010 if (paramMap[
"nonce"].isEmpty() || paramMap[
"username"].isEmpty() ||
2011 paramMap[
"realm"].isEmpty() || paramMap[
"uri"].isEmpty() ||
2012 paramMap[
"response"].isEmpty() || paramMap[
"qop"].isEmpty() ||
2013 paramMap[
"cnonce"].isEmpty() || paramMap[
"nc"].isEmpty())
2015 LOG(VB_GENERAL, LOG_WARNING,
"Missing required parameters in Authorization header");
2019 if (paramMap[
"username"] ==
"nouser")
2024 LOG(VB_GENERAL, LOG_WARNING,
"Authorization URI doesn't match the "
2030 if (paramMap[
"realm"] != realm)
2032 LOG(VB_GENERAL, LOG_WARNING,
"Authorization realm doesn't match the "
2033 "realm of the requested content");
2037 QByteArray nonce = paramMap[
"nonce"].toLatin1();
2038 if (nonce.length() < 20)
2040 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce is too short");
2044 QString nonceTimeStampStr = nonce.left(20);
2047 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce doesn't match reference");
2048 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 vs %2").
arg(QString(nonce))
2053 const int AUTH_TIMEOUT = 2 * 60;
2055 if (!nonceTimeStamp.isValid())
2057 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce timestamp is invalid.");
2058 LOG(VB_HTTP, LOG_DEBUG, QString(
"Timestamp was '%1'").
arg(nonceTimeStampStr));
2064 LOG(VB_HTTP, LOG_NOTICE,
"Authorization nonce timestamp is invalid or too old.");
2075 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
2079 if (paramMap[
"response"].length() != 32)
2081 LOG(VB_GENERAL, LOG_WARNING,
"Authorization response field is invalid length");
2089 QString methodDigest = QString(
"%1:%2").arg(
GetRequestType()).arg(paramMap[
"uri"]);
2090 QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2091 QCryptographicHash::Md5).toHex();
2093 QString responseDigest = QString(
"%1:%2:%3:%4:%5:%6").arg(QString(a1))
2094 .arg(paramMap[
"nonce"])
2095 .arg(paramMap[
"nc"])
2096 .arg(paramMap[
"cnonce"])
2097 .arg(paramMap[
"qop"])
2099 QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2100 QCryptographicHash::Md5).toHex();
2102 if (paramMap[
"response"].toLatin1() == kd)
2104 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
2105 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
2111 LOG(VB_GENERAL, LOG_ERR,
"Valid Authorization received, but we "
2112 "failed to create a valid session");
2125 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password digest");
2126 LOG(VB_HTTP, LOG_DEBUG, QString(
"Received hash was '%1', calculated hash was '%2'")
2127 .
arg(paramMap[
"response"])
2143 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
2145 if (oList.count() < 2)
2148 if (oList[0].compare(
"basic", Qt::CaseInsensitive ) == 0)
2150 if (oList[0].compare(
"digest", Qt::CaseInsensitive ) == 0)
2174 const QDateTime &expiryDate,
bool secure)
2178 LOG(VB_GENERAL, LOG_WARNING, QString(
"HTTPRequest::SetCookie(%1=%2): "
2179 "A secure cookie cannot be set on an unencrypted connection.")
2184 QStringList cookieAttributes;
2187 cookieAttributes.append(QString(
"%1=%2").
arg(sKey).
arg(sValue));
2193 cookieAttributes.append(
"Path=/");
2197 cookieAttributes.append(QString(
"Expires=%1").
arg(expires));
2202 cookieAttributes.append(
"Secure");
2205 cookieAttributes.append(
"HttpOnly");
2227 return hostname.section(
"]:", 0 , 0);
2231 return hostname.section(
":", 0 , 0);
2270 type =
"UNSUBSCRIBE";
2308 QStringList allowedOrigins;
2312 serverStatusPort + 10);
2314 QString masterAddrPort = QString(
"%1:%2")
2316 .arg(serverStatusPort);
2317 QString masterTLSAddrPort = QString(
"%1:%2")
2319 .arg(backendSSLPort);
2321 allowedOrigins << QString(
"http://%1").arg(masterAddrPort);
2322 allowedOrigins << QString(
"https://%2").arg(masterTLSAddrPort);
2324 QString localhostname = QHostInfo::localHostName();
2325 if (!localhostname.isEmpty())
2327 allowedOrigins << QString(
"http://%1:%2")
2328 .arg(localhostname).arg(serverStatusPort);
2329 allowedOrigins << QString(
"https://%1:%2")
2330 .arg(localhostname).arg(backendSSLPort);
2333 QStringList allowedOriginsList =
2335 "https://chromecast.mythtv.org,"
2336 "http://chromecast.mythtvcast.com"
2339 for (
const auto & origin : qAsConst(allowedOriginsList))
2341 if (origin.isEmpty())
2344 if (origin ==
"*" || (!origin.startsWith(
"http://") &&
2345 !origin.startsWith(
"https://")))
2347 LOG(VB_GENERAL, LOG_ERR, QString(
"Illegal AllowedOriginsList"
2348 " entry '%1'. Must start with http[s]:// and not be *")
2353 allowedOrigins << origin;
2359 for (
const auto & origin : qAsConst(allowedOrigins))
2360 LOG(VB_HTTP, LOG_DEBUG, QString(
"Will allow Origin: %1").
arg(origin));
2363 if (allowedOrigins.contains(sOrigin))
2368 LOG(VB_HTTP, LOG_DEBUG, QString(
"Allow-Origin: %1)").
arg(sOrigin));
2372 LOG(VB_GENERAL, LOG_CRIT, QString(
"HTTPRequest: Cross-origin request "
2373 "received with origin (%1)")
2391 m_pSocket->state() == QAbstractSocket::ConnectedState)
2400 if ( timer.
elapsed() >= msecs )
2403 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2422 m_pSocket->state() == QAbstractSocket::ConnectedState)
2425 return(
m_pSocket->read( pData, nMaxLen ));
2427 bool bTimeout =
false;
2430 while ( (
m_pSocket->bytesAvailable() < (
int)nMaxLen) && !bTimeout )
2432 bTimeout = !(
m_pSocket->waitForReadyRead( msecs ));
2434 if ( timer.
elapsed() >= msecs )
2437 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2443 return(
m_pSocket->read( pData, nMaxLen ));
2455 qint64 bytesWritten = -1;
2457 m_pSocket->state() == QAbstractSocket::ConnectedState)
2459 bytesWritten =
m_pSocket->write( pData, nLen );
2463 return( bytesWritten );
2472 return(
m_pSocket->localAddress().toString() );
2491 return(
m_pSocket->peerAddress().toString() );