VirtualBox

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

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

IPRT/http: Extended RTHttpReset with a flag parameter so headers can be kept. bugref:9167

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