VirtualBox

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

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

http-curl.cpp: OSE build fix

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