14 #include <QCoreApplication>
15 #include <QDesktopServices>
16 #include <QElapsedTimer>
20 #include <QMutexLocker>
21 #include <QNetworkAccessManager>
22 #include <QNetworkDiskCache>
23 #include <QNetworkInterface>
24 #include <QNetworkProxy>
25 #include <QNetworkReply>
26 #include <QNetworkRequest>
27 #include <QScopedPointer>
31 #include <QSslConfiguration>
45 static const QString
LOC { QStringLiteral(
"[netstream] ") };
63 static const QEvent::Type
kType = QEvent::User;
79 static const QEvent::Type
kType =
static_cast< QEvent::Type
>(QEvent::User + 1);
99 m_cert(
std::move(cert))
101 setObjectName(
"NetStream " + url.toString());
103 m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
106 mode ==
kNeverCache ? QNetworkRequest::AlwaysNetwork :
107 QNetworkRequest::PreferNetwork );
135 static inline QString
Source(
const QNetworkRequest &request)
137 switch (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt())
139 case QNetworkRequest::AlwaysCache:
return "cache";
140 case QNetworkRequest::PreferCache:
return "cache-preferred";
141 case QNetworkRequest::PreferNetwork:
return "net-preferred";
142 case QNetworkRequest::AlwaysNetwork:
return "net";
147 static inline QString
Source(
const QNetworkReply* reply)
149 return reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() ?
159 LOG(VB_GENERAL, LOG_WARNING,
LOC +
160 QString(
"(%1) Request unsupported URL: %2")
161 .arg(
m_id).arg(url.toString()) );
184 const QByteArray ua(
"User-Agent");
186 m_request.setRawHeader(ua,
"UK-MHEG/2 MYT001/001 MHGGNU/001");
189 m_request.setRawHeader(
"Range", QString(
"bytes=%1-").arg(
m_pos).toLatin1());
191 #ifndef QT_NO_OPENSSL
194 QSslConfiguration ssl(QSslConfiguration::defaultConfiguration());
196 QList<QSslCertificate> clist;
199 clist = QSslCertificate::fromData(
m_cert, QSsl::Der);
201 LOG(VB_GENERAL, LOG_WARNING,
LOC + QString(
"Invalid certificate: %1")
202 .arg(
m_cert.toPercentEncoding().constData()) );
208 ssl.setPeerVerifyMode(QSslSocket::VerifyNone);
212 ssl.setCaCertificates(clist);
219 if (!fname.isEmpty())
221 QFile f1(QFile::exists(fname) ? fname :
GetShareDir() + fname);
222 if (f1.open(QIODevice::ReadOnly))
224 QSslCertificate cert(&f1, QSsl::Pem);
226 ssl.setLocalCertificate(cert);
228 LOG(VB_GENERAL, LOG_WARNING,
LOC +
229 QString(
"'%1' is an invalid certificate").arg(f1.fileName()) );
233 LOG(VB_GENERAL, LOG_WARNING,
LOC +
234 QString(
"Opening client certificate '%1': %2")
235 .arg(f1.fileName(), f1.errorString()) );
240 if (!fname.isEmpty())
242 QFile f2(QFile::exists(fname) ? fname :
GetShareDir() + fname);
243 if (f2.open(QIODevice::ReadOnly))
245 QSslKey key(&f2, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey,
248 ssl.setPrivateKey(key);
250 LOG(VB_GENERAL, LOG_WARNING,
LOC +
251 QString(
"'%1' is an invalid key").arg(f2.fileName()) );
255 LOG(VB_GENERAL, LOG_WARNING,
LOC +
256 QString(
"Opening private key '%1': %2")
257 .arg(f2.fileName(), f2.errorString()) );
266 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Request %2 bytes=%3- from %4")
286 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Started 0x%2")
287 .arg(
m_id).arg(quintptr(reply),0,16) );
299 #ifndef QT_NO_OPENSSL
300 connect(reply, &QNetworkReply::sslErrors,
this,
307 LOG(VB_GENERAL, LOG_ERR,
LOC +
308 QString(
"(%1) Started but m_reply not NULL").arg(
m_id));
314 qlonglong len = reply->header(QNetworkRequest::ContentLengthHeader)
316 return ok ? len : -1;
320 qulonglong &first, qulonglong &last)
322 QByteArray range = reply->rawHeader(
"Content-Range");
328 const char *fmt =
" bytes %20" SCNd64
" - %20" SCNd64
" / %20" SCNd64;
329 if (3 != std::sscanf(range.constData(), fmt, &first, &last, &len))
331 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Invalid Content-Range:'%1'")
332 .arg(range.constData()) );
336 return static_cast<qlonglong
>(len);
340 static bool inline RequestRange(
const QNetworkRequest &request,
341 qlonglong &first, qlonglong &last)
345 QByteArray range = request.rawHeader(
"Range");
349 if (1 > std::sscanf(range.constData(),
" bytes %20lld - %20lld", &first, &last))
351 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Invalid Range:'%1'")
352 .arg(range.constData()) );
367 qint64 avail =
m_reply->bytesAvailable();
369 (avail <= 4 *
kMaxBuffer) ? LOG_INFO : LOG_WARNING,
370 LOC + QString(
"(%1) Ready 0x%2, %3 bytes available").arg(
m_id)
371 .arg(quintptr(
m_reply),0,16).arg(avail) );
375 qulonglong first = 0;
381 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Ready 0x%2, range %3-%4/%5")
382 .arg(
m_id).arg(quintptr(
m_reply),0,16).arg(first).arg(last).arg(len) );
387 if (m_state < kReady || m_size >= 0)
389 LOG(VB_FILE, LOG_INFO,
LOC +
390 QString(
"(%1) Ready 0x%2, content length %3")
406 LOG(VB_GENERAL, LOG_ERR,
LOC +
407 QString(
"(%1) ReadyRead but m_reply = NULL").arg(
m_id));
418 if (QNetworkReply::NoError ==
error)
422 QNetworkRequest::RedirectionTargetAttribute).toUrl();
429 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1) Too many redirections")
435 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1) Redirection loop to %2")
436 .arg(
m_id).arg(url.toString()) );
441 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Redirecting").arg(
m_id));
447 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1): %2")
457 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Finished 0x%2 %3/%4 bytes from %5")
468 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"(%1) Finished but m_reply = NULL")
472 #ifndef QT_NO_OPENSSL
481 for (
const auto& e : qAsConst(errors))
483 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) SSL error %2: ")
484 .arg(
m_id).arg(e.error()) + e.errorString() );
487 #if 1 // The BBC use a self certified cert
488 case QSslError::SelfSignedCertificateInChain:
499 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) SSL errors ignored").arg(
m_id));
500 m_reply->ignoreSslErrors(errors);
504 LOG(VB_GENERAL, LOG_ERR,
LOC +
505 QString(
"(%1) SSL error but m_reply = NULL").arg(
m_id) );
516 return url.isValid() &&
517 (url.scheme() ==
"http" || url.scheme() ==
"https") &&
518 !url.authority().isEmpty() &&
519 !url.path().isEmpty();
534 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Cancelled").arg(
m_id) );
542 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Abort 0x%2")
555 QElapsedTimer
t;
t.start();
563 unsigned elapsed =
t.elapsed();
564 if (elapsed >= millisecs)
575 qint64 avail =
m_reply->read(
reinterpret_cast< char*
>(data), sz);
579 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) safe_read @ %4 => %2/%3, %5 mS")
580 .arg(
m_id).arg(avail).arg(sz).arg(
m_pos).arg(
t.elapsed()) );
594 LOG(VB_GENERAL, LOG_ERR,
LOC +
595 QString(
"(%1) Seek(%2) out of range [0..%3]")
600 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Seek(%2) curr %3 end %4")
628 QElapsedTimer
t;
t.start();
631 auto elapsed = std::chrono::milliseconds(
t.elapsed());
645 QElapsedTimer
t;
t.start();
648 auto elapsed = std::chrono::milliseconds(
t.elapsed());
661 return !
m_reply ? QNetworkReply::OperationCanceledError :
m_reply->error();
683 QByteArray data =
m_reply->readAll();
684 m_pos += data.size();
732 QMutexLocker locker(&
s_mtx);
742 setObjectName(
"NAMThread");
744 #ifndef QT_NO_OPENSSL
747 qRegisterMetaType< QList<QSslError> >();
761 LOG(VB_FILE, LOG_INFO,
LOC +
"NAMThread starting");
763 m_nam =
new QNetworkAccessManager();
764 m_nam->setObjectName(
"NetStream NAM");
767 QScopedPointer<QNetworkDiskCache> cache(
new QNetworkDiskCache());
769 cache->setCacheDirectory(
GetConfDir() +
"/cache/netstream-" +
772 m_nam->setCache(cache.take());
776 QString proxy(qEnvironmentVariable(
"MYTHMHEG_PROXY"));
777 if (!proxy.isEmpty())
779 QUrl url(proxy, QUrl::TolerantMode);
780 QNetworkProxy::ProxyType
type =
781 url.scheme().isEmpty() ? QNetworkProxy::HttpProxy :
782 url.scheme() ==
"socks" ? QNetworkProxy::Socks5Proxy :
783 url.scheme() ==
"http" ? QNetworkProxy::HttpProxy :
784 url.scheme() ==
"https" ? QNetworkProxy::HttpProxy :
785 url.scheme() ==
"cache" ? QNetworkProxy::HttpCachingProxy :
786 url.scheme() ==
"ftp" ? QNetworkProxy::FtpCachingProxy :
787 QNetworkProxy::NoProxy;
788 if (QNetworkProxy::NoProxy !=
type)
790 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Using proxy: " + proxy);
791 m_nam->setProxy(QNetworkProxy(
792 type, url.host(), url.port(), url.userName(), url.password() ));
796 LOG(VB_MHEG, LOG_ERR,
LOC + QString(
"Unknown proxy type %1")
797 .arg(url.scheme()) );
802 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
811 QCoreApplication::processEvents();
822 QScopedPointer< QEvent > ev(
m_workQ.dequeue());
834 LOG(VB_FILE, LOG_INFO,
LOC +
"NAMThread stopped");
852 switch (event->type())
856 #pragma GCC diagnostic push
857 #pragma GCC diagnostic ignored "-Wswitch"
860 #pragma GCC diagnostic pop
871 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Invalid NetStreamRequest");
875 if (!
p->m_bCancelled)
877 QNetworkReply *reply =
m_nam->get(
p->m_req);
878 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) StartRequest 0x%2")
879 .arg(
p->m_id).arg(quintptr(reply),0,16) );
883 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) NetStreamRequest cancelled").arg(
p->m_id) );
891 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Invalid NetStreamAbort");
895 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) AbortRequest 0x%2").arg(
p->m_id)
896 .arg(quintptr(
p->m_reply),0,16) );
898 p->m_reply->disconnect();
906 auto interfaces = QNetworkInterface::allInterfaces();
907 return std::any_of(interfaces.begin(), interfaces.end(),
908 [](
const QNetworkInterface& iface)
910 auto f = iface.flags();
911 if (f.testFlag(QNetworkInterface::IsLoopBack))
913 return f.testFlag(QNetworkInterface::IsRunning);
923 QMutexLocker locker(&m.
m_mutex);
928 QAbstractNetworkCache *cache = m.
m_nam->cache();
932 QNetworkCacheMetaData meta = cache->metaData(url);
935 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1') not in cache")
936 .arg(url.toString()));
941 QDateTime
const now(QDateTime::currentDateTime());
942 QDateTime expire = meta.expirationDate();
943 if (expire.isValid() && expire.toLocalTime() < now)
945 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"GetLastModified('%1') past expiration %2")
946 .arg(url.toString(), expire.toString()));
951 QDateTime lastMod = meta.lastModified();
953 QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
954 for (
const auto& h : qAsConst(headers))
957 static const QString kSzFormat {
"ddd, dd MMM yyyy HH:mm:ss 'GMT'" };
959 QString
const first(h.first.toLower());
960 if (first ==
"cache-control")
962 QString
const second(h.second.toLower());
963 if (second ==
"no-cache" || second ==
"no-store")
965 LOG(VB_FILE, LOG_INFO,
LOC +
966 QString(
"GetLastModified('%1') Cache-Control disabled")
967 .arg(url.toString()) );
972 else if (first ==
"date")
977 LOG(VB_GENERAL, LOG_WARNING,
LOC +
978 QString(
"GetLastModified invalid Date header '%1'")
979 .arg(h.second.constData()));
982 d.setTimeSpec(Qt::UTC);
987 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1') last modified %2")
988 .arg(url.toString(), lastMod.toString()));