VirtualBox

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

Last change on this file since 72056 was 70141, checked in by vboxsync, 7 years ago

http-curl.cpp: rtHttpWinConfigureProxyForUrl - also assume no proxy if
we get ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT. This avoid assertion
in the debug build.

That error is what I get on my home network without automatic proxy,
not ERROR_WINHTTP_AUTODETECTION_FAILED. The latter is not listed as a
possible error for WinHttpGetProxyForUrl function(), but for
WinHttpDetectAutoProxyConfigUrl().

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