VirtualBox

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

Last change on this file since 102916 was 102916, checked in by vboxsync, 5 months ago

Runtime/http-curl.cpp: Make RTHttpSetProxy() accept a NULL pcszProxy parameter indicating complete disabling of any proxy settings

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

© 2023 Oracle
ContactPrivacy policyTerms of Use