VirtualBox

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

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

IPRT/http: Added RTHttpSetProxyByUrl, deprecating RTHttpSetProxy in favor of it. bugref:9248 [libcurl archeology]

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