1 | /* $Id: UINetworkReply.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * VBox Qt GUI - UINetworkReply stuff implementation.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2012-2024 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. */
|
---|
60 | class UINetworkReplyPrivateThread : public QThread
|
---|
61 | {
|
---|
62 | #ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
|
---|
63 | Q_OBJECT;
|
---|
64 |
|
---|
65 | signals:
|
---|
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 |
|
---|
73 | public:
|
---|
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 |
|
---|
98 | private:
|
---|
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() RT_OVERRIDE RT_FINAL;
|
---|
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
|
---|
190 | public:
|
---|
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. */
|
---|
200 | class UINetworkReplyPrivate : public QObject
|
---|
201 | {
|
---|
202 | Q_OBJECT;
|
---|
203 |
|
---|
204 | signals:
|
---|
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 |
|
---|
214 | public:
|
---|
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 |
|
---|
237 | private slots:
|
---|
238 |
|
---|
239 | /** Handles signal about reply has finished processing. */
|
---|
240 | void sltFinished();
|
---|
241 |
|
---|
242 | private:
|
---|
243 |
|
---|
244 | /** Holds the last cached error of the reply. */
|
---|
245 | UINetworkReply::NetworkError m_error;
|
---|
246 |
|
---|
247 | /** Holds the reply thread instance. */
|
---|
248 | UINetworkReplyPrivateThread *m_pThread;
|
---|
249 | };
|
---|
250 | #endif /* !VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
251 |
|
---|
252 |
|
---|
253 | /*********************************************************************************************************************************
|
---|
254 | * Class UINetworkReplyPrivateThread implementation. *
|
---|
255 | *********************************************************************************************************************************/
|
---|
256 |
|
---|
257 | /* static */
|
---|
258 | const RTCRCERTWANTED UINetworkReplyPrivateThread::s_aCerts[] =
|
---|
259 | {
|
---|
260 | /*[0] =*/
|
---|
261 | {
|
---|
262 | /*.pszSubject =*/
|
---|
263 | "C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert Global Root CA",
|
---|
264 | /*.cbEncoded =*/ 0x3b3,
|
---|
265 | /*.Sha1Fingerprint =*/ true,
|
---|
266 | /*.Sha512Fingerprint =*/ true,
|
---|
267 | /*.abSha1 =*/
|
---|
268 | {
|
---|
269 | 0xa8, 0x98, 0x5d, 0x3a, 0x65, 0xe5, 0xe5, 0xc4, 0xb2, 0xd7,
|
---|
270 | 0xd6, 0x6d, 0x40, 0xc6, 0xdd, 0x2f, 0xb1, 0x9c, 0x54, 0x36
|
---|
271 | },
|
---|
272 | /*.abSha512 =*/
|
---|
273 | {
|
---|
274 | 0x53, 0xb4, 0x44, 0xe5, 0x65, 0x18, 0x32, 0x01,
|
---|
275 | 0xa6, 0x1e, 0xeb, 0x46, 0x12, 0x09, 0xb2, 0xdc,
|
---|
276 | 0x30, 0x89, 0x5e, 0xec, 0xa4, 0x87, 0x23, 0x8d,
|
---|
277 | 0x15, 0xa0, 0x26, 0x73, 0x5f, 0x22, 0x9a, 0x81,
|
---|
278 | 0x9e, 0x5b, 0x19, 0xcb, 0xd7, 0xe2, 0xfa, 0x27,
|
---|
279 | 0x68, 0xab, 0x2a, 0x64, 0xf6, 0xeb, 0xcd, 0x9d,
|
---|
280 | 0x1e, 0x72, 0x13, 0x41, 0xc9, 0xed, 0x5d, 0xd0,
|
---|
281 | 0x9f, 0xc0, 0xd5, 0xe4, 0x3d, 0x68, 0xbc, 0xa7
|
---|
282 | },
|
---|
283 | /*.pvUser =*/ NULL,
|
---|
284 | },
|
---|
285 | };
|
---|
286 |
|
---|
287 | /* static */
|
---|
288 | const QString UINetworkReplyPrivateThread::s_strCertificateFileName = QString("vbox-ssl-cacertificate.crt");
|
---|
289 |
|
---|
290 | UINetworkReplyPrivateThread::UINetworkReplyPrivateThread(UINetworkRequestType type,
|
---|
291 | const QUrl &url,
|
---|
292 | const QString &strTarget,
|
---|
293 | const UserDictionary &requestHeaders)
|
---|
294 | : m_type(type)
|
---|
295 | , m_url(url)
|
---|
296 | , m_strTarget(strTarget)
|
---|
297 | , m_requestHeaders(requestHeaders)
|
---|
298 | , m_hHttp(NIL_RTHTTP)
|
---|
299 | , m_iError(VINF_SUCCESS)
|
---|
300 | {
|
---|
301 | }
|
---|
302 |
|
---|
303 | void UINetworkReplyPrivateThread::abort()
|
---|
304 | {
|
---|
305 | /* Call for abort: */
|
---|
306 | if (m_hHttp != NIL_RTHTTP)
|
---|
307 | RTHttpAbort(m_hHttp);
|
---|
308 | }
|
---|
309 |
|
---|
310 | QString UINetworkReplyPrivateThread::header(UINetworkReply::KnownHeader type) const
|
---|
311 | {
|
---|
312 | /* Look for known header type: */
|
---|
313 | switch (type)
|
---|
314 | {
|
---|
315 | case UINetworkReply::ContentTypeHeader: return m_headers.value("Content-Type");
|
---|
316 | case UINetworkReply::ContentLengthHeader: return m_headers.value("Content-Length");
|
---|
317 | case UINetworkReply::LastModifiedHeader: return m_headers.value("Last-Modified");
|
---|
318 | case UINetworkReply::LocationHeader: return m_headers.value("Location");
|
---|
319 | default: break;
|
---|
320 | }
|
---|
321 | /* Return null-string by default: */
|
---|
322 | return QString();
|
---|
323 | }
|
---|
324 |
|
---|
325 | int UINetworkReplyPrivateThread::applyConfiguration()
|
---|
326 | {
|
---|
327 | /* Install downloading progress callback: */
|
---|
328 | return RTHttpSetDownloadProgressCallback(m_hHttp, &UINetworkReplyPrivateThread::handleProgressChange, this);
|
---|
329 | }
|
---|
330 |
|
---|
331 | int UINetworkReplyPrivateThread::applyProxyRules()
|
---|
332 | {
|
---|
333 | /* Set thread context: */
|
---|
334 | m_strContext = tr("During proxy configuration");
|
---|
335 |
|
---|
336 | #ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
|
---|
337 | /* If the specific proxy settings are enabled, we'll use them
|
---|
338 | * unless user disabled that functionality manually. */
|
---|
339 | const CSystemProperties comProperties = gpGlobalSession->virtualBox().GetSystemProperties();
|
---|
340 | const KProxyMode enmProxyMode = comProperties.GetProxyMode();
|
---|
341 | AssertReturn(comProperties.isOk(), VERR_INTERNAL_ERROR_3);
|
---|
342 | switch (enmProxyMode)
|
---|
343 | {
|
---|
344 | case KProxyMode_Manual:
|
---|
345 | return RTHttpSetProxyByUrl(m_hHttp, comProperties.GetProxyURL().toUtf8().constData());
|
---|
346 | case KProxyMode_NoProxy:
|
---|
347 | return VINF_SUCCESS;
|
---|
348 | default:
|
---|
349 | break;
|
---|
350 | }
|
---|
351 | #endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
352 |
|
---|
353 | /* By default, use system proxy: */
|
---|
354 | return RTHttpUseSystemProxySettings(m_hHttp);
|
---|
355 | }
|
---|
356 |
|
---|
357 | int UINetworkReplyPrivateThread::applyHttpsCertificates()
|
---|
358 | {
|
---|
359 | /* Check if we really need SSL: */
|
---|
360 | if (!url().toString().startsWith("https:", Qt::CaseInsensitive))
|
---|
361 | return VINF_SUCCESS;
|
---|
362 |
|
---|
363 | /* Set thread context: */
|
---|
364 | m_strContext = tr("During certificate downloading");
|
---|
365 |
|
---|
366 | /*
|
---|
367 | * Calc the filename of the CA certificate file.
|
---|
368 | */
|
---|
369 | const QString strFullCertificateFileName(fullCertificateFileName());
|
---|
370 | QByteArray utf8FullCertificateFileName = strFullCertificateFileName.toUtf8();
|
---|
371 | const char *pszCaCertFile = utf8FullCertificateFileName.constData();
|
---|
372 |
|
---|
373 | /*
|
---|
374 | * Check the state of our CA certificate file, it's one of the following:
|
---|
375 | * - Missing, recreate from scratch (= refresh).
|
---|
376 | * - Everything is there and it is less than 28 days old, do nothing.
|
---|
377 | * - Everything is there but it's older than 28 days, refresh.
|
---|
378 | * - Missing certificates and is older than 1 min, refresh.
|
---|
379 | *
|
---|
380 | * Start by creating a store for loading the current state into, as we'll
|
---|
381 | * be need that for the refresh.
|
---|
382 | */
|
---|
383 | RTCRSTORE hCurStore = NIL_RTCRSTORE;
|
---|
384 | int rc = RTCrStoreCreateInMem(&hCurStore, 256);
|
---|
385 | if (RT_SUCCESS(rc))
|
---|
386 | {
|
---|
387 | bool fRefresh = true;
|
---|
388 | bool afCertsFound[RT_ELEMENTS(s_aCerts)];
|
---|
389 | RT_ZERO(afCertsFound);
|
---|
390 |
|
---|
391 | /*
|
---|
392 | * Load the file if it exists.
|
---|
393 | *
|
---|
394 | * To effect regular updates, we need the modification date of the file,
|
---|
395 | * so we use RTPathQueryInfoEx here and not RTFileExists.
|
---|
396 | */
|
---|
397 | RTFSOBJINFO Info;
|
---|
398 | rc = RTPathQueryInfoEx(pszCaCertFile, &Info, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
|
---|
399 | if ( RT_SUCCESS(rc)
|
---|
400 | && RTFS_IS_FILE(Info.Attr.fMode))
|
---|
401 | {
|
---|
402 | RTERRINFOSTATIC StaticErrInfo;
|
---|
403 | rc = RTCrStoreCertAddFromFile(hCurStore, RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, pszCaCertFile,
|
---|
404 | RTErrInfoInitStatic(&StaticErrInfo));
|
---|
405 | if (RTErrInfoIsSet(&StaticErrInfo.Core))
|
---|
406 | LogRel(("checkCertificates: %s\n", StaticErrInfo.Core.pszMsg));
|
---|
407 | else
|
---|
408 | AssertRC(rc);
|
---|
409 |
|
---|
410 | /*
|
---|
411 | * Scan the store the for certificates we need, then see what we
|
---|
412 | * need to do wrt file age.
|
---|
413 | */
|
---|
414 | rc = RTCrStoreCertCheckWanted(hCurStore, s_aCerts, RT_ELEMENTS(s_aCerts), afCertsFound);
|
---|
415 | AssertRC(rc);
|
---|
416 | RTTIMESPEC RefreshAge;
|
---|
417 | uint32_t cSecRefresh = rc == VINF_SUCCESS ? 28 * RT_SEC_1DAY /* all found */ : 60 /* stuff missing */;
|
---|
418 | fRefresh = RTTimeSpecCompare(&Info.ModificationTime, RTTimeSpecSubSeconds(RTTimeNow(&RefreshAge), cSecRefresh)) <= 0;
|
---|
419 | }
|
---|
420 |
|
---|
421 | /*
|
---|
422 | * Refresh the file if necessary.
|
---|
423 | */
|
---|
424 | if (fRefresh)
|
---|
425 | refreshCertificates(&hCurStore, afCertsFound, pszCaCertFile);
|
---|
426 |
|
---|
427 | RTCrStoreRelease(hCurStore);
|
---|
428 |
|
---|
429 | /*
|
---|
430 | * Final verdict.
|
---|
431 | */
|
---|
432 | if (areAllCertsFound(afCertsFound))
|
---|
433 | rc = VINF_SUCCESS;
|
---|
434 | else
|
---|
435 | rc = VERR_NOT_FOUND; /** @todo r=bird: Why not try and let RTHttpGet* bitch if the necessary certs are missing? */
|
---|
436 |
|
---|
437 | /*
|
---|
438 | * Set our custom CA file.
|
---|
439 | */
|
---|
440 | if (RT_SUCCESS(rc))
|
---|
441 | rc = RTHttpSetCAFile(m_hHttp, pszCaCertFile);
|
---|
442 | }
|
---|
443 | return rc;
|
---|
444 | }
|
---|
445 |
|
---|
446 | int UINetworkReplyPrivateThread::applyRawHeaders()
|
---|
447 | {
|
---|
448 | /* Set thread context: */
|
---|
449 | m_strContext = tr("During network request");
|
---|
450 |
|
---|
451 | /* Make sure we have a raw headers at all: */
|
---|
452 | if (m_requestHeaders.isEmpty())
|
---|
453 | return VINF_SUCCESS;
|
---|
454 |
|
---|
455 | /* Apply raw headers: */
|
---|
456 | return applyRawHeaders(m_hHttp, m_requestHeaders);
|
---|
457 | }
|
---|
458 |
|
---|
459 | int UINetworkReplyPrivateThread::performMainRequest()
|
---|
460 | {
|
---|
461 | /* Set thread context: */
|
---|
462 | m_strContext = tr("During network request");
|
---|
463 |
|
---|
464 | /* Paranoia: */
|
---|
465 | m_reply.clear();
|
---|
466 |
|
---|
467 | /* Prepare result: */
|
---|
468 | int rc = 0;
|
---|
469 |
|
---|
470 | /* Depending on request type: */
|
---|
471 | switch (m_type)
|
---|
472 | {
|
---|
473 | case UINetworkRequestType_HEAD:
|
---|
474 | {
|
---|
475 | /* Perform blocking HTTP HEAD request: */
|
---|
476 | void *pvResponse = 0;
|
---|
477 | size_t cbResponse = 0;
|
---|
478 | rc = RTHttpGetHeaderBinary(m_hHttp, m_url.toString().toUtf8().constData(), &pvResponse, &cbResponse);
|
---|
479 | if (RT_SUCCESS(rc))
|
---|
480 | {
|
---|
481 | m_reply = QByteArray((char*)pvResponse, (int)cbResponse);
|
---|
482 | RTHttpFreeResponse(pvResponse);
|
---|
483 | }
|
---|
484 |
|
---|
485 | /* Paranoia: */
|
---|
486 | m_headers.clear();
|
---|
487 |
|
---|
488 | /* Parse header contents: */
|
---|
489 | const QString strHeaders = QString(m_reply);
|
---|
490 | foreach (const QString &strHeader, strHeaders.split("\n", Qt::SkipEmptyParts))
|
---|
491 | {
|
---|
492 | const QStringList values = strHeader.split(": ", Qt::SkipEmptyParts);
|
---|
493 | if (values.size() > 1)
|
---|
494 | m_headers[values.at(0)] = values.at(1);
|
---|
495 | }
|
---|
496 |
|
---|
497 | /* Special handling of redirection header: */
|
---|
498 | if (rc == VERR_HTTP_REDIRECTED)
|
---|
499 | {
|
---|
500 | char *pszBuf = 0;
|
---|
501 | const int rrc = RTHttpGetRedirLocation(m_hHttp, &pszBuf);
|
---|
502 | if (RT_SUCCESS(rrc))
|
---|
503 | m_headers["Location"] = QString(pszBuf);
|
---|
504 | if (pszBuf)
|
---|
505 | RTMemFree(pszBuf);
|
---|
506 | }
|
---|
507 |
|
---|
508 | break;
|
---|
509 | }
|
---|
510 | case UINetworkRequestType_GET:
|
---|
511 | {
|
---|
512 | /* Perform blocking HTTP GET request.
|
---|
513 | * Keep in mind that if the target parameter is provided,
|
---|
514 | * we are trying to download contents to file directly,
|
---|
515 | * otherwise it will be downloaded to memory and it's
|
---|
516 | * customer responsibility to save it afterwards. */
|
---|
517 | if (m_strTarget.isEmpty())
|
---|
518 | {
|
---|
519 | void *pvResponse = 0;
|
---|
520 | size_t cbResponse = 0;
|
---|
521 | rc = RTHttpGetBinary(m_hHttp, m_url.toString().toUtf8().constData(), &pvResponse, &cbResponse);
|
---|
522 | if (RT_SUCCESS(rc))
|
---|
523 | {
|
---|
524 | m_reply = QByteArray((char*)pvResponse, (int)cbResponse);
|
---|
525 | RTHttpFreeResponse(pvResponse);
|
---|
526 | }
|
---|
527 | }
|
---|
528 | else
|
---|
529 | {
|
---|
530 | rc = RTHttpGetFile(m_hHttp, m_url.toString().toUtf8().constData(), m_strTarget.toUtf8().constData());
|
---|
531 | if (RT_SUCCESS(rc))
|
---|
532 | {
|
---|
533 | QFile file(m_strTarget);
|
---|
534 | if (file.open(QIODevice::ReadOnly))
|
---|
535 | m_reply = file.readAll();
|
---|
536 | }
|
---|
537 | }
|
---|
538 |
|
---|
539 | break;
|
---|
540 | }
|
---|
541 | default:
|
---|
542 | break;
|
---|
543 | }
|
---|
544 |
|
---|
545 | /* Return result: */
|
---|
546 | return rc;
|
---|
547 | }
|
---|
548 |
|
---|
549 | void UINetworkReplyPrivateThread::run()
|
---|
550 | {
|
---|
551 | /* Create HTTP client: */
|
---|
552 | m_iError = RTHttpCreate(&m_hHttp);
|
---|
553 | if (RT_SUCCESS(m_iError))
|
---|
554 | {
|
---|
555 | /* Apply configuration: */
|
---|
556 | if (RT_SUCCESS(m_iError))
|
---|
557 | m_iError = applyConfiguration();
|
---|
558 |
|
---|
559 | /* Apply proxy-rules: */
|
---|
560 | if (RT_SUCCESS(m_iError))
|
---|
561 | m_iError = applyProxyRules();
|
---|
562 |
|
---|
563 | /* Apply https-certificates: */
|
---|
564 | if (RT_SUCCESS(m_iError))
|
---|
565 | m_iError = applyHttpsCertificates();
|
---|
566 |
|
---|
567 | /* Assign raw-headers: */
|
---|
568 | if (RT_SUCCESS(m_iError))
|
---|
569 | m_iError = applyRawHeaders();
|
---|
570 |
|
---|
571 | /* Perform main request: */
|
---|
572 | if (RT_SUCCESS(m_iError))
|
---|
573 | m_iError = performMainRequest();
|
---|
574 |
|
---|
575 | /* Destroy HTTP client: */
|
---|
576 | RTHTTP hHttp = m_hHttp;
|
---|
577 | if (hHttp != NIL_RTHTTP)
|
---|
578 | {
|
---|
579 | /** @todo r=bird: There is a race here between this and abort()! */
|
---|
580 | m_hHttp = NIL_RTHTTP;
|
---|
581 | RTHttpDestroy(hHttp);
|
---|
582 | }
|
---|
583 | }
|
---|
584 | }
|
---|
585 |
|
---|
586 | void UINetworkReplyPrivateThread::handleProgressChange(uint64_t cbDownloadTotal, uint64_t cbDownloaded)
|
---|
587 | {
|
---|
588 | #ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
|
---|
589 | /* Notify listeners about progress change: */
|
---|
590 | emit sigDownloadProgress(cbDownloaded, cbDownloadTotal);
|
---|
591 | #else /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
592 | Q_UNUSED(cbDownloaded);
|
---|
593 | Q_UNUSED(cbDownloadTotal);
|
---|
594 | #endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
595 | }
|
---|
596 |
|
---|
597 | /* static */
|
---|
598 | QString UINetworkReplyPrivateThread::fullCertificateFileName()
|
---|
599 | {
|
---|
600 | #ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
|
---|
601 | const QDir homeDir(QDir::toNativeSeparators(gpGlobalSession->homeFolder()));
|
---|
602 | return QDir::toNativeSeparators(homeDir.absoluteFilePath(s_strCertificateFileName));
|
---|
603 | #else /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
604 | return QString("/not/such/agency/non-existing-file.cer");
|
---|
605 | #endif /* VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|
606 | }
|
---|
607 |
|
---|
608 | /* static */
|
---|
609 | int UINetworkReplyPrivateThread::applyRawHeaders(RTHTTP hHttp, const UserDictionary &headers)
|
---|
610 | {
|
---|
611 | /* Make sure HTTP is created: */
|
---|
612 | if (hHttp == NIL_RTHTTP)
|
---|
613 | return VERR_INVALID_HANDLE;
|
---|
614 |
|
---|
615 | /* We should format them first: */
|
---|
616 | QVector<QByteArray> formattedHeaders;
|
---|
617 | QVector<const char*> formattedHeaderPointers;
|
---|
618 | foreach (const QString &header, headers.keys())
|
---|
619 | {
|
---|
620 | /* Prepare formatted representation: */
|
---|
621 | QString strFormattedString = QString("%1: %2").arg(header, headers.value(header));
|
---|
622 | formattedHeaders << strFormattedString.toUtf8();
|
---|
623 | formattedHeaderPointers << formattedHeaders.last().constData();
|
---|
624 | }
|
---|
625 | const char **ppFormattedHeaders = formattedHeaderPointers.data();
|
---|
626 |
|
---|
627 | /* Apply HTTP headers: */
|
---|
628 | return RTHttpSetHeaders(hHttp, formattedHeaderPointers.size(), ppFormattedHeaders);
|
---|
629 | }
|
---|
630 |
|
---|
631 | /* static */
|
---|
632 | unsigned UINetworkReplyPrivateThread::countCertsFound(bool const *pafFoundCerts)
|
---|
633 | {
|
---|
634 | unsigned cFound = 0;
|
---|
635 | for (uint32_t i = 0; i < RT_ELEMENTS(s_aCerts); i++)
|
---|
636 | cFound += pafFoundCerts[i];
|
---|
637 | return cFound;
|
---|
638 | }
|
---|
639 |
|
---|
640 | /* static */
|
---|
641 | bool UINetworkReplyPrivateThread::areAllCertsFound(bool const *pafFoundCerts)
|
---|
642 | {
|
---|
643 | for (uint32_t i = 0; i < RT_ELEMENTS(s_aCerts); i++)
|
---|
644 | if (!pafFoundCerts[i])
|
---|
645 | return false;
|
---|
646 | return true;
|
---|
647 | }
|
---|
648 |
|
---|
649 | /* static */
|
---|
650 | int UINetworkReplyPrivateThread::refreshCertificates(PRTCRSTORE phStore, bool *pafFoundCerts, const char *pszCaCertFile)
|
---|
651 | {
|
---|
652 | /*
|
---|
653 | * Collect the standard assortment of SSL certificates.
|
---|
654 | */
|
---|
655 | uint32_t cHint = RTCrStoreCertCount(*phStore);
|
---|
656 | RTCRSTORE hNewStore;
|
---|
657 | int rc = RTCrStoreCreateInMem(&hNewStore, cHint > 32 && cHint < _32K ? cHint + 16 : 256);
|
---|
658 | if (RT_SUCCESS(rc))
|
---|
659 | {
|
---|
660 | RTERRINFOSTATIC StaticErrInfo;
|
---|
661 | rc = RTHttpGatherCaCertsInStore(hNewStore, 0 /*fFlags*/, RTErrInfoInitStatic(&StaticErrInfo));
|
---|
662 | if (RTErrInfoIsSet(&StaticErrInfo.Core))
|
---|
663 | LogRel(("refreshCertificates/#1: %s\n", StaticErrInfo.Core.pszMsg));
|
---|
664 | else if (rc == VERR_NOT_FOUND)
|
---|
665 | LogRel(("refreshCertificates/#1: No trusted SSL certs found on the system, will try download...\n"));
|
---|
666 | else
|
---|
667 | AssertLogRelRC(rc);
|
---|
668 | if (RT_SUCCESS(rc) || rc == VERR_NOT_FOUND)
|
---|
669 | {
|
---|
670 | /*
|
---|
671 | * Check and see what we've got. If we haven't got all we desire,
|
---|
672 | * try add it from the previous store.
|
---|
673 | */
|
---|
674 | bool afNewFoundCerts[RT_ELEMENTS(s_aCerts)];
|
---|
675 | RT_ZERO(afNewFoundCerts); /* paranoia */
|
---|
676 |
|
---|
677 | rc = RTCrStoreCertCheckWanted(hNewStore, s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts);
|
---|
678 | AssertLogRelRC(rc);
|
---|
679 | Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
|
---|
680 | if (rc != VINF_SUCCESS)
|
---|
681 | {
|
---|
682 | rc = RTCrStoreCertAddWantedFromStore(hNewStore,
|
---|
683 | RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
|
---|
684 | *phStore, s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts);
|
---|
685 | AssertLogRelRC(rc);
|
---|
686 | Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
|
---|
687 | }
|
---|
688 |
|
---|
689 | /*
|
---|
690 | * If that didn't help, seek out certificates in more obscure places,
|
---|
691 | * like java, mozilla and mutt.
|
---|
692 | */
|
---|
693 | if (rc != VINF_SUCCESS)
|
---|
694 | {
|
---|
695 | rc = RTCrStoreCertAddWantedFromFishingExpedition(hNewStore,
|
---|
696 | RTCRCERTCTX_F_ADD_IF_NOT_FOUND
|
---|
697 | | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
|
---|
698 | s_aCerts, RT_ELEMENTS(s_aCerts), afNewFoundCerts,
|
---|
699 | RTErrInfoInitStatic(&StaticErrInfo));
|
---|
700 | if (RTErrInfoIsSet(&StaticErrInfo.Core))
|
---|
701 | LogRel(("refreshCertificates/#2: %s\n", StaticErrInfo.Core.pszMsg));
|
---|
702 | Assert(rc != VINF_SUCCESS || areAllCertsFound(afNewFoundCerts));
|
---|
703 | }
|
---|
704 |
|
---|
705 | /*
|
---|
706 | * If we've got the same or better hit rate than the old store,
|
---|
707 | * replace the CA certs file.
|
---|
708 | */
|
---|
709 | if ( areAllCertsFound(afNewFoundCerts)
|
---|
710 | || countCertsFound(afNewFoundCerts) >= countCertsFound(pafFoundCerts) )
|
---|
711 | {
|
---|
712 | rc = RTCrStoreCertExportAsPem(hNewStore, 0 /*fFlags*/, pszCaCertFile);
|
---|
713 | if (RT_SUCCESS(rc))
|
---|
714 | {
|
---|
715 | LogRel(("refreshCertificates/#3: Found %u/%u SSL certs we/you trust (previously %u/%u).\n",
|
---|
716 | countCertsFound(afNewFoundCerts), RTCrStoreCertCount(hNewStore),
|
---|
717 | countCertsFound(pafFoundCerts), RTCrStoreCertCount(*phStore) ));
|
---|
718 |
|
---|
719 | memcpy(pafFoundCerts, afNewFoundCerts, sizeof(afNewFoundCerts));
|
---|
720 | RTCrStoreRelease(*phStore);
|
---|
721 | *phStore = hNewStore;
|
---|
722 | hNewStore = NIL_RTCRSTORE;
|
---|
723 | }
|
---|
724 | else
|
---|
725 | {
|
---|
726 | RT_ZERO(pafFoundCerts);
|
---|
727 | LogRel(("refreshCertificates/#3: RTCrStoreCertExportAsPem unexpectedly failed with %Rrc\n", rc));
|
---|
728 | }
|
---|
729 | }
|
---|
730 | else
|
---|
731 | LogRel(("refreshCertificates/#3: Sticking with the old file, missing essential certs.\n"));
|
---|
732 | }
|
---|
733 | RTCrStoreRelease(hNewStore);
|
---|
734 | }
|
---|
735 | return rc;
|
---|
736 | }
|
---|
737 |
|
---|
738 | /* static */
|
---|
739 | DECLCALLBACK(void) UINetworkReplyPrivateThread::handleProgressChange(RTHTTP hHttp, void *pvUser, uint64_t cbDownloadTotal, uint64_t cbDownloaded)
|
---|
740 | {
|
---|
741 | /* Redirect callback to particular object: */
|
---|
742 | Q_UNUSED(hHttp);
|
---|
743 | AssertPtrReturnVoid(pvUser);
|
---|
744 | static_cast<UINetworkReplyPrivateThread*>(pvUser)->handleProgressChange(cbDownloadTotal, cbDownloaded);
|
---|
745 | }
|
---|
746 |
|
---|
747 |
|
---|
748 | #ifndef VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS
|
---|
749 |
|
---|
750 |
|
---|
751 | /*********************************************************************************************************************************
|
---|
752 | * Class UINetworkReplyPrivate implementation. *
|
---|
753 | *********************************************************************************************************************************/
|
---|
754 |
|
---|
755 | UINetworkReplyPrivate::UINetworkReplyPrivate(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders)
|
---|
756 | : m_error(UINetworkReply::NoError)
|
---|
757 | , m_pThread(0)
|
---|
758 | {
|
---|
759 | /* Create and run reply thread: */
|
---|
760 | m_pThread = new UINetworkReplyPrivateThread(type, url, strTarget, requestHeaders);
|
---|
761 | connect(m_pThread, &UINetworkReplyPrivateThread::sigDownloadProgress,
|
---|
762 | this, &UINetworkReplyPrivate::downloadProgress, Qt::QueuedConnection);
|
---|
763 | connect(m_pThread, &UINetworkReplyPrivateThread::finished,
|
---|
764 | this, &UINetworkReplyPrivate::sltFinished);
|
---|
765 | m_pThread->start();
|
---|
766 | }
|
---|
767 |
|
---|
768 | UINetworkReplyPrivate::~UINetworkReplyPrivate()
|
---|
769 | {
|
---|
770 | /* Terminate network-reply thread: */
|
---|
771 | m_pThread->abort();
|
---|
772 | m_pThread->wait();
|
---|
773 | delete m_pThread;
|
---|
774 | m_pThread = 0;
|
---|
775 | }
|
---|
776 |
|
---|
777 | QString UINetworkReplyPrivate::errorString() const
|
---|
778 | {
|
---|
779 | /* Look for known error codes: */
|
---|
780 | switch (m_error)
|
---|
781 | {
|
---|
782 | case UINetworkReply::NoError: break;
|
---|
783 | case UINetworkReply::RemoteHostClosedError: return QString("%1: %2").arg(m_pThread->context(), tr("Unable to initialize HTTP library"));
|
---|
784 | case UINetworkReply::UrlNotFoundError: return QString("%1: %2").arg(m_pThread->context(), tr("Url not found on the server"));
|
---|
785 | case UINetworkReply::HostNotFoundError: return QString("%1: %2").arg(m_pThread->context(), tr("Host not found"));
|
---|
786 | case UINetworkReply::ContentAccessDenied: return QString("%1: %2").arg(m_pThread->context(), tr("Content access denied"));
|
---|
787 | case UINetworkReply::ProtocolFailure: return QString("%1: %2").arg(m_pThread->context(), tr("Protocol failure"));
|
---|
788 | case UINetworkReply::ConnectionRefusedError: return QString("%1: %2").arg(m_pThread->context(), tr("Connection refused"));
|
---|
789 | case UINetworkReply::SslHandshakeFailedError: return QString("%1: %2").arg(m_pThread->context(), tr("SSL authentication failed"));
|
---|
790 | case UINetworkReply::AuthenticationRequiredError: return QString("%1: %2").arg(m_pThread->context(), tr("Wrong SSL certificate format"));
|
---|
791 | case UINetworkReply::ContentReSendError: return QString("%1: %2").arg(m_pThread->context(), tr("Content moved"));
|
---|
792 | case UINetworkReply::ProxyNotFoundError: return QString("%1: %2").arg(m_pThread->context(), tr("Proxy not found"));
|
---|
793 | default: return QString("%1: %2").arg(m_pThread->context(), tr("Unknown reason"));
|
---|
794 | }
|
---|
795 | /* Return null-string by default: */
|
---|
796 | return QString();
|
---|
797 | }
|
---|
798 |
|
---|
799 | void UINetworkReplyPrivate::sltFinished()
|
---|
800 | {
|
---|
801 | /* Look for known error codes: */
|
---|
802 | switch (m_pThread->error())
|
---|
803 | {
|
---|
804 | case VINF_SUCCESS: m_error = UINetworkReply::NoError; break;
|
---|
805 | case VERR_HTTP_INIT_FAILED: m_error = UINetworkReply::RemoteHostClosedError; break;
|
---|
806 | case VERR_HTTP_NOT_FOUND: m_error = UINetworkReply::UrlNotFoundError; break;
|
---|
807 | case VERR_HTTP_HOST_NOT_FOUND: m_error = UINetworkReply::HostNotFoundError; break;
|
---|
808 | case VERR_HTTP_ACCESS_DENIED: m_error = UINetworkReply::ContentAccessDenied; break;
|
---|
809 | case VERR_HTTP_BAD_REQUEST: m_error = UINetworkReply::ProtocolFailure; break;
|
---|
810 | case VERR_HTTP_COULDNT_CONNECT: m_error = UINetworkReply::ConnectionRefusedError; break;
|
---|
811 | case VERR_HTTP_SSL_CONNECT_ERROR: m_error = UINetworkReply::SslHandshakeFailedError; break;
|
---|
812 | case VERR_HTTP_CACERT_WRONG_FORMAT: m_error = UINetworkReply::AuthenticationRequiredError; break;
|
---|
813 | case VERR_HTTP_CACERT_CANNOT_AUTHENTICATE: m_error = UINetworkReply::AuthenticationRequiredError; break;
|
---|
814 | case VERR_HTTP_ABORTED: m_error = UINetworkReply::OperationCanceledError; break;
|
---|
815 | case VERR_HTTP_REDIRECTED: m_error = UINetworkReply::ContentReSendError; break;
|
---|
816 | case VERR_HTTP_PROXY_NOT_FOUND: m_error = UINetworkReply::ProxyNotFoundError; break;
|
---|
817 | default: m_error = UINetworkReply::UnknownNetworkError; break;
|
---|
818 | }
|
---|
819 | /* Redirect signal to external listeners: */
|
---|
820 | emit finished();
|
---|
821 | }
|
---|
822 |
|
---|
823 |
|
---|
824 | /*********************************************************************************************************************************
|
---|
825 | * Class UINetworkReply implementation. *
|
---|
826 | *********************************************************************************************************************************/
|
---|
827 |
|
---|
828 | UINetworkReply::UINetworkReply(UINetworkRequestType type, const QUrl &url, const QString &strTarget, const UserDictionary &requestHeaders)
|
---|
829 | : m_pReply(new UINetworkReplyPrivate(type, url, strTarget, requestHeaders))
|
---|
830 | {
|
---|
831 | /* Prepare network-reply object connections: */
|
---|
832 | connect(m_pReply, &UINetworkReplyPrivate::downloadProgress, this, &UINetworkReply::downloadProgress);
|
---|
833 | connect(m_pReply, &UINetworkReplyPrivate::finished, this, &UINetworkReply::finished);
|
---|
834 | }
|
---|
835 |
|
---|
836 | UINetworkReply::~UINetworkReply()
|
---|
837 | {
|
---|
838 | /* Cleanup network-reply object: */
|
---|
839 | if (m_pReply)
|
---|
840 | {
|
---|
841 | delete m_pReply;
|
---|
842 | m_pReply = 0;
|
---|
843 | }
|
---|
844 | }
|
---|
845 |
|
---|
846 | void UINetworkReply::abort()
|
---|
847 | {
|
---|
848 | return m_pReply->abort();
|
---|
849 | }
|
---|
850 |
|
---|
851 | QUrl UINetworkReply::url() const
|
---|
852 | {
|
---|
853 | return m_pReply->url();
|
---|
854 | }
|
---|
855 |
|
---|
856 | UINetworkReply::NetworkError UINetworkReply::error() const
|
---|
857 | {
|
---|
858 | return m_pReply->error();
|
---|
859 | }
|
---|
860 |
|
---|
861 | QString UINetworkReply::errorString() const
|
---|
862 | {
|
---|
863 | return m_pReply->errorString();
|
---|
864 | }
|
---|
865 |
|
---|
866 | QByteArray UINetworkReply::readAll() const
|
---|
867 | {
|
---|
868 | return m_pReply->readAll();
|
---|
869 | }
|
---|
870 |
|
---|
871 | QVariant UINetworkReply::header(UINetworkReply::KnownHeader header) const
|
---|
872 | {
|
---|
873 | return m_pReply->header(header);
|
---|
874 | }
|
---|
875 |
|
---|
876 | #include "UINetworkReply.moc"
|
---|
877 |
|
---|
878 | #endif /* !VBOX_GUI_IN_TST_SSL_CERT_DOWNLOADS */
|
---|