/* $Id: http-curl.cpp 96407 2022-08-22 17:43:14Z vboxsync $ */ /** @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 . * * 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 #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal/magics.h" #ifdef RT_OS_WINDOWS /* curl.h drags in windows.h which isn't necessarily -Wall clean. */ # include #endif #include #ifdef RT_OS_DARWIN # include # include # include #endif #ifdef RT_OS_WINDOWS # include # include "../r3/win/internal-r3-win.h" #endif #ifdef RT_OS_LINUX # define IPRT_USE_LIBPROXY #endif #ifdef IPRT_USE_LIBPROXY # include /* 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 * ["://"][[@]:][":"] * @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 , 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 , 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 "" 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("") - 1 && memcmp(pszEntry, RT_STR_TUPLE("")) == 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: * [=]["://"][":"] */ 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; }