VirtualBox

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

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

IPRT/http-curl: nits (some mine)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 143.5 KB
Line 
1/* $Id: http-curl.cpp 102589 2023-12-12 17:35:32Z 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 AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER);
2083 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
2084
2085 /*
2086 * Update the settings.
2087 *
2088 * Currently, we don't make alot of effort parsing or checking the input, we
2089 * leave that to cURL. (A bit afraid of breaking user settings.)
2090 */
2091 pThis->fUseSystemProxySettings = false;
2092 return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd);
2093}
2094
2095
2096
2097/*********************************************************************************************************************************
2098* HTTP Headers *
2099*********************************************************************************************************************************/
2100
2101/**
2102 * Helper for RTHttpSetHeaders and RTHttpAddRawHeader that unsets the user agent
2103 * if it is now in one of the headers.
2104 */
2105static int rtHttpUpdateUserAgentHeader(PRTHTTPINTERNAL pThis, PRTHTTPHEADER pNewHdr)
2106{
2107 static const char s_szUserAgent[] = "User-Agent";
2108 if ( pNewHdr->cchName == sizeof(s_szUserAgent) - 1
2109 && RTStrNICmpAscii(pNewHdr->szData, RT_STR_TUPLE(s_szUserAgent)) == 0)
2110 {
2111 pThis->fHaveUserAgentHeader = true;
2112 if (pThis->fHaveSetUserAgent)
2113 {
2114 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL);
2115 Assert(CURL_SUCCESS(rcCurl)); NOREF(rcCurl);
2116 pThis->fHaveSetUserAgent = false;
2117 }
2118 }
2119 return VINF_SUCCESS;
2120}
2121
2122
2123/**
2124 * Free the headers associated with the instance (w/o telling cURL about it).
2125 *
2126 * @param pThis The HTTP client instance.
2127 */
2128static void rtHttpFreeHeaders(PRTHTTPINTERNAL pThis)
2129{
2130 struct curl_slist *pHead = pThis->pHeaders;
2131 pThis->pHeaders = NULL;
2132 pThis->ppHeadersTail = &pThis->pHeaders;
2133 pThis->fHaveUserAgentHeader = false;
2134
2135 while (pHead)
2136 {
2137 struct curl_slist *pFree = pHead;
2138 pHead = pHead->next;
2139 ASMCompilerBarrier(); /* paranoia */
2140
2141 pFree->next = NULL;
2142 pFree->data = NULL;
2143 RTMemFree(pFree);
2144 }
2145}
2146
2147
2148/**
2149 * Worker for RTHttpSetHeaders and RTHttpAddHeader.
2150 *
2151 * @returns IPRT status code.
2152 * @param pThis The HTTP client instance.
2153 * @param pchName The field name. Does not need to be terminated.
2154 * @param cchName The field name length.
2155 * @param pchValue The field value. Does not need to be terminated.
2156 * @param cchValue The field value length.
2157 * @param fFlags RTHTTPADDHDR_F_XXX.
2158 */
2159static int rtHttpAddHeaderWorker(PRTHTTPINTERNAL pThis, const char *pchName, size_t cchName,
2160 const char *pchValue, size_t cchValue, uint32_t fFlags)
2161{
2162 /*
2163 * Create the list entry.
2164 */
2165 size_t cbData = cchName + 2 + cchValue + 1;
2166 PRTHTTPHEADER pHdr = (PRTHTTPHEADER)RTMemAlloc(RT_UOFFSETOF_DYN(RTHTTPHEADER, szData[cbData]));
2167 if (pHdr)
2168 {
2169 pHdr->Core.next = NULL;
2170 pHdr->Core.data = pHdr->szData;
2171 pHdr->cchName = (uint32_t)cchName;
2172 pHdr->offValue = (uint32_t)(cchName + 2);
2173 char *psz = pHdr->szData;
2174 memcpy(psz, pchName, cchName);
2175 psz += cchName;
2176 *psz++ = ':';
2177 *psz++ = ' ';
2178 memcpy(psz, pchValue, cchValue);
2179 psz[cchValue] = '\0';
2180
2181 /*
2182 * Appending to an existing list requires no cURL interaction.
2183 */
2184 AssertCompile(RTHTTPADDHDR_F_FRONT != 0);
2185 if ( !(fFlags & RTHTTPADDHDR_F_FRONT)
2186 && pThis->pHeaders != NULL)
2187 {
2188 *pThis->ppHeadersTail = &pHdr->Core;
2189 pThis->ppHeadersTail = &pHdr->Core.next;
2190 return rtHttpUpdateUserAgentHeader(pThis, pHdr);
2191 }
2192
2193 /*
2194 * When prepending or adding the first header we need to inform cURL
2195 * about the new list head.
2196 */
2197 pHdr->Core.next = pThis->pHeaders;
2198 if (!pThis->pHeaders)
2199 pThis->ppHeadersTail = &pHdr->Core.next;
2200 pThis->pHeaders = &pHdr->Core;
2201
2202 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
2203 if (CURL_SUCCESS(rcCurl))
2204 return rtHttpUpdateUserAgentHeader(pThis, pHdr);
2205 return VERR_HTTP_CURL_ERROR;
2206 }
2207 return VERR_NO_MEMORY;
2208}
2209
2210
2211RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders)
2212{
2213 PRTHTTPINTERNAL pThis = hHttp;
2214 RTHTTP_VALID_RETURN(pThis);
2215
2216 /*
2217 * Drop old headers and reset state.
2218 */
2219 if (pThis->pHeaders)
2220 {
2221 rtHttpFreeHeaders(pThis);
2222 curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL);
2223 }
2224 pThis->ppHeadersTail = &pThis->pHeaders;
2225 pThis->fHaveUserAgentHeader = false;
2226
2227 /*
2228 * We're done if no headers specified.
2229 */
2230 if (!cHeaders)
2231 return VINF_SUCCESS;
2232
2233 /*
2234 * Add the headers, one by one.
2235 */
2236 int rc = VINF_SUCCESS;
2237 for (size_t i = 0; i < cHeaders; i++)
2238 {
2239 const char *pszHeader = papszHeaders[i];
2240 size_t cchHeader = strlen(pszHeader);
2241 size_t cchName = (const char *)memchr(pszHeader, ':', cchHeader) - pszHeader;
2242 AssertBreakStmt(cchName < cchHeader, rc = VERR_INVALID_PARAMETER);
2243 size_t offValue = RT_C_IS_BLANK(pszHeader[cchName + 1]) ? cchName + 2 : cchName + 1;
2244 rc = rtHttpAddHeaderWorker(pThis, pszHeader, cchName, &pszHeader[offValue], cchHeader - offValue, RTHTTPADDHDR_F_BACK);
2245 AssertRCBreak(rc);
2246 }
2247 if (RT_SUCCESS(rc))
2248 return rc;
2249 rtHttpFreeHeaders(pThis);
2250 curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL);
2251 return rc;
2252}
2253
2254
2255#if 0 /** @todo reimplement RTHttpAddRawHeader if ever actually needed. */
2256RTR3DECL(int) RTHttpAddRawHeader(RTHTTP hHttp, const char *pszHeader, uint32_t fFlags)
2257{
2258 PRTHTTPINTERNAL pThis = hHttp;
2259 RTHTTP_VALID_RETURN(pThis);
2260 AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS);
2261/** @todo implement RTHTTPADDHDR_F_FRONT */
2262
2263 /*
2264 * Append it to the header list, checking for User-Agent and such.
2265 */
2266 struct curl_slist *pHeaders = pThis->pHeaders;
2267 struct curl_slist *pNewHeaders = curl_slist_append(pHeaders, pszHeader);
2268 if (pNewHeaders)
2269 pHeaders = pNewHeaders;
2270 else
2271 return VERR_NO_MEMORY;
2272
2273 if (strncmp(pszHeader, RT_STR_TUPLE("User-Agent:")) == 0)
2274 pThis->fHaveUserAgentHeader = true;
2275
2276 /*
2277 * If this is the first header, we need to tell curl.
2278 */
2279 if (!pThis->pHeaders)
2280 {
2281 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders);
2282 if (CURL_FAILURE(rcCurl))
2283 {
2284 curl_slist_free_all(pHeaders);
2285 return VERR_INVALID_PARAMETER;
2286 }
2287 pThis->pHeaders = pHeaders;
2288 }
2289 else
2290 Assert(pThis->pHeaders == pHeaders);
2291
2292 rtHttpUpdateUserAgentHeader(pThis);
2293
2294 return VINF_SUCCESS;
2295}
2296#endif
2297
2298
2299RTR3DECL(int) RTHttpAddHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, size_t cchValue, uint32_t fFlags)
2300{
2301 /*
2302 * Validate input and calc string lengths.
2303 */
2304 PRTHTTPINTERNAL pThis = hHttp;
2305 RTHTTP_VALID_RETURN(pThis);
2306 AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS);
2307 AssertPtr(pszField);
2308 size_t const cchField = strlen(pszField);
2309 AssertReturn(cchField > 0, VERR_INVALID_PARAMETER);
2310 AssertReturn(pszField[cchField - 1] != ':', VERR_INVALID_PARAMETER);
2311 AssertReturn(!RT_C_IS_SPACE(pszField[cchField - 1]), VERR_INVALID_PARAMETER);
2312#ifdef RT_STRICT
2313 for (size_t i = 0; i < cchField; i++)
2314 {
2315 char const ch = pszField[i];
2316 Assert(RT_C_IS_PRINT(ch) && ch != ':');
2317 }
2318#endif
2319
2320 AssertPtr(pszValue);
2321 if (cchValue == RTSTR_MAX)
2322 cchValue = strlen(pszValue);
2323
2324 /*
2325 * Just pass it along to the worker.
2326 */
2327 return rtHttpAddHeaderWorker(pThis, pszField, cchField, pszValue, cchValue, fFlags);
2328}
2329
2330
2331RTR3DECL(const char *) RTHttpGetHeader(RTHTTP hHttp, const char *pszField, size_t cchField)
2332{
2333 PRTHTTPINTERNAL pThis = hHttp;
2334 RTHTTP_VALID_RETURN_RC(pThis, NULL);
2335
2336 PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders;
2337 if (pCur)
2338 {
2339 if (cchField == RTSTR_MAX)
2340 cchField = strlen(pszField);
2341 do
2342 {
2343 if ( pCur->cchName == cchField
2344 && RTStrNICmpAscii(pCur->szData, pszField, cchField) == 0)
2345 return &pCur->szData[pCur->offValue];
2346
2347 /* next field. */
2348 pCur = (PRTHTTPHEADER)pCur->Core.next;
2349 } while (pCur);
2350 }
2351 return NULL;
2352}
2353
2354
2355RTR3DECL(size_t) RTHttpGetHeaderCount(RTHTTP hHttp)
2356{
2357 PRTHTTPINTERNAL pThis = hHttp;
2358 RTHTTP_VALID_RETURN_RC(pThis, 0);
2359
2360 /* Note! Only for test cases and debugging, so we don't care about performance. */
2361 size_t cHeaders = 0;
2362 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next)
2363 cHeaders++;
2364 return cHeaders;
2365}
2366
2367
2368RTR3DECL(const char *) RTHttpGetByOrdinal(RTHTTP hHttp, size_t iOrdinal)
2369{
2370 PRTHTTPINTERNAL pThis = hHttp;
2371 RTHTTP_VALID_RETURN_RC(pThis, NULL);
2372
2373 /* Note! Only for test cases and debugging, so we don't care about performance. */
2374 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next)
2375 {
2376 if (iOrdinal == 0)
2377 return pCur->szData;
2378 iOrdinal--;
2379 }
2380
2381 return NULL;
2382}
2383
2384
2385
2386RTR3DECL(int) RTHttpSignHeaders(RTHTTP hHttp, RTHTTPMETHOD enmMethod, const char *pszUrl,
2387 RTCRKEY hKey, const char *pszKeyId, uint32_t fFlags)
2388{
2389 PRTHTTPINTERNAL pThis = hHttp;
2390 RTHTTP_VALID_RETURN(pThis);
2391 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
2392 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
2393 AssertReturn(!fFlags, VERR_INVALID_FLAGS);
2394 AssertPtrReturn(pszKeyId, VERR_INVALID_POINTER);
2395
2396 /*
2397 * Do a little bit of preprocessing while we can easily return without
2398 * needing clean anything up..
2399 */
2400 RTURIPARSED ParsedUrl;
2401 int rc = RTUriParse(pszUrl, &ParsedUrl);
2402 AssertRCReturn(rc, rc);
2403 const char * const pszPath = pszUrl + ParsedUrl.offPath;
2404
2405 const char *pszMethodSp = NULL;
2406 switch (enmMethod)
2407 {
2408 case RTHTTPMETHOD_GET: pszMethodSp = "get "; break;
2409 case RTHTTPMETHOD_PUT: pszMethodSp = "put "; break;
2410 case RTHTTPMETHOD_POST: pszMethodSp = "post "; break;
2411 case RTHTTPMETHOD_PATCH: pszMethodSp = "patch "; break;
2412 case RTHTTPMETHOD_DELETE: pszMethodSp = "delete "; break;
2413 case RTHTTPMETHOD_HEAD: pszMethodSp = "head "; break;
2414 case RTHTTPMETHOD_OPTIONS: pszMethodSp = "options "; break;
2415 case RTHTTPMETHOD_TRACE: pszMethodSp = "trace "; break;
2416#ifdef IPRT_HTTP_WITH_WEBDAV
2417 case RTHTTPMETHOD_PROPFIND: pszMethodSp = "propfind "; break;
2418#endif
2419 /* no default! */
2420 case RTHTTPMETHOD_INVALID:
2421 case RTHTTPMETHOD_END:
2422 case RTHTTPMETHOD_32BIT_HACK:
2423 break;
2424 }
2425 AssertReturn(pszMethodSp, VERR_INTERNAL_ERROR_4);
2426
2427 /*
2428 * We work the authorization header entry directly here to avoid extra copying and stuff.
2429 */
2430
2431 /* Estimate required string length first. */
2432 static const char s_szSuffixFmt[] = "Authorization: Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\"";
2433 static const char s_szInfix[] = "\",signature=\"";
2434 static const char s_szPostfix[] = "\"";
2435 static const char s_szRequestField[] = "(request-target)";
2436 size_t const cchKeyId = strlen(pszKeyId);
2437 size_t const cbSigRaw = (RTCrKeyGetBitCount(hKey) + 7) / 8;
2438 size_t const cbSigRawAligned = RT_ALIGN_Z(cbSigRaw, 8);
2439 size_t const cchSigStr = RTBase64EncodedLengthEx(cbSigRaw, RTBASE64_FLAGS_NO_LINE_BREAKS);
2440 size_t cbEstimated = sizeof(s_szSuffixFmt) + sizeof(s_szInfix) + sizeof(s_szPostfix)
2441 + cchKeyId + sizeof(s_szRequestField) + cchSigStr;
2442 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur; pCur = (PRTHTTPHEADER)pCur->Core.next)
2443 cbEstimated += pCur->cchName + 1;
2444 cbEstimated += 32; /* safetype fudge */
2445 /* Lazy bird: Put the raw signature at the end. */
2446 cbEstimated = RT_ALIGN_Z(cbEstimated, 8) + cbSigRawAligned;
2447
2448 /* Allocate and initialize header entry. */
2449 PRTHTTPHEADER const pHdr = (PRTHTTPHEADER)RTMemAllocZ(cbEstimated);
2450 AssertPtrReturn(pHdr, VERR_NO_MEMORY);
2451 uint8_t * const pbSigRaw = (uint8_t *)pHdr + cbEstimated - cbSigRawAligned;
2452
2453 pHdr->cchName = sizeof("Authorization") - 1;
2454 pHdr->offValue = sizeof("Authorization") + 1;
2455 pHdr->Core.next = NULL;
2456 pHdr->Core.data = pHdr->szData;
2457 char *pszLeft = pHdr->szData;
2458 size_t cbLeft = cbEstimated - RT_UOFFSETOF(RTHTTPHEADER, szData) - cbSigRawAligned;
2459
2460 size_t cch = RTStrPrintf(pszLeft, cbLeft, s_szSuffixFmt, pszKeyId);
2461 cbLeft -= cch;
2462 pszLeft += cch;
2463
2464 /*
2465 * Instantiate the digest.
2466 */
2467 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2468 rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
2469 if (RT_SUCCESS(rc))
2470 {
2471 /*
2472 * Add the request-target pseudo header first.
2473 */
2474 Assert(cbLeft > sizeof(s_szRequestField));
2475 memcpy(pszLeft, RT_STR_TUPLE(s_szRequestField));
2476 pszLeft += sizeof(s_szRequestField) - 1;
2477
2478 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(s_szRequestField));
2479 if (RT_SUCCESS(rc))
2480 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2481 if (RT_SUCCESS(rc))
2482 rc = RTCrDigestUpdate(hDigest, pszMethodSp, strlen(pszMethodSp));
2483 if (RT_SUCCESS(rc))
2484 rc = RTCrDigestUpdate(hDigest, pszPath, strlen(pszPath));
2485
2486 /*
2487 * Add the header fields.
2488 */
2489 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur && RT_SUCCESS(rc); pCur = (PRTHTTPHEADER)pCur->Core.next)
2490 {
2491 AssertBreakStmt(cbLeft > pCur->cchName, rc = VERR_INTERNAL_ERROR_3);
2492 *pszLeft++ = ' ';
2493 cbLeft--;
2494 memcpy(pszLeft, pCur->szData, pCur->cchName);
2495 pszLeft[pCur->cchName] = '\0';
2496 RTStrToLower(pszLeft);
2497
2498 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE("\n"));
2499 AssertRCBreak(rc);
2500 rc = RTCrDigestUpdate(hDigest, pszLeft, pCur->cchName);
2501 AssertRCBreak(rc);
2502 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2503 AssertRCBreak(rc);
2504 const char *pszValue = &pCur->szData[pCur->offValue];
2505 rc = RTCrDigestUpdate(hDigest, pszValue, strlen(pszValue));
2506 AssertRCBreak(rc);
2507
2508 pszLeft += pCur->cchName;
2509 cbLeft -= pCur->cchName;
2510 }
2511 if (RT_SUCCESS(rc))
2512 AssertStmt(cbLeft > sizeof(s_szInfix) + cchSigStr + sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2513 if (RT_SUCCESS(rc))
2514 {
2515 /* Complete the header field part. */
2516 memcpy(pszLeft, RT_STR_TUPLE(s_szInfix));
2517 pszLeft += sizeof(s_szInfix) - 1;
2518 cbLeft -= sizeof(s_szInfix) - 1;
2519
2520 /*
2521 * Sign the digest.
2522 */
2523 RTCRPKIXSIGNATURE hSigner;
2524 rc = RTCrPkixSignatureCreateByObjIdString(&hSigner, RTCR_PKCS1_SHA256_WITH_RSA_OID, hKey, NULL, true /*fSigning*/);
2525 AssertRC(rc);
2526 if (RT_SUCCESS(rc))
2527 {
2528 size_t cbActual = cbSigRawAligned;
2529 rc = RTCrPkixSignatureSign(hSigner, hDigest, pbSigRaw, &cbActual);
2530 AssertRC(rc);
2531 if (RT_SUCCESS(rc))
2532 {
2533 Assert(cbActual == cbSigRaw);
2534 RTCrPkixSignatureRelease(hSigner);
2535 hSigner = NIL_RTCRPKIXSIGNATURE;
2536 RTCrDigestRelease(hDigest);
2537 hDigest = NIL_RTCRDIGEST;
2538
2539 /*
2540 * Convert the signature to Base64 and append it to the string.
2541 */
2542 size_t cchActual;
2543 rc = RTBase64EncodeEx(pbSigRaw, cbActual, RTBASE64_FLAGS_NO_LINE_BREAKS, pszLeft, cbLeft, &cchActual);
2544 AssertRC(rc);
2545 if (RT_SUCCESS(rc))
2546 {
2547 Assert(cchActual == cchSigStr);
2548 pszLeft += cchActual;
2549 cbLeft -= cchActual;
2550
2551 /*
2552 * Append the postfix and add the header to the front of the list.
2553 */
2554 AssertStmt(cbLeft >= sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2555 if (RT_SUCCESS(rc))
2556 {
2557 memcpy(pszLeft, s_szPostfix, sizeof(s_szPostfix));
2558
2559 pHdr->Core.next = pThis->pHeaders;
2560 if (!pThis->pHeaders)
2561 pThis->ppHeadersTail = &pHdr->Core.next;
2562 pThis->pHeaders = &pHdr->Core;
2563
2564 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
2565 if (CURL_SUCCESS(rcCurl))
2566 return VINF_SUCCESS;
2567 rc = VERR_HTTP_CURL_ERROR;
2568 }
2569 }
2570 }
2571 RTCrPkixSignatureRelease(hSigner);
2572 }
2573 }
2574 RTCrDigestRelease(hDigest);
2575 }
2576 RTMemFree(pHdr);
2577 return rc;
2578}
2579
2580
2581/*********************************************************************************************************************************
2582* HTTPS and root certficates *
2583*********************************************************************************************************************************/
2584
2585/**
2586 * Set the CA file to NULL, deleting any temporary file if necessary.
2587 *
2588 * @param pThis The HTTP/HTTPS client instance.
2589 */
2590static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
2591{
2592 if (pThis->pszCaFile)
2593 {
2594 if (pThis->fDeleteCaFile)
2595 {
2596 int rc2 = RTFileDelete(pThis->pszCaFile); RT_NOREF_PV(rc2);
2597 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
2598 }
2599 RTStrFree(pThis->pszCaFile);
2600 pThis->pszCaFile = NULL;
2601 }
2602}
2603
2604
2605RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
2606{
2607 PRTHTTPINTERNAL pThis = hHttp;
2608 RTHTTP_VALID_RETURN(pThis);
2609
2610 rtHttpUnsetCaFile(pThis);
2611
2612 pThis->fDeleteCaFile = false;
2613 if (pszCaFile)
2614 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
2615 return VINF_SUCCESS;
2616}
2617
2618
2619RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
2620{
2621 PRTHTTPINTERNAL pThis = hHttp;
2622 RTHTTP_VALID_RETURN(pThis);
2623
2624 /*
2625 * Create a temporary file.
2626 */
2627 int rc = VERR_NO_STR_MEMORY;
2628 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
2629 if (pszCaFile)
2630 {
2631 RTFILE hFile;
2632 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
2633 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
2634 if (RT_SUCCESS(rc))
2635 {
2636 /*
2637 * Gather certificates into a temporary store and export them to the temporary file.
2638 */
2639 RTCRSTORE hStore;
2640 rc = RTCrStoreCreateInMem(&hStore, 256);
2641 if (RT_SUCCESS(rc))
2642 {
2643 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
2644 if (RT_SUCCESS(rc))
2645 /** @todo Consider adding an API for exporting to a RTFILE... */
2646 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2647 RTCrStoreRelease(hStore);
2648 }
2649 RTFileClose(hFile);
2650 if (RT_SUCCESS(rc))
2651 {
2652 /*
2653 * Set the CA file for the instance.
2654 */
2655 rtHttpUnsetCaFile(pThis);
2656
2657 pThis->fDeleteCaFile = true;
2658 pThis->pszCaFile = pszCaFile;
2659 return VINF_SUCCESS;
2660 }
2661
2662 int rc2 = RTFileDelete(pszCaFile);
2663 AssertRC(rc2);
2664 }
2665 else
2666 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
2667
2668 RTStrFree(pszCaFile);
2669 }
2670 return rc;
2671}
2672
2673
2674RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
2675{
2676 uint32_t const cBefore = RTCrStoreCertCount(hStore);
2677 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
2678 RT_NOREF_PV(fFlags);
2679
2680 /*
2681 * Add the user store, quitely ignoring any errors.
2682 */
2683 RTCRSTORE hSrcStore;
2684 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2685 if (RT_SUCCESS(rcUser))
2686 {
2687 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2688 hSrcStore);
2689 RTCrStoreRelease(hSrcStore);
2690 }
2691
2692 /*
2693 * Ditto for the system store.
2694 */
2695 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2696 if (RT_SUCCESS(rcSystem))
2697 {
2698 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2699 hSrcStore);
2700 RTCrStoreRelease(hSrcStore);
2701 }
2702
2703 /*
2704 * If the number of certificates increased, we consider it a success.
2705 */
2706 if (RTCrStoreCertCount(hStore) > cBefore)
2707 {
2708 if (RT_FAILURE(rcSystem))
2709 return -rcSystem;
2710 if (RT_FAILURE(rcUser))
2711 return -rcUser;
2712 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
2713 }
2714
2715 if (RT_FAILURE(rcSystem))
2716 return rcSystem;
2717 if (RT_FAILURE(rcUser))
2718 return rcUser;
2719 return VERR_NOT_FOUND;
2720}
2721
2722
2723RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
2724{
2725 RTCRSTORE hStore;
2726 int rc = RTCrStoreCreateInMem(&hStore, 256);
2727 if (RT_SUCCESS(rc))
2728 {
2729 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2730 if (RT_SUCCESS(rc))
2731 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2732 RTCrStoreRelease(hStore);
2733 }
2734 return rc;
2735}
2736
2737
2738RTR3DECL(bool) RTHttpGetVerifyPeer(RTHTTP hHttp)
2739{
2740 PRTHTTPINTERNAL pThis = hHttp;
2741 RTHTTP_VALID_RETURN_RC(pThis, false);
2742 return pThis->fVerifyPeer;
2743}
2744
2745
2746RTR3DECL(int) RTHttpSetVerifyPeer(RTHTTP hHttp, bool fVerify)
2747{
2748 PRTHTTPINTERNAL pThis = hHttp;
2749 RTHTTP_VALID_RETURN(pThis);
2750 AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER);
2751
2752 if (pThis->fVerifyPeer != fVerify)
2753 {
2754 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSL_VERIFYPEER, (long)fVerify);
2755 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_SSL_VERIFYPEER=%RTbool: %d (%#x)\n", fVerify, rcCurl, rcCurl),
2756 VERR_HTTP_CURL_ERROR);
2757 pThis->fVerifyPeer = fVerify;
2758 }
2759
2760 return VINF_SUCCESS;
2761}
2762
2763
2764
2765/*********************************************************************************************************************************
2766* .......
2767*********************************************************************************************************************************/
2768
2769
2770/**
2771 * Figures out the IPRT status code for a GET.
2772 *
2773 * @returns IPRT status code.
2774 * @param pThis The HTTP/HTTPS client instance.
2775 * @param rcCurl What curl returned.
2776 * @param puHttpStatus Where to optionally return the HTTP status. If specified,
2777 * the HTTP statuses are not translated to IPRT status codes.
2778 */
2779static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, CURLcode rcCurl, uint32_t *puHttpStatus)
2780{
2781 int rc = VERR_HTTP_CURL_ERROR;
2782
2783 if (pThis->pszRedirLocation)
2784 {
2785 RTStrFree(pThis->pszRedirLocation);
2786 pThis->pszRedirLocation = NULL;
2787 }
2788 if (CURL_SUCCESS(rcCurl))
2789 {
2790 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2791 if (puHttpStatus)
2792 {
2793 *puHttpStatus = pThis->lLastResp;
2794 rc = VINF_SUCCESS;
2795 }
2796
2797 switch (pThis->lLastResp)
2798 {
2799 case 200:
2800 /* OK, request was fulfilled */
2801 case 204:
2802 /* empty response */
2803 rc = VINF_SUCCESS;
2804 break;
2805 case 301: /* Moved permantently. */
2806 case 302: /* Found / Moved temporarily. */
2807 case 303: /* See Other. */
2808 case 307: /* Temporary redirect. */
2809 case 308: /* Permanent redirect. */
2810 {
2811 const char *pszRedirect = NULL;
2812 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2813 size_t cb = pszRedirect ? strlen(pszRedirect) : 0;
2814 if (cb > 0 && cb < 2048)
2815 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2816 if (!puHttpStatus)
2817 rc = VERR_HTTP_REDIRECTED;
2818 break;
2819 }
2820 case 400:
2821 /* bad request */
2822 if (!puHttpStatus)
2823 rc = VERR_HTTP_BAD_REQUEST;
2824 break;
2825 case 403:
2826 /* forbidden, authorization will not help */
2827 if (!puHttpStatus)
2828 rc = VERR_HTTP_ACCESS_DENIED;
2829 break;
2830 case 404:
2831 /* URL not found */
2832 if (!puHttpStatus)
2833 rc = VERR_HTTP_NOT_FOUND;
2834 break;
2835 }
2836
2837 if (pThis->pszRedirLocation)
2838 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2839 else
2840 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2841 }
2842 else
2843 {
2844 switch (rcCurl)
2845 {
2846 case CURLE_URL_MALFORMAT:
2847 case CURLE_COULDNT_RESOLVE_HOST:
2848 rc = VERR_HTTP_HOST_NOT_FOUND;
2849 break;
2850 case CURLE_COULDNT_CONNECT:
2851 rc = VERR_HTTP_COULDNT_CONNECT;
2852 break;
2853 case CURLE_SSL_CONNECT_ERROR:
2854 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2855 break;
2856 case CURLE_SSL_CACERT:
2857 /* The peer certificate cannot be authenticated with the CA certificates
2858 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2859 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2860 break;
2861 case CURLE_SSL_CACERT_BADFILE:
2862 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2863 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2864 break;
2865 case CURLE_ABORTED_BY_CALLBACK:
2866 /* forcefully aborted */
2867 rc = VERR_HTTP_ABORTED;
2868 break;
2869 case CURLE_COULDNT_RESOLVE_PROXY:
2870 rc = VERR_HTTP_PROXY_NOT_FOUND;
2871 break;
2872 case CURLE_WRITE_ERROR:
2873 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2874 break;
2875 //case CURLE_READ_ERROR
2876
2877 default:
2878 break;
2879 }
2880 Log(("%s: %Rrc: %u = %s%s%.*s\n",
2881 __FUNCTION__,
2882 rc, rcCurl, curl_easy_strerror((CURLcode)rcCurl),
2883 pThis->szErrorBuffer[0] != '\0' ? ": " : "",
2884 (int) sizeof(pThis->szErrorBuffer),
2885 pThis->szErrorBuffer[0] != '\0' ? pThis->szErrorBuffer : ""));
2886 }
2887
2888 return rc;
2889}
2890
2891
2892/**
2893 * cURL callback for reporting progress, we use it for checking for abort.
2894 */
2895static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded) RT_NOTHROW_DEF
2896{
2897 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2898 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2899 RT_NOREF_PV(rdTotalUpload);
2900 RT_NOREF_PV(rdUploaded);
2901
2902 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2903
2904 if (pThis->pfnDownloadProgress)
2905 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2906
2907 return pThis->fAbort ? 1 : 0;
2908}
2909
2910
2911/**
2912 * Whether we're likely to need SSL to handle the give URL.
2913 *
2914 * @returns true if we need, false if we probably don't.
2915 * @param pszUrl The URL.
2916 */
2917static bool rtHttpNeedSsl(const char *pszUrl)
2918{
2919 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2920}
2921
2922
2923/**
2924 * Applies recoded settings to the cURL instance before doing work.
2925 *
2926 * @returns IPRT status code.
2927 * @param pThis The HTTP/HTTPS client instance.
2928 * @param pszUrl The URL.
2929 */
2930static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2931{
2932 /*
2933 * The URL.
2934 */
2935 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2936 if (CURL_FAILURE(rcCurl))
2937 return VERR_INVALID_PARAMETER;
2938
2939 /*
2940 * Proxy config.
2941 */
2942 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2943 if (RT_FAILURE(rc))
2944 return rc;
2945
2946 /*
2947 * Setup SSL. Can be a bit of work.
2948 */
2949 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2950 if (CURL_FAILURE(rcCurl))
2951 return VERR_INVALID_PARAMETER;
2952
2953 const char *pszCaFile = pThis->pszCaFile;
2954 if ( !pszCaFile
2955 && rtHttpNeedSsl(pszUrl))
2956 {
2957 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2958 if (RT_SUCCESS(rc))
2959 pszCaFile = pThis->pszCaFile;
2960 else
2961 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2962 }
2963 if (pszCaFile)
2964 {
2965 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2966 if (CURL_FAILURE(rcCurl))
2967 return VERR_HTTP_CURL_ERROR;
2968 }
2969
2970 /*
2971 * Progress/abort.
2972 */
2973 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2974 if (CURL_FAILURE(rcCurl))
2975 return VERR_HTTP_CURL_ERROR;
2976 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2977 if (CURL_FAILURE(rcCurl))
2978 return VERR_HTTP_CURL_ERROR;
2979 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2980 if (CURL_FAILURE(rcCurl))
2981 return VERR_HTTP_CURL_ERROR;
2982
2983 /*
2984 * Set default user agent string if necessary. Some websites take offence
2985 * if we don't set it.
2986 */
2987 if ( !pThis->fHaveSetUserAgent
2988 && !pThis->fHaveUserAgentHeader)
2989 {
2990 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2991 if (CURL_FAILURE(rcCurl))
2992 return VERR_HTTP_CURL_ERROR;
2993 pThis->fHaveSetUserAgent = true;
2994 }
2995
2996 /*
2997 * Use GET by default.
2998 */
2999 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
3000 if (CURL_FAILURE(rcCurl))
3001 return VERR_HTTP_CURL_ERROR;
3002 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
3003 if (CURL_FAILURE(rcCurl))
3004 return VERR_HTTP_CURL_ERROR;
3005
3006 return VINF_SUCCESS;
3007}
3008
3009
3010/**
3011 * Resets state.
3012 *
3013 * @param pThis HTTP client instance.
3014 */
3015static void rtHttpResetState(PRTHTTPINTERNAL pThis)
3016{
3017 pThis->fAbort = false;
3018 pThis->rcOutput = VINF_SUCCESS;
3019 pThis->uDownloadHttpStatus = UINT32_MAX;
3020 pThis->cbDownloadContent = UINT64_MAX;
3021 pThis->offDownloadContent = 0;
3022 pThis->offUploadContent = 0;
3023 pThis->rcOutput = VINF_SUCCESS;
3024 pThis->cbDownloadHint = 0;
3025 Assert(pThis->BodyOutput.pHttp == pThis);
3026 Assert(pThis->HeadersOutput.pHttp == pThis);
3027}
3028
3029
3030/**
3031 * Tries to determin uDownloadHttpStatus and cbDownloadContent.
3032 *
3033 * @param pThis HTTP client instance.
3034 */
3035static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis)
3036{
3037 long lHttpStatus = 0;
3038 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus);
3039 pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus;
3040
3041#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
3042 curl_off_t cbContent = -1;
3043 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent);
3044 if (cbContent >= 0)
3045 pThis->cbDownloadContent = (uint64_t)cbContent;
3046#else
3047 double rdContent = -1.0;
3048 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent);
3049 if (rdContent >= 0.0)
3050 pThis->cbDownloadContent = (uint64_t)rdContent;
3051#endif
3052}
3053
3054
3055/**
3056 * Worker for rtHttpWriteHeaderData and rtHttpWriteBodyData.
3057 */
3058static size_t rtHttpWriteDataToMemOutput(PRTHTTPINTERNAL pThis, RTHTTPOUTPUTDATA *pOutput, char const *pchBuf, size_t cbToAppend)
3059{
3060 /*
3061 * Do max size and overflow checks.
3062 */
3063 size_t const cbCurSize = pOutput->uData.Mem.cb;
3064 size_t const cbNewSize = cbCurSize + cbToAppend;
3065 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3066 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
3067 {
3068 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
3069 {
3070 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend);
3071 pOutput->uData.Mem.cb = cbNewSize;
3072 pOutput->uData.Mem.pb[cbNewSize] = '\0';
3073 return cbToAppend;
3074 }
3075
3076 /*
3077 * We need to reallocate the output buffer.
3078 */
3079 /** @todo this could do with a better strategy wrt growth. */
3080 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
3081 if ( cbAlloc <= pThis->cbDownloadHint
3082 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
3083 && pOutput == &pThis->BodyOutput)
3084 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
3085
3086 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
3087 if (pbNew)
3088 {
3089 memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend);
3090 pbNew[cbNewSize] = '\0';
3091
3092 pOutput->uData.Mem.cbAllocated = cbAlloc;
3093 pOutput->uData.Mem.pb = pbNew;
3094 pOutput->uData.Mem.cb = cbNewSize;
3095 return cbToAppend;
3096 }
3097
3098 pThis->rcOutput = VERR_NO_MEMORY;
3099 }
3100 else
3101 pThis->rcOutput = VERR_TOO_MUCH_DATA;
3102
3103 /*
3104 * Failure - abort.
3105 */
3106 RTMemFree(pOutput->uData.Mem.pb);
3107 pOutput->uData.Mem.pb = NULL;
3108 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
3109 pThis->fAbort = true;
3110 return 0;
3111}
3112
3113
3114/**
3115 * cURL callback for writing body data.
3116 */
3117static size_t rtHttpWriteBodyData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3118{
3119 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3120 size_t const cbToAppend = cbUnit * cUnits;
3121
3122 /*
3123 * Check if this belongs to the body download callback.
3124 */
3125 if (pThis->pfnDownloadCallback)
3126 {
3127 if (pThis->offDownloadContent == 0)
3128 rtHttpGetDownloadStatusAndLength(pThis);
3129
3130 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3131 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3132 {
3133 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3134 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3135 if (RT_SUCCESS(rc))
3136 {
3137 pThis->offDownloadContent += cbToAppend;
3138 return cbToAppend;
3139 }
3140 if (RT_SUCCESS(pThis->rcOutput))
3141 pThis->rcOutput = rc;
3142 pThis->fAbort = true;
3143 return 0;
3144 }
3145 }
3146
3147 /*
3148 * Otherwise, copy to memory output buffer.
3149 */
3150 return rtHttpWriteDataToMemOutput(pThis, &pThis->BodyOutput, pchBuf, cbToAppend);
3151}
3152
3153
3154/**
3155 * cURL callback for writing header data.
3156 */
3157static size_t rtHttpWriteHeaderData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3158{
3159 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3160 size_t const cbToAppend = cbUnit * cUnits;
3161
3162 /*
3163 * Work the header callback, if one.
3164 * ASSUMES cURL is giving us one header at a time.
3165 */
3166 if (pThis->pfnHeaderCallback)
3167 {
3168 /*
3169 * Find the end of the field name first.
3170 */
3171 uint32_t uMatchWord;
3172 size_t cchField;
3173 const char *pchField = pchBuf;
3174 size_t cchValue;
3175 const char *pchValue = (const char *)memchr(pchBuf, ':', cbToAppend);
3176 if (pchValue)
3177 {
3178 cchField = pchValue - pchField;
3179 if (RT_LIKELY(cchField >= 3))
3180 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, RT_C_TO_LOWER(pchBuf[0]),
3181 RT_C_TO_LOWER(pchBuf[1]), RT_C_TO_LOWER(pchBuf[2]));
3182 else
3183 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField,
3184 cchField >= 1 ? RT_C_TO_LOWER(pchBuf[0]) : 0,
3185 cchField >= 2 ? RT_C_TO_LOWER(pchBuf[1]) : 0,
3186 0);
3187 pchValue++;
3188 cchValue = cbToAppend - cchField - 1;
3189 }
3190 /* Since cURL gives us the "HTTP/{version} {code} {status}" line too,
3191 we slap a fictitious field name ':http-status-line' in front of it. */
3192 else if (cbToAppend > 5 && pchBuf[0] == 'H' && pchBuf[1] == 'T' && pchBuf[2] == 'T' && pchBuf[3] == 'P' && pchBuf[4] == '/')
3193 {
3194 pchField = ":http-status-line";
3195 cchField = 17;
3196 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(17, ':', 'h', 't');
3197 pchValue = pchBuf;
3198 cchValue = cbToAppend;
3199 }
3200 /* cURL also gives us the empty line before the body, so we slap another
3201 fictitious field name ':end-of-headers' in front of it as well. */
3202 else if (cbToAppend == 2 && pchBuf[0] == '\r' && pchBuf[1] == '\n')
3203 {
3204 pchField = ":end-of-headers";
3205 cchField = 15;
3206 uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(15, ':', 'e', 'n');
3207 pchValue = pchBuf;
3208 cchValue = cbToAppend;
3209 }
3210 else
3211 AssertMsgFailedReturn(("pchBuf=%.*s\n", cbToAppend, pchBuf), cbToAppend);
3212
3213 /*
3214 * Determin the field value, stripping one leading blank and all
3215 * trailing spaces.
3216 */
3217 if (cchValue > 0 && RT_C_IS_BLANK(*pchValue))
3218 pchValue++, cchValue--;
3219 while (cchValue > 0 && RT_C_IS_SPACE(pchValue[cchValue - 1]))
3220 cchValue--;
3221
3222 /*
3223 * Pass it to the callback.
3224 */
3225 Log6(("rtHttpWriteHeaderData: %.*s: %.*s\n", cchField, pchBuf, cchValue, pchValue));
3226 int rc = pThis->pfnHeaderCallback(pThis, uMatchWord, pchBuf, cchField,
3227 pchValue, cchValue, pThis->pvHeaderCallbackUser);
3228 if (RT_SUCCESS(rc))
3229 return cbToAppend;
3230
3231 /* Abort on error. */
3232 if (RT_SUCCESS(pThis->rcOutput))
3233 pThis->rcOutput = rc;
3234 pThis->fAbort = true;
3235 return 0;
3236 }
3237
3238 return rtHttpWriteDataToMemOutput(pThis, &pThis->HeadersOutput, pchBuf, cbToAppend);
3239}
3240
3241
3242/**
3243 * cURL callback for working the upload callback.
3244 */
3245static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3246{
3247 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3248 size_t const cbBuf = cbUnit * cUnits;
3249
3250 /* Get download info the first time we're called. */
3251 if (pThis->offDownloadContent == 0)
3252 rtHttpGetDownloadStatusAndLength(pThis);
3253
3254 /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */
3255 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
3256 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
3257 {
3258 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
3259 pThis->cbDownloadContent, pThis->pvDownloadCallbackUser);
3260 if (RT_SUCCESS(rc))
3261 { /* likely */ }
3262 else
3263 {
3264 if (RT_SUCCESS(pThis->rcOutput))
3265 pThis->rcOutput = rc;
3266 pThis->fAbort = true;
3267 return 0;
3268 }
3269 }
3270 pThis->offDownloadContent += cbBuf;
3271 return cbBuf;
3272}
3273
3274
3275/**
3276 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
3277 */
3278static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3279{
3280 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3281 size_t const cbReq = cbUnit * cUnits;
3282 size_t const offMem = pThis->ReadData.Mem.offMem;
3283 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
3284 if (cbToCopy > cbReq)
3285 cbToCopy = cbReq;
3286 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
3287 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
3288 return cbToCopy;
3289}
3290
3291
3292/**
3293 * Callback feeding cURL data via the user upload callback.
3294 */
3295static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3296{
3297 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3298 size_t const cbReq = cbUnit * cUnits;
3299
3300 size_t cbActual = 0;
3301 int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser);
3302 if (RT_SUCCESS(rc))
3303 {
3304 pThis->offUploadContent += cbActual;
3305 return cbActual;
3306 }
3307
3308 if (RT_SUCCESS(pThis->rcOutput))
3309 pThis->rcOutput = rc;
3310 pThis->fAbort = true;
3311 return CURL_READFUNC_ABORT;
3312}
3313
3314
3315/**
3316 * Helper for installing a (body) write callback function.
3317 *
3318 * @returns cURL status code.
3319 * @param pThis The HTTP client instance.
3320 * @param pfnWrite The callback.
3321 * @param pvUser The callback user argument.
3322 */
3323static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3324{
3325 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
3326 if (CURL_SUCCESS(rcCurl))
3327 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
3328 return rcCurl;
3329}
3330
3331
3332/**
3333 * Helper for installing a header write callback function.
3334 *
3335 * @returns cURL status code.
3336 * @param pThis The HTTP client instance.
3337 * @param pfnWrite The callback.
3338 * @param pvUser The callback user argument.
3339 */
3340static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3341{
3342 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3343 if (CURL_SUCCESS(rcCurl))
3344 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3345 return rcCurl;
3346}
3347
3348
3349/**
3350 * Helper for installing a (body) read callback function.
3351 *
3352 * @returns cURL status code.
3353 * @param pThis The HTTP client instance.
3354 * @param pfnRead The callback.
3355 * @param pvUser The callback user argument.
3356 */
3357static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3358{
3359 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3360 if (CURL_SUCCESS(rcCurl))
3361 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3362 return rcCurl;
3363}
3364
3365
3366/**
3367 * Internal worker that performs a HTTP GET.
3368 *
3369 * @returns IPRT status code.
3370 * @param hHttp The HTTP/HTTPS client instance.
3371 * @param pszUrl The URL.
3372 * @param fNoBody Set to suppress the body.
3373 * @param ppvResponse Where to return the pointer to the allocated
3374 * response data (RTMemFree). There will always be
3375 * an zero terminator char after the response, that
3376 * is not part of the size returned via @a pcb.
3377 * @param pcb The size of the response data.
3378 *
3379 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
3380 * threads, because that will probably blow up!
3381 */
3382static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
3383{
3384 PRTHTTPINTERNAL pThis = hHttp;
3385 RTHTTP_VALID_RETURN(pThis);
3386
3387 /*
3388 * Reset the return values in case of more "GUI programming" on the client
3389 * side (i.e. a programming style not bothering checking return codes).
3390 */
3391 *ppvResponse = NULL;
3392 *pcb = 0;
3393
3394 /*
3395 * Set the busy flag (paranoia).
3396 */
3397 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3398 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3399
3400 /*
3401 * Reset the state and apply settings.
3402 */
3403 rtHttpResetState(pThis);
3404 int rc = rtHttpApplySettings(hHttp, pszUrl);
3405 if (RT_SUCCESS(rc))
3406 {
3407 RT_ZERO(pThis->BodyOutput.uData.Mem);
3408 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteBodyData, pThis);
3409 if (fNoBody)
3410 {
3411 if (CURL_SUCCESS(rcCurl))
3412 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3413 if (CURL_SUCCESS(rcCurl))
3414 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
3415 }
3416 if (CURL_SUCCESS(rcCurl))
3417 {
3418 /*
3419 * Perform the HTTP operation.
3420 */
3421 rcCurl = curl_easy_perform(pThis->pCurl);
3422 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3423 if (RT_SUCCESS(rc))
3424 rc = pThis->rcOutput;
3425 if (RT_SUCCESS(rc))
3426 {
3427 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
3428 *pcb = pThis->BodyOutput.uData.Mem.cb;
3429 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
3430 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3431 }
3432 else if (pThis->BodyOutput.uData.Mem.pb)
3433 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3434 RT_ZERO(pThis->BodyOutput.uData.Mem);
3435 }
3436 else
3437 rc = VERR_HTTP_CURL_ERROR;
3438 }
3439
3440 ASMAtomicWriteBool(&pThis->fBusy, false);
3441 return rc;
3442}
3443
3444
3445RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3446{
3447 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3448 uint8_t *pv;
3449 size_t cb;
3450 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
3451 if (RT_SUCCESS(rc))
3452 {
3453 if (pv) /* paranoia */
3454 *ppszNotUtf8 = (char *)pv;
3455 else
3456 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3457 }
3458 else
3459 *ppszNotUtf8 = NULL;
3460 return rc;
3461}
3462
3463
3464RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3465{
3466 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3467 uint8_t *pv;
3468 size_t cb;
3469 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
3470 if (RT_SUCCESS(rc))
3471 {
3472 if (pv) /* paranoia */
3473 *ppszNotUtf8 = (char *)pv;
3474 else
3475 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3476 }
3477 else
3478 *ppszNotUtf8 = NULL;
3479 return rc;
3480
3481}
3482
3483
3484RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
3485{
3486 RTMemFree(pszNotUtf8);
3487}
3488
3489
3490RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3491{
3492 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3493 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3494}
3495
3496
3497RTR3DECL(int) RTHttpGetHeaderBinary(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, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3501}
3502
3503
3504RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
3505{
3506 RTMemFree(pvResponse);
3507}
3508
3509
3510/**
3511 * cURL callback for writing data to a file.
3512 */
3513static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF
3514{
3515 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
3516 PRTHTTPINTERNAL pThis = pOutput->pHttp;
3517
3518 size_t cbWritten = 0;
3519 int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten);
3520 if (RT_SUCCESS(rc))
3521 return cbWritten;
3522
3523 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
3524 pThis->rcOutput = rc;
3525 return 0;
3526}
3527
3528
3529RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
3530{
3531 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
3532 PRTHTTPINTERNAL pThis = hHttp;
3533 RTHTTP_VALID_RETURN(pThis);
3534
3535 /*
3536 * Set the busy flag (paranoia).
3537 */
3538 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3539 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3540
3541 /*
3542 * Reset the state and apply settings.
3543 */
3544 rtHttpResetState(pThis);
3545 int rc = rtHttpApplySettings(hHttp, pszUrl);
3546 if (RT_SUCCESS(rc))
3547 {
3548 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3549 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
3550 if (CURL_SUCCESS(rcCurl))
3551 {
3552 /*
3553 * Open the output file.
3554 */
3555 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
3556 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
3557 if (RT_SUCCESS(rc))
3558 {
3559 /*
3560 * Perform the HTTP operation.
3561 */
3562 rcCurl = curl_easy_perform(pThis->pCurl);
3563 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3564 if (RT_SUCCESS(rc))
3565 rc = pThis->rcOutput;
3566
3567 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
3568 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
3569 rc = rc2;
3570 }
3571 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3572 }
3573 else
3574 rc = VERR_HTTP_CURL_ERROR;
3575 }
3576
3577 ASMAtomicWriteBool(&pThis->fBusy, false);
3578 return rc;
3579}
3580
3581
3582RTR3DECL(int) RTHttpQueryProxyInfoForUrl(RTHTTP hHttp, const char *pszUrl, PRTHTTPPROXYINFO pProxy)
3583{
3584 /*
3585 * Validate input and clear output.
3586 */
3587 Log(("RTHttpQueryProxyInfoForUrl: hHttp=%p pszUrl=%s pProxy=%s\n", hHttp, pszUrl, pProxy));
3588 RT_ZERO(*pProxy);
3589 pProxy->uProxyPort = UINT32_MAX;
3590
3591 PRTHTTPINTERNAL pThis = hHttp;
3592 RTHTTP_VALID_RETURN(pThis);
3593
3594 /*
3595 * Set up the proxy for the URL.
3596 */
3597 rtHttpResetState(pThis);
3598 /** @todo this does a bit too much (we don't need to set up SSL for instance). */
3599 int rc = rtHttpApplySettings(pThis, pszUrl);
3600 if (RT_SUCCESS(rc))
3601 {
3602 /*
3603 * Copy out the result.
3604 */
3605 if (pThis->fNoProxy)
3606 pProxy->enmProxyType = RTHTTPPROXYTYPE_NOPROXY;
3607 else
3608 {
3609 switch (pThis->enmProxyType)
3610 {
3611 case CURLPROXY_HTTP:
3612#ifdef CURL_AT_LEAST_VERSION
3613# if CURL_AT_LEAST_VERSION(7,19,4)
3614 case CURLPROXY_HTTP_1_0:
3615# endif
3616#endif
3617 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTP;
3618 break;
3619#ifdef CURL_AT_LEAST_VERSION
3620# if CURL_AT_LEAST_VERSION(7,52,0)
3621 case CURLPROXY_HTTPS:
3622 pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTPS;
3623 break;
3624# endif
3625#endif
3626 case CURLPROXY_SOCKS4:
3627 case CURLPROXY_SOCKS4A:
3628 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS4;
3629 break;
3630 case CURLPROXY_SOCKS5:
3631 case CURLPROXY_SOCKS5_HOSTNAME:
3632 pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS5;
3633 break;
3634 default:
3635 AssertFailed();
3636 pProxy->enmProxyType = RTHTTPPROXYTYPE_UNKNOWN;
3637 break;
3638 }
3639 pProxy->uProxyPort = pThis->uProxyPort;
3640 if (pThis->pszProxyHost != NULL)
3641 {
3642 rc = RTStrDupEx(&pProxy->pszProxyHost, pThis->pszProxyHost);
3643 if (pThis->pszProxyUsername && RT_SUCCESS(rc))
3644 rc = RTStrDupEx(&pProxy->pszProxyUsername, pThis->pszProxyUsername);
3645 if (pThis->pszProxyPassword && RT_SUCCESS(rc))
3646 rc = RTStrDupEx(&pProxy->pszProxyPassword, pThis->pszProxyPassword);
3647 if (RT_FAILURE(rc))
3648 RTHttpFreeProxyInfo(pProxy);
3649 }
3650 else
3651 {
3652 AssertFailed();
3653 rc = VERR_INTERNAL_ERROR;
3654 }
3655 }
3656 }
3657 return rc;
3658}
3659
3660
3661RTR3DECL(int) RTHttpFreeProxyInfo(PRTHTTPPROXYINFO pProxy)
3662{
3663 if (pProxy)
3664 {
3665 RTStrFree(pProxy->pszProxyHost);
3666 RTStrFree(pProxy->pszProxyUsername);
3667 RTStrFree(pProxy->pszProxyPassword);
3668 pProxy->pszProxyHost = NULL;
3669 pProxy->pszProxyUsername = NULL;
3670 pProxy->pszProxyPassword = NULL;
3671 pProxy->enmProxyType = RTHTTPPROXYTYPE_INVALID;
3672 pProxy->uProxyPort = UINT32_MAX;
3673 }
3674 return VINF_SUCCESS;
3675}
3676
3677
3678RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
3679 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
3680{
3681 /*
3682 * Set safe return values and validate input.
3683 */
3684 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
3685 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
3686
3687 if (ppvHeaders)
3688 *ppvHeaders = NULL;
3689 if (pcbHeaders)
3690 *pcbHeaders = 0;
3691 if (ppvBody)
3692 *ppvBody = NULL;
3693 if (pcbBody)
3694 *pcbBody = 0;
3695 if (puHttpStatus)
3696 *puHttpStatus = UINT32_MAX;
3697
3698 PRTHTTPINTERNAL pThis = hHttp;
3699 RTHTTP_VALID_RETURN(pThis);
3700 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
3701 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
3702
3703#ifdef LOG_ENABLED
3704 if (LogIs4Enabled() && pThis->pHeaders)
3705 {
3706 Log4(("RTHttpPerform: headers:\n"));
3707 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
3708 Log4(("%s\n", pCur->data));
3709 }
3710 if (pvReqBody && cbReqBody)
3711 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
3712#endif
3713
3714 /*
3715 * Set the busy flag (paranoia).
3716 */
3717 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3718 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3719
3720 /*
3721 * Reset the state and apply settings.
3722 */
3723 rtHttpResetState(pThis);
3724 int rc = rtHttpApplySettings(hHttp, pszUrl);
3725 if (RT_SUCCESS(rc))
3726 {
3727 /* Set the HTTP method. */
3728 CURLcode rcCurl = CURLE_BAD_FUNCTION_ARGUMENT;
3729 switch (enmMethod)
3730 {
3731 case RTHTTPMETHOD_GET:
3732 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3733 break;
3734 case RTHTTPMETHOD_PUT:
3735 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3736 break;
3737 case RTHTTPMETHOD_POST:
3738 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3739 break;
3740 case RTHTTPMETHOD_PATCH:
3741 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
3742 break;
3743 case RTHTTPMETHOD_DELETE:
3744 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
3745 break;
3746 case RTHTTPMETHOD_HEAD:
3747 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3748 if (CURL_SUCCESS(rcCurl))
3749 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3750 break;
3751 case RTHTTPMETHOD_OPTIONS:
3752 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
3753 break;
3754 case RTHTTPMETHOD_TRACE:
3755 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
3756 break;
3757#ifdef IPRT_HTTP_WITH_WEBDAV
3758 case RTHTTPMETHOD_PROPFIND:
3759 RT_FALL_THROUGH();
3760#endif
3761 case RTHTTPMETHOD_END:
3762 case RTHTTPMETHOD_INVALID:
3763 case RTHTTPMETHOD_32BIT_HACK:
3764 AssertFailed();
3765 }
3766
3767 /* Request body. POST requests should always have a body. */
3768 if ( pvReqBody
3769 && CURL_SUCCESS(rcCurl)
3770 && ( cbReqBody > 0
3771 || enmMethod == RTHTTPMETHOD_POST) )
3772 {
3773 if (enmMethod == RTHTTPMETHOD_POST)
3774 {
3775 /** @todo ??? */
3776 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
3777 if (CURL_SUCCESS(rcCurl))
3778 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
3779 }
3780 else
3781 {
3782 pThis->ReadData.Mem.pvMem = pvReqBody;
3783 pThis->ReadData.Mem.cbMem = cbReqBody;
3784 pThis->ReadData.Mem.offMem = 0;
3785 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis);
3786 /* curl will use chunked transfer is it doesn't know the body size */
3787 if (enmMethod == RTHTTPMETHOD_PUT && CURL_SUCCESS(rcCurl))
3788 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbReqBody);
3789 }
3790 }
3791 else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl))
3792 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis);
3793
3794 /* Headers. */
3795 if (CURL_SUCCESS(rcCurl))
3796 {
3797 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3798 rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteHeaderData, pThis);
3799 }
3800
3801 /* Body */
3802 if (ppvBody && CURL_SUCCESS(rcCurl))
3803 {
3804 RT_ZERO(pThis->BodyOutput.uData.Mem);
3805 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteBodyData, pThis);
3806 }
3807 else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl))
3808 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis);
3809
3810 if (CURL_SUCCESS(rcCurl))
3811 {
3812 /*
3813 * Perform the HTTP operation.
3814 */
3815 rcCurl = curl_easy_perform(pThis->pCurl);
3816 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
3817 if (RT_SUCCESS(rc))
3818 rc = pThis->rcOutput;
3819 if (RT_SUCCESS(rc))
3820 {
3821 if (ppvHeaders)
3822 {
3823 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
3824 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
3825 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
3826 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
3827 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
3828 pThis->HeadersOutput.uData.Mem.pb = NULL;
3829 }
3830 if (ppvBody)
3831 {
3832 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
3833 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3834 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3835 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3836 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3837 pThis->BodyOutput.uData.Mem.pb = NULL;
3838 }
3839 }
3840 }
3841 else
3842 rc = VERR_HTTP_CURL_ERROR;
3843
3844 /* Ensure we've freed all unused output and dropped references to input memory.*/
3845 if (pThis->HeadersOutput.uData.Mem.pb)
3846 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3847 if (pThis->BodyOutput.uData.Mem.pb)
3848 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3849 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3850 RT_ZERO(pThis->BodyOutput.uData.Mem);
3851 RT_ZERO(pThis->ReadData);
3852 }
3853
3854 ASMAtomicWriteBool(&pThis->fBusy, false);
3855 return rc;
3856}
3857
3858
3859/*********************************************************************************************************************************
3860* Callback APIs. *
3861*********************************************************************************************************************************/
3862
3863RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser)
3864{
3865 PRTHTTPINTERNAL pThis = hHttp;
3866 RTHTTP_VALID_RETURN(pThis);
3867
3868 pThis->pfnUploadCallback = pfnCallback;
3869 pThis->pvUploadCallbackUser = pvUser;
3870 pThis->cbUploadContent = cbContent;
3871 pThis->offUploadContent = 0;
3872
3873 if (cbContent != UINT64_MAX)
3874 {
3875 AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t));
3876 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent);
3877 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR);
3878 }
3879 return VINF_SUCCESS;
3880}
3881
3882
3883RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser)
3884{
3885 PRTHTTPINTERNAL pThis = hHttp;
3886 RTHTTP_VALID_RETURN(pThis);
3887 AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS);
3888
3889 pThis->pfnDownloadCallback = pfnCallback;
3890 pThis->pvDownloadCallbackUser = pvUser;
3891 pThis->fDownloadCallback = fFlags;
3892 pThis->uDownloadHttpStatus = UINT32_MAX;
3893 pThis->cbDownloadContent = UINT64_MAX;
3894 pThis->offDownloadContent = 0;
3895
3896 return VINF_SUCCESS;
3897}
3898
3899
3900RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser)
3901{
3902 PRTHTTPINTERNAL pThis = hHttp;
3903 RTHTTP_VALID_RETURN(pThis);
3904
3905 pThis->pfnDownloadProgress = pfnCallback;
3906 pThis->pvDownloadProgressUser = pvUser;
3907 return VINF_SUCCESS;
3908}
3909
3910
3911RTR3DECL(int) RTHttpSetHeaderCallback(RTHTTP hHttp, PFNRTHTTPHEADERCALLBACK pfnCallback, void *pvUser)
3912{
3913 PRTHTTPINTERNAL pThis = hHttp;
3914 RTHTTP_VALID_RETURN(pThis);
3915
3916 pThis->pfnHeaderCallback = pfnCallback;
3917 pThis->pvHeaderCallbackUser = pvUser;
3918 return VINF_SUCCESS;
3919}
3920
3921
3922/*********************************************************************************************************************************
3923* Temporary raw cURL stuff. Will be gone before 6.0 is out! *
3924*********************************************************************************************************************************/
3925
3926RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3927{
3928 CURLcode rcCurl;
3929
3930 PRTHTTPINTERNAL pThis = hHttp;
3931 RTHTTP_VALID_RETURN(pThis);
3932
3933 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
3934 if (RT_FAILURE(rc))
3935 return rc;
3936
3937 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3938 if (CURL_FAILURE(rcCurl))
3939 return VERR_HTTP_CURL_ERROR;
3940
3941 return VINF_SUCCESS;
3942}
3943
3944
3945RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3946{
3947 CURLcode rcCurl;
3948
3949 PRTHTTPINTERNAL pThis = hHttp;
3950 RTHTTP_VALID_RETURN(pThis);
3951
3952 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3953 if (CURL_FAILURE(rcCurl))
3954 return VERR_HTTP_CURL_ERROR;
3955
3956 return VINF_SUCCESS;
3957}
3958
3959
3960RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3961{
3962 CURLcode rcCurl;
3963
3964 PRTHTTPINTERNAL pThis = hHttp;
3965 RTHTTP_VALID_RETURN(pThis);
3966
3967 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3968 if (CURL_FAILURE(rcCurl))
3969 return VERR_HTTP_CURL_ERROR;
3970
3971 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3972 if (CURL_FAILURE(rcCurl))
3973 return VERR_HTTP_CURL_ERROR;
3974
3975 return VINF_SUCCESS;
3976}
3977
3978
3979RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3980{
3981 CURLcode rcCurl;
3982
3983 PRTHTTPINTERNAL pThis = hHttp;
3984 RTHTTP_VALID_RETURN(pThis);
3985
3986 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3987 if (CURL_FAILURE(rcCurl))
3988 return VERR_HTTP_CURL_ERROR;
3989
3990 return VINF_SUCCESS;
3991}
3992
3993
3994RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
3995{
3996 CURLcode rcCurl;
3997
3998 PRTHTTPINTERNAL pThis = hHttp;
3999 RTHTTP_VALID_RETURN(pThis);
4000
4001 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
4002 if (CURL_FAILURE(rcCurl))
4003 return VERR_HTTP_CURL_ERROR;
4004
4005 return VINF_SUCCESS;
4006}
4007
4008
4009RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
4010{
4011 /* curl doesn't provide an option for this */
4012 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
4013}
4014
4015
4016RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
4017{
4018 CURLcode rcCurl;
4019
4020 PRTHTTPINTERNAL pThis = hHttp;
4021 RTHTTP_VALID_RETURN(pThis);
4022
4023 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
4024 if (CURL_FAILURE(rcCurl))
4025 return VERR_HTTP_CURL_ERROR;
4026
4027 return VINF_SUCCESS;
4028}
4029
4030
4031RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
4032{
4033 CURLcode rcCurl;
4034
4035 PRTHTTPINTERNAL pThis = hHttp;
4036 RTHTTP_VALID_RETURN(pThis);
4037
4038 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
4039 if (CURL_FAILURE(rcCurl))
4040 return VERR_HTTP_CURL_ERROR;
4041
4042 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
4043 if (CURL_FAILURE(rcCurl))
4044 return VERR_HTTP_CURL_ERROR;
4045
4046 return VINF_SUCCESS;
4047}
4048
4049RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
4050{
4051 CURLcode rcCurl;
4052
4053 PRTHTTPINTERNAL pThis = hHttp;
4054 RTHTTP_VALID_RETURN(pThis);
4055
4056 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
4057 if (CURL_FAILURE(rcCurl))
4058 return VERR_HTTP_CURL_ERROR;
4059
4060 return VINF_SUCCESS;
4061}
4062
4063
4064RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
4065{
4066 CURLcode rcCurl;
4067
4068 PRTHTTPINTERNAL pThis = hHttp;
4069 RTHTTP_VALID_RETURN(pThis);
4070
4071 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
4072 if (CURL_FAILURE(rcCurl))
4073 return VERR_HTTP_CURL_ERROR;
4074
4075 return VINF_SUCCESS;
4076}
4077
4078
4079RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
4080{
4081 CURLcode rcCurl;
4082
4083 PRTHTTPINTERNAL pThis = hHttp;
4084 RTHTTP_VALID_RETURN(pThis);
4085
4086 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
4087 if (CURL_FAILURE(rcCurl))
4088 return VERR_HTTP_CURL_ERROR;
4089
4090 return VINF_SUCCESS;
4091}
4092
4093
4094RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
4095{
4096 CURLcode rcCurl;
4097
4098 PRTHTTPINTERNAL pThis = hHttp;
4099 RTHTTP_VALID_RETURN(pThis);
4100
4101 /*
4102 * XXX: Do this here for now as a stop-gap measure as
4103 * RTHttpReset() resets this (and proxy settings).
4104 */
4105 if (pThis->pszCaFile)
4106 {
4107 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile);
4108 if (CURL_FAILURE(rcCurl))
4109 return VERR_HTTP_CURL_ERROR;
4110 }
4111
4112 rcCurl = curl_easy_perform(pThis->pCurl);
4113 if (CURL_FAILURE(rcCurl))
4114 return VERR_HTTP_CURL_ERROR;
4115
4116 return VINF_SUCCESS;
4117}
4118
4119
4120RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
4121{
4122 CURLcode rcCurl;
4123
4124 PRTHTTPINTERNAL pThis = hHttp;
4125 RTHTTP_VALID_RETURN(pThis);
4126 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
4127
4128 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
4129 if (CURL_FAILURE(rcCurl))
4130 return VERR_HTTP_CURL_ERROR;
4131
4132 return VINF_SUCCESS;
4133}
4134
4135
4136RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
4137{
4138 CURLcode rcCurl;
4139
4140 PRTHTTPINTERNAL pThis = hHttp;
4141 RTHTTP_VALID_RETURN(pThis);
4142
4143 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
4144 if (CURL_FAILURE(rcCurl))
4145 return VERR_HTTP_CURL_ERROR;
4146
4147 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
4148 if (CURL_FAILURE(rcCurl))
4149 return VERR_HTTP_CURL_ERROR;
4150
4151 return VINF_SUCCESS;
4152}
4153
4154
4155RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4156{
4157 PRTHTTPINTERNAL pThis = hHttp;
4158 RTHTTP_VALID_RETURN(pThis);
4159
4160 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
4161 if (CURL_FAILURE(rcCurl))
4162 return VERR_HTTP_CURL_ERROR;
4163
4164 return VINF_SUCCESS;
4165}
4166
4167
4168RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
4169{
4170 CURLcode rcCurl;
4171
4172 PRTHTTPINTERNAL pThis = hHttp;
4173 RTHTTP_VALID_RETURN(pThis);
4174
4175 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
4176 if (CURL_FAILURE(rcCurl))
4177 return VERR_HTTP_CURL_ERROR;
4178
4179 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
4180 if (CURL_FAILURE(rcCurl))
4181 return VERR_HTTP_CURL_ERROR;
4182
4183 return VINF_SUCCESS;
4184}
4185
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette