VirtualBox

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

Last change on this file since 107346 was 106660, checked in by vboxsync, 3 months ago

IPRT/http-curl: In RTHttpSetProxyByUrl() disable curl's use of the
system proxy when a manual proxy URL has been supplied. bugref:10793

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 143.7 KB
Line 
1/* $Id: http-curl.cpp 106660 2024-10-24 12:58:33Z 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-2024 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/** Can be NULL, as it was introduced with libproxy v0.4.16 (2020-12-04). */
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
942 pThis->fUseSystemProxySettings = false;
943
944 if (RTStrNICmpAscii(pszUrl, RT_STR_TUPLE("direct://")) == 0)
945 return rtHttpUpdateAutomaticProxyDisable(pThis);
946 return rtHttpConfigureProxyFromUrl(pThis, pszUrl);
947}
948
949
950/**
951 * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out
952 * the proxy config.
953 *
954 * @returns IPRT status code.
955 * @param pThis The HTTP client instance.
956 * @param pszUrl The URL to configure a proxy for.
957 */
958static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl)
959{
960 char szTmp[_1K];
961
962 /*
963 * First we consult the "no_proxy" / "NO_PROXY" environment variable.
964 */
965 const char *pszNoProxyVar;
966 size_t cchActual;
967 char *pszNoProxyFree = NULL;
968 char *pszNoProxy = szTmp;
969 int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual);
970 if (rc == VERR_ENV_VAR_NOT_FOUND)
971 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual);
972 if (rc == VERR_BUFFER_OVERFLOW)
973 {
974 pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K);
975 AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY);
976 rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL);
977 }
978 AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc));
979 bool fNoProxy = false;
980 if (RT_SUCCESS(rc))
981 fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy));
982 RTMemTmpFree(pszNoProxyFree);
983 if (!fNoProxy)
984 {
985 /*
986 * Get the schema specific specific env var, falling back on the
987 * generic all_proxy if not found.
988 */
989 const char *apszEnvVars[4];
990 unsigned cEnvVars = 0;
991 if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:")))
992 apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */
993 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")))
994 {
995 apszEnvVars[cEnvVars++] = "https_proxy";
996 apszEnvVars[cEnvVars++] = "HTTPS_PROXY";
997 }
998 else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:")))
999 {
1000 apszEnvVars[cEnvVars++] = "ftp_proxy";
1001 apszEnvVars[cEnvVars++] = "FTP_PROXY";
1002 }
1003 else
1004 AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED);
1005 apszEnvVars[cEnvVars++] = "all_proxy";
1006 apszEnvVars[cEnvVars++] = "ALL_PROXY";
1007
1008 /*
1009 * We try the env vars out and goes with the first one we can make sense out of.
1010 * If we cannot make sense of any, we return the first unexpected rc we got.
1011 */
1012 rc = VINF_SUCCESS;
1013 for (uint32_t i = 0; i < cEnvVars; i++)
1014 {
1015 size_t cchValue;
1016 int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue);
1017 if (RT_SUCCESS(rc2))
1018 {
1019 if (cchValue != 0)
1020 {
1021 /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */
1022 if (!strstr(szTmp, "://"))
1023 {
1024 memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1);
1025 memcpy(szTmp, RT_STR_TUPLE("http://"));
1026 }
1027
1028 rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp);
1029 if (RT_SUCCESS(rc2))
1030 rc = rc2;
1031 }
1032 /*
1033 * The variable is empty. Guess that means no proxying wanted.
1034 */
1035 else
1036 {
1037 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
1038 break;
1039 }
1040 }
1041 else
1042 AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2);
1043 }
1044 }
1045 /*
1046 * The host is the no-proxy list, it seems.
1047 */
1048 else
1049 rc = rtHttpUpdateAutomaticProxyDisable(pThis);
1050
1051 return rc;
1052}
1053
1054#ifdef IPRT_USE_LIBPROXY
1055
1056/**
1057 * @callback_method_impl{FNRTONCE,
1058 * Attempts to load libproxy.so.1 and resolves APIs}
1059 */
1060static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser)
1061{
1062 RTLDRMOD hMod;
1063 int rc = RTLdrLoadSystem("libproxy.so.1", false /*fNoUnload*/, &hMod);
1064 if (RT_SUCCESS(rc))
1065 {
1066 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor);
1067 if (RT_SUCCESS(rc))
1068 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor);
1069 if (RT_SUCCESS(rc))
1070 rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies);
1071 if (RT_SUCCESS(rc))
1072 /* libproxy < 0.4.16 does not have this function, so ignore the return code. */
1073 RTLdrGetSymbol(hMod, "px_proxy_factory_free_proxies", (void **)&g_pfnLibProxyFactoryFreeProxies);
1074 if (RT_SUCCESS(rc))
1075 {
1076 RTMEM_WILL_LEAK(hMod);
1077 g_hLdrLibProxy = hMod;
1078 }
1079 else
1080 RTLdrClose(hMod);
1081 AssertRC(rc);
1082 }
1083
1084 NOREF(pvUser);
1085 return rc;
1086}
1087
1088/**
1089 * Reconfigures the cURL proxy settings for the given URL, libproxy style.
1090 *
1091 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1092 * @param pThis The HTTP client instance.
1093 * @param pszUrl The URL.
1094 */
1095static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1096{
1097 int rcRet = VINF_NOT_SUPPORTED;
1098
1099 int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL);
1100 if (RT_SUCCESS(rc))
1101 {
1102 /*
1103 * Instanciate the factory and ask for a list of proxies.
1104 */
1105 PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor();
1106 if (pFactory)
1107 {
1108 char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl);
1109 if (papszProxies)
1110 {
1111 /*
1112 * Look for something we can use.
1113 */
1114 for (unsigned i = 0; papszProxies[i]; i++)
1115 {
1116 if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0)
1117 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1118 else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0
1119 || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0
1120 || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0
1121 || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */
1122 )
1123 rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]);
1124 else
1125 continue;
1126 if (rcRet != VINF_NOT_SUPPORTED)
1127 break;
1128 }
1129
1130 /* Free the result. */
1131 if (g_pfnLibProxyFactoryFreeProxies) /* libproxy >= 0.4.16. */
1132 g_pfnLibProxyFactoryFreeProxies(papszProxies);
1133 else
1134 {
1135 for (unsigned i = 0; papszProxies[i]; i++)
1136 free(papszProxies[i]);
1137 free(papszProxies);
1138 }
1139 papszProxies = NULL;
1140 }
1141 g_pfnLibProxyFactoryDtor(pFactory);
1142 }
1143 }
1144
1145 return rcRet;
1146}
1147
1148#endif /* IPRT_USE_LIBPROXY */
1149
1150#ifdef RT_OS_DARWIN
1151
1152/**
1153 * Get a boolean like integer value from a dictionary.
1154 *
1155 * @returns true / false.
1156 * @param hDict The dictionary.
1157 * @param pvKey The dictionary value key.
1158 */
1159static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault)
1160{
1161 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey);
1162 if (hNum)
1163 {
1164 int fEnabled;
1165 if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled))
1166 return fDefault;
1167 return fEnabled != 0;
1168 }
1169 return fDefault;
1170}
1171
1172
1173/**
1174 * Creates a CFURL object for an URL.
1175 *
1176 * @returns CFURL object reference.
1177 * @param pszUrl The URL.
1178 */
1179static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl)
1180{
1181 /* CFURLCreateStringByAddingPercentEscapes is deprecated, so try use CFURLCreateWithBytes
1182 as it doesn't validate as much as as CFUrlCreateWithString does. */
1183#if 0
1184 CFURLRef hUrl = NULL;
1185 CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8);
1186 if (hStrUrl)
1187 {
1188 CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl,
1189 NULL /*charactersToLeaveUnescaped*/,
1190 NULL /*legalURLCharactersToBeEscaped*/,
1191 kCFStringEncodingUTF8);
1192 if (hStrUrlEscaped)
1193 {
1194 hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/);
1195 Assert(hUrl);
1196 CFRelease(hStrUrlEscaped);
1197 }
1198 else
1199 AssertFailed();
1200 CFRelease(hStrUrl);
1201 }
1202 else
1203 AssertFailed();
1204#else
1205 CFURLRef hUrl = CFURLCreateWithBytes(kCFAllocatorDefault, (const uint8_t *)pszUrl, strlen(pszUrl),
1206 kCFStringEncodingUTF8, NULL /*baseURL*/);
1207 Assert(hUrl);
1208#endif
1209 return hUrl;
1210}
1211
1212
1213/**
1214 * For passing results from rtHttpDarwinPacCallback to
1215 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1216 */
1217typedef struct RTHTTPDARWINPACRESULT
1218{
1219 CFArrayRef hArrayProxies;
1220 CFErrorRef hError;
1221} RTHTTPDARWINPACRESULT;
1222typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT;
1223
1224/**
1225 * Stupid callback for getting the result from
1226 * CFNetworkExecuteProxyAutoConfigurationURL.
1227 *
1228 * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of
1229 * rtHttpDarwinExecuteProxyAutoConfigurationUrl.
1230 * @param hArrayProxies The result array.
1231 * @param hError Errors, if any.
1232 */
1233static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError)
1234{
1235 PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser;
1236
1237 Assert(pResult->hArrayProxies == NULL);
1238 if (hArrayProxies)
1239 pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies);
1240
1241 Assert(pResult->hError == NULL);
1242 if (hError)
1243 pResult->hError = (CFErrorRef)CFRetain(hError);
1244
1245 CFRunLoopStop(CFRunLoopGetCurrent());
1246}
1247
1248
1249/**
1250 * Executes a PAC script and returning the proxies it suggests.
1251 *
1252 * @returns Array of proxy configs (CFProxySupport.h style).
1253 * @param hUrlTarget The URL we're about to use.
1254 * @param hUrlScript The PAC script URL.
1255 */
1256static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(CFURLRef hUrlTarget, CFURLRef hUrlScript)
1257{
1258 char szTmp[256];
1259 if (LogIsFlowEnabled())
1260 {
1261 szTmp[0] = '\0';
1262 CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1263 LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp));
1264 }
1265
1266 /*
1267 * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to
1268 * download the script ourselves and mess around with too many CF APIs.
1269 */
1270 CFRunLoopRef hRunLoop = CFRunLoopGetCurrent();
1271 AssertReturn(hRunLoop, NULL);
1272
1273 RTHTTPDARWINPACRESULT Result = { NULL, NULL };
1274 CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL };
1275 CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget,
1276 rtHttpDarwinPacCallback, &Ctx);
1277 AssertReturn(hRunLoopSrc, NULL);
1278
1279 CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool");
1280 CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode);
1281 CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */
1282 CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode);
1283
1284 /** @todo convert errors, maybe even fail. */
1285
1286 /*
1287 * Autoconfig (or missing wpad server) typically results in:
1288 * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2).
1289 *
1290 * In the autoconfig case, it looks like we're getting two entries, first
1291 * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to
1292 * be very upset if this fails, just continue trying alternatives.
1293 */
1294 if (Result.hError)
1295 {
1296 if (LogIsEnabled())
1297 {
1298 szTmp[0] = '\0';
1299 CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8);
1300 Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp));
1301 }
1302 CFRelease(Result.hError);
1303 }
1304 return Result.hArrayProxies;
1305}
1306
1307
1308/**
1309 * Attempt to configure the proxy according to @a hDictProxy.
1310 *
1311 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1312 * the caller should try out alternative proxy configs and fallbacks.
1313 * @param pThis The HTTP client instance.
1314 * @param hDictProxy The proxy configuration (see CFProxySupport.h).
1315 * @param hUrlTarget The URL we're about to use.
1316 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1317 * javascript URL). This is set when we're processing
1318 * the output from a PAC script.
1319 */
1320static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType)
1321{
1322 CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey);
1323 AssertReturn(hStrProxyType, VINF_NOT_SUPPORTED);
1324
1325 /*
1326 * No proxy is fairly simple and common.
1327 */
1328 if (CFEqual(hStrProxyType, kCFProxyTypeNone))
1329 return rtHttpUpdateAutomaticProxyDisable(pThis);
1330
1331 /*
1332 * PAC URL means recursion, however we only do one level.
1333 */
1334 if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL))
1335 {
1336 AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED);
1337
1338 CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey);
1339 AssertReturn(hUrlScript, VINF_NOT_SUPPORTED);
1340
1341 int rcRet = VINF_NOT_SUPPORTED;
1342 CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1343 if (hArray)
1344 {
1345 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1346 CFRelease(hArray);
1347 }
1348 return rcRet;
1349 }
1350
1351 /*
1352 * Determine the proxy type (not entirely sure about type == proxy type and
1353 * not scheme/protocol)...
1354 */
1355 curl_proxytype enmProxyType = CURLPROXY_HTTP;
1356 uint32_t uDefaultProxyPort = 8080;
1357 if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP)
1358 || CFEqual(hStrProxyType, kCFProxyTypeHTTPS))
1359 { /* defaults */ }
1360 else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS))
1361 {
1362 /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or
1363 * SOCK5 on the other side... Selecting SOCKS5 for now. */
1364 enmProxyType = CURLPROXY_SOCKS5;
1365 uDefaultProxyPort = 1080;
1366 }
1367 /* Unknown proxy type. */
1368 else
1369 return VINF_NOT_SUPPORTED;
1370
1371 /*
1372 * Extract the proxy configuration.
1373 */
1374 /* The proxy host name. */
1375 char szHostname[_1K];
1376 CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey);
1377 AssertReturn(hStr, VINF_NOT_SUPPORTED);
1378 AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1379
1380 /* Get the port number (optional). */
1381 SInt32 iProxyPort;
1382 CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey);
1383 if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort))
1384 AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort);
1385 else
1386 iProxyPort = uDefaultProxyPort;
1387
1388 /* The proxy username. */
1389 char szUsername[256];
1390 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey);
1391 if (hStr)
1392 AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1393 else
1394 szUsername[0] = '\0';
1395
1396 /* The proxy password. */
1397 char szPassword[384];
1398 hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey);
1399 if (hStr)
1400 AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED);
1401 else
1402 szPassword[0] = '\0';
1403
1404 /*
1405 * Apply the proxy config.
1406 */
1407 return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort,
1408 szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL);
1409}
1410
1411
1412/**
1413 * Try do proxy config for our HTTP client instance given an array of proxies.
1414 *
1415 * This is used with the output from a CFProxySupport.h API.
1416 *
1417 * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and
1418 * we might want to try out fallbacks.
1419 * @param pThis The HTTP client instance.
1420 * @param hArrayProxies The proxies CFPRoxySupport have given us.
1421 * @param hUrlTarget The URL we're about to use.
1422 * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e.
1423 * javascript URL). This is set when we're processing
1424 * the output from a PAC script.
1425 */
1426static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType)
1427{
1428 int rcRet = VINF_NOT_SUPPORTED;
1429 CFIndex const cEntries = CFArrayGetCount(hArrayProxies);
1430 LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries));
1431 for (CFIndex i = 0; i < cEntries; i++)
1432 {
1433 CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i);
1434 AssertContinue(hDictProxy);
1435
1436 rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType);
1437 if (rcRet != VINF_NOT_SUPPORTED)
1438 break;
1439 }
1440 return rcRet;
1441}
1442
1443
1444/**
1445 * Inner worker for rtHttpWinConfigureProxyForUrl.
1446 *
1447 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1448 * @param pThis The HTTP client instance.
1449 * @param pszUrl The URL.
1450 */
1451static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies,
1452 const char *pszUrl, const char *pszHost)
1453{
1454 CFArrayRef hArray;
1455
1456 /*
1457 * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply
1458 * proxy exclusion rules (tested on 10.9). So, do that manually.
1459 */
1460 RTNETADDRU HostAddr;
1461 int fIsHostIpv4Address = -1;
1462 char szTmp[_4K];
1463
1464 /* If we've got a simple hostname, something containing no dots, we must check
1465 whether such simple hostnames are excluded from proxying by default or not. */
1466 if (strchr(pszHost, '.') == NULL)
1467 {
1468 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false))
1469 return rtHttpUpdateAutomaticProxyDisable(pThis);
1470 fIsHostIpv4Address = false;
1471 }
1472
1473 /* Consult the exclusion list. This is an array of strings.
1474 This is very similar to what we do on windows. */
1475 hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList);
1476 if (hArray)
1477 {
1478 CFIndex const cEntries = CFArrayGetCount(hArray);
1479 for (CFIndex i = 0; i < cEntries; i++)
1480 {
1481 CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i);
1482 AssertContinue(hStr);
1483 AssertContinue(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8));
1484 RTStrToLower(szTmp);
1485
1486 bool fRet;
1487 if ( strchr(szTmp, '*')
1488 || strchr(szTmp, '?'))
1489 fRet = RTStrSimplePatternMatch(szTmp, pszHost);
1490 else
1491 {
1492 if (fIsHostIpv4Address == -1)
1493 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1494 RTNETADDRIPV4 Network, Netmask;
1495 if ( fIsHostIpv4Address
1496 && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) )
1497 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1498 else
1499 fRet = strcmp(szTmp, pszHost) == 0;
1500 }
1501 if (fRet)
1502 return rtHttpUpdateAutomaticProxyDisable(pThis);
1503 }
1504 }
1505
1506#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */
1507 /*
1508 * Is proxy auto config (PAC) enabled? If so, we must consult it first.
1509 */
1510 if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false))
1511 {
1512 /* Convert the auto config url string to a CFURL object. */
1513 CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString);
1514 if (hStrAutoConfigUrl)
1515 {
1516 if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8))
1517 {
1518 CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp);
1519 if (hUrlScript)
1520 {
1521 int rcRet = VINF_NOT_SUPPORTED;
1522 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1523 if (hUrlTarget)
1524 {
1525 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1526 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1527 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL);
1528 if (hArray)
1529 CFRelease(hArray);
1530
1531 hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript);
1532 if (hArray)
1533 {
1534 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/);
1535 CFRelease(hArray);
1536 }
1537 }
1538 CFRelease(hUrlScript);
1539 if (rcRet != VINF_NOT_SUPPORTED)
1540 return rcRet;
1541 }
1542 }
1543 }
1544 }
1545
1546 /*
1547 * Try static proxy configs.
1548 */
1549 /** @todo later if needed. */
1550 return VERR_NOT_SUPPORTED;
1551
1552#else
1553 /*
1554 * Simple solution - "just" use CFNetworkCopyProxiesForURL.
1555 */
1556 CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl);
1557 AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR);
1558 int rcRet = VINF_NOT_SUPPORTED;
1559
1560 /* Work around for <rdar://problem/5530166>, whatever that is. Initializes
1561 some internal CFNetwork state, they say. See CFPRoxySupportTool example. */
1562 CFDictionaryRef hDictNull = (CFDictionaryRef)(42-42); /*workaround for -Wnonnull warning in Clang 11. */
1563 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictNull);
1564 if (hArray)
1565 CFRelease(hArray);
1566
1567 /* The actual run. */
1568 hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies);
1569 if (hArray)
1570 {
1571 rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/);
1572 CFRelease(hArray);
1573 }
1574 CFRelease(hUrlTarget);
1575
1576 return rcRet;
1577#endif
1578}
1579
1580/**
1581 * Reconfigures the cURL proxy settings for the given URL, OS X style.
1582 *
1583 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1584 * @param pThis The HTTP client instance.
1585 * @param pszUrl The URL.
1586 */
1587static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1588{
1589 /*
1590 * Parse the URL, if there isn't any host name (like for file:///xxx.txt)
1591 * we don't need to run thru proxy settings to know what to do.
1592 */
1593 RTURIPARSED Parsed;
1594 int rc = RTUriParse(pszUrl, &Parsed);
1595 AssertRCReturn(rc, false);
1596 if (Parsed.cchAuthorityHost == 0)
1597 return rtHttpUpdateAutomaticProxyDisable(pThis);
1598 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1599 AssertReturn(pszHost, VERR_NO_STR_MEMORY);
1600 RTStrToLower(pszHost);
1601
1602 /*
1603 * Get a copy of the proxy settings (10.6 API).
1604 */
1605 CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */
1606 if (hDictProxies)
1607 rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, pszHost);
1608 else
1609 rc = VINF_NOT_SUPPORTED;
1610 CFRelease(hDictProxies);
1611
1612 RTStrFree(pszHost);
1613 return rc;
1614}
1615
1616#endif /* RT_OS_DARWIN */
1617
1618#ifdef RT_OS_WINDOWS
1619
1620/**
1621 * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs}
1622 */
1623static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser)
1624{
1625 /*
1626 * winhttp.dll is not present on NT4 and probably was first introduced with XP.
1627 */
1628 RTLDRMOD hMod;
1629 int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod);
1630 if (RT_SUCCESS(rc))
1631 {
1632 rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen);
1633 if (RT_SUCCESS(rc))
1634 rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle);
1635 if (RT_SUCCESS(rc))
1636 rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl);
1637 if (RT_SUCCESS(rc))
1638 rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration);
1639 if (RT_SUCCESS(rc))
1640 rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser);
1641 RTLdrClose(hMod);
1642 AssertRC(rc);
1643 }
1644 else
1645 AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc));
1646
1647 NOREF(pvUser);
1648 return rc;
1649}
1650
1651
1652/**
1653 * Matches the URL against the given Windows by-pass list.
1654 *
1655 * @returns true if we should by-pass the proxy for this URL, false if not.
1656 * @param pszUrl The URL.
1657 * @param pwszBypass The Windows by-pass list.
1658 */
1659static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass)
1660{
1661 /*
1662 * Don't bother parsing the URL if we've actually got nothing to work with
1663 * in the by-pass list.
1664 */
1665 if (!pwszBypass)
1666 return false;
1667
1668 RTUTF16 wc;
1669 while ( (wc = *pwszBypass) != '\0'
1670 && ( RTUniCpIsSpace(wc)
1671 || wc == ';') )
1672 pwszBypass++;
1673 if (wc == '\0')
1674 return false;
1675
1676 /*
1677 * We now need to parse the URL and extract the host name.
1678 */
1679 RTURIPARSED Parsed;
1680 int rc = RTUriParse(pszUrl, &Parsed);
1681 AssertRCReturn(rc, false);
1682 char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed);
1683 if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */
1684 return false;
1685 RTStrToLower(pszHost);
1686
1687 bool fRet = false;
1688 char *pszBypassFree;
1689 rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree);
1690 if (RT_SUCCESS(rc))
1691 {
1692 /*
1693 * Walk the by-pass list.
1694 *
1695 * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx
1696 * a by-pass list is semicolon delimited list. The entries are either host
1697 * names or IP addresses, and may use wildcard ('*', '?', I guess). There
1698 * special "<local>" entry matches anything without a dot.
1699 */
1700 RTNETADDRU HostAddr = { 0, 0 };
1701 int fIsHostIpv4Address = -1;
1702 char *pszEntry = pszBypassFree;
1703 while (*pszEntry != '\0')
1704 {
1705 /*
1706 * Find end of entry.
1707 */
1708 char ch;
1709 size_t cchEntry = 1;
1710 while ( (ch = pszEntry[cchEntry]) != '\0'
1711 && ch != ';'
1712 && !RT_C_IS_SPACE(ch))
1713 cchEntry++;
1714
1715 char chSaved = pszEntry[cchEntry];
1716 pszEntry[cchEntry] = '\0';
1717 RTStrToLower(pszEntry);
1718
1719 if ( cchEntry == sizeof("<local>") - 1
1720 && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0)
1721 fRet = strchr(pszHost, '.') == NULL;
1722 else if ( memchr(pszEntry, '*', cchEntry) != NULL
1723 || memchr(pszEntry, '?', cchEntry) != NULL)
1724 fRet = RTStrSimplePatternMatch(pszEntry, pszHost);
1725 else
1726 {
1727 if (fIsHostIpv4Address == -1)
1728 fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4));
1729 RTNETADDRIPV4 Network, Netmask;
1730 if ( fIsHostIpv4Address
1731 && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) )
1732 fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u;
1733 else
1734 fRet = strcmp(pszEntry, pszHost) == 0;
1735 }
1736
1737 pszEntry[cchEntry] = chSaved;
1738 if (fRet)
1739 break;
1740
1741 /*
1742 * Next entry.
1743 */
1744 pszEntry += cchEntry;
1745 while ( (ch = *pszEntry) != '\0'
1746 && ( ch == ';'
1747 || RT_C_IS_SPACE(ch)) )
1748 pszEntry++;
1749 }
1750
1751 RTStrFree(pszBypassFree);
1752 }
1753
1754 RTStrFree(pszHost);
1755 return false;
1756}
1757
1758
1759/**
1760 * Searches a Windows proxy server list for the best fitting proxy to use, then
1761 * reconfigures the HTTP client instance to use it.
1762 *
1763 * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback.
1764 * @param pThis The HTTP client instance.
1765 * @param pszUrl The URL needing proxying.
1766 * @param pwszProxies The list of proxy servers to choose from.
1767 */
1768static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies)
1769{
1770 /*
1771 * Fend off empty strings (very unlikely, but just in case).
1772 */
1773 if (!pwszProxies)
1774 return VINF_NOT_SUPPORTED;
1775
1776 RTUTF16 wc;
1777 while ( (wc = *pwszProxies) != '\0'
1778 && ( RTUniCpIsSpace(wc)
1779 || wc == ';') )
1780 pwszProxies++;
1781 if (wc == '\0')
1782 return VINF_NOT_SUPPORTED;
1783
1784 /*
1785 * We now need to parse the URL and extract the scheme.
1786 */
1787 RTURIPARSED Parsed;
1788 int rc = RTUriParse(pszUrl, &Parsed);
1789 AssertRCReturn(rc, false);
1790 char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed);
1791 AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY);
1792 size_t const cchUrlScheme = strlen(pszUrlScheme);
1793
1794 int rcRet = VINF_NOT_SUPPORTED;
1795 char *pszProxiesFree;
1796 rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree);
1797 if (RT_SUCCESS(rc))
1798 {
1799 /*
1800 * Walk the server list.
1801 *
1802 * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx
1803 * this is also a semicolon delimited list. The entries are on the form:
1804 * [<scheme>=][<scheme>"://"]<server>[":"<port>]
1805 */
1806 bool fBestEntryHasSameScheme = false;
1807 const char *pszBestEntry = NULL;
1808 char *pszEntry = pszProxiesFree;
1809 while (*pszEntry != '\0')
1810 {
1811 /*
1812 * Find end of entry. We include spaces here in addition to ';'.
1813 */
1814 char ch;
1815 size_t cchEntry = 1;
1816 while ( (ch = pszEntry[cchEntry]) != '\0'
1817 && ch != ';'
1818 && !RT_C_IS_SPACE(ch))
1819 cchEntry++;
1820
1821 char const chSaved = pszEntry[cchEntry];
1822 pszEntry[cchEntry] = '\0';
1823
1824 /* Parse the entry. */
1825 const char *pszEndOfScheme = strstr(pszEntry, "://");
1826 const char *pszEqual = (const char *)memchr(pszEntry, '=',
1827 pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry);
1828 if (pszEqual)
1829 {
1830 if ( (uintptr_t)(pszEqual - pszEntry) == cchUrlScheme
1831 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0)
1832 {
1833 pszBestEntry = pszEqual + 1;
1834 break;
1835 }
1836 }
1837 else
1838 {
1839 bool fSchemeMatch = pszEndOfScheme
1840 && (uintptr_t)(pszEndOfScheme - pszEntry) == cchUrlScheme
1841 && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0;
1842 if ( !pszBestEntry
1843 || ( !fBestEntryHasSameScheme
1844 && fSchemeMatch) )
1845 {
1846 pszBestEntry = pszEntry;
1847 fBestEntryHasSameScheme = fSchemeMatch;
1848 }
1849 }
1850
1851 /*
1852 * Next entry.
1853 */
1854 if (!chSaved)
1855 break;
1856 pszEntry += cchEntry + 1;
1857 while ( (ch = *pszEntry) != '\0'
1858 && ( ch == ';'
1859 || RT_C_IS_SPACE(ch)) )
1860 pszEntry++;
1861 }
1862
1863 /*
1864 * If we found something, try use it.
1865 */
1866 if (pszBestEntry)
1867 rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry);
1868
1869 RTStrFree(pszProxiesFree);
1870 }
1871
1872 RTStrFree(pszUrlScheme);
1873 return rc;
1874}
1875
1876
1877/**
1878 * Reconfigures the cURL proxy settings for the given URL, Windows style.
1879 *
1880 * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback.
1881 * @param pThis The HTTP client instance.
1882 * @param pszUrl The URL.
1883 */
1884static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
1885{
1886 int rcRet = VINF_NOT_SUPPORTED;
1887
1888 int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL);
1889 if (RT_SUCCESS(rc))
1890 {
1891 /*
1892 * Try get some proxy info for the URL. We first try getting the IE
1893 * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser
1894 * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags.
1895 */
1896 WINHTTP_PROXY_INFO ProxyInfo;
1897 WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions;
1898 RT_ZERO(AutoProxyOptions);
1899 RT_ZERO(ProxyInfo);
1900
1901 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig;
1902 if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig))
1903 {
1904 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1905 AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl;
1906 if (IeProxyConfig.fAutoDetect)
1907 {
1908 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS;
1909 AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
1910 }
1911 else if (AutoProxyOptions.lpszAutoConfigUrl)
1912 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1913 else if (ProxyInfo.lpszProxy)
1914 ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
1915 ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy;
1916 ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass;
1917 }
1918 else
1919 {
1920 AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError()));
1921 if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo))
1922 {
1923 AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError()));
1924 RT_ZERO(ProxyInfo);
1925 }
1926 }
1927
1928 /*
1929 * Should we try WinHttGetProxyForUrl?
1930 */
1931 if (AutoProxyOptions.dwFlags != 0)
1932 {
1933 HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY,
1934 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ );
1935 if (hSession != NULL)
1936 {
1937 PRTUTF16 pwszUrl;
1938 rc = RTStrToUtf16(pszUrl, &pwszUrl);
1939 if (RT_SUCCESS(rc))
1940 {
1941 /*
1942 * Try autodetect first, then fall back on the config URL if there is one.
1943 *
1944 * Also, we first try without auto authentication, then with. This will according
1945 * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with
1946 * caching the result when it's processed out-of-process (seems default here on W10).
1947 */
1948 WINHTTP_PROXY_INFO TmpProxyInfo;
1949 BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1950 if ( !fRc
1951 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1952 {
1953 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1954 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1955 }
1956
1957 if ( !fRc
1958 && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL
1959 && AutoProxyOptions.lpszAutoConfigUrl)
1960 {
1961 AutoProxyOptions.fAutoLogonIfChallenged = FALSE;
1962 AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL;
1963 AutoProxyOptions.dwAutoDetectFlags = 0;
1964 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1965 if ( !fRc
1966 && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE)
1967 {
1968 AutoProxyOptions.fAutoLogonIfChallenged = TRUE;
1969 fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo);
1970 }
1971 }
1972
1973 if (fRc)
1974 {
1975 if (ProxyInfo.lpszProxy)
1976 GlobalFree(ProxyInfo.lpszProxy);
1977 if (ProxyInfo.lpszProxyBypass)
1978 GlobalFree(ProxyInfo.lpszProxyBypass);
1979 ProxyInfo = TmpProxyInfo;
1980 }
1981 /*
1982 * If the autodetection failed, assume no proxy.
1983 */
1984 else
1985 {
1986 DWORD dwErr = GetLastError();
1987 if ( dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED
1988 || dwErr == ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT
1989 || ( dwErr == ERROR_WINHTTP_UNRECOGNIZED_SCHEME
1990 && ( RTStrNICmp(pszUrl, RT_STR_TUPLE("https://")) == 0
1991 || RTStrNICmp(pszUrl, RT_STR_TUPLE("http://")) == 0) ) )
1992 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
1993 else
1994 AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl(%s) -> %u; lpszAutoConfigUrl=%sx\n",
1995 pszUrl, dwErr, AutoProxyOptions.lpszAutoConfigUrl));
1996 }
1997 RTUtf16Free(pwszUrl);
1998 }
1999 else
2000 {
2001 AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc));
2002 rcRet = rc;
2003 }
2004 g_pfnWinHttpCloseHandle(hSession);
2005 }
2006 else
2007 AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError()));
2008 }
2009
2010 /*
2011 * Try use the proxy info we've found.
2012 */
2013 switch (ProxyInfo.dwAccessType)
2014 {
2015 case WINHTTP_ACCESS_TYPE_NO_PROXY:
2016 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
2017 break;
2018
2019 case WINHTTP_ACCESS_TYPE_NAMED_PROXY:
2020 if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass))
2021 rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy);
2022 else
2023 rcRet = rtHttpUpdateAutomaticProxyDisable(pThis);
2024 break;
2025
2026 case 0:
2027 break;
2028
2029 default:
2030 AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType));
2031 }
2032
2033 /*
2034 * Cleanup.
2035 */
2036 if (ProxyInfo.lpszProxy)
2037 GlobalFree(ProxyInfo.lpszProxy);
2038 if (ProxyInfo.lpszProxyBypass)
2039 GlobalFree(ProxyInfo.lpszProxyBypass);
2040 if (AutoProxyOptions.lpszAutoConfigUrl)
2041 GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl);
2042 }
2043
2044 return rcRet;
2045}
2046
2047#endif /* RT_OS_WINDOWS */
2048
2049
2050static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl)
2051{
2052 if (pThis->fUseSystemProxySettings)
2053 {
2054#ifdef IPRT_USE_LIBPROXY
2055 int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl);
2056 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2057 return rc;
2058 Assert(rc == VINF_NOT_SUPPORTED);
2059#endif
2060#ifdef RT_OS_DARWIN
2061 int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl);
2062 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2063 return rc;
2064 Assert(rc == VINF_NOT_SUPPORTED);
2065#endif
2066#ifdef RT_OS_WINDOWS
2067 int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl);
2068 if (rc == VINF_SUCCESS || RT_FAILURE(rc))
2069 return rc;
2070 Assert(rc == VINF_NOT_SUPPORTED);
2071#endif
2072/** @todo system specific class here, fall back on env vars if necessary. */
2073 return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl);
2074 }
2075
2076 return VINF_SUCCESS;
2077}
2078
2079
2080RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort,
2081 const char *pcszProxyUser, const char *pcszProxyPwd)
2082{
2083 PRTHTTPINTERNAL pThis = hHttp;
2084 RTHTTP_VALID_RETURN(pThis);
2085 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
2086
2087 /*
2088 * Update the settings.
2089 *
2090 * Currently, we don't make alot of effort parsing or checking the input, we
2091 * leave that to cURL. (A bit afraid of breaking user settings.)
2092 */
2093 pThis->fUseSystemProxySettings = false;
2094 if (!pcszProxy)
2095 return rtHttpUpdateAutomaticProxyDisable(pThis);
2096
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 case 501:
2841 /* Requested function not supported */
2842 if (!puHttpStatus)
2843 rc = VERR_HTTP_NOT_SUPPORTED;
2844 break;
2845 }
2846
2847 if (pThis->pszRedirLocation)
2848 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2849 else
2850 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2851 }
2852 else
2853 {
2854 switch (rcCurl)
2855 {
2856 case CURLE_URL_MALFORMAT:
2857 case CURLE_COULDNT_RESOLVE_HOST:
2858 rc = VERR_HTTP_HOST_NOT_FOUND;
2859 break;
2860 case CURLE_COULDNT_CONNECT:
2861 rc = VERR_HTTP_COULDNT_CONNECT;
2862 break;
2863 case CURLE_SSL_CONNECT_ERROR:
2864 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2865 break;
2866 case CURLE_SSL_CACERT:
2867 /* The peer certificate cannot be authenticated with the CA certificates
2868 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2869 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2870 break;
2871 case CURLE_SSL_CACERT_BADFILE:
2872 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2873 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2874 break;
2875 case CURLE_ABORTED_BY_CALLBACK:
2876 /* forcefully aborted */
2877 rc = VERR_HTTP_ABORTED;
2878 break;
2879 case CURLE_COULDNT_RESOLVE_PROXY:
2880 rc = VERR_HTTP_PROXY_NOT_FOUND;
2881 break;
2882 case CURLE_WRITE_ERROR:
2883 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2884 break;
2885 //case CURLE_READ_ERROR
2886
2887 default:
2888 break;
2889 }
2890 Log(("%s: %Rrc: %u = %s%s%.*s\n",
2891 __FUNCTION__,
2892 rc, rcCurl, curl_easy_strerror((CURLcode)rcCurl),
2893 pThis->szErrorBuffer[0] != '\0' ? ": " : "",
2894 (int) sizeof(pThis->szErrorBuffer),
2895 pThis->szErrorBuffer[0] != '\0' ? pThis->szErrorBuffer : ""));
2896 }
2897
2898 return rc;
2899}
2900
2901
2902/**
2903 * cURL callback for reporting progress, we use it for checking for abort.
2904 */
2905static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded) RT_NOTHROW_DEF
2906{
2907 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2908 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2909 RT_NOREF_PV(rdTotalUpload);
2910 RT_NOREF_PV(rdUploaded);
2911
2912 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2913
2914 if (pThis->pfnDownloadProgress)
2915 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2916
2917 return pThis->fAbort ? 1 : 0;
2918}
2919
2920
2921/**
2922 * Whether we're likely to need SSL to handle the give URL.
2923 *
2924 * @returns true if we need, false if we probably don't.
2925 * @param pszUrl The URL.
2926 */
2927static bool rtHttpNeedSsl(const char *pszUrl)
2928{
2929 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2930}
2931
2932
2933/**
2934 * Applies recoded settings to the cURL instance before doing work.
2935 *
2936 * @returns IPRT status code.
2937 * @param pThis The HTTP/HTTPS client instance.
2938 * @param pszUrl The URL.
2939 */
2940static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2941{
2942 /*
2943 * The URL.
2944 */
2945 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2946 if (CURL_FAILURE(rcCurl))
2947 return VERR_INVALID_PARAMETER;
2948
2949 /*
2950 * Proxy config.
2951 */
2952 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2953 if (RT_FAILURE(rc))
2954 return rc;
2955
2956 /*
2957 * Setup SSL. Can be a bit of work.
2958 */
2959 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2960 if (CURL_FAILURE(rcCurl))
2961 return VERR_INVALID_PARAMETER;
2962
2963 const char *pszCaFile = pThis->pszCaFile;
2964 if ( !pszCaFile
2965 && rtHttpNeedSsl(pszUrl))
2966 {
2967 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2968 if (RT_SUCCESS(rc))
2969 pszCaFile = pThis->pszCaFile;
2970 else
2971 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2972 }
2973 if (pszCaFile)
2974 {
2975 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2976 if (CURL_FAILURE(rcCurl))
2977 return VERR_HTTP_CURL_ERROR;
2978 }
2979
2980 /*
2981 * Progress/abort.
2982 */
2983 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2984 if (CURL_FAILURE(rcCurl))
2985 return VERR_HTTP_CURL_ERROR;
2986 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2987 if (CURL_FAILURE(rcCurl))
2988 return VERR_HTTP_CURL_ERROR;
2989 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2990 if (CURL_FAILURE(rcCurl))
2991 return VERR_HTTP_CURL_ERROR;
2992
2993 /*
2994 * Set default user agent string if necessary. Some websites take offence
2995 * if we don't set it.
2996 */
2997 if ( !pThis->fHaveSetUserAgent
2998 && !pThis->fHaveUserAgentHeader)
2999 {
3000 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
3001 if (CURL_FAILURE(rcCurl))
3002 return VERR_HTTP_CURL_ERROR;
3003 pThis->fHaveSetUserAgent = true;
3004 }
3005
3006 /*
3007 * Use GET by default.
3008 */
3009 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
3010 if (CURL_FAILURE(rcCurl))
3011 return VERR_HTTP_CURL_ERROR;
3012 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
3013 if (CURL_FAILURE(rcCurl))
3014 return VERR_HTTP_CURL_ERROR;
3015
3016 return VINF_SUCCESS;
3017}
3018
3019
3020/**
3021 * Resets state.
3022 *
3023 * @param pThis HTTP client instance.
3024 */
3025static void rtHttpResetState(PRTHTTPINTERNAL pThis)
3026{
3027 pThis->fAbort = false;
3028 pThis->rcOutput = VINF_SUCCESS;
3029 pThis->uDownloadHttpStatus = UINT32_MAX;
3030 pThis->cbDownloadContent = UINT64_MAX;
3031 pThis->offDownloadContent = 0;
3032 pThis->offUploadContent = 0;
3033 pThis->rcOutput = VINF_SUCCESS;
3034 pThis->cbDownloadHint = 0;
3035 Assert(pThis->BodyOutput.pHttp == pThis);
3036 Assert(pThis->HeadersOutput.pHttp == pThis);
3037}
3038
3039
3040/**
3041 * Tries to determin uDownloadHttpStatus and cbDownloadContent.
3042 *
3043 * @param pThis HTTP client instance.
3044 */
3045static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis)
3046{
3047 long lHttpStatus = 0;
3048 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus);
3049 pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus;
3050
3051#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
3052 curl_off_t cbContent = -1;
3053 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent);
3054 if (cbContent >= 0)
3055 pThis->cbDownloadContent = (uint64_t)cbContent;
3056#else
3057 double rdContent = -1.0;
3058 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent);
3059 if (rdContent >= 0.0)
3060 pThis->cbDownloadContent = (uint64_t)rdContent;
3061#endif
3062}
3063
3064
3065/**
3066 * Worker for rtHttpWriteHeaderData and rtHttpWriteBodyData.
3067 */
3068static size_t rtHttpWriteDataToMemOutput(PRTHTTPINTERNAL pThis, RTHTTPOUTPUTDATA *pOutput, char const *pchBuf, size_t cbToAppend)
3069{
3070 /*
3071 * Do max size and overflow checks.
3072 */
3073 size_t const cbCurSize = pOutput->uData.Mem.cb;
3074 size_t const cbNewSize = cbCurSize + cbToAppend;
3075 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3076 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
3077 {
3078 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
3079 {
3080 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend);
3081 pOutput->uData.Mem.cb = cbNewSize;
3082 pOutput->uData.Mem.pb[cbNewSize] = '\0';
3083 return cbToAppend;
3084 }
3085
3086 /*
3087 * We need to reallocate the output buffer.
3088 */
3089 /** @todo this could do with a better strategy wrt growth. */
3090 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
3091 if ( cbAlloc <= pThis->cbDownloadHint
3092 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3093 && pOutput == &pThis->BodyOutput)
3094 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
3095
3096 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
3097 if (pbNew)
3098 {
3099 memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend);
3100 pbNew[cbNewSize] = '\0';
3101
3102 pOutput->uData.Mem.cbAllocated = cbAlloc;
3103 pOutput->uData.Mem.pb = pbNew;
3104 pOutput->uData.Mem.cb = cbNewSize;
3105 return cbToAppend;
3106 }
3107
3108 pThis->rcOutput = VERR_NO_MEMORY;
3109 }
3110 else
3111 pThis->rcOutput = VERR_TOO_MUCH_DATA;
3112
3113 /*
3114 * Failure - abort.
3115 */
3116 RTMemFree(pOutput->uData.Mem.pb);
3117 pOutput->uData.Mem.pb = NULL;
3118 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
3119 pThis->fAbort = true;
3120 return 0;
3121}
3122
3123
3124/**
3125 * cURL callback for writing body data.
3126 */
3127static size_t rtHttpWriteBodyData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3128{
3129 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3130 size_t const cbToAppend = cbUnit * cUnits;
3131
3132 /*
3133 * Check if this belongs to the body download callback.
3134 */
3135 if (pThis->pfnDownloadCallback)
3136 {
3137 if (pThis->offDownloadContent == 0)
3138 rtHttpGetDownloadStatusAndLength(pThis);
3139
3140 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3141 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3142 {
3143 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3144 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3145 if (RT_SUCCESS(rc))
3146 {
3147 pThis->offDownloadContent += cbToAppend;
3148 return cbToAppend;
3149 }
3150 if (RT_SUCCESS(pThis->rcOutput))
3151 pThis->rcOutput = rc;
3152 pThis->fAbort = true;
3153 return 0;
3154 }
3155 }
3156
3157 /*
3158 * Otherwise, copy to memory output buffer.
3159 */
3160 return rtHttpWriteDataToMemOutput(pThis, &pThis->BodyOutput, pchBuf, cbToAppend);
3161}
3162
3163
3164/**
3165 * cURL callback for writing header data.
3166 */
3167static size_t rtHttpWriteHeaderData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3168{
3169 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3170 size_t const cbToAppend = cbUnit * cUnits;
3171
3172 /*
3173 * Work the header callback, if one.
3174 * ASSUMES cURL is giving us one header at a time.
3175 */
3176 if (pThis->pfnHeaderCallback)
3177 {
3178 /*
3179 * Find the end of the field name first.
3180 */
3181 uint32_t uMatchWord;
3182 size_t cchField;
3183 const char *pchField = pchBuf;
3184 size_t cchValue;
3185 const char *pchValue = (const char *)memchr(pchBuf, ':', cbToAppend);
3186 if (pchValue)
3187 {
3188 cchField = pchValue - pchField;
3189 if (RT_LIKELY(cchField >= 3))
3190 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, RT_C_TO_LOWER(pchBuf[0]),
3191 RT_C_TO_LOWER(pchBuf[1]), RT_C_TO_LOWER(pchBuf[2]));
3192 else
3193 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField,
3194 cchField >= 1 ? RT_C_TO_LOWER(pchBuf[0]) : 0,
3195 cchField >= 2 ? RT_C_TO_LOWER(pchBuf[1]) : 0,
3196 0);
3197 pchValue++;
3198 cchValue = cbToAppend - cchField - 1;
3199 }
3200 /* Since cURL gives us the "HTTP/{version} {code} {status}" line too,
3201 we slap a fictitious field name ':http-status-line' in front of it. */
3202 else if (cbToAppend > 5 && pchBuf[0] == 'H' && pchBuf[1] == 'T' && pchBuf[2] == 'T' && pchBuf[3] == 'P' && pchBuf[4] == '/')
3203 {
3204 pchField = ":http-status-line";
3205 cchField = 17;
3206 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(17, ':', 'h', 't');
3207 pchValue = pchBuf;
3208 cchValue = cbToAppend;
3209 }
3210 /* cURL also gives us the empty line before the body, so we slap another
3211 fictitious field name ':end-of-headers' in front of it as well. */
3212 else if (cbToAppend == 2 && pchBuf[0] == '\r' && pchBuf[1] == '\n')
3213 {
3214 pchField = ":end-of-headers";
3215 cchField = 15;
3216 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(15, ':', 'e', 'n');
3217 pchValue = pchBuf;
3218 cchValue = cbToAppend;
3219 }
3220 else
3221 AssertMsgFailedReturn(("pchBuf=%.*s\n", cbToAppend, pchBuf), cbToAppend);
3222
3223 /*
3224 * Determin the field value, stripping one leading blank and all
3225 * trailing spaces.
3226 */
3227 if (cchValue > 0 && RT_C_IS_BLANK(*pchValue))
3228 pchValue++, cchValue--;
3229 while (cchValue > 0 && RT_C_IS_SPACE(pchValue[cchValue - 1]))
3230 cchValue--;
3231
3232 /*
3233 * Pass it to the callback.
3234 */
3235 Log6(("rtHttpWriteHeaderData: %.*s: %.*s\n", cchField, pchBuf, cchValue, pchValue));
3236 int rc = pThis->pfnHeaderCallback(pThis, uMatchWord, pchBuf, cchField,
3237 pchValue, cchValue, pThis->pvHeaderCallbackUser);
3238 if (RT_SUCCESS(rc))
3239 return cbToAppend;
3240
3241 /* Abort on error. */
3242 if (RT_SUCCESS(pThis->rcOutput))
3243 pThis->rcOutput = rc;
3244 pThis->fAbort = true;
3245 return 0;
3246 }
3247
3248 return rtHttpWriteDataToMemOutput(pThis, &pThis->HeadersOutput, pchBuf, cbToAppend);
3249}
3250
3251
3252/**
3253 * cURL callback for working the upload callback.
3254 */
3255static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3256{
3257 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3258 size_t const cbBuf = cbUnit * cUnits;
3259
3260 /* Get download info the first time we're called. */
3261 if (pThis->offDownloadContent == 0)
3262 rtHttpGetDownloadStatusAndLength(pThis);
3263
3264 /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */
3265 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3266 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3267 {
3268 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3269 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3270 if (RT_SUCCESS(rc))
3271 { /* likely */ }
3272 else
3273 {
3274 if (RT_SUCCESS(pThis->rcOutput))
3275 pThis->rcOutput = rc;
3276 pThis->fAbort = true;
3277 return 0;
3278 }
3279 }
3280 pThis->offDownloadContent += cbBuf;
3281 return cbBuf;
3282}
3283
3284
3285/**
3286 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
3287 */
3288static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3289{
3290 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3291 size_t const cbReq = cbUnit * cUnits;
3292 size_t const offMem = pThis->ReadData.Mem.offMem;
3293 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
3294 if (cbToCopy > cbReq)
3295 cbToCopy = cbReq;
3296 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
3297 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
3298 return cbToCopy;
3299}
3300
3301
3302/**
3303 * Callback feeding cURL data via the user upload callback.
3304 */
3305static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3306{
3307 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3308 size_t const cbReq = cbUnit * cUnits;
3309
3310 size_t cbActual = 0;
3311 int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser);
3312 if (RT_SUCCESS(rc))
3313 {
3314 pThis->offUploadContent += cbActual;
3315 return cbActual;
3316 }
3317
3318 if (RT_SUCCESS(pThis->rcOutput))
3319 pThis->rcOutput = rc;
3320 pThis->fAbort = true;
3321 return CURL_READFUNC_ABORT;
3322}
3323
3324
3325/**
3326 * Helper for installing a (body) write callback function.
3327 *
3328 * @returns cURL status code.
3329 * @param pThis The HTTP client instance.
3330 * @param pfnWrite The callback.
3331 * @param pvUser The callback user argument.
3332 */
3333static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3334{
3335 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
3336 if (CURL_SUCCESS(rcCurl))
3337 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
3338 return rcCurl;
3339}
3340
3341
3342/**
3343 * Helper for installing a header write callback function.
3344 *
3345 * @returns cURL status code.
3346 * @param pThis The HTTP client instance.
3347 * @param pfnWrite The callback.
3348 * @param pvUser The callback user argument.
3349 */
3350static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3351{
3352 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3353 if (CURL_SUCCESS(rcCurl))
3354 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3355 return rcCurl;
3356}
3357
3358
3359/**
3360 * Helper for installing a (body) read callback function.
3361 *
3362 * @returns cURL status code.
3363 * @param pThis The HTTP client instance.
3364 * @param pfnRead The callback.
3365 * @param pvUser The callback user argument.
3366 */
3367static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3368{
3369 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3370 if (CURL_SUCCESS(rcCurl))
3371 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3372 return rcCurl;
3373}
3374
3375
3376/**
3377 * Internal worker that performs a HTTP GET.
3378 *
3379 * @returns IPRT status code.
3380 * @param hHttp The HTTP/HTTPS client instance.
3381 * @param pszUrl The URL.
3382 * @param fNoBody Set to suppress the body.
3383 * @param ppvResponse Where to return the pointer to the allocated
3384 * response data (RTMemFree). There will always be
3385 * an zero terminator char after the response, that
3386 * is not part of the size returned via @a pcb.
3387 * @param pcb The size of the response data.
3388 *
3389 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
3390 * threads, because that will probably blow up!
3391 */
3392static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
3393{
3394 PRTHTTPINTERNAL pThis = hHttp;
3395 RTHTTP_VALID_RETURN(pThis);
3396
3397 /*
3398 * Reset the return values in case of more "GUI programming" on the client
3399 * side (i.e. a programming style not bothering checking return codes).
3400 */
3401 *ppvResponse = NULL;
3402 *pcb = 0;
3403
3404 /*
3405 * Set the busy flag (paranoia).
3406 */
3407 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3408 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3409
3410 /*
3411 * Reset the state and apply settings.
3412 */
3413 rtHttpResetState(pThis);
3414 int rc = rtHttpApplySettings(hHttp, pszUrl);
3415 if (RT_SUCCESS(rc))
3416 {
3417 RT_ZERO(pThis->BodyOutput.uData.Mem);
3418 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteBodyData, pThis);
3419 if (fNoBody)
3420 {
3421 if (CURL_SUCCESS(rcCurl))
3422 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3423 if (CURL_SUCCESS(rcCurl))
3424 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
3425 }
3426 if (CURL_SUCCESS(rcCurl))
3427 {
3428 /*
3429 * Perform the HTTP operation.
3430 */
3431 rcCurl = curl_easy_perform(pThis->pCurl);
3432 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3433 if (RT_SUCCESS(rc))
3434 rc = pThis->rcOutput;
3435 if (RT_SUCCESS(rc))
3436 {
3437 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
3438 *pcb = pThis->BodyOutput.uData.Mem.cb;
3439 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
3440 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3441 }
3442 else if (pThis->BodyOutput.uData.Mem.pb)
3443 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3444 RT_ZERO(pThis->BodyOutput.uData.Mem);
3445 }
3446 else
3447 rc = VERR_HTTP_CURL_ERROR;
3448 }
3449
3450 ASMAtomicWriteBool(&pThis->fBusy, false);
3451 return rc;
3452}
3453
3454
3455RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3456{
3457 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3458 uint8_t *pv;
3459 size_t cb;
3460 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
3461 if (RT_SUCCESS(rc))
3462 {
3463 if (pv) /* paranoia */
3464 *ppszNotUtf8 = (char *)pv;
3465 else
3466 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3467 }
3468 else
3469 *ppszNotUtf8 = NULL;
3470 return rc;
3471}
3472
3473
3474RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3475{
3476 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3477 uint8_t *pv;
3478 size_t cb;
3479 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
3480 if (RT_SUCCESS(rc))
3481 {
3482 if (pv) /* paranoia */
3483 *ppszNotUtf8 = (char *)pv;
3484 else
3485 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3486 }
3487 else
3488 *ppszNotUtf8 = NULL;
3489 return rc;
3490
3491}
3492
3493
3494RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
3495{
3496 RTMemFree(pszNotUtf8);
3497}
3498
3499
3500RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3501{
3502 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3503 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3504}
3505
3506
3507RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3508{
3509 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3510 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3511}
3512
3513
3514RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
3515{
3516 RTMemFree(pvResponse);
3517}
3518
3519
3520/**
3521 * cURL callback for writing data to a file.
3522 */
3523static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3524{
3525 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
3526 PRTHTTPINTERNAL pThis = pOutput->pHttp;
3527
3528 size_t cbWritten = 0;
3529 int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten);
3530 if (RT_SUCCESS(rc))
3531 return cbWritten;
3532
3533 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
3534 pThis->rcOutput = rc;
3535 return 0;
3536}
3537
3538
3539RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
3540{
3541 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
3542 PRTHTTPINTERNAL pThis = hHttp;
3543 RTHTTP_VALID_RETURN(pThis);
3544
3545 /*
3546 * Set the busy flag (paranoia).
3547 */
3548 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3549 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3550
3551 /*
3552 * Reset the state and apply settings.
3553 */
3554 rtHttpResetState(pThis);
3555 int rc = rtHttpApplySettings(hHttp, pszUrl);
3556 if (RT_SUCCESS(rc))
3557 {
3558 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3559 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
3560 if (CURL_SUCCESS(rcCurl))
3561 {
3562 /*
3563 * Open the output file.
3564 */
3565 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
3566 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
3567 if (RT_SUCCESS(rc))
3568 {
3569 /*
3570 * Perform the HTTP operation.
3571 */
3572 rcCurl = curl_easy_perform(pThis->pCurl);
3573 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3574 if (RT_SUCCESS(rc))
3575 rc = pThis->rcOutput;
3576
3577 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
3578 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
3579 rc = rc2;
3580 }
3581 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3582 }
3583 else
3584 rc = VERR_HTTP_CURL_ERROR;
3585 }
3586
3587 ASMAtomicWriteBool(&pThis->fBusy, false);
3588 return rc;
3589}
3590
3591
3592RTR3DECL(int) RTHttpQueryProxyInfoForUrl(RTHTTP hHttp, const char *pszUrl, PRTHTTPPROXYINFO pProxy)
3593{
3594 /*
3595 * Validate input and clear output.
3596 */
3597 Log(("RTHttpQueryProxyInfoForUrl: hHttp=%p pszUrl=%s pProxy=%s\n", hHttp, pszUrl, pProxy));
3598 RT_ZERO(*pProxy);
3599 pProxy->uProxyPort = UINT32_MAX;
3600
3601 PRTHTTPINTERNAL pThis = hHttp;
3602 RTHTTP_VALID_RETURN(pThis);
3603
3604 /*
3605 * Set up the proxy for the URL.
3606 */
3607 rtHttpResetState(pThis);
3608 /** @todo this does a bit too much (we don't need to set up SSL for instance). */
3609 int rc = rtHttpApplySettings(pThis, pszUrl);
3610 if (RT_SUCCESS(rc))
3611 {
3612 /*
3613 * Copy out the result.
3614 */
3615 if (pThis->fNoProxy)
3616 pProxy->enmProxyType = RTHTTPPROXYTYPE_NOPROXY;
3617 else
3618 {
3619 switch (pThis->enmProxyType)
3620 {
3621 case CURLPROXY_HTTP:
3622#ifdef CURL_AT_LEAST_VERSION
3623# if CURL_AT_LEAST_VERSION(7,19,4)
3624 case CURLPROXY_HTTP_1_0:
3625# endif
3626#endif
3627 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTP;
3628 break;
3629#ifdef CURL_AT_LEAST_VERSION
3630# if CURL_AT_LEAST_VERSION(7,52,0)
3631 case CURLPROXY_HTTPS:
3632 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTPS;
3633 break;
3634# endif
3635#endif
3636 case CURLPROXY_SOCKS4:
3637 case CURLPROXY_SOCKS4A:
3638 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS4;
3639 break;
3640 case CURLPROXY_SOCKS5:
3641 case CURLPROXY_SOCKS5_HOSTNAME:
3642 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS5;
3643 break;
3644 default:
3645 AssertFailed();
3646 pProxy->enmProxyType = RTHTTPPROXYTYPE_UNKNOWN;
3647 break;
3648 }
3649 pProxy->uProxyPort = pThis->uProxyPort;
3650 if (pThis->pszProxyHost != NULL)
3651 {
3652 rc = RTStrDupEx(&pProxy->pszProxyHost, pThis->pszProxyHost);
3653 if (pThis->pszProxyUsername && RT_SUCCESS(rc))
3654 rc = RTStrDupEx(&pProxy->pszProxyUsername, pThis->pszProxyUsername);
3655 if (pThis->pszProxyPassword && RT_SUCCESS(rc))
3656 rc = RTStrDupEx(&pProxy->pszProxyPassword, pThis->pszProxyPassword);
3657 if (RT_FAILURE(rc))
3658 RTHttpFreeProxyInfo(pProxy);
3659 }
3660 else
3661 {
3662 AssertFailed();
3663 rc = VERR_INTERNAL_ERROR;
3664 }
3665 }
3666 }
3667 return rc;
3668}
3669
3670
3671RTR3DECL(int) RTHttpFreeProxyInfo(PRTHTTPPROXYINFO pProxy)
3672{
3673 if (pProxy)
3674 {
3675 RTStrFree(pProxy->pszProxyHost);
3676 RTStrFree(pProxy->pszProxyUsername);
3677 RTStrFree(pProxy->pszProxyPassword);
3678 pProxy->pszProxyHost = NULL;
3679 pProxy->pszProxyUsername = NULL;
3680 pProxy->pszProxyPassword = NULL;
3681 pProxy->enmProxyType = RTHTTPPROXYTYPE_INVALID;
3682 pProxy->uProxyPort = UINT32_MAX;
3683 }
3684 return VINF_SUCCESS;
3685}
3686
3687
3688RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
3689 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
3690{
3691 /*
3692 * Set safe return values and validate input.
3693 */
3694 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
3695 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
3696
3697 if (ppvHeaders)
3698 *ppvHeaders = NULL;
3699 if (pcbHeaders)
3700 *pcbHeaders = 0;
3701 if (ppvBody)
3702 *ppvBody = NULL;
3703 if (pcbBody)
3704 *pcbBody = 0;
3705 if (puHttpStatus)
3706 *puHttpStatus = UINT32_MAX;
3707
3708 PRTHTTPINTERNAL pThis = hHttp;
3709 RTHTTP_VALID_RETURN(pThis);
3710 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
3711 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
3712
3713#ifdef LOG_ENABLED
3714 if (LogIs4Enabled() && pThis->pHeaders)
3715 {
3716 Log4(("RTHttpPerform: headers:\n"));
3717 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
3718 Log4(("%s\n", pCur->data));
3719 }
3720 if (pvReqBody && cbReqBody)
3721 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
3722#endif
3723
3724 /*
3725 * Set the busy flag (paranoia).
3726 */
3727 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3728 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3729
3730 /*
3731 * Reset the state and apply settings.
3732 */
3733 rtHttpResetState(pThis);
3734 int rc = rtHttpApplySettings(hHttp, pszUrl);
3735 if (RT_SUCCESS(rc))
3736 {
3737 /* Set the HTTP method. */
3738 CURLcode rcCurl = CURLE_BAD_FUNCTION_ARGUMENT;
3739 switch (enmMethod)
3740 {
3741 case RTHTTPMETHOD_GET:
3742 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3743 break;
3744 case RTHTTPMETHOD_PUT:
3745 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3746 break;
3747 case RTHTTPMETHOD_POST:
3748 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3749 break;
3750 case RTHTTPMETHOD_PATCH:
3751 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
3752 break;
3753 case RTHTTPMETHOD_DELETE:
3754 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
3755 break;
3756 case RTHTTPMETHOD_HEAD:
3757 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3758 if (CURL_SUCCESS(rcCurl))
3759 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3760 break;
3761 case RTHTTPMETHOD_OPTIONS:
3762 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
3763 break;
3764 case RTHTTPMETHOD_TRACE:
3765 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
3766 break;
3767#ifdef IPRT_HTTP_WITH_WEBDAV
3768 case RTHTTPMETHOD_PROPFIND:
3769 RT_FALL_THROUGH();
3770#endif
3771 case RTHTTPMETHOD_END:
3772 case RTHTTPMETHOD_INVALID:
3773 case RTHTTPMETHOD_32BIT_HACK:
3774 AssertFailed();
3775 }
3776
3777 /* Request body. POST requests should always have a body. */
3778 if ( pvReqBody
3779 && CURL_SUCCESS(rcCurl)
3780 && ( cbReqBody > 0
3781 || enmMethod == RTHTTPMETHOD_POST) )
3782 {
3783 if (enmMethod == RTHTTPMETHOD_POST)
3784 {
3785 /** @todo ??? */
3786 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
3787 if (CURL_SUCCESS(rcCurl))
3788 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
3789 }
3790 else
3791 {
3792 pThis->ReadData.Mem.pvMem = pvReqBody;
3793 pThis->ReadData.Mem.cbMem = cbReqBody;
3794 pThis->ReadData.Mem.offMem = 0;
3795 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis);
3796 /* curl will use chunked transfer is it doesn't know the body size */
3797 if (enmMethod == RTHTTPMETHOD_PUT && CURL_SUCCESS(rcCurl))
3798 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbReqBody);
3799 }
3800 }
3801 else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl))
3802 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis);
3803
3804 /* Headers. */
3805 if (CURL_SUCCESS(rcCurl))
3806 {
3807 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3808 rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteHeaderData, pThis);
3809 }
3810
3811 /* Body */
3812 if (ppvBody && CURL_SUCCESS(rcCurl))
3813 {
3814 RT_ZERO(pThis->BodyOutput.uData.Mem);
3815 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteBodyData, pThis);
3816 }
3817 else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl))
3818 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis);
3819
3820 if (CURL_SUCCESS(rcCurl))
3821 {
3822 /*
3823 * Perform the HTTP operation.
3824 */
3825 rcCurl = curl_easy_perform(pThis->pCurl);
3826 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
3827 if (RT_SUCCESS(rc))
3828 rc = pThis->rcOutput;
3829 if (RT_SUCCESS(rc))
3830 {
3831 if (ppvHeaders)
3832 {
3833 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
3834 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
3835 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
3836 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
3837 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
3838 pThis->HeadersOutput.uData.Mem.pb = NULL;
3839 }
3840 if (ppvBody)
3841 {
3842 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
3843 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3844 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3845 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3846 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3847 pThis->BodyOutput.uData.Mem.pb = NULL;
3848 }
3849 }
3850 }
3851 else
3852 rc = VERR_HTTP_CURL_ERROR;
3853
3854 /* Ensure we've freed all unused output and dropped references to input memory.*/
3855 if (pThis->HeadersOutput.uData.Mem.pb)
3856 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3857 if (pThis->BodyOutput.uData.Mem.pb)
3858 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3859 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3860 RT_ZERO(pThis->BodyOutput.uData.Mem);
3861 RT_ZERO(pThis->ReadData);
3862 }
3863
3864 ASMAtomicWriteBool(&pThis->fBusy, false);
3865 return rc;
3866}
3867
3868
3869/*********************************************************************************************************************************
3870* Callback APIs. *
3871*********************************************************************************************************************************/
3872
3873RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser)
3874{
3875 PRTHTTPINTERNAL pThis = hHttp;
3876 RTHTTP_VALID_RETURN(pThis);
3877
3878 pThis->pfnUploadCallback = pfnCallback;
3879 pThis->pvUploadCallbackUser = pvUser;
3880 pThis->cbUploadContent = cbContent;
3881 pThis->offUploadContent = 0;
3882
3883 if (cbContent != UINT64_MAX)
3884 {
3885 AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t));
3886 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent);
3887 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR);
3888 }
3889 return VINF_SUCCESS;
3890}
3891
3892
3893RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser)
3894{
3895 PRTHTTPINTERNAL pThis = hHttp;
3896 RTHTTP_VALID_RETURN(pThis);
3897 AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS);
3898
3899 pThis->pfnDownloadCallback = pfnCallback;
3900 pThis->pvDownloadCallbackUser = pvUser;
3901 pThis->fDownloadCallback = fFlags;
3902 pThis->uDownloadHttpStatus = UINT32_MAX;
3903 pThis->cbDownloadContent = UINT64_MAX;
3904 pThis->offDownloadContent = 0;
3905
3906 return VINF_SUCCESS;
3907}
3908
3909
3910RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser)
3911{
3912 PRTHTTPINTERNAL pThis = hHttp;
3913 RTHTTP_VALID_RETURN(pThis);
3914
3915 pThis->pfnDownloadProgress = pfnCallback;
3916 pThis->pvDownloadProgressUser = pvUser;
3917 return VINF_SUCCESS;
3918}
3919
3920
3921RTR3DECL(int) RTHttpSetHeaderCallback(RTHTTP hHttp, PFNRTHTTPHEADERCALLBACK pfnCallback, void *pvUser)
3922{
3923 PRTHTTPINTERNAL pThis = hHttp;
3924 RTHTTP_VALID_RETURN(pThis);
3925
3926 pThis->pfnHeaderCallback = pfnCallback;
3927 pThis->pvHeaderCallbackUser = pvUser;
3928 return VINF_SUCCESS;
3929}
3930
3931
3932/*********************************************************************************************************************************
3933* Temporary raw cURL stuff. Will be gone before 6.0 is out! *
3934*********************************************************************************************************************************/
3935
3936RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3937{
3938 CURLcode rcCurl;
3939
3940 PRTHTTPINTERNAL pThis = hHttp;
3941 RTHTTP_VALID_RETURN(pThis);
3942
3943 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
3944 if (RT_FAILURE(rc))
3945 return rc;
3946
3947 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3948 if (CURL_FAILURE(rcCurl))
3949 return VERR_HTTP_CURL_ERROR;
3950
3951 return VINF_SUCCESS;
3952}
3953
3954
3955RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3956{
3957 CURLcode rcCurl;
3958
3959 PRTHTTPINTERNAL pThis = hHttp;
3960 RTHTTP_VALID_RETURN(pThis);
3961
3962 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3963 if (CURL_FAILURE(rcCurl))
3964 return VERR_HTTP_CURL_ERROR;
3965
3966 return VINF_SUCCESS;
3967}
3968
3969
3970RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3971{
3972 CURLcode rcCurl;
3973
3974 PRTHTTPINTERNAL pThis = hHttp;
3975 RTHTTP_VALID_RETURN(pThis);
3976
3977 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3978 if (CURL_FAILURE(rcCurl))
3979 return VERR_HTTP_CURL_ERROR;
3980
3981 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3982 if (CURL_FAILURE(rcCurl))
3983 return VERR_HTTP_CURL_ERROR;
3984
3985 return VINF_SUCCESS;
3986}
3987
3988
3989RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3990{
3991 CURLcode rcCurl;
3992
3993 PRTHTTPINTERNAL pThis = hHttp;
3994 RTHTTP_VALID_RETURN(pThis);
3995
3996 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3997 if (CURL_FAILURE(rcCurl))
3998 return VERR_HTTP_CURL_ERROR;
3999
4000 return VINF_SUCCESS;
4001}
4002
4003
4004RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
4005{
4006 CURLcode rcCurl;
4007
4008 PRTHTTPINTERNAL pThis = hHttp;
4009 RTHTTP_VALID_RETURN(pThis);
4010
4011 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
4012 if (CURL_FAILURE(rcCurl))
4013 return VERR_HTTP_CURL_ERROR;
4014
4015 return VINF_SUCCESS;
4016}
4017
4018
4019RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
4020{
4021 /* curl doesn't provide an option for this */
4022 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
4023}
4024
4025
4026RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
4027{
4028 CURLcode rcCurl;
4029
4030 PRTHTTPINTERNAL pThis = hHttp;
4031 RTHTTP_VALID_RETURN(pThis);
4032
4033 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
4034 if (CURL_FAILURE(rcCurl))
4035 return VERR_HTTP_CURL_ERROR;
4036
4037 return VINF_SUCCESS;
4038}
4039
4040
4041RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
4042{
4043 CURLcode rcCurl;
4044
4045 PRTHTTPINTERNAL pThis = hHttp;
4046 RTHTTP_VALID_RETURN(pThis);
4047
4048 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
4049 if (CURL_FAILURE(rcCurl))
4050 return VERR_HTTP_CURL_ERROR;
4051
4052 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
4053 if (CURL_FAILURE(rcCurl))
4054 return VERR_HTTP_CURL_ERROR;
4055
4056 return VINF_SUCCESS;
4057}
4058
4059RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
4060{
4061 CURLcode rcCurl;
4062
4063 PRTHTTPINTERNAL pThis = hHttp;
4064 RTHTTP_VALID_RETURN(pThis);
4065
4066 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
4067 if (CURL_FAILURE(rcCurl))
4068 return VERR_HTTP_CURL_ERROR;
4069
4070 return VINF_SUCCESS;
4071}
4072
4073
4074RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
4075{
4076 CURLcode rcCurl;
4077
4078 PRTHTTPINTERNAL pThis = hHttp;
4079 RTHTTP_VALID_RETURN(pThis);
4080
4081 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
4082 if (CURL_FAILURE(rcCurl))
4083 return VERR_HTTP_CURL_ERROR;
4084
4085 return VINF_SUCCESS;
4086}
4087
4088
4089RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
4090{
4091 CURLcode rcCurl;
4092
4093 PRTHTTPINTERNAL pThis = hHttp;
4094 RTHTTP_VALID_RETURN(pThis);
4095
4096 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
4097 if (CURL_FAILURE(rcCurl))
4098 return VERR_HTTP_CURL_ERROR;
4099
4100 return VINF_SUCCESS;
4101}
4102
4103
4104RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
4105{
4106 CURLcode rcCurl;
4107
4108 PRTHTTPINTERNAL pThis = hHttp;
4109 RTHTTP_VALID_RETURN(pThis);
4110
4111 /*
4112 * XXX: Do this here for now as a stop-gap measure as
4113 * RTHttpReset() resets this (and proxy settings).
4114 */
4115 if (pThis->pszCaFile)
4116 {
4117 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile);
4118 if (CURL_FAILURE(rcCurl))
4119 return VERR_HTTP_CURL_ERROR;
4120 }
4121
4122 rcCurl = curl_easy_perform(pThis->pCurl);
4123 if (CURL_FAILURE(rcCurl))
4124 return VERR_HTTP_CURL_ERROR;
4125
4126 return VINF_SUCCESS;
4127}
4128
4129
4130RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
4131{
4132 CURLcode rcCurl;
4133
4134 PRTHTTPINTERNAL pThis = hHttp;
4135 RTHTTP_VALID_RETURN(pThis);
4136 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
4137
4138 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
4139 if (CURL_FAILURE(rcCurl))
4140 return VERR_HTTP_CURL_ERROR;
4141
4142 return VINF_SUCCESS;
4143}
4144
4145
4146RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
4147{
4148 CURLcode rcCurl;
4149
4150 PRTHTTPINTERNAL pThis = hHttp;
4151 RTHTTP_VALID_RETURN(pThis);
4152
4153 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
4154 if (CURL_FAILURE(rcCurl))
4155 return VERR_HTTP_CURL_ERROR;
4156
4157 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
4158 if (CURL_FAILURE(rcCurl))
4159 return VERR_HTTP_CURL_ERROR;
4160
4161 return VINF_SUCCESS;
4162}
4163
4164
4165RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4166{
4167 PRTHTTPINTERNAL pThis = hHttp;
4168 RTHTTP_VALID_RETURN(pThis);
4169
4170 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
4171 if (CURL_FAILURE(rcCurl))
4172 return VERR_HTTP_CURL_ERROR;
4173
4174 return VINF_SUCCESS;
4175}
4176
4177
4178RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4179{
4180 CURLcode rcCurl;
4181
4182 PRTHTTPINTERNAL pThis = hHttp;
4183 RTHTTP_VALID_RETURN(pThis);
4184
4185 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
4186 if (CURL_FAILURE(rcCurl))
4187 return VERR_HTTP_CURL_ERROR;
4188
4189 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
4190 if (CURL_FAILURE(rcCurl))
4191 return VERR_HTTP_CURL_ERROR;
4192
4193 return VINF_SUCCESS;
4194}
4195
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