VirtualBox

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

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

IPRT/http: build fix. bugref:9167

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