VirtualBox

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

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

Runtime/http-curl: added RTHttpSetDownloadProgressCallback()

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