VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/networking/UINetworkReply.cpp@ 103977

Last change on this file since 103977 was 103771, checked in by vboxsync, 9 months ago

FE/Qt: UICommon: Switching dependency from UICommon to UIGlobalSession whenever is possible.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/Frontends/VirtualBox/src/net/UINetworkManager.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/Frontends/VirtualBox/src/net/UINetworkManager.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/Frontends/VirtualBox/src/net/UINetworkManager.cpp70873
    /branches/VBox-4.1/src/VBox/Frontends/VirtualBox/src/net/UINetworkManager.cpp74233
    /branches/VBox-4.2/src/VBox/Frontends/VirtualBox/src/net/UINetworkReply.cpp91503-91504,​91506-91508,​91510,​91514-91515,​91521
    /branches/VBox-4.3/src/VBox/Frontends/VirtualBox/src/net/UINetworkReply.cpp91223
    /branches/VBox-4.3/trunk/src/VBox/Frontends/VirtualBox/src/net/UINetworkReply.cpp91223
    /branches/dsen/gui/src/VBox/Frontends/VirtualBox/src/net/UINetworkRequest.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/Frontends/VirtualBox/src/net/UINetworkRequest.cpp79224,​79228,​79233,​79235,​79258,​79262-79263,​79273,​79341,​79345,​79354,​79357,​79387-79388,​79559-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/Frontends/VirtualBox/src/net/UINetworkRequest.cpp79645-79692
File size: 34.3 KB
Line 
1/* $Id: UINetworkReply.cpp 103771 2024-03-11 15:16:04Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UINetworkReply stuff implementation.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QDir>
30#include <QFile>
31#include <QThread>
32#include <QVector>
33#include <QVariant>
34
35/* GUI includes: */
36#include "UILoggingDefs.h"
37#include "UINetworkReply.h"
38#include "UINetworkRequestManager.h"
39#include "UIExtraDataManager.h"
40#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
41# include "UIGlobalSession.h"
42# include "VBoxUtils.h"
43# include "CSystemProperties.h"
44#endif
45
46/* Other VBox includes: */
47#include <iprt/initterm.h>
48#include <iprt/crypto/pem.h>
49#include <iprt/crypto/store.h>
50#include <iprt/err.h>
51#include <iprt/http.h>
52#include <iprt/path.h>
53#include <iprt/sha.h>
54#include <iprt/string.h>
55#include <iprt/zip.h>
56
57
58/** QThread extension
59 * used as network-reply private thread interface. */
60class UINetworkReplyPrivateThread : public QThread
61{
62#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
63 Q_OBJECT;
64
65signals:
66
67 /** Notifies listeners about reply progress change.
68 * @param iBytesReceived Holds the current amount of bytes received.
69 * @param iBytesTotal Holds the total amount of bytes to be received. */
70 void sigDownloadProgress(qint64 iBytesReceived, qint64 iBytesTotal);
71#endif /* !VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
72
73public:
74
75 /** Constructs network-reply thread of the passed @a type for the passed @a url and @a requestHeaders. */
76 UINetworkReplyPrivateThread(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders);
77
78 /** @name APIs
79 * @{ */
80 /** Aborts reply. */
81 void abort();
82
83 /** Returns the URL of the reply which is the URL of the request for now. */
84 const QUrl& url() const { return m_url; }
85
86 /** Returns the last cached IPRT HTTP error of the reply. */
87 int error() const { return m_iError; }
88
89 /** Returns binary content of the reply. */
90 const QByteArray& readAll() const { return m_reply; }
91 /** Returns value for the cached reply header of the passed @a type. */
92 QString header(UINetworkReply::KnownHeader type) const;
93
94 /** Returns short descriptive context of thread's current operation. */
95 const QString context() const { return m_strContext; }
96 /** @} */
97
98private:
99
100 /** @name Helpers for HTTP and Certificates handling.
101 * @{ */
102 /** Applies configuration. */
103 int applyConfiguration();
104 /** Applies proxy rules. */
105 int applyProxyRules();
106 /** Applies security certificates. */
107 int applyHttpsCertificates();
108 /** Applies raw headers. */
109 int applyRawHeaders();
110 /** Performs main request. */
111 int performMainRequest();
112
113 /** Performs whole thread functionality. */
114 void run();
115
116 /** Handles download progress callback.
117 * @param cbDownloadTotal Brings the total amount of bytes to be received.
118 * @param cbDownloaded Brings the current amount of bytes received. */
119 void handleProgressChange(uint64_t cbDownloadTotal, uint64_t cbDownloaded);
120 /** @} */
121
122 /** @name Static helpers for HTTP and Certificates handling.
123 * @{ */
124 /** Returns full certificate file-name. */
125 static QString fullCertificateFileName();
126
127 /** Applies proxy rules.
128 * @remarks Implementation doesn't exists, to be removed? */
129 static int applyProxyRules(RTHTTP hHttp, const QString &strHostName, int iPort);
130
131 /** Applies raw headers.
132 * @param hHttp Brings the HTTP client instance.
133 * @param headers Brings the map of headers to be applied. */
134 static int applyRawHeaders(RTHTTP hHttp, const UserDictionary &headers);
135
136 /** Returns the number of certificates found in a search result array.
137 * @param pafFoundCerts Brings the array parallel to s_aCerts with the status of each wanted certificate. */
138 static unsigned countCertsFound(bool const *pafFoundCerts);
139
140 /** Returns whether we've found all the necessary certificates.
141 * @param pafFoundCerts Brings the array parallel to s_aCerts with the status of each wanted certificate. */
142 static bool areAllCertsFound(bool const *pafFoundCerts);
143
144 /** Refreshes the certificates.
145 * @param phStore On input, this holds the current store, so that we can fish out wanted
146 * certificates from it. On successful return, this is replaced with a new
147 * store reflecting the refrehsed content of @a pszCaCertFile.
148 * @param pafFoundCerts On input, this holds the certificates found in the current store.
149 * On return, this reflects what is current in the @a pszCaCertFile.
150 * The array runs parallel to s_aCerts.
151 * @param pszCaCertFile Where to write the refreshed certificates if we've managed to gather
152 * a collection that is at least as good as the old one. */
153 static int refreshCertificates(PRTCRSTORE phStore, bool *pafFoundCerts, const char *pszCaCertFile);
154
155 /** Redirects download progress callback to particular object which can handle it.
156 * @param hHttp Brings the HTTP client instance.
157 * @param pvUser Brings the convenience pointer for the
158 * user-agent object which should handle that callback.
159 * @param cbDownloadTotal Brings the total amount of bytes to be received.
160 * @param cbDownloaded Brings the current amount of bytes received. */
161 static DECLCALLBACK(void) handleProgressChange(RTHTTP hHttp, void *pvUser, uint64_t cbDownloadTotal, uint64_t cbDownloaded);
162 /** @} */
163
164 /** Holds the request type. */
165 const UINetworkRequestType m_type;
166 /** Holds the request url. */
167 const QUrl m_url;
168 /** Holds the request target. */
169 const QString m_strTarget;
170 /** Holds the request headers. */
171 const UserDictionary m_requestHeaders;
172
173 /** Holds the IPRT HTTP client instance handle. */
174 RTHTTP m_hHttp;
175 /** Holds the last cached IPRT HTTP error of the reply. */
176 int m_iError;
177 /** Holds short descriptive context of thread's current operation. */
178 QString m_strContext;
179 /** Holds the reply instance. */
180 QByteArray m_reply;
181 /** Holds the cached reply headers. */
182 UserDictionary m_headers;
183
184 /** Holds the details on the certificates we are after. */
185 static const RTCRCERTWANTED s_aCerts[];
186 /** Holds the certificate file name (no path). */
187 static const QString s_strCertificateFileName;
188
189#ifdef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
190public:
191 /** Starts the test routine. */
192 static void testIt(RTTEST hTest);
193#endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
194};
195
196
197#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
198/** QObject extension
199 * used as network-reply private data interface. */
200class UINetworkReplyPrivate : public QObject
201{
202 Q_OBJECT;
203
204signals:
205
206 /** Notifies listeners about reply progress change.
207 * @param iBytesReceived Holds the current amount of bytes received.
208 * @param iBytesTotal Holds the total amount of bytes to be received. */
209 void downloadProgress(qint64 iBytesReceived, qint64 iBytesTotal);
210
211 /** Notifies listeners about reply has finished processing. */
212 void finished();
213
214public:
215
216 /** Constructs network-reply private data of the passed @a type for the passed @a url and @a requestHeaders. */
217 UINetworkReplyPrivate(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders);
218 /** Destructs reply private data. */
219 ~UINetworkReplyPrivate();
220
221 /** Aborts reply. */
222 void abort() { m_pThread->abort(); }
223
224 /** Returns URL of the reply. */
225 QUrl url() const { return m_pThread->url(); }
226
227 /** Returns the last cached error of the reply. */
228 UINetworkReply::NetworkError error() const { return m_error; }
229 /** Returns the user-oriented string corresponding to the last cached error of the reply. */
230 QString errorString() const;
231
232 /** Returns binary content of the reply. */
233 QByteArray readAll() const { return m_pThread->readAll(); }
234 /** Returns value for the cached reply header of the passed @a type. */
235 QString header(UINetworkReply::KnownHeader type) const { return m_pThread->header(type); }
236
237private slots:
238
239 /** Handles signal about reply has finished processing. */
240 void sltFinished();
241
242private:
243
244 /** Holds full error template in "Context description: Error description" form. */
245 QString m_strErrorTemplate;
246
247 /** Holds the last cached error of the reply. */
248 UINetworkReply::NetworkError m_error;
249
250 /** Holds the reply thread instance. */
251 UINetworkReplyPrivateThread *m_pThread;
252};
253#endif /* !VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
254
255
256/*********************************************************************************************************************************
257* Class UINetworkReplyPrivateThread implementation. *
258*********************************************************************************************************************************/
259
260/* static */
261const RTCRCERTWANTED UINetworkReplyPrivateThread::s_aCerts[] =
262{
263 /*[0] =*/
264 {
265 /*.pszSubject =*/
266 "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA",
267 /*.cbEncoded =*/ 0x3b3,
268 /*.Sha1Fingerprint =*/ true,
269 /*.Sha512Fingerprint =*/ true,
270 /*.abSha1 =*/
271 {
272 0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7,
273 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36
274 },
275 /*.abSha512 =*/
276 {
277 0x53, 0xb4, 0x44, 0xe5, 0x65, 0x18, 0x32, 0x01,
278 0xa6, 0x1e, 0xeb, 0x46, 0x12, 0x09, 0xb2, 0xdc,
279 0x30, 0x89, 0x5e, 0xec, 0xa4, 0x87, 0x23, 0x8d,
280 0x15, 0xa0, 0x26, 0x73, 0x5f, 0x22, 0x9a, 0x81,
281 0x9e, 0x5b, 0x19, 0xcb, 0xd7, 0xe2, 0xfa, 0x27,
282 0x68, 0xab, 0x2a, 0x64, 0xf6, 0xeb, 0xcd, 0x9d,
283 0x1e, 0x72, 0x13, 0x41, 0xc9, 0xed, 0x5d, 0xd0,
284 0x9f, 0xc0, 0xd5, 0xe4, 0x3d, 0x68, 0xbc, 0xa7
285 },
286 /*.pvUser =*/ NULL,
287 },
288};
289
290/* static */
291const QString UINetworkReplyPrivateThread::s_strCertificateFileName = QString("vbox-ssl-cacertificate.crt");
292
293UINetworkReplyPrivateThread::UINetworkReplyPrivateThread(UINetworkRequestType type,
294 const QUrl &url,
295 const QString &strTarget,
296 const UserDictionary &requestHeaders)
297 : m_type(type)
298 , m_url(url)
299 , m_strTarget(strTarget)
300 , m_requestHeaders(requestHeaders)
301 , m_hHttp(NIL_RTHTTP)
302 , m_iError(VINF_SUCCESS)
303{
304}
305
306void UINetworkReplyPrivateThread::abort()
307{
308 /* Call for abort: */
309 if (m_hHttp != NIL_RTHTTP)
310 RTHttpAbort(m_hHttp);
311}
312
313QString UINetworkReplyPrivateThread::header(UINetworkReply::KnownHeader type) const
314{
315 /* Look for known header type: */
316 switch (type)
317 {
318 case UINetworkReply::ContentTypeHeader: return m_headers.value("Content-Type");
319 case UINetworkReply::ContentLengthHeader: return m_headers.value("Content-Length");
320 case UINetworkReply::LastModifiedHeader: return m_headers.value("Last-Modified");
321 case UINetworkReply::LocationHeader: return m_headers.value("Location");
322 default: break;
323 }
324 /* Return null-string by default: */
325 return QString();
326}
327
328int UINetworkReplyPrivateThread::applyConfiguration()
329{
330 /* Install downloading progress callback: */
331 return RTHttpSetDownloadProgressCallback(m_hHttp, &UINetworkReplyPrivateThread::handleProgressChange, this);
332}
333
334int UINetworkReplyPrivateThread::applyProxyRules()
335{
336 /* Set thread context: */
337 m_strContext = tr("During proxy configuration");
338
339#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
340 /* If the specific proxy settings are enabled, we'll use them
341 * unless user disabled that functionality manually. */
342 const CSystemProperties comProperties = gpGlobalSession->virtualBox().GetSystemProperties();
343 const KProxyMode enmProxyMode = comProperties.GetProxyMode();
344 AssertReturn(comProperties.isOk(), VERR_INTERNAL_ERROR_3);
345 switch (enmProxyMode)
346 {
347 case KProxyMode_Manual:
348 return RTHttpSetProxyByUrl(m_hHttp, comProperties.GetProxyURL().toUtf8().constData());
349 case KProxyMode_NoProxy:
350 return VINF_SUCCESS;
351 default:
352 break;
353 }
354#endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
355
356 /* By default, use system proxy: */
357 return RTHttpUseSystemProxySettings(m_hHttp);
358}
359
360int UINetworkReplyPrivateThread::applyHttpsCertificates()
361{
362 /* Check if we really need SSL: */
363 if (!url().toString().startsWith("https:", Qt::CaseInsensitive))
364 return VINF_SUCCESS;
365
366 /* Set thread context: */
367 m_strContext = tr("During certificate downloading");
368
369 /*
370 * Calc the filename of the CA certificate file.
371 */
372 const QString strFullCertificateFileName(fullCertificateFileName());
373 QByteArray utf8FullCertificateFileName = strFullCertificateFileName.toUtf8();
374 const char *pszCaCertFile = utf8FullCertificateFileName.constData();
375
376 /*
377 * Check the state of our CA certificate file, it's one of the following:
378 * - Missing, recreate from scratch (= refresh).
379 * - Everything is there and it is less than 28 days old, do nothing.
380 * - Everything is there but it's older than 28 days, refresh.
381 * - Missing certificates and is older than 1 min, refresh.
382 *
383 * Start by creating a store for loading the current state into, as we'll
384 * be need that for the refresh.
385 */
386 RTCRSTORE hCurStore = NIL_RTCRSTORE;
387 int rc = RTCrStoreCreateInMem(&hCurStore, 256);
388 if (RT_SUCCESS(rc))
389 {
390 bool fRefresh = true;
391 bool afCertsFound[RT_ELEMENTS(s_aCerts)];
392 RT_ZERO(afCertsFound);
393
394 /*
395 * Load the file if it exists.
396 *
397 * To effect regular updates, we need the modification date of the file,
398 * so we use RTPathQueryInfoEx here and not RTFileExists.
399 */
400 RTFSOBJINFO Info;
401 rc = RTPathQueryInfoEx(pszCaCertFile, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
402 if ( RT_SUCCESS(rc)
403 && RTFS_IS_FILE(Info.Attr.fMode))
404 {
405 RTERRINFOSTATIC StaticErrInfo;
406 rc = RTCrStoreCertAddFromFile(hCurStore, RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, pszCaCertFile,
407 RTErrInfoInitStatic(&StaticErrInfo));
408 if (RTErrInfoIsSet(&StaticErrInfo.Core))
409 LogRel(("checkCertificates: %s\n", StaticErrInfo.Core.pszMsg));
410 else
411 AssertRC(rc);
412
413 /*
414 * Scan the store the for certificates we need, then see what we
415 * need to do wrt file age.
416 */
417 rc = RTCrStoreCertCheckWanted(hCurStore, s_aCerts, RT_ELEMENTS(s_aCerts), afCertsFound);
418 AssertRC(rc);
419 RTTIMESPEC RefreshAge;
420 uint32_t cSecRefresh = rc == VINF_SUCCESS ? 28 * RT_SEC_1DAY /* all found */ : 60 /* stuff missing */;
421 fRefresh = RTTimeSpecCompare(&Info.ModificationTime, RTTimeSpecSubSeconds(RTTimeNow(&RefreshAge), cSecRefresh)) <= 0;
422 }
423
424 /*
425 * Refresh the file if necessary.
426 */
427 if (fRefresh)
428 refreshCertificates(&hCurStore, afCertsFound, pszCaCertFile);
429
430 RTCrStoreRelease(hCurStore);
431
432 /*
433 * Final verdict.
434 */
435 if (areAllCertsFound(afCertsFound))
436 rc = VINF_SUCCESS;
437 else
438 rc = VERR_NOT_FOUND; /** @todo r=bird: Why not try and let RTHttpGet* bitch if the necessary certs are missing? */
439
440 /*
441 * Set our custom CA file.
442 */
443 if (RT_SUCCESS(rc))
444 rc = RTHttpSetCAFile(m_hHttp, pszCaCertFile);
445 }
446 return rc;
447}
448
449int UINetworkReplyPrivateThread::applyRawHeaders()
450{
451 /* Set thread context: */
452 m_strContext = tr("During network request");
453
454 /* Make sure we have a raw headers at all: */
455 if (m_requestHeaders.isEmpty())
456 return VINF_SUCCESS;
457
458 /* Apply raw headers: */
459 return applyRawHeaders(m_hHttp, m_requestHeaders);
460}
461
462int UINetworkReplyPrivateThread::performMainRequest()
463{
464 /* Set thread context: */
465 m_strContext = tr("During network request");
466
467 /* Paranoia: */
468 m_reply.clear();
469
470 /* Prepare result: */
471 int rc = 0;
472
473 /* Depending on request type: */
474 switch (m_type)
475 {
476 case UINetworkRequestType_HEAD:
477 {
478 /* Perform blocking HTTP HEAD request: */
479 void *pvResponse = 0;
480 size_t cbResponse = 0;
481 rc = RTHttpGetHeaderBinary(m_hHttp, m_url.toString().toUtf8().constData(), &pvResponse, &cbResponse);
482 if (RT_SUCCESS(rc))
483 {
484 m_reply = QByteArray((char*)pvResponse, (int)cbResponse);
485 RTHttpFreeResponse(pvResponse);
486 }
487
488 /* Paranoia: */
489 m_headers.clear();
490
491 /* Parse header contents: */
492 const QString strHeaders = QString(m_reply);
493#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
494 const QStringList headers = strHeaders.split("\n", Qt::SkipEmptyParts);
495#else
496 const QStringList headers = strHeaders.split("\n", QString::SkipEmptyParts);
497#endif
498 foreach (const QString &strHeader, headers)
499 {
500#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
501 const QStringList values = strHeader.split(": ", Qt::SkipEmptyParts);
502#else
503 const QStringList values = strHeader.split(": ", QString::SkipEmptyParts);
504#endif
505 if (values.size() > 1)
506 m_headers[values.at(0)] = values.at(1);
507 }
508
509 /* Special handling of redirection header: */
510 if (rc == VERR_HTTP_REDIRECTED)
511 {
512 char *pszBuf = 0;
513 const int rrc = RTHttpGetRedirLocation(m_hHttp, &pszBuf);
514 if (RT_SUCCESS(rrc))
515 m_headers["Location"] = QString(pszBuf);
516 if (pszBuf)
517 RTMemFree(pszBuf);
518 }
519
520 break;
521 }
522 case UINetworkRequestType_GET:
523 {
524 /* Perform blocking HTTP GET request.
525 * Keep in mind that if the target parameter is provided,
526 * we are trying to download contents to file directly,
527 * otherwise it will be downloaded to memory and it's
528 * customer responsibility to save it afterwards. */
529 if (m_strTarget.isEmpty())
530 {
531 void *pvResponse = 0;
532 size_t cbResponse = 0;
533 rc = RTHttpGetBinary(m_hHttp, m_url.toString().toUtf8().constData(), &pvResponse, &cbResponse);
534 if (RT_SUCCESS(rc))
535 {
536 m_reply = QByteArray((char*)pvResponse, (int)cbResponse);
537 RTHttpFreeResponse(pvResponse);
538 }
539 }
540 else
541 {
542 rc = RTHttpGetFile(m_hHttp, m_url.toString().toUtf8().constData(), m_strTarget.toUtf8().constData());
543 if (RT_SUCCESS(rc))
544 {
545 QFile file(m_strTarget);
546 if (file.open(QIODevice::ReadOnly))
547 m_reply = file.readAll();
548 }
549 }
550
551 break;
552 }
553 default:
554 break;
555 }
556
557 /* Return result: */
558 return rc;
559}
560
561void UINetworkReplyPrivateThread::run()
562{
563 /* Create HTTP client: */
564 m_iError = RTHttpCreate(&m_hHttp);
565 if (RT_SUCCESS(m_iError))
566 {
567 /* Apply configuration: */
568 if (RT_SUCCESS(m_iError))
569 m_iError = applyConfiguration();
570
571 /* Apply proxy-rules: */
572 if (RT_SUCCESS(m_iError))
573 m_iError = applyProxyRules();
574
575 /* Apply https-certificates: */
576 if (RT_SUCCESS(m_iError))
577 m_iError = applyHttpsCertificates();
578
579 /* Assign raw-headers: */
580 if (RT_SUCCESS(m_iError))
581 m_iError = applyRawHeaders();
582
583 /* Perform main request: */
584 if (RT_SUCCESS(m_iError))
585 m_iError = performMainRequest();
586
587 /* Destroy HTTP client: */
588 RTHTTP hHttp = m_hHttp;
589 if (hHttp != NIL_RTHTTP)
590 {
591 /** @todo r=bird: There is a race here between this and abort()! */
592 m_hHttp = NIL_RTHTTP;
593 RTHttpDestroy(hHttp);
594 }
595 }
596}
597
598void UINetworkReplyPrivateThread::handleProgressChange(uint64_t cbDownloadTotal, uint64_t cbDownloaded)
599{
600#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
601 /* Notify listeners about progress change: */
602 emit sigDownloadProgress(cbDownloaded, cbDownloadTotal);
603#else /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
604 Q_UNUSED(cbDownloaded);
605 Q_UNUSED(cbDownloadTotal);
606#endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
607}
608
609/* static */
610QString UINetworkReplyPrivateThread::fullCertificateFileName()
611{
612#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
613 const QDir homeDir(QDir::toNativeSeparators(gpGlobalSession->homeFolder()));
614 return QDir::toNativeSeparators(homeDir.absoluteFilePath(s_strCertificateFileName));
615#else /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
616 return QString("/not/such/agency/non-existing-file.cer");
617#endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
618}
619
620/* static */
621int UINetworkReplyPrivateThread::applyRawHeaders(RTHTTP hHttp, const UserDictionary &headers)
622{
623 /* Make sure HTTP is created: */
624 if (hHttp == NIL_RTHTTP)
625 return VERR_INVALID_HANDLE;
626
627 /* We should format them first: */
628 QVector<QByteArray> formattedHeaders;
629 QVector<const char*> formattedHeaderPointers;
630 foreach (const QString &header, headers.keys())
631 {
632 /* Prepare formatted representation: */
633 QString strFormattedString = QString("%1: %2").arg(header, headers.value(header));
634 formattedHeaders << strFormattedString.toUtf8();
635 formattedHeaderPointers << formattedHeaders.last().constData();
636 }
637 const char **ppFormattedHeaders = formattedHeaderPointers.data();
638
639 /* Apply HTTP headers: */
640 return RTHttpSetHeaders(hHttp, formattedHeaderPointers.size(), ppFormattedHeaders);
641}
642
643/* static */
644unsigned UINetworkReplyPrivateThread::countCertsFound(bool const *pafFoundCerts)
645{
646 unsigned cFound = 0;
647 for (uint32_t i = 0; i < RT_ELEMENTS(s_aCerts); i++)
648 cFound += pafFoundCerts[i];
649 return cFound;
650}
651
652/* static */
653bool UINetworkReplyPrivateThread::areAllCertsFound(bool const *pafFoundCerts)
654{
655 for (uint32_t i = 0; i < RT_ELEMENTS(s_aCerts); i++)
656 if (!pafFoundCerts[i])
657 return false;
658 return true;
659}
660
661/* static */
662int UINetworkReplyPrivateThread::refreshCertificates(PRTCRSTORE phStore, bool *pafFoundCerts, const char *pszCaCertFile)
663{
664 /*
665 * Collect the standard assortment of SSL certificates.
666 */
667 uint32_t cHint = RTCrStoreCertCount(*phStore);
668 RTCRSTORE hNewStore;
669 int rc = RTCrStoreCreateInMem(&hNewStore, cHint > 32 && cHint < _32K ? cHint + 16 : 256);
670 if (RT_SUCCESS(rc))
671 {
672 RTERRINFOSTATIC StaticErrInfo;
673 rc = RTHttpGatherCaCertsInStore(hNewStore, 0 /*fFlags*/, RTErrInfoInitStatic(&StaticErrInfo));
674 if (RTErrInfoIsSet(&StaticErrInfo.Core))
675 LogRel(("refreshCertificates/#1: %s\n", StaticErrInfo.Core.pszMsg));
676 else if (rc == VERR_NOT_FOUND)
677 LogRel(("refreshCertificates/#1: No trusted SSL certs found on the system, will try download...\n"));
678 else
679 AssertLogRelRC(rc);
680 if (RT_SUCCESS(rc) || rc == VERR_NOT_FOUND)
681 {
682 /*
683 * Check and see what we've got. If we haven't got all we desire,
684 * try add it from the previous store.
685 */
686 bool afNewFoundCerts[RT_ELEMENTS(s_aCerts)];
687 RT_ZERO(afNewFoundCerts); /* paranoia */
688
689 rc = RTCrStoreCertCheckWanted(hNewStore, s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts);
690 AssertLogRelRC(rc);
691 Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
692 if (rc != VINF_SUCCESS)
693 {
694 rc = RTCrStoreCertAddWantedFromStore(hNewStore,
695 RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
696 *phStore, s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts);
697 AssertLogRelRC(rc);
698 Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
699 }
700
701 /*
702 * If that didn't help, seek out certificates in more obscure places,
703 * like java, mozilla and mutt.
704 */
705 if (rc != VINF_SUCCESS)
706 {
707 rc = RTCrStoreCertAddWantedFromFishingExpedition(hNewStore,
708 RTCRCERTCTX_F_ADD_IF_NOT_FOUND
709 | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
710 s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts,
711 RTErrInfoInitStatic(&StaticErrInfo));
712 if (RTErrInfoIsSet(&StaticErrInfo.Core))
713 LogRel(("refreshCertificates/#2: %s\n", StaticErrInfo.Core.pszMsg));
714 Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
715 }
716
717 /*
718 * If we've got the same or better hit rate than the old store,
719 * replace the CA certs file.
720 */
721 if ( areAllCertsFound(afNewFoundCerts)
722 || countCertsFound(afNewFoundCerts) >= countCertsFound(pafFoundCerts) )
723 {
724 rc = RTCrStoreCertExportAsPem(hNewStore, 0 /*fFlags*/, pszCaCertFile);
725 if (RT_SUCCESS(rc))
726 {
727 LogRel(("refreshCertificates/#3: Found %u/%u SSL certs we/you trust (previously %u/%u).\n",
728 countCertsFound(afNewFoundCerts), RTCrStoreCertCount(hNewStore),
729 countCertsFound(pafFoundCerts), RTCrStoreCertCount(*phStore) ));
730
731 memcpy(pafFoundCerts, afNewFoundCerts, sizeof(afNewFoundCerts));
732 RTCrStoreRelease(*phStore);
733 *phStore = hNewStore;
734 hNewStore = NIL_RTCRSTORE;
735 }
736 else
737 {
738 RT_ZERO(pafFoundCerts);
739 LogRel(("refreshCertificates/#3: RTCrStoreCertExportAsPem unexpectedly failed with %Rrc\n", rc));
740 }
741 }
742 else
743 LogRel(("refreshCertificates/#3: Sticking with the old file, missing essential certs.\n"));
744 }
745 RTCrStoreRelease(hNewStore);
746 }
747 return rc;
748}
749
750/* static */
751DECLCALLBACK(void) UINetworkReplyPrivateThread::handleProgressChange(RTHTTP hHttp, void *pvUser, uint64_t cbDownloadTotal, uint64_t cbDownloaded)
752{
753 /* Redirect callback to particular object: */
754 Q_UNUSED(hHttp);
755 AssertPtrReturnVoid(pvUser);
756 static_cast<UINetworkReplyPrivateThread*>(pvUser)->handleProgressChange(cbDownloadTotal, cbDownloaded);
757}
758
759
760#ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
761
762
763/*********************************************************************************************************************************
764* Class UINetworkReplyPrivate implementation. *
765*********************************************************************************************************************************/
766
767UINetworkReplyPrivate::UINetworkReplyPrivate(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders)
768 : m_error(UINetworkReply::NoError)
769 , m_pThread(0)
770{
771 /* Prepare full error template: */
772 m_strErrorTemplate = tr("%1: %2", "Context description: Error description");
773
774 /* Create and run reply thread: */
775 m_pThread = new UINetworkReplyPrivateThread(type, url, strTarget, requestHeaders);
776 connect(m_pThread, &UINetworkReplyPrivateThread::sigDownloadProgress,
777 this, &UINetworkReplyPrivate::downloadProgress, Qt::QueuedConnection);
778 connect(m_pThread, &UINetworkReplyPrivateThread::finished,
779 this, &UINetworkReplyPrivate::sltFinished);
780 m_pThread->start();
781}
782
783UINetworkReplyPrivate::~UINetworkReplyPrivate()
784{
785 /* Terminate network-reply thread: */
786 m_pThread->abort();
787 m_pThread->wait();
788 delete m_pThread;
789 m_pThread = 0;
790}
791
792QString UINetworkReplyPrivate::errorString() const
793{
794 /* Look for known error codes: */
795 switch (m_error)
796 {
797 case UINetworkReply::NoError: break;
798 case UINetworkReply::RemoteHostClosedError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Unable to initialize HTTP library"));
799 case UINetworkReply::UrlNotFoundError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Url not found on the server"));
800 case UINetworkReply::HostNotFoundError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Host not found"));
801 case UINetworkReply::ContentAccessDenied: return m_strErrorTemplate.arg(m_pThread->context(), tr("Content access denied"));
802 case UINetworkReply::ProtocolFailure: return m_strErrorTemplate.arg(m_pThread->context(), tr("Protocol failure"));
803 case UINetworkReply::ConnectionRefusedError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Connection refused"));
804 case UINetworkReply::SslHandshakeFailedError: return m_strErrorTemplate.arg(m_pThread->context(), tr("SSL authentication failed"));
805 case UINetworkReply::AuthenticationRequiredError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Wrong SSL certificate format"));
806 case UINetworkReply::ContentReSendError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Content moved"));
807 case UINetworkReply::ProxyNotFoundError: return m_strErrorTemplate.arg(m_pThread->context(), tr("Proxy not found"));
808 default: return m_strErrorTemplate.arg(m_pThread->context(), tr("Unknown reason"));
809 }
810 /* Return null-string by default: */
811 return QString();
812}
813
814void UINetworkReplyPrivate::sltFinished()
815{
816 /* Look for known error codes: */
817 switch (m_pThread->error())
818 {
819 case VINF_SUCCESS: m_error = UINetworkReply::NoError; break;
820 case VERR_HTTP_INIT_FAILED: m_error = UINetworkReply::RemoteHostClosedError; break;
821 case VERR_HTTP_NOT_FOUND: m_error = UINetworkReply::UrlNotFoundError; break;
822 case VERR_HTTP_HOST_NOT_FOUND: m_error = UINetworkReply::HostNotFoundError; break;
823 case VERR_HTTP_ACCESS_DENIED: m_error = UINetworkReply::ContentAccessDenied; break;
824 case VERR_HTTP_BAD_REQUEST: m_error = UINetworkReply::ProtocolFailure; break;
825 case VERR_HTTP_COULDNT_CONNECT: m_error = UINetworkReply::ConnectionRefusedError; break;
826 case VERR_HTTP_SSL_CONNECT_ERROR: m_error = UINetworkReply::SslHandshakeFailedError; break;
827 case VERR_HTTP_CACERT_WRONG_FORMAT: m_error = UINetworkReply::AuthenticationRequiredError; break;
828 case VERR_HTTP_CACERT_CANNOT_AUTHENTICATE: m_error = UINetworkReply::AuthenticationRequiredError; break;
829 case VERR_HTTP_ABORTED: m_error = UINetworkReply::OperationCanceledError; break;
830 case VERR_HTTP_REDIRECTED: m_error = UINetworkReply::ContentReSendError; break;
831 case VERR_HTTP_PROXY_NOT_FOUND: m_error = UINetworkReply::ProxyNotFoundError; break;
832 default: m_error = UINetworkReply::UnknownNetworkError; break;
833 }
834 /* Redirect signal to external listeners: */
835 emit finished();
836}
837
838
839/*********************************************************************************************************************************
840* Class UINetworkReply implementation. *
841*********************************************************************************************************************************/
842
843UINetworkReply::UINetworkReply(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders)
844 : m_pReply(new UINetworkReplyPrivate(type, url, strTarget, requestHeaders))
845{
846 /* Prepare network-reply object connections: */
847 connect(m_pReply, &UINetworkReplyPrivate::downloadProgress, this, &UINetworkReply::downloadProgress);
848 connect(m_pReply, &UINetworkReplyPrivate::finished, this, &UINetworkReply::finished);
849}
850
851UINetworkReply::~UINetworkReply()
852{
853 /* Cleanup network-reply object: */
854 if (m_pReply)
855 {
856 delete m_pReply;
857 m_pReply = 0;
858 }
859}
860
861void UINetworkReply::abort()
862{
863 return m_pReply->abort();
864}
865
866QUrl UINetworkReply::url() const
867{
868 return m_pReply->url();
869}
870
871UINetworkReply::NetworkError UINetworkReply::error() const
872{
873 return m_pReply->error();
874}
875
876QString UINetworkReply::errorString() const
877{
878 return m_pReply->errorString();
879}
880
881QByteArray UINetworkReply::readAll() const
882{
883 return m_pReply->readAll();
884}
885
886QVariant UINetworkReply::header(UINetworkReply::KnownHeader header) const
887{
888 return m_pReply->header(header);
889}
890
891#include "UINetworkReply.moc"
892
893#endif /* !VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette