13#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
14#include <QStringConverter>
20#include <QHostAddress>
23#include <QDomDocument>
34static constexpr std::chrono::milliseconds
MAX_WAIT { 30s };
36#define LOC QString("UPnPSub: ")
58 if (addr.protocol() == QAbstractSocket::IPv6Protocol)
59 host =
"[" + addr.toString() +
"]";
61 host = addr.toString();
63 m_callback = QString(
"http://%1:%2/Subscriptions/event?usn=")
64 .arg(host, QString::number(port));
71 while (!usns.isEmpty())
76 LOG(VB_UPNP, LOG_DEBUG,
LOC +
"Finished");
82 LOG(VB_UPNP, LOG_DEBUG,
LOC + QString(
"Subscribe %1 %2 %3")
83 .arg(usn, url.toString(), path));
96 LOG(VB_GENERAL, LOG_WARNING,
LOC +
97 "Re-subscribing with different url and path.");
116 QString uuid = QString();
134 LOG(VB_UPNP, LOG_DEBUG,
LOC + QString(
"Renew: %1").arg(usn));
150 LOG(VB_UPNP, LOG_ERR,
LOC + QString(
"Unrecognised renewal usn: %1")
161 LOG(VB_UPNP, LOG_ERR,
LOC + QString(
"No uuid - not renewing usn: %1")
175 LOG(VB_UPNP, LOG_INFO,
LOC + QString(
"Removing %1").arg(usn));
192 LOG(VB_UPNP, LOG_DEBUG,
LOC + QString(
"%1\n%2")
204 if (nt.isEmpty() || nts.isEmpty() || !no)
211 if (nt !=
"upnp:event" || nts !=
"upnp:propchange")
216 if (usn.isEmpty() || sid.isEmpty())
229 int loc = pRequest->
m_sPayload.lastIndexOf(
"propertyset>");
230 QString payload = (loc > -1) ? pRequest->
m_sPayload.left(loc + 12) :
233 LOG(VB_UPNP, LOG_DEBUG,
LOC + QString(
"Payload:\n%1").arg(payload));
237#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
241 if (!body.setContent(payload,
true, &
error, &errorLine, &errorCol))
243 LOG(VB_GENERAL, LOG_ERR,
LOC +
244 QString(
"Failed to parse event: Line: %1 Col: %2 Error: '%3'")
245 .arg(errorLine).arg(errorCol).arg(
error));
250 body.setContent(payload,
251 QDomDocument::ParseOption::UseNamespaceProcessing);
254 LOG(VB_GENERAL, LOG_ERR,
LOC +
255 QString(
"Failed to parse event: Line: %1 Col: %2 Error: '%3'")
256 .arg(parseResult.errorLine).arg(parseResult.errorColumn)
257 .arg(parseResult.errorMessage));
262 LOG(VB_UPNP, LOG_DEBUG,
LOC +
"/n/n" + body.toString(4) +
"/n/n");
264 QDomNodeList properties = body.elementsByTagName(
"property");
269 for (
int i = 0; i < properties.size(); i++)
271 QDomNodeList arguments = properties.at(i).childNodes();
272 for (
int j = 0; j < arguments.size(); j++)
274 QDomElement e = arguments.at(j).toElement();
275 if (!e.isNull() && !e.text().isEmpty() && !e.tagName().isEmpty())
276 results.insert(e.tagName(), e.text());
300 bool success =
false;
301 QString host = url.host();
302 int port = url.port();
305 QTextStream data(&sub);
306#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
307 data.setCodec(QTextCodec::codecForName(
"UTF-8"));
309 data.setEncoding(QStringConverter::Utf8);
312 data << QString(
"UNSUBSCRIBE %1 HTTP/1.1\r\n").arg(path);
313 data << QString(
"HOST: %1:%2\r\n").arg(host, QString::number(port));
314 data << QString(
"SID: uuid:%1\r\n").arg(uuid);
318 LOG(VB_UPNP, LOG_DEBUG,
LOC +
"\n\n" + sub);
320 auto *sockdev =
new MSocketDevice(MSocketDevice::Stream);
322 sockdev->setBlocking(
true);
324 if (sock->Connect(QHostAddress(host), port))
326 if (sock->WriteBlockDirect(sub.constData(), sub.size()) != -1)
328 QString line = sock->ReadLine(
MAX_WAIT);
329 success = !line.isEmpty();
333 LOG(VB_GENERAL, LOG_ERR,
LOC +
334 QString(
"Socket write error for %1:%2") .arg(host).arg(port));
340 LOG(VB_GENERAL, LOG_ERR,
LOC +
341 QString(
"Failed to open socket for %1:%2") .arg(host).arg(port));
347 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Unsubscribed to %1").arg(usn));
349 LOG(VB_UPNP, LOG_WARNING,
LOC + QString(
"Failed to unsubscribe to %1")
358 const QString &uuidin,
361 QString host = url.host();
362 int port = url.port();
365 QTextStream data(&sub);
366#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
367 data.setCodec(QTextCodec::codecForName(
"UTF-8"));
369 data.setEncoding(QStringConverter::Utf8);
372 data << QString(
"SUBSCRIBE %1 HTTP/1.1\r\n").arg(path);
373 data << QString(
"HOST: %1:%2\r\n").arg(host, QString::number(port));
376 if (uuidin.isEmpty())
378 data << QString(
"CALLBACK: <%1%2>\r\n")
380 data <<
"NT: upnp:event\r\n";
385 data << QString(
"SID: uuid:%1\r\n").arg(uuidin);
392 LOG(VB_UPNP, LOG_DEBUG,
LOC +
"\n\n" + sub);
394 auto *sockdev =
new MSocketDevice(MSocketDevice::Stream);
396 sockdev->setBlocking(
true);
400 std::chrono::seconds result = 0s;
402 if (sock->Connect(QHostAddress(host), port))
404 if (sock->WriteBlockDirect(sub.constData(), sub.size()) != -1)
407 QString line = sock->ReadLine(
MAX_WAIT);
408 while (!line.isEmpty())
410 LOG(VB_UPNP, LOG_DEBUG,
LOC + line);
411 if (line.contains(
"HTTP/1.1 200 OK", Qt::CaseInsensitive))
413 if (line.startsWith(
"SID:", Qt::CaseInsensitive))
414 uuid = line.mid(4).trimmed().mid(5).trimmed();
415 if (line.startsWith(
"TIMEOUT:", Qt::CaseInsensitive))
416 timeout = line.mid(8).trimmed().mid(7).trimmed();
417 if (ok && !uuid.isEmpty() && !
timeout.isEmpty())
422 if (ok && !uuid.isEmpty() && !
timeout.isEmpty())
425 result = std::chrono::seconds(
timeout.toUInt());
429 LOG(VB_GENERAL, LOG_ERR,
LOC +
430 QString(
"Failed to subscribe to %1").arg(usn));
435 LOG(VB_GENERAL, LOG_ERR,
LOC +
436 QString(
"Socket write error for %1:%2") .arg(host).arg(port));
442 LOG(VB_GENERAL, LOG_ERR,
LOC +
443 QString(
"Failed to open socket for %1:%2") .arg(host).arg(port));
HttpResponseType m_eResponseType
QString GetLastHeader(const QString &sType) const
void dispatch(const MythEvent &event)
Dispatch an event to all listeners.
Subscription(QUrl url, QString path)
~UPNPSubscription() override
QRecursiveMutex m_subscriptionLock
bool ProcessRequest(HTTPRequest *pRequest) override
UPNPSubscription(const QString &share_path, int port)
void Unsubscribe(const QString &usn)
static std::chrono::seconds SendSubscribeRequest(const QString &callback, const QString &usn, const QUrl &url, const QString &path, const QString &uuidin, QString &uuidout)
void Remove(const QString &usn)
std::chrono::seconds Renew(const QString &usn)
QHash< QString, Subscription * > m_subscriptions
std::chrono::seconds Subscribe(const QString &usn, const QUrl &url, const QString &path)
static bool SendUnsubscribeRequest(const QString &usn, const QUrl &url, const QString &path, const QString &uuid)
static QList< QHostAddress > g_IPAddrList
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QHash< QString, QString > InfoMap
static constexpr std::chrono::milliseconds MAX_WAIT
static constexpr uint16_t SUBSCRIPTION_TIME