7 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
18 #include <QCoreApplication>
19 #include <QDesktopServices>
20 #include <QElapsedTimer>
24 #include <QMutexLocker>
25 #include <QNetworkAccessManager>
26 #include <QNetworkDiskCache>
27 #include <QNetworkProxy>
28 #include <QNetworkReply>
29 #include <QNetworkRequest>
30 #include <QScopedPointer>
34 #include <QSslConfiguration>
45 #if QT_VERSION < QT_VERSION_CHECK(5,10,0)
46 #define qEnvironmentVariable getenv
52 #define LOC "[netstream] "
70 static const QEvent::Type
kType = QEvent::User;
87 static const QEvent::Type
kType =
static_cast< QEvent::Type
>(QEvent::User + 1);
107 m_cert(std::move(cert))
109 setObjectName(
"NetStream " + url.toString());
111 m_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
114 mode ==
kNeverCache ? QNetworkRequest::AlwaysNetwork :
115 QNetworkRequest::PreferNetwork );
143 static inline QString
Source(
const QNetworkRequest &request)
145 switch (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt())
147 case QNetworkRequest::AlwaysCache:
return "cache";
148 case QNetworkRequest::PreferCache:
return "cache-preferred";
149 case QNetworkRequest::PreferNetwork:
return "net-preferred";
150 case QNetworkRequest::AlwaysNetwork:
return "net";
155 static inline QString
Source(
const QNetworkReply* reply)
157 return reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool() ?
167 LOG(VB_GENERAL, LOG_WARNING,
LOC +
168 QString(
"(%1) Request unsupported URL: %2")
192 const QByteArray ua(
"User-Agent");
194 m_request.setRawHeader(ua,
"UK-MHEG/2 MYT001/001 MHGGNU/001");
199 #ifndef QT_NO_OPENSSL
202 QSslConfiguration ssl(QSslConfiguration::defaultConfiguration());
204 QList<QSslCertificate> clist;
207 clist = QSslCertificate::fromData(
m_cert, QSsl::Der);
209 LOG(VB_GENERAL, LOG_WARNING,
LOC + QString(
"Invalid certificate: %1")
210 .
arg(
m_cert.toPercentEncoding().constData()) );
216 ssl.setPeerVerifyMode(QSslSocket::VerifyNone);
220 ssl.setCaCertificates(clist);
227 if (!fname.isEmpty())
229 QFile f1(QFile::exists(fname) ? fname :
GetShareDir() + fname);
230 if (f1.open(QIODevice::ReadOnly))
232 QSslCertificate cert(&f1, QSsl::Pem);
234 ssl.setLocalCertificate(cert);
236 LOG(VB_GENERAL, LOG_WARNING,
LOC +
237 QString(
"'%1' is an invalid certificate").
arg(f1.fileName()) );
241 LOG(VB_GENERAL, LOG_WARNING,
LOC +
242 QString(
"Opening client certificate '%1': %2")
243 .
arg(f1.fileName()).arg(f1.errorString()) );
248 if (!fname.isEmpty())
250 QFile f2(QFile::exists(fname) ? fname :
GetShareDir() + fname);
251 if (f2.open(QIODevice::ReadOnly))
253 QSslKey key(&f2, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey,
256 ssl.setPrivateKey(key);
258 LOG(VB_GENERAL, LOG_WARNING,
LOC +
259 QString(
"'%1' is an invalid key").
arg(f2.fileName()) );
263 LOG(VB_GENERAL, LOG_WARNING,
LOC +
264 QString(
"Opening private key '%1': %2")
265 .
arg(f2.fileName()).arg(f2.errorString()) );
274 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Request %2 bytes=%3- from %4")
294 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Started 0x%2")
307 #ifndef QT_NO_OPENSSL
308 connect(reply, &QNetworkReply::sslErrors,
this,
315 LOG(VB_GENERAL, LOG_ERR,
LOC +
316 QString(
"(%1) Started but m_reply not NULL").
arg(
m_id));
322 qlonglong len = reply->header(QNetworkRequest::ContentLengthHeader)
324 return ok ? len : -1;
328 qulonglong &first, qulonglong &last)
330 QByteArray range = reply->rawHeader(
"Content-Range");
336 const char *fmt =
" bytes %20" SCNd64
" - %20" SCNd64
" / %20" SCNd64;
337 if (3 != std::sscanf(range.constData(), fmt, &first, &last, &len))
339 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Invalid Content-Range:'%1'")
340 .
arg(range.constData()) );
344 return static_cast<qlonglong
>(len);
348 static bool inline RequestRange(
const QNetworkRequest &request,
349 qlonglong &first, qlonglong &last)
353 QByteArray range = request.rawHeader(
"Range");
357 if (1 > std::sscanf(range.constData(),
" bytes %20lld - %20lld", &first, &last))
359 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"Invalid Range:'%1'")
360 .
arg(range.constData()) );
375 qint64 avail =
m_reply->bytesAvailable();
377 (avail <= 4 *
kMaxBuffer) ? LOG_INFO : LOG_WARNING,
378 LOC + QString(
"(%1) Ready 0x%2, %3 bytes available").
arg(
m_id)
383 qulonglong first = 0;
389 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Ready 0x%2, range %3-%4/%5")
395 if (m_state < kReady || m_size >= 0)
397 LOG(VB_FILE, LOG_INFO,
LOC +
398 QString(
"(%1) Ready 0x%2, content length %3")
414 LOG(VB_GENERAL, LOG_ERR,
LOC +
415 QString(
"(%1) ReadyRead but m_reply = NULL").
arg(
m_id));
426 if (QNetworkReply::NoError ==
error)
430 QNetworkRequest::RedirectionTargetAttribute).toUrl();
437 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1) Too many redirections")
443 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1) Redirection loop to %2")
449 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Redirecting").
arg(
m_id));
455 LOG(VB_FILE, LOG_WARNING,
LOC + QString(
"(%1): %2")
465 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Finished 0x%2 %3/%4 bytes from %5")
476 LOG(VB_GENERAL, LOG_ERR,
LOC + QString(
"(%1) Finished but m_reply = NULL")
480 #ifndef QT_NO_OPENSSL
489 for (
const auto&
e : qAsConst(errors))
491 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) SSL error %2: ")
495 #if 1 // The BBC use a self certified cert
496 case QSslError::SelfSignedCertificateInChain:
507 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) SSL errors ignored").
arg(
m_id));
508 m_reply->ignoreSslErrors(errors);
512 LOG(VB_GENERAL, LOG_ERR,
LOC +
513 QString(
"(%1) SSL error but m_reply = NULL").
arg(
m_id) );
524 return url.isValid() &&
525 (url.scheme() ==
"http" || url.scheme() ==
"https") &&
526 !url.authority().isEmpty() &&
527 !url.path().isEmpty();
542 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Cancelled").
arg(
m_id) );
550 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Abort 0x%2")
563 QElapsedTimer
t;
t.start();
571 unsigned elapsed =
t.elapsed();
572 if (elapsed >= millisecs)
583 qint64 avail =
m_reply->read(
reinterpret_cast< char*
>(data), sz);
587 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) safe_read @ %4 => %2/%3, %5 mS")
602 LOG(VB_GENERAL, LOG_ERR,
LOC +
603 QString(
"(%1) Seek(%2) out of range [0..%3]")
608 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) Seek(%2) curr %3 end %4")
636 QElapsedTimer
t;
t.start();
639 unsigned elapsed =
t.elapsed();
640 if (elapsed > milliseconds)
653 QElapsedTimer
t;
t.start();
656 unsigned elapsed =
t.elapsed();
657 if (elapsed > milliseconds)
669 return !
m_reply ? QNetworkReply::OperationCanceledError :
m_reply->error();
691 QByteArray data =
m_reply->readAll();
692 m_pos += data.size();
740 QMutexLocker locker(&
s_mtx);
750 setObjectName(
"NAMThread");
752 #ifndef QT_NO_OPENSSL
755 qRegisterMetaType< QList<QSslError> >();
769 LOG(VB_FILE, LOG_INFO,
LOC "NAMThread starting");
771 m_nam =
new QNetworkAccessManager();
772 m_nam->setObjectName(
"NetStream NAM");
775 QScopedPointer<QNetworkDiskCache> cache(
new QNetworkDiskCache());
777 cache->setCacheDirectory(
GetConfDir() +
"/cache/netstream-" +
780 m_nam->setCache(cache.take());
784 QString proxy(qEnvironmentVariable(
"MYTHMHEG_PROXY"));
785 if (!proxy.isEmpty())
787 QUrl url(proxy, QUrl::TolerantMode);
788 QNetworkProxy::ProxyType
type =
789 url.scheme().isEmpty() ? QNetworkProxy::HttpProxy :
790 url.scheme() ==
"socks" ? QNetworkProxy::Socks5Proxy :
791 url.scheme() ==
"http" ? QNetworkProxy::HttpProxy :
792 url.scheme() ==
"https" ? QNetworkProxy::HttpProxy :
793 url.scheme() ==
"cache" ? QNetworkProxy::HttpCachingProxy :
794 url.scheme() ==
"ftp" ? QNetworkProxy::FtpCachingProxy :
795 QNetworkProxy::NoProxy;
796 if (QNetworkProxy::NoProxy !=
type)
798 LOG(VB_GENERAL, LOG_INFO,
LOC "Using proxy: " + proxy);
799 m_nam->setProxy(QNetworkProxy(
800 type, url.host(), url.port(), url.userName(), url.password() ));
804 LOG(VB_MHEG, LOG_ERR,
LOC + QString(
"Unknown proxy type %1")
805 .
arg(url.scheme()) );
810 connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
819 QCoreApplication::processEvents();
830 QScopedPointer< QEvent > ev(
m_workQ.dequeue());
842 LOG(VB_FILE, LOG_INFO,
LOC "NAMThread stopped");
860 switch (event->type())
864 #pragma GCC diagnostic push
865 #pragma GCC diagnostic ignored "-Wswitch"
868 #pragma GCC diagnostic pop
879 LOG(VB_GENERAL, LOG_ERR,
LOC "Invalid NetStreamRequest");
883 if (!
p->m_bCancelled)
885 QNetworkReply *reply =
m_nam->get(
p->m_req);
886 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) StartRequest 0x%2")
887 .
arg(
p->m_id).arg(quintptr(reply),0,16) );
891 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"(%1) NetStreamRequest cancelled").
arg(
p->m_id) );
899 LOG(VB_GENERAL, LOG_ERR,
LOC "Invalid NetStreamAbort");
903 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"(%1) AbortRequest 0x%2").
arg(
p->m_id)
904 .arg(quintptr(
p->m_reply),0,16) );
906 p->m_reply->disconnect();
921 QMutexLocker locker(&m.
m_mutex);
926 switch (m.
m_nam->networkAccessible())
928 case QNetworkAccessManager::Accessible:
return true;
929 case QNetworkAccessManager::NotAccessible:
return false;
930 case QNetworkAccessManager::UnknownAccessibility:
return true;
941 QMutexLocker locker(&m.
m_mutex);
946 QAbstractNetworkCache *cache = m.
m_nam->cache();
950 QNetworkCacheMetaData meta = cache->metaData(url);
953 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1') not in cache")
954 .
arg(url.toString()));
959 QDateTime
const now(QDateTime::currentDateTime());
960 QDateTime expire = meta.expirationDate();
961 if (expire.isValid() && expire.toLocalTime() < now)
963 LOG(VB_FILE, LOG_INFO,
LOC + QString(
"GetLastModified('%1') past expiration %2")
964 .
arg(url.toString()).arg(expire.toString()));
969 QDateTime lastMod = meta.lastModified();
971 QNetworkCacheMetaData::RawHeaderList headers = meta.rawHeaders();
972 for (
const auto& h : qAsConst(headers))
975 static const QString kSzFormat {
"ddd, dd MMM yyyy HH:mm:ss 'GMT'" };
977 QString
const first(h.first.toLower());
978 if (first ==
"cache-control")
980 QString
const second(h.second.toLower());
981 if (second ==
"no-cache" || second ==
"no-store")
983 LOG(VB_FILE, LOG_INFO,
LOC +
984 QString(
"GetLastModified('%1') Cache-Control disabled")
985 .
arg(url.toString()) );
990 else if (first ==
"date")
995 LOG(VB_GENERAL, LOG_WARNING,
LOC +
996 QString(
"GetLastModified invalid Date header '%1'")
997 .
arg(h.second.constData()));
1000 d.setTimeSpec(Qt::UTC);
1005 LOG(VB_FILE, LOG_DEBUG,
LOC + QString(
"GetLastModified('%1') last modified %2")
1006 .
arg(url.toString()).arg(lastMod.toString()));