VirtualBox

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

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

IPRT/http: Simplified the append-to-empty-list case in rtHttpAddHeaderWorker. bugref:9167

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