VirtualBox

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

Last change on this file since 73334 was 73334, checked in by vboxsync, 6 years ago

IPRT: Added RTHttpSetFollowRedirects (translates to CURLOPT_FOLLOWLOCATION+CURLOPT_MAXREDIRS) and make use of it RTDbgCfg and RTHttp[.exe]. This should fix the current pdb download issues as we're getting 302 redirects from the servers. Also, changed RTHttp to return VERR_HTTP_REDIRECTED when receiving 302, 303, 307 & 308 HTTP statuses.

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