VirtualBox

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

Last change on this file since 94016 was 93115, checked in by vboxsync, 3 years ago

scm --update-copyright-year

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