VirtualBox

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

Last change on this file since 95924 was 94520, checked in by vboxsync, 3 years ago

Runtime/http-curl.c|ai: CURLOPT_NOPROXY is an enum and not a define, finally enables the proxy code, bugref:9469

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 142.5 KB
Line 
1/* $Id: http-curl.cpp 94520 2022-04-07 15:16:39Z 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-2022 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/base64.h>
45#include <iprt/cidr.h>
46#include <iprt/crypto/store.h>
47#include <iprt/ctype.h>
48#include <iprt/env.h>
49#include <iprt/err.h>
50#include <iprt/file.h>
51#include <iprt/ldr.h>
52#include <iprt/log.h>
53#include <iprt/mem.h>
54#include <iprt/net.h>
55#include <iprt/once.h>
56#include <iprt/path.h>
57#include <iprt/stream.h>
58#include <iprt/string.h>
59#include <iprt/uni.h>
60#include <iprt/uri.h>
61#include <iprt/utf16.h>
62#include <iprt/crypto/digest.h>
63#include <iprt/crypto/pkix.h>
64#include <iprt/crypto/key.h>
65
66
67#include "internal/magics.h"
68
69#ifdef RT_OS_WINDOWS /* curl.h drags in windows.h which isn't necessarily -Wall clean. */
70# include <iprt/win/windows.h>
71#endif
72#include <curl/curl.h>
73
74#ifdef RT_OS_DARWIN
75# include <CoreFoundation/CoreFoundation.h>
76# include <SystemConfiguration/SystemConfiguration.h>
77# include <CoreServices/CoreServices.h>
78#endif
79#ifdef RT_OS_WINDOWS
80# include <Winhttp.h>
81# include "../r3/win/internal-r3-win.h"
82#endif
83
84#ifdef RT_OS_LINUX
85# define IPRT_USE_LIBPROXY
86#endif
87#ifdef IPRT_USE_LIBPROXY
88# include <stdlib.h> /* free */
89#endif
90
91
92/*********************************************************************************************************************************
93* Structures and Typedefs *
94*********************************************************************************************************************************/
95/** Output collection data. */
96typedef struct RTHTTPOUTPUTDATA
97{
98 /** Pointer to the HTTP client instance structure. */
99 struct RTHTTPINTERNAL *pHttp;
100 /** Callback specific data. */
101 union
102 {
103 /** For file destination. */
104 RTFILE hFile;
105 /** For memory destination. */
106 struct
107 {
108 /** The current size (sans terminator char). */
109 size_t cb;
110 /** The currently allocated size. */
111 size_t cbAllocated;
112 /** Pointer to the buffer. */
113 uint8_t *pb;
114 } Mem;
115 } uData;
116} RTHTTPOUTPUTDATA;
117
118/**
119 * HTTP header.
120 */
121typedef struct RTHTTPHEADER
122{
123 /** The core list structure. */
124 struct curl_slist Core;
125 /** The field name length. */
126 uint32_t cchName;
127 /** The value offset. */
128 uint32_t offValue;
129 /** The full header field. */
130 RT_FLEXIBLE_ARRAY_EXTENSION
131 RT_GCC_EXTENSION char szData[RT_FLEXIBLE_ARRAY];
132} RTHTTPHEADER;
133/** Pointer to a HTTP header. */
134typedef RTHTTPHEADER *PRTHTTPHEADER;
135
136/**
137 * Internal HTTP client instance.
138 */
139typedef struct RTHTTPINTERNAL
140{
141 /** Magic value. */
142 uint32_t u32Magic;
143 /** cURL handle. */
144 CURL *pCurl;
145 /** The last response code. */
146 long lLastResp;
147 /** Custom headers (PRTHTTPHEADER).
148 * The list head is registered with curl, though we do all the allocating. */
149 struct curl_slist *pHeaders;
150 /** Where to append the next header. */
151 struct curl_slist **ppHeadersTail;
152
153 /** CA certificate file for HTTPS authentication. */
154 char *pszCaFile;
155 /** Whether to delete the CA on destruction. */
156 bool fDeleteCaFile;
157
158 /** Set if we've applied a CURLOTP_USERAGENT already. */
159 bool fHaveSetUserAgent;
160 /** Set if we've got a user agent header, otherwise clear. */
161 bool fHaveUserAgentHeader;
162
163 /** @name Proxy settings.
164 * When fUseSystemProxySettings is set, the other members will be updated each
165 * time we're presented with a new URL. The members reflect the cURL
166 * configuration.
167 *
168 * @{ */
169 /** Set if we should use the system proxy settings for a URL.
170 * This means reconfiguring cURL for each request. */
171 bool fUseSystemProxySettings;
172 /** Set if we've detected no proxy necessary. */
173 bool fNoProxy;
174 /** Set if we've reset proxy info in cURL and need to reapply it. */
175 bool fReapplyProxyInfo;
176 /** Proxy host name (RTStrFree). */
177 char *pszProxyHost;
178 /** Proxy port number (UINT32_MAX if not specified). */
179 uint32_t uProxyPort;
180 /** The proxy type (CURLPROXY_HTTP, CURLPROXY_SOCKS5, ++). */
181 curl_proxytype enmProxyType;
182 /** Proxy username (RTStrFree). */
183 char *pszProxyUsername;
184 /** Proxy password (RTStrFree). */
185 char *pszProxyPassword;
186 /** @} */
187
188 /** @name Cached settings.
189 * @{ */
190 /** Maximum number of redirects to follow.
191 * Zero if not automatically following (default). */
192 uint32_t cMaxRedirects;
193 /** Whether to check if Peer lies about his SSL certificate. */
194 bool fVerifyPeer;
195 /** @} */
196
197 /** Abort the current HTTP request if true. */
198 bool volatile fAbort;
199 /** Set if someone is preforming an HTTP operation. */
200 bool volatile fBusy;
201 /** The location field for 301 responses. */
202 char *pszRedirLocation;
203
204 union
205 {
206 struct
207 {
208 /** Pointer to the memory block we're feeding the cURL/server. */
209 void const *pvMem;
210 /** Size of the memory block. */
211 size_t cbMem;
212 /** Current memory block offset. */
213 size_t offMem;
214 } Mem;
215 } ReadData;
216
217 /** Body output callback data. */
218 RTHTTPOUTPUTDATA BodyOutput;
219 /** Headers output callback data. */
220 RTHTTPOUTPUTDATA HeadersOutput;
221 /** The output status.*/
222 int rcOutput;
223
224 /** @name Upload callback
225 * @{ */
226 /** Pointer to the upload callback function, if any. */
227 PFNRTHTTPUPLOADCALLBACK pfnUploadCallback;
228 /** The user argument for the upload callback function. */
229 void *pvUploadCallbackUser;
230 /** The expected upload size, UINT64_MAX if not known. */
231 uint64_t cbUploadContent;
232 /** The current upload offset. */
233 uint64_t offUploadContent;
234 /** @} */
235
236 /** @name Download callback.
237 * @{ */
238 /** Pointer to the download callback function, if any. */
239 PFNRTHTTPDOWNLOADCALLBACK pfnDownloadCallback;
240 /** The user argument for the download callback function. */
241 void *pvDownloadCallbackUser;
242 /** The flags for the download callback function. */
243 uint32_t fDownloadCallback;
244 /** HTTP status for passing to the download callback, UINT32_MAX if not known. */
245 uint32_t uDownloadHttpStatus;
246 /** The download content length, or UINT64_MAX. */
247 uint64_t cbDownloadContent;
248 /** The current download offset. */
249 uint64_t offDownloadContent;
250 /** @} */
251
252 /** @name Download progress callback.
253 * @{ */
254 /** Download size hint set by the progress callback. */
255 uint64_t cbDownloadHint;
256 /** Callback called during download. */
257 PFNRTHTTPDOWNLDPROGRCALLBACK pfnDownloadProgress;
258 /** User pointer parameter for pfnDownloadProgress. */
259 void *pvDownloadProgressUser;
260 /** @} */
261
262 /** @name Header callback.
263 * @{ */
264 /** Pointer to the header callback function, if any. */
265 PFNRTHTTPHEADERCALLBACK pfnHeaderCallback;
266 /** User pointer parameter for pfnHeaderCallback. */
267 void *pvHeaderCallbackUser;
268 /** @} */
269
270 /** Buffer for human readable error messages from curl on failures or problems. */
271 char szErrorBuffer[CURL_ERROR_SIZE];
272} RTHTTPINTERNAL;
273/** Pointer to an internal HTTP client instance. */
274typedef RTHTTPINTERNAL *PRTHTTPINTERNAL;
275
276
277#ifdef RT_OS_WINDOWS
278/** @name Windows: Types for dynamically resolved APIs
279 * @{ */
280typedef HINTERNET (WINAPI * PFNWINHTTPOPEN)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
281typedef BOOL (WINAPI * PFNWINHTTPCLOSEHANDLE)(HINTERNET);
282typedef BOOL (WINAPI * PFNWINHTTPGETPROXYFORURL)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS *, WINHTTP_PROXY_INFO *);
283typedef BOOL (WINAPI * PFNWINHTTPGETDEFAULTPROXYCONFIGURATION)(WINHTTP_PROXY_INFO *);
284typedef BOOL (WINAPI * PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *);
285/** @} */
286#endif
287
288#ifdef IPRT_USE_LIBPROXY
289typedef struct px_proxy_factory *PLIBPROXYFACTORY;
290typedef PLIBPROXYFACTORY (* PFNLIBPROXYFACTORYCTOR)(void);
291typedef void (* PFNLIBPROXYFACTORYDTOR)(PLIBPROXYFACTORY);
292typedef char ** (* PFNLIBPROXYFACTORYGETPROXIES)(PLIBPROXYFACTORY, const char *);
293#endif
294
295
296/*********************************************************************************************************************************
297* Defined Constants And Macros *
298*********************************************************************************************************************************/
299/** @def RTHTTP_MAX_MEM_DOWNLOAD_SIZE
300 * The max size we are allowed to download to a memory buffer.
301 *
302 * @remarks The minus 1 is for the trailing zero terminator we always add.
303 */
304#if ARCH_BITS == 64
305# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1)
306#else
307# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1)
308#endif
309
310/** Checks whether a cURL return code indicates success. */
311#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK)
312/** Checks whether a cURL return code indicates failure. */
313#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK)
314
315/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
316#define RTHTTP_VALID_RETURN_RC(hHttp, a_rc) \
317 do { \
318 AssertPtrReturn((hHttp), (a_rc)); \
319 AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (a_rc)); \
320 } while (0)
321
322/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */
323#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE)
324
325/** Validates a handle and returns (void) if not valid. */
326#define RTHTTP_VALID_RETURN_VOID(hHttp) \
327 do { \
328 AssertPtrReturnVoid(hHttp); \
329 AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \
330 } while (0)
331
332
333/*********************************************************************************************************************************
334* Global Variables *
335*********************************************************************************************************************************/
336#ifdef RT_OS_WINDOWS
337/** @name Windows: Dynamically resolved APIs
338 * @{ */
339static RTONCE g_WinResolveImportsOnce = RTONCE_INITIALIZER;
340static PFNWINHTTPOPEN g_pfnWinHttpOpen = NULL;
341static PFNWINHTTPCLOSEHANDLE g_pfnWinHttpCloseHandle = NULL;
342static PFNWINHTTPGETPROXYFORURL g_pfnWinHttpGetProxyForUrl = NULL;
343static PFNWINHTTPGETDEFAULTPROXYCONFIGURATION g_pfnWinHttpGetDefaultProxyConfiguration = NULL;
344static PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER g_pfnWinHttpGetIEProxyConfigForCurrentUser = NULL;
345/** @} */
346#endif
347
348#ifdef IPRT_USE_LIBPROXY
349/** @name Dynamaically resolved libproxy APIs.
350 * @{ */
351static RTONCE g_LibProxyResolveImportsOnce = RTONCE_INITIALIZER;
352static RTLDRMOD g_hLdrLibProxy = NIL_RTLDRMOD;
353static PFNLIBPROXYFACTORYCTOR g_pfnLibProxyFactoryCtor = NULL;
354static PFNLIBPROXYFACTORYDTOR g_pfnLibProxyFactoryDtor = NULL;
355static PFNLIBPROXYFACTORYGETPROXIES g_pfnLibProxyFactoryGetProxies = NULL;
356/** @} */
357#endif
358
359
360/*********************************************************************************************************************************
361* Internal Functions *
362*********************************************************************************************************************************/
363static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis);
364#ifdef RT_OS_DARWIN
365static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType);
366#endif
367static void rtHttpFreeHeaders(PRTHTTPINTERNAL pThis);
368
369
370RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp)
371{
372 AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER);
373
374 /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a
375 * problem if multiple threads get here at the same time. */
376 int rc = VERR_HTTP_INIT_FAILED;
377 CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL);
378 if (CURL_SUCCESS(rcCurl))
379 {
380 CURL *pCurl = curl_easy_init();
381 if (pCurl)
382 {
383 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL));
384 if (pThis)
385 {
386 pThis->u32Magic = RTHTTP_MAGIC;
387 pThis->pCurl = pCurl;
388 pThis->ppHeadersTail = &pThis->pHeaders;
389 pThis->fHaveSetUserAgent = false;
390 pThis->fHaveUserAgentHeader = false;
391 pThis->fUseSystemProxySettings = true;
392 pThis->cMaxRedirects = 0; /* no automatic redir following */
393 pThis->fVerifyPeer = true;
394 pThis->BodyOutput.pHttp = pThis;
395 pThis->HeadersOutput.pHttp = pThis;
396 pThis->uDownloadHttpStatus = UINT32_MAX;
397 pThis->cbDownloadContent = UINT64_MAX;
398 pThis->offDownloadContent = 0;
399 pThis->cbUploadContent = UINT64_MAX;
400 pThis->offUploadContent = 0;
401
402 /* ask curl to give us back error messages */
403 curl_easy_setopt(pThis->pCurl, CURLOPT_ERRORBUFFER, pThis->szErrorBuffer);
404
405 *phHttp = (RTHTTP)pThis;
406
407 return VINF_SUCCESS;
408 }
409 rc = VERR_NO_MEMORY;
410 }
411 else
412 rc = VERR_HTTP_INIT_FAILED;
413 }
414 curl_global_cleanup();
415 return rc;
416}
417
418
419RTR3DECL(int) RTHttpReset(RTHTTP hHttp, uint32_t fFlags)
420{
421 /* Validate the instance handle, state and flags. */
422 PRTHTTPINTERNAL pThis = hHttp;
423 RTHTTP_VALID_RETURN(pThis);
424 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
425 AssertReturn(!(fFlags & ~RTHTTP_RESET_F_VALID_MASK), VERR_INVALID_FLAGS);
426
427 /* This resets options, but keeps open connections, cookies, etc. */
428 curl_easy_reset(pThis->pCurl);
429
430 /** @todo check if CURLOPT_SSL_VERIFYPEER is affected by curl_easy_reset. */
431
432 if (!(fFlags & RTHTTP_RESET_F_KEEP_HEADERS))
433 rtHttpFreeHeaders(pThis);
434
435 pThis->uDownloadHttpStatus = UINT32_MAX;
436 pThis->cbDownloadContent = UINT64_MAX;
437 pThis->offDownloadContent = 0;
438 pThis->cbUploadContent = UINT64_MAX;
439 pThis->offUploadContent = 0;
440 pThis->rcOutput = VINF_SUCCESS;
441
442 /* Tell the proxy configuration code to reapply settings even if they
443 didn't change as cURL has forgotten them: */
444 pThis->fReapplyProxyInfo = true;
445
446 return VINF_SUCCESS;
447}
448
449
450RTR3DECL(int) RTHttpDestroy(RTHTTP hHttp)
451{
452 if (hHttp == NIL_RTHTTP)
453 return VINF_SUCCESS;
454
455 PRTHTTPINTERNAL pThis = hHttp;
456 RTHTTP_VALID_RETURN(pThis);
457
458 Assert(!pThis->fBusy);
459
460 pThis->u32Magic = RTHTTP_MAGIC_DEAD;
461
462 curl_easy_cleanup(pThis->pCurl);
463 pThis->pCurl = NULL;
464
465 rtHttpFreeHeaders(pThis);
466
467 rtHttpUnsetCaFile(pThis);
468 Assert(!pThis->pszCaFile);
469
470 if (pThis->pszRedirLocation)
471 {
472 RTStrFree(pThis->pszRedirLocation);
473 pThis->pszRedirLocation = NULL;
474 }
475
476 RTStrFree(pThis->pszProxyHost);
477 pThis->pszProxyHost = NULL;
478 RTStrFree(pThis->pszProxyUsername);
479 pThis->pszProxyUsername = NULL;
480 if (pThis->pszProxyPassword)
481 {
482 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
483 RTStrFree(pThis->pszProxyPassword);
484 pThis->pszProxyPassword = NULL;
485 }
486
487 RTMemFree(pThis);
488
489 curl_global_cleanup();
490
491 return VINF_SUCCESS;
492}
493
494
495RTR3DECL(int) RTHttpAbort(RTHTTP hHttp)
496{
497 PRTHTTPINTERNAL pThis = hHttp;
498 RTHTTP_VALID_RETURN(pThis);
499
500 pThis->fAbort = true;
501
502 return VINF_SUCCESS;
503}
504
505
506RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation)
507{
508 PRTHTTPINTERNAL pThis = hHttp;
509 RTHTTP_VALID_RETURN(pThis);
510 Assert(!pThis->fBusy);
511
512 if (!pThis->pszRedirLocation)
513 return VERR_HTTP_NOT_FOUND;
514
515 return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation);
516}
517
518
519RTR3DECL(int) RTHttpSetFollowRedirects(RTHTTP hHttp, uint32_t cMaxRedirects)
520{
521 PRTHTTPINTERNAL pThis = hHttp;
522 RTHTTP_VALID_RETURN(pThis);
523 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
524
525 /*
526 * Update the redirection settings.
527 */
528 if (pThis->cMaxRedirects != cMaxRedirects)
529 {
530 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_MAXREDIRS, (long)cMaxRedirects);
531 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_MAXREDIRS=%u: %d (%#x)\n", cMaxRedirects, rcCurl, rcCurl),
532 VERR_HTTP_CURL_ERROR);
533
534 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_FOLLOWLOCATION, (long)(cMaxRedirects > 0));
535 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_FOLLOWLOCATION=%d: %d (%#x)\n", cMaxRedirects > 0, rcCurl, rcCurl),
536 VERR_HTTP_CURL_ERROR);
537
538 pThis->cMaxRedirects = cMaxRedirects;
539 }
540 return VINF_SUCCESS;
541}
542
543
544RTR3DECL(uint32_t) RTHttpGetFollowRedirects(RTHTTP hHttp)
545{
546 PRTHTTPINTERNAL pThis = hHttp;
547 RTHTTP_VALID_RETURN_RC(pThis, 0);
548 return pThis->cMaxRedirects;
549}
550
551
552/*********************************************************************************************************************************
553* Proxy handling. *
554*********************************************************************************************************************************/
555
556RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp)
557{
558 PRTHTTPINTERNAL pThis = hHttp;
559 RTHTTP_VALID_RETURN(pThis);
560 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
561
562 /*
563 * Change the settings.
564 */
565 pThis->fUseSystemProxySettings = true;
566 return VINF_SUCCESS;
567}
568
569
570/**
571 * rtHttpConfigureProxyForUrl: Update cURL proxy settings as needed.
572 *
573 * @returns IPRT status code.
574 * @param pThis The HTTP client instance.
575 * @param enmProxyType The proxy type.
576 * @param pszHost The proxy host name.
577 * @param uPort The proxy port number.
578 * @param pszUsername The proxy username, or NULL if none.
579 * @param pszPassword The proxy password, or NULL if none.
580 */
581static int rtHttpUpdateProxyConfig(PRTHTTPINTERNAL pThis, curl_proxytype enmProxyType, const char *pszHost,
582 uint32_t uPort, const char *pszUsername, const char *pszPassword)
583{
584 CURLcode rcCurl;
585 AssertReturn(pszHost, VERR_INVALID_PARAMETER);
586 Log(("rtHttpUpdateProxyConfig: pThis=%p type=%d host='%s' port=%u user='%s'%s\n",
587 pThis, enmProxyType, pszHost, uPort, pszUsername, pszPassword ? " with password" : " without password"));
588
589 if (pThis->fNoProxy)
590 {
591 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, (const char *)NULL);
592 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_NOPROXY=NULL: %d (%#x)\n", rcCurl, rcCurl),
593 VERR_HTTP_CURL_PROXY_CONFIG);
594 pThis->fNoProxy = false;
595 }
596
597 if ( pThis->fReapplyProxyInfo
598 || enmProxyType != pThis->enmProxyType)
599 {
600 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)enmProxyType);
601 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYTYPE=%d: %d (%#x)\n", enmProxyType, rcCurl, rcCurl),
602 VERR_HTTP_CURL_PROXY_CONFIG);
603 pThis->enmProxyType = enmProxyType;
604 }
605
606 if ( pThis->fReapplyProxyInfo
607 || uPort != pThis->uProxyPort)
608 {
609 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort);
610 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYPORT=%d: %d (%#x)\n", uPort, rcCurl, rcCurl),
611 VERR_HTTP_CURL_PROXY_CONFIG);
612 pThis->uProxyPort = uPort;
613 }
614
615 if ( pThis->fReapplyProxyInfo
616 || pszUsername != pThis->pszProxyUsername
617 || RTStrCmp(pszUsername, pThis->pszProxyUsername))
618 {
619 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pszUsername);
620 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYUSERNAME=%s: %d (%#x)\n", pszUsername, rcCurl, rcCurl),
621 VERR_HTTP_CURL_PROXY_CONFIG);
622 if (pThis->pszProxyUsername)
623 {
624 RTStrFree(pThis->pszProxyUsername);
625 pThis->pszProxyUsername = NULL;
626 }
627 if (pszUsername)
628 {
629 pThis->pszProxyUsername = RTStrDup(pszUsername);
630 AssertReturn(pThis->pszProxyUsername, VERR_NO_STR_MEMORY);
631 }
632 }
633
634 if ( pThis->fReapplyProxyInfo
635 || pszPassword != pThis->pszProxyPassword
636 || RTStrCmp(pszPassword, pThis->pszProxyPassword))
637 {
638 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pszPassword);
639 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYPASSWORD=%s: %d (%#x)\n", pszPassword ? "xxx" : NULL, rcCurl, rcCurl),
640 VERR_HTTP_CURL_PROXY_CONFIG);
641 if (pThis->pszProxyPassword)
642 {
643 RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2);
644 RTStrFree(pThis->pszProxyPassword);
645 pThis->pszProxyPassword = NULL;
646 }
647 if (pszPassword)
648 {
649 pThis->pszProxyPassword = RTStrDup(pszPassword);
650 AssertReturn(pThis->pszProxyPassword, VERR_NO_STR_MEMORY);
651 }
652 }
653
654 if ( pThis->fReapplyProxyInfo
655 || pszHost != pThis->pszProxyHost
656 || RTStrCmp(pszHost, pThis->pszProxyHost))
657 {
658 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pszHost);
659 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXY=%s: %d (%#x)\n", pszHost, rcCurl, rcCurl),
660 VERR_HTTP_CURL_PROXY_CONFIG);
661 if (pThis->pszProxyHost)
662 {
663 RTStrFree(pThis->pszProxyHost);
664 pThis->pszProxyHost = NULL;
665 }
666 if (pszHost)
667 {
668 pThis->pszProxyHost = RTStrDup(pszHost);
669 AssertReturn(pThis->pszProxyHost, VERR_NO_STR_MEMORY);
670 }
671 }
672
673 pThis->fReapplyProxyInfo = false;
674 return VINF_SUCCESS;
675}
676
677
678/**
679 * rtHttpConfigureProxyForUrl: Disables proxying.
680 *
681 * @returns IPRT status code.
682 * @param pThis The HTTP client instance.
683 */
684static int rtHttpUpdateAutomaticProxyDisable(PRTHTTPINTERNAL pThis)
685{
686 Log(("rtHttpUpdateAutomaticProxyDisable: pThis=%p\n", pThis));
687
688 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP) == CURLE_OK, VERR_INTERNAL_ERROR_2);
689 pThis->enmProxyType = CURLPROXY_HTTP;
690
691 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)1080) == CURLE_OK, VERR_INTERNAL_ERROR_2);
692 pThis->uProxyPort = 1080;
693
694 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
695 if (pThis->pszProxyUsername)
696 {
697 RTStrFree(pThis->pszProxyUsername);
698 pThis->pszProxyUsername = NULL;
699 }
700
701 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2);
702 if (pThis->pszProxyPassword)
703 {
704 RTStrFree(pThis->pszProxyPassword);
705 pThis->pszProxyPassword = NULL;
706 }
707
708 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, "") == CURLE_OK, VERR_INTERNAL_ERROR_2);
709 if (pThis->pszProxyHost)
710 {
711 RTStrFree(pThis->pszProxyHost);
712 pThis->pszProxyHost = NULL;
713 }
714
715 /* No proxy for everything! */
716 AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, "*") == CURLE_OK, CURLOPT_PROXY);
717 pThis->fNoProxy = true;
718
719 return VINF_SUCCESS;
720}
721
722
723/**
724 * See if the host name of the URL is included in the stripped no_proxy list.
725 *
726 * The no_proxy list is a colon or space separated list of domain names for
727 * which there should be no proxying. Given "no_proxy=oracle.com" neither the
728 * URL "http://www.oracle.com" nor "http://oracle.com" will not be proxied, but
729 * "http://notoracle.com" will be.
730 *
731 * @returns true if the URL is in the no_proxy list, otherwise false.
732 * @param pszUrl The URL.
733 * @param pszNoProxyList The stripped no_proxy list.
734 */
735static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList)
736{
737 /*
738 * Check for just '*', diabling proxying for everything.
739 * (Caller stripped pszNoProxyList.)
740 */
741 if (*pszNoProxyList == '*' && pszNoProxyList[1] == '\0')
742 return true;
743
744 /*
745 * Empty list? (Caller stripped it, remember).
746 */
747 if (!*pszNoProxyList)
748 return false;
749
750 /*
751 * We now need to parse the URL and extract the host name.
752 */
753 RTURIPARSED Parsed;
754 int rc = RTUriParse(pszUrl, &Parsed);
755 AssertRCReturn(rc, false);
756 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
757 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
758 return false;
759
760 bool fRet = false;
761 size_t const cchHost = strlen(pszHost);
762 if (cchHost)
763 {
764 /*
765 * The list is comma or space separated, walk it and match host names.
766 */
767 while (*pszNoProxyList != '\0')
768 {
769 /* Strip leading slashes, commas and dots. */
770 char ch;
771 while ( (ch = *pszNoProxyList) == ','
772 || ch == '.'
773 || RT_C_IS_SPACE(ch))
774 pszNoProxyList++;
775
776 /* Find the end. */
777 size_t cch = RTStrOffCharOrTerm(pszNoProxyList, ',');
778 size_t offNext = RTStrOffCharOrTerm(pszNoProxyList, ' ');
779 cch = RT_MIN(cch, offNext);
780 offNext = cch;
781
782 /* Trip trailing spaces, well tabs and stuff. */
783 while (cch > 0 && RT_C_IS_SPACE(pszNoProxyList[cch - 1]))
784 cch--;
785
786 /* Do the matching, if we have anything to work with. */
787 if (cch > 0)
788 {
789 if ( ( cch == cchHost
790 && RTStrNICmp(pszNoProxyList, pszHost, cch) == 0)
791 || ( cch < cchHost
792 && pszHost[cchHost - cch - 1] == '.'
793 && RTStrNICmp(pszNoProxyList, &pszHost[cchHost - cch], cch) == 0) )
794 {
795 fRet = true;
796 break;
797 }
798 }
799
800 /* Next. */
801 pszNoProxyList += offNext;
802 }
803 }
804
805 RTStrFree(pszHost);
806 return fRet;
807}
808
809
810/**
811 * Configures a proxy given a "URL" like specification.
812 *
813 * The format is:
814 * @verbatim
815 * [<scheme>"://"][<userid>[@<password>]:]<server>[":"<port>]
816 * @endverbatim
817 *
818 * Where the scheme gives the type of proxy server we're dealing with rather
819 * than the protocol of the external server we wish to talk to.
820 *
821 * @returns IPRT status code.
822 * @param pThis The HTTP client instance.
823 * @param pszProxyUrl The proxy server "URL".
824 */
825static int rtHttpConfigureProxyFromUrl(PRTHTTPINTERNAL pThis, const char *pszProxyUrl)
826{
827 /*
828 * Make sure it can be parsed as an URL.
829 */
830 char *pszFreeMe = NULL;
831 if (!strstr(pszProxyUrl, "://"))
832 {
833 static const char s_szPrefix[] = "http://";
834 size_t cchProxyUrl = strlen(pszProxyUrl);
835 pszFreeMe = (char *)RTMemTmpAlloc(sizeof(s_szPrefix) + cchProxyUrl);
836 if (pszFreeMe)
837 {
838 memcpy(pszFreeMe, s_szPrefix, sizeof(s_szPrefix) - 1);
839 memcpy(&pszFreeMe[sizeof(s_szPrefix) - 1], pszProxyUrl, cchProxyUrl);
840 pszFreeMe[sizeof(s_szPrefix) - 1 + cchProxyUrl] = '\0';
841 pszProxyUrl = pszFreeMe;
842 }
843 else
844 return VERR_NO_TMP_MEMORY;
845 }
846
847 RTURIPARSED Parsed;
848 int rc = RTUriParse(pszProxyUrl, &Parsed);
849 if (RT_SUCCESS(rc))
850 {
851 char *pszHost = RTUriParsedAuthorityHost(pszProxyUrl, &Parsed);
852 if (pszHost)
853 {
854 /*
855 * We've got a host name, try get the rest.
856 */
857 char *pszUsername = RTUriParsedAuthorityUsername(pszProxyUrl, &Parsed);
858 char *pszPassword = RTUriParsedAuthorityPassword(pszProxyUrl, &Parsed);
859 uint32_t uProxyPort = RTUriParsedAuthorityPort(pszProxyUrl, &Parsed);
860 bool fUnknownProxyType = false;
861 curl_proxytype enmProxyType;
862 if (RTUriIsSchemeMatch(pszProxyUrl, "http"))
863 {
864 enmProxyType = CURLPROXY_HTTP;
865 if (uProxyPort == UINT32_MAX)
866 uProxyPort = 80;
867 }
868#ifdef CURL_AT_LEAST_VERSION
869# if CURL_AT_LEAST_VERSION(7,52,0)
870 else if (RTUriIsSchemeMatch(pszProxyUrl, "https"))
871 {
872 enmProxyType = CURLPROXY_HTTPS;
873 if (uProxyPort == UINT32_MAX)
874 uProxyPort = 443;
875 }
876# endif
877#endif
878 else if ( RTUriIsSchemeMatch(pszProxyUrl, "socks4")
879 || RTUriIsSchemeMatch(pszProxyUrl, "socks"))
880 enmProxyType = CURLPROXY_SOCKS4;
881 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks4a"))
882 enmProxyType = CURLPROXY_SOCKS4A;
883 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5"))
884 enmProxyType = CURLPROXY_SOCKS5;
885 else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5h"))
886 enmProxyType = CURLPROXY_SOCKS5_HOSTNAME;
887 else
888 {
889 fUnknownProxyType = true;
890 enmProxyType = CURLPROXY_HTTP;
891 if (uProxyPort == UINT32_MAX)
892 uProxyPort = 8080;
893 }
894
895 /* Guess the port from the proxy type if not given. */
896 if (uProxyPort == UINT32_MAX)
897 uProxyPort = 1080; /* CURL_DEFAULT_PROXY_PORT */
898
899 rc = rtHttpUpdateProxyConfig(pThis, enmProxyType, pszHost, uProxyPort, pszUsername, pszPassword);
900 if (RT_SUCCESS(rc) && fUnknownProxyType)
901 rc = VWRN_WRONG_TYPE;
902
903 RTStrFree(pszUsername);
904 RTStrFree(pszPassword);
905 RTStrFree(pszHost);
906 }
907 else
908 AssertMsgFailed(("RTUriParsedAuthorityHost('%s',) -> NULL\n", pszProxyUrl));
909 }
910 else
911 AssertMsgFailed(("RTUriParse('%s',) -> %Rrc\n", pszProxyUrl, rc));
912
913 if (pszFreeMe)
914 RTMemTmpFree(pszFreeMe);
915 return rc;
916}
917
918
919RTR3DECL(int) RTHttpSetProxyByUrl(RTHTTP hHttp, const char *pszUrl)
920{
921 PRTHTTPINTERNAL pThis = hHttp;
922 RTHTTP_VALID_RETURN(pThis);
923 AssertPtrNullReturn(pszUrl, VERR_INVALID_PARAMETER);
924 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
925
926 if (!pszUrl || !*pszUrl)
927 return RTHttpUseSystemProxySettings(pThis);
928 if (RTStrNICmpAscii(pszUrl, RT_STR_TUPLE("direct://")) == 0)
929 return rtHttpUpdateAutomaticProxyDisable(pThis);
930 return rtHttpConfigureProxyFromUrl(pThis, pszUrl);
931}
932
933
934/**
935 * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out
936 * the proxy config.
937 *
938 * @returns IPRT status code.
939 * @param pThis The HTTP client instance.
940 * @param pszUrl The URL to configure a proxy for.
941 */
942static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl)
943{
944 char szTmp[_1K];
945
946 /*
947 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
948 */
949 const char *pszNoProxyVar;
950 size_t cchActual;
951 char *pszNoProxyFree = NULL;
952 char *pszNoProxy = szTmp;
953 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
954 if (rc == VERR_ENV_VAR_NOT_FOUND)
955 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
956 if (rc == VERR_BUFFER_OVERFLOW)
957 {
958 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
959 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
960 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
961 }
962 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
963 bool fNoProxy = false;
964 if (RT_SUCCESS(rc))
965 fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
966 RTMemTmpFree(pszNoProxyFree);
967 if (!fNoProxy)
968 {
969 /*
970 * Get the schema specific specific env var, falling back on the
971 * generic all_proxy if not found.
972 */
973 const char *apszEnvVars[4];
974 unsigned cEnvVars = 0;
975 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
976 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
977 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
978 {
979 apszEnvVars[cEnvVars++] = "https_proxy";
980 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
981 }
982 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
983 {
984 apszEnvVars[cEnvVars++] = "ftp_proxy";
985 apszEnvVars[cEnvVars++] = "FTP_PROXY";
986 }
987 else
988 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
989 apszEnvVars[cEnvVars++] = "all_proxy";
990 apszEnvVars[cEnvVars++] = "ALL_PROXY";
991
992 /*
993 * We try the env vars out and goes with the first one we can make sense out of.
994 * If we cannot make sense of any, we return the first unexpected rc we got.
995 */
996 rc = VINF_SUCCESS;
997 for (uint32_t i = 0; i < cEnvVars; i++)
998 {
999 size_t cchValue;
1000 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
1001 if (RT_SUCCESS(rc2))
1002 {
1003 if (cchValue != 0)
1004 {
1005 /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */
1006 if (!strstr(szTmp, "://"))
1007 {
1008 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
1009 memcpy(szTmp, RT_STR_TUPLE("http://"));
1010 }
1011
1012 rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp);
1013 if (RT_SUCCESS(rc2))
1014 rc = rc2;
1015 }
1016 /*
1017 * The variable is empty. Guess that means no proxying wanted.
1018 */
1019 else
1020 {
1021 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
1022 break;
1023 }
1024 }
1025 else
1026 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
1027 }
1028 }
1029 /*
1030 * The host is the no-proxy list, it seems.
1031 */
1032 else
1033 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
1034
1035 return rc;
1036}
1037
1038#ifdef IPRT_USE_LIBPROXY
1039
1040/**
1041 * @callback_method_impl{FNRTONCE,
1042 * Attempts to load libproxy.so.1 and resolves APIs}
1043 */
1044static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser)
1045{
1046 RTLDRMOD hMod;
1047 int rc = RTLdrLoadSystem("libproxy.so.1", false /*fNoUnload*/, &hMod);
1048 if (RT_SUCCESS(rc))
1049 {
1050 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor);
1051 if (RT_SUCCESS(rc))
1052 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor);
1053 if (RT_SUCCESS(rc))
1054 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies);
1055 if (RT_SUCCESS(rc))
1056 {
1057 RTMEM_WILL_LEAK(hMod);
1058 g_hLdrLibProxy = hMod;
1059 }
1060 else
1061 RTLdrClose(hMod);
1062 AssertRC(rc);
1063 }
1064
1065 NOREF(pvUser);
1066 return rc;
1067}
1068
1069/**
1070 * Reconfigures the cURL proxy settings for the given URL, libproxy style.
1071 *
1072 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1073 * @param pThis The HTTP client instance.
1074 * @param pszUrl The URL.
1075 */
1076static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1077{
1078 int rcRet = VINF_NOT_SUPPORTED;
1079
1080 int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL);
1081 if (RT_SUCCESS(rc))
1082 {
1083 /*
1084 * Instance the factory and ask for a list of proxies.
1085 */
1086 PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor();
1087 if (pFactory)
1088 {
1089 char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl);
1090 g_pfnLibProxyFactoryDtor(pFactory);
1091 if (papszProxies)
1092 {
1093 /*
1094 * Look for something we can use.
1095 */
1096 for (unsigned i = 0; papszProxies[i]; i++)
1097 {
1098 if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0)
1099 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1100 else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0
1101 || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0
1102 || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0
1103 || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */
1104 )
1105 rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]);
1106 else
1107 continue;
1108 if (rcRet != VINF_NOT_SUPPORTED)
1109 break;
1110 }
1111
1112 /* free the result. */
1113 for (unsigned i = 0; papszProxies[i]; i++)
1114 free(papszProxies[i]);
1115 free(papszProxies);
1116 }
1117 }
1118 }
1119
1120 return rcRet;
1121}
1122
1123#endif /* IPRT_USE_LIBPROXY */
1124
1125#ifdef RT_OS_DARWIN
1126
1127/**
1128 * Get a boolean like integer value from a dictionary.
1129 *
1130 * @returns true / false.
1131 * @param hDict The dictionary.
1132 * @param pvKey The dictionary value key.
1133 */
1134static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault)
1135{
1136 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey);
1137 if (hNum)
1138 {
1139 int fEnabled;
1140 if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled))
1141 return fDefault;
1142 return fEnabled != 0;
1143 }
1144 return fDefault;
1145}
1146
1147
1148/**
1149 * Creates a CFURL object for an URL.
1150 *
1151 * @returns CFURL object reference.
1152 * @param pszUrl The URL.
1153 */
1154static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl)
1155{
1156 /* CFURLCreateStringByAddingPercentEscapes is deprecated, so try use CFURLCreateWithBytes
1157 as it doesn't validate as much as as CFUrlCreateWithString does. */
1158#if 0
1159 CFURLRef hUrl = NULL;
1160 CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8);
1161 if (hStrUrl)
1162 {
1163 CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl,
1164 NULL /*charactersToLeaveUnescaped*/,
1165 NULL /*legalURLCharactersToBeEscaped*/,
1166 kCFStringEncodingUTF8);
1167 if (hStrUrlEscaped)
1168 {
1169 hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/);
1170 Assert(hUrl);
1171 CFRelease(hStrUrlEscaped);
1172 }
1173 else
1174 AssertFailed();
1175 CFRelease(hStrUrl);
1176 }
1177 else
1178 AssertFailed();
1179#else
1180 CFURLRef hUrl = CFURLCreateWithBytes(kCFAllocatorDefault, (const uint8_t *)pszUrl, strlen(pszUrl),
1181 kCFStringEncodingUTF8, NULL /*baseURL*/);
1182 Assert(hUrl);
1183#endif
1184 return hUrl;
1185}
1186
1187
1188/**
1189 * For passing results from rtHttpDarwinPacCallback to
1190 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1191 */
1192typedef struct RTHTTPDARWINPACRESULT
1193{
1194 CFArrayRef hArrayProxies;
1195 CFErrorRef hError;
1196} RTHTTPDARWINPACRESULT;
1197typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT;
1198
1199/**
1200 * Stupid callback for getting the result from
1201 * CFNetworkExecuteProxyAutoConfigurationURL.
1202 *
1203 * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of
1204 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1205 * @param hArrayProxies The result array.
1206 * @param hError Errors, if any.
1207 */
1208static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError)
1209{
1210 PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser;
1211
1212 Assert(pResult->hArrayProxies == NULL);
1213 if (hArrayProxies)
1214 pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies);
1215
1216 Assert(pResult->hError == NULL);
1217 if (hError)
1218 pResult->hError = (CFErrorRef)CFRetain(hError);
1219
1220 CFRunLoopStop(CFRunLoopGetCurrent());
1221}
1222
1223
1224/**
1225 * Executes a PAC script and returning the proxies it suggests.
1226 *
1227 * @returns Array of proxy configs (CFProxySupport.h style).
1228 * @param hUrlTarget The URL we're about to use.
1229 * @param hUrlScript The PAC script URL.
1230 */
1231static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(CFURLRef hUrlTarget, CFURLRef hUrlScript)
1232{
1233 char szTmp[256];
1234 if (LogIsFlowEnabled())
1235 {
1236 szTmp[0] = '\0';
1237 CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1238 LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp));
1239 }
1240
1241 /*
1242 * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to
1243 * download the script ourselves and mess around with too many CF APIs.
1244 */
1245 CFRunLoopRef hRunLoop = CFRunLoopGetCurrent();
1246 AssertReturn(hRunLoop, NULL);
1247
1248 RTHTTPDARWINPACRESULT Result = { NULL, NULL };
1249 CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL };
1250 CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget,
1251 rtHttpDarwinPacCallback, &Ctx);
1252 AssertReturn(hRunLoopSrc, NULL);
1253
1254 CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool");
1255 CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode);
1256 CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */
1257 CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode);
1258
1259 /** @todo convert errors, maybe even fail. */
1260
1261 /*
1262 * Autoconfig (or missing wpad server) typically results in:
1263 * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2).
1264 *
1265 * In the autoconfig case, it looks like we're getting two entries, first
1266 * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to
1267 * be very upset if this fails, just continue trying alternatives.
1268 */
1269 if (Result.hError)
1270 {
1271 if (LogIsEnabled())
1272 {
1273 szTmp[0] = '\0';
1274 CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1275 Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp));
1276 }
1277 CFRelease(Result.hError);
1278 }
1279 return Result.hArrayProxies;
1280}
1281
1282
1283/**
1284 * Attempt to configure the proxy according to @a hDictProxy.
1285 *
1286 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1287 * the caller should try out alternative proxy configs and fallbacks.
1288 * @param pThis The HTTP client instance.
1289 * @param hDictProxy The proxy configuration (see CFProxySupport.h).
1290 * @param hUrlTarget The URL we're about to use.
1291 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1292 * javascript URL). This is set when we're processing
1293 * the output from a PAC script.
1294 */
1295static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType)
1296{
1297 CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey);
1298 AssertReturn(hStrProxyType, VINF_NOT_SUPPORTED);
1299
1300 /*
1301 * No proxy is fairly simple and common.
1302 */
1303 if (CFEqual(hStrProxyType, kCFProxyTypeNone))
1304 return rtHttpUpdateAutomaticProxyDisable(pThis);
1305
1306 /*
1307 * PAC URL means recursion, however we only do one level.
1308 */
1309 if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL))
1310 {
1311 AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED);
1312
1313 CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey);
1314 AssertReturn(hUrlScript, VINF_NOT_SUPPORTED);
1315
1316 int rcRet = VINF_NOT_SUPPORTED;
1317 CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1318 if (hArray)
1319 {
1320 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1321 CFRelease(hArray);
1322 }
1323 return rcRet;
1324 }
1325
1326 /*
1327 * Determine the proxy type (not entirely sure about type == proxy type and
1328 * not scheme/protocol)...
1329 */
1330 curl_proxytype enmProxyType = CURLPROXY_HTTP;
1331 uint32_t uDefaultProxyPort = 8080;
1332 if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP)
1333 || CFEqual(hStrProxyType, kCFProxyTypeHTTPS))
1334 { /* defaults */ }
1335 else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS))
1336 {
1337 /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or
1338 * SOCK5 on the other side... Selecting SOCKS5 for now. */
1339 enmProxyType = CURLPROXY_SOCKS5;
1340 uDefaultProxyPort = 1080;
1341 }
1342 /* Unknown proxy type. */
1343 else
1344 return VINF_NOT_SUPPORTED;
1345
1346 /*
1347 * Extract the proxy configuration.
1348 */
1349 /* The proxy host name. */
1350 char szHostname[_1K];
1351 CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey);
1352 AssertReturn(hStr, VINF_NOT_SUPPORTED);
1353 AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1354
1355 /* Get the port number (optional). */
1356 SInt32 iProxyPort;
1357 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey);
1358 if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort))
1359 AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort);
1360 else
1361 iProxyPort = uDefaultProxyPort;
1362
1363 /* The proxy username. */
1364 char szUsername[256];
1365 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey);
1366 if (hStr)
1367 AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1368 else
1369 szUsername[0] = '\0';
1370
1371 /* The proxy password. */
1372 char szPassword[384];
1373 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey);
1374 if (hStr)
1375 AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1376 else
1377 szPassword[0] = '\0';
1378
1379 /*
1380 * Apply the proxy config.
1381 */
1382 return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort,
1383 szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL);
1384}
1385
1386
1387/**
1388 * Try do proxy config for our HTTP client instance given an array of proxies.
1389 *
1390 * This is used with the output from a CFProxySupport.h API.
1391 *
1392 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1393 * we might want to try out fallbacks.
1394 * @param pThis The HTTP client instance.
1395 * @param hArrayProxies The proxies CFPRoxySupport have given us.
1396 * @param hUrlTarget The URL we're about to use.
1397 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1398 * javascript URL). This is set when we're processing
1399 * the output from a PAC script.
1400 */
1401static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType)
1402{
1403 int rcRet = VINF_NOT_SUPPORTED;
1404 CFIndex const cEntries = CFArrayGetCount(hArrayProxies);
1405 LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries));
1406 for (CFIndex i = 0; i < cEntries; i++)
1407 {
1408 CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i);
1409 AssertContinue(hDictProxy);
1410
1411 rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType);
1412 if (rcRet != VINF_NOT_SUPPORTED)
1413 break;
1414 }
1415 return rcRet;
1416}
1417
1418
1419/**
1420 * Inner worker for rtHttpWinConfigureProxyForUrl.
1421 *
1422 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1423 * @param pThis The HTTP client instance.
1424 * @param pszUrl The URL.
1425 */
1426static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies,
1427 const char *pszUrl, const char *pszHost)
1428{
1429 CFArrayRef hArray;
1430
1431 /*
1432 * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply
1433 * proxy exclusion rules (tested on 10.9). So, do that manually.
1434 */
1435 RTNETADDRU HostAddr;
1436 int fIsHostIpv4Address = -1;
1437 char szTmp[_4K];
1438
1439 /* If we've got a simple hostname, something containing no dots, we must check
1440 whether such simple hostnames are excluded from proxying by default or not. */
1441 if (strchr(pszHost, '.') == NULL)
1442 {
1443 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false))
1444 return rtHttpUpdateAutomaticProxyDisable(pThis);
1445 fIsHostIpv4Address = false;
1446 }
1447
1448 /* Consult the exclusion list. This is an array of strings.
1449 This is very similar to what we do on windows. */
1450 hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList);
1451 if (hArray)
1452 {
1453 CFIndex const cEntries = CFArrayGetCount(hArray);
1454 for (CFIndex i = 0; i < cEntries; i++)
1455 {
1456 CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i);
1457 AssertContinue(hStr);
1458 AssertContinue(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8));
1459 RTStrToLower(szTmp);
1460
1461 bool fRet;
1462 if ( strchr(szTmp, '*')
1463 || strchr(szTmp, '?'))
1464 fRet = RTStrSimplePatternMatch(szTmp, pszHost);
1465 else
1466 {
1467 if (fIsHostIpv4Address == -1)
1468 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1469 RTNETADDRIPV4 Network, Netmask;
1470 if ( fIsHostIpv4Address
1471 && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) )
1472 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1473 else
1474 fRet = strcmp(szTmp, pszHost) == 0;
1475 }
1476 if (fRet)
1477 return rtHttpUpdateAutomaticProxyDisable(pThis);
1478 }
1479 }
1480
1481#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */
1482 /*
1483 * Is proxy auto config (PAC) enabled? If so, we must consult it first.
1484 */
1485 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false))
1486 {
1487 /* Convert the auto config url string to a CFURL object. */
1488 CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString);
1489 if (hStrAutoConfigUrl)
1490 {
1491 if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8))
1492 {
1493 CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp);
1494 if (hUrlScript)
1495 {
1496 int rcRet = VINF_NOT_SUPPORTED;
1497 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1498 if (hUrlTarget)
1499 {
1500 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1501 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1502 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1503 if (hArray)
1504 CFRelease(hArray);
1505
1506 hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1507 if (hArray)
1508 {
1509 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1510 CFRelease(hArray);
1511 }
1512 }
1513 CFRelease(hUrlScript);
1514 if (rcRet != VINF_NOT_SUPPORTED)
1515 return rcRet;
1516 }
1517 }
1518 }
1519 }
1520
1521 /*
1522 * Try static proxy configs.
1523 */
1524 /** @todo later if needed. */
1525 return VERR_NOT_SUPPORTED;
1526
1527#else
1528 /*
1529 * Simple solution - "just" use CFNetworkCopyProxiesForURL.
1530 */
1531 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1532 AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR);
1533 int rcRet = VINF_NOT_SUPPORTED;
1534
1535 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1536 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1537 CFDictionaryRef hDictNull = (CFDictionaryRef)(42-42); /*workaround for -Wnonnull warning in Clang 11. */
1538 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictNull);
1539 if (hArray)
1540 CFRelease(hArray);
1541
1542 /* The actual run. */
1543 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies);
1544 if (hArray)
1545 {
1546 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/);
1547 CFRelease(hArray);
1548 }
1549 CFRelease(hUrlTarget);
1550
1551 return rcRet;
1552#endif
1553}
1554
1555/**
1556 * Reconfigures the cURL proxy settings for the given URL, OS X style.
1557 *
1558 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1559 * @param pThis The HTTP client instance.
1560 * @param pszUrl The URL.
1561 */
1562static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1563{
1564 /*
1565 * Parse the URL, if there isn't any host name (like for file:///xxx.txt)
1566 * we don't need to run thru proxy settings to know what to do.
1567 */
1568 RTURIPARSED Parsed;
1569 int rc = RTUriParse(pszUrl, &Parsed);
1570 AssertRCReturn(rc, false);
1571 if (Parsed.cchAuthorityHost == 0)
1572 return rtHttpUpdateAutomaticProxyDisable(pThis);
1573 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1574 AssertReturn(pszHost, VERR_NO_STR_MEMORY);
1575 RTStrToLower(pszHost);
1576
1577 /*
1578 * Get a copy of the proxy settings (10.6 API).
1579 */
1580 CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */
1581 if (hDictProxies)
1582 rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, pszHost);
1583 else
1584 rc = VINF_NOT_SUPPORTED;
1585 CFRelease(hDictProxies);
1586
1587 RTStrFree(pszHost);
1588 return rc;
1589}
1590
1591#endif /* RT_OS_DARWIN */
1592
1593#ifdef RT_OS_WINDOWS
1594
1595/**
1596 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
1597 */
1598static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
1599{
1600 /*
1601 * winhttp.dll is not present on NT4 and probably was first introduced with XP.
1602 */
1603 RTLDRMOD hMod;
1604 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
1605 if (RT_SUCCESS(rc))
1606 {
1607 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
1608 if (RT_SUCCESS(rc))
1609 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
1610 if (RT_SUCCESS(rc))
1611 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
1612 if (RT_SUCCESS(rc))
1613 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
1614 if (RT_SUCCESS(rc))
1615 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
1616 RTLdrClose(hMod);
1617 AssertRC(rc);
1618 }
1619 else
1620 AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc));
1621
1622 NOREF(pvUser);
1623 return rc;
1624}
1625
1626
1627/**
1628 * Matches the URL against the given Windows by-pass list.
1629 *
1630 * @returns true if we should by-pass the proxy for this URL, false if not.
1631 * @param pszUrl The URL.
1632 * @param pwszBypass The Windows by-pass list.
1633 */
1634static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass)
1635{
1636 /*
1637 * Don't bother parsing the URL if we've actually got nothing to work with
1638 * in the by-pass list.
1639 */
1640 if (!pwszBypass)
1641 return false;
1642
1643 RTUTF16 wc;
1644 while ( (wc = *pwszBypass) != '\0'
1645 && ( RTUniCpIsSpace(wc)
1646 || wc == ';') )
1647 pwszBypass++;
1648 if (wc == '\0')
1649 return false;
1650
1651 /*
1652 * We now need to parse the URL and extract the host name.
1653 */
1654 RTURIPARSED Parsed;
1655 int rc = RTUriParse(pszUrl, &Parsed);
1656 AssertRCReturn(rc, false);
1657 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1658 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
1659 return false;
1660 RTStrToLower(pszHost);
1661
1662 bool fRet = false;
1663 char *pszBypassFree;
1664 rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree);
1665 if (RT_SUCCESS(rc))
1666 {
1667 /*
1668 * Walk the by-pass list.
1669 *
1670 * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx
1671 * a by-pass list is semicolon delimited list. The entries are either host
1672 * names or IP addresses, and may use wildcard ('*', '?', I guess). There
1673 * special "<local>" entry matches anything without a dot.
1674 */
1675 RTNETADDRU HostAddr = { 0, 0 };
1676 int fIsHostIpv4Address = -1;
1677 char *pszEntry = pszBypassFree;
1678 while (*pszEntry != '\0')
1679 {
1680 /*
1681 * Find end of entry.
1682 */
1683 char ch;
1684 size_t cchEntry = 1;
1685 while ( (ch = pszEntry[cchEntry]) != '\0'
1686 && ch != ';'
1687 && !RT_C_IS_SPACE(ch))
1688 cchEntry++;
1689
1690 char chSaved = pszEntry[cchEntry];
1691 pszEntry[cchEntry] = '\0';
1692 RTStrToLower(pszEntry);
1693
1694 if ( cchEntry == sizeof("<local>") - 1
1695 && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0)
1696 fRet = strchr(pszHost, '.') == NULL;
1697 else if ( memchr(pszEntry, '*', cchEntry) != NULL
1698 || memchr(pszEntry, '?', cchEntry) != NULL)
1699 fRet = RTStrSimplePatternMatch(pszEntry, pszHost);
1700 else
1701 {
1702 if (fIsHostIpv4Address == -1)
1703 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1704 RTNETADDRIPV4 Network, Netmask;
1705 if ( fIsHostIpv4Address
1706 && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) )
1707 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1708 else
1709 fRet = strcmp(pszEntry, pszHost) == 0;
1710 }
1711
1712 pszEntry[cchEntry] = chSaved;
1713 if (fRet)
1714 break;
1715
1716 /*
1717 * Next entry.
1718 */
1719 pszEntry += cchEntry;
1720 while ( (ch = *pszEntry) != '\0'
1721 && ( ch == ';'
1722 || RT_C_IS_SPACE(ch)) )
1723 pszEntry++;
1724 }
1725
1726 RTStrFree(pszBypassFree);
1727 }
1728
1729 RTStrFree(pszHost);
1730 return false;
1731}
1732
1733
1734/**
1735 * Searches a Windows proxy server list for the best fitting proxy to use, then
1736 * reconfigures the HTTP client instance to use it.
1737 *
1738 * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback.
1739 * @param pThis The HTTP client instance.
1740 * @param pszUrl The URL needing proxying.
1741 * @param pwszProxies The list of proxy servers to choose from.
1742 */
1743static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies)
1744{
1745 /*
1746 * Fend off empty strings (very unlikely, but just in case).
1747 */
1748 if (!pwszProxies)
1749 return VINF_NOT_SUPPORTED;
1750
1751 RTUTF16 wc;
1752 while ( (wc = *pwszProxies) != '\0'
1753 && ( RTUniCpIsSpace(wc)
1754 || wc == ';') )
1755 pwszProxies++;
1756 if (wc == '\0')
1757 return VINF_NOT_SUPPORTED;
1758
1759 /*
1760 * We now need to parse the URL and extract the scheme.
1761 */
1762 RTURIPARSED Parsed;
1763 int rc = RTUriParse(pszUrl, &Parsed);
1764 AssertRCReturn(rc, false);
1765 char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed);
1766 AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY);
1767 size_t const cchUrlScheme = strlen(pszUrlScheme);
1768
1769 int rcRet = VINF_NOT_SUPPORTED;
1770 char *pszProxiesFree;
1771 rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree);
1772 if (RT_SUCCESS(rc))
1773 {
1774 /*
1775 * Walk the server list.
1776 *
1777 * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx
1778 * this is also a semicolon delimited list. The entries are on the form:
1779 * [<scheme>=][<scheme>"://"]<server>[":"<port>]
1780 */
1781 bool fBestEntryHasSameScheme = false;
1782 const char *pszBestEntry = NULL;
1783 char *pszEntry = pszProxiesFree;
1784 while (*pszEntry != '\0')
1785 {
1786 /*
1787 * Find end of entry. We include spaces here in addition to ';'.
1788 */
1789 char ch;
1790 size_t cchEntry = 1;
1791 while ( (ch = pszEntry[cchEntry]) != '\0'
1792 && ch != ';'
1793 && !RT_C_IS_SPACE(ch))
1794 cchEntry++;
1795
1796 char const chSaved = pszEntry[cchEntry];
1797 pszEntry[cchEntry] = '\0';
1798
1799 /* Parse the entry. */
1800 const char *pszEndOfScheme = strstr(pszEntry, "://");
1801 const char *pszEqual = (const char *)memchr(pszEntry, '=',
1802 pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry);
1803 if (pszEqual)
1804 {
1805 if ( (uintptr_t)(pszEqual - pszEntry) == cchUrlScheme
1806 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0)
1807 {
1808 pszBestEntry = pszEqual + 1;
1809 break;
1810 }
1811 }
1812 else
1813 {
1814 bool fSchemeMatch = pszEndOfScheme
1815 && (uintptr_t)(pszEndOfScheme - pszEntry) == cchUrlScheme
1816 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0;
1817 if ( !pszBestEntry
1818 || ( !fBestEntryHasSameScheme
1819 && fSchemeMatch) )
1820 {
1821 pszBestEntry = pszEntry;
1822 fBestEntryHasSameScheme = fSchemeMatch;
1823 }
1824 }
1825
1826 /*
1827 * Next entry.
1828 */
1829 if (!chSaved)
1830 break;
1831 pszEntry += cchEntry + 1;
1832 while ( (ch = *pszEntry) != '\0'
1833 && ( ch == ';'
1834 || RT_C_IS_SPACE(ch)) )
1835 pszEntry++;
1836 }
1837
1838 /*
1839 * If we found something, try use it.
1840 */
1841 if (pszBestEntry)
1842 rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry);
1843
1844 RTStrFree(pszProxiesFree);
1845 }
1846
1847 RTStrFree(pszUrlScheme);
1848 return rc;
1849}
1850
1851
1852/**
1853 * Reconfigures the cURL proxy settings for the given URL, Windows style.
1854 *
1855 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1856 * @param pThis The HTTP client instance.
1857 * @param pszUrl The URL.
1858 */
1859static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1860{
1861 int rcRet = VINF_NOT_SUPPORTED;
1862
1863 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
1864 if (RT_SUCCESS(rc))
1865 {
1866 /*
1867 * Try get some proxy info for the URL. We first try getting the IE
1868 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
1869 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
1870 */
1871 WINHTTP_PROXY_INFO ProxyInfo;
1872 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
1873 RT_ZERO(AutoProxyOptions);
1874 RT_ZERO(ProxyInfo);
1875
1876 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
1877 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
1878 {
1879 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1880 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
1881 if (IeProxyConfig.fAutoDetect)
1882 {
1883 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
1884 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
1885 }
1886 else if (AutoProxyOptions.lpszAutoConfigUrl)
1887 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1888 else if (ProxyInfo.lpszProxy)
1889 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
1890 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
1891 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
1892 }
1893 else
1894 {
1895 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
1896 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
1897 {
1898 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
1899 RT_ZERO(ProxyInfo);
1900 }
1901 }
1902
1903 /*
1904 * Should we try WinHttGetProxyForUrl?
1905 */
1906 if (AutoProxyOptions.dwFlags != 0)
1907 {
1908 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
1909 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
1910 if (hSession != NULL)
1911 {
1912 PRTUTF16 pwszUrl;
1913 rc = RTStrToUtf16(pszUrl, &pwszUrl);
1914 if (RT_SUCCESS(rc))
1915 {
1916 /*
1917 * Try autodetect first, then fall back on the config URL if there is one.
1918 *
1919 * Also, we first try without auto authentication, then with. This will according
1920 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
1921 * caching the result when it's processed out-of-process (seems default here on W10).
1922 */
1923 WINHTTP_PROXY_INFO TmpProxyInfo;
1924 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1925 if ( !fRc
1926 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1927 {
1928 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1929 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1930 }
1931
1932 if ( !fRc
1933 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
1934 && AutoProxyOptions.lpszAutoConfigUrl)
1935 {
1936 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1937 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1938 AutoProxyOptions.dwAutoDetectFlags = 0;
1939 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1940 if ( !fRc
1941 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1942 {
1943 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1944 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1945 }
1946 }
1947
1948 if (fRc)
1949 {
1950 if (ProxyInfo.lpszProxy)
1951 GlobalFree(ProxyInfo.lpszProxy);
1952 if (ProxyInfo.lpszProxyBypass)
1953 GlobalFree(ProxyInfo.lpszProxyBypass);
1954 ProxyInfo = TmpProxyInfo;
1955 }
1956 /*
1957 * If the autodetection failed, assume no proxy.
1958 */
1959 else
1960 {
1961 DWORD dwErr = GetLastError();
1962 if ( dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED
1963 || dwErr == ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT
1964 || ( dwErr == ERROR_WINHTTP_UNRECOGNIZED_SCHEME
1965 && ( RTStrNICmp(pszUrl, RT_STR_TUPLE("https://")) == 0
1966 || RTStrNICmp(pszUrl, RT_STR_TUPLE("http://")) == 0) ) )
1967 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1968 else
1969 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl(%s) -> %u; lpszAutoConfigUrl=%sx\n",
1970 pszUrl, dwErr, AutoProxyOptions.lpszAutoConfigUrl));
1971 }
1972 RTUtf16Free(pwszUrl);
1973 }
1974 else
1975 {
1976 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
1977 rcRet = rc;
1978 }
1979 g_pfnWinHttpCloseHandle(hSession);
1980 }
1981 else
1982 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
1983 }
1984
1985 /*
1986 * Try use the proxy info we've found.
1987 */
1988 switch (ProxyInfo.dwAccessType)
1989 {
1990 case WINHTTP_ACCESS_TYPE_NO_PROXY:
1991 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1992 break;
1993
1994 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
1995 if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass))
1996 rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy);
1997 else
1998 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1999 break;
2000
2001 case 0:
2002 break;
2003
2004 default:
2005 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
2006 }
2007
2008 /*
2009 * Cleanup.
2010 */
2011 if (ProxyInfo.lpszProxy)
2012 GlobalFree(ProxyInfo.lpszProxy);
2013 if (ProxyInfo.lpszProxyBypass)
2014 GlobalFree(ProxyInfo.lpszProxyBypass);
2015 if (AutoProxyOptions.lpszAutoConfigUrl)
2016 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
2017 }
2018
2019 return rcRet;
2020}
2021
2022#endif /* RT_OS_WINDOWS */
2023
2024
2025static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
2026{
2027 if (pThis->fUseSystemProxySettings)
2028 {
2029#ifdef IPRT_USE_LIBPROXY
2030 int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl);
2031 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2032 return rc;
2033 Assert(rc == VINF_NOT_SUPPORTED);
2034#endif
2035#ifdef RT_OS_DARWIN
2036 int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl);
2037 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2038 return rc;
2039 Assert(rc == VINF_NOT_SUPPORTED);
2040#endif
2041#ifdef RT_OS_WINDOWS
2042 int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl);
2043 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2044 return rc;
2045 Assert(rc == VINF_NOT_SUPPORTED);
2046#endif
2047/** @todo system specific class here, fall back on env vars if necessary. */
2048 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
2049 }
2050
2051 return VINF_SUCCESS;
2052}
2053
2054
2055RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
2056 const char *pcszProxyUser, const char *pcszProxyPwd)
2057{
2058 PRTHTTPINTERNAL pThis = hHttp;
2059 RTHTTP_VALID_RETURN(pThis);
2060 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
2061 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
2062
2063 /*
2064 * Update the settings.
2065 *
2066 * Currently, we don't make alot of effort parsing or checking the input, we
2067 * leave that to cURL. (A bit afraid of breaking user settings.)
2068 */
2069 pThis->fUseSystemProxySettings = false;
2070 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
2071}
2072
2073
2074
2075/*********************************************************************************************************************************
2076* HTTP Headers *
2077*********************************************************************************************************************************/
2078
2079/**
2080 * Helper for RTHttpSetHeaders and RTHttpAddRawHeader that unsets the user agent
2081 * if it is now in one of the headers.
2082 */
2083static int rtHttpUpdateUserAgentHeader(PRTHTTPINTERNAL pThis, PRTHTTPHEADER pNewHdr)
2084{
2085 static const char s_szUserAgent[] = "User-Agent";
2086 if ( pNewHdr->cchName == sizeof(s_szUserAgent) - 1
2087 && RTStrNICmpAscii(pNewHdr->szData, RT_STR_TUPLE(s_szUserAgent)) == 0)
2088 {
2089 pThis->fHaveUserAgentHeader = true;
2090 if (pThis->fHaveSetUserAgent)
2091 {
2092 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL);
2093 Assert(CURL_SUCCESS(rcCurl)); NOREF(rcCurl);
2094 pThis->fHaveSetUserAgent = false;
2095 }
2096 }
2097 return VINF_SUCCESS;
2098}
2099
2100
2101/**
2102 * Free the headers associated with the instance (w/o telling cURL about it).
2103 *
2104 * @param pThis The HTTP client instance.
2105 */
2106static void rtHttpFreeHeaders(PRTHTTPINTERNAL pThis)
2107{
2108 struct curl_slist *pHead = pThis->pHeaders;
2109 pThis->pHeaders = NULL;
2110 pThis->ppHeadersTail = &pThis->pHeaders;
2111 pThis->fHaveUserAgentHeader = false;
2112
2113 while (pHead)
2114 {
2115 struct curl_slist *pFree = pHead;
2116 pHead = pHead->next;
2117 ASMCompilerBarrier(); /* paranoia */
2118
2119 pFree->next = NULL;
2120 pFree->data = NULL;
2121 RTMemFree(pFree);
2122 }
2123}
2124
2125
2126/**
2127 * Worker for RTHttpSetHeaders and RTHttpAddHeader.
2128 *
2129 * @returns IPRT status code.
2130 * @param pThis The HTTP client instance.
2131 * @param pchName The field name. Does not need to be terminated.
2132 * @param cchName The field name length.
2133 * @param pchValue The field value. Does not need to be terminated.
2134 * @param cchValue The field value length.
2135 * @param fFlags RTHTTPADDHDR_F_XXX.
2136 */
2137static int rtHttpAddHeaderWorker(PRTHTTPINTERNAL pThis, const char *pchName, size_t cchName,
2138 const char *pchValue, size_t cchValue, uint32_t fFlags)
2139{
2140 /*
2141 * Create the list entry.
2142 */
2143 size_t cbData = cchName + 2 + cchValue + 1;
2144 PRTHTTPHEADER pHdr = (PRTHTTPHEADER)RTMemAlloc(RT_UOFFSETOF_DYN(RTHTTPHEADER, szData[cbData]));
2145 if (pHdr)
2146 {
2147 pHdr->Core.next = NULL;
2148 pHdr->Core.data = pHdr->szData;
2149 pHdr->cchName = (uint32_t)cchName;
2150 pHdr->offValue = (uint32_t)(cchName + 2);
2151 char *psz = pHdr->szData;
2152 memcpy(psz, pchName, cchName);
2153 psz += cchName;
2154 *psz++ = ':';
2155 *psz++ = ' ';
2156 memcpy(psz, pchValue, cchValue);
2157 psz[cchValue] = '\0';
2158
2159 /*
2160 * Appending to an existing list requires no cURL interaction.
2161 */
2162 AssertCompile(RTHTTPADDHDR_F_FRONT != 0);
2163 if ( !(fFlags & RTHTTPADDHDR_F_FRONT)
2164 && pThis->pHeaders != NULL)
2165 {
2166 *pThis->ppHeadersTail = &pHdr->Core;
2167 pThis->ppHeadersTail = &pHdr->Core.next;
2168 return rtHttpUpdateUserAgentHeader(pThis, pHdr);
2169 }
2170
2171 /*
2172 * When prepending or adding the first header we need to inform cURL
2173 * about the new list head.
2174 */
2175 pHdr->Core.next = pThis->pHeaders;
2176 if (!pThis->pHeaders)
2177 pThis->ppHeadersTail = &pHdr->Core.next;
2178 pThis->pHeaders = &pHdr->Core;
2179
2180 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
2181 if (CURL_SUCCESS(rcCurl))
2182 return rtHttpUpdateUserAgentHeader(pThis, pHdr);
2183 return VERR_HTTP_CURL_ERROR;
2184 }
2185 return VERR_NO_MEMORY;
2186}
2187
2188
2189RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
2190{
2191 PRTHTTPINTERNAL pThis = hHttp;
2192 RTHTTP_VALID_RETURN(pThis);
2193
2194 /*
2195 * Drop old headers and reset state.
2196 */
2197 if (pThis->pHeaders)
2198 {
2199 rtHttpFreeHeaders(pThis);
2200 curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL);
2201 }
2202 pThis->ppHeadersTail = &pThis->pHeaders;
2203 pThis->fHaveUserAgentHeader = false;
2204
2205 /*
2206 * We're done if no headers specified.
2207 */
2208 if (!cHeaders)
2209 return VINF_SUCCESS;
2210
2211 /*
2212 * Add the headers, one by one.
2213 */
2214 int rc = VINF_SUCCESS;
2215 for (size_t i = 0; i < cHeaders; i++)
2216 {
2217 const char *pszHeader = papszHeaders[i];
2218 size_t cchHeader = strlen(pszHeader);
2219 size_t cchName = (const char *)memchr(pszHeader, ':', cchHeader) - pszHeader;
2220 AssertBreakStmt(cchName < cchHeader, rc = VERR_INVALID_PARAMETER);
2221 size_t offValue = RT_C_IS_BLANK(pszHeader[cchName + 1]) ? cchName + 2 : cchName + 1;
2222 rc = rtHttpAddHeaderWorker(pThis, pszHeader, cchName, &pszHeader[offValue], cchHeader - offValue, RTHTTPADDHDR_F_BACK);
2223 AssertRCBreak(rc);
2224 }
2225 if (RT_SUCCESS(rc))
2226 return rc;
2227 rtHttpFreeHeaders(pThis);
2228 curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL);
2229 return rc;
2230}
2231
2232
2233#if 0 /** @todo reimplement RTHttpAddRawHeader if ever actually needed. */
2234RTR3DECL(int) RTHttpAddRawHeader(RTHTTP hHttp, const char *pszHeader, uint32_t fFlags)
2235{
2236 PRTHTTPINTERNAL pThis = hHttp;
2237 RTHTTP_VALID_RETURN(pThis);
2238 AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS);
2239/** @todo implement RTHTTPADDHDR_F_FRONT */
2240
2241 /*
2242 * Append it to the header list, checking for User-Agent and such.
2243 */
2244 struct curl_slist *pHeaders = pThis->pHeaders;
2245 struct curl_slist *pNewHeaders = curl_slist_append(pHeaders, pszHeader);
2246 if (pNewHeaders)
2247 pHeaders = pNewHeaders;
2248 else
2249 return VERR_NO_MEMORY;
2250
2251 if (strncmp(pszHeader, RT_STR_TUPLE("User-Agent:")) == 0)
2252 pThis->fHaveUserAgentHeader = true;
2253
2254 /*
2255 * If this is the first header, we need to tell curl.
2256 */
2257 if (!pThis->pHeaders)
2258 {
2259 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
2260 if (CURL_FAILURE(rcCurl))
2261 {
2262 curl_slist_free_all(pHeaders);
2263 return VERR_INVALID_PARAMETER;
2264 }
2265 pThis->pHeaders = pHeaders;
2266 }
2267 else
2268 Assert(pThis->pHeaders == pHeaders);
2269
2270 rtHttpUpdateUserAgentHeader(pThis);
2271
2272 return VINF_SUCCESS;
2273}
2274#endif
2275
2276
2277RTR3DECL(int) RTHttpAddHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, size_t cchValue, uint32_t fFlags)
2278{
2279 /*
2280 * Validate input and calc string lengths.
2281 */
2282 PRTHTTPINTERNAL pThis = hHttp;
2283 RTHTTP_VALID_RETURN(pThis);
2284 AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS);
2285 AssertPtr(pszField);
2286 size_t const cchField = strlen(pszField);
2287 AssertReturn(cchField > 0, VERR_INVALID_PARAMETER);
2288 AssertReturn(pszField[cchField - 1] != ':', VERR_INVALID_PARAMETER);
2289 AssertReturn(!RT_C_IS_SPACE(pszField[cchField - 1]), VERR_INVALID_PARAMETER);
2290#ifdef RT_STRICT
2291 for (size_t i = 0; i < cchField; i++)
2292 {
2293 char const ch = pszField[i];
2294 Assert(RT_C_IS_PRINT(ch) && ch != ':');
2295 }
2296#endif
2297
2298 AssertPtr(pszValue);
2299 if (cchValue == RTSTR_MAX)
2300 cchValue = strlen(pszValue);
2301
2302 /*
2303 * Just pass it along to the worker.
2304 */
2305 return rtHttpAddHeaderWorker(pThis, pszField, cchField, pszValue, cchValue, fFlags);
2306}
2307
2308
2309RTR3DECL(const char *) RTHttpGetHeader(RTHTTP hHttp, const char *pszField, size_t cchField)
2310{
2311 PRTHTTPINTERNAL pThis = hHttp;
2312 RTHTTP_VALID_RETURN_RC(pThis, NULL);
2313
2314 PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders;
2315 if (pCur)
2316 {
2317 if (cchField == RTSTR_MAX)
2318 cchField = strlen(pszField);
2319 do
2320 {
2321 if ( pCur->cchName == cchField
2322 && RTStrNICmpAscii(pCur->szData, pszField, cchField) == 0)
2323 return &pCur->szData[pCur->offValue];
2324
2325 /* next field. */
2326 pCur = (PRTHTTPHEADER)pCur->Core.next;
2327 } while (pCur);
2328 }
2329 return NULL;
2330}
2331
2332
2333RTR3DECL(size_t) RTHttpGetHeaderCount(RTHTTP hHttp)
2334{
2335 PRTHTTPINTERNAL pThis = hHttp;
2336 RTHTTP_VALID_RETURN_RC(pThis, 0);
2337
2338 /* Note! Only for test cases and debugging, so we don't care about performance. */
2339 size_t cHeaders = 0;
2340 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next)
2341 cHeaders++;
2342 return cHeaders;
2343}
2344
2345
2346RTR3DECL(const char *) RTHttpGetByOrdinal(RTHTTP hHttp, size_t iOrdinal)
2347{
2348 PRTHTTPINTERNAL pThis = hHttp;
2349 RTHTTP_VALID_RETURN_RC(pThis, NULL);
2350
2351 /* Note! Only for test cases and debugging, so we don't care about performance. */
2352 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next)
2353 {
2354 if (iOrdinal == 0)
2355 return pCur->szData;
2356 iOrdinal--;
2357 }
2358
2359 return NULL;
2360}
2361
2362
2363
2364RTR3DECL(int) RTHttpSignHeaders(RTHTTP hHttp, RTHTTPMETHOD enmMethod, const char *pszUrl,
2365 RTCRKEY hKey, const char *pszKeyId, uint32_t fFlags)
2366{
2367 PRTHTTPINTERNAL pThis = hHttp;
2368 RTHTTP_VALID_RETURN(pThis);
2369 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
2370 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
2371 AssertReturn(!fFlags, VERR_INVALID_FLAGS);
2372 AssertPtrReturn(pszKeyId, VERR_INVALID_POINTER);
2373
2374 /*
2375 * Do a little bit of preprocessing while we can easily return without
2376 * needing clean anything up..
2377 */
2378 RTURIPARSED ParsedUrl;
2379 int rc = RTUriParse(pszUrl, &ParsedUrl);
2380 AssertRCReturn(rc, rc);
2381 const char * const pszPath = pszUrl + ParsedUrl.offPath;
2382
2383 const char *pszMethodSp = NULL;
2384 switch (enmMethod)
2385 {
2386 case RTHTTPMETHOD_GET: pszMethodSp = "get "; break;
2387 case RTHTTPMETHOD_PUT: pszMethodSp = "put "; break;
2388 case RTHTTPMETHOD_POST: pszMethodSp = "post "; break;
2389 case RTHTTPMETHOD_PATCH: pszMethodSp = "patch "; break;
2390 case RTHTTPMETHOD_DELETE: pszMethodSp = "delete "; break;
2391 case RTHTTPMETHOD_HEAD: pszMethodSp = "head "; break;
2392 case RTHTTPMETHOD_OPTIONS: pszMethodSp = "options "; break;
2393 case RTHTTPMETHOD_TRACE: pszMethodSp = "trace "; break;
2394#ifdef IPRT_HTTP_WITH_WEBDAV
2395 case RTHTTPMETHOD_PROPFIND: pszMethodSp = "propfind "; break;
2396#endif
2397 /* no default! */
2398 case RTHTTPMETHOD_INVALID:
2399 case RTHTTPMETHOD_END:
2400 case RTHTTPMETHOD_32BIT_HACK:
2401 break;
2402 }
2403 AssertReturn(pszMethodSp, VERR_INTERNAL_ERROR_4);
2404
2405 /*
2406 * We work the authorization header entry directly here to avoid extra copying and stuff.
2407 */
2408
2409 /* Estimate required string length first. */
2410 static const char s_szSuffixFmt[] = "Authorization: Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\"";
2411 static const char s_szInfix[] = "\",signature=\"";
2412 static const char s_szPostfix[] = "\"";
2413 static const char s_szRequestField[] = "(request-target)";
2414 size_t const cchKeyId = strlen(pszKeyId);
2415 size_t const cbSigRaw = (RTCrKeyGetBitCount(hKey) + 7) / 8;
2416 size_t const cbSigRawAligned = RT_ALIGN_Z(cbSigRaw, 8);
2417 size_t const cchSigStr = RTBase64EncodedLengthEx(cbSigRaw, RTBASE64_FLAGS_NO_LINE_BREAKS);
2418 size_t cbEstimated = sizeof(s_szSuffixFmt) + sizeof(s_szInfix) + sizeof(s_szPostfix)
2419 + cchKeyId + sizeof(s_szRequestField) + cchSigStr;
2420 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur; pCur = (PRTHTTPHEADER)pCur->Core.next)
2421 cbEstimated += pCur->cchName + 1;
2422 cbEstimated += 32; /* safetype fudge */
2423 /* Lazy bird: Put the raw signature at the end. */
2424 cbEstimated = RT_ALIGN_Z(cbEstimated, 8) + cbSigRawAligned;
2425
2426 /* Allocate and initialize header entry. */
2427 PRTHTTPHEADER const pHdr = (PRTHTTPHEADER)RTMemAllocZ(cbEstimated);
2428 AssertPtrReturn(pHdr, VERR_NO_MEMORY);
2429 uint8_t * const pbSigRaw = (uint8_t *)pHdr + cbEstimated - cbSigRawAligned;
2430
2431 pHdr->cchName = sizeof("Authorization") - 1;
2432 pHdr->offValue = sizeof("Authorization") + 1;
2433 pHdr->Core.next = NULL;
2434 pHdr->Core.data = pHdr->szData;
2435 char *pszLeft = pHdr->szData;
2436 size_t cbLeft = cbEstimated - RT_UOFFSETOF(RTHTTPHEADER, szData) - cbSigRawAligned;
2437
2438 size_t cch = RTStrPrintf(pszLeft, cbLeft, s_szSuffixFmt, pszKeyId);
2439 cbLeft -= cch;
2440 pszLeft += cch;
2441
2442 /*
2443 * Instantiate the digest.
2444 */
2445 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2446 rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
2447 if (RT_SUCCESS(rc))
2448 {
2449 /*
2450 * Add the request-target pseudo header first.
2451 */
2452 Assert(cbLeft > sizeof(s_szRequestField));
2453 memcpy(pszLeft, RT_STR_TUPLE(s_szRequestField));
2454 pszLeft += sizeof(s_szRequestField) - 1;
2455
2456 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(s_szRequestField));
2457 if (RT_SUCCESS(rc))
2458 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2459 if (RT_SUCCESS(rc))
2460 rc = RTCrDigestUpdate(hDigest, pszMethodSp, strlen(pszMethodSp));
2461 if (RT_SUCCESS(rc))
2462 rc = RTCrDigestUpdate(hDigest, pszPath, strlen(pszPath));
2463
2464 /*
2465 * Add the header fields.
2466 */
2467 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur && RT_SUCCESS(rc); pCur = (PRTHTTPHEADER)pCur->Core.next)
2468 {
2469 AssertBreakStmt(cbLeft > pCur->cchName, rc = VERR_INTERNAL_ERROR_3);
2470 *pszLeft++ = ' ';
2471 cbLeft--;
2472 memcpy(pszLeft, pCur->szData, pCur->cchName);
2473 pszLeft[pCur->cchName] = '\0';
2474 RTStrToLower(pszLeft);
2475
2476 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE("\n"));
2477 AssertRCBreak(rc);
2478 rc = RTCrDigestUpdate(hDigest, pszLeft, pCur->cchName);
2479 AssertRCBreak(rc);
2480 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2481 AssertRCBreak(rc);
2482 const char *pszValue = &pCur->szData[pCur->offValue];
2483 rc = RTCrDigestUpdate(hDigest, pszValue, strlen(pszValue));
2484 AssertRCBreak(rc);
2485
2486 pszLeft += pCur->cchName;
2487 cbLeft -= pCur->cchName;
2488 }
2489 if (RT_SUCCESS(rc))
2490 AssertStmt(cbLeft > sizeof(s_szInfix) + cchSigStr + sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2491 if (RT_SUCCESS(rc))
2492 {
2493 /* Complete the header field part. */
2494 memcpy(pszLeft, RT_STR_TUPLE(s_szInfix));
2495 pszLeft += sizeof(s_szInfix) - 1;
2496 cbLeft -= sizeof(s_szInfix) - 1;
2497
2498 /*
2499 * Sign the digest.
2500 */
2501 RTCRPKIXSIGNATURE hSigner;
2502 rc = RTCrPkixSignatureCreateByObjIdString(&hSigner, RTCR_PKCS1_SHA256_WITH_RSA_OID, hKey, NULL, true /*fSigning*/);
2503 AssertRC(rc);
2504 if (RT_SUCCESS(rc))
2505 {
2506 size_t cbActual = cbSigRawAligned;
2507 rc = RTCrPkixSignatureSign(hSigner, hDigest, pbSigRaw, &cbActual);
2508 AssertRC(rc);
2509 if (RT_SUCCESS(rc))
2510 {
2511 Assert(cbActual == cbSigRaw);
2512 RTCrPkixSignatureRelease(hSigner);
2513 hSigner = NIL_RTCRPKIXSIGNATURE;
2514 RTCrDigestRelease(hDigest);
2515 hDigest = NIL_RTCRDIGEST;
2516
2517 /*
2518 * Convert the signature to Base64 and append it to the string.
2519 */
2520 size_t cchActual;
2521 rc = RTBase64EncodeEx(pbSigRaw, cbActual, RTBASE64_FLAGS_NO_LINE_BREAKS, pszLeft, cbLeft, &cchActual);
2522 AssertRC(rc);
2523 if (RT_SUCCESS(rc))
2524 {
2525 Assert(cchActual == cchSigStr);
2526 pszLeft += cchActual;
2527 cbLeft -= cchActual;
2528
2529 /*
2530 * Append the postfix and add the header to the front of the list.
2531 */
2532 AssertStmt(cbLeft >= sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2533 if (RT_SUCCESS(rc))
2534 {
2535 memcpy(pszLeft, s_szPostfix, sizeof(s_szPostfix));
2536
2537 pHdr->Core.next = pThis->pHeaders;
2538 if (!pThis->pHeaders)
2539 pThis->ppHeadersTail = &pHdr->Core.next;
2540 pThis->pHeaders = &pHdr->Core;
2541
2542 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
2543 if (CURL_SUCCESS(rcCurl))
2544 return VINF_SUCCESS;
2545 rc = VERR_HTTP_CURL_ERROR;
2546 }
2547 }
2548 }
2549 RTCrPkixSignatureRelease(hSigner);
2550 }
2551 }
2552 RTCrDigestRelease(hDigest);
2553 }
2554 RTMemFree(pHdr);
2555 return rc;
2556}
2557
2558
2559/*********************************************************************************************************************************
2560* HTTPS and root certficates *
2561*********************************************************************************************************************************/
2562
2563/**
2564 * Set the CA file to NULL, deleting any temporary file if necessary.
2565 *
2566 * @param pThis The HTTP/HTTPS client instance.
2567 */
2568static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
2569{
2570 if (pThis->pszCaFile)
2571 {
2572 if (pThis->fDeleteCaFile)
2573 {
2574 int rc2 = RTFileDelete(pThis->pszCaFile); RT_NOREF_PV(rc2);
2575 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
2576 }
2577 RTStrFree(pThis->pszCaFile);
2578 pThis->pszCaFile = NULL;
2579 }
2580}
2581
2582
2583RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
2584{
2585 PRTHTTPINTERNAL pThis = hHttp;
2586 RTHTTP_VALID_RETURN(pThis);
2587
2588 rtHttpUnsetCaFile(pThis);
2589
2590 pThis->fDeleteCaFile = false;
2591 if (pszCaFile)
2592 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
2593 return VINF_SUCCESS;
2594}
2595
2596
2597RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
2598{
2599 PRTHTTPINTERNAL pThis = hHttp;
2600 RTHTTP_VALID_RETURN(pThis);
2601
2602 /*
2603 * Create a temporary file.
2604 */
2605 int rc = VERR_NO_STR_MEMORY;
2606 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
2607 if (pszCaFile)
2608 {
2609 RTFILE hFile;
2610 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
2611 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
2612 if (RT_SUCCESS(rc))
2613 {
2614 /*
2615 * Gather certificates into a temporary store and export them to the temporary file.
2616 */
2617 RTCRSTORE hStore;
2618 rc = RTCrStoreCreateInMem(&hStore, 256);
2619 if (RT_SUCCESS(rc))
2620 {
2621 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
2622 if (RT_SUCCESS(rc))
2623 /** @todo Consider adding an API for exporting to a RTFILE... */
2624 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2625 RTCrStoreRelease(hStore);
2626 }
2627 RTFileClose(hFile);
2628 if (RT_SUCCESS(rc))
2629 {
2630 /*
2631 * Set the CA file for the instance.
2632 */
2633 rtHttpUnsetCaFile(pThis);
2634
2635 pThis->fDeleteCaFile = true;
2636 pThis->pszCaFile = pszCaFile;
2637 return VINF_SUCCESS;
2638 }
2639
2640 int rc2 = RTFileDelete(pszCaFile);
2641 AssertRC(rc2);
2642 }
2643 else
2644 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
2645
2646 RTStrFree(pszCaFile);
2647 }
2648 return rc;
2649}
2650
2651
2652RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
2653{
2654 uint32_t const cBefore = RTCrStoreCertCount(hStore);
2655 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
2656 RT_NOREF_PV(fFlags);
2657
2658 /*
2659 * Add the user store, quitely ignoring any errors.
2660 */
2661 RTCRSTORE hSrcStore;
2662 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2663 if (RT_SUCCESS(rcUser))
2664 {
2665 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2666 hSrcStore);
2667 RTCrStoreRelease(hSrcStore);
2668 }
2669
2670 /*
2671 * Ditto for the system store.
2672 */
2673 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2674 if (RT_SUCCESS(rcSystem))
2675 {
2676 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2677 hSrcStore);
2678 RTCrStoreRelease(hSrcStore);
2679 }
2680
2681 /*
2682 * If the number of certificates increased, we consider it a success.
2683 */
2684 if (RTCrStoreCertCount(hStore) > cBefore)
2685 {
2686 if (RT_FAILURE(rcSystem))
2687 return -rcSystem;
2688 if (RT_FAILURE(rcUser))
2689 return -rcUser;
2690 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
2691 }
2692
2693 if (RT_FAILURE(rcSystem))
2694 return rcSystem;
2695 if (RT_FAILURE(rcUser))
2696 return rcUser;
2697 return VERR_NOT_FOUND;
2698}
2699
2700
2701RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
2702{
2703 RTCRSTORE hStore;
2704 int rc = RTCrStoreCreateInMem(&hStore, 256);
2705 if (RT_SUCCESS(rc))
2706 {
2707 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2708 if (RT_SUCCESS(rc))
2709 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2710 RTCrStoreRelease(hStore);
2711 }
2712 return rc;
2713}
2714
2715
2716RTR3DECL(bool) RTHttpGetVerifyPeer(RTHTTP hHttp)
2717{
2718 PRTHTTPINTERNAL pThis = hHttp;
2719 RTHTTP_VALID_RETURN_RC(pThis, false);
2720 return pThis->fVerifyPeer;
2721}
2722
2723
2724RTR3DECL(int) RTHttpSetVerifyPeer(RTHTTP hHttp, bool fVerify)
2725{
2726 PRTHTTPINTERNAL pThis = hHttp;
2727 RTHTTP_VALID_RETURN(pThis);
2728 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
2729
2730 if (pThis->fVerifyPeer != fVerify)
2731 {
2732 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSL_VERIFYPEER, (long)fVerify);
2733 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_SSL_VERIFYPEER=%RTbool: %d (%#x)\n", fVerify, rcCurl, rcCurl),
2734 VERR_HTTP_CURL_ERROR);
2735 pThis->fVerifyPeer = fVerify;
2736 }
2737
2738 return VINF_SUCCESS;
2739}
2740
2741
2742
2743/*********************************************************************************************************************************
2744* .......
2745*********************************************************************************************************************************/
2746
2747
2748/**
2749 * Figures out the IPRT status code for a GET.
2750 *
2751 * @returns IPRT status code.
2752 * @param pThis The HTTP/HTTPS client instance.
2753 * @param rcCurl What curl returned.
2754 * @param puHttpStatus Where to optionally return the HTTP status. If specified,
2755 * the HTTP statuses are not translated to IPRT status codes.
2756 */
2757static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, CURLcode rcCurl, uint32_t *puHttpStatus)
2758{
2759 int rc = VERR_HTTP_CURL_ERROR;
2760
2761 if (pThis->pszRedirLocation)
2762 {
2763 RTStrFree(pThis->pszRedirLocation);
2764 pThis->pszRedirLocation = NULL;
2765 }
2766 if (CURL_SUCCESS(rcCurl))
2767 {
2768 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2769 if (puHttpStatus)
2770 {
2771 *puHttpStatus = pThis->lLastResp;
2772 rc = VINF_SUCCESS;
2773 }
2774
2775 switch (pThis->lLastResp)
2776 {
2777 case 200:
2778 /* OK, request was fulfilled */
2779 case 204:
2780 /* empty response */
2781 rc = VINF_SUCCESS;
2782 break;
2783 case 301: /* Moved permantently. */
2784 case 302: /* Found / Moved temporarily. */
2785 case 303: /* See Other. */
2786 case 307: /* Temporary redirect. */
2787 case 308: /* Permanent redirect. */
2788 {
2789 const char *pszRedirect = NULL;
2790 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2791 size_t cb = pszRedirect ? strlen(pszRedirect) : 0;
2792 if (cb > 0 && cb < 2048)
2793 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2794 if (!puHttpStatus)
2795 rc = VERR_HTTP_REDIRECTED;
2796 break;
2797 }
2798 case 400:
2799 /* bad request */
2800 if (!puHttpStatus)
2801 rc = VERR_HTTP_BAD_REQUEST;
2802 break;
2803 case 403:
2804 /* forbidden, authorization will not help */
2805 if (!puHttpStatus)
2806 rc = VERR_HTTP_ACCESS_DENIED;
2807 break;
2808 case 404:
2809 /* URL not found */
2810 if (!puHttpStatus)
2811 rc = VERR_HTTP_NOT_FOUND;
2812 break;
2813 }
2814
2815 if (pThis->pszRedirLocation)
2816 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2817 else
2818 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2819 }
2820 else
2821 {
2822 switch (rcCurl)
2823 {
2824 case CURLE_URL_MALFORMAT:
2825 case CURLE_COULDNT_RESOLVE_HOST:
2826 rc = VERR_HTTP_HOST_NOT_FOUND;
2827 break;
2828 case CURLE_COULDNT_CONNECT:
2829 rc = VERR_HTTP_COULDNT_CONNECT;
2830 break;
2831 case CURLE_SSL_CONNECT_ERROR:
2832 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2833 break;
2834 case CURLE_SSL_CACERT:
2835 /* The peer certificate cannot be authenticated with the CA certificates
2836 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2837 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2838 break;
2839 case CURLE_SSL_CACERT_BADFILE:
2840 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2841 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2842 break;
2843 case CURLE_ABORTED_BY_CALLBACK:
2844 /* forcefully aborted */
2845 rc = VERR_HTTP_ABORTED;
2846 break;
2847 case CURLE_COULDNT_RESOLVE_PROXY:
2848 rc = VERR_HTTP_PROXY_NOT_FOUND;
2849 break;
2850 case CURLE_WRITE_ERROR:
2851 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2852 break;
2853 //case CURLE_READ_ERROR
2854
2855 default:
2856 break;
2857 }
2858 Log(("%s: %Rrc: %u = %s%s%.*s\n",
2859 __FUNCTION__,
2860 rc, rcCurl, curl_easy_strerror((CURLcode)rcCurl),
2861 pThis->szErrorBuffer[0] != '\0' ? ": " : "",
2862 (int) sizeof(pThis->szErrorBuffer),
2863 pThis->szErrorBuffer[0] != '\0' ? pThis->szErrorBuffer : ""));
2864 }
2865
2866 return rc;
2867}
2868
2869
2870/**
2871 * cURL callback for reporting progress, we use it for checking for abort.
2872 */
2873static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded) RT_NOTHROW_DEF
2874{
2875 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2876 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2877 RT_NOREF_PV(rdTotalUpload);
2878 RT_NOREF_PV(rdUploaded);
2879
2880 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2881
2882 if (pThis->pfnDownloadProgress)
2883 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2884
2885 return pThis->fAbort ? 1 : 0;
2886}
2887
2888
2889/**
2890 * Whether we're likely to need SSL to handle the give URL.
2891 *
2892 * @returns true if we need, false if we probably don't.
2893 * @param pszUrl The URL.
2894 */
2895static bool rtHttpNeedSsl(const char *pszUrl)
2896{
2897 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2898}
2899
2900
2901/**
2902 * Applies recoded settings to the cURL instance before doing work.
2903 *
2904 * @returns IPRT status code.
2905 * @param pThis The HTTP/HTTPS client instance.
2906 * @param pszUrl The URL.
2907 */
2908static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2909{
2910 /*
2911 * The URL.
2912 */
2913 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2914 if (CURL_FAILURE(rcCurl))
2915 return VERR_INVALID_PARAMETER;
2916
2917 /*
2918 * Proxy config.
2919 */
2920 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2921 if (RT_FAILURE(rc))
2922 return rc;
2923
2924 /*
2925 * Setup SSL. Can be a bit of work.
2926 */
2927 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2928 if (CURL_FAILURE(rcCurl))
2929 return VERR_INVALID_PARAMETER;
2930
2931 const char *pszCaFile = pThis->pszCaFile;
2932 if ( !pszCaFile
2933 && rtHttpNeedSsl(pszUrl))
2934 {
2935 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2936 if (RT_SUCCESS(rc))
2937 pszCaFile = pThis->pszCaFile;
2938 else
2939 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2940 }
2941 if (pszCaFile)
2942 {
2943 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2944 if (CURL_FAILURE(rcCurl))
2945 return VERR_HTTP_CURL_ERROR;
2946 }
2947
2948 /*
2949 * Progress/abort.
2950 */
2951 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2952 if (CURL_FAILURE(rcCurl))
2953 return VERR_HTTP_CURL_ERROR;
2954 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2955 if (CURL_FAILURE(rcCurl))
2956 return VERR_HTTP_CURL_ERROR;
2957 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2958 if (CURL_FAILURE(rcCurl))
2959 return VERR_HTTP_CURL_ERROR;
2960
2961 /*
2962 * Set default user agent string if necessary. Some websites take offence
2963 * if we don't set it.
2964 */
2965 if ( !pThis->fHaveSetUserAgent
2966 && !pThis->fHaveUserAgentHeader)
2967 {
2968 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2969 if (CURL_FAILURE(rcCurl))
2970 return VERR_HTTP_CURL_ERROR;
2971 pThis->fHaveSetUserAgent = true;
2972 }
2973
2974 /*
2975 * Use GET by default.
2976 */
2977 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
2978 if (CURL_FAILURE(rcCurl))
2979 return VERR_HTTP_CURL_ERROR;
2980 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
2981 if (CURL_FAILURE(rcCurl))
2982 return VERR_HTTP_CURL_ERROR;
2983
2984 return VINF_SUCCESS;
2985}
2986
2987
2988/**
2989 * Resets state.
2990 *
2991 * @param pThis HTTP client instance.
2992 */
2993static void rtHttpResetState(PRTHTTPINTERNAL pThis)
2994{
2995 pThis->fAbort = false;
2996 pThis->rcOutput = VINF_SUCCESS;
2997 pThis->uDownloadHttpStatus = UINT32_MAX;
2998 pThis->cbDownloadContent = UINT64_MAX;
2999 pThis->offDownloadContent = 0;
3000 pThis->offUploadContent = 0;
3001 pThis->rcOutput = VINF_SUCCESS;
3002 pThis->cbDownloadHint = 0;
3003 Assert(pThis->BodyOutput.pHttp == pThis);
3004 Assert(pThis->HeadersOutput.pHttp == pThis);
3005}
3006
3007
3008/**
3009 * Tries to determin uDownloadHttpStatus and cbDownloadContent.
3010 *
3011 * @param pThis HTTP client instance.
3012 */
3013static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis)
3014{
3015 long lHttpStatus = 0;
3016 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus);
3017 pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus;
3018
3019#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
3020 curl_off_t cbContent = -1;
3021 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent);
3022 if (cbContent >= 0)
3023 pThis->cbDownloadContent = (uint64_t)cbContent;
3024#else
3025 double rdContent = -1.0;
3026 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent);
3027 if (rdContent >= 0.0)
3028 pThis->cbDownloadContent = (uint64_t)rdContent;
3029#endif
3030}
3031
3032
3033/**
3034 * Worker for rtHttpWriteHeaderData and rtHttpWriteBodyData.
3035 */
3036static size_t rtHttpWriteDataToMemOutput(PRTHTTPINTERNAL pThis, RTHTTPOUTPUTDATA *pOutput, char const *pchBuf, size_t cbToAppend)
3037{
3038 /*
3039 * Do max size and overflow checks.
3040 */
3041 size_t const cbCurSize = pOutput->uData.Mem.cb;
3042 size_t const cbNewSize = cbCurSize + cbToAppend;
3043 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3044 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
3045 {
3046 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
3047 {
3048 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend);
3049 pOutput->uData.Mem.cb = cbNewSize;
3050 pOutput->uData.Mem.pb[cbNewSize] = '\0';
3051 return cbToAppend;
3052 }
3053
3054 /*
3055 * We need to reallocate the output buffer.
3056 */
3057 /** @todo this could do with a better strategy wrt growth. */
3058 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
3059 if ( cbAlloc <= pThis->cbDownloadHint
3060 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3061 && pOutput == &pThis->BodyOutput)
3062 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
3063
3064 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
3065 if (pbNew)
3066 {
3067 memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend);
3068 pbNew[cbNewSize] = '\0';
3069
3070 pOutput->uData.Mem.cbAllocated = cbAlloc;
3071 pOutput->uData.Mem.pb = pbNew;
3072 pOutput->uData.Mem.cb = cbNewSize;
3073 return cbToAppend;
3074 }
3075
3076 pThis->rcOutput = VERR_NO_MEMORY;
3077 }
3078 else
3079 pThis->rcOutput = VERR_TOO_MUCH_DATA;
3080
3081 /*
3082 * Failure - abort.
3083 */
3084 RTMemFree(pOutput->uData.Mem.pb);
3085 pOutput->uData.Mem.pb = NULL;
3086 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
3087 pThis->fAbort = true;
3088 return 0;
3089}
3090
3091
3092/**
3093 * cURL callback for writing body data.
3094 */
3095static size_t rtHttpWriteBodyData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3096{
3097 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3098 size_t const cbToAppend = cbUnit * cUnits;
3099
3100 /*
3101 * Check if this belongs to the body download callback.
3102 */
3103 if (pThis->pfnDownloadCallback)
3104 {
3105 if (pThis->offDownloadContent == 0)
3106 rtHttpGetDownloadStatusAndLength(pThis);
3107
3108 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3109 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3110 {
3111 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3112 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3113 if (RT_SUCCESS(rc))
3114 {
3115 pThis->offDownloadContent += cbToAppend;
3116 return cbToAppend;
3117 }
3118 if (RT_SUCCESS(pThis->rcOutput))
3119 pThis->rcOutput = rc;
3120 pThis->fAbort = true;
3121 return 0;
3122 }
3123 }
3124
3125 /*
3126 * Otherwise, copy to memory output buffer.
3127 */
3128 return rtHttpWriteDataToMemOutput(pThis, &pThis->BodyOutput, pchBuf, cbToAppend);
3129}
3130
3131
3132/**
3133 * cURL callback for writing header data.
3134 */
3135static size_t rtHttpWriteHeaderData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3136{
3137 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3138 size_t const cbToAppend = cbUnit * cUnits;
3139
3140 /*
3141 * Work the header callback, if one.
3142 * ASSUMES cURL is giving us one header at a time.
3143 */
3144 if (pThis->pfnHeaderCallback)
3145 {
3146 /*
3147 * Find the end of the field name first.
3148 */
3149 uint32_t uMatchWord;
3150 size_t cchField;
3151 const char *pchField = pchBuf;
3152 size_t cchValue;
3153 const char *pchValue = (const char *)memchr(pchBuf, ':', cbToAppend);
3154 if (pchValue)
3155 {
3156 cchField = pchValue - pchField;
3157 if (RT_LIKELY(cchField >= 3))
3158 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, RT_C_TO_LOWER(pchBuf[0]),
3159 RT_C_TO_LOWER(pchBuf[1]), RT_C_TO_LOWER(pchBuf[2]));
3160 else
3161 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField,
3162 cchField >= 1 ? RT_C_TO_LOWER(pchBuf[0]) : 0,
3163 cchField >= 2 ? RT_C_TO_LOWER(pchBuf[1]) : 0,
3164 0);
3165 pchValue++;
3166 cchValue = cbToAppend - cchField - 1;
3167 }
3168 /* Since cURL gives us the "HTTP/{version} {code} {status}" line too,
3169 we slap a fictitious field name ':http-status-line' in front of it. */
3170 else if (cbToAppend > 5 && pchBuf[0] == 'H' && pchBuf[1] == 'T' && pchBuf[2] == 'T' && pchBuf[3] == 'P' && pchBuf[4] == '/')
3171 {
3172 pchField = ":http-status-line";
3173 cchField = 17;
3174 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(17, ':', 'h', 't');
3175 pchValue = pchBuf;
3176 cchValue = cbToAppend;
3177 }
3178 /* cURL also gives us the empty line before the body, so we slap another
3179 fictitious field name ':end-of-headers' in front of it as well. */
3180 else if (cbToAppend == 2 && pchBuf[0] == '\r' && pchBuf[1] == '\n')
3181 {
3182 pchField = ":end-of-headers";
3183 cchField = 15;
3184 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(15, ':', 'e', 'n');
3185 pchValue = pchBuf;
3186 cchValue = cbToAppend;
3187 }
3188 else
3189 AssertMsgFailedReturn(("pchBuf=%.*s\n", cbToAppend, pchBuf), cbToAppend);
3190
3191 /*
3192 * Determin the field value, stripping one leading blank and all
3193 * trailing spaces.
3194 */
3195 if (cchValue > 0 && RT_C_IS_BLANK(*pchValue))
3196 pchValue++, cchValue--;
3197 while (cchValue > 0 && RT_C_IS_SPACE(pchValue[cchValue - 1]))
3198 cchValue--;
3199
3200 /*
3201 * Pass it to the callback.
3202 */
3203 Log6(("rtHttpWriteHeaderData: %.*s: %.*s\n", cchField, pchBuf, cchValue, pchValue));
3204 int rc = pThis->pfnHeaderCallback(pThis, uMatchWord, pchBuf, cchField,
3205 pchValue, cchValue, pThis->pvHeaderCallbackUser);
3206 if (RT_SUCCESS(rc))
3207 return cbToAppend;
3208
3209 /* Abort on error. */
3210 if (RT_SUCCESS(pThis->rcOutput))
3211 pThis->rcOutput = rc;
3212 pThis->fAbort = true;
3213 return 0;
3214 }
3215
3216 return rtHttpWriteDataToMemOutput(pThis, &pThis->HeadersOutput, pchBuf, cbToAppend);
3217}
3218
3219
3220/**
3221 * cURL callback for working the upload callback.
3222 */
3223static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3224{
3225 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3226 size_t const cbBuf = cbUnit * cUnits;
3227
3228 /* Get download info the first time we're called. */
3229 if (pThis->offDownloadContent == 0)
3230 rtHttpGetDownloadStatusAndLength(pThis);
3231
3232 /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */
3233 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3234 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3235 {
3236 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3237 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3238 if (RT_SUCCESS(rc))
3239 { /* likely */ }
3240 else
3241 {
3242 if (RT_SUCCESS(pThis->rcOutput))
3243 pThis->rcOutput = rc;
3244 pThis->fAbort = true;
3245 return 0;
3246 }
3247 }
3248 pThis->offDownloadContent += cbBuf;
3249 return cbBuf;
3250}
3251
3252
3253/**
3254 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
3255 */
3256static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3257{
3258 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3259 size_t const cbReq = cbUnit * cUnits;
3260 size_t const offMem = pThis->ReadData.Mem.offMem;
3261 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
3262 if (cbToCopy > cbReq)
3263 cbToCopy = cbReq;
3264 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
3265 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
3266 return cbToCopy;
3267}
3268
3269
3270/**
3271 * Callback feeding cURL data via the user upload callback.
3272 */
3273static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3274{
3275 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3276 size_t const cbReq = cbUnit * cUnits;
3277
3278 size_t cbActual = 0;
3279 int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser);
3280 if (RT_SUCCESS(rc))
3281 {
3282 pThis->offUploadContent += cbActual;
3283 return cbActual;
3284 }
3285
3286 if (RT_SUCCESS(pThis->rcOutput))
3287 pThis->rcOutput = rc;
3288 pThis->fAbort = true;
3289 return CURL_READFUNC_ABORT;
3290}
3291
3292
3293/**
3294 * Helper for installing a (body) write callback function.
3295 *
3296 * @returns cURL status code.
3297 * @param pThis The HTTP client instance.
3298 * @param pfnWrite The callback.
3299 * @param pvUser The callback user argument.
3300 */
3301static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3302{
3303 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
3304 if (CURL_SUCCESS(rcCurl))
3305 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
3306 return rcCurl;
3307}
3308
3309
3310/**
3311 * Helper for installing a header write callback function.
3312 *
3313 * @returns cURL status code.
3314 * @param pThis The HTTP client instance.
3315 * @param pfnWrite The callback.
3316 * @param pvUser The callback user argument.
3317 */
3318static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3319{
3320 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3321 if (CURL_SUCCESS(rcCurl))
3322 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3323 return rcCurl;
3324}
3325
3326
3327/**
3328 * Helper for installing a (body) read callback function.
3329 *
3330 * @returns cURL status code.
3331 * @param pThis The HTTP client instance.
3332 * @param pfnRead The callback.
3333 * @param pvUser The callback user argument.
3334 */
3335static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3336{
3337 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3338 if (CURL_SUCCESS(rcCurl))
3339 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3340 return rcCurl;
3341}
3342
3343
3344/**
3345 * Internal worker that performs a HTTP GET.
3346 *
3347 * @returns IPRT status code.
3348 * @param hHttp The HTTP/HTTPS client instance.
3349 * @param pszUrl The URL.
3350 * @param fNoBody Set to suppress the body.
3351 * @param ppvResponse Where to return the pointer to the allocated
3352 * response data (RTMemFree). There will always be
3353 * an zero terminator char after the response, that
3354 * is not part of the size returned via @a pcb.
3355 * @param pcb The size of the response data.
3356 *
3357 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
3358 * threads, because that will probably blow up!
3359 */
3360static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
3361{
3362 PRTHTTPINTERNAL pThis = hHttp;
3363 RTHTTP_VALID_RETURN(pThis);
3364
3365 /*
3366 * Reset the return values in case of more "GUI programming" on the client
3367 * side (i.e. a programming style not bothering checking return codes).
3368 */
3369 *ppvResponse = NULL;
3370 *pcb = 0;
3371
3372 /*
3373 * Set the busy flag (paranoia).
3374 */
3375 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3376 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3377
3378 /*
3379 * Reset the state and apply settings.
3380 */
3381 rtHttpResetState(pThis);
3382 int rc = rtHttpApplySettings(hHttp, pszUrl);
3383 if (RT_SUCCESS(rc))
3384 {
3385 RT_ZERO(pThis->BodyOutput.uData.Mem);
3386 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteBodyData, pThis);
3387 if (fNoBody)
3388 {
3389 if (CURL_SUCCESS(rcCurl))
3390 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3391 if (CURL_SUCCESS(rcCurl))
3392 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
3393 }
3394 if (CURL_SUCCESS(rcCurl))
3395 {
3396 /*
3397 * Perform the HTTP operation.
3398 */
3399 rcCurl = curl_easy_perform(pThis->pCurl);
3400 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3401 if (RT_SUCCESS(rc))
3402 rc = pThis->rcOutput;
3403 if (RT_SUCCESS(rc))
3404 {
3405 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
3406 *pcb = pThis->BodyOutput.uData.Mem.cb;
3407 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
3408 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3409 }
3410 else if (pThis->BodyOutput.uData.Mem.pb)
3411 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3412 RT_ZERO(pThis->BodyOutput.uData.Mem);
3413 }
3414 else
3415 rc = VERR_HTTP_CURL_ERROR;
3416 }
3417
3418 ASMAtomicWriteBool(&pThis->fBusy, false);
3419 return rc;
3420}
3421
3422
3423RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3424{
3425 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3426 uint8_t *pv;
3427 size_t cb;
3428 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
3429 if (RT_SUCCESS(rc))
3430 {
3431 if (pv) /* paranoia */
3432 *ppszNotUtf8 = (char *)pv;
3433 else
3434 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3435 }
3436 else
3437 *ppszNotUtf8 = NULL;
3438 return rc;
3439}
3440
3441
3442RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3443{
3444 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3445 uint8_t *pv;
3446 size_t cb;
3447 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
3448 if (RT_SUCCESS(rc))
3449 {
3450 if (pv) /* paranoia */
3451 *ppszNotUtf8 = (char *)pv;
3452 else
3453 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3454 }
3455 else
3456 *ppszNotUtf8 = NULL;
3457 return rc;
3458
3459}
3460
3461
3462RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
3463{
3464 RTMemFree(pszNotUtf8);
3465}
3466
3467
3468RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3469{
3470 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3471 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3472}
3473
3474
3475RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3476{
3477 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3478 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3479}
3480
3481
3482RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
3483{
3484 RTMemFree(pvResponse);
3485}
3486
3487
3488/**
3489 * cURL callback for writing data to a file.
3490 */
3491static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3492{
3493 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
3494 PRTHTTPINTERNAL pThis = pOutput->pHttp;
3495
3496 size_t cbWritten = 0;
3497 int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten);
3498 if (RT_SUCCESS(rc))
3499 return cbWritten;
3500
3501 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
3502 pThis->rcOutput = rc;
3503 return 0;
3504}
3505
3506
3507RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
3508{
3509 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
3510 PRTHTTPINTERNAL pThis = hHttp;
3511 RTHTTP_VALID_RETURN(pThis);
3512
3513 /*
3514 * Set the busy flag (paranoia).
3515 */
3516 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3517 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3518
3519 /*
3520 * Reset the state and apply settings.
3521 */
3522 rtHttpResetState(pThis);
3523 int rc = rtHttpApplySettings(hHttp, pszUrl);
3524 if (RT_SUCCESS(rc))
3525 {
3526 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3527 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
3528 if (CURL_SUCCESS(rcCurl))
3529 {
3530 /*
3531 * Open the output file.
3532 */
3533 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
3534 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
3535 if (RT_SUCCESS(rc))
3536 {
3537 /*
3538 * Perform the HTTP operation.
3539 */
3540 rcCurl = curl_easy_perform(pThis->pCurl);
3541 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3542 if (RT_SUCCESS(rc))
3543 rc = pThis->rcOutput;
3544
3545 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
3546 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
3547 rc = rc2;
3548 }
3549 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3550 }
3551 else
3552 rc = VERR_HTTP_CURL_ERROR;
3553 }
3554
3555 ASMAtomicWriteBool(&pThis->fBusy, false);
3556 return rc;
3557}
3558
3559
3560RTR3DECL(int) RTHttpQueryProxyInfoForUrl(RTHTTP hHttp, const char *pszUrl, PRTHTTPPROXYINFO pProxy)
3561{
3562 /*
3563 * Validate input and clear output.
3564 */
3565 Log(("RTHttpQueryProxyInfoForUrl: hHttp=%p pszUrl=%s pProxy=%s\n", hHttp, pszUrl, pProxy));
3566 RT_ZERO(*pProxy);
3567 pProxy->uProxyPort = UINT32_MAX;
3568
3569 PRTHTTPINTERNAL pThis = hHttp;
3570 RTHTTP_VALID_RETURN(pThis);
3571
3572 /*
3573 * Set up the proxy for the URL.
3574 */
3575 rtHttpResetState(pThis);
3576 /** @todo this does a bit too much (we don't need to set up SSL for instance). */
3577 int rc = rtHttpApplySettings(pThis, pszUrl);
3578 if (RT_SUCCESS(rc))
3579 {
3580 /*
3581 * Copy out the result.
3582 */
3583 if (pThis->fNoProxy)
3584 pProxy->enmProxyType = RTHTTPPROXYTYPE_NOPROXY;
3585 else
3586 {
3587 switch (pThis->enmProxyType)
3588 {
3589 case CURLPROXY_HTTP:
3590#ifdef CURL_AT_LEAST_VERSION
3591# if CURL_AT_LEAST_VERSION(7,19,4)
3592 case CURLPROXY_HTTP_1_0:
3593# endif
3594#endif
3595 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTP;
3596 break;
3597#ifdef CURL_AT_LEAST_VERSION
3598# if CURL_AT_LEAST_VERSION(7,52,0)
3599 case CURLPROXY_HTTPS:
3600 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTPS;
3601 break;
3602# endif
3603#endif
3604 case CURLPROXY_SOCKS4:
3605 case CURLPROXY_SOCKS4A:
3606 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS4;
3607 break;
3608 case CURLPROXY_SOCKS5:
3609 case CURLPROXY_SOCKS5_HOSTNAME:
3610 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS5;
3611 break;
3612 default:
3613 AssertFailed();
3614 pProxy->enmProxyType = RTHTTPPROXYTYPE_UNKNOWN;
3615 break;
3616 }
3617 pProxy->uProxyPort = pThis->uProxyPort;
3618 if (pThis->pszProxyHost != NULL)
3619 {
3620 rc = RTStrDupEx(&pProxy->pszProxyHost, pThis->pszProxyHost);
3621 if (pThis->pszProxyUsername && RT_SUCCESS(rc))
3622 rc = RTStrDupEx(&pProxy->pszProxyUsername, pThis->pszProxyUsername);
3623 if (pThis->pszProxyPassword && RT_SUCCESS(rc))
3624 rc = RTStrDupEx(&pProxy->pszProxyPassword, pThis->pszProxyPassword);
3625 if (RT_FAILURE(rc))
3626 RTHttpFreeProxyInfo(pProxy);
3627 }
3628 else
3629 {
3630 AssertFailed();
3631 rc = VERR_INTERNAL_ERROR;
3632 }
3633 }
3634 }
3635 return rc;
3636}
3637
3638
3639RTR3DECL(int) RTHttpFreeProxyInfo(PRTHTTPPROXYINFO pProxy)
3640{
3641 if (pProxy)
3642 {
3643 RTStrFree(pProxy->pszProxyHost);
3644 RTStrFree(pProxy->pszProxyUsername);
3645 RTStrFree(pProxy->pszProxyPassword);
3646 pProxy->pszProxyHost = NULL;
3647 pProxy->pszProxyUsername = NULL;
3648 pProxy->pszProxyPassword = NULL;
3649 pProxy->enmProxyType = RTHTTPPROXYTYPE_INVALID;
3650 pProxy->uProxyPort = UINT32_MAX;
3651 }
3652 return VINF_SUCCESS;
3653}
3654
3655
3656RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
3657 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
3658{
3659 /*
3660 * Set safe return values and validate input.
3661 */
3662 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
3663 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
3664
3665 if (ppvHeaders)
3666 *ppvHeaders = NULL;
3667 if (pcbHeaders)
3668 *pcbHeaders = 0;
3669 if (ppvBody)
3670 *ppvBody = NULL;
3671 if (pcbBody)
3672 *pcbBody = 0;
3673 if (puHttpStatus)
3674 *puHttpStatus = UINT32_MAX;
3675
3676 PRTHTTPINTERNAL pThis = hHttp;
3677 RTHTTP_VALID_RETURN(pThis);
3678 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
3679 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
3680
3681#ifdef LOG_ENABLED
3682 if (LogIs4Enabled() && pThis->pHeaders)
3683 {
3684 Log4(("RTHttpPerform: headers:\n"));
3685 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
3686 Log4(("%s\n", pCur->data));
3687 }
3688 if (pvReqBody && cbReqBody)
3689 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
3690#endif
3691
3692 /*
3693 * Set the busy flag (paranoia).
3694 */
3695 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3696 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3697
3698 /*
3699 * Reset the state and apply settings.
3700 */
3701 rtHttpResetState(pThis);
3702 int rc = rtHttpApplySettings(hHttp, pszUrl);
3703 if (RT_SUCCESS(rc))
3704 {
3705 /* Set the HTTP method. */
3706 CURLcode rcCurl = CURLE_BAD_FUNCTION_ARGUMENT;
3707 switch (enmMethod)
3708 {
3709 case RTHTTPMETHOD_GET:
3710 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3711 break;
3712 case RTHTTPMETHOD_PUT:
3713 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3714 break;
3715 case RTHTTPMETHOD_POST:
3716 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3717 break;
3718 case RTHTTPMETHOD_PATCH:
3719 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
3720 break;
3721 case RTHTTPMETHOD_DELETE:
3722 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
3723 break;
3724 case RTHTTPMETHOD_HEAD:
3725 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3726 if (CURL_SUCCESS(rcCurl))
3727 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3728 break;
3729 case RTHTTPMETHOD_OPTIONS:
3730 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
3731 break;
3732 case RTHTTPMETHOD_TRACE:
3733 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
3734 break;
3735#ifdef IPRT_HTTP_WITH_WEBDAV
3736 case RTHTTPMETHOD_PROPFIND:
3737 RT_FALL_THROUGH();
3738#endif
3739 case RTHTTPMETHOD_END:
3740 case RTHTTPMETHOD_INVALID:
3741 case RTHTTPMETHOD_32BIT_HACK:
3742 AssertFailed();
3743 }
3744
3745 /* Request body. POST requests should always have a body. */
3746 if ( pvReqBody
3747 && CURL_SUCCESS(rcCurl)
3748 && ( cbReqBody > 0
3749 || enmMethod == RTHTTPMETHOD_POST) )
3750 {
3751 if (enmMethod == RTHTTPMETHOD_POST)
3752 {
3753 /** @todo ??? */
3754 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
3755 if (CURL_SUCCESS(rcCurl))
3756 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
3757 }
3758 else
3759 {
3760 pThis->ReadData.Mem.pvMem = pvReqBody;
3761 pThis->ReadData.Mem.cbMem = cbReqBody;
3762 pThis->ReadData.Mem.offMem = 0;
3763 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis);
3764 /* curl will use chunked transfer is it doesn't know the body size */
3765 if (enmMethod == RTHTTPMETHOD_PUT && CURL_SUCCESS(rcCurl))
3766 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbReqBody);
3767 }
3768 }
3769 else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl))
3770 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis);
3771
3772 /* Headers. */
3773 if (CURL_SUCCESS(rcCurl))
3774 {
3775 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3776 rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteHeaderData, pThis);
3777 }
3778
3779 /* Body */
3780 if (ppvBody && CURL_SUCCESS(rcCurl))
3781 {
3782 RT_ZERO(pThis->BodyOutput.uData.Mem);
3783 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteBodyData, pThis);
3784 }
3785 else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl))
3786 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis);
3787
3788 if (CURL_SUCCESS(rcCurl))
3789 {
3790 /*
3791 * Perform the HTTP operation.
3792 */
3793 rcCurl = curl_easy_perform(pThis->pCurl);
3794 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
3795 if (RT_SUCCESS(rc))
3796 rc = pThis->rcOutput;
3797 if (RT_SUCCESS(rc))
3798 {
3799 if (ppvHeaders)
3800 {
3801 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
3802 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
3803 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
3804 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
3805 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
3806 pThis->HeadersOutput.uData.Mem.pb = NULL;
3807 }
3808 if (ppvBody)
3809 {
3810 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
3811 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3812 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3813 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3814 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3815 pThis->BodyOutput.uData.Mem.pb = NULL;
3816 }
3817 }
3818 }
3819 else
3820 rc = VERR_HTTP_CURL_ERROR;
3821
3822 /* Ensure we've freed all unused output and dropped references to input memory.*/
3823 if (pThis->HeadersOutput.uData.Mem.pb)
3824 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3825 if (pThis->BodyOutput.uData.Mem.pb)
3826 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3827 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3828 RT_ZERO(pThis->BodyOutput.uData.Mem);
3829 RT_ZERO(pThis->ReadData);
3830 }
3831
3832 ASMAtomicWriteBool(&pThis->fBusy, false);
3833 return rc;
3834}
3835
3836
3837/*********************************************************************************************************************************
3838* Callback APIs. *
3839*********************************************************************************************************************************/
3840
3841RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser)
3842{
3843 PRTHTTPINTERNAL pThis = hHttp;
3844 RTHTTP_VALID_RETURN(pThis);
3845
3846 pThis->pfnUploadCallback = pfnCallback;
3847 pThis->pvUploadCallbackUser = pvUser;
3848 pThis->cbUploadContent = cbContent;
3849 pThis->offUploadContent = 0;
3850
3851 if (cbContent != UINT64_MAX)
3852 {
3853 AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t));
3854 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent);
3855 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR);
3856 }
3857 return VINF_SUCCESS;
3858}
3859
3860
3861RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser)
3862{
3863 PRTHTTPINTERNAL pThis = hHttp;
3864 RTHTTP_VALID_RETURN(pThis);
3865 AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS);
3866
3867 pThis->pfnDownloadCallback = pfnCallback;
3868 pThis->pvDownloadCallbackUser = pvUser;
3869 pThis->fDownloadCallback = fFlags;
3870 pThis->uDownloadHttpStatus = UINT32_MAX;
3871 pThis->cbDownloadContent = UINT64_MAX;
3872 pThis->offDownloadContent = 0;
3873
3874 return VINF_SUCCESS;
3875}
3876
3877
3878RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser)
3879{
3880 PRTHTTPINTERNAL pThis = hHttp;
3881 RTHTTP_VALID_RETURN(pThis);
3882
3883 pThis->pfnDownloadProgress = pfnCallback;
3884 pThis->pvDownloadProgressUser = pvUser;
3885 return VINF_SUCCESS;
3886}
3887
3888
3889RTR3DECL(int) RTHttpSetHeaderCallback(RTHTTP hHttp, PFNRTHTTPHEADERCALLBACK pfnCallback, void *pvUser)
3890{
3891 PRTHTTPINTERNAL pThis = hHttp;
3892 RTHTTP_VALID_RETURN(pThis);
3893
3894 pThis->pfnHeaderCallback = pfnCallback;
3895 pThis->pvHeaderCallbackUser = pvUser;
3896 return VINF_SUCCESS;
3897}
3898
3899
3900/*********************************************************************************************************************************
3901* Temporary raw cURL stuff. Will be gone before 6.0 is out! *
3902*********************************************************************************************************************************/
3903
3904RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3905{
3906 CURLcode rcCurl;
3907
3908 PRTHTTPINTERNAL pThis = hHttp;
3909 RTHTTP_VALID_RETURN(pThis);
3910
3911 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
3912 if (RT_FAILURE(rc))
3913 return rc;
3914
3915 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3916 if (CURL_FAILURE(rcCurl))
3917 return VERR_HTTP_CURL_ERROR;
3918
3919 return VINF_SUCCESS;
3920}
3921
3922
3923RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3924{
3925 CURLcode rcCurl;
3926
3927 PRTHTTPINTERNAL pThis = hHttp;
3928 RTHTTP_VALID_RETURN(pThis);
3929
3930 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3931 if (CURL_FAILURE(rcCurl))
3932 return VERR_HTTP_CURL_ERROR;
3933
3934 return VINF_SUCCESS;
3935}
3936
3937
3938RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3939{
3940 CURLcode rcCurl;
3941
3942 PRTHTTPINTERNAL pThis = hHttp;
3943 RTHTTP_VALID_RETURN(pThis);
3944
3945 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3946 if (CURL_FAILURE(rcCurl))
3947 return VERR_HTTP_CURL_ERROR;
3948
3949 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3950 if (CURL_FAILURE(rcCurl))
3951 return VERR_HTTP_CURL_ERROR;
3952
3953 return VINF_SUCCESS;
3954}
3955
3956
3957RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3958{
3959 CURLcode rcCurl;
3960
3961 PRTHTTPINTERNAL pThis = hHttp;
3962 RTHTTP_VALID_RETURN(pThis);
3963
3964 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3965 if (CURL_FAILURE(rcCurl))
3966 return VERR_HTTP_CURL_ERROR;
3967
3968 return VINF_SUCCESS;
3969}
3970
3971
3972RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
3973{
3974 CURLcode rcCurl;
3975
3976 PRTHTTPINTERNAL pThis = hHttp;
3977 RTHTTP_VALID_RETURN(pThis);
3978
3979 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3980 if (CURL_FAILURE(rcCurl))
3981 return VERR_HTTP_CURL_ERROR;
3982
3983 return VINF_SUCCESS;
3984}
3985
3986
3987RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
3988{
3989 /* curl doesn't provide an option for this */
3990 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
3991}
3992
3993
3994RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
3995{
3996 CURLcode rcCurl;
3997
3998 PRTHTTPINTERNAL pThis = hHttp;
3999 RTHTTP_VALID_RETURN(pThis);
4000
4001 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
4002 if (CURL_FAILURE(rcCurl))
4003 return VERR_HTTP_CURL_ERROR;
4004
4005 return VINF_SUCCESS;
4006}
4007
4008
4009RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
4010{
4011 CURLcode rcCurl;
4012
4013 PRTHTTPINTERNAL pThis = hHttp;
4014 RTHTTP_VALID_RETURN(pThis);
4015
4016 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
4017 if (CURL_FAILURE(rcCurl))
4018 return VERR_HTTP_CURL_ERROR;
4019
4020 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
4021 if (CURL_FAILURE(rcCurl))
4022 return VERR_HTTP_CURL_ERROR;
4023
4024 return VINF_SUCCESS;
4025}
4026
4027RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
4028{
4029 CURLcode rcCurl;
4030
4031 PRTHTTPINTERNAL pThis = hHttp;
4032 RTHTTP_VALID_RETURN(pThis);
4033
4034 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
4035 if (CURL_FAILURE(rcCurl))
4036 return VERR_HTTP_CURL_ERROR;
4037
4038 return VINF_SUCCESS;
4039}
4040
4041
4042RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
4043{
4044 CURLcode rcCurl;
4045
4046 PRTHTTPINTERNAL pThis = hHttp;
4047 RTHTTP_VALID_RETURN(pThis);
4048
4049 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
4050 if (CURL_FAILURE(rcCurl))
4051 return VERR_HTTP_CURL_ERROR;
4052
4053 return VINF_SUCCESS;
4054}
4055
4056
4057RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
4058{
4059 CURLcode rcCurl;
4060
4061 PRTHTTPINTERNAL pThis = hHttp;
4062 RTHTTP_VALID_RETURN(pThis);
4063
4064 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
4065 if (CURL_FAILURE(rcCurl))
4066 return VERR_HTTP_CURL_ERROR;
4067
4068 return VINF_SUCCESS;
4069}
4070
4071
4072RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
4073{
4074 CURLcode rcCurl;
4075
4076 PRTHTTPINTERNAL pThis = hHttp;
4077 RTHTTP_VALID_RETURN(pThis);
4078
4079 /*
4080 * XXX: Do this here for now as a stop-gap measure as
4081 * RTHttpReset() resets this (and proxy settings).
4082 */
4083 if (pThis->pszCaFile)
4084 {
4085 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile);
4086 if (CURL_FAILURE(rcCurl))
4087 return VERR_HTTP_CURL_ERROR;
4088 }
4089
4090 rcCurl = curl_easy_perform(pThis->pCurl);
4091 if (CURL_FAILURE(rcCurl))
4092 return VERR_HTTP_CURL_ERROR;
4093
4094 return VINF_SUCCESS;
4095}
4096
4097
4098RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
4099{
4100 CURLcode rcCurl;
4101
4102 PRTHTTPINTERNAL pThis = hHttp;
4103 RTHTTP_VALID_RETURN(pThis);
4104 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
4105
4106 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
4107 if (CURL_FAILURE(rcCurl))
4108 return VERR_HTTP_CURL_ERROR;
4109
4110 return VINF_SUCCESS;
4111}
4112
4113
4114RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
4115{
4116 CURLcode rcCurl;
4117
4118 PRTHTTPINTERNAL pThis = hHttp;
4119 RTHTTP_VALID_RETURN(pThis);
4120
4121 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
4122 if (CURL_FAILURE(rcCurl))
4123 return VERR_HTTP_CURL_ERROR;
4124
4125 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
4126 if (CURL_FAILURE(rcCurl))
4127 return VERR_HTTP_CURL_ERROR;
4128
4129 return VINF_SUCCESS;
4130}
4131
4132
4133RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4134{
4135 PRTHTTPINTERNAL pThis = hHttp;
4136 RTHTTP_VALID_RETURN(pThis);
4137
4138 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
4139 if (CURL_FAILURE(rcCurl))
4140 return VERR_HTTP_CURL_ERROR;
4141
4142 return VINF_SUCCESS;
4143}
4144
4145
4146RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4147{
4148 CURLcode rcCurl;
4149
4150 PRTHTTPINTERNAL pThis = hHttp;
4151 RTHTTP_VALID_RETURN(pThis);
4152
4153 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
4154 if (CURL_FAILURE(rcCurl))
4155 return VERR_HTTP_CURL_ERROR;
4156
4157 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
4158 if (CURL_FAILURE(rcCurl))
4159 return VERR_HTTP_CURL_ERROR;
4160
4161 return VINF_SUCCESS;
4162}
4163
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