VirtualBox

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

Last change on this file since 86697 was 86650, checked in by vboxsync, 4 years ago

Fixed bad commit. Hope this help.

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