VirtualBox

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

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

Runtime: build fix

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 86.3 KB
Line 
1/* $Id: http-curl.cpp 60056 2016-03-16 08:40:59Z 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 = false;
727 if (RT_SUCCESS(rc))
728 fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
729 RTMemTmpFree(pszNoProxyFree);
730 if (!fNoProxy)
731 {
732 /*
733 * Get the schema specific specific env var, falling back on the
734 * generic all_proxy if not found.
735 */
736 const char *apszEnvVars[4];
737 unsigned cEnvVars = 0;
738 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
739 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
740 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
741 {
742 apszEnvVars[cEnvVars++] = "https_proxy";
743 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
744 }
745 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
746 {
747 apszEnvVars[cEnvVars++] = "ftp_proxy";
748 apszEnvVars[cEnvVars++] = "FTP_PROXY";
749 }
750 else
751 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
752 apszEnvVars[cEnvVars++] = "all_proxy";
753 apszEnvVars[cEnvVars++] = "ALL_PROXY";
754
755 /*
756 * We try the env vars out and goes with the first one we can make sense out of.
757 * If we cannot make sense of any, we return the first unexpected rc we got.
758 */
759 rc = VINF_SUCCESS;
760 for (uint32_t i = 0; i < cEnvVars; i++)
761 {
762 size_t cchValue;
763 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
764 if (RT_SUCCESS(rc2))
765 {
766 if (cchValue != 0)
767 {
768 /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */
769 if (!strstr(szTmp, "://"))
770 {
771 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
772 memcpy(szTmp, RT_STR_TUPLE("http://"));
773 }
774
775 rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp);
776 if (RT_SUCCESS(rc2))
777 rc = rc2;
778 }
779 /*
780 * The variable is empty. Guess that means no proxying wanted.
781 */
782 else
783 {
784 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
785 break;
786 }
787 }
788 else
789 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
790 }
791 }
792 /*
793 * The host is the no-proxy list, it seems.
794 */
795 else
796 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
797
798 return rc;
799}
800
801#ifdef IPRT_USE_LIBPROXY
802
803/**
804 * @callback_method_impl{FNRTONCE,
805 * Attempts to load libproxy.so.1 and resolves APIs}
806 */
807static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser)
808{
809 RTLDRMOD hMod;
810 int rc = RTLdrLoad("/usr/lib/libproxy.so.1", &hMod);
811 if (RT_SUCCESS(rc))
812 {
813 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor);
814 if (RT_SUCCESS(rc))
815 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor);
816 if (RT_SUCCESS(rc))
817 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies);
818 if (RT_SUCCESS(rc))
819 g_hLdrLibProxy = hMod;
820 else
821 RTLdrClose(hMod);
822 AssertRC(rc);
823 }
824
825 NOREF(pvUser);
826 return rc;
827}
828
829/**
830 * Reconfigures the cURL proxy settings for the given URL, libproxy style.
831 *
832 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
833 * @param pThis The HTTP client instance.
834 * @param pszUrl The URL.
835 */
836static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
837{
838 int rcRet = VINF_NOT_SUPPORTED;
839
840 int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL);
841 if (RT_SUCCESS(rc))
842 {
843 /*
844 * Instance the factory and ask for a list of proxies.
845 */
846 PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor();
847 if (pFactory)
848 {
849 char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl);
850 g_pfnLibProxyFactoryDtor(pFactory);
851 if (papszProxies)
852 {
853 /*
854 * Look for something we can use.
855 */
856 for (unsigned i = 0; papszProxies[i]; i++)
857 {
858 if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0)
859 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
860 else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0
861 || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0
862 || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0
863 || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */
864 )
865 rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]);
866 else
867 continue;
868 if (rcRet != VINF_NOT_SUPPORTED)
869 break;
870 }
871
872 /* free the result. */
873 for (unsigned i = 0; papszProxies[i]; i++)
874 free(papszProxies[i]);
875 free(papszProxies);
876 }
877 }
878 }
879
880 return rcRet;
881}
882
883#endif /* IPRT_USE_LIBPROXY */
884
885#ifdef RT_OS_DARWIN
886
887/**
888 * Get a boolean like integer value from a dictionary.
889 *
890 * @returns true / false.
891 * @param hDict The dictionary.
892 * @param pvKey The dictionary value key.
893 */
894static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault)
895{
896 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey);
897 if (hNum)
898 {
899 int fEnabled;
900 if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled))
901 return fDefault;
902 return fEnabled != 0;
903 }
904 return fDefault;
905}
906
907
908/**
909 * Creates a CFURL object for an URL.
910 *
911 * @returns CFURL object reference.
912 * @param pszUrl The URL.
913 */
914static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl)
915{
916 CFURLRef hUrl = NULL;
917 CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8);
918 if (hStrUrl)
919 {
920 CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl,
921 NULL /*charactersToLeaveUnescaped*/,
922 NULL /*legalURLCharactersToBeEscaped*/,
923 kCFStringEncodingUTF8);
924 if (hStrUrlEscaped)
925 {
926 hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/);
927 Assert(hUrl);
928 CFRelease(hStrUrlEscaped);
929 }
930 else
931 AssertFailed();
932 CFRelease(hStrUrl);
933 }
934 else
935 AssertFailed();
936 return hUrl;
937}
938
939
940/**
941 * For passing results from rtHttpDarwinPacCallback to
942 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
943 */
944typedef struct RTHTTPDARWINPACRESULT
945{
946 CFArrayRef hArrayProxies;
947 CFErrorRef hError;
948} RTHTTPDARWINPACRESULT;
949typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT;
950
951/**
952 * Stupid callback for getting the result from
953 * CFNetworkExecuteProxyAutoConfigurationURL.
954 *
955 * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of
956 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
957 * @param hArrayProxies The result array.
958 * @param hError Errors, if any.
959 */
960static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError)
961{
962 PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser;
963
964 Assert(pResult->hArrayProxies == NULL);
965 if (hArrayProxies)
966 pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies);
967
968 Assert(pResult->hError == NULL);
969 if (hError)
970 pResult->hError = (CFErrorRef)CFRetain(hError);
971
972 CFRunLoopStop(CFRunLoopGetCurrent());
973}
974
975
976/**
977 * Executes a PAC script and returning the proxies it suggests.
978 *
979 * @returns Array of proxy configs (CFProxySupport.h style).
980 * @param pThis The HTTP client instance.
981 * @param hUrlTarget The URL we're about to use.
982 * @param hUrlScript The PAC script URL.
983 */
984static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(PRTHTTPINTERNAL pThis, CFURLRef hUrlTarget, CFURLRef hUrlScript)
985{
986 char szTmp[256];
987 if (LogIsFlowEnabled())
988 {
989 szTmp[0] = '\0';
990 CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
991 LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp));
992 }
993
994 /*
995 * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to
996 * download the script ourselves and mess around with too many CF APIs.
997 */
998 CFRunLoopRef hRunLoop = CFRunLoopGetCurrent();
999 AssertReturn(hRunLoop, NULL);
1000
1001 RTHTTPDARWINPACRESULT Result = { NULL, NULL };
1002 CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL };
1003 CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget,
1004 rtHttpDarwinPacCallback, &Ctx);
1005 AssertReturn(hRunLoopSrc, NULL);
1006
1007 CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool");
1008 CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode);
1009 CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */
1010 CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode);
1011
1012 /** @todo convert errors, maybe even fail. */
1013
1014 /*
1015 * Autoconfig (or missing wpad server) typically results in:
1016 * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2).
1017 *
1018 * In the autoconfig case, it looks like we're getting two entries, first
1019 * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to
1020 * be very upset if this fails, just continue trying alternatives.
1021 */
1022 if (Result.hError)
1023 {
1024 if (LogIsEnabled())
1025 {
1026 szTmp[0] = '\0';
1027 CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1028 Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp));
1029 }
1030 CFRelease(Result.hError);
1031 }
1032 return Result.hArrayProxies;
1033}
1034
1035
1036/**
1037 * Attempt to configure the proxy according to @a hDictProxy.
1038 *
1039 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1040 * the caller should try out alternative proxy configs and fallbacks.
1041 * @param pThis The HTTP client instance.
1042 * @param hDictProxy The proxy configuration (see CFProxySupport.h).
1043 * @param hUrlTarget The URL we're about to use.
1044 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1045 * javascript URL). This is set when we're processing
1046 * the output from a PAC script.
1047 */
1048static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType)
1049{
1050 CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey);
1051 AssertReturn(hStrProxyType, VINF_NOT_SUPPORTED);
1052
1053 /*
1054 * No proxy is fairly simple and common.
1055 */
1056 if (CFEqual(hStrProxyType, kCFProxyTypeNone))
1057 return rtHttpUpdateAutomaticProxyDisable(pThis);
1058
1059 /*
1060 * PAC URL means recursion, however we only do one level.
1061 */
1062 if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL))
1063 {
1064 AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED);
1065
1066 CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey);
1067 AssertReturn(hUrlScript, VINF_NOT_SUPPORTED);
1068
1069 int rcRet = VINF_NOT_SUPPORTED;
1070 CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(pThis, hUrlTarget, hUrlScript);
1071 if (hArray)
1072 {
1073 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1074 CFRelease(hArray);
1075 }
1076 return rcRet;
1077 }
1078
1079 /*
1080 * Determine the proxy type (not entirely sure about type == proxy type and
1081 * not scheme/protocol)...
1082 */
1083 curl_proxytype enmProxyType = CURLPROXY_HTTP;
1084 uint32_t uDefaultProxyPort = 8080;
1085 if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP)
1086 || CFEqual(hStrProxyType, kCFProxyTypeHTTPS))
1087 { /* defaults */ }
1088 else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS))
1089 {
1090 /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or
1091 * SOCK5 on the other side... Selecting SOCKS5 for now. */
1092 enmProxyType = CURLPROXY_SOCKS5;
1093 uDefaultProxyPort = 1080;
1094 }
1095 /* Unknown proxy type. */
1096 else
1097 return VINF_NOT_SUPPORTED;
1098
1099 /*
1100 * Extract the proxy configuration.
1101 */
1102 /* The proxy host name. */
1103 char szHostname[_1K];
1104 CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey);
1105 AssertReturn(hStr, VINF_NOT_SUPPORTED);
1106 AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1107
1108 /* Get the port number (optional). */
1109 SInt32 iProxyPort;
1110 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey);
1111 if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort))
1112 AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort);
1113 else
1114 iProxyPort = uDefaultProxyPort;
1115
1116 /* The proxy username. */
1117 char szUsername[256];
1118 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey);
1119 if (hStr)
1120 AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1121 else
1122 szUsername[0] = '\0';
1123
1124 /* The proxy password. */
1125 char szPassword[384];
1126 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey);
1127 if (hStr)
1128 AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1129 else
1130 szPassword[0] = '\0';
1131
1132 /*
1133 * Apply the proxy config.
1134 */
1135 return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort,
1136 szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL);
1137}
1138
1139
1140/**
1141 * Try do proxy config for our HTTP client instance given an array of proxies.
1142 *
1143 * This is used with the output from a CFProxySupport.h API.
1144 *
1145 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1146 * we might want to try out fallbacks.
1147 * @param pThis The HTTP client instance.
1148 * @param hArrayProxies The proxies CFPRoxySupport have given us.
1149 * @param hUrlTarget The URL we're about to use.
1150 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1151 * javascript URL). This is set when we're processing
1152 * the output from a PAC script.
1153 */
1154static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType)
1155{
1156 int rcRet = VINF_NOT_SUPPORTED;
1157 CFIndex const cEntries = CFArrayGetCount(hArrayProxies);
1158 LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries));
1159 for (CFIndex i = 0; i < cEntries; i++)
1160 {
1161 CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i);
1162 AssertContinue(hDictProxy);
1163
1164 rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType);
1165 if (rcRet != VINF_NOT_SUPPORTED)
1166 break;
1167 }
1168 return rcRet;
1169}
1170
1171
1172/**
1173 * Inner worker for rtHttpWinConfigureProxyForUrl.
1174 *
1175 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1176 * @param pThis The HTTP client instance.
1177 * @param pszUrl The URL.
1178 */
1179static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies,
1180 const char *pszUrl, PRTURIPARSED pParsed, const char *pszHost)
1181{
1182 CFArrayRef hArray;
1183
1184 /*
1185 * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply
1186 * proxy exclusion rules (tested on 10.9). So, do that manually.
1187 */
1188 RTNETADDRU HostAddr;
1189 int fIsHostIpv4Address = -1;
1190 char szTmp[_4K];
1191
1192 /* If we've got a simple hostname, something containing no dots, we must check
1193 whether such simple hostnames are excluded from proxying by default or not. */
1194 if (strchr(pszHost, '.') == NULL)
1195 {
1196 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false))
1197 return rtHttpUpdateAutomaticProxyDisable(pThis);
1198 fIsHostIpv4Address = false;
1199 }
1200
1201 /* Consult the exclusion list. This is an array of strings.
1202 This is very similar to what we do on windows. */
1203 hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList);
1204 if (hArray)
1205 {
1206 CFIndex const cEntries = CFArrayGetCount(hArray);
1207 for (CFIndex i = 0; i < cEntries; i++)
1208 {
1209 CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i);
1210 AssertContinue(hStr);
1211 AssertContinue(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8));
1212 RTStrToLower(szTmp);
1213
1214 bool fRet;
1215 if ( strchr(szTmp, '*')
1216 || strchr(szTmp, '?'))
1217 fRet = RTStrSimplePatternMatch(szTmp, pszHost);
1218 else
1219 {
1220 if (fIsHostIpv4Address == -1)
1221 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1222 RTNETADDRIPV4 Network, Netmask;
1223 if ( fIsHostIpv4Address
1224 && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) )
1225 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1226 else
1227 fRet = strcmp(szTmp, pszHost) == 0;
1228 }
1229 if (fRet)
1230 return rtHttpUpdateAutomaticProxyDisable(pThis);
1231 }
1232 }
1233
1234#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */
1235 /*
1236 * Is proxy auto config (PAC) enabled? If so, we must consult it first.
1237 */
1238 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false))
1239 {
1240 /* Convert the auto config url string to a CFURL object. */
1241 CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString);
1242 if (hStrAutoConfigUrl)
1243 {
1244 if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8))
1245 {
1246 CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp);
1247 if (hUrlScript)
1248 {
1249 int rcRet = VINF_NOT_SUPPORTED;
1250 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1251 if (hUrlTarget)
1252 {
1253 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1254 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1255 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1256 if (hArray)
1257 CFRelease(hArray);
1258
1259 hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(pThis, hUrlTarget, hUrlScript);
1260 if (hArray)
1261 {
1262 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1263 CFRelease(hArray);
1264 }
1265 }
1266 CFRelease(hUrlScript);
1267 if (rcRet != VINF_NOT_SUPPORTED)
1268 return rcRet;
1269 }
1270 }
1271 }
1272 }
1273
1274 /*
1275 * Try static proxy configs.
1276 */
1277 /** @todo later if needed. */
1278 return VERR_NOT_SUPPORTED;
1279
1280#else
1281 /*
1282 * Simple solution - "just" use CFNetworkCopyProxiesForURL.
1283 */
1284 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1285 AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR);
1286 int rcRet = VINF_NOT_SUPPORTED;
1287
1288 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1289 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1290 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1291 if (hArray)
1292 CFRelease(hArray);
1293
1294 /* The actual run. */
1295 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies);
1296 if (hArray)
1297 {
1298 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/);
1299 CFRelease(hArray);
1300 }
1301 CFRelease(hUrlTarget);
1302
1303 return rcRet;
1304#endif
1305}
1306
1307/**
1308 * Reconfigures the cURL proxy settings for the given URL, OS X style.
1309 *
1310 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1311 * @param pThis The HTTP client instance.
1312 * @param pszUrl The URL.
1313 */
1314static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1315{
1316 /*
1317 * Parse the URL, if there isn't any host name (like for file:///xxx.txt)
1318 * we don't need to run thru proxy settings to know what to do.
1319 */
1320 RTURIPARSED Parsed;
1321 int rc = RTUriParse(pszUrl, &Parsed);
1322 AssertRCReturn(rc, false);
1323 if (Parsed.cchAuthorityHost == 0)
1324 return rtHttpUpdateAutomaticProxyDisable(pThis);
1325 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1326 AssertReturn(pszHost, VERR_NO_STR_MEMORY);
1327 RTStrToLower(pszHost);
1328
1329 /*
1330 * Get a copy of the proxy settings (10.6 API).
1331 */
1332 CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */
1333 if (hDictProxies)
1334 rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, &Parsed, pszHost);
1335 else
1336 rc = VINF_NOT_SUPPORTED;
1337 CFRelease(hDictProxies);
1338
1339 RTStrFree(pszHost);
1340 return rc;
1341}
1342
1343#endif /* RT_OS_DARWIN */
1344
1345#ifdef RT_OS_WINDOWS
1346
1347/**
1348 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
1349 */
1350static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
1351{
1352 /*
1353 * winhttp.dll is not present on NT4 and probably was first introduced with XP.
1354 */
1355 RTLDRMOD hMod;
1356 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
1357 if (RT_SUCCESS(rc))
1358 {
1359 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
1360 if (RT_SUCCESS(rc))
1361 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
1362 if (RT_SUCCESS(rc))
1363 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
1364 if (RT_SUCCESS(rc))
1365 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
1366 if (RT_SUCCESS(rc))
1367 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
1368 RTLdrClose(hMod);
1369 AssertRC(rc);
1370 }
1371 else
1372 AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc));
1373
1374 NOREF(pvUser);
1375 return rc;
1376}
1377
1378
1379/**
1380 * Matches the URL against the given Windows by-pass list.
1381 *
1382 * @returns true if we should by-pass the proxy for this URL, false if not.
1383 * @param pszUrl The URL.
1384 * @param pwszBypass The Windows by-pass list.
1385 */
1386static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass)
1387{
1388 /*
1389 * Don't bother parsing the URL if we've actually got nothing to work with
1390 * in the by-pass list.
1391 */
1392 if (!pwszBypass)
1393 return false;
1394
1395 RTUTF16 wc;
1396 while ( (wc = *pwszBypass) != '\0'
1397 && ( RTUniCpIsSpace(wc)
1398 || wc == ';') )
1399 pwszBypass++;
1400 if (wc == '\0')
1401 return false;
1402
1403 /*
1404 * We now need to parse the URL and extract the host name.
1405 */
1406 RTURIPARSED Parsed;
1407 int rc = RTUriParse(pszUrl, &Parsed);
1408 AssertRCReturn(rc, false);
1409 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1410 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
1411 return false;
1412 RTStrToLower(pszHost);
1413
1414 bool fRet = false;
1415 char *pszBypassFree;
1416 rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree);
1417 if (RT_SUCCESS(rc))
1418 {
1419 /*
1420 * Walk the by-pass list.
1421 *
1422 * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx
1423 * a by-pass list is semicolon delimited list. The entries are either host
1424 * names or IP addresses, and may use wildcard ('*', '?', I guess). There
1425 * special "<local>" entry matches anything without a dot.
1426 */
1427 RTNETADDRU HostAddr;
1428 int fIsHostIpv4Address = -1;
1429 char *pszEntry = pszBypassFree;
1430 while (*pszEntry != '\0')
1431 {
1432 /*
1433 * Find end of entry.
1434 */
1435 char ch;
1436 size_t cchEntry = 1;
1437 while ( (ch = pszEntry[cchEntry]) != '\0'
1438 && ch != ';'
1439 && !RT_C_IS_SPACE(ch))
1440 cchEntry++;
1441
1442 char chSaved = pszEntry[cchEntry];
1443 pszEntry[cchEntry] = '\0';
1444 RTStrToLower(pszEntry);
1445
1446 if ( cchEntry == sizeof("<local>") - 1
1447 && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0)
1448 fRet = strchr(pszHost, '.') == NULL;
1449 else if ( memchr(pszEntry, '*', cchEntry) != NULL
1450 || memchr(pszEntry, '?', cchEntry) != NULL)
1451 fRet = RTStrSimplePatternMatch(pszEntry, pszHost);
1452 else
1453 {
1454 if (fIsHostIpv4Address == -1)
1455 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1456 RTNETADDRIPV4 Network, Netmask;
1457 if ( fIsHostIpv4Address
1458 && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) )
1459 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1460 else
1461 fRet = strcmp(pszEntry, pszHost) == 0;
1462 }
1463
1464 pszEntry[cchEntry] = chSaved;
1465 if (fRet)
1466 break;
1467
1468 /*
1469 * Next entry.
1470 */
1471 pszEntry += cchEntry;
1472 while ( (ch = *pszEntry) != '\0'
1473 && ( ch == ';'
1474 || RT_C_IS_SPACE(ch)) )
1475 pszEntry++;
1476 }
1477
1478 RTStrFree(pszBypassFree);
1479 }
1480
1481 RTStrFree(pszHost);
1482 return false;
1483}
1484
1485
1486/**
1487 * Searches a Windows proxy server list for the best fitting proxy to use, then
1488 * reconfigures the HTTP client instance to use it.
1489 *
1490 * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback.
1491 * @param pThis The HTTP client instance.
1492 * @param pszUrl The URL needing proxying.
1493 * @param pwszProxies The list of proxy servers to choose from.
1494 */
1495static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies)
1496{
1497 /*
1498 * Fend off empty strings (very unlikely, but just in case).
1499 */
1500 if (!pwszProxies)
1501 return VINF_NOT_SUPPORTED;
1502
1503 RTUTF16 wc;
1504 while ( (wc = *pwszProxies) != '\0'
1505 && ( RTUniCpIsSpace(wc)
1506 || wc == ';') )
1507 pwszProxies++;
1508 if (wc == '\0')
1509 return VINF_NOT_SUPPORTED;
1510
1511 /*
1512 * We now need to parse the URL and extract the scheme.
1513 */
1514 RTURIPARSED Parsed;
1515 int rc = RTUriParse(pszUrl, &Parsed);
1516 AssertRCReturn(rc, false);
1517 char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed);
1518 AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY);
1519 size_t const cchUrlScheme = strlen(pszUrlScheme);
1520
1521 int rcRet = VINF_NOT_SUPPORTED;
1522 char *pszProxiesFree;
1523 rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree);
1524 if (RT_SUCCESS(rc))
1525 {
1526 /*
1527 * Walk the server list.
1528 *
1529 * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx
1530 * this is also a semicolon delimited list. The entries are on the form:
1531 * [<scheme>=][<scheme>"://"]<server>[":"<port>]
1532 */
1533 bool fBestEntryHasSameScheme = false;
1534 const char *pszBestEntry = NULL;
1535 char *pszEntry = pszProxiesFree;
1536 while (*pszEntry != '\0')
1537 {
1538 /*
1539 * Find end of entry. We include spaces here in addition to ';'.
1540 */
1541 char ch;
1542 size_t cchEntry = 1;
1543 while ( (ch = pszEntry[cchEntry]) != '\0'
1544 && ch != ';'
1545 && !RT_C_IS_SPACE(ch))
1546 cchEntry++;
1547
1548 char const chSaved = pszEntry[cchEntry];
1549 pszEntry[cchEntry] = '\0';
1550
1551 /* Parse the entry. */
1552 const char *pszEndOfScheme = strstr(pszEntry, "://");
1553 const char *pszEqual = (const char *)memchr(pszEntry, '=',
1554 pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry);
1555 if (pszEqual)
1556 {
1557 if ( pszEqual - pszEntry == cchUrlScheme
1558 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0)
1559 {
1560 pszBestEntry = pszEqual + 1;
1561 break;
1562 }
1563 }
1564 else
1565 {
1566 bool fSchemeMatch = pszEndOfScheme
1567 && pszEndOfScheme - pszEntry == cchUrlScheme
1568 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0;
1569 if ( !pszBestEntry
1570 || ( !fBestEntryHasSameScheme
1571 && fSchemeMatch) )
1572 {
1573 pszBestEntry = pszEntry;
1574 fBestEntryHasSameScheme = fSchemeMatch;
1575 }
1576 }
1577
1578 /*
1579 * Next entry.
1580 */
1581 if (!chSaved)
1582 break;
1583 pszEntry += cchEntry + 1;
1584 while ( (ch = *pszEntry) != '\0'
1585 && ( ch == ';'
1586 || RT_C_IS_SPACE(ch)) )
1587 pszEntry++;
1588 }
1589
1590 /*
1591 * If we found something, try use it.
1592 */
1593 if (pszBestEntry)
1594 rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry);
1595
1596 RTStrFree(pszProxiesFree);
1597 }
1598
1599 RTStrFree(pszUrlScheme);
1600 return rc;
1601}
1602
1603
1604/**
1605 * Reconfigures the cURL proxy settings for the given URL, Windows style.
1606 *
1607 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1608 * @param pThis The HTTP client instance.
1609 * @param pszUrl The URL.
1610 */
1611static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1612{
1613 int rcRet = VINF_NOT_SUPPORTED;
1614
1615 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
1616 if (RT_SUCCESS(rc))
1617 {
1618 /*
1619 * Try get some proxy info for the URL. We first try getting the IE
1620 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
1621 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
1622 */
1623 WINHTTP_PROXY_INFO ProxyInfo;
1624 PRTUTF16 pwszProxy = NULL;
1625 PRTUTF16 pwszNoProxy = NULL;
1626 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
1627 RT_ZERO(AutoProxyOptions);
1628 RT_ZERO(ProxyInfo);
1629
1630 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
1631 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
1632 {
1633 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1634 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
1635 if (IeProxyConfig.fAutoDetect)
1636 {
1637 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
1638 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
1639 }
1640 else if (AutoProxyOptions.lpszAutoConfigUrl)
1641 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1642 else if (ProxyInfo.lpszProxy)
1643 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
1644 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
1645 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
1646 }
1647 else
1648 {
1649 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
1650 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
1651 {
1652 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
1653 RT_ZERO(ProxyInfo);
1654 }
1655 }
1656
1657 /*
1658 * Should we try WinHttGetProxyForUrl?
1659 */
1660 if (AutoProxyOptions.dwFlags != 0)
1661 {
1662 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
1663 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
1664 if (hSession != NULL)
1665 {
1666 PRTUTF16 pwszUrl;
1667 rc = RTStrToUtf16(pszUrl, &pwszUrl);
1668 if (RT_SUCCESS(rc))
1669 {
1670 /*
1671 * Try autodetect first, then fall back on the config URL if there is one.
1672 *
1673 * Also, we first try without auto authentication, then with. This will according
1674 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
1675 * caching the result when it's processed out-of-process (seems default here on W10).
1676 */
1677 WINHTTP_PROXY_INFO TmpProxyInfo;
1678 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1679 if ( !fRc
1680 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1681 {
1682 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1683 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1684 }
1685
1686 if ( !fRc
1687 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
1688 && AutoProxyOptions.lpszAutoConfigUrl)
1689 {
1690 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1691 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1692 AutoProxyOptions.dwAutoDetectFlags = 0;
1693 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1694 if ( !fRc
1695 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1696 {
1697 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1698 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1699 }
1700 }
1701
1702 if (fRc)
1703 {
1704 if (ProxyInfo.lpszProxy)
1705 GlobalFree(ProxyInfo.lpszProxy);
1706 if (ProxyInfo.lpszProxyBypass)
1707 GlobalFree(ProxyInfo.lpszProxyBypass);
1708 ProxyInfo = TmpProxyInfo;
1709 }
1710 /*
1711 * If the autodetection failed, assume no proxy.
1712 */
1713 else
1714 {
1715 DWORD dwErr = GetLastError();
1716 if (dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED)
1717 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1718 else
1719 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl -> %u\n", dwErr));
1720 }
1721 RTUtf16Free(pwszUrl);
1722 }
1723 else
1724 {
1725 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
1726 rcRet = rc;
1727 }
1728 g_pfnWinHttpCloseHandle(hSession);
1729 }
1730 else
1731 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
1732 }
1733
1734 /*
1735 * Try use the proxy info we've found.
1736 */
1737 switch (ProxyInfo.dwAccessType)
1738 {
1739 case WINHTTP_ACCESS_TYPE_NO_PROXY:
1740 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1741 break;
1742
1743 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
1744 if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass))
1745 rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy);
1746 else
1747 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1748 break;
1749
1750 case 0:
1751 break;
1752
1753 default:
1754 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
1755 }
1756
1757 /*
1758 * Cleanup.
1759 */
1760 if (ProxyInfo.lpszProxy)
1761 GlobalFree(ProxyInfo.lpszProxy);
1762 if (ProxyInfo.lpszProxyBypass)
1763 GlobalFree(ProxyInfo.lpszProxyBypass);
1764 if (AutoProxyOptions.lpszAutoConfigUrl)
1765 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
1766 }
1767
1768 return rcRet;
1769}
1770
1771#endif /* RT_OS_WINDOWS */
1772
1773
1774static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1775{
1776 if (pThis->fUseSystemProxySettings)
1777 {
1778#ifdef IPRT_USE_LIBPROXY
1779 int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl);
1780 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1781 return rc;
1782 Assert(rc == VINF_NOT_SUPPORTED);
1783#endif
1784#ifdef RT_OS_DARWIN
1785 int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl);
1786 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1787 return rc;
1788 Assert(rc == VINF_NOT_SUPPORTED);
1789#endif
1790#ifdef RT_OS_WINDOWS
1791 int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl);
1792 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1793 return rc;
1794 Assert(rc == VINF_NOT_SUPPORTED);
1795#endif
1796/** @todo system specific class here, fall back on env vars if necessary. */
1797 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
1798 }
1799
1800 return VINF_SUCCESS;
1801}
1802
1803
1804RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
1805 const char *pcszProxyUser, const char *pcszProxyPwd)
1806{
1807 PRTHTTPINTERNAL pThis = hHttp;
1808 RTHTTP_VALID_RETURN(pThis);
1809 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
1810 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
1811
1812 /*
1813 * Update the settings.
1814 *
1815 * Currently, we don't make alot of effort parsing or checking the input, we
1816 * leave that to cURL. (A bit afraid of breaking user settings.)
1817 */
1818 pThis->fUseSystemProxySettings = false;
1819 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
1820}
1821
1822
1823RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
1824{
1825 PRTHTTPINTERNAL pThis = hHttp;
1826 RTHTTP_VALID_RETURN(pThis);
1827
1828 pThis->fHaveUserAgentHeader = false;
1829 if (!cHeaders)
1830 {
1831 if (pThis->pHeaders)
1832 curl_slist_free_all(pThis->pHeaders);
1833 pThis->pHeaders = 0;
1834 return VINF_SUCCESS;
1835 }
1836
1837 struct curl_slist *pHeaders = NULL;
1838 for (size_t i = 0; i < cHeaders; i++)
1839 {
1840 pHeaders = curl_slist_append(pHeaders, papszHeaders[i]);
1841 if (strncmp(papszHeaders[i], RT_STR_TUPLE("User-Agent:")) == 0)
1842 pThis->fHaveUserAgentHeader = true;
1843 }
1844
1845 pThis->pHeaders = pHeaders;
1846 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
1847 if (CURL_FAILURE(rcCurl))
1848 return VERR_INVALID_PARAMETER;
1849
1850 /*
1851 * Unset the user agent if it's in one of the headers.
1852 */
1853 if ( pThis->fHaveUserAgentHeader
1854 && pThis->fHaveSetUserAgent)
1855 {
1856 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL);
1857 Assert(CURL_SUCCESS(rcCurl));
1858 pThis->fHaveSetUserAgent = false;
1859 }
1860
1861 return VINF_SUCCESS;
1862}
1863
1864
1865/**
1866 * Set the CA file to NULL, deleting any temporary file if necessary.
1867 *
1868 * @param pThis The HTTP/HTTPS client instance.
1869 */
1870static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
1871{
1872 if (pThis->pszCaFile)
1873 {
1874 if (pThis->fDeleteCaFile)
1875 {
1876 int rc2 = RTFileDelete(pThis->pszCaFile);
1877 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
1878 }
1879 RTStrFree(pThis->pszCaFile);
1880 pThis->pszCaFile = NULL;
1881 }
1882}
1883
1884
1885RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
1886{
1887 PRTHTTPINTERNAL pThis = hHttp;
1888 RTHTTP_VALID_RETURN(pThis);
1889
1890 rtHttpUnsetCaFile(pThis);
1891
1892 pThis->fDeleteCaFile = false;
1893 if (pszCaFile)
1894 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
1895 return VINF_SUCCESS;
1896}
1897
1898
1899RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
1900{
1901 PRTHTTPINTERNAL pThis = hHttp;
1902 RTHTTP_VALID_RETURN(pThis);
1903
1904 /*
1905 * Create a temporary file.
1906 */
1907 int rc = VERR_NO_STR_MEMORY;
1908 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
1909 if (pszCaFile)
1910 {
1911 RTFILE hFile;
1912 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
1913 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1914 if (RT_SUCCESS(rc))
1915 {
1916 /*
1917 * Gather certificates into a temporary store and export them to the temporary file.
1918 */
1919 RTCRSTORE hStore;
1920 rc = RTCrStoreCreateInMem(&hStore, 256);
1921 if (RT_SUCCESS(rc))
1922 {
1923 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
1924 if (RT_SUCCESS(rc))
1925 /** @todo Consider adding an API for exporting to a RTFILE... */
1926 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
1927 RTCrStoreRelease(hStore);
1928 }
1929 RTFileClose(hFile);
1930 if (RT_SUCCESS(rc))
1931 {
1932 /*
1933 * Set the CA file for the instance.
1934 */
1935 rtHttpUnsetCaFile(pThis);
1936
1937 pThis->fDeleteCaFile = true;
1938 pThis->pszCaFile = pszCaFile;
1939 return VINF_SUCCESS;
1940 }
1941
1942 int rc2 = RTFileDelete(pszCaFile);
1943 AssertRC(rc2);
1944 }
1945 else
1946 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
1947
1948 RTStrFree(pszCaFile);
1949 }
1950 return rc;
1951}
1952
1953
1954RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
1955{
1956 uint32_t const cBefore = RTCrStoreCertCount(hStore);
1957 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
1958
1959 /*
1960 * Add the user store, quitely ignoring any errors.
1961 */
1962 RTCRSTORE hSrcStore;
1963 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1964 if (RT_SUCCESS(rcUser))
1965 {
1966 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1967 hSrcStore);
1968 RTCrStoreRelease(hSrcStore);
1969 }
1970
1971 /*
1972 * Ditto for the system store.
1973 */
1974 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
1975 if (RT_SUCCESS(rcSystem))
1976 {
1977 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
1978 hSrcStore);
1979 RTCrStoreRelease(hSrcStore);
1980 }
1981
1982 /*
1983 * If the number of certificates increased, we consider it a success.
1984 */
1985 if (RTCrStoreCertCount(hStore) > cBefore)
1986 {
1987 if (RT_FAILURE(rcSystem))
1988 return -rcSystem;
1989 if (RT_FAILURE(rcUser))
1990 return -rcUser;
1991 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
1992 }
1993
1994 if (RT_FAILURE(rcSystem))
1995 return rcSystem;
1996 if (RT_FAILURE(rcUser))
1997 return rcUser;
1998 return VERR_NOT_FOUND;
1999}
2000
2001
2002RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
2003{
2004 RTCRSTORE hStore;
2005 int rc = RTCrStoreCreateInMem(&hStore, 256);
2006 if (RT_SUCCESS(rc))
2007 {
2008 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2009 if (RT_SUCCESS(rc))
2010 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2011 RTCrStoreRelease(hStore);
2012 }
2013 return rc;
2014}
2015
2016
2017
2018/**
2019 * Figures out the IPRT status code for a GET.
2020 *
2021 * @returns IPRT status code.
2022 * @param pThis The HTTP/HTTPS client instance.
2023 * @param rcCurl What curl returned.
2024 */
2025static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl)
2026{
2027 int rc = VERR_HTTP_CURL_ERROR;
2028
2029 if (pThis->pszRedirLocation)
2030 {
2031 RTStrFree(pThis->pszRedirLocation);
2032 pThis->pszRedirLocation = NULL;
2033 }
2034 if (rcCurl == CURLE_OK)
2035 {
2036 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2037 switch (pThis->lLastResp)
2038 {
2039 case 200:
2040 /* OK, request was fulfilled */
2041 case 204:
2042 /* empty response */
2043 rc = VINF_SUCCESS;
2044 break;
2045 case 301:
2046 {
2047 const char *pszRedirect;
2048 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2049 size_t cb = strlen(pszRedirect);
2050 if (cb > 0 && cb < 2048)
2051 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2052 rc = VERR_HTTP_REDIRECTED;
2053 break;
2054 }
2055 case 400:
2056 /* bad request */
2057 rc = VERR_HTTP_BAD_REQUEST;
2058 break;
2059 case 403:
2060 /* forbidden, authorization will not help */
2061 rc = VERR_HTTP_ACCESS_DENIED;
2062 break;
2063 case 404:
2064 /* URL not found */
2065 rc = VERR_HTTP_NOT_FOUND;
2066 break;
2067 }
2068
2069 if (pThis->pszRedirLocation)
2070 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2071 else
2072 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2073 }
2074 else
2075 {
2076 switch (rcCurl)
2077 {
2078 case CURLE_URL_MALFORMAT:
2079 case CURLE_COULDNT_RESOLVE_HOST:
2080 rc = VERR_HTTP_HOST_NOT_FOUND;
2081 break;
2082 case CURLE_COULDNT_CONNECT:
2083 rc = VERR_HTTP_COULDNT_CONNECT;
2084 break;
2085 case CURLE_SSL_CONNECT_ERROR:
2086 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2087 break;
2088 case CURLE_SSL_CACERT:
2089 /* The peer certificate cannot be authenticated with the CA certificates
2090 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2091 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2092 break;
2093 case CURLE_SSL_CACERT_BADFILE:
2094 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2095 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2096 break;
2097 case CURLE_ABORTED_BY_CALLBACK:
2098 /* forcefully aborted */
2099 rc = VERR_HTTP_ABORTED;
2100 break;
2101 case CURLE_COULDNT_RESOLVE_PROXY:
2102 rc = VERR_HTTP_PROXY_NOT_FOUND;
2103 break;
2104 case CURLE_WRITE_ERROR:
2105 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2106 break;
2107 //case CURLE_READ_ERROR
2108
2109 default:
2110 break;
2111 }
2112 Log(("rtHttpGetCalcStatus: rc=%Rrc rcCurl=%u\n", rc, rcCurl));
2113 }
2114
2115 return rc;
2116}
2117
2118
2119/**
2120 * cURL callback for reporting progress, we use it for checking for abort.
2121 */
2122static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
2123{
2124 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2125 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2126
2127 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2128
2129 if (pThis->pfnDownloadProgress)
2130 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2131
2132 return pThis->fAbort ? 1 : 0;
2133}
2134
2135
2136/**
2137 * Whether we're likely to need SSL to handle the give URL.
2138 *
2139 * @returns true if we need, false if we probably don't.
2140 * @param pszUrl The URL.
2141 */
2142static bool rtHttpNeedSsl(const char *pszUrl)
2143{
2144 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2145}
2146
2147
2148/**
2149 * Applies recoded settings to the cURL instance before doing work.
2150 *
2151 * @returns IPRT status code.
2152 * @param pThis The HTTP/HTTPS client instance.
2153 * @param pszUrl The URL.
2154 */
2155static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2156{
2157 /*
2158 * The URL.
2159 */
2160 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2161 if (CURL_FAILURE(rcCurl))
2162 return VERR_INVALID_PARAMETER;
2163
2164 /*
2165 * Proxy config.
2166 */
2167 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2168 if (RT_FAILURE(rc))
2169 return rc;
2170
2171 /*
2172 * Setup SSL. Can be a bit of work.
2173 */
2174 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2175 if (CURL_FAILURE(rcCurl))
2176 return VERR_INVALID_PARAMETER;
2177
2178 const char *pszCaFile = pThis->pszCaFile;
2179 if ( !pszCaFile
2180 && rtHttpNeedSsl(pszUrl))
2181 {
2182 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2183 if (RT_SUCCESS(rc))
2184 pszCaFile = pThis->pszCaFile;
2185 else
2186 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2187 }
2188 if (pszCaFile)
2189 {
2190 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2191 if (CURL_FAILURE(rcCurl))
2192 return VERR_HTTP_CURL_ERROR;
2193 }
2194
2195 /*
2196 * Progress/abort.
2197 */
2198 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2199 if (CURL_FAILURE(rcCurl))
2200 return VERR_HTTP_CURL_ERROR;
2201 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2202 if (CURL_FAILURE(rcCurl))
2203 return VERR_HTTP_CURL_ERROR;
2204 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2205 if (CURL_FAILURE(rcCurl))
2206 return VERR_HTTP_CURL_ERROR;
2207
2208 /*
2209 * Set default user agent string if necessary. Some websites take offence
2210 * if we don't set it.
2211 */
2212 if ( !pThis->fHaveSetUserAgent
2213 && !pThis->fHaveUserAgentHeader)
2214 {
2215 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2216 if (CURL_FAILURE(rcCurl))
2217 return VERR_HTTP_CURL_ERROR;
2218 pThis->fHaveSetUserAgent = true;
2219 }
2220
2221 /*
2222 * Use GET by default.
2223 */
2224 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
2225 if (CURL_FAILURE(rcCurl))
2226 return VERR_HTTP_CURL_ERROR;
2227 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
2228 if (CURL_FAILURE(rcCurl))
2229 return VERR_HTTP_CURL_ERROR;
2230
2231 return VINF_SUCCESS;
2232}
2233
2234
2235/**
2236 * cURL callback for writing data.
2237 */
2238static size_t rtHttpWriteData(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2239{
2240 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2241
2242 /*
2243 * Do max size and overflow checks.
2244 */
2245 size_t const cbToAppend = cbUnit * cUnits;
2246 size_t const cbCurSize = pThis->Output.Mem.cb;
2247 size_t const cbNewSize = cbCurSize + cbToAppend;
2248 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2249 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2250 {
2251 if (cbNewSize + 1 <= pThis->Output.Mem.cbAllocated)
2252 {
2253 memcpy(&pThis->Output.Mem.pb[cbCurSize], pvBuf, cbToAppend);
2254 pThis->Output.Mem.cb = cbNewSize;
2255 pThis->Output.Mem.pb[cbNewSize] = '\0';
2256 return cbToAppend;
2257 }
2258
2259 /*
2260 * We need to reallocate the output buffer.
2261 */
2262 /** @todo this could do with a better strategy wrt growth. */
2263 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
2264 if ( cbAlloc <= pThis->cbDownloadHint
2265 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2266 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
2267
2268 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pThis->Output.Mem.pb, cbAlloc);
2269 if (pbNew)
2270 {
2271 memcpy(&pbNew[cbCurSize], pvBuf, cbToAppend);
2272 pbNew[cbNewSize] = '\0';
2273
2274 pThis->Output.Mem.cbAllocated = cbAlloc;
2275 pThis->Output.Mem.pb = pbNew;
2276 pThis->Output.Mem.cb = cbNewSize;
2277 return cbToAppend;
2278 }
2279
2280 pThis->rcOutput = VERR_NO_MEMORY;
2281 }
2282 else
2283 pThis->rcOutput = VERR_TOO_MUCH_DATA;
2284
2285 /*
2286 * Failure - abort.
2287 */
2288 RTMemFree(pThis->Output.Mem.pb);
2289 pThis->Output.Mem.pb = NULL;
2290 pThis->Output.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
2291 pThis->fAbort = true;
2292 return 0;
2293}
2294
2295
2296/**
2297 * Internal worker that performs a HTTP GET.
2298 *
2299 * @returns IPRT status code.
2300 * @param hHttp The HTTP/HTTPS client instance.
2301 * @param pszUrl The URL.
2302 * @param fNoBody Set to suppress the body.
2303 * @param ppvResponse Where to return the pointer to the allocated
2304 * response data (RTMemFree). There will always be
2305 * an zero terminator char after the response, that
2306 * is not part of the size returned via @a pcb.
2307 * @param pcb The size of the response data.
2308 *
2309 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
2310 * threads, because that will probably blow up!
2311 */
2312static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
2313{
2314 PRTHTTPINTERNAL pThis = hHttp;
2315 RTHTTP_VALID_RETURN(pThis);
2316
2317 /*
2318 * Reset the return values in case of more "GUI programming" on the client
2319 * side (i.e. a programming style not bothering checking return codes).
2320 */
2321 *ppvResponse = NULL;
2322 *pcb = 0;
2323
2324 /*
2325 * Set the busy flag (paranoia).
2326 */
2327 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2328 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2329
2330 /*
2331 * Reset the state and apply settings.
2332 */
2333 pThis->fAbort = false;
2334 pThis->rcOutput = VINF_SUCCESS;
2335 pThis->cbDownloadHint = 0;
2336
2337 int rc = rtHttpApplySettings(hHttp, pszUrl);
2338 if (RT_SUCCESS(rc))
2339 {
2340 RT_ZERO(pThis->Output.Mem);
2341 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteData);
2342 if (!CURL_FAILURE(rcCurl))
2343 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
2344 if (fNoBody)
2345 {
2346 if (!CURL_FAILURE(rcCurl))
2347 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
2348 if (!CURL_FAILURE(rcCurl))
2349 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
2350 }
2351 if (!CURL_FAILURE(rcCurl))
2352 {
2353 /*
2354 * Perform the HTTP operation.
2355 */
2356 rcCurl = curl_easy_perform(pThis->pCurl);
2357 rc = rtHttpGetCalcStatus(pThis, rcCurl);
2358 if (RT_SUCCESS(rc))
2359 rc = pThis->rcOutput;
2360 if (RT_SUCCESS(rc))
2361 {
2362 *ppvResponse = pThis->Output.Mem.pb;
2363 *pcb = pThis->Output.Mem.cb;
2364 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n", pThis->Output.Mem.cb, pThis->Output.Mem.cbAllocated));
2365 }
2366 else if (pThis->Output.Mem.pb)
2367 RTMemFree(pThis->Output.Mem.pb);
2368 RT_ZERO(pThis->Output.Mem);
2369 }
2370 else
2371 rc = VERR_HTTP_CURL_ERROR;
2372 }
2373
2374 ASMAtomicWriteBool(&pThis->fBusy, false);
2375 return rc;
2376}
2377
2378
2379RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2380{
2381 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2382 uint8_t *pv;
2383 size_t cb;
2384 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
2385 if (RT_SUCCESS(rc))
2386 {
2387 if (pv) /* paranoia */
2388 *ppszNotUtf8 = (char *)pv;
2389 else
2390 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2391 }
2392 else
2393 *ppszNotUtf8 = NULL;
2394 return rc;
2395}
2396
2397
2398RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2399{
2400 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2401 uint8_t *pv;
2402 size_t cb;
2403 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
2404 if (RT_SUCCESS(rc))
2405 {
2406 if (pv) /* paranoia */
2407 *ppszNotUtf8 = (char *)pv;
2408 else
2409 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2410 }
2411 else
2412 *ppszNotUtf8 = NULL;
2413 return rc;
2414
2415}
2416
2417
2418RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
2419{
2420 RTMemFree(pszNotUtf8);
2421}
2422
2423
2424RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2425{
2426 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2427 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2428}
2429
2430
2431RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2432{
2433 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2434 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2435}
2436
2437
2438RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
2439{
2440 RTMemFree(pvResponse);
2441}
2442
2443
2444/**
2445 * cURL callback for writing data to a file.
2446 */
2447static size_t rtHttpWriteDataToFile(void *pvBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2448{
2449 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2450 size_t cbWritten = 0;
2451 int rc = RTFileWrite(pThis->Output.hFile, pvBuf, cbUnit * cUnits, &cbWritten);
2452 if (RT_SUCCESS(rc))
2453 return cbWritten;
2454 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
2455 pThis->rcOutput = rc;
2456 return 0;
2457}
2458
2459
2460RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
2461{
2462 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
2463 PRTHTTPINTERNAL pThis = hHttp;
2464 RTHTTP_VALID_RETURN(pThis);
2465
2466 /*
2467 * Set the busy flag (paranoia).
2468 */
2469 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2470 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2471
2472 /*
2473 * Reset the state and apply settings.
2474 */
2475 pThis->fAbort = false;
2476 pThis->rcOutput = VINF_SUCCESS;
2477 pThis->cbDownloadHint = 0;
2478
2479 int rc = rtHttpApplySettings(hHttp, pszUrl);
2480 if (RT_SUCCESS(rc))
2481 {
2482 pThis->Output.hFile = NIL_RTFILE;
2483 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, &rtHttpWriteDataToFile);
2484 if (!CURL_FAILURE(rcCurl))
2485 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, (void *)pThis);
2486 if (!CURL_FAILURE(rcCurl))
2487 {
2488 /*
2489 * Open the output file.
2490 */
2491 rc = RTFileOpen(&pThis->Output.hFile, pszDstFile, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
2492 if (RT_SUCCESS(rc))
2493 {
2494 /*
2495 * Perform the HTTP operation.
2496 */
2497 rcCurl = curl_easy_perform(pThis->pCurl);
2498 rc = rtHttpGetCalcStatus(pThis, rcCurl);
2499 if (RT_SUCCESS(rc))
2500 rc = pThis->rcOutput;
2501
2502 int rc2 = RTFileClose(pThis->Output.hFile);
2503 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
2504 rc = rc2;
2505 }
2506 pThis->Output.hFile = NIL_RTFILE;
2507 }
2508 else
2509 rc = VERR_HTTP_CURL_ERROR;
2510 }
2511
2512 ASMAtomicWriteBool(&pThis->fBusy, false);
2513 return rc;
2514}
2515
2516
2517RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PRTHTTPDOWNLDPROGRCALLBACK pfnDownloadProgress, void *pvUser)
2518{
2519 PRTHTTPINTERNAL pThis = hHttp;
2520 RTHTTP_VALID_RETURN(pThis);
2521
2522 pThis->pfnDownloadProgress = pfnDownloadProgress;
2523 pThis->pvDownloadProgressUser = pvUser;
2524 return VINF_SUCCESS;
2525}
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