diff --git a/td/telegram/LinkManager.cpp b/td/telegram/LinkManager.cpp index ecbf75039..b2480cf98 100644 --- a/td/telegram/LinkManager.cpp +++ b/td/telegram/LinkManager.cpp @@ -295,7 +295,7 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { result.is_internal_ = true; result.is_tg_ = true; - result.query_ = link; + result.query_ = link.str(); return result; } else { if (http_url.port_ != 80 && http_url.port_ != 443) { @@ -303,26 +303,28 @@ LinkManager::LinkInfo LinkManager::get_link_info(Slice link) { } vector t_me_urls{Slice("t.me"), Slice("telegram.me"), Slice("telegram.dog")}; - string cur_t_me_url = G()->shared_config().get_option_string("t_me_url"); - if (tolower_begins_with(cur_t_me_url, "http://") || tolower_begins_with(cur_t_me_url, "https://")) { - Slice t_me_url = cur_t_me_url; - t_me_url = t_me_url.substr(t_me_url[4] == 's' ? 8 : 7); - if (!td::contains(t_me_urls, t_me_url)) { - t_me_urls.push_back(t_me_url); + if (Scheduler::context() != nullptr) { + string cur_t_me_url = G()->shared_config().get_option_string("t_me_url"); + if (tolower_begins_with(cur_t_me_url, "http://") || tolower_begins_with(cur_t_me_url, "https://")) { + Slice t_me_url = cur_t_me_url; + t_me_url = t_me_url.substr(t_me_url[4] == 's' ? 8 : 7); + if (!td::contains(t_me_urls, t_me_url)) { + t_me_urls.push_back(t_me_url); + } } } - // host is already lowercased - Slice host = http_url.host_; + auto host = url_decode(http_url.host_, false); + to_lower_inplace(host); if (begins_with(host, "www.")) { - host.remove_prefix(4); + host = host.substr(4); } for (auto t_me_url : t_me_urls) { if (host == t_me_url) { result.is_internal_ = true; result.is_tg_ = false; - result.query_ = http_url.query_; + result.query_ = std::move(http_url.query_); return result; } } @@ -342,26 +344,51 @@ unique_ptr LinkManager::parse_internal_link(Slice lin } } +namespace { +struct CopyArg { + Slice name_; + const HttpUrlQuery *url_query_; + bool *is_first_; + + CopyArg(Slice name, const HttpUrlQuery *url_query, bool *is_first) + : name_(name), url_query_(url_query), is_first_(is_first) { + } +}; + +StringBuilder &operator<<(StringBuilder &string_builder, const CopyArg ©_arg) { + auto arg = copy_arg.url_query_->get_arg(copy_arg.name_); + if (arg.empty()) { + return string_builder; + } + char c = *copy_arg.is_first_ ? '?' : '&'; + *copy_arg.is_first_ = false; + return string_builder << c << copy_arg.name_ << '=' << url_encode(arg); +} +} // namespace + unique_ptr LinkManager::parse_tg_link_query(Slice query) { const auto url_query = parse_url_query(query); const auto &path = url_query.path_; + bool is_first_arg = true; auto copy_arg = [&](Slice name) { - auto arg = url_query.get_arg(name); - if (arg.empty()) { - return string(); - } - return PSTRING() << name << '=' << url_encode(arg); + return CopyArg(name, &url_query, &is_first_arg); + }; + auto get_arg = [&](Slice name) { + return url_encode(url_query.get_arg(name)); + }; + auto has_arg = [&](Slice name) { + return !url_query.get_arg(name).empty(); }; if (path.size() == 1 && path[0] == "resolve") { // resolve?domain=username&post=12345&single - if (!url_query.get_arg("domain").empty() && !url_query.get_arg("post").empty()) { + if (has_arg("domain") && has_arg("post")) { return td::make_unique(); } } else if (path.size() == 1 && path[0] == "privatepost") { // privatepost?channel=123456789&msg_id=12345 - if (!url_query.get_arg("channel").empty() && !url_query.get_arg("msg_id").empty()) { + if (has_arg("channel") && has_arg("msg_id")) { return td::make_unique(); } } else if (path.size() == 1 && path[0] == "bg") { @@ -370,17 +397,16 @@ unique_ptr LinkManager::parse_tg_link_query(Slice que // bg?gradient=~~~ // bg?slug=&mode=blur+motion // bg?slug=&intensity=...&bg_color=...&mode=blur+motion - if (!url_query.get_arg("color").empty()) { - return td::make_unique(url_query.get_arg("color").str()); + if (has_arg("color")) { + return td::make_unique(get_arg("color")); } - if (!url_query.get_arg("gradient").empty()) { - return td::make_unique(PSTRING() << url_encode(url_query.get_arg("gradient")) << '?' - << copy_arg("rotation")); + if (has_arg("gradient")) { + return td::make_unique(PSTRING() << get_arg("gradient") << copy_arg("rotation")); } - if (!url_query.get_arg("slug").empty()) { - return td::make_unique(PSTRING() << url_encode(url_query.get_arg("slug")) << '?' - << copy_arg("mode") << copy_arg("intensity") - << copy_arg("bg_color") << copy_arg("rotation")); + if (has_arg("slug")) { + return td::make_unique(PSTRING() + << get_arg("slug") << copy_arg("mode") << copy_arg("intensity") + << copy_arg("bg_color") << copy_arg("rotation")); } } else if (path.size() == 1 && (path[0] == "share" || path[0] == "msg" || path[0] == "msg_url")) { // msg_url?url= @@ -397,24 +423,45 @@ unique_ptr LinkManager::parse_t_me_link_query(Slice q CHECK(query[0] == '/'); const auto url_query = parse_url_query(query); const auto &path = url_query.path_; - if (path.size() == 3 && path[0] == "c") { - // /c/123456789/12345 - return td::make_unique(); - } else if (path.size() == 2 && path[0] == "bg") { - // /bg/ - // /bg/-?rotation=... - // /bg/~~~ - // /bg/?mode=blur+motion - // /bg/?intensity=...&bg_color=...&mode=blur+motion - return td::make_unique(query.substr(4).str()); - } else if (path.size() >= 1 && (path[0] == "share" || path[0] == "msg") && - (path.size() == 1 || (path[1] != "bookmarklet" && path[1] != "embed"))) { - // /share?url= - // /share/url?url=&text= - return get_internal_link_message_draft(url_query.get_arg("url"), url_query.get_arg("text")); - } else if (path.size() == 2 && !path[0].empty()) { - // //12345?single - return td::make_unique(); + if (path.empty() || path[0].empty()) { + return nullptr; + } + + bool is_first_arg = true; + auto copy_arg = [&](Slice name) { + return CopyArg(name, &url_query, &is_first_arg); + }; + auto get_arg = [&](Slice name) { + return url_encode(url_query.get_arg(name)); + }; + + if (path[0] == "c") { + if (path.size() >= 3 && to_integer(path[1]) > 0 && to_integer(path[2]) > 0) { + // /c/123456789/12345 + return td::make_unique(); + } + } else if (path[0] == "bg") { + if (path.size() >= 2 && !path[1].empty()) { + // /bg/ + // /bg/-?rotation=... + // /bg/~~~ + // /bg/?mode=blur+motion + // /bg/?intensity=...&bg_color=...&mode=blur+motion + return td::make_unique(PSTRING() + << url_encode(path[1]) << copy_arg("mode") << copy_arg("intensity") + << copy_arg("bg_color") << copy_arg("rotation")); + } + } else if (path[0] == "share" || path[0] == "msg") { + if (!(path.size() > 1 && (path[1] == "bookmarklet" || path[1] == "embed"))) { + // /share?url= + // /share/url?url=&text= + return get_internal_link_message_draft(url_query.get_arg("url"), url_query.get_arg("text")); + } + } else { + if (path.size() >= 2 && to_integer(path[1]) > 0) { + // //12345?single + return td::make_unique(); + } } return nullptr; } diff --git a/td/telegram/LinkManager.h b/td/telegram/LinkManager.h index 0e465111f..2cdae189f 100644 --- a/td/telegram/LinkManager.h +++ b/td/telegram/LinkManager.h @@ -55,7 +55,7 @@ class LinkManager : public Actor { struct LinkInfo { bool is_internal_ = false; bool is_tg_ = false; - Slice query_; + string query_; }; // returns information about the link static LinkInfo get_link_info(Slice link); diff --git a/test/link.cpp b/test/link.cpp index a1b5a17b1..ced3a6830 100644 --- a/test/link.cpp +++ b/test/link.cpp @@ -6,6 +6,9 @@ // #include "td/telegram/LinkManager.h" +#include "td/telegram/td_api.h" + +#include "td/utils/common.h" #include "td/utils/tests.h" static void check_link(td::string url, td::string expected) { @@ -45,3 +48,175 @@ TEST(Link, check_link) { check_link("..", "http://../"); check_link("https://.", ""); } + +static void parse_internal_link(td::string url, td::td_api::object_ptr expected) { + // LOG(ERROR) << url; + auto result = td::LinkManager::parse_internal_link(url); + if (result != nullptr) { + auto object = result->get_internal_link_type_object(); + if (object->get_id() == td::td_api::internalLinkTypeMessageDraft::ID) { + static_cast(object.get())->text_->entities_.clear(); + } + ASSERT_STREQ(to_string(expected), to_string(object)); + } else { + ASSERT_TRUE(expected == nullptr); + } +} + +TEST(Link, parse_internal_link) { + auto background = [](td::string background_name) { + return td::td_api::make_object(background_name); + }; + auto message = [] { + return td::td_api::make_object(); + }; + auto message_draft = [](td::string text, bool contains_url) { + auto formatted_text = td::td_api::make_object(); + formatted_text->text_ = std::move(text); + return td::td_api::make_object(std::move(formatted_text), contains_url); + }; + auto unknown_deep_link = [] { + return td::td_api::make_object(); + }; + + parse_internal_link("t.me/levlam/1", message()); + parse_internal_link("telegram.me/levlam/1", message()); + parse_internal_link("telegram.dog/levlam/1", message()); + parse_internal_link("www.t.me/levlam/1", message()); + parse_internal_link("www%2etelegram.me/levlam/1", message()); + parse_internal_link("www%2Etelegram.dog/levlam/1", message()); + parse_internal_link("www%252Etelegram.dog/levlam/1", nullptr); + parse_internal_link("http://t.me/levlam/1", message()); + parse_internal_link("https://t.me/levlam/1", message()); + parse_internal_link("hTtp://www.t.me:443/levlam/1", message()); + parse_internal_link("httPs://t.me:80/levlam/1", message()); + parse_internal_link("https://t.me:200/levlam/1", nullptr); + parse_internal_link("http:t.me/levlam/1", nullptr); + parse_internal_link("t.dog/levlam/1", nullptr); + parse_internal_link("t.m/levlam/1", nullptr); + parse_internal_link("t.men/levlam/1", nullptr); + + parse_internal_link("tg:resolve?domain=username&post=12345&single", message()); + parse_internal_link("TG://resolve?domain=username&post=12345&single", message()); + parse_internal_link("TG://test@resolve?domain=username&post=12345&single", nullptr); + parse_internal_link("tg:resolve:80?domain=username&post=12345&single", nullptr); + parse_internal_link("tg:http://resolve?domain=username&post=12345&single", nullptr); + parse_internal_link("tg:https://resolve?domain=username&post=12345&single", nullptr); + parse_internal_link("tg:resolve?domain=&post=12345&single", unknown_deep_link()); + parse_internal_link("tg:resolve?domain=telegram&post=&single", unknown_deep_link()); + + parse_internal_link("t.me/username/12345?single", message()); + parse_internal_link("t.me/username/12345?asdasd", message()); + parse_internal_link("t.me/username/12345", message()); + parse_internal_link("t.me/username/12345/", message()); + parse_internal_link("t.me/username/12345#asdasd", message()); + parse_internal_link("t.me/username/12345//?single", message()); + parse_internal_link("t.me/username/12345/asdasd//asd/asd/asd/?single", message()); + parse_internal_link("t.me/username/1asdasdas/asdasd//asd/asd/asd/?single", message()); + parse_internal_link("t.me/username/asd", nullptr); + parse_internal_link("t.me/username/0", nullptr); + parse_internal_link("t.me/username/-12345", nullptr); + parse_internal_link("t.me//12345?single", nullptr); + parse_internal_link("https://telegram.dog/telegram/?single", nullptr); + + parse_internal_link("tg:privatepost?domain=username/12345&single", unknown_deep_link()); + parse_internal_link("tg:privatepost?channel=username/12345&single", unknown_deep_link()); + parse_internal_link("tg:privatepost?channel=username&msg_id=12345", message()); + + parse_internal_link("t.me/c/12345?single", nullptr); + parse_internal_link("t.me/c/1/c?single", nullptr); + parse_internal_link("t.me/c/c/1?single", nullptr); + parse_internal_link("t.me/c//1?single", nullptr); + parse_internal_link("t.me/c/12345/123?single", message()); + parse_internal_link("t.me/c/12345/123/asd/asd////?single", message()); + + parse_internal_link("tg:bg?color=111111#asdasd", background("111111")); + parse_internal_link("tg:bg?color=11111%31", background("111111")); + parse_internal_link("tg:bg?color=11111%20", background("11111%20")); + parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222")); + parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20", + background("111111-222222%20?rotation=180%20")); + parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222")); + parse_internal_link("tg:bg?gradient=abacaba", background("abacaba")); + parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3&rotation=4")); + parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3")); + parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3", unknown_deep_link()); + + parse_internal_link("tg:bg?color=111111#asdasd", background("111111")); + parse_internal_link("tg:bg?color=11111%31", background("111111")); + parse_internal_link("tg:bg?color=11111%20", background("11111%20")); + parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222")); + parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20", + background("111111-222222%20?rotation=180%20")); + parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222")); + parse_internal_link("tg:bg?gradient=abacaba", background("abacaba")); + parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12")); + parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3&rotation=4")); + parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3")); + parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3", unknown_deep_link()); + + parse_internal_link("%54.me/bg/111111#asdasd", background("111111")); + parse_internal_link("t.me/bg/11111%31", background("111111")); + parse_internal_link("t.me/bg/11111%20", background("11111%20")); + parse_internal_link("t.me/bg/111111-222222", background("111111-222222")); + parse_internal_link("t.me/bg/111111-222222%20?rotation=180%20", background("111111-222222%20?rotation=180%20")); + parse_internal_link("t.me/bg/111111~222222", background("111111~222222")); + parse_internal_link("t.me/bg/abacaba", background("abacaba")); + parse_internal_link("t.me/Bg/abacaba", nullptr); + parse_internal_link("t.me/bg/111111~222222#asdasd", background("111111~222222")); + parse_internal_link("t.me/bg/111111~222222?mode=12", background("111111~222222?mode=12")); + parse_internal_link("t.me/bg/111111~222222?mode=12&text=1", background("111111~222222?mode=12")); + parse_internal_link("t.me/bg/111111~222222?mode=12&mode=1", background("111111~222222?mode=12")); + parse_internal_link("t.me/bg/test?mode=12&rotation=4&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3&rotation=4")); + parse_internal_link("t.me/%62g/test/?mode=12&&&intensity=2&bg_color=3", + background("test?mode=12&intensity=2&bg_color=3")); + parse_internal_link("t.me/bg//", nullptr); + parse_internal_link("t.me/bg/%20/", background("%20")); + parse_internal_link("t.me/bg/", nullptr); + + parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true)); + parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("tg:msg_url?url=google.com&text=text", message_draft("google.com\ntext", true)); + parse_internal_link("tg:msg_url?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("tg:msg_url?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("tg:msg?url=google.com&text=text", message_draft("google.com\ntext", true)); + parse_internal_link("tg:msg?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("tg:msg?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("tg:msg?url=&text=\n\n\n\n\n\n\n\n", nullptr); + parse_internal_link("tg:msg?url=%20\n&text=", nullptr); + parse_internal_link("tg:msg?url=%20\n&text=google.com", message_draft("google.com", false)); + parse_internal_link("tg:msg?url=@&text=", message_draft(" @", false)); + parse_internal_link("tg:msg?url=&text=@", message_draft(" @", false)); + parse_internal_link("tg:msg?url=@&text=@", message_draft(" @\n@", true)); + parse_internal_link("tg:msg?url=%FF&text=1", nullptr); + + parse_internal_link("https://t.me/share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true)); + parse_internal_link("https://t.me/share?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("https://t.me/share?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true)); + parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true)); + parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=&text=\n\n\n\n\n\n\n\n", nullptr); + parse_internal_link("https://t.me/msg?url=%20\n&text=", nullptr); + parse_internal_link("https://t.me/msg?url=%20\n&text=google.com", message_draft("google.com", false)); + parse_internal_link("https://t.me/msg?url=@&text=", message_draft(" @", false)); + parse_internal_link("https://t.me/msg?url=&text=@", message_draft(" @", false)); + parse_internal_link("https://t.me/msg?url=@&text=@", message_draft(" @\n@", true)); + parse_internal_link("https://t.me/msg?url=%FF&text=1", nullptr); +}