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"