18 #include <QStringList>
19 #include <QCryptographicHash>
27 #include <sys/types.h>
33 #include <netinet/tcp.h>
44 #include "libmythbase/mythversion.h"
59 {
"gif" ,
"image/gif" },
60 {
"ico" ,
"image/x-icon" },
61 {
"jpeg",
"image/jpeg" },
62 {
"jpg" ,
"image/jpeg" },
63 {
"mng" ,
"image/x-mng" },
64 {
"png" ,
"image/png" },
65 {
"svg" ,
"image/svg+xml" },
66 {
"svgz",
"image/svg+xml" },
67 {
"tif" ,
"image/tiff" },
68 {
"tiff",
"image/tiff" },
70 {
"htm" ,
"text/html" },
71 {
"html",
"text/html" },
72 {
"qsp" ,
"text/html" },
73 {
"txt" ,
"text/plain" },
74 {
"xml" ,
"text/xml" },
75 {
"qxml",
"text/xml" },
76 {
"xslt",
"text/xml" },
77 {
"css" ,
"text/css" },
79 {
"crt" ,
"application/x-x509-ca-cert" },
80 {
"doc" ,
"application/vnd.ms-word" },
81 {
"gz" ,
"application/x-tar" },
82 {
"js" ,
"application/javascript" },
83 {
"m3u" ,
"application/x-mpegurl" },
84 {
"m3u8",
"application/x-mpegurl" },
85 {
"ogx" ,
"application/ogg" },
86 {
"pdf" ,
"application/pdf" },
87 {
"pem" ,
"application/x-x509-ca-cert" },
88 {
"qjs" ,
"application/javascript" },
89 {
"rm" ,
"application/vnd.rn-realmedia" },
90 {
"swf" ,
"application/x-shockwave-flash" },
91 {
"xls" ,
"application/vnd.ms-excel" },
92 {
"zip" ,
"application/x-tar" },
94 {
"aac" ,
"audio/mp4" },
95 {
"ac3" ,
"audio/vnd.dolby.dd-raw" },
96 {
"flac",
"audio/x-flac" },
97 {
"m4a" ,
"audio/x-m4a" },
98 {
"mid" ,
"audio/midi" },
99 {
"mka" ,
"audio/x-matroska" },
100 {
"mp3" ,
"audio/mpeg" },
101 {
"oga" ,
"audio/ogg" },
102 {
"ogg" ,
"audio/ogg" },
103 {
"wav" ,
"audio/wav" },
104 {
"wma" ,
"audio/x-ms-wma" },
106 {
"3gp" ,
"video/3gpp" },
107 {
"3g2" ,
"video/3gpp2" },
108 {
"asx" ,
"video/x-ms-asf" },
109 {
"asf" ,
"video/x-ms-asf" },
110 {
"avi" ,
"video/x-msvideo" },
111 {
"m2p" ,
"video/mp2p" },
112 {
"m4v" ,
"video/mp4" },
113 {
"mpeg",
"video/mp2p" },
114 {
"mpeg2",
"video/mp2p" },
115 {
"mpg" ,
"video/mp2p" },
116 {
"mpg2",
"video/mp2p" },
117 {
"mov" ,
"video/quicktime" },
118 {
"mp4" ,
"video/mp4" },
119 {
"mkv" ,
"video/x-matroska" },
120 {
"nuv" ,
"video/nupplevideo" },
121 {
"ogv" ,
"video/ogg" },
122 {
"ps" ,
"video/mp2p" },
123 {
"ts" ,
"video/mp2t" },
124 {
"vob" ,
"video/mpeg" },
125 {
"wmv" ,
"video/x-ms-wmv" },
127 {
"ttf" ,
"font/ttf" },
128 {
"woff" ,
"font/woff" },
129 {
"woff2",
"font/woff2" }
142 "<TITLE>Error %1</TITLE>"
143 "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
145 "<BODY><H1>%2.</H1></BODY>"
157 if (!values.isEmpty())
158 return values.last();
182 LOG(VB_HTTP, LOG_INFO,
183 QString(
"HTTPRequest::SentRequestType( %1 ) - returning Unknown.")
256 if (sTransferMode.isEmpty())
260 sTransferMode =
"Streaming";
262 sTransferMode =
"Interactive";
265 if (sTransferMode ==
"Streaming")
267 else if (sTransferMode ==
"Background")
269 else if (sTransferMode ==
"Interactive")
274 SetResponseHeader(
"contentFeatures.dlna.org",
"DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000");
278 for (
const auto & value : std::as_const(values))
281 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
284 QMap<QString, QString>::iterator it;
287 LOG(VB_HTTP, LOG_INFO, QString(
"(Response Header) %1: %2").arg(it.key(), it.value()));
310 LOG(VB_HTTP, LOG_INFO,
311 QString(
"HTTPRequest::SendResponse( None ) :%1 -> %2:")
325 if (
file.exists() &&
file.size() < (2LL * 1024 * 1024) &&
326 file.open(QIODevice::ReadOnly | QIODevice::Text))
337 LOG(VB_HTTP, LOG_INFO,
338 QString(
"HTTPRequest::SendResponse( File ) :%1 -> %2:")
347 LOG(VB_HTTP, LOG_INFO,
348 QString(
"HTTPRequest::SendResponse(xml/html) (%1) :%2 -> %3: %4")
360 LOG(VB_HTTP, LOG_INFO,
361 QString(
"HTTPRequest::SendResponse(%1) - Cached")
373 int nContentLen =
m_response.buffer().length();
379 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
380 std::cout <<
m_response.buffer().constData() << std::endl;
383 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Content Length: %1").arg(nContentLen));
392 bool gzip_found = std::any_of(values.cbegin(), values.cend(),
393 [](
const auto & value)
394 {return value.contains(
"gzip" ); });
396 if (( nContentLen > 0 ) && gzip_found)
399 compBuffer.setData( compressed );
401 if (!compBuffer.buffer().isEmpty())
403 pBuffer = &compBuffer;
406 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Compressed Content Length: %1").arg(compBuffer.buffer().length()));
414 nContentLen = pBuffer->buffer().length();
418 QByteArray sHeader = rHeader.toUtf8();
419 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").arg(sHeader.length()));
420 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
422 if (nBytes < sHeader.length())
424 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponse(): "
425 "Incomplete write of header, "
427 .arg(nBytes).arg(sHeader.length()));
437 qint64 bytesWritten =
SendData( pBuffer, 0, nContentLen );
440 if (bytesWritten != nContentLen)
441 LOG(VB_HTTP, LOG_ERR,
"HttpRequest::SendResponse(): Error occurred while writing response body.");
443 nBytes += bytesWritten;
456 long long llSize = 0;
457 long long llStart = 0;
460 LOG(VB_HTTP, LOG_INFO, QString(
"SendResponseFile ( %1 )").arg(sFileName));
465 QFile tmpFile( sFileName );
466 if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
475 llSize = llEnd = tmpFile.size( );
486 if (!sRange.isEmpty())
488 bRange =
ParseRange( sRange, llSize, &llStart, &llEnd );
495 if ((llSize > llStart) && (llSize > llEnd) && (llEnd > llStart))
504 llSize = (llEnd - llStart) + 1;
516 LOG(VB_HTTP, LOG_INFO,
517 QString(
"HTTPRequest::SendResponseFile(%1) - "
518 "invalid byte range %2-%3/%4")
519 .arg(sFileName) .arg(llStart) .arg(llEnd)
537 LOG(VB_HTTP, LOG_INFO,
538 QString(
"HTTPRequest::SendResponseFile(%1) - cannot find file!")
551 QByteArray sHeader = rHeader.toUtf8();
552 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").arg(sHeader.length()));
553 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
555 if (nBytes < sHeader.length())
557 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponseFile(): "
558 "Incomplete write of header, "
560 .arg(nBytes).arg(sHeader.length()));
568 LOG(VB_HTTP, LOG_DEBUG,
569 QString(
"SendResponseFile : size = %1, start = %2, end = %3")
570 .arg(llSize).arg(llStart).arg(llEnd));
574 long long sent =
SendFile( tmpFile, llStart, llSize );
578 LOG(VB_HTTP, LOG_INFO,
579 QString(
"SendResponseFile( %1 ) Error: %2 [%3]" )
580 .arg(sFileName) .arg(errno) .arg(strerror(errno)));
600 bool bShouldClose =
false;
603 if (!pDevice->isOpen())
605 pDevice->open( QIODevice::ReadOnly );
613 if ( !pDevice->seek( llStart ))
616 std::array<char,SENDFILE_BUFFER_SIZE> aBuffer {};
618 qint64 llBytesRemaining = llBytes;
619 qint64 llBytesToRead = 0;
620 qint64 llBytesRead = 0;
622 while ((sent < llBytes) && !pDevice->atEnd())
626 if (( llBytesRead = pDevice->read( aBuffer.data(), llBytesToRead )) != -1 )
628 if (
WriteBlock( aBuffer.data(), llBytesRead ) == -1)
634 llBytesRemaining -= llBytesRead;
650 qint64 sent =
SendData( (QIODevice *)(&
file), llStart, llBytes );
661 const QString &sFaultString,
662 const QString &sDetails )
669 stream << R
"(<?xml version="1.0" encoding="utf-8"?>)";
671 QString sWhere = ( bServerError ) ? "s:Server" :
"s:Client";
679 <<
"<faultcode>" << sWhere <<
"</faultcode>"
680 <<
"<faultstring>" << sFaultString <<
"</faultstring>";
683 if (!sDetails.isEmpty())
685 stream <<
"<detail>" << sDetails <<
"</detail>";
722 stream <<
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
729 <<
"<u:" <<
m_sMethod <<
"Response xmlns:u=\""
733 stream <<
"<" <<
m_sMethod <<
"Response>\r\n";
735 for (
const auto & arg : std::as_const(
args))
737 stream <<
"<" << arg.m_sName;
739 if (arg.m_pAttributes)
741 for (
const auto & attr : std::as_const(*arg.m_pAttributes))
743 stream <<
" " << attr.m_sName <<
"='"
744 <<
Encode( attr.m_sValue ) <<
"'";
751 stream <<
Encode( arg.m_sValue );
753 stream << arg.m_sValue;
755 stream <<
"</" << arg.m_sName <<
">\r\n";
760 stream <<
"</u:" <<
m_sMethod <<
"Response>\r\n"
764 stream <<
"</" <<
m_sMethod <<
"Response>\r\n";
796 ims.setTimeSpec(Qt::OffsetFromUTC);
797 if (ims.isValid() && ims <=
file.lastModified())
818 LOG(VB_HTTP, LOG_INFO,
819 QString(
"HTTPRequest::FormatFileResponse('%1') - cannot find file")
830 m_sProtocol = sLine.section(
'/', 0, 0 ).trimmed();
831 QString sVersion = sLine.section(
'/', 1 ).trimmed();
833 m_nMajor = sVersion.section(
'.', 0, 0 ).toInt();
834 m_nMinor = sVersion.section(
'.', 1 ).toInt();
875 if ((sType ==
"application/x-www-form-urlencoded" ) ||
876 (sType.startsWith(
"application/x-www-form-urlencoded;")))
879 if ((sType ==
"text/xml" ) ||
880 (sType.startsWith(
"text/xml;") ))
883 if ((sType ==
"application/json") ||
884 sType.startsWith(
"application/json;"))
899 case 200:
return(
"200 OK" );
900 case 201:
return(
"201 Created" );
901 case 202:
return(
"202 Accepted" );
902 case 204:
return(
"204 No Content" );
903 case 205:
return(
"205 Reset Content" );
904 case 206:
return(
"206 Partial Content" );
905 case 300:
return(
"300 Multiple Choices" );
906 case 301:
return(
"301 Moved Permanently" );
907 case 302:
return(
"302 Found" );
908 case 303:
return(
"303 See Other" );
909 case 304:
return(
"304 Not Modified" );
910 case 305:
return(
"305 Use Proxy" );
911 case 307:
return(
"307 Temporary Redirect" );
912 case 308:
return(
"308 Permanent Redirect" );
913 case 400:
return(
"400 Bad Request" );
914 case 401:
return(
"401 Unauthorized" );
915 case 403:
return(
"403 Forbidden" );
916 case 404:
return(
"404 Not Found" );
917 case 405:
return(
"405 Method Not Allowed" );
918 case 406:
return(
"406 Not Acceptable" );
919 case 408:
return(
"408 Request Timeout" );
920 case 410:
return(
"410 Gone" );
921 case 411:
return(
"411 Length Required" );
922 case 412:
return(
"412 Precondition Failed" );
923 case 413:
return(
"413 Request Entity Too Large" );
924 case 414:
return(
"414 Request-URI Too Long" );
925 case 415:
return(
"415 Unsupported Media Type" );
926 case 416:
return(
"416 Requested Range Not Satisfiable" );
927 case 417:
return(
"417 Expectation Failed" );
929 case 428:
return(
"428 Precondition Required" );
930 case 429:
return(
"429 Too Many Requests" );
931 case 431:
return(
"431 Request Header Fields Too Large" );
932 case 500:
return(
"500 Internal Server Error" );
933 case 501:
return(
"501 Not Implemented" );
934 case 502:
return(
"502 Bad Gateway" );
935 case 503:
return(
"503 Service Unavailable" );
936 case 504:
return(
"504 Gateway Timeout" );
937 case 505:
return(
"505 HTTP Version Not Supported" );
938 case 510:
return(
"510 Not Extended" );
939 case 511:
return(
"511 Network Authentication Required" );
971 return(
"text/plain" );
984 ext =
type.pszExtension;
986 if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
987 return(
type.pszType );
990 return(
"text/plain" );
999 QStringList mimeTypes;
1003 if (!mimeTypes.contains(
type.pszType ))
1004 mimeTypes.append(
type.pszType );
1016 QFileInfo info( sFileName );
1017 QString sLOC =
"HTTPRequest::TestMimeType(" + sFileName +
") - ";
1018 QString sSuffix = info.suffix().toLower();
1021 if ( sSuffix ==
"nuv" )
1024 QFile
file( sFileName );
1026 if (
file.open(QIODevice::ReadOnly | QIODevice::Text) )
1028 QByteArray head =
file.read(8);
1029 QString sHex = head.toHex();
1031 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"file starts with " + sHex);
1033 if ( sHex ==
"000001ba44000400" )
1034 sMIME =
"video/mp2p";
1036 if ( head ==
"MythTVVi" )
1039 head =
file.read(4);
1041 if ( head ==
"DIVX" )
1043 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"('MythTVVi...DIVXLAME')");
1044 sMIME =
"video/mp4";
1054 LOG(VB_GENERAL, LOG_ERR, sLOC +
"Could not read file");
1057 LOG(VB_HTTP, LOG_INFO, sLOC +
"type is " + sMIME);
1069 LOG(VB_HTTP, LOG_INFO, QString(
"sParams: '%1'").arg(sParams));
1074 sParams.replace(
"&",
"&" );
1076 if (!sParams.isEmpty())
1078 QStringList params = sParams.split(
'&', Qt::SkipEmptyParts);
1079 for (
const auto & param : std::as_const(params))
1081 QString sName = param.section(
'=', 0, 0 );
1082 QString sValue = param.section(
'=', 1 );
1083 sValue.replace(
"+",
" ");
1085 if (!sName.isEmpty())
1087 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1088 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1090 mapParams.insert( sName.trimmed(), sValue );
1127 sHeader += it.key() +
": ";
1128 sHeader += *it +
"\r\n";
1144 bool bKeepAlive =
true;
1153 QString sConnection =
GetRequestHeader(
"connection",
"default" ).toLower();
1155 QStringList sValueList = sConnection.split(
",");
1157 if ( sValueList.contains(
"close") )
1159 LOG(VB_HTTP, LOG_DEBUG,
"Client requested the connection be closed");
1162 else if (sValueList.contains(
"keep-alive"))
1174 QStringList sCookieList =
m_mapHeaders.values(
"cookie");
1176 QStringList::iterator it;
1177 for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1179 QString key = (*it).section(
'=', 0, 0);
1180 QString value = (*it).section(
'=', 1);
1192 bool bSuccess =
false;
1197 QString sRequestLine =
ReadLine( 2s );
1199 if ( sRequestLine.isEmpty() )
1201 LOG(VB_GENERAL, LOG_ERR,
"Timeout reading first line of request." );
1233 while (( !sLine.isEmpty() ) && !bDone )
1235 if (sLine !=
"\r\n")
1237 QString sName = sLine.section(
':', 0, 0 ).trimmed();
1238 QString sValue = sLine.section(
':', 1 );
1240 sValue.truncate( sValue.length() - 2 );
1242 if (!sName.isEmpty() && !sValue.isEmpty())
1244 m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1256 LOG(VB_HTTP, LOG_INFO, QString(
"(Request Header) %1: %2")
1257 .arg(it.key(), *it));
1269 LOG(VB_GENERAL, LOG_INFO,
"Timeout waiting for request header." );
1323 long nPayloadSize =
GetLastHeader(
"content-length" ).toLong();
1325 if (nPayloadSize > 0)
1327 char *pszPayload =
new char[ nPayloadSize + 2 ];
1330 nBytes =
ReadBlock( pszPayload, nPayloadSize, 5s );
1331 if (nBytes == nPayloadSize )
1333 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1343 LOG(VB_GENERAL, LOG_ERR,
1344 QString(
"Unable to read entire payload (read %1 of %2 bytes)")
1345 .arg( nBytes ) .arg( nPayloadSize ) );
1349 delete [] pszPayload;
1355 if (!sSOAPAction.isEmpty())
1362 LOG(VB_HTTP, LOG_DEBUG,
1363 QString(
"HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1364 "Method (%3) - Bytes in Socket Buffer (%4)")
1366 .arg(
m_sMethod) .arg(BytesAvailable()));
1371 LOG(VB_GENERAL, LOG_WARNING,
1372 "Unexpected exception in HTTPRequest::ParseRequest" );
1387 int nCount = tokens.count();
1391 if ( sLine.startsWith( QString(
"HTTP/") ))
1415 m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1421 QString sQueryStr = tokens[1].section(
'?', 1, 1 );
1423 if (!sQueryStr.isEmpty())
1451 long long *pllStart,
1459 if (sRange.isEmpty())
1471 sRange.remove( 0, nIdx );
1477 QStringList ranges = sRange.split(
',', Qt::SkipEmptyParts);
1478 if (ranges.count() == 0)
1485 QStringList parts = ranges[0].split(
'-');
1487 if (parts.count() != 2)
1490 if (parts[0].isEmpty() && parts[1].isEmpty())
1497 bool conv_ok =
false;
1498 if (parts[0].isEmpty())
1504 long long llValue = parts[1].toLongLong(&conv_ok);
1505 if (!conv_ok)
return false;
1507 *pllStart = llSize - llValue;
1508 *pllEnd = llSize - 1;
1510 else if (parts[1].isEmpty())
1516 *pllStart = parts[0].toLongLong(&conv_ok);
1521 *pllEnd = llSize - 1;
1529 *pllStart = parts[0].toLongLong(&conv_ok);
1530 if (!conv_ok)
return false;
1531 *pllEnd = parts[1].toLongLong(&conv_ok);
1532 if (!conv_ok)
return false;
1534 if (*pllStart > *pllEnd)
1538 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 Range Requested %2 - %3")
1553 static const QRegularExpression re {
"^http[s]?://.*?/"};
1556 QStringList sList =
m_sBaseUrl.split(
'/', Qt::SkipEmptyParts);
1559 if (!sList.isEmpty())
1566 LOG(VB_HTTP, LOG_INFO, QString(
"ExtractMethodFromURL(end) : %1 : %2")
1576 bool bSuccess =
false;
1582 LOG(VB_HTTP, LOG_INFO,
1583 QString(
"HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1584 QDomDocument doc (
"request" );
1590 if (!doc.setContent(
m_sPayload,
true, &sErrMsg, &nErrLine, &nErrCol ))
1592 LOG(VB_GENERAL, LOG_ERR,
1593 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1594 .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1604 if (sSOAPAction.contains(
'#' ))
1606 m_sNameSpace = sSOAPAction.section(
'#', 0, 0).remove( 0, 1);
1607 m_sMethod = sSOAPAction.section(
'#', 1 );
1612 if (sSOAPAction.contains(
'/' ))
1614 int nPos = sSOAPAction.lastIndexOf(
'/' );
1617 sSOAPAction.length() - nPos - 2);
1634 if (oNodeList.count() == 0)
1637 doc.elementsByTagNameNS(
"http://schemas.xmlsoap.org/soap/envelope/",
1641 if (oNodeList.count() > 0)
1643 QDomNode oMethod = oNodeList.item(0);
1645 if (!oMethod.isNull())
1649 for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1650 oNode = oNode.nextSibling() )
1652 QDomElement e = oNode.toElement();
1656 QString sName = e.tagName();
1657 QString sValue =
"";
1659 QDomText oText = oNode.firstChild().toText();
1661 if (!oText.isNull())
1662 sValue = oText.nodeValue();
1664 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1665 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1667 m_mapParams.insert( sName.trimmed().toLower(), sValue );
1695 if (sAccept.contains(
"application/json", Qt::CaseInsensitive ) ||
1696 sAccept.contains(
"text/javascript", Qt::CaseInsensitive ))
1701 else if (sAccept.contains(
"text/x-apple-plist+xml", Qt::CaseInsensitive ))
1709 if (pSerializer ==
nullptr)
1723 LOG(VB_HTTP, LOG_DEBUG,
1724 QString(
"HTTPRequest::Encode Input : %1").arg(sStr));
1726 sStr.replace(
'&',
"&" );
1727 sStr.replace(
'<',
"<" );
1728 sStr.replace(
'>',
">" );
1729 sStr.replace(
'"',
""");
1730 sStr.replace(
"'",
"'");
1733 LOG(VB_HTTP, LOG_DEBUG,
1734 QString(
"HTTPRequest::Encode Output : %1").arg(sStr));
1746 sStr.replace(
"&",
"&");
1747 sStr.replace(
"<",
"<");
1748 sStr.replace(
">",
">");
1749 sStr.replace(
""",
"\"");
1750 sStr.replace(
"'",
"'");
1761 QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1763 return (
"\"" + hash.toHex() +
"\"");
1774 QStringList oList = sProtected.split(
';' );
1776 for(
int nIdx = 0; nIdx < oList.count(); nIdx++)
1778 if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1794 QString realm =
"MythTV";
1802 QString stale = isStale ?
"true" :
"false";
1803 authHeader = QString(
"Digest realm=\"%1\",nonce=\"%2\","
1804 "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1805 .arg(realm, nonce, stale);
1809 authHeader = QString(
"Basic realm=\"%1\"").arg(realm);
1822 QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex();
1823 QString nonce = QString(
"%1%2").arg(timeStamp, hash);
1833 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Basic Authentication");
1834 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
1838 LOG(VB_GENERAL, LOG_WARNING,
"Basic authentication is only allowed for HTTP 1.0");
1842 QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1844 oList = sCredentials.split(
':' );
1846 if (oList.count() < 2)
1848 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid number of tokens");
1852 QString sUsername = oList[0];
1853 QString sPassword = oList[1];
1855 if (sUsername ==
"nouser")
1861 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
1865 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
1871 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password");
1875 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
1892 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Digest Authentication");
1893 QString realm =
"MythTV";
1895 QString authMethod =
GetLastHeader(
"authorization" ).section(
' ', 0, 0).toLower();
1897 if (authMethod !=
"digest")
1899 LOG(VB_GENERAL, LOG_WARNING,
"Invalid method in Authorization header");
1903 QString parameterStr =
GetLastHeader(
"authorization" ).section(
' ', 1);
1905 QMap<QString, QString> paramMap;
1906 QStringList paramList = parameterStr.split(
',');
1907 QStringList::iterator it;
1908 for (it = paramList.begin(); it != paramList.end(); ++it)
1910 QString key = (*it).section(
'=', 0, 0).toLower().trimmed();
1912 QString value = (*it).section(
'=', 1).trimmed();
1915 paramMap[key] = value;
1918 if (paramMap.size() < 8)
1920 LOG(VB_GENERAL, LOG_WARNING,
"Invalid number of parameters in Authorization header");
1924 if (paramMap[
"nonce"].isEmpty() || paramMap[
"username"].isEmpty() ||
1925 paramMap[
"realm"].isEmpty() || paramMap[
"uri"].isEmpty() ||
1926 paramMap[
"response"].isEmpty() || paramMap[
"qop"].isEmpty() ||
1927 paramMap[
"cnonce"].isEmpty() || paramMap[
"nc"].isEmpty())
1929 LOG(VB_GENERAL, LOG_WARNING,
"Missing required parameters in Authorization header");
1933 if (paramMap[
"username"] ==
"nouser")
1938 LOG(VB_GENERAL, LOG_WARNING,
"Authorization URI doesn't match the "
1944 if (paramMap[
"realm"] != realm)
1946 LOG(VB_GENERAL, LOG_WARNING,
"Authorization realm doesn't match the "
1947 "realm of the requested content");
1951 QByteArray nonce = paramMap[
"nonce"].toLatin1();
1952 if (nonce.length() < 20)
1954 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce is too short");
1958 QString nonceTimeStampStr = nonce.left(20);
1961 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce doesn't match reference");
1962 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 vs %2").arg(QString(nonce),
1967 constexpr std::chrono::seconds AUTH_TIMEOUT { 2min };
1969 if (!nonceTimeStamp.isValid())
1971 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce timestamp is invalid.");
1972 LOG(VB_HTTP, LOG_DEBUG, QString(
"Timestamp was '%1'").arg(nonceTimeStampStr));
1978 LOG(VB_HTTP, LOG_NOTICE,
"Authorization nonce timestamp is invalid or too old.");
1989 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
1993 if (paramMap[
"response"].length() != 32)
1995 LOG(VB_GENERAL, LOG_WARNING,
"Authorization response field is invalid length");
2003 QString methodDigest = QString(
"%1:%2").arg(
GetRequestType(), paramMap[
"uri"]);
2004 QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2005 QCryptographicHash::Md5).toHex();
2007 QString responseDigest = QString(
"%1:%2:%3:%4:%5:%6").arg(a1,
2013 QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2014 QCryptographicHash::Md5).toHex();
2016 if (paramMap[
"response"].toLatin1() == kd)
2018 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
2019 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
2025 LOG(VB_GENERAL, LOG_ERR,
"Valid Authorization received, but we "
2026 "failed to create a valid session");
2039 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password digest");
2040 LOG(VB_HTTP, LOG_DEBUG, QString(
"Received hash was '%1', calculated hash was '%2'")
2041 .arg(paramMap[
"response"], QString(kd)));
2056 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
2058 if (oList.count() < 2)
2061 if (oList[0].compare(
"basic", Qt::CaseInsensitive ) == 0)
2063 if (oList[0].compare(
"digest", Qt::CaseInsensitive ) == 0)
2087 const QDateTime &expiryDate,
bool secure)
2091 LOG(VB_GENERAL, LOG_WARNING, QString(
"HTTPRequest::SetCookie(%1=%2): "
2092 "A secure cookie cannot be set on an unencrypted connection.")
2093 .arg(sKey, sValue));
2097 QStringList cookieAttributes;
2100 cookieAttributes.append(QString(
"%1=%2").arg(sKey, sValue));
2106 cookieAttributes.append(
"Path=/");
2110 cookieAttributes.append(QString(
"Expires=%1").arg(expires));
2115 cookieAttributes.append(
"Secure");
2118 cookieAttributes.append(
"HttpOnly");
2140 return hostname.section(
"]:", 0 , 0);
2144 return hostname.section(
":", 0 , 0);
2183 type =
"UNSUBSCRIBE";
2221 QStringList allowedOrigins;
2225 serverStatusPort + 10);
2227 QString masterAddrPort = QString(
"%1:%2")
2229 .arg(serverStatusPort);
2230 QString masterTLSAddrPort = QString(
"%1:%2")
2232 .arg(backendSSLPort);
2234 allowedOrigins << QString(
"http://%1").arg(masterAddrPort);
2235 allowedOrigins << QString(
"https://%2").arg(masterTLSAddrPort);
2237 QString localhostname = QHostInfo::localHostName();
2238 if (!localhostname.isEmpty())
2240 allowedOrigins << QString(
"http://%1:%2")
2241 .arg(localhostname).arg(serverStatusPort);
2242 allowedOrigins << QString(
"https://%1:%2")
2243 .arg(localhostname).arg(backendSSLPort);
2246 QStringList allowedOriginsList =
2248 "https://chromecast.mythtv.org")).split(
",");
2250 for (
const auto & origin : std::as_const(allowedOriginsList))
2252 if (origin.isEmpty())
2255 if (origin ==
"*" || (!origin.startsWith(
"http://") &&
2256 !origin.startsWith(
"https://")))
2258 LOG(VB_GENERAL, LOG_ERR, QString(
"Illegal AllowedOriginsList"
2259 " entry '%1'. Must start with http[s]:// and not be *")
2264 allowedOrigins << origin;
2270 for (
const auto & origin : std::as_const(allowedOrigins))
2271 LOG(VB_HTTP, LOG_DEBUG, QString(
"Will allow Origin: %1").arg(origin));
2274 if (allowedOrigins.contains(sOrigin))
2279 LOG(VB_HTTP, LOG_DEBUG, QString(
"Allow-Origin: %1)").arg(sOrigin));
2283 LOG(VB_GENERAL, LOG_CRIT, QString(
"HTTPRequest: Cross-origin request "
2284 "received with origin (%1)")
2302 m_pSocket->state() == QAbstractSocket::ConnectedState)
2311 if ( timer.
elapsed() >= msecs )
2314 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2330 std::chrono::milliseconds msecs)
2333 m_pSocket->state() == QAbstractSocket::ConnectedState)
2336 return(
m_pSocket->read( pData, nMaxLen ));
2338 bool bTimeout =
false;
2341 while ( (
m_pSocket->bytesAvailable() < (
int)nMaxLen) && !bTimeout )
2343 bTimeout = !(
m_pSocket->waitForReadyRead( msecs.count() ));
2345 if ( timer.
elapsed() >= msecs )
2348 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2354 return(
m_pSocket->read( pData, nMaxLen ));
2366 qint64 bytesWritten = -1;
2368 m_pSocket->state() == QAbstractSocket::ConnectedState)
2370 bytesWritten =
m_pSocket->write( pData, nLen );
2374 return( bytesWritten );
2383 return(
m_pSocket->localAddress().toString() );
2402 return(
m_pSocket->peerAddress().toString() );