mtproto_proxy_data.cpp 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "mtproto/mtproto_proxy_data.h"
  8. #include "base/qthelp_url.h"
  9. #include "base/qt/qt_string_view.h"
  10. namespace MTP {
  11. namespace {
  12. [[nodiscard]] bool IsHexMtprotoPassword(const QString &password) {
  13. const auto size = password.size();
  14. if (size < 32 || size % 2 == 1) {
  15. return false;
  16. }
  17. const auto bad = [](QChar ch) {
  18. const auto code = ch.unicode();
  19. return (code < 'a' || code > 'f')
  20. && (code < 'A' || code > 'F')
  21. && (code < '0' || code > '9');
  22. };
  23. const auto i = std::find_if(password.begin(), password.end(), bad);
  24. return (i == password.end());
  25. }
  26. [[nodiscard]] ProxyData::Status HexMtprotoPasswordStatus(
  27. const QString &password) {
  28. const auto size = password.size() / 2;
  29. const auto type1 = password[0].toLower();
  30. const auto type2 = password[1].toLower();
  31. const auto valid = (size == 16)
  32. || (size == 17 && (type1 == 'd') && (type2 == 'd'))
  33. || (size >= 21 && (type1 == 'e') && (type2 == 'e'));
  34. if (valid) {
  35. return ProxyData::Status::Valid;
  36. } else if (size < 16) {
  37. return ProxyData::Status::Invalid;
  38. }
  39. return ProxyData::Status::Unsupported;
  40. }
  41. [[nodiscard]] bytes::vector SecretFromHexMtprotoPassword(
  42. const QString &password) {
  43. Expects(password.size() % 2 == 0);
  44. const auto size = password.size() / 2;
  45. const auto fromHex = [](QChar ch) -> int {
  46. const auto code = int(ch.unicode());
  47. if (code >= '0' && code <= '9') {
  48. return (code - '0');
  49. } else if (code >= 'A' && code <= 'F') {
  50. return 10 + (code - 'A');
  51. } else if (ch >= 'a' && ch <= 'f') {
  52. return 10 + (code - 'a');
  53. }
  54. Unexpected("Code in ProxyData fromHex.");
  55. };
  56. auto result = bytes::vector(size);
  57. for (auto i = 0; i != size; ++i) {
  58. const auto high = fromHex(password[2 * i]);
  59. const auto low = fromHex(password[2 * i + 1]);
  60. if (high < 0 || low < 0) {
  61. return {};
  62. }
  63. result[i] = static_cast<bytes::type>(high * 16 + low);
  64. }
  65. return result;
  66. }
  67. [[nodiscard]] QStringView Base64UrlInner(const QString &password) {
  68. Expects(password.size() > 2);
  69. // Skip one or two '=' at the end of the string.
  70. return base::StringViewMid(password, 0, [&] {
  71. auto result = password.size();
  72. for (auto i = 0; i != 2; ++i) {
  73. const auto prev = result - 1;
  74. if (password[prev] != '=') {
  75. break;
  76. }
  77. result = prev;
  78. }
  79. return result;
  80. }());
  81. }
  82. [[nodiscard]] bool IsBase64UrlMtprotoPassword(const QString &password) {
  83. const auto size = password.size();
  84. if (size < 22 || size % 4 == 1) {
  85. return false;
  86. }
  87. const auto bad = [](QChar ch) {
  88. const auto code = ch.unicode();
  89. return (code < 'a' || code > 'z')
  90. && (code < 'A' || code > 'Z')
  91. && (code < '0' || code > '9')
  92. && (code != '_')
  93. && (code != '-');
  94. };
  95. const auto inner = Base64UrlInner(password);
  96. const auto begin = inner.data();
  97. const auto end = begin + inner.size();
  98. return (std::find_if(begin, end, bad) == end);
  99. }
  100. [[nodiscard]] ProxyData::Status Base64UrlMtprotoPasswordStatus(
  101. const QString &password) {
  102. const auto inner = Base64UrlInner(password);
  103. const auto size = (inner.size() * 3) / 4;
  104. const auto valid = (size == 16)
  105. || (size == 17
  106. && (password[0] == '3')
  107. && ((password[1] >= 'Q' && password[1] <= 'Z')
  108. || (password[1] >= 'a' && password[1] <= 'f')))
  109. || (size >= 21
  110. && (password[0] == '7')
  111. && (password[1] >= 'g')
  112. && (password[1] <= 'v'));
  113. if (size < 16) {
  114. return ProxyData::Status::Invalid;
  115. } else if (valid) {
  116. return ProxyData::Status::Valid;
  117. }
  118. return ProxyData::Status::Unsupported;
  119. }
  120. [[nodiscard]] bytes::vector SecretFromBase64UrlMtprotoPassword(
  121. const QString &password) {
  122. const auto result = QByteArray::fromBase64(
  123. password.toLatin1(),
  124. QByteArray::Base64UrlEncoding);
  125. return bytes::make_vector(bytes::make_span(result));
  126. }
  127. } // namespace
  128. bool ProxyData::valid() const {
  129. return status() == Status::Valid;
  130. }
  131. ProxyData::Status ProxyData::status() const {
  132. if (type == Type::None || host.isEmpty() || !port) {
  133. return Status::Invalid;
  134. } else if (type == Type::Mtproto) {
  135. return MtprotoPasswordStatus(password);
  136. }
  137. return Status::Valid;
  138. }
  139. bool ProxyData::supportsCalls() const {
  140. return (type == Type::Socks5);
  141. }
  142. bool ProxyData::tryCustomResolve() const {
  143. static const auto RegExp = QRegularExpression(
  144. QStringLiteral("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
  145. );
  146. return (type == Type::Socks5 || type == Type::Mtproto)
  147. && !qthelp::is_ipv6(host)
  148. && !RegExp.match(host).hasMatch();
  149. }
  150. bytes::vector ProxyData::secretFromMtprotoPassword() const {
  151. Expects(type == Type::Mtproto);
  152. if (IsHexMtprotoPassword(password)) {
  153. return SecretFromHexMtprotoPassword(password);
  154. } else if (IsBase64UrlMtprotoPassword(password)) {
  155. return SecretFromBase64UrlMtprotoPassword(password);
  156. }
  157. return {};
  158. }
  159. ProxyData::operator bool() const {
  160. return valid();
  161. }
  162. bool ProxyData::operator==(const ProxyData &other) const {
  163. if (!valid()) {
  164. return !other.valid();
  165. }
  166. return (type == other.type)
  167. && (host == other.host)
  168. && (port == other.port)
  169. && (user == other.user)
  170. && (password == other.password);
  171. }
  172. bool ProxyData::operator!=(const ProxyData &other) const {
  173. return !(*this == other);
  174. }
  175. bool ProxyData::ValidMtprotoPassword(const QString &password) {
  176. return MtprotoPasswordStatus(password) == Status::Valid;
  177. }
  178. ProxyData::Status ProxyData::MtprotoPasswordStatus(const QString &password) {
  179. if (IsHexMtprotoPassword(password)) {
  180. return HexMtprotoPasswordStatus(password);
  181. } else if (IsBase64UrlMtprotoPassword(password)) {
  182. return Base64UrlMtprotoPasswordStatus(password);
  183. }
  184. return Status::Invalid;
  185. }
  186. ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {
  187. if (!proxy.tryCustomResolve()
  188. || ipIndex < 0
  189. || ipIndex >= proxy.resolvedIPs.size()) {
  190. return proxy;
  191. }
  192. return {
  193. proxy.type,
  194. proxy.resolvedIPs[ipIndex],
  195. proxy.port,
  196. proxy.user,
  197. proxy.password
  198. };
  199. }
  200. QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
  201. if (proxy.type == ProxyData::Type::None) {
  202. return QNetworkProxy::DefaultProxy;
  203. } else if (proxy.type == ProxyData::Type::Mtproto) {
  204. return QNetworkProxy::NoProxy;
  205. }
  206. return QNetworkProxy(
  207. (proxy.type == ProxyData::Type::Socks5
  208. ? QNetworkProxy::Socks5Proxy
  209. : QNetworkProxy::HttpProxy),
  210. proxy.host,
  211. proxy.port,
  212. proxy.user,
  213. proxy.password);
  214. }
  215. } // namespace MTP