File indexing completed on 2024-04-28 04:44:21

0001 /*
0002    SPDX-FileCopyrightText: 2015 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
0003 
0004    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005  */
0006 
0007 #include "upnpcontrolabstractservice.h"
0008 
0009 #include "upnplogging.h"
0010 
0011 #include "upnpbasictypes.h"
0012 #include "upnphttpserver.h"
0013 #include "upnpservereventobject.h"
0014 
0015 #include "upnpactiondescription.h"
0016 #include "upnpservicedescription.h"
0017 
0018 #include <KDSoapClient/KDSoapClientInterface.h>
0019 #include <KDSoapClient/KDSoapMessage.h>
0020 
0021 #include <QDnsLookup>
0022 #include <QHostInfo>
0023 #include <QNetworkAccessManager>
0024 #include <QNetworkInterface>
0025 #include <QNetworkReply>
0026 
0027 #include <QDomDocument>
0028 
0029 #include <QBuffer>
0030 #include <QPointer>
0031 #include <QTextStream>
0032 #include <QTimer>
0033 
0034 #include <QLoggingCategory>
0035 
0036 class UpnpAbstractServiceDescriptionPrivate
0037 {
0038 public:
0039     QNetworkAccessManager mNetworkAccess;
0040 
0041     std::unique_ptr<KDSoapClientInterface> mInterface;
0042 
0043     UpnpHttpServer mEventServer;
0044 
0045     QHostAddress mPublicAddress;
0046 
0047     std::unique_ptr<QTimer> mEventSubscriptionTimer;
0048 
0049     int mRealEventSubscriptionTimeout = 0;
0050 };
0051 
0052 UpnpControlAbstractService::UpnpControlAbstractService(QObject *parent)
0053     : UpnpAbstractService(parent)
0054     , d(std::make_unique<UpnpAbstractServiceDescriptionPrivate>())
0055 {
0056     connect(&d->mNetworkAccess, &QNetworkAccessManager::finished, this, &UpnpControlAbstractService::finishedDownload);
0057 
0058     d->mEventServer.setService(this);
0059 
0060     d->mEventServer.listen(QHostAddress::Any);
0061 
0062     const QList<QHostAddress> &list = QNetworkInterface::allAddresses();
0063     for (const auto &address : list) {
0064         if (!address.isLoopback()) {
0065             if (address.protocol() == QAbstractSocket::IPv4Protocol) {
0066                 d->mPublicAddress = address;
0067                 break;
0068             }
0069         }
0070     }
0071 }
0072 
0073 UpnpControlAbstractService::~UpnpControlAbstractService() = default;
0074 
0075 UpnpControlAbstractServiceReply *UpnpControlAbstractService::callAction(const QString &actionName, const QMap<QString, QVariant> &arguments)
0076 {
0077     KDSoapMessage message;
0078 
0079     const UpnpActionDescription &actionDescription(action(actionName));
0080 
0081     for (const auto &argumentDescription : actionDescription.mArguments) {
0082         if (argumentDescription.mDirection == UpnpArgumentDirection::In &&
0083                 arguments[argumentDescription.mName].isValid() &&
0084                 !arguments[argumentDescription.mName].toString().isEmpty()) {
0085             message.addArgument(argumentDescription.mName, arguments[argumentDescription.mName]);
0086         }
0087     }
0088 
0089     if (!d->mInterface) {
0090         d->mInterface = std::make_unique<KDSoapClientInterface>(description().controlURL().toString(), description().serviceType());
0091         d->mInterface->setSoapVersion(KDSoapClientInterface::SOAP1_1);
0092         d->mInterface->setStyle(KDSoapClientInterface::RPCStyle);
0093     }
0094 
0095     return new UpnpControlAbstractServiceReply(d->mInterface->asyncCall(actionName, message, description().serviceType() + QStringLiteral("#") + actionName), this);
0096 }
0097 
0098 void UpnpControlAbstractService::subscribeEvents(int duration)
0099 {
0100     QString webServerAddess(QStringLiteral("<http://"));
0101 
0102     if (!d->mPublicAddress.isNull()) {
0103         webServerAddess += d->mPublicAddress.toString();
0104     } else {
0105         webServerAddess += QStringLiteral("127.0.0.1");
0106     }
0107 
0108     webServerAddess += QStringLiteral(":") + QString::number(d->mEventServer.serverPort()) + QStringLiteral(">");
0109 
0110     QNetworkRequest myRequest(description().eventURL());
0111     myRequest.setRawHeader("CALLBACK", webServerAddess.toUtf8());
0112     myRequest.setRawHeader("NT", "upnp:event");
0113     QString timeoutDefinition(QStringLiteral("Second-"));
0114     timeoutDefinition += QString::number(duration);
0115     myRequest.setRawHeader("TIMEOUT", timeoutDefinition.toLatin1());
0116 
0117     d->mNetworkAccess.sendCustomRequest(myRequest, "SUBSCRIBE");
0118 }
0119 
0120 void UpnpControlAbstractService::handleEventNotification(const QByteArray &requestData, const QMap<QByteArray, QByteArray> &headers)
0121 {
0122     Q_UNUSED(headers)
0123 
0124     const QString &requestAnswer(QString::fromLatin1(requestData));
0125 
0126     QDomDocument requestDocument;
0127     requestDocument.setContent(requestAnswer);
0128 
0129     const QDomElement &eventRoot = requestDocument.documentElement();
0130 
0131     const QDomElement &propertysetRoot = eventRoot.firstChildElement();
0132     if (!propertysetRoot.isNull()) {
0133         const QDomElement &propertyNode = propertysetRoot.firstChildElement();
0134         if (!propertyNode.isNull()) {
0135             QDomNode currentChild = propertyNode.firstChild();
0136             if (!currentChild.isNull()) {
0137                 parseEventNotification(propertyNode.tagName(), currentChild.toCharacterData().data());
0138             }
0139         }
0140     }
0141 }
0142 
0143 void UpnpControlAbstractService::downloadServiceDescription(const QUrl &serviceUrl)
0144 {
0145     d->mNetworkAccess.get(QNetworkRequest(serviceUrl));
0146 }
0147 
0148 void UpnpControlAbstractService::finishedDownload(QNetworkReply *reply)
0149 {
0150     if (reply->isFinished() && reply->error() == QNetworkReply::NoError) {
0151         if (reply->url() == description().eventURL()) {
0152             if (reply->hasRawHeader("TIMEOUT")) {
0153                 if (reply->rawHeader("TIMEOUT").startsWith("Second-")) {
0154                     d->mRealEventSubscriptionTimeout = reply->rawHeader("TIMEOUT").mid(7).toInt();
0155 
0156                     if (!d->mEventSubscriptionTimer) {
0157                         d->mEventSubscriptionTimer = std::make_unique<QTimer>();
0158                         connect(d->mEventSubscriptionTimer.get(), &QTimer::timeout, this, &UpnpControlAbstractService::eventSubscriptionTimeout);
0159                         d->mEventSubscriptionTimer->setInterval(1000 * (d->mRealEventSubscriptionTimeout > 60 ? d->mRealEventSubscriptionTimeout - 60 : d->mRealEventSubscriptionTimeout));
0160                         d->mEventSubscriptionTimer->start();
0161                     }
0162                 }
0163             }
0164         } else {
0165             parseServiceDescription(reply);
0166         }
0167     } else if (reply->isFinished()) {
0168         qCDebug(orgKdeUpnpLibQtUpnp()) << "UpnpAbstractServiceDescription::finishedDownload"
0169                                        << "error";
0170     }
0171 }
0172 
0173 void UpnpControlAbstractService::eventSubscriptionTimeout()
0174 {
0175     subscribeEvents(d->mRealEventSubscriptionTimeout);
0176 }
0177 
0178 void UpnpControlAbstractService::parseServiceDescription(QIODevice *serviceDescriptionContent)
0179 {
0180     QDomDocument serviceDescriptionDocument;
0181     serviceDescriptionDocument.setContent(serviceDescriptionContent);
0182 
0183     const QDomElement &scpdRoot = serviceDescriptionDocument.documentElement();
0184 
0185     const QDomElement &actionListRoot = scpdRoot.firstChildElement(QStringLiteral("actionList"));
0186     QDomNode currentChild = actionListRoot.firstChild();
0187     while (!currentChild.isNull()) {
0188         const QDomNode &nameNode = currentChild.firstChildElement(QStringLiteral("name"));
0189 
0190         QString actionName;
0191         if (!nameNode.isNull()) {
0192             actionName = nameNode.toElement().text();
0193         }
0194 
0195         UpnpActionDescription newAction;
0196 
0197         newAction.mName = actionName;
0198 
0199         const QDomNode &argumentListNode = currentChild.firstChildElement(QStringLiteral("argumentList"));
0200         QDomNode argumentNode = argumentListNode.firstChild();
0201         while (!argumentNode.isNull()) {
0202             const QDomNode &argumentNameNode = argumentNode.firstChildElement(QStringLiteral("name"));
0203             const QDomNode &argumentDirectionNode = argumentNode.firstChildElement(QStringLiteral("direction"));
0204             const QDomNode &argumentRetvalNode = argumentNode.firstChildElement(QStringLiteral("retval"));
0205             const QDomNode &argumentRelatedStateVariableNode = argumentNode.firstChildElement(QStringLiteral("relatedStateVariable"));
0206 
0207             UpnpActionArgumentDescription newArgument;
0208             newArgument.mName = argumentNameNode.toElement().text();
0209             newArgument.mDirection = (argumentDirectionNode.toElement().text() == QStringLiteral("in") ? UpnpArgumentDirection::In : UpnpArgumentDirection::Out);
0210             newArgument.mIsReturnValue = !argumentRetvalNode.isNull();
0211             newArgument.mRelatedStateVariable = argumentRelatedStateVariableNode.toElement().text();
0212 
0213             newAction.mArguments.push_back(newArgument);
0214 
0215             addAction(newAction);
0216 
0217             argumentNode = argumentNode.nextSibling();
0218         }
0219 
0220         currentChild = currentChild.nextSibling();
0221     }
0222 
0223 #if 0
0224     const QDomElement &serviceStateTableRoot = scpdRoot.firstChildElement(QStringLiteral("serviceStateTable"));
0225     currentChild = serviceStateTableRoot.firstChild();
0226     while (!currentChild.isNull()) {
0227         const QDomNode &nameNode = currentChild.firstChildElement(QStringLiteral("name"));
0228         if (!nameNode.isNull()) {
0229             qCDebug(orgKdeUpnpLibQtUpnp()) << "state variable name" << nameNode.toElement().text();
0230         }
0231 
0232         currentChild = currentChild.nextSibling();
0233     }
0234 #endif
0235 }
0236 
0237 void UpnpControlAbstractService::parseEventNotification(const QString &eventName, const QString &eventValue)
0238 {
0239     Q_UNUSED(eventName)
0240     Q_UNUSED(eventValue)
0241 }
0242 
0243 #include "moc_upnpcontrolabstractservice.cpp"