VirtualBox

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

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

IPRT/rest: More request array and map setter methods. Fixed string defaults. Safer copying and resetToDefaults. bugref:9167

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 110.9 KB
Line 
1/* $Id: http-curl.cpp 73977 2018-08-30 12:13:02Z vboxsync $ */
2/** @file
3 * IPRT - HTTP client API, cURL based.
4 *
5 * Logging groups:
6 * Log4 - request headers.
7 * Log5 - request body.
8 * Log6 - response headers.
9 * Log7 - response body.
10 */
11
12/*
13 * Copyright (C) 2012-2017 Oracle Corporation
14 *
15 * This file is part of VirtualBox Open Source Edition (OSE), as
16 * available from http://www.virtualbox.org. This file is free software;
17 * you can redistribute it and/or modify it under the terms of the GNU
18 * General Public License (GPL) as published by the Free Software
19 * Foundation, in version 2 as it comes in the "COPYING" file of the
20 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
21 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22 *
23 * The contents of this file may alternatively be used under the terms
24 * of the Common Development and Distribution License Version 1.0
25 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
26 * VirtualBox OSE distribution, in which case the provisions of the
27 * CDDL are applicable instead of those of the GPL.
28 *
29 * You may elect to license modified versions of this file under the
30 * terms and conditions of either the GPL or the CDDL or both.
31 */
32
33
34/*********************************************************************************************************************************
35* Header Files *
36*********************************************************************************************************************************/
37#define LOG_GROUP RTLOGGROUP_HTTP
38#include <iprt/http.h>
39#include "internal/iprt.h"
40
41#include <iprt/alloca.h>
42#include <iprt/asm.h>
43#include <iprt/assert.h>
44#include <iprt/cidr.h>
45#include <iprt/crypto/store.h>
46#include <iprt/ctype.h>
47#include <iprt/env.h>
48#include <iprt/err.h>
49#include <iprt/file.h>
50#include <iprt/ldr.h>
51#include <iprt/log.h>
52#include <iprt/mem.h>
53#include <iprt/net.h>
54#include <iprt/once.h>
55#include <iprt/path.h>
56#include <iprt/stream.h>
57#include <iprt/string.h>
58#include <iprt/uni.h>
59#include <iprt/uri.h>
60
61#include "internal/magics.h"
62
63#ifdef RT_OS_WINDOWS /* curl.h drags in windows.h which isn't necessarily -Wall clean. */
64# include <iprt/win/windows.h>
65#endif
66#include <curl/curl.h>
67
68#ifdef RT_OS_DARWIN
69# include <CoreFoundation/CoreFoundation.h>
70# include <SystemConfiguration/SystemConfiguration.h>
71# include <CoreServices/CoreServices.h>
72#endif
73#ifdef RT_OS_WINDOWS
74# include <Winhttp.h>
75# include "../r3/win/internal-r3-win.h"
76#endif
77
78#ifdef RT_OS_LINUX
79//# define IPRT_USE_LIBPROXY
80#endif
81#ifdef IPRT_USE_LIBPROXY
82# include <stdlib.h> /* free */
83#endif
84
85
86/*********************************************************************************************************************************
87* Structures and Typedefs *
88*********************************************************************************************************************************/
89/** Output collection data. */
90typedef struct RTHTTPOUTPUTDATA
91{
92 /** Pointer to the HTTP client instance structure. */
93 struct RTHTTPINTERNAL *pHttp;
94 /** Callback specific data. */
95 union
96 {
97 /** For file destination. */
98 RTFILE hFile;
99 /** For memory destination. */
100 struct
101 {
102 /** The current size (sans terminator char). */
103 size_t cb;
104 /** The currently allocated size. */
105 size_t cbAllocated;
106 /** Pointer to the buffer. */
107 uint8_t *pb;
108 } Mem;
109 } uData;
110} RTHTTPOUTPUTDATA;
111
112/**
113 * Internal HTTP client instance.
114 */
115typedef struct RTHTTPINTERNAL
116{
117 /** Magic value. */
118 uint32_t u32Magic;
119 /** cURL handle. */
120 CURL *pCurl;
121 /** The last response code. */
122 long lLastResp;
123 /** Custom headers/ */
124 struct curl_slist *pHeaders;
125 /** CA certificate file for HTTPS authentication. */
126 char *pszCaFile;
127 /** Whether to delete the CA on destruction. */
128 bool fDeleteCaFile;
129
130 /** Set if we've applied a CURLOTP_USERAGENT already. */
131 bool fHaveSetUserAgent;
132 /** Set if we've got a user agent header, otherwise clear. */
133 bool fHaveUserAgentHeader;
134
135 /** @name Proxy settings.
136 * When fUseSystemProxySettings is set, the other members will be updated each
137 * time we're presented with a new URL. The members reflect the cURL
138 * configuration.
139 *
140 * @{ */
141 /** Set if we should use the system proxy settings for a URL.
142 * This means reconfiguring cURL for each request. */
143 bool fUseSystemProxySettings;
144 /** Set if we've detected no proxy necessary. */
145 bool fNoProxy;
146 /** Proxy host name (RTStrFree). */
147 char *pszProxyHost;
148 /** Proxy port number (UINT32_MAX if not specified). */
149 uint32_t uProxyPort;
150 /** The proxy type (CURLPROXY_HTTP, CURLPROXY_SOCKS5, ++). */
151 curl_proxytype enmProxyType;
152 /** Proxy username (RTStrFree). */
153 char *pszProxyUsername;
154 /** Proxy password (RTStrFree). */
155 char *pszProxyPassword;
156 /** @} */
157
158 /** @name Cached settings.
159 * @{ */
160 /** Maximum number of redirects to follow.
161 * Zero if not automatically following (default). */
162 uint32_t cMaxRedirects;
163 /** @} */
164
165 /** Abort the current HTTP request if true. */
166 bool volatile fAbort;
167 /** Set if someone is preforming an HTTP operation. */
168 bool volatile fBusy;
169 /** The location field for 301 responses. */
170 char *pszRedirLocation;
171
172 union
173 {
174 struct
175 {
176 /** Pointer to the memory block we're feeding the cURL/server. */
177 void const *pvMem;
178 /** Size of the memory block. */
179 size_t cbMem;
180 /** Current memory block offset. */
181 size_t offMem;
182 } Mem;
183 } ReadData;
184
185 /** Body output callback data. */
186 RTHTTPOUTPUTDATA BodyOutput;
187 /** Headers output callback data. */
188 RTHTTPOUTPUTDATA HeadersOutput;
189 /** The output status.*/
190 int rcOutput;
191
192 /** Download size hint set by the progress callback. */
193 uint64_t cbDownloadHint;
194 /** Callback called during download. */
195 PFNRTHTTPDOWNLDPROGRCALLBACK pfnDownloadProgress;
196 /** User pointer parameter for pfnDownloadProgress. */
197 void *pvDownloadProgressUser;
198} RTHTTPINTERNAL;
199/** Pointer to an internal HTTP client instance. */
200typedef RTHTTPINTERNAL *PRTHTTPINTERNAL;
201
202
203#ifdef RT_OS_WINDOWS
204/** @name Windows: Types for dynamically resolved APIs
205 * @{ */
206typedef HINTERNET (WINAPI * PFNWINHTTPOPEN)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
207typedef BOOL (WINAPI * PFNWINHTTPCLOSEHANDLE)(HINTERNET);
208typedef BOOL (WINAPI * PFNWINHTTPGETPROXYFORURL)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS *, WINHTTP_PROXY_INFO *);
209typedef BOOL (WINAPI * PFNWINHTTPGETDEFAULTPROXYCONFIGURATION)(WINHTTP_PROXY_INFO *);
210typedef BOOL (WINAPI * PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *);
211/** @} */
212#endif
213
214#ifdef IPRT_USE_LIBPROXY
215typedef struct px_proxy_factory *PLIBPROXYFACTORY;
216typedef PLIBPROXYFACTORY (* PFNLIBPROXYFACTORYCTOR)(void);
217typedef void (* PFNLIBPROXYFACTORYDTOR)(PLIBPROXYFACTORY);
218typedef char ** (* PFNLIBPROXYFACTORYGETPROXIES)(PLIBPROXYFACTORY, const char *);
219#endif
220
221
222/*********************************************************************************************************************************
223* Defined Constants And Macros *
224*********************************************************************************************************************************/
225/** @def RTHTTP_MAX_MEM_DOWNLOAD_SIZE
226 * The max size we are allowed to download to a memory buffer.
227 *
228 * @remarks The minus 1 is for the trailing zero terminator we always add.
229 */
230#if ARCH_BITS == 64
231# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1)
232#else
233# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1)
234#endif
235
236/** Checks whether a cURL return code indicates success. */
237#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK)
238/** Checks whether a cURL return code indicates failure. */
239#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK)
240
241/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
242#define RTHTTP_VALID_RETURN_RC(hHttp, rcCurl) \
243 do { \
244 AssertPtrReturn((hHttp), (rcCurl)); \
245 AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (rcCurl)); \
246 } while (0)
247
248/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
249#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE)
250
251/** Validates a handle and returns (void) if not valid. */
252#define RTHTTP_VALID_RETURN_VOID(hHttp) \
253 do { \
254 AssertPtrReturnVoid(hHttp); \
255 AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \
256 } while (0)
257
258
259/*********************************************************************************************************************************
260* Global Variables *
261*********************************************************************************************************************************/
262#ifdef RT_OS_WINDOWS
263/** @name Windows: Dynamically resolved APIs
264 * @{ */
265static RTONCE g_WinResolveImportsOnce = RTONCE_INITIALIZER;
266static PFNWINHTTPOPEN g_pfnWinHttpOpen = NULL;
267static PFNWINHTTPCLOSEHANDLE g_pfnWinHttpCloseHandle = NULL;
268static PFNWINHTTPGETPROXYFORURL g_pfnWinHttpGetProxyForUrl = NULL;
269static PFNWINHTTPGETDEFAULTPROXYCONFIGURATION g_pfnWinHttpGetDefaultProxyConfiguration = NULL;
270static PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER g_pfnWinHttpGetIEProxyConfigForCurrentUser = NULL;
271/** @} */
272#endif
273
274#ifdef IPRT_USE_LIBPROXY
275/** @name Dynamaically resolved libproxy APIs.
276 * @{ */
277static RTONCE g_LibProxyResolveImportsOnce = RTONCE_INITIALIZER;
278static RTLDRMOD g_hLdrLibProxy = NIL_RTLDRMOD;
279static PFNLIBPROXYFACTORYCTOR g_pfnLibProxyFactoryCtor = NULL;
280static PFNLIBPROXYFACTORYDTOR g_pfnLibProxyFactoryDtor = NULL;
281static PFNLIBPROXYFACTORYGETPROXIES g_pfnLibProxyFactoryGetProxies = NULL;
282/** @} */
283#endif
284
285
286/*********************************************************************************************************************************
287* Internal Functions *
288*********************************************************************************************************************************/
289static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis);
290#ifdef RT_OS_DARWIN
291static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType);
292#endif
293
294
295RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp)
296{
297 AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER);
298
299 /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a
300 * problem if multiple threads get here at the same time. */
301 int rc = VERR_HTTP_INIT_FAILED;
302 CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL);
303 if (CURL_SUCCESS(rcCurl))
304 {
305 CURL *pCurl = curl_easy_init();
306 if (pCurl)
307 {
308 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL));
309 if (pThis)
310 {
311 pThis->u32Magic = RTHTTP_MAGIC;
312 pThis->pCurl = pCurl;
313 pThis->fUseSystemProxySettings = true;
314 pThis->cMaxRedirects = 0; /* no automatic redir following */
315 pThis->BodyOutput.pHttp = pThis;
316 pThis->HeadersOutput.pHttp = pThis;
317
318 *phHttp = (RTHTTP)pThis;
319
320 return VINF_SUCCESS;
321 }
322 rc = VERR_NO_MEMORY;
323 }
324 else
325 rc = VERR_HTTP_INIT_FAILED;
326 }
327 curl_global_cleanup();
328 return rc;
329}
330
331
332RTR3DECL(int) RTHttpReset(RTHTTP hHttp)
333{
334 if (hHttp == NIL_RTHTTP)
335 return VERR_INVALID_HANDLE;
336
337 PRTHTTPINTERNAL pThis = hHttp;
338 RTHTTP_VALID_RETURN(pThis);
339
340 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
341
342 /* This resets options, but keeps open connections, cookies, etc. */
343 curl_easy_reset(pThis->pCurl);
344 return VINF_SUCCESS;
345}
346
347
348RTR3DECL(int) RTHttpDestroy(RTHTTP hHttp)
349{
350 if (hHttp == NIL_RTHTTP)
351 return VINF_SUCCESS;
352
353 PRTHTTPINTERNAL pThis = hHttp;
354 RTHTTP_VALID_RETURN(pThis);
355
356 Assert(!pThis->fBusy);
357
358 pThis->u32Magic = RTHTTP_MAGIC_DEAD;
359
360 curl_easy_cleanup(pThis->pCurl);
361 pThis->pCurl = NULL;
362
363 if (pThis->pHeaders)
364 {
365 curl_slist_free_all(pThis->pHeaders);
366 pThis->pHeaders = NULL;
367 }
368
369 rtHttpUnsetCaFile(pThis);
370 Assert(!pThis->pszCaFile);
371
372 if (pThis->pszRedirLocation)
373 {
374 RTStrFree(pThis->pszRedirLocation);
375 pThis->pszRedirLocation = NULL;
376 }
377
378 RTStrFree(pThis->pszProxyHost);
379 pThis->pszProxyHost = NULL;
380 RTStrFree(pThis->pszProxyUsername);
381 pThis->pszProxyUsername = NULL;
382 if (pThis->pszProxyPassword)
383 {
384 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
385 RTStrFree(pThis->pszProxyPassword);
386 pThis->pszProxyPassword = NULL;
387 }
388
389 RTMemFree(pThis);
390
391 curl_global_cleanup();
392
393 return VINF_SUCCESS;
394}
395
396
397RTR3DECL(int) RTHttpAbort(RTHTTP hHttp)
398{
399 PRTHTTPINTERNAL pThis = hHttp;
400 RTHTTP_VALID_RETURN(pThis);
401
402 pThis->fAbort = true;
403
404 return VINF_SUCCESS;
405}
406
407
408RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation)
409{
410 PRTHTTPINTERNAL pThis = hHttp;
411 RTHTTP_VALID_RETURN(pThis);
412 Assert(!pThis->fBusy);
413
414 if (!pThis->pszRedirLocation)
415 return VERR_HTTP_NOT_FOUND;
416
417 return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation);
418}
419
420
421RTR3DECL(int) RTHttpSetFollowRedirects(RTHTTP hHttp, uint32_t cMaxRedirects)
422{
423 PRTHTTPINTERNAL pThis = hHttp;
424 RTHTTP_VALID_RETURN(pThis);
425 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
426
427 /*
428 * Update the redirection settings.
429 */
430 if (pThis->cMaxRedirects != cMaxRedirects)
431 {
432 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_MAXREDIRS, (long)cMaxRedirects);
433 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_MAXREDIRS=%u: %d (%#x)\n", cMaxRedirects, rcCurl, rcCurl),
434 VERR_HTTP_CURL_ERROR);
435
436 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_FOLLOWLOCATION, (long)(cMaxRedirects > 0));
437 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_FOLLOWLOCATION=%d: %d (%#x)\n", cMaxRedirects > 0, rcCurl, rcCurl),
438 VERR_HTTP_CURL_ERROR);
439
440 pThis->cMaxRedirects = cMaxRedirects;
441 }
442 return VINF_SUCCESS;
443}
444
445
446/*********************************************************************************************************************************
447* Proxy handling. *
448*********************************************************************************************************************************/
449
450RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp)
451{
452 PRTHTTPINTERNAL pThis = hHttp;
453 RTHTTP_VALID_RETURN(pThis);
454 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
455
456 /*
457 * Change the settings.
458 */
459 pThis->fUseSystemProxySettings = true;
460 return VINF_SUCCESS;
461}
462
463
464/**
465 * rtHttpConfigureProxyForUrl: Update cURL proxy settings as needed.
466 *
467 * @returns IPRT status code.
468 * @param pThis The HTTP client instance.
469 * @param enmProxyType The proxy type.
470 * @param pszHost The proxy host name.
471 * @param uPort The proxy port number.
472 * @param pszUsername The proxy username, or NULL if none.
473 * @param pszPassword The proxy password, or NULL if none.
474 */
475static int rtHttpUpdateProxyConfig(PRTHTTPINTERNAL pThis, curl_proxytype enmProxyType, const char *pszHost,
476 uint32_t uPort, const char *pszUsername, const char *pszPassword)
477{
478 int rcCurl;
479 AssertReturn(pszHost, VERR_INVALID_PARAMETER);
480 Log(("rtHttpUpdateProxyConfig: pThis=%p type=%d host='%s' port=%u user='%s'%s\n",
481 pThis, enmProxyType, pszHost, uPort, pszUsername, pszPassword ? " with password" : " without password"));
482
483#ifdef CURLOPT_NOPROXY
484 if (pThis->fNoProxy)
485 {
486 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, (const char *)NULL);
487 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_NOPROXY=NULL: %d (%#x)\n", rcCurl, rcCurl),
488 VERR_HTTP_CURL_PROXY_CONFIG);
489 pThis->fNoProxy = false;
490 }
491#endif
492
493 if (enmProxyType != pThis->enmProxyType)
494 {
495 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)enmProxyType);
496 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYTYPE=%d: %d (%#x)\n", enmProxyType, rcCurl, rcCurl),
497 VERR_HTTP_CURL_PROXY_CONFIG);
498 pThis->enmProxyType = CURLPROXY_HTTP;
499 }
500
501 if (uPort != pThis->uProxyPort)
502 {
503 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort);
504 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPORT=%d: %d (%#x)\n", uPort, rcCurl, rcCurl),
505 VERR_HTTP_CURL_PROXY_CONFIG);
506 pThis->uProxyPort = uPort;
507 }
508
509 if ( pszUsername != pThis->pszProxyUsername
510 || RTStrCmp(pszUsername, pThis->pszProxyUsername))
511 {
512 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pszUsername);
513 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYUSERNAME=%s: %d (%#x)\n", pszUsername, rcCurl, rcCurl),
514 VERR_HTTP_CURL_PROXY_CONFIG);
515 if (pThis->pszProxyUsername)
516 {
517 RTStrFree(pThis->pszProxyUsername);
518 pThis->pszProxyUsername = NULL;
519 }
520 if (pszUsername)
521 {
522 pThis->pszProxyUsername = RTStrDup(pszUsername);
523 AssertReturn(pThis->pszProxyUsername, VERR_NO_STR_MEMORY);
524 }
525 }
526
527 if ( pszPassword != pThis->pszProxyPassword
528 || RTStrCmp(pszPassword, pThis->pszProxyPassword))
529 {
530 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pszPassword);
531 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXYPASSWORD=%s: %d (%#x)\n", pszPassword ? "xxx" : NULL, rcCurl, rcCurl),
532 VERR_HTTP_CURL_PROXY_CONFIG);
533 if (pThis->pszProxyPassword)
534 {
535 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
536 RTStrFree(pThis->pszProxyPassword);
537 pThis->pszProxyPassword = NULL;
538 }
539 if (pszPassword)
540 {
541 pThis->pszProxyPassword = RTStrDup(pszPassword);
542 AssertReturn(pThis->pszProxyPassword, VERR_NO_STR_MEMORY);
543 }
544 }
545
546 if ( pszHost != pThis->pszProxyHost
547 || RTStrCmp(pszHost, pThis->pszProxyHost))
548 {
549 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pszHost);
550 AssertMsgReturn(rcCurl == CURLE_OK, ("CURLOPT_PROXY=%s: %d (%#x)\n", pszHost, rcCurl, rcCurl),
551 VERR_HTTP_CURL_PROXY_CONFIG);
552 if (pThis->pszProxyHost)
553 {
554 RTStrFree(pThis->pszProxyHost);
555 pThis->pszProxyHost = NULL;
556 }
557 if (pszHost)
558 {
559 pThis->pszProxyHost = RTStrDup(pszHost);
560 AssertReturn(pThis->pszProxyHost, VERR_NO_STR_MEMORY);
561 }
562 }
563
564 return VINF_SUCCESS;
565}
566
567
568/**
569 * rtHttpConfigureProxyForUrl: Disables proxying.
570 *
571 * @returns IPRT status code.
572 * @param pThis The HTTP client instance.
573 */
574static int rtHttpUpdateAutomaticProxyDisable(PRTHTTPINTERNAL pThis)
575{
576 Log(("rtHttpUpdateAutomaticProxyDisable: pThis=%p\n", pThis));
577
578 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP) == CURLE_OK, VERR_INTERNAL_ERROR_2);
579 pThis->enmProxyType = CURLPROXY_HTTP;
580
581 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)1080) == CURLE_OK, VERR_INTERNAL_ERROR_2);
582 pThis->uProxyPort = 1080;
583
584 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
585 if (pThis->pszProxyUsername)
586 {
587 RTStrFree(pThis->pszProxyUsername);
588 pThis->pszProxyUsername = NULL;
589 }
590
591 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
592 if (pThis->pszProxyPassword)
593 {
594 RTStrFree(pThis->pszProxyPassword);
595 pThis->pszProxyPassword = NULL;
596 }
597
598 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
599 if (pThis->pszProxyHost)
600 {
601 RTStrFree(pThis->pszProxyHost);
602 pThis->pszProxyHost = NULL;
603 }
604
605#ifdef CURLOPT_NOPROXY
606 /* No proxy for everything! */
607 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, "*") == CURLE_OK, CURLOPT_PROXY);
608 pThis->fNoProxy = true;
609#endif
610
611 return VINF_SUCCESS;
612}
613
614
615/**
616 * See if the host name of the URL is included in the stripped no_proxy list.
617 *
618 * The no_proxy list is a colon or space separated list of domain names for
619 * which there should be no proxying. Given "no_proxy=oracle.com" neither the
620 * URL "http://www.oracle.com" nor "http://oracle.com" will not be proxied, but
621 * "http://notoracle.com" will be.
622 *
623 * @returns true if the URL is in the no_proxy list, otherwise false.
624 * @param pszUrl The URL.
625 * @param pszNoProxyList The stripped no_proxy list.
626 */
627static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList)
628{
629 /*
630 * Check for just '*', diabling proxying for everything.
631 * (Caller stripped pszNoProxyList.)
632 */
633 if (*pszNoProxyList == '*' && pszNoProxyList[1] == '\0')
634 return true;
635
636 /*
637 * Empty list? (Caller stripped it, remember).
638 */
639 if (!*pszNoProxyList)
640 return false;
641
642 /*
643 * We now need to parse the URL and extract the host name.
644 */
645 RTURIPARSED Parsed;
646 int rc = RTUriParse(pszUrl, &Parsed);
647 AssertRCReturn(rc, false);
648 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
649 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
650 return false;
651
652 bool fRet = false;
653 size_t const cchHost = strlen(pszHost);
654 if (cchHost)
655 {
656 /*
657 * The list is comma or space separated, walk it and match host names.
658 */
659 while (*pszNoProxyList != '\0')
660 {
661 /* Strip leading slashes, commas and dots. */
662 char ch;
663 while ( (ch = *pszNoProxyList) == ','
664 || ch == '.'
665 || RT_C_IS_SPACE(ch))
666 pszNoProxyList++;
667
668 /* Find the end. */
669 size_t cch = RTStrOffCharOrTerm(pszNoProxyList, ',');
670 size_t offNext = RTStrOffCharOrTerm(pszNoProxyList, ' ');
671 cch = RT_MIN(cch, offNext);
672 offNext = cch;
673
674 /* Trip trailing spaces, well tabs and stuff. */
675 while (cch > 0 && RT_C_IS_SPACE(pszNoProxyList[cch - 1]))
676 cch--;
677
678 /* Do the matching, if we have anything to work with. */
679 if (cch > 0)
680 {
681 if ( ( cch == cchHost
682 && RTStrNICmp(pszNoProxyList, pszHost, cch) == 0)
683 || ( cch < cchHost
684 && pszHost[cchHost - cch - 1] == '.'
685 && RTStrNICmp(pszNoProxyList, &pszHost[cchHost - cch], cch) == 0) )
686 {
687 fRet = true;
688 break;
689 }
690 }
691
692 /* Next. */
693 pszNoProxyList += offNext;
694 }
695 }
696
697 RTStrFree(pszHost);
698 return fRet;
699}
700
701
702/**
703 * Configures a proxy given a "URL" like specification.
704 *
705 * The format is:
706 * @verbatim
707 * [<scheme>"://"][<userid>[@<password>]:]<server>[":"<port>]
708 * @endverbatim
709 *
710 * Where the scheme gives the type of proxy server we're dealing with rather
711 * than the protocol of the external server we wish to talk to.
712 *
713 * @returns IPRT status code.
714 * @param pThis The HTTP client instance.
715 * @param pszProxyUrl The proxy server "URL".
716 */
717static int rtHttpConfigureProxyFromUrl(PRTHTTPINTERNAL pThis, const char *pszProxyUrl)
718{
719 /*
720 * Make sure it can be parsed as an URL.
721 */
722 char *pszFreeMe = NULL;
723 if (!strstr(pszProxyUrl, "://"))
724 {
725 static const char s_szPrefix[] = "http://";
726 size_t cchProxyUrl = strlen(pszProxyUrl);
727 pszFreeMe = (char *)RTMemTmpAlloc(sizeof(s_szPrefix) + cchProxyUrl);
728 if (pszFreeMe)
729 {
730 memcpy(pszFreeMe, s_szPrefix, sizeof(s_szPrefix) - 1);
731 memcpy(&pszFreeMe[sizeof(s_szPrefix) - 1], pszProxyUrl, cchProxyUrl);
732 pszFreeMe[sizeof(s_szPrefix) - 1 + cchProxyUrl] = '\0';
733 pszProxyUrl = pszFreeMe;
734 }
735 else
736 return VERR_NO_TMP_MEMORY;
737 }
738
739 RTURIPARSED Parsed;
740 int rc = RTUriParse(pszProxyUrl, &Parsed);
741 if (RT_SUCCESS(rc))
742 {
743 char *pszHost = RTUriParsedAuthorityHost(pszProxyUrl, &Parsed);
744 if (pszHost)
745 {
746 /*
747 * We've got a host name, try get the rest.
748 */
749 char *pszUsername = RTUriParsedAuthorityUsername(pszProxyUrl, &Parsed);
750 char *pszPassword = RTUriParsedAuthorityPassword(pszProxyUrl, &Parsed);
751 uint32_t uProxyPort = RTUriParsedAuthorityPort(pszProxyUrl, &Parsed);
752 curl_proxytype enmProxyType;
753 if (RTUriIsSchemeMatch(pszProxyUrl, "http"))
754 {
755 enmProxyType = CURLPROXY_HTTP;
756 if (uProxyPort == UINT32_MAX)
757 uProxyPort = 80;
758 }
759 else if ( RTUriIsSchemeMatch(pszProxyUrl, "socks4")
760 || RTUriIsSchemeMatch(pszProxyUrl, "socks"))
761 enmProxyType = CURLPROXY_SOCKS4;
762 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks4a"))
763 enmProxyType = CURLPROXY_SOCKS4A;
764 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5"))
765 enmProxyType = CURLPROXY_SOCKS5;
766 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5h"))
767 enmProxyType = CURLPROXY_SOCKS5_HOSTNAME;
768 else
769 {
770 enmProxyType = CURLPROXY_HTTP;
771 if (uProxyPort == UINT32_MAX)
772 uProxyPort = 8080;
773 }
774
775 /* Guess the port from the proxy type if not given. */
776 if (uProxyPort == UINT32_MAX)
777 uProxyPort = 1080; /* CURL_DEFAULT_PROXY_PORT */
778
779 rc = rtHttpUpdateProxyConfig(pThis, enmProxyType, pszHost, uProxyPort, pszUsername, pszPassword);
780
781 RTStrFree(pszUsername);
782 RTStrFree(pszPassword);
783 RTStrFree(pszHost);
784 }
785 else
786 AssertMsgFailed(("RTUriParsedAuthorityHost('%s',) -> NULL\n", pszProxyUrl));
787 }
788 else
789 AssertMsgFailed(("RTUriParse('%s',) -> %Rrc\n", pszProxyUrl, rc));
790
791 if (pszFreeMe)
792 RTMemTmpFree(pszFreeMe);
793 return rc;
794}
795
796
797/**
798 * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out
799 * the proxy config.
800 *
801 * @returns IPRT status code.
802 * @param pThis The HTTP client instance.
803 * @param pszUrl The URL to configure a proxy for.
804 */
805static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl)
806{
807 char szTmp[_1K];
808
809 /*
810 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
811 */
812 const char *pszNoProxyVar;
813 size_t cchActual;
814 char *pszNoProxyFree = NULL;
815 char *pszNoProxy = szTmp;
816 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
817 if (rc == VERR_ENV_VAR_NOT_FOUND)
818 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
819 if (rc == VERR_BUFFER_OVERFLOW)
820 {
821 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
822 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
823 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
824 }
825 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
826 bool fNoProxy = false;
827 if (RT_SUCCESS(rc))
828 fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
829 RTMemTmpFree(pszNoProxyFree);
830 if (!fNoProxy)
831 {
832 /*
833 * Get the schema specific specific env var, falling back on the
834 * generic all_proxy if not found.
835 */
836 const char *apszEnvVars[4];
837 unsigned cEnvVars = 0;
838 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
839 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
840 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
841 {
842 apszEnvVars[cEnvVars++] = "https_proxy";
843 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
844 }
845 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
846 {
847 apszEnvVars[cEnvVars++] = "ftp_proxy";
848 apszEnvVars[cEnvVars++] = "FTP_PROXY";
849 }
850 else
851 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
852 apszEnvVars[cEnvVars++] = "all_proxy";
853 apszEnvVars[cEnvVars++] = "ALL_PROXY";
854
855 /*
856 * We try the env vars out and goes with the first one we can make sense out of.
857 * If we cannot make sense of any, we return the first unexpected rc we got.
858 */
859 rc = VINF_SUCCESS;
860 for (uint32_t i = 0; i < cEnvVars; i++)
861 {
862 size_t cchValue;
863 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
864 if (RT_SUCCESS(rc2))
865 {
866 if (cchValue != 0)
867 {
868 /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */
869 if (!strstr(szTmp, "://"))
870 {
871 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
872 memcpy(szTmp, RT_STR_TUPLE("http://"));
873 }
874
875 rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp);
876 if (RT_SUCCESS(rc2))
877 rc = rc2;
878 }
879 /*
880 * The variable is empty. Guess that means no proxying wanted.
881 */
882 else
883 {
884 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
885 break;
886 }
887 }
888 else
889 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
890 }
891 }
892 /*
893 * The host is the no-proxy list, it seems.
894 */
895 else
896 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
897
898 return rc;
899}
900
901#ifdef IPRT_USE_LIBPROXY
902
903/**
904 * @callback_method_impl{FNRTONCE,
905 * Attempts to load libproxy.so.1 and resolves APIs}
906 */
907static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser)
908{
909 RTLDRMOD hMod;
910 int rc = RTLdrLoad("/usr/lib/libproxy.so.1", &hMod);
911 if (RT_SUCCESS(rc))
912 {
913 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor);
914 if (RT_SUCCESS(rc))
915 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor);
916 if (RT_SUCCESS(rc))
917 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies);
918 if (RT_SUCCESS(rc))
919 g_hLdrLibProxy = hMod;
920 else
921 RTLdrClose(hMod);
922 AssertRC(rc);
923 }
924
925 NOREF(pvUser);
926 return rc;
927}
928
929/**
930 * Reconfigures the cURL proxy settings for the given URL, libproxy style.
931 *
932 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
933 * @param pThis The HTTP client instance.
934 * @param pszUrl The URL.
935 */
936static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
937{
938 int rcRet = VINF_NOT_SUPPORTED;
939
940 int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL);
941 if (RT_SUCCESS(rc))
942 {
943 /*
944 * Instance the factory and ask for a list of proxies.
945 */
946 PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor();
947 if (pFactory)
948 {
949 char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl);
950 g_pfnLibProxyFactoryDtor(pFactory);
951 if (papszProxies)
952 {
953 /*
954 * Look for something we can use.
955 */
956 for (unsigned i = 0; papszProxies[i]; i++)
957 {
958 if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0)
959 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
960 else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0
961 || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0
962 || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0
963 || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */
964 )
965 rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]);
966 else
967 continue;
968 if (rcRet != VINF_NOT_SUPPORTED)
969 break;
970 }
971
972 /* free the result. */
973 for (unsigned i = 0; papszProxies[i]; i++)
974 free(papszProxies[i]);
975 free(papszProxies);
976 }
977 }
978 }
979
980 return rcRet;
981}
982
983#endif /* IPRT_USE_LIBPROXY */
984
985#ifdef RT_OS_DARWIN
986
987/**
988 * Get a boolean like integer value from a dictionary.
989 *
990 * @returns true / false.
991 * @param hDict The dictionary.
992 * @param pvKey The dictionary value key.
993 */
994static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault)
995{
996 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey);
997 if (hNum)
998 {
999 int fEnabled;
1000 if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled))
1001 return fDefault;
1002 return fEnabled != 0;
1003 }
1004 return fDefault;
1005}
1006
1007
1008/**
1009 * Creates a CFURL object for an URL.
1010 *
1011 * @returns CFURL object reference.
1012 * @param pszUrl The URL.
1013 */
1014static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl)
1015{
1016 CFURLRef hUrl = NULL;
1017 CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8);
1018 if (hStrUrl)
1019 {
1020 CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl,
1021 NULL /*charactersToLeaveUnescaped*/,
1022 NULL /*legalURLCharactersToBeEscaped*/,
1023 kCFStringEncodingUTF8);
1024 if (hStrUrlEscaped)
1025 {
1026 hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/);
1027 Assert(hUrl);
1028 CFRelease(hStrUrlEscaped);
1029 }
1030 else
1031 AssertFailed();
1032 CFRelease(hStrUrl);
1033 }
1034 else
1035 AssertFailed();
1036 return hUrl;
1037}
1038
1039
1040/**
1041 * For passing results from rtHttpDarwinPacCallback to
1042 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1043 */
1044typedef struct RTHTTPDARWINPACRESULT
1045{
1046 CFArrayRef hArrayProxies;
1047 CFErrorRef hError;
1048} RTHTTPDARWINPACRESULT;
1049typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT;
1050
1051/**
1052 * Stupid callback for getting the result from
1053 * CFNetworkExecuteProxyAutoConfigurationURL.
1054 *
1055 * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of
1056 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1057 * @param hArrayProxies The result array.
1058 * @param hError Errors, if any.
1059 */
1060static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError)
1061{
1062 PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser;
1063
1064 Assert(pResult->hArrayProxies == NULL);
1065 if (hArrayProxies)
1066 pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies);
1067
1068 Assert(pResult->hError == NULL);
1069 if (hError)
1070 pResult->hError = (CFErrorRef)CFRetain(hError);
1071
1072 CFRunLoopStop(CFRunLoopGetCurrent());
1073}
1074
1075
1076/**
1077 * Executes a PAC script and returning the proxies it suggests.
1078 *
1079 * @returns Array of proxy configs (CFProxySupport.h style).
1080 * @param hUrlTarget The URL we're about to use.
1081 * @param hUrlScript The PAC script URL.
1082 */
1083static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(CFURLRef hUrlTarget, CFURLRef hUrlScript)
1084{
1085 char szTmp[256];
1086 if (LogIsFlowEnabled())
1087 {
1088 szTmp[0] = '\0';
1089 CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1090 LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp));
1091 }
1092
1093 /*
1094 * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to
1095 * download the script ourselves and mess around with too many CF APIs.
1096 */
1097 CFRunLoopRef hRunLoop = CFRunLoopGetCurrent();
1098 AssertReturn(hRunLoop, NULL);
1099
1100 RTHTTPDARWINPACRESULT Result = { NULL, NULL };
1101 CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL };
1102 CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget,
1103 rtHttpDarwinPacCallback, &Ctx);
1104 AssertReturn(hRunLoopSrc, NULL);
1105
1106 CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool");
1107 CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode);
1108 CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */
1109 CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode);
1110
1111 /** @todo convert errors, maybe even fail. */
1112
1113 /*
1114 * Autoconfig (or missing wpad server) typically results in:
1115 * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2).
1116 *
1117 * In the autoconfig case, it looks like we're getting two entries, first
1118 * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to
1119 * be very upset if this fails, just continue trying alternatives.
1120 */
1121 if (Result.hError)
1122 {
1123 if (LogIsEnabled())
1124 {
1125 szTmp[0] = '\0';
1126 CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1127 Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp));
1128 }
1129 CFRelease(Result.hError);
1130 }
1131 return Result.hArrayProxies;
1132}
1133
1134
1135/**
1136 * Attempt to configure the proxy according to @a hDictProxy.
1137 *
1138 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1139 * the caller should try out alternative proxy configs and fallbacks.
1140 * @param pThis The HTTP client instance.
1141 * @param hDictProxy The proxy configuration (see CFProxySupport.h).
1142 * @param hUrlTarget The URL we're about to use.
1143 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1144 * javascript URL). This is set when we're processing
1145 * the output from a PAC script.
1146 */
1147static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType)
1148{
1149 CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey);
1150 AssertReturn(hStrProxyType, VINF_NOT_SUPPORTED);
1151
1152 /*
1153 * No proxy is fairly simple and common.
1154 */
1155 if (CFEqual(hStrProxyType, kCFProxyTypeNone))
1156 return rtHttpUpdateAutomaticProxyDisable(pThis);
1157
1158 /*
1159 * PAC URL means recursion, however we only do one level.
1160 */
1161 if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL))
1162 {
1163 AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED);
1164
1165 CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey);
1166 AssertReturn(hUrlScript, VINF_NOT_SUPPORTED);
1167
1168 int rcRet = VINF_NOT_SUPPORTED;
1169 CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1170 if (hArray)
1171 {
1172 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1173 CFRelease(hArray);
1174 }
1175 return rcRet;
1176 }
1177
1178 /*
1179 * Determine the proxy type (not entirely sure about type == proxy type and
1180 * not scheme/protocol)...
1181 */
1182 curl_proxytype enmProxyType = CURLPROXY_HTTP;
1183 uint32_t uDefaultProxyPort = 8080;
1184 if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP)
1185 || CFEqual(hStrProxyType, kCFProxyTypeHTTPS))
1186 { /* defaults */ }
1187 else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS))
1188 {
1189 /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or
1190 * SOCK5 on the other side... Selecting SOCKS5 for now. */
1191 enmProxyType = CURLPROXY_SOCKS5;
1192 uDefaultProxyPort = 1080;
1193 }
1194 /* Unknown proxy type. */
1195 else
1196 return VINF_NOT_SUPPORTED;
1197
1198 /*
1199 * Extract the proxy configuration.
1200 */
1201 /* The proxy host name. */
1202 char szHostname[_1K];
1203 CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey);
1204 AssertReturn(hStr, VINF_NOT_SUPPORTED);
1205 AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1206
1207 /* Get the port number (optional). */
1208 SInt32 iProxyPort;
1209 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey);
1210 if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort))
1211 AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort);
1212 else
1213 iProxyPort = uDefaultProxyPort;
1214
1215 /* The proxy username. */
1216 char szUsername[256];
1217 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey);
1218 if (hStr)
1219 AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1220 else
1221 szUsername[0] = '\0';
1222
1223 /* The proxy password. */
1224 char szPassword[384];
1225 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey);
1226 if (hStr)
1227 AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1228 else
1229 szPassword[0] = '\0';
1230
1231 /*
1232 * Apply the proxy config.
1233 */
1234 return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort,
1235 szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL);
1236}
1237
1238
1239/**
1240 * Try do proxy config for our HTTP client instance given an array of proxies.
1241 *
1242 * This is used with the output from a CFProxySupport.h API.
1243 *
1244 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1245 * we might want to try out fallbacks.
1246 * @param pThis The HTTP client instance.
1247 * @param hArrayProxies The proxies CFPRoxySupport have given us.
1248 * @param hUrlTarget The URL we're about to use.
1249 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1250 * javascript URL). This is set when we're processing
1251 * the output from a PAC script.
1252 */
1253static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType)
1254{
1255 int rcRet = VINF_NOT_SUPPORTED;
1256 CFIndex const cEntries = CFArrayGetCount(hArrayProxies);
1257 LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries));
1258 for (CFIndex i = 0; i < cEntries; i++)
1259 {
1260 CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i);
1261 AssertContinue(hDictProxy);
1262
1263 rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType);
1264 if (rcRet != VINF_NOT_SUPPORTED)
1265 break;
1266 }
1267 return rcRet;
1268}
1269
1270
1271/**
1272 * Inner worker for rtHttpWinConfigureProxyForUrl.
1273 *
1274 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1275 * @param pThis The HTTP client instance.
1276 * @param pszUrl The URL.
1277 */
1278static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies,
1279 const char *pszUrl, const char *pszHost)
1280{
1281 CFArrayRef hArray;
1282
1283 /*
1284 * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply
1285 * proxy exclusion rules (tested on 10.9). So, do that manually.
1286 */
1287 RTNETADDRU HostAddr;
1288 int fIsHostIpv4Address = -1;
1289 char szTmp[_4K];
1290
1291 /* If we've got a simple hostname, something containing no dots, we must check
1292 whether such simple hostnames are excluded from proxying by default or not. */
1293 if (strchr(pszHost, '.') == NULL)
1294 {
1295 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false))
1296 return rtHttpUpdateAutomaticProxyDisable(pThis);
1297 fIsHostIpv4Address = false;
1298 }
1299
1300 /* Consult the exclusion list. This is an array of strings.
1301 This is very similar to what we do on windows. */
1302 hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList);
1303 if (hArray)
1304 {
1305 CFIndex const cEntries = CFArrayGetCount(hArray);
1306 for (CFIndex i = 0; i < cEntries; i++)
1307 {
1308 CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i);
1309 AssertContinue(hStr);
1310 AssertContinue(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8));
1311 RTStrToLower(szTmp);
1312
1313 bool fRet;
1314 if ( strchr(szTmp, '*')
1315 || strchr(szTmp, '?'))
1316 fRet = RTStrSimplePatternMatch(szTmp, pszHost);
1317 else
1318 {
1319 if (fIsHostIpv4Address == -1)
1320 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1321 RTNETADDRIPV4 Network, Netmask;
1322 if ( fIsHostIpv4Address
1323 && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) )
1324 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1325 else
1326 fRet = strcmp(szTmp, pszHost) == 0;
1327 }
1328 if (fRet)
1329 return rtHttpUpdateAutomaticProxyDisable(pThis);
1330 }
1331 }
1332
1333#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */
1334 /*
1335 * Is proxy auto config (PAC) enabled? If so, we must consult it first.
1336 */
1337 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false))
1338 {
1339 /* Convert the auto config url string to a CFURL object. */
1340 CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString);
1341 if (hStrAutoConfigUrl)
1342 {
1343 if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8))
1344 {
1345 CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp);
1346 if (hUrlScript)
1347 {
1348 int rcRet = VINF_NOT_SUPPORTED;
1349 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1350 if (hUrlTarget)
1351 {
1352 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1353 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1354 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1355 if (hArray)
1356 CFRelease(hArray);
1357
1358 hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1359 if (hArray)
1360 {
1361 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1362 CFRelease(hArray);
1363 }
1364 }
1365 CFRelease(hUrlScript);
1366 if (rcRet != VINF_NOT_SUPPORTED)
1367 return rcRet;
1368 }
1369 }
1370 }
1371 }
1372
1373 /*
1374 * Try static proxy configs.
1375 */
1376 /** @todo later if needed. */
1377 return VERR_NOT_SUPPORTED;
1378
1379#else
1380 /*
1381 * Simple solution - "just" use CFNetworkCopyProxiesForURL.
1382 */
1383 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1384 AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR);
1385 int rcRet = VINF_NOT_SUPPORTED;
1386
1387 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1388 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1389 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1390 if (hArray)
1391 CFRelease(hArray);
1392
1393 /* The actual run. */
1394 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies);
1395 if (hArray)
1396 {
1397 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/);
1398 CFRelease(hArray);
1399 }
1400 CFRelease(hUrlTarget);
1401
1402 return rcRet;
1403#endif
1404}
1405
1406/**
1407 * Reconfigures the cURL proxy settings for the given URL, OS X style.
1408 *
1409 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1410 * @param pThis The HTTP client instance.
1411 * @param pszUrl The URL.
1412 */
1413static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1414{
1415 /*
1416 * Parse the URL, if there isn't any host name (like for file:///xxx.txt)
1417 * we don't need to run thru proxy settings to know what to do.
1418 */
1419 RTURIPARSED Parsed;
1420 int rc = RTUriParse(pszUrl, &Parsed);
1421 AssertRCReturn(rc, false);
1422 if (Parsed.cchAuthorityHost == 0)
1423 return rtHttpUpdateAutomaticProxyDisable(pThis);
1424 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1425 AssertReturn(pszHost, VERR_NO_STR_MEMORY);
1426 RTStrToLower(pszHost);
1427
1428 /*
1429 * Get a copy of the proxy settings (10.6 API).
1430 */
1431 CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */
1432 if (hDictProxies)
1433 rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, pszHost);
1434 else
1435 rc = VINF_NOT_SUPPORTED;
1436 CFRelease(hDictProxies);
1437
1438 RTStrFree(pszHost);
1439 return rc;
1440}
1441
1442#endif /* RT_OS_DARWIN */
1443
1444#ifdef RT_OS_WINDOWS
1445
1446/**
1447 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
1448 */
1449static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
1450{
1451 /*
1452 * winhttp.dll is not present on NT4 and probably was first introduced with XP.
1453 */
1454 RTLDRMOD hMod;
1455 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
1456 if (RT_SUCCESS(rc))
1457 {
1458 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
1459 if (RT_SUCCESS(rc))
1460 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
1461 if (RT_SUCCESS(rc))
1462 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
1463 if (RT_SUCCESS(rc))
1464 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
1465 if (RT_SUCCESS(rc))
1466 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
1467 RTLdrClose(hMod);
1468 AssertRC(rc);
1469 }
1470 else
1471 AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc));
1472
1473 NOREF(pvUser);
1474 return rc;
1475}
1476
1477
1478/**
1479 * Matches the URL against the given Windows by-pass list.
1480 *
1481 * @returns true if we should by-pass the proxy for this URL, false if not.
1482 * @param pszUrl The URL.
1483 * @param pwszBypass The Windows by-pass list.
1484 */
1485static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass)
1486{
1487 /*
1488 * Don't bother parsing the URL if we've actually got nothing to work with
1489 * in the by-pass list.
1490 */
1491 if (!pwszBypass)
1492 return false;
1493
1494 RTUTF16 wc;
1495 while ( (wc = *pwszBypass) != '\0'
1496 && ( RTUniCpIsSpace(wc)
1497 || wc == ';') )
1498 pwszBypass++;
1499 if (wc == '\0')
1500 return false;
1501
1502 /*
1503 * We now need to parse the URL and extract the host name.
1504 */
1505 RTURIPARSED Parsed;
1506 int rc = RTUriParse(pszUrl, &Parsed);
1507 AssertRCReturn(rc, false);
1508 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1509 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
1510 return false;
1511 RTStrToLower(pszHost);
1512
1513 bool fRet = false;
1514 char *pszBypassFree;
1515 rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree);
1516 if (RT_SUCCESS(rc))
1517 {
1518 /*
1519 * Walk the by-pass list.
1520 *
1521 * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx
1522 * a by-pass list is semicolon delimited list. The entries are either host
1523 * names or IP addresses, and may use wildcard ('*', '?', I guess). There
1524 * special "<local>" entry matches anything without a dot.
1525 */
1526 RTNETADDRU HostAddr = { 0, 0 };
1527 int fIsHostIpv4Address = -1;
1528 char *pszEntry = pszBypassFree;
1529 while (*pszEntry != '\0')
1530 {
1531 /*
1532 * Find end of entry.
1533 */
1534 char ch;
1535 size_t cchEntry = 1;
1536 while ( (ch = pszEntry[cchEntry]) != '\0'
1537 && ch != ';'
1538 && !RT_C_IS_SPACE(ch))
1539 cchEntry++;
1540
1541 char chSaved = pszEntry[cchEntry];
1542 pszEntry[cchEntry] = '\0';
1543 RTStrToLower(pszEntry);
1544
1545 if ( cchEntry == sizeof("<local>") - 1
1546 && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0)
1547 fRet = strchr(pszHost, '.') == NULL;
1548 else if ( memchr(pszEntry, '*', cchEntry) != NULL
1549 || memchr(pszEntry, '?', cchEntry) != NULL)
1550 fRet = RTStrSimplePatternMatch(pszEntry, pszHost);
1551 else
1552 {
1553 if (fIsHostIpv4Address == -1)
1554 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1555 RTNETADDRIPV4 Network, Netmask;
1556 if ( fIsHostIpv4Address
1557 && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) )
1558 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1559 else
1560 fRet = strcmp(pszEntry, pszHost) == 0;
1561 }
1562
1563 pszEntry[cchEntry] = chSaved;
1564 if (fRet)
1565 break;
1566
1567 /*
1568 * Next entry.
1569 */
1570 pszEntry += cchEntry;
1571 while ( (ch = *pszEntry) != '\0'
1572 && ( ch == ';'
1573 || RT_C_IS_SPACE(ch)) )
1574 pszEntry++;
1575 }
1576
1577 RTStrFree(pszBypassFree);
1578 }
1579
1580 RTStrFree(pszHost);
1581 return false;
1582}
1583
1584
1585/**
1586 * Searches a Windows proxy server list for the best fitting proxy to use, then
1587 * reconfigures the HTTP client instance to use it.
1588 *
1589 * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback.
1590 * @param pThis The HTTP client instance.
1591 * @param pszUrl The URL needing proxying.
1592 * @param pwszProxies The list of proxy servers to choose from.
1593 */
1594static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies)
1595{
1596 /*
1597 * Fend off empty strings (very unlikely, but just in case).
1598 */
1599 if (!pwszProxies)
1600 return VINF_NOT_SUPPORTED;
1601
1602 RTUTF16 wc;
1603 while ( (wc = *pwszProxies) != '\0'
1604 && ( RTUniCpIsSpace(wc)
1605 || wc == ';') )
1606 pwszProxies++;
1607 if (wc == '\0')
1608 return VINF_NOT_SUPPORTED;
1609
1610 /*
1611 * We now need to parse the URL and extract the scheme.
1612 */
1613 RTURIPARSED Parsed;
1614 int rc = RTUriParse(pszUrl, &Parsed);
1615 AssertRCReturn(rc, false);
1616 char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed);
1617 AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY);
1618 size_t const cchUrlScheme = strlen(pszUrlScheme);
1619
1620 int rcRet = VINF_NOT_SUPPORTED;
1621 char *pszProxiesFree;
1622 rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree);
1623 if (RT_SUCCESS(rc))
1624 {
1625 /*
1626 * Walk the server list.
1627 *
1628 * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx
1629 * this is also a semicolon delimited list. The entries are on the form:
1630 * [<scheme>=][<scheme>"://"]<server>[":"<port>]
1631 */
1632 bool fBestEntryHasSameScheme = false;
1633 const char *pszBestEntry = NULL;
1634 char *pszEntry = pszProxiesFree;
1635 while (*pszEntry != '\0')
1636 {
1637 /*
1638 * Find end of entry. We include spaces here in addition to ';'.
1639 */
1640 char ch;
1641 size_t cchEntry = 1;
1642 while ( (ch = pszEntry[cchEntry]) != '\0'
1643 && ch != ';'
1644 && !RT_C_IS_SPACE(ch))
1645 cchEntry++;
1646
1647 char const chSaved = pszEntry[cchEntry];
1648 pszEntry[cchEntry] = '\0';
1649
1650 /* Parse the entry. */
1651 const char *pszEndOfScheme = strstr(pszEntry, "://");
1652 const char *pszEqual = (const char *)memchr(pszEntry, '=',
1653 pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry);
1654 if (pszEqual)
1655 {
1656 if ( (uintptr_t)(pszEqual - pszEntry) == cchUrlScheme
1657 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0)
1658 {
1659 pszBestEntry = pszEqual + 1;
1660 break;
1661 }
1662 }
1663 else
1664 {
1665 bool fSchemeMatch = pszEndOfScheme
1666 && (uintptr_t)(pszEndOfScheme - pszEntry) == cchUrlScheme
1667 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0;
1668 if ( !pszBestEntry
1669 || ( !fBestEntryHasSameScheme
1670 && fSchemeMatch) )
1671 {
1672 pszBestEntry = pszEntry;
1673 fBestEntryHasSameScheme = fSchemeMatch;
1674 }
1675 }
1676
1677 /*
1678 * Next entry.
1679 */
1680 if (!chSaved)
1681 break;
1682 pszEntry += cchEntry + 1;
1683 while ( (ch = *pszEntry) != '\0'
1684 && ( ch == ';'
1685 || RT_C_IS_SPACE(ch)) )
1686 pszEntry++;
1687 }
1688
1689 /*
1690 * If we found something, try use it.
1691 */
1692 if (pszBestEntry)
1693 rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry);
1694
1695 RTStrFree(pszProxiesFree);
1696 }
1697
1698 RTStrFree(pszUrlScheme);
1699 return rc;
1700}
1701
1702
1703/**
1704 * Reconfigures the cURL proxy settings for the given URL, Windows style.
1705 *
1706 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1707 * @param pThis The HTTP client instance.
1708 * @param pszUrl The URL.
1709 */
1710static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1711{
1712 int rcRet = VINF_NOT_SUPPORTED;
1713
1714 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
1715 if (RT_SUCCESS(rc))
1716 {
1717 /*
1718 * Try get some proxy info for the URL. We first try getting the IE
1719 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
1720 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
1721 */
1722 WINHTTP_PROXY_INFO ProxyInfo;
1723 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
1724 RT_ZERO(AutoProxyOptions);
1725 RT_ZERO(ProxyInfo);
1726
1727 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
1728 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
1729 {
1730 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1731 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
1732 if (IeProxyConfig.fAutoDetect)
1733 {
1734 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
1735 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
1736 }
1737 else if (AutoProxyOptions.lpszAutoConfigUrl)
1738 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1739 else if (ProxyInfo.lpszProxy)
1740 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
1741 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
1742 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
1743 }
1744 else
1745 {
1746 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
1747 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
1748 {
1749 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
1750 RT_ZERO(ProxyInfo);
1751 }
1752 }
1753
1754 /*
1755 * Should we try WinHttGetProxyForUrl?
1756 */
1757 if (AutoProxyOptions.dwFlags != 0)
1758 {
1759 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
1760 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
1761 if (hSession != NULL)
1762 {
1763 PRTUTF16 pwszUrl;
1764 rc = RTStrToUtf16(pszUrl, &pwszUrl);
1765 if (RT_SUCCESS(rc))
1766 {
1767 /*
1768 * Try autodetect first, then fall back on the config URL if there is one.
1769 *
1770 * Also, we first try without auto authentication, then with. This will according
1771 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
1772 * caching the result when it's processed out-of-process (seems default here on W10).
1773 */
1774 WINHTTP_PROXY_INFO TmpProxyInfo;
1775 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1776 if ( !fRc
1777 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1778 {
1779 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1780 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1781 }
1782
1783 if ( !fRc
1784 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
1785 && AutoProxyOptions.lpszAutoConfigUrl)
1786 {
1787 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1788 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1789 AutoProxyOptions.dwAutoDetectFlags = 0;
1790 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1791 if ( !fRc
1792 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1793 {
1794 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1795 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1796 }
1797 }
1798
1799 if (fRc)
1800 {
1801 if (ProxyInfo.lpszProxy)
1802 GlobalFree(ProxyInfo.lpszProxy);
1803 if (ProxyInfo.lpszProxyBypass)
1804 GlobalFree(ProxyInfo.lpszProxyBypass);
1805 ProxyInfo = TmpProxyInfo;
1806 }
1807 /*
1808 * If the autodetection failed, assume no proxy.
1809 */
1810 else
1811 {
1812 DWORD dwErr = GetLastError();
1813 if ( dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED
1814 || dwErr == ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT
1815 || ( dwErr == ERROR_WINHTTP_UNRECOGNIZED_SCHEME
1816 && ( RTStrNICmp(pszUrl, RT_STR_TUPLE("https://")) == 0
1817 || RTStrNICmp(pszUrl, RT_STR_TUPLE("http://")) == 0) ) )
1818 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1819 else
1820 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl(%s) -> %u; lpszAutoConfigUrl=%sx\n",
1821 pszUrl, dwErr, AutoProxyOptions.lpszAutoConfigUrl));
1822 }
1823 RTUtf16Free(pwszUrl);
1824 }
1825 else
1826 {
1827 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
1828 rcRet = rc;
1829 }
1830 g_pfnWinHttpCloseHandle(hSession);
1831 }
1832 else
1833 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
1834 }
1835
1836 /*
1837 * Try use the proxy info we've found.
1838 */
1839 switch (ProxyInfo.dwAccessType)
1840 {
1841 case WINHTTP_ACCESS_TYPE_NO_PROXY:
1842 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1843 break;
1844
1845 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
1846 if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass))
1847 rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy);
1848 else
1849 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1850 break;
1851
1852 case 0:
1853 break;
1854
1855 default:
1856 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
1857 }
1858
1859 /*
1860 * Cleanup.
1861 */
1862 if (ProxyInfo.lpszProxy)
1863 GlobalFree(ProxyInfo.lpszProxy);
1864 if (ProxyInfo.lpszProxyBypass)
1865 GlobalFree(ProxyInfo.lpszProxyBypass);
1866 if (AutoProxyOptions.lpszAutoConfigUrl)
1867 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
1868 }
1869
1870 return rcRet;
1871}
1872
1873#endif /* RT_OS_WINDOWS */
1874
1875
1876static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1877{
1878 if (pThis->fUseSystemProxySettings)
1879 {
1880#ifdef IPRT_USE_LIBPROXY
1881 int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl);
1882 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1883 return rc;
1884 Assert(rc == VINF_NOT_SUPPORTED);
1885#endif
1886#ifdef RT_OS_DARWIN
1887 int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl);
1888 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1889 return rc;
1890 Assert(rc == VINF_NOT_SUPPORTED);
1891#endif
1892#ifdef RT_OS_WINDOWS
1893 int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl);
1894 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
1895 return rc;
1896 Assert(rc == VINF_NOT_SUPPORTED);
1897#endif
1898/** @todo system specific class here, fall back on env vars if necessary. */
1899 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
1900 }
1901
1902 return VINF_SUCCESS;
1903}
1904
1905
1906RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
1907 const char *pcszProxyUser, const char *pcszProxyPwd)
1908{
1909 PRTHTTPINTERNAL pThis = hHttp;
1910 RTHTTP_VALID_RETURN(pThis);
1911 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
1912 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
1913
1914 /*
1915 * Update the settings.
1916 *
1917 * Currently, we don't make alot of effort parsing or checking the input, we
1918 * leave that to cURL. (A bit afraid of breaking user settings.)
1919 */
1920 pThis->fUseSystemProxySettings = false;
1921 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
1922}
1923
1924
1925
1926/*********************************************************************************************************************************
1927* HTTP Headers *
1928*********************************************************************************************************************************/
1929
1930/**
1931 * Helper for RTHttpSetHeaders and RTHttpAppendHeader that unsets the user agent
1932 * if it is now in one of the headers.
1933 */
1934static void rtHttpUpdateUserAgentHeader(PRTHTTPINTERNAL pThis)
1935{
1936 if ( pThis->fHaveUserAgentHeader
1937 && pThis->fHaveSetUserAgent)
1938 {
1939 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL);
1940 Assert(CURL_SUCCESS(rcCurl)); NOREF(rcCurl);
1941 pThis->fHaveSetUserAgent = false;
1942 }
1943}
1944
1945
1946RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
1947{
1948 PRTHTTPINTERNAL pThis = hHttp;
1949 RTHTTP_VALID_RETURN(pThis);
1950
1951 /*
1952 * Drop old headers and reset state.
1953 */
1954 if (pThis->pHeaders)
1955 {
1956 curl_slist_free_all(pThis->pHeaders);
1957 pThis->pHeaders = NULL;
1958 curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
1959 }
1960 pThis->fHaveUserAgentHeader = false;
1961
1962 /*
1963 * We're done if no headers specified.
1964 */
1965 if (!cHeaders)
1966 return VINF_SUCCESS;
1967
1968 /*
1969 * Convert the headers into a curl string list, checkig each string for User-Agent.
1970 */
1971 struct curl_slist *pHeaders = NULL;
1972 for (size_t i = 0; i < cHeaders; i++)
1973 {
1974 struct curl_slist *pNewHeaders = curl_slist_append(pHeaders, papszHeaders[i]);
1975 if (pNewHeaders)
1976 pHeaders = pNewHeaders;
1977 else
1978 {
1979 if (pHeaders)
1980 curl_slist_free_all(pHeaders);
1981 return VERR_NO_MEMORY;
1982 }
1983
1984 if (strncmp(papszHeaders[i], RT_STR_TUPLE("User-Agent:")) == 0)
1985 pThis->fHaveUserAgentHeader = true;
1986 }
1987
1988 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
1989 if (CURL_FAILURE(rcCurl))
1990 {
1991 curl_slist_free_all(pHeaders);
1992 return VERR_INVALID_PARAMETER;
1993 }
1994 pThis->pHeaders = pHeaders;
1995
1996 rtHttpUpdateUserAgentHeader(pThis);
1997
1998 return VINF_SUCCESS;
1999}
2000
2001
2002RTR3DECL(int) RTHttpAppendRawHeader(RTHTTP hHttp, const char *pszHeader)
2003{
2004 PRTHTTPINTERNAL pThis = hHttp;
2005 RTHTTP_VALID_RETURN(pThis);
2006
2007 /*
2008 * Append it to the header list, checking for User-Agent and such.
2009 */
2010 struct curl_slist *pHeaders = pThis->pHeaders;
2011 struct curl_slist *pNewHeaders = curl_slist_append(pHeaders, pszHeader);
2012 if (pNewHeaders)
2013 pHeaders = pNewHeaders;
2014 else
2015 return VERR_NO_MEMORY;
2016
2017 if (strncmp(pszHeader, RT_STR_TUPLE("User-Agent:")) == 0)
2018 pThis->fHaveUserAgentHeader = true;
2019
2020 /*
2021 * If this is the first header, we need to tell curl.
2022 */
2023 if (!pThis->pHeaders)
2024 {
2025 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
2026 if (CURL_FAILURE(rcCurl))
2027 {
2028 curl_slist_free_all(pHeaders);
2029 return VERR_INVALID_PARAMETER;
2030 }
2031 pThis->pHeaders = pHeaders;
2032 }
2033 else
2034 Assert(pThis->pHeaders == pHeaders);
2035
2036 rtHttpUpdateUserAgentHeader(pThis);
2037
2038 return VINF_SUCCESS;
2039}
2040
2041
2042RTR3DECL(int) RTHttpAppendHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, uint32_t fFlags)
2043{
2044
2045 /*
2046 * Currently we don't any encoding here, so we just glue the two strings together.
2047 */
2048 AssertPtr(pszField);
2049 size_t const cchField = strlen(pszField);
2050 AssertReturn(cchField > 0, VERR_INVALID_PARAMETER);
2051 AssertReturn(pszField[cchField - 1] != ':', VERR_INVALID_PARAMETER);
2052 AssertReturn(!RT_C_IS_SPACE(pszField[cchField - 1]), VERR_INVALID_PARAMETER);
2053#ifdef RT_STRICT
2054 for (size_t i = 0; i < cchField; i++)
2055 {
2056 char const ch = pszField[i];
2057 Assert(RT_C_IS_PRINT(ch) && ch != ':');
2058 }
2059#endif
2060
2061 AssertPtr(pszValue);
2062 size_t const cchValue = strlen(pszValue);
2063
2064 AssertReturn(!fFlags, VERR_INVALID_FLAGS);
2065
2066 /*
2067 * Allocate a temporary buffer, construct the raw header string in it,
2068 * then use RTHttpAppendRawHeader to do the grunt work.
2069 */
2070 size_t const cbNeeded = cchField + 2 + cchValue + 1;
2071 char *pszHeaderFree = NULL;
2072 char *pszHeader;
2073 if (cbNeeded < _2K)
2074 pszHeader = (char *)alloca(cbNeeded);
2075 else
2076 pszHeaderFree = pszHeader = (char *)RTMemTmpAlloc(cbNeeded);
2077 if (!pszHeader)
2078 return VERR_NO_TMP_MEMORY;
2079
2080 memcpy(pszHeader, pszField, cchField);
2081 pszHeader[cchField] = ':';
2082 pszHeader[cchField + 1] = ' ';
2083 memcpy(&pszHeader[cchField + 2], pszValue, cchValue);
2084 pszHeader[cbNeeded - 1] = '\0';
2085
2086 int rc = RTHttpAppendRawHeader(hHttp, pszHeader);
2087
2088 if (pszHeaderFree)
2089 RTMemTmpFree(pszHeaderFree);
2090 return rc;
2091}
2092
2093
2094
2095/*********************************************************************************************************************************
2096* HTTPS and root certficates *
2097*********************************************************************************************************************************/
2098
2099/**
2100 * Set the CA file to NULL, deleting any temporary file if necessary.
2101 *
2102 * @param pThis The HTTP/HTTPS client instance.
2103 */
2104static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
2105{
2106 if (pThis->pszCaFile)
2107 {
2108 if (pThis->fDeleteCaFile)
2109 {
2110 int rc2 = RTFileDelete(pThis->pszCaFile); RT_NOREF_PV(rc2);
2111 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
2112 }
2113 RTStrFree(pThis->pszCaFile);
2114 pThis->pszCaFile = NULL;
2115 }
2116}
2117
2118
2119RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
2120{
2121 PRTHTTPINTERNAL pThis = hHttp;
2122 RTHTTP_VALID_RETURN(pThis);
2123
2124 rtHttpUnsetCaFile(pThis);
2125
2126 pThis->fDeleteCaFile = false;
2127 if (pszCaFile)
2128 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
2129 return VINF_SUCCESS;
2130}
2131
2132
2133RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
2134{
2135 PRTHTTPINTERNAL pThis = hHttp;
2136 RTHTTP_VALID_RETURN(pThis);
2137
2138 /*
2139 * Create a temporary file.
2140 */
2141 int rc = VERR_NO_STR_MEMORY;
2142 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
2143 if (pszCaFile)
2144 {
2145 RTFILE hFile;
2146 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
2147 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
2148 if (RT_SUCCESS(rc))
2149 {
2150 /*
2151 * Gather certificates into a temporary store and export them to the temporary file.
2152 */
2153 RTCRSTORE hStore;
2154 rc = RTCrStoreCreateInMem(&hStore, 256);
2155 if (RT_SUCCESS(rc))
2156 {
2157 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
2158 if (RT_SUCCESS(rc))
2159 /** @todo Consider adding an API for exporting to a RTFILE... */
2160 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2161 RTCrStoreRelease(hStore);
2162 }
2163 RTFileClose(hFile);
2164 if (RT_SUCCESS(rc))
2165 {
2166 /*
2167 * Set the CA file for the instance.
2168 */
2169 rtHttpUnsetCaFile(pThis);
2170
2171 pThis->fDeleteCaFile = true;
2172 pThis->pszCaFile = pszCaFile;
2173 return VINF_SUCCESS;
2174 }
2175
2176 int rc2 = RTFileDelete(pszCaFile);
2177 AssertRC(rc2);
2178 }
2179 else
2180 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
2181
2182 RTStrFree(pszCaFile);
2183 }
2184 return rc;
2185}
2186
2187
2188RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
2189{
2190 uint32_t const cBefore = RTCrStoreCertCount(hStore);
2191 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
2192 RT_NOREF_PV(fFlags);
2193
2194
2195 /*
2196 * Add the user store, quitely ignoring any errors.
2197 */
2198 RTCRSTORE hSrcStore;
2199 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2200 if (RT_SUCCESS(rcUser))
2201 {
2202 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2203 hSrcStore);
2204 RTCrStoreRelease(hSrcStore);
2205 }
2206
2207 /*
2208 * Ditto for the system store.
2209 */
2210 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2211 if (RT_SUCCESS(rcSystem))
2212 {
2213 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2214 hSrcStore);
2215 RTCrStoreRelease(hSrcStore);
2216 }
2217
2218 /*
2219 * If the number of certificates increased, we consider it a success.
2220 */
2221 if (RTCrStoreCertCount(hStore) > cBefore)
2222 {
2223 if (RT_FAILURE(rcSystem))
2224 return -rcSystem;
2225 if (RT_FAILURE(rcUser))
2226 return -rcUser;
2227 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
2228 }
2229
2230 if (RT_FAILURE(rcSystem))
2231 return rcSystem;
2232 if (RT_FAILURE(rcUser))
2233 return rcUser;
2234 return VERR_NOT_FOUND;
2235}
2236
2237
2238RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
2239{
2240 RTCRSTORE hStore;
2241 int rc = RTCrStoreCreateInMem(&hStore, 256);
2242 if (RT_SUCCESS(rc))
2243 {
2244 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2245 if (RT_SUCCESS(rc))
2246 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2247 RTCrStoreRelease(hStore);
2248 }
2249 return rc;
2250}
2251
2252
2253
2254/*********************************************************************************************************************************
2255* .......
2256*********************************************************************************************************************************/
2257
2258
2259/**
2260 * Figures out the IPRT status code for a GET.
2261 *
2262 * @returns IPRT status code.
2263 * @param pThis The HTTP/HTTPS client instance.
2264 * @param rcCurl What curl returned.
2265 * @param puHttpStatus Where to optionally return the HTTP status. If specified,
2266 * the HTTP statuses are not translated to IPRT status codes.
2267 */
2268static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl, uint32_t *puHttpStatus)
2269{
2270 int rc = VERR_HTTP_CURL_ERROR;
2271
2272 if (pThis->pszRedirLocation)
2273 {
2274 RTStrFree(pThis->pszRedirLocation);
2275 pThis->pszRedirLocation = NULL;
2276 }
2277 if (rcCurl == CURLE_OK)
2278 {
2279 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2280 if (puHttpStatus)
2281 {
2282 *puHttpStatus = pThis->lLastResp;
2283 rc = VINF_SUCCESS;
2284 }
2285
2286 switch (pThis->lLastResp)
2287 {
2288 case 200:
2289 /* OK, request was fulfilled */
2290 case 204:
2291 /* empty response */
2292 rc = VINF_SUCCESS;
2293 break;
2294 case 301: /* Moved permantently. */
2295 case 302: /* Found / Moved temporarily. */
2296 case 303: /* See Other. */
2297 case 307: /* Temporary redirect. */
2298 case 308: /* Permanent redirect. */
2299 {
2300 const char *pszRedirect = NULL;
2301 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2302 size_t cb = pszRedirect ? strlen(pszRedirect) : 0;
2303 if (cb > 0 && cb < 2048)
2304 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2305 if (!puHttpStatus)
2306 rc = VERR_HTTP_REDIRECTED;
2307 break;
2308 }
2309 case 400:
2310 /* bad request */
2311 if (!puHttpStatus)
2312 rc = VERR_HTTP_BAD_REQUEST;
2313 break;
2314 case 403:
2315 /* forbidden, authorization will not help */
2316 if (!puHttpStatus)
2317 rc = VERR_HTTP_ACCESS_DENIED;
2318 break;
2319 case 404:
2320 /* URL not found */
2321 if (!puHttpStatus)
2322 rc = VERR_HTTP_NOT_FOUND;
2323 break;
2324 }
2325
2326 if (pThis->pszRedirLocation)
2327 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2328 else
2329 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2330 }
2331 else
2332 {
2333 switch (rcCurl)
2334 {
2335 case CURLE_URL_MALFORMAT:
2336 case CURLE_COULDNT_RESOLVE_HOST:
2337 rc = VERR_HTTP_HOST_NOT_FOUND;
2338 break;
2339 case CURLE_COULDNT_CONNECT:
2340 rc = VERR_HTTP_COULDNT_CONNECT;
2341 break;
2342 case CURLE_SSL_CONNECT_ERROR:
2343 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2344 break;
2345 case CURLE_SSL_CACERT:
2346 /* The peer certificate cannot be authenticated with the CA certificates
2347 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2348 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2349 break;
2350 case CURLE_SSL_CACERT_BADFILE:
2351 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2352 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2353 break;
2354 case CURLE_ABORTED_BY_CALLBACK:
2355 /* forcefully aborted */
2356 rc = VERR_HTTP_ABORTED;
2357 break;
2358 case CURLE_COULDNT_RESOLVE_PROXY:
2359 rc = VERR_HTTP_PROXY_NOT_FOUND;
2360 break;
2361 case CURLE_WRITE_ERROR:
2362 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2363 break;
2364 //case CURLE_READ_ERROR
2365
2366 default:
2367 break;
2368 }
2369 Log(("rtHttpGetCalcStatus: rc=%Rrc rcCurl=%u\n", rc, rcCurl));
2370 }
2371
2372 return rc;
2373}
2374
2375
2376/**
2377 * cURL callback for reporting progress, we use it for checking for abort.
2378 */
2379static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
2380{
2381 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2382 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2383 RT_NOREF_PV(rdTotalUpload);
2384 RT_NOREF_PV(rdUploaded);
2385
2386 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2387
2388 if (pThis->pfnDownloadProgress)
2389 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2390
2391 return pThis->fAbort ? 1 : 0;
2392}
2393
2394
2395/**
2396 * Whether we're likely to need SSL to handle the give URL.
2397 *
2398 * @returns true if we need, false if we probably don't.
2399 * @param pszUrl The URL.
2400 */
2401static bool rtHttpNeedSsl(const char *pszUrl)
2402{
2403 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2404}
2405
2406
2407/**
2408 * Applies recoded settings to the cURL instance before doing work.
2409 *
2410 * @returns IPRT status code.
2411 * @param pThis The HTTP/HTTPS client instance.
2412 * @param pszUrl The URL.
2413 */
2414static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2415{
2416 /*
2417 * The URL.
2418 */
2419 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2420 if (CURL_FAILURE(rcCurl))
2421 return VERR_INVALID_PARAMETER;
2422
2423 /*
2424 * Proxy config.
2425 */
2426 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2427 if (RT_FAILURE(rc))
2428 return rc;
2429
2430 /*
2431 * Setup SSL. Can be a bit of work.
2432 */
2433 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2434 if (CURL_FAILURE(rcCurl))
2435 return VERR_INVALID_PARAMETER;
2436
2437 const char *pszCaFile = pThis->pszCaFile;
2438 if ( !pszCaFile
2439 && rtHttpNeedSsl(pszUrl))
2440 {
2441 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2442 if (RT_SUCCESS(rc))
2443 pszCaFile = pThis->pszCaFile;
2444 else
2445 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2446 }
2447 if (pszCaFile)
2448 {
2449 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2450 if (CURL_FAILURE(rcCurl))
2451 return VERR_HTTP_CURL_ERROR;
2452 }
2453
2454 /*
2455 * Progress/abort.
2456 */
2457 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2458 if (CURL_FAILURE(rcCurl))
2459 return VERR_HTTP_CURL_ERROR;
2460 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2461 if (CURL_FAILURE(rcCurl))
2462 return VERR_HTTP_CURL_ERROR;
2463 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2464 if (CURL_FAILURE(rcCurl))
2465 return VERR_HTTP_CURL_ERROR;
2466
2467 /*
2468 * Set default user agent string if necessary. Some websites take offence
2469 * if we don't set it.
2470 */
2471 if ( !pThis->fHaveSetUserAgent
2472 && !pThis->fHaveUserAgentHeader)
2473 {
2474 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2475 if (CURL_FAILURE(rcCurl))
2476 return VERR_HTTP_CURL_ERROR;
2477 pThis->fHaveSetUserAgent = true;
2478 }
2479
2480 /*
2481 * Use GET by default.
2482 */
2483 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
2484 if (CURL_FAILURE(rcCurl))
2485 return VERR_HTTP_CURL_ERROR;
2486 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
2487 if (CURL_FAILURE(rcCurl))
2488 return VERR_HTTP_CURL_ERROR;
2489
2490 return VINF_SUCCESS;
2491}
2492
2493
2494/**
2495 * Resets state.
2496 *
2497 * @param pThis HTTP client instance.
2498 */
2499static void rtHttpResetState(PRTHTTPINTERNAL pThis)
2500{
2501 pThis->fAbort = false;
2502 pThis->rcOutput = VINF_SUCCESS;
2503 pThis->cbDownloadHint = 0;
2504 Assert(pThis->BodyOutput.pHttp == pThis);
2505 Assert(pThis->HeadersOutput.pHttp == pThis);
2506}
2507
2508
2509
2510/**
2511 * cURL callback for writing data.
2512 */
2513static size_t rtHttpWriteData(char *pcBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2514{
2515 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
2516 PRTHTTPINTERNAL pThis = pOutput->pHttp;
2517
2518 /*
2519 * Do max size and overflow checks.
2520 */
2521 size_t const cbToAppend = cbUnit * cUnits;
2522 size_t const cbCurSize = pOutput->uData.Mem.cb;
2523 size_t const cbNewSize = cbCurSize + cbToAppend;
2524 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2525 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2526 {
2527 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
2528 {
2529 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pcBuf, cbToAppend);
2530 pOutput->uData.Mem.cb = cbNewSize;
2531 pOutput->uData.Mem.pb[cbNewSize] = '\0';
2532 return cbToAppend;
2533 }
2534
2535 /*
2536 * We need to reallocate the output buffer.
2537 */
2538 /** @todo this could do with a better strategy wrt growth. */
2539 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
2540 if ( cbAlloc <= pThis->cbDownloadHint
2541 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2542 && pOutput == &pThis->BodyOutput)
2543 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
2544
2545 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
2546 if (pbNew)
2547 {
2548 memcpy(&pbNew[cbCurSize], pcBuf, cbToAppend);
2549 pbNew[cbNewSize] = '\0';
2550
2551 pOutput->uData.Mem.cbAllocated = cbAlloc;
2552 pOutput->uData.Mem.pb = pbNew;
2553 pOutput->uData.Mem.cb = cbNewSize;
2554 return cbToAppend;
2555 }
2556
2557 pThis->rcOutput = VERR_NO_MEMORY;
2558 }
2559 else
2560 pThis->rcOutput = VERR_TOO_MUCH_DATA;
2561
2562 /*
2563 * Failure - abort.
2564 */
2565 RTMemFree(pOutput->uData.Mem.pb);
2566 pOutput->uData.Mem.pb = NULL;
2567 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
2568 pThis->fAbort = true;
2569 return 0;
2570}
2571
2572
2573/**
2574 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
2575 */
2576static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser)
2577{
2578 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2579 size_t const cbReq = cbUnit * cUnits;
2580 size_t const offMem = pThis->ReadData.Mem.offMem;
2581 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
2582 if (cbToCopy > cbReq)
2583 cbToCopy = cbReq;
2584 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
2585 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
2586 return cbToCopy;
2587}
2588
2589
2590/**
2591 * Helper for installing a (body) write callback function.
2592 *
2593 * @returns cURL status code.
2594 * @param pThis The HTTP client instance.
2595 * @param pfnWrite The callback.
2596 * @param pvUser The callback user argument.
2597 */
2598static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACK pfnWrite, void *pvUser)
2599{
2600 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
2601 if (CURL_SUCCESS(rcCurl))
2602 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
2603 return rcCurl;
2604}
2605
2606
2607/**
2608 * Helper for installing a header write callback function.
2609 *
2610 * @returns cURL status code.
2611 * @param pThis The HTTP client instance.
2612 * @param pfnWrite The callback.
2613 * @param pvUser The callback user argument.
2614 */
2615static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACK pfnWrite, void *pvUser)
2616{
2617 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
2618 if (CURL_SUCCESS(rcCurl))
2619 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
2620 return rcCurl;
2621}
2622
2623
2624/**
2625 * Helper for installing a (body) read callback function.
2626 *
2627 * @returns cURL status code.
2628 * @param pThis The HTTP client instance.
2629 * @param pfnRead The callback.
2630 * @param pvUser The callback user argument.
2631 */
2632static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACK pfnRead, void *pvUser)
2633{
2634 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
2635 if (CURL_SUCCESS(rcCurl))
2636 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
2637 return rcCurl;
2638}
2639
2640
2641/**
2642 * Internal worker that performs a HTTP GET.
2643 *
2644 * @returns IPRT status code.
2645 * @param hHttp The HTTP/HTTPS client instance.
2646 * @param pszUrl The URL.
2647 * @param fNoBody Set to suppress the body.
2648 * @param ppvResponse Where to return the pointer to the allocated
2649 * response data (RTMemFree). There will always be
2650 * an zero terminator char after the response, that
2651 * is not part of the size returned via @a pcb.
2652 * @param pcb The size of the response data.
2653 *
2654 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
2655 * threads, because that will probably blow up!
2656 */
2657static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
2658{
2659 PRTHTTPINTERNAL pThis = hHttp;
2660 RTHTTP_VALID_RETURN(pThis);
2661
2662 /*
2663 * Reset the return values in case of more "GUI programming" on the client
2664 * side (i.e. a programming style not bothering checking return codes).
2665 */
2666 *ppvResponse = NULL;
2667 *pcb = 0;
2668
2669 /*
2670 * Set the busy flag (paranoia).
2671 */
2672 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2673 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2674
2675 /*
2676 * Reset the state and apply settings.
2677 */
2678 rtHttpResetState(pThis);
2679 int rc = rtHttpApplySettings(hHttp, pszUrl);
2680 if (RT_SUCCESS(rc))
2681 {
2682 RT_ZERO(pThis->BodyOutput.uData.Mem);
2683 int rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteData, (void *)&pThis->BodyOutput);
2684 if (fNoBody)
2685 {
2686 if (CURL_SUCCESS(rcCurl))
2687 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
2688 if (CURL_SUCCESS(rcCurl))
2689 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
2690 }
2691 if (CURL_SUCCESS(rcCurl))
2692 {
2693 /*
2694 * Perform the HTTP operation.
2695 */
2696 rcCurl = curl_easy_perform(pThis->pCurl);
2697 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
2698 if (RT_SUCCESS(rc))
2699 rc = pThis->rcOutput;
2700 if (RT_SUCCESS(rc))
2701 {
2702 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
2703 *pcb = pThis->BodyOutput.uData.Mem.cb;
2704 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
2705 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
2706 }
2707 else if (pThis->BodyOutput.uData.Mem.pb)
2708 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
2709 RT_ZERO(pThis->BodyOutput.uData.Mem);
2710 }
2711 else
2712 rc = VERR_HTTP_CURL_ERROR;
2713 }
2714
2715 ASMAtomicWriteBool(&pThis->fBusy, false);
2716 return rc;
2717}
2718
2719
2720RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2721{
2722 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2723 uint8_t *pv;
2724 size_t cb;
2725 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
2726 if (RT_SUCCESS(rc))
2727 {
2728 if (pv) /* paranoia */
2729 *ppszNotUtf8 = (char *)pv;
2730 else
2731 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2732 }
2733 else
2734 *ppszNotUtf8 = NULL;
2735 return rc;
2736}
2737
2738
2739RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
2740{
2741 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2742 uint8_t *pv;
2743 size_t cb;
2744 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
2745 if (RT_SUCCESS(rc))
2746 {
2747 if (pv) /* paranoia */
2748 *ppszNotUtf8 = (char *)pv;
2749 else
2750 *ppszNotUtf8 = (char *)RTMemDup("", 1);
2751 }
2752 else
2753 *ppszNotUtf8 = NULL;
2754 return rc;
2755
2756}
2757
2758
2759RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
2760{
2761 RTMemFree(pszNotUtf8);
2762}
2763
2764
2765RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2766{
2767 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2768 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2769}
2770
2771
2772RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
2773{
2774 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
2775 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
2776}
2777
2778
2779RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
2780{
2781 RTMemFree(pvResponse);
2782}
2783
2784
2785/**
2786 * cURL callback for writing data to a file.
2787 */
2788static size_t rtHttpWriteDataToFile(char *pcBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2789{
2790 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
2791 PRTHTTPINTERNAL pThis = pOutput->pHttp;
2792
2793 size_t cbWritten = 0;
2794 int rc = RTFileWrite(pOutput->uData.hFile, pcBuf, cbUnit * cUnits, &cbWritten);
2795 if (RT_SUCCESS(rc))
2796 return cbWritten;
2797
2798 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
2799 pThis->rcOutput = rc;
2800 return 0;
2801}
2802
2803
2804RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
2805{
2806 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
2807 PRTHTTPINTERNAL pThis = hHttp;
2808 RTHTTP_VALID_RETURN(pThis);
2809
2810 /*
2811 * Set the busy flag (paranoia).
2812 */
2813 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2814 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2815
2816 /*
2817 * Reset the state and apply settings.
2818 */
2819 rtHttpResetState(pThis);
2820 int rc = rtHttpApplySettings(hHttp, pszUrl);
2821 if (RT_SUCCESS(rc))
2822 {
2823 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
2824 int rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
2825 if (CURL_SUCCESS(rcCurl))
2826 {
2827 /*
2828 * Open the output file.
2829 */
2830 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
2831 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
2832 if (RT_SUCCESS(rc))
2833 {
2834 /*
2835 * Perform the HTTP operation.
2836 */
2837 rcCurl = curl_easy_perform(pThis->pCurl);
2838 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
2839 if (RT_SUCCESS(rc))
2840 rc = pThis->rcOutput;
2841
2842 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
2843 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
2844 rc = rc2;
2845 }
2846 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
2847 }
2848 else
2849 rc = VERR_HTTP_CURL_ERROR;
2850 }
2851
2852 ASMAtomicWriteBool(&pThis->fBusy, false);
2853 return rc;
2854}
2855
2856
2857RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
2858 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
2859{
2860 /*
2861 * Set safe return values and validate input.
2862 */
2863 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
2864 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
2865
2866 if (ppvHeaders)
2867 *ppvHeaders = NULL;
2868 if (pcbHeaders)
2869 *pcbHeaders = 0;
2870 if (ppvBody)
2871 *ppvBody = NULL;
2872 if (pcbBody)
2873 *pcbBody = 0;
2874 if (puHttpStatus)
2875 *puHttpStatus = UINT32_MAX;
2876
2877 PRTHTTPINTERNAL pThis = hHttp;
2878 RTHTTP_VALID_RETURN(pThis);
2879 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
2880 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
2881
2882#ifdef LOG_ENABLED
2883 if (LogIs6Enabled() && pThis->pHeaders)
2884 {
2885 Log4(("RTHttpPerform: headers:\n"));
2886 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
2887 Log4(("%s", pCur->data));
2888 }
2889 if (pvReqBody && cbReqBody)
2890 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
2891#endif
2892
2893 /*
2894 * Set the busy flag (paranoia).
2895 */
2896 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
2897 AssertReturn(!fBusy, VERR_WRONG_ORDER);
2898
2899 /*
2900 * Reset the state and apply settings.
2901 */
2902 rtHttpResetState(pThis);
2903 int rc = rtHttpApplySettings(hHttp, pszUrl);
2904 if (RT_SUCCESS(rc))
2905 {
2906 /* Set the HTTP method. */
2907 int rcCurl = 1;
2908 switch (enmMethod)
2909 {
2910 case RTHTTPMETHOD_GET:
2911 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
2912 break;
2913 case RTHTTPMETHOD_PUT:
2914 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
2915 break;
2916 case RTHTTPMETHOD_POST:
2917 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
2918 break;
2919 case RTHTTPMETHOD_PATCH:
2920 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
2921 break;
2922 case RTHTTPMETHOD_DELETE:
2923 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
2924 break;
2925 case RTHTTPMETHOD_HEAD:
2926 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
2927 if (CURL_SUCCESS(rcCurl))
2928 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
2929 break;
2930 case RTHTTPMETHOD_OPTIONS:
2931 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
2932 break;
2933 case RTHTTPMETHOD_TRACE:
2934 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
2935 break;
2936 case RTHTTPMETHOD_END:
2937 case RTHTTPMETHOD_INVALID:
2938 case RTHTTPMETHOD_32BIT_HACK:
2939 AssertFailed();
2940 }
2941
2942 /* Request body. */
2943 if (pvReqBody && cbReqBody > 0 && CURL_SUCCESS(rcCurl))
2944 {
2945 if (enmMethod == RTHTTPMETHOD_POST)
2946 {
2947 /** @todo ??? */
2948 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
2949 if (CURL_SUCCESS(rcCurl))
2950 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
2951 }
2952 else
2953 {
2954 pThis->ReadData.Mem.pvMem = pvReqBody;
2955 pThis->ReadData.Mem.cbMem = cbReqBody;
2956 pThis->ReadData.Mem.offMem = 0;
2957 rcCurl = rtHttpSetReadCallback(pThis, &rtHttpReadData, pThis);
2958 }
2959 }
2960
2961 /* Headers. */
2962 if (ppvHeaders && CURL_SUCCESS(rcCurl))
2963 {
2964 RT_ZERO(pThis->HeadersOutput.uData.Mem);
2965 rcCurl = rtHttpSetHeaderCallback(pThis, &rtHttpWriteData, &pThis->HeadersOutput);
2966 }
2967
2968 /* Body */
2969 if (ppvBody && CURL_SUCCESS(rcCurl))
2970 {
2971 RT_ZERO(pThis->BodyOutput.uData.Mem);
2972 rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteData, &pThis->BodyOutput);
2973 }
2974
2975 if (CURL_SUCCESS(rcCurl))
2976 {
2977 /*
2978 * Perform the HTTP operation.
2979 */
2980 rcCurl = curl_easy_perform(pThis->pCurl);
2981 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
2982 if (RT_SUCCESS(rc))
2983 rc = pThis->rcOutput;
2984 if (RT_SUCCESS(rc))
2985 {
2986 if (ppvHeaders)
2987 {
2988 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
2989 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
2990 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
2991 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
2992 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
2993 pThis->HeadersOutput.uData.Mem.pb = NULL;
2994 }
2995 if (ppvBody)
2996 {
2997 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
2998 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
2999 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3000 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3001 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3002 pThis->BodyOutput.uData.Mem.pb = NULL;
3003 }
3004 }
3005 }
3006 else
3007 rc = VERR_HTTP_CURL_ERROR;
3008
3009 /* Ensure we've freed all unused output and dropped references to input memory.*/
3010 if (pThis->HeadersOutput.uData.Mem.pb)
3011 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3012 if (pThis->BodyOutput.uData.Mem.pb)
3013 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3014 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3015 RT_ZERO(pThis->BodyOutput.uData.Mem);
3016 RT_ZERO(pThis->ReadData);
3017 }
3018
3019 ASMAtomicWriteBool(&pThis->fBusy, false);
3020 return rc;
3021}
3022
3023
3024RTR3DECL(const char *) RTHttpMethodName(RTHTTPMETHOD enmMethod)
3025{
3026 switch (enmMethod)
3027 {
3028 case RTHTTPMETHOD_INVALID: return "invalid";
3029 case RTHTTPMETHOD_GET: return "GET";
3030 case RTHTTPMETHOD_PUT: return "PUT";
3031 case RTHTTPMETHOD_POST: return "POST";
3032 case RTHTTPMETHOD_PATCH: return "PATCH";
3033 case RTHTTPMETHOD_DELETE: return "DELETE";
3034 case RTHTTPMETHOD_HEAD: return "HEAD";
3035 case RTHTTPMETHOD_OPTIONS: return "OPTIONS";
3036 case RTHTTPMETHOD_TRACE: return "TRACE";
3037
3038 case RTHTTPMETHOD_END:
3039 case RTHTTPMETHOD_32BIT_HACK:
3040 break;
3041 }
3042 return "unknown";
3043}
3044
3045
3046/*********************************************************************************************************************************
3047* Callback APIs. *
3048*********************************************************************************************************************************/
3049
3050RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnDownloadProgress, void *pvUser)
3051{
3052 PRTHTTPINTERNAL pThis = hHttp;
3053 RTHTTP_VALID_RETURN(pThis);
3054
3055 pThis->pfnDownloadProgress = pfnDownloadProgress;
3056 pThis->pvDownloadProgressUser = pvUser;
3057 return VINF_SUCCESS;
3058}
3059
3060
3061
3062/** @todo questionable wrt calling convension */
3063RTR3DECL(int) RTHttpSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACK pfnRead, void *pvUser)
3064{
3065 CURLcode rcCurl;
3066
3067 PRTHTTPINTERNAL pThis = hHttp;
3068 RTHTTP_VALID_RETURN(pThis);
3069
3070 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3071 if (CURL_FAILURE(rcCurl))
3072 return VERR_HTTP_CURL_ERROR;
3073
3074 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3075 if (CURL_FAILURE(rcCurl))
3076 return VERR_HTTP_CURL_ERROR;
3077
3078 return VINF_SUCCESS;
3079}
3080
3081
3082/** @todo questionable wrt calling convension */
3083RTR3DECL(int) RTHttpSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACK pfnWrite, void *pvUser)
3084{
3085 PRTHTTPINTERNAL pThis = hHttp;
3086 RTHTTP_VALID_RETURN(pThis);
3087
3088 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
3089 if (CURL_FAILURE(rcCurl))
3090 return VERR_HTTP_CURL_ERROR;
3091
3092 return VINF_SUCCESS;
3093}
3094
3095
3096/** @todo questionable wrt calling convension */
3097RTR3DECL(int) RTHttpSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACK pfnWrite, void *pvUser)
3098{
3099 CURLcode rcCurl;
3100
3101 PRTHTTPINTERNAL pThis = hHttp;
3102 RTHTTP_VALID_RETURN(pThis);
3103
3104 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3105 if (CURL_FAILURE(rcCurl))
3106 return VERR_HTTP_CURL_ERROR;
3107
3108 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3109 if (CURL_FAILURE(rcCurl))
3110 return VERR_HTTP_CURL_ERROR;
3111
3112 return VINF_SUCCESS;
3113}
3114
3115
3116
3117
3118/*********************************************************************************************************************************
3119* Temporary raw cURL stuff. *
3120*********************************************************************************************************************************/
3121
3122RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3123{
3124 CURLcode rcCurl;
3125
3126 PRTHTTPINTERNAL pThis = hHttp;
3127 RTHTTP_VALID_RETURN(pThis);
3128
3129 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3130 if (CURL_FAILURE(rcCurl))
3131 return VERR_HTTP_CURL_ERROR;
3132
3133 return VINF_SUCCESS;
3134}
3135
3136
3137RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3138{
3139 CURLcode rcCurl;
3140
3141 PRTHTTPINTERNAL pThis = hHttp;
3142 RTHTTP_VALID_RETURN(pThis);
3143
3144 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3145 if (CURL_FAILURE(rcCurl))
3146 return VERR_HTTP_CURL_ERROR;
3147
3148 return VINF_SUCCESS;
3149}
3150
3151
3152RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3153{
3154 CURLcode rcCurl;
3155
3156 PRTHTTPINTERNAL pThis = hHttp;
3157 RTHTTP_VALID_RETURN(pThis);
3158
3159 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3160 if (CURL_FAILURE(rcCurl))
3161 return VERR_HTTP_CURL_ERROR;
3162
3163 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3164 if (CURL_FAILURE(rcCurl))
3165 return VERR_HTTP_CURL_ERROR;
3166
3167 return VINF_SUCCESS;
3168}
3169
3170
3171RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3172{
3173 CURLcode rcCurl;
3174
3175 PRTHTTPINTERNAL pThis = hHttp;
3176 RTHTTP_VALID_RETURN(pThis);
3177
3178 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3179 if (CURL_FAILURE(rcCurl))
3180 return VERR_HTTP_CURL_ERROR;
3181
3182 return VINF_SUCCESS;
3183}
3184
3185
3186RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
3187{
3188 CURLcode rcCurl;
3189
3190 PRTHTTPINTERNAL pThis = hHttp;
3191 RTHTTP_VALID_RETURN(pThis);
3192
3193 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3194 if (CURL_FAILURE(rcCurl))
3195 return VERR_HTTP_CURL_ERROR;
3196
3197 return VINF_SUCCESS;
3198}
3199
3200
3201RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
3202{
3203 /* curl doesn't provide an option for this */
3204 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
3205}
3206
3207
3208RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
3209{
3210 CURLcode rcCurl;
3211
3212 PRTHTTPINTERNAL pThis = hHttp;
3213 RTHTTP_VALID_RETURN(pThis);
3214
3215 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
3216 if (CURL_FAILURE(rcCurl))
3217 return VERR_HTTP_CURL_ERROR;
3218
3219 return VINF_SUCCESS;
3220}
3221
3222
3223RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
3224{
3225 CURLcode rcCurl;
3226
3227 PRTHTTPINTERNAL pThis = hHttp;
3228 RTHTTP_VALID_RETURN(pThis);
3229
3230 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
3231 if (CURL_FAILURE(rcCurl))
3232 return VERR_HTTP_CURL_ERROR;
3233
3234 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
3235 if (CURL_FAILURE(rcCurl))
3236 return VERR_HTTP_CURL_ERROR;
3237
3238 return VINF_SUCCESS;
3239}
3240
3241RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
3242{
3243 CURLcode rcCurl;
3244
3245 PRTHTTPINTERNAL pThis = hHttp;
3246 RTHTTP_VALID_RETURN(pThis);
3247
3248 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
3249 if (CURL_FAILURE(rcCurl))
3250 return VERR_HTTP_CURL_ERROR;
3251
3252 return VINF_SUCCESS;
3253}
3254
3255
3256RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
3257{
3258 CURLcode rcCurl;
3259
3260 PRTHTTPINTERNAL pThis = hHttp;
3261 RTHTTP_VALID_RETURN(pThis);
3262
3263 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
3264 if (CURL_FAILURE(rcCurl))
3265 return VERR_HTTP_CURL_ERROR;
3266
3267 return VINF_SUCCESS;
3268}
3269
3270
3271RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
3272{
3273 CURLcode rcCurl;
3274
3275 PRTHTTPINTERNAL pThis = hHttp;
3276 RTHTTP_VALID_RETURN(pThis);
3277
3278 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
3279 if (CURL_FAILURE(rcCurl))
3280 return VERR_HTTP_CURL_ERROR;
3281
3282 return VINF_SUCCESS;
3283}
3284
3285
3286RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
3287{
3288 CURLcode rcCurl;
3289
3290 PRTHTTPINTERNAL pThis = hHttp;
3291 RTHTTP_VALID_RETURN(pThis);
3292
3293 rcCurl = curl_easy_perform(pThis->pCurl);
3294 if (CURL_FAILURE(rcCurl))
3295 return VERR_HTTP_CURL_ERROR;
3296
3297 return VINF_SUCCESS;
3298}
3299
3300
3301RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
3302{
3303 CURLcode rcCurl;
3304
3305 PRTHTTPINTERNAL pThis = hHttp;
3306 RTHTTP_VALID_RETURN(pThis);
3307 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
3308
3309 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
3310 if (CURL_FAILURE(rcCurl))
3311 return VERR_HTTP_CURL_ERROR;
3312
3313 return VINF_SUCCESS;
3314}
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