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())
625 llBytesRead = pDevice->read( aBuffer.data(), llBytesToRead );
626 if ( llBytesRead != -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=\""
734 stream <<
"<" <<
m_sMethod <<
"Response>\r\n";
737 for (
const auto & arg : std::as_const(
args))
739 stream <<
"<" << arg.m_sName;
741 if (arg.m_pAttributes)
743 for (
const auto & attr : std::as_const(*arg.m_pAttributes))
745 stream <<
" " << attr.m_sName <<
"='"
746 <<
Encode( attr.m_sValue ) <<
"'";
753 stream <<
Encode( arg.m_sValue );
755 stream << arg.m_sValue;
757 stream <<
"</" << arg.m_sName <<
">\r\n";
762 stream <<
"</u:" <<
m_sMethod <<
"Response>\r\n"
767 stream <<
"</" <<
m_sMethod <<
"Response>\r\n";
800 ims.setTimeSpec(Qt::OffsetFromUTC);
801 if (ims.isValid() && ims <=
file.lastModified())
822 LOG(VB_HTTP, LOG_INFO,
823 QString(
"HTTPRequest::FormatFileResponse('%1') - cannot find file")
834 m_sProtocol = sLine.section(
'/', 0, 0 ).trimmed();
835 QString sVersion = sLine.section(
'/', 1 ).trimmed();
837 m_nMajor = sVersion.section(
'.', 0, 0 ).toInt();
838 m_nMinor = sVersion.section(
'.', 1 ).toInt();
879 if ((sType ==
"application/x-www-form-urlencoded" ) ||
880 (sType.startsWith(
"application/x-www-form-urlencoded;")))
883 if ((sType ==
"text/xml" ) ||
884 (sType.startsWith(
"text/xml;") ))
887 if ((sType ==
"application/json") ||
888 sType.startsWith(
"application/json;"))
903 case 200:
return(
"200 OK" );
904 case 201:
return(
"201 Created" );
905 case 202:
return(
"202 Accepted" );
906 case 204:
return(
"204 No Content" );
907 case 205:
return(
"205 Reset Content" );
908 case 206:
return(
"206 Partial Content" );
909 case 300:
return(
"300 Multiple Choices" );
910 case 301:
return(
"301 Moved Permanently" );
911 case 302:
return(
"302 Found" );
912 case 303:
return(
"303 See Other" );
913 case 304:
return(
"304 Not Modified" );
914 case 305:
return(
"305 Use Proxy" );
915 case 307:
return(
"307 Temporary Redirect" );
916 case 308:
return(
"308 Permanent Redirect" );
917 case 400:
return(
"400 Bad Request" );
918 case 401:
return(
"401 Unauthorized" );
919 case 403:
return(
"403 Forbidden" );
920 case 404:
return(
"404 Not Found" );
921 case 405:
return(
"405 Method Not Allowed" );
922 case 406:
return(
"406 Not Acceptable" );
923 case 408:
return(
"408 Request Timeout" );
924 case 410:
return(
"410 Gone" );
925 case 411:
return(
"411 Length Required" );
926 case 412:
return(
"412 Precondition Failed" );
927 case 413:
return(
"413 Request Entity Too Large" );
928 case 414:
return(
"414 Request-URI Too Long" );
929 case 415:
return(
"415 Unsupported Media Type" );
930 case 416:
return(
"416 Requested Range Not Satisfiable" );
931 case 417:
return(
"417 Expectation Failed" );
933 case 428:
return(
"428 Precondition Required" );
934 case 429:
return(
"429 Too Many Requests" );
935 case 431:
return(
"431 Request Header Fields Too Large" );
936 case 500:
return(
"500 Internal Server Error" );
937 case 501:
return(
"501 Not Implemented" );
938 case 502:
return(
"502 Bad Gateway" );
939 case 503:
return(
"503 Service Unavailable" );
940 case 504:
return(
"504 Gateway Timeout" );
941 case 505:
return(
"505 HTTP Version Not Supported" );
942 case 510:
return(
"510 Not Extended" );
943 case 511:
return(
"511 Network Authentication Required" );
975 return(
"text/plain" );
988 ext =
type.pszExtension;
990 if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
991 return(
type.pszType );
994 return(
"text/plain" );
1003 QStringList mimeTypes;
1007 if (!mimeTypes.contains(
type.pszType ))
1008 mimeTypes.append(
type.pszType );
1020 QFileInfo
info( sFileName );
1021 QString sLOC =
"HTTPRequest::TestMimeType(" + sFileName +
") - ";
1022 QString sSuffix =
info.suffix().toLower();
1025 if ( sSuffix ==
"nuv" )
1028 QFile
file( sFileName );
1030 if (
file.open(QIODevice::ReadOnly | QIODevice::Text) )
1032 QByteArray head =
file.read(8);
1033 QString sHex = head.toHex();
1035 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"file starts with " + sHex);
1037 if ( sHex ==
"000001ba44000400" )
1038 sMIME =
"video/mp2p";
1040 if ( head ==
"MythTVVi" )
1043 head =
file.read(4);
1045 if ( head ==
"DIVX" )
1047 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"('MythTVVi...DIVXLAME')");
1048 sMIME =
"video/mp4";
1059 LOG(VB_GENERAL, LOG_ERR, sLOC +
"Could not read file");
1063 LOG(VB_HTTP, LOG_INFO, sLOC +
"type is " + sMIME);
1075 LOG(VB_HTTP, LOG_INFO, QString(
"sParams: '%1'").arg(sParams));
1080 sParams.replace(
"&",
"&" );
1082 if (!sParams.isEmpty())
1084 QStringList params = sParams.split(
'&', Qt::SkipEmptyParts);
1085 for (
const auto & param : std::as_const(params))
1087 QString sName = param.section(
'=', 0, 0 );
1088 QString sValue = param.section(
'=', 1 );
1089 sValue.replace(
"+",
" ");
1091 if (!sName.isEmpty())
1093 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1094 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1096 mapParams.insert( sName.trimmed(), sValue );
1133 sHeader += it.key() +
": ";
1134 sHeader += *it +
"\r\n";
1150 bool bKeepAlive =
true;
1159 QString sConnection =
GetRequestHeader(
"connection",
"default" ).toLower();
1161 QStringList sValueList = sConnection.split(
",");
1163 if ( sValueList.contains(
"close") )
1165 LOG(VB_HTTP, LOG_DEBUG,
"Client requested the connection be closed");
1168 else if (sValueList.contains(
"keep-alive"))
1182 QStringList sCookieList =
m_mapHeaders.values(
"cookie");
1184 QStringList::iterator it;
1185 for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1187 QString key = (*it).section(
'=', 0, 0);
1188 QString value = (*it).section(
'=', 1);
1200 bool bSuccess =
false;
1205 QString sRequestLine =
ReadLine( 2s );
1207 if ( sRequestLine.isEmpty() )
1209 LOG(VB_GENERAL, LOG_ERR,
"Timeout reading first line of request." );
1241 while (( !sLine.isEmpty() ) && !bDone )
1243 if (sLine !=
"\r\n")
1245 QString sName = sLine.section(
':', 0, 0 ).trimmed();
1246 QString sValue = sLine.section(
':', 1 );
1248 sValue.truncate( sValue.length() - 2 );
1250 if (!sName.isEmpty() && !sValue.isEmpty())
1252 m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1266 LOG(VB_HTTP, LOG_INFO, QString(
"(Request Header) %1: %2")
1267 .arg(it.key(), *it));
1279 LOG(VB_GENERAL, LOG_INFO,
"Timeout waiting for request header." );
1333 long nPayloadSize =
GetLastHeader(
"content-length" ).toLong();
1335 if (nPayloadSize > 0)
1337 char *pszPayload =
new char[ nPayloadSize + 2 ];
1340 nBytes =
ReadBlock( pszPayload, nPayloadSize, 5s );
1341 if (nBytes == nPayloadSize )
1343 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1353 LOG(VB_GENERAL, LOG_ERR,
1354 QString(
"Unable to read entire payload (read %1 of %2 bytes)")
1355 .arg( nBytes ) .arg( nPayloadSize ) );
1359 delete [] pszPayload;
1365 if (!sSOAPAction.isEmpty())
1372 LOG(VB_HTTP, LOG_DEBUG,
1373 QString(
"HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1374 "Method (%3) - Bytes in Socket Buffer (%4)")
1376 .arg(
m_sMethod) .arg(BytesAvailable()));
1381 LOG(VB_GENERAL, LOG_WARNING,
1382 "Unexpected exception in HTTPRequest::ParseRequest" );
1397 int nCount = tokens.count();
1401 if ( sLine.startsWith( QString(
"HTTP/") ))
1425 m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1431 QString sQueryStr = tokens[1].section(
'?', 1, 1 );
1433 if (!sQueryStr.isEmpty())
1461 long long *pllStart,
1469 if (sRange.isEmpty())
1481 sRange.remove( 0, nIdx );
1487 QStringList ranges = sRange.split(
',', Qt::SkipEmptyParts);
1488 if (ranges.count() == 0)
1495 QStringList parts = ranges[0].split(
'-');
1497 if (parts.count() != 2)
1500 if (parts[0].isEmpty() && parts[1].isEmpty())
1507 bool conv_ok =
false;
1508 if (parts[0].isEmpty())
1514 long long llValue = parts[1].toLongLong(&conv_ok);
1515 if (!conv_ok)
return false;
1517 *pllStart = llSize - llValue;
1518 *pllEnd = llSize - 1;
1520 else if (parts[1].isEmpty())
1526 *pllStart = parts[0].toLongLong(&conv_ok);
1531 *pllEnd = llSize - 1;
1539 *pllStart = parts[0].toLongLong(&conv_ok);
1540 if (!conv_ok)
return false;
1541 *pllEnd = parts[1].toLongLong(&conv_ok);
1542 if (!conv_ok)
return false;
1544 if (*pllStart > *pllEnd)
1548 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 Range Requested %2 - %3")
1563 static const QRegularExpression re {
"^http[s]?://.*?/"};
1566 QStringList sList =
m_sBaseUrl.split(
'/', Qt::SkipEmptyParts);
1569 if (!sList.isEmpty())
1576 LOG(VB_HTTP, LOG_INFO, QString(
"ExtractMethodFromURL(end) : %1 : %2")
1586 bool bSuccess =
false;
1592 LOG(VB_HTTP, LOG_INFO,
1593 QString(
"HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1594 QDomDocument doc (
"request" );
1600 if (!doc.setContent(
m_sPayload,
true, &sErrMsg, &nErrLine, &nErrCol ))
1602 LOG(VB_GENERAL, LOG_ERR,
1603 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1604 .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1614 if (sSOAPAction.contains(
'#' ))
1616 m_sNameSpace = sSOAPAction.section(
'#', 0, 0).remove( 0, 1);
1617 m_sMethod = sSOAPAction.section(
'#', 1 );
1622 if (sSOAPAction.contains(
'/' ))
1624 int nPos = sSOAPAction.lastIndexOf(
'/' );
1627 sSOAPAction.length() - nPos - 2);
1644 if (oNodeList.count() == 0)
1647 doc.elementsByTagNameNS(
"http://schemas.xmlsoap.org/soap/envelope/",
1651 if (oNodeList.count() > 0)
1653 QDomNode oMethod = oNodeList.item(0);
1655 if (!oMethod.isNull())
1659 for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1660 oNode = oNode.nextSibling() )
1662 QDomElement e = oNode.toElement();
1666 QString sName = e.tagName();
1667 QString sValue =
"";
1669 QDomText oText = oNode.firstChild().toText();
1671 if (!oText.isNull())
1672 sValue = oText.nodeValue();
1674 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1675 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1677 m_mapParams.insert( sName.trimmed().toLower(), sValue );
1705 if (sAccept.contains(
"application/json", Qt::CaseInsensitive ) ||
1706 sAccept.contains(
"text/javascript", Qt::CaseInsensitive ))
1711 else if (sAccept.contains(
"text/x-apple-plist+xml", Qt::CaseInsensitive ))
1719 if (pSerializer ==
nullptr)
1733 LOG(VB_HTTP, LOG_DEBUG,
1734 QString(
"HTTPRequest::Encode Input : %1").arg(sStr));
1736 sStr.replace(
'&',
"&" );
1737 sStr.replace(
'<',
"<" );
1738 sStr.replace(
'>',
">" );
1739 sStr.replace(
'"',
""");
1740 sStr.replace(
"'",
"'");
1743 LOG(VB_HTTP, LOG_DEBUG,
1744 QString(
"HTTPRequest::Encode Output : %1").arg(sStr));
1756 sStr.replace(
"&",
"&");
1757 sStr.replace(
"<",
"<");
1758 sStr.replace(
">",
">");
1759 sStr.replace(
""",
"\"");
1760 sStr.replace(
"'",
"'");
1771 QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1773 return (
"\"" + hash.toHex() +
"\"");
1784 QStringList oList = sProtected.split(
';' );
1786 for(
int nIdx = 0; nIdx < oList.count(); nIdx++)
1788 if (sBaseUrl.startsWith( oList[nIdx], Qt::CaseInsensitive ))
1804 QString realm =
"MythTV";
1812 QString stale = isStale ?
"true" :
"false";
1813 authHeader = QString(
"Digest realm=\"%1\",nonce=\"%2\","
1814 "qop=\"auth\",stale=\"%3\",algorithm=\"MD5\"")
1815 .arg(realm, nonce, stale);
1819 authHeader = QString(
"Basic realm=\"%1\"").arg(realm);
1832 QString hash = QCryptographicHash::hash( uniqueID.toLatin1(), QCryptographicHash::Sha1).toHex();
1833 QString nonce = QString(
"%1%2").arg(timeStamp, hash);
1843 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Basic Authentication");
1844 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
1848 LOG(VB_GENERAL, LOG_WARNING,
"Basic authentication is only allowed for HTTP 1.0");
1852 QString sCredentials = QByteArray::fromBase64( oList[1].toUtf8() );
1854 oList = sCredentials.split(
':' );
1856 if (oList.count() < 2)
1858 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid number of tokens");
1862 QString sUsername = oList[0];
1863 QString sPassword = oList[1];
1865 if (sUsername ==
"nouser")
1871 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
1875 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
1881 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password");
1885 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
1902 LOG(VB_HTTP, LOG_NOTICE,
"Attempting HTTP Digest Authentication");
1903 QString realm =
"MythTV";
1905 QString authMethod =
GetLastHeader(
"authorization" ).section(
' ', 0, 0).toLower();
1907 if (authMethod !=
"digest")
1909 LOG(VB_GENERAL, LOG_WARNING,
"Invalid method in Authorization header");
1913 QString parameterStr =
GetLastHeader(
"authorization" ).section(
' ', 1);
1915 QMap<QString, QString> paramMap;
1916 QStringList paramList = parameterStr.split(
',');
1917 QStringList::iterator it;
1918 for (it = paramList.begin(); it != paramList.end(); ++it)
1920 QString key = (*it).section(
'=', 0, 0).toLower().trimmed();
1922 QString value = (*it).section(
'=', 1).trimmed();
1925 paramMap[key] = value;
1928 if (paramMap.size() < 8)
1930 LOG(VB_GENERAL, LOG_WARNING,
"Invalid number of parameters in Authorization header");
1934 if (paramMap[
"nonce"].isEmpty() || paramMap[
"username"].isEmpty() ||
1935 paramMap[
"realm"].isEmpty() || paramMap[
"uri"].isEmpty() ||
1936 paramMap[
"response"].isEmpty() || paramMap[
"qop"].isEmpty() ||
1937 paramMap[
"cnonce"].isEmpty() || paramMap[
"nc"].isEmpty())
1939 LOG(VB_GENERAL, LOG_WARNING,
"Missing required parameters in Authorization header");
1943 if (paramMap[
"username"] ==
"nouser")
1948 LOG(VB_GENERAL, LOG_WARNING,
"Authorization URI doesn't match the "
1954 if (paramMap[
"realm"] != realm)
1956 LOG(VB_GENERAL, LOG_WARNING,
"Authorization realm doesn't match the "
1957 "realm of the requested content");
1961 QByteArray nonce = paramMap[
"nonce"].toLatin1();
1962 if (nonce.length() < 20)
1964 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce is too short");
1968 QString nonceTimeStampStr = nonce.left(20);
1971 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce doesn't match reference");
1972 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 vs %2").arg(QString(nonce),
1977 constexpr std::chrono::seconds AUTH_TIMEOUT { 2min };
1979 if (!nonceTimeStamp.isValid())
1981 LOG(VB_GENERAL, LOG_WARNING,
"Authorization nonce timestamp is invalid.");
1982 LOG(VB_HTTP, LOG_DEBUG, QString(
"Timestamp was '%1'").arg(nonceTimeStampStr));
1988 LOG(VB_HTTP, LOG_NOTICE,
"Authorization nonce timestamp is invalid or too old.");
1999 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid username");
2003 if (paramMap[
"response"].length() != 32)
2005 LOG(VB_GENERAL, LOG_WARNING,
"Authorization response field is invalid length");
2013 QString methodDigest = QString(
"%1:%2").arg(
GetRequestType(), paramMap[
"uri"]);
2014 QByteArray a2 = QCryptographicHash::hash(methodDigest.toLatin1(),
2015 QCryptographicHash::Md5).toHex();
2017 QString responseDigest = QString(
"%1:%2:%3:%4:%5:%6").arg(a1,
2023 QByteArray kd = QCryptographicHash::hash(responseDigest.toLatin1(),
2024 QCryptographicHash::Md5).toHex();
2026 if (paramMap[
"response"].toLatin1() == kd)
2028 LOG(VB_HTTP, LOG_NOTICE,
"Valid Authorization received");
2029 QString client = QString(
"WebFrontend_%1").arg(
GetPeerAddress());
2035 LOG(VB_GENERAL, LOG_ERR,
"Valid Authorization received, but we "
2036 "failed to create a valid session");
2049 LOG(VB_GENERAL, LOG_WARNING,
"Authorization attempt with invalid password digest");
2050 LOG(VB_HTTP, LOG_DEBUG, QString(
"Received hash was '%1', calculated hash was '%2'")
2051 .arg(paramMap[
"response"], QString(kd)));
2066 QStringList oList =
GetLastHeader(
"authorization" ).split(
' ' );
2068 if (oList.count() < 2)
2071 if (oList[0].compare(
"basic", Qt::CaseInsensitive ) == 0)
2073 if (oList[0].compare(
"digest", Qt::CaseInsensitive ) == 0)
2097 const QDateTime &expiryDate,
bool secure)
2101 LOG(VB_GENERAL, LOG_WARNING, QString(
"HTTPRequest::SetCookie(%1=%2): "
2102 "A secure cookie cannot be set on an unencrypted connection.")
2103 .arg(sKey, sValue));
2107 QStringList cookieAttributes;
2110 cookieAttributes.append(QString(
"%1=%2").arg(sKey, sValue));
2116 cookieAttributes.append(
"Path=/");
2120 cookieAttributes.append(QString(
"Expires=%1").arg(expires));
2125 cookieAttributes.append(
"Secure");
2128 cookieAttributes.append(
"HttpOnly");
2150 return hostname.section(
"]:", 0 , 0);
2154 return hostname.section(
":", 0 , 0);
2193 type =
"UNSUBSCRIBE";
2231 QStringList allowedOrigins;
2235 serverStatusPort + 10);
2237 QString masterAddrPort = QString(
"%1:%2")
2239 .arg(serverStatusPort);
2240 QString masterTLSAddrPort = QString(
"%1:%2")
2242 .arg(backendSSLPort);
2244 allowedOrigins << QString(
"http://%1").arg(masterAddrPort);
2245 allowedOrigins << QString(
"https://%2").arg(masterTLSAddrPort);
2247 QString localhostname = QHostInfo::localHostName();
2248 if (!localhostname.isEmpty())
2250 allowedOrigins << QString(
"http://%1:%2")
2251 .arg(localhostname).arg(serverStatusPort);
2252 allowedOrigins << QString(
"https://%1:%2")
2253 .arg(localhostname).arg(backendSSLPort);
2256 QStringList allowedOriginsList =
2258 "https://chromecast.mythtv.org")).split(
",");
2260 for (
const auto & origin : std::as_const(allowedOriginsList))
2262 if (origin.isEmpty())
2265 if (origin ==
"*" || (!origin.startsWith(
"http://") &&
2266 !origin.startsWith(
"https://")))
2268 LOG(VB_GENERAL, LOG_ERR, QString(
"Illegal AllowedOriginsList"
2269 " entry '%1'. Must start with http[s]:// and not be *")
2274 allowedOrigins << origin;
2280 for (
const auto & origin : std::as_const(allowedOrigins))
2281 LOG(VB_HTTP, LOG_DEBUG, QString(
"Will allow Origin: %1").arg(origin));
2284 if (allowedOrigins.contains(sOrigin))
2289 LOG(VB_HTTP, LOG_DEBUG, QString(
"Allow-Origin: %1)").arg(sOrigin));
2293 LOG(VB_GENERAL, LOG_CRIT, QString(
"HTTPRequest: Cross-origin request "
2294 "received with origin (%1)")
2312 m_pSocket->state() == QAbstractSocket::ConnectedState)
2321 if ( timer.
elapsed() >= msecs )
2324 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2340 std::chrono::milliseconds msecs)
2343 m_pSocket->state() == QAbstractSocket::ConnectedState)
2346 return(
m_pSocket->read( pData, nMaxLen ));
2348 bool bTimeout =
false;
2351 while ( (
m_pSocket->bytesAvailable() < (
int)nMaxLen) && !bTimeout )
2353 bTimeout = !(
m_pSocket->waitForReadyRead( msecs.count() ));
2355 if ( timer.
elapsed() >= msecs )
2358 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2364 return(
m_pSocket->read( pData, nMaxLen ));
2376 qint64 bytesWritten = -1;
2378 m_pSocket->state() == QAbstractSocket::ConnectedState)
2380 bytesWritten =
m_pSocket->write( pData, nLen );
2384 return( bytesWritten );
2393 return(
m_pSocket->localAddress().toString() );
2412 return(
m_pSocket->peerAddress().toString() );