VirtualBox

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

Last change on this file since 102563 was 102563, checked in by vboxsync, 12 months ago

IPRT/http-curl: Added trying to use the dedicated px_proxy_factory_free_proxies() API if available, instead of free'ing stuff ourselves. Only for libproxy >= 0.4.16.

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