VirtualBox

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

Last change on this file since 85546 was 85162, checked in by vboxsync, 5 years ago

IPRT/http-curl.cpp: Deprecated function, missing RT_NOTHROW_DEF. bugref:9790 bugref:9794

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