VirtualBox

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

Last change on this file since 83102 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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