source: SMSSender/common/network/snetworkhelper.cpp @ 375:c57f1c92bcb8

separation-frontend-backend
Last change on this file since 375:c57f1c92bcb8 was 375:c57f1c92bcb8, checked in by Sämy Zehnder <saemy.zehnder@…>, 7 years ago
  • Removes explicit listing of CAs wherever possible (uses the systems trusted CAs).
  • Cleans up the locales in the resource files.
File size: 15.4 KB
Line 
1/*
2 smssender - A frontend for fast and easy SMS sending over different gateways.
3 Copyright (C) 2007-2014, gorrión. See http://smssender.gorrion.ch
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18#include "snetworkhelper.h"
19
20#include <QCoreApplication>
21#include <QEventLoop>
22#include <QFile>
23#include <QNetworkCookieJar>
24
25#include "common/domain/sversion.h"
26#include "common/exceptions/enetworkexception.h"
27#include "common/network/snetworkreply.h"
28
29const uint SNetworkHelper::MAX_REDIRECT_COUNT = 10;
30
31SNetworkHelper::SNetworkHelper(QObject *parent)
32    : QObject(parent)
33    , MULTIPART_BOUNDARY("---------------------------11677076429873974811095575830")
34    , sharedCookieJar_(new QNetworkCookieJar(this))
35{
36    qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
37
38    initDefaultSSLConfiguration();
39}
40
41SNetworkHelper::~SNetworkHelper() {
42    foreach (QThread* thread, networkAccessManager_.keys()) {
43        networkAccessManager_.take(thread)->deleteLater();
44    }
45}
46
47/**
48 * QNetworkAccessManager can not be accessed from different threads simultaneous. Therefore we create an own instance
49 * for each thread and share the cookie jar.
50 */
51QNetworkAccessManager &SNetworkHelper::networkAccessManager() {
52    if (!networkAccessManager_.contains(QThread::currentThread())) {
53        QNetworkAccessManager* nam = new QNetworkAccessManager;
54        nam->setCookieJar(sharedCookieJar_);
55        sharedCookieJar_->setParent(this); // setCookieJar takes ownership
56
57        connect(QThread::currentThread(), SIGNAL(terminated()), this, SLOT(onThreadTerminated()));
58        networkAccessManager_.insert(QThread::currentThread(), nam);
59
60        connect(nam, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError>&)),
61                this, SLOT(onSslErrors(QNetworkReply*, const QList<QSslError>&)),
62                Qt::DirectConnection);
63    }
64    return *networkAccessManager_.value(QThread::currentThread());
65}
66void SNetworkHelper::onThreadTerminated() {
67    networkAccessManager_.take(QThread::currentThread())->deleteLater();
68}
69
70
71void SNetworkHelper::initDefaultSSLConfiguration() {
72}
73
74void SNetworkHelper::addTrustedCA(const QString& certFilename, QSsl::EncodingFormat format) {
75    QFile certFile(certFilename);
76    bool canOpen = certFile.open(QIODevice::ReadOnly);
77    Q_ASSERT(canOpen);
78
79    QSslCertificate cert(&certFile, format);
80    Q_ASSERT(cert.isValid() && !cert.isNull());
81
82    QList<QSslCertificate> caCertificates = defaultSSLConfiguration().caCertificates();
83    caCertificates.append(cert);
84    defaultSSLConfiguration().setCaCertificates(caCertificates);
85}
86
87/**
88 * Does a http GET on the given desitnation url and waits for the result. The headers of the request are automatically filled.
89 * If desired, redirects are followed.
90 *
91 * @param destination The destination url
92 * @param redirectHandling If redirects should be handled
93 * @return The network reply
94 */
95SNetworkReply SNetworkHelper::syncGet(const QUrl& destination, SNetworkHelper::RedirectHandling redirectHandling) {
96    QNetworkRequest request = createDefaultRequest(destination);
97    return syncGet(request, redirectHandling);
98}
99/**
100 * Does a http GET for the given request and waits for the result. If desired, redirects are followed.
101 *
102 * @param request The request
103 * @param redirectHandling If redirects should be handled
104 * @return The network reply
105 */
106SNetworkReply SNetworkHelper::syncGet(QNetworkRequest& request, SNetworkHelper::RedirectHandling redirectHandling) {
107    SNetworkReply reply = asyncGet(request);
108    waitFor(reply);
109
110    reply = handleRedirect(reply, redirectHandling);
111
112    return reply;
113}
114
115/**
116 * Does a http GET on the given desitnation url and returns immediately. The headers of the request are automatically filled.
117 *
118 * @param destination The destination url
119 * @return The network reply
120 */
121SNetworkReply SNetworkHelper::asyncGet(const QUrl& destination) {
122    QNetworkRequest request = createDefaultRequest(destination);
123    return asyncGet(request);
124}
125/**
126 * Does a http GET for the given request and returns immediately.
127 *
128 * @param request The request
129 * @return The network reply
130 */
131SNetworkReply SNetworkHelper::asyncGet(QNetworkRequest& request) {
132    lastUrl_.insert(&networkAccessManager(), request.url());
133
134    SNetworkReply reply(networkAccessManager().get(request));
135    return reply;
136}
137
138
139/**
140 * Does a http POST on the given desitnation url and waits for the result. The headers of the request are automatically filled.
141 * If desired, redirects are followed.
142 *
143 * @param destination The destination url
144 * @param posts The post params which should be sent. Ownership is taken.
145 * @param postType If we should send the params urlencoded or with multipart.
146 * @param redirectHandling If redirects should be handled
147 * @return The network reply
148 */
149SNetworkReply SNetworkHelper::syncPost(const QUrl& destination, QList<IParam*>& posts, SNetworkHelper::PostType postType, SNetworkHelper::RedirectHandling redirectHandling) {
150    QNetworkRequest request = createDefaultRequest(destination);
151    return syncPost(request, posts, postType, redirectHandling);
152}
153/**
154 * Does a http POST for the given request and waits for the result. If desired, redirects are followed.
155 *
156 * @param request The request
157 * @param posts The post params which should be sent. Ownership is taken.
158 * @param postType If we should send the params urlencoded or with multipart.
159 * @param redirectHandling If redirects should be handled
160 * @return The network reply
161 */
162SNetworkReply SNetworkHelper::syncPost(QNetworkRequest& request, QList<IParam*>& posts, SNetworkHelper::PostType postType, SNetworkHelper::RedirectHandling redirectHandling) {
163    SNetworkReply reply = asyncPost(request, posts, postType);
164    waitFor(reply);
165
166    reply = handleRedirect(reply, redirectHandling);
167
168    return reply;
169}
170
171/**
172 * Does a http POST on the given desitnation url and returns immediately. The headers of the request are automatically filled.
173 *
174 * @param destination The destination url
175 * @param posts The post params which should be sent. Ownership is taken.
176 * @param postType If we should send the params urlencoded or with multipart.
177 * @return The network reply
178 */
179SNetworkReply SNetworkHelper::asyncPost(const QUrl& destination, QList<IParam*>& posts, SNetworkHelper::PostType postType) {
180    QNetworkRequest request  = createDefaultRequest(destination);
181    return asyncPost(request, posts, postType);
182}
183/**
184 * Does a http POST for the given request and returns immediately.
185 *
186 * @param request The request
187 * @param posts The post params which should be sent. Ownership is taken.
188 * @param postType If we should send the params urlencoded or with multipart.
189 * @return The network reply
190 */
191SNetworkReply SNetworkHelper::asyncPost(QNetworkRequest& request, QList<IParam*>& posts, SNetworkHelper::PostType postType) {
192    lastUrl_.insert(&networkAccessManager(), request.url());
193
194    // Set the content type header
195    if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
196        switch (postType) {
197            case SNetworkHelper::ptUrlEncoded: {
198                request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
199                /* Perhaps you want: application/x-www-form-urlencoded; charset=utf-8
200                 * But do not set this as default because some http-servers are crashing because of that!*/
201                break;
202            }
203
204            case SNetworkHelper::ptMultipart: {
205                request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data; boundary=" + MULTIPART_BOUNDARY);
206                break;
207            }
208        }
209    }
210
211    // Generate the posts body
212    QByteArray postData = paramsToEncoded(posts, postType);
213    request.setHeader(QNetworkRequest::ContentLengthHeader, postData.length());
214
215    // Cleanup the params
216    foreach (IParam* param, posts) {
217        delete param;
218    }
219    posts.clear();
220
221    // Post the request
222    SNetworkReply reply(networkAccessManager().post(request, postData));
223    return reply;
224}
225
226QUrl SNetworkHelper::getRedirectUrl(const SNetworkReply& reply) {
227    return reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
228}
229
230void SNetworkHelper::waitFor(const SNetworkReply& networkReply) {
231    QEventLoop eventLoop;
232    connect(networkReply, SIGNAL(finished()), &eventLoop, SLOT(quit()));
233    eventLoop.exec(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents);
234    Q_ASSERT(networkReply->isFinished());
235
236    if (networkReply->error() != QNetworkReply::NoError) {
237        ENetworkException(networkReply->error(), networkReply->errorString())
238                .addDebugInfo("url", networkReply->request().url().toString())
239                .raise();
240    }
241}
242
243SNetworkReply SNetworkHelper::handleRedirect(SNetworkReply reply, SNetworkHelper::RedirectHandling redirectHandling) {
244    switch (redirectHandling) {
245        case SNetworkHelper::rhHandleRedirects: {
246            QUrl redirectUrl = getRedirectUrl(reply);
247            if (!redirectUrl.isEmpty()) {
248                QNetworkRequest request = createDefaultRequest(redirectUrl);
249
250                // Set referer header
251                QByteArray refererUrl = reply->request().url().toEncoded();
252                if (reply->request().hasRawHeader("Referer")) {
253                    refererUrl = reply->request().rawHeader("Referer");
254                }
255                request.setRawHeader("Referer", refererUrl);
256
257                reply = syncGet(request, redirectHandling);
258            }
259
260            break;
261        }
262
263        case SNetworkHelper::rhNone:
264            break;
265    }
266
267    return reply;
268}
269
270void SNetworkHelper::adjustUrl(QUrl& url) {
271    QUrl lstUrl = lastUrl();
272    if (lstUrl.isEmpty()) {
273        return;
274    }
275
276    if (url.scheme().isEmpty()) {
277        url.setScheme(lstUrl.scheme());
278    }
279    if (url.host().isEmpty()) {
280        url.setHost(lstUrl.host());
281    }
282    if (url.port() == -1) {
283        url.setPort(lstUrl.port());
284    }
285}
286QUrl SNetworkHelper::lastUrl() {
287    return lastUrl_.value(&networkAccessManager());
288}
289
290
291QByteArray SNetworkHelper::paramsToEncoded(const QList<IParam*>& posts, SNetworkHelper::PostType postType) {
292    QByteArray delim;
293    QByteArray startMarker;
294    QByteArray endMarker;
295    switch (postType) {
296        case SNetworkHelper::ptUrlEncoded: {
297            delim       = "&";
298            startMarker = "";
299            endMarker   = "";
300
301            break;
302        }
303
304        case SNetworkHelper::ptMultipart: {
305            delim       = QString("\r\n--" + MULTIPART_BOUNDARY + "\r\n").toUtf8();
306            startMarker = QString("--" + MULTIPART_BOUNDARY + "\r\n").toUtf8();
307            endMarker   = QString("\r\n--" + MULTIPART_BOUNDARY + "--\r\n").toUtf8();
308
309            break;
310        }
311    }
312
313    QByteArray result = startMarker;
314    foreach (IParam* param, posts) {
315        result += param->encode(postType) + delim;
316    }
317    result.remove(result.length() - delim.length(), delim.length()); // Remove last delim
318    result += endMarker;
319
320    return result;
321}
322
323
324QNetworkRequest SNetworkHelper::createDefaultRequest(const QUrl& url) {
325    QUrl dest(url);
326    adjustUrl(dest);
327
328    if (dest.host().isEmpty()) {
329        dest.setHost(lastUrl().host());
330        dest.setPort(lastUrl().port());
331        dest.setScheme(lastUrl().scheme());
332    }
333    QNetworkRequest request(dest);
334    attachDefaultHeaders(request);
335    attachDefaultSSLConfiguration(request);
336
337    return request;
338}
339
340void SNetworkHelper::attachDefaultHeaders(QNetworkRequest& request) {
341    foreach (const QByteArray header, defaultHeaders().keys()) {
342        request.setRawHeader(header, defaultHeaders().value(header));
343    }
344
345    if (!request.hasRawHeader("User-Agent")) {
346        if (request.url().host().toLower().endsWith("gorrion.ch")) {
347            SVersion appVersion(qApp->applicationVersion());
348            request.setRawHeader("User-Agent", QString("Mozilla/4.0 (compatible; SMSSender %1; http://www.gorrion.ch/p/smssender)").arg(appVersion.toString(true)).toUtf8());
349        } else {
350            request.setRawHeader("User-Agent", "Mozilla/4.0 (compatible; SMSSender; http://www.gorrion.ch/p/smssender)");
351        }
352    }
353
354    if (!request.hasRawHeader("Host") && !request.url().host().isEmpty()) {
355        request.setRawHeader("Host", request.url().host().toUtf8());
356    }
357
358    if (!request.hasRawHeader("Connection")) {
359        request.setRawHeader("Connection", "Keep-Alive");
360    }
361}
362
363void SNetworkHelper::clearCookies() {
364    networkAccessManager().setCookieJar(new QNetworkCookieJar);
365}
366
367void SNetworkHelper::attachDefaultSSLConfiguration(QNetworkRequest& request) {
368    request.setSslConfiguration(defaultSSLConfiguration());
369}
370
371
372QString certToString(const QSslCertificate &cert, const QString &delim) {
373    QString subjectOrganization = cert.subjectInfo(QSslCertificate::Organization);
374    QString subjectCommonName = cert.subjectInfo(QSslCertificate::CommonName);
375    QString subjectLocalityName = cert.subjectInfo(QSslCertificate::LocalityName);
376    QString subjectOrganizationalUnitName = cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
377
378    QString issuerOrganization = cert.issuerInfo(QSslCertificate::Organization);
379    QString issuerCommonName = cert.issuerInfo(QSslCertificate::CommonName);
380    QString issuerLocalityName = cert.issuerInfo(QSslCertificate::LocalityName);
381    QString issuerOrganizationalUnitName = cert.issuerInfo(QSslCertificate::OrganizationalUnitName);
382
383    return QString("s:[%1]%2i:[%3]")
384            .arg(QString("O=%1,CN=%2,L=%3,OU=%4,S=%5")
385                 .arg(subjectOrganization)
386                 .arg(subjectCommonName)
387                 .arg(subjectLocalityName)
388                 .arg(subjectOrganizationalUnitName)
389                 .arg(QString(cert.serialNumber())))
390            .arg(delim)
391            .arg(QString("O=%1,CN=%2,L=%3,OU=%4")
392                 .arg(issuerOrganization)
393                 .arg(issuerCommonName)
394                 .arg(issuerLocalityName)
395                 .arg(issuerOrganizationalUnitName));
396}
397
398void SNetworkHelper::onSslErrors(QNetworkReply* reply, const QList<QSslError>& errors) {
399    qDebug() << QString("SSL errors occured while connecting to %1:")
400                .arg(reply->request().url().toString());
401
402    foreach (QSslError error, errors) {
403        qDebug() << QString("%1\n  %2")
404                    .arg(error.errorString())
405                    .arg(certToString(error.certificate(), "\n  "));
406    }
407
408    qDebug() << "Installed CAs:";
409    unsigned i = 0;
410    foreach (const QSslCertificate &cert, reply->request().sslConfiguration().caCertificates()) {
411        qDebug() << QString("%1: %2")
412                    .arg(i)
413                    .arg(certToString(cert, "\n   "));
414        ++i;
415    }
416}
417
418HeadersMap& SNetworkHelper::defaultHeaders() {
419    return defaultHeaders_;
420}
421void SNetworkHelper::setDefaultHeaders(const HeadersMap& headers) {
422    defaultHeaders_ = headers;
423}
424
425QSslConfiguration& SNetworkHelper::defaultSSLConfiguration() {
426    return defaultSSLConfiguration_;
427}
428void SNetworkHelper::setDefaultSSLConfiguration(const QSslConfiguration& sslConfiguration) {
429    defaultSSLConfiguration_ = sslConfiguration;
430}
Note: See TracBrowser for help on using the repository browser.