VirtualBox

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

Last change on this file was 104308, checked in by vboxsync, 5 weeks ago

IPRT/http-curl: Also handle the 501 HTTP status code. Required for testing unsupported stuff in the testcases. bugref:9437

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 143.7 KB
Line 
1/* $Id: http-curl.cpp 104308 2024-04-12 10:04:21Z 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 case 501:
2838 /* Requested function not supported */
2839 if (!puHttpStatus)
2840 rc = VERR_HTTP_NOT_SUPPORTED;
2841 break;
2842 }
2843
2844 if (pThis->pszRedirLocation)
2845 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2846 else
2847 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2848 }
2849 else
2850 {
2851 switch (rcCurl)
2852 {
2853 case CURLE_URL_MALFORMAT:
2854 case CURLE_COULDNT_RESOLVE_HOST:
2855 rc = VERR_HTTP_HOST_NOT_FOUND;
2856 break;
2857 case CURLE_COULDNT_CONNECT:
2858 rc = VERR_HTTP_COULDNT_CONNECT;
2859 break;
2860 case CURLE_SSL_CONNECT_ERROR:
2861 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2862 break;
2863 case CURLE_SSL_CACERT:
2864 /* The peer certificate cannot be authenticated with the CA certificates
2865 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2866 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2867 break;
2868 case CURLE_SSL_CACERT_BADFILE:
2869 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2870 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2871 break;
2872 case CURLE_ABORTED_BY_CALLBACK:
2873 /* forcefully aborted */
2874 rc = VERR_HTTP_ABORTED;
2875 break;
2876 case CURLE_COULDNT_RESOLVE_PROXY:
2877 rc = VERR_HTTP_PROXY_NOT_FOUND;
2878 break;
2879 case CURLE_WRITE_ERROR:
2880 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2881 break;
2882 //case CURLE_READ_ERROR
2883
2884 default:
2885 break;
2886 }
2887 Log(("%s: %Rrc: %u = %s%s%.*s\n",
2888 __FUNCTION__,
2889 rc, rcCurl, curl_easy_strerror((CURLcode)rcCurl),
2890 pThis->szErrorBuffer[0] != '\0' ? ": " : "",
2891 (int) sizeof(pThis->szErrorBuffer),
2892 pThis->szErrorBuffer[0] != '\0' ? pThis->szErrorBuffer : ""));
2893 }
2894
2895 return rc;
2896}
2897
2898
2899/**
2900 * cURL callback for reporting progress, we use it for checking for abort.
2901 */
2902static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded) RT_NOTHROW_DEF
2903{
2904 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2905 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2906 RT_NOREF_PV(rdTotalUpload);
2907 RT_NOREF_PV(rdUploaded);
2908
2909 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2910
2911 if (pThis->pfnDownloadProgress)
2912 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2913
2914 return pThis->fAbort ? 1 : 0;
2915}
2916
2917
2918/**
2919 * Whether we're likely to need SSL to handle the give URL.
2920 *
2921 * @returns true if we need, false if we probably don't.
2922 * @param pszUrl The URL.
2923 */
2924static bool rtHttpNeedSsl(const char *pszUrl)
2925{
2926 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2927}
2928
2929
2930/**
2931 * Applies recoded settings to the cURL instance before doing work.
2932 *
2933 * @returns IPRT status code.
2934 * @param pThis The HTTP/HTTPS client instance.
2935 * @param pszUrl The URL.
2936 */
2937static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2938{
2939 /*
2940 * The URL.
2941 */
2942 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2943 if (CURL_FAILURE(rcCurl))
2944 return VERR_INVALID_PARAMETER;
2945
2946 /*
2947 * Proxy config.
2948 */
2949 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2950 if (RT_FAILURE(rc))
2951 return rc;
2952
2953 /*
2954 * Setup SSL. Can be a bit of work.
2955 */
2956 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2957 if (CURL_FAILURE(rcCurl))
2958 return VERR_INVALID_PARAMETER;
2959
2960 const char *pszCaFile = pThis->pszCaFile;
2961 if ( !pszCaFile
2962 && rtHttpNeedSsl(pszUrl))
2963 {
2964 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2965 if (RT_SUCCESS(rc))
2966 pszCaFile = pThis->pszCaFile;
2967 else
2968 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2969 }
2970 if (pszCaFile)
2971 {
2972 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2973 if (CURL_FAILURE(rcCurl))
2974 return VERR_HTTP_CURL_ERROR;
2975 }
2976
2977 /*
2978 * Progress/abort.
2979 */
2980 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2981 if (CURL_FAILURE(rcCurl))
2982 return VERR_HTTP_CURL_ERROR;
2983 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2984 if (CURL_FAILURE(rcCurl))
2985 return VERR_HTTP_CURL_ERROR;
2986 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2987 if (CURL_FAILURE(rcCurl))
2988 return VERR_HTTP_CURL_ERROR;
2989
2990 /*
2991 * Set default user agent string if necessary. Some websites take offence
2992 * if we don't set it.
2993 */
2994 if ( !pThis->fHaveSetUserAgent
2995 && !pThis->fHaveUserAgentHeader)
2996 {
2997 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2998 if (CURL_FAILURE(rcCurl))
2999 return VERR_HTTP_CURL_ERROR;
3000 pThis->fHaveSetUserAgent = true;
3001 }
3002
3003 /*
3004 * Use GET by default.
3005 */
3006 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
3007 if (CURL_FAILURE(rcCurl))
3008 return VERR_HTTP_CURL_ERROR;
3009 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
3010 if (CURL_FAILURE(rcCurl))
3011 return VERR_HTTP_CURL_ERROR;
3012
3013 return VINF_SUCCESS;
3014}
3015
3016
3017/**
3018 * Resets state.
3019 *
3020 * @param pThis HTTP client instance.
3021 */
3022static void rtHttpResetState(PRTHTTPINTERNAL pThis)
3023{
3024 pThis->fAbort = false;
3025 pThis->rcOutput = VINF_SUCCESS;
3026 pThis->uDownloadHttpStatus = UINT32_MAX;
3027 pThis->cbDownloadContent = UINT64_MAX;
3028 pThis->offDownloadContent = 0;
3029 pThis->offUploadContent = 0;
3030 pThis->rcOutput = VINF_SUCCESS;
3031 pThis->cbDownloadHint = 0;
3032 Assert(pThis->BodyOutput.pHttp == pThis);
3033 Assert(pThis->HeadersOutput.pHttp == pThis);
3034}
3035
3036
3037/**
3038 * Tries to determin uDownloadHttpStatus and cbDownloadContent.
3039 *
3040 * @param pThis HTTP client instance.
3041 */
3042static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis)
3043{
3044 long lHttpStatus = 0;
3045 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus);
3046 pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus;
3047
3048#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
3049 curl_off_t cbContent = -1;
3050 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent);
3051 if (cbContent >= 0)
3052 pThis->cbDownloadContent = (uint64_t)cbContent;
3053#else
3054 double rdContent = -1.0;
3055 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent);
3056 if (rdContent >= 0.0)
3057 pThis->cbDownloadContent = (uint64_t)rdContent;
3058#endif
3059}
3060
3061
3062/**
3063 * Worker for rtHttpWriteHeaderData and rtHttpWriteBodyData.
3064 */
3065static size_t rtHttpWriteDataToMemOutput(PRTHTTPINTERNAL pThis, RTHTTPOUTPUTDATA *pOutput, char const *pchBuf, size_t cbToAppend)
3066{
3067 /*
3068 * Do max size and overflow checks.
3069 */
3070 size_t const cbCurSize = pOutput->uData.Mem.cb;
3071 size_t const cbNewSize = cbCurSize + cbToAppend;
3072 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3073 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
3074 {
3075 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
3076 {
3077 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend);
3078 pOutput->uData.Mem.cb = cbNewSize;
3079 pOutput->uData.Mem.pb[cbNewSize] = '\0';
3080 return cbToAppend;
3081 }
3082
3083 /*
3084 * We need to reallocate the output buffer.
3085 */
3086 /** @todo this could do with a better strategy wrt growth. */
3087 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
3088 if ( cbAlloc <= pThis->cbDownloadHint
3089 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3090 && pOutput == &pThis->BodyOutput)
3091 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
3092
3093 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
3094 if (pbNew)
3095 {
3096 memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend);
3097 pbNew[cbNewSize] = '\0';
3098
3099 pOutput->uData.Mem.cbAllocated = cbAlloc;
3100 pOutput->uData.Mem.pb = pbNew;
3101 pOutput->uData.Mem.cb = cbNewSize;
3102 return cbToAppend;
3103 }
3104
3105 pThis->rcOutput = VERR_NO_MEMORY;
3106 }
3107 else
3108 pThis->rcOutput = VERR_TOO_MUCH_DATA;
3109
3110 /*
3111 * Failure - abort.
3112 */
3113 RTMemFree(pOutput->uData.Mem.pb);
3114 pOutput->uData.Mem.pb = NULL;
3115 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
3116 pThis->fAbort = true;
3117 return 0;
3118}
3119
3120
3121/**
3122 * cURL callback for writing body data.
3123 */
3124static size_t rtHttpWriteBodyData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3125{
3126 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3127 size_t const cbToAppend = cbUnit * cUnits;
3128
3129 /*
3130 * Check if this belongs to the body download callback.
3131 */
3132 if (pThis->pfnDownloadCallback)
3133 {
3134 if (pThis->offDownloadContent == 0)
3135 rtHttpGetDownloadStatusAndLength(pThis);
3136
3137 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3138 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3139 {
3140 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3141 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3142 if (RT_SUCCESS(rc))
3143 {
3144 pThis->offDownloadContent += cbToAppend;
3145 return cbToAppend;
3146 }
3147 if (RT_SUCCESS(pThis->rcOutput))
3148 pThis->rcOutput = rc;
3149 pThis->fAbort = true;
3150 return 0;
3151 }
3152 }
3153
3154 /*
3155 * Otherwise, copy to memory output buffer.
3156 */
3157 return rtHttpWriteDataToMemOutput(pThis, &pThis->BodyOutput, pchBuf, cbToAppend);
3158}
3159
3160
3161/**
3162 * cURL callback for writing header data.
3163 */
3164static size_t rtHttpWriteHeaderData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3165{
3166 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3167 size_t const cbToAppend = cbUnit * cUnits;
3168
3169 /*
3170 * Work the header callback, if one.
3171 * ASSUMES cURL is giving us one header at a time.
3172 */
3173 if (pThis->pfnHeaderCallback)
3174 {
3175 /*
3176 * Find the end of the field name first.
3177 */
3178 uint32_t uMatchWord;
3179 size_t cchField;
3180 const char *pchField = pchBuf;
3181 size_t cchValue;
3182 const char *pchValue = (const char *)memchr(pchBuf, ':', cbToAppend);
3183 if (pchValue)
3184 {
3185 cchField = pchValue - pchField;
3186 if (RT_LIKELY(cchField >= 3))
3187 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, RT_C_TO_LOWER(pchBuf[0]),
3188 RT_C_TO_LOWER(pchBuf[1]), RT_C_TO_LOWER(pchBuf[2]));
3189 else
3190 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField,
3191 cchField >= 1 ? RT_C_TO_LOWER(pchBuf[0]) : 0,
3192 cchField >= 2 ? RT_C_TO_LOWER(pchBuf[1]) : 0,
3193 0);
3194 pchValue++;
3195 cchValue = cbToAppend - cchField - 1;
3196 }
3197 /* Since cURL gives us the "HTTP/{version} {code} {status}" line too,
3198 we slap a fictitious field name ':http-status-line' in front of it. */
3199 else if (cbToAppend > 5 && pchBuf[0] == 'H' && pchBuf[1] == 'T' && pchBuf[2] == 'T' && pchBuf[3] == 'P' && pchBuf[4] == '/')
3200 {
3201 pchField = ":http-status-line";
3202 cchField = 17;
3203 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(17, ':', 'h', 't');
3204 pchValue = pchBuf;
3205 cchValue = cbToAppend;
3206 }
3207 /* cURL also gives us the empty line before the body, so we slap another
3208 fictitious field name ':end-of-headers' in front of it as well. */
3209 else if (cbToAppend == 2 && pchBuf[0] == '\r' && pchBuf[1] == '\n')
3210 {
3211 pchField = ":end-of-headers";
3212 cchField = 15;
3213 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(15, ':', 'e', 'n');
3214 pchValue = pchBuf;
3215 cchValue = cbToAppend;
3216 }
3217 else
3218 AssertMsgFailedReturn(("pchBuf=%.*s\n", cbToAppend, pchBuf), cbToAppend);
3219
3220 /*
3221 * Determin the field value, stripping one leading blank and all
3222 * trailing spaces.
3223 */
3224 if (cchValue > 0 && RT_C_IS_BLANK(*pchValue))
3225 pchValue++, cchValue--;
3226 while (cchValue > 0 && RT_C_IS_SPACE(pchValue[cchValue - 1]))
3227 cchValue--;
3228
3229 /*
3230 * Pass it to the callback.
3231 */
3232 Log6(("rtHttpWriteHeaderData: %.*s: %.*s\n", cchField, pchBuf, cchValue, pchValue));
3233 int rc = pThis->pfnHeaderCallback(pThis, uMatchWord, pchBuf, cchField,
3234 pchValue, cchValue, pThis->pvHeaderCallbackUser);
3235 if (RT_SUCCESS(rc))
3236 return cbToAppend;
3237
3238 /* Abort on error. */
3239 if (RT_SUCCESS(pThis->rcOutput))
3240 pThis->rcOutput = rc;
3241 pThis->fAbort = true;
3242 return 0;
3243 }
3244
3245 return rtHttpWriteDataToMemOutput(pThis, &pThis->HeadersOutput, pchBuf, cbToAppend);
3246}
3247
3248
3249/**
3250 * cURL callback for working the upload callback.
3251 */
3252static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3253{
3254 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3255 size_t const cbBuf = cbUnit * cUnits;
3256
3257 /* Get download info the first time we're called. */
3258 if (pThis->offDownloadContent == 0)
3259 rtHttpGetDownloadStatusAndLength(pThis);
3260
3261 /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */
3262 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3263 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3264 {
3265 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3266 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3267 if (RT_SUCCESS(rc))
3268 { /* likely */ }
3269 else
3270 {
3271 if (RT_SUCCESS(pThis->rcOutput))
3272 pThis->rcOutput = rc;
3273 pThis->fAbort = true;
3274 return 0;
3275 }
3276 }
3277 pThis->offDownloadContent += cbBuf;
3278 return cbBuf;
3279}
3280
3281
3282/**
3283 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
3284 */
3285static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3286{
3287 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3288 size_t const cbReq = cbUnit * cUnits;
3289 size_t const offMem = pThis->ReadData.Mem.offMem;
3290 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
3291 if (cbToCopy > cbReq)
3292 cbToCopy = cbReq;
3293 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
3294 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
3295 return cbToCopy;
3296}
3297
3298
3299/**
3300 * Callback feeding cURL data via the user upload callback.
3301 */
3302static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3303{
3304 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3305 size_t const cbReq = cbUnit * cUnits;
3306
3307 size_t cbActual = 0;
3308 int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser);
3309 if (RT_SUCCESS(rc))
3310 {
3311 pThis->offUploadContent += cbActual;
3312 return cbActual;
3313 }
3314
3315 if (RT_SUCCESS(pThis->rcOutput))
3316 pThis->rcOutput = rc;
3317 pThis->fAbort = true;
3318 return CURL_READFUNC_ABORT;
3319}
3320
3321
3322/**
3323 * Helper for installing a (body) write callback function.
3324 *
3325 * @returns cURL status code.
3326 * @param pThis The HTTP client instance.
3327 * @param pfnWrite The callback.
3328 * @param pvUser The callback user argument.
3329 */
3330static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3331{
3332 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
3333 if (CURL_SUCCESS(rcCurl))
3334 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
3335 return rcCurl;
3336}
3337
3338
3339/**
3340 * Helper for installing a header write callback function.
3341 *
3342 * @returns cURL status code.
3343 * @param pThis The HTTP client instance.
3344 * @param pfnWrite The callback.
3345 * @param pvUser The callback user argument.
3346 */
3347static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3348{
3349 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3350 if (CURL_SUCCESS(rcCurl))
3351 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3352 return rcCurl;
3353}
3354
3355
3356/**
3357 * Helper for installing a (body) read callback function.
3358 *
3359 * @returns cURL status code.
3360 * @param pThis The HTTP client instance.
3361 * @param pfnRead The callback.
3362 * @param pvUser The callback user argument.
3363 */
3364static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3365{
3366 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3367 if (CURL_SUCCESS(rcCurl))
3368 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3369 return rcCurl;
3370}
3371
3372
3373/**
3374 * Internal worker that performs a HTTP GET.
3375 *
3376 * @returns IPRT status code.
3377 * @param hHttp The HTTP/HTTPS client instance.
3378 * @param pszUrl The URL.
3379 * @param fNoBody Set to suppress the body.
3380 * @param ppvResponse Where to return the pointer to the allocated
3381 * response data (RTMemFree). There will always be
3382 * an zero terminator char after the response, that
3383 * is not part of the size returned via @a pcb.
3384 * @param pcb The size of the response data.
3385 *
3386 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
3387 * threads, because that will probably blow up!
3388 */
3389static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
3390{
3391 PRTHTTPINTERNAL pThis = hHttp;
3392 RTHTTP_VALID_RETURN(pThis);
3393
3394 /*
3395 * Reset the return values in case of more "GUI programming" on the client
3396 * side (i.e. a programming style not bothering checking return codes).
3397 */
3398 *ppvResponse = NULL;
3399 *pcb = 0;
3400
3401 /*
3402 * Set the busy flag (paranoia).
3403 */
3404 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3405 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3406
3407 /*
3408 * Reset the state and apply settings.
3409 */
3410 rtHttpResetState(pThis);
3411 int rc = rtHttpApplySettings(hHttp, pszUrl);
3412 if (RT_SUCCESS(rc))
3413 {
3414 RT_ZERO(pThis->BodyOutput.uData.Mem);
3415 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteBodyData, pThis);
3416 if (fNoBody)
3417 {
3418 if (CURL_SUCCESS(rcCurl))
3419 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3420 if (CURL_SUCCESS(rcCurl))
3421 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
3422 }
3423 if (CURL_SUCCESS(rcCurl))
3424 {
3425 /*
3426 * Perform the HTTP operation.
3427 */
3428 rcCurl = curl_easy_perform(pThis->pCurl);
3429 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3430 if (RT_SUCCESS(rc))
3431 rc = pThis->rcOutput;
3432 if (RT_SUCCESS(rc))
3433 {
3434 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
3435 *pcb = pThis->BodyOutput.uData.Mem.cb;
3436 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
3437 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3438 }
3439 else if (pThis->BodyOutput.uData.Mem.pb)
3440 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3441 RT_ZERO(pThis->BodyOutput.uData.Mem);
3442 }
3443 else
3444 rc = VERR_HTTP_CURL_ERROR;
3445 }
3446
3447 ASMAtomicWriteBool(&pThis->fBusy, false);
3448 return rc;
3449}
3450
3451
3452RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3453{
3454 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3455 uint8_t *pv;
3456 size_t cb;
3457 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
3458 if (RT_SUCCESS(rc))
3459 {
3460 if (pv) /* paranoia */
3461 *ppszNotUtf8 = (char *)pv;
3462 else
3463 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3464 }
3465 else
3466 *ppszNotUtf8 = NULL;
3467 return rc;
3468}
3469
3470
3471RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3472{
3473 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3474 uint8_t *pv;
3475 size_t cb;
3476 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
3477 if (RT_SUCCESS(rc))
3478 {
3479 if (pv) /* paranoia */
3480 *ppszNotUtf8 = (char *)pv;
3481 else
3482 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3483 }
3484 else
3485 *ppszNotUtf8 = NULL;
3486 return rc;
3487
3488}
3489
3490
3491RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
3492{
3493 RTMemFree(pszNotUtf8);
3494}
3495
3496
3497RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3498{
3499 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3500 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3501}
3502
3503
3504RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3505{
3506 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3507 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3508}
3509
3510
3511RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
3512{
3513 RTMemFree(pvResponse);
3514}
3515
3516
3517/**
3518 * cURL callback for writing data to a file.
3519 */
3520static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3521{
3522 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
3523 PRTHTTPINTERNAL pThis = pOutput->pHttp;
3524
3525 size_t cbWritten = 0;
3526 int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten);
3527 if (RT_SUCCESS(rc))
3528 return cbWritten;
3529
3530 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
3531 pThis->rcOutput = rc;
3532 return 0;
3533}
3534
3535
3536RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
3537{
3538 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
3539 PRTHTTPINTERNAL pThis = hHttp;
3540 RTHTTP_VALID_RETURN(pThis);
3541
3542 /*
3543 * Set the busy flag (paranoia).
3544 */
3545 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3546 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3547
3548 /*
3549 * Reset the state and apply settings.
3550 */
3551 rtHttpResetState(pThis);
3552 int rc = rtHttpApplySettings(hHttp, pszUrl);
3553 if (RT_SUCCESS(rc))
3554 {
3555 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3556 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
3557 if (CURL_SUCCESS(rcCurl))
3558 {
3559 /*
3560 * Open the output file.
3561 */
3562 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
3563 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
3564 if (RT_SUCCESS(rc))
3565 {
3566 /*
3567 * Perform the HTTP operation.
3568 */
3569 rcCurl = curl_easy_perform(pThis->pCurl);
3570 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3571 if (RT_SUCCESS(rc))
3572 rc = pThis->rcOutput;
3573
3574 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
3575 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
3576 rc = rc2;
3577 }
3578 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3579 }
3580 else
3581 rc = VERR_HTTP_CURL_ERROR;
3582 }
3583
3584 ASMAtomicWriteBool(&pThis->fBusy, false);
3585 return rc;
3586}
3587
3588
3589RTR3DECL(int) RTHttpQueryProxyInfoForUrl(RTHTTP hHttp, const char *pszUrl, PRTHTTPPROXYINFO pProxy)
3590{
3591 /*
3592 * Validate input and clear output.
3593 */
3594 Log(("RTHttpQueryProxyInfoForUrl: hHttp=%p pszUrl=%s pProxy=%s\n", hHttp, pszUrl, pProxy));
3595 RT_ZERO(*pProxy);
3596 pProxy->uProxyPort = UINT32_MAX;
3597
3598 PRTHTTPINTERNAL pThis = hHttp;
3599 RTHTTP_VALID_RETURN(pThis);
3600
3601 /*
3602 * Set up the proxy for the URL.
3603 */
3604 rtHttpResetState(pThis);
3605 /** @todo this does a bit too much (we don't need to set up SSL for instance). */
3606 int rc = rtHttpApplySettings(pThis, pszUrl);
3607 if (RT_SUCCESS(rc))
3608 {
3609 /*
3610 * Copy out the result.
3611 */
3612 if (pThis->fNoProxy)
3613 pProxy->enmProxyType = RTHTTPPROXYTYPE_NOPROXY;
3614 else
3615 {
3616 switch (pThis->enmProxyType)
3617 {
3618 case CURLPROXY_HTTP:
3619#ifdef CURL_AT_LEAST_VERSION
3620# if CURL_AT_LEAST_VERSION(7,19,4)
3621 case CURLPROXY_HTTP_1_0:
3622# endif
3623#endif
3624 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTP;
3625 break;
3626#ifdef CURL_AT_LEAST_VERSION
3627# if CURL_AT_LEAST_VERSION(7,52,0)
3628 case CURLPROXY_HTTPS:
3629 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTPS;
3630 break;
3631# endif
3632#endif
3633 case CURLPROXY_SOCKS4:
3634 case CURLPROXY_SOCKS4A:
3635 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS4;
3636 break;
3637 case CURLPROXY_SOCKS5:
3638 case CURLPROXY_SOCKS5_HOSTNAME:
3639 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS5;
3640 break;
3641 default:
3642 AssertFailed();
3643 pProxy->enmProxyType = RTHTTPPROXYTYPE_UNKNOWN;
3644 break;
3645 }
3646 pProxy->uProxyPort = pThis->uProxyPort;
3647 if (pThis->pszProxyHost != NULL)
3648 {
3649 rc = RTStrDupEx(&pProxy->pszProxyHost, pThis->pszProxyHost);
3650 if (pThis->pszProxyUsername && RT_SUCCESS(rc))
3651 rc = RTStrDupEx(&pProxy->pszProxyUsername, pThis->pszProxyUsername);
3652 if (pThis->pszProxyPassword && RT_SUCCESS(rc))
3653 rc = RTStrDupEx(&pProxy->pszProxyPassword, pThis->pszProxyPassword);
3654 if (RT_FAILURE(rc))
3655 RTHttpFreeProxyInfo(pProxy);
3656 }
3657 else
3658 {
3659 AssertFailed();
3660 rc = VERR_INTERNAL_ERROR;
3661 }
3662 }
3663 }
3664 return rc;
3665}
3666
3667
3668RTR3DECL(int) RTHttpFreeProxyInfo(PRTHTTPPROXYINFO pProxy)
3669{
3670 if (pProxy)
3671 {
3672 RTStrFree(pProxy->pszProxyHost);
3673 RTStrFree(pProxy->pszProxyUsername);
3674 RTStrFree(pProxy->pszProxyPassword);
3675 pProxy->pszProxyHost = NULL;
3676 pProxy->pszProxyUsername = NULL;
3677 pProxy->pszProxyPassword = NULL;
3678 pProxy->enmProxyType = RTHTTPPROXYTYPE_INVALID;
3679 pProxy->uProxyPort = UINT32_MAX;
3680 }
3681 return VINF_SUCCESS;
3682}
3683
3684
3685RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
3686 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
3687{
3688 /*
3689 * Set safe return values and validate input.
3690 */
3691 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
3692 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
3693
3694 if (ppvHeaders)
3695 *ppvHeaders = NULL;
3696 if (pcbHeaders)
3697 *pcbHeaders = 0;
3698 if (ppvBody)
3699 *ppvBody = NULL;
3700 if (pcbBody)
3701 *pcbBody = 0;
3702 if (puHttpStatus)
3703 *puHttpStatus = UINT32_MAX;
3704
3705 PRTHTTPINTERNAL pThis = hHttp;
3706 RTHTTP_VALID_RETURN(pThis);
3707 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
3708 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
3709
3710#ifdef LOG_ENABLED
3711 if (LogIs4Enabled() && pThis->pHeaders)
3712 {
3713 Log4(("RTHttpPerform: headers:\n"));
3714 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
3715 Log4(("%s\n", pCur->data));
3716 }
3717 if (pvReqBody && cbReqBody)
3718 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
3719#endif
3720
3721 /*
3722 * Set the busy flag (paranoia).
3723 */
3724 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3725 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3726
3727 /*
3728 * Reset the state and apply settings.
3729 */
3730 rtHttpResetState(pThis);
3731 int rc = rtHttpApplySettings(hHttp, pszUrl);
3732 if (RT_SUCCESS(rc))
3733 {
3734 /* Set the HTTP method. */
3735 CURLcode rcCurl = CURLE_BAD_FUNCTION_ARGUMENT;
3736 switch (enmMethod)
3737 {
3738 case RTHTTPMETHOD_GET:
3739 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3740 break;
3741 case RTHTTPMETHOD_PUT:
3742 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3743 break;
3744 case RTHTTPMETHOD_POST:
3745 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3746 break;
3747 case RTHTTPMETHOD_PATCH:
3748 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
3749 break;
3750 case RTHTTPMETHOD_DELETE:
3751 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
3752 break;
3753 case RTHTTPMETHOD_HEAD:
3754 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3755 if (CURL_SUCCESS(rcCurl))
3756 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3757 break;
3758 case RTHTTPMETHOD_OPTIONS:
3759 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
3760 break;
3761 case RTHTTPMETHOD_TRACE:
3762 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
3763 break;
3764#ifdef IPRT_HTTP_WITH_WEBDAV
3765 case RTHTTPMETHOD_PROPFIND:
3766 RT_FALL_THROUGH();
3767#endif
3768 case RTHTTPMETHOD_END:
3769 case RTHTTPMETHOD_INVALID:
3770 case RTHTTPMETHOD_32BIT_HACK:
3771 AssertFailed();
3772 }
3773
3774 /* Request body. POST requests should always have a body. */
3775 if ( pvReqBody
3776 && CURL_SUCCESS(rcCurl)
3777 && ( cbReqBody > 0
3778 || enmMethod == RTHTTPMETHOD_POST) )
3779 {
3780 if (enmMethod == RTHTTPMETHOD_POST)
3781 {
3782 /** @todo ??? */
3783 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
3784 if (CURL_SUCCESS(rcCurl))
3785 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
3786 }
3787 else
3788 {
3789 pThis->ReadData.Mem.pvMem = pvReqBody;
3790 pThis->ReadData.Mem.cbMem = cbReqBody;
3791 pThis->ReadData.Mem.offMem = 0;
3792 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis);
3793 /* curl will use chunked transfer is it doesn't know the body size */
3794 if (enmMethod == RTHTTPMETHOD_PUT && CURL_SUCCESS(rcCurl))
3795 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbReqBody);
3796 }
3797 }
3798 else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl))
3799 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis);
3800
3801 /* Headers. */
3802 if (CURL_SUCCESS(rcCurl))
3803 {
3804 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3805 rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteHeaderData, pThis);
3806 }
3807
3808 /* Body */
3809 if (ppvBody && CURL_SUCCESS(rcCurl))
3810 {
3811 RT_ZERO(pThis->BodyOutput.uData.Mem);
3812 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteBodyData, pThis);
3813 }
3814 else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl))
3815 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis);
3816
3817 if (CURL_SUCCESS(rcCurl))
3818 {
3819 /*
3820 * Perform the HTTP operation.
3821 */
3822 rcCurl = curl_easy_perform(pThis->pCurl);
3823 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
3824 if (RT_SUCCESS(rc))
3825 rc = pThis->rcOutput;
3826 if (RT_SUCCESS(rc))
3827 {
3828 if (ppvHeaders)
3829 {
3830 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
3831 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
3832 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
3833 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
3834 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
3835 pThis->HeadersOutput.uData.Mem.pb = NULL;
3836 }
3837 if (ppvBody)
3838 {
3839 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
3840 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3841 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3842 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3843 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3844 pThis->BodyOutput.uData.Mem.pb = NULL;
3845 }
3846 }
3847 }
3848 else
3849 rc = VERR_HTTP_CURL_ERROR;
3850
3851 /* Ensure we've freed all unused output and dropped references to input memory.*/
3852 if (pThis->HeadersOutput.uData.Mem.pb)
3853 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3854 if (pThis->BodyOutput.uData.Mem.pb)
3855 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3856 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3857 RT_ZERO(pThis->BodyOutput.uData.Mem);
3858 RT_ZERO(pThis->ReadData);
3859 }
3860
3861 ASMAtomicWriteBool(&pThis->fBusy, false);
3862 return rc;
3863}
3864
3865
3866/*********************************************************************************************************************************
3867* Callback APIs. *
3868*********************************************************************************************************************************/
3869
3870RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser)
3871{
3872 PRTHTTPINTERNAL pThis = hHttp;
3873 RTHTTP_VALID_RETURN(pThis);
3874
3875 pThis->pfnUploadCallback = pfnCallback;
3876 pThis->pvUploadCallbackUser = pvUser;
3877 pThis->cbUploadContent = cbContent;
3878 pThis->offUploadContent = 0;
3879
3880 if (cbContent != UINT64_MAX)
3881 {
3882 AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t));
3883 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent);
3884 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR);
3885 }
3886 return VINF_SUCCESS;
3887}
3888
3889
3890RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser)
3891{
3892 PRTHTTPINTERNAL pThis = hHttp;
3893 RTHTTP_VALID_RETURN(pThis);
3894 AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS);
3895
3896 pThis->pfnDownloadCallback = pfnCallback;
3897 pThis->pvDownloadCallbackUser = pvUser;
3898 pThis->fDownloadCallback = fFlags;
3899 pThis->uDownloadHttpStatus = UINT32_MAX;
3900 pThis->cbDownloadContent = UINT64_MAX;
3901 pThis->offDownloadContent = 0;
3902
3903 return VINF_SUCCESS;
3904}
3905
3906
3907RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser)
3908{
3909 PRTHTTPINTERNAL pThis = hHttp;
3910 RTHTTP_VALID_RETURN(pThis);
3911
3912 pThis->pfnDownloadProgress = pfnCallback;
3913 pThis->pvDownloadProgressUser = pvUser;
3914 return VINF_SUCCESS;
3915}
3916
3917
3918RTR3DECL(int) RTHttpSetHeaderCallback(RTHTTP hHttp, PFNRTHTTPHEADERCALLBACK pfnCallback, void *pvUser)
3919{
3920 PRTHTTPINTERNAL pThis = hHttp;
3921 RTHTTP_VALID_RETURN(pThis);
3922
3923 pThis->pfnHeaderCallback = pfnCallback;
3924 pThis->pvHeaderCallbackUser = pvUser;
3925 return VINF_SUCCESS;
3926}
3927
3928
3929/*********************************************************************************************************************************
3930* Temporary raw cURL stuff. Will be gone before 6.0 is out! *
3931*********************************************************************************************************************************/
3932
3933RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3934{
3935 CURLcode rcCurl;
3936
3937 PRTHTTPINTERNAL pThis = hHttp;
3938 RTHTTP_VALID_RETURN(pThis);
3939
3940 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
3941 if (RT_FAILURE(rc))
3942 return rc;
3943
3944 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3945 if (CURL_FAILURE(rcCurl))
3946 return VERR_HTTP_CURL_ERROR;
3947
3948 return VINF_SUCCESS;
3949}
3950
3951
3952RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3953{
3954 CURLcode rcCurl;
3955
3956 PRTHTTPINTERNAL pThis = hHttp;
3957 RTHTTP_VALID_RETURN(pThis);
3958
3959 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3960 if (CURL_FAILURE(rcCurl))
3961 return VERR_HTTP_CURL_ERROR;
3962
3963 return VINF_SUCCESS;
3964}
3965
3966
3967RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3968{
3969 CURLcode rcCurl;
3970
3971 PRTHTTPINTERNAL pThis = hHttp;
3972 RTHTTP_VALID_RETURN(pThis);
3973
3974 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3975 if (CURL_FAILURE(rcCurl))
3976 return VERR_HTTP_CURL_ERROR;
3977
3978 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3979 if (CURL_FAILURE(rcCurl))
3980 return VERR_HTTP_CURL_ERROR;
3981
3982 return VINF_SUCCESS;
3983}
3984
3985
3986RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3987{
3988 CURLcode rcCurl;
3989
3990 PRTHTTPINTERNAL pThis = hHttp;
3991 RTHTTP_VALID_RETURN(pThis);
3992
3993 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3994 if (CURL_FAILURE(rcCurl))
3995 return VERR_HTTP_CURL_ERROR;
3996
3997 return VINF_SUCCESS;
3998}
3999
4000
4001RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
4002{
4003 CURLcode rcCurl;
4004
4005 PRTHTTPINTERNAL pThis = hHttp;
4006 RTHTTP_VALID_RETURN(pThis);
4007
4008 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
4009 if (CURL_FAILURE(rcCurl))
4010 return VERR_HTTP_CURL_ERROR;
4011
4012 return VINF_SUCCESS;
4013}
4014
4015
4016RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
4017{
4018 /* curl doesn't provide an option for this */
4019 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
4020}
4021
4022
4023RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
4024{
4025 CURLcode rcCurl;
4026
4027 PRTHTTPINTERNAL pThis = hHttp;
4028 RTHTTP_VALID_RETURN(pThis);
4029
4030 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
4031 if (CURL_FAILURE(rcCurl))
4032 return VERR_HTTP_CURL_ERROR;
4033
4034 return VINF_SUCCESS;
4035}
4036
4037
4038RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
4039{
4040 CURLcode rcCurl;
4041
4042 PRTHTTPINTERNAL pThis = hHttp;
4043 RTHTTP_VALID_RETURN(pThis);
4044
4045 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
4046 if (CURL_FAILURE(rcCurl))
4047 return VERR_HTTP_CURL_ERROR;
4048
4049 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
4050 if (CURL_FAILURE(rcCurl))
4051 return VERR_HTTP_CURL_ERROR;
4052
4053 return VINF_SUCCESS;
4054}
4055
4056RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
4057{
4058 CURLcode rcCurl;
4059
4060 PRTHTTPINTERNAL pThis = hHttp;
4061 RTHTTP_VALID_RETURN(pThis);
4062
4063 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
4064 if (CURL_FAILURE(rcCurl))
4065 return VERR_HTTP_CURL_ERROR;
4066
4067 return VINF_SUCCESS;
4068}
4069
4070
4071RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
4072{
4073 CURLcode rcCurl;
4074
4075 PRTHTTPINTERNAL pThis = hHttp;
4076 RTHTTP_VALID_RETURN(pThis);
4077
4078 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
4079 if (CURL_FAILURE(rcCurl))
4080 return VERR_HTTP_CURL_ERROR;
4081
4082 return VINF_SUCCESS;
4083}
4084
4085
4086RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
4087{
4088 CURLcode rcCurl;
4089
4090 PRTHTTPINTERNAL pThis = hHttp;
4091 RTHTTP_VALID_RETURN(pThis);
4092
4093 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
4094 if (CURL_FAILURE(rcCurl))
4095 return VERR_HTTP_CURL_ERROR;
4096
4097 return VINF_SUCCESS;
4098}
4099
4100
4101RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
4102{
4103 CURLcode rcCurl;
4104
4105 PRTHTTPINTERNAL pThis = hHttp;
4106 RTHTTP_VALID_RETURN(pThis);
4107
4108 /*
4109 * XXX: Do this here for now as a stop-gap measure as
4110 * RTHttpReset() resets this (and proxy settings).
4111 */
4112 if (pThis->pszCaFile)
4113 {
4114 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile);
4115 if (CURL_FAILURE(rcCurl))
4116 return VERR_HTTP_CURL_ERROR;
4117 }
4118
4119 rcCurl = curl_easy_perform(pThis->pCurl);
4120 if (CURL_FAILURE(rcCurl))
4121 return VERR_HTTP_CURL_ERROR;
4122
4123 return VINF_SUCCESS;
4124}
4125
4126
4127RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
4128{
4129 CURLcode rcCurl;
4130
4131 PRTHTTPINTERNAL pThis = hHttp;
4132 RTHTTP_VALID_RETURN(pThis);
4133 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
4134
4135 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
4136 if (CURL_FAILURE(rcCurl))
4137 return VERR_HTTP_CURL_ERROR;
4138
4139 return VINF_SUCCESS;
4140}
4141
4142
4143RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
4144{
4145 CURLcode rcCurl;
4146
4147 PRTHTTPINTERNAL pThis = hHttp;
4148 RTHTTP_VALID_RETURN(pThis);
4149
4150 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
4151 if (CURL_FAILURE(rcCurl))
4152 return VERR_HTTP_CURL_ERROR;
4153
4154 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
4155 if (CURL_FAILURE(rcCurl))
4156 return VERR_HTTP_CURL_ERROR;
4157
4158 return VINF_SUCCESS;
4159}
4160
4161
4162RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4163{
4164 PRTHTTPINTERNAL pThis = hHttp;
4165 RTHTTP_VALID_RETURN(pThis);
4166
4167 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
4168 if (CURL_FAILURE(rcCurl))
4169 return VERR_HTTP_CURL_ERROR;
4170
4171 return VINF_SUCCESS;
4172}
4173
4174
4175RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4176{
4177 CURLcode rcCurl;
4178
4179 PRTHTTPINTERNAL pThis = hHttp;
4180 RTHTTP_VALID_RETURN(pThis);
4181
4182 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
4183 if (CURL_FAILURE(rcCurl))
4184 return VERR_HTTP_CURL_ERROR;
4185
4186 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
4187 if (CURL_FAILURE(rcCurl))
4188 return VERR_HTTP_CURL_ERROR;
4189
4190 return VINF_SUCCESS;
4191}
4192
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use