1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5 
6 #include "mumble_pch.hpp"
7 
8 #include "WebFetch.h"
9 
10 #include "Global.h"
11 #include "NetworkConfig.h"
12 
WebFetch(QString service,QUrl url,QObject * obj,const char * slot)13 WebFetch::WebFetch(QString service, QUrl url, QObject *obj, const char *slot)
14 	: QObject()
15 	, qoObject(obj)
16 	, cpSlot(slot)
17 	, m_service(service) {
18 
19 	url.setScheme(QLatin1String("https"));
20 
21 	if (!g.s.qsServicePrefix.isEmpty()) {
22 		url.setHost(prefixedServiceHost());
23 	} else {
24 		url.setHost(serviceHost());
25 	}
26 
27 	qnr = Network::get(url);
28 	connect(qnr, SIGNAL(finished()), this, SLOT(finished()));
29 	connect(this, SIGNAL(fetched(QByteArray,QUrl,QMap<QString,QString>)), obj, slot);
30 }
31 
prefixedServiceHost() const32 QString WebFetch::prefixedServiceHost() const {
33 	if (g.s.qsServicePrefix.isEmpty()) {
34 		return serviceHost();
35 	}
36 	return QString::fromLatin1("%1-%2.mumble.info").arg(g.s.qsServicePrefix, m_service);
37 }
38 
serviceHost() const39 QString WebFetch::serviceHost() const {
40 	return QString::fromLatin1("%1.mumble.info").arg(m_service);
41 }
42 
fromUtf8(const QByteArray & qba)43 static QString fromUtf8(const QByteArray &qba) {
44 	if (qba.isEmpty())
45 		return QString();
46 	return QString::fromUtf8(qba.constData(), qba.length());
47 }
48 
finished()49 void WebFetch::finished() {
50 	// Note that if this functions succeeds, it should deleteLater() itself, as this is a temporary object.
51 	Q_ASSERT(qobject_cast<QNetworkReply *>(sender()) == qnr);
52 	qnr->disconnect();
53 	qnr->deleteLater();
54 
55 	QUrl url = qnr->request().url();
56 
57 	if (qnr->error() == QNetworkReply::NoError) {
58 		QByteArray a = qnr->readAll();
59 
60 		// empty response is not an error
61 		if (a.isNull())
62 			a.append("");
63 
64 		QMap<QString, QString> headers;
65 
66 		foreach(const QByteArray &headerName, qnr->rawHeaderList()) {
67 			QString name = fromUtf8(headerName);
68 			QString value = fromUtf8(qnr->rawHeader(headerName));
69 			if (! name.isEmpty() && ! value.isEmpty()) {
70 				headers.insert(name, value);
71 				if (name == QLatin1String("Use-Service-Prefix")) {
72 					QRegExp servicePrefixRegExp(QLatin1String("^[a-zA-Z]+$"));
73 					if (servicePrefixRegExp.exactMatch(value)) {
74 						g.s.qsServicePrefix = value.toLower();
75 					}
76 				}
77 			}
78 		}
79 
80 		emit fetched(a, url, headers);
81 		deleteLater();
82 	} else if (url.host() == prefixedServiceHost() && url.host() != serviceHost()) {
83 		// We have tried to fetch from a local service domain (e.g. de-update.mumble.info)
84 		// which has failed, so naturally we want to try the non-local one (update.mumble.info)
85 		// as well as maybe that one will work.
86 		// This of course only makes sense, if prefixedServiceHost() and serviceHost() are in fact
87 		// different hosts.
88 		url.setHost(serviceHost());
89 
90 		qnr = Network::get(url);
91 		connect(qnr, SIGNAL(finished()), this, SLOT(finished()));
92 	} else {
93 		emit fetched(QByteArray(), url, QMap<QString,QString>());
94 		deleteLater();
95 	}
96 }
97 
98 /**
99  * @brief Fetch URL from mumble servers.
100  *
101  * If fetching fails, the slot is invoked with a null QByteArray.
102  * @param url URL to fetch. Hostname and scheme must be blank.
103  * @param obj Object to invoke slot on.
104  * @param slot Slot to be triggered, invoked with the signature of \link fetched.
105  */
fetch(const QString & service,const QUrl & url,QObject * obj,const char * slot)106 void WebFetch::fetch(const QString &service, const QUrl &url, QObject *obj, const char *slot) {
107 	Q_ASSERT(!service.isEmpty());
108 	Q_ASSERT(url.scheme().isEmpty());
109 	Q_ASSERT(url.host().isEmpty());
110 	Q_ASSERT(obj);
111 	Q_ASSERT(slot);
112 
113 	new WebFetch(service, url, obj, slot);
114 }
115