VirtualBox

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

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

cleanup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 85.3 KB
Line 
1/* $Id: http-curl.cpp 58201 2015-10-12 15:36:52Z 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#define LOG_GROUP RTLOGGROUP_HTTP
32#include <iprt/http.h>
33#include "internal/iprt.h"
34
35#include <iprt/asm.h>
36#include <iprt/assert.h>
37#include <iprt/cidr.h>
38#include <iprt/crypto/store.h>
39#include <iprt/ctype.h>
40#include <iprt/env.h>
41#include <iprt/err.h>
42#include <iprt/file.h>
43#include <iprt/ldr.h>
44#include <iprt/log.h>
45#include <iprt/mem.h>
46#include <iprt/net.h>
47#include <iprt/once.h>
48#include <iprt/path.h>
49#include <iprt/stream.h>
50#include <iprt/string.h>
51#include <iprt/uni.h>
52#include <iprt/uri.h>
53
54#include "internal/magics.h"
55
56#include <curl/curl.h>
57
58#ifdef RT_OS_DARWIN
59# include <CoreFoundation/CoreFoundation.h>
60# include <SystemConfiguration/SystemConfiguration.h>
61# include <CoreServices/CoreServices.h>
62#endif
63#ifdef RT_OS_WINDOWS
64# include <Winhttp.h>
65# include "../r3/win/internal-r3-win.h"
66#endif
67
68#ifdef RT_OS_LINUX
69//# define IPRT_USE_LIBPROXY
70#endif
71#ifdef IPRT_USE_LIBPROXY
72# include <stdlib.h> /* free */
73#endif
74
75
76/*********************************************************************************************************************************
77* Structures and Typedefs *
78*********************************************************************************************************************************/
79/**
80 * Internal HTTP client instance.
81 */
82typedef struct RTHTTPINTERNAL
83{
84 /** Magic value. */
85 uint32_t u32Magic;
86 /** cURL handle. */
87 CURL *pCurl;
88 /** The last response code. */
89 long lLastResp;
90 /** Custom headers/ */
91 struct curl_slist *pHeaders;
92 /** CA certificate file for HTTPS authentication. */
93 char *pszCaFile;
94 /** Whether to delete the CA on destruction. */
95 bool fDeleteCaFile;
96
97 /** Set if we've applied a CURLOTP_USERAGENT already. */
98 bool fHaveSetUserAgent;
99 /** Set if we've got a user agent header, otherwise clear. */
100 bool fHaveUserAgentHeader;
101
102 /** @name Proxy settings.
103 * When fUseSystemProxySettings is set, the other members will be updated each
104 * time we're presented with a new URL. The members reflect the cURL
105 * configuration.
106 *
107 * @{ */
108 /** Set if we should use the system proxy settings for a URL.
109 * This means reconfiguring cURL for each request. */
110 bool fUseSystemProxySettings;
111 /** Set if we've detected no proxy necessary. */
112 bool fNoProxy;
113 /** Proxy host name (RTStrFree). */
114 char *pszProxyHost;
115 /** Proxy port number (UINT32_MAX if not specified). */
116 uint32_t uProxyPort;
117 /** The proxy type (CURLPROXY_HTTP, CURLPROXY_SOCKS5, ++). */
118 curl_proxytype enmProxyType;
119 /** Proxy username (RTStrFree). */
120 char *pszProxyUsername;
121 /** Proxy password (RTStrFree). */
122 char *pszProxyPassword;
123 /** @} */
124
125 /** Abort the current HTTP request if true. */
126 bool volatile fAbort;
127 /** Set if someone is preforming an HTTP operation. */
128 bool volatile fBusy;
129 /** The location field for 301 responses. */
130 char *pszRedirLocation;
131
132 /** Output callback data. */
133 union
134 {
135 /** For file destination. */
136 RTFILE hFile;
137 /** For memory destination. */
138 struct
139 {
140 /** The current size (sans terminator char). */
141 size_t cb;
142 /** The currently allocated size. */
143 size_t cbAllocated;
144 /** Pointer to the buffer. */
145 uint8_t *pb;
146 } Mem;
147 } Output;
148 /** Output callback status. */
149 int rcOutput;
150 /** Download size hint set by the progress callback. */
151 uint64_t cbDownloadHint;
152} RTHTTPINTERNAL;
153/** Pointer to an internal HTTP client instance. */
154typedef RTHTTPINTERNAL *PRTHTTPINTERNAL;
155
156
157#ifdef RT_OS_WINDOWS
158/** @name Windows: Types for dynamically resolved APIs
159 * @{ */
160typedef HINTERNET (WINAPI * PFNWINHTTPOPEN)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
161typedef BOOL (WINAPI * PFNWINHTTPCLOSEHANDLE)(HINTERNET);
162typedef BOOL (WINAPI * PFNWINHTTPGETPROXYFORURL)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS *, WINHTTP_PROXY_INFO *);
163typedef BOOL (WINAPI * PFNWINHTTPGETDEFAULTPROXYCONFIGURATION)(WINHTTP_PROXY_INFO *);
164typedef BOOL (WINAPI * PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *);
165/** @} */
166#endif
167
168#ifdef IPRT_USE_LIBPROXY
169typedef struct px_proxy_factory *PLIBPROXYFACTORY;
170typedef PLIBPROXYFACTORY (* PFNLIBPROXYFACTORYCTOR)(void);
171typedef void (* PFNLIBPROXYFACTORYDTOR)(PLIBPROXYFACTORY);
172typedef char ** (* PFNLIBPROXYFACTORYGETPROXIES)(PLIBPROXYFACTORY, const char *);
173#endif
174
175
176/*********************************************************************************************************************************
177* Defined Constants And Macros *
178*********************************************************************************************************************************/
179/** @def RTHTTP_MAX_MEM_DOWNLOAD_SIZE
180 * The max size we are allowed to download to a memory buffer.
181 *
182 * @remarks The minus 1 is for the trailing zero terminator we always add.
183 */
184#if ARCH_BITS == 64
185# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1)
186#else
187# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1)
188#endif
189
190/** Checks whether a cURL return code indicates success. */
191#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK)
192/** Checks whether a cURL return code indicates failure. */
193#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK)
194
195/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
196#define RTHTTP_VALID_RETURN_RC(hHttp, rcCurl) \
197 do { \
198 AssertPtrReturn((hHttp), (rcCurl)); \
199 AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (rcCurl)); \
200 } while (0)
201
202/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
203#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE)
204
205/** Validates a handle and returns (void) if not valid. */
206#define RTHTTP_VALID_RETURN_VOID(hHttp) \
207 do { \
208 AssertPtrReturnVoid(hHttp); \
209 AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \
210 } while (0)
211
212
213/*********************************************************************************************************************************
214* Global Variables *
215*********************************************************************************************************************************/
216#ifdef RT_OS_WINDOWS
217/** @name Windows: Dynamically resolved APIs
218 * @{ */
219static RTONCE g_WinResolveImportsOnce = RTONCE_INITIALIZER;
220static PFNWINHTTPOPEN g_pfnWinHttpOpen = NULL;
221static PFNWINHTTPCLOSEHANDLE g_pfnWinHttpCloseHandle = NULL;
222static PFNWINHTTPGETPROXYFORURL g_pfnWinHttpGetProxyForUrl = NULL;
223static PFNWINHTTPGETDEFAULTPROXYCONFIGURATION g_pfnWinHttpGetDefaultProxyConfiguration = NULL;
224static PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER g_pfnWinHttpGetIEProxyConfigForCurrentUser = NULL;
225/** @} */
226#endif
227
228#ifdef IPRT_USE_LIBPROXY
229/** @name Dynamaically resolved libproxy APIs.
230 * @{ */
231static RTONCE g_LibProxyResolveImportsOnce = RTONCE_INITIALIZER;
232static RTLDRMOD g_hLdrLibProxy = NIL_RTLDRMOD;
233static PFNLIBPROXYFACTORYCTOR g_pfnLibProxyFactoryCtor = NULL;
234static PFNLIBPROXYFACTORYDTOR g_pfnLibProxyFactoryDtor = NULL;
235static PFNLIBPROXYFACTORYGETPROXIES g_pfnLibProxyFactoryGetProxies = NULL;
236/** @} */
237#endif
238
239
240/*********************************************************************************************************************************
241* Internal Functions *
242*********************************************************************************************************************************/
243static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis);
244#ifdef RT_OS_DARWIN
245static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType);
246#endif
247
248
249RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp)
250{
251 AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER);
252
253 /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a
254 * problem if multiple threads get here at the same time. */
255 int rc = VERR_HTTP_INIT_FAILED;
256 CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL);
257 if (!CURL_FAILURE(rcCurl))
258 {
259 CURL *pCurl = curl_easy_init();
260 if (pCurl)
261 {
262 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL));
263 if (pThis)
264 {
265 pThis->u32Magic = RTHTTP_MAGIC;
266 pThis->pCurl = pCurl;
267 pThis->fUseSystemProxySettings = true;
268
269 *phHttp = (RTHTTP)pThis;
270
271 return VINF_SUCCESS;
272 }
273 rc = VERR_NO_MEMORY;
274 }
275 else
276 rc = VERR_HTTP_INIT_FAILED;
277 }
278 curl_global_cleanup();
279 return rc;
280}
281
282
283RTR3DECL(void) RTHttpDestroy(RTHTTP hHttp)
284{
285 if (hHttp == NIL_RTHTTP)
286 return;
287
288 PRTHTTPINTERNAL pThis = hHttp;
289 RTHTTP_VALID_RETURN_VOID(pThis);
290
291 Assert(!pThis->fBusy);
292
293 pThis->u32Magic = RTHTTP_MAGIC_DEAD;
294
295 curl_easy_cleanup(pThis->pCurl);
296 pThis->pCurl = NULL;
297
298 if (pThis->pHeaders)
299 curl_slist_free_all(pThis->pHeaders);
300
301 rtHttpUnsetCaFile(pThis);
302 Assert(!pThis->pszCaFile);
303
304 if (pThis->pszRedirLocation)
305 RTStrFree(pThis->pszRedirLocation);
306
307 RTStrFree(pThis->pszProxyHost);
308 RTStrFree(pThis->pszProxyUsername);
309 if (pThis->pszProxyPassword)
310 {
311 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
312 RTStrFree(pThis->pszProxyPassword);
313 }
314
315 RTMemFree(pThis);
316
317 curl_global_cleanup();
318}
319
320
321RTR3DECL(int) RTHttpAbort(RTHTTP hHttp)
322{
323 PRTHTTPINTERNAL pThis = hHttp;
324 RTHTTP_VALID_RETURN(pThis);
325
326 pThis->fAbort = true;
327
328 return VINF_SUCCESS;
329}
330
331
332RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation)
333{
334 PRTHTTPINTERNAL pThis = hHttp;
335 RTHTTP_VALID_RETURN(pThis);
336 Assert(!pThis->fBusy);
337
338 if (!pThis->pszRedirLocation)
339 return VERR_HTTP_NOT_FOUND;
340
341 return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation);
342}
343
344
345RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp)
346{
347 PRTHTTPINTERNAL pThis = hHttp;
348 RTHTTP_VALID_RETURN(pThis);
349 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
350
351 /*
352 * Change the settings.
353 */
354 pThis->fUseSystemProxySettings = true;
355 return VINF_SUCCESS;
356}
357
358
359/**
360 * rtHttpConfigureProxyForUrl: Update cURL proxy settings as needed.
361 *
362 * @returns IPRT status code.
363 * @param pThis The HTTP client instance.
364 * @param enmProxyType The proxy type.
365 * @param pszHost The proxy host name.
366 * @param uPort The proxy port number.
367 * @param pszUsername The proxy username, or NULL if none.
368 * @param pszPassword The proxy password, or NULL if none.
369 */
370static int rtHttpUpdateProxyConfig(PRTHTTPINTERNAL pThis, curl_proxytype enmProxyType, const char *pszHost,
371 uint32_t uPort, const char *pszUsername, const char *pszPassword)
372{
373 int rcCurl;
374 AssertReturn(pszHost, VERR_INVALID_PARAMETER);
375 Log(("rtHttpUpdateProxyConfig: pThis=%p type=%d host='%s' port=%u user='%s'%s\n",
376 pThis, enmProxyType, pszHost, uPort, pszUsername, pszPassword ? " with password" : " without password"));
377
378#ifdef CURLOPT_NOPROXY
379 if (pThis->fNoProxy)
380 {
381 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, (const char *)NULL);
382 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_NOPROXY=NULL: %d (%#x)\n", rcCurl, rcCurl),
383 VERR_HTTP_CURL_PROXY_CONFIG);
384 pThis->fNoProxy = false;
385 }
386#endif
387
388 if (enmProxyType != pThis->enmProxyType)
389 {
390 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)enmProxyType);
391 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYTYPE=%d: %d (%#x)\n", enmProxyType, rcCurl, rcCurl),
392 VERR_HTTP_CURL_PROXY_CONFIG);
393 pThis->enmProxyType = CURLPROXY_HTTP;
394 }
395
396 if (uPort != pThis->uProxyPort)
397 {
398 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort);
399 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPORT=%d: %d (%#x)\n", uPort, rcCurl, rcCurl),
400 VERR_HTTP_CURL_PROXY_CONFIG);
401 pThis->uProxyPort = uPort;
402 }
403
404 if ( pszUsername != pThis->pszProxyUsername
405 || RTStrCmp(pszUsername, pThis->pszProxyUsername))
406 {
407 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pszUsername);
408 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYUSERNAME=%s: %d (%#x)\n", pszUsername, rcCurl, rcCurl),
409 VERR_HTTP_CURL_PROXY_CONFIG);
410 if (pThis->pszProxyUsername)
411 {
412 RTStrFree(pThis->pszProxyUsername);
413 pThis->pszProxyUsername = NULL;
414 }
415 if (pszUsername)
416 {
417 pThis->pszProxyUsername = RTStrDup(pszUsername);
418 AssertReturn(pThis->pszProxyUsername, VERR_NO_STR_MEMORY);
419 }
420 }
421
422 if ( pszPassword != pThis->pszProxyPassword
423 || RTStrCmp(pszPassword, pThis->pszProxyPassword))
424 {
425 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pszPassword);
426 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPASSWORD=%s: %d (%#x)\n", pszPassword ? "xxx" : NULL, rcCurl, rcCurl),
427 VERR_HTTP_CURL_PROXY_CONFIG);
428 if (pThis->pszProxyPassword)
429 {
430 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
431 RTStrFree(pThis->pszProxyPassword);
432 pThis->pszProxyPassword = NULL;
433 }
434 if (pszPassword)
435 {
436 pThis->pszProxyPassword = RTStrDup(pszPassword);
437 AssertReturn(pThis->pszProxyPassword, VERR_NO_STR_MEMORY);
438 }
439 }
440
441 if ( pszHost != pThis->pszProxyHost
442 || RTStrCmp(pszHost, pThis->pszProxyHost))
443 {
444 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pszHost);
445 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXY=%s: %d (%#x)\n", pszHost, rcCurl, rcCurl),
446 VERR_HTTP_CURL_PROXY_CONFIG);
447 if (pThis->pszProxyHost)
448 {
449 RTStrFree(pThis->pszProxyHost);
450 pThis->pszProxyHost = NULL;
451 }
452 if (pszHost)
453 {
454 pThis->pszProxyHost = RTStrDup(pszHost);
455 AssertReturn(pThis->pszProxyHost, VERR_NO_STR_MEMORY);
456 }
457 }
458
459 return VINF_SUCCESS;
460}
461
462
463/**
464 * rtHttpConfigureProxyForUrl: Disables proxying.
465 *
466 * @returns IPRT status code.
467 * @param pThis The HTTP client instance.
468 */
469static int rtHttpUpdateAutomaticProxyDisable(PRTHTTPINTERNAL pThis)
470{
471 Log(("rtHttpUpdateAutomaticProxyDisable: pThis=%p\n", pThis));
472
473 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP) == CURLE_OK, VERR_INTERNAL_ERROR_2);
474 pThis->enmProxyType = CURLPROXY_HTTP;
475
476 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)1080) == CURLE_OK, VERR_INTERNAL_ERROR_2);
477 pThis->uProxyPort = 1080;
478
479 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
480 if (pThis->pszProxyUsername)
481 {
482 RTStrFree(pThis->pszProxyUsername);
483 pThis->pszProxyUsername = NULL;
484 }
485
486 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
487 if (pThis->pszProxyPassword)
488 {
489 RTStrFree(pThis->pszProxyPassword);
490 pThis->pszProxyPassword = NULL;
491 }
492
493 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
494 if (pThis->pszProxyHost)
495 {
496 RTStrFree(pThis->pszProxyHost);
497 pThis->pszProxyHost = NULL;
498 }
499
500#ifdef CURLOPT_NOPROXY
501 /* No proxy for everything! */
502 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, "*") == CURLE_OK, CURLOPT_PROXY);
503 pThis->fNoProxy = true;
504#endif
505
506 return VINF_SUCCESS;
507}
508
509
510/**
511 * See if the host name of the URL is included in the stripped no_proxy list.
512 *
513 * The no_proxy list is a colon or space separated list of domain names for
514 * which there should be no proxying. Given "no_proxy=oracle.com" neither the
515 * URL "http://www.oracle.com" nor "http://oracle.com" will not be proxied, but
516 * "http://notoracle.com" will be.
517 *
518 * @returns true if the URL is in the no_proxy list, otherwise false.
519 * @param pszUrl The URL.
520 * @param pszNoProxyList The stripped no_proxy list.
521 */
522static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList)
523{
524 /*
525 * Check for just '*', diabling proxying for everything.
526 * (Caller stripped pszNoProxyList.)
527 */
528 if (*pszNoProxyList == '*' && pszNoProxyList[1] == '\0')
529 return true;
530
531 /*
532 * Empty list? (Caller stripped it, remember).
533 */
534 if (!*pszNoProxyList)
535 return false;
536
537 /*
538 * We now need to parse the URL and extract the host name.
539 */
540 RTURIPARSED Parsed;
541 int rc = RTUriParse(pszUrl, &Parsed);
542 AssertRCReturn(rc, false);
543 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
544 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
545 return false;
546
547 bool fRet = false;
548 size_t const cchHost = strlen(pszHost);
549 if (cchHost)
550 {
551 /*
552 * The list is comma or space separated, walk it and match host names.
553 */
554 while (*pszNoProxyList != '\0')
555 {
556 /* Strip leading slashes, commas and dots. */
557 char ch;
558 while ( (ch = *pszNoProxyList) == ','
559 || ch == '.'
560 || RT_C_IS_SPACE(ch))
561 pszNoProxyList++;
562
563 /* Find the end. */
564 size_t cch = RTStrOffCharOrTerm(pszNoProxyList, ',');
565 size_t offNext = RTStrOffCharOrTerm(pszNoProxyList, ' ');
566 cch = RT_MIN(cch, offNext);
567 offNext = cch;
568
569 /* Trip trailing spaces, well tabs and stuff. */
570 while (cch > 0 && RT_C_IS_SPACE(pszNoProxyList[cch - 1]))
571 cch--;
572
573 /* Do the matching, if we have anything to work with. */
574 if (cch > 0)
575 {
576 if ( ( cch == cchHost
577 && RTStrNICmp(pszNoProxyList, pszHost, cch) == 0)
578 || ( cch < cchHost
579 && pszHost[cchHost - cch - 1] == '.'
580 && RTStrNICmp(pszNoProxyList, &pszHost[cchHost - cch], cch) == 0) )
581 {
582 fRet = true;
583 break;
584 }
585 }
586
587 /* Next. */
588 pszNoProxyList += offNext;
589 }
590 }
591
592 RTStrFree(pszHost);
593 return fRet;
594}
595
596
597/**
598 * Configures a proxy given a "URL" like specification.
599 *
600 * The format is:
601 * @verbatim
602 * [<scheme>"://"][<userid>[@<password>]:]<server>[":"<port>]
603 * @endverbatim
604 *
605 * Where the scheme gives the type of proxy server we're dealing with rather
606 * than the protocol of the external server we wish to talk to.
607 *
608 * @returns IPRT status code.
609 * @param pThis The HTTP client instance.
610 * @param pszProxyUrl The proxy server "URL".
611 */
612static int rtHttpConfigureProxyFromUrl(PRTHTTPINTERNAL pThis, const char *pszProxyUrl)
613{
614 /*
615 * Make sure it can be parsed as an URL.
616 */
617 char *pszFreeMe = NULL;
618 if (!strstr(pszProxyUrl, "://"))
619 {
620 static const char s_szPrefix[] = "http://";
621 size_t cchProxyUrl = strlen(pszProxyUrl);
622 pszFreeMe = (char *)RTMemTmpAlloc(sizeof(s_szPrefix) + cchProxyUrl);
623 if (pszFreeMe)
624 {
625 memcpy(pszFreeMe, s_szPrefix, sizeof(s_szPrefix) - 1);
626 memcpy(&pszFreeMe[sizeof(s_szPrefix) - 1], pszProxyUrl, cchProxyUrl);
627 pszFreeMe[sizeof(s_szPrefix) - 1 + cchProxyUrl] = '\0';
628 pszProxyUrl = pszFreeMe;
629 }
630 else
631 return VERR_NO_TMP_MEMORY;
632 }
633
634 RTURIPARSED Parsed;
635 int rc = RTUriParse(pszProxyUrl, &Parsed);
636 if (RT_SUCCESS(rc))
637 {
638 bool fDone = false;
639 char *pszHost = RTUriParsedAuthorityHost(pszProxyUrl, &Parsed);
640 if (pszHost)
641 {
642 /*
643 * We've got a host name, try get the rest.
644 */
645 char *pszUsername = RTUriParsedAuthorityUsername(pszProxyUrl, &Parsed);
646 char *pszPassword = RTUriParsedAuthorityPassword(pszProxyUrl, &Parsed);
647 uint32_t uProxyPort = RTUriParsedAuthorityPort(pszProxyUrl, &Parsed);
648 curl_proxytype enmProxyType;
649 if (RTUriIsSchemeMatch(pszProxyUrl, "http"))
650 {
651 enmProxyType = CURLPROXY_HTTP;
652 if (uProxyPort == UINT32_MAX)
653 uProxyPort = 80;
654 }
655 else if ( RTUriIsSchemeMatch(pszProxyUrl, "socks4")
656 || RTUriIsSchemeMatch(pszProxyUrl, "socks"))
657 enmProxyType = CURLPROXY_SOCKS4;
658 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks4a"))
659 enmProxyType = CURLPROXY_SOCKS4A;
660 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5"))
661 enmProxyType = CURLPROXY_SOCKS5;
662 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5h"))
663 enmProxyType = CURLPROXY_SOCKS5_HOSTNAME;
664 else
665 {
666 enmProxyType = CURLPROXY_HTTP;
667 if (uProxyPort == UINT32_MAX)
668 uProxyPort = 8080;
669 }
670
671 /* Guess the port from the proxy type if not given. */
672 if (uProxyPort == UINT32_MAX)
673 uProxyPort = 1080; /* CURL_DEFAULT_PROXY_PORT */
674
675 rc = rtHttpUpdateProxyConfig(pThis, enmProxyType, pszHost, uProxyPort, pszUsername, pszPassword);
676
677 RTStrFree(pszUsername);
678 RTStrFree(pszPassword);
679 RTStrFree(pszHost);
680 }
681 else
682 AssertMsgFailed(("RTUriParsedAuthorityHost('%s',) -> NULL\n", pszProxyUrl));
683 }
684 else
685 AssertMsgFailed(("RTUriParse('%s',) -> %Rrc\n", pszProxyUrl, rc));
686
687 if (pszFreeMe)
688 RTMemTmpFree(pszFreeMe);
689 return rc;
690}
691
692
693/**
694 * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out
695 * the proxy config.
696 *
697 * @returns IPRT status code.
698 * @param pThis The HTTP client instance.
699 * @param pszUrl The URL to configure a proxy for.
700 */
701static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl)
702{
703 char szTmp[_1K];
704
705 /*
706 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
707 */
708 const char *pszNoProxyVar;
709 size_t cchActual;
710 char *pszNoProxyFree = NULL;
711 char *pszNoProxy = szTmp;
712 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
713 if (rc == VERR_ENV_VAR_NOT_FOUND)
714 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
715 if (rc == VERR_BUFFER_OVERFLOW)
716 {
717 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
718 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
719 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
720 }
721 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
722 bool fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
723 RTMemTmpFree(pszNoProxyFree);
724 if (!fNoProxy)
725 {
726 /*
727 * Get the schema specific specific env var, falling back on the
728 * generic all_proxy if not found.
729 */
730 const char *apszEnvVars[4];
731 unsigned cEnvVars = 0;
732 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
733 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
734 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
735 {
736 apszEnvVars[cEnvVars++] = "https_proxy";
737 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
738 }
739 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
740 {
741 apszEnvVars[cEnvVars++] = "ftp_proxy";
742 apszEnvVars[cEnvVars++] = "FTP_PROXY";
743 }
744 else
745 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
746 apszEnvVars[cEnvVars++] = "all_proxy";
747 apszEnvVars[cEnvVars++] = "ALL_PROXY";
748
749 /*
750 * We try the env vars out and goes with the first one we can make sense out of.
751 * If we cannot make sense of any, we return the first unexpected rc we got.
752 */
753 rc = VINF_SUCCESS;
754 for (uint32_t i = 0; i < cEnvVars; i++)
755 {
756 size_t cchValue;
757 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
758 if (RT_SUCCESS(rc2))
759 {
760 if (cchValue != 0)
761 {
762 /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */
763 if (!strstr(szTmp, "://"))
764 {
765 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
766 memcpy(szTmp, RT_STR_TUPLE("http://"));
767 }
768
769 rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp);
770 if (RT_SUCCESS(rc2))
771 rc = rc2;
772 }
773 /*
774 * The variable is empty. Guess that means no proxying wanted.
775 */
776 else
777 {
778 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
779 break;
780 }
781 }
782 else
783 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
784 }
785 }
786 /*
787 * The host is the no-proxy list, it seems.
788 */
789 else
790 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
791
792 return rc;
793}
794
795#ifdef IPRT_USE_LIBPROXY
796
797/**
798 * @callback_method_impl{FNRTONCE,
799 * Attempts to load libproxy.so.1 and resolves APIs}
800 */
801static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser)
802{
803 RTLDRMOD hMod;
804 int rc = RTLdrLoad("/usr/lib/libproxy.so.1", &hMod);
805 if (RT_SUCCESS(rc))
806 {
807 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor);
808 if (RT_SUCCESS(rc))
809 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor);
810 if (RT_SUCCESS(rc))
811 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies);
812 if (RT_SUCCESS(rc))
813 g_hLdrLibProxy = hMod;
814 else
815 RTLdrClose(hMod);
816 AssertRC(rc);
817 }
818
819 NOREF(pvUser);
820 return rc;
821}
822
823/**
824 * Reconfigures the cURL proxy settings for the given URL, libproxy style.
825 *
826 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
827 * @param pThis The HTTP client instance.
828 * @param pszUrl The URL.
829 */
830static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
831{
832 int rcRet = VINF_NOT_SUPPORTED;
833
834 int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL);
835 if (RT_SUCCESS(rc))
836 {
837 /*
838 * Instance the factory and ask for a list of proxies.
839 */
840 PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor();
841 if (pFactory)
842 {
843 char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl);
844 g_pfnLibProxyFactoryDtor(pFactory);
845 if (papszProxies)
846 {
847 /*
848 * Look for something we can use.
849 */
850 for (unsigned i = 0; papszProxies[i]; i++)
851 {
852 if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0)
853 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
854 else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0
855 || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0
856 || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0
857 || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */
858 )
859 rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]);
860 else
861 continue;
862 if (rcRet != VINF_NOT_SUPPORTED)
863 break;
864 }
865
866 /* free the result. */
867 for (unsigned i = 0; papszProxies[i]; i++)
868 free(papszProxies[i]);
869 free(papszProxies);
870 }
871 }
872 }
873
874 return rcRet;
875}
876
877#endif /* IPRT_USE_LIBPROXY */
878
879#ifdef RT_OS_DARWIN
880
881/**
882 * Get a boolean like integer value from a dictionary.
883 *
884 * @returns true / false.
885 * @param hDict The dictionary.
886 * @param pvKey The dictionary value key.
887 */
888static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault)
889{
890 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey);
891 if (hNum)
892 {
893 int fEnabled;
894 if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled))
895 return fDefault;
896 return fEnabled != 0;
897 }
898 return fDefault;
899}
900
901
902/**
903 * Creates a CFURL object for an URL.
904 *
905 * @returns CFURL object reference.
906 * @param pszUrl The URL.
907 */
908static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl)
909{
910 CFURLRef hUrl = NULL;
911 CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8);
912 if (hStrUrl)
913 {
914 CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl,
915 NULL /*charactersToLeaveUnescaped*/,
916 NULL /*legalURLCharactersToBeEscaped*/,
917 kCFStringEncodingUTF8);
918 if (hStrUrlEscaped)
919 {
920 hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/);
921 Assert(hUrl);
922 CFRelease(hStrUrlEscaped);
923 }
924 else
925 AssertFailed();
926 CFRelease(hStrUrl);
927 }
928 else
929 AssertFailed();
930 return hUrl;
931}
932
933
934/**
935 * For passing results from rtHttpDarwinPacCallback to
936 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
937 */
938typedef struct RTHTTPDARWINPACRESULT
939{
940 CFArrayRef hArrayProxies;
941 CFErrorRef hError;
942} RTHTTPDARWINPACRESULT;
943typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT;
944
945/**
946 * Stupid callback for getting the result from
947 * CFNetworkExecuteProxyAutoConfigurationURL.
948 *
949 * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of
950 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
951 * @param hArrayProxies The result array.
952 * @param hError Errors, if any.
953 */
954static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError)
955{
956 PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser;
957
958 Assert(pResult->hArrayProxies == NULL);
959 if (hArrayProxies)
960 pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies);
961
962 Assert(pResult->hError == NULL);
963 if (hError)
964 pResult->hError = (CFErrorRef)CFRetain(hError);
965
966 CFRunLoopStop(CFRunLoopGetCurrent());
967}
968
969
970/**
971 * Executes a PAC script and returning the proxies it suggests.
972 *
973 * @returns Array of proxy configs (CFProxySupport.h style).
974 * @param pThis The HTTP client instance.
975 * @param hUrlTarget The URL we're about to use.
976 * @param hUrlScript The PAC script URL.
977 */
978static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(PRTHTTPINTERNAL pThis, CFURLRef hUrlTarget, CFURLRef hUrlScript)
979{
980 char szTmp[256];
981 if (LogIsFlowEnabled())
982 {
983 szTmp[0] = '\0';
984 CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
985 LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp));
986 }
987
988 /*
989 * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to
990 * download the script ourselves and mess around with too many CF APIs.
991 */
992 CFRunLoopRef hRunLoop = CFRunLoopGetCurrent();
993 AssertReturn(hRunLoop, NULL);
994
995 RTHTTPDARWINPACRESULT Result = { NULL, NULL };
996 CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL };
997 CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget,
998 rtHttpDarwinPacCallback, &Ctx);
999 AssertReturn(hRunLoopSrc, NULL);
1000
1001 CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool");
1002 CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode);
1003 CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */
1004 CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode);
1005
1006 /** @todo convert errors, maybe even fail. */
1007
1008 /*
1009 * Autoconfig (or missing wpad server) typically results in:
1010 * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2).
1011 *
1012 * In the autoconfig case, it looks like we're getting two entries, first
1013 * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to
1014 * be very upset if this fails, just continue trying alternatives.
1015 */
1016 if (Result.hError)
1017 {
1018 if (LogIsEnabled())
1019 {
1020 szTmp[0] = '\0';
1021 CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1022 Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp));
1023 }
1024 CFRelease(Result.hError);
1025 }
1026 return Result.hArrayProxies;
1027}
1028
1029
1030/**
1031 * Attempt to configure the proxy according to @a hDictProxy.
1032 *
1033 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1034 * the caller should try out alternative proxy configs and fallbacks.
1035 * @param pThis The HTTP client instance.
1036 * @param hDictProxy The proxy configuration (see CFProxySupport.h).
1037 * @param hUrlTarget The URL we're about to use.
1038 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1039 * javascript URL). This is set when we're processing
1040 * the output from a PAC script.
1041 */
1042static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType)
1043{
1044 CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey);
1045 AssertStmt(hStrProxyType, continue);
1046
1047 /*
1048 * No proxy is fairly simple and common.
1049 */
1050 if (CFEqual(hStrProxyType, kCFProxyTypeNone))
1051 return rtHttpUpdateAutomaticProxyDisable(pThis);
1052
1053 /*
1054 * PAC URL means recursion, however we only do one level.
1055 */
1056 if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL))
1057 {
1058 AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED);
1059
1060 CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey);
1061 AssertReturn(hUrlScript, VINF_NOT_SUPPORTED);
1062
1063 int rcRet = VINF_NOT_SUPPORTED;
1064 CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(pThis, hUrlTarget, hUrlScript);
1065 if (hArray)
1066 {
1067 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1068 CFRelease(hArray);
1069 }
1070 return rcRet;
1071 }
1072
1073 /*
1074 * Determine the proxy type (not entirely sure about type == proxy type and
1075 * not scheme/protocol)...
1076 */
1077 curl_proxytype enmProxyType = CURLPROXY_HTTP;
1078 uint32_t uDefaultProxyPort = 8080;
1079 if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP)
1080 || CFEqual(hStrProxyType, kCFProxyTypeHTTPS))
1081 { /* defaults */ }
1082 else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS))
1083 {
1084 /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or
1085 * SOCK5 on the other side... Selecting SOCKS5 for now. */
1086 enmProxyType = CURLPROXY_SOCKS5;
1087 uDefaultProxyPort = 1080;
1088 }
1089 /* Unknown proxy type. */
1090 else
1091 return VINF_NOT_SUPPORTED;
1092
1093 /*
1094 * Extract the proxy configuration.
1095 */
1096 /* The proxy host name. */
1097 char szHostname[_1K];
1098 CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey);
1099 AssertReturn(hStr, VINF_NOT_SUPPORTED);
1100 AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1101
1102 /* Get the port number (optional). */
1103 SInt32 iProxyPort;
1104 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey);
1105 if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort))
1106 AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort);
1107 else
1108 iProxyPort = uDefaultProxyPort;
1109
1110 /* The proxy username. */
1111 char szUsername[256];
1112 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey);
1113 if (hStr)
1114 AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1115 else
1116 szUsername[0] = '\0';
1117
1118 /* The proxy password. */
1119 char szPassword[384];
1120 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey);
1121 if (hStr)
1122 AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1123 else
1124 szPassword[0] = '\0';
1125
1126 /*
1127 * Apply the proxy config.
1128 */
1129 return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort,
1130 szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL);
1131}
1132
1133
1134/**
1135 * Try do proxy config for our HTTP client instance given an array of proxies.
1136 *
1137 * This is used with the output from a CFProxySupport.h API.
1138 *
1139 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1140 * we might want to try out fallbacks.
1141 * @param pThis The HTTP client instance.
1142 * @param hArrayProxies The proxies CFPRoxySupport have given us.
1143 * @param hUrlTarget The URL we're about to use.
1144 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1145 * javascript URL). This is set when we're processing
1146 * the output from a PAC script.
1147 */
1148static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType)
1149{
1150 int rcRet = VINF_NOT_SUPPORTED;
1151 CFIndex const cEntries = CFArrayGetCount(hArrayProxies);
1152 LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries));
1153 for (CFIndex i = 0; i < cEntries; i++)
1154 {
1155 CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i);
1156 AssertStmt(hDictProxy, continue);
1157
1158 rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType);
1159 if (rcRet != VINF_NOT_SUPPORTED)
1160 break;
1161 }
1162 return rcRet;
1163}
1164
1165
1166/**
1167 * Inner worker for rtHttpWinConfigureProxyForUrl.
1168 *
1169 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1170 * @param pThis The HTTP client instance.
1171 * @param pszUrl The URL.
1172 */
1173static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies,
1174 const char *pszUrl, PRTURIPARSED pParsed, const char *pszHost)
1175{
1176 CFArrayRef hArray;
1177
1178 /*
1179 * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply
1180 * proxy exclusion rules (tested on 10.9). So, do that manually.
1181 */
1182 RTNETADDRU HostAddr;
1183 int fIsHostIpv4Address = -1;
1184 char szTmp[_4K];
1185
1186 /* If we've got a simple hostname, something containing no dots, we must check
1187 whether such simple hostnames are excluded from proxying by default or not. */
1188 if (strchr(pszHost, '.') == NULL)
1189 {
1190 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false))
1191 return rtHttpUpdateAutomaticProxyDisable(pThis);
1192 fIsHostIpv4Address = false;
1193 }
1194
1195 /* Consult the exclusion list. This is an array of strings.
1196 This is very similar to what we do on windows. */
1197 hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList);
1198 if (hArray)
1199 {
1200 CFIndex const cEntries = CFArrayGetCount(hArray);
1201 for (CFIndex i = 0; i < cEntries; i++)
1202 {
1203 CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i);
1204 AssertStmt(hStr, continue);
1205 AssertStmt(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8), continue);
1206 RTStrToLower(szTmp);
1207
1208 bool fRet;
1209 if ( strchr(szTmp, '*')
1210 || strchr(szTmp, '?'))
1211 fRet = RTStrSimplePatternMatch(szTmp, pszHost);
1212 else
1213 {
1214 if (fIsHostIpv4Address == -1)
1215 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1216 RTNETADDRIPV4 Network, Netmask;
1217 if ( fIsHostIpv4Address
1218 && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) )
1219 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1220 else
1221 fRet = strcmp(szTmp, pszHost) == 0;
1222 }
1223 if (fRet)
1224 return rtHttpUpdateAutomaticProxyDisable(pThis);
1225 }
1226 }
1227
1228#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */
1229 /*
1230 * Is proxy auto config (PAC) enabled? If so, we must consult it first.
1231 */
1232 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false))
1233 {
1234 /* Convert the auto config url string to a CFURL object. */
1235 CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString);
1236 if (hStrAutoConfigUrl)
1237 {
1238 if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8))
1239 {
1240 CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp);
1241 if (hUrlScript)
1242 {
1243 int rcRet = VINF_NOT_SUPPORTED;
1244 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1245 if (hUrlTarget)
1246 {
1247 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1248 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1249 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1250 if (hArray)
1251 CFRelease(hArray);
1252
1253 hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(pThis, hUrlTarget, hUrlScript);
1254 if (hArray)
1255 {
1256 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1257 CFRelease(hArray);
1258 }
1259 }
1260 CFRelease(hUrlScript);
1261 if (rcRet != VINF_NOT_SUPPORTED)
1262 return rcRet;
1263 }
1264 }
1265 }
1266 }
1267
1268 /*
1269 * Try static proxy configs.
1270 */
1271 /** @todo later if needed. */
1272 return VERR_NOT_SUPPORTED;
1273
1274#else
1275 /*
1276 * Simple solution - "just" use CFNetworkCopyProxiesForURL.
1277 */
1278 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1279 AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR);
1280 int rcRet = VINF_NOT_SUPPORTED;
1281
1282 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1283 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1284 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1285 if (hArray)
1286 CFRelease(hArray);
1287
1288 /* The actual run. */
1289 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies);
1290 if (hArray)
1291 {
1292 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/);
1293 CFRelease(hArray);
1294 }
1295 CFRelease(hUrlTarget);
1296
1297 return rcRet;
1298#endif
1299}
1300
1301/**
1302 * Reconfigures the cURL proxy settings for the given URL, OS X style.
1303 *
1304 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1305 * @param pThis The HTTP client instance.
1306 * @param pszUrl The URL.
1307 */
1308static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1309{
1310 /*
1311 * Parse the URL, if there isn't any host name (like for file:///xxx.txt)
1312 * we don't need to run thru proxy settings to know what to do.
1313 */
1314 RTURIPARSED Parsed;
1315 int rc = RTUriParse(pszUrl, &Parsed);
1316 AssertRCReturn(rc, false);
1317 if (Parsed.cchAuthorityHost == 0)
1318 return rtHttpUpdateAutomaticProxyDisable(pThis);
1319 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1320 AssertReturn(pszHost, VERR_NO_STR_MEMORY);
1321 RTStrToLower(pszHost);
1322
1323 /*
1324 * Get a copy of the proxy settings (10.6 API).
1325 */
1326 CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */
1327 if (hDictProxies)
1328 rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, &Parsed, pszHost);
1329 else
1330 rc = VINF_NOT_SUPPORTED;
1331 CFRelease(hDictProxies);
1332
1333 RTStrFree(pszHost);
1334 return rc;
1335}
1336
1337#endif /* RT_OS_DARWIN */
1338
1339#ifdef RT_OS_WINDOWS
1340
1341/**
1342 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
1343 */
1344static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
1345{
1346 /*
1347 * winhttp.dll is not present on NT4 and probably was first introduced with XP.
1348 */
1349 RTLDRMOD hMod;
1350 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
1351 if (RT_SUCCESS(rc))
1352 {
1353 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
1354 if (RT_SUCCESS(rc))
1355 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
1356 if (RT_SUCCESS(rc))
1357 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
1358 if (RT_SUCCESS(rc))
1359 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
1360 if (RT_SUCCESS(rc))
1361 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
1362 RTLdrClose(hMod);
1363 AssertRC(rc);
1364 }
1365 else
1366 AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc));
1367
1368 NOREF(pvUser);
1369 return rc;
1370}
1371
1372
1373/**
1374 * Matches the URL against the given Windows by-pass list.
1375 *
1376 * @returns true if we should by-pass the proxy for this URL, false if not.
1377 * @param pszUrl The URL.
1378 * @param pwszBypass The Windows by-pass list.
1379 */
1380static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass)
1381{
1382 /*
1383 * Don't bother parsing the URL if we've actually got nothing to work with
1384 * in the by-pass list.
1385 */
1386 if (!pwszBypass)
1387 return false;
1388
1389 RTUTF16 wc;
1390 while ( (wc = *pwszBypass) != '\0'
1391 && ( RTUniCpIsSpace(wc)
1392 || wc == ';') )
1393 pwszBypass++;
1394 if (wc == '\0')
1395 return false;
1396
1397 /*
1398 * We now need to parse the URL and extract the host name.
1399 */
1400 RTURIPARSED Parsed;
1401 int rc = RTUriParse(pszUrl, &Parsed);
1402 AssertRCReturn(rc, false);
1403 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1404 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
1405 return false;
1406 RTStrToLower(pszHost);
1407
1408 bool fRet = false;
1409 char *pszBypassFree;
1410 rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree);
1411 if (RT_SUCCESS(rc))
1412 {
1413 /*
1414 * Walk the by-pass list.
1415 *
1416 * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx
1417 * a by-pass list is semicolon delimited list. The entries are either host
1418 * names or IP addresses, and may use wildcard ('*', '?', I guess). There
1419 * special "<local>" entry matches anything without a dot.
1420 */
1421 RTNETADDRU HostAddr;
1422 int fIsHostIpv4Address = -1;
1423 char *pszEntry = pszBypassFree;
1424 while (*pszEntry != '\0')
1425 {
1426 /*
1427 * Find end of entry.
1428 */
1429 char ch;
1430 size_t cchEntry = 1;
1431 while ( (ch = pszEntry[cchEntry]) != '\0'
1432 && ch != ';'
1433 && !RT_C_IS_SPACE(ch))
1434 cchEntry++;
1435
1436 char chSaved = pszEntry[cchEntry];
1437 pszEntry[cchEntry] = '\0';
1438 RTStrToLower(pszEntry);
1439
1440 if ( cchEntry == sizeof("<local>") - 1
1441 && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0)
1442 fRet = strchr(pszHost, '.') == NULL;
1443 else if ( memchr(pszEntry, '*', cchEntry) != NULL
1444 || memchr(pszEntry, '?', cchEntry) != NULL)
1445 fRet = RTStrSimplePatternMatch(pszEntry, pszHost);
1446 else
1447 {
1448 if (fIsHostIpv4Address == -1)
1449 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1450 RTNETADDRIPV4 Network, Netmask;
1451 if ( fIsHostIpv4Address
1452 && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) )
1453 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1454 else
1455 fRet = strcmp(pszEntry, pszHost) == 0;
1456 }
1457
1458 pszEntry[cchEntry] = chSaved;
1459 if (fRet)
1460 break;
1461
1462 /*
1463 * Next entry.
1464 */
1465 pszEntry += cchEntry;
1466 while ( (ch = *pszEntry) != '\0'
1467 && ( ch == ';'
1468 || RT_C_IS_SPACE(ch)) )
1469 pszEntry++;
1470 }
1471
1472 RTStrFree(pszBypassFree);
1473 }
1474
1475 RTStrFree(pszHost);
1476 return false;
1477}
1478
1479
1480/**
1481 * Searches a Windows proxy server list for the best fitting proxy to use, then
1482 * reconfigures the HTTP client instance to use it.
1483 *
1484 * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback.
1485 * @param pThis The HTTP client instance.
1486 * @param pszUrl The URL needing proxying.
1487 * @param pwszProxies The list of proxy servers to choose from.
1488 */
1489static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies)
1490{
1491 /*
1492 * Fend off empty strings (very unlikely, but just in case).
1493 */
1494 if (!pwszProxies)
1495 return VINF_NOT_SUPPORTED;
1496
1497 RTUTF16 wc;
1498 while ( (wc = *pwszProxies) != '\0'
1499 && ( RTUniCpIsSpace(wc)
1500 || wc == ';') )
1501 pwszProxies++;
1502 if (wc == '\0')
1503 return VINF_NOT_SUPPORTED;
1504
1505 /*
1506 * We now need to parse the URL and extract the scheme.
1507 */
1508 RTURIPARSED Parsed;
1509 int rc = RTUriParse(pszUrl, &Parsed);
1510 AssertRCReturn(rc, false);
1511 char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed);
1512 AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY);
1513 size_t const cchUrlScheme = strlen(pszUrlScheme);
1514
1515 int rcRet = VINF_NOT_SUPPORTED;
1516 char *pszProxiesFree;
1517 rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree);
1518 if (RT_SUCCESS(rc))
1519 {
1520 /*
1521 * Walk the server list.
1522 *
1523 * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx
1524 * this is also a semicolon delimited list. The entries are on the form:
1525 * [<scheme>=][<scheme>"://"]<server>[":"<port>]
1526 */
1527 bool fBestEntryHasSameScheme = false;
1528 const char *pszBestEntry = NULL;
1529 char *pszEntry = pszProxiesFree;
1530 while (*pszEntry != '\0')
1531 {
1532 /*
1533 * Find end of entry. We include spaces here in addition to ';'.
1534 */
1535 char ch;
1536 size_t cchEntry = 1;
1537 while ( (ch = pszEntry[cchEntry]) != '\0'
1538 && ch != ';'
1539 && !RT_C_IS_SPACE(ch))
1540 cchEntry++;
1541
1542 char const chSaved = pszEntry[cchEntry];
1543 pszEntry[cchEntry] = '\0';
1544
1545 /* Parse the entry. */
1546 const char *pszEndOfScheme = strstr(pszEntry, "://");
1547 const char *pszEqual = (const char *)memchr(pszEntry, '=',
1548 pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry);
1549 if (pszEqual)
1550 {
1551 if ( pszEqual - pszEntry == cchUrlScheme
1552 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0)
1553 {
1554 pszBestEntry = pszEqual + 1;
1555 break;
1556 }
1557 }
1558 else
1559 {
1560 bool fSchemeMatch = pszEndOfScheme
1561 && pszEndOfScheme - pszEntry == cchUrlScheme
1562 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0;
1563 if ( !pszBestEntry
1564 || ( !fBestEntryHasSameScheme
1565 && fSchemeMatch) )
1566 {
1567 pszBestEntry = pszEntry;
1568 fBestEntryHasSameScheme = fSchemeMatch;
1569 }
1570 }
1571
1572 /*
1573 * Next entry.
1574 */
1575 if (!chSaved)
1576 break;
1577 pszEntry += cchEntry + 1;
1578 while ( (ch = *pszEntry) != '\0'
1579 && ( ch == ';'
1580 || RT_C_IS_SPACE(ch)) )
1581 pszEntry++;
1582 }
1583
1584 /*
1585 * If we found something, try use it.
1586 */
1587 if (pszBestEntry)
1588 rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry);
1589
1590 RTStrFree(pszProxiesFree);
1591 }
1592
1593 RTStrFree(pszUrlScheme);
1594 return rc;
1595}
1596
1597
1598/**
1599 * Reconfigures the cURL proxy settings for the given URL, Windows style.
1600 *
1601 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1602 * @param pThis The HTTP client instance.
1603 * @param pszUrl The URL.
1604 */
1605static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1606{
1607 int rcRet = VINF_NOT_SUPPORTED;
1608
1609 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
1610 if (RT_SUCCESS(rc))
1611 {
1612 /*
1613 * Try get some proxy info for the URL. We first try getting the IE
1614 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
1615 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
1616 */
1617 WINHTTP_PROXY_INFO ProxyInfo;
1618 PRTUTF16 pwszProxy = NULL;
1619 PRTUTF16 pwszNoProxy = NULL;
1620 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
1621 RT_ZERO(AutoProxyOptions);
1622 RT_ZERO(ProxyInfo);
1623
1624 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
1625 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
1626 {
1627 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1628 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
1629 if (IeProxyConfig.fAutoDetect)
1630 {
1631 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
1632 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
1633 }
1634 else if (AutoProxyOptions.lpszAutoConfigUrl)
1635 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1636 else if (ProxyInfo.lpszProxy)
1637 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
1638 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
1639 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
1640 }
1641 else
1642 {
1643 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
1644 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
1645 {
1646 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
1647 RT_ZERO(ProxyInfo);
1648 }
1649 }
1650
1651 /*
1652 * Should we try WinHttGetProxyForUrl?
1653 */
1654 if (AutoProxyOptions.dwFlags != 0)
1655 {
1656 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
1657 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
1658 if (hSession != NULL)
1659 {
1660 PRTUTF16 pwszUrl;
1661 rc = RTStrToUtf16(pszUrl, &pwszUrl);
1662 if (RT_SUCCESS(rc))
1663 {
1664 /*
1665 * Try autodetect first, then fall back on the config URL if there is one.
1666 *
1667 * Also, we first try without auto authentication, then with. This will according
1668 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
1669 * caching the result when it's processed out-of-process (seems default here on W10).
1670 */
1671 WINHTTP_PROXY_INFO TmpProxyInfo;
1672 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1673 if ( !fRc
1674 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1675 {
1676 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1677 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1678 }
1679
1680 if ( !fRc
1681 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
1682 && AutoProxyOptions.lpszAutoConfigUrl)
1683 {
1684 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1685 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1686 AutoProxyOptions.dwAutoDetectFlags = 0;
1687 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1688 if ( !fRc
1689 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1690 {
1691 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1692 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1693 }
1694 }
1695
1696 if (fRc)
1697 {
1698 if (ProxyInfo.lpszProxy)
1699 GlobalFree(ProxyInfo.lpszProxy);
1700 if (ProxyInfo.lpszProxyBypass)
1701 GlobalFree(ProxyInfo.lpszProxyBypass);
1702 ProxyInfo = TmpProxyInfo;
1703 }
1704 /*
1705 * If the autodetection failed, assume no proxy.
1706 */
1707 else
1708 {
1709 DWORD dwErr = GetLastError();
1710 if (dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED)
1711 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1712 else
1713 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl -> %u\n", dwErr));
1714 }
1715 RTUtf16Free(pwszUrl);
1716 }
1717 else
1718 {
1719 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
1720 rcRet = rc;
1721 }
1722 g_pfnWinHttpCloseHandle(hSession);
1723 }
1724 else
1725 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
1726 }
1727
1728 /*
1729 * Try use the proxy info we've found.
1730 */
1731 switch (ProxyInfo.dwAccessType)
1732 {
1733 case WINHTTP_ACCESS_TYPE_NO_PROXY:
1734 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1735 break;
1736
1737 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
1738 if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass))
1739 rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy);
1740 else
1741 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1742 break;
1743
1744 case 0:
1745 break;
1746
1747 default:
1748 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
1749 }
1750
1751 /*
1752 * Cleanup.
1753 */
1754 if (ProxyInfo.lpszProxy)
1755 GlobalFree(ProxyInfo.lpszProxy);
1756 if (ProxyInfo.lpszProxyBypass)
1757 GlobalFree(ProxyInfo.lpszProxyBypass);
1758 if (AutoProxyOptions.lpszAutoConfigUrl)
1759 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
1760 }
1761
1762 return rcRet;
1763}
1764
1765#endif /* RT_OS_WINDOWS */
1766
1767
1768static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1769{
1770 if (pThis->fUseSystemProxySettings)
1771 {
1772#ifdef IPRT_USE_LIBPROXY
1773 int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl);
1774 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1775 return rc;
1776 Assert(rc == VINF_NOT_SUPPORTED);
1777#endif
1778#ifdef RT_OS_DARWIN
1779 int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl);
1780 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1781 return rc;
1782 Assert(rc == VINF_NOT_SUPPORTED);
1783#endif
1784#ifdef RT_OS_WINDOWS
1785 int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl);
1786 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1787 return rc;
1788 Assert(rc == VINF_NOT_SUPPORTED);
1789#endif
1790/** @todo system specific class here, fall back on env vars if necessary. */
1791 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
1792 }
1793
1794 return VINF_SUCCESS;
1795}
1796
1797
1798RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
1799 const char *pcszProxyUser, const char *pcszProxyPwd)
1800{
1801 PRTHTTPINTERNAL pThis = hHttp;
1802 RTHTTP_VALID_RETURN(pThis);
1803 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
1804 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
1805
1806 /*
1807 * Update the settings.
1808 *
1809 * Currently, we don't make alot of effort parsing or checking the input, we
1810 * leave that to cURL. (A bit afraid of breaking user settings.)
1811 */
1812 pThis->fUseSystemProxySettings = false;
1813 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
1814}
1815
1816
1817RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
1818{
1819 PRTHTTPINTERNAL pThis = hHttp;
1820 RTHTTP_VALID_RETURN(pThis);
1821
1822 pThis->fHaveUserAgentHeader = false;
1823 if (!cHeaders)
1824 {
1825 if (pThis->pHeaders)
1826 curl_slist_free_all(pThis->pHeaders);
1827 pThis->pHeaders = 0;
1828 return VINF_SUCCESS;
1829 }
1830
1831 struct curl_slist *pHeaders = NULL;
1832 for (size_t i = 0; i < cHeaders; i++)
1833 {
1834 pHeaders = curl_slist_append(pHeaders, papszHeaders[i]);
1835 if (strncmp(papszHeaders[i], RT_STR_TUPLE("User-Agent:")) == 0)
1836 pThis->fHaveUserAgentHeader = true;
1837 }
1838
1839 pThis->pHeaders = pHeaders;
1840 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
1841 if (CURL_FAILURE(rcCurl))
1842 return VERR_INVALID_PARAMETER;
1843
1844 /*
1845 * Unset the user agent if it's in one of the headers.
1846 */
1847 if ( pThis->fHaveUserAgentHeader
1848 && pThis->fHaveSetUserAgent)
1849 {
1850 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL);
1851 Assert(CURL_SUCCESS(rcCurl));
1852 pThis->fHaveSetUserAgent = false;
1853 }
1854
1855 return VINF_SUCCESS;
1856}
1857
1858
1859/**
1860 * Set the CA file to NULL, deleting any temporary file if necessary.
1861 *
1862 * @param pThis The HTTP/HTTPS client instance.
1863 */
1864static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
1865{
1866 if (pThis->pszCaFile)
1867 {
1868 if (pThis->fDeleteCaFile)
1869 {
1870 int rc2 = RTFileDelete(pThis->pszCaFile);
1871 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
1872 }
1873 RTStrFree(pThis->pszCaFile);
1874 pThis->pszCaFile = NULL;
1875 }
1876}
1877
1878
1879RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
1880{
1881 PRTHTTPINTERNAL pThis = hHttp;
1882 RTHTTP_VALID_RETURN(pThis);
1883
1884 rtHttpUnsetCaFile(pThis);
1885
1886 pThis->fDeleteCaFile = false;
1887 if (pszCaFile)
1888 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
1889 return VINF_SUCCESS;
1890}
1891
1892
1893RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
1894{
1895 PRTHTTPINTERNAL pThis = hHttp;
1896 RTHTTP_VALID_RETURN(pThis);
1897
1898 /*
1899 * Create a temporary file.
1900 */
1901 int rc = VERR_NO_STR_MEMORY;
1902 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
1903 if (pszCaFile)
1904 {
1905 RTFILE hFile;
1906 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
1907 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1908 if (RT_SUCCESS(rc))
1909 {
1910 /*
1911 * Gather certificates into a temporary store and export them to the temporary file.
1912 */
1913 RTCRSTORE hStore;
1914 rc = RTCrStoreCreateInMem(&hStore, 256);
1915 if (RT_SUCCESS(rc))
1916 {
1917 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
1918 if (RT_SUCCESS(rc))
1919 /** @todo Consider adding an API for exporting to a RTFILE... */
1920 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
1921 RTCrStoreRelease(hStore);
1922 }
1923 RTFileClose(hFile);
1924 if (RT_SUCCESS(rc))
1925 {
1926 /*
1927 * Set the CA file for the instance.
1928 */
1929 rtHttpUnsetCaFile(pThis);
1930
1931 pThis->fDeleteCaFile = true;
1932 pThis->pszCaFile = pszCaFile;
1933 return VINF_SUCCESS;
1934 }
1935
1936 int rc2 = RTFileDelete(pszCaFile);
1937 AssertRC(rc2);
1938 }
1939 else
1940 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
1941
1942 RTStrFree(pszCaFile);
1943 }
1944 return rc;
1945}
1946
1947
1948RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
1949{
1950 uint32_t const cBefore = RTCrStoreCertCount(hStore);
1951 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
1952
1953 /*
1954 * Add the user store, quitely ignoring any errors.
1955 */
1956 RTCRSTORE hSrcStore;
1957 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1958 if (RT_SUCCESS(rcUser))
1959 {
1960 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1961 hSrcStore);
1962 RTCrStoreRelease(hSrcStore);
1963 }
1964
1965 /*
1966 * Ditto for the system store.
1967 */
1968 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1969 if (RT_SUCCESS(rcSystem))
1970 {
1971 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1972 hSrcStore);
1973 RTCrStoreRelease(hSrcStore);
1974 }
1975
1976 /*
1977 * If the number of certificates increased, we consider it a success.
1978 */
1979 if (RTCrStoreCertCount(hStore) > cBefore)
1980 {
1981 if (RT_FAILURE(rcSystem))
1982 return -rcSystem;
1983 if (RT_FAILURE(rcUser))
1984 return -rcUser;
1985 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
1986 }
1987
1988 if (RT_FAILURE(rcSystem))
1989 return rcSystem;
1990 if (RT_FAILURE(rcUser))
1991 return rcUser;
1992 return VERR_NOT_FOUND;
1993}
1994
1995
1996RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
1997{
1998 RTCRSTORE hStore;
1999 int rc = RTCrStoreCreateInMem(&hStore, 256);
2000 if (RT_SUCCESS(rc))
2001 {
2002 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2003 if (RT_SUCCESS(rc))
2004 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2005 RTCrStoreRelease(hStore);
2006 }
2007 return rc;
2008}
2009
2010
2011
2012/**
2013 * Figures out the IPRT status code for a GET.
2014 *
2015 * @returns IPRT status code.
2016 * @param pThis The HTTP/HTTPS client instance.
2017 * @param rcCurl What curl returned.
2018 */
2019static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl)
2020{
2021 int rc = VERR_HTTP_CURL_ERROR;
2022
2023 if (pThis->pszRedirLocation)
2024 {
2025 RTStrFree(pThis->pszRedirLocation);
2026 pThis->pszRedirLocation = NULL;
2027 }
2028 if (rcCurl == CURLE_OK)
2029 {
2030 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2031 switch (pThis->lLastResp)
2032 {
2033 case 200:
2034 /* OK, request was fulfilled */
2035 case 204:
2036 /* empty response */
2037 rc = VINF_SUCCESS;
2038 break;
2039 case 301:
2040 {
2041 const char *pszRedirect;
2042 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2043 size_t cb = strlen(pszRedirect);
2044 if (cb > 0 && cb < 2048)
2045 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2046 rc = VERR_HTTP_REDIRECTED;
2047 break;
2048 }
2049 case 400:
2050 /* bad request */
2051 rc = VERR_HTTP_BAD_REQUEST;
2052 break;
2053 case 403:
2054 /* forbidden, authorization will not help */
2055 rc = VERR_HTTP_ACCESS_DENIED;
2056 break;
2057 case 404:
2058 /* URL not found */
2059 rc = VERR_HTTP_NOT_FOUND;
2060 break;
2061 }
2062
2063 if (pThis->pszRedirLocation)
2064 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2065 else
2066 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2067 }
2068 else
2069 {
2070 switch (rcCurl)
2071 {
2072 case CURLE_URL_MALFORMAT:
2073 case CURLE_COULDNT_RESOLVE_HOST:
2074 rc = VERR_HTTP_NOT_FOUND;
2075 break;
2076 case CURLE_COULDNT_CONNECT:
2077 rc = VERR_HTTP_COULDNT_CONNECT;
2078 break;
2079 case CURLE_SSL_CONNECT_ERROR:
2080 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2081 break;
2082 case CURLE_SSL_CACERT:
2083 /* The peer certificate cannot be authenticated with the CA certificates
2084 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2085 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2086 break;
2087 case CURLE_SSL_CACERT_BADFILE:
2088 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2089 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2090 break;
2091 case CURLE_ABORTED_BY_CALLBACK:
2092 /* forcefully aborted */
2093 rc = VERR_HTTP_ABORTED;
2094 break;
2095 case CURLE_COULDNT_RESOLVE_PROXY:
2096 rc = VERR_HTTP_PROXY_NOT_FOUND;
2097 break;
2098 case CURLE_WRITE_ERROR:
2099 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2100 break;
2101 //case CURLE_READ_ERROR
2102
2103 default:
2104 break;
2105 }
2106 Log(("rtHttpGetCalcStatus: rc=%Rrc rcCurl=%u\n", rc, rcCurl));
2107 }
2108
2109 return rc;
2110}
2111
2112
2113/**
2114 * cURL callback for reporting progress, we use it for checking for abort.
2115 */
2116static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
2117{
2118 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2119 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2120
2121 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2122
2123 return pThis->fAbort ? 1 : 0;
2124}
2125
2126
2127/**
2128 * Whether we're likely to need SSL to handle the give URL.
2129 *
2130 * @returns true if we need, false if we probably don't.
2131 * @param pszUrl The URL.
2132 */
2133static bool rtHttpNeedSsl(const char *pszUrl)
2134{
2135 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2136}
2137
2138
2139/**
2140 * Applies recoded settings to the cURL instance before doing work.
2141 *
2142 * @returns IPRT status code.
2143 * @param pThis The HTTP/HTTPS client instance.
2144 * @param pszUrl The URL.
2145 */
2146static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2147{
2148 /*
2149 * The URL.
2150 */
2151 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2152 if (CURL_FAILURE(rcCurl))
2153 return VERR_INVALID_PARAMETER;
2154
2155 /*
2156 * Proxy config.
2157 */
2158 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2159 if (RT_FAILURE(rc))
2160 return rc;
2161
2162 /*
2163 * Setup SSL. Can be a bit of work.
2164 */
2165 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2166 if (CURL_FAILURE(rcCurl))
2167 return VERR_INVALID_PARAMETER;
2168
2169 const char *pszCaFile = pThis->pszCaFile;
2170 if ( !pszCaFile
2171 && rtHttpNeedSsl(pszUrl))
2172 {
2173 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2174 if (RT_SUCCESS(rc))
2175 pszCaFile = pThis->pszCaFile;
2176 else
2177 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2178 }
2179 if (pszCaFile)
2180 {
2181 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2182 if (CURL_FAILURE(rcCurl))
2183 return VERR_HTTP_CURL_ERROR;
2184 }
2185
2186 /*
2187 * Progress/abort.
2188 */
2189 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2190 if (CURL_FAILURE(rcCurl))
2191 return VERR_HTTP_CURL_ERROR;
2192 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2193 if (CURL_FAILURE(rcCurl))
2194 return VERR_HTTP_CURL_ERROR;
2195 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2196 if (CURL_FAILURE(rcCurl))
2197 return VERR_HTTP_CURL_ERROR;
2198
2199 /*
2200 * Set default user agent string if necessary. Some websites take offence
2201 * if we don't set it.
2202 */
2203 if ( !pThis->fHaveSetUserAgent
2204 && !pThis->fHaveUserAgentHeader)
2205 {
2206 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2207 if (CURL_FAILURE(rcCurl))
2208 return VERR_HTTP_CURL_ERROR;
2209 pThis->fHaveSetUserAgent = true;
2210 }
2211
2212 /*
2213 * Use GET by default.
2214 */
2215 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
2216 if (CURL_FAILURE(rcCurl))
2217 return VERR_HTTP_CURL_ERROR;
2218
2219 return VINF_SUCCESS;
2220}
2221
2222
2223/**
2224 * cURL callback for writing data.
2225 */
2226static size_t rtHttpWriteData(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2227{
2228 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2229
2230 /*
2231 * Do max size and overflow checks.
2232 */
2233 size_t const cbToAppend = cbUnit * cUnits;
2234 size_t const cbCurSize = pThis->Output.Mem.cb;
2235 size_t const cbNewSize = cbCurSize + cbToAppend;
2236 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2237 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2238 {
2239 if (cbNewSize + 1 <= pThis->Output.Mem.cbAllocated)
2240 {
2241 memcpy(&pThis->Output.Mem.pb[cbCurSize], pvBuf, cbToAppend);
2242 pThis->Output.Mem.cb = cbNewSize;
2243 pThis->Output.Mem.pb[cbNewSize] = '\0';
2244 return cbToAppend;
2245 }
2246
2247 /*
2248 * We need to reallocate the output buffer.
2249 */
2250 /** @todo this could do with a better strategy wrt growth. */
2251 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
2252 if ( cbAlloc <= pThis->cbDownloadHint
2253 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2254 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
2255
2256 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pThis->Output.Mem.pb, cbAlloc);
2257 if (pbNew)
2258 {
2259 memcpy(&pbNew[cbCurSize], pvBuf, cbToAppend);
2260 pbNew[cbNewSize] = '\0';
2261
2262 pThis->Output.Mem.cbAllocated = cbAlloc;
2263 pThis->Output.Mem.pb = pbNew;
2264 pThis->Output.Mem.cb = cbNewSize;
2265 return cbToAppend;
2266 }
2267
2268 pThis->rcOutput = VERR_NO_MEMORY;
2269 }
2270 else
2271 pThis->rcOutput = VERR_TOO_MUCH_DATA;
2272
2273 /*
2274 * Failure - abort.
2275 */
2276 RTMemFree(pThis->Output.Mem.pb);
2277 pThis->Output.Mem.pb = NULL;
2278 pThis->Output.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
2279 pThis->fAbort = true;
2280 return 0;
2281}
2282
2283
2284/**
2285 * Internal worker that performs a HTTP GET.
2286 *
2287 * @returns IPRT status code.
2288 * @param hHttp The HTTP/HTTPS client instance.
2289 * @param pszUrl The URL.
2290 * @param fNoBody Set to suppress the body.
2291 * @param ppvResponse Where to return the pointer to the allocated
2292 * response data (RTMemFree). There will always be
2293 * an zero terminator char after the response, that
2294 * is not part of the size returned via @a pcb.
2295 * @param pcb The size of the response data.
2296 *
2297 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
2298 * threads, because that will probably blow up!
2299 */
2300static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
2301{
2302 PRTHTTPINTERNAL pThis = hHttp;
2303 RTHTTP_VALID_RETURN(pThis);
2304
2305 /*
2306 * Reset the return values in case of more "GUI programming" on the client
2307 * side (i.e. a programming style not bothering checking return codes).
2308 */
2309 *ppvResponse = NULL;
2310 *pcb = 0;
2311
2312 /*
2313 * Set the busy flag (paranoia).
2314 */
2315 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2316 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2317
2318 /*
2319 * Reset the state and apply settings.
2320 */
2321 pThis->fAbort = false;
2322 pThis->rcOutput = VINF_SUCCESS;
2323 pThis->cbDownloadHint = 0;
2324
2325 int rc = rtHttpApplySettings(hHttp, pszUrl);
2326 if (RT_SUCCESS(rc))
2327 {
2328 RT_ZERO(pThis->Output.Mem);
2329 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteData);
2330 if (!CURL_FAILURE(rcCurl))
2331 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
2332 if (!CURL_FAILURE(rcCurl))
2333 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, fNoBody : 1L : 0L);
2334 if (!CURL_FAILURE(rcCurl))
2335 {
2336 /*
2337 * Perform the HTTP operation.
2338 */
2339 rcCurl = curl_easy_perform(pThis->pCurl);
2340 rc = rtHttpGetCalcStatus(pThis, rcCurl);
2341 if (RT_SUCCESS(rc))
2342 rc = pThis->rcOutput;
2343 if (RT_SUCCESS(rc))
2344 {
2345 *ppvResponse = pThis->Output.Mem.pb;
2346 *pcb = pThis->Output.Mem.cb;
2347 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n", pThis->Output.Mem.cb, pThis->Output.Mem.cbAllocated));
2348 }
2349 else if (pThis->Output.Mem.pb)
2350 RTMemFree(pThis->Output.Mem.pb);
2351 RT_ZERO(pThis->Output.Mem);
2352 }
2353 else
2354 rc = VERR_HTTP_CURL_ERROR;
2355 }
2356
2357 ASMAtomicWriteBool(&pThis->fBusy, false);
2358 return rc;
2359}
2360
2361
2362RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2363{
2364 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2365 uint8_t *pv;
2366 size_t cb;
2367 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
2368 if (RT_SUCCESS(rc))
2369 {
2370 if (pv) /* paranoia */
2371 *ppszNotUtf8 = (char *)pv;
2372 else
2373 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2374 }
2375 else
2376 *ppszNotUtf8 = NULL;
2377 return rc;
2378}
2379
2380
2381RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2382{
2383 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2384 uint8_t *pv;
2385 size_t cb;
2386 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
2387 if (RT_SUCCESS(rc))
2388 {
2389 if (pv) /* paranoia */
2390 *ppszNotUtf8 = (char *)pv;
2391 else
2392 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2393 }
2394 else
2395 *ppszNotUtf8 = NULL;
2396 return rc;
2397
2398}
2399
2400
2401RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
2402{
2403 RTMemFree(pszNotUtf8);
2404}
2405
2406
2407RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2408{
2409 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2410 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2411}
2412
2413
2414RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2415{
2416 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2417 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2418}
2419
2420
2421RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
2422{
2423 RTMemFree(pvResponse);
2424}
2425
2426
2427/**
2428 * cURL callback for writing data to a file.
2429 */
2430static size_t rtHttpWriteDataToFile(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2431{
2432 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2433 size_t cbWritten = 0;
2434 int rc = RTFileWrite(pThis->Output.hFile, pvBuf, cbUnit * cUnits, &cbWritten);
2435 if (RT_SUCCESS(rc))
2436 return cbWritten;
2437 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
2438 pThis->rcOutput = rc;
2439 return 0;
2440}
2441
2442
2443RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
2444{
2445 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
2446 PRTHTTPINTERNAL pThis = hHttp;
2447 RTHTTP_VALID_RETURN(pThis);
2448
2449 /*
2450 * Set the busy flag (paranoia).
2451 */
2452 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2453 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2454
2455 /*
2456 * Reset the state and apply settings.
2457 */
2458 pThis->fAbort = false;
2459 pThis->rcOutput = VINF_SUCCESS;
2460 pThis->cbDownloadHint = 0;
2461
2462 int rc = rtHttpApplySettings(hHttp, pszUrl);
2463 if (RT_SUCCESS(rc))
2464 {
2465 pThis->Output.hFile = NIL_RTFILE;
2466 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteDataToFile);
2467 if (!CURL_FAILURE(rcCurl))
2468 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
2469 if (!CURL_FAILURE(rcCurl))
2470 {
2471 /*
2472 * Open the output file.
2473 */
2474 rc = RTFileOpen(&pThis->Output.hFile, pszDstFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
2475 if (RT_SUCCESS(rc))
2476 {
2477 /*
2478 * Perform the HTTP operation.
2479 */
2480 rcCurl = curl_easy_perform(pThis->pCurl);
2481 rc = rtHttpGetCalcStatus(pThis, rcCurl);
2482 if (RT_SUCCESS(rc))
2483 rc = pThis->rcOutput;
2484
2485 int rc2 = RTFileClose(pThis->Output.hFile);
2486 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
2487 rc = rc2;
2488 }
2489 pThis->Output.hFile = NIL_RTFILE;
2490 }
2491 else
2492 rc = VERR_HTTP_CURL_ERROR;
2493 }
2494
2495 ASMAtomicWriteBool(&pThis->fBusy, false);
2496 return rc;
2497}
2498
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