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