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 "ConnectDialog.h"
9 
10 #ifdef USE_BONJOUR
11 #include "BonjourClient.h"
12 #include "BonjourServiceBrowser.h"
13 #include "BonjourServiceResolver.h"
14 #endif
15 
16 #include "Channel.h"
17 #include "Database.h"
18 #include "ServerHandler.h"
19 #include "WebFetch.h"
20 #include "ServerResolver.h"
21 
22 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
23 #include "Global.h"
24 
25 QMap<QString, QIcon> ServerItem::qmIcons;
26 QList<PublicInfo> ConnectDialog::qlPublicServers;
27 QString ConnectDialog::qsUserCountry, ConnectDialog::qsUserCountryCode, ConnectDialog::qsUserContinentCode;
28 Timer ConnectDialog::tPublicServers;
29 
30 
PingStats()31 PingStats::PingStats() {
32 	init();
33 }
34 
~PingStats()35 PingStats::~PingStats() {
36 	delete asQuantile;
37 }
38 
init()39 void PingStats::init() {
40 	boost::array<double, 3> probs = {{0.75, 0.80, 0.95 }};
41 
42 	asQuantile = new asQuantileType(boost::accumulators::tag::extended_p_square::probabilities = probs);
43 	dPing = 0.0;
44 	uiPing = 0;
45 	uiPingSort = 0;
46 	uiUsers = 0;
47 	uiMaxUsers = 0;
48 	uiBandwidth = 0;
49 	uiSent = 0;
50 	uiRecv = 0;
51 	uiVersion = 0;
52 }
53 
reset()54 void PingStats::reset() {
55 	delete asQuantile;
56 	init();
57 }
58 
ServerViewDelegate(QObject * p)59 ServerViewDelegate::ServerViewDelegate(QObject *p) : QStyledItemDelegate(p) {
60 }
61 
~ServerViewDelegate()62 ServerViewDelegate::~ServerViewDelegate() {
63 }
64 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const65 void ServerViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
66 	// Allow a ServerItem's BackgroundRole to override the current theme's default color.
67 	QVariant bg = index.data(Qt::BackgroundRole);
68 	if (bg.isValid()) {
69 		painter->fillRect(option.rect, bg.value<QBrush>());
70 	}
71 
72 	QStyledItemDelegate::paint(painter, option, index);
73 }
74 
ServerView(QWidget * p)75 ServerView::ServerView(QWidget *p) : QTreeWidget(p) {
76 	siFavorite = new ServerItem(tr("Favorite"), ServerItem::FavoriteType);
77 	addTopLevelItem(siFavorite);
78 	siFavorite->setExpanded(true);
79 	siFavorite->setHidden(true);
80 
81 #ifdef USE_BONJOUR
82 	siLAN = new ServerItem(tr("LAN"), ServerItem::LANType);
83 	addTopLevelItem(siLAN);
84 	siLAN->setExpanded(true);
85 	siLAN->setHidden(true);
86 #else
87 	siLAN = NULL;
88 #endif
89 
90 	if (!g.s.disablePublicList) {
91 		siPublic = new ServerItem(tr("Public Internet"), ServerItem::PublicType);
92 		siPublic->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
93 		addTopLevelItem(siPublic);
94 
95 		siPublic->setExpanded(false);
96 
97 		// The continent code is empty when the server's IP address is not in the GeoIP database
98 		qmContinentNames.insert(QLatin1String(""), tr("Unknown"));
99 
100 		qmContinentNames.insert(QLatin1String("af"), tr("Africa"));
101 		qmContinentNames.insert(QLatin1String("as"), tr("Asia"));
102 		qmContinentNames.insert(QLatin1String("na"), tr("North America"));
103 		qmContinentNames.insert(QLatin1String("sa"), tr("South America"));
104 		qmContinentNames.insert(QLatin1String("eu"), tr("Europe"));
105 		qmContinentNames.insert(QLatin1String("oc"), tr("Oceania"));
106 	} else {
107 		qWarning()<< "Public list disabled";
108 
109 		siPublic = NULL;
110 	}
111 }
112 
~ServerView()113 ServerView::~ServerView() {
114 	delete siFavorite;
115 	delete siLAN;
116 	delete siPublic;
117 }
118 
mimeData(const QList<QTreeWidgetItem * > mimeitems) const119 QMimeData *ServerView::mimeData(const QList<QTreeWidgetItem *> mimeitems) const {
120 	if (mimeitems.isEmpty())
121 		return NULL;
122 
123 	ServerItem *si = static_cast<ServerItem *>(mimeitems.first());
124 	return si->toMimeData();
125 }
126 
mimeTypes() const127 QStringList ServerView::mimeTypes() const {
128 	QStringList qsl;
129 	qsl << QStringList(QLatin1String("text/uri-list"));
130 	qsl << QStringList(QLatin1String("text/plain"));
131 	return qsl;
132 }
133 
supportedDropActions() const134 Qt::DropActions ServerView::supportedDropActions() const {
135 	return Qt::CopyAction | Qt::LinkAction;
136 }
137 
138 /* Extract and append (2), (3) etc to the end of a servers name if it is cloned. */
fixupName(ServerItem * si)139 void ServerView::fixupName(ServerItem *si) {
140 	QString name = si->qsName;
141 
142 	int tag = 1;
143 
144 	QRegExp tmatch(QLatin1String("(.+)\\((\\d+)\\)"));
145 	tmatch.setMinimal(true);
146 	if (tmatch.exactMatch(name)) {
147 		name = tmatch.capturedTexts().at(1).trimmed();
148 		tag = tmatch.capturedTexts().at(2).toInt();
149 	}
150 
151 	bool found;
152 	QString cmpname;
153 	do {
154 		found = false;
155 		if (tag > 1)
156 			cmpname = name + QString::fromLatin1(" (%1)").arg(tag);
157 		else
158 			cmpname = name;
159 
160 		foreach(ServerItem *f, siFavorite->qlChildren)
161 			if (f->qsName == cmpname)
162 				found = true;
163 
164 		++tag;
165 	} while (found);
166 
167 	si->qsName = cmpname;
168 }
169 
dropMimeData(QTreeWidgetItem *,int,const QMimeData * mime,Qt::DropAction)170 bool ServerView::dropMimeData(QTreeWidgetItem *, int, const QMimeData *mime, Qt::DropAction) {
171 	ServerItem *si = ServerItem::fromMimeData(mime);
172 	if (! si)
173 		return false;
174 
175 	fixupName(si);
176 
177 	qobject_cast<ConnectDialog *>(parent())->qlItems << si;
178 	siFavorite->addServerItem(si);
179 
180 	qobject_cast<ConnectDialog *>(parent())->startDns(si);
181 
182 	setCurrentItem(si);
183 
184 	return true;
185 }
186 
getParent(const QString & continentcode,const QString & countrycode,const QString & countryname,const QString & usercontinent,const QString & usercountry)187 ServerItem *ServerView::getParent(const QString &continentcode, const QString &countrycode, const QString &countryname, const QString &usercontinent, const QString &usercountry) {
188 	ServerItem *continent = qmContinent.value(continentcode);
189 	if (!continent) {
190 		QString name = qmContinentNames.value(continentcode);
191 		if (name.isEmpty())
192 			name = continentcode;
193 		continent = new ServerItem(name, ServerItem::PublicType, continentcode);
194 		qmContinent.insert(continentcode, continent);
195 		siPublic->addServerItem(continent);
196 
197 		if (!continentcode.isEmpty()) {
198 			if (continentcode == usercontinent) {
199 				continent->setExpanded(true);
200 				scrollToItem(continent, QAbstractItemView::PositionAtTop);
201 			}
202 		} else {
203 			continent->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
204 		}
205 	}
206 
207 	// If the continent code is empty, we put the server directly into the "Unknown" continent
208 	if (continentcode.isEmpty()) {
209 		return continent;
210 	}
211 
212 	ServerItem *country = qmCountry.value(countrycode);
213 	if (!country) {
214 		country = new ServerItem(countryname, ServerItem::PublicType, continentcode, countrycode);
215 		qmCountry.insert(countrycode, country);
216 		country->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
217 
218 		continent->addServerItem(country);
219 
220 		if (!countrycode.isEmpty() && countrycode == usercountry) {
221 			country->setExpanded(true);
222 			scrollToItem(country, QAbstractItemView::PositionAtTop);
223 		}
224 	}
225 	return country;
226 }
227 
228 
init()229 void ServerItem::init() {
230 	// Without this, columncount is wrong.
231 	setData(0, Qt::DisplayRole, QVariant());
232 	setData(1, Qt::DisplayRole, QVariant());
233 	setData(2, Qt::DisplayRole, QVariant());
234 	emitDataChanged();
235 }
236 
ServerItem(const FavoriteServer & fs)237 ServerItem::ServerItem(const FavoriteServer &fs) : QTreeWidgetItem(QTreeWidgetItem::UserType) {
238 	siParent = NULL;
239 	bParent = false;
240 
241 	itType = FavoriteType;
242 	qsName = fs.qsName;
243 	usPort = fs.usPort;
244 
245 	qsUsername = fs.qsUsername;
246 	qsPassword = fs.qsPassword;
247 
248 	qsUrl = fs.qsUrl;
249 
250 	bCA = false;
251 
252 	if (fs.qsHostname.startsWith(QLatin1Char('@'))) {
253 		qsBonjourHost = fs.qsHostname.mid(1);
254 		brRecord = BonjourRecord(qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
255 	} else {
256 		qsHostname = fs.qsHostname;
257 	}
258 
259 	init();
260 }
261 
ServerItem(const PublicInfo & pi)262 ServerItem::ServerItem(const PublicInfo &pi) : QTreeWidgetItem(QTreeWidgetItem::UserType) {
263 	siParent = NULL;
264 	bParent = false;
265 	itType = PublicType;
266 	qsName = pi.qsName;
267 	qsHostname = pi.qsIp;
268 	usPort = pi.usPort;
269 	qsUrl = pi.quUrl.toString();
270 	qsCountry = pi.qsCountry;
271 	qsCountryCode = pi.qsCountryCode;
272 	qsContinentCode = pi.qsContinentCode;
273 	bCA = pi.bCA;
274 
275 	init();
276 }
277 
ServerItem(const QString & name,const QString & host,unsigned short port,const QString & username,const QString & password)278 ServerItem::ServerItem(const QString &name, const QString &host, unsigned short port, const QString &username, const QString &password) : QTreeWidgetItem(QTreeWidgetItem::UserType) {
279 	siParent = NULL;
280 	bParent = false;
281 	itType = FavoriteType;
282 	qsName = name;
283 	usPort = port;
284 	qsUsername = username;
285 	qsPassword = password;
286 
287 	bCA = false;
288 
289 	if (host.startsWith(QLatin1Char('@'))) {
290 		qsBonjourHost = host.mid(1);
291 		brRecord = BonjourRecord(qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
292 	} else {
293 		qsHostname = host;
294 	}
295 
296 	init();
297 }
298 
ServerItem(const BonjourRecord & br)299 ServerItem::ServerItem(const BonjourRecord &br) : QTreeWidgetItem(QTreeWidgetItem::UserType) {
300 	siParent = NULL;
301 	bParent = false;
302 	itType = LANType;
303 	qsName = br.serviceName;
304 	qsBonjourHost = qsName;
305 	brRecord = br;
306 	usPort = 0;
307 	bCA = false;
308 
309 	init();
310 }
311 
ServerItem(const QString & name,ItemType itype,const QString & continent,const QString & country)312 ServerItem::ServerItem(const QString &name, ItemType itype, const QString &continent, const QString &country) {
313 	siParent = NULL;
314 	bParent = true;
315 	qsName = name;
316 	itType = itype;
317 	if (itType == PublicType) {
318 		qsCountryCode = country;
319 		qsContinentCode = continent;
320 	}
321 	setFlags(flags() & ~Qt::ItemIsDragEnabled);
322 	bCA = false;
323 
324 	init();
325 }
326 
ServerItem(const ServerItem * si)327 ServerItem::ServerItem(const ServerItem *si) {
328 	siParent = NULL;
329 	bParent = false;
330 	itType = FavoriteType;
331 
332 	qsName = si->qsName;
333 	qsHostname = si->qsHostname;
334 	usPort = si->usPort;
335 	qsUsername = si->qsUsername;
336 	qsPassword = si->qsPassword;
337 	qsCountry = si->qsCountry;
338 	qsCountryCode = si->qsCountryCode;
339 	qsContinentCode = si->qsContinentCode;
340 	qsUrl = si->qsUrl;
341 	qsBonjourHost = si->qsBonjourHost;
342 	brRecord = si->brRecord;
343 	qlAddresses = si->qlAddresses;
344 	bCA = si->bCA;
345 
346 	uiVersion = si->uiVersion;
347 	uiPing = si->uiPing;
348 	uiPingSort = si->uiPing;
349 	uiUsers = si->uiUsers;
350 	uiMaxUsers = si->uiMaxUsers;
351 	uiBandwidth = si->uiBandwidth;
352 	uiSent = si->uiSent;
353 	dPing = si->dPing;
354 	*asQuantile = * si->asQuantile;
355 }
356 
~ServerItem()357 ServerItem::~ServerItem() {
358 	if (siParent) {
359 		siParent->qlChildren.removeAll(this);
360 		if (siParent->bParent && siParent->qlChildren.isEmpty())
361 			siParent->setHidden(true);
362 	}
363 
364 	// This is just for cleanup when exiting the dialog, it won't stop pending DNS for the children.
365 	foreach(ServerItem *si, qlChildren)
366 		delete si;
367 }
368 
fromMimeData(const QMimeData * mime,bool default_name,QWidget * p,bool convertHttpUrls)369 ServerItem *ServerItem::fromMimeData(const QMimeData *mime, bool default_name, QWidget *p, bool convertHttpUrls) {
370 	if (mime->hasFormat(QLatin1String("OriginatedInMumble")))
371 		return NULL;
372 
373 	QUrl url;
374 	if (mime->hasUrls() && ! mime->urls().isEmpty())
375 		url = mime->urls().at(0);
376 	else if (mime->hasText())
377 		url = QUrl::fromEncoded(mime->text().toUtf8());
378 
379 	QString qsFile = url.toLocalFile();
380 	if (! qsFile.isEmpty()) {
381 		QFile f(qsFile);
382 		// Make sure we don't accidently read something big the user
383 		// happened to have in his clipboard. We only want to look
384 		// at small link files.
385 		if (f.open(QIODevice::ReadOnly) && f.size() < 10240) {
386 			QByteArray qba = f.readAll();
387 			f.close();
388 
389 			url = QUrl::fromEncoded(qba, QUrl::StrictMode);
390 			if (! url.isValid()) {
391 				// Windows internet shortcut files (.url) are an ini with an URL value
392 				QSettings qs(qsFile, QSettings::IniFormat);
393 				url = QUrl::fromEncoded(qs.value(QLatin1String("InternetShortcut/URL")).toByteArray(), QUrl::StrictMode);
394 			}
395 		}
396 	}
397 
398 	if (default_name) {
399 #if QT_VERSION >= 0x050000
400 		QUrlQuery query(url);
401 		if (! query.hasQueryItem(QLatin1String("title"))) {
402 			query.addQueryItem(QLatin1String("title"), url.host());
403 		}
404 #else
405 		if (! url.hasQueryItem(QLatin1String("title"))) {
406 			url.addQueryItem(QLatin1String("title"), url.host());
407 		}
408 #endif
409 	}
410 
411 	if (! url.isValid()) {
412 		return NULL;
413 	}
414 
415 	// An URL from text without a scheme will have the hostname text
416 	// in the QUrl scheme and no hostname. We do not want to use that.
417 	if (url.host().isEmpty()) {
418 		return NULL;
419 	}
420 
421 	// Some communication programs automatically create http links from domains.
422 	// When a user sends another user a domain to connect to, and http is added wrongly,
423 	// we do our best to remove it again.
424 	if (convertHttpUrls && (
425 	    url.scheme() == QLatin1String("http")
426 	    || url.scheme() == QLatin1String("https"))) {
427 		url.setScheme(QLatin1String("mumble"));
428 	}
429 
430 	return fromUrl(url, p);
431 }
432 
fromUrl(QUrl url,QWidget * p)433 ServerItem *ServerItem::fromUrl(QUrl url, QWidget *p) {
434 	if (! url.isValid() || (url.scheme() != QLatin1String("mumble"))) {
435 		return NULL;
436 	}
437 
438 #if QT_VERSION >= 0x050000
439 	QUrlQuery query(url);
440 #endif
441 
442 	if (url.userName().isEmpty()) {
443 		if (g.s.qsUsername.isEmpty()) {
444 			bool ok;
445 			QString defUserName = QInputDialog::getText(p, ConnectDialog::tr("Adding host %1").arg(url.host()), ConnectDialog::tr("Enter username"), QLineEdit::Normal, g.s.qsUsername, &ok).trimmed();
446 			if (! ok)
447 				return NULL;
448 			if (defUserName.isEmpty())
449 				return NULL;
450 			g.s.qsUsername = defUserName;
451 		}
452 		url.setUserName(g.s.qsUsername);
453 	}
454 
455 #if QT_VERSION >= 0x050000
456 	ServerItem *si = new ServerItem(query.queryItemValue(QLatin1String("title")), url.host(), static_cast<unsigned short>(url.port(DEFAULT_MUMBLE_PORT)), url.userName(), url.password());
457 
458 	if (query.hasQueryItem(QLatin1String("url")))
459 		si->qsUrl = query.queryItemValue(QLatin1String("url"));
460 #else
461 	ServerItem *si = new ServerItem(url.queryItemValue(QLatin1String("title")), url.host(), static_cast<unsigned short>(url.port(DEFAULT_MUMBLE_PORT)), url.userName(), url.password());
462 
463 	if (url.hasQueryItem(QLatin1String("url")))
464 		si->qsUrl = url.queryItemValue(QLatin1String("url"));
465 #endif
466 
467 	return si;
468 }
469 
data(int column,int role) const470 QVariant ServerItem::data(int column, int role) const {
471 	if (bParent) {
472 		if (column == 0) {
473 			switch (role) {
474 				case Qt::DisplayRole:
475 					return qsName;
476 				case Qt::DecorationRole:
477 					if (itType == FavoriteType)
478 						return loadIcon(QLatin1String("skin:emblems/emblem-favorite.svg"));
479 					else if (itType == LANType)
480 						return loadIcon(QLatin1String("skin:places/network-workgroup.svg"));
481 					else if (! qsCountryCode.isEmpty()) {
482 						QString flag = QString::fromLatin1(":/flags/%1.svg").arg(qsCountryCode);
483 						if (!QFileInfo(flag).exists()) {
484 							flag = QLatin1String("skin:categories/applications-internet.svg");
485 						}
486 						return loadIcon(flag);
487 					}
488 					else
489 						return loadIcon(QLatin1String("skin:categories/applications-internet.svg"));
490 			}
491 		}
492 	} else {
493 		if (role == Qt::DisplayRole) {
494 			switch (column) {
495 				case 0:
496 					return qsName;
497 				case 1:
498 					return (dPing > 0.0) ? QString::number(uiPing) : QVariant();
499 				case 2:
500 					return uiUsers ? QString::fromLatin1("%1/%2 ").arg(uiUsers).arg(uiMaxUsers) : QVariant();
501 			}
502 		} else if (role == Qt::ToolTipRole) {
503 			QStringList qsl;
504 			foreach(const ServerAddress &addr, qlAddresses) {
505 				qsl << Qt::escape(addr.host.toString() + QLatin1String(":") + QString::number(static_cast<unsigned long>(addr.port)));
506 			}
507 
508 			double ploss = 100.0;
509 
510 			if (uiSent > 0)
511 				ploss = (uiSent - qMin(uiRecv, uiSent)) * 100. / uiSent;
512 
513 			QString qs;
514 			qs +=
515 			    QLatin1String("<table>") +
516 			    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Servername"), Qt::escape(qsName)) +
517 			    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Hostname"), Qt::escape(qsHostname));
518 
519 			if (! qsBonjourHost.isEmpty())
520 				qs += QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Bonjour name"), Qt::escape(qsBonjourHost));
521 
522 			qs +=
523 			    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Port")).arg(usPort) +
524 			    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Addresses"), qsl.join(QLatin1String(", ")));
525 
526 			if (! qsUrl.isEmpty())
527 				qs += QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Website"), Qt::escape(qsUrl));
528 
529 			if (uiSent > 0) {
530 				qs += QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Packet loss"), QString::fromLatin1("%1% (%2/%3)").arg(ploss, 0, 'f', 1).arg(uiRecv).arg(uiSent));
531 				if (uiRecv > 0) {
532 					qs +=
533 					    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Ping (80%)"), ConnectDialog::tr("%1 ms").
534 					            arg(boost::accumulators::extended_p_square(* asQuantile)[1] / 1000., 0, 'f', 2)) +
535 					    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Ping (95%)"), ConnectDialog::tr("%1 ms").
536 					            arg(boost::accumulators::extended_p_square(* asQuantile)[2] / 1000., 0, 'f', 2)) +
537 					    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Bandwidth"), ConnectDialog::tr("%1 kbit/s").arg(uiBandwidth / 1000)) +
538 					    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Users"), QString::fromLatin1("%1/%2").arg(uiUsers).arg(uiMaxUsers)) +
539 					    QString::fromLatin1("<tr><th align=left>%1</th><td>%2</td></tr>").arg(ConnectDialog::tr("Version")).arg(MumbleVersion::toString(uiVersion));
540 				}
541 			}
542 			qs += QLatin1String("</table>");
543 			return qs;
544 		} else if (role == Qt::BackgroundRole) {
545 			if (bCA) {
546 				QColor qc(Qt::green);
547 				qc.setAlpha(32);
548 				return qc;
549 			}
550 		}
551 	}
552 	return QTreeWidgetItem::data(column, role);
553 }
554 
addServerItem(ServerItem * childitem)555 void ServerItem::addServerItem(ServerItem *childitem) {
556 	Q_ASSERT(childitem->siParent == NULL);
557 
558 	childitem->siParent = this;
559 	qlChildren.append(childitem);
560 	childitem->hideCheck();
561 
562 	if (bParent && (itType != PublicType) && isHidden())
563 		setHidden(false);
564 }
565 
566 // If all child items are hidden, there is no child indicator, regardless of policy, so we have to add/remove instead.
hideCheck()567 void ServerItem::hideCheck() {
568 	bool hide = false;
569 	bool ishidden = (parent() == NULL);
570 
571 	if (! bParent && (itType == PublicType)) {
572 		if (g.s.ssFilter == Settings::ShowReachable)
573 			hide = (dPing == 0.0);
574 		else if (g.s.ssFilter == Settings::ShowPopulated)
575 			hide = (uiUsers == 0);
576 	}
577 	if (hide != ishidden) {
578 		if (hide)
579 			siParent->removeChild(this);
580 		else
581 			siParent->addChild(this);
582 	}
583 }
584 
setDatas(double elapsed,quint32 users,quint32 maxusers)585 void ServerItem::setDatas(double elapsed, quint32 users, quint32 maxusers) {
586 	if (elapsed == 0.0) {
587 		emitDataChanged();
588 		return;
589 	}
590 
591 	(*asQuantile)(static_cast<double>(elapsed));
592 	dPing = boost::accumulators::extended_p_square(*asQuantile)[0];
593 	if (dPing == 0.0)
594 		dPing = elapsed;
595 
596 	quint32 ping = static_cast<quint32>(lround(dPing / 1000.));
597 	uiRecv = static_cast<quint32>(boost::accumulators::count(* asQuantile));
598 
599 	bool changed = (ping != uiPing) || (users != uiUsers) || (maxusers != uiMaxUsers);
600 
601 	uiUsers = users;
602 	uiMaxUsers = maxusers;
603 	uiPing = ping;
604 
605 	double grace = qMax(5000., 50. * uiPingSort);
606 	double diff = fabs(1000. * uiPingSort - dPing);
607 
608 	if ((uiPingSort == 0) || ((uiSent >= 10) && (diff >= grace)))
609 		uiPingSort = ping;
610 
611 	if (changed)
612 		emitDataChanged();
613 }
614 
toFavoriteServer() const615 FavoriteServer ServerItem::toFavoriteServer() const {
616 	FavoriteServer fs;
617 	fs.qsName = qsName;
618 	if (! qsBonjourHost.isEmpty())
619 		fs.qsHostname = QLatin1Char('@') + qsBonjourHost;
620 	else
621 		fs.qsHostname = qsHostname;
622 	fs.usPort = usPort;
623 	fs.qsUsername = qsUsername;
624 	fs.qsPassword = qsPassword;
625 	fs.qsUrl = qsUrl;
626 	return fs;
627 }
628 
629 
630 /**
631  * This function turns a ServerItem object into a QMimeData object holding a URL to the server.
632  */
toMimeData() const633 QMimeData *ServerItem::toMimeData() const {
634 	QMimeData *mime = ServerItem::toMimeData(qsName, qsHostname, usPort);
635 
636 	if (itType == FavoriteType)
637 		mime->setData(QLatin1String("OriginatedInMumble"), QByteArray());
638 
639 	return mime;
640 }
641 
642 /**
643  * This function creates a QMimeData object containing a URL to the server at host and port. name is passed in the
644  * query string as "title", which is used for adding a server to favorites. channel may be omitted, but if specified it
645  * should be in the format of "/path/to/channel".
646  */
toMimeData(const QString & name,const QString & host,unsigned short port,const QString & channel)647 QMimeData *ServerItem::toMimeData(const QString &name, const QString &host, unsigned short port, const QString &channel) {
648 	QUrl url;
649 	url.setScheme(QLatin1String("mumble"));
650 	url.setHost(host);
651 	if (port != DEFAULT_MUMBLE_PORT)
652 		url.setPort(port);
653 	url.setPath(channel);
654 
655 #if QT_VERSION >= 0x050000
656 	QUrlQuery query;
657 	query.addQueryItem(QLatin1String("title"), name);
658 	query.addQueryItem(QLatin1String("version"), QLatin1String("1.2.0"));
659 	url.setQuery(query);
660 #else
661 	url.addQueryItem(QLatin1String("title"), name);
662 	url.addQueryItem(QLatin1String("version"), QLatin1String("1.2.0"));
663 #endif
664 
665 	QString qs = QLatin1String(url.toEncoded());
666 
667 	QMimeData *mime = new QMimeData;
668 
669 #ifdef Q_OS_WIN
670 	QString contents = QString::fromLatin1("[InternetShortcut]\r\nURL=%1\r\n").arg(qs);
671 	QString urlname = QString::fromLatin1("%1.url").arg(name);
672 
673 	FILEGROUPDESCRIPTORA fgda;
674 	ZeroMemory(&fgda, sizeof(fgda));
675 	fgda.cItems = 1;
676 	fgda.fgd[0].dwFlags = FD_LINKUI | FD_FILESIZE;
677 	fgda.fgd[0].nFileSizeLow=contents.length();
678 	strcpy_s(fgda.fgd[0].cFileName, MAX_PATH, urlname.toLocal8Bit().constData());
679 	mime->setData(QLatin1String("FileGroupDescriptor"), QByteArray(reinterpret_cast<const char *>(&fgda), sizeof(fgda)));
680 
681 	FILEGROUPDESCRIPTORW fgdw;
682 	ZeroMemory(&fgdw, sizeof(fgdw));
683 	fgdw.cItems = 1;
684 	fgdw.fgd[0].dwFlags = FD_LINKUI | FD_FILESIZE;
685 	fgdw.fgd[0].nFileSizeLow=contents.length();
686 	wcscpy_s(fgdw.fgd[0].cFileName, MAX_PATH, urlname.toStdWString().c_str());
687 	mime->setData(QLatin1String("FileGroupDescriptorW"), QByteArray(reinterpret_cast<const char *>(&fgdw), sizeof(fgdw)));
688 
689 	mime->setData(QString::fromWCharArray(CFSTR_FILECONTENTS), contents.toLocal8Bit());
690 
691 	DWORD context[4];
692 	context[0] = 0;
693 	context[1] = 1;
694 	context[2] = 0;
695 	context[3] = 0;
696 	mime->setData(QLatin1String("DragContext"), QByteArray(reinterpret_cast<const char *>(&context[0]), sizeof(context)));
697 
698 	DWORD dropaction;
699 	dropaction = DROPEFFECT_LINK;
700 	mime->setData(QString::fromWCharArray(CFSTR_PREFERREDDROPEFFECT), QByteArray(reinterpret_cast<const char *>(&dropaction), sizeof(dropaction)));
701 #endif
702 	QList<QUrl> urls;
703 	urls << url;
704 	mime->setUrls(urls);
705 
706 	mime->setText(qs);
707 	mime->setHtml(QString::fromLatin1("<a href=\"%1\">%2</a>").arg(qs).arg(Qt::escape(name)));
708 
709 	return mime;
710 }
711 
operator <(const QTreeWidgetItem & o) const712 bool ServerItem::operator <(const QTreeWidgetItem &o) const {
713 	const ServerItem &other = static_cast<const ServerItem &>(o);
714 	const QTreeWidget *w = treeWidget();
715 
716 	const int column = w ? w->sortColumn() : 0;
717 
718 	if (itType != other.itType) {
719 		const bool inverse = w ? (w->header()->sortIndicatorOrder() == Qt::DescendingOrder) : false;
720 		bool less;
721 
722 		if (itType == FavoriteType)
723 			less = true;
724 		else if ((itType == LANType) && (other.itType == PublicType))
725 			less = true;
726 		else
727 			less = false;
728 		return less ^ inverse;
729 	}
730 
731 	if (bParent) {
732 		const bool inverse = w ? (w->header()->sortIndicatorOrder() == Qt::DescendingOrder) : false;
733 		return (qsName < other.qsName) ^ inverse;
734 	}
735 
736 	if (column == 0) {
737 		QString a = qsName.toLower();
738 		QString b = other.qsName.toLower();
739 
740 		QRegExp re(QLatin1String("[^0-9a-z]"));
741 		a.remove(re);
742 		b.remove(re);
743 		return a < b;
744 	} else if (column == 1) {
745 		quint32 a = uiPingSort ? uiPingSort : UINT_MAX;
746 		quint32 b = other.uiPingSort ? other.uiPingSort : UINT_MAX;
747 		return a < b;
748 	} else if (column == 2) {
749 		return uiUsers < other.uiUsers;
750 	}
751 	return false;
752 }
753 
loadIcon(const QString & name)754 QIcon ServerItem::loadIcon(const QString &name) {
755 	if (! qmIcons.contains(name))
756 		qmIcons.insert(name, QIcon(name));
757 	return qmIcons.value(name);
758 }
759 
ConnectDialogEdit(QWidget * p,const QString & name,const QString & host,const QString & user,unsigned short port,const QString & password)760 ConnectDialogEdit::ConnectDialogEdit(QWidget *p, const QString &name, const QString &host, const QString &user, unsigned short port, const QString &password) : QDialog(p) {
761 	setupUi(this);
762 	init();
763 
764 	bCustomLabel = ! name.simplified().isEmpty();
765 
766 	qleName->setText(name);
767 	qleServer->setText(host);
768 	qleUsername->setText(user);
769 	qlePort->setText(QString::number(port));
770 	qlePassword->setText(password);
771 
772 	validate();
773 }
774 
ConnectDialogEdit(QWidget * parent)775 ConnectDialogEdit::ConnectDialogEdit(QWidget *parent) : QDialog(parent) {
776 	setupUi(this);
777 	setWindowTitle(tr("Add Server"));
778 	init();
779 
780 	if (!updateFromClipboard()) {
781 		// If connected to a server assume the user wants to add it
782 		if (g.sh && g.sh->isRunning()) {
783 			QString host, name, user, pw;
784 			unsigned short port = DEFAULT_MUMBLE_PORT;
785 
786 			g.sh->getConnectionInfo(host, port, user, pw);
787 			Channel *c = Channel::get(0);
788 			if (c && c->qsName != QLatin1String("Root")) {
789 				name = c->qsName;
790 			}
791 
792 			showNotice(tr("You are currently connected to a server.\nDo you want to fill the dialog with the connection data of this server?\nHost: %1 Port: %2").arg(host).arg(port));
793 			m_si = new ServerItem(name, host, port, user, pw);
794 		}
795 	}
796 	qleUsername->setText(g.s.qsUsername);
797 }
798 
init()799 void ConnectDialogEdit::init() {
800 	m_si = NULL;
801 	usPort = 0;
802 	bOk = true;
803 	bCustomLabel = false;
804 
805 	qwInlineNotice->hide();
806 
807 	qlePort->setValidator(new QIntValidator(1, 65535, qlePort));
808 	qlePort->setText(QString::number(DEFAULT_MUMBLE_PORT));
809 	qlePassword->setEchoMode(QLineEdit::Password);
810 
811 	connect(qleName, SIGNAL(textChanged(const QString &)), this, SLOT(validate()));
812 	connect(qleServer, SIGNAL(textChanged(const QString &)), this, SLOT(validate()));
813 	connect(qlePort, SIGNAL(textChanged(const QString &)), this, SLOT(validate()));
814 	connect(qleUsername, SIGNAL(textChanged(const QString &)), this, SLOT(validate()));
815 	connect(qlePassword, SIGNAL(textChanged(const QString &)), this, SLOT(validate()));
816 
817 	validate();
818 }
819 
~ConnectDialogEdit()820 ConnectDialogEdit::~ConnectDialogEdit() {
821 	delete m_si;
822 }
823 
showNotice(const QString & text)824 void ConnectDialogEdit::showNotice(const QString &text) {
825 	QLabel *label = qwInlineNotice->findChild<QLabel *>(QLatin1String("qlPasteNotice"));
826 	Q_ASSERT(label);
827 	label->setText(text);
828 	qwInlineNotice->show();
829 	adjustSize();
830 }
831 
updateFromClipboard()832 bool ConnectDialogEdit::updateFromClipboard() {
833 	delete m_si;
834 	m_si = ServerItem::fromMimeData(QApplication::clipboard()->mimeData(), false, NULL, true);
835 	bool hasServerData = m_si != NULL;
836 	if (hasServerData) {
837 		showNotice(tr("You have an URL in your clipboard.\nDo you want to fill the dialog with this data?\nHost: %1 Port: %2").arg(m_si->qsHostname).arg(m_si->usPort));
838 		return true;
839 	} else {
840 		qwInlineNotice->hide();
841 		adjustSize();
842 		return false;
843 	}
844 }
845 
on_qbFill_clicked()846 void ConnectDialogEdit::on_qbFill_clicked() {
847 	Q_ASSERT(m_si);
848 
849 	qwInlineNotice->hide();
850 	adjustSize();
851 
852 	qleName->setText(m_si->qsName);
853 	qleServer->setText(m_si->qsHostname);
854 	qleUsername->setText(m_si->qsUsername);
855 	qlePort->setText(QString::number(m_si->usPort));
856 	qlePassword->setText(m_si->qsPassword);
857 
858 	delete m_si;
859 	m_si = NULL;
860 }
861 
on_qbDiscard_clicked()862 void ConnectDialogEdit::on_qbDiscard_clicked() {
863 	qwInlineNotice->hide();
864 	adjustSize();
865 }
866 
on_qleName_textEdited(const QString & name)867 void ConnectDialogEdit::on_qleName_textEdited(const QString& name) {
868 	if (bCustomLabel) {
869 		// If empty, then reset to automatic label.
870 		// NOTE(nik@jnstw.us): You may be tempted to set qleName to qleServer, but that results in the odd
871 		// UI behavior that clearing the field doesn't clear it; it'll immediately equal qleServer. Instead,
872 		// leave it empty and let it update the next time qleServer updates. Code in accept will default it
873 		// to qleServer if it isn't updated beforehand.
874 		if (name.simplified().isEmpty()) {
875 			bCustomLabel = false;
876 		}
877 	} else {
878 		// If manually edited, set to Custom
879 		bCustomLabel = true;
880 	}
881 }
882 
on_qleServer_textEdited(const QString & server)883 void ConnectDialogEdit::on_qleServer_textEdited(const QString& server) {
884 	// If using automatic label, update it
885 	if (!bCustomLabel) {
886 		qleName->setText(server);
887 	}
888 }
889 
validate()890 void ConnectDialogEdit::validate() {
891 	qsName = qleName->text().simplified();
892 	qsHostname = qleServer->text().simplified();
893 	usPort = qlePort->text().toUShort();
894 	qsUsername = qleUsername->text().simplified();
895 	qsPassword = qlePassword->text();
896 
897 	// For bonjour hosts disable the port field as it's auto-detected
898 	qlePort->setDisabled(!qsHostname.isEmpty() && qsHostname.startsWith(QLatin1Char('@')));
899 
900 	// For SuperUser show password edit
901 	if (qsUsername.toLower() == QLatin1String("superuser")) {
902 		qliPassword->setVisible(true);
903 		qlePassword->setVisible(true);
904 		qcbShowPassword->setVisible(true);
905 		adjustSize();
906 	} else if (qsPassword.isEmpty()) {
907 		qliPassword->setVisible(false);
908 		qlePassword->setVisible(false);
909 		qcbShowPassword->setVisible(false);
910 		adjustSize();
911 	}
912 
913 	bOk = ! qsHostname.isEmpty() && ! qsUsername.isEmpty() && usPort;
914 	qdbbButtonBox->button(QDialogButtonBox::Ok)->setEnabled(bOk);
915 }
916 
accept()917 void ConnectDialogEdit::accept() {
918 	validate();
919 	if (bOk) {
920 		QString server = qleServer->text().simplified();
921 
922 		// If the user accidentally added a schema or path part, drop it now.
923 		// We can't do so during editing as that is quite jarring.
924 		const int schemaPos = server.indexOf(QLatin1String("://"));
925 		if (schemaPos != -1) {
926 			server.remove(0, schemaPos + 3);
927 		}
928 
929 		const int pathPos = server.indexOf(QLatin1Char('/'));
930 		if (pathPos != -1) {
931 			server.resize(pathPos);
932 		}
933 
934 		qleServer->setText(server);
935 
936 		if (qleName->text().simplified().isEmpty() || !bCustomLabel) {
937 			qleName->setText(server);
938 		}
939 
940 		QDialog::accept();
941 	}
942 }
943 
on_qcbShowPassword_toggled(bool checked)944 void ConnectDialogEdit::on_qcbShowPassword_toggled(bool checked) {
945 	qlePassword->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password);
946 }
947 
ConnectDialog(QWidget * p,bool autoconnect)948 ConnectDialog::ConnectDialog(QWidget *p, bool autoconnect) : QDialog(p), bAutoConnect(autoconnect) {
949 	setupUi(this);
950 #ifdef Q_OS_MAC
951 	setWindowModality(Qt::WindowModal);
952 #endif
953 	bPublicInit = false;
954 
955 	siAutoConnect = NULL;
956 
957 	bAllowPing = g.s.ptProxyType == Settings::NoProxy;
958 	bAllowHostLookup = g.s.ptProxyType == Settings::NoProxy;
959 	bAllowBonjour = g.s.ptProxyType == Settings::NoProxy;
960 	bAllowFilters = g.s.ptProxyType == Settings::NoProxy;
961 
962 	if (tPublicServers.elapsed() >= 60 * 24 * 1000000ULL) {
963 		qlPublicServers.clear();
964 	}
965 
966 	qdbbButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
967 	qdbbButtonBox->button(QDialogButtonBox::Ok)->setText(tr("&Connect"));
968 
969 	QPushButton *qpbAdd = new QPushButton(tr("&Add New..."), this);
970 	qpbAdd->setDefault(false);
971 	qpbAdd->setAutoDefault(false);
972 	connect(qpbAdd, SIGNAL(clicked()), qaFavoriteAddNew, SIGNAL(triggered()));
973 	qdbbButtonBox->addButton(qpbAdd, QDialogButtonBox::ActionRole);
974 
975 
976 	qpbEdit = new QPushButton(tr("&Edit..."), this);
977 	qpbEdit->setEnabled(false);
978 	qpbEdit->setDefault(false);
979 	qpbEdit->setAutoDefault(false);
980 	connect(qpbEdit, SIGNAL(clicked()), qaFavoriteEdit, SIGNAL(triggered()));
981 	qdbbButtonBox->addButton(qpbEdit, QDialogButtonBox::ActionRole);
982 
983 	qpbAdd->setHidden(g.s.disableConnectDialogEditing);
984 	qpbEdit->setHidden(g.s.disableConnectDialogEditing);
985 
986 	qtwServers->setItemDelegate(new ServerViewDelegate());
987 
988 	// Hide ping and user count if we are not allowed to ping.
989 	if (!bAllowPing) {
990 		qtwServers->setColumnCount(1);
991 	}
992 
993 	qtwServers->sortItems(1, Qt::AscendingOrder);
994 
995 #if QT_VERSION >= 0x050000
996 	qtwServers->header()->setSectionResizeMode(0, QHeaderView::Stretch);
997 	if (qtwServers->columnCount() >= 2) {
998 		qtwServers->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
999 	}
1000 	if (qtwServers->columnCount() >= 3) {
1001 		qtwServers->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
1002 	}
1003 #else
1004 	qtwServers->header()->setResizeMode(0, QHeaderView::Stretch);
1005 	if (qtwServers->columnCount() >= 2) {
1006 		qtwServers->header()->setResizeMode(1, QHeaderView::ResizeToContents);
1007 	}
1008 	if (qtwServers->columnCount() >= 3) {
1009 		qtwServers->header()->setResizeMode(2, QHeaderView::ResizeToContents);
1010 	}
1011 #endif
1012 
1013 	connect(qtwServers->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(OnSortChanged(int, Qt::SortOrder)));
1014 
1015 	qaShowAll->setChecked(false);
1016 	qaShowReachable->setChecked(false);
1017 	qaShowPopulated->setChecked(false);
1018 
1019 	if (bAllowFilters) {
1020 		switch (g.s.ssFilter) {
1021 			case Settings::ShowPopulated:
1022 				qaShowPopulated->setChecked(true);
1023 				break;
1024 			case Settings::ShowReachable:
1025 				qaShowReachable->setChecked(true);
1026 				break;
1027 			default:
1028 				qaShowAll->setChecked(true);
1029 				break;
1030 		}
1031 	} else {
1032 		qaShowAll->setChecked(true);
1033 	}
1034 
1035 	qagFilters = new QActionGroup(this);
1036 	qagFilters->addAction(qaShowAll);
1037 	qagFilters->addAction(qaShowReachable);
1038 	qagFilters->addAction(qaShowPopulated);
1039 
1040 	connect(qagFilters, SIGNAL(triggered(QAction *)), this, SLOT(onFiltersTriggered(QAction *)));
1041 
1042 	qmPopup = new QMenu(this);
1043 	qmFilters = new QMenu(tr("&Filters"), this);
1044 	qmFilters->addAction(qaShowAll);
1045 	qmFilters->addAction(qaShowReachable);
1046 	qmFilters->addAction(qaShowPopulated);
1047 
1048 	if (!bAllowFilters) {
1049 		qmFilters->setEnabled(false);
1050 	}
1051 
1052 	QList<QTreeWidgetItem *> ql;
1053 	QList<FavoriteServer> favorites = g.db->getFavorites();
1054 
1055 	foreach(const FavoriteServer &fs, favorites) {
1056 		ServerItem *si = new ServerItem(fs);
1057 		qlItems << si;
1058 		startDns(si);
1059 		qtwServers->siFavorite->addServerItem(si);
1060 	}
1061 
1062 #ifdef USE_BONJOUR
1063 	// Make sure the we got the objects we need, then wire them up
1064 	if (bAllowBonjour && g.bc->bsbBrowser && g.bc->bsrResolver) {
1065 		connect(g.bc->bsbBrowser, SIGNAL(error(DNSServiceErrorType)),
1066 		        this, SLOT(onLanBrowseError(DNSServiceErrorType)));
1067 		connect(g.bc->bsbBrowser, SIGNAL(currentBonjourRecordsChanged(const QList<BonjourRecord> &)),
1068 		        this, SLOT(onUpdateLanList(const QList<BonjourRecord> &)));
1069 		connect(g.bc->bsrResolver, SIGNAL(error(BonjourRecord, DNSServiceErrorType)),
1070 		        this, SLOT(onLanResolveError(BonjourRecord, DNSServiceErrorType)));
1071 		connect(g.bc->bsrResolver, SIGNAL(bonjourRecordResolved(BonjourRecord, QString, int)),
1072 		        this, SLOT(onResolved(BonjourRecord, QString, int)));
1073 		onUpdateLanList(g.bc->bsbBrowser->currentRecords());
1074 	}
1075 #endif
1076 
1077 	qtPingTick = new QTimer(this);
1078 	connect(qtPingTick, SIGNAL(timeout()), this, SLOT(timeTick()));
1079 
1080 	qusSocket4 = new QUdpSocket(this);
1081 	qusSocket6 = new QUdpSocket(this);
1082 	bIPv4 = qusSocket4->bind(QHostAddress(QHostAddress::Any), 0);
1083 	bIPv6 = qusSocket6->bind(QHostAddress(QHostAddress::AnyIPv6), 0);
1084 	connect(qusSocket4, SIGNAL(readyRead()), this, SLOT(udpReply()));
1085 	connect(qusSocket6, SIGNAL(readyRead()), this, SLOT(udpReply()));
1086 
1087 	if (qtwServers->siFavorite->isHidden()
1088 	    && (!qtwServers->siLAN || qtwServers->siLAN->isHidden())
1089 	    && qtwServers->siPublic != NULL) {
1090 		qtwServers->siPublic->setExpanded(true);
1091 	}
1092 
1093 	iPingIndex = -1;
1094 	qtPingTick->start(50);
1095 
1096 	new QShortcut(QKeySequence(QKeySequence::Copy), this, SLOT(on_qaFavoriteCopy_triggered()));
1097 	new QShortcut(QKeySequence(QKeySequence::Paste), this, SLOT(on_qaFavoritePaste_triggered()));
1098 
1099 	qtwServers->setCurrentItem(NULL);
1100 	bLastFound = false;
1101 
1102 	qmPingCache = g.db->getPingCache();
1103 
1104 	if (! g.s.qbaConnectDialogGeometry.isEmpty())
1105 		restoreGeometry(g.s.qbaConnectDialogGeometry);
1106 	if (! g.s.qbaConnectDialogHeader.isEmpty())
1107 		qtwServers->header()->restoreState(g.s.qbaConnectDialogHeader);
1108 }
1109 
~ConnectDialog()1110 ConnectDialog::~ConnectDialog() {
1111 	ServerItem::qmIcons.clear();
1112 
1113 	QList<FavoriteServer> ql;
1114 	qmPingCache.clear();
1115 
1116 	foreach(ServerItem *si, qlItems) {
1117 		if (si->uiPing)
1118 			qmPingCache.insert(UnresolvedServerAddress(si->qsHostname, si->usPort), si->uiPing);
1119 
1120 		if (si->itType != ServerItem::FavoriteType)
1121 			continue;
1122 		ql << si->toFavoriteServer();
1123 	}
1124 	g.db->setFavorites(ql);
1125 	g.db->setPingCache(qmPingCache);
1126 
1127 	g.s.qbaConnectDialogHeader = qtwServers->header()->saveState();
1128 	g.s.qbaConnectDialogGeometry = saveGeometry();
1129 }
1130 
accept()1131 void ConnectDialog::accept() {
1132 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1133 	if (! si || (bAllowHostLookup && si->qlAddresses.isEmpty()) || si->qsHostname.isEmpty()) {
1134 		qWarning() << "Invalid server";
1135 		return;
1136 	}
1137 
1138 	qsPassword = si->qsPassword;
1139 	qsServer = si->qsHostname;
1140 	usPort = si->usPort;
1141 
1142 	if (si->qsUsername.isEmpty()) {
1143 		bool ok;
1144 		QString defUserName = QInputDialog::getText(this, tr("Connecting to %1").arg(si->qsName), tr("Enter username"), QLineEdit::Normal, g.s.qsUsername, &ok).trimmed();
1145 		if (! ok)
1146 			return;
1147 		g.s.qsUsername = si->qsUsername = defUserName;
1148 	}
1149 
1150 	qsUsername = si->qsUsername;
1151 
1152 	g.s.qsLastServer = si->qsName;
1153 
1154 	QDialog::accept();
1155 }
1156 
OnSortChanged(int logicalIndex,Qt::SortOrder)1157 void ConnectDialog::OnSortChanged(int logicalIndex, Qt::SortOrder) {
1158 	if (logicalIndex != 2) {
1159 		return;
1160 	}
1161 
1162 	foreach(ServerItem *si, qlItems) {
1163 		if (si->uiPing && (si->uiPing != si->uiPingSort)) {
1164 			si->uiPingSort = si->uiPing;
1165 			si->setDatas();
1166 		}
1167 	}
1168 }
1169 
on_qaFavoriteAdd_triggered()1170 void ConnectDialog::on_qaFavoriteAdd_triggered() {
1171 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1172 	if (! si || (si->itType == ServerItem::FavoriteType))
1173 		return;
1174 
1175 	si = new ServerItem(si);
1176 	qtwServers->fixupName(si);
1177 	qlItems << si;
1178 	qtwServers->siFavorite->addServerItem(si);
1179 	qtwServers->setCurrentItem(si);
1180 	startDns(si);
1181 }
1182 
on_qaFavoriteAddNew_triggered()1183 void ConnectDialog::on_qaFavoriteAddNew_triggered() {
1184 	ConnectDialogEdit *cde = new ConnectDialogEdit(this);
1185 
1186 	if (cde->exec() == QDialog::Accepted) {
1187 		ServerItem *si = new ServerItem(cde->qsName, cde->qsHostname, cde->usPort, cde->qsUsername, cde->qsPassword);
1188 		qlItems << si;
1189 		qtwServers->siFavorite->addServerItem(si);
1190 		qtwServers->setCurrentItem(si);
1191 		startDns(si);
1192 	}
1193 	delete cde;
1194 }
1195 
on_qaFavoriteEdit_triggered()1196 void ConnectDialog::on_qaFavoriteEdit_triggered() {
1197 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1198 	if (! si || (si->itType != ServerItem::FavoriteType))
1199 		return;
1200 
1201 	QString host;
1202 	if (! si->qsBonjourHost.isEmpty())
1203 		host = QLatin1Char('@') + si->qsBonjourHost;
1204 	else
1205 		host = si->qsHostname;
1206 
1207 	ConnectDialogEdit *cde = new ConnectDialogEdit(this, si->qsName, host, si->qsUsername, si->usPort, si->qsPassword);
1208 
1209 	if (cde->exec() == QDialog::Accepted) {
1210 
1211 		si->qsName = cde->qsName;
1212 		si->qsUsername = cde->qsUsername;
1213 		si->qsPassword = cde->qsPassword;
1214 		if ((cde->qsHostname != host) || (cde->usPort != si->usPort)) {
1215 			stopDns(si);
1216 
1217 			si->qlAddresses.clear();
1218 			si->reset();
1219 
1220 			si->usPort = cde->usPort;
1221 
1222 			if (cde->qsHostname.startsWith(QLatin1Char('@'))) {
1223 				si->qsHostname = QString();
1224 				si->qsBonjourHost = cde->qsHostname.mid(1);
1225 				si->brRecord = BonjourRecord(si->qsBonjourHost, QLatin1String("_mumble._tcp."), QLatin1String("local."));
1226 			} else {
1227 				si->qsHostname = cde->qsHostname;
1228 				si->qsBonjourHost = QString();
1229 				si->brRecord = BonjourRecord();
1230 			}
1231 			startDns(si);
1232 		}
1233 		si->setDatas();
1234 	}
1235 	delete cde;
1236 }
1237 
on_qaFavoriteRemove_triggered()1238 void ConnectDialog::on_qaFavoriteRemove_triggered() {
1239 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1240 	if (! si || (si->itType != ServerItem::FavoriteType))
1241 		return;
1242 
1243 	stopDns(si);
1244 	qlItems.removeAll(si);
1245 	delete si;
1246 }
1247 
on_qaFavoriteCopy_triggered()1248 void ConnectDialog::on_qaFavoriteCopy_triggered() {
1249 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1250 	if (! si)
1251 		return;
1252 
1253 	QApplication::clipboard()->setMimeData(si->toMimeData());
1254 }
1255 
on_qaFavoritePaste_triggered()1256 void ConnectDialog::on_qaFavoritePaste_triggered() {
1257 	ServerItem *si = ServerItem::fromMimeData(QApplication::clipboard()->mimeData());
1258 	if (! si)
1259 		return;
1260 
1261 	qlItems << si;
1262 	qtwServers->siFavorite->addServerItem(si);
1263 	qtwServers->setCurrentItem(si);
1264 	startDns(si);
1265 }
1266 
on_qaUrl_triggered()1267 void ConnectDialog::on_qaUrl_triggered() {
1268 	ServerItem *si = static_cast<ServerItem *>(qtwServers->currentItem());
1269 	if (! si || si->qsUrl.isEmpty())
1270 		return;
1271 
1272 	QDesktopServices::openUrl(QUrl(si->qsUrl));
1273 }
1274 
onFiltersTriggered(QAction * act)1275 void ConnectDialog::onFiltersTriggered(QAction *act) {
1276 	if (act == qaShowAll)
1277 		g.s.ssFilter = Settings::ShowAll;
1278 	else if (act == qaShowReachable)
1279 		g.s.ssFilter = Settings::ShowReachable;
1280 	else if (act == qaShowPopulated)
1281 		g.s.ssFilter = Settings::ShowPopulated;
1282 
1283 	foreach(ServerItem *si, qlItems)
1284 		si->hideCheck();
1285 }
1286 
on_qtwServers_customContextMenuRequested(const QPoint & mpos)1287 void ConnectDialog::on_qtwServers_customContextMenuRequested(const QPoint &mpos) {
1288 	ServerItem *si = static_cast<ServerItem *>(qtwServers->itemAt(mpos));
1289 	qmPopup->clear();
1290 
1291 	if (si != NULL && si->bParent) {
1292 		si = NULL;
1293 	}
1294 
1295 	if (si != NULL) {
1296 
1297 		if (!g.s.disableConnectDialogEditing) {
1298 			if (si->itType == ServerItem::FavoriteType) {
1299 				qmPopup->addAction(qaFavoriteEdit);
1300 				qmPopup->addAction(qaFavoriteRemove);
1301 			} else {
1302 				qmPopup->addAction(qaFavoriteAdd);
1303 			}
1304 		}
1305 
1306 		if (!si->qsUrl.isEmpty()) {
1307 			qmPopup->addAction(qaUrl);
1308 		}
1309 	}
1310 
1311 	if (! qmPopup->isEmpty()) {
1312 		qmPopup->addSeparator();
1313 	}
1314 
1315 	qmPopup->addMenu(qmFilters);
1316 
1317 	qmPopup->popup(qtwServers->viewport()->mapToGlobal(mpos), NULL);
1318 }
1319 
on_qtwServers_itemDoubleClicked(QTreeWidgetItem * item,int)1320 void ConnectDialog::on_qtwServers_itemDoubleClicked(QTreeWidgetItem *item, int) {
1321 	qtwServers->setCurrentItem(item);
1322 	accept();
1323 }
1324 
on_qtwServers_currentItemChanged(QTreeWidgetItem * item,QTreeWidgetItem *)1325 void ConnectDialog::on_qtwServers_currentItemChanged(QTreeWidgetItem *item, QTreeWidgetItem *) {
1326 	ServerItem *si = static_cast<ServerItem *>(item);
1327 
1328 	if (si->siParent == qtwServers->siFavorite) {
1329 		qpbEdit->setEnabled(true);
1330 	} else {
1331 		qpbEdit->setEnabled(false);
1332 	}
1333 
1334 	bool bOk = !si->qlAddresses.isEmpty();
1335 	if (!bAllowHostLookup) {
1336 		bOk = true;
1337 	}
1338 	qdbbButtonBox->button(QDialogButtonBox::Ok)->setEnabled(bOk);
1339 
1340 	bLastFound = true;
1341 }
1342 
on_qtwServers_itemExpanded(QTreeWidgetItem * item)1343 void ConnectDialog::on_qtwServers_itemExpanded(QTreeWidgetItem *item) {
1344 	if (qtwServers->siPublic != NULL && item == qtwServers->siPublic) {
1345 		initList();
1346 		fillList();
1347 	}
1348 
1349 	ServerItem *p = static_cast<ServerItem *>(item);
1350 
1351 	foreach(ServerItem *si, p->qlChildren)
1352 		startDns(si);
1353 }
1354 
initList()1355 void ConnectDialog::initList() {
1356 	if (bPublicInit || (qlPublicServers.count() > 0))
1357 		return;
1358 
1359 	bPublicInit = true;
1360 
1361 	QUrl url;
1362 	url.setPath(QLatin1String("/v1/list"));
1363 #if QT_VERSION >= 0x050000
1364 	QUrlQuery query;
1365 	query.addQueryItem(QLatin1String("version"), QLatin1String(MUMTEXT(MUMBLE_VERSION_STRING)));
1366 	url.setQuery(query);
1367 #else
1368 	url.addQueryItem(QLatin1String("version"), QLatin1String(MUMTEXT(MUMBLE_VERSION_STRING)));
1369 #endif
1370 
1371 	WebFetch::fetch(QLatin1String("publist"), url, this, SLOT(fetched(QByteArray,QUrl,QMap<QString,QString>)));
1372 }
1373 
1374 #ifdef USE_BONJOUR
onResolved(BonjourRecord record,QString host,int port)1375 void ConnectDialog::onResolved(BonjourRecord record, QString host, int port) {
1376 	qlBonjourActive.removeAll(record);
1377 	foreach(ServerItem *si, qlItems) {
1378 		if (si->brRecord == record) {
1379 			unsigned short usport = static_cast<unsigned short>(port);
1380 			if ((host != si->qsHostname) || (usport != si->usPort)) {
1381 				stopDns(si);
1382 				si->usPort = static_cast<unsigned short>(port);
1383 				si->qsHostname = host;
1384 				startDns(si);
1385 			}
1386 		}
1387 	}
1388 }
1389 
onUpdateLanList(const QList<BonjourRecord> & list)1390 void ConnectDialog::onUpdateLanList(const QList<BonjourRecord> &list) {
1391 	QSet<ServerItem *> items;
1392 	QSet<ServerItem *> old = qtwServers->siLAN->qlChildren.toSet();
1393 
1394 	foreach(const BonjourRecord &record, list) {
1395 		bool found = false;
1396 		foreach(ServerItem *si, old) {
1397 			if (si->brRecord == record) {
1398 				items.insert(si);
1399 				found = true;
1400 				break;
1401 			}
1402 		}
1403 		if (! found) {
1404 			ServerItem *si = new ServerItem(record);
1405 			qlItems << si;
1406 			g.bc->bsrResolver->resolveBonjourRecord(record);
1407 			startDns(si);
1408 			qtwServers->siLAN->addServerItem(si);
1409 		}
1410 	}
1411 	QSet<ServerItem *> remove = old.subtract(items);
1412 	foreach(ServerItem *si, remove) {
1413 		stopDns(si);
1414 		qlItems.removeAll(si);
1415 		delete si;
1416 	}
1417 }
1418 
onLanBrowseError(DNSServiceErrorType err)1419 void ConnectDialog::onLanBrowseError(DNSServiceErrorType err) {
1420 	qWarning()<<"Bonjour reported browser error "<< err;
1421 }
1422 
onLanResolveError(BonjourRecord br,DNSServiceErrorType err)1423 void ConnectDialog::onLanResolveError(BonjourRecord br, DNSServiceErrorType err) {
1424 	qlBonjourActive.removeAll(br);
1425 	qWarning()<<"Bonjour reported resolver error "<< err;
1426 }
1427 #endif
1428 
fillList()1429 void ConnectDialog::fillList() {
1430 	QList<QTreeWidgetItem *> ql;
1431 	QList<QTreeWidgetItem *> qlNew;
1432 
1433 	foreach(const PublicInfo &pi, qlPublicServers) {
1434 		bool found = false;
1435 		foreach(ServerItem *si, qlItems) {
1436 			if ((pi.qsIp == si->qsHostname) && (pi.usPort == si->usPort)) {
1437 				si->qsCountry = pi.qsCountry;
1438 				si->qsCountryCode = pi.qsCountryCode;
1439 				si->qsContinentCode = pi.qsContinentCode;
1440 				si->qsUrl = pi.quUrl.toString();
1441 				si->bCA = pi.bCA;
1442 				si->setDatas();
1443 
1444 				if (si->itType == ServerItem::PublicType)
1445 					found = true;
1446 			}
1447 		}
1448 		if (! found)
1449 			ql << new ServerItem(pi);
1450 	}
1451 
1452 	while (! ql.isEmpty()) {
1453 		ServerItem *si = static_cast<ServerItem *>(ql.takeAt(qrand() % ql.count()));
1454 		qlNew << si;
1455 		qlItems << si;
1456 	}
1457 
1458 	foreach(QTreeWidgetItem *qtwi, qlNew) {
1459 		ServerItem *si = static_cast<ServerItem *>(qtwi);
1460 		ServerItem *p = qtwServers->getParent(si->qsContinentCode, si->qsCountryCode, si->qsCountry, qsUserContinentCode, qsUserCountryCode);
1461 		p->addServerItem(si);
1462 
1463 		if (p->isExpanded() && p->parent()->isExpanded())
1464 			startDns(si);
1465 	}
1466 }
1467 
timeTick()1468 void ConnectDialog::timeTick() {
1469 	if (! bLastFound && ! g.s.qsLastServer.isEmpty()) {
1470 		QList<QTreeWidgetItem *> items = qtwServers->findItems(g.s.qsLastServer, Qt::MatchExactly | Qt::MatchRecursive);
1471 		if (!items.isEmpty()) {
1472 			bLastFound = true;
1473 			qtwServers->setCurrentItem(items.at(0));
1474 			if (g.s.bAutoConnect && bAutoConnect) {
1475 				siAutoConnect = static_cast<ServerItem *>(items.at(0));
1476 				if (! siAutoConnect->qlAddresses.isEmpty()) {
1477 					accept();
1478 					return;
1479 				} else if (!bAllowHostLookup) {
1480 					accept();
1481 					return;
1482 				}
1483 			}
1484 		}
1485 	}
1486 
1487 	if (bAllowHostLookup) {
1488 		// Start DNS Lookup of first unknown hostname
1489 		foreach(const UnresolvedServerAddress &unresolved, qlDNSLookup) {
1490 			if (qsDNSActive.contains(unresolved)) {
1491 				continue;
1492 			}
1493 
1494 			qlDNSLookup.removeAll(unresolved);
1495 			qlDNSLookup.append(unresolved);
1496 
1497 			qsDNSActive.insert(unresolved);
1498 			ServerResolver *sr = new ServerResolver();
1499 			QObject::connect(sr, SIGNAL(resolved()), this, SLOT(lookedUp()));
1500 			sr->resolve(unresolved.hostname, unresolved.port);
1501 			break;
1502 		}
1503 	}
1504 
1505 	ServerItem *current = static_cast<ServerItem *>(qtwServers->currentItem());
1506 	ServerItem *hover = static_cast<ServerItem *>(qtwServers->itemAt(qtwServers->viewport()->mapFromGlobal(QCursor::pos())));
1507 
1508 	ServerItem *si = NULL;
1509 
1510 	if (tCurrent.elapsed() >= 1000000ULL)
1511 		si = current;
1512 	if (! si && (tHover.elapsed() >= 1000000ULL))
1513 		si = hover;
1514 
1515 	if (si) {
1516 		QString hostname = si->qsHostname.toLower();
1517 		unsigned short port = si->usPort;
1518 		UnresolvedServerAddress unresolved(hostname, port);
1519 
1520 		if (si->qlAddresses.isEmpty()) {
1521 			if (! hostname.isEmpty()) {
1522 				qlDNSLookup.removeAll(unresolved);
1523 				qlDNSLookup.prepend(unresolved);
1524 			}
1525 			si = NULL;
1526 		}
1527 	}
1528 
1529 	if (!si) {
1530 		if (qlItems.isEmpty())
1531 			return;
1532 
1533 		bool expanded;
1534 
1535 		do {
1536 			++iPingIndex;
1537 			if (iPingIndex >= qlItems.count()) {
1538 				if (tRestart.isElapsed(1000000ULL))
1539 					iPingIndex = 0;
1540 				else
1541 					return;
1542 			}
1543 			si = qlItems.at(iPingIndex);
1544 
1545 			ServerItem *p = si->siParent;
1546 			expanded = true;
1547 			while (p && expanded) {
1548 				expanded = expanded && p->isExpanded();
1549 				p = p->siParent;
1550 			}
1551 		} while (si->qlAddresses.isEmpty() || ! expanded);
1552 	}
1553 
1554 	if (si == current)
1555 		tCurrent.restart();
1556 	if (si == hover)
1557 		tHover.restart();
1558 
1559 	foreach(const ServerAddress &addr, si->qlAddresses) {
1560 		sendPing(addr.host.toAddress(), addr.port);
1561 	}
1562 }
1563 
1564 
startDns(ServerItem * si)1565 void ConnectDialog::startDns(ServerItem *si) {
1566 	if (!bAllowHostLookup) {
1567 		return;
1568 	}
1569 
1570 	QString hostname = si->qsHostname.toLower();
1571 	unsigned short port = si->usPort;
1572 	UnresolvedServerAddress unresolved(hostname, port);
1573 
1574 	if (si->qlAddresses.isEmpty()) {
1575 		// Determine if qsHostname is an IP address
1576 		// or a hostname. If it is an IP address, we
1577 		// can treat it as resolved as-is.
1578 		QHostAddress qha(si->qsHostname);
1579 		bool hostnameIsIPAddress = !qha.isNull();
1580 		if (hostnameIsIPAddress) {
1581 			si->qlAddresses.append(ServerAddress(HostAddress(qha), port));
1582 		} else {
1583 			si->qlAddresses = qhDNSCache.value(unresolved);
1584 		}
1585 	}
1586 
1587 	if (qtwServers->currentItem() == si)
1588 		qdbbButtonBox->button(QDialogButtonBox::Ok)->setEnabled(! si->qlAddresses.isEmpty());
1589 
1590 	if (! si->qlAddresses.isEmpty()) {
1591 		foreach(const ServerAddress &addr, si->qlAddresses) {
1592 			qhPings[addr].insert(si);
1593 		}
1594 		return;
1595 	}
1596 
1597 #ifdef USE_BONJOUR
1598 	if (bAllowBonjour && si->qsHostname.isEmpty() && ! si->brRecord.serviceName.isEmpty()) {
1599 		if (! qlBonjourActive.contains(si->brRecord)) {
1600 			g.bc->bsrResolver->resolveBonjourRecord(si->brRecord);
1601 			qlBonjourActive.append(si->brRecord);
1602 		}
1603 		return;
1604 	}
1605 #endif
1606 
1607 	if (! qhDNSWait.contains(unresolved)) {
1608 		if (si->itType == ServerItem::PublicType)
1609 			qlDNSLookup.append(unresolved);
1610 		else
1611 			qlDNSLookup.prepend(unresolved);
1612 	}
1613 	qhDNSWait[unresolved].insert(si);
1614 }
1615 
stopDns(ServerItem * si)1616 void ConnectDialog::stopDns(ServerItem *si) {
1617 	if (!bAllowHostLookup) {
1618 		return;
1619 	}
1620 
1621 	foreach(const ServerAddress &addr, si->qlAddresses) {
1622 		if (qhPings.contains(addr)) {
1623 			qhPings[addr].remove(si);
1624 			if (qhPings[addr].isEmpty()) {
1625 				qhPings.remove(addr);
1626 				qhPingRand.remove(addr);
1627 			}
1628 		}
1629 	}
1630 
1631 	QString hostname = si->qsHostname.toLower();
1632 	unsigned short port = si->usPort;
1633 	UnresolvedServerAddress unresolved(hostname, port);
1634 
1635 	if (qhDNSWait.contains(unresolved)) {
1636 		qhDNSWait[unresolved].remove(si);
1637 		if (qhDNSWait[unresolved].isEmpty()) {
1638 			qhDNSWait.remove(unresolved);
1639 			qlDNSLookup.removeAll(unresolved);
1640 		}
1641 	}
1642 }
1643 
lookedUp()1644 void ConnectDialog::lookedUp() {
1645 	ServerResolver *sr = qobject_cast<ServerResolver *>(QObject::sender());
1646 	sr->deleteLater();
1647 
1648 	QString hostname = sr->hostname().toLower();
1649 	unsigned short port = sr->port();
1650 	UnresolvedServerAddress unresolved(hostname, port);
1651 
1652 	qsDNSActive.remove(unresolved);
1653 
1654 	// An error occurred, or no records were found.
1655 	if (sr->records().size() == 0) {
1656 		return;
1657 	}
1658 
1659 	QSet<ServerAddress> qs;
1660 	foreach (ServerResolverRecord record, sr->records()) {
1661 		foreach(const HostAddress &ha, record.addresses()) {
1662 			qs.insert(ServerAddress(ha, record.port()));
1663 		}
1664 	}
1665 
1666 	QSet<ServerItem *> waiting = qhDNSWait[unresolved];
1667 	foreach(ServerItem *si, waiting) {
1668 		foreach (const ServerAddress &addr, qs) {
1669 			qhPings[addr].insert(si);
1670 		}
1671 
1672 		si->qlAddresses = qs.toList();
1673 	}
1674 
1675 	qlDNSLookup.removeAll(unresolved);
1676 	qhDNSCache.insert(unresolved, qs.toList());
1677 	qhDNSWait.remove(unresolved);
1678 
1679 	foreach(ServerItem *si, waiting) {
1680 		if (si == qtwServers->currentItem()) {
1681 			on_qtwServers_currentItemChanged(si, si);
1682 			if (si == siAutoConnect)
1683 				accept();
1684 		}
1685 	}
1686 
1687 	if (bAllowPing) {
1688 		foreach(const ServerAddress &addr, qs) {
1689 			sendPing(addr.host.toAddress(), addr.port);
1690 		}
1691 	}
1692 }
1693 
sendPing(const QHostAddress & host,unsigned short port)1694 void ConnectDialog::sendPing(const QHostAddress &host, unsigned short port) {
1695 	char blob[16];
1696 
1697 	ServerAddress addr(HostAddress(host), port);
1698 
1699 	quint64 uiRand;
1700 	if (qhPingRand.contains(addr)) {
1701 		uiRand = qhPingRand.value(addr);
1702 	} else {
1703 		uiRand = (static_cast<quint64>(qrand()) << 32) | static_cast<quint64>(qrand());
1704 		qhPingRand.insert(addr, uiRand);
1705 	}
1706 
1707 	memset(blob, 0, sizeof(blob));
1708 	* reinterpret_cast<quint64 *>(blob+8) = tPing.elapsed() ^ uiRand;
1709 
1710 	if (bIPv4 && host.protocol() == QAbstractSocket::IPv4Protocol)
1711 		qusSocket4->writeDatagram(blob+4, 12, host, port);
1712 	else if (bIPv6 && host.protocol() == QAbstractSocket::IPv6Protocol)
1713 		qusSocket6->writeDatagram(blob+4, 12, host, port);
1714 	else
1715 		return;
1716 
1717 	const QSet<ServerItem *> &qs = qhPings.value(addr);
1718 
1719 	foreach(ServerItem *si, qs)
1720 		++ si->uiSent;
1721 }
1722 
udpReply()1723 void ConnectDialog::udpReply() {
1724 	QUdpSocket *sock = qobject_cast<QUdpSocket *>(sender());
1725 
1726 	while (sock->hasPendingDatagrams()) {
1727 		char blob[64];
1728 
1729 		QHostAddress host;
1730 		unsigned short port;
1731 
1732 		qint64 len = sock->readDatagram(blob+4, 24, &host, &port);
1733 		if (len == 24) {
1734 			if (host.scopeId() == QLatin1String("0"))
1735 				host.setScopeId(QLatin1String(""));
1736 
1737 			ServerAddress address(HostAddress(host), port);
1738 
1739 			if (qhPings.contains(address)) {
1740 				quint32 *ping = reinterpret_cast<quint32 *>(blob+4);
1741 				quint64 *ts = reinterpret_cast<quint64 *>(blob+8);
1742 
1743 				quint64 elapsed = tPing.elapsed() - (*ts ^ qhPingRand.value(address));
1744 
1745 				foreach(ServerItem *si, qhPings.value(address)) {
1746 					si->uiVersion = qFromBigEndian(ping[0]);
1747 					quint32 users = qFromBigEndian(ping[3]);
1748 					quint32 maxusers = qFromBigEndian(ping[4]);
1749 					si->uiBandwidth = qFromBigEndian(ping[5]);
1750 
1751 					if (! si->uiPingSort)
1752 						si->uiPingSort = qmPingCache.value(UnresolvedServerAddress(si->qsHostname, si->usPort));
1753 
1754 					si->setDatas(static_cast<double>(elapsed), users, maxusers);
1755 					si->hideCheck();
1756 				}
1757 			}
1758 		}
1759 	}
1760 }
1761 
fetched(QByteArray xmlData,QUrl,QMap<QString,QString> headers)1762 void ConnectDialog::fetched(QByteArray xmlData, QUrl, QMap<QString, QString> headers) {
1763 	if (xmlData.isNull()) {
1764 		QMessageBox::warning(this, QLatin1String("Mumble"), tr("Failed to fetch server list"), QMessageBox::Ok);
1765 		return;
1766 	}
1767 
1768 	QDomDocument doc;
1769 	doc.setContent(xmlData);
1770 
1771 	qlPublicServers.clear();
1772 	qsUserCountry = headers.value(QLatin1String("Geo-Country"));
1773 	qsUserCountryCode = headers.value(QLatin1String("Geo-Country-Code")).toLower();
1774 	qsUserContinentCode = headers.value(QLatin1String("Geo-Continent-Code")).toLower();
1775 
1776 	QDomElement root=doc.documentElement();
1777 	QDomNode n = root.firstChild();
1778 	while (!n.isNull()) {
1779 		QDomElement e = n.toElement();
1780 		if (!e.isNull()) {
1781 			if (e.tagName() == QLatin1String("server")) {
1782 				PublicInfo pi;
1783 				pi.qsName = e.attribute(QLatin1String("name"));
1784 				pi.quUrl = e.attribute(QLatin1String("url"));
1785 				pi.qsIp = e.attribute(QLatin1String("ip"));
1786 				pi.usPort = e.attribute(QLatin1String("port")).toUShort();
1787 				pi.qsCountry = e.attribute(QLatin1String("country"), tr("Unknown"));
1788 				pi.qsCountryCode = e.attribute(QLatin1String("country_code")).toLower();
1789 				pi.qsContinentCode = e.attribute(QLatin1String("continent_code")).toLower();
1790 				pi.bCA = e.attribute(QLatin1String("ca")).toInt() ? true : false;
1791 
1792 				qlPublicServers << pi;
1793 			}
1794 		}
1795 		n = n.nextSibling();
1796 	}
1797 
1798 	tPublicServers.restart();
1799 
1800 	fillList();
1801 }
1802