VirtualBox

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

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

IPRT/http: render therefore unto the download callback the user data
that are its, not that of the upload callback.

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