diff options
Diffstat (limited to 'src/VBox/Runtime/generic/http-curl.cpp')
-rw-r--r-- | src/VBox/Runtime/generic/http-curl.cpp | 4173 |
1 files changed, 4173 insertions, 0 deletions
diff --git a/src/VBox/Runtime/generic/http-curl.cpp b/src/VBox/Runtime/generic/http-curl.cpp new file mode 100644 index 00000000..2fdfe2d4 --- /dev/null +++ b/src/VBox/Runtime/generic/http-curl.cpp @@ -0,0 +1,4173 @@ +/* $Id: http-curl.cpp $ */ +/** @file + * IPRT - HTTP client API, cURL based. + * + * Logging groups: + * Log4 - request headers. + * Log5 - request body. + * Log6 - response headers. + * Log7 - response body. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_HTTP +#include <iprt/http.h> +#include "internal/iprt.h" + +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/base64.h> +#include <iprt/cidr.h> +#include <iprt/crypto/store.h> +#include <iprt/ctype.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/ldr.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/net.h> +#include <iprt/once.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/uni.h> +#include <iprt/uri.h> +#include <iprt/utf16.h> +#include <iprt/crypto/digest.h> +#include <iprt/crypto/pkix.h> +#include <iprt/crypto/key.h> + + +#include "internal/magics.h" + +#ifdef RT_OS_WINDOWS /* curl.h drags in windows.h which isn't necessarily -Wall clean. */ +# include <iprt/win/windows.h> +#endif +#include <curl/curl.h> + +#ifdef RT_OS_DARWIN +# include <CoreFoundation/CoreFoundation.h> +# include <SystemConfiguration/SystemConfiguration.h> +# include <CoreServices/CoreServices.h> +#endif +#ifdef RT_OS_WINDOWS +# include <Winhttp.h> +# include "../r3/win/internal-r3-win.h" +#endif + +#ifdef RT_OS_LINUX +# define IPRT_USE_LIBPROXY +#endif +#ifdef IPRT_USE_LIBPROXY +# include <stdlib.h> /* free */ +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Output collection data. */ +typedef struct RTHTTPOUTPUTDATA +{ + /** Pointer to the HTTP client instance structure. */ + struct RTHTTPINTERNAL *pHttp; + /** Callback specific data. */ + union + { + /** For file destination. */ + RTFILE hFile; + /** For memory destination. */ + struct + { + /** The current size (sans terminator char). */ + size_t cb; + /** The currently allocated size. */ + size_t cbAllocated; + /** Pointer to the buffer. */ + uint8_t *pb; + } Mem; + } uData; +} RTHTTPOUTPUTDATA; + +/** + * HTTP header. + */ +typedef struct RTHTTPHEADER +{ + /** The core list structure. */ + struct curl_slist Core; + /** The field name length. */ + uint32_t cchName; + /** The value offset. */ + uint32_t offValue; + /** The full header field. */ + RT_FLEXIBLE_ARRAY_EXTENSION + RT_GCC_EXTENSION char szData[RT_FLEXIBLE_ARRAY]; +} RTHTTPHEADER; +/** Pointer to a HTTP header. */ +typedef RTHTTPHEADER *PRTHTTPHEADER; + +/** + * Internal HTTP client instance. + */ +typedef struct RTHTTPINTERNAL +{ + /** Magic value. */ + uint32_t u32Magic; + /** cURL handle. */ + CURL *pCurl; + /** The last response code. */ + long lLastResp; + /** Custom headers (PRTHTTPHEADER). + * The list head is registered with curl, though we do all the allocating. */ + struct curl_slist *pHeaders; + /** Where to append the next header. */ + struct curl_slist **ppHeadersTail; + + /** CA certificate file for HTTPS authentication. */ + char *pszCaFile; + /** Whether to delete the CA on destruction. */ + bool fDeleteCaFile; + + /** Set if we've applied a CURLOTP_USERAGENT already. */ + bool fHaveSetUserAgent; + /** Set if we've got a user agent header, otherwise clear. */ + bool fHaveUserAgentHeader; + + /** @name Proxy settings. + * When fUseSystemProxySettings is set, the other members will be updated each + * time we're presented with a new URL. The members reflect the cURL + * configuration. + * + * @{ */ + /** Set if we should use the system proxy settings for a URL. + * This means reconfiguring cURL for each request. */ + bool fUseSystemProxySettings; + /** Set if we've detected no proxy necessary. */ + bool fNoProxy; + /** Set if we've reset proxy info in cURL and need to reapply it. */ + bool fReapplyProxyInfo; + /** Proxy host name (RTStrFree). */ + char *pszProxyHost; + /** Proxy port number (UINT32_MAX if not specified). */ + uint32_t uProxyPort; + /** The proxy type (CURLPROXY_HTTP, CURLPROXY_SOCKS5, ++). */ + curl_proxytype enmProxyType; + /** Proxy username (RTStrFree). */ + char *pszProxyUsername; + /** Proxy password (RTStrFree). */ + char *pszProxyPassword; + /** @} */ + + /** @name Cached settings. + * @{ */ + /** Maximum number of redirects to follow. + * Zero if not automatically following (default). */ + uint32_t cMaxRedirects; + /** Whether to check if Peer lies about his SSL certificate. */ + bool fVerifyPeer; + /** @} */ + + /** Abort the current HTTP request if true. */ + bool volatile fAbort; + /** Set if someone is preforming an HTTP operation. */ + bool volatile fBusy; + /** The location field for 301 responses. */ + char *pszRedirLocation; + + union + { + struct + { + /** Pointer to the memory block we're feeding the cURL/server. */ + void const *pvMem; + /** Size of the memory block. */ + size_t cbMem; + /** Current memory block offset. */ + size_t offMem; + } Mem; + } ReadData; + + /** Body output callback data. */ + RTHTTPOUTPUTDATA BodyOutput; + /** Headers output callback data. */ + RTHTTPOUTPUTDATA HeadersOutput; + /** The output status.*/ + int rcOutput; + + /** @name Upload callback + * @{ */ + /** Pointer to the upload callback function, if any. */ + PFNRTHTTPUPLOADCALLBACK pfnUploadCallback; + /** The user argument for the upload callback function. */ + void *pvUploadCallbackUser; + /** The expected upload size, UINT64_MAX if not known. */ + uint64_t cbUploadContent; + /** The current upload offset. */ + uint64_t offUploadContent; + /** @} */ + + /** @name Download callback. + * @{ */ + /** Pointer to the download callback function, if any. */ + PFNRTHTTPDOWNLOADCALLBACK pfnDownloadCallback; + /** The user argument for the download callback function. */ + void *pvDownloadCallbackUser; + /** The flags for the download callback function. */ + uint32_t fDownloadCallback; + /** HTTP status for passing to the download callback, UINT32_MAX if not known. */ + uint32_t uDownloadHttpStatus; + /** The download content length, or UINT64_MAX. */ + uint64_t cbDownloadContent; + /** The current download offset. */ + uint64_t offDownloadContent; + /** @} */ + + /** @name Download progress callback. + * @{ */ + /** Download size hint set by the progress callback. */ + uint64_t cbDownloadHint; + /** Callback called during download. */ + PFNRTHTTPDOWNLDPROGRCALLBACK pfnDownloadProgress; + /** User pointer parameter for pfnDownloadProgress. */ + void *pvDownloadProgressUser; + /** @} */ + + /** @name Header callback. + * @{ */ + /** Pointer to the header callback function, if any. */ + PFNRTHTTPHEADERCALLBACK pfnHeaderCallback; + /** User pointer parameter for pfnHeaderCallback. */ + void *pvHeaderCallbackUser; + /** @} */ + + /** Buffer for human readable error messages from curl on failures or problems. */ + char szErrorBuffer[CURL_ERROR_SIZE]; +} RTHTTPINTERNAL; +/** Pointer to an internal HTTP client instance. */ +typedef RTHTTPINTERNAL *PRTHTTPINTERNAL; + + +#ifdef RT_OS_WINDOWS +/** @name Windows: Types for dynamically resolved APIs + * @{ */ +typedef HINTERNET (WINAPI * PFNWINHTTPOPEN)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD); +typedef BOOL (WINAPI * PFNWINHTTPCLOSEHANDLE)(HINTERNET); +typedef BOOL (WINAPI * PFNWINHTTPGETPROXYFORURL)(HINTERNET, LPCWSTR, WINHTTP_AUTOPROXY_OPTIONS *, WINHTTP_PROXY_INFO *); +typedef BOOL (WINAPI * PFNWINHTTPGETDEFAULTPROXYCONFIGURATION)(WINHTTP_PROXY_INFO *); +typedef BOOL (WINAPI * PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER)(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *); +/** @} */ +#endif + +#ifdef IPRT_USE_LIBPROXY +typedef struct px_proxy_factory *PLIBPROXYFACTORY; +typedef PLIBPROXYFACTORY (* PFNLIBPROXYFACTORYCTOR)(void); +typedef void (* PFNLIBPROXYFACTORYDTOR)(PLIBPROXYFACTORY); +typedef char ** (* PFNLIBPROXYFACTORYGETPROXIES)(PLIBPROXYFACTORY, const char *); +#endif + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def RTHTTP_MAX_MEM_DOWNLOAD_SIZE + * The max size we are allowed to download to a memory buffer. + * + * @remarks The minus 1 is for the trailing zero terminator we always add. + */ +#if ARCH_BITS == 64 +# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(64)*_1M - 1) +#else +# define RTHTTP_MAX_MEM_DOWNLOAD_SIZE (UINT32_C(32)*_1M - 1) +#endif + +/** Checks whether a cURL return code indicates success. */ +#define CURL_SUCCESS(rcCurl) RT_LIKELY(rcCurl == CURLE_OK) +/** Checks whether a cURL return code indicates failure. */ +#define CURL_FAILURE(rcCurl) RT_UNLIKELY(rcCurl != CURLE_OK) + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTHTTP_VALID_RETURN_RC(hHttp, a_rc) \ + do { \ + AssertPtrReturn((hHttp), (a_rc)); \ + AssertReturn((hHttp)->u32Magic == RTHTTP_MAGIC, (a_rc)); \ + } while (0) + +/** Validates a handle and returns VERR_INVALID_HANDLE if not valid. */ +#define RTHTTP_VALID_RETURN(hHTTP) RTHTTP_VALID_RETURN_RC((hHttp), VERR_INVALID_HANDLE) + +/** Validates a handle and returns (void) if not valid. */ +#define RTHTTP_VALID_RETURN_VOID(hHttp) \ + do { \ + AssertPtrReturnVoid(hHttp); \ + AssertReturnVoid((hHttp)->u32Magic == RTHTTP_MAGIC); \ + } while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +/** @name Windows: Dynamically resolved APIs + * @{ */ +static RTONCE g_WinResolveImportsOnce = RTONCE_INITIALIZER; +static PFNWINHTTPOPEN g_pfnWinHttpOpen = NULL; +static PFNWINHTTPCLOSEHANDLE g_pfnWinHttpCloseHandle = NULL; +static PFNWINHTTPGETPROXYFORURL g_pfnWinHttpGetProxyForUrl = NULL; +static PFNWINHTTPGETDEFAULTPROXYCONFIGURATION g_pfnWinHttpGetDefaultProxyConfiguration = NULL; +static PFNWINHTTPGETIEPROXYCONFIGFORCURRENTUSER g_pfnWinHttpGetIEProxyConfigForCurrentUser = NULL; +/** @} */ +#endif + +#ifdef IPRT_USE_LIBPROXY +/** @name Dynamaically resolved libproxy APIs. + * @{ */ +static RTONCE g_LibProxyResolveImportsOnce = RTONCE_INITIALIZER; +static RTLDRMOD g_hLdrLibProxy = NIL_RTLDRMOD; +static PFNLIBPROXYFACTORYCTOR g_pfnLibProxyFactoryCtor = NULL; +static PFNLIBPROXYFACTORYDTOR g_pfnLibProxyFactoryDtor = NULL; +static PFNLIBPROXYFACTORYGETPROXIES g_pfnLibProxyFactoryGetProxies = NULL; +/** @} */ +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis); +#ifdef RT_OS_DARWIN +static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType); +#endif +static void rtHttpFreeHeaders(PRTHTTPINTERNAL pThis); + + +RTR3DECL(int) RTHttpCreate(PRTHTTP phHttp) +{ + AssertPtrReturn(phHttp, VERR_INVALID_PARAMETER); + + /** @todo r=bird: rainy day: curl_global_init is not thread safe, only a + * problem if multiple threads get here at the same time. */ + int rc = VERR_HTTP_INIT_FAILED; + CURLcode rcCurl = curl_global_init(CURL_GLOBAL_ALL); + if (CURL_SUCCESS(rcCurl)) + { + CURL *pCurl = curl_easy_init(); + if (pCurl) + { + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)RTMemAllocZ(sizeof(RTHTTPINTERNAL)); + if (pThis) + { + pThis->u32Magic = RTHTTP_MAGIC; + pThis->pCurl = pCurl; + pThis->ppHeadersTail = &pThis->pHeaders; + pThis->fHaveSetUserAgent = false; + pThis->fHaveUserAgentHeader = false; + pThis->fUseSystemProxySettings = true; + pThis->cMaxRedirects = 0; /* no automatic redir following */ + pThis->fVerifyPeer = true; + pThis->BodyOutput.pHttp = pThis; + pThis->HeadersOutput.pHttp = pThis; + pThis->uDownloadHttpStatus = UINT32_MAX; + pThis->cbDownloadContent = UINT64_MAX; + pThis->offDownloadContent = 0; + pThis->cbUploadContent = UINT64_MAX; + pThis->offUploadContent = 0; + + /* ask curl to give us back error messages */ + curl_easy_setopt(pThis->pCurl, CURLOPT_ERRORBUFFER, pThis->szErrorBuffer); + + *phHttp = (RTHTTP)pThis; + + return VINF_SUCCESS; + } + rc = VERR_NO_MEMORY; + } + else + rc = VERR_HTTP_INIT_FAILED; + } + curl_global_cleanup(); + return rc; +} + + +RTR3DECL(int) RTHttpReset(RTHTTP hHttp, uint32_t fFlags) +{ + /* Validate the instance handle, state and flags. */ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + AssertReturn(!(fFlags & ~RTHTTP_RESET_F_VALID_MASK), VERR_INVALID_FLAGS); + + /* This resets options, but keeps open connections, cookies, etc. */ + curl_easy_reset(pThis->pCurl); + + /** @todo check if CURLOPT_SSL_VERIFYPEER is affected by curl_easy_reset. */ + + if (!(fFlags & RTHTTP_RESET_F_KEEP_HEADERS)) + rtHttpFreeHeaders(pThis); + + pThis->uDownloadHttpStatus = UINT32_MAX; + pThis->cbDownloadContent = UINT64_MAX; + pThis->offDownloadContent = 0; + pThis->cbUploadContent = UINT64_MAX; + pThis->offUploadContent = 0; + pThis->rcOutput = VINF_SUCCESS; + + /* Tell the proxy configuration code to reapply settings even if they + didn't change as cURL has forgotten them: */ + pThis->fReapplyProxyInfo = true; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpDestroy(RTHTTP hHttp) +{ + if (hHttp == NIL_RTHTTP) + return VINF_SUCCESS; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + Assert(!pThis->fBusy); + + pThis->u32Magic = RTHTTP_MAGIC_DEAD; + + curl_easy_cleanup(pThis->pCurl); + pThis->pCurl = NULL; + + rtHttpFreeHeaders(pThis); + + rtHttpUnsetCaFile(pThis); + Assert(!pThis->pszCaFile); + + if (pThis->pszRedirLocation) + { + RTStrFree(pThis->pszRedirLocation); + pThis->pszRedirLocation = NULL; + } + + RTStrFree(pThis->pszProxyHost); + pThis->pszProxyHost = NULL; + RTStrFree(pThis->pszProxyUsername); + pThis->pszProxyUsername = NULL; + if (pThis->pszProxyPassword) + { + RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2); + RTStrFree(pThis->pszProxyPassword); + pThis->pszProxyPassword = NULL; + } + + RTMemFree(pThis); + + curl_global_cleanup(); + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpAbort(RTHTTP hHttp) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + pThis->fAbort = true; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpGetRedirLocation(RTHTTP hHttp, char **ppszRedirLocation) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + Assert(!pThis->fBusy); + + if (!pThis->pszRedirLocation) + return VERR_HTTP_NOT_FOUND; + + return RTStrDupEx(ppszRedirLocation, pThis->pszRedirLocation); +} + + +RTR3DECL(int) RTHttpSetFollowRedirects(RTHTTP hHttp, uint32_t cMaxRedirects) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + + /* + * Update the redirection settings. + */ + if (pThis->cMaxRedirects != cMaxRedirects) + { + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_MAXREDIRS, (long)cMaxRedirects); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_MAXREDIRS=%u: %d (%#x)\n", cMaxRedirects, rcCurl, rcCurl), + VERR_HTTP_CURL_ERROR); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_FOLLOWLOCATION, (long)(cMaxRedirects > 0)); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_FOLLOWLOCATION=%d: %d (%#x)\n", cMaxRedirects > 0, rcCurl, rcCurl), + VERR_HTTP_CURL_ERROR); + + pThis->cMaxRedirects = cMaxRedirects; + } + return VINF_SUCCESS; +} + + +RTR3DECL(uint32_t) RTHttpGetFollowRedirects(RTHTTP hHttp) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN_RC(pThis, 0); + return pThis->cMaxRedirects; +} + + +/********************************************************************************************************************************* +* Proxy handling. * +*********************************************************************************************************************************/ + +RTR3DECL(int) RTHttpUseSystemProxySettings(RTHTTP hHttp) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + + /* + * Change the settings. + */ + pThis->fUseSystemProxySettings = true; + return VINF_SUCCESS; +} + + +/** + * rtHttpConfigureProxyForUrl: Update cURL proxy settings as needed. + * + * @returns IPRT status code. + * @param pThis The HTTP client instance. + * @param enmProxyType The proxy type. + * @param pszHost The proxy host name. + * @param uPort The proxy port number. + * @param pszUsername The proxy username, or NULL if none. + * @param pszPassword The proxy password, or NULL if none. + */ +static int rtHttpUpdateProxyConfig(PRTHTTPINTERNAL pThis, curl_proxytype enmProxyType, const char *pszHost, + uint32_t uPort, const char *pszUsername, const char *pszPassword) +{ + CURLcode rcCurl; + AssertReturn(pszHost, VERR_INVALID_PARAMETER); + Log(("rtHttpUpdateProxyConfig: pThis=%p type=%d host='%s' port=%u user='%s'%s\n", + pThis, enmProxyType, pszHost, uPort, pszUsername, pszPassword ? " with password" : " without password")); + + if (pThis->fNoProxy) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, (const char *)NULL); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_NOPROXY=NULL: %d (%#x)\n", rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + pThis->fNoProxy = false; + } + + if ( pThis->fReapplyProxyInfo + || enmProxyType != pThis->enmProxyType) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)enmProxyType); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYTYPE=%d: %d (%#x)\n", enmProxyType, rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + pThis->enmProxyType = enmProxyType; + } + + if ( pThis->fReapplyProxyInfo + || uPort != pThis->uProxyPort) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)uPort); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYPORT=%d: %d (%#x)\n", uPort, rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + pThis->uProxyPort = uPort; + } + + if ( pThis->fReapplyProxyInfo + || pszUsername != pThis->pszProxyUsername + || RTStrCmp(pszUsername, pThis->pszProxyUsername)) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, pszUsername); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYUSERNAME=%s: %d (%#x)\n", pszUsername, rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + if (pThis->pszProxyUsername) + { + RTStrFree(pThis->pszProxyUsername); + pThis->pszProxyUsername = NULL; + } + if (pszUsername) + { + pThis->pszProxyUsername = RTStrDup(pszUsername); + AssertReturn(pThis->pszProxyUsername, VERR_NO_STR_MEMORY); + } + } + + if ( pThis->fReapplyProxyInfo + || pszPassword != pThis->pszProxyPassword + || RTStrCmp(pszPassword, pThis->pszProxyPassword)) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, pszPassword); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXYPASSWORD=%s: %d (%#x)\n", pszPassword ? "xxx" : NULL, rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + if (pThis->pszProxyPassword) + { + RTMemWipeThoroughly(pThis->pszProxyPassword, strlen(pThis->pszProxyPassword), 2); + RTStrFree(pThis->pszProxyPassword); + pThis->pszProxyPassword = NULL; + } + if (pszPassword) + { + pThis->pszProxyPassword = RTStrDup(pszPassword); + AssertReturn(pThis->pszProxyPassword, VERR_NO_STR_MEMORY); + } + } + + if ( pThis->fReapplyProxyInfo + || pszHost != pThis->pszProxyHost + || RTStrCmp(pszHost, pThis->pszProxyHost)) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, pszHost); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_PROXY=%s: %d (%#x)\n", pszHost, rcCurl, rcCurl), + VERR_HTTP_CURL_PROXY_CONFIG); + if (pThis->pszProxyHost) + { + RTStrFree(pThis->pszProxyHost); + pThis->pszProxyHost = NULL; + } + if (pszHost) + { + pThis->pszProxyHost = RTStrDup(pszHost); + AssertReturn(pThis->pszProxyHost, VERR_NO_STR_MEMORY); + } + } + + pThis->fReapplyProxyInfo = false; + return VINF_SUCCESS; +} + + +/** + * rtHttpConfigureProxyForUrl: Disables proxying. + * + * @returns IPRT status code. + * @param pThis The HTTP client instance. + */ +static int rtHttpUpdateAutomaticProxyDisable(PRTHTTPINTERNAL pThis) +{ + Log(("rtHttpUpdateAutomaticProxyDisable: pThis=%p\n", pThis)); + + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYTYPE, (long)CURLPROXY_HTTP) == CURLE_OK, VERR_INTERNAL_ERROR_2); + pThis->enmProxyType = CURLPROXY_HTTP; + + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPORT, (long)1080) == CURLE_OK, VERR_INTERNAL_ERROR_2); + pThis->uProxyPort = 1080; + + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYUSERNAME, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2); + if (pThis->pszProxyUsername) + { + RTStrFree(pThis->pszProxyUsername); + pThis->pszProxyUsername = NULL; + } + + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXYPASSWORD, (const char *)NULL) == CURLE_OK, VERR_INTERNAL_ERROR_2); + if (pThis->pszProxyPassword) + { + RTStrFree(pThis->pszProxyPassword); + pThis->pszProxyPassword = NULL; + } + + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_PROXY, "") == CURLE_OK, VERR_INTERNAL_ERROR_2); + if (pThis->pszProxyHost) + { + RTStrFree(pThis->pszProxyHost); + pThis->pszProxyHost = NULL; + } + + /* No proxy for everything! */ + AssertReturn(curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROXY, "*") == CURLE_OK, CURLOPT_PROXY); + pThis->fNoProxy = true; + + return VINF_SUCCESS; +} + + +/** + * See if the host name of the URL is included in the stripped no_proxy list. + * + * The no_proxy list is a colon or space separated list of domain names for + * which there should be no proxying. Given "no_proxy=oracle.com" neither the + * URL "http://www.oracle.com" nor "http://oracle.com" will not be proxied, but + * "http://notoracle.com" will be. + * + * @returns true if the URL is in the no_proxy list, otherwise false. + * @param pszUrl The URL. + * @param pszNoProxyList The stripped no_proxy list. + */ +static bool rtHttpUrlInNoProxyList(const char *pszUrl, const char *pszNoProxyList) +{ + /* + * Check for just '*', diabling proxying for everything. + * (Caller stripped pszNoProxyList.) + */ + if (*pszNoProxyList == '*' && pszNoProxyList[1] == '\0') + return true; + + /* + * Empty list? (Caller stripped it, remember). + */ + if (!*pszNoProxyList) + return false; + + /* + * We now need to parse the URL and extract the host name. + */ + RTURIPARSED Parsed; + int rc = RTUriParse(pszUrl, &Parsed); + AssertRCReturn(rc, false); + char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed); + if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */ + return false; + + bool fRet = false; + size_t const cchHost = strlen(pszHost); + if (cchHost) + { + /* + * The list is comma or space separated, walk it and match host names. + */ + while (*pszNoProxyList != '\0') + { + /* Strip leading slashes, commas and dots. */ + char ch; + while ( (ch = *pszNoProxyList) == ',' + || ch == '.' + || RT_C_IS_SPACE(ch)) + pszNoProxyList++; + + /* Find the end. */ + size_t cch = RTStrOffCharOrTerm(pszNoProxyList, ','); + size_t offNext = RTStrOffCharOrTerm(pszNoProxyList, ' '); + cch = RT_MIN(cch, offNext); + offNext = cch; + + /* Trip trailing spaces, well tabs and stuff. */ + while (cch > 0 && RT_C_IS_SPACE(pszNoProxyList[cch - 1])) + cch--; + + /* Do the matching, if we have anything to work with. */ + if (cch > 0) + { + if ( ( cch == cchHost + && RTStrNICmp(pszNoProxyList, pszHost, cch) == 0) + || ( cch < cchHost + && pszHost[cchHost - cch - 1] == '.' + && RTStrNICmp(pszNoProxyList, &pszHost[cchHost - cch], cch) == 0) ) + { + fRet = true; + break; + } + } + + /* Next. */ + pszNoProxyList += offNext; + } + } + + RTStrFree(pszHost); + return fRet; +} + + +/** + * Configures a proxy given a "URL" like specification. + * + * The format is: + * @verbatim + * [<scheme>"://"][<userid>[@<password>]:]<server>[":"<port>] + * @endverbatim + * + * Where the scheme gives the type of proxy server we're dealing with rather + * than the protocol of the external server we wish to talk to. + * + * @returns IPRT status code. + * @param pThis The HTTP client instance. + * @param pszProxyUrl The proxy server "URL". + */ +static int rtHttpConfigureProxyFromUrl(PRTHTTPINTERNAL pThis, const char *pszProxyUrl) +{ + /* + * Make sure it can be parsed as an URL. + */ + char *pszFreeMe = NULL; + if (!strstr(pszProxyUrl, "://")) + { + static const char s_szPrefix[] = "http://"; + size_t cchProxyUrl = strlen(pszProxyUrl); + pszFreeMe = (char *)RTMemTmpAlloc(sizeof(s_szPrefix) + cchProxyUrl); + if (pszFreeMe) + { + memcpy(pszFreeMe, s_szPrefix, sizeof(s_szPrefix) - 1); + memcpy(&pszFreeMe[sizeof(s_szPrefix) - 1], pszProxyUrl, cchProxyUrl); + pszFreeMe[sizeof(s_szPrefix) - 1 + cchProxyUrl] = '\0'; + pszProxyUrl = pszFreeMe; + } + else + return VERR_NO_TMP_MEMORY; + } + + RTURIPARSED Parsed; + int rc = RTUriParse(pszProxyUrl, &Parsed); + if (RT_SUCCESS(rc)) + { + char *pszHost = RTUriParsedAuthorityHost(pszProxyUrl, &Parsed); + if (pszHost) + { + /* + * We've got a host name, try get the rest. + */ + char *pszUsername = RTUriParsedAuthorityUsername(pszProxyUrl, &Parsed); + char *pszPassword = RTUriParsedAuthorityPassword(pszProxyUrl, &Parsed); + uint32_t uProxyPort = RTUriParsedAuthorityPort(pszProxyUrl, &Parsed); + bool fUnknownProxyType = false; + curl_proxytype enmProxyType; + if (RTUriIsSchemeMatch(pszProxyUrl, "http")) + { + enmProxyType = CURLPROXY_HTTP; + if (uProxyPort == UINT32_MAX) + uProxyPort = 80; + } +#ifdef CURL_AT_LEAST_VERSION +# if CURL_AT_LEAST_VERSION(7,52,0) + else if (RTUriIsSchemeMatch(pszProxyUrl, "https")) + { + enmProxyType = CURLPROXY_HTTPS; + if (uProxyPort == UINT32_MAX) + uProxyPort = 443; + } +# endif +#endif + else if ( RTUriIsSchemeMatch(pszProxyUrl, "socks4") + || RTUriIsSchemeMatch(pszProxyUrl, "socks")) + enmProxyType = CURLPROXY_SOCKS4; + else if (RTUriIsSchemeMatch(pszProxyUrl, "socks4a")) + enmProxyType = CURLPROXY_SOCKS4A; + else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5")) + enmProxyType = CURLPROXY_SOCKS5; + else if (RTUriIsSchemeMatch(pszProxyUrl, "socks5h")) + enmProxyType = CURLPROXY_SOCKS5_HOSTNAME; + else + { + fUnknownProxyType = true; + enmProxyType = CURLPROXY_HTTP; + if (uProxyPort == UINT32_MAX) + uProxyPort = 8080; + } + + /* Guess the port from the proxy type if not given. */ + if (uProxyPort == UINT32_MAX) + uProxyPort = 1080; /* CURL_DEFAULT_PROXY_PORT */ + + rc = rtHttpUpdateProxyConfig(pThis, enmProxyType, pszHost, uProxyPort, pszUsername, pszPassword); + if (RT_SUCCESS(rc) && fUnknownProxyType) + rc = VWRN_WRONG_TYPE; + + RTStrFree(pszUsername); + RTStrFree(pszPassword); + RTStrFree(pszHost); + } + else + AssertMsgFailed(("RTUriParsedAuthorityHost('%s',) -> NULL\n", pszProxyUrl)); + } + else + AssertMsgFailed(("RTUriParse('%s',) -> %Rrc\n", pszProxyUrl, rc)); + + if (pszFreeMe) + RTMemTmpFree(pszFreeMe); + return rc; +} + + +RTR3DECL(int) RTHttpSetProxyByUrl(RTHTTP hHttp, const char *pszUrl) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertPtrNullReturn(pszUrl, VERR_INVALID_PARAMETER); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + + if (!pszUrl || !*pszUrl) + return RTHttpUseSystemProxySettings(pThis); + if (RTStrNICmpAscii(pszUrl, RT_STR_TUPLE("direct://")) == 0) + return rtHttpUpdateAutomaticProxyDisable(pThis); + return rtHttpConfigureProxyFromUrl(pThis, pszUrl); +} + + +/** + * Consults enviornment variables that cURL/lynx/wget/lynx uses for figuring out + * the proxy config. + * + * @returns IPRT status code. + * @param pThis The HTTP client instance. + * @param pszUrl The URL to configure a proxy for. + */ +static int rtHttpConfigureProxyForUrlFromEnv(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + char szTmp[_1K]; + + /* + * First we consult the "no_proxy" / "NO_PROXY" environment variable. + */ + const char *pszNoProxyVar; + size_t cchActual; + char *pszNoProxyFree = NULL; + char *pszNoProxy = szTmp; + int rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "no_proxy", szTmp, sizeof(szTmp), &cchActual); + if (rc == VERR_ENV_VAR_NOT_FOUND) + rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar = "NO_PROXY", szTmp, sizeof(szTmp), &cchActual); + if (rc == VERR_BUFFER_OVERFLOW) + { + pszNoProxyFree = pszNoProxy = (char *)RTMemTmpAlloc(cchActual + _1K); + AssertReturn(pszNoProxy, VERR_NO_TMP_MEMORY); + rc = RTEnvGetEx(RTENV_DEFAULT, pszNoProxyVar, pszNoProxy, cchActual + _1K, NULL); + } + AssertMsg(rc == VINF_SUCCESS || rc == VERR_ENV_VAR_NOT_FOUND, ("rc=%Rrc\n", rc)); + bool fNoProxy = false; + if (RT_SUCCESS(rc)) + fNoProxy = rtHttpUrlInNoProxyList(pszUrl, RTStrStrip(pszNoProxy)); + RTMemTmpFree(pszNoProxyFree); + if (!fNoProxy) + { + /* + * Get the schema specific specific env var, falling back on the + * generic all_proxy if not found. + */ + const char *apszEnvVars[4]; + unsigned cEnvVars = 0; + if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("http:"))) + apszEnvVars[cEnvVars++] = "http_proxy"; /* Skip HTTP_PROXY because of cgi paranoia */ + else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("https:"))) + { + apszEnvVars[cEnvVars++] = "https_proxy"; + apszEnvVars[cEnvVars++] = "HTTPS_PROXY"; + } + else if (!RTStrNICmp(pszUrl, RT_STR_TUPLE("ftp:"))) + { + apszEnvVars[cEnvVars++] = "ftp_proxy"; + apszEnvVars[cEnvVars++] = "FTP_PROXY"; + } + else + AssertMsgFailedReturn(("Unknown/unsupported schema in URL: '%s'\n", pszUrl), VERR_NOT_SUPPORTED); + apszEnvVars[cEnvVars++] = "all_proxy"; + apszEnvVars[cEnvVars++] = "ALL_PROXY"; + + /* + * We try the env vars out and goes with the first one we can make sense out of. + * If we cannot make sense of any, we return the first unexpected rc we got. + */ + rc = VINF_SUCCESS; + for (uint32_t i = 0; i < cEnvVars; i++) + { + size_t cchValue; + int rc2 = RTEnvGetEx(RTENV_DEFAULT, apszEnvVars[i], szTmp, sizeof(szTmp) - sizeof("http://"), &cchValue); + if (RT_SUCCESS(rc2)) + { + if (cchValue != 0) + { + /* Add a http:// prefix so RTUriParse groks it (cheaper to do it here). */ + if (!strstr(szTmp, "://")) + { + memmove(&szTmp[sizeof("http://") - 1], szTmp, cchValue + 1); + memcpy(szTmp, RT_STR_TUPLE("http://")); + } + + rc2 = rtHttpConfigureProxyFromUrl(pThis, szTmp); + if (RT_SUCCESS(rc2)) + rc = rc2; + } + /* + * The variable is empty. Guess that means no proxying wanted. + */ + else + { + rc = rtHttpUpdateAutomaticProxyDisable(pThis); + break; + } + } + else + AssertMsgStmt(rc2 == VERR_ENV_VAR_NOT_FOUND, ("%Rrc\n", rc2), if (RT_SUCCESS(rc)) rc = rc2); + } + } + /* + * The host is the no-proxy list, it seems. + */ + else + rc = rtHttpUpdateAutomaticProxyDisable(pThis); + + return rc; +} + +#ifdef IPRT_USE_LIBPROXY + +/** + * @callback_method_impl{FNRTONCE, + * Attempts to load libproxy.so.1 and resolves APIs} + */ +static DECLCALLBACK(int) rtHttpLibProxyResolveImports(void *pvUser) +{ + RTLDRMOD hMod; + int rc = RTLdrLoadSystem("libproxy.so.1", false /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "px_proxy_factory_new", (void **)&g_pfnLibProxyFactoryCtor); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "px_proxy_factory_free", (void **)&g_pfnLibProxyFactoryDtor); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "px_proxy_factory_get_proxies", (void **)&g_pfnLibProxyFactoryGetProxies); + if (RT_SUCCESS(rc)) + { + RTMEM_WILL_LEAK(hMod); + g_hLdrLibProxy = hMod; + } + else + RTLdrClose(hMod); + AssertRC(rc); + } + + NOREF(pvUser); + return rc; +} + +/** + * Reconfigures the cURL proxy settings for the given URL, libproxy style. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback. + * @param pThis The HTTP client instance. + * @param pszUrl The URL. + */ +static int rtHttpLibProxyConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + int rcRet = VINF_NOT_SUPPORTED; + + int rc = RTOnce(&g_LibProxyResolveImportsOnce, rtHttpLibProxyResolveImports, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Instance the factory and ask for a list of proxies. + */ + PLIBPROXYFACTORY pFactory = g_pfnLibProxyFactoryCtor(); + if (pFactory) + { + char **papszProxies = g_pfnLibProxyFactoryGetProxies(pFactory, pszUrl); + g_pfnLibProxyFactoryDtor(pFactory); + if (papszProxies) + { + /* + * Look for something we can use. + */ + for (unsigned i = 0; papszProxies[i]; i++) + { + if (strncmp(papszProxies[i], RT_STR_TUPLE("direct://")) == 0) + rcRet = rtHttpUpdateAutomaticProxyDisable(pThis); + else if ( strncmp(papszProxies[i], RT_STR_TUPLE("http://")) == 0 + || strncmp(papszProxies[i], RT_STR_TUPLE("socks5://")) == 0 + || strncmp(papszProxies[i], RT_STR_TUPLE("socks4://")) == 0 + || strncmp(papszProxies[i], RT_STR_TUPLE("socks://")) == 0 /** @todo same problem as on OS X. */ + ) + rcRet = rtHttpConfigureProxyFromUrl(pThis, papszProxies[i]); + else + continue; + if (rcRet != VINF_NOT_SUPPORTED) + break; + } + + /* free the result. */ + for (unsigned i = 0; papszProxies[i]; i++) + free(papszProxies[i]); + free(papszProxies); + } + } + } + + return rcRet; +} + +#endif /* IPRT_USE_LIBPROXY */ + +#ifdef RT_OS_DARWIN + +/** + * Get a boolean like integer value from a dictionary. + * + * @returns true / false. + * @param hDict The dictionary. + * @param pvKey The dictionary value key. + */ +static bool rtHttpDarwinGetBooleanFromDict(CFDictionaryRef hDict, void const *pvKey, bool fDefault) +{ + CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDict, pvKey); + if (hNum) + { + int fEnabled; + if (!CFNumberGetValue(hNum, kCFNumberIntType, &fEnabled)) + return fDefault; + return fEnabled != 0; + } + return fDefault; +} + + +/** + * Creates a CFURL object for an URL. + * + * @returns CFURL object reference. + * @param pszUrl The URL. + */ +static CFURLRef rtHttpDarwinUrlToCFURL(const char *pszUrl) +{ + /* CFURLCreateStringByAddingPercentEscapes is deprecated, so try use CFURLCreateWithBytes + as it doesn't validate as much as as CFUrlCreateWithString does. */ +#if 0 + CFURLRef hUrl = NULL; + CFStringRef hStrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pszUrl, kCFStringEncodingUTF8); + if (hStrUrl) + { + CFStringRef hStrUrlEscaped = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, hStrUrl, + NULL /*charactersToLeaveUnescaped*/, + NULL /*legalURLCharactersToBeEscaped*/, + kCFStringEncodingUTF8); + if (hStrUrlEscaped) + { + hUrl = CFURLCreateWithString(kCFAllocatorDefault, hStrUrlEscaped, NULL /*baseURL*/); + Assert(hUrl); + CFRelease(hStrUrlEscaped); + } + else + AssertFailed(); + CFRelease(hStrUrl); + } + else + AssertFailed(); +#else + CFURLRef hUrl = CFURLCreateWithBytes(kCFAllocatorDefault, (const uint8_t *)pszUrl, strlen(pszUrl), + kCFStringEncodingUTF8, NULL /*baseURL*/); + Assert(hUrl); +#endif + return hUrl; +} + + +/** + * For passing results from rtHttpDarwinPacCallback to + * rtHttpDarwinExecuteProxyAutoConfigurationUrl. + */ +typedef struct RTHTTPDARWINPACRESULT +{ + CFArrayRef hArrayProxies; + CFErrorRef hError; +} RTHTTPDARWINPACRESULT; +typedef RTHTTPDARWINPACRESULT *PRTHTTPDARWINPACRESULT; + +/** + * Stupid callback for getting the result from + * CFNetworkExecuteProxyAutoConfigurationURL. + * + * @param pvUser Pointer to a RTHTTPDARWINPACRESULT on the stack of + * rtHttpDarwinExecuteProxyAutoConfigurationUrl. + * @param hArrayProxies The result array. + * @param hError Errors, if any. + */ +static void rtHttpDarwinPacCallback(void *pvUser, CFArrayRef hArrayProxies, CFErrorRef hError) +{ + PRTHTTPDARWINPACRESULT pResult = (PRTHTTPDARWINPACRESULT)pvUser; + + Assert(pResult->hArrayProxies == NULL); + if (hArrayProxies) + pResult->hArrayProxies = (CFArrayRef)CFRetain(hArrayProxies); + + Assert(pResult->hError == NULL); + if (hError) + pResult->hError = (CFErrorRef)CFRetain(hError); + + CFRunLoopStop(CFRunLoopGetCurrent()); +} + + +/** + * Executes a PAC script and returning the proxies it suggests. + * + * @returns Array of proxy configs (CFProxySupport.h style). + * @param hUrlTarget The URL we're about to use. + * @param hUrlScript The PAC script URL. + */ +static CFArrayRef rtHttpDarwinExecuteProxyAutoConfigurationUrl(CFURLRef hUrlTarget, CFURLRef hUrlScript) +{ + char szTmp[256]; + if (LogIsFlowEnabled()) + { + szTmp[0] = '\0'; + CFStringGetCString(CFURLGetString(hUrlScript), szTmp, sizeof(szTmp), kCFStringEncodingUTF8); + LogFlow(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: hUrlScript=%p:%s\n", hUrlScript, szTmp)); + } + + /* + * Use CFNetworkExecuteProxyAutoConfigurationURL here so we don't have to + * download the script ourselves and mess around with too many CF APIs. + */ + CFRunLoopRef hRunLoop = CFRunLoopGetCurrent(); + AssertReturn(hRunLoop, NULL); + + RTHTTPDARWINPACRESULT Result = { NULL, NULL }; + CFStreamClientContext Ctx = { 0, &Result, NULL, NULL, NULL }; + CFRunLoopSourceRef hRunLoopSrc = CFNetworkExecuteProxyAutoConfigurationURL(hUrlScript, hUrlTarget, + rtHttpDarwinPacCallback, &Ctx); + AssertReturn(hRunLoopSrc, NULL); + + CFStringRef kMode = CFSTR("com.apple.dts.CFProxySupportTool"); + CFRunLoopAddSource(hRunLoop, hRunLoopSrc, kMode); + CFRunLoopRunInMode(kMode, 1.0e10, false); /* callback will force a return. */ + CFRunLoopRemoveSource(hRunLoop, hRunLoopSrc, kMode); + + /** @todo convert errors, maybe even fail. */ + + /* + * Autoconfig (or missing wpad server) typically results in: + * domain:kCFErrorDomainCFNetwork; code=kCFHostErrorUnknown (2). + * + * In the autoconfig case, it looks like we're getting two entries, first + * one that's http://wpad/wpad.dat and a noproxy entry. So, no reason to + * be very upset if this fails, just continue trying alternatives. + */ + if (Result.hError) + { + if (LogIsEnabled()) + { + szTmp[0] = '\0'; + CFStringGetCString(CFErrorCopyDescription(Result.hError), szTmp, sizeof(szTmp), kCFStringEncodingUTF8); + Log(("rtHttpDarwinExecuteProxyAutoConfigurationUrl: error! code=%ld desc='%s'\n", (long)CFErrorGetCode(Result.hError), szTmp)); + } + CFRelease(Result.hError); + } + return Result.hArrayProxies; +} + + +/** + * Attempt to configure the proxy according to @a hDictProxy. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and + * the caller should try out alternative proxy configs and fallbacks. + * @param pThis The HTTP client instance. + * @param hDictProxy The proxy configuration (see CFProxySupport.h). + * @param hUrlTarget The URL we're about to use. + * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e. + * javascript URL). This is set when we're processing + * the output from a PAC script. + */ +static int rtHttpDarwinTryConfigProxy(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxy, CFURLRef hUrlTarget, bool fIgnorePacType) +{ + CFStringRef hStrProxyType = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyTypeKey); + AssertReturn(hStrProxyType, VINF_NOT_SUPPORTED); + + /* + * No proxy is fairly simple and common. + */ + if (CFEqual(hStrProxyType, kCFProxyTypeNone)) + return rtHttpUpdateAutomaticProxyDisable(pThis); + + /* + * PAC URL means recursion, however we only do one level. + */ + if (CFEqual(hStrProxyType, kCFProxyTypeAutoConfigurationURL)) + { + AssertReturn(!fIgnorePacType, VINF_NOT_SUPPORTED); + + CFURLRef hUrlScript = (CFURLRef)CFDictionaryGetValue(hDictProxy, kCFProxyAutoConfigurationURLKey); + AssertReturn(hUrlScript, VINF_NOT_SUPPORTED); + + int rcRet = VINF_NOT_SUPPORTED; + CFArrayRef hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript); + if (hArray) + { + rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/); + CFRelease(hArray); + } + return rcRet; + } + + /* + * Determine the proxy type (not entirely sure about type == proxy type and + * not scheme/protocol)... + */ + curl_proxytype enmProxyType = CURLPROXY_HTTP; + uint32_t uDefaultProxyPort = 8080; + if ( CFEqual(hStrProxyType, kCFProxyTypeHTTP) + || CFEqual(hStrProxyType, kCFProxyTypeHTTPS)) + { /* defaults */ } + else if (CFEqual(hStrProxyType, kCFProxyTypeSOCKS)) + { + /** @todo All we get from darwin is 'SOCKS', no idea whether it's SOCK4 or + * SOCK5 on the other side... Selecting SOCKS5 for now. */ + enmProxyType = CURLPROXY_SOCKS5; + uDefaultProxyPort = 1080; + } + /* Unknown proxy type. */ + else + return VINF_NOT_SUPPORTED; + + /* + * Extract the proxy configuration. + */ + /* The proxy host name. */ + char szHostname[_1K]; + CFStringRef hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyHostNameKey); + AssertReturn(hStr, VINF_NOT_SUPPORTED); + AssertReturn(CFStringGetCString(hStr, szHostname, sizeof(szHostname), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED); + + /* Get the port number (optional). */ + SInt32 iProxyPort; + CFNumberRef hNum = (CFNumberRef)CFDictionaryGetValue(hDictProxy, kCFProxyPortNumberKey); + if (hNum && CFNumberGetValue(hNum, kCFNumberSInt32Type, &iProxyPort)) + AssertMsgStmt(iProxyPort > 0 && iProxyPort < _64K, ("%d\n", iProxyPort), iProxyPort = uDefaultProxyPort); + else + iProxyPort = uDefaultProxyPort; + + /* The proxy username. */ + char szUsername[256]; + hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyUsernameKey); + if (hStr) + AssertReturn(CFStringGetCString(hStr, szUsername, sizeof(szUsername), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED); + else + szUsername[0] = '\0'; + + /* The proxy password. */ + char szPassword[384]; + hStr = (CFStringRef)CFDictionaryGetValue(hDictProxy, kCFProxyPasswordKey); + if (hStr) + AssertReturn(CFStringGetCString(hStr, szPassword, sizeof(szPassword), kCFStringEncodingUTF8), VINF_NOT_SUPPORTED); + else + szPassword[0] = '\0'; + + /* + * Apply the proxy config. + */ + return rtHttpUpdateProxyConfig(pThis, enmProxyType, szHostname, iProxyPort, + szUsername[0] ? szUsername : NULL, szPassword[0] ? szPassword : NULL); +} + + +/** + * Try do proxy config for our HTTP client instance given an array of proxies. + * + * This is used with the output from a CFProxySupport.h API. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if not able to configure it and + * we might want to try out fallbacks. + * @param pThis The HTTP client instance. + * @param hArrayProxies The proxies CFPRoxySupport have given us. + * @param hUrlTarget The URL we're about to use. + * @param fIgnorePacType Whether to ignore PAC type proxy entries (i.e. + * javascript URL). This is set when we're processing + * the output from a PAC script. + */ +static int rtHttpDarwinTryConfigProxies(PRTHTTPINTERNAL pThis, CFArrayRef hArrayProxies, CFURLRef hUrlTarget, bool fIgnorePacType) +{ + int rcRet = VINF_NOT_SUPPORTED; + CFIndex const cEntries = CFArrayGetCount(hArrayProxies); + LogFlow(("rtHttpDarwinTryConfigProxies: cEntries=%d\n", cEntries)); + for (CFIndex i = 0; i < cEntries; i++) + { + CFDictionaryRef hDictProxy = (CFDictionaryRef)CFArrayGetValueAtIndex(hArrayProxies, i); + AssertContinue(hDictProxy); + + rcRet = rtHttpDarwinTryConfigProxy(pThis, hDictProxy, hUrlTarget, fIgnorePacType); + if (rcRet != VINF_NOT_SUPPORTED) + break; + } + return rcRet; +} + + +/** + * Inner worker for rtHttpWinConfigureProxyForUrl. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback. + * @param pThis The HTTP client instance. + * @param pszUrl The URL. + */ +static int rtHttpDarwinConfigureProxyForUrlWorker(PRTHTTPINTERNAL pThis, CFDictionaryRef hDictProxies, + const char *pszUrl, const char *pszHost) +{ + CFArrayRef hArray; + + /* + * From what I can tell, the CFNetworkCopyProxiesForURL API doesn't apply + * proxy exclusion rules (tested on 10.9). So, do that manually. + */ + RTNETADDRU HostAddr; + int fIsHostIpv4Address = -1; + char szTmp[_4K]; + + /* If we've got a simple hostname, something containing no dots, we must check + whether such simple hostnames are excluded from proxying by default or not. */ + if (strchr(pszHost, '.') == NULL) + { + if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesExcludeSimpleHostnames, false)) + return rtHttpUpdateAutomaticProxyDisable(pThis); + fIsHostIpv4Address = false; + } + + /* Consult the exclusion list. This is an array of strings. + This is very similar to what we do on windows. */ + hArray = (CFArrayRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesExceptionsList); + if (hArray) + { + CFIndex const cEntries = CFArrayGetCount(hArray); + for (CFIndex i = 0; i < cEntries; i++) + { + CFStringRef hStr = (CFStringRef)CFArrayGetValueAtIndex(hArray, i); + AssertContinue(hStr); + AssertContinue(CFStringGetCString(hStr, szTmp, sizeof(szTmp), kCFStringEncodingUTF8)); + RTStrToLower(szTmp); + + bool fRet; + if ( strchr(szTmp, '*') + || strchr(szTmp, '?')) + fRet = RTStrSimplePatternMatch(szTmp, pszHost); + else + { + if (fIsHostIpv4Address == -1) + fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4)); + RTNETADDRIPV4 Network, Netmask; + if ( fIsHostIpv4Address + && RT_SUCCESS(RTCidrStrToIPv4(szTmp, &Network, &Netmask)) ) + fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u; + else + fRet = strcmp(szTmp, pszHost) == 0; + } + if (fRet) + return rtHttpUpdateAutomaticProxyDisable(pThis); + } + } + +#if 0 /* The start of a manual alternative to CFNetworkCopyProxiesForURL below, hopefully we won't need this. */ + /* + * Is proxy auto config (PAC) enabled? If so, we must consult it first. + */ + if (rtHttpDarwinGetBooleanFromDict(hDictProxies, kSCPropNetProxiesProxyAutoConfigEnable, false)) + { + /* Convert the auto config url string to a CFURL object. */ + CFStringRef hStrAutoConfigUrl = (CFStringRef)CFDictionaryGetValue(hDictProxies, kSCPropNetProxiesProxyAutoConfigURLString); + if (hStrAutoConfigUrl) + { + if (CFStringGetCString(hStrAutoConfigUrl, szTmp, sizeof(szTmp), kCFStringEncodingUTF8)) + { + CFURLRef hUrlScript = rtHttpDarwinUrlToCFURL(szTmp); + if (hUrlScript) + { + int rcRet = VINF_NOT_SUPPORTED; + CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl); + if (hUrlTarget) + { + /* Work around for <rdar://problem/5530166>, whatever that is. Initializes + some internal CFNetwork state, they say. See CFPRoxySupportTool example. */ + hArray = CFNetworkCopyProxiesForURL(hUrlTarget, NULL); + if (hArray) + CFRelease(hArray); + + hArray = rtHttpDarwinExecuteProxyAutoConfigurationUrl(hUrlTarget, hUrlScript); + if (hArray) + { + rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, true /*fIgnorePacType*/); + CFRelease(hArray); + } + } + CFRelease(hUrlScript); + if (rcRet != VINF_NOT_SUPPORTED) + return rcRet; + } + } + } + } + + /* + * Try static proxy configs. + */ + /** @todo later if needed. */ + return VERR_NOT_SUPPORTED; + +#else + /* + * Simple solution - "just" use CFNetworkCopyProxiesForURL. + */ + CFURLRef hUrlTarget = rtHttpDarwinUrlToCFURL(pszUrl); + AssertReturn(hUrlTarget, VERR_INTERNAL_ERROR); + int rcRet = VINF_NOT_SUPPORTED; + + /* Work around for <rdar://problem/5530166>, whatever that is. Initializes + some internal CFNetwork state, they say. See CFPRoxySupportTool example. */ + CFDictionaryRef hDictNull = (CFDictionaryRef)(42-42); /*workaround for -Wnonnull warning in Clang 11. */ + hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictNull); + if (hArray) + CFRelease(hArray); + + /* The actual run. */ + hArray = CFNetworkCopyProxiesForURL(hUrlTarget, hDictProxies); + if (hArray) + { + rcRet = rtHttpDarwinTryConfigProxies(pThis, hArray, hUrlTarget, false /*fIgnorePacType*/); + CFRelease(hArray); + } + CFRelease(hUrlTarget); + + return rcRet; +#endif +} + +/** + * Reconfigures the cURL proxy settings for the given URL, OS X style. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback. + * @param pThis The HTTP client instance. + * @param pszUrl The URL. + */ +static int rtHttpDarwinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + /* + * Parse the URL, if there isn't any host name (like for file:///xxx.txt) + * we don't need to run thru proxy settings to know what to do. + */ + RTURIPARSED Parsed; + int rc = RTUriParse(pszUrl, &Parsed); + AssertRCReturn(rc, false); + if (Parsed.cchAuthorityHost == 0) + return rtHttpUpdateAutomaticProxyDisable(pThis); + char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed); + AssertReturn(pszHost, VERR_NO_STR_MEMORY); + RTStrToLower(pszHost); + + /* + * Get a copy of the proxy settings (10.6 API). + */ + CFDictionaryRef hDictProxies = CFNetworkCopySystemProxySettings(); /* Alt for 10.5: SCDynamicStoreCopyProxies(NULL); */ + if (hDictProxies) + rc = rtHttpDarwinConfigureProxyForUrlWorker(pThis, hDictProxies, pszUrl, pszHost); + else + rc = VINF_NOT_SUPPORTED; + CFRelease(hDictProxies); + + RTStrFree(pszHost); + return rc; +} + +#endif /* RT_OS_DARWIN */ + +#ifdef RT_OS_WINDOWS + +/** + * @callback_method_impl{FNRTONCE, Loads WinHttp.dll and resolves APIs} + */ +static DECLCALLBACK(int) rtHttpWinResolveImports(void *pvUser) +{ + /* + * winhttp.dll is not present on NT4 and probably was first introduced with XP. + */ + RTLDRMOD hMod; + int rc = RTLdrLoadSystem("winhttp.dll", true /*fNoUnload*/, &hMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hMod, "WinHttpOpen", (void **)&g_pfnWinHttpOpen); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "WinHttpCloseHandle", (void **)&g_pfnWinHttpCloseHandle); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "WinHttpGetProxyForUrl", (void **)&g_pfnWinHttpGetProxyForUrl); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "WinHttpGetDefaultProxyConfiguration", (void **)&g_pfnWinHttpGetDefaultProxyConfiguration); + if (RT_SUCCESS(rc)) + rc = RTLdrGetSymbol(hMod, "WinHttpGetIEProxyConfigForCurrentUser", (void **)&g_pfnWinHttpGetIEProxyConfigForCurrentUser); + RTLdrClose(hMod); + AssertRC(rc); + } + else + AssertMsg(g_enmWinVer < kRTWinOSType_XP, ("%Rrc\n", rc)); + + NOREF(pvUser); + return rc; +} + + +/** + * Matches the URL against the given Windows by-pass list. + * + * @returns true if we should by-pass the proxy for this URL, false if not. + * @param pszUrl The URL. + * @param pwszBypass The Windows by-pass list. + */ +static bool rtHttpWinIsUrlInBypassList(const char *pszUrl, PCRTUTF16 pwszBypass) +{ + /* + * Don't bother parsing the URL if we've actually got nothing to work with + * in the by-pass list. + */ + if (!pwszBypass) + return false; + + RTUTF16 wc; + while ( (wc = *pwszBypass) != '\0' + && ( RTUniCpIsSpace(wc) + || wc == ';') ) + pwszBypass++; + if (wc == '\0') + return false; + + /* + * We now need to parse the URL and extract the host name. + */ + RTURIPARSED Parsed; + int rc = RTUriParse(pszUrl, &Parsed); + AssertRCReturn(rc, false); + char *pszHost = RTUriParsedAuthorityHost(pszUrl, &Parsed); + if (!pszHost) /* Don't assert, in case of file:///xxx or similar blunder. */ + return false; + RTStrToLower(pszHost); + + bool fRet = false; + char *pszBypassFree; + rc = RTUtf16ToUtf8(pwszBypass, &pszBypassFree); + if (RT_SUCCESS(rc)) + { + /* + * Walk the by-pass list. + * + * According to https://msdn.microsoft.com/en-us/library/aa384098(v=vs.85).aspx + * a by-pass list is semicolon delimited list. The entries are either host + * names or IP addresses, and may use wildcard ('*', '?', I guess). There + * special "<local>" entry matches anything without a dot. + */ + RTNETADDRU HostAddr = { 0, 0 }; + int fIsHostIpv4Address = -1; + char *pszEntry = pszBypassFree; + while (*pszEntry != '\0') + { + /* + * Find end of entry. + */ + char ch; + size_t cchEntry = 1; + while ( (ch = pszEntry[cchEntry]) != '\0' + && ch != ';' + && !RT_C_IS_SPACE(ch)) + cchEntry++; + + char chSaved = pszEntry[cchEntry]; + pszEntry[cchEntry] = '\0'; + RTStrToLower(pszEntry); + + if ( cchEntry == sizeof("<local>") - 1 + && memcmp(pszEntry, RT_STR_TUPLE("<local>")) == 0) + fRet = strchr(pszHost, '.') == NULL; + else if ( memchr(pszEntry, '*', cchEntry) != NULL + || memchr(pszEntry, '?', cchEntry) != NULL) + fRet = RTStrSimplePatternMatch(pszEntry, pszHost); + else + { + if (fIsHostIpv4Address == -1) + fIsHostIpv4Address = RT_SUCCESS(RTNetStrToIPv4Addr(pszHost, &HostAddr.IPv4)); + RTNETADDRIPV4 Network, Netmask; + if ( fIsHostIpv4Address + && RT_SUCCESS(RTCidrStrToIPv4(pszEntry, &Network, &Netmask)) ) + fRet = (HostAddr.IPv4.u & Netmask.u) == Network.u; + else + fRet = strcmp(pszEntry, pszHost) == 0; + } + + pszEntry[cchEntry] = chSaved; + if (fRet) + break; + + /* + * Next entry. + */ + pszEntry += cchEntry; + while ( (ch = *pszEntry) != '\0' + && ( ch == ';' + || RT_C_IS_SPACE(ch)) ) + pszEntry++; + } + + RTStrFree(pszBypassFree); + } + + RTStrFree(pszHost); + return false; +} + + +/** + * Searches a Windows proxy server list for the best fitting proxy to use, then + * reconfigures the HTTP client instance to use it. + * + * @returns IPRT status code, VINF_NOT_SUPPORTED if we need to consult fallback. + * @param pThis The HTTP client instance. + * @param pszUrl The URL needing proxying. + * @param pwszProxies The list of proxy servers to choose from. + */ +static int rtHttpWinSelectProxyFromList(PRTHTTPINTERNAL pThis, const char *pszUrl, PCRTUTF16 pwszProxies) +{ + /* + * Fend off empty strings (very unlikely, but just in case). + */ + if (!pwszProxies) + return VINF_NOT_SUPPORTED; + + RTUTF16 wc; + while ( (wc = *pwszProxies) != '\0' + && ( RTUniCpIsSpace(wc) + || wc == ';') ) + pwszProxies++; + if (wc == '\0') + return VINF_NOT_SUPPORTED; + + /* + * We now need to parse the URL and extract the scheme. + */ + RTURIPARSED Parsed; + int rc = RTUriParse(pszUrl, &Parsed); + AssertRCReturn(rc, false); + char *pszUrlScheme = RTUriParsedScheme(pszUrl, &Parsed); + AssertReturn(pszUrlScheme, VERR_NO_STR_MEMORY); + size_t const cchUrlScheme = strlen(pszUrlScheme); + + int rcRet = VINF_NOT_SUPPORTED; + char *pszProxiesFree; + rc = RTUtf16ToUtf8(pwszProxies, &pszProxiesFree); + if (RT_SUCCESS(rc)) + { + /* + * Walk the server list. + * + * According to https://msdn.microsoft.com/en-us/library/aa383912(v=vs.85).aspx + * this is also a semicolon delimited list. The entries are on the form: + * [<scheme>=][<scheme>"://"]<server>[":"<port>] + */ + bool fBestEntryHasSameScheme = false; + const char *pszBestEntry = NULL; + char *pszEntry = pszProxiesFree; + while (*pszEntry != '\0') + { + /* + * Find end of entry. We include spaces here in addition to ';'. + */ + char ch; + size_t cchEntry = 1; + while ( (ch = pszEntry[cchEntry]) != '\0' + && ch != ';' + && !RT_C_IS_SPACE(ch)) + cchEntry++; + + char const chSaved = pszEntry[cchEntry]; + pszEntry[cchEntry] = '\0'; + + /* Parse the entry. */ + const char *pszEndOfScheme = strstr(pszEntry, "://"); + const char *pszEqual = (const char *)memchr(pszEntry, '=', + pszEndOfScheme ? pszEndOfScheme - pszEntry : cchEntry); + if (pszEqual) + { + if ( (uintptr_t)(pszEqual - pszEntry) == cchUrlScheme + && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0) + { + pszBestEntry = pszEqual + 1; + break; + } + } + else + { + bool fSchemeMatch = pszEndOfScheme + && (uintptr_t)(pszEndOfScheme - pszEntry) == cchUrlScheme + && RTStrNICmp(pszEntry, pszUrlScheme, cchUrlScheme) == 0; + if ( !pszBestEntry + || ( !fBestEntryHasSameScheme + && fSchemeMatch) ) + { + pszBestEntry = pszEntry; + fBestEntryHasSameScheme = fSchemeMatch; + } + } + + /* + * Next entry. + */ + if (!chSaved) + break; + pszEntry += cchEntry + 1; + while ( (ch = *pszEntry) != '\0' + && ( ch == ';' + || RT_C_IS_SPACE(ch)) ) + pszEntry++; + } + + /* + * If we found something, try use it. + */ + if (pszBestEntry) + rcRet = rtHttpConfigureProxyFromUrl(pThis, pszBestEntry); + + RTStrFree(pszProxiesFree); + } + + RTStrFree(pszUrlScheme); + return rc; +} + + +/** + * Reconfigures the cURL proxy settings for the given URL, Windows style. + * + * @returns IPRT status code. VINF_NOT_SUPPORTED if we should try fallback. + * @param pThis The HTTP client instance. + * @param pszUrl The URL. + */ +static int rtHttpWinConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + int rcRet = VINF_NOT_SUPPORTED; + + int rc = RTOnce(&g_WinResolveImportsOnce, rtHttpWinResolveImports, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Try get some proxy info for the URL. We first try getting the IE + * config and seeing if we can use WinHttpGetIEProxyConfigForCurrentUser + * in some way, if we can we prepare ProxyOptions with a non-zero dwFlags. + */ + WINHTTP_PROXY_INFO ProxyInfo; + WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions; + RT_ZERO(AutoProxyOptions); + RT_ZERO(ProxyInfo); + + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG IeProxyConfig; + if (g_pfnWinHttpGetIEProxyConfigForCurrentUser(&IeProxyConfig)) + { + AutoProxyOptions.fAutoLogonIfChallenged = FALSE; + AutoProxyOptions.lpszAutoConfigUrl = IeProxyConfig.lpszAutoConfigUrl; + if (IeProxyConfig.fAutoDetect) + { + AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT | WINHTTP_AUTOPROXY_RUN_INPROCESS; + AutoProxyOptions.dwAutoDetectFlags = WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } + else if (AutoProxyOptions.lpszAutoConfigUrl) + AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + else if (ProxyInfo.lpszProxy) + ProxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + ProxyInfo.lpszProxy = IeProxyConfig.lpszProxy; + ProxyInfo.lpszProxyBypass = IeProxyConfig.lpszProxyBypass; + } + else + { + AssertMsgFailed(("WinHttpGetIEProxyConfigForCurrentUser -> %u\n", GetLastError())); + if (!g_pfnWinHttpGetDefaultProxyConfiguration(&ProxyInfo)) + { + AssertMsgFailed(("WinHttpGetDefaultProxyConfiguration -> %u\n", GetLastError())); + RT_ZERO(ProxyInfo); + } + } + + /* + * Should we try WinHttGetProxyForUrl? + */ + if (AutoProxyOptions.dwFlags != 0) + { + HINTERNET hSession = g_pfnWinHttpOpen(NULL /*pwszUserAgent*/, WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0 /*dwFlags*/ ); + if (hSession != NULL) + { + PRTUTF16 pwszUrl; + rc = RTStrToUtf16(pszUrl, &pwszUrl); + if (RT_SUCCESS(rc)) + { + /* + * Try autodetect first, then fall back on the config URL if there is one. + * + * Also, we first try without auto authentication, then with. This will according + * to http://msdn.microsoft.com/en-us/library/aa383153%28v=VS.85%29.aspx help with + * caching the result when it's processed out-of-process (seems default here on W10). + */ + WINHTTP_PROXY_INFO TmpProxyInfo; + BOOL fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo); + if ( !fRc + && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE) + { + AutoProxyOptions.fAutoLogonIfChallenged = TRUE; + fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo); + } + + if ( !fRc + && AutoProxyOptions.dwFlags != WINHTTP_AUTOPROXY_CONFIG_URL + && AutoProxyOptions.lpszAutoConfigUrl) + { + AutoProxyOptions.fAutoLogonIfChallenged = FALSE; + AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + AutoProxyOptions.dwAutoDetectFlags = 0; + fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo); + if ( !fRc + && GetLastError() == ERROR_WINHTTP_LOGIN_FAILURE) + { + AutoProxyOptions.fAutoLogonIfChallenged = TRUE; + fRc = g_pfnWinHttpGetProxyForUrl(hSession, pwszUrl, &AutoProxyOptions, &TmpProxyInfo); + } + } + + if (fRc) + { + if (ProxyInfo.lpszProxy) + GlobalFree(ProxyInfo.lpszProxy); + if (ProxyInfo.lpszProxyBypass) + GlobalFree(ProxyInfo.lpszProxyBypass); + ProxyInfo = TmpProxyInfo; + } + /* + * If the autodetection failed, assume no proxy. + */ + else + { + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_WINHTTP_AUTODETECTION_FAILED + || dwErr == ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT + || ( dwErr == ERROR_WINHTTP_UNRECOGNIZED_SCHEME + && ( RTStrNICmp(pszUrl, RT_STR_TUPLE("https://")) == 0 + || RTStrNICmp(pszUrl, RT_STR_TUPLE("http://")) == 0) ) ) + rcRet = rtHttpUpdateAutomaticProxyDisable(pThis); + else + AssertMsgFailed(("g_pfnWinHttpGetProxyForUrl(%s) -> %u; lpszAutoConfigUrl=%sx\n", + pszUrl, dwErr, AutoProxyOptions.lpszAutoConfigUrl)); + } + RTUtf16Free(pwszUrl); + } + else + { + AssertMsgFailed(("RTStrToUtf16(%s,) -> %Rrc\n", pszUrl, rc)); + rcRet = rc; + } + g_pfnWinHttpCloseHandle(hSession); + } + else + AssertMsgFailed(("g_pfnWinHttpOpen -> %u\n", GetLastError())); + } + + /* + * Try use the proxy info we've found. + */ + switch (ProxyInfo.dwAccessType) + { + case WINHTTP_ACCESS_TYPE_NO_PROXY: + rcRet = rtHttpUpdateAutomaticProxyDisable(pThis); + break; + + case WINHTTP_ACCESS_TYPE_NAMED_PROXY: + if (!rtHttpWinIsUrlInBypassList(pszUrl, ProxyInfo.lpszProxyBypass)) + rcRet = rtHttpWinSelectProxyFromList(pThis, pszUrl, ProxyInfo.lpszProxy); + else + rcRet = rtHttpUpdateAutomaticProxyDisable(pThis); + break; + + case 0: + break; + + default: + AssertMsgFailed(("%#x\n", ProxyInfo.dwAccessType)); + } + + /* + * Cleanup. + */ + if (ProxyInfo.lpszProxy) + GlobalFree(ProxyInfo.lpszProxy); + if (ProxyInfo.lpszProxyBypass) + GlobalFree(ProxyInfo.lpszProxyBypass); + if (AutoProxyOptions.lpszAutoConfigUrl) + GlobalFree((PRTUTF16)AutoProxyOptions.lpszAutoConfigUrl); + } + + return rcRet; +} + +#endif /* RT_OS_WINDOWS */ + + +static int rtHttpConfigureProxyForUrl(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + if (pThis->fUseSystemProxySettings) + { +#ifdef IPRT_USE_LIBPROXY + int rc = rtHttpLibProxyConfigureProxyForUrl(pThis, pszUrl); + if (rc == VINF_SUCCESS || RT_FAILURE(rc)) + return rc; + Assert(rc == VINF_NOT_SUPPORTED); +#endif +#ifdef RT_OS_DARWIN + int rc = rtHttpDarwinConfigureProxyForUrl(pThis, pszUrl); + if (rc == VINF_SUCCESS || RT_FAILURE(rc)) + return rc; + Assert(rc == VINF_NOT_SUPPORTED); +#endif +#ifdef RT_OS_WINDOWS + int rc = rtHttpWinConfigureProxyForUrl(pThis, pszUrl); + if (rc == VINF_SUCCESS || RT_FAILURE(rc)) + return rc; + Assert(rc == VINF_NOT_SUPPORTED); +#endif +/** @todo system specific class here, fall back on env vars if necessary. */ + return rtHttpConfigureProxyForUrlFromEnv(pThis, pszUrl); + } + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpSetProxy(RTHTTP hHttp, const char *pcszProxy, uint32_t uPort, + const char *pcszProxyUser, const char *pcszProxyPwd) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertPtrReturn(pcszProxy, VERR_INVALID_PARAMETER); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + + /* + * Update the settings. + * + * Currently, we don't make alot of effort parsing or checking the input, we + * leave that to cURL. (A bit afraid of breaking user settings.) + */ + pThis->fUseSystemProxySettings = false; + return rtHttpUpdateProxyConfig(pThis, CURLPROXY_HTTP, pcszProxy, uPort ? uPort : 1080, pcszProxyUser, pcszProxyPwd); +} + + + +/********************************************************************************************************************************* +* HTTP Headers * +*********************************************************************************************************************************/ + +/** + * Helper for RTHttpSetHeaders and RTHttpAddRawHeader that unsets the user agent + * if it is now in one of the headers. + */ +static int rtHttpUpdateUserAgentHeader(PRTHTTPINTERNAL pThis, PRTHTTPHEADER pNewHdr) +{ + static const char s_szUserAgent[] = "User-Agent"; + if ( pNewHdr->cchName == sizeof(s_szUserAgent) - 1 + && RTStrNICmpAscii(pNewHdr->szData, RT_STR_TUPLE(s_szUserAgent)) == 0) + { + pThis->fHaveUserAgentHeader = true; + if (pThis->fHaveSetUserAgent) + { + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, (char *)NULL); + Assert(CURL_SUCCESS(rcCurl)); NOREF(rcCurl); + pThis->fHaveSetUserAgent = false; + } + } + return VINF_SUCCESS; +} + + +/** + * Free the headers associated with the instance (w/o telling cURL about it). + * + * @param pThis The HTTP client instance. + */ +static void rtHttpFreeHeaders(PRTHTTPINTERNAL pThis) +{ + struct curl_slist *pHead = pThis->pHeaders; + pThis->pHeaders = NULL; + pThis->ppHeadersTail = &pThis->pHeaders; + pThis->fHaveUserAgentHeader = false; + + while (pHead) + { + struct curl_slist *pFree = pHead; + pHead = pHead->next; + ASMCompilerBarrier(); /* paranoia */ + + pFree->next = NULL; + pFree->data = NULL; + RTMemFree(pFree); + } +} + + +/** + * Worker for RTHttpSetHeaders and RTHttpAddHeader. + * + * @returns IPRT status code. + * @param pThis The HTTP client instance. + * @param pchName The field name. Does not need to be terminated. + * @param cchName The field name length. + * @param pchValue The field value. Does not need to be terminated. + * @param cchValue The field value length. + * @param fFlags RTHTTPADDHDR_F_XXX. + */ +static int rtHttpAddHeaderWorker(PRTHTTPINTERNAL pThis, const char *pchName, size_t cchName, + const char *pchValue, size_t cchValue, uint32_t fFlags) +{ + /* + * Create the list entry. + */ + size_t cbData = cchName + 2 + cchValue + 1; + PRTHTTPHEADER pHdr = (PRTHTTPHEADER)RTMemAlloc(RT_UOFFSETOF_DYN(RTHTTPHEADER, szData[cbData])); + if (pHdr) + { + pHdr->Core.next = NULL; + pHdr->Core.data = pHdr->szData; + pHdr->cchName = (uint32_t)cchName; + pHdr->offValue = (uint32_t)(cchName + 2); + char *psz = pHdr->szData; + memcpy(psz, pchName, cchName); + psz += cchName; + *psz++ = ':'; + *psz++ = ' '; + memcpy(psz, pchValue, cchValue); + psz[cchValue] = '\0'; + + /* + * Appending to an existing list requires no cURL interaction. + */ + AssertCompile(RTHTTPADDHDR_F_FRONT != 0); + if ( !(fFlags & RTHTTPADDHDR_F_FRONT) + && pThis->pHeaders != NULL) + { + *pThis->ppHeadersTail = &pHdr->Core; + pThis->ppHeadersTail = &pHdr->Core.next; + return rtHttpUpdateUserAgentHeader(pThis, pHdr); + } + + /* + * When prepending or adding the first header we need to inform cURL + * about the new list head. + */ + pHdr->Core.next = pThis->pHeaders; + if (!pThis->pHeaders) + pThis->ppHeadersTail = &pHdr->Core.next; + pThis->pHeaders = &pHdr->Core; + + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders); + if (CURL_SUCCESS(rcCurl)) + return rtHttpUpdateUserAgentHeader(pThis, pHdr); + return VERR_HTTP_CURL_ERROR; + } + return VERR_NO_MEMORY; +} + + +RTR3DECL(int) RTHttpSetHeaders(RTHTTP hHttp, size_t cHeaders, const char * const *papszHeaders) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * Drop old headers and reset state. + */ + if (pThis->pHeaders) + { + rtHttpFreeHeaders(pThis); + curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL); + } + pThis->ppHeadersTail = &pThis->pHeaders; + pThis->fHaveUserAgentHeader = false; + + /* + * We're done if no headers specified. + */ + if (!cHeaders) + return VINF_SUCCESS; + + /* + * Add the headers, one by one. + */ + int rc = VINF_SUCCESS; + for (size_t i = 0; i < cHeaders; i++) + { + const char *pszHeader = papszHeaders[i]; + size_t cchHeader = strlen(pszHeader); + size_t cchName = (const char *)memchr(pszHeader, ':', cchHeader) - pszHeader; + AssertBreakStmt(cchName < cchHeader, rc = VERR_INVALID_PARAMETER); + size_t offValue = RT_C_IS_BLANK(pszHeader[cchName + 1]) ? cchName + 2 : cchName + 1; + rc = rtHttpAddHeaderWorker(pThis, pszHeader, cchName, &pszHeader[offValue], cchHeader - offValue, RTHTTPADDHDR_F_BACK); + AssertRCBreak(rc); + } + if (RT_SUCCESS(rc)) + return rc; + rtHttpFreeHeaders(pThis); + curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, (struct curl_slist *)NULL); + return rc; +} + + +#if 0 /** @todo reimplement RTHttpAddRawHeader if ever actually needed. */ +RTR3DECL(int) RTHttpAddRawHeader(RTHTTP hHttp, const char *pszHeader, uint32_t fFlags) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS); +/** @todo implement RTHTTPADDHDR_F_FRONT */ + + /* + * Append it to the header list, checking for User-Agent and such. + */ + struct curl_slist *pHeaders = pThis->pHeaders; + struct curl_slist *pNewHeaders = curl_slist_append(pHeaders, pszHeader); + if (pNewHeaders) + pHeaders = pNewHeaders; + else + return VERR_NO_MEMORY; + + if (strncmp(pszHeader, RT_STR_TUPLE("User-Agent:")) == 0) + pThis->fHaveUserAgentHeader = true; + + /* + * If this is the first header, we need to tell curl. + */ + if (!pThis->pHeaders) + { + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pHeaders); + if (CURL_FAILURE(rcCurl)) + { + curl_slist_free_all(pHeaders); + return VERR_INVALID_PARAMETER; + } + pThis->pHeaders = pHeaders; + } + else + Assert(pThis->pHeaders == pHeaders); + + rtHttpUpdateUserAgentHeader(pThis); + + return VINF_SUCCESS; +} +#endif + + +RTR3DECL(int) RTHttpAddHeader(RTHTTP hHttp, const char *pszField, const char *pszValue, size_t cchValue, uint32_t fFlags) +{ + /* + * Validate input and calc string lengths. + */ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!(fFlags & ~RTHTTPADDHDR_F_BACK), VERR_INVALID_FLAGS); + AssertPtr(pszField); + size_t const cchField = strlen(pszField); + AssertReturn(cchField > 0, VERR_INVALID_PARAMETER); + AssertReturn(pszField[cchField - 1] != ':', VERR_INVALID_PARAMETER); + AssertReturn(!RT_C_IS_SPACE(pszField[cchField - 1]), VERR_INVALID_PARAMETER); +#ifdef RT_STRICT + for (size_t i = 0; i < cchField; i++) + { + char const ch = pszField[i]; + Assert(RT_C_IS_PRINT(ch) && ch != ':'); + } +#endif + + AssertPtr(pszValue); + if (cchValue == RTSTR_MAX) + cchValue = strlen(pszValue); + + /* + * Just pass it along to the worker. + */ + return rtHttpAddHeaderWorker(pThis, pszField, cchField, pszValue, cchValue, fFlags); +} + + +RTR3DECL(const char *) RTHttpGetHeader(RTHTTP hHttp, const char *pszField, size_t cchField) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN_RC(pThis, NULL); + + PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; + if (pCur) + { + if (cchField == RTSTR_MAX) + cchField = strlen(pszField); + do + { + if ( pCur->cchName == cchField + && RTStrNICmpAscii(pCur->szData, pszField, cchField) == 0) + return &pCur->szData[pCur->offValue]; + + /* next field. */ + pCur = (PRTHTTPHEADER)pCur->Core.next; + } while (pCur); + } + return NULL; +} + + +RTR3DECL(size_t) RTHttpGetHeaderCount(RTHTTP hHttp) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN_RC(pThis, 0); + + /* Note! Only for test cases and debugging, so we don't care about performance. */ + size_t cHeaders = 0; + for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next) + cHeaders++; + return cHeaders; +} + + +RTR3DECL(const char *) RTHttpGetByOrdinal(RTHTTP hHttp, size_t iOrdinal) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN_RC(pThis, NULL); + + /* Note! Only for test cases and debugging, so we don't care about performance. */ + for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur != NULL; pCur = (PRTHTTPHEADER)pCur->Core.next) + { + if (iOrdinal == 0) + return pCur->szData; + iOrdinal--; + } + + return NULL; +} + + + +RTR3DECL(int) RTHttpSignHeaders(RTHTTP hHttp, RTHTTPMETHOD enmMethod, const char *pszUrl, + RTCRKEY hKey, const char *pszKeyId, uint32_t fFlags) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszUrl, VERR_INVALID_POINTER); + AssertReturn(!fFlags, VERR_INVALID_FLAGS); + AssertPtrReturn(pszKeyId, VERR_INVALID_POINTER); + + /* + * Do a little bit of preprocessing while we can easily return without + * needing clean anything up.. + */ + RTURIPARSED ParsedUrl; + int rc = RTUriParse(pszUrl, &ParsedUrl); + AssertRCReturn(rc, rc); + const char * const pszPath = pszUrl + ParsedUrl.offPath; + + const char *pszMethodSp = NULL; + switch (enmMethod) + { + case RTHTTPMETHOD_GET: pszMethodSp = "get "; break; + case RTHTTPMETHOD_PUT: pszMethodSp = "put "; break; + case RTHTTPMETHOD_POST: pszMethodSp = "post "; break; + case RTHTTPMETHOD_PATCH: pszMethodSp = "patch "; break; + case RTHTTPMETHOD_DELETE: pszMethodSp = "delete "; break; + case RTHTTPMETHOD_HEAD: pszMethodSp = "head "; break; + case RTHTTPMETHOD_OPTIONS: pszMethodSp = "options "; break; + case RTHTTPMETHOD_TRACE: pszMethodSp = "trace "; break; +#ifdef IPRT_HTTP_WITH_WEBDAV + case RTHTTPMETHOD_PROPFIND: pszMethodSp = "propfind "; break; +#endif + /* no default! */ + case RTHTTPMETHOD_INVALID: + case RTHTTPMETHOD_END: + case RTHTTPMETHOD_32BIT_HACK: + break; + } + AssertReturn(pszMethodSp, VERR_INTERNAL_ERROR_4); + + /* + * We work the authorization header entry directly here to avoid extra copying and stuff. + */ + + /* Estimate required string length first. */ + static const char s_szSuffixFmt[] = "Authorization: Signature version=\"1\",keyId=\"%s\",algorithm=\"rsa-sha256\",headers=\""; + static const char s_szInfix[] = "\",signature=\""; + static const char s_szPostfix[] = "\""; + static const char s_szRequestField[] = "(request-target)"; + size_t const cchKeyId = strlen(pszKeyId); + size_t const cbSigRaw = (RTCrKeyGetBitCount(hKey) + 7) / 8; + size_t const cbSigRawAligned = RT_ALIGN_Z(cbSigRaw, 8); + size_t const cchSigStr = RTBase64EncodedLengthEx(cbSigRaw, RTBASE64_FLAGS_NO_LINE_BREAKS); + size_t cbEstimated = sizeof(s_szSuffixFmt) + sizeof(s_szInfix) + sizeof(s_szPostfix) + + cchKeyId + sizeof(s_szRequestField) + cchSigStr; + for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur; pCur = (PRTHTTPHEADER)pCur->Core.next) + cbEstimated += pCur->cchName + 1; + cbEstimated += 32; /* safetype fudge */ + /* Lazy bird: Put the raw signature at the end. */ + cbEstimated = RT_ALIGN_Z(cbEstimated, 8) + cbSigRawAligned; + + /* Allocate and initialize header entry. */ + PRTHTTPHEADER const pHdr = (PRTHTTPHEADER)RTMemAllocZ(cbEstimated); + AssertPtrReturn(pHdr, VERR_NO_MEMORY); + uint8_t * const pbSigRaw = (uint8_t *)pHdr + cbEstimated - cbSigRawAligned; + + pHdr->cchName = sizeof("Authorization") - 1; + pHdr->offValue = sizeof("Authorization") + 1; + pHdr->Core.next = NULL; + pHdr->Core.data = pHdr->szData; + char *pszLeft = pHdr->szData; + size_t cbLeft = cbEstimated - RT_UOFFSETOF(RTHTTPHEADER, szData) - cbSigRawAligned; + + size_t cch = RTStrPrintf(pszLeft, cbLeft, s_szSuffixFmt, pszKeyId); + cbLeft -= cch; + pszLeft += cch; + + /* + * Instantiate the digest. + */ + RTCRDIGEST hDigest = NIL_RTCRDIGEST; + rc = RTCrDigestCreateByType(&hDigest, RTDIGESTTYPE_SHA256); + if (RT_SUCCESS(rc)) + { + /* + * Add the request-target pseudo header first. + */ + Assert(cbLeft > sizeof(s_szRequestField)); + memcpy(pszLeft, RT_STR_TUPLE(s_szRequestField)); + pszLeft += sizeof(s_szRequestField) - 1; + + rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(s_szRequestField)); + if (RT_SUCCESS(rc)) + rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": ")); + if (RT_SUCCESS(rc)) + rc = RTCrDigestUpdate(hDigest, pszMethodSp, strlen(pszMethodSp)); + if (RT_SUCCESS(rc)) + rc = RTCrDigestUpdate(hDigest, pszPath, strlen(pszPath)); + + /* + * Add the header fields. + */ + for (PRTHTTPHEADER pCur = (PRTHTTPHEADER)pThis->pHeaders; pCur && RT_SUCCESS(rc); pCur = (PRTHTTPHEADER)pCur->Core.next) + { + AssertBreakStmt(cbLeft > pCur->cchName, rc = VERR_INTERNAL_ERROR_3); + *pszLeft++ = ' '; + cbLeft--; + memcpy(pszLeft, pCur->szData, pCur->cchName); + pszLeft[pCur->cchName] = '\0'; + RTStrToLower(pszLeft); + + rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE("\n")); + AssertRCBreak(rc); + rc = RTCrDigestUpdate(hDigest, pszLeft, pCur->cchName); + AssertRCBreak(rc); + rc = RTCrDigestUpdate(hDigest, RT_STR_TUPLE(": ")); + AssertRCBreak(rc); + const char *pszValue = &pCur->szData[pCur->offValue]; + rc = RTCrDigestUpdate(hDigest, pszValue, strlen(pszValue)); + AssertRCBreak(rc); + + pszLeft += pCur->cchName; + cbLeft -= pCur->cchName; + } + if (RT_SUCCESS(rc)) + AssertStmt(cbLeft > sizeof(s_szInfix) + cchSigStr + sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3); + if (RT_SUCCESS(rc)) + { + /* Complete the header field part. */ + memcpy(pszLeft, RT_STR_TUPLE(s_szInfix)); + pszLeft += sizeof(s_szInfix) - 1; + cbLeft -= sizeof(s_szInfix) - 1; + + /* + * Sign the digest. + */ + RTCRPKIXSIGNATURE hSigner; + rc = RTCrPkixSignatureCreateByObjIdString(&hSigner, RTCR_PKCS1_SHA256_WITH_RSA_OID, hKey, NULL, true /*fSigning*/); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + size_t cbActual = cbSigRawAligned; + rc = RTCrPkixSignatureSign(hSigner, hDigest, pbSigRaw, &cbActual); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Assert(cbActual == cbSigRaw); + RTCrPkixSignatureRelease(hSigner); + hSigner = NIL_RTCRPKIXSIGNATURE; + RTCrDigestRelease(hDigest); + hDigest = NIL_RTCRDIGEST; + + /* + * Convert the signature to Base64 and append it to the string. + */ + size_t cchActual; + rc = RTBase64EncodeEx(pbSigRaw, cbActual, RTBASE64_FLAGS_NO_LINE_BREAKS, pszLeft, cbLeft, &cchActual); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Assert(cchActual == cchSigStr); + pszLeft += cchActual; + cbLeft -= cchActual; + + /* + * Append the postfix and add the header to the front of the list. + */ + AssertStmt(cbLeft >= sizeof(s_szPostfix), rc = VERR_INTERNAL_ERROR_3); + if (RT_SUCCESS(rc)) + { + memcpy(pszLeft, s_szPostfix, sizeof(s_szPostfix)); + + pHdr->Core.next = pThis->pHeaders; + if (!pThis->pHeaders) + pThis->ppHeadersTail = &pHdr->Core.next; + pThis->pHeaders = &pHdr->Core; + + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPHEADER, pThis->pHeaders); + if (CURL_SUCCESS(rcCurl)) + return VINF_SUCCESS; + rc = VERR_HTTP_CURL_ERROR; + } + } + } + RTCrPkixSignatureRelease(hSigner); + } + } + RTCrDigestRelease(hDigest); + } + RTMemFree(pHdr); + return rc; +} + + +/********************************************************************************************************************************* +* HTTPS and root certficates * +*********************************************************************************************************************************/ + +/** + * Set the CA file to NULL, deleting any temporary file if necessary. + * + * @param pThis The HTTP/HTTPS client instance. + */ +static void rtHttpUnsetCaFile(PRTHTTPINTERNAL pThis) +{ + if (pThis->pszCaFile) + { + if (pThis->fDeleteCaFile) + { + int rc2 = RTFileDelete(pThis->pszCaFile); RT_NOREF_PV(rc2); + AssertMsg(RT_SUCCESS(rc2) || !RTFileExists(pThis->pszCaFile), ("rc=%Rrc '%s'\n", rc2, pThis->pszCaFile)); + } + RTStrFree(pThis->pszCaFile); + pThis->pszCaFile = NULL; + } +} + + +RTR3DECL(int) RTHttpSetCAFile(RTHTTP hHttp, const char *pszCaFile) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rtHttpUnsetCaFile(pThis); + + pThis->fDeleteCaFile = false; + if (pszCaFile) + return RTStrDupEx(&pThis->pszCaFile, pszCaFile); + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpUseTemporaryCaFile(RTHTTP hHttp, PRTERRINFO pErrInfo) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * Create a temporary file. + */ + int rc = VERR_NO_STR_MEMORY; + char *pszCaFile = RTStrAlloc(RTPATH_MAX); + if (pszCaFile) + { + RTFILE hFile; + rc = RTFileOpenTemp(&hFile, pszCaFile, RTPATH_MAX, + RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + /* + * Gather certificates into a temporary store and export them to the temporary file. + */ + RTCRSTORE hStore; + rc = RTCrStoreCreateInMem(&hStore, 256); + if (RT_SUCCESS(rc)) + { + rc = RTHttpGatherCaCertsInStore(hStore, 0 /*fFlags*/, pErrInfo); + if (RT_SUCCESS(rc)) + /** @todo Consider adding an API for exporting to a RTFILE... */ + rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile); + RTCrStoreRelease(hStore); + } + RTFileClose(hFile); + if (RT_SUCCESS(rc)) + { + /* + * Set the CA file for the instance. + */ + rtHttpUnsetCaFile(pThis); + + pThis->fDeleteCaFile = true; + pThis->pszCaFile = pszCaFile; + return VINF_SUCCESS; + } + + int rc2 = RTFileDelete(pszCaFile); + AssertRC(rc2); + } + else + RTErrInfoAddF(pErrInfo, rc, "Error creating temorary file: %Rrc", rc); + + RTStrFree(pszCaFile); + } + return rc; +} + + +RTR3DECL(int) RTHttpGatherCaCertsInStore(RTCRSTORE hStore, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + uint32_t const cBefore = RTCrStoreCertCount(hStore); + AssertReturn(cBefore != UINT32_MAX, VERR_INVALID_HANDLE); + RT_NOREF_PV(fFlags); + + /* + * Add the user store, quitely ignoring any errors. + */ + RTCRSTORE hSrcStore; + int rcUser = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo); + if (RT_SUCCESS(rcUser)) + { + rcUser = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, + hSrcStore); + RTCrStoreRelease(hSrcStore); + } + + /* + * Ditto for the system store. + */ + int rcSystem = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo); + if (RT_SUCCESS(rcSystem)) + { + rcSystem = RTCrStoreCertAddFromStore(hStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR, + hSrcStore); + RTCrStoreRelease(hSrcStore); + } + + /* + * If the number of certificates increased, we consider it a success. + */ + if (RTCrStoreCertCount(hStore) > cBefore) + { + if (RT_FAILURE(rcSystem)) + return -rcSystem; + if (RT_FAILURE(rcUser)) + return -rcUser; + return rcSystem != VINF_SUCCESS ? rcSystem : rcUser; + } + + if (RT_FAILURE(rcSystem)) + return rcSystem; + if (RT_FAILURE(rcUser)) + return rcUser; + return VERR_NOT_FOUND; +} + + +RTR3DECL(int) RTHttpGatherCaCertsInFile(const char *pszCaFile, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + RTCRSTORE hStore; + int rc = RTCrStoreCreateInMem(&hStore, 256); + if (RT_SUCCESS(rc)) + { + rc = RTHttpGatherCaCertsInStore(hStore, fFlags, pErrInfo); + if (RT_SUCCESS(rc)) + rc = RTCrStoreCertExportAsPem(hStore, 0 /*fFlags*/, pszCaFile); + RTCrStoreRelease(hStore); + } + return rc; +} + + +RTR3DECL(bool) RTHttpGetVerifyPeer(RTHTTP hHttp) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN_RC(pThis, false); + return pThis->fVerifyPeer; +} + + +RTR3DECL(int) RTHttpSetVerifyPeer(RTHTTP hHttp, bool fVerify) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!pThis->fBusy, VERR_WRONG_ORDER); + + if (pThis->fVerifyPeer != fVerify) + { + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSL_VERIFYPEER, (long)fVerify); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("CURLOPT_SSL_VERIFYPEER=%RTbool: %d (%#x)\n", fVerify, rcCurl, rcCurl), + VERR_HTTP_CURL_ERROR); + pThis->fVerifyPeer = fVerify; + } + + return VINF_SUCCESS; +} + + + +/********************************************************************************************************************************* +* ....... +*********************************************************************************************************************************/ + + +/** + * Figures out the IPRT status code for a GET. + * + * @returns IPRT status code. + * @param pThis The HTTP/HTTPS client instance. + * @param rcCurl What curl returned. + * @param puHttpStatus Where to optionally return the HTTP status. If specified, + * the HTTP statuses are not translated to IPRT status codes. + */ +static int rtHttpGetCalcStatus(PRTHTTPINTERNAL pThis, CURLcode rcCurl, uint32_t *puHttpStatus) +{ + int rc = VERR_HTTP_CURL_ERROR; + + if (pThis->pszRedirLocation) + { + RTStrFree(pThis->pszRedirLocation); + pThis->pszRedirLocation = NULL; + } + if (CURL_SUCCESS(rcCurl)) + { + curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &pThis->lLastResp); + if (puHttpStatus) + { + *puHttpStatus = pThis->lLastResp; + rc = VINF_SUCCESS; + } + + switch (pThis->lLastResp) + { + case 200: + /* OK, request was fulfilled */ + case 204: + /* empty response */ + rc = VINF_SUCCESS; + break; + case 301: /* Moved permantently. */ + case 302: /* Found / Moved temporarily. */ + case 303: /* See Other. */ + case 307: /* Temporary redirect. */ + case 308: /* Permanent redirect. */ + { + const char *pszRedirect = NULL; + curl_easy_getinfo(pThis->pCurl, CURLINFO_REDIRECT_URL, &pszRedirect); + size_t cb = pszRedirect ? strlen(pszRedirect) : 0; + if (cb > 0 && cb < 2048) + pThis->pszRedirLocation = RTStrDup(pszRedirect); + if (!puHttpStatus) + rc = VERR_HTTP_REDIRECTED; + break; + } + case 400: + /* bad request */ + if (!puHttpStatus) + rc = VERR_HTTP_BAD_REQUEST; + break; + case 403: + /* forbidden, authorization will not help */ + if (!puHttpStatus) + rc = VERR_HTTP_ACCESS_DENIED; + break; + case 404: + /* URL not found */ + if (!puHttpStatus) + rc = VERR_HTTP_NOT_FOUND; + break; + } + + if (pThis->pszRedirLocation) + Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu redir='%s'\n", rc, pThis->lLastResp, pThis->pszRedirLocation)); + else + Log(("rtHttpGetCalcStatus: rc=%Rrc lastResp=%lu\n", rc, pThis->lLastResp)); + } + else + { + switch (rcCurl) + { + case CURLE_URL_MALFORMAT: + case CURLE_COULDNT_RESOLVE_HOST: + rc = VERR_HTTP_HOST_NOT_FOUND; + break; + case CURLE_COULDNT_CONNECT: + rc = VERR_HTTP_COULDNT_CONNECT; + break; + case CURLE_SSL_CONNECT_ERROR: + rc = VERR_HTTP_SSL_CONNECT_ERROR; + break; + case CURLE_SSL_CACERT: + /* The peer certificate cannot be authenticated with the CA certificates + * set by RTHttpSetCAFile(). We need other or additional CA certificates. */ + rc = VERR_HTTP_CACERT_CANNOT_AUTHENTICATE; + break; + case CURLE_SSL_CACERT_BADFILE: + /* CAcert file (see RTHttpSetCAFile()) has wrong format */ + rc = VERR_HTTP_CACERT_WRONG_FORMAT; + break; + case CURLE_ABORTED_BY_CALLBACK: + /* forcefully aborted */ + rc = VERR_HTTP_ABORTED; + break; + case CURLE_COULDNT_RESOLVE_PROXY: + rc = VERR_HTTP_PROXY_NOT_FOUND; + break; + case CURLE_WRITE_ERROR: + rc = RT_FAILURE_NP(pThis->rcOutput) ? pThis->rcOutput : VERR_WRITE_ERROR; + break; + //case CURLE_READ_ERROR + + default: + break; + } + Log(("%s: %Rrc: %u = %s%s%.*s\n", + __FUNCTION__, + rc, rcCurl, curl_easy_strerror((CURLcode)rcCurl), + pThis->szErrorBuffer[0] != '\0' ? ": " : "", + (int) sizeof(pThis->szErrorBuffer), + pThis->szErrorBuffer[0] != '\0' ? pThis->szErrorBuffer : "")); + } + + return rc; +} + + +/** + * cURL callback for reporting progress, we use it for checking for abort. + */ +static int rtHttpProgress(void *pData, double rdTotalDownload, double rdDownloaded, double rdTotalUpload, double rdUploaded) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pData; + AssertReturn(pThis->u32Magic == RTHTTP_MAGIC, 1); + RT_NOREF_PV(rdTotalUpload); + RT_NOREF_PV(rdUploaded); + + pThis->cbDownloadHint = (uint64_t)rdTotalDownload; + + if (pThis->pfnDownloadProgress) + pThis->pfnDownloadProgress(pThis, pThis->pvDownloadProgressUser, (uint64_t)rdTotalDownload, (uint64_t)rdDownloaded); + + return pThis->fAbort ? 1 : 0; +} + + +/** + * Whether we're likely to need SSL to handle the give URL. + * + * @returns true if we need, false if we probably don't. + * @param pszUrl The URL. + */ +static bool rtHttpNeedSsl(const char *pszUrl) +{ + return RTStrNICmp(pszUrl, RT_STR_TUPLE("https:")) == 0; +} + + +/** + * Applies recoded settings to the cURL instance before doing work. + * + * @returns IPRT status code. + * @param pThis The HTTP/HTTPS client instance. + * @param pszUrl The URL. + */ +static int rtHttpApplySettings(PRTHTTPINTERNAL pThis, const char *pszUrl) +{ + /* + * The URL. + */ + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl); + if (CURL_FAILURE(rcCurl)) + return VERR_INVALID_PARAMETER; + + /* + * Proxy config. + */ + int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl); + if (RT_FAILURE(rc)) + return rc; + + /* + * Setup SSL. Can be a bit of work. + */ + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_SSLVERSION, (long)CURL_SSLVERSION_TLSv1); + if (CURL_FAILURE(rcCurl)) + return VERR_INVALID_PARAMETER; + + const char *pszCaFile = pThis->pszCaFile; + if ( !pszCaFile + && rtHttpNeedSsl(pszUrl)) + { + rc = RTHttpUseTemporaryCaFile(pThis, NULL); + if (RT_SUCCESS(rc)) + pszCaFile = pThis->pszCaFile; + else + return rc; /* Non-portable alternative: pszCaFile = "/etc/ssl/certs/ca-certificates.crt"; */ + } + if (pszCaFile) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pszCaFile); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + } + + /* + * Progress/abort. + */ + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSFUNCTION, &rtHttpProgress); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PROGRESSDATA, (void *)pThis); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOPROGRESS, (long)0); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + /* + * Set default user agent string if necessary. Some websites take offence + * if we don't set it. + */ + if ( !pThis->fHaveSetUserAgent + && !pThis->fHaveUserAgentHeader) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_USERAGENT, "Mozilla/5.0 (AgnosticOS; Blend) IPRT/64.42"); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + pThis->fHaveSetUserAgent = true; + } + + /* + * Use GET by default. + */ + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 0L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 0L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +/** + * Resets state. + * + * @param pThis HTTP client instance. + */ +static void rtHttpResetState(PRTHTTPINTERNAL pThis) +{ + pThis->fAbort = false; + pThis->rcOutput = VINF_SUCCESS; + pThis->uDownloadHttpStatus = UINT32_MAX; + pThis->cbDownloadContent = UINT64_MAX; + pThis->offDownloadContent = 0; + pThis->offUploadContent = 0; + pThis->rcOutput = VINF_SUCCESS; + pThis->cbDownloadHint = 0; + Assert(pThis->BodyOutput.pHttp == pThis); + Assert(pThis->HeadersOutput.pHttp == pThis); +} + + +/** + * Tries to determin uDownloadHttpStatus and cbDownloadContent. + * + * @param pThis HTTP client instance. + */ +static void rtHttpGetDownloadStatusAndLength(PRTHTTPINTERNAL pThis) +{ + long lHttpStatus = 0; + curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, &lHttpStatus); + pThis->uDownloadHttpStatus = (uint32_t)lHttpStatus; + +#ifdef CURLINFO_CONTENT_LENGTH_DOWNLOAD_T + curl_off_t cbContent = -1; + curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &cbContent); + if (cbContent >= 0) + pThis->cbDownloadContent = (uint64_t)cbContent; +#else + double rdContent = -1.0; + curl_easy_getinfo(pThis->pCurl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &rdContent); + if (rdContent >= 0.0) + pThis->cbDownloadContent = (uint64_t)rdContent; +#endif +} + + +/** + * Worker for rtHttpWriteHeaderData and rtHttpWriteBodyData. + */ +static size_t rtHttpWriteDataToMemOutput(PRTHTTPINTERNAL pThis, RTHTTPOUTPUTDATA *pOutput, char const *pchBuf, size_t cbToAppend) +{ + /* + * Do max size and overflow checks. + */ + size_t const cbCurSize = pOutput->uData.Mem.cb; + size_t const cbNewSize = cbCurSize + cbToAppend; + if ( cbToAppend < RTHTTP_MAX_MEM_DOWNLOAD_SIZE + && cbNewSize < RTHTTP_MAX_MEM_DOWNLOAD_SIZE) + { + if (cbNewSize + 1 <= pOutput->uData.Mem.cbAllocated) + { + memcpy(&pOutput->uData.Mem.pb[cbCurSize], pchBuf, cbToAppend); + pOutput->uData.Mem.cb = cbNewSize; + pOutput->uData.Mem.pb[cbNewSize] = '\0'; + return cbToAppend; + } + + /* + * We need to reallocate the output buffer. + */ + /** @todo this could do with a better strategy wrt growth. */ + size_t cbAlloc = RT_ALIGN_Z(cbNewSize + 1, 64); + if ( cbAlloc <= pThis->cbDownloadHint + && pThis->cbDownloadHint < RTHTTP_MAX_MEM_DOWNLOAD_SIZE + && pOutput == &pThis->BodyOutput) + cbAlloc = RT_ALIGN_Z(pThis->cbDownloadHint + 1, 64); + + uint8_t *pbNew = (uint8_t *)RTMemRealloc(pOutput->uData.Mem.pb, cbAlloc); + if (pbNew) + { + memcpy(&pbNew[cbCurSize], pchBuf, cbToAppend); + pbNew[cbNewSize] = '\0'; + + pOutput->uData.Mem.cbAllocated = cbAlloc; + pOutput->uData.Mem.pb = pbNew; + pOutput->uData.Mem.cb = cbNewSize; + return cbToAppend; + } + + pThis->rcOutput = VERR_NO_MEMORY; + } + else + pThis->rcOutput = VERR_TOO_MUCH_DATA; + + /* + * Failure - abort. + */ + RTMemFree(pOutput->uData.Mem.pb); + pOutput->uData.Mem.pb = NULL; + pOutput->uData.Mem.cb = RTHTTP_MAX_MEM_DOWNLOAD_SIZE; + pThis->fAbort = true; + return 0; +} + + +/** + * cURL callback for writing body data. + */ +static size_t rtHttpWriteBodyData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser; + size_t const cbToAppend = cbUnit * cUnits; + + /* + * Check if this belongs to the body download callback. + */ + if (pThis->pfnDownloadCallback) + { + if (pThis->offDownloadContent == 0) + rtHttpGetDownloadStatusAndLength(pThis); + + if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS + || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus) + { + int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbToAppend, pThis->uDownloadHttpStatus, pThis->offDownloadContent, + pThis->cbDownloadContent, pThis->pvDownloadCallbackUser); + if (RT_SUCCESS(rc)) + { + pThis->offDownloadContent += cbToAppend; + return cbToAppend; + } + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + pThis->fAbort = true; + return 0; + } + } + + /* + * Otherwise, copy to memory output buffer. + */ + return rtHttpWriteDataToMemOutput(pThis, &pThis->BodyOutput, pchBuf, cbToAppend); +} + + +/** + * cURL callback for writing header data. + */ +static size_t rtHttpWriteHeaderData(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser; + size_t const cbToAppend = cbUnit * cUnits; + + /* + * Work the header callback, if one. + * ASSUMES cURL is giving us one header at a time. + */ + if (pThis->pfnHeaderCallback) + { + /* + * Find the end of the field name first. + */ + uint32_t uMatchWord; + size_t cchField; + const char *pchField = pchBuf; + size_t cchValue; + const char *pchValue = (const char *)memchr(pchBuf, ':', cbToAppend); + if (pchValue) + { + cchField = pchValue - pchField; + if (RT_LIKELY(cchField >= 3)) + uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, RT_C_TO_LOWER(pchBuf[0]), + RT_C_TO_LOWER(pchBuf[1]), RT_C_TO_LOWER(pchBuf[2])); + else + uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(cchField, + cchField >= 1 ? RT_C_TO_LOWER(pchBuf[0]) : 0, + cchField >= 2 ? RT_C_TO_LOWER(pchBuf[1]) : 0, + 0); + pchValue++; + cchValue = cbToAppend - cchField - 1; + } + /* Since cURL gives us the "HTTP/{version} {code} {status}" line too, + we slap a fictitious field name ':http-status-line' in front of it. */ + else if (cbToAppend > 5 && pchBuf[0] == 'H' && pchBuf[1] == 'T' && pchBuf[2] == 'T' && pchBuf[3] == 'P' && pchBuf[4] == '/') + { + pchField = ":http-status-line"; + cchField = 17; + uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(17, ':', 'h', 't'); + pchValue = pchBuf; + cchValue = cbToAppend; + } + /* cURL also gives us the empty line before the body, so we slap another + fictitious field name ':end-of-headers' in front of it as well. */ + else if (cbToAppend == 2 && pchBuf[0] == '\r' && pchBuf[1] == '\n') + { + pchField = ":end-of-headers"; + cchField = 15; + uMatchWord = RTHTTP_MAKE_HDR_MATCH_WORD(15, ':', 'e', 'n'); + pchValue = pchBuf; + cchValue = cbToAppend; + } + else + AssertMsgFailedReturn(("pchBuf=%.*s\n", cbToAppend, pchBuf), cbToAppend); + + /* + * Determin the field value, stripping one leading blank and all + * trailing spaces. + */ + if (cchValue > 0 && RT_C_IS_BLANK(*pchValue)) + pchValue++, cchValue--; + while (cchValue > 0 && RT_C_IS_SPACE(pchValue[cchValue - 1])) + cchValue--; + + /* + * Pass it to the callback. + */ + Log6(("rtHttpWriteHeaderData: %.*s: %.*s\n", cchField, pchBuf, cchValue, pchValue)); + int rc = pThis->pfnHeaderCallback(pThis, uMatchWord, pchBuf, cchField, + pchValue, cchValue, pThis->pvHeaderCallbackUser); + if (RT_SUCCESS(rc)) + return cbToAppend; + + /* Abort on error. */ + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + pThis->fAbort = true; + return 0; + } + + return rtHttpWriteDataToMemOutput(pThis, &pThis->HeadersOutput, pchBuf, cbToAppend); +} + + +/** + * cURL callback for working the upload callback. + */ +static size_t rtHttpWriteDataToDownloadCallback(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser; + size_t const cbBuf = cbUnit * cUnits; + + /* Get download info the first time we're called. */ + if (pThis->offDownloadContent == 0) + rtHttpGetDownloadStatusAndLength(pThis); + + /* Call the callback if the HTTP status code matches, otherwise let it go to /dev/null. */ + if ( (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == RTHTTPDOWNLOAD_F_ANY_STATUS + || (pThis->fDownloadCallback & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) == pThis->uDownloadHttpStatus) + { + int rc = pThis->pfnDownloadCallback(pThis, pchBuf, cbBuf, pThis->uDownloadHttpStatus, pThis->offDownloadContent, + pThis->cbDownloadContent, pThis->pvDownloadCallbackUser); + if (RT_SUCCESS(rc)) + { /* likely */ } + else + { + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + pThis->fAbort = true; + return 0; + } + } + pThis->offDownloadContent += cbBuf; + return cbBuf; +} + + +/** + * Callback feeding cURL data from RTHTTPINTERNAL::ReadData::Mem. + */ +static size_t rtHttpReadData(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser; + size_t const cbReq = cbUnit * cUnits; + size_t const offMem = pThis->ReadData.Mem.offMem; + size_t cbToCopy = pThis->ReadData.Mem.cbMem - offMem; + if (cbToCopy > cbReq) + cbToCopy = cbReq; + memcpy(pvDst, (uint8_t const *)pThis->ReadData.Mem.pvMem + offMem, cbToCopy); + pThis->ReadData.Mem.offMem = offMem + cbToCopy; + return cbToCopy; +} + + +/** + * Callback feeding cURL data via the user upload callback. + */ +static size_t rtHttpReadDataFromUploadCallback(void *pvDst, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + PRTHTTPINTERNAL pThis = (PRTHTTPINTERNAL)pvUser; + size_t const cbReq = cbUnit * cUnits; + + size_t cbActual = 0; + int rc = pThis->pfnUploadCallback(pThis, pvDst, cbReq, pThis->offUploadContent, &cbActual, pThis->pvUploadCallbackUser); + if (RT_SUCCESS(rc)) + { + pThis->offUploadContent += cbActual; + return cbActual; + } + + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + pThis->fAbort = true; + return CURL_READFUNC_ABORT; +} + + +/** + * Helper for installing a (body) write callback function. + * + * @returns cURL status code. + * @param pThis The HTTP client instance. + * @param pfnWrite The callback. + * @param pvUser The callback user argument. + */ +static CURLcode rtHttpSetWriteCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser) +{ + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEFUNCTION, pfnWrite); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_WRITEDATA, pvUser); + return rcCurl; +} + + +/** + * Helper for installing a header write callback function. + * + * @returns cURL status code. + * @param pThis The HTTP client instance. + * @param pfnWrite The callback. + * @param pvUser The callback user argument. + */ +static CURLcode rtHttpSetHeaderCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser) +{ + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser); + return rcCurl; +} + + +/** + * Helper for installing a (body) read callback function. + * + * @returns cURL status code. + * @param pThis The HTTP client instance. + * @param pfnRead The callback. + * @param pvUser The callback user argument. + */ +static CURLcode rtHttpSetReadCallback(PRTHTTPINTERNAL pThis, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser) +{ + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser); + return rcCurl; +} + + +/** + * Internal worker that performs a HTTP GET. + * + * @returns IPRT status code. + * @param hHttp The HTTP/HTTPS client instance. + * @param pszUrl The URL. + * @param fNoBody Set to suppress the body. + * @param ppvResponse Where to return the pointer to the allocated + * response data (RTMemFree). There will always be + * an zero terminator char after the response, that + * is not part of the size returned via @a pcb. + * @param pcb The size of the response data. + * + * @remarks We ASSUME the API user doesn't do concurrent GETs in different + * threads, because that will probably blow up! + */ +static int rtHttpGetToMem(RTHTTP hHttp, const char *pszUrl, bool fNoBody, uint8_t **ppvResponse, size_t *pcb) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * Reset the return values in case of more "GUI programming" on the client + * side (i.e. a programming style not bothering checking return codes). + */ + *ppvResponse = NULL; + *pcb = 0; + + /* + * Set the busy flag (paranoia). + */ + bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true); + AssertReturn(!fBusy, VERR_WRONG_ORDER); + + /* + * Reset the state and apply settings. + */ + rtHttpResetState(pThis); + int rc = rtHttpApplySettings(hHttp, pszUrl); + if (RT_SUCCESS(rc)) + { + RT_ZERO(pThis->BodyOutput.uData.Mem); + CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteBodyData, pThis); + if (fNoBody) + { + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADER, 1L); + } + if (CURL_SUCCESS(rcCurl)) + { + /* + * Perform the HTTP operation. + */ + rcCurl = curl_easy_perform(pThis->pCurl); + rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL); + if (RT_SUCCESS(rc)) + rc = pThis->rcOutput; + if (RT_SUCCESS(rc)) + { + *ppvResponse = pThis->BodyOutput.uData.Mem.pb; + *pcb = pThis->BodyOutput.uData.Mem.cb; + Log(("rtHttpGetToMem: %zx bytes (allocated %zx)\n", + pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated)); + } + else if (pThis->BodyOutput.uData.Mem.pb) + RTMemFree(pThis->BodyOutput.uData.Mem.pb); + RT_ZERO(pThis->BodyOutput.uData.Mem); + } + else + rc = VERR_HTTP_CURL_ERROR; + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +RTR3DECL(int) RTHttpGetText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8) +{ + Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl)); + uint8_t *pv; + size_t cb; + int rc = rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, &pv, &cb); + if (RT_SUCCESS(rc)) + { + if (pv) /* paranoia */ + *ppszNotUtf8 = (char *)pv; + else + *ppszNotUtf8 = (char *)RTMemDup("", 1); + } + else + *ppszNotUtf8 = NULL; + return rc; +} + + +RTR3DECL(int) RTHttpGetHeaderText(RTHTTP hHttp, const char *pszUrl, char **ppszNotUtf8) +{ + Log(("RTHttpGetText: hHttp=%p pszUrl=%s\n", hHttp, pszUrl)); + uint8_t *pv; + size_t cb; + int rc = rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, &pv, &cb); + if (RT_SUCCESS(rc)) + { + if (pv) /* paranoia */ + *ppszNotUtf8 = (char *)pv; + else + *ppszNotUtf8 = (char *)RTMemDup("", 1); + } + else + *ppszNotUtf8 = NULL; + return rc; + +} + + +RTR3DECL(void) RTHttpFreeResponseText(char *pszNotUtf8) +{ + RTMemFree(pszNotUtf8); +} + + +RTR3DECL(int) RTHttpGetBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb) +{ + Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl)); + return rtHttpGetToMem(hHttp, pszUrl, false /*fNoBody*/, (uint8_t **)ppvResponse, pcb); +} + + +RTR3DECL(int) RTHttpGetHeaderBinary(RTHTTP hHttp, const char *pszUrl, void **ppvResponse, size_t *pcb) +{ + Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s\n", hHttp, pszUrl)); + return rtHttpGetToMem(hHttp, pszUrl, true /*fNoBody*/, (uint8_t **)ppvResponse, pcb); +} + + +RTR3DECL(void) RTHttpFreeResponse(void *pvResponse) +{ + RTMemFree(pvResponse); +} + + +/** + * cURL callback for writing data to a file. + */ +static size_t rtHttpWriteDataToFile(char *pchBuf, size_t cbUnit, size_t cUnits, void *pvUser) RT_NOTHROW_DEF +{ + RTHTTPOUTPUTDATA *pOutput = (RTHTTPOUTPUTDATA *)pvUser; + PRTHTTPINTERNAL pThis = pOutput->pHttp; + + size_t cbWritten = 0; + int rc = RTFileWrite(pOutput->uData.hFile, pchBuf, cbUnit * cUnits, &cbWritten); + if (RT_SUCCESS(rc)) + return cbWritten; + + Log(("rtHttpWriteDataToFile: rc=%Rrc cbUnit=%zd cUnits=%zu\n", rc, cbUnit, cUnits)); + pThis->rcOutput = rc; + return 0; +} + + +RTR3DECL(int) RTHttpGetFile(RTHTTP hHttp, const char *pszUrl, const char *pszDstFile) +{ + Log(("RTHttpGetBinary: hHttp=%p pszUrl=%s pszDstFile=%s\n", hHttp, pszUrl, pszDstFile)); + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * Set the busy flag (paranoia). + */ + bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true); + AssertReturn(!fBusy, VERR_WRONG_ORDER); + + /* + * Reset the state and apply settings. + */ + rtHttpResetState(pThis); + int rc = rtHttpApplySettings(hHttp, pszUrl); + if (RT_SUCCESS(rc)) + { + pThis->BodyOutput.uData.hFile = NIL_RTFILE; + CURLcode rcCurl = rtHttpSetWriteCallback(pThis, &rtHttpWriteDataToFile, (void *)&pThis->BodyOutput); + if (CURL_SUCCESS(rcCurl)) + { + /* + * Open the output file. + */ + rc = RTFileOpen(&pThis->BodyOutput.uData.hFile, pszDstFile, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_READWRITE); + if (RT_SUCCESS(rc)) + { + /* + * Perform the HTTP operation. + */ + rcCurl = curl_easy_perform(pThis->pCurl); + rc = rtHttpGetCalcStatus(pThis, rcCurl, NULL); + if (RT_SUCCESS(rc)) + rc = pThis->rcOutput; + + int rc2 = RTFileClose(pThis->BodyOutput.uData.hFile); + if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) + rc = rc2; + } + pThis->BodyOutput.uData.hFile = NIL_RTFILE; + } + else + rc = VERR_HTTP_CURL_ERROR; + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +RTR3DECL(int) RTHttpQueryProxyInfoForUrl(RTHTTP hHttp, const char *pszUrl, PRTHTTPPROXYINFO pProxy) +{ + /* + * Validate input and clear output. + */ + Log(("RTHttpQueryProxyInfoForUrl: hHttp=%p pszUrl=%s pProxy=%s\n", hHttp, pszUrl, pProxy)); + RT_ZERO(*pProxy); + pProxy->uProxyPort = UINT32_MAX; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * Set up the proxy for the URL. + */ + rtHttpResetState(pThis); + /** @todo this does a bit too much (we don't need to set up SSL for instance). */ + int rc = rtHttpApplySettings(pThis, pszUrl); + if (RT_SUCCESS(rc)) + { + /* + * Copy out the result. + */ + if (pThis->fNoProxy) + pProxy->enmProxyType = RTHTTPPROXYTYPE_NOPROXY; + else + { + switch (pThis->enmProxyType) + { + case CURLPROXY_HTTP: +#ifdef CURL_AT_LEAST_VERSION +# if CURL_AT_LEAST_VERSION(7,19,4) + case CURLPROXY_HTTP_1_0: +# endif +#endif + pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTP; + break; +#ifdef CURL_AT_LEAST_VERSION +# if CURL_AT_LEAST_VERSION(7,52,0) + case CURLPROXY_HTTPS: + pProxy->enmProxyType = RTHTTPPROXYTYPE_HTTPS; + break; +# endif +#endif + case CURLPROXY_SOCKS4: + case CURLPROXY_SOCKS4A: + pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS4; + break; + case CURLPROXY_SOCKS5: + case CURLPROXY_SOCKS5_HOSTNAME: + pProxy->enmProxyType = RTHTTPPROXYTYPE_SOCKS5; + break; + default: + AssertFailed(); + pProxy->enmProxyType = RTHTTPPROXYTYPE_UNKNOWN; + break; + } + pProxy->uProxyPort = pThis->uProxyPort; + if (pThis->pszProxyHost != NULL) + { + rc = RTStrDupEx(&pProxy->pszProxyHost, pThis->pszProxyHost); + if (pThis->pszProxyUsername && RT_SUCCESS(rc)) + rc = RTStrDupEx(&pProxy->pszProxyUsername, pThis->pszProxyUsername); + if (pThis->pszProxyPassword && RT_SUCCESS(rc)) + rc = RTStrDupEx(&pProxy->pszProxyPassword, pThis->pszProxyPassword); + if (RT_FAILURE(rc)) + RTHttpFreeProxyInfo(pProxy); + } + else + { + AssertFailed(); + rc = VERR_INTERNAL_ERROR; + } + } + } + return rc; +} + + +RTR3DECL(int) RTHttpFreeProxyInfo(PRTHTTPPROXYINFO pProxy) +{ + if (pProxy) + { + RTStrFree(pProxy->pszProxyHost); + RTStrFree(pProxy->pszProxyUsername); + RTStrFree(pProxy->pszProxyPassword); + pProxy->pszProxyHost = NULL; + pProxy->pszProxyUsername = NULL; + pProxy->pszProxyPassword = NULL; + pProxy->enmProxyType = RTHTTPPROXYTYPE_INVALID; + pProxy->uProxyPort = UINT32_MAX; + } + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpPerform(RTHTTP hHttp, const char *pszUrl, RTHTTPMETHOD enmMethod, void const *pvReqBody, size_t cbReqBody, + uint32_t *puHttpStatus, void **ppvHeaders, size_t *pcbHeaders, void **ppvBody, size_t *pcbBody) +{ + /* + * Set safe return values and validate input. + */ + Log(("RTHttpPerform: hHttp=%p pszUrl=%s enmMethod=%d pvReqBody=%p cbReqBody=%zu puHttpStatus=%p ppvHeaders=%p ppvBody=%p\n", + hHttp, pszUrl, enmMethod, pvReqBody, cbReqBody, puHttpStatus, ppvHeaders, ppvBody)); + + if (ppvHeaders) + *ppvHeaders = NULL; + if (pcbHeaders) + *pcbHeaders = 0; + if (ppvBody) + *ppvBody = NULL; + if (pcbBody) + *pcbBody = 0; + if (puHttpStatus) + *puHttpStatus = UINT32_MAX; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(enmMethod > RTHTTPMETHOD_INVALID && enmMethod < RTHTTPMETHOD_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszUrl, VERR_INVALID_POINTER); + +#ifdef LOG_ENABLED + if (LogIs4Enabled() && pThis->pHeaders) + { + Log4(("RTHttpPerform: headers:\n")); + for (struct curl_slist const *pCur = pThis->pHeaders; pCur; pCur = pCur->next) + Log4(("%s\n", pCur->data)); + } + if (pvReqBody && cbReqBody) + Log5(("RTHttpPerform: request body:\n%.*Rhxd\n", cbReqBody, pvReqBody)); +#endif + + /* + * Set the busy flag (paranoia). + */ + bool fBusy = ASMAtomicXchgBool(&pThis->fBusy, true); + AssertReturn(!fBusy, VERR_WRONG_ORDER); + + /* + * Reset the state and apply settings. + */ + rtHttpResetState(pThis); + int rc = rtHttpApplySettings(hHttp, pszUrl); + if (RT_SUCCESS(rc)) + { + /* Set the HTTP method. */ + CURLcode rcCurl = CURLE_BAD_FUNCTION_ARGUMENT; + switch (enmMethod) + { + case RTHTTPMETHOD_GET: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L); + break; + case RTHTTPMETHOD_PUT: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L); + break; + case RTHTTPMETHOD_POST: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L); + break; + case RTHTTPMETHOD_PATCH: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "PATCH"); + break; + case RTHTTPMETHOD_DELETE: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "DELETE"); + break; + case RTHTTPMETHOD_HEAD: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L); + break; + case RTHTTPMETHOD_OPTIONS: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + break; + case RTHTTPMETHOD_TRACE: + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, "TRACE"); + break; +#ifdef IPRT_HTTP_WITH_WEBDAV + case RTHTTPMETHOD_PROPFIND: + RT_FALL_THROUGH(); +#endif + case RTHTTPMETHOD_END: + case RTHTTPMETHOD_INVALID: + case RTHTTPMETHOD_32BIT_HACK: + AssertFailed(); + } + + /* Request body. POST requests should always have a body. */ + if ( pvReqBody + && CURL_SUCCESS(rcCurl) + && ( cbReqBody > 0 + || enmMethod == RTHTTPMETHOD_POST) ) + { + if (enmMethod == RTHTTPMETHOD_POST) + { + /** @todo ??? */ + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cbReqBody); + if (CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pvReqBody); + } + else + { + pThis->ReadData.Mem.pvMem = pvReqBody; + pThis->ReadData.Mem.cbMem = cbReqBody; + pThis->ReadData.Mem.offMem = 0; + rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadData, pThis); + /* curl will use chunked transfer is it doesn't know the body size */ + if (enmMethod == RTHTTPMETHOD_PUT && CURL_SUCCESS(rcCurl)) + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbReqBody); + } + } + else if (pThis->pfnUploadCallback && CURL_SUCCESS(rcCurl)) + rcCurl = rtHttpSetReadCallback(pThis, rtHttpReadDataFromUploadCallback, pThis); + + /* Headers. */ + if (CURL_SUCCESS(rcCurl)) + { + RT_ZERO(pThis->HeadersOutput.uData.Mem); + rcCurl = rtHttpSetHeaderCallback(pThis, rtHttpWriteHeaderData, pThis); + } + + /* Body */ + if (ppvBody && CURL_SUCCESS(rcCurl)) + { + RT_ZERO(pThis->BodyOutput.uData.Mem); + rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteBodyData, pThis); + } + else if (pThis->pfnDownloadCallback && CURL_SUCCESS(rcCurl)) + rcCurl = rtHttpSetWriteCallback(pThis, rtHttpWriteDataToDownloadCallback, pThis); + + if (CURL_SUCCESS(rcCurl)) + { + /* + * Perform the HTTP operation. + */ + rcCurl = curl_easy_perform(pThis->pCurl); + rc = rtHttpGetCalcStatus(pThis, rcCurl, puHttpStatus); + if (RT_SUCCESS(rc)) + rc = pThis->rcOutput; + if (RT_SUCCESS(rc)) + { + if (ppvHeaders) + { + Log(("RTHttpPerform: headers: %zx bytes (allocated %zx)\n", + pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.cbAllocated)); + Log6(("RTHttpPerform: headers blob:\n%.*Rhxd\n", pThis->HeadersOutput.uData.Mem.cb, pThis->HeadersOutput.uData.Mem.pb)); + *ppvHeaders = pThis->HeadersOutput.uData.Mem.pb; + *pcbHeaders = pThis->HeadersOutput.uData.Mem.cb; + pThis->HeadersOutput.uData.Mem.pb = NULL; + } + if (ppvBody) + { + Log(("RTHttpPerform: body: %zx bytes (allocated %zx)\n", + pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.cbAllocated)); + Log7(("RTHttpPerform: body blob:\n%.*Rhxd\n", pThis->BodyOutput.uData.Mem.cb, pThis->BodyOutput.uData.Mem.pb)); + *ppvBody = pThis->BodyOutput.uData.Mem.pb; + *pcbBody = pThis->BodyOutput.uData.Mem.cb; + pThis->BodyOutput.uData.Mem.pb = NULL; + } + } + } + else + rc = VERR_HTTP_CURL_ERROR; + + /* Ensure we've freed all unused output and dropped references to input memory.*/ + if (pThis->HeadersOutput.uData.Mem.pb) + RTMemFree(pThis->HeadersOutput.uData.Mem.pb); + if (pThis->BodyOutput.uData.Mem.pb) + RTMemFree(pThis->BodyOutput.uData.Mem.pb); + RT_ZERO(pThis->HeadersOutput.uData.Mem); + RT_ZERO(pThis->BodyOutput.uData.Mem); + RT_ZERO(pThis->ReadData); + } + + ASMAtomicWriteBool(&pThis->fBusy, false); + return rc; +} + + +/********************************************************************************************************************************* +* Callback APIs. * +*********************************************************************************************************************************/ + +RTR3DECL(int) RTHttpSetUploadCallback(RTHTTP hHttp, uint64_t cbContent, PFNRTHTTPUPLOADCALLBACK pfnCallback, void *pvUser) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + pThis->pfnUploadCallback = pfnCallback; + pThis->pvUploadCallbackUser = pvUser; + pThis->cbUploadContent = cbContent; + pThis->offUploadContent = 0; + + if (cbContent != UINT64_MAX) + { + AssertCompile(sizeof(curl_off_t) == sizeof(uint64_t)); + CURLcode rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cbContent); + AssertMsgReturn(CURL_SUCCESS(rcCurl), ("%d (%#x)\n", rcCurl, rcCurl), VERR_HTTP_CURL_ERROR); + } + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpSetDownloadCallback(RTHTTP hHttp, uint32_t fFlags, PFNRTHTTPDOWNLOADCALLBACK pfnCallback, void *pvUser) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertReturn(!pfnCallback || (fFlags & RTHTTPDOWNLOAD_F_ONLY_STATUS_MASK) != 0, VERR_INVALID_FLAGS); + + pThis->pfnDownloadCallback = pfnCallback; + pThis->pvDownloadCallbackUser = pvUser; + pThis->fDownloadCallback = fFlags; + pThis->uDownloadHttpStatus = UINT32_MAX; + pThis->cbDownloadContent = UINT64_MAX; + pThis->offDownloadContent = 0; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpSetDownloadProgressCallback(RTHTTP hHttp, PFNRTHTTPDOWNLDPROGRCALLBACK pfnCallback, void *pvUser) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + pThis->pfnDownloadProgress = pfnCallback; + pThis->pvDownloadProgressUser = pvUser; + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpSetHeaderCallback(RTHTTP hHttp, PFNRTHTTPHEADERCALLBACK pfnCallback, void *pvUser) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + pThis->pfnHeaderCallback = pfnCallback; + pThis->pvHeaderCallbackUser = pvUser; + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Temporary raw cURL stuff. Will be gone before 6.0 is out! * +*********************************************************************************************************************************/ + +RTR3DECL(int) RTHttpRawSetUrl(RTHTTP hHttp, const char *pszUrl) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + int rc = rtHttpConfigureProxyForUrl(pThis, pszUrl); + if (RT_FAILURE(rc)) + return rc; + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_URL, pszUrl); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetGet(RTHTTP hHttp) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetHead(RTHTTP hHttp) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HTTPGET, 1L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_NOBODY, 1L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetPost(RTHTTP hHttp) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POST, 1L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetPut(RTHTTP hHttp) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_PUT, 1L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetDelete(RTHTTP hHttp) +{ + /* curl doesn't provide an option for this */ + return RTHttpRawSetCustomRequest(hHttp, "DELETE"); +} + + +RTR3DECL(int) RTHttpRawSetCustomRequest(RTHTTP hHttp, const char *pszVerb) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CUSTOMREQUEST, pszVerb); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetPostFields(RTHTTP hHttp, const void *pv, size_t cb) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDSIZE, cb); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_POSTFIELDS, pv); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + +RTR3DECL(int) RTHttpRawSetInfileSize(RTHTTP hHttp, RTFOFF cb) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_INFILESIZE_LARGE, cb); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetVerbose(RTHTTP hHttp, bool fValue) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_VERBOSE, fValue ? 1L : 0L); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetTimeout(RTHTTP hHttp, long sec) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_TIMEOUT, sec); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawPerform(RTHTTP hHttp) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + /* + * XXX: Do this here for now as a stop-gap measure as + * RTHttpReset() resets this (and proxy settings). + */ + if (pThis->pszCaFile) + { + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_CAINFO, pThis->pszCaFile); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + } + + rcCurl = curl_easy_perform(pThis->pCurl); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawGetResponseCode(RTHTTP hHttp, long *plCode) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + AssertPtrReturn(plCode, VERR_INVALID_PARAMETER); + + rcCurl = curl_easy_getinfo(pThis->pCurl, CURLINFO_RESPONSE_CODE, plCode); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetReadCallback(RTHTTP hHttp, PFNRTHTTPREADCALLBACKRAW pfnRead, void *pvUser) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READFUNCTION, pfnRead); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_READDATA, pvUser); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetWriteCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser) +{ + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + CURLcode rcCurl = rtHttpSetWriteCallback(pThis, pfnWrite, pvUser); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + + +RTR3DECL(int) RTHttpRawSetWriteHeaderCallback(RTHTTP hHttp, PFNRTHTTPWRITECALLBACKRAW pfnWrite, void *pvUser) +{ + CURLcode rcCurl; + + PRTHTTPINTERNAL pThis = hHttp; + RTHTTP_VALID_RETURN(pThis); + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERFUNCTION, pfnWrite); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + rcCurl = curl_easy_setopt(pThis->pCurl, CURLOPT_HEADERDATA, pvUser); + if (CURL_FAILURE(rcCurl)) + return VERR_HTTP_CURL_ERROR; + + return VINF_SUCCESS; +} + |