16#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
17#include <QtSystemDetection>
23#include <QCryptographicHash>
39#include <netinet/tcp.h>
48#include "libmythbase/mythversion.h"
123 { .pszExtension=
"gif" , .pszType=
"image/gif" },
124 { .pszExtension=
"ico" , .pszType=
"image/x-icon" },
125 { .pszExtension=
"jpeg", .pszType=
"image/jpeg" },
126 { .pszExtension=
"jpg" , .pszType=
"image/jpeg" },
127 { .pszExtension=
"mng" , .pszType=
"image/x-mng" },
128 { .pszExtension=
"png" , .pszType=
"image/png" },
129 { .pszExtension=
"svg" , .pszType=
"image/svg+xml" },
130 { .pszExtension=
"svgz", .pszType=
"image/svg+xml" },
131 { .pszExtension=
"tif" , .pszType=
"image/tiff" },
132 { .pszExtension=
"tiff", .pszType=
"image/tiff" },
134 { .pszExtension=
"htm" , .pszType=
"text/html" },
135 { .pszExtension=
"html", .pszType=
"text/html" },
136 { .pszExtension=
"qsp" , .pszType=
"text/html" },
137 { .pszExtension=
"txt" , .pszType=
"text/plain" },
138 { .pszExtension=
"xml" , .pszType=
"text/xml" },
139 { .pszExtension=
"qxml", .pszType=
"text/xml" },
140 { .pszExtension=
"xslt", .pszType=
"text/xml" },
141 { .pszExtension=
"css" , .pszType=
"text/css" },
143 { .pszExtension=
"crt" , .pszType=
"application/x-x509-ca-cert" },
144 { .pszExtension=
"doc" , .pszType=
"application/vnd.ms-word" },
145 { .pszExtension=
"gz" , .pszType=
"application/x-tar" },
146 { .pszExtension=
"js" , .pszType=
"application/javascript" },
147 { .pszExtension=
"m3u" , .pszType=
"application/x-mpegurl" },
148 { .pszExtension=
"m3u8", .pszType=
"application/x-mpegurl" },
149 { .pszExtension=
"ogx" , .pszType=
"application/ogg" },
150 { .pszExtension=
"pdf" , .pszType=
"application/pdf" },
151 { .pszExtension=
"pem" , .pszType=
"application/x-x509-ca-cert" },
152 { .pszExtension=
"qjs" , .pszType=
"application/javascript" },
153 { .pszExtension=
"rm" , .pszType=
"application/vnd.rn-realmedia" },
154 { .pszExtension=
"swf" , .pszType=
"application/x-shockwave-flash" },
155 { .pszExtension=
"xls" , .pszType=
"application/vnd.ms-excel" },
156 { .pszExtension=
"zip" , .pszType=
"application/x-tar" },
158 { .pszExtension=
"aac" , .pszType=
"audio/mp4" },
159 { .pszExtension=
"ac3" , .pszType=
"audio/vnd.dolby.dd-raw" },
160 { .pszExtension=
"flac", .pszType=
"audio/x-flac" },
161 { .pszExtension=
"m4a" , .pszType=
"audio/x-m4a" },
162 { .pszExtension=
"mid" , .pszType=
"audio/midi" },
163 { .pszExtension=
"mka" , .pszType=
"audio/x-matroska" },
164 { .pszExtension=
"mp3" , .pszType=
"audio/mpeg" },
165 { .pszExtension=
"oga" , .pszType=
"audio/ogg" },
166 { .pszExtension=
"ogg" , .pszType=
"audio/ogg" },
167 { .pszExtension=
"wav" , .pszType=
"audio/wav" },
168 { .pszExtension=
"wma" , .pszType=
"audio/x-ms-wma" },
170 { .pszExtension=
"3gp" , .pszType=
"video/3gpp" },
171 { .pszExtension=
"3g2" , .pszType=
"video/3gpp2" },
172 { .pszExtension=
"asx" , .pszType=
"video/x-ms-asf" },
173 { .pszExtension=
"asf" , .pszType=
"video/x-ms-asf" },
174 { .pszExtension=
"avi" , .pszType=
"video/x-msvideo" },
175 { .pszExtension=
"m2p" , .pszType=
"video/mp2p" },
176 { .pszExtension=
"m4v" , .pszType=
"video/mp4" },
177 { .pszExtension=
"mpeg", .pszType=
"video/mp2p" },
178 { .pszExtension=
"mpeg2",.pszType=
"video/mp2p" },
179 { .pszExtension=
"mpg" , .pszType=
"video/mp2p" },
180 { .pszExtension=
"mpg2", .pszType=
"video/mp2p" },
181 { .pszExtension=
"mov" , .pszType=
"video/quicktime" },
182 { .pszExtension=
"mp4" , .pszType=
"video/mp4" },
183 { .pszExtension=
"mkv" , .pszType=
"video/x-matroska" },
184 { .pszExtension=
"nuv" , .pszType=
"video/nupplevideo" },
185 { .pszExtension=
"ogv" , .pszType=
"video/ogg" },
186 { .pszExtension=
"ps" , .pszType=
"video/mp2p" },
187 { .pszExtension=
"ts" , .pszType=
"video/mp2t" },
188 { .pszExtension=
"vob" , .pszType=
"video/mpeg" },
189 { .pszExtension=
"wmv" , .pszType=
"video/x-ms-wmv" },
191 { .pszExtension=
"ttf" , .pszType=
"font/ttf" },
192 { .pszExtension=
"woff" , .pszType=
"font/woff" },
193 { .pszExtension=
"woff2", .pszType=
"font/woff2" }
206 "<TITLE>Error %1</TITLE>"
207 "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">"
209 "<BODY><H1>%2.</H1></BODY>"
221 if (!values.isEmpty())
222 return values.last();
246 LOG(VB_HTTP, LOG_INFO,
247 QString(
"HTTPRequest::SentRequestType( %1 ) - returning Unknown.")
320 if (sTransferMode.isEmpty())
324 sTransferMode =
"Streaming";
326 sTransferMode =
"Interactive";
329 if (sTransferMode ==
"Streaming")
331 else if (sTransferMode ==
"Background")
333 else if (sTransferMode ==
"Interactive")
338 SetResponseHeader(
"contentFeatures.dlna.org",
"DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000");
342 for (
const auto & value : std::as_const(values))
345 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
348 QMap<QString, QString>::iterator it;
351 LOG(VB_HTTP, LOG_INFO, QString(
"(Response Header) %1: %2").arg(it.key(), it.value()));
374 LOG(VB_HTTP, LOG_INFO,
375 QString(
"HTTPRequest::SendResponse( None ) :%1 -> %2:")
389 if (
file.exists() &&
file.size() < (2LL * 1024 * 1024) &&
390 file.open(QIODevice::ReadOnly | QIODevice::Text))
401 LOG(VB_HTTP, LOG_INFO,
402 QString(
"HTTPRequest::SendResponse( File ) :%1 -> %2:")
411 LOG(VB_HTTP, LOG_INFO,
412 QString(
"HTTPRequest::SendResponse(xml/html) (%1) :%2 -> %3: %4")
424 LOG(VB_HTTP, LOG_INFO,
425 QString(
"HTTPRequest::SendResponse(%1) - Cached")
437 int nContentLen =
m_response.buffer().length();
443 if (qEnvironmentVariableIsSet(
"HTTPREQUEST_DEBUG"))
444 std::cout <<
m_response.buffer().constData() <<
'\n';
447 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Content Length: %1").arg(nContentLen));
456 bool gzip_found = std::ranges::any_of(std::as_const(values),
457 [](
const auto & value)
458 {
return value.contains(
"gzip" ); });
460 if (( nContentLen > 0 ) && gzip_found)
463 compBuffer.setData( compressed );
465 if (!compBuffer.buffer().isEmpty())
467 pBuffer = &compBuffer;
470 LOG(VB_HTTP, LOG_DEBUG, QString(
"Reponse Compressed Content Length: %1").arg(compBuffer.buffer().length()));
478 nContentLen = pBuffer->buffer().length();
482 QByteArray sHeader = rHeader.toUtf8();
483 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").arg(sHeader.length()));
484 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
486 if (nBytes < sHeader.length())
488 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponse(): "
489 "Incomplete write of header, "
491 .arg(nBytes).arg(sHeader.length()));
501 qint64 bytesWritten =
SendData( pBuffer, 0, nContentLen );
504 if (bytesWritten != nContentLen)
505 LOG(VB_HTTP, LOG_ERR,
"HttpRequest::SendResponse(): Error occurred while writing response body.");
507 nBytes += bytesWritten;
519 QString sUrl =
"http://" +
GetLastHeader(
"host" ) + sItems[1];
522 (
"BackendServerAddr",hostName,hostName);
523 url.setHost( ipAddress );
527 LOG(VB_UPNP, LOG_INFO, QString(
"Sending http redirect to: %1")
528 .arg(url.toString()));
540 long long llSize = 0;
541 long long llStart = 0;
544 LOG(VB_HTTP, LOG_INFO, QString(
"SendResponseFile ( %1 )").arg(sFileName));
549 QFile tmpFile( sFileName );
550 if (tmpFile.exists( ) && tmpFile.open( QIODevice::ReadOnly ))
559 llSize = llEnd = tmpFile.size( );
570 if (!sRange.isEmpty())
572 bRange =
ParseRange( sRange, llSize, &llStart, &llEnd );
579 if ((llSize > llStart) && (llSize > llEnd) && (llEnd > llStart))
588 llSize = (llEnd - llStart) + 1;
600 LOG(VB_HTTP, LOG_INFO,
601 QString(
"HTTPRequest::SendResponseFile(%1) - "
602 "invalid byte range %2-%3/%4")
603 .arg(sFileName) .arg(llStart) .arg(llEnd)
621 LOG(VB_HTTP, LOG_INFO,
622 QString(
"HTTPRequest::SendResponseFile(%1) - cannot find file!")
635 QByteArray sHeader = rHeader.toUtf8();
636 LOG(VB_HTTP, LOG_DEBUG, QString(
"Response header size: %1 bytes").arg(sHeader.length()));
637 nBytes =
WriteBlock( sHeader.constData(), sHeader.length() );
639 if (nBytes < sHeader.length())
641 LOG( VB_HTTP, LOG_ERR, QString(
"HttpRequest::SendResponseFile(): "
642 "Incomplete write of header, "
644 .arg(nBytes).arg(sHeader.length()));
652 LOG(VB_HTTP, LOG_DEBUG,
653 QString(
"SendResponseFile : size = %1, start = %2, end = %3")
654 .arg(llSize).arg(llStart).arg(llEnd));
658 long long sent =
SendFile( tmpFile, llStart, llSize );
662 LOG(VB_HTTP, LOG_INFO,
663 QString(
"SendResponseFile( %1 ) Error: %2 [%3]" )
664 .arg(sFileName) .arg(errno) .arg(strerror(errno)));
684 bool bShouldClose =
false;
687 if (!pDevice->isOpen())
689 pDevice->open( QIODevice::ReadOnly );
697 if ( !pDevice->seek( llStart ))
700 std::array<char,SENDFILE_BUFFER_SIZE> aBuffer {};
702 qint64 llBytesRemaining = llBytes;
703 qint64 llBytesToRead = 0;
704 qint64 llBytesRead = 0;
706 while ((sent < llBytes) && !pDevice->atEnd())
709 llBytesRead = pDevice->read( aBuffer.data(), llBytesToRead );
710 if ( llBytesRead != -1 )
712 if (
WriteBlock( aBuffer.data(), llBytesRead ) == -1)
718 llBytesRemaining -= llBytesRead;
734 qint64 sent =
SendData( (QIODevice *)(&
file), llStart, llBytes );
745 const QString &sFaultString,
746 const QString &sDetails )
753 stream << R
"(<?xml version="1.0" encoding="utf-8"?>)";
755 QString sWhere = bServerError ? "s:Server" :
"s:Client";
763 <<
"<faultcode>" << sWhere <<
"</faultcode>"
764 <<
"<faultstring>" << sFaultString <<
"</faultstring>";
767 if (!sDetails.isEmpty())
769 stream <<
"<detail>" << sDetails <<
"</detail>";
783 QString sDetails =
"";
786 sDetails =
"<UPnPResult xmlns=\"urn:schemas-upnp-org:control-1-0\">";
788 if (sMsg.length() == 0)
791 sDetails += QString(
"<errorCode>%1</errorCode>"
792 "<errorDescription>%2</errorDescription>" )
794 .arg(QString::fromUtf8(QUrl::toPercentEncoding(sMsg)));
797 sDetails +=
"</UPnPResult>";
800 "UPnPResult", sDetails);
829 stream <<
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n";
836 <<
"<u:" <<
m_sMethod <<
"Response xmlns:u=\""
841 stream <<
"<" <<
m_sMethod <<
"Response>\r\n";
844 for (
const auto & arg : std::as_const(
args))
846 stream <<
"<" << arg.m_sName;
848 if (arg.m_pAttributes)
850 for (
const auto & attr : std::as_const(*arg.m_pAttributes))
852 stream <<
" " << attr.m_sName <<
"='"
853 << QUrl::toPercentEncoding(attr.m_sValue) <<
"'";
860 stream << QUrl::toPercentEncoding(arg.m_sValue);
862 stream << arg.m_sValue;
864 stream <<
"</" << arg.m_sName <<
">\r\n";
869 stream <<
"</u:" <<
m_sMethod <<
"Response>\r\n"
874 stream <<
"</" <<
m_sMethod <<
"Response>\r\n";
907#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
908 ims.setTimeSpec(Qt::UTC);
910 ims.setTimeZone(QTimeZone(QTimeZone::UTC));
912 if (ims.isValid() && ims <=
file.lastModified())
933 LOG(VB_HTTP, LOG_INFO,
934 QString(
"HTTPRequest::FormatFileResponse('%1') - cannot find file")
945 m_sProtocol = sLine.section(
'/', 0, 0 ).trimmed();
946 QString sVersion = sLine.section(
'/', 1 ).trimmed();
948 m_nMajor = sVersion.section(
'.', 0, 0 ).toInt();
949 m_nMinor = sVersion.section(
'.', 1 ).toInt();
990 if ((sType ==
"application/x-www-form-urlencoded" ) ||
991 (sType.startsWith(
"application/x-www-form-urlencoded;")))
994 if ((sType ==
"text/xml" ) ||
995 (sType.startsWith(
"text/xml;") ))
998 if ((sType ==
"application/json") ||
999 sType.startsWith(
"application/json;"))
1014 case 200:
return "200 OK";
1015 case 201:
return "201 Created";
1016 case 202:
return "202 Accepted";
1017 case 204:
return "204 No Content";
1018 case 205:
return "205 Reset Content";
1019 case 206:
return "206 Partial Content";
1020 case 300:
return "300 Multiple Choices";
1021 case 301:
return "301 Moved Permanently";
1022 case 302:
return "302 Found";
1023 case 303:
return "303 See Other";
1024 case 304:
return "304 Not Modified";
1025 case 305:
return "305 Use Proxy";
1026 case 307:
return "307 Temporary Redirect";
1027 case 308:
return "308 Permanent Redirect";
1028 case 400:
return "400 Bad Request";
1029 case 401:
return "401 Unauthorized";
1030 case 403:
return "403 Forbidden";
1031 case 404:
return "404 Not Found";
1032 case 405:
return "405 Method Not Allowed";
1033 case 406:
return "406 Not Acceptable";
1034 case 408:
return "408 Request Timeout";
1035 case 410:
return "410 Gone";
1036 case 411:
return "411 Length Required";
1037 case 412:
return "412 Precondition Failed";
1038 case 413:
return "413 Request Entity Too Large";
1039 case 414:
return "414 Request-URI Too Long";
1040 case 415:
return "415 Unsupported Media Type";
1041 case 416:
return "416 Requested Range Not Satisfiable";
1042 case 417:
return "417 Expectation Failed";
1044 case 428:
return "428 Precondition Required";
1045 case 429:
return "429 Too Many Requests";
1046 case 431:
return "431 Request Header Fields Too Large";
1047 case 500:
return "500 Internal Server Error";
1048 case 501:
return "501 Not Implemented";
1049 case 502:
return "502 Bad Gateway";
1050 case 503:
return "503 Service Unavailable";
1051 case 504:
return "504 Gateway Timeout";
1052 case 505:
return "505 HTTP Version Not Supported";
1053 case 510:
return "510 Not Extended";
1054 case 511:
return "511 Network Authentication Required";
1086 return "text/plain";
1099 ext =
type.pszExtension;
1101 if ( sFileExtension.compare(ext, Qt::CaseInsensitive) == 0 )
1102 return(
type.pszType );
1105 return "text/plain";
1114 QStringList mimeTypes;
1118 if (!mimeTypes.contains(
type.pszType ))
1119 mimeTypes.append(
type.pszType );
1131 QFileInfo
info( sFileName );
1132 QString sLOC =
"HTTPRequest::TestMimeType(" + sFileName +
") - ";
1133 QString sSuffix =
info.suffix().toLower();
1136 if ( sSuffix ==
"nuv" )
1139 QFile
file( sFileName );
1141 if (
file.open(QIODevice::ReadOnly | QIODevice::Text) )
1143 QByteArray head =
file.read(8);
1144 QString sHex = head.toHex();
1146 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"file starts with " + sHex);
1148 if ( sHex ==
"000001ba44000400" )
1149 sMIME =
"video/mp2p";
1151 if ( head ==
"MythTVVi" )
1154 head =
file.read(4);
1156 if ( head ==
"DIVX" )
1158 LOG(VB_HTTP, LOG_DEBUG, sLOC +
"('MythTVVi...DIVXLAME')");
1159 sMIME =
"video/mp4";
1170 LOG(VB_GENERAL, LOG_ERR, sLOC +
"Could not read file");
1174 LOG(VB_HTTP, LOG_INFO, sLOC +
"type is " + sMIME);
1186 LOG(VB_HTTP, LOG_INFO, QString(
"sParams: '%1'").arg(sParams));
1191 sParams.replace(
"&",
"&" );
1193 if (!sParams.isEmpty())
1195 QStringList params = sParams.split(
'&', Qt::SkipEmptyParts);
1196 for (
const auto & param : std::as_const(params))
1198 QString sName = param.section(
'=', 0, 0 );
1199 QString sValue = param.section(
'=', 1 );
1200 sValue.replace(
"+",
" ");
1202 if (!sName.isEmpty())
1204 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1205 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1207 mapParams.insert( sName.trimmed(), sValue );
1244 sHeader += it.key() +
": ";
1245 sHeader += *it +
"\r\n";
1261 bool bKeepAlive =
true;
1270 QString sConnection =
GetRequestHeader(
"connection",
"default" ).toLower();
1272 QStringList sValueList = sConnection.split(
",");
1274 if ( sValueList.contains(
"close") )
1276 LOG(VB_HTTP, LOG_DEBUG,
"Client requested the connection be closed");
1279 else if (sValueList.contains(
"keep-alive"))
1293 QStringList sCookieList =
m_mapHeaders.values(
"cookie");
1295 QStringList::iterator it;
1296 for (it = sCookieList.begin(); it != sCookieList.end(); ++it)
1298 QString key = (*it).section(
'=', 0, 0);
1299 QString value = (*it).section(
'=', 1);
1311 bool bSuccess =
false;
1316 QString sRequestLine =
ReadLine( 2s );
1318 if ( sRequestLine.isEmpty() )
1320 LOG(VB_GENERAL, LOG_ERR,
"Timeout reading first line of request." );
1352 while (( !sLine.isEmpty() ) && !bDone )
1354 if (sLine !=
"\r\n")
1356 QString sName = sLine.section(
':', 0, 0 ).trimmed();
1357 QString sValue = sLine.section(
':', 1 );
1359 sValue.truncate( sValue.length() - 2 );
1361 if (!sName.isEmpty() && !sValue.isEmpty())
1363 m_mapHeaders.insert(sName.toLower(), sValue.trimmed());
1377 LOG(VB_HTTP, LOG_INFO, QString(
"(Request Header) %1: %2")
1378 .arg(it.key(), *it));
1390 LOG(VB_GENERAL, LOG_INFO,
"Timeout waiting for request header." );
1426 long nPayloadSize =
GetLastHeader(
"content-length" ).toLong();
1428 if (nPayloadSize > 0)
1430 char *pszPayload =
new char[ nPayloadSize + 2 ];
1433 nBytes =
ReadBlock( pszPayload, nPayloadSize, 5s );
1434 if (nBytes == nPayloadSize )
1436 m_sPayload = QString::fromUtf8( pszPayload, nPayloadSize );
1446 LOG(VB_GENERAL, LOG_ERR,
1447 QString(
"Unable to read entire payload (read %1 of %2 bytes)")
1448 .arg( nBytes ) .arg( nPayloadSize ) );
1452 delete [] pszPayload;
1458 if (!sSOAPAction.isEmpty())
1465 LOG(VB_HTTP, LOG_DEBUG,
1466 QString(
"HTTPRequest::ParseRequest - Socket (%1) Base (%2) "
1467 "Method (%3) - Bytes in Socket Buffer (%4)")
1469 .arg(
m_sMethod) .arg(BytesAvailable()));
1474 LOG(VB_GENERAL, LOG_WARNING,
1475 "Unexpected exception in HTTPRequest::ParseRequest" );
1490 int nCount = tokens.count();
1494 if ( sLine.startsWith( QString(
"HTTP/") ))
1518 m_sRequestUrl = QUrl::fromPercentEncoding(tokens[1].toUtf8());
1524 QString sQueryStr = tokens[1].section(
'?', 1, 1 );
1526 if (!sQueryStr.isEmpty())
1554 long long *pllStart,
1562 if (sRange.isEmpty())
1574 sRange.remove( 0, nIdx );
1580 QStringList ranges = sRange.split(
',', Qt::SkipEmptyParts);
1581 if (ranges.count() == 0)
1588 QStringList parts = ranges[0].split(
'-');
1590 if (parts.count() != 2)
1593 if (parts[0].isEmpty() && parts[1].isEmpty())
1600 bool conv_ok =
false;
1601 if (parts[0].isEmpty())
1607 long long llValue = parts[1].toLongLong(&conv_ok);
1608 if (!conv_ok)
return false;
1610 *pllStart = llSize - llValue;
1611 *pllEnd = llSize - 1;
1613 else if (parts[1].isEmpty())
1619 *pllStart = parts[0].toLongLong(&conv_ok);
1624 *pllEnd = llSize - 1;
1632 *pllStart = parts[0].toLongLong(&conv_ok);
1633 if (!conv_ok)
return false;
1634 *pllEnd = parts[1].toLongLong(&conv_ok);
1635 if (!conv_ok)
return false;
1637 if (*pllStart > *pllEnd)
1641 LOG(VB_HTTP, LOG_DEBUG, QString(
"%1 Range Requested %2 - %3")
1656 static const QRegularExpression re {
"^http[s]?://.*?/"};
1659 QStringList sList =
m_sBaseUrl.split(
'/', Qt::SkipEmptyParts);
1662 if (!sList.isEmpty())
1669 LOG(VB_HTTP, LOG_INFO, QString(
"ExtractMethodFromURL(end) : %1 : %2")
1679 bool bSuccess =
false;
1685 LOG(VB_HTTP, LOG_INFO,
1686 QString(
"HTTPRequest::ProcessSOAPPayload : %1 : ").arg(sSOAPAction));
1687 QDomDocument doc (
"request" );
1689#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
1694 if (!doc.setContent(
m_sPayload,
true, &sErrMsg, &nErrLine, &nErrCol ))
1696 LOG(VB_GENERAL, LOG_ERR,
1697 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1698 .arg(nErrLine) .arg(nErrCol) .arg(sErrMsg));
1702 auto parseResult =doc.setContent(
m_sPayload,
1703 QDomDocument::ParseOption::UseNamespaceProcessing );
1706 LOG(VB_GENERAL, LOG_ERR,
1707 QString(
"Error parsing request at line: %1 column: %2 : %3" )
1708 .arg(parseResult.errorLine).arg(parseResult.errorColumn)
1709 .arg(parseResult.errorMessage));
1720 if (sSOAPAction.contains(
'#' ))
1722 m_sNameSpace = sSOAPAction.section(
'#', 0, 0).remove( 0, 1);
1723 m_sMethod = sSOAPAction.section(
'#', 1 );
1728 if (sSOAPAction.contains(
'/' ))
1730 int nPos = sSOAPAction.lastIndexOf(
'/' );
1733 sSOAPAction.length() - nPos - 2);
1750 if (oNodeList.count() == 0)
1753 doc.elementsByTagNameNS(
"http://schemas.xmlsoap.org/soap/envelope/",
1757 if (oNodeList.count() > 0)
1759 QDomNode oMethod = oNodeList.item(0);
1761 if (!oMethod.isNull())
1765 for ( QDomNode oNode = oMethod.firstChild(); !oNode.isNull();
1766 oNode = oNode.nextSibling() )
1768 QDomElement e = oNode.toElement();
1772 QString sName = e.tagName();
1773 QString sValue =
"";
1775 QDomText oText = oNode.firstChild().toText();
1777 if (!oText.isNull())
1778 sValue = oText.nodeValue();
1780 sName = QUrl::fromPercentEncoding(sName.toUtf8());
1781 sValue = QUrl::fromPercentEncoding(sValue.toUtf8());
1783 m_mapParams.insert( sName.trimmed().toLower(), sValue );
1811 if (sAccept.contains(
"application/json", Qt::CaseInsensitive ) ||
1812 sAccept.contains(
"text/javascript", Qt::CaseInsensitive ))
1817 else if (sAccept.contains(
"text/x-apple-plist+xml", Qt::CaseInsensitive ))
1825 if (pSerializer ==
nullptr)
1837 QByteArray hash = QCryptographicHash::hash( data.data(), QCryptographicHash::Sha1);
1839 return (
"\"" + hash.toHex() +
"\"");
1860 const QDateTime &expiryDate,
bool secure)
1864 LOG(VB_GENERAL, LOG_WARNING, QString(
"HTTPRequest::SetCookie(%1=%2): "
1865 "A secure cookie cannot be set on an unencrypted connection.")
1866 .arg(sKey, sValue));
1870 QStringList cookieAttributes;
1873 cookieAttributes.append(QString(
"%1=%2").arg(sKey, sValue));
1879 cookieAttributes.append(
"Path=/");
1883 cookieAttributes.append(QString(
"Expires=%1").arg(expires));
1888 cookieAttributes.append(
"Secure");
1891 cookieAttributes.append(
"HttpOnly");
1913 return hostname.section(
"]:", 0 , 0);
1917 return hostname.section(
":", 0 , 0);
1956 type =
"UNSUBSCRIBE";
1994 QStringList allowedOrigins;
1998 serverStatusPort + 10);
2000 QString masterAddrPort = QString(
"%1:%2")
2002 .arg(serverStatusPort);
2003 QString masterTLSAddrPort = QString(
"%1:%2")
2005 .arg(backendSSLPort);
2007 allowedOrigins << QString(
"http://%1").arg(masterAddrPort);
2008 allowedOrigins << QString(
"https://%2").arg(masterTLSAddrPort);
2010 QString localhostname = QHostInfo::localHostName();
2011 if (!localhostname.isEmpty())
2013 allowedOrigins << QString(
"http://%1:%2")
2014 .arg(localhostname).arg(serverStatusPort);
2015 allowedOrigins << QString(
"https://%1:%2")
2016 .arg(localhostname).arg(backendSSLPort);
2019 QStringList allowedOriginsList =
2021 "https://chromecast.mythtv.org")).split(
",");
2023 for (
const auto & origin : std::as_const(allowedOriginsList))
2025 if (origin.isEmpty())
2028 if (origin ==
"*" || (!origin.startsWith(
"http://") &&
2029 !origin.startsWith(
"https://")))
2031 LOG(VB_GENERAL, LOG_ERR, QString(
"Illegal AllowedOriginsList"
2032 " entry '%1'. Must start with http[s]:// and not be *")
2037 allowedOrigins << origin;
2043 for (
const auto & origin : std::as_const(allowedOrigins))
2044 LOG(VB_HTTP, LOG_DEBUG, QString(
"Will allow Origin: %1").arg(origin));
2047 if (allowedOrigins.contains(sOrigin))
2052 LOG(VB_HTTP, LOG_DEBUG, QString(
"Allow-Origin: %1)").arg(sOrigin));
2056 LOG(VB_GENERAL, LOG_CRIT, QString(
"HTTPRequest: Cross-origin request "
2057 "received with origin (%1)")
2075 m_pSocket->state() == QAbstractSocket::ConnectedState)
2084 if ( timer.
elapsed() >= msecs )
2087 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadLine() - Exceeded Total Elapsed Wait Time." );
2103 std::chrono::milliseconds msecs)
2106 m_pSocket->state() == QAbstractSocket::ConnectedState)
2109 return(
m_pSocket->read( pData, nMaxLen ));
2111 bool bTimeout =
false;
2114 while ( (
m_pSocket->bytesAvailable() < (
int)nMaxLen) && !bTimeout )
2116 bTimeout = !(
m_pSocket->waitForReadyRead( msecs.count() ));
2118 if ( timer.
elapsed() >= msecs )
2121 LOG(VB_HTTP, LOG_INFO,
"BufferedSocketDeviceRequest::ReadBlock() - Exceeded Total Elapsed Wait Time." );
2127 return(
m_pSocket->read( pData, nMaxLen ));
2139 qint64 bytesWritten = -1;
2141 m_pSocket->state() == QAbstractSocket::ConnectedState)
2143 bytesWritten =
m_pSocket->write( pData, nLen );
2147 return bytesWritten;
2156 return(
m_pSocket->localAddress().toString() );
2175 return(
m_pSocket->peerAddress().toString() );
quint16 GetHostPort() override
QString GetPeerAddress() override
QString ReadLine(std::chrono::milliseconds msecs) override
qint64 ReadBlock(char *pData, qint64 nMaxLen, std::chrono::milliseconds msecs=0ms) override
qint64 WriteBlock(const char *pData, qint64 nLen) override
QString GetHostAddress() override
QByteArray GetResponsePage(void)
QString BuildResponseHeader(long long nSize)
HttpResponseType m_eResponseType
static QString GetETagHash(const QByteArray &data)
virtual QString GetHostAddress()=0
qint64 SendResponse(void)
QString m_sResponseTypeText
bool ParseRange(QString sRange, long long llSize, long long *pllStart, long long *pllEnd)
void FormatRawResponse(const QString &sXML)
void FormatErrorResponse(bool bServerError, const QString &sFaultString, const QString &sDetails)
HttpContentType m_eContentType
QRegularExpression m_parseRangeExp
static QStringList GetSupportedMimeTypes()
QString GetRequestHeader(const QString &sKey, const QString &sDefault)
void FormatFileResponse(const QString &sFileName)
static QString GetResponseProtocol()
static QString TestMimeType(const QString &sFileName)
QString GetResponseType(void) const
void ProcessRequestLine(const QString &sLine)
Serializer * GetSerializer()
virtual int getSocketHandle()=0
MythUserSession m_userSession
qint64 SendFile(QFile &file, qint64 llStart, qint64 llBytes)
void ExtractMethodFromURL()
std::chrono::seconds m_nKeepAliveTimeout
QString GetResponseStatus(void) const
QStringMultiMap m_mapHeaders
static QString GetMimeType(const QString &sFileExtension)
HttpContentType SetContentType(const QString &sType)
virtual QString GetHostName()
void FormatActionResponse(Serializer *ser)
HttpRequestType SetRequestType(const QString &sType)
virtual QString ReadLine(std::chrono::milliseconds msecs)=0
QString GetRequestProtocol() const
void AddCORSHeaders(const QString &sOrigin)
void SendResponseRedirect(const QString &hostName)
QString GetLastHeader(const QString &sType) const
QStringMap m_mapRespHeaders
virtual qint64 ReadBlock(char *pData, qint64 nMaxLen, std::chrono::milliseconds msecs=0ms)=0
virtual qint64 WriteBlock(const char *pData, qint64 nLen)=0
bool ParseKeepAlive(void)
void SetRequestProtocol(const QString &sLine)
void SetCookie(const QString &sKey, const QString &sValue, const QDateTime &expiryDate, bool secure)
bool ProcessSOAPPayload(const QString &sSOAPAction)
static long GetParameters(QString sParams, QStringMap &mapParams)
void SetResponseHeader(const QString &sKey, const QString &sValue, bool replace=false)
static const char * s_szServerHeaders
QString GetRequestType() const
qint64 SendData(QIODevice *pDevice, qint64 llStart, qint64 llBytes)
qint64 SendResponseFile(const QString &sFileName)
virtual QString GetPeerAddress()=0
QString GetResponseHeaders(void)
QRegularExpression m_procReqLineExp
static QString GetServerVersion(void)
QString GetMasterServerIP(void)
Returns the Master Backend IP address If the address is an IPv6 address, the scope Id is removed.
MythSessionManager * GetSessionManager(void)
QString GetSetting(const QString &key, const QString &defaultval="")
QString GetSettingOnHost(const QString &key, const QString &host, const QString &defaultval="")
T GetDurSetting(const QString &key, T defaultval=T::zero())
int GetMasterServerStatusPort(void)
Returns the Master Backend status port If no master server status port has been defined in the databa...
int GetNumSetting(const QString &key, int defaultval=0)
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
We use digest authentication because it protects the password over unprotected networks.
MythUserSession GetSession(const QString &sessionToken)
Load the session details and return.
A QElapsedTimer based timer to replace use of QTime as a timer.
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
void start(void)
starts measuring elapsed time.
bool IsValid(void) const
Check if this session object appears properly constructed, it DOES NOT validate whether it is a valid...
virtual void AddHeaders(QStringMap &headers)
virtual QString GetContentType()=0
static std::array< const MIMETypes, 66 > g_MIMETypes
static constexpr size_t SENDFILE_BUFFER_SIZE
QString UPnPResultCodeDesc(UPnPResultCode eCode)
static QString StaticPage
static constexpr const char * SOAP_ENVELOPE_END
static constexpr const char * SOAP_ENVELOPE_BEGIN
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
@ kOverrideUTC
Present date/time in UTC.
@ kRFC822
HTTP Date format.
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
QByteArray gzipCompress(const QByteArray &data)
@ UPnPResult_MS_AccessDenied
@ UPnPResult_CDS_NoSuchDestRes
@ UPnPResult_InvalidControlURL
@ UPnPResult_ArgumentValueOutOfRange
@ UPnPResult_CDS_InvalidSortCriteria
@ UPnPResult_CDS_NoSuchSourceResource
@ UPnPResult_CDS_ParameterMismatch
@ UPnPResult_CDS_TransferBusy
@ UPnPResult_CDS_InvalidNewTagValue
@ UPnPResult_CDS_InvalidSearchCriteria
@ UPnPResult_NotEncrypted
@ UPnPResult_CDS_DestResAccessDenied
@ UPnPResult_HumanInterventionRequired
@ UPnPResult_OptionalActionNotImplemented
@ UPnPResult_CDS_CannotProcessRequest
@ UPnPResult_ArgumentValueInvalid
@ UPnPResult_MythTV_XmlParseError
@ UPnPResult_InvalidAction
@ UPnPResult_CDS_NoSuchContainer
@ UPnPResult_StringArgumentTooLong
@ UPnPResult_ActionFailed
@ UPnPResult_CDS_NoSuchObject
@ UPnPResult_CDS_RestrictedObject
@ UPnPResult_CDS_BadMetadata
@ UPnPResult_CDS_InvalidCurrentTagValue
@ UPnPResult_CDS_ResrtictedParentObject
@ UPnPResult_ActionNotAuthorized
@ UPnPResult_NoSuchSession
@ UPnPResult_CDS_ResourceAccessDenied
@ UPnPResult_CDS_RequiredTag
@ UPnPResult_CMGR_NotInNetwork
@ UPnPResult_CDS_NoSuchFileTransfer
@ UPnPResult_InvalidSequence
@ UPnPResult_CDS_ReadOnlyTag
@ UPnPResult_SignatureMissing
@ UPnPResult_MythTV_NoNamespaceGiven
@ UPnPResult_SignatureFailure
QMap< QString, QString > QStringMap