VirtualBox

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

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

IRPT/rest,http: Use header callbacks for capturing header values in responses. Removed [P]FNCREATEINSTANCE as it isn't needed any more (and it didn't work like expected for RTCRestString). Some HTTP header callback updates. bugref:9167

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