diff --git a/Telegram/SourceFiles/mtproto/special_config_request.cpp b/Telegram/SourceFiles/mtproto/special_config_request.cpp index 921a1d567e..439a91cc32 100644 --- a/Telegram/SourceFiles/mtproto/special_config_request.cpp +++ b/Telegram/SourceFiles/mtproto/special_config_request.cpp @@ -16,6 +16,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace MTP { namespace { +constexpr auto kSendNextTimeout = TimeMs(1000); + constexpr auto kPublicKey = str_const("\ -----BEGIN RSA PUBLIC KEY-----\n\ MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\ @@ -27,6 +29,9 @@ Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\ -----END RSA PUBLIC KEY-----\ "); +constexpr auto kUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36"; + bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) { auto result = false; for (const auto &prefix : rules.split(',')) { @@ -41,8 +46,85 @@ bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) { return result; } +QByteArray ParseDnsResponse(const QByteArray &response) { + // Read and store to "entries" map all the data bytes from the response: + // { .., + // "Answer": [ + // { .., "data": "bytes1", .. }, + // { .., "data": "bytes2", .. } + // ], + // .. } + auto entries = QMap(); + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + auto document = QJsonDocument::fromJson(response, &error); + if (error.error != QJsonParseError::NoError) { + LOG(("Config Error: Failed to parse dns response JSON, error: %1" + ).arg(error.errorString())); + } else if (!document.isObject()) { + LOG(("Config Error: Not an object received in dns response JSON.")); + } else { + auto response = document.object(); + auto answerIt = response.find(qsl("Answer")); + if (answerIt == response.constEnd()) { + LOG(("Config Error: Could not find Answer " + "in dns response JSON.")); + } else if (!(*answerIt).isArray()) { + LOG(("Config Error: Not an array received " + "in Answer in dns response JSON.")); + } else { + for (auto elem : (*answerIt).toArray()) { + if (!elem.isObject()) { + LOG(("Config Error: Not an object found " + "in Answer array in dns response JSON.")); + } else { + auto object = elem.toObject(); + auto dataIt = object.find(qsl("data")); + if (dataIt == object.constEnd()) { + LOG(("Config Error: Could not find data " + "in Answer array entry in dns response JSON.")); + } else if (!(*dataIt).isString()) { + LOG(("Config Error: Not a string data found " + "in Answer array entry in dns response JSON.")); + } else { + auto data = (*dataIt).toString(); + entries.insertMulti(INT_MAX - data.size(), data); + } + } + } + } + } + return QStringList(entries.values()).join(QString()).toLatin1(); +} + } // namespace +SpecialConfigRequest::Request::Request(not_null reply) +: reply(reply.get()) { +} + +SpecialConfigRequest::Request::Request(Request &&other) +: reply(base::take(other.reply)) { +} + +auto SpecialConfigRequest::Request::operator=(Request &&other) -> Request& { + if (reply != other.reply) { + destroy(); + reply = base::take(other.reply); + } + return *this; +} + +void SpecialConfigRequest::Request::destroy() { + if (const auto value = base::take(reply)) { + value->deleteLater(); + value->abort(); + } +} + +SpecialConfigRequest::Request::~Request() { + destroy(); +} + SpecialConfigRequest::SpecialConfigRequest( base::lambda reply) { + const auto result = finalizeRequest(reply); + switch (type) { + case Type::App: handleResponse(result); break; + case Type::Dns: handleResponse(ParseDnsResponse(result)); break; + default: Unexpected("Type in SpecialConfigRequest::requestFinished."); + } } -void SpecialConfigRequest::appFinished() { - if (!_appReply) { - return; +QByteArray SpecialConfigRequest::finalizeRequest( + not_null reply) { + if (reply->error() != QNetworkReply::NoError) { + LOG(("Config Error: Failed to get response from %1, error: %2 (%3)" + ).arg(reply->request().url().toDisplayString() + ).arg(reply->errorString() + ).arg(reply->error())); } - auto result = _appReply->readAll(); - _appReply.release()->deleteLater(); - handleResponse(result); -} - -void SpecialConfigRequest::dnsFinished() { - if (!_dnsReply) { - return; - } - if (_dnsReply->error() != QNetworkReply::NoError) { - LOG(("Config Error: Failed to get dns response JSON, error: %1 (%2)").arg(_dnsReply->errorString()).arg(_dnsReply->error())); - } - auto result = _dnsReply->readAll(); - _dnsReply.release()->deleteLater(); - - // Read and store to "entries" map all the data bytes from this response: - // { .., "Answer": [ { .., "data": "bytes1", .. }, { .., "data": "bytes2", .. } ], .. } - auto entries = QMap(); - auto error = QJsonParseError { 0, QJsonParseError::NoError }; - auto document = QJsonDocument::fromJson(result, &error); - if (error.error != QJsonParseError::NoError) { - LOG(("Config Error: Failed to parse dns response JSON, error: %1").arg(error.errorString())); - } else if (!document.isObject()) { - LOG(("Config Error: Not an object received in dns response JSON.")); - } else { - auto response = document.object(); - auto answerIt = response.find(qsl("Answer")); - if (answerIt == response.constEnd()) { - LOG(("Config Error: Could not find Answer in dns response JSON.")); - } else if (!(*answerIt).isArray()) { - LOG(("Config Error: Not an array received in Answer in dns response JSON.")); - } else { - for (auto elem : (*answerIt).toArray()) { - if (!elem.isObject()) { - LOG(("Config Error: Not an object found in Answer array in dns response JSON.")); - } else { - auto object = elem.toObject(); - auto dataIt = object.find(qsl("data")); - if (dataIt == object.constEnd()) { - LOG(("Config Error: Could not find data in Answer array entry in dns response JSON.")); - } else if (!(*dataIt).isString()) { - LOG(("Config Error: Not a string data found in Answer array entry in dns response JSON.")); - } else { - auto data = (*dataIt).toString(); - entries.insertMulti(INT_MAX - data.size(), data); - } - } - } - } - } - auto text = QStringList(entries.values()).join(QString()); - handleResponse(text.toLatin1()); + const auto result = reply->readAll(); + const auto from = ranges::remove( + _requests, + reply, + [](const Request &request) { return request.reply; }); + _requests.erase(from, end(_requests)); + return result; } bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) { @@ -270,13 +344,4 @@ void SpecialConfigRequest::handleResponse(const QByteArray &bytes) { } } -SpecialConfigRequest::~SpecialConfigRequest() { - if (_appReply) { - _appReply->abort(); - } - if (_dnsReply) { - _dnsReply->abort(); - } -} - } // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/special_config_request.h b/Telegram/SourceFiles/mtproto/special_config_request.h index c1d78bf410..b44f1d9ef3 100644 --- a/Telegram/SourceFiles/mtproto/special_config_request.h +++ b/Telegram/SourceFiles/mtproto/special_config_request.h @@ -21,13 +21,31 @@ public: bytes::const_span secret)> callback, const QString &phone); - ~SpecialConfigRequest(); - private: - void performAppRequest(); - void performDnsRequest(); - void appFinished(); - void dnsFinished(); + enum class Type { + App, + Dns, + }; + struct Attempt { + Type type; + QString domain; + }; + struct Request { + Request(not_null reply); + Request(Request &&other); + Request &operator=(Request &&other); + ~Request(); + + void destroy(); + + QPointer reply; + + }; + + void sendNextRequest(); + void performRequest(const Attempt &attempt); + void requestFinished(Type type, not_null reply); + QByteArray finalizeRequest(not_null reply); void handleResponse(const QByteArray &bytes); bool decryptSimpleConfig(const QByteArray &bytes); @@ -40,11 +58,8 @@ private: MTPhelp_ConfigSimple _simpleConfig; QNetworkAccessManager _manager; - std::unique_ptr _appReply; - std::unique_ptr _dnsReply; - - std::unique_ptr _localOptions; - std::unique_ptr _localInstance; + std::vector _attempts; + std::vector _requests; };