VirtualBox

source: vbox/trunk/src/VBox/Runtime/generic/http-curl.cpp@ 57768

Last change on this file since 57768 was 57749, checked in by vboxsync, 10 years ago

http-curl.cpp: Halfways there on windows, just need to make use of the settings now.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 52.4 KB
Line 
1/* $Id: http-curl.cpp 57749 2015-09-14 19:33:23Z vboxsync $ */
2/** @file
3 * IPRT - HTTP client API, cURL based.
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/ctype.h>
38#include <iprt/env.h>
39#include <iprt/err.h>
40#include <iprt/file.h>
41#ifdef RT_OS_WINDOWS
42# include <iprt/ldr.h>
43#endif
44#include <iprt/mem.h>
45#include <iprt/once.h>
46#include <iprt/path.h>
47#include <iprt/stream.h>
48#include <iprt/string.h>
49#include <iprt/uri.h>
50
51#include "internal/magics.h"
52
53#include <curl/curl.h>
54
55#ifdef RT_OS_WINDOWS
56# include <Winhttp.h>
57#endif
58
59
60/*********************************************************************************************************************************
61* Structures and Typedefs *
62*********************************************************************************************************************************/
63/**
64 * Internal HTTP client instance.
65 */
66typedef struct RTHTTPINTERNAL
67{
68 /** Magic value. */
69 uint32_t u32Magic;
70 /** cURL handle. */
71 CURL *pCurl;
72 /** The last response code. */
73 long lLastResp;
74 /** Custom headers/ */
75 struct curl_slist *pHeaders;
76 /** CA certificate file for HTTPS authentication. */
77 char *pszCaFile;
78 /** Whether to delete the CA on destruction. */
79 bool fDeleteCaFile;
80
81 /** @name Proxy settings.
82 * When fUseSystemProxySettings is set, the other members will be updated each
83 * time we're presented with a new URL. The members reflect the cURL
84 * configuration.
85 *
86 * @{ */
87 /** Set if we should use the system proxy settings for a URL.
88 * This means reconfiguring cURL for each request. */
89 bool fUseSystemProxySettings;
90 /** Set if we've detected no proxy necessary. */
91 bool fNoProxy;
92 /** Proxy host name (RTStrFree). */
93 char *pszProxyHost;
94 /** Proxy port number (UINT32_MAX if not specified). */
95 uint32_t uProxyPort;
96 /** The proxy type (CURLPROXY_HTTP, CURLPROXY_SOCKS5, ++). */
97 curl_proxytype enmProxyType;
98 /** Proxy username (RTStrFree). */
99 char *pszProxyUsername;
100 /** Proxy password (RTStrFree). */
101 char *pszProxyPassword;
102 /** @} */
103
104 /** Abort the current HTTP request if true. */
105 bool volatile fAbort;
106 /** Set if someone is preforming an HTTP operation. */
107 bool volatile fBusy;
108 /** The location field for 301 responses. */
109 char *pszRedirLocation;
110
111 /** Output callback data. */
112 union
113 {
114 /** For file destination. */
115 RTFILE hFile;
116 /** For memory destination. */
117 struct
118 {
119 /** The current size (sans terminator char). */
120 size_t cb;
121 /** The currently allocated size. */
122 size_t cbAllocated;
123 /** Pointer to the buffer. */
124 uint8_t *pb;
125 } Mem;
126 } Output;
127 /** Output callback status. */
128 int rcOutput;
129 /** Download size hint set by the progress callback. */
130 uint64_t cbDownloadHint;
131} RTHTTPINTERNAL;
132/** Pointer to an internal HTTP client instance. */
133typedef RTHTTPINTERNAL *PRTHTTPINTERNAL;
134
135
136#ifdef RT_OS_WINDOWS
137/** @name Windows: Types for dynamically resolved APIs
138 * @{ */
139typedef HINTERNET (WINAPI * PFNWINHTTPOPEN)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
140typedef BOOL (WINAPI * PFNWINHTTPCLOSEHANDLE)(HINTERNET);
141typedef BOOL (WINAPI * PFNWINHTTPGETPROXYFORURL)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS *, WINHTTP_PROXY_INFO *);
142typedef BOOL (WINAPI * PFNWINHTTPGETDEFAULTPROXYCONFIGURATION)(WINHTTP_PROXY_INFO *);
143typedef BOOL (WINAPI * PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *);
144/** @} */
145#endif
146
147
148/*********************************************************************************************************************************
149* Defined Constants And Macros *
150*********************************************************************************************************************************/
151/** @def RTHTTP_MAX_MEM_DOWNLOAD
152 * The max size we are allowed to download to a memory buffer.
153 *
154 * @remarks The minus 1 is for the trailing zero terminator we always add.
155 */
156#if ARCH_BITS == 64
157# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1)
158#else
159# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1)
160#endif
161
162/** Checks whether a cURL return code indicates success. */
163#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK)
164/** Checks whether a cURL return code indicates failure. */
165#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK)
166
167/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
168#define RTHTTP_VALID_RETURN_RC(hHttp, rcCurl) \
169 do { \
170 AssertPtrReturn((hHttp), (rcCurl)); \
171 AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (rcCurl)); \
172 } while (0)
173
174/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
175#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE)
176
177/** Validates a handle and returns (void) if not valid. */
178#define RTHTTP_VALID_RETURN_VOID(hHttp) \
179 do { \
180 AssertPtrReturnVoid(hHttp); \
181 AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \
182 } while (0)
183
184
185/*********************************************************************************************************************************
186* Global Variables *
187*********************************************************************************************************************************/
188#ifdef RT_OS_WINDOWS
189/** @name Windows: Dynamically resolved APIs
190 * @{ */
191static RTONCE g_WinResolveImportsOnce = RTONCE_INITIALIZER;
192static RTLDRMOD g_hWinHttpMod = NIL_RTLDRMOD;
193static PFNWINHTTPOPEN g_pfnWinHttpOpen = NULL;
194static PFNWINHTTPCLOSEHANDLE g_pfnWinHttpCloseHandle = NULL;
195static PFNWINHTTPGETPROXYFORURL g_pfnWinHttpGetProxyForUrl = NULL;
196static PFNWINHTTPGETDEFAULTPROXYCONFIGURATION g_pfnWinHttpGetDefaultProxyConfiguration = NULL;
197static PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER g_pfnWinHttpGetIEProxyConfigForCurrentUser = NULL;
198/** @} */
199#endif
200
201
202/*********************************************************************************************************************************
203* Internal Functions *
204*********************************************************************************************************************************/
205static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis);
206
207
208RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp)
209{
210 AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER);
211
212 /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a
213 * problem if multiple threads get here at the same time. */
214 int rc = VERR_HTTP_INIT_FAILED;
215 CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL);
216 if (!CURL_FAILURE(rcCurl))
217 {
218 CURL *pCurl = curl_easy_init();
219 if (pCurl)
220 {
221 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL));
222 if (pThis)
223 {
224 pThis->u32Magic = RTHTTP_MAGIC;
225 pThis->pCurl = pCurl;
226 pThis->fUseSystemProxySettings = true;
227
228 *phHttp = (RTHTTP)pThis;
229
230 return VINF_SUCCESS;
231 }
232 rc = VERR_NO_MEMORY;
233 }
234 else
235 rc = VERR_HTTP_INIT_FAILED;
236 }
237 curl_global_cleanup();
238 return rc;
239}
240
241
242RTR3DECL(void) RTHttpDestroy(RTHTTP hHttp)
243{
244 if (hHttp == NIL_RTHTTP)
245 return;
246
247 PRTHTTPINTERNAL pThis = hHttp;
248 RTHTTP_VALID_RETURN_VOID(pThis);
249
250 Assert(!pThis->fBusy);
251
252 pThis->u32Magic = RTHTTP_MAGIC_DEAD;
253
254 curl_easy_cleanup(pThis->pCurl);
255 pThis->pCurl = NULL;
256
257 if (pThis->pHeaders)
258 curl_slist_free_all(pThis->pHeaders);
259
260 rtHttpUnsetCaFile(pThis);
261 Assert(!pThis->pszCaFile);
262
263 if (pThis->pszRedirLocation)
264 RTStrFree(pThis->pszRedirLocation);
265
266 RTStrFree(pThis->pszProxyHost);
267 RTStrFree(pThis->pszProxyUsername);
268 if (pThis->pszProxyPassword)
269 {
270 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
271 RTStrFree(pThis->pszProxyPassword);
272 }
273
274 RTMemFree(pThis);
275
276 curl_global_cleanup();
277}
278
279
280RTR3DECL(int) RTHttpAbort(RTHTTP hHttp)
281{
282 PRTHTTPINTERNAL pThis = hHttp;
283 RTHTTP_VALID_RETURN(pThis);
284
285 pThis->fAbort = true;
286
287 return VINF_SUCCESS;
288}
289
290
291RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation)
292{
293 PRTHTTPINTERNAL pThis = hHttp;
294 RTHTTP_VALID_RETURN(pThis);
295 Assert(!pThis->fBusy);
296
297 if (!pThis->pszRedirLocation)
298 return VERR_HTTP_NOT_FOUND;
299
300 return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation);
301}
302
303
304RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp)
305{
306 PRTHTTPINTERNAL pThis = hHttp;
307 RTHTTP_VALID_RETURN(pThis);
308 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
309
310 /*
311 * Change the settings.
312 */
313 pThis->fUseSystemProxySettings = true;
314 return VINF_SUCCESS;
315}
316
317
318/**
319 * rtHttpConfigureProxyForUrl: Update cURL proxy settings as needed.
320 *
321 * @returns IPRT status code.
322 * @param pThis The HTTP client instance.
323 * @param enmProxyType The proxy type.
324 * @param pszHost The proxy host name.
325 * @param uPort The proxy port number.
326 * @param pszUsername The proxy username, or NULL if none.
327 * @param pszPassword The proxy password, or NULL if none.
328 */
329static int rtHttpUpdateProxyConfig(PRTHTTPINTERNAL pThis, curl_proxytype enmProxyType, const char *pszHost,
330 uint32_t uPort, const char *pszUsername, const char *pszPassword)
331{
332 int rcCurl;
333 AssertReturn(pszHost, VERR_INVALID_PARAMETER);
334
335 if (pThis->fNoProxy)
336 {
337 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, (const char *)NULL);
338 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_NOPROXY=NULL: %d (%#x)\n", rcCurl, rcCurl),
339 VERR_HTTP_CURL_PROXY_CONFIG);
340 pThis->fNoProxy = false;
341 }
342
343 if (enmProxyType != pThis->enmProxyType)
344 {
345 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)enmProxyType);
346 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYTYPE=%d: %d (%#x)\n", enmProxyType, rcCurl, rcCurl),
347 VERR_HTTP_CURL_PROXY_CONFIG);
348 pThis->enmProxyType = CURLPROXY_HTTP;
349 }
350
351 if (uPort != pThis->uProxyPort)
352 {
353 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort);
354 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPORT=%d: %d (%#x)\n", uPort, rcCurl, rcCurl),
355 VERR_HTTP_CURL_PROXY_CONFIG);
356 pThis->uProxyPort = uPort;
357 }
358
359 if ( pszUsername != pThis->pszProxyUsername
360 || RTStrCmp(pszUsername, pThis->pszProxyUsername))
361 {
362 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pszUsername);
363 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYUSERNAME=%s: %d (%#x)\n", pszUsername, rcCurl, rcCurl),
364 VERR_HTTP_CURL_PROXY_CONFIG);
365 if (pThis->pszProxyUsername)
366 {
367 RTStrFree(pThis->pszProxyUsername);
368 pThis->pszProxyUsername = NULL;
369 }
370 if (pszUsername)
371 {
372 pThis->pszProxyUsername = RTStrDup(pszUsername);
373 AssertReturn(pThis->pszProxyUsername, VERR_NO_STR_MEMORY);
374 }
375 }
376
377 if ( pszPassword != pThis->pszProxyPassword
378 || RTStrCmp(pszPassword, pThis->pszProxyPassword))
379 {
380 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pszPassword);
381 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPASSWORD=%s: %d (%#x)\n", pszPassword ? "xxx" : NULL, rcCurl, rcCurl),
382 VERR_HTTP_CURL_PROXY_CONFIG);
383 if (pThis->pszProxyPassword)
384 {
385 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
386 RTStrFree(pThis->pszProxyPassword);
387 pThis->pszProxyPassword = NULL;
388 }
389 if (pszPassword)
390 {
391 pThis->pszProxyPassword = RTStrDup(pszPassword);
392 AssertReturn(pThis->pszProxyPassword, VERR_NO_STR_MEMORY);
393 }
394 }
395
396 if ( pszHost != pThis->pszProxyHost
397 || RTStrCmp(pszHost, pThis->pszProxyHost))
398 {
399 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pszHost);
400 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXY=%s: %d (%#x)\n", pszHost, rcCurl, rcCurl),
401 VERR_HTTP_CURL_PROXY_CONFIG);
402 if (pThis->pszProxyHost)
403 {
404 RTStrFree(pThis->pszProxyHost);
405 pThis->pszProxyHost = NULL;
406 }
407 if (pszHost)
408 {
409 pThis->pszProxyHost = RTStrDup(pszHost);
410 AssertReturn(pThis->pszProxyHost, VERR_NO_STR_MEMORY);
411 }
412 }
413
414 return VINF_SUCCESS;
415}
416
417
418/**
419 * rtHttpConfigureProxyForUrl: Disables proxying.
420 *
421 * @returns IPRT status code.
422 * @param pThis The HTTP client instance.
423 */
424static int rtHttpUpdateAutomaticProxyDisable(PRTHTTPINTERNAL pThis)
425{
426 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP) == CURLE_OK, VERR_INTERNAL_ERROR_2);
427 pThis->enmProxyType = CURLPROXY_HTTP;
428
429 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)1080) == CURLE_OK, VERR_INTERNAL_ERROR_2);
430 pThis->uProxyPort = 1080;
431
432 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
433 if (pThis->pszProxyUsername)
434 {
435 RTStrFree(pThis->pszProxyUsername);
436 pThis->pszProxyUsername = NULL;
437 }
438
439 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
440 if (pThis->pszProxyPassword)
441 {
442 RTStrFree(pThis->pszProxyPassword);
443 pThis->pszProxyPassword = NULL;
444 }
445
446 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
447 if (pThis->pszProxyHost)
448 {
449 RTStrFree(pThis->pszProxyHost);
450 pThis->pszProxyHost = NULL;
451 }
452
453 /* No proxy for everything! */
454 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, "*") == CURLE_OK, CURLOPT_PROXY);
455 pThis->fNoProxy = true;
456
457 return VINF_SUCCESS;
458}
459
460
461/**
462 * See if the host name of the URL is included in the stripped no_proxy list.
463 *
464 * The no_proxy list is a colon or space separated list of domain names for
465 * which there should be no proxying. Given "no_proxy=oracle.com" neither the
466 * URL "http://www.oracle.com" nor "http://oracle.com" will not be proxied, but
467 * "http://notoracle.com" will be.
468 *
469 * @returns true if the URL is in the no_proxy list, otherwise false.
470 * @param pszUrl The URL.
471 * @param pszNoProxyList The stripped no_proxy list.
472 */
473static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList)
474{
475 /*
476 * Check for just '*', diabling proxying for everything.
477 * (Caller stripped pszNoProxyList.)
478 */
479 if (*pszNoProxyList == '*' && pszNoProxyList[1] == '\0')
480 return true;
481
482 /*
483 * Empty list? (Caller stripped it, remember).
484 */
485 if (!*pszNoProxyList)
486 return false;
487
488 /*
489 * We now need to parse the URL and extract the host name.
490 */
491 RTURIPARSED Parsed;
492 int rc = RTUriParse(pszUrl, &Parsed);
493 AssertRCReturn(rc, false);
494 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
495 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
496 return false;
497
498 bool fRet = false;
499 size_t const cchHost = strlen(pszHost);
500 if (cchHost)
501 {
502 /*
503 * The list is comma or space separated, walk it and match host names.
504 */
505 while (*pszNoProxyList != '\0')
506 {
507 /* Strip leading slashes, commas and dots. */
508 char ch;
509 while ( (ch = *pszNoProxyList) == ','
510 || ch == '.'
511 || RT_C_IS_SPACE(ch))
512 pszNoProxyList++;
513
514 /* Find the end. */
515 size_t cch = RTStrOffCharOrTerm(pszNoProxyList, ',');
516 size_t offNext = RTStrOffCharOrTerm(pszNoProxyList, ' ');
517 cch = RT_MIN(cch, offNext);
518 offNext = cch;
519
520 /* Trip trailing spaces, well tabs and stuff. */
521 while (cch > 0 && RT_C_IS_SPACE(pszNoProxyList[cch - 1]))
522 cch--;
523
524 /* Do the matching, if we have anything to work with. */
525 if (cch > 0)
526 {
527 if ( ( cch == cchHost
528 && RTStrNICmp(pszNoProxyList, pszHost, cch) == 0)
529 || ( cch < cchHost
530 && pszHost[cchHost - cch - 1] == '.'
531 && RTStrNICmp(pszNoProxyList, &pszHost[cchHost - cch], cch) == 0) )
532 {
533 fRet = true;
534 break;
535 }
536 }
537
538 /* Next. */
539 pszNoProxyList += offNext;
540 }
541 }
542
543 RTStrFree(pszHost);
544 return fRet;
545}
546
547
548/**
549 * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out
550 * the proxy config.
551 *
552 * @returns IPRT status code.
553 * @param pThis The HTTP client instance.
554 * @param pszUrl The URL to configure a proxy for.
555 */
556static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl)
557{
558 char szTmp[_1K];
559
560 /*
561 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
562 */
563 const char *pszNoProxyVar;
564 size_t cchActual;
565 char *pszNoProxyFree = NULL;
566 char *pszNoProxy = szTmp;
567 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
568 if (rc == VERR_ENV_VAR_NOT_FOUND)
569 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
570 if (rc == VERR_BUFFER_OVERFLOW)
571 {
572 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
573 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
574 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
575 }
576 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
577 bool fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
578 RTMemTmpFree(pszNoProxyFree);
579 if (!fNoProxy)
580 {
581 /*
582 * Get the schema specific specific env var, falling back on the
583 * generic all_proxy if not found.
584 */
585 const char *apszEnvVars[4];
586 unsigned cEnvVars = 0;
587 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
588 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
589 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
590 {
591 apszEnvVars[cEnvVars++] = "https_proxy";
592 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
593 }
594 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
595 {
596 apszEnvVars[cEnvVars++] = "ftp_proxy";
597 apszEnvVars[cEnvVars++] = "FTP_PROXY";
598 }
599 else
600 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
601 apszEnvVars[cEnvVars++] = "all_proxy";
602 apszEnvVars[cEnvVars++] = "ALL_PROXY";
603
604 /*
605 * We try the env vars out and goes with the first one we can make sense out of.
606 * If we cannot make sense of any, we return the first unexpected rc we got.
607 */
608 rc = VINF_SUCCESS;
609 for (uint32_t i = 0; i < cEnvVars; i++)
610 {
611 size_t cchValue;
612 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
613 if (RT_SUCCESS(rc2))
614 {
615 if (cchValue != 0)
616 {
617 /* Add a http:// prefix so RTUriParse groks it. */
618 if (!strstr(szTmp, "://"))
619 {
620 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
621 memcpy(szTmp, RT_STR_TUPLE("http://"));
622 }
623
624 RTURIPARSED Parsed;
625 rc2 = RTUriParse(szTmp, &Parsed);
626 if (RT_SUCCESS(rc))
627 {
628 bool fDone = false;
629 char *pszHost = RTUriParsedAuthorityHost(szTmp, &Parsed);
630 if (pszHost)
631 {
632 /*
633 * We've got a host name, try get the rest.
634 */
635 char *pszUsername = RTUriParsedAuthorityUsername(szTmp, &Parsed);
636 char *pszPassword = RTUriParsedAuthorityPassword(szTmp, &Parsed);
637 uint32_t uProxyPort = RTUriParsedAuthorityPort(szTmp, &Parsed);
638 curl_proxytype enmProxyType;
639 if (RTUriIsSchemeMatch(szTmp, "http"))
640 enmProxyType = CURLPROXY_HTTP;
641 else if ( RTUriIsSchemeMatch(szTmp, "socks4")
642 || RTUriIsSchemeMatch(szTmp, "socks"))
643 enmProxyType = CURLPROXY_SOCKS4;
644 else if (RTUriIsSchemeMatch(szTmp, "socks4a"))
645 enmProxyType = CURLPROXY_SOCKS4A;
646 else if (RTUriIsSchemeMatch(szTmp, "socks5"))
647 enmProxyType = CURLPROXY_SOCKS5;
648 else if (RTUriIsSchemeMatch(szTmp, "socks5h"))
649 enmProxyType = CURLPROXY_SOCKS5_HOSTNAME;
650 else
651 enmProxyType = CURLPROXY_HTTP;
652
653 /* Guess the port from the proxy type if not given. */
654 if (uProxyPort == UINT32_MAX)
655 switch (enmProxyType)
656 {
657 case CURLPROXY_HTTP: uProxyPort = 80; break;
658 default: uProxyPort = 1080 /* CURL_DEFAULT_PROXY_PORT */; break;
659 }
660
661 rc2 = rtHttpUpdateProxyConfig(pThis, enmProxyType, pszHost, uProxyPort, pszUsername, pszPassword);
662
663 RTStrFree(pszUsername);
664 RTStrFree(pszPassword);
665 RTStrFree(pszHost);
666
667 /* If that succeeded we're done. */
668 if (RT_SUCCESS(rc2))
669 {
670 rc = rc2;
671 break;
672 }
673
674 if (RT_SUCCESS(rc))
675 rc = rc2;
676 }
677 else
678 AssertMsgFailed(("RTUriParsedAuthorityHost('%s',) -> NULL\n", szTmp));
679 }
680 else
681 {
682 AssertMsgFailed(("RTUriParse('%s',) -> %Rrc\n", szTmp, rc2));
683 if (RT_SUCCESS(rc))
684 rc = rc2;
685 }
686 }
687 /*
688 * The variable is empty. Guess that means no proxying wanted.
689 */
690 else
691 {
692 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
693 break;
694 }
695 }
696 else
697 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
698 }
699 }
700 /*
701 * The host is the no-proxy list, it seems.
702 */
703 else
704 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
705
706 return rc;
707}
708
709#ifdef RT_OS_WINDOWS
710
711/**
712 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
713 */
714static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
715{
716 RTLDRMOD hMod;
717 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
718 if (RT_SUCCESS(rc))
719 {
720 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
721 if (RT_SUCCESS(rc))
722 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
723 if (RT_SUCCESS(rc))
724 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
725 if (RT_SUCCESS(rc))
726 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
727 if (RT_SUCCESS(rc))
728 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
729 RTLdrClose(hMod);
730 }
731 AssertRC(rc);
732
733 NOREF(pvUser);
734 return rc;
735}
736
737
738/**
739 * Reconfigures the cURL proxy settings for the given URL.
740 *
741 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
742 * @param pThis The HTTP client instance.
743 * @param pszUrl The URL.
744 */
745static int rtHttpConfigureProxyForUrlWindows(PRTHTTPINTERNAL pThis, const char *pszUrl)
746{
747 int rcRet = VINF_NOT_SUPPORTED;
748
749 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
750 if (RT_SUCCESS(rc))
751 {
752 /*
753 * Try get some proxy info for the URL. We first try getting the IE
754 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
755 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
756 */
757 WINHTTP_PROXY_INFO ProxyInfo;
758 PRTUTF16 pwszProxy = NULL;
759 PRTUTF16 pwszNoProxy = NULL;
760 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
761 RT_ZERO(AutoProxyOptions);
762 RT_ZERO(ProxyInfo);
763
764 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
765 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
766 {
767 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
768 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
769 if (IeProxyConfig.fAutoDetect)
770 {
771 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
772 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
773 }
774 else if (AutoProxyOptions.lpszAutoConfigUrl)
775 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
776 else if (ProxyInfo.lpszProxy)
777 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
778 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
779 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
780 }
781 else
782 {
783 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
784 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
785 {
786 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
787 RT_ZERO(ProxyInfo);
788 }
789 }
790
791 /*
792 * Should we try WinHttGetProxyForUrl?
793 */
794 if (AutoProxyOptions.dwFlags != 0)
795 {
796 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
797 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
798 if (hSession != NULL)
799 {
800 PRTUTF16 pwszUrl;
801 rc = RTStrToUtf16(pszUrl, &pwszUrl);
802 if (RT_SUCCESS(rc))
803 {
804 /*
805 * Try autodetect first, then fall back on the config URL if there is one.
806 *
807 * Also, we first try without auto authentication, then with. This will according
808 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
809 * caching the result when it's processed out-of-process (seems default here on W10).
810 */
811 WINHTTP_PROXY_INFO TmpProxyInfo;
812 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
813 if ( !fRc
814 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
815 {
816 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
817 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
818 }
819
820 if ( !fRc
821 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
822 && AutoProxyOptions.lpszAutoConfigUrl)
823 {
824 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
825 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
826 AutoProxyOptions.dwAutoDetectFlags = 0;
827 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
828 if ( !fRc
829 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
830 {
831 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
832 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
833 }
834 }
835
836 if (fRc)
837 {
838 if (ProxyInfo.lpszProxy)
839 GlobalFree(ProxyInfo.lpszProxy);
840 if (ProxyInfo.lpszProxyBypass)
841 GlobalFree(ProxyInfo.lpszProxyBypass);
842 ProxyInfo = TmpProxyInfo;
843 }
844 /*
845 * If the autodetection failed, assume no proxy.
846 */
847 else
848 {
849 DWORD dwErr = GetLastError();
850 if (dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED)
851 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
852 else
853 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl -> %u\n", dwErr));
854 }
855 RTUtf16Free(pwszUrl);
856 }
857 else
858 {
859 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
860 rcRet = rc;
861 }
862 g_pfnWinHttpCloseHandle(hSession);
863 }
864 else
865 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
866 }
867
868 /*
869 * Try use the proxy info we've found.
870 */
871 switch (ProxyInfo.dwAccessType)
872 {
873 case WINHTTP_ACCESS_TYPE_NO_PROXY:
874 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
875 break;
876
877 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
878/** @todo Continue here: parse the proxy thingy. */
879//AssertMsgFailed(("lpszProxy='%ls'\n", ProxyInfo.lpszProxy));
880 break;
881
882 case 0:
883 break;
884
885 default:
886 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
887 }
888
889 /*
890 * Cleanup.
891 */
892 if (ProxyInfo.lpszProxy)
893 GlobalFree(ProxyInfo.lpszProxy);
894 if (ProxyInfo.lpszProxyBypass)
895 GlobalFree(ProxyInfo.lpszProxyBypass);
896 if (AutoProxyOptions.lpszAutoConfigUrl)
897 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
898 }
899
900 return rcRet;
901}
902
903#endif /* RT_OS_WINDOWS */
904
905
906static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
907{
908 if (pThis->fUseSystemProxySettings)
909 {
910#ifdef RT_OS_WINDOWS
911 int rc = rtHttpConfigureProxyForUrlWindows(pThis, pszUrl);
912 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
913 return rc;
914 Assert(rc == VINF_NOT_SUPPORTED);
915#endif
916/** @todo system specific class here, fall back on env vars if necessary. */
917 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
918 }
919
920 return VINF_SUCCESS;
921}
922
923
924RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
925 const char *pcszProxyUser, const char *pcszProxyPwd)
926{
927 PRTHTTPINTERNAL pThis = hHttp;
928 RTHTTP_VALID_RETURN(pThis);
929 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
930 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
931
932 /*
933 * Update the settings.
934 *
935 * Currently, we don't make alot of effort parsing or checking the input, we
936 * leave that to cURL. (A bit afraid of breaking user settings.)
937 */
938 pThis->fUseSystemProxySettings = false;
939 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
940}
941
942
943RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
944{
945 PRTHTTPINTERNAL pThis = hHttp;
946 RTHTTP_VALID_RETURN(pThis);
947
948 if (!cHeaders)
949 {
950 if (pThis->pHeaders)
951 curl_slist_free_all(pThis->pHeaders);
952 pThis->pHeaders = 0;
953 return VINF_SUCCESS;
954 }
955
956 struct curl_slist *pHeaders = NULL;
957 for (size_t i = 0; i < cHeaders; i++)
958 pHeaders = curl_slist_append(pHeaders, papszHeaders[i]);
959
960 pThis->pHeaders = pHeaders;
961 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
962 if (CURL_FAILURE(rcCurl))
963 return VERR_INVALID_PARAMETER;
964
965 return VINF_SUCCESS;
966}
967
968
969/**
970 * Set the CA file to NULL, deleting any temporary file if necessary.
971 *
972 * @param pThis The HTTP/HTTPS client instance.
973 */
974static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
975{
976 if (pThis->pszCaFile)
977 {
978 if (pThis->fDeleteCaFile)
979 {
980 int rc2 = RTFileDelete(pThis->pszCaFile);
981 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
982 }
983 RTStrFree(pThis->pszCaFile);
984 pThis->pszCaFile = NULL;
985 }
986}
987
988
989RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
990{
991 PRTHTTPINTERNAL pThis = hHttp;
992 RTHTTP_VALID_RETURN(pThis);
993
994 rtHttpUnsetCaFile(pThis);
995
996 pThis->fDeleteCaFile = false;
997 if (pszCaFile)
998 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
999 return VINF_SUCCESS;
1000}
1001
1002
1003RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
1004{
1005 PRTHTTPINTERNAL pThis = hHttp;
1006 RTHTTP_VALID_RETURN(pThis);
1007
1008 /*
1009 * Create a temporary file.
1010 */
1011 int rc = VERR_NO_STR_MEMORY;
1012 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
1013 if (pszCaFile)
1014 {
1015 RTFILE hFile;
1016 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
1017 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1018 if (RT_SUCCESS(rc))
1019 {
1020 /*
1021 * Gather certificates into a temporary store and export them to the temporary file.
1022 */
1023 RTCRSTORE hStore;
1024 rc = RTCrStoreCreateInMem(&hStore, 256);
1025 if (RT_SUCCESS(rc))
1026 {
1027 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
1028 if (RT_SUCCESS(rc))
1029 /** @todo Consider adding an API for exporting to a RTFILE... */
1030 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
1031 RTCrStoreRelease(hStore);
1032 }
1033 RTFileClose(hFile);
1034 if (RT_SUCCESS(rc))
1035 {
1036 /*
1037 * Set the CA file for the instance.
1038 */
1039 rtHttpUnsetCaFile(pThis);
1040
1041 pThis->fDeleteCaFile = true;
1042 pThis->pszCaFile = pszCaFile;
1043 return VINF_SUCCESS;
1044 }
1045
1046 int rc2 = RTFileDelete(pszCaFile);
1047 AssertRC(rc2);
1048 }
1049 else
1050 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
1051
1052 RTStrFree(pszCaFile);
1053 }
1054 return rc;
1055}
1056
1057
1058RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
1059{
1060 uint32_t const cBefore = RTCrStoreCertCount(hStore);
1061 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
1062
1063 /*
1064 * Add the user store, quitely ignoring any errors.
1065 */
1066 RTCRSTORE hSrcStore;
1067 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1068 if (RT_SUCCESS(rcUser))
1069 {
1070 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1071 hSrcStore);
1072 RTCrStoreRelease(hSrcStore);
1073 }
1074
1075 /*
1076 * Ditto for the system store.
1077 */
1078 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1079 if (RT_SUCCESS(rcSystem))
1080 {
1081 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1082 hSrcStore);
1083 RTCrStoreRelease(hSrcStore);
1084 }
1085
1086 /*
1087 * If the number of certificates increased, we consider it a success.
1088 */
1089 if (RTCrStoreCertCount(hStore) > cBefore)
1090 {
1091 if (RT_FAILURE(rcSystem))
1092 return -rcSystem;
1093 if (RT_FAILURE(rcUser))
1094 return -rcUser;
1095 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
1096 }
1097
1098 if (RT_FAILURE(rcSystem))
1099 return rcSystem;
1100 if (RT_FAILURE(rcUser))
1101 return rcUser;
1102 return VERR_NOT_FOUND;
1103}
1104
1105
1106RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
1107{
1108 RTCRSTORE hStore;
1109 int rc = RTCrStoreCreateInMem(&hStore, 256);
1110 if (RT_SUCCESS(rc))
1111 {
1112 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
1113 if (RT_SUCCESS(rc))
1114 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
1115 RTCrStoreRelease(hStore);
1116 }
1117 return rc;
1118}
1119
1120
1121
1122/**
1123 * Figures out the IPRT status code for a GET.
1124 *
1125 * @returns IPRT status code.
1126 * @param pThis The HTTP/HTTPS client instance.
1127 * @param rcCurl What curl returned.
1128 */
1129static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl)
1130{
1131 int rc = VERR_HTTP_CURL_ERROR;
1132
1133 if (pThis->pszRedirLocation)
1134 {
1135 RTStrFree(pThis->pszRedirLocation);
1136 pThis->pszRedirLocation = NULL;
1137 }
1138 if (rcCurl == CURLE_OK)
1139 {
1140 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
1141 switch (pThis->lLastResp)
1142 {
1143 case 200:
1144 /* OK, request was fulfilled */
1145 case 204:
1146 /* empty response */
1147 rc = VINF_SUCCESS;
1148 break;
1149 case 301:
1150 {
1151 const char *pszRedirect;
1152 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
1153 size_t cb = strlen(pszRedirect);
1154 if (cb > 0 && cb < 2048)
1155 pThis->pszRedirLocation = RTStrDup(pszRedirect);
1156 rc = VERR_HTTP_REDIRECTED;
1157 break;
1158 }
1159 case 400:
1160 /* bad request */
1161 rc = VERR_HTTP_BAD_REQUEST;
1162 break;
1163 case 403:
1164 /* forbidden, authorization will not help */
1165 rc = VERR_HTTP_ACCESS_DENIED;
1166 break;
1167 case 404:
1168 /* URL not found */
1169 rc = VERR_HTTP_NOT_FOUND;
1170 break;
1171 }
1172 }
1173 else
1174 {
1175 switch (rcCurl)
1176 {
1177 case CURLE_URL_MALFORMAT:
1178 case CURLE_COULDNT_RESOLVE_HOST:
1179 rc = VERR_HTTP_NOT_FOUND;
1180 break;
1181 case CURLE_COULDNT_CONNECT:
1182 rc = VERR_HTTP_COULDNT_CONNECT;
1183 break;
1184 case CURLE_SSL_CONNECT_ERROR:
1185 rc = VERR_HTTP_SSL_CONNECT_ERROR;
1186 break;
1187 case CURLE_SSL_CACERT:
1188 /* The peer certificate cannot be authenticated with the CA certificates
1189 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
1190 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
1191 break;
1192 case CURLE_SSL_CACERT_BADFILE:
1193 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
1194 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
1195 break;
1196 case CURLE_ABORTED_BY_CALLBACK:
1197 /* forcefully aborted */
1198 rc = VERR_HTTP_ABORTED;
1199 break;
1200 case CURLE_COULDNT_RESOLVE_PROXY:
1201 rc = VERR_HTTP_PROXY_NOT_FOUND;
1202 break;
1203 case CURLE_WRITE_ERROR:
1204 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
1205 break;
1206 //case CURLE_READ_ERROR
1207
1208 default:
1209 break;
1210 }
1211 }
1212
1213 return rc;
1214}
1215
1216
1217/**
1218 * cURL callback for reporting progress, we use it for checking for abort.
1219 */
1220static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
1221{
1222 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
1223 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
1224
1225 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
1226
1227 return pThis->fAbort ? 1 : 0;
1228}
1229
1230
1231/**
1232 * Whether we're likely to need SSL to handle the give URL.
1233 *
1234 * @returns true if we need, false if we probably don't.
1235 * @param pszUrl The URL.
1236 */
1237static bool rtHttpNeedSsl(const char *pszUrl)
1238{
1239 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
1240}
1241
1242
1243/**
1244 * Applies recoded settings to the cURL instance before doing work.
1245 *
1246 * @returns IPRT status code.
1247 * @param pThis The HTTP/HTTPS client instance.
1248 * @param pszUrl The URL.
1249 */
1250static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
1251{
1252 /*
1253 * The URL.
1254 */
1255 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
1256 if (CURL_FAILURE(rcCurl))
1257 return VERR_INVALID_PARAMETER;
1258
1259 /*
1260 * Proxy config.
1261 */
1262 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
1263 if (RT_FAILURE(rc))
1264 return rc;
1265
1266 /*
1267 * Setup SSL. Can be a bit of work.
1268 */
1269 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
1270 if (CURL_FAILURE(rcCurl))
1271 return VERR_INVALID_PARAMETER;
1272
1273 const char *pszCaFile = pThis->pszCaFile;
1274 if ( !pszCaFile
1275 && rtHttpNeedSsl(pszUrl))
1276 {
1277 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
1278 if (RT_SUCCESS(rc))
1279 pszCaFile = pThis->pszCaFile;
1280 else
1281 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
1282 }
1283 if (pszCaFile)
1284 {
1285 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
1286 if (CURL_FAILURE(rcCurl))
1287 return VERR_HTTP_CURL_ERROR;
1288 }
1289
1290 /*
1291 * Progress/abort.
1292 */
1293 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
1294 if (CURL_FAILURE(rcCurl))
1295 return VERR_HTTP_CURL_ERROR;
1296 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
1297 if (CURL_FAILURE(rcCurl))
1298 return VERR_HTTP_CURL_ERROR;
1299 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
1300 if (CURL_FAILURE(rcCurl))
1301 return VERR_HTTP_CURL_ERROR;
1302
1303 return VINF_SUCCESS;
1304}
1305
1306
1307/**
1308 * cURL callback for writing data.
1309 */
1310static size_t rtHttpWriteData(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
1311{
1312 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
1313
1314 /*
1315 * Do max size and overflow checks.
1316 */
1317 size_t const cbToAppend = cbUnit * cUnits;
1318 size_t const cbCurSize = pThis->Output.Mem.cb;
1319 size_t const cbNewSize = cbCurSize + cbToAppend;
1320 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
1321 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
1322 {
1323 if (cbNewSize + 1 <= pThis->Output.Mem.cbAllocated)
1324 {
1325 memcpy(&pThis->Output.Mem.pb[cbCurSize], pvBuf, cbToAppend);
1326 pThis->Output.Mem.cb = cbNewSize;
1327 pThis->Output.Mem.pb[cbNewSize] = '\0';
1328 return cbToAppend;
1329 }
1330
1331 /*
1332 * We need to reallocate the output buffer.
1333 */
1334 /** @todo this could do with a better strategy wrt growth. */
1335 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
1336 if ( cbAlloc <= pThis->cbDownloadHint
1337 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
1338 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
1339
1340 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pThis->Output.Mem.pb, cbAlloc);
1341 if (pbNew)
1342 {
1343 memcpy(&pbNew[cbCurSize], pvBuf, cbToAppend);
1344 pbNew[cbNewSize] = '\0';
1345
1346 pThis->Output.Mem.cbAllocated = cbAlloc;
1347 pThis->Output.Mem.pb = pbNew;
1348 pThis->Output.Mem.cb = cbNewSize;
1349 return cbToAppend;
1350 }
1351
1352 pThis->rcOutput = VERR_NO_MEMORY;
1353 }
1354 else
1355 pThis->rcOutput = VERR_TOO_MUCH_DATA;
1356
1357 /*
1358 * Failure - abort.
1359 */
1360 RTMemFree(pThis->Output.Mem.pb);
1361 pThis->Output.Mem.pb = NULL;
1362 pThis->Output.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
1363 pThis->fAbort = true;
1364 return 0;
1365}
1366
1367
1368/**
1369 * Internal worker that performs a HTTP GET.
1370 *
1371 * @returns IPRT status code.
1372 * @param hHttp The HTTP/HTTPS client instance.
1373 * @param pszUrl The URL.
1374 * @param ppvResponse Where to return the pointer to the allocated
1375 * response data (RTMemFree). There will always be
1376 * an zero terminator char after the response, that
1377 * is not part of the size returned via @a pcb.
1378 * @param pcb The size of the response data.
1379 *
1380 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
1381 * threads, because that will probably blow up!
1382 */
1383static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, uint8_t **ppvResponse, size_t *pcb)
1384{
1385 PRTHTTPINTERNAL pThis = hHttp;
1386 RTHTTP_VALID_RETURN(pThis);
1387
1388 /*
1389 * Reset the return values in case of more "GUI programming" on the client
1390 * side (i.e. a programming style not bothering checking return codes).
1391 */
1392 *ppvResponse = NULL;
1393 *pcb = 0;
1394
1395 /*
1396 * Set the busy flag (paranoia).
1397 */
1398 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
1399 AssertReturn(!fBusy, VERR_WRONG_ORDER);
1400
1401 /*
1402 * Reset the state and apply settings.
1403 */
1404 pThis->fAbort = false;
1405 pThis->rcOutput = VINF_SUCCESS;
1406 pThis->cbDownloadHint = 0;
1407
1408 int rc = rtHttpApplySettings(hHttp, pszUrl);
1409 if (RT_SUCCESS(rc))
1410 {
1411 RT_ZERO(pThis->Output.Mem);
1412 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteData);
1413 if (!CURL_FAILURE(rcCurl))
1414 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
1415 if (!CURL_FAILURE(rcCurl))
1416 {
1417 /*
1418 * Perform the HTTP operation.
1419 */
1420 rcCurl = curl_easy_perform(pThis->pCurl);
1421 rc = rtHttpGetCalcStatus(pThis, rcCurl);
1422 if (RT_SUCCESS(rc))
1423 rc = pThis->rcOutput;
1424 if (RT_SUCCESS(rc))
1425 {
1426 *ppvResponse = pThis->Output.Mem.pb;
1427 *pcb = pThis->Output.Mem.cb;
1428 }
1429 else if (pThis->Output.Mem.pb)
1430 RTMemFree(pThis->Output.Mem.pb);
1431 RT_ZERO(pThis->Output.Mem);
1432 }
1433 else
1434 rc = VERR_HTTP_CURL_ERROR;
1435 }
1436
1437 ASMAtomicWriteBool(&pThis->fBusy, false);
1438 return rc;
1439}
1440
1441
1442RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
1443{
1444 uint8_t *pv;
1445 size_t cb;
1446 int rc = rtHttpGetToMem(hHttp, pszUrl, &pv, &cb);
1447 if (RT_SUCCESS(rc))
1448 {
1449 if (pv) /* paranoia */
1450 *ppszNotUtf8 = (char *)pv;
1451 else
1452 *ppszNotUtf8 = (char *)RTMemDup("", 1);
1453 }
1454 else
1455 *ppszNotUtf8 = NULL;
1456 return rc;
1457}
1458
1459
1460RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
1461{
1462 RTMemFree(pszNotUtf8);
1463}
1464
1465
1466RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
1467{
1468 return rtHttpGetToMem(hHttp, pszUrl, (uint8_t **)ppvResponse, pcb);
1469}
1470
1471
1472RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
1473{
1474 RTMemFree(pvResponse);
1475}
1476
1477
1478/**
1479 * cURL callback for writing data to a file.
1480 */
1481static size_t rtHttpWriteDataToFile(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
1482{
1483 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
1484 size_t cbWritten = 0;
1485 int rc = RTFileWrite(pThis->Output.hFile, pvBuf, cbUnit * cUnits, &cbWritten);
1486 if (RT_SUCCESS(rc))
1487 return cbWritten;
1488 pThis->rcOutput = rc;
1489 return 0;
1490}
1491
1492
1493RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
1494{
1495 PRTHTTPINTERNAL pThis = hHttp;
1496 RTHTTP_VALID_RETURN(pThis);
1497
1498 /*
1499 * Set the busy flag (paranoia).
1500 */
1501 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
1502 AssertReturn(!fBusy, VERR_WRONG_ORDER);
1503
1504 /*
1505 * Reset the state and apply settings.
1506 */
1507 pThis->fAbort = false;
1508 pThis->rcOutput = VINF_SUCCESS;
1509 pThis->cbDownloadHint = 0;
1510
1511 int rc = rtHttpApplySettings(hHttp, pszUrl);
1512 if (RT_SUCCESS(rc))
1513 {
1514 pThis->Output.hFile = NIL_RTFILE;
1515 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteDataToFile);
1516 if (!CURL_FAILURE(rcCurl))
1517 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
1518 if (!CURL_FAILURE(rcCurl))
1519 {
1520 /*
1521 * Open the output file.
1522 */
1523 rc = RTFileOpen(&pThis->Output.hFile, pszDstFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
1524 if (RT_SUCCESS(rc))
1525 {
1526 /*
1527 * Perform the HTTP operation.
1528 */
1529 rcCurl = curl_easy_perform(pThis->pCurl);
1530 rc = rtHttpGetCalcStatus(pThis, rcCurl);
1531 if (RT_SUCCESS(rc))
1532 rc = pThis->rcOutput;
1533
1534 int rc2 = RTFileClose(pThis->Output.hFile);
1535 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
1536 rc = rc2;
1537 }
1538 pThis->Output.hFile = NIL_RTFILE;
1539 }
1540 else
1541 rc = VERR_HTTP_CURL_ERROR;
1542 }
1543
1544 ASMAtomicWriteBool(&pThis->fBusy, false);
1545 return rc;
1546}
1547
Note: See TracBrowser for help on using the repository browser.

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