1 #include "webclient.hpp"
2 #include "kristall.hpp"
3
4 #include <QNetworkRequest>
5 #include <QNetworkReply>
6
WebClient()7 WebClient::WebClient() :
8 ProtocolHandler(nullptr),
9 current_reply(nullptr)
10 {
11 manager.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
12
13 emit this->requestStateChange(RequestState::None);
14 }
15
~WebClient()16 WebClient::~WebClient()
17 {
18
19 }
20
supportsScheme(const QString & scheme) const21 bool WebClient::supportsScheme(const QString &scheme) const
22 {
23 return (scheme == "https") or (scheme == "http");
24 }
25
startRequest(const QUrl & url,RequestOptions options)26 bool WebClient::startRequest(const QUrl &url, RequestOptions options)
27 {
28 if(url.scheme() != "http" and url.scheme() != "https")
29 return false;
30
31 if(this->current_reply != nullptr)
32 return true;
33
34 emit this->requestStateChange(RequestState::StartedWeb);
35
36 this->options = options;
37 this->body.clear();
38
39 QNetworkRequest request(url);
40
41 auto ssl_config = request.sslConfiguration();
42 // ssl_config.setProtocol(QSsl::TlsV1_2);
43 if(kristall::globals().trust.https.enable_ca)
44 ssl_config.setCaCertificates(QSslConfiguration::systemCaCertificates());
45 else
46 ssl_config.setCaCertificates(QList<QSslCertificate> { });
47
48 if(this->current_identity.isValid()) {
49 ssl_config.setLocalCertificate(this->current_identity.certificate);
50 ssl_config.setPrivateKey(this->current_identity.private_key);
51 }
52
53 // request.setMaximumRedirectsAllowed(5);
54 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
55 request.setSslConfiguration(ssl_config);
56
57 this->manager.clearAccessCache();
58 this->manager.clearConnectionCache();
59 this->current_reply = manager.get(request);
60 if(this->current_reply == nullptr)
61 return false;
62
63 this->suppress_socket_tls_error = true;
64
65 connect(this->current_reply, &QNetworkReply::readyRead, this, &WebClient::on_data);
66 connect(this->current_reply, &QNetworkReply::finished, this, &WebClient::on_finished);
67 connect(this->current_reply, &QNetworkReply::sslErrors, this, &WebClient::on_sslErrors);
68 connect(this->current_reply, &QNetworkReply::redirected, this, &WebClient::on_redirected);
69
70 return true;
71 }
72
isInProgress() const73 bool WebClient::isInProgress() const
74 {
75 return (this->current_reply != nullptr);
76 }
77
cancelRequest()78 bool WebClient::cancelRequest()
79 {
80 if(this->current_reply != nullptr)
81 {
82 this->current_reply->abort();
83 this->current_reply = nullptr;
84 }
85 this->body.clear();
86 return true;
87 }
88
enableClientCertificate(const CryptoIdentity & ident)89 bool WebClient::enableClientCertificate(const CryptoIdentity &ident)
90 {
91 current_identity = ident;
92 return true;
93 }
94
disableClientCertificate()95 void WebClient::disableClientCertificate()
96 {
97 current_identity = CryptoIdentity();
98 }
99
on_data()100 void WebClient::on_data()
101 {
102 this->body.append(this->current_reply->readAll());
103 emit this->requestProgress(this->body.size());
104 }
105
on_finished()106 void WebClient::on_finished()
107 {
108 emit this->requestStateChange(RequestState::None);
109
110 emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate());
111
112 auto * const reply = this->current_reply;
113 this->current_reply = nullptr;
114
115 reply->deleteLater();
116
117 if(reply->error() != QNetworkReply::NoError)
118 {
119 NetworkError error = UnknownError;
120 switch(reply->error())
121 {
122 case QNetworkReply::ConnectionRefusedError: error = ConnectionRefused; break;
123 case QNetworkReply::RemoteHostClosedError: error = ProtocolViolation; break;
124 case QNetworkReply::HostNotFoundError: error = HostNotFound; break;
125 case QNetworkReply::TimeoutError: error = Timeout; break;
126 case QNetworkReply::SslHandshakeFailedError: error = TlsFailure; break;
127
128 case QNetworkReply::ContentAccessDenied: error = Unauthorized; break;
129 case QNetworkReply::ContentOperationNotPermittedError: error = BadRequest; break;
130 case QNetworkReply::ContentNotFoundError: error = ResourceNotFound; break;
131 case QNetworkReply::AuthenticationRequiredError: error = Unauthorized; break;
132 case QNetworkReply::ContentGoneError: error = ResourceNotFound; break;
133
134 case QNetworkReply::InternalServerError: error = InternalServerError; break;
135 case QNetworkReply::OperationNotImplementedError: error = InternalServerError; break;
136 case QNetworkReply::ServiceUnavailableError: error = InternalServerError; break;
137 default:
138 qDebug() << "Unhandled server error:" << reply->error();
139 break;
140 }
141
142 qDebug() << "web network error" << reply->errorString();
143 qDebug() << this->body;
144
145 if(not this->suppress_socket_tls_error) {
146 emit this->networkError(error, reply->errorString());
147 }
148 }
149 else
150 {
151 int statusCode =reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
152
153 if(statusCode >= 200 and statusCode < 300) {
154 auto mime = reply->header(QNetworkRequest::ContentTypeHeader).toString();
155 emit this->requestComplete(this->body, mime);
156 }
157 else if(statusCode >= 300 and statusCode < 400) {
158 auto url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
159
160 emit this->redirected(url, (statusCode == 301) or (statusCode == 308));
161 }
162 else {
163 emit networkError(UnknownError, QString("Unhandled HTTP status code %1").arg(statusCode));
164 }
165
166 this->body.clear();
167 }
168 }
169
on_sslErrors(const QList<QSslError> & errors)170 void WebClient::on_sslErrors(const QList<QSslError> &errors)
171 {
172 emit this->hostCertificateLoaded(this->current_reply->sslConfiguration().peerCertificate());
173
174 if(options & IgnoreTlsErrors) {
175 this->current_reply->ignoreSslErrors(errors);
176 return;
177 }
178
179 QList<QSslError> remaining_errors = errors;
180 QList<QSslError> ignored_errors;
181
182 int i = 0;
183 while(i < remaining_errors.size())
184 {
185 auto const & err = remaining_errors.at(i);
186
187 bool ignore = false;
188 if(SslTrust::isTrustRelated(err.error()))
189 {
190 auto cert = this->current_reply->sslConfiguration().peerCertificate();
191 switch(kristall::globals().trust.https.getTrust(this->current_reply->url(), cert))
192 {
193 case SslTrust::Trusted:
194 ignore = true;
195 break;
196 case SslTrust::Untrusted:
197 this->suppress_socket_tls_error = true;
198 emit this->networkError(UntrustedHost, toFingerprintString(cert));
199 return;
200 case SslTrust::Mistrusted:
201 this->suppress_socket_tls_error = true;
202 emit this->networkError(MistrustedHost, toFingerprintString(cert));
203 return;
204 }
205 }
206 else if(err.error() == QSslError::UnableToVerifyFirstCertificate)
207 {
208 ignore = true;
209 }
210
211 if(ignore) {
212 ignored_errors.append(err);
213 remaining_errors.removeAt(0);
214 } else {
215 i += 1;
216 }
217 }
218
219 current_reply->ignoreSslErrors(ignored_errors);
220
221 qDebug() << "ignoring" << ignored_errors.size() << "out of" << errors.size();
222
223 for(auto const & error : remaining_errors) {
224 qWarning() << int(error.error()) << error.errorString();
225 }
226
227 if(remaining_errors.size() > 0) {
228 emit this->networkError(TlsFailure, remaining_errors.first().errorString());
229 }
230 }
231
on_redirected(const QUrl & url)232 void WebClient::on_redirected(const QUrl &url)
233 {
234 qDebug() << "redirected to" << url;
235 }
236