 8e89486fbc
			
		
	
	
		8e89486fbc
		
	
	
	
	
		
			
			All errors that lead to MTP request resending by default error handler now can be handled differently. For example inline bot requests are not being resent on 5XX error codes. + extensive use of auto keyword in MTP types handling.
		
			
				
	
	
		
			218 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop version of Telegram messaging app, see https://telegram.org
 | |
| 
 | |
| Telegram Desktop is free software: you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation, either version 3 of the License, or
 | |
| (at your option) any later version.
 | |
| 
 | |
| It is distributed in the hope that it will be useful,
 | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | |
| GNU General Public License for more details.
 | |
| 
 | |
| In addition, as a special exception, the copyright holders give permission
 | |
| to link the code of portions of this program with the OpenSSL library.
 | |
| 
 | |
| Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
 | |
| Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
 | |
| */
 | |
| #include "stdafx.h"
 | |
| 
 | |
| #include "mtproto/connection_http.h"
 | |
| 
 | |
| namespace MTP {
 | |
| namespace internal {
 | |
| 
 | |
| mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) {
 | |
| 	QByteArray response = reply->readAll();
 | |
| 	TCP_LOG(("HTTP Info: read %1 bytes").arg(response.size()));
 | |
| 
 | |
| 	if (response.isEmpty()) return mtpBuffer();
 | |
| 
 | |
| 	if (response.size() & 0x03 || response.size() < 8) {
 | |
| 		LOG(("HTTP Error: bad response size %1").arg(response.size()));
 | |
| 		return mtpBuffer(1, -500);
 | |
| 	}
 | |
| 
 | |
| 	mtpBuffer data(response.size() >> 2);
 | |
| 	memcpy(data.data(), response.constData(), response.size());
 | |
| 
 | |
| 	return data;
 | |
| }
 | |
| 
 | |
| bool HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe bad key"
 | |
| 	bool mayBeBadKey = false;
 | |
| 
 | |
| 	QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
 | |
| 	if (statusCode.isValid()) {
 | |
| 		int status = statusCode.toInt();
 | |
| 		mayBeBadKey = (status == 410);
 | |
| 		if (status == 429) {
 | |
| 			LOG(("Protocol Error: 429 flood code returned!"));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	switch (reply->error()) {
 | |
| 	case QNetworkReply::ConnectionRefusedError: LOG(("HTTP Error: connection refused - %1").arg(reply->errorString())); break;
 | |
| 	case QNetworkReply::RemoteHostClosedError: LOG(("HTTP Error: remote host closed - %1").arg(reply->errorString())); break;
 | |
| 	case QNetworkReply::HostNotFoundError: LOG(("HTTP Error: host not found - %2").arg(reply->error()).arg(reply->errorString())); break;
 | |
| 	case QNetworkReply::TimeoutError: LOG(("HTTP Error: timeout - %2").arg(reply->error()).arg(reply->errorString())); break;
 | |
| 	case QNetworkReply::OperationCanceledError: LOG(("HTTP Error: cancelled - %2").arg(reply->error()).arg(reply->errorString())); break;
 | |
| 	case QNetworkReply::SslHandshakeFailedError:
 | |
| 	case QNetworkReply::TemporaryNetworkFailureError:
 | |
| 	case QNetworkReply::NetworkSessionFailedError:
 | |
| 	case QNetworkReply::BackgroundRequestNotAllowedError:
 | |
| 	case QNetworkReply::UnknownNetworkError: LOG(("HTTP Error: network error %1 - %2").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:LOG(("HTTP Error: proxy error %1 - %2").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: LOG(("HTTP Error: content error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
 | |
| 
 | |
| 		// protocol errors
 | |
| 	case QNetworkReply::ProtocolUnknownError:
 | |
| 	case QNetworkReply::ProtocolInvalidOperationError:
 | |
| 	case QNetworkReply::ProtocolFailure: LOG(("HTTP Error: protocol error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
 | |
| 	};
 | |
| 	TCP_LOG(("HTTP Error %1, restarting! - %2").arg(reply->error()).arg(reply->errorString()));
 | |
| 
 | |
| 	return mayBeBadKey;
 | |
| }
 | |
| 
 | |
| HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread)
 | |
| , status(WaitingHttp)
 | |
| , httpNonce(rand_value<MTPint128>())
 | |
| , _flags(0) {
 | |
| 	manager.moveToThread(thread);
 | |
| 	App::setProxySettings(manager);
 | |
| }
 | |
| 
 | |
| void HTTPConnection::sendData(mtpBuffer &buffer) {
 | |
| 	if (status == FinishedWork) return;
 | |
| 
 | |
| 	if (buffer.size() < 3) {
 | |
| 		LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
 | |
| 		TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
 | |
| 		emit error();
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
 | |
| 
 | |
| 	QNetworkRequest request(address);
 | |
| 	request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
 | |
| 	request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
 | |
| 
 | |
| 	TCP_LOG(("HTTP Info: sending %1 len request %2").arg(requestSize).arg(Logs::mb(&buffer[2], requestSize).str()));
 | |
| 	requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
 | |
| }
 | |
| 
 | |
| void HTTPConnection::disconnectFromServer() {
 | |
| 	if (status == FinishedWork) return;
 | |
| 	status = FinishedWork;
 | |
| 
 | |
| 	Requests copy = requests;
 | |
| 	requests.clear();
 | |
| 	for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) {
 | |
| 		(*i)->abort();
 | |
| 		(*i)->deleteLater();
 | |
| 	}
 | |
| 
 | |
| 	disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
 | |
| 
 | |
| 	address = QUrl();
 | |
| }
 | |
| 
 | |
| void HTTPConnection::connectHttp(const QString &addr, int32 p, MTPDdcOption::Flags flags) {
 | |
| 	address = QUrl(((flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
 | |
| 	TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
 | |
| 	connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
 | |
| 
 | |
| 	_flags = flags;
 | |
| 
 | |
| 	mtpBuffer buffer(preparePQFake(httpNonce));
 | |
| 
 | |
| 	DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
 | |
| 
 | |
| 	sendData(buffer);
 | |
| }
 | |
| 
 | |
| bool HTTPConnection::isConnected() const {
 | |
| 	return (status == UsingHttp);
 | |
| }
 | |
| 
 | |
| void HTTPConnection::requestFinished(QNetworkReply *reply) {
 | |
| 	if (status == FinishedWork) return;
 | |
| 
 | |
| 	reply->deleteLater();
 | |
| 	if (reply->error() == QNetworkReply::NoError) {
 | |
| 		requests.remove(reply);
 | |
| 
 | |
| 		mtpBuffer data = handleResponse(reply);
 | |
| 		if (data.size() == 1) {
 | |
| 			emit error();
 | |
| 		} else if (!data.isEmpty()) {
 | |
| 			if (status == UsingHttp) {
 | |
| 				receivedQueue.push_back(data);
 | |
| 				emit receivedData();
 | |
| 			} else {
 | |
| 				try {
 | |
| 					auto res_pq = readPQFakeReply(data);
 | |
| 					const auto &res_pq_data(res_pq.c_resPQ());
 | |
| 					if (res_pq_data.vnonce == httpNonce) {
 | |
| 						DEBUG_LOG(("Connection Info: HTTP/%1-transport connected by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
 | |
| 						status = UsingHttp;
 | |
| 						emit connected();
 | |
| 					}
 | |
| 				} catch (Exception &e) {
 | |
| 					DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what()));
 | |
| 					emit error();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (!requests.remove(reply)) {
 | |
| 			return;
 | |
| 		}
 | |
| 
 | |
| 		bool mayBeBadKey = handleError(reply) && _sentEncrypted;
 | |
| 
 | |
| 		emit error(mayBeBadKey);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool HTTPConnection::usingHttpWait() {
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool HTTPConnection::needHttpWait() {
 | |
| 	return requests.isEmpty();
 | |
| }
 | |
| 
 | |
| int32 HTTPConnection::debugState() const {
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| QString HTTPConnection::transport() const {
 | |
| 	if (status == UsingHttp) {
 | |
| 		return qsl("HTTP");
 | |
| 	} else {
 | |
| 		return QString();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| } // namespace internal
 | |
| } // namespace MTP
 |