VirtualBox

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

Last change on this file since 74202 was 74202, checked in by vboxsync, 6 years ago

IPRT/rest: More client request base tests. Added RTHttpGetHeaderCount and RTHttpGetByOrdinal for testing purposes. bugref:9167

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