VirtualBox

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

Last change on this file since 84968 was 84509, checked in by vboxsync, 5 years ago

iprt/cdefs.h,*: Introducing RT_FLEXIBLE_ARRAY_EXTENSION as a g++ hack that allows us to use RT_FLEXIBLE_ARRAY without the compiler going all pendantic on us. Only tested with 10.1.0. bugref:9746

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