VirtualBox

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

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

IPRT/rest: More work on binary downloads and uploads. bugref:9167

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 130.7 KB
Line 
1/* $Id: http-curl.cpp 74126 2018-09-06 18:29:40Z 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(int) RTHttpSignHeaders(RTHTTP hHttp, RTHTTPMETHOD enmMethod, const char *pszUrl,
2255 RTCRKEY hKey, const char *pszKeyId, uint32_t fFlags)
2256{
2257 PRTHTTPINTERNAL pThis = hHttp;
2258 RTHTTP_VALID_RETURN(pThis);
2259 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
2260 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
2261 AssertReturn(!fFlags, VERR_INVALID_FLAGS);
2262 AssertPtrReturn(pszKeyId, VERR_INVALID_POINTER);
2263
2264 /*
2265 * Do a little bit of preprocessing while we can easily return without
2266 * needing clean anything up..
2267 */
2268 RTURIPARSED ParsedUrl;
2269 int rc = RTUriParse(pszUrl, &ParsedUrl);
2270 AssertRCReturn(rc, rc);
2271 const char * const pszPath = pszUrl + ParsedUrl.offPath;
2272
2273 const char *pszMethodSp = NULL;
2274 switch (enmMethod)
2275 {
2276 case RTHTTPMETHOD_GET: pszMethodSp = "get "; break;
2277 case RTHTTPMETHOD_PUT: pszMethodSp = "put "; break;
2278 case RTHTTPMETHOD_POST: pszMethodSp = "post "; break;
2279 case RTHTTPMETHOD_PATCH: pszMethodSp = "patch "; break;
2280 case RTHTTPMETHOD_DELETE: pszMethodSp = "delete "; break;
2281 case RTHTTPMETHOD_HEAD: pszMethodSp = "head "; break;
2282 case RTHTTPMETHOD_OPTIONS: pszMethodSp = "options "; break;
2283 case RTHTTPMETHOD_TRACE: pszMethodSp = "trace "; break;
2284 /* no default! */
2285 case RTHTTPMETHOD_INVALID:
2286 case RTHTTPMETHOD_END:
2287 case RTHTTPMETHOD_32BIT_HACK:
2288 break;
2289 }
2290 AssertReturn(pszMethodSp, VERR_INTERNAL_ERROR_4);
2291
2292 /*
2293 * We work the authorization header entry directly here to avoid extra copying and stuff.
2294 */
2295
2296 /* Estimate required string length first. */
2297 static const char s_szSuffixFmt[] = "Authorization: Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\"";
2298 static const char s_szInfix[] = "\",signature=\"";
2299 static const char s_szPostfix[] = "\"";
2300 static const char s_szRequestField[] = "(request-target)";
2301 size_t const cchKeyId = strlen(pszKeyId);
2302 size_t const cbSigRaw = (RTCrKeyGetBitCount(hKey) + 7) / 8;
2303 size_t const cbSigRawAligned = RT_ALIGN_Z(cbSigRaw, 8);
2304 size_t const cchSigStr = RTBase64EncodedLengthEx(cbSigRaw, RTBASE64_FLAGS_NO_LINE_BREAKS);
2305 size_t cbEstimated = sizeof(s_szSuffixFmt) + sizeof(s_szInfix) + sizeof(s_szPostfix)
2306 + cchKeyId + sizeof(s_szRequestField) + cchSigStr;
2307 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur; pCur = (PRTHTTPHEADER)pCur->Core.next)
2308 cbEstimated += pCur->cchName + 1;
2309 cbEstimated += 32; /* safetype fudge */
2310 /* Lazy bird: Put the raw signature at the end. */
2311 cbEstimated = RT_ALIGN_Z(cbEstimated, 8) + cbSigRawAligned;
2312
2313 /* Allocate and initialize header entry. */
2314 PRTHTTPHEADER const pHdr = (PRTHTTPHEADER)RTMemAllocZ(cbEstimated);
2315 AssertPtrReturn(pHdr, VERR_NO_MEMORY);
2316 uint8_t * const pbSigRaw = (uint8_t *)pHdr + cbEstimated - cbSigRawAligned;
2317
2318 pHdr->cchName = sizeof("Authorization") - 1;
2319 pHdr->offValue = sizeof("Authorization") + 1;
2320 pHdr->Core.next = NULL;
2321 pHdr->Core.data = pHdr->szData;
2322 char *pszLeft = pHdr->szData;
2323 size_t cbLeft = cbEstimated - RT_UOFFSETOF(RTHTTPHEADER, szData) - cbSigRawAligned;
2324
2325 size_t cch = RTStrPrintf(pszLeft, cbLeft, s_szSuffixFmt, pszKeyId);
2326 cbLeft -= cch;
2327 pszLeft += cch;
2328
2329 /*
2330 * Instantiate the digest.
2331 */
2332 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2333 rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256);
2334 if (RT_SUCCESS(rc))
2335 {
2336 /*
2337 * Add the request-target pseudo header first.
2338 */
2339 Assert(cbLeft > sizeof(s_szRequestField));
2340 memcpy(pszLeft, RT_STR_TUPLE(s_szRequestField));
2341 pszLeft += sizeof(s_szRequestField) - 1;
2342
2343 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(s_szRequestField));
2344 if (RT_SUCCESS(rc))
2345 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2346 if (RT_SUCCESS(rc))
2347 rc = RTCrDigestUpdate(hDigest, pszMethodSp, strlen(pszMethodSp));
2348 if (RT_SUCCESS(rc))
2349 rc = RTCrDigestUpdate(hDigest, pszPath, strlen(pszPath));
2350
2351 /*
2352 * Add the header fields.
2353 */
2354 for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur && RT_SUCCESS(rc); pCur = (PRTHTTPHEADER)pCur->Core.next)
2355 {
2356 AssertBreakStmt(cbLeft > pCur->cchName, rc = VERR_INTERNAL_ERROR_3);
2357 *pszLeft++ = ' ';
2358 cbLeft--;
2359 memcpy(pszLeft, pCur->szData, pCur->cchName);
2360 pszLeft[pCur->cchName] = '\0';
2361 RTStrToLower(pszLeft);
2362
2363 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE("\n"));
2364 AssertRCBreak(rc);
2365 rc = RTCrDigestUpdate(hDigest, pszLeft, pCur->cchName);
2366 AssertRCBreak(rc);
2367 rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": "));
2368 AssertRCBreak(rc);
2369 const char *pszValue = &pCur->szData[pCur->offValue];
2370 rc = RTCrDigestUpdate(hDigest, pszValue, strlen(pszValue));
2371 AssertRCBreak(rc);
2372
2373 pszLeft += pCur->cchName;
2374 cbLeft -= pCur->cchName;
2375 }
2376 if (RT_SUCCESS(rc))
2377 AssertStmt(cbLeft > sizeof(s_szInfix) + cchSigStr + sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2378 if (RT_SUCCESS(rc))
2379 {
2380 /* Complete the header field part. */
2381 memcpy(pszLeft, RT_STR_TUPLE(s_szInfix));
2382 pszLeft += sizeof(s_szInfix) - 1;
2383 cbLeft -= sizeof(s_szInfix) - 1;
2384
2385 /*
2386 * Sign the digest.
2387 */
2388 RTCRPKIXSIGNATURE hSigner;
2389 rc = RTCrPkixSignatureCreateByObjIdString(&hSigner, RTCR_PKCS1_SHA256_WITH_RSA_OID, hKey, NULL, true /*fSigning*/);
2390 AssertRC(rc);
2391 if (RT_SUCCESS(rc))
2392 {
2393 size_t cbActual = cbSigRawAligned;
2394 rc = RTCrPkixSignatureSign(hSigner, hDigest, pbSigRaw, &cbActual);
2395 AssertRC(rc);
2396 if (RT_SUCCESS(rc))
2397 {
2398 Assert(cbActual == cbSigRaw);
2399 RTCrPkixSignatureRelease(hSigner);
2400 hSigner = NIL_RTCRPKIXSIGNATURE;
2401 RTCrDigestRelease(hDigest);
2402 hDigest = NIL_RTCRDIGEST;
2403
2404 /*
2405 * Convert the signature to Base64 and append it to the string.
2406 */
2407 size_t cchActual;
2408 rc = RTBase64EncodeEx(pbSigRaw, cbActual, RTBASE64_FLAGS_NO_LINE_BREAKS, pszLeft, cbLeft, &cchActual);
2409 AssertRC(rc);
2410 if (RT_SUCCESS(rc))
2411 {
2412 Assert(cchActual == cchSigStr);
2413 pszLeft += cchActual;
2414 cbLeft -= cchActual;
2415
2416 /*
2417 * Append the postfix and add the header to the front of the list.
2418 */
2419 AssertStmt(cbLeft >= sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3);
2420 if (RT_SUCCESS(rc))
2421 {
2422 memcpy(pszLeft, s_szPostfix, sizeof(s_szPostfix));
2423
2424 pHdr->Core.next = pThis->pHeaders;
2425 if (!pThis->pHeaders)
2426 pThis->ppHeadersTail = &pHdr->Core.next;
2427 pThis->pHeaders = &pHdr->Core;
2428
2429 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders);
2430 if (CURL_SUCCESS(rcCurl))
2431 return VINF_SUCCESS;
2432 rc = VERR_HTTP_CURL_ERROR;
2433 }
2434 }
2435 }
2436 RTCrPkixSignatureRelease(hSigner);
2437 }
2438 }
2439 RTCrDigestRelease(hDigest);
2440 }
2441 RTMemFree(pHdr);
2442 return rc;
2443}
2444
2445
2446/*********************************************************************************************************************************
2447* HTTPS and root certficates *
2448*********************************************************************************************************************************/
2449
2450/**
2451 * Set the CA file to NULL, deleting any temporary file if necessary.
2452 *
2453 * @param pThis The HTTP/HTTPS client instance.
2454 */
2455static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis)
2456{
2457 if (pThis->pszCaFile)
2458 {
2459 if (pThis->fDeleteCaFile)
2460 {
2461 int rc2 = RTFileDelete(pThis->pszCaFile); RT_NOREF_PV(rc2);
2462 AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile));
2463 }
2464 RTStrFree(pThis->pszCaFile);
2465 pThis->pszCaFile = NULL;
2466 }
2467}
2468
2469
2470RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile)
2471{
2472 PRTHTTPINTERNAL pThis = hHttp;
2473 RTHTTP_VALID_RETURN(pThis);
2474
2475 rtHttpUnsetCaFile(pThis);
2476
2477 pThis->fDeleteCaFile = false;
2478 if (pszCaFile)
2479 return RTStrDupEx(&pThis->pszCaFile, pszCaFile);
2480 return VINF_SUCCESS;
2481}
2482
2483
2484RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo)
2485{
2486 PRTHTTPINTERNAL pThis = hHttp;
2487 RTHTTP_VALID_RETURN(pThis);
2488
2489 /*
2490 * Create a temporary file.
2491 */
2492 int rc = VERR_NO_STR_MEMORY;
2493 char *pszCaFile = RTStrAlloc(RTPATH_MAX);
2494 if (pszCaFile)
2495 {
2496 RTFILE hFile;
2497 rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX,
2498 RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
2499 if (RT_SUCCESS(rc))
2500 {
2501 /*
2502 * Gather certificates into a temporary store and export them to the temporary file.
2503 */
2504 RTCRSTORE hStore;
2505 rc = RTCrStoreCreateInMem(&hStore, 256);
2506 if (RT_SUCCESS(rc))
2507 {
2508 rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo);
2509 if (RT_SUCCESS(rc))
2510 /** @todo Consider adding an API for exporting to a RTFILE... */
2511 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2512 RTCrStoreRelease(hStore);
2513 }
2514 RTFileClose(hFile);
2515 if (RT_SUCCESS(rc))
2516 {
2517 /*
2518 * Set the CA file for the instance.
2519 */
2520 rtHttpUnsetCaFile(pThis);
2521
2522 pThis->fDeleteCaFile = true;
2523 pThis->pszCaFile = pszCaFile;
2524 return VINF_SUCCESS;
2525 }
2526
2527 int rc2 = RTFileDelete(pszCaFile);
2528 AssertRC(rc2);
2529 }
2530 else
2531 RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc);
2532
2533 RTStrFree(pszCaFile);
2534 }
2535 return rc;
2536}
2537
2538
2539RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo)
2540{
2541 uint32_t const cBefore = RTCrStoreCertCount(hStore);
2542 AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE);
2543 RT_NOREF_PV(fFlags);
2544
2545
2546 /*
2547 * Add the user store, quitely ignoring any errors.
2548 */
2549 RTCRSTORE hSrcStore;
2550 int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2551 if (RT_SUCCESS(rcUser))
2552 {
2553 rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2554 hSrcStore);
2555 RTCrStoreRelease(hSrcStore);
2556 }
2557
2558 /*
2559 * Ditto for the system store.
2560 */
2561 int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
2562 if (RT_SUCCESS(rcSystem))
2563 {
2564 rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
2565 hSrcStore);
2566 RTCrStoreRelease(hSrcStore);
2567 }
2568
2569 /*
2570 * If the number of certificates increased, we consider it a success.
2571 */
2572 if (RTCrStoreCertCount(hStore) > cBefore)
2573 {
2574 if (RT_FAILURE(rcSystem))
2575 return -rcSystem;
2576 if (RT_FAILURE(rcUser))
2577 return -rcUser;
2578 return rcSystem != VINF_SUCCESS ? rcSystem : rcUser;
2579 }
2580
2581 if (RT_FAILURE(rcSystem))
2582 return rcSystem;
2583 if (RT_FAILURE(rcUser))
2584 return rcUser;
2585 return VERR_NOT_FOUND;
2586}
2587
2588
2589RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo)
2590{
2591 RTCRSTORE hStore;
2592 int rc = RTCrStoreCreateInMem(&hStore, 256);
2593 if (RT_SUCCESS(rc))
2594 {
2595 rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo);
2596 if (RT_SUCCESS(rc))
2597 rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile);
2598 RTCrStoreRelease(hStore);
2599 }
2600 return rc;
2601}
2602
2603
2604
2605/*********************************************************************************************************************************
2606* .......
2607*********************************************************************************************************************************/
2608
2609
2610/**
2611 * Figures out the IPRT status code for a GET.
2612 *
2613 * @returns IPRT status code.
2614 * @param pThis The HTTP/HTTPS client instance.
2615 * @param rcCurl What curl returned.
2616 * @param puHttpStatus Where to optionally return the HTTP status. If specified,
2617 * the HTTP statuses are not translated to IPRT status codes.
2618 */
2619static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, int rcCurl, uint32_t *puHttpStatus)
2620{
2621 int rc = VERR_HTTP_CURL_ERROR;
2622
2623 if (pThis->pszRedirLocation)
2624 {
2625 RTStrFree(pThis->pszRedirLocation);
2626 pThis->pszRedirLocation = NULL;
2627 }
2628 if (rcCurl == CURLE_OK)
2629 {
2630 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp);
2631 if (puHttpStatus)
2632 {
2633 *puHttpStatus = pThis->lLastResp;
2634 rc = VINF_SUCCESS;
2635 }
2636
2637 switch (pThis->lLastResp)
2638 {
2639 case 200:
2640 /* OK, request was fulfilled */
2641 case 204:
2642 /* empty response */
2643 rc = VINF_SUCCESS;
2644 break;
2645 case 301: /* Moved permantently. */
2646 case 302: /* Found / Moved temporarily. */
2647 case 303: /* See Other. */
2648 case 307: /* Temporary redirect. */
2649 case 308: /* Permanent redirect. */
2650 {
2651 const char *pszRedirect = NULL;
2652 curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect);
2653 size_t cb = pszRedirect ? strlen(pszRedirect) : 0;
2654 if (cb > 0 && cb < 2048)
2655 pThis->pszRedirLocation = RTStrDup(pszRedirect);
2656 if (!puHttpStatus)
2657 rc = VERR_HTTP_REDIRECTED;
2658 break;
2659 }
2660 case 400:
2661 /* bad request */
2662 if (!puHttpStatus)
2663 rc = VERR_HTTP_BAD_REQUEST;
2664 break;
2665 case 403:
2666 /* forbidden, authorization will not help */
2667 if (!puHttpStatus)
2668 rc = VERR_HTTP_ACCESS_DENIED;
2669 break;
2670 case 404:
2671 /* URL not found */
2672 if (!puHttpStatus)
2673 rc = VERR_HTTP_NOT_FOUND;
2674 break;
2675 }
2676
2677 if (pThis->pszRedirLocation)
2678 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation));
2679 else
2680 Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp));
2681 }
2682 else
2683 {
2684 switch (rcCurl)
2685 {
2686 case CURLE_URL_MALFORMAT:
2687 case CURLE_COULDNT_RESOLVE_HOST:
2688 rc = VERR_HTTP_HOST_NOT_FOUND;
2689 break;
2690 case CURLE_COULDNT_CONNECT:
2691 rc = VERR_HTTP_COULDNT_CONNECT;
2692 break;
2693 case CURLE_SSL_CONNECT_ERROR:
2694 rc = VERR_HTTP_SSL_CONNECT_ERROR;
2695 break;
2696 case CURLE_SSL_CACERT:
2697 /* The peer certificate cannot be authenticated with the CA certificates
2698 * set by RTHttpSetCAFile(). We need other or additional CA certificates. */
2699 rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE;
2700 break;
2701 case CURLE_SSL_CACERT_BADFILE:
2702 /* CAcert file (see RTHttpSetCAFile()) has wrong format */
2703 rc = VERR_HTTP_CACERT_WRONG_FORMAT;
2704 break;
2705 case CURLE_ABORTED_BY_CALLBACK:
2706 /* forcefully aborted */
2707 rc = VERR_HTTP_ABORTED;
2708 break;
2709 case CURLE_COULDNT_RESOLVE_PROXY:
2710 rc = VERR_HTTP_PROXY_NOT_FOUND;
2711 break;
2712 case CURLE_WRITE_ERROR:
2713 rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR;
2714 break;
2715 //case CURLE_READ_ERROR
2716
2717 default:
2718 break;
2719 }
2720 Log(("rtHttpGetCalcStatus: rc=%Rrc rcCurl=%u\n", rc, rcCurl));
2721 }
2722
2723 return rc;
2724}
2725
2726
2727/**
2728 * cURL callback for reporting progress, we use it for checking for abort.
2729 */
2730static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded)
2731{
2732 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData;
2733 AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1);
2734 RT_NOREF_PV(rdTotalUpload);
2735 RT_NOREF_PV(rdUploaded);
2736
2737 pThis->cbDownloadHint = (uint64_t)rdTotalDownload;
2738
2739 if (pThis->pfnDownloadProgress)
2740 pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded);
2741
2742 return pThis->fAbort ? 1 : 0;
2743}
2744
2745
2746/**
2747 * Whether we're likely to need SSL to handle the give URL.
2748 *
2749 * @returns true if we need, false if we probably don't.
2750 * @param pszUrl The URL.
2751 */
2752static bool rtHttpNeedSsl(const char *pszUrl)
2753{
2754 return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0;
2755}
2756
2757
2758/**
2759 * Applies recoded settings to the cURL instance before doing work.
2760 *
2761 * @returns IPRT status code.
2762 * @param pThis The HTTP/HTTPS client instance.
2763 * @param pszUrl The URL.
2764 */
2765static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl)
2766{
2767 /*
2768 * The URL.
2769 */
2770 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
2771 if (CURL_FAILURE(rcCurl))
2772 return VERR_INVALID_PARAMETER;
2773
2774 /*
2775 * Proxy config.
2776 */
2777 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
2778 if (RT_FAILURE(rc))
2779 return rc;
2780
2781 /*
2782 * Setup SSL. Can be a bit of work.
2783 */
2784 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1);
2785 if (CURL_FAILURE(rcCurl))
2786 return VERR_INVALID_PARAMETER;
2787
2788 const char *pszCaFile = pThis->pszCaFile;
2789 if ( !pszCaFile
2790 && rtHttpNeedSsl(pszUrl))
2791 {
2792 rc = RTHttpUseTemporaryCaFile(pThis, NULL);
2793 if (RT_SUCCESS(rc))
2794 pszCaFile = pThis->pszCaFile;
2795 else
2796 return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */
2797 }
2798 if (pszCaFile)
2799 {
2800 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile);
2801 if (CURL_FAILURE(rcCurl))
2802 return VERR_HTTP_CURL_ERROR;
2803 }
2804
2805 /*
2806 * Progress/abort.
2807 */
2808 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress);
2809 if (CURL_FAILURE(rcCurl))
2810 return VERR_HTTP_CURL_ERROR;
2811 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis);
2812 if (CURL_FAILURE(rcCurl))
2813 return VERR_HTTP_CURL_ERROR;
2814 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0);
2815 if (CURL_FAILURE(rcCurl))
2816 return VERR_HTTP_CURL_ERROR;
2817
2818 /*
2819 * Set default user agent string if necessary. Some websites take offence
2820 * if we don't set it.
2821 */
2822 if ( !pThis->fHaveSetUserAgent
2823 && !pThis->fHaveUserAgentHeader)
2824 {
2825 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42");
2826 if (CURL_FAILURE(rcCurl))
2827 return VERR_HTTP_CURL_ERROR;
2828 pThis->fHaveSetUserAgent = true;
2829 }
2830
2831 /*
2832 * Use GET by default.
2833 */
2834 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L);
2835 if (CURL_FAILURE(rcCurl))
2836 return VERR_HTTP_CURL_ERROR;
2837 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L);
2838 if (CURL_FAILURE(rcCurl))
2839 return VERR_HTTP_CURL_ERROR;
2840
2841 return VINF_SUCCESS;
2842}
2843
2844
2845/**
2846 * Resets state.
2847 *
2848 * @param pThis HTTP client instance.
2849 */
2850static void rtHttpResetState(PRTHTTPINTERNAL pThis)
2851{
2852 pThis->fAbort = false;
2853 pThis->rcOutput = VINF_SUCCESS;
2854 pThis->uDownloadHttpStatus = UINT32_MAX;
2855 pThis->cbDownloadContent = UINT64_MAX;
2856 pThis->offDownloadContent = 0;
2857 pThis->offUploadContent = 0;
2858 pThis->rcOutput = VINF_SUCCESS;
2859 pThis->cbDownloadHint = 0;
2860 Assert(pThis->BodyOutput.pHttp == pThis);
2861 Assert(pThis->HeadersOutput.pHttp == pThis);
2862}
2863
2864
2865/**
2866 * Tries to determin uDownloadHttpStatus and cbDownloadContent.
2867 *
2868 * @param pThis HTTP client instance.
2869 */
2870static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis)
2871{
2872 long lHttpStatus = 0;
2873 curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus);
2874 pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus;
2875
2876#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
2877 curl_off_t cbContent = -1;
2878 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent);
2879 if (cbContent >= 0)
2880 pThis->cbDownloadContent = (uint64_t)cbContent;
2881#else
2882 double rdContent = -1.0;
2883 curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent);
2884 if (rdContent >= 0.0)
2885 pThis->cbDownloadContent = (uint64_t)rdContent;
2886#endif
2887}
2888
2889
2890/**
2891 * cURL callback for writing data.
2892 */
2893static size_t rtHttpWriteData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2894{
2895 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
2896 PRTHTTPINTERNAL pThis = pOutput->pHttp;
2897 size_t const cbToAppend = cbUnit * cUnits;
2898
2899 /*
2900 * If called on the body, check if this belongs to the body download callback.
2901 */
2902 if ( pThis->pfnDownloadCallback
2903 && pOutput == &pThis->BodyOutput)
2904 {
2905 if (pThis->offDownloadContent == 0)
2906 rtHttpGetDownloadStatusAndLength(pThis);
2907
2908 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
2909 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
2910 {
2911 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
2912 pThis->cbDownloadContent, pThis->pvUploadCallbackUser);
2913 if (RT_SUCCESS(rc))
2914 {
2915 pThis->offDownloadContent += cbToAppend;
2916 return cbToAppend;
2917 }
2918 if (RT_SUCCESS(pThis->rcOutput))
2919 pThis->rcOutput = rc;
2920 pThis->fAbort = true;
2921 return 0;
2922 }
2923 }
2924
2925 /*
2926 * Do max size and overflow checks.
2927 */
2928 size_t const cbCurSize = pOutput->uData.Mem.cb;
2929 size_t const cbNewSize = cbCurSize + cbToAppend;
2930 if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2931 && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE)
2932 {
2933 if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated)
2934 {
2935 memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend);
2936 pOutput->uData.Mem.cb = cbNewSize;
2937 pOutput->uData.Mem.pb[cbNewSize] = '\0';
2938 return cbToAppend;
2939 }
2940
2941 /*
2942 * We need to reallocate the output buffer.
2943 */
2944 /** @todo this could do with a better strategy wrt growth. */
2945 size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64);
2946 if ( cbAlloc <= pThis->cbDownloadHint
2947 && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE
2948 && pOutput == &pThis->BodyOutput)
2949 cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64);
2950
2951 uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc);
2952 if (pbNew)
2953 {
2954 memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend);
2955 pbNew[cbNewSize] = '\0';
2956
2957 pOutput->uData.Mem.cbAllocated = cbAlloc;
2958 pOutput->uData.Mem.pb = pbNew;
2959 pOutput->uData.Mem.cb = cbNewSize;
2960 return cbToAppend;
2961 }
2962
2963 pThis->rcOutput = VERR_NO_MEMORY;
2964 }
2965 else
2966 pThis->rcOutput = VERR_TOO_MUCH_DATA;
2967
2968 /*
2969 * Failure - abort.
2970 */
2971 RTMemFree(pOutput->uData.Mem.pb);
2972 pOutput->uData.Mem.pb = NULL;
2973 pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE;
2974 pThis->fAbort = true;
2975 return 0;
2976}
2977
2978
2979/**
2980 * cURL callback for working the upload callback.
2981 */
2982static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser)
2983{
2984 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
2985 size_t const cbBuf = cbUnit * cUnits;
2986
2987 /* Get download info the first time we're called. */
2988 if (pThis->offDownloadContent == 0)
2989 rtHttpGetDownloadStatusAndLength(pThis);
2990
2991 /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */
2992 if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS
2993 || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus)
2994 {
2995 int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent,
2996 pThis->cbDownloadContent, pThis->pvUploadCallbackUser);
2997 if (RT_SUCCESS(rc))
2998 { /* likely */ }
2999 else
3000 {
3001 if (RT_SUCCESS(pThis->rcOutput))
3002 pThis->rcOutput = rc;
3003 pThis->fAbort = true;
3004 return 0;
3005 }
3006 }
3007 pThis->offDownloadContent += cbBuf;
3008 return cbBuf;
3009}
3010
3011
3012/**
3013 * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem.
3014 */
3015static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser)
3016{
3017 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3018 size_t const cbReq = cbUnit * cUnits;
3019 size_t const offMem = pThis->ReadData.Mem.offMem;
3020 size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem;
3021 if (cbToCopy > cbReq)
3022 cbToCopy = cbReq;
3023 memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy);
3024 pThis->ReadData.Mem.offMem = offMem + cbToCopy;
3025 return cbToCopy;
3026}
3027
3028
3029/**
3030 * Callback feeding cURL data via the user upload callback.
3031 */
3032static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser)
3033{
3034 PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser;
3035 size_t const cbReq = cbUnit * cUnits;
3036
3037 size_t cbActual = 0;
3038 int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser);
3039 if (RT_SUCCESS(rc))
3040 {
3041 pThis->offUploadContent += cbActual;
3042 return cbActual;
3043 }
3044
3045 if (RT_SUCCESS(pThis->rcOutput))
3046 pThis->rcOutput = rc;
3047 pThis->fAbort = true;
3048 return CURL_READFUNC_ABORT;
3049}
3050
3051
3052/**
3053 * Helper for installing a (body) write callback function.
3054 *
3055 * @returns cURL status code.
3056 * @param pThis The HTTP client instance.
3057 * @param pfnWrite The callback.
3058 * @param pvUser The callback user argument.
3059 */
3060static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3061{
3062 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite);
3063 if (CURL_SUCCESS(rcCurl))
3064 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser);
3065 return rcCurl;
3066}
3067
3068
3069/**
3070 * Helper for installing a header write callback function.
3071 *
3072 * @returns cURL status code.
3073 * @param pThis The HTTP client instance.
3074 * @param pfnWrite The callback.
3075 * @param pvUser The callback user argument.
3076 */
3077static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3078{
3079 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3080 if (CURL_SUCCESS(rcCurl))
3081 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3082 return rcCurl;
3083}
3084
3085
3086/**
3087 * Helper for installing a (body) read callback function.
3088 *
3089 * @returns cURL status code.
3090 * @param pThis The HTTP client instance.
3091 * @param pfnRead The callback.
3092 * @param pvUser The callback user argument.
3093 */
3094static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3095{
3096 CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3097 if (CURL_SUCCESS(rcCurl))
3098 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3099 return rcCurl;
3100}
3101
3102
3103/**
3104 * Internal worker that performs a HTTP GET.
3105 *
3106 * @returns IPRT status code.
3107 * @param hHttp The HTTP/HTTPS client instance.
3108 * @param pszUrl The URL.
3109 * @param fNoBody Set to suppress the body.
3110 * @param ppvResponse Where to return the pointer to the allocated
3111 * response data (RTMemFree). There will always be
3112 * an zero terminator char after the response, that
3113 * is not part of the size returned via @a pcb.
3114 * @param pcb The size of the response data.
3115 *
3116 * @remarks We ASSUME the API user doesn't do concurrent GETs in different
3117 * threads, because that will probably blow up!
3118 */
3119static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb)
3120{
3121 PRTHTTPINTERNAL pThis = hHttp;
3122 RTHTTP_VALID_RETURN(pThis);
3123
3124 /*
3125 * Reset the return values in case of more "GUI programming" on the client
3126 * side (i.e. a programming style not bothering checking return codes).
3127 */
3128 *ppvResponse = NULL;
3129 *pcb = 0;
3130
3131 /*
3132 * Set the busy flag (paranoia).
3133 */
3134 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3135 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3136
3137 /*
3138 * Reset the state and apply settings.
3139 */
3140 rtHttpResetState(pThis);
3141 int rc = rtHttpApplySettings(hHttp, pszUrl);
3142 if (RT_SUCCESS(rc))
3143 {
3144 RT_ZERO(pThis->BodyOutput.uData.Mem);
3145 int rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteData, (void *)&pThis->BodyOutput);
3146 if (fNoBody)
3147 {
3148 if (CURL_SUCCESS(rcCurl))
3149 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3150 if (CURL_SUCCESS(rcCurl))
3151 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L);
3152 }
3153 if (CURL_SUCCESS(rcCurl))
3154 {
3155 /*
3156 * Perform the HTTP operation.
3157 */
3158 rcCurl = curl_easy_perform(pThis->pCurl);
3159 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3160 if (RT_SUCCESS(rc))
3161 rc = pThis->rcOutput;
3162 if (RT_SUCCESS(rc))
3163 {
3164 *ppvResponse = pThis->BodyOutput.uData.Mem.pb;
3165 *pcb = pThis->BodyOutput.uData.Mem.cb;
3166 Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n",
3167 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3168 }
3169 else if (pThis->BodyOutput.uData.Mem.pb)
3170 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3171 RT_ZERO(pThis->BodyOutput.uData.Mem);
3172 }
3173 else
3174 rc = VERR_HTTP_CURL_ERROR;
3175 }
3176
3177 ASMAtomicWriteBool(&pThis->fBusy, false);
3178 return rc;
3179}
3180
3181
3182RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3183{
3184 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3185 uint8_t *pv;
3186 size_t cb;
3187 int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb);
3188 if (RT_SUCCESS(rc))
3189 {
3190 if (pv) /* paranoia */
3191 *ppszNotUtf8 = (char *)pv;
3192 else
3193 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3194 }
3195 else
3196 *ppszNotUtf8 = NULL;
3197 return rc;
3198}
3199
3200
3201RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8)
3202{
3203 Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3204 uint8_t *pv;
3205 size_t cb;
3206 int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb);
3207 if (RT_SUCCESS(rc))
3208 {
3209 if (pv) /* paranoia */
3210 *ppszNotUtf8 = (char *)pv;
3211 else
3212 *ppszNotUtf8 = (char *)RTMemDup("", 1);
3213 }
3214 else
3215 *ppszNotUtf8 = NULL;
3216 return rc;
3217
3218}
3219
3220
3221RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8)
3222{
3223 RTMemFree(pszNotUtf8);
3224}
3225
3226
3227RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3228{
3229 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3230 return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3231}
3232
3233
3234RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb)
3235{
3236 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl));
3237 return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb);
3238}
3239
3240
3241RTR3DECL(void) RTHttpFreeResponse(void *pvResponse)
3242{
3243 RTMemFree(pvResponse);
3244}
3245
3246
3247/**
3248 * cURL callback for writing data to a file.
3249 */
3250static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser)
3251{
3252 RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser;
3253 PRTHTTPINTERNAL pThis = pOutput->pHttp;
3254
3255 size_t cbWritten = 0;
3256 int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten);
3257 if (RT_SUCCESS(rc))
3258 return cbWritten;
3259
3260 Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits));
3261 pThis->rcOutput = rc;
3262 return 0;
3263}
3264
3265
3266RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile)
3267{
3268 Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile));
3269 PRTHTTPINTERNAL pThis = hHttp;
3270 RTHTTP_VALID_RETURN(pThis);
3271
3272 /*
3273 * Set the busy flag (paranoia).
3274 */
3275 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3276 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3277
3278 /*
3279 * Reset the state and apply settings.
3280 */
3281 rtHttpResetState(pThis);
3282 int rc = rtHttpApplySettings(hHttp, pszUrl);
3283 if (RT_SUCCESS(rc))
3284 {
3285 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3286 int rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput);
3287 if (CURL_SUCCESS(rcCurl))
3288 {
3289 /*
3290 * Open the output file.
3291 */
3292 rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile,
3293 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE);
3294 if (RT_SUCCESS(rc))
3295 {
3296 /*
3297 * Perform the HTTP operation.
3298 */
3299 rcCurl = curl_easy_perform(pThis->pCurl);
3300 rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL);
3301 if (RT_SUCCESS(rc))
3302 rc = pThis->rcOutput;
3303
3304 int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile);
3305 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
3306 rc = rc2;
3307 }
3308 pThis->BodyOutput.uData.hFile = NIL_RTFILE;
3309 }
3310 else
3311 rc = VERR_HTTP_CURL_ERROR;
3312 }
3313
3314 ASMAtomicWriteBool(&pThis->fBusy, false);
3315 return rc;
3316}
3317
3318
3319RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody,
3320 uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody)
3321{
3322 /*
3323 * Set safe return values and validate input.
3324 */
3325 Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n",
3326 hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody));
3327
3328 if (ppvHeaders)
3329 *ppvHeaders = NULL;
3330 if (pcbHeaders)
3331 *pcbHeaders = 0;
3332 if (ppvBody)
3333 *ppvBody = NULL;
3334 if (pcbBody)
3335 *pcbBody = 0;
3336 if (puHttpStatus)
3337 *puHttpStatus = UINT32_MAX;
3338
3339 PRTHTTPINTERNAL pThis = hHttp;
3340 RTHTTP_VALID_RETURN(pThis);
3341 AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER);
3342 AssertPtrReturn(pszUrl, VERR_INVALID_POINTER);
3343
3344#ifdef LOG_ENABLED
3345 if (LogIs6Enabled() && pThis->pHeaders)
3346 {
3347 Log4(("RTHttpPerform: headers:\n"));
3348 for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next)
3349 Log4(("%s\n", pCur->data));
3350 }
3351 if (pvReqBody && cbReqBody)
3352 Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody));
3353#endif
3354
3355 /*
3356 * Set the busy flag (paranoia).
3357 */
3358 bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true);
3359 AssertReturn(!fBusy, VERR_WRONG_ORDER);
3360
3361 /*
3362 * Reset the state and apply settings.
3363 */
3364 rtHttpResetState(pThis);
3365 int rc = rtHttpApplySettings(hHttp, pszUrl);
3366 if (RT_SUCCESS(rc))
3367 {
3368 /* Set the HTTP method. */
3369 int rcCurl = 1;
3370 switch (enmMethod)
3371 {
3372 case RTHTTPMETHOD_GET:
3373 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3374 break;
3375 case RTHTTPMETHOD_PUT:
3376 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3377 break;
3378 case RTHTTPMETHOD_POST:
3379 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3380 break;
3381 case RTHTTPMETHOD_PATCH:
3382 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH");
3383 break;
3384 case RTHTTPMETHOD_DELETE:
3385 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
3386 break;
3387 case RTHTTPMETHOD_HEAD:
3388 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3389 if (CURL_SUCCESS(rcCurl))
3390 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3391 break;
3392 case RTHTTPMETHOD_OPTIONS:
3393 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS");
3394 break;
3395 case RTHTTPMETHOD_TRACE:
3396 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE");
3397 break;
3398 case RTHTTPMETHOD_END:
3399 case RTHTTPMETHOD_INVALID:
3400 case RTHTTPMETHOD_32BIT_HACK:
3401 AssertFailed();
3402 }
3403
3404 /* Request body. POST requests should always have a body. */
3405 if ( pvReqBody
3406 && CURL_SUCCESS(rcCurl)
3407 && ( cbReqBody > 0
3408 || enmMethod == RTHTTPMETHOD_POST) )
3409 {
3410 if (enmMethod == RTHTTPMETHOD_POST)
3411 {
3412 /** @todo ??? */
3413 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody);
3414 if (CURL_SUCCESS(rcCurl))
3415 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody);
3416 }
3417 else
3418 {
3419 pThis->ReadData.Mem.pvMem = pvReqBody;
3420 pThis->ReadData.Mem.cbMem = cbReqBody;
3421 pThis->ReadData.Mem.offMem = 0;
3422 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis);
3423 }
3424 }
3425 else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl))
3426 rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis);
3427
3428 /* Headers. */
3429 if (ppvHeaders && CURL_SUCCESS(rcCurl))
3430 {
3431 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3432 rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteData, &pThis->HeadersOutput);
3433 }
3434
3435 /* Body */
3436 if (ppvBody && CURL_SUCCESS(rcCurl))
3437 {
3438 RT_ZERO(pThis->BodyOutput.uData.Mem);
3439 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteData, &pThis->BodyOutput);
3440 }
3441 else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl))
3442 rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis);
3443
3444 if (CURL_SUCCESS(rcCurl))
3445 {
3446 /*
3447 * Perform the HTTP operation.
3448 */
3449 rcCurl = curl_easy_perform(pThis->pCurl);
3450 rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus);
3451 if (RT_SUCCESS(rc))
3452 rc = pThis->rcOutput;
3453 if (RT_SUCCESS(rc))
3454 {
3455 if (ppvHeaders)
3456 {
3457 Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n",
3458 pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated));
3459 Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb));
3460 *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb;
3461 *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb;
3462 pThis->HeadersOutput.uData.Mem.pb = NULL;
3463 }
3464 if (ppvBody)
3465 {
3466 Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n",
3467 pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated));
3468 Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb));
3469 *ppvBody = pThis->BodyOutput.uData.Mem.pb;
3470 *pcbBody = pThis->BodyOutput.uData.Mem.cb;
3471 pThis->BodyOutput.uData.Mem.pb = NULL;
3472 }
3473 }
3474 }
3475 else
3476 rc = VERR_HTTP_CURL_ERROR;
3477
3478 /* Ensure we've freed all unused output and dropped references to input memory.*/
3479 if (pThis->HeadersOutput.uData.Mem.pb)
3480 RTMemFree(pThis->HeadersOutput.uData.Mem.pb);
3481 if (pThis->BodyOutput.uData.Mem.pb)
3482 RTMemFree(pThis->BodyOutput.uData.Mem.pb);
3483 RT_ZERO(pThis->HeadersOutput.uData.Mem);
3484 RT_ZERO(pThis->BodyOutput.uData.Mem);
3485 RT_ZERO(pThis->ReadData);
3486 }
3487
3488 ASMAtomicWriteBool(&pThis->fBusy, false);
3489 return rc;
3490}
3491
3492
3493RTR3DECL(const char *) RTHttpMethodName(RTHTTPMETHOD enmMethod)
3494{
3495 switch (enmMethod)
3496 {
3497 case RTHTTPMETHOD_INVALID: return "invalid";
3498 case RTHTTPMETHOD_GET: return "GET";
3499 case RTHTTPMETHOD_PUT: return "PUT";
3500 case RTHTTPMETHOD_POST: return "POST";
3501 case RTHTTPMETHOD_PATCH: return "PATCH";
3502 case RTHTTPMETHOD_DELETE: return "DELETE";
3503 case RTHTTPMETHOD_HEAD: return "HEAD";
3504 case RTHTTPMETHOD_OPTIONS: return "OPTIONS";
3505 case RTHTTPMETHOD_TRACE: return "TRACE";
3506
3507 case RTHTTPMETHOD_END:
3508 case RTHTTPMETHOD_32BIT_HACK:
3509 break;
3510 }
3511 return "unknown";
3512}
3513
3514
3515/*********************************************************************************************************************************
3516* Callback APIs. *
3517*********************************************************************************************************************************/
3518
3519RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser)
3520{
3521 PRTHTTPINTERNAL pThis = hHttp;
3522 RTHTTP_VALID_RETURN(pThis);
3523
3524 pThis->pfnUploadCallback = pfnCallback;
3525 pThis->pvUploadCallbackUser = pvUser;
3526 pThis->cbUploadContent = cbContent;
3527 pThis->offUploadContent = 0;
3528
3529 if (cbContent != UINT64_MAX)
3530 {
3531 AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t));
3532 int rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent);
3533 AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR);
3534 }
3535 return VINF_SUCCESS;
3536}
3537
3538
3539RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser)
3540{
3541 PRTHTTPINTERNAL pThis = hHttp;
3542 RTHTTP_VALID_RETURN(pThis);
3543 AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS);
3544
3545 pThis->pfnDownloadCallback = pfnCallback;
3546 pThis->pvDownloadCallbackUser = pvUser;
3547 pThis->fDownloadCallback = fFlags;
3548 pThis->uDownloadHttpStatus = UINT32_MAX;
3549 pThis->cbDownloadContent = UINT64_MAX;
3550 pThis->offDownloadContent = 0;
3551
3552 return VINF_SUCCESS;
3553}
3554
3555
3556RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser)
3557{
3558 PRTHTTPINTERNAL pThis = hHttp;
3559 RTHTTP_VALID_RETURN(pThis);
3560
3561 pThis->pfnDownloadProgress = pfnCallback;
3562 pThis->pvDownloadProgressUser = pvUser;
3563 return VINF_SUCCESS;
3564}
3565
3566
3567/** @todo header field callback. */
3568
3569
3570/*********************************************************************************************************************************
3571* Temporary raw cURL stuff. Will be gone before 6.0 is out! *
3572*********************************************************************************************************************************/
3573
3574RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl)
3575{
3576 CURLcode rcCurl;
3577
3578 PRTHTTPINTERNAL pThis = hHttp;
3579 RTHTTP_VALID_RETURN(pThis);
3580
3581 int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl);
3582 if (RT_FAILURE(rc))
3583 return rc;
3584
3585 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl);
3586 if (CURL_FAILURE(rcCurl))
3587 return VERR_HTTP_CURL_ERROR;
3588
3589 return VINF_SUCCESS;
3590}
3591
3592
3593RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp)
3594{
3595 CURLcode rcCurl;
3596
3597 PRTHTTPINTERNAL pThis = hHttp;
3598 RTHTTP_VALID_RETURN(pThis);
3599
3600 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3601 if (CURL_FAILURE(rcCurl))
3602 return VERR_HTTP_CURL_ERROR;
3603
3604 return VINF_SUCCESS;
3605}
3606
3607
3608RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp)
3609{
3610 CURLcode rcCurl;
3611
3612 PRTHTTPINTERNAL pThis = hHttp;
3613 RTHTTP_VALID_RETURN(pThis);
3614
3615 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L);
3616 if (CURL_FAILURE(rcCurl))
3617 return VERR_HTTP_CURL_ERROR;
3618
3619 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L);
3620 if (CURL_FAILURE(rcCurl))
3621 return VERR_HTTP_CURL_ERROR;
3622
3623 return VINF_SUCCESS;
3624}
3625
3626
3627RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp)
3628{
3629 CURLcode rcCurl;
3630
3631 PRTHTTPINTERNAL pThis = hHttp;
3632 RTHTTP_VALID_RETURN(pThis);
3633
3634 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L);
3635 if (CURL_FAILURE(rcCurl))
3636 return VERR_HTTP_CURL_ERROR;
3637
3638 return VINF_SUCCESS;
3639}
3640
3641
3642RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp)
3643{
3644 CURLcode rcCurl;
3645
3646 PRTHTTPINTERNAL pThis = hHttp;
3647 RTHTTP_VALID_RETURN(pThis);
3648
3649 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L);
3650 if (CURL_FAILURE(rcCurl))
3651 return VERR_HTTP_CURL_ERROR;
3652
3653 return VINF_SUCCESS;
3654}
3655
3656
3657RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp)
3658{
3659 /* curl doesn't provide an option for this */
3660 return RTHttpRawSetCustomRequest(hHttp, "DELETE");
3661}
3662
3663
3664RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb)
3665{
3666 CURLcode rcCurl;
3667
3668 PRTHTTPINTERNAL pThis = hHttp;
3669 RTHTTP_VALID_RETURN(pThis);
3670
3671 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb);
3672 if (CURL_FAILURE(rcCurl))
3673 return VERR_HTTP_CURL_ERROR;
3674
3675 return VINF_SUCCESS;
3676}
3677
3678
3679RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb)
3680{
3681 CURLcode rcCurl;
3682
3683 PRTHTTPINTERNAL pThis = hHttp;
3684 RTHTTP_VALID_RETURN(pThis);
3685
3686 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb);
3687 if (CURL_FAILURE(rcCurl))
3688 return VERR_HTTP_CURL_ERROR;
3689
3690 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv);
3691 if (CURL_FAILURE(rcCurl))
3692 return VERR_HTTP_CURL_ERROR;
3693
3694 return VINF_SUCCESS;
3695}
3696
3697RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb)
3698{
3699 CURLcode rcCurl;
3700
3701 PRTHTTPINTERNAL pThis = hHttp;
3702 RTHTTP_VALID_RETURN(pThis);
3703
3704 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb);
3705 if (CURL_FAILURE(rcCurl))
3706 return VERR_HTTP_CURL_ERROR;
3707
3708 return VINF_SUCCESS;
3709}
3710
3711
3712RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue)
3713{
3714 CURLcode rcCurl;
3715
3716 PRTHTTPINTERNAL pThis = hHttp;
3717 RTHTTP_VALID_RETURN(pThis);
3718
3719 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L);
3720 if (CURL_FAILURE(rcCurl))
3721 return VERR_HTTP_CURL_ERROR;
3722
3723 return VINF_SUCCESS;
3724}
3725
3726
3727RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec)
3728{
3729 CURLcode rcCurl;
3730
3731 PRTHTTPINTERNAL pThis = hHttp;
3732 RTHTTP_VALID_RETURN(pThis);
3733
3734 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec);
3735 if (CURL_FAILURE(rcCurl))
3736 return VERR_HTTP_CURL_ERROR;
3737
3738 return VINF_SUCCESS;
3739}
3740
3741
3742RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp)
3743{
3744 CURLcode rcCurl;
3745
3746 PRTHTTPINTERNAL pThis = hHttp;
3747 RTHTTP_VALID_RETURN(pThis);
3748
3749 /*
3750 * XXX: Do this here for now as a stop-gap measure as
3751 * RTHttpReset() resets this (and proxy settings).
3752 */
3753 if (pThis->pszCaFile)
3754 {
3755 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile);
3756 if (CURL_FAILURE(rcCurl))
3757 return VERR_HTTP_CURL_ERROR;
3758 }
3759
3760 rcCurl = curl_easy_perform(pThis->pCurl);
3761 if (CURL_FAILURE(rcCurl))
3762 return VERR_HTTP_CURL_ERROR;
3763
3764 return VINF_SUCCESS;
3765}
3766
3767
3768RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode)
3769{
3770 CURLcode rcCurl;
3771
3772 PRTHTTPINTERNAL pThis = hHttp;
3773 RTHTTP_VALID_RETURN(pThis);
3774 AssertPtrReturn(plCode, VERR_INVALID_PARAMETER);
3775
3776 rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode);
3777 if (CURL_FAILURE(rcCurl))
3778 return VERR_HTTP_CURL_ERROR;
3779
3780 return VINF_SUCCESS;
3781}
3782
3783
3784RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser)
3785{
3786 CURLcode rcCurl;
3787
3788 PRTHTTPINTERNAL pThis = hHttp;
3789 RTHTTP_VALID_RETURN(pThis);
3790
3791 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead);
3792 if (CURL_FAILURE(rcCurl))
3793 return VERR_HTTP_CURL_ERROR;
3794
3795 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser);
3796 if (CURL_FAILURE(rcCurl))
3797 return VERR_HTTP_CURL_ERROR;
3798
3799 return VINF_SUCCESS;
3800}
3801
3802
3803RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3804{
3805 PRTHTTPINTERNAL pThis = hHttp;
3806 RTHTTP_VALID_RETURN(pThis);
3807
3808 CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser);
3809 if (CURL_FAILURE(rcCurl))
3810 return VERR_HTTP_CURL_ERROR;
3811
3812 return VINF_SUCCESS;
3813}
3814
3815
3816RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser)
3817{
3818 CURLcode rcCurl;
3819
3820 PRTHTTPINTERNAL pThis = hHttp;
3821 RTHTTP_VALID_RETURN(pThis);
3822
3823 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite);
3824 if (CURL_FAILURE(rcCurl))
3825 return VERR_HTTP_CURL_ERROR;
3826
3827 rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser);
3828 if (CURL_FAILURE(rcCurl))
3829 return VERR_HTTP_CURL_ERROR;
3830
3831 return VINF_SUCCESS;
3832}
3833
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