From 2ff0ed50be10da0f96d530205d032aedffb57642 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Thu, 1 Aug 2024 16:35:55 +0200
Subject: [PATCH] Encode/Decode tonsite:// punycode correctly.

---
 Telegram/Resources/langs/lang.strings     |  1 +
 Telegram/SourceFiles/iv/iv_controller.cpp | 78 ++++++++++++++---------
 Telegram/SourceFiles/iv/iv_controller.h   |  1 +
 Telegram/SourceFiles/iv/iv_instance.cpp   |  6 +-
 Telegram/cmake/td_iv.cmake                |  1 +
 5 files changed, 57 insertions(+), 30 deletions(-)

diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 64bbb68d8..3924a85f6 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -5318,6 +5318,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_iv_join_channel" = "Join";
 "lng_iv_window_title" = "Instant View";
 "lng_iv_wrong_layout" = "Wrong layout?";
+"lng_iv_not_supported" = "This link appears to be invalid.";
 
 "lng_limit_download_title" = "Download speed limited";
 "lng_limit_download_subscribe" = "Subscribe to {link} and increase download speed {increase}.";
diff --git a/Telegram/SourceFiles/iv/iv_controller.cpp b/Telegram/SourceFiles/iv/iv_controller.cpp
index 11df08527..759ef0db3 100644
--- a/Telegram/SourceFiles/iv/iv_controller.cpp
+++ b/Telegram/SourceFiles/iv/iv_controller.cpp
@@ -44,6 +44,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include <QtGui/QWindow>
 #include <charconv>
 
+#include <ada.h>
+
 namespace Iv {
 namespace {
 
@@ -137,50 +139,62 @@ namespace {
 
 [[nodiscard]] QString TonsiteToHttps(QString value) {
 	const auto ChangeHost = [](QString tonsite) {
+		const auto fake = "http://" + tonsite.toStdString();
+		const auto parsed = ada::parse<ada::url>(fake);
+		if (!parsed) {
+			return QString();
+		}
+		tonsite = QString::fromStdString(parsed->get_hostname());
 		tonsite = tonsite.replace('-', "-h");
 		tonsite = tonsite.replace('.', "-d");
 		return tonsite + ".magic.org";
 	};
-	auto parsed = QUrl(value);
-	if (parsed.isValid()) {
-		parsed.setScheme("https");
-		parsed.setHost(ChangeHost(parsed.host()));
-		if (parsed.path().isEmpty()) {
-			parsed.setPath(u"/"_q);
-		}
-		return parsed.toString();
+	const auto prefix = u"tonsite://"_q;
+	if (!value.toLower().startsWith(prefix)) {
+		return QString();
 	}
-	const auto part = value.mid(u"tonsite://"_q.size());
+	const auto part = value.mid(prefix.size());
 	const auto split = part.indexOf('/');
-	return "https://"
-		+ ChangeHost((split < 0) ? part : part.left(split))
-		+ ((split < 0) ? u"/"_q : part.mid(split));
+	const auto host = ChangeHost((split < 0) ? part : part.left(split));
+	if (host.isEmpty()) {
+		return QString();
+	}
+	return "https://" + host + ((split < 0) ? u"/"_q : part.mid(split));
 }
 
 [[nodiscard]] QString HttpsToTonsite(QString value) {
 	const auto ChangeHost = [](QString https) {
-		https.replace(".magic.org", QString());
+		const auto dot = https.indexOf('.');
+		if (dot < 0 || https.mid(dot).toLower() != u".magic.org"_q) {
+			return QString();
+		}
+		https = https.mid(0, dot);
 		https = https.replace("-d", ".");
 		https = https.replace("-h", "-");
-		return https;
+		auto parts = https.split('.');
+		for (auto &part : parts) {
+			if (part.startsWith(u"xn--"_q)) {
+				const auto utf8 = part.mid(4).toStdString();
+				auto out = std::u32string();
+				if (ada::idna::punycode_to_utf32(utf8, out)) {
+					part = QString::fromUcs4(out.data(), out.size());
+				}
+			}
+		}
+		return parts.join('.');
 	};
-	auto parsed = QUrl(value);
-	if (parsed.isValid()) {
-		const auto host = ChangeHost(parsed.host());
-		const auto emptyPath = parsed.path().isEmpty();
-		parsed.setScheme("tonsite");
-		parsed.setHost(host);
-		if (emptyPath) {
-			parsed.setPath(u"/"_q);
-		}
-		if (parsed.isValid()) {
-			return parsed.toString();
-		}
+	const auto prefix = u"https://"_q;
+	if (!value.toLower().startsWith(prefix)) {
+		return value;
 	}
-	const auto part = value.mid(u"https://"_q.size());
+	const auto part = value.mid(prefix.size());
 	const auto split = part.indexOf('/');
+	const auto host = ChangeHost((split < 0) ? part : part.left(split));
+	if (host.isEmpty()) {
+		return value;
+	}
 	return "tonsite://"
-		+ ChangeHost((split < 0) ? part : part.left(split))
+		+ host
 		+ ((split < 0) ? u"/"_q : part.mid(split));
 }
 
@@ -342,10 +356,16 @@ void Controller::update(Prepared page) {
 	}
 }
 
+bool Controller::IsGoodTonSiteUrl(const QString &uri) {
+	return !TonsiteToHttps(uri).isEmpty();
+}
+
 void Controller::showTonSite(
 		const Webview::StorageId &storageId,
 		QString uri) {
 	const auto url = TonsiteToHttps(uri);
+	Assert(!url.isEmpty());
+
 	if (!_webview) {
 		createWebview(storageId);
 	}
@@ -360,7 +380,7 @@ void Controller::showTonSite(
 	}) | rpl::map([=](QString value) {
 		return HttpsToTonsite(value);
 	});
-	_windowTitleText = _subtitleText;
+	_windowTitleText = _subtitleText.value();
 	_menuToggle->hide();
 }
 
diff --git a/Telegram/SourceFiles/iv/iv_controller.h b/Telegram/SourceFiles/iv/iv_controller.h
index 30e975d02..9b5af4e6b 100644
--- a/Telegram/SourceFiles/iv/iv_controller.h
+++ b/Telegram/SourceFiles/iv/iv_controller.h
@@ -76,6 +76,7 @@ public:
 		base::flat_map<QByteArray, rpl::producer<bool>> inChannelValues);
 	void update(Prepared page);
 
+	[[nodiscard]] static bool IsGoodTonSiteUrl(const QString &uri);
 	void showTonSite(const Webview::StorageId &storageId, QString uri);
 
 	[[nodiscard]] bool active() const;
diff --git a/Telegram/SourceFiles/iv/iv_instance.cpp b/Telegram/SourceFiles/iv/iv_instance.cpp
index 3b327e5a6..8078828d6 100644
--- a/Telegram/SourceFiles/iv/iv_instance.cpp
+++ b/Telegram/SourceFiles/iv/iv_instance.cpp
@@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "ui/boxes/confirm_box.h"
 #include "ui/layers/layer_widget.h"
 #include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
 #include "ui/basic_click_handlers.h"
 #include "webview/webview_data_stream_memory.h"
 #include "webview/webview_interface.h"
@@ -1074,7 +1075,10 @@ void Instance::openWithIvPreferred(
 void Instance::showTonSite(
 		const QString &uri,
 		QVariant context) {
-	if (Platform::IsMac()) {
+	if (!Controller::IsGoodTonSiteUrl(uri)) {
+		Ui::Toast::Show(tr::lng_iv_not_supported(tr::now));
+		return;
+	} else if (Platform::IsMac()) {
 		// Otherwise IV is not visible under the media viewer.
 		Core::App().hideMediaView();
 	}
diff --git a/Telegram/cmake/td_iv.cmake b/Telegram/cmake/td_iv.cmake
index 602abf41c..1d4edf9f5 100644
--- a/Telegram/cmake/td_iv.cmake
+++ b/Telegram/cmake/td_iv.cmake
@@ -38,6 +38,7 @@ PUBLIC
     tdesktop::td_scheme
 PRIVATE
     desktop-app::lib_webview
+    desktop-app::external_ada
     tdesktop::td_lang
     tdesktop::td_ui
 )