VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/misc/http.cpp@ 57720

Last change on this file since 57720 was 57720, checked in by vboxsync, 9 years ago

RTUri: Preps for parsing the authority bits into smaller pieces for cURL proxy config.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.2 KB
Line 
1/* $Id: http.cpp 57720 2015-09-11 14:49:21Z vboxsync $ */
2/** @file
3 * IPRT - HTTP communication API.
4 */
5
6/*
7 * Copyright (C) 2012-2015 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include <iprt/http.h>
32#include "internal/iprt.h"
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/crypto/store.h>
37#include <iprt/env.h>
38#include <iprt/err.h>
39#include <iprt/file.h>
40#include <iprt/mem.h>
41#include <iprt/path.h>
42#include <iprt/stream.h>
43#include <iprt/string.h>
44
45#include "internal/magics.h"
46
47#include <curl/curl.h>
48
49
50/*********************************************************************************************************************************
51* Structures and Typedefs *
52*********************************************************************************************************************************/
53/**
54 * Internal HTTP client instance.
55 */
56typedef struct RTHTTPINTERNAL
57{
58 /** Magic value. */
59 uint32_t u32Magic;
60 /** cURL handle. */
61 CURL *pCurl;
62 /** The last response code. */
63 long lLastResp;
64 /** Custom headers/ */
65 struct curl_slist *pHeaders;
66 /** CA certificate file for HTTPS authentication. */
67 char *pszCaFile;
68 /** Whether to delete the CA on destruction. */
69 bool fDeleteCaFile;
70 /** Set if we should use the system proxy settings for a URL.
71 * This means reconfiguring cURL for each request. */
72 bool fUseSystemProxySettings;
73 /** Abort the current HTTP request if true. */
74 bool volatile fAbort;
75 /** Set if someone is preforming an HTTP operation. */
76 bool volatile fBusy;
77 /** The location field for 301 responses. */
78 char *pszRedirLocation;
79
80 /** Output callback data. */
81 union
82 {
83 /** For file destination. */
84 RTFILE hFile;
85 /** For memory destination. */
86 struct
87 {
88 /** The current size (sans terminator char). */
89 size_t cb;
90 /** The currently allocated size. */
91 size_t cbAllocated;
92 /** Pointer to the buffer. */
93 uint8_t *pb;
94 } Mem;
95 } Output;
96 /** Output callback status. */
97 int rcOutput;
98 /** Download size hint set by the progress callback. */
99 uint64_t cbDownloadHint;
100} RTHTTPINTERNAL;
101/** Pointer to an internal HTTP client instance. */
102typedef RTHTTPINTERNAL *PRTHTTPINTERNAL;
103
104
105/*********************************************************************************************************************************
106* Defined Constants And Macros *
107*********************************************************************************************************************************/
108/** @def RTHTTP_MAX_MEM_DOWNLOAD
109 * The max size we are allowed to download to a memory buffer.
110 *
111 * @remarks The minus 1 is for the trailing zero terminator we always add.
112 */
113#if ARCH_BITS == 64
114# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1)
115#else
116# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1)
117#endif
118
119/** Checks whether a cURL return code indicates success. */
120#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK)
121/** Checks whether a cURL return code indicates failure. */
122#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK)
123
124/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
125#define RTHTTP_VALID_RETURN_RC(hHttp, rcCurl) \
126 do { \
127 AssertPtrReturn((hHttp), (rcCurl)); \
128 AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (rcCurl)); \
129 } while (0)
130
131/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
132#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE)
133
134/** Validates a handle and returns (void) if not valid. */
135#define RTHTTP_VALID_RETURN_VOID(hHttp) \
136 do { \
137 AssertPtrReturnVoid(hHttp); \
138 AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \
139 } while (0)
140
141
142/*********************************************************************************************************************************
143* Internal Functions *
144*********************************************************************************************************************************/
145static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis);
146
147
148RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp)
149{
150 AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER);
151
152 /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a
153 * problem if multiple threads get here at the same time. */
154 int rc = VERR_HTTP_INIT_FAILED;
155 CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL);
156 if (!CURL_FAILURE(rcCurl))
157 {
158 CURL *pCurl = curl_easy_init();
159 if (pCurl)
160 {
161 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL));
162 if (pThis)
163 {
164 pThis->u32Magic = RTHTTP_MAGIC;
165 pThis->pCurl = pCurl;
166 pThis->fUseSystemProxySettings = true;
167
168 *phHttp = (RTHTTP)pThis;
169
170 return VINF_SUCCESS;
171 }
172 rc = VERR_NO_MEMORY;
173 }
174 else
175 rc = VERR_HTTP_INIT_FAILED;
176 }
177 curl_global_cleanup();
178 return rc;
179}
180
181
182RTR3DECL(void) RTHttpDestroy(RTHTTP hHttp)
183{
184 if (hHttp == NIL_RTHTTP)
185 return;
186
187 PRTHTTPINTERNAL pThis = hHttp;
188 RTHTTP_VALID_RETURN_VOID(pThis);
189
190 Assert(!pThis->fBusy);
191
192 pThis->u32Magic = RTHTTP_MAGIC_DEAD;
193
194 curl_easy_cleanup(pThis->pCurl);
195 pThis->pCurl = NULL;
196
197 if (pThis->pHeaders)
198 curl_slist_free_all(pThis->pHeaders);
199
200 rtHttpUnsetCaFile(pThis);
201 Assert(!pThis->pszCaFile);
202
203 if (pThis->pszRedirLocation)
204 RTStrFree(pThis->pszRedirLocation);
205
206 RTMemFree(pThis);
207
208 curl_global_cleanup();
209}
210
211
212RTR3DECL(int) RTHttpAbort(RTHTTP hHttp)
213{
214 PRTHTTPINTERNAL pThis = hHttp;
215 RTHTTP_VALID_RETURN(pThis);
216
217 pThis->fAbort = true;
218
219 return VINF_SUCCESS;
220}
221
222
223RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation)
224{
225 PRTHTTPINTERNAL pThis = hHttp;
226 RTHTTP_VALID_RETURN(pThis);
227 Assert(!pThis->fBusy);
228
229 if (!pThis->pszRedirLocation)
230 return VERR_HTTP_NOT_FOUND;
231
232 return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation);
233}
234
235
236RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp)
237{
238 PRTHTTPINTERNAL pThis = hHttp;
239 RTHTTP_VALID_RETURN(pThis);
240
241 /*
242 * Very limited right now, just enought to make it work for ourselves.
243 */
244 char szProxy[_1K];
245 int rc = RTEnvGetEx(RTENV_DEFAULT, "http_proxy", szProxy, sizeof(szProxy), NULL);
246 if (RT_SUCCESS(rc))
247 {
248 int rcCurl;
249 if (!strncmp(szProxy, RT_STR_TUPLE("http://")))
250 {
251 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, &szProxy[sizeof("http://") - 1]);
252 if (CURL_FAILURE(rcCurl))
253 return VERR_INVALID_PARAMETER;
254 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, 80);
255 if (CURL_FAILURE(rcCurl))
256 return VERR_INVALID_PARAMETER;
257 }
258 else
259 {
260 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, &szProxy[sizeof("http://") - 1]);
261 if (CURL_FAILURE(rcCurl))
262 return VERR_INVALID_PARAMETER;
263 }
264 }
265 else if (rc == VERR_ENV_VAR_NOT_FOUND)
266 rc = VINF_SUCCESS;
267
268 return rc;
269}
270
271
272static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList)
273{
274 /** @todo implement me. */
275 return false;
276}
277
278
279static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
280{
281 const char *pszProxy = NULL;
282 long uPort = 0;
283 const char *pszUser = NULL;
284 const char *pszPassword = NULL;
285 bool fNoProxy = true;
286
287
288 char szTmp[_1K];
289
290 /*
291 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
292 */
293 const char *pszNoProxyVar;
294 size_t cchActual;
295 char *pszNoProxyFree = NULL;
296 char *pszNoProxy = szTmp;
297 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
298 if (rc == VERR_ENV_VAR_NOT_FOUND)
299 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
300 if (rc == VERR_BUFFER_OVERFLOW)
301 {
302 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
303 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
304 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
305 }
306 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
307 fNoProxy = rtHttpUrlInNoProxyList(pszUrl, pszNoProxy);
308 RTMemTmpFree(pszNoProxy);
309 if (!fNoProxy)
310 {
311 /*
312 * Get the schema specific specific env var, falling back on the
313 * generic all_proxy if not found.
314 */
315 const char *apszEnvVars[4];
316 unsigned cEnvVars = 0;
317 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
318 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
319 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
320 {
321 apszEnvVars[cEnvVars++] = "https_proxy";
322 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
323 }
324 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
325 {
326 apszEnvVars[cEnvVars++] = "ftp_proxy";
327 apszEnvVars[cEnvVars++] = "FTP_PROXY";
328 }
329 else
330 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
331 apszEnvVars[cEnvVars++] = "all_proxy";
332 apszEnvVars[cEnvVars++] = "ALL_PROXY";
333
334 for (uint32_t i = 0; i < cEnvVars; i++)
335 {
336 size_t cchValue;
337 rc = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
338 if (RT_SUCCESS(rc))
339 {
340 if (!strstr(szTmp, "://"))
341 {
342 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
343 memcpy(szTmp, RT_STR_TUPLE("http://"));
344 }
345 /** @todo continue where using RTUriParse... */
346 }
347 else
348 AssertMsg(rc == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n"));
349 }
350
351#if 0
352 if (RT_SUCCESS(rc))
353 {
354 int rcCurl;
355 if (!strncmp(szProxy, RT_STR_TUPLE("http://")))
356 {
357 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, &szProxy[sizeof("http://") - 1]);
358 if (CURL_FAILURE(rcCurl))
359 return VERR_INVALID_PARAMETER;
360 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, 80);
361 if (CURL_FAILURE(rcCurl))
362 return VERR_INVALID_PARAMETER;
363 }
364 else
365 {
366 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, &szProxy[sizeof("http://") - 1]);
367 if (CURL_FAILURE(rcCurl))
368 return VERR_INVALID_PARAMETER;
369 }
370 }
371 else if (rc == VERR_ENV_VAR_NOT_FOUND)
372 rc = VINF_SUCCESS;
373#endif
374 }
375
376 return rc;
377}
378
379
380RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
381 const char *pcszProxyUser, const char *pcszProxyPwd)
382{
383 PRTHTTPINTERNAL pThis = hHttp;
384 RTHTTP_VALID_RETURN(pThis);
385 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
386
387 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pcszProxy);
388 if (CURL_FAILURE(rcCurl))
389 return VERR_INVALID_PARAMETER;
390
391 if (uPort != 0)
392 {
393 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort);
394 if (CURL_FAILURE(rcCurl))
395 return VERR_INVALID_PARAMETER;
396 }
397
398 if (pcszProxyUser && pcszProxyPwd)
399 {
400 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pcszProxyUser);
401 if (CURL_FAILURE(rcCurl))
402 return VERR_INVALID_PARAMETER;
403
404 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pcszProxyPwd);
405 if (CURL_FAILURE(rcCurl))
406 return VERR_INVALID_PARAMETER;
407 }
408
409 return VINF_SUCCESS;
410}
411
412
413RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
414{
415 PRTHTTPINTERNAL pThis = hHttp;
416 RTHTTP_VALID_RETURN(pThis);
417
418 if (!cHeaders)
419 {
420 if (pThis->pHeaders)
421 curl_slist_free_all(pThis->pHeaders);
422 pThis->pHeaders = 0;
423 return VINF_SUCCESS;
424 }
425
426 struct curl_slist *pHeaders = NULL;
427 for (size_t i = 0; i < cHeaders; i++)
428 pHeaders = curl_slist_append(pHeaders, papszHeaders[i]);
429
430 pThis->pHeaders = pHeaders;
431 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
432 if (CURL_FAILURE(rcCurl))
433 return VERR_INVALID_PARAMETER;
434
435 return VINF_SUCCESS;
436}
437
438
439/**
440 * Set the CA file to NULL, deleting any temporary file if necessary.
441 *
442 * @param pThis The HTTP/HTTPS client instance.
443 */
444static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
445{
446 if (pThis->pszCaFile)
447 {
448 if (pThis->fDeleteCaFile)
449 {
450 int rc2 = RTFileDelete(pThis->pszCaFile);
451 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
452 }
453 RTStrFree(pThis->pszCaFile);
454 pThis->pszCaFile = NULL;
455 }
456}
457
458
459RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
460{
461 PRTHTTPINTERNAL pThis = hHttp;
462 RTHTTP_VALID_RETURN(pThis);
463
464 rtHttpUnsetCaFile(pThis);
465
466 pThis->fDeleteCaFile = false;
467 if (pszCaFile)
468 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
469 return VINF_SUCCESS;
470}
471
472
473RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
474{
475 PRTHTTPINTERNAL pThis = hHttp;
476 RTHTTP_VALID_RETURN(pThis);
477
478 /*
479 * Create a temporary file.
480 */
481 int rc = VERR_NO_STR_MEMORY;
482 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
483 if (pszCaFile)
484 {
485 RTFILE hFile;
486 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
487 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
488 if (RT_SUCCESS(rc))
489 {
490 /*
491 * Gather certificates into a temporary store and export them to the temporary file.
492 */
493 RTCRSTORE hStore;
494 rc = RTCrStoreCreateInMem(&hStore, 256);
495 if (RT_SUCCESS(rc))
496 {
497 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
498 if (RT_SUCCESS(rc))
499 /** @todo Consider adding an API for exporting to a RTFILE... */
500 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
501 RTCrStoreRelease(hStore);
502 }
503 RTFileClose(hFile);
504 if (RT_SUCCESS(rc))
505 {
506 /*
507 * Set the CA file for the instance.
508 */
509 rtHttpUnsetCaFile(pThis);
510
511 pThis->fDeleteCaFile = true;
512 pThis->pszCaFile = pszCaFile;
513 return VINF_SUCCESS;
514 }
515
516 int rc2 = RTFileDelete(pszCaFile);
517 AssertRC(rc2);
518 }
519 else
520 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
521
522 RTStrFree(pszCaFile);
523 }
524 return rc;
525}
526
527
528RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
529{
530 uint32_t const cBefore = RTCrStoreCertCount(hStore);
531 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
532
533 /*
534 * Add the user store, quitely ignoring any errors.
535 */
536 RTCRSTORE hSrcStore;
537 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
538 if (RT_SUCCESS(rcUser))
539 {
540 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
541 hSrcStore);
542 RTCrStoreRelease(hSrcStore);
543 }
544
545 /*
546 * Ditto for the system store.
547 */
548 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
549 if (RT_SUCCESS(rcSystem))
550 {
551 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
552 hSrcStore);
553 RTCrStoreRelease(hSrcStore);
554 }
555
556 /*
557 * If the number of certificates increased, we consider it a success.
558 */
559 if (RTCrStoreCertCount(hStore) > cBefore)
560 {
561 if (RT_FAILURE(rcSystem))
562 return -rcSystem;
563 if (RT_FAILURE(rcUser))
564 return -rcUser;
565 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
566 }
567
568 if (RT_FAILURE(rcSystem))
569 return rcSystem;
570 if (RT_FAILURE(rcUser))
571 return rcUser;
572 return VERR_NOT_FOUND;
573}
574
575
576RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
577{
578 RTCRSTORE hStore;
579 int rc = RTCrStoreCreateInMem(&hStore, 256);
580 if (RT_SUCCESS(rc))
581 {
582 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
583 if (RT_SUCCESS(rc))
584 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
585 RTCrStoreRelease(hStore);
586 }
587 return rc;
588}
589
590
591
592/**
593 * Figures out the IPRT status code for a GET.
594 *
595 * @returns IPRT status code.
596 * @param pThis The HTTP/HTTPS client instance.
597 * @param rcCurl What curl returned.
598 */
599static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl)
600{
601 int rc = VERR_INTERNAL_ERROR;
602
603 if (pThis->pszRedirLocation)
604 {
605 RTStrFree(pThis->pszRedirLocation);
606 pThis->pszRedirLocation = NULL;
607 }
608 if (rcCurl == CURLE_OK)
609 {
610 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
611 switch (pThis->lLastResp)
612 {
613 case 200:
614 /* OK, request was fulfilled */
615 case 204:
616 /* empty response */
617 rc = VINF_SUCCESS;
618 break;
619 case 301:
620 {
621 const char *pszRedirect;
622 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
623 size_t cb = strlen(pszRedirect);
624 if (cb > 0 && cb < 2048)
625 pThis->pszRedirLocation = RTStrDup(pszRedirect);
626 rc = VERR_HTTP_REDIRECTED;
627 break;
628 }
629 case 400:
630 /* bad request */
631 rc = VERR_HTTP_BAD_REQUEST;
632 break;
633 case 403:
634 /* forbidden, authorization will not help */
635 rc = VERR_HTTP_ACCESS_DENIED;
636 break;
637 case 404:
638 /* URL not found */
639 rc = VERR_HTTP_NOT_FOUND;
640 break;
641 }
642 }
643 else
644 {
645 switch (rcCurl)
646 {
647 case CURLE_URL_MALFORMAT:
648 case CURLE_COULDNT_RESOLVE_HOST:
649 rc = VERR_HTTP_NOT_FOUND;
650 break;
651 case CURLE_COULDNT_CONNECT:
652 rc = VERR_HTTP_COULDNT_CONNECT;
653 break;
654 case CURLE_SSL_CONNECT_ERROR:
655 rc = VERR_HTTP_SSL_CONNECT_ERROR;
656 break;
657 case CURLE_SSL_CACERT:
658 /* The peer certificate cannot be authenticated with the CA certificates
659 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
660 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
661 break;
662 case CURLE_SSL_CACERT_BADFILE:
663 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
664 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
665 break;
666 case CURLE_ABORTED_BY_CALLBACK:
667 /* forcefully aborted */
668 rc = VERR_HTTP_ABORTED;
669 break;
670 case CURLE_COULDNT_RESOLVE_PROXY:
671 rc = VERR_HTTP_PROXY_NOT_FOUND;
672 break;
673 case CURLE_WRITE_ERROR:
674 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
675 break;
676 //case CURLE_READ_ERROR
677
678 default:
679 break;
680 }
681 }
682
683 return rc;
684}
685
686
687/**
688 * cURL callback for reporting progress, we use it for checking for abort.
689 */
690static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
691{
692 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
693 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
694
695 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
696
697 return pThis->fAbort ? 1 : 0;
698}
699
700
701/**
702 * Whether we're likely to need SSL to handle the give URL.
703 *
704 * @returns true if we need, false if we probably don't.
705 * @param pszUrl The URL.
706 */
707static bool rtHttpNeedSsl(const char *pszUrl)
708{
709 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
710}
711
712
713/**
714 * Applies recoded settings to the cURL instance before doing work.
715 *
716 * @returns IPRT status code.
717 * @param pThis The HTTP/HTTPS client instance.
718 * @param pszUrl The URL.
719 */
720static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
721{
722 /*
723 * The URL.
724 */
725 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
726 if (CURL_FAILURE(rcCurl))
727 return VERR_INVALID_PARAMETER;
728
729 /*
730 * Setup SSL. Can be a bit of work.
731 */
732 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
733 if (CURL_FAILURE(rcCurl))
734 return VERR_INVALID_PARAMETER;
735
736 const char *pszCaFile = pThis->pszCaFile;
737 if ( !pszCaFile
738 && rtHttpNeedSsl(pszUrl))
739 {
740 int rc = RTHttpUseTemporaryCaFile(pThis, NULL);
741 if (RT_SUCCESS(rc))
742 pszCaFile = pThis->pszCaFile;
743 else
744 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
745 }
746 if (pszCaFile)
747 {
748 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
749 if (CURL_FAILURE(rcCurl))
750 return VERR_INTERNAL_ERROR;
751 }
752
753 /*
754 * Progress/abort.
755 */
756 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
757 if (CURL_FAILURE(rcCurl))
758 return VERR_INTERNAL_ERROR;
759 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
760 if (CURL_FAILURE(rcCurl))
761 return VERR_INTERNAL_ERROR;
762 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
763 if (CURL_FAILURE(rcCurl))
764 return VERR_INTERNAL_ERROR;
765
766 return VINF_SUCCESS;
767}
768
769
770/**
771 * cURL callback for writing data.
772 */
773static size_t rtHttpWriteData(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
774{
775 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
776
777 /*
778 * Do max size and overflow checks.
779 */
780 size_t const cbToAppend = cbUnit * cUnits;
781 size_t const cbCurSize = pThis->Output.Mem.cb;
782 size_t const cbNewSize = cbCurSize + cbToAppend;
783 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
784 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
785 {
786 if (cbNewSize + 1 <= pThis->Output.Mem.cbAllocated)
787 {
788 memcpy(&pThis->Output.Mem.pb[cbCurSize], pvBuf, cbToAppend);
789 pThis->Output.Mem.cb = cbNewSize;
790 pThis->Output.Mem.pb[cbNewSize] = '\0';
791 return cbToAppend;
792 }
793
794 /*
795 * We need to reallocate the output buffer.
796 */
797 /** @todo this could do with a better strategy wrt growth. */
798 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
799 if ( cbAlloc <= pThis->cbDownloadHint
800 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
801 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
802
803 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pThis->Output.Mem.pb, cbAlloc);
804 if (pbNew)
805 {
806 memcpy(&pbNew[cbCurSize], pvBuf, cbToAppend);
807 pbNew[cbNewSize] = '\0';
808
809 pThis->Output.Mem.cbAllocated = cbAlloc;
810 pThis->Output.Mem.pb = pbNew;
811 pThis->Output.Mem.cb = cbNewSize;
812 return cbToAppend;
813 }
814
815 pThis->rcOutput = VERR_NO_MEMORY;
816 }
817 else
818 pThis->rcOutput = VERR_TOO_MUCH_DATA;
819
820 /*
821 * Failure - abort.
822 */
823 RTMemFree(pThis->Output.Mem.pb);
824 pThis->Output.Mem.pb = NULL;
825 pThis->Output.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
826 pThis->fAbort = true;
827 return 0;
828}
829
830
831/**
832 * Internal worker that performs a HTTP GET.
833 *
834 * @returns IPRT status code.
835 * @param hHttp The HTTP/HTTPS client instance.
836 * @param pszUrl The URL.
837 * @param ppvResponse Where to return the pointer to the allocated
838 * response data (RTMemFree). There will always be
839 * an zero terminator char after the response, that
840 * is not part of the size returned via @a pcb.
841 * @param pcb The size of the response data.
842 *
843 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
844 * threads, because that will probably blow up!
845 */
846static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, uint8_t **ppvResponse, size_t *pcb)
847{
848 PRTHTTPINTERNAL pThis = hHttp;
849 RTHTTP_VALID_RETURN(pThis);
850
851 /*
852 * Reset the return values in case of more "GUI programming" on the client
853 * side (i.e. a programming style not bothering checking return codes).
854 */
855 *ppvResponse = NULL;
856 *pcb = 0;
857
858 /*
859 * Set the busy flag (paranoia).
860 */
861 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
862 AssertReturn(!fBusy, VERR_WRONG_ORDER);
863
864 /*
865 * Reset the state and apply settings.
866 */
867 pThis->fAbort = false;
868 pThis->rcOutput = VINF_SUCCESS;
869 pThis->cbDownloadHint = 0;
870
871 int rc = rtHttpApplySettings(hHttp, pszUrl);
872 if (RT_SUCCESS(rc))
873 {
874 RT_ZERO(pThis->Output.Mem);
875 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteData);
876 if (!CURL_FAILURE(rcCurl))
877 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
878 if (!CURL_FAILURE(rcCurl))
879 {
880 /*
881 * Perform the HTTP operation.
882 */
883 rcCurl = curl_easy_perform(pThis->pCurl);
884 rc = rtHttpGetCalcStatus(pThis, rcCurl);
885 if (RT_SUCCESS(rc))
886 rc = pThis->rcOutput;
887 if (RT_SUCCESS(rc))
888 {
889 *ppvResponse = pThis->Output.Mem.pb;
890 *pcb = pThis->Output.Mem.cb;
891 }
892 else if (pThis->Output.Mem.pb)
893 RTMemFree(pThis->Output.Mem.pb);
894 RT_ZERO(pThis->Output.Mem);
895 }
896 else
897 rc = VERR_INTERNAL_ERROR_3;
898 }
899
900 ASMAtomicWriteBool(&pThis->fBusy, false);
901 return rc;
902}
903
904
905RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
906{
907 uint8_t *pv;
908 size_t cb;
909 int rc = rtHttpGetToMem(hHttp, pszUrl, &pv, &cb);
910 if (RT_SUCCESS(rc))
911 {
912 if (pv) /* paranoia */
913 *ppszNotUtf8 = (char *)pv;
914 else
915 *ppszNotUtf8 = (char *)RTMemDup("", 1);
916 }
917 else
918 *ppszNotUtf8 = NULL;
919 return rc;
920}
921
922
923RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
924{
925 RTMemFree(pszNotUtf8);
926}
927
928
929RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
930{
931 return rtHttpGetToMem(hHttp, pszUrl, (uint8_t **)ppvResponse, pcb);
932}
933
934
935RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
936{
937 RTMemFree(pvResponse);
938}
939
940
941/**
942 * cURL callback for writing data to a file.
943 */
944static size_t rtHttpWriteDataToFile(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
945{
946 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
947 size_t cbWritten = 0;
948 int rc = RTFileWrite(pThis->Output.hFile, pvBuf, cbUnit * cUnits, &cbWritten);
949 if (RT_SUCCESS(rc))
950 return cbWritten;
951 pThis->rcOutput = rc;
952 return 0;
953}
954
955
956RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
957{
958 PRTHTTPINTERNAL pThis = hHttp;
959 RTHTTP_VALID_RETURN(pThis);
960
961 /*
962 * Set the busy flag (paranoia).
963 */
964 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
965 AssertReturn(!fBusy, VERR_WRONG_ORDER);
966
967 /*
968 * Reset the state and apply settings.
969 */
970 pThis->fAbort = false;
971 pThis->rcOutput = VINF_SUCCESS;
972 pThis->cbDownloadHint = 0;
973
974 int rc = rtHttpApplySettings(hHttp, pszUrl);
975 if (RT_SUCCESS(rc))
976 {
977 pThis->Output.hFile = NIL_RTFILE;
978 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteDataToFile);
979 if (!CURL_FAILURE(rcCurl))
980 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
981 if (!CURL_FAILURE(rcCurl))
982 {
983 /*
984 * Open the output file.
985 */
986 rc = RTFileOpen(&pThis->Output.hFile, pszDstFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
987 if (RT_SUCCESS(rc))
988 {
989 /*
990 * Perform the HTTP operation.
991 */
992 rcCurl = curl_easy_perform(pThis->pCurl);
993 rc = rtHttpGetCalcStatus(pThis, rcCurl);
994 if (RT_SUCCESS(rc))
995 rc = pThis->rcOutput;
996
997 int rc2 = RTFileClose(pThis->Output.hFile);
998 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
999 rc = rc2;
1000 }
1001 pThis->Output.hFile = NIL_RTFILE;
1002 }
1003 else
1004 rc = VERR_INTERNAL_ERROR_3;
1005 }
1006
1007 ASMAtomicWriteBool(&pThis->fBusy, false);
1008 return rc;
1009}
1010
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