VirtualBox

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

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

IPRT/http: RTHttpReset - free headers from the previous request.

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