1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/LinkManager.h"
8 
9 #include "td/telegram/AccessRights.h"
10 #include "td/telegram/ChannelId.h"
11 #include "td/telegram/ConfigManager.h"
12 #include "td/telegram/ConfigShared.h"
13 #include "td/telegram/ContactsManager.h"
14 #include "td/telegram/DialogId.h"
15 #include "td/telegram/Global.h"
16 #include "td/telegram/MessageEntity.h"
17 #include "td/telegram/MessageId.h"
18 #include "td/telegram/MessagesManager.h"
19 #include "td/telegram/misc.h"
20 #include "td/telegram/ServerMessageId.h"
21 #include "td/telegram/Td.h"
22 #include "td/telegram/TdDb.h"
23 #include "td/telegram/telegram_api.h"
24 #include "td/telegram/UserId.h"
25 
26 #include "td/mtproto/ProxySecret.h"
27 
28 #include "td/utils/algorithm.h"
29 #include "td/utils/base64.h"
30 #include "td/utils/buffer.h"
31 #include "td/utils/HttpUrl.h"
32 #include "td/utils/logging.h"
33 #include "td/utils/misc.h"
34 #include "td/utils/SliceBuilder.h"
35 #include "td/utils/StringBuilder.h"
36 #include "td/utils/Time.h"
37 
38 #include <tuple>
39 
40 namespace td {
41 
is_valid_start_parameter(Slice start_parameter)42 static bool is_valid_start_parameter(Slice start_parameter) {
43   return start_parameter.size() <= 64 && is_base64url_characters(start_parameter);
44 }
45 
is_valid_username(Slice username)46 static bool is_valid_username(Slice username) {
47   if (username.empty() || username.size() > 32) {
48     return false;
49   }
50   if (!is_alpha(username[0])) {
51     return false;
52   }
53   for (auto c : username) {
54     if (!is_alpha(c) && !is_digit(c) && c != '_') {
55       return false;
56     }
57   }
58   if (username.back() == '_') {
59     return false;
60   }
61   for (size_t i = 1; i < username.size(); i++) {
62     if (username[i - 1] == '_' && username[i] == '_') {
63       return false;
64     }
65   }
66 
67   return true;
68 }
69 
get_url_query_hash(bool is_tg,const HttpUrlQuery & url_query)70 static string get_url_query_hash(bool is_tg, const HttpUrlQuery &url_query) {
71   const auto &path = url_query.path_;
72   if (is_tg) {
73     if (path.size() == 1 && path[0] == "join" && !url_query.get_arg("invite").empty()) {
74       // join?invite=abcdef
75       return url_query.get_arg("invite").str();
76     }
77   } else {
78     if (path.size() >= 2 && path[0] == "joinchat" && !path[1].empty()) {
79       // /joinchat/<link>
80       return path[1];
81     }
82     if (!path.empty() && path[0].size() >= 2 && (path[0][0] == ' ' || path[0][0] == '+')) {
83       // /+<link>
84       return path[0].substr(1);
85     }
86   }
87   return string();
88 }
89 
90 class LinkManager::InternalLinkActiveSessions final : public InternalLink {
get_internal_link_type_object() const91   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
92     return td_api::make_object<td_api::internalLinkTypeActiveSessions>();
93   }
94 };
95 
96 class LinkManager::InternalLinkAuthenticationCode final : public InternalLink {
97   string code_;
98 
get_internal_link_type_object() const99   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
100     return td_api::make_object<td_api::internalLinkTypeAuthenticationCode>(code_);
101   }
102 
103  public:
InternalLinkAuthenticationCode(string code)104   explicit InternalLinkAuthenticationCode(string code) : code_(std::move(code)) {
105   }
106 };
107 
108 class LinkManager::InternalLinkBackground final : public InternalLink {
109   string background_name_;
110 
get_internal_link_type_object() const111   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
112     return td_api::make_object<td_api::internalLinkTypeBackground>(background_name_);
113   }
114 
115  public:
InternalLinkBackground(string background_name)116   explicit InternalLinkBackground(string background_name) : background_name_(std::move(background_name)) {
117   }
118 };
119 
120 class LinkManager::InternalLinkBotStart final : public InternalLink {
121   string bot_username_;
122   string start_parameter_;
123 
get_internal_link_type_object() const124   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
125     return td_api::make_object<td_api::internalLinkTypeBotStart>(bot_username_, start_parameter_);
126   }
127 
128  public:
InternalLinkBotStart(string bot_username,string start_parameter)129   InternalLinkBotStart(string bot_username, string start_parameter)
130       : bot_username_(std::move(bot_username)), start_parameter_(std::move(start_parameter)) {
131   }
132 };
133 
134 class LinkManager::InternalLinkBotStartInGroup final : public InternalLink {
135   string bot_username_;
136   string start_parameter_;
137 
get_internal_link_type_object() const138   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
139     return td_api::make_object<td_api::internalLinkTypeBotStartInGroup>(bot_username_, start_parameter_);
140   }
141 
142  public:
InternalLinkBotStartInGroup(string bot_username,string start_parameter)143   InternalLinkBotStartInGroup(string bot_username, string start_parameter)
144       : bot_username_(std::move(bot_username)), start_parameter_(std::move(start_parameter)) {
145   }
146 };
147 
148 class LinkManager::InternalLinkChangePhoneNumber final : public InternalLink {
get_internal_link_type_object() const149   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
150     return td_api::make_object<td_api::internalLinkTypeChangePhoneNumber>();
151   }
152 };
153 
154 class LinkManager::InternalLinkConfirmPhone final : public InternalLink {
155   string hash_;
156   string phone_number_;
157 
get_internal_link_type_object() const158   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
159     return td_api::make_object<td_api::internalLinkTypePhoneNumberConfirmation>(hash_, phone_number_);
160   }
161 
162  public:
InternalLinkConfirmPhone(string hash,string phone_number)163   InternalLinkConfirmPhone(string hash, string phone_number)
164       : hash_(std::move(hash)), phone_number_(std::move(phone_number)) {
165   }
166 };
167 
168 class LinkManager::InternalLinkDialogInvite final : public InternalLink {
169   string url_;
170 
get_internal_link_type_object() const171   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
172     return td_api::make_object<td_api::internalLinkTypeChatInvite>(url_);
173   }
174 
175  public:
InternalLinkDialogInvite(string url)176   explicit InternalLinkDialogInvite(string url) : url_(std::move(url)) {
177   }
178 };
179 
180 class LinkManager::InternalLinkFilterSettings final : public InternalLink {
get_internal_link_type_object() const181   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
182     return td_api::make_object<td_api::internalLinkTypeFilterSettings>();
183   }
184 };
185 
186 class LinkManager::InternalLinkGame final : public InternalLink {
187   string bot_username_;
188   string game_short_name_;
189 
get_internal_link_type_object() const190   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
191     return td_api::make_object<td_api::internalLinkTypeGame>(bot_username_, game_short_name_);
192   }
193 
194  public:
InternalLinkGame(string bot_username,string game_short_name)195   InternalLinkGame(string bot_username, string game_short_name)
196       : bot_username_(std::move(bot_username)), game_short_name_(std::move(game_short_name)) {
197   }
198 };
199 
200 class LinkManager::InternalLinkLanguage final : public InternalLink {
201   string language_pack_id_;
202 
get_internal_link_type_object() const203   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
204     return td_api::make_object<td_api::internalLinkTypeLanguagePack>(language_pack_id_);
205   }
206 
207  public:
InternalLinkLanguage(string language_pack_id)208   explicit InternalLinkLanguage(string language_pack_id) : language_pack_id_(std::move(language_pack_id)) {
209   }
210 };
211 
212 class LinkManager::InternalLinkMessage final : public InternalLink {
213   string url_;
214 
get_internal_link_type_object() const215   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
216     return td_api::make_object<td_api::internalLinkTypeMessage>(url_);
217   }
218 
219  public:
InternalLinkMessage(string url)220   explicit InternalLinkMessage(string url) : url_(std::move(url)) {
221   }
222 };
223 
224 class LinkManager::InternalLinkMessageDraft final : public InternalLink {
225   FormattedText text_;
226   bool contains_link_ = false;
227 
get_internal_link_type_object() const228   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
229     return td_api::make_object<td_api::internalLinkTypeMessageDraft>(get_formatted_text_object(text_, true, -1),
230                                                                      contains_link_);
231   }
232 
233  public:
InternalLinkMessageDraft(FormattedText && text,bool contains_link)234   InternalLinkMessageDraft(FormattedText &&text, bool contains_link)
235       : text_(std::move(text)), contains_link_(contains_link) {
236   }
237 };
238 
239 class LinkManager::InternalLinkPassportDataRequest final : public InternalLink {
240   UserId bot_user_id_;
241   string scope_;
242   string public_key_;
243   string nonce_;
244   string callback_url_;
245 
get_internal_link_type_object() const246   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
247     return td_api::make_object<td_api::internalLinkTypePassportDataRequest>(bot_user_id_.get(), scope_, public_key_,
248                                                                             nonce_, callback_url_);
249   }
250 
251  public:
InternalLinkPassportDataRequest(UserId bot_user_id,string scope,string public_key,string nonce,string callback_url)252   InternalLinkPassportDataRequest(UserId bot_user_id, string scope, string public_key, string nonce,
253                                   string callback_url)
254       : bot_user_id_(bot_user_id)
255       , scope_(std::move(scope))
256       , public_key_(std::move(public_key))
257       , nonce_(std::move(nonce))
258       , callback_url_(std::move(callback_url)) {
259   }
260 };
261 
262 class LinkManager::InternalLinkProxy final : public InternalLink {
263   string server_;
264   int32 port_;
265   td_api::object_ptr<td_api::ProxyType> type_;
266 
get_internal_link_type_object() const267   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
268     CHECK(type_ != nullptr);
269     auto proxy_type = [type = type_.get()]() -> td_api::object_ptr<td_api::ProxyType> {
270       switch (type->get_id()) {
271         case td_api::proxyTypeSocks5::ID: {
272           auto type_socks = static_cast<const td_api::proxyTypeSocks5 *>(type);
273           return td_api::make_object<td_api::proxyTypeSocks5>(type_socks->username_, type_socks->password_);
274         }
275         case td_api::proxyTypeMtproto::ID: {
276           auto type_mtproto = static_cast<const td_api::proxyTypeMtproto *>(type);
277           return td_api::make_object<td_api::proxyTypeMtproto>(type_mtproto->secret_);
278         }
279         default:
280           UNREACHABLE();
281           return nullptr;
282       }
283     }();
284     return td_api::make_object<td_api::internalLinkTypeProxy>(server_, port_, std::move(proxy_type));
285   }
286 
287  public:
InternalLinkProxy(string server,int32 port,td_api::object_ptr<td_api::ProxyType> type)288   InternalLinkProxy(string server, int32 port, td_api::object_ptr<td_api::ProxyType> type)
289       : server_(std::move(server)), port_(port), type_(std::move(type)) {
290   }
291 };
292 
293 class LinkManager::InternalLinkPublicDialog final : public InternalLink {
294   string dialog_username_;
295 
get_internal_link_type_object() const296   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
297     return td_api::make_object<td_api::internalLinkTypePublicChat>(dialog_username_);
298   }
299 
300  public:
InternalLinkPublicDialog(string dialog_username)301   explicit InternalLinkPublicDialog(string dialog_username) : dialog_username_(std::move(dialog_username)) {
302   }
303 };
304 
305 class LinkManager::InternalLinkQrCodeAuthentication final : public InternalLink {
get_internal_link_type_object() const306   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
307     return td_api::make_object<td_api::internalLinkTypeQrCodeAuthentication>();
308   }
309 };
310 
311 class LinkManager::InternalLinkSettings final : public InternalLink {
get_internal_link_type_object() const312   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
313     return td_api::make_object<td_api::internalLinkTypeSettings>();
314   }
315 };
316 
317 class LinkManager::InternalLinkStickerSet final : public InternalLink {
318   string sticker_set_name_;
319 
get_internal_link_type_object() const320   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
321     return td_api::make_object<td_api::internalLinkTypeStickerSet>(sticker_set_name_);
322   }
323 
324  public:
InternalLinkStickerSet(string sticker_set_name)325   explicit InternalLinkStickerSet(string sticker_set_name) : sticker_set_name_(std::move(sticker_set_name)) {
326   }
327 };
328 
329 class LinkManager::InternalLinkTheme final : public InternalLink {
330   string theme_name_;
331 
get_internal_link_type_object() const332   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
333     return td_api::make_object<td_api::internalLinkTypeTheme>(theme_name_);
334   }
335 
336  public:
InternalLinkTheme(string theme_name)337   explicit InternalLinkTheme(string theme_name) : theme_name_(std::move(theme_name)) {
338   }
339 };
340 
341 class LinkManager::InternalLinkThemeSettings final : public InternalLink {
get_internal_link_type_object() const342   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
343     return td_api::make_object<td_api::internalLinkTypeThemeSettings>();
344   }
345 };
346 
347 class LinkManager::InternalLinkUnknownDeepLink final : public InternalLink {
348   string link_;
349 
get_internal_link_type_object() const350   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
351     return td_api::make_object<td_api::internalLinkTypeUnknownDeepLink>(link_);
352   }
353 
354  public:
InternalLinkUnknownDeepLink(string link)355   explicit InternalLinkUnknownDeepLink(string link) : link_(std::move(link)) {
356   }
357 };
358 
359 class LinkManager::InternalLinkUnsupportedProxy final : public InternalLink {
get_internal_link_type_object() const360   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
361     return td_api::make_object<td_api::internalLinkTypeUnsupportedProxy>();
362   }
363 };
364 
365 class LinkManager::InternalLinkVoiceChat final : public InternalLink {
366   string dialog_username_;
367   string invite_hash_;
368   bool is_live_stream_;
369 
get_internal_link_type_object() const370   td_api::object_ptr<td_api::InternalLinkType> get_internal_link_type_object() const final {
371     return td_api::make_object<td_api::internalLinkTypeVideoChat>(dialog_username_, invite_hash_, is_live_stream_);
372   }
373 
374  public:
InternalLinkVoiceChat(string dialog_username,string invite_hash,bool is_live_stream)375   InternalLinkVoiceChat(string dialog_username, string invite_hash, bool is_live_stream)
376       : dialog_username_(std::move(dialog_username))
377       , invite_hash_(std::move(invite_hash))
378       , is_live_stream_(is_live_stream) {
379   }
380 };
381 
382 class GetDeepLinkInfoQuery final : public Td::ResultHandler {
383   Promise<td_api::object_ptr<td_api::deepLinkInfo>> promise_;
384 
385  public:
GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> && promise)386   explicit GetDeepLinkInfoQuery(Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise)
387       : promise_(std::move(promise)) {
388   }
389 
send(Slice link)390   void send(Slice link) {
391     send_query(G()->net_query_creator().create_unauth(telegram_api::help_getDeepLinkInfo(link.str())));
392   }
393 
on_result(BufferSlice packet)394   void on_result(BufferSlice packet) final {
395     auto result_ptr = fetch_result<telegram_api::help_getDeepLinkInfo>(packet);
396     if (result_ptr.is_error()) {
397       return on_error(result_ptr.move_as_error());
398     }
399 
400     auto result = result_ptr.move_as_ok();
401     switch (result->get_id()) {
402       case telegram_api::help_deepLinkInfoEmpty::ID:
403         return promise_.set_value(nullptr);
404       case telegram_api::help_deepLinkInfo::ID: {
405         auto info = telegram_api::move_object_as<telegram_api::help_deepLinkInfo>(result);
406         auto entities = get_message_entities(nullptr, std::move(info->entities_), "GetDeepLinkInfoQuery");
407         auto status = fix_formatted_text(info->message_, entities, true, true, true, true, true);
408         if (status.is_error()) {
409           LOG(ERROR) << "Receive error " << status << " while parsing deep link info " << info->message_;
410           if (!clean_input_string(info->message_)) {
411             info->message_.clear();
412           }
413           entities = find_entities(info->message_, true, true);
414         }
415         FormattedText text{std::move(info->message_), std::move(entities)};
416         return promise_.set_value(
417             td_api::make_object<td_api::deepLinkInfo>(get_formatted_text_object(text, true, -1), info->update_app_));
418       }
419       default:
420         UNREACHABLE();
421     }
422   }
423 
on_error(Status status)424   void on_error(Status status) final {
425     promise_.set_error(std::move(status));
426   }
427 };
428 
429 class RequestUrlAuthQuery final : public Td::ResultHandler {
430   Promise<td_api::object_ptr<td_api::LoginUrlInfo>> promise_;
431   string url_;
432   DialogId dialog_id_;
433 
434  public:
RequestUrlAuthQuery(Promise<td_api::object_ptr<td_api::LoginUrlInfo>> && promise)435   explicit RequestUrlAuthQuery(Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise)
436       : promise_(std::move(promise)) {
437   }
438 
send(string url,FullMessageId full_message_id,int32 button_id)439   void send(string url, FullMessageId full_message_id, int32 button_id) {
440     url_ = std::move(url);
441     int32 flags = 0;
442     tl_object_ptr<telegram_api::InputPeer> input_peer;
443     if (full_message_id.get_dialog_id().is_valid()) {
444       dialog_id_ = full_message_id.get_dialog_id();
445       input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
446       CHECK(input_peer != nullptr);
447       flags |= telegram_api::messages_requestUrlAuth::PEER_MASK;
448     } else {
449       flags |= telegram_api::messages_requestUrlAuth::URL_MASK;
450     }
451     send_query(G()->net_query_creator().create(telegram_api::messages_requestUrlAuth(
452         flags, std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(), button_id,
453         url_)));
454   }
455 
on_result(BufferSlice packet)456   void on_result(BufferSlice packet) final {
457     auto result_ptr = fetch_result<telegram_api::messages_requestUrlAuth>(packet);
458     if (result_ptr.is_error()) {
459       return on_error(result_ptr.move_as_error());
460     }
461 
462     auto result = result_ptr.move_as_ok();
463     LOG(INFO) << "Receive result for RequestUrlAuthQuery: " << to_string(result);
464     switch (result->get_id()) {
465       case telegram_api::urlAuthResultRequest::ID: {
466         auto request = telegram_api::move_object_as<telegram_api::urlAuthResultRequest>(result);
467         UserId bot_user_id = ContactsManager::get_user_id(request->bot_);
468         if (!bot_user_id.is_valid()) {
469           return on_error(Status::Error(500, "Receive invalid bot_user_id"));
470         }
471         td_->contacts_manager_->on_get_user(std::move(request->bot_), "RequestUrlAuthQuery");
472         promise_.set_value(td_api::make_object<td_api::loginUrlInfoRequestConfirmation>(
473             url_, request->domain_, td_->contacts_manager_->get_user_id_object(bot_user_id, "RequestUrlAuthQuery"),
474             request->request_write_access_));
475         break;
476       }
477       case telegram_api::urlAuthResultAccepted::ID: {
478         auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
479         promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(accepted->url_, true));
480         break;
481       }
482       case telegram_api::urlAuthResultDefault::ID:
483         promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
484         break;
485     }
486   }
487 
on_error(Status status)488   void on_error(Status status) final {
489     if (!dialog_id_.is_valid() ||
490         !td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "RequestUrlAuthQuery")) {
491       LOG(INFO) << "Receive error for RequestUrlAuthQuery: " << status;
492     }
493     promise_.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url_, false));
494   }
495 };
496 
497 class AcceptUrlAuthQuery final : public Td::ResultHandler {
498   Promise<td_api::object_ptr<td_api::httpUrl>> promise_;
499   string url_;
500   DialogId dialog_id_;
501 
502  public:
AcceptUrlAuthQuery(Promise<td_api::object_ptr<td_api::httpUrl>> && promise)503   explicit AcceptUrlAuthQuery(Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) : promise_(std::move(promise)) {
504   }
505 
send(string url,FullMessageId full_message_id,int32 button_id,bool allow_write_access)506   void send(string url, FullMessageId full_message_id, int32 button_id, bool allow_write_access) {
507     url_ = std::move(url);
508     int32 flags = 0;
509     tl_object_ptr<telegram_api::InputPeer> input_peer;
510     if (full_message_id.get_dialog_id().is_valid()) {
511       dialog_id_ = full_message_id.get_dialog_id();
512       input_peer = td_->messages_manager_->get_input_peer(dialog_id_, AccessRights::Read);
513       CHECK(input_peer != nullptr);
514       flags |= telegram_api::messages_acceptUrlAuth::PEER_MASK;
515     } else {
516       flags |= telegram_api::messages_acceptUrlAuth::URL_MASK;
517     }
518     if (allow_write_access) {
519       flags |= telegram_api::messages_acceptUrlAuth::WRITE_ALLOWED_MASK;
520     }
521     send_query(G()->net_query_creator().create(telegram_api::messages_acceptUrlAuth(
522         flags, false /*ignored*/, std::move(input_peer), full_message_id.get_message_id().get_server_message_id().get(),
523         button_id, url_)));
524   }
525 
on_result(BufferSlice packet)526   void on_result(BufferSlice packet) final {
527     auto result_ptr = fetch_result<telegram_api::messages_acceptUrlAuth>(packet);
528     if (result_ptr.is_error()) {
529       return on_error(result_ptr.move_as_error());
530     }
531 
532     auto result = result_ptr.move_as_ok();
533     LOG(INFO) << "Receive " << to_string(result);
534     switch (result->get_id()) {
535       case telegram_api::urlAuthResultRequest::ID:
536         LOG(ERROR) << "Receive unexpected " << to_string(result);
537         return on_error(Status::Error(500, "Receive unexpected urlAuthResultRequest"));
538       case telegram_api::urlAuthResultAccepted::ID: {
539         auto accepted = telegram_api::move_object_as<telegram_api::urlAuthResultAccepted>(result);
540         promise_.set_value(td_api::make_object<td_api::httpUrl>(accepted->url_));
541         break;
542       }
543       case telegram_api::urlAuthResultDefault::ID:
544         promise_.set_value(td_api::make_object<td_api::httpUrl>(url_));
545         break;
546     }
547   }
548 
on_error(Status status)549   void on_error(Status status) final {
550     if (!dialog_id_.is_valid() ||
551         !td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "AcceptUrlAuthQuery")) {
552       LOG(INFO) << "Receive error for AcceptUrlAuthQuery: " << status;
553     }
554     promise_.set_error(std::move(status));
555   }
556 };
557 
LinkManager(Td * td,ActorShared<> parent)558 LinkManager::LinkManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
559 }
560 
561 LinkManager::~LinkManager() = default;
562 
start_up()563 void LinkManager::start_up() {
564   autologin_update_time_ = Time::now() - 365 * 86400;
565   autologin_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("autologin_domains"), '\xFF');
566 
567   url_auth_domains_ = full_split(G()->td_db()->get_binlog_pmc()->get("url_auth_domains"), '\xFF');
568 }
569 
tear_down()570 void LinkManager::tear_down() {
571   parent_.reset();
572 }
573 
tolower_begins_with(Slice str,Slice prefix)574 static bool tolower_begins_with(Slice str, Slice prefix) {
575   if (prefix.size() > str.size()) {
576     return false;
577   }
578   for (size_t i = 0; i < prefix.size(); i++) {
579     if (to_lower(str[i]) != prefix[i]) {
580       return false;
581     }
582   }
583   return true;
584 }
585 
check_link(Slice link)586 Result<string> LinkManager::check_link(Slice link) {
587   bool is_tg = false;
588   bool is_ton = false;
589   if (tolower_begins_with(link, "tg:")) {
590     link.remove_prefix(3);
591     is_tg = true;
592   } else if (tolower_begins_with(link, "ton:")) {
593     link.remove_prefix(4);
594     is_ton = true;
595   }
596   if ((is_tg || is_ton) && begins_with(link, "//")) {
597     link.remove_prefix(2);
598   }
599   TRY_RESULT(http_url, parse_url(link));
600   if (is_tg || is_ton) {
601     if (tolower_begins_with(link, "http://") || http_url.protocol_ == HttpUrl::Protocol::Https ||
602         !http_url.userinfo_.empty() || http_url.specified_port_ != 0 || http_url.is_ipv6_) {
603       return Status::Error(is_tg ? Slice("Wrong tg URL") : Slice("Wrong ton URL"));
604     }
605 
606     Slice query(http_url.query_);
607     CHECK(query[0] == '/');
608     if (query.size() > 1 && query[1] == '?') {
609       query.remove_prefix(1);
610     }
611     for (auto c : http_url.host_) {
612       if (!is_alnum(c) && c != '-' && c != '_') {
613         return Status::Error("Unallowed characters in URL host");
614       }
615     }
616     return PSTRING() << (is_tg ? "tg" : "ton") << "://" << http_url.host_ << query;
617   }
618 
619   if (http_url.host_.find('.') == string::npos && !http_url.is_ipv6_) {
620     return Status::Error("Wrong HTTP URL");
621   }
622   return http_url.get_url();
623 }
624 
get_link_info(Slice link)625 LinkManager::LinkInfo LinkManager::get_link_info(Slice link) {
626   LinkInfo result;
627   if (link.empty()) {
628     return result;
629   }
630   link.truncate(link.find('#'));
631 
632   bool is_tg = false;
633   if (tolower_begins_with(link, "tg:")) {
634     link.remove_prefix(3);
635     if (begins_with(link, "//")) {
636       link.remove_prefix(2);
637     }
638     is_tg = true;
639   }
640 
641   auto r_http_url = parse_url(link);
642   if (r_http_url.is_error()) {
643     return result;
644   }
645   auto http_url = r_http_url.move_as_ok();
646 
647   if (!http_url.userinfo_.empty() || http_url.is_ipv6_) {
648     return result;
649   }
650 
651   if (is_tg) {
652     if (tolower_begins_with(link, "http://") || http_url.protocol_ == HttpUrl::Protocol::Https ||
653         http_url.specified_port_ != 0) {
654       return result;
655     }
656 
657     result.is_internal_ = true;
658     result.is_tg_ = true;
659     result.query_ = link.str();
660     return result;
661   } else {
662     if (http_url.port_ != 80 && http_url.port_ != 443) {
663       return result;
664     }
665 
666     vector<Slice> t_me_urls{Slice("t.me"), Slice("telegram.me"), Slice("telegram.dog")};
667     if (Scheduler::context() != nullptr) {  // for tests only
668       string cur_t_me_url = G()->shared_config().get_option_string("t_me_url");
669       if (tolower_begins_with(cur_t_me_url, "http://") || tolower_begins_with(cur_t_me_url, "https://")) {
670         Slice t_me_url = cur_t_me_url;
671         t_me_url = t_me_url.substr(t_me_url[4] == 's' ? 8 : 7);
672         if (!td::contains(t_me_urls, t_me_url)) {
673           t_me_urls.push_back(t_me_url);
674         }
675       }
676     }
677 
678     auto host = url_decode(http_url.host_, false);
679     to_lower_inplace(host);
680     if (begins_with(host, "www.")) {
681       host = host.substr(4);
682     }
683 
684     for (auto t_me_url : t_me_urls) {
685       if (host == t_me_url) {
686         result.is_internal_ = true;
687         result.is_tg_ = false;
688 
689         Slice query = http_url.query_;
690         while (true) {
691           if (begins_with(query, "/s/")) {
692             query.remove_prefix(2);
693             continue;
694           }
695           if (begins_with(query, "/%73/")) {
696             query.remove_prefix(4);
697             continue;
698           }
699           break;
700         }
701         result.query_ = query.str();
702         return result;
703       }
704     }
705   }
706   return result;
707 }
708 
parse_internal_link(Slice link)709 unique_ptr<LinkManager::InternalLink> LinkManager::parse_internal_link(Slice link) {
710   auto info = get_link_info(link);
711   if (!info.is_internal_) {
712     return nullptr;
713   }
714   if (info.is_tg_) {
715     return parse_tg_link_query(info.query_);
716   } else {
717     return parse_t_me_link_query(info.query_);
718   }
719 }
720 
721 namespace {
722 struct CopyArg {
723   Slice name_;
724   const HttpUrlQuery *url_query_;
725   bool *is_first_;
726 
CopyArgtd::__anone586a99a0211::CopyArg727   CopyArg(Slice name, const HttpUrlQuery *url_query, bool *is_first)
728       : name_(name), url_query_(url_query), is_first_(is_first) {
729   }
730 };
731 
operator <<(StringBuilder & string_builder,const CopyArg & copy_arg)732 StringBuilder &operator<<(StringBuilder &string_builder, const CopyArg &copy_arg) {
733   auto arg = copy_arg.url_query_->get_arg(copy_arg.name_);
734   if (arg.empty()) {
735     for (const auto &query_arg : copy_arg.url_query_->args_) {
736       if (query_arg.first == copy_arg.name_) {
737         char c = *copy_arg.is_first_ ? '?' : '&';
738         *copy_arg.is_first_ = false;
739         return string_builder << c << copy_arg.name_;
740       }
741     }
742     return string_builder;
743   }
744   char c = *copy_arg.is_first_ ? '?' : '&';
745   *copy_arg.is_first_ = false;
746   return string_builder << c << copy_arg.name_ << '=' << url_encode(arg);
747 }
748 }  // namespace
749 
parse_tg_link_query(Slice query)750 unique_ptr<LinkManager::InternalLink> LinkManager::parse_tg_link_query(Slice query) {
751   const auto url_query = parse_url_query(query);
752   const auto &path = url_query.path_;
753 
754   bool is_first_arg = true;
755   auto copy_arg = [&](Slice name) {
756     return CopyArg(name, &url_query, &is_first_arg);
757   };
758   auto pass_arg = [&](Slice name) {
759     return url_encode(url_query.get_arg(name));
760   };
761   auto get_arg = [&](Slice name) {
762     return url_query.get_arg(name).str();
763   };
764   auto has_arg = [&](Slice name) {
765     return !url_query.get_arg(name).empty();
766   };
767 
768   if (path.size() == 1 && path[0] == "resolve") {
769     if (is_valid_username(get_arg("domain"))) {
770       if (has_arg("post")) {
771         // resolve?domain=<username>&post=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
772         return td::make_unique<InternalLinkMessage>(PSTRING() << "tg:resolve" << copy_arg("domain") << copy_arg("post")
773                                                               << copy_arg("single") << copy_arg("thread")
774                                                               << copy_arg("comment") << copy_arg("t"));
775       }
776       auto username = get_arg("domain");
777       for (auto &arg : url_query.args_) {
778         if (arg.first == "voicechat" || arg.first == "videochat" || arg.first == "livestream") {
779           // resolve?domain=<username>&videochat
780           // resolve?domain=<username>&videochat=<invite_hash>
781           if (Scheduler::context() != nullptr) {
782             send_closure(G()->messages_manager(), &MessagesManager::reload_voice_chat_on_search, username);
783           }
784           return td::make_unique<InternalLinkVoiceChat>(std::move(username), arg.second, arg.first == "livestream");
785         }
786         if (arg.first == "start" && is_valid_start_parameter(arg.second)) {
787           // resolve?domain=<bot_username>?start=<parameter>
788           return td::make_unique<InternalLinkBotStart>(std::move(username), arg.second);
789         }
790         if (arg.first == "startgroup" && is_valid_start_parameter(arg.second)) {
791           // resolve?domain=<bot_username>?startgroup=<parameter>
792           return td::make_unique<InternalLinkBotStartInGroup>(std::move(username), arg.second);
793         }
794         if (arg.first == "game" && !arg.second.empty()) {
795           // resolve?domain=<bot_username>?game=<short_name>
796           return td::make_unique<InternalLinkGame>(std::move(username), arg.second);
797         }
798       }
799       if (username == "telegrampassport") {
800         // resolve?domain=telegrampassport&bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
801         return get_internal_link_passport(query, url_query.args_);
802       }
803       // resolve?domain=<username>
804       return td::make_unique<InternalLinkPublicDialog>(std::move(username));
805     }
806   } else if (path.size() == 1 && path[0] == "login") {
807     // login?code=123456
808     if (has_arg("code")) {
809       return td::make_unique<InternalLinkAuthenticationCode>(get_arg("code"));
810     }
811     // login?token=<token>
812     if (has_arg("token")) {
813       return td::make_unique<InternalLinkQrCodeAuthentication>();
814     }
815   } else if (path.size() == 1 && path[0] == "passport") {
816     // passport?bot_id=<bot_user_id>&scope=<scope>&public_key=<public_key>&nonce=<nonce>
817     return get_internal_link_passport(query, url_query.args_);
818   } else if (!path.empty() && path[0] == "settings") {
819     if (path.size() == 2 && path[1] == "change_number") {
820       // settings/change_number
821       return td::make_unique<InternalLinkChangePhoneNumber>();
822     }
823     if (path.size() == 2 && path[1] == "devices") {
824       // settings/devices
825       return td::make_unique<InternalLinkActiveSessions>();
826     }
827     if (path.size() == 2 && path[1] == "folders") {
828       // settings/folders
829       return td::make_unique<InternalLinkFilterSettings>();
830     }
831     if (path.size() == 2 && path[1] == "themes") {
832       // settings/themes
833       return td::make_unique<InternalLinkThemeSettings>();
834     }
835     // settings
836     return td::make_unique<InternalLinkSettings>();
837   } else if (path.size() == 1 && path[0] == "join") {
838     // join?invite=<hash>
839     if (has_arg("invite")) {
840       return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
841                                                                  << url_encode(get_url_query_hash(true, url_query)));
842     }
843   } else if (path.size() == 1 && path[0] == "addstickers") {
844     // addstickers?set=<name>
845     if (has_arg("set")) {
846       return td::make_unique<InternalLinkStickerSet>(get_arg("set"));
847     }
848   } else if (path.size() == 1 && path[0] == "setlanguage") {
849     // setlanguage?lang=<name>
850     if (has_arg("lang")) {
851       return td::make_unique<InternalLinkLanguage>(get_arg("lang"));
852     }
853   } else if (path.size() == 1 && path[0] == "addtheme") {
854     // addtheme?slug=<name>
855     if (has_arg("slug")) {
856       return td::make_unique<InternalLinkTheme>(get_arg("slug"));
857     }
858   } else if (path.size() == 1 && path[0] == "confirmphone") {
859     if (has_arg("hash") && has_arg("phone")) {
860       // confirmphone?phone=<phone>&hash=<hash>
861       return td::make_unique<InternalLinkConfirmPhone>(get_arg("hash"), get_arg("phone"));
862     }
863   } else if (path.size() == 1 && path[0] == "socks") {
864     if (has_arg("server") && has_arg("port")) {
865       // socks?server=<server>&port=<port>&user=<user>&pass=<pass>
866       auto port = to_integer<int32>(get_arg("port"));
867       if (0 < port && port < 65536) {
868         return td::make_unique<InternalLinkProxy>(
869             get_arg("server"), port, td_api::make_object<td_api::proxyTypeSocks5>(get_arg("user"), get_arg("pass")));
870       } else {
871         return td::make_unique<InternalLinkUnsupportedProxy>();
872       }
873     }
874   } else if (path.size() == 1 && path[0] == "proxy") {
875     if (has_arg("server") && has_arg("port")) {
876       // proxy?server=<server>&port=<port>&secret=<secret>
877       auto port = to_integer<int32>(get_arg("port"));
878       if (0 < port && port < 65536 && mtproto::ProxySecret::from_link(get_arg("secret")).is_ok()) {
879         return td::make_unique<InternalLinkProxy>(get_arg("server"), port,
880                                                   td_api::make_object<td_api::proxyTypeMtproto>(get_arg("secret")));
881       } else {
882         return td::make_unique<InternalLinkUnsupportedProxy>();
883       }
884     }
885   } else if (path.size() == 1 && path[0] == "privatepost") {
886     // privatepost?channel=123456789&msg_id=12345&single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
887     if (has_arg("channel") && has_arg("msg_id")) {
888       return td::make_unique<InternalLinkMessage>(
889           PSTRING() << "tg:privatepost" << copy_arg("channel") << copy_arg("msg_id") << copy_arg("single")
890                     << copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
891     }
892   } else if (path.size() == 1 && path[0] == "bg") {
893     // bg?color=<color>
894     // bg?gradient=<hex_color>-<hex_color>&rotation=...
895     // bg?gradient=<hex_color>~<hex_color>~<hex_color>~<hex_color>
896     // bg?slug=<background_name>&mode=blur+motion
897     // bg?slug=<pattern_name>&intensity=...&bg_color=...&mode=blur+motion
898     if (has_arg("color")) {
899       return td::make_unique<InternalLinkBackground>(pass_arg("color"));
900     }
901     if (has_arg("gradient")) {
902       return td::make_unique<InternalLinkBackground>(PSTRING() << pass_arg("gradient") << copy_arg("rotation"));
903     }
904     if (has_arg("slug")) {
905       return td::make_unique<InternalLinkBackground>(PSTRING()
906                                                      << pass_arg("slug") << copy_arg("mode") << copy_arg("intensity")
907                                                      << copy_arg("bg_color") << copy_arg("rotation"));
908     }
909   } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) {
910     // msg_url?url=<url>
911     // msg_url?url=<url>&text=<text>
912     return get_internal_link_message_draft(get_arg("url"), get_arg("text"));
913   }
914   if (!path.empty() && !path[0].empty()) {
915     return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
916   }
917   return nullptr;
918 }
919 
parse_t_me_link_query(Slice query)920 unique_ptr<LinkManager::InternalLink> LinkManager::parse_t_me_link_query(Slice query) {
921   CHECK(query[0] == '/');
922   const auto url_query = parse_url_query(query);
923   const auto &path = url_query.path_;
924   if (path.empty() || path[0].empty()) {
925     return nullptr;
926   }
927 
928   bool is_first_arg = true;
929   auto copy_arg = [&](Slice name) {
930     return CopyArg(name, &url_query, &is_first_arg);
931   };
932 
933   auto get_arg = [&](Slice name) {
934     return url_query.get_arg(name).str();
935   };
936   auto has_arg = [&](Slice name) {
937     return !url_query.get_arg(name).empty();
938   };
939 
940   if (path[0] == "c") {
941     if (path.size() >= 3 && to_integer<int64>(path[1]) > 0 && to_integer<int64>(path[2]) > 0) {
942       // /c/123456789/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
943       is_first_arg = false;
944       return td::make_unique<InternalLinkMessage>(PSTRING()
945                                                   << "tg:privatepost?channel=" << to_integer<int64>(path[1])
946                                                   << "&msg_id=" << to_integer<int64>(path[2]) << copy_arg("single")
947                                                   << copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
948     }
949   } else if (path[0] == "login") {
950     if (path.size() >= 2 && !path[1].empty()) {
951       // /login/<code>
952       return td::make_unique<InternalLinkAuthenticationCode>(path[1]);
953     }
954   } else if (path[0] == "joinchat") {
955     if (path.size() >= 2 && !path[1].empty()) {
956       // /joinchat/<link>
957       return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
958                                                                  << url_encode(get_url_query_hash(false, url_query)));
959     }
960   } else if (path[0][0] == ' ' || path[0][0] == '+') {
961     if (path[0].size() >= 2) {
962       // /+<link>
963       return td::make_unique<InternalLinkDialogInvite>(PSTRING() << "tg:join?invite="
964                                                                  << url_encode(get_url_query_hash(false, url_query)));
965     }
966   } else if (path[0] == "addstickers") {
967     if (path.size() >= 2 && !path[1].empty()) {
968       // /addstickers/<name>
969       return td::make_unique<InternalLinkStickerSet>(path[1]);
970     }
971   } else if (path[0] == "setlanguage") {
972     if (path.size() >= 2 && !path[1].empty()) {
973       // /setlanguage/<name>
974       return td::make_unique<InternalLinkLanguage>(path[1]);
975     }
976   } else if (path[0] == "addtheme") {
977     if (path.size() >= 2 && !path[1].empty()) {
978       // /addtheme/<name>
979       return td::make_unique<InternalLinkTheme>(path[1]);
980     }
981   } else if (path[0] == "confirmphone") {
982     if (has_arg("hash") && has_arg("phone")) {
983       // /confirmphone?phone=<phone>&hash=<hash>
984       return td::make_unique<InternalLinkConfirmPhone>(get_arg("hash"), get_arg("phone"));
985     }
986   } else if (path[0] == "socks") {
987     if (has_arg("server") && has_arg("port")) {
988       // /socks?server=<server>&port=<port>&user=<user>&pass=<pass>
989       auto port = to_integer<int32>(get_arg("port"));
990       if (0 < port && port < 65536) {
991         return td::make_unique<InternalLinkProxy>(
992             get_arg("server"), port, td_api::make_object<td_api::proxyTypeSocks5>(get_arg("user"), get_arg("pass")));
993       } else {
994         return td::make_unique<InternalLinkUnsupportedProxy>();
995       }
996     }
997   } else if (path[0] == "proxy") {
998     if (has_arg("server") && has_arg("port")) {
999       // /proxy?server=<server>&port=<port>&secret=<secret>
1000       auto port = to_integer<int32>(get_arg("port"));
1001       if (0 < port && port < 65536 && mtproto::ProxySecret::from_link(get_arg("secret")).is_ok()) {
1002         return td::make_unique<InternalLinkProxy>(get_arg("server"), port,
1003                                                   td_api::make_object<td_api::proxyTypeMtproto>(get_arg("secret")));
1004       } else {
1005         return td::make_unique<InternalLinkUnsupportedProxy>();
1006       }
1007     }
1008   } else if (path[0] == "bg") {
1009     if (path.size() >= 2 && !path[1].empty()) {
1010       // /bg/<hex_color>
1011       // /bg/<hex_color>-<hex_color>?rotation=...
1012       // /bg/<hex_color>~<hex_color>~<hex_color>~<hex_color>
1013       // /bg/<background_name>?mode=blur+motion
1014       // /bg/<pattern_name>?intensity=...&bg_color=...&mode=blur+motion
1015       return td::make_unique<InternalLinkBackground>(PSTRING()
1016                                                      << url_encode(path[1]) << copy_arg("mode") << copy_arg("intensity")
1017                                                      << copy_arg("bg_color") << copy_arg("rotation"));
1018     }
1019   } else if (path[0] == "share" || path[0] == "msg") {
1020     if (!(path.size() > 1 && (path[1] == "bookmarklet" || path[1] == "embed"))) {
1021       // /share?url=<url>
1022       // /share/url?url=<url>&text=<text>
1023       return get_internal_link_message_draft(get_arg("url"), get_arg("text"));
1024     }
1025   } else if (is_valid_username(path[0])) {
1026     if (path.size() >= 2 && to_integer<int64>(path[1]) > 0) {
1027       // /<username>/12345?single&thread=<thread_id>&comment=<message_id>&t=<media_timestamp>
1028       is_first_arg = false;
1029       return td::make_unique<InternalLinkMessage>(
1030           PSTRING() << "tg:resolve?domain=" << url_encode(path[0]) << "&post=" << to_integer<int64>(path[1])
1031                     << copy_arg("single") << copy_arg("thread") << copy_arg("comment") << copy_arg("t"));
1032     }
1033     auto username = path[0];
1034     for (auto &arg : url_query.args_) {
1035       if (arg.first == "voicechat" || arg.first == "videochat" || arg.first == "livestream") {
1036         // /<username>?videochat
1037         // /<username>?videochat=<invite_hash>
1038         if (Scheduler::context() != nullptr) {
1039           send_closure(G()->messages_manager(), &MessagesManager::reload_voice_chat_on_search, username);
1040         }
1041         return td::make_unique<InternalLinkVoiceChat>(std::move(username), arg.second, arg.first == "livestream");
1042       }
1043       if (arg.first == "start" && is_valid_start_parameter(arg.second)) {
1044         // /<bot_username>?start=<parameter>
1045         return td::make_unique<InternalLinkBotStart>(std::move(username), arg.second);
1046       }
1047       if (arg.first == "startgroup" && is_valid_start_parameter(arg.second)) {
1048         // /<bot_username>?startgroup=<parameter>
1049         return td::make_unique<InternalLinkBotStartInGroup>(std::move(username), arg.second);
1050       }
1051       if (arg.first == "game" && !arg.second.empty()) {
1052         // /<bot_username>?game=<short_name>
1053         return td::make_unique<InternalLinkGame>(std::move(username), arg.second);
1054       }
1055     }
1056     // /<username>
1057     return td::make_unique<InternalLinkPublicDialog>(std::move(username));
1058   }
1059   return nullptr;
1060 }
1061 
get_internal_link_message_draft(Slice url,Slice text)1062 unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_message_draft(Slice url, Slice text) {
1063   if (url.empty() && text.empty()) {
1064     return nullptr;
1065   }
1066   while (!text.empty() && text.back() == '\n') {
1067     text.remove_suffix(1);
1068   }
1069   url = trim(url);
1070   if (url.empty()) {
1071     url = text;
1072     text = Slice();
1073   }
1074   FormattedText full_text;
1075   bool contains_url = false;
1076   if (!text.empty()) {
1077     contains_url = true;
1078     full_text.text = PSTRING() << url << '\n' << text;
1079   } else {
1080     full_text.text = url.str();
1081   }
1082   if (fix_formatted_text(full_text.text, full_text.entities, false, false, false, true, true).is_error()) {
1083     return nullptr;
1084   }
1085   if (full_text.text[0] == '@') {
1086     full_text.text = ' ' + full_text.text;
1087     for (auto &entity : full_text.entities) {
1088       entity.offset++;
1089     }
1090   }
1091   return td::make_unique<InternalLinkMessageDraft>(std::move(full_text), contains_url);
1092 }
1093 
get_internal_link_passport(Slice query,const vector<std::pair<string,string>> & args)1094 unique_ptr<LinkManager::InternalLink> LinkManager::get_internal_link_passport(
1095     Slice query, const vector<std::pair<string, string>> &args) {
1096   auto get_arg = [&args](Slice key) {
1097     for (auto &arg : args) {
1098       if (arg.first == key) {
1099         return Slice(arg.second);
1100       }
1101     }
1102     return Slice();
1103   };
1104 
1105   UserId bot_user_id(to_integer<int64>(get_arg("bot_id")));
1106   auto scope = get_arg("scope");
1107   auto public_key = get_arg("public_key");
1108   auto nonce = get_arg("nonce");
1109   if (nonce.empty()) {
1110     nonce = get_arg("payload");
1111   }
1112   auto callback_url = get_arg("callback_url");
1113 
1114   if (!bot_user_id.is_valid() || scope.empty() || public_key.empty() || nonce.empty()) {
1115     return td::make_unique<InternalLinkUnknownDeepLink>(PSTRING() << "tg://" << query);
1116   }
1117   return td::make_unique<InternalLinkPassportDataRequest>(bot_user_id, scope.str(), public_key.str(), nonce.str(),
1118                                                           callback_url.str());
1119 }
1120 
update_autologin_domains(string autologin_token,vector<string> autologin_domains,vector<string> url_auth_domains)1121 void LinkManager::update_autologin_domains(string autologin_token, vector<string> autologin_domains,
1122                                            vector<string> url_auth_domains) {
1123   autologin_update_time_ = Time::now();
1124   autologin_token_ = std::move(autologin_token);
1125   if (autologin_domains_ != autologin_domains) {
1126     autologin_domains_ = std::move(autologin_domains);
1127     G()->td_db()->get_binlog_pmc()->set("autologin_domains", implode(autologin_domains_, '\xFF'));
1128   }
1129   if (url_auth_domains_ != url_auth_domains) {
1130     url_auth_domains_ = std::move(url_auth_domains);
1131     G()->td_db()->get_binlog_pmc()->set("url_auth_domains", implode(url_auth_domains_, '\xFF'));
1132   }
1133 }
1134 
get_deep_link_info(Slice link,Promise<td_api::object_ptr<td_api::deepLinkInfo>> && promise)1135 void LinkManager::get_deep_link_info(Slice link, Promise<td_api::object_ptr<td_api::deepLinkInfo>> &&promise) {
1136   Slice link_scheme("tg:");
1137   if (begins_with(link, link_scheme)) {
1138     link.remove_prefix(link_scheme.size());
1139     if (begins_with(link, "//")) {
1140       link.remove_prefix(2);
1141     }
1142   }
1143   size_t pos = 0;
1144   while (pos < link.size() && link[pos] != '/' && link[pos] != '?' && link[pos] != '#') {
1145     pos++;
1146   }
1147   link.truncate(pos);
1148   td_->create_handler<GetDeepLinkInfoQuery>(std::move(promise))->send(link);
1149 }
1150 
get_external_link_info(string && link,Promise<td_api::object_ptr<td_api::LoginUrlInfo>> && promise)1151 void LinkManager::get_external_link_info(string &&link, Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
1152   auto default_result = td_api::make_object<td_api::loginUrlInfoOpen>(link, false);
1153   if (G()->close_flag()) {
1154     return promise.set_value(std::move(default_result));
1155   }
1156 
1157   auto r_url = parse_url(link);
1158   if (r_url.is_error()) {
1159     return promise.set_value(std::move(default_result));
1160   }
1161 
1162   if (!td::contains(autologin_domains_, r_url.ok().host_)) {
1163     if (td::contains(url_auth_domains_, r_url.ok().host_)) {
1164       td_->create_handler<RequestUrlAuthQuery>(std::move(promise))->send(link, FullMessageId(), 0);
1165       return;
1166     }
1167     return promise.set_value(std::move(default_result));
1168   }
1169 
1170   if (autologin_update_time_ < Time::now() - 10000) {
1171     auto query_promise =
1172         PromiseCreator::lambda([link = std::move(link), promise = std::move(promise)](Result<Unit> &&result) mutable {
1173           if (result.is_error()) {
1174             return promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(link, false));
1175           }
1176           send_closure(G()->link_manager(), &LinkManager::get_external_link_info, std::move(link), std::move(promise));
1177         });
1178     return send_closure(G()->config_manager(), &ConfigManager::reget_app_config, std::move(query_promise));
1179   }
1180 
1181   if (autologin_token_.empty()) {
1182     return promise.set_value(std::move(default_result));
1183   }
1184 
1185   auto url = r_url.move_as_ok();
1186   url.protocol_ = HttpUrl::Protocol::Https;
1187   Slice path = url.query_;
1188   path.truncate(url.query_.find_first_of("?#"));
1189   Slice parameters_hash = Slice(url.query_).substr(path.size());
1190   Slice parameters = parameters_hash;
1191   parameters.truncate(parameters.find('#'));
1192   Slice hash = parameters_hash.substr(parameters.size());
1193 
1194   string added_parameter;
1195   if (parameters.empty()) {
1196     added_parameter = '?';
1197   } else if (parameters.size() == 1) {
1198     CHECK(parameters == "?");
1199   } else {
1200     added_parameter = '&';
1201   }
1202   added_parameter += "autologin_token=";
1203   added_parameter += autologin_token_;
1204 
1205   url.query_ = PSTRING() << path << parameters << added_parameter << hash;
1206 
1207   promise.set_value(td_api::make_object<td_api::loginUrlInfoOpen>(url.get_url(), false));
1208 }
1209 
get_login_url_info(FullMessageId full_message_id,int64 button_id,Promise<td_api::object_ptr<td_api::LoginUrlInfo>> && promise)1210 void LinkManager::get_login_url_info(FullMessageId full_message_id, int64 button_id,
1211                                      Promise<td_api::object_ptr<td_api::LoginUrlInfo>> &&promise) {
1212   TRY_RESULT_PROMISE(promise, url, td_->messages_manager_->get_login_button_url(full_message_id, button_id));
1213   td_->create_handler<RequestUrlAuthQuery>(std::move(promise))
1214       ->send(std::move(url), full_message_id, narrow_cast<int32>(button_id));
1215 }
1216 
get_login_url(FullMessageId full_message_id,int64 button_id,bool allow_write_access,Promise<td_api::object_ptr<td_api::httpUrl>> && promise)1217 void LinkManager::get_login_url(FullMessageId full_message_id, int64 button_id, bool allow_write_access,
1218                                 Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
1219   TRY_RESULT_PROMISE(promise, url, td_->messages_manager_->get_login_button_url(full_message_id, button_id));
1220   td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))
1221       ->send(std::move(url), full_message_id, narrow_cast<int32>(button_id), allow_write_access);
1222 }
1223 
get_link_login_url(const string & url,bool allow_write_access,Promise<td_api::object_ptr<td_api::httpUrl>> && promise)1224 void LinkManager::get_link_login_url(const string &url, bool allow_write_access,
1225                                      Promise<td_api::object_ptr<td_api::httpUrl>> &&promise) {
1226   td_->create_handler<AcceptUrlAuthQuery>(std::move(promise))->send(url, FullMessageId(), 0, allow_write_access);
1227 }
1228 
get_dialog_invite_link_hash(Slice invite_link)1229 string LinkManager::get_dialog_invite_link_hash(Slice invite_link) {
1230   auto link_info = get_link_info(invite_link);
1231   if (!link_info.is_internal_) {
1232     return string();
1233   }
1234   const auto url_query = parse_url_query(link_info.query_);
1235   return get_url_query_hash(link_info.is_tg_, url_query);
1236 }
1237 
get_link_user_id(Slice url)1238 UserId LinkManager::get_link_user_id(Slice url) {
1239   string lower_cased_url = to_lower(url);
1240   url = lower_cased_url;
1241 
1242   Slice link_scheme("tg:");
1243   if (!begins_with(url, link_scheme)) {
1244     return UserId();
1245   }
1246   url.remove_prefix(link_scheme.size());
1247   if (begins_with(url, "//")) {
1248     url.remove_prefix(2);
1249   }
1250 
1251   Slice host("user");
1252   if (!begins_with(url, host)) {
1253     return UserId();
1254   }
1255   url.remove_prefix(host.size());
1256   if (begins_with(url, "/")) {
1257     url.remove_prefix(1);
1258   }
1259   if (!begins_with(url, "?")) {
1260     return UserId();
1261   }
1262   url.remove_prefix(1);
1263   url.truncate(url.find('#'));
1264 
1265   for (auto parameter : full_split(url, '&')) {
1266     Slice key;
1267     Slice value;
1268     std::tie(key, value) = split(parameter, '=');
1269     if (key == Slice("id")) {
1270       auto r_user_id = to_integer_safe<int64>(value);
1271       if (r_user_id.is_error()) {
1272         return UserId();
1273       }
1274       return UserId(r_user_id.ok());
1275     }
1276   }
1277   return UserId();
1278 }
1279 
get_message_link_info(Slice url)1280 Result<MessageLinkInfo> LinkManager::get_message_link_info(Slice url) {
1281   if (url.empty()) {
1282     return Status::Error("URL must be non-empty");
1283   }
1284   auto link_info = get_link_info(url);
1285   if (!link_info.is_internal_) {
1286     return Status::Error("Invalid message link URL");
1287   }
1288   url = link_info.query_;
1289 
1290   Slice username;
1291   Slice channel_id_slice;
1292   Slice message_id_slice;
1293   Slice comment_message_id_slice = "0";
1294   Slice media_timestamp_slice;
1295   bool is_single = false;
1296   bool for_comment = false;
1297   if (link_info.is_tg_) {
1298     // resolve?domain=username&post=12345&single&t=123&comment=12&thread=21
1299     // privatepost?channel=123456789&msg_id=12345&single&t=123&comment=12&thread=21
1300 
1301     bool is_resolve = false;
1302     if (begins_with(url, "resolve")) {
1303       url = url.substr(7);
1304       is_resolve = true;
1305     } else if (begins_with(url, "privatepost")) {
1306       url = url.substr(11);
1307     } else {
1308       return Status::Error("Wrong message link URL");
1309     }
1310 
1311     if (begins_with(url, "/")) {
1312       url = url.substr(1);
1313     }
1314     if (!begins_with(url, "?")) {
1315       return Status::Error("Wrong message link URL");
1316     }
1317     url = url.substr(1);
1318 
1319     auto args = full_split(url, '&');
1320     for (auto arg : args) {
1321       auto key_value = split(arg, '=');
1322       if (is_resolve) {
1323         if (key_value.first == "domain") {
1324           username = key_value.second;
1325         }
1326         if (key_value.first == "post") {
1327           message_id_slice = key_value.second;
1328         }
1329       } else {
1330         if (key_value.first == "channel") {
1331           channel_id_slice = key_value.second;
1332         }
1333         if (key_value.first == "msg_id") {
1334           message_id_slice = key_value.second;
1335         }
1336       }
1337       if (key_value.first == "t") {
1338         media_timestamp_slice = key_value.second;
1339       }
1340       if (key_value.first == "single") {
1341         is_single = true;
1342       }
1343       if (key_value.first == "comment") {
1344         comment_message_id_slice = key_value.second;
1345       }
1346       if (key_value.first == "thread") {
1347         for_comment = true;
1348       }
1349     }
1350   } else {
1351     // /c/123456789/12345
1352     // /username/12345?single
1353 
1354     CHECK(!url.empty() && url[0] == '/');
1355     url.remove_prefix(1);
1356 
1357     auto username_end_pos = url.find('/');
1358     if (username_end_pos == Slice::npos) {
1359       return Status::Error("Wrong message link URL");
1360     }
1361     username = url.substr(0, username_end_pos);
1362     url = url.substr(username_end_pos + 1);
1363     if (username == "c") {
1364       username = Slice();
1365       auto channel_id_end_pos = url.find('/');
1366       if (channel_id_end_pos == Slice::npos) {
1367         return Status::Error("Wrong message link URL");
1368       }
1369       channel_id_slice = url.substr(0, channel_id_end_pos);
1370       url = url.substr(channel_id_end_pos + 1);
1371     }
1372 
1373     auto query_pos = url.find('?');
1374     message_id_slice = url.substr(0, query_pos);
1375     if (query_pos != Slice::npos) {
1376       auto args = full_split(url.substr(query_pos + 1), '&');
1377       for (auto arg : args) {
1378         auto key_value = split(arg, '=');
1379         if (key_value.first == "t") {
1380           media_timestamp_slice = key_value.second;
1381         }
1382         if (key_value.first == "single") {
1383           is_single = true;
1384         }
1385         if (key_value.first == "comment") {
1386           comment_message_id_slice = key_value.second;
1387         }
1388         if (key_value.first == "thread") {
1389           for_comment = true;
1390         }
1391       }
1392     }
1393   }
1394 
1395   ChannelId channel_id;
1396   if (username.empty()) {
1397     auto r_channel_id = to_integer_safe<int64>(channel_id_slice);
1398     if (r_channel_id.is_error() || !ChannelId(r_channel_id.ok()).is_valid()) {
1399       return Status::Error("Wrong channel ID");
1400     }
1401     channel_id = ChannelId(r_channel_id.ok());
1402   }
1403 
1404   auto r_message_id = to_integer_safe<int32>(message_id_slice);
1405   if (r_message_id.is_error() || !ServerMessageId(r_message_id.ok()).is_valid()) {
1406     return Status::Error("Wrong message ID");
1407   }
1408 
1409   auto r_comment_message_id = to_integer_safe<int32>(comment_message_id_slice);
1410   if (r_comment_message_id.is_error() ||
1411       !(r_comment_message_id.ok() == 0 || ServerMessageId(r_comment_message_id.ok()).is_valid())) {
1412     return Status::Error("Wrong comment message ID");
1413   }
1414 
1415   bool is_media_timestamp_invalid = false;
1416   int32 media_timestamp = 0;
1417   const int32 MAX_MEDIA_TIMESTAMP = 10000000;
1418   if (!media_timestamp_slice.empty()) {
1419     int32 current_value = 0;
1420     for (size_t i = 0; i <= media_timestamp_slice.size(); i++) {
1421       auto c = i < media_timestamp_slice.size() ? media_timestamp_slice[i] : 's';
1422       if ('0' <= c && c <= '9') {
1423         current_value = current_value * 10 + c - '0';
1424         if (current_value > MAX_MEDIA_TIMESTAMP) {
1425           is_media_timestamp_invalid = true;
1426           break;
1427         }
1428       } else {
1429         auto mul = 0;
1430         switch (to_lower(c)) {
1431           case 'h':
1432             mul = 3600;
1433             break;
1434           case 'm':
1435             mul = 60;
1436             break;
1437           case 's':
1438             mul = 1;
1439             break;
1440         }
1441         if (mul == 0 || current_value > MAX_MEDIA_TIMESTAMP / mul ||
1442             media_timestamp + current_value * mul > MAX_MEDIA_TIMESTAMP) {
1443           is_media_timestamp_invalid = true;
1444           break;
1445         }
1446         media_timestamp += current_value * mul;
1447         current_value = 0;
1448       }
1449     }
1450   }
1451 
1452   MessageLinkInfo info;
1453   info.username = username.str();
1454   info.channel_id = channel_id;
1455   info.message_id = MessageId(ServerMessageId(r_message_id.ok()));
1456   info.comment_message_id = MessageId(ServerMessageId(r_comment_message_id.ok()));
1457   info.media_timestamp = is_media_timestamp_invalid ? 0 : media_timestamp;
1458   info.is_single = is_single;
1459   info.for_comment = for_comment;
1460   LOG(INFO) << "Have link to " << info.message_id << " in chat @" << info.username << "/" << channel_id.get();
1461   return std::move(info);
1462 }
1463 
1464 }  // namespace td
1465