VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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