00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include <QTextCodec>
00012
00013 #include "mythcorecontext.h"
00014 #include "mythlogging.h"
00015 #include "upnpsubscription.h"
00016
00017
00018 #define SUBSCRIPTION_TIME 1800
00019
00020 #define MAX_WAIT 30000
00021
00022 #define LOC QString("UPnPSub: ")
00023
00024 class Subscription
00025 {
00026 public:
00027 Subscription(QUrl url, QString path)
00028 : m_url(url), m_path(path), m_uuid(QString()) { }
00029 QUrl m_url;
00030 QString m_path;
00031 QString m_uuid;
00032 };
00033
00034 UPNPSubscription::UPNPSubscription(const QString &share_path, int port)
00035 : HttpServerExtension("UPnPSubscriptionManager", share_path),
00036 m_subscriptionLock(QMutex::Recursive), m_callback(QString("NOTSET"))
00037 {
00038 QString host;
00039 if (!UPnp::g_IPAddrList.isEmpty())
00040 host = UPnp::g_IPAddrList.at(0);
00041
00042
00043 #if !defined(QT_NO_IPV6)
00044 QHostAddress addr(host);
00045 if ((addr.protocol() == QAbstractSocket::IPv6Protocol) ||
00046 (host.contains(":")))
00047 host = "[" + host + "]";
00048 #endif
00049
00050 m_callback = QString("http://%1:%2/Subscriptions/event?usn=")
00051 .arg(host).arg(QString::number(port));
00052 }
00053
00054 UPNPSubscription::~UPNPSubscription()
00055 {
00056 m_subscriptionLock.lock();
00057 QList<QString> usns = m_subscriptions.keys();
00058 while (!usns.isEmpty())
00059 Unsubscribe(usns.takeLast());
00060 m_subscriptions.clear();
00061 m_subscriptionLock.unlock();
00062
00063 LOG(VB_UPNP, LOG_DEBUG, LOC + "Finished");
00064 }
00065
00066 int UPNPSubscription::Subscribe(const QString &usn, const QUrl &url,
00067 const QString &path)
00068 {
00069 LOG(VB_UPNP, LOG_DEBUG, LOC + QString("Subscribe %1 %2 %3")
00070 .arg(usn).arg(url.toString()).arg(path));
00071
00072
00073
00074
00075
00076
00077 QMutexLocker lock(&m_subscriptionLock);
00078 if (m_subscriptions.contains(usn))
00079 {
00080 if (m_subscriptions[usn]->m_url != url ||
00081 m_subscriptions[usn]->m_path != path)
00082 {
00083 LOG(VB_GENERAL, LOG_WARNING, LOC +
00084 "Re-subscribing with different url and path.");
00085 m_subscriptions[usn]->m_url = url;
00086 m_subscriptions[usn]->m_path = path;
00087 m_subscriptions[usn]->m_uuid = QString();
00088 }
00089 }
00090 else
00091 {
00092 m_subscriptions.insert(usn, new Subscription(url, path));
00093 }
00094
00095 return SendSubscribeRequest(m_callback, usn, url, path, QString(),
00096 m_subscriptions[usn]->m_uuid);
00097 }
00098
00099 void UPNPSubscription::Unsubscribe(const QString &usn)
00100 {
00101 QUrl url;
00102 QString path;
00103 QString uuid = QString();
00104 m_subscriptionLock.lock();
00105 if (m_subscriptions.contains(usn))
00106 {
00107 url = m_subscriptions[usn]->m_url;
00108 path = m_subscriptions[usn]->m_path;
00109 uuid = m_subscriptions[usn]->m_uuid;
00110 delete m_subscriptions.value(usn);
00111 m_subscriptions.remove(usn);
00112 }
00113 m_subscriptionLock.unlock();
00114
00115 if (!uuid.isEmpty())
00116 SendUnsubscribeRequest(usn, url, path, uuid);
00117 }
00118
00119 int UPNPSubscription::Renew(const QString &usn)
00120 {
00121 LOG(VB_UPNP, LOG_DEBUG, LOC + QString("Renew: %1").arg(usn));
00122
00123 QUrl url;
00124 QString path;
00125 QString sid;
00126
00127
00128 QMutexLocker lock(&m_subscriptionLock);
00129 if (m_subscriptions.contains(usn))
00130 {
00131 url = m_subscriptions[usn]->m_url;
00132 path = m_subscriptions[usn]->m_path;
00133 sid = m_subscriptions[usn]->m_uuid;
00134 }
00135 else
00136 {
00137 LOG(VB_UPNP, LOG_ERR, LOC + QString("Unrecognised renewal usn: %1")
00138 .arg(usn));
00139 return 0;
00140 }
00141
00142 if (!sid.isEmpty())
00143 {
00144 return SendSubscribeRequest(m_callback, usn, url, path, sid,
00145 m_subscriptions[usn]->m_uuid);
00146 }
00147
00148 LOG(VB_UPNP, LOG_ERR, LOC + QString("No uuid - not renewing usn: %1")
00149 .arg(usn));
00150 return 0;
00151 }
00152
00153 void UPNPSubscription::Remove(const QString &usn)
00154 {
00155
00156
00157
00158
00159 m_subscriptionLock.lock();
00160 if (m_subscriptions.contains(usn))
00161 {
00162 LOG(VB_UPNP, LOG_INFO, LOC + QString("Removing %1").arg(usn));
00163 delete m_subscriptions.value(usn);
00164 m_subscriptions.remove(usn);
00165 }
00166 m_subscriptionLock.unlock();
00167 }
00168
00169 bool UPNPSubscription::ProcessRequest(HTTPRequest *pRequest)
00170 {
00171 if (!pRequest)
00172 return false;
00173
00174 if (pRequest->m_sBaseUrl != "/Subscriptions")
00175 return false;
00176 if (pRequest->m_sMethod != "event")
00177 return false;
00178
00179 LOG(VB_UPNP, LOG_DEBUG, LOC + QString("%1\n%2")
00180 .arg(pRequest->m_sRawRequest).arg(pRequest->m_sPayload));
00181
00182 if (pRequest->m_sPayload.isEmpty())
00183 return true;
00184
00185 pRequest->m_eResponseType = ResponseTypeHTML;
00186
00187 QString nt = pRequest->m_mapHeaders["nt"];
00188 QString nts = pRequest->m_mapHeaders["nts"];
00189 bool no = pRequest->m_sRawRequest.startsWith("NOTIFY");
00190
00191 if (nt.isEmpty() || nts.isEmpty() || !no)
00192 {
00193 pRequest->m_nResponseStatus = 400;
00194 return true;
00195 }
00196
00197 pRequest->m_nResponseStatus = 412;
00198 if (nt != "upnp:event" || nts != "upnp:propchange")
00199 return true;
00200
00201 QString usn = pRequest->m_mapParams["usn"];
00202 QString sid = pRequest->m_mapHeaders["sid"];
00203 if (usn.isEmpty() || sid.isEmpty())
00204 return true;
00205
00206
00207
00208
00209
00210
00211
00212 QString seq = pRequest->m_mapHeaders["seq"];
00213
00214
00215
00216 int loc = pRequest->m_sPayload.lastIndexOf("propertyset>");
00217 QString payload = (loc > -1) ? pRequest->m_sPayload.left(loc + 12) :
00218 pRequest->m_sPayload;
00219
00220 LOG(VB_UPNP, LOG_DEBUG, LOC + QString("Payload:\n%1").arg(payload));
00221
00222 pRequest->m_nResponseStatus = 400;
00223 QDomDocument body;
00224 QString error;
00225 int errorCol = 0;
00226 int errorLine = 0;
00227 if (!body.setContent(payload, true, &error, &errorLine, &errorCol))
00228 {
00229 LOG(VB_GENERAL, LOG_ERR, LOC +
00230 QString("Failed to parse event: Line: %1 Col: %2 Error: '%3'")
00231 .arg(errorLine).arg(errorCol).arg(error));
00232 return true;
00233 }
00234
00235 LOG(VB_UPNP, LOG_DEBUG, LOC + "/n/n" + body.toString(4) + "/n/n");
00236
00237 QDomNodeList properties = body.elementsByTagName("property");
00238 QHash<QString,QString> results;
00239
00240
00241
00242 for (int i = 0; i < properties.size(); i++)
00243 {
00244 QDomNodeList arguments = properties.at(i).childNodes();
00245 for (int j = 0; j < arguments.size(); j++)
00246 {
00247 QDomElement e = arguments.at(j).toElement();
00248 if (!e.isNull() && !e.text().isEmpty() && !e.tagName().isEmpty())
00249 results.insert(e.tagName(), e.text());
00250 }
00251 }
00252
00253
00254
00255
00256 if (!results.isEmpty())
00257 {
00258 pRequest->m_nResponseStatus = 200;
00259 results.insert("usn", usn);
00260 results.insert("seq", seq);
00261 MythInfoMapEvent me("UPNP_EVENT", results);
00262 dispatch(me);
00263 }
00264
00265 return true;
00266 }
00267
00268 bool UPNPSubscription::SendUnsubscribeRequest(const QString &usn,
00269 const QUrl &url,
00270 const QString &path,
00271 const QString &uuid)
00272 {
00273 bool success = false;
00274 QString host = url.host();
00275 int port = url.port();
00276
00277 QByteArray sub;
00278 QTextStream data(&sub);
00279 data.setCodec(QTextCodec::codecForName("UTF-8"));
00280
00281 data << QString("UNSUBSCRIBE %1 HTTP/1.1\r\n").arg(path);
00282 data << QString("HOST: %1:%2\r\n").arg(host).arg(QString::number(port));
00283 data << QString("SID: uuid:%1\r\n").arg(uuid);
00284 data << "\r\n";
00285 data.flush();
00286
00287 LOG(VB_UPNP, LOG_DEBUG, LOC + "\n\n" + sub);
00288
00289 MSocketDevice *sockdev = new MSocketDevice(MSocketDevice::Stream);
00290 BufferedSocketDevice *sock = new BufferedSocketDevice(sockdev);
00291 sockdev->setBlocking(true);
00292
00293 if (sock->Connect(QHostAddress(host), port))
00294 {
00295 if (sock->WriteBlockDirect(sub.constData(), sub.size()) != -1)
00296 {
00297 QString line = sock->ReadLine(MAX_WAIT);
00298 success = !line.isEmpty();
00299 }
00300 else
00301 {
00302 LOG(VB_GENERAL, LOG_ERR, LOC +
00303 QString("Socket write error for %1:%2") .arg(host).arg(port));
00304 }
00305 sock->Close();
00306 }
00307 else
00308 {
00309 LOG(VB_GENERAL, LOG_ERR, LOC +
00310 QString("Failed to open socket for %1:%2") .arg(host).arg(port));
00311 }
00312
00313 delete sock;
00314 delete sockdev;
00315 if (success)
00316 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Unsubscribed to %1").arg(usn));
00317 else
00318 LOG(VB_UPNP, LOG_WARNING, LOC + QString("Failed to unsubscribe to %1")
00319 .arg(usn));
00320 return success;
00321 }
00322
00323 int UPNPSubscription::SendSubscribeRequest(const QString &callback,
00324 const QString &usn,
00325 const QUrl &url,
00326 const QString &path,
00327 const QString &uuidin,
00328 QString &uuidout)
00329 {
00330 QString host = url.host();
00331 int port = url.port();
00332
00333 QByteArray sub;
00334 QTextStream data(&sub);
00335 data.setCodec(QTextCodec::codecForName("UTF-8"));
00336
00337 data << QString("SUBSCRIBE %1 HTTP/1.1\r\n").arg(path);
00338 data << QString("HOST: %1:%2\r\n").arg(host).arg(QString::number(port));
00339
00340
00341 if (uuidin.isEmpty())
00342 {
00343 data << QString("CALLBACK: <%1%2>\r\n")
00344 .arg(callback).arg(usn);
00345 data << "NT: upnp:event\r\n";
00346 }
00347 else
00348 data << QString("SID: uuid:%1\r\n").arg(uuidin);
00349
00350 data << QString("TIMEOUT: Second-%1\r\n").arg(SUBSCRIPTION_TIME);
00351 data << "\r\n";
00352 data.flush();
00353
00354 LOG(VB_UPNP, LOG_DEBUG, LOC + "\n\n" + sub);
00355
00356 MSocketDevice *sockdev = new MSocketDevice(MSocketDevice::Stream);
00357 BufferedSocketDevice *sock = new BufferedSocketDevice(sockdev);
00358 sockdev->setBlocking(true);
00359
00360 QString uuid;
00361 QString timeout;
00362 uint result = 0;
00363
00364 if (sock->Connect(QHostAddress(host), port))
00365 {
00366 if (sock->WriteBlockDirect(sub.constData(), sub.size()) != -1)
00367 {
00368 bool ok = false;
00369 QString line = sock->ReadLine(MAX_WAIT);
00370 while (!line.isEmpty())
00371 {
00372 LOG(VB_UPNP, LOG_DEBUG, LOC + line);
00373 if (line.contains("HTTP/1.1 200 OK", Qt::CaseInsensitive))
00374 ok = true;
00375 if (line.startsWith("SID:", Qt::CaseInsensitive))
00376 uuid = line.mid(4).trimmed().mid(5).trimmed();
00377 if (line.startsWith("TIMEOUT:", Qt::CaseInsensitive))
00378 timeout = line.mid(8).trimmed().mid(7).trimmed();
00379 if (ok && !uuid.isEmpty() && !timeout.isEmpty())
00380 break;
00381 line = sock->ReadLine(MAX_WAIT);
00382 }
00383
00384 if (ok && !uuid.isEmpty() && !timeout.isEmpty())
00385 {
00386 uuidout = uuid;
00387 result = timeout.toUInt();
00388 }
00389 else
00390 {
00391 LOG(VB_GENERAL, LOG_ERR, LOC +
00392 QString("Failed to subscribe to %1").arg(usn));
00393 }
00394 }
00395 else
00396 {
00397 LOG(VB_GENERAL, LOG_ERR, LOC +
00398 QString("Socket write error for %1:%2") .arg(host).arg(port));
00399 }
00400 sock->Close();
00401 }
00402 else
00403 {
00404 LOG(VB_GENERAL, LOG_ERR, LOC +
00405 QString("Failed to open socket for %1:%2") .arg(host).arg(port));
00406 }
00407
00408 delete sock;
00409 delete sockdev;
00410 return result;
00411 }