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")
628 .arg(llSize).arg(llStart).arg(llEnd));
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 ) <<
"'";
822 stream <<
Encode( arg.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;") ))
954 if ((sType ==
"application/json") ||
955 sType.startsWith(
"application/json;"))
970 case 200:
return(
"200 OK" );
971 case 201:
return(
"201 Created" );
972 case 202:
return(
"202 Accepted" );
973 case 204:
return(
"204 No Content" );
974 case 205:
return(
"205 Reset Content" );
975 case 206:
return(
"206 Partial Content" );
976 case 300:
return(
"300 Multiple Choices" );
977 case 301:
return(
"301 Moved Permanently" );
978 case 302:
return(
"302 Found" );
979 case 303:
return(
"303 See Other" );
980 case 304:
return(
"304 Not Modified" );
981 case 305:
return(
"305 Use Proxy" );
982 case 307:
return(
"307 Temporary Redirect" );
983 case 308:
return(
"308 Permanent Redirect" );
984 case 400:
return(
"400 Bad Request" );
985 case 401:
return(
"401 Unauthorized" );
986 case 403:
return(
"403 Forbidden" );
987 case 404:
return(
"404 Not Found" );
988 case 405:
return(
"405 Method Not Allowed" );
989 case 406:
return(
"406 Not Acceptable" );
990 case 408:
return(
"408 Request Timeout" );
991 case 410:
return(
"410 Gone" );
992 case 411:
return(
"411 Length Required" );
993 case 412:
return(
"412 Precondition Failed" );
994 case 413:
return(
"413 Request Entity Too Large" );
995 case 414:
return(
"414 Request-URI Too Long" );
996 case 415:
return(
"415 Unsupported Media Type" );
997 case 416:
return(
"416 Requested Range Not Satisfiable" );
998 case 417:
return(
"417 Expectation Failed" );
1000 case 428:
return(
"428 Precondition Required" );
1001 case 429:
return(
"429 Too Many Requests" );
1002 case 431:
return(
"431 Request Header Fields Too Large" );
1003 case 500:
return(
"500 Internal Server Error" );
1004 case 501:
return(
"501 Not Implemented" );
1005 case 502:
return(
"502 Bad Gateway" );
1006 case 503:
return(
"503 Service Unavailable" );
1007 case 504:
return(
"504 Gateway Timeout" );
1008 case 505:
return(
"505 HTTP Version Not Supported" );
1009 case 510:
return(
"510 Not Extended" );
1010 case 511:
return(
"511 Network Authentication Required" );
1042 return(
"text/plain" );
1055 ext =
type.pszExtension;
1057 if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
1058 return(
type.pszType );
1061 return(
"text/plain" );
1070 QStringList mimeTypes;
1074 if (!mimeTypes.contains(
type.pszType ))
1075 mimeTypes.append(
type.pszType );
1087 QFileInfo info( sFileName );
1088 QString sLOC =
"HTTPRequest::TestMimeType(" + sFileName +
") - ";
1089 QString sSuffix = info.suffix().toLower();
1092 if ( sSuffix ==
"nuv" )
1095 QFile
file( sFileName );
1097 if (
file.open(QIODevice::ReadOnly | QIODevice::Text) )
1099 QByteArray head =
file.read(8);
1100 QString sHex = head.toHex();
1102 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"file starts with " + sHex);
1104 if ( sHex ==
"000001ba44000400" )
1105 sMIME =
"video/mp2p";
1107 if ( head ==
"MythTVVi" )
1110 head =
file.read(4);
1112 if ( head ==
"DIVX" )
1114 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"('MythTVVi...DIVXLAME')");
1115 sMIME =
"video/mp4";
1125 LOG(VB_GENERAL, LOG_ERR, sLOC +
"Could not read file");
1128 LOG(VB_HTTP, LOG_INFO, sLOC +
"type is " + sMIME);
1140 LOG(VB_HTTP, LOG_INFO, QString(
"sParams: '%1'").arg(sParams));
1145 sParams.replace(
"&",
"&" );
1147 if (!sParams.isEmpty())
1149 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1150 QStringList params = sParams.split(
'&', QString::SkipEmptyParts);
1152 QStringList params = sParams.split(
'&', Qt::SkipEmptyParts);
1155 for (
const auto & param : qAsConst(params))
1157 QString sName = param.section(
'=', 0, 0 );
1158 QString sValue = param.section(
'=', 1 );
1159 sValue.replace(
"+",
" ");
1161 if (!sName.isEmpty())
1163 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1164 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1166 mapParams.insert( sName.trimmed(), sValue );
1203 sHeader += it.key() +
": ";
1204 sHeader += *it +
"\r\n";
1220 bool bKeepAlive =
true;
1229 QString sConnection =
GetRequestHeader(
"connection",
"default" ).toLower();
1231 QStringList sValueList = sConnection.split(
",");
1233 if ( sValueList.contains(
"close") )
1235 LOG(VB_HTTP, LOG_DEBUG,
"Client requested the connection be closed");
1238 else if (sValueList.contains(
"keep-alive"))
1250 QStringList sCookieList =
m_mapHeaders.values(
"cookie");
1252 QStringList::iterator it;
1253 for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1255 QString key = (*it).section(
'=', 0, 0);
1256 QString value = (*it).section(
'=', 1);
1268 bool bSuccess =
false;
1273 QString sRequestLine =
ReadLine( 2s );
1275 if ( sRequestLine.isEmpty() )
1277 LOG(VB_GENERAL, LOG_ERR,
"Timeout reading first line of request." );
1309 while (( !sLine.isEmpty() ) && !bDone )
1311 if (sLine !=
"\r\n")
1313 QString sName = sLine.section(
':', 0, 0 ).trimmed();
1314 QString sValue = sLine.section(
':', 1 );
1316 sValue.truncate( sValue.length() - 2 );
1318 if (!sName.isEmpty() && !sValue.isEmpty())
1320 m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1332 LOG(VB_HTTP, LOG_INFO, QString(
"(Request Header) %1: %2")
1333 .arg(it.key()).arg(*it));
1345 LOG(VB_GENERAL, LOG_INFO,
"Timeout waiting for request header." );
1399 long nPayloadSize =
GetLastHeader(
"content-length" ).toLong();
1401 if (nPayloadSize > 0)
1403 char *pszPayload =
new char[ nPayloadSize + 2 ];
1406 nBytes =
ReadBlock( pszPayload, nPayloadSize, 5s );
1407 if (nBytes == nPayloadSize )
1409 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1419 LOG(VB_GENERAL, LOG_ERR,
1420 QString(
"Unable to read entire payload (read %1 of %2 bytes)")
1421 .arg( nBytes ) .arg( nPayloadSize ) );
1425 delete [] pszPayload;
1431 if (!sSOAPAction.isEmpty())
1438 LOG(VB_HTTP, LOG_DEBUG,
1439 QString(
"HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1440 "Method (%3) - Bytes in Socket Buffer (%4)")
1442 .arg(
m_sMethod) .arg(BytesAvailable()));
1447 LOG(VB_GENERAL, LOG_WARNING,
1448 "Unexpected exception in HTTPRequest::ParseRequest" );
1462 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1463 QStringList tokens = sLine.split(
m_procReqLineExp, QString::SkipEmptyParts);
1467 int nCount = tokens.count();
1471 if ( sLine.startsWith( QString(
"HTTP/") ))
1495 m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1501 QString sQueryStr = tokens[1].section(
'?', 1, 1 );
1503 if (!sQueryStr.isEmpty())
1531 long long *pllStart,
1539 if (sRange.isEmpty())
1551 sRange.remove( 0, nIdx );
1557 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1558 QStringList ranges = sRange.split(
',', QString::SkipEmptyParts);
1560 QStringList ranges = sRange.split(
',', Qt::SkipEmptyParts);
1563 if (ranges.count() == 0)
1570 QStringList parts = ranges[0].split(
'-');
1572 if (parts.count() != 2)
1575 if (parts[0].isEmpty() && parts[1].isEmpty())
1582 bool conv_ok =
false;
1583 if (parts[0].isEmpty())
1589 long long llValue = parts[1].toLongLong(&conv_ok);
1590 if (!conv_ok)
return false;
1592 *pllStart = llSize - llValue;
1593 *pllEnd = llSize - 1;
1595 else if (parts[1].isEmpty())
1601 *pllStart = parts[0].toLongLong(&conv_ok);
1606 *pllEnd = llSize - 1;
1614 *pllStart = parts[0].toLongLong(&conv_ok);
1615 if (!conv_ok)
return false;
1616 *pllEnd = parts[1].toLongLong(&conv_ok);
1617 if (!conv_ok)
return false;
1619 if (*pllStart > *pllEnd)
1623 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 Range Requested %2 - %3")
1638 QRegularExpression re {
"^http[s]?://.*?/"};
1641 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
1642 QStringList sList =
m_sBaseUrl.split(
'/', QString::SkipEmptyParts);
1644 QStringList sList =
m_sBaseUrl.split(
'/', Qt::SkipEmptyParts);
1649 if (!sList.isEmpty())
1656 LOG(VB_HTTP, LOG_INFO, QString(
"ExtractMethodFromURL(end) : %1 : %2")
1666 bool bSuccess =
false;
1672 LOG(VB_HTTP, LOG_INFO,
1673 QString(
"HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1674 QDomDocument doc (
"request" );
1680 if (!doc.setContent(
m_sPayload,
true, &sErrMsg, &nErrLine, &nErrCol ))
1682 LOG(VB_GENERAL, LOG_ERR,
1683 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1684 .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1694 if (sSOAPAction.contains(
'#' ))
1696 m_sNameSpace = sSOAPAction.section(
'#', 0, 0).remove( 0, 1);
1697 m_sMethod = sSOAPAction.section(
'#', 1 );
1702 if (sSOAPAction.contains(
'/' ))
1704 int nPos = sSOAPAction.lastIndexOf(
'/' );
1707 sSOAPAction.length() - nPos - 2);
1724 if (oNodeList.count() == 0)
1727 doc.elementsByTagNameNS(
"http://schemas.xmlsoap.org/soap/envelope/",
1731 if (oNodeList.count() > 0)
1733 QDomNode oMethod = oNodeList.item(0);
1735 if (!oMethod.isNull())
1739 for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1740 oNode = oNode.nextSibling() )
1742 QDomElement e = oNode.toElement();
1746 QString sName = e.tagName();
1747 QString sValue =
"";
1749 QDomText oText = oNode.firstChild().toText();
1751 if (!oText.isNull())
1752 sValue = oText.nodeValue();
1754 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1755 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1757 m_mapParams.insert( sName.trimmed().toLower(), sValue );
1785 if (sAccept.contains(
"application/json", Qt::CaseInsensitive ) ||
1786 sAccept.contains(
"text/javascript", Qt::CaseInsensitive ))
1791 else if (sAccept.contains(
"text/x-apple-plist+xml", Qt::CaseInsensitive ))
1799 if (pSerializer ==
nullptr)
1813 LOG(VB_HTTP, LOG_DEBUG,
1814 QString(
"HTTPRequest::Encode Input : %1").arg(sStr));
1816 sStr.replace(
'&',
"&" );
1817 sStr.replace(
'<',
"<" );
1818 sStr.replace(
'>',
">" );
1819 sStr.replace(
'"',
""");
1820 sStr.replace(
"'",
"'");
1823 LOG(VB_HTTP, LOG_DEBUG,
1824 QString(
"HTTPRequest::Encode Output : %1").arg(sStr));
1836 sStr.replace(
"&",
"&");
1837 sStr.replace(
"<",
"<");
1838 sStr.replace(
">",
">");
1839 sStr.replace(
""",
"\"");
1840 sStr.replace(
"'",
"'");
1851 QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1853 return (
"\"" + hash.toHex() +
"\"");
1864 QStringList oList = sProtected.split(
';' );
1866 for(
int nIdx = 0; nIdx < oList.count(); nIdx++)
1868 if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1884 QString realm =
"MythTV";
1892 QString stale = isStale ?
"true" :
"false";
1893 authHeader = QString(
"Digest realm=\"%1\",nonce=\"%2\","
1894 "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1895 .arg(realm).arg(nonce).arg(stale);
1899 authHeader = QString(
"Basic realm=\"%1\"").arg(realm);
1911 QString uniqueID = QString(
"%1:%2").arg(timeStamp).arg(
m_sPrivateToken);
1912 QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex();
1913 QString nonce = QString(
"%1%2").arg(timeStamp).arg(hash);
1923 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Basic Authentication");
1924 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
1928 LOG(VB_GENERAL, LOG_WARNING,
"Basic authentication is only allowed for HTTP 1.0");
1932 QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1934 oList = sCredentials.split(
':' );
1936 if (oList.count() < 2)
1938 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid number of tokens");
1942 QString sUsername = oList[0];
1943 QString sPassword = oList[1];
1945 if (sUsername ==
"nouser")
1951 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
1955 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
1961 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password");
1965 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
1982 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Digest Authentication");
1983 QString realm =
"MythTV";
1985 QString authMethod =
GetLastHeader(
"authorization" ).section(
' ', 0, 0).toLower();
1987 if (authMethod !=
"digest")
1989 LOG(VB_GENERAL, LOG_WARNING,
"Invalid method in Authorization header");
1993 QString parameterStr =
GetLastHeader(
"authorization" ).section(
' ', 1);
1995 QMap<QString, QString> paramMap;
1996 QStringList paramList = parameterStr.split(
',');
1997 QStringList::iterator it;
1998 for (it = paramList.begin(); it != paramList.end(); ++it)
2000 QString key = (*it).section(
'=', 0, 0).toLower().trimmed();
2002 QString value = (*it).section(
'=', 1).trimmed();
2005 paramMap[key] = value;
2008 if (paramMap.size() < 8)
2010 LOG(VB_GENERAL, LOG_WARNING,
"Invalid number of parameters in Authorization header");
2014 if (paramMap[
"nonce"].isEmpty() || paramMap[
"username"].isEmpty() ||
2015 paramMap[
"realm"].isEmpty() || paramMap[
"uri"].isEmpty() ||
2016 paramMap[
"response"].isEmpty() || paramMap[
"qop"].isEmpty() ||
2017 paramMap[
"cnonce"].isEmpty() || paramMap[
"nc"].isEmpty())
2019 LOG(VB_GENERAL, LOG_WARNING,
"Missing required parameters in Authorization header");
2023 if (paramMap[
"username"] ==
"nouser")
2028 LOG(VB_GENERAL, LOG_WARNING,
"Authorization URI doesn't match the "
2034 if (paramMap[
"realm"] != realm)
2036 LOG(VB_GENERAL, LOG_WARNING,
"Authorization realm doesn't match the "
2037 "realm of the requested content");
2041 QByteArray nonce = paramMap[
"nonce"].toLatin1();
2042 if (nonce.length() < 20)
2044 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce is too short");
2048 QString nonceTimeStampStr = nonce.left(20);
2051 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce doesn't match reference");
2052 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 vs %2").arg(QString(nonce))
2057 constexpr std::chrono::seconds AUTH_TIMEOUT { 2min };
2059 if (!nonceTimeStamp.isValid())
2061 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce timestamp is invalid.");
2062 LOG(VB_HTTP, LOG_DEBUG, QString(
"Timestamp was '%1'").arg(nonceTimeStampStr));
2068 LOG(VB_HTTP, LOG_NOTICE,
"Authorization nonce timestamp is invalid or too old.");
2079 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
2083 if (paramMap[
"response"].length() != 32)
2085 LOG(VB_GENERAL, LOG_WARNING,
"Authorization response field is invalid length");
2093 QString methodDigest = QString(
"%1:%2").arg(
GetRequestType()).arg(paramMap[
"uri"]);
2094 QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2095 QCryptographicHash::Md5).toHex();
2097 QString responseDigest = QString(
"%1:%2:%3:%4:%5:%6").arg(QString(a1))
2098 .arg(paramMap[
"nonce"])
2099 .arg(paramMap[
"nc"])
2100 .arg(paramMap[
"cnonce"])
2101 .arg(paramMap[
"qop"])
2103 QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2104 QCryptographicHash::Md5).toHex();
2106 if (paramMap[
"response"].toLatin1() == kd)
2108 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
2109 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
2115 LOG(VB_GENERAL, LOG_ERR,
"Valid Authorization received, but we "
2116 "failed to create a valid session");
2129 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password digest");
2130 LOG(VB_HTTP, LOG_DEBUG, QString(
"Received hash was '%1', calculated hash was '%2'")
2131 .arg(paramMap[
"response"])
2147 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
2149 if (oList.count() < 2)
2152 if (oList[0].compare(
"basic", Qt::CaseInsensitive ) == 0)
2154 if (oList[0].compare(
"digest", Qt::CaseInsensitive ) == 0)
2178 const QDateTime &expiryDate,
bool secure)
2182 LOG(VB_GENERAL, LOG_WARNING, QString(
"HTTPRequest::SetCookie(%1=%2): "
2183 "A secure cookie cannot be set on an unencrypted connection.")
2184 .arg(sKey).arg(sValue));
2188 QStringList cookieAttributes;
2191 cookieAttributes.append(QString(
"%1=%2").arg(sKey).arg(sValue));
2197 cookieAttributes.append(
"Path=/");
2201 cookieAttributes.append(QString(
"Expires=%1").arg(expires));
2206 cookieAttributes.append(
"Secure");
2209 cookieAttributes.append(
"HttpOnly");
2231 return hostname.section(
"]:", 0 , 0);
2235 return hostname.section(
":", 0 , 0);
2274 type =
"UNSUBSCRIBE";
2312 QStringList allowedOrigins;
2316 serverStatusPort + 10);
2318 QString masterAddrPort = QString(
"%1:%2")
2320 .arg(serverStatusPort);
2321 QString masterTLSAddrPort = QString(
"%1:%2")
2323 .arg(backendSSLPort);
2325 allowedOrigins << QString(
"http://%1").arg(masterAddrPort);
2326 allowedOrigins << QString(
"https://%2").arg(masterTLSAddrPort);
2328 QString localhostname = QHostInfo::localHostName();
2329 if (!localhostname.isEmpty())
2331 allowedOrigins << QString(
"http://%1:%2")
2332 .arg(localhostname).arg(serverStatusPort);
2333 allowedOrigins << QString(
"https://%1:%2")
2334 .arg(localhostname).arg(backendSSLPort);
2337 QStringList allowedOriginsList =
2339 "https://chromecast.mythtv.org,"
2340 "http://chromecast.mythtvcast.com"
2343 for (
const auto & origin : qAsConst(allowedOriginsList))
2345 if (origin.isEmpty())
2348 if (origin ==
"*" || (!origin.startsWith(
"http://") &&
2349 !origin.startsWith(
"https://")))
2351 LOG(VB_GENERAL, LOG_ERR, QString(
"Illegal AllowedOriginsList"
2352 " entry '%1'. Must start with http[s]:// and not be *")
2357 allowedOrigins << origin;
2363 for (
const auto & origin : qAsConst(allowedOrigins))
2364 LOG(VB_HTTP, LOG_DEBUG, QString(
"Will allow Origin: %1").arg(origin));
2367 if (allowedOrigins.contains(sOrigin))
2372 LOG(VB_HTTP, LOG_DEBUG, QString(
"Allow-Origin: %1)").arg(sOrigin));
2376 LOG(VB_GENERAL, LOG_CRIT, QString(
"HTTPRequest: Cross-origin request "
2377 "received with origin (%1)")
2395 m_pSocket->state() == QAbstractSocket::ConnectedState)
2404 if ( timer.
elapsed() >= msecs )
2407 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2423 std::chrono::milliseconds msecs)
2426 m_pSocket->state() == QAbstractSocket::ConnectedState)
2429 return(
m_pSocket->read( pData, nMaxLen ));
2431 bool bTimeout =
false;
2434 while ( (
m_pSocket->bytesAvailable() < (
int)nMaxLen) && !bTimeout )
2436 bTimeout = !(
m_pSocket->waitForReadyRead( msecs.count() ));
2438 if ( timer.
elapsed() >= msecs )
2441 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2447 return(
m_pSocket->read( pData, nMaxLen ));
2459 qint64 bytesWritten = -1;
2461 m_pSocket->state() == QAbstractSocket::ConnectedState)
2463 bytesWritten =
m_pSocket->write( pData, nLen );
2467 return( bytesWritten );
2476 return(
m_pSocket->localAddress().toString() );
2495 return(
m_pSocket->peerAddress().toString() );