403 lines
11 KiB
C++
403 lines
11 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "mtproto/transport/connection_http.h"
|
|
|
|
#include "mtproto/protocol/mtproto_binary.h"
|
|
#include "mtproto/proxy/diagnostics.h"
|
|
#include "mtproto/runtime/runtime_environment.h"
|
|
#include "base/invoke_queued.h"
|
|
#include "base/random.h"
|
|
#include "base/qthelp_url.h"
|
|
|
|
namespace MTP {
|
|
namespace details {
|
|
namespace {
|
|
|
|
constexpr auto kForceHttpPort = 80;
|
|
constexpr auto kFullConnectionTimeout = crl::time(8000);
|
|
|
|
ProxyConnectionError ReplyProxyConnectionError(
|
|
QNetworkReply::NetworkError error) {
|
|
switch (error) {
|
|
case QNetworkReply::HostNotFoundError:
|
|
case QNetworkReply::ProxyNotFoundError:
|
|
return ProxyConnectionError::HostNotFound;
|
|
|
|
case QNetworkReply::ConnectionRefusedError:
|
|
case QNetworkReply::ProxyConnectionRefusedError:
|
|
return ProxyConnectionError::ConnectionRefused;
|
|
|
|
case QNetworkReply::TimeoutError:
|
|
case QNetworkReply::ProxyTimeoutError:
|
|
return ProxyConnectionError::Timeout;
|
|
|
|
case QNetworkReply::ProxyAuthenticationRequiredError:
|
|
case QNetworkReply::AuthenticationRequiredError:
|
|
return ProxyConnectionError::Authentication;
|
|
|
|
case QNetworkReply::ProxyConnectionClosedError:
|
|
case QNetworkReply::RemoteHostClosedError:
|
|
return ProxyConnectionError::RemoteClosed;
|
|
|
|
case QNetworkReply::ProtocolUnknownError:
|
|
case QNetworkReply::ProtocolInvalidOperationError:
|
|
case QNetworkReply::ProtocolFailure:
|
|
return ProxyConnectionError::ProxyProtocol;
|
|
|
|
case QNetworkReply::TemporaryNetworkFailureError:
|
|
case QNetworkReply::NetworkSessionFailedError:
|
|
case QNetworkReply::BackgroundRequestNotAllowedError:
|
|
case QNetworkReply::UnknownNetworkError:
|
|
return ProxyConnectionError::Network;
|
|
}
|
|
return ProxyConnectionError::Unknown;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
HttpConnection::HttpConnection(
|
|
not_null<RuntimeEnvironment*> runtime,
|
|
QThread *thread,
|
|
const ProxyData &proxy)
|
|
: AbstractConnection(runtime, thread, proxy)
|
|
, _checkNonce(base::RandomValue<MTPint128>()) {
|
|
_manager.moveToThread(thread);
|
|
_manager.setProxy(ToNetworkProxy(proxy));
|
|
}
|
|
|
|
ConnectionPointer HttpConnection::clone(const ProxyData &proxy) {
|
|
return ConnectionPointer::New<HttpConnection>(_runtime, thread(), proxy);
|
|
}
|
|
|
|
void HttpConnection::sendData(mtpBuffer &&buffer, SendDataContext) {
|
|
Expects(buffer.size() > 2);
|
|
|
|
if (_status == Status::Finished) {
|
|
return;
|
|
}
|
|
|
|
int32 requestSize = (buffer.size() - 2) * sizeof(mtpPrime);
|
|
|
|
QNetworkRequest request(url());
|
|
request.setHeader(
|
|
QNetworkRequest::ContentLengthHeader,
|
|
QVariant(requestSize));
|
|
request.setHeader(
|
|
QNetworkRequest::ContentTypeHeader,
|
|
QVariant(u"application/x-www-form-urlencoded"_q));
|
|
|
|
CONNECTION_LOG_INFO(u"Sending %1 len request."_q.arg(requestSize));
|
|
auto payload = QByteArray(requestSize, Qt::Uninitialized);
|
|
binary::Copy(
|
|
bytes::make_detached_span(payload),
|
|
bytes::make_span(buffer).subspan(2 * sizeof(mtpPrime), requestSize));
|
|
_requests.insert(_manager.post(request, payload));
|
|
}
|
|
|
|
void HttpConnection::disconnectFromServer() {
|
|
if (_status == Status::Finished) return;
|
|
_status = Status::Finished;
|
|
|
|
const auto requests = base::take(_requests);
|
|
for (const auto request : requests) {
|
|
request->abort();
|
|
request->deleteLater();
|
|
}
|
|
|
|
disconnect(
|
|
&_manager,
|
|
&QNetworkAccessManager::finished,
|
|
this,
|
|
&HttpConnection::requestFinished);
|
|
}
|
|
|
|
void HttpConnection::connectToServer(
|
|
const QString &address,
|
|
int port,
|
|
const bytes::vector &protocolSecret,
|
|
int16 protocolDcId,
|
|
bool protocolForFiles,
|
|
ConnectionStartContext context) {
|
|
_mtproxyAttempt = context.mtproxyAttempt;
|
|
_address = address;
|
|
connect(
|
|
&_manager,
|
|
&QNetworkAccessManager::finished,
|
|
this,
|
|
&HttpConnection::requestFinished);
|
|
|
|
auto buffer = preparePQFake(_checkNonce);
|
|
|
|
if (Logs::DebugEnabled()) {
|
|
_debugId = u"%1(dc:%2,%3)"_q
|
|
.arg(_debugId.toInt())
|
|
.arg(ProtocolDcDebugId(protocolDcId), url().toDisplayString());
|
|
}
|
|
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Connecting,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"connecting to proxy"_q,
|
|
});
|
|
_pingTime = crl::now();
|
|
sendData(std::move(buffer), {});
|
|
}
|
|
|
|
mtpBuffer HttpConnection::handleResponse(QNetworkReply *reply) {
|
|
QByteArray response = reply->readAll();
|
|
CONNECTION_LOG_INFO(u"Read %1 bytes."_q.arg(response.size()));
|
|
|
|
if (response.isEmpty()) return mtpBuffer();
|
|
|
|
if (response.size() & 0x03 || response.size() < 8) {
|
|
CONNECTION_LOG_ERROR(u"Bad response size %1."_q.arg(response.size()));
|
|
return mtpBuffer(1, -500);
|
|
}
|
|
|
|
mtpBuffer data(response.size() >> 2);
|
|
binary::Copy(
|
|
bytes::make_span(data),
|
|
bytes::make_span(response.constData(), response.size()));
|
|
|
|
return data;
|
|
}
|
|
|
|
// Returns "maybe bad key".
|
|
qint32 HttpConnection::handleError(QNetworkReply *reply) {
|
|
auto result = qint32(kErrorCodeOther);
|
|
|
|
QVariant statusCode = reply->attribute(
|
|
QNetworkRequest::HttpStatusCodeAttribute);
|
|
if (statusCode.isValid()) {
|
|
int status = statusCode.toInt();
|
|
result = -status;
|
|
}
|
|
|
|
switch (reply->error()) {
|
|
case QNetworkReply::ConnectionRefusedError:
|
|
CONNECTION_LOG_ERROR(u"Connection refused - %1."_q
|
|
.arg(reply->errorString()));
|
|
break;
|
|
case QNetworkReply::RemoteHostClosedError:
|
|
CONNECTION_LOG_ERROR(u"Remote host closed - %1."_q
|
|
.arg(reply->errorString()));
|
|
break;
|
|
case QNetworkReply::HostNotFoundError:
|
|
CONNECTION_LOG_ERROR(u"Host not found - %1."_q
|
|
.arg(reply->errorString()));
|
|
break;
|
|
case QNetworkReply::TimeoutError:
|
|
CONNECTION_LOG_ERROR(u"Timeout - %1."_q
|
|
.arg(reply->errorString()));
|
|
break;
|
|
case QNetworkReply::OperationCanceledError:
|
|
CONNECTION_LOG_ERROR(u"Cancelled - %1."_q
|
|
.arg(reply->errorString()));
|
|
break;
|
|
case QNetworkReply::SslHandshakeFailedError:
|
|
case QNetworkReply::TemporaryNetworkFailureError:
|
|
case QNetworkReply::NetworkSessionFailedError:
|
|
case QNetworkReply::BackgroundRequestNotAllowedError:
|
|
case QNetworkReply::UnknownNetworkError:
|
|
CONNECTION_LOG_ERROR(u"Network error %1 - %2."_q
|
|
.arg(reply->error())
|
|
.arg(reply->errorString()));
|
|
break;
|
|
|
|
// proxy errors (101-199):
|
|
case QNetworkReply::ProxyConnectionRefusedError:
|
|
case QNetworkReply::ProxyConnectionClosedError:
|
|
case QNetworkReply::ProxyNotFoundError:
|
|
case QNetworkReply::ProxyTimeoutError:
|
|
case QNetworkReply::ProxyAuthenticationRequiredError:
|
|
case QNetworkReply::UnknownProxyError:
|
|
CONNECTION_LOG_ERROR(u"Proxy error %1 - %2."_q
|
|
.arg(reply->error())
|
|
.arg(reply->errorString()));
|
|
break;
|
|
|
|
// content errors (201-299):
|
|
case QNetworkReply::ContentAccessDenied:
|
|
case QNetworkReply::ContentOperationNotPermittedError:
|
|
case QNetworkReply::ContentNotFoundError:
|
|
case QNetworkReply::AuthenticationRequiredError:
|
|
case QNetworkReply::ContentReSendError:
|
|
case QNetworkReply::UnknownContentError:
|
|
CONNECTION_LOG_ERROR(u"Content error %1 - %2."_q
|
|
.arg(reply->error())
|
|
.arg(reply->errorString()));
|
|
break;
|
|
|
|
// protocol errors
|
|
case QNetworkReply::ProtocolUnknownError:
|
|
case QNetworkReply::ProtocolInvalidOperationError:
|
|
case QNetworkReply::ProtocolFailure:
|
|
CONNECTION_LOG_ERROR(u"Protocol error %1 - %2."_q
|
|
.arg(reply->error())
|
|
.arg(reply->errorString()));
|
|
break;
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
bool HttpConnection::isConnected() const {
|
|
return (_status == Status::Ready);
|
|
}
|
|
|
|
void HttpConnection::requestFinished(QNetworkReply *reply) {
|
|
if (_status == Status::Finished) return;
|
|
|
|
reply->deleteLater();
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
_requests.remove(reply);
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::TelegramCheck,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"checking telegram through proxy"_q,
|
|
});
|
|
|
|
mtpBuffer data = handleResponse(reply);
|
|
if (data.size() == 1) {
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"bad proxy response"_q,
|
|
});
|
|
error(data[0]);
|
|
} else if (!data.isEmpty()) {
|
|
if (_status == Status::Ready) {
|
|
_receivedQueue.push_back(data);
|
|
receivedData();
|
|
} else if (const auto res_pq = readPQFakeReply(data)) {
|
|
const auto &data = res_pq->c_resPQ();
|
|
if (data.vnonce() == _checkNonce) {
|
|
CONNECTION_LOG_INFO(
|
|
"HTTP-transport connected by pq-response.");
|
|
_status = Status::Ready;
|
|
_pingTime = crl::now() - _pingTime;
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Connected,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"proxy connected"_q,
|
|
});
|
|
connected();
|
|
} else {
|
|
CONNECTION_LOG_ERROR(
|
|
"Wrong nonce in HTTP fake pq-response.");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"wrong nonce in proxy response"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
}
|
|
} else {
|
|
CONNECTION_LOG_ERROR(
|
|
"Could not parse HTTP fake pq-response.");
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ProxyConnectionError::BadResponse,
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"could not parse proxy response"_q,
|
|
});
|
|
error(kErrorCodeOther);
|
|
}
|
|
}
|
|
} else {
|
|
if (!_requests.remove(reply)) {
|
|
return;
|
|
}
|
|
|
|
ReportProxyEvent(_runtime, {
|
|
.phase = ProxyDiagnosticsPhase::Failed,
|
|
.error = ReplyProxyConnectionError(reply->error()),
|
|
.attempt = _mtproxyAttempt,
|
|
.proxy = _proxy,
|
|
.transport = u"HTTP"_q,
|
|
.connectionId = _debugId,
|
|
.message = u"proxy request failed"_q,
|
|
});
|
|
error(handleError(reply));
|
|
}
|
|
}
|
|
|
|
crl::time HttpConnection::pingTime() const {
|
|
return isConnected() ? _pingTime : crl::time(0);
|
|
}
|
|
|
|
crl::time HttpConnection::fullConnectTimeout() const {
|
|
return kFullConnectionTimeout;
|
|
}
|
|
|
|
auto HttpConnection::serviceRequest() const -> TransportServiceRequest {
|
|
return TransportServiceRequest::HttpWait;
|
|
}
|
|
|
|
bool HttpConnection::serviceRequestNeeded(
|
|
TransportServiceRequest request) const {
|
|
return (request == TransportServiceRequest::HttpWait)
|
|
&& _requests.isEmpty();
|
|
}
|
|
|
|
int32 HttpConnection::debugState() const {
|
|
return -1;
|
|
}
|
|
|
|
QString HttpConnection::transport() const {
|
|
if (!isConnected()) {
|
|
return QString();
|
|
}
|
|
auto result = u"HTTP"_q;
|
|
if (qthelp::is_ipv6(_address)) {
|
|
result += u"/IPv6"_q;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QString HttpConnection::tag() const {
|
|
auto result = u"HTTP"_q;
|
|
if (qthelp::is_ipv6(_address)) {
|
|
result += u"/IPv6"_q;
|
|
} else {
|
|
result += u"/IPv4"_q;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QUrl HttpConnection::url() const {
|
|
const auto pattern = qthelp::is_ipv6(_address)
|
|
? u"http://[%1]:%2/api"_q
|
|
: u"http://%1:%2/api"_q;
|
|
|
|
// Not endpoint.port - always 80 port for http transport.
|
|
return QUrl(pattern.arg(_address).arg(kForceHttpPort));
|
|
}
|
|
|
|
} // namespace details
|
|
} // namespace MTP
|