mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-06-15 14:15:24 -06:00
896 lines
34 KiB
C++
896 lines
34 KiB
C++
/******************************************************************************
|
|
* $Id: cpl_http.cpp 29152 2015-05-04 11:45:44Z rouault $
|
|
*
|
|
* Project: libcurl based HTTP client
|
|
* Purpose: libcurl based HTTP client
|
|
* Author: Frank Warmerdam, warmerdam@pobox.com
|
|
*
|
|
******************************************************************************
|
|
* Copyright (c) 2006, Frank Warmerdam
|
|
* Copyright (c) 2008-2013, Even Rouault <even dot rouault at mines-paris dot org>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included
|
|
* in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
****************************************************************************/
|
|
|
|
#include <map>
|
|
#include "cpl_http.h"
|
|
#include "cpl_multiproc.h"
|
|
|
|
#ifdef HAVE_CURL
|
|
# include <curl/curl.h>
|
|
|
|
void CPLHTTPSetOptions(CURL *http_handle, char** papszOptions);
|
|
|
|
/* CURLINFO_RESPONSE_CODE was known as CURLINFO_HTTP_CODE in libcurl 7.10.7 and earlier */
|
|
#if LIBCURL_VERSION_NUM < 0x070a07
|
|
#define CURLINFO_RESPONSE_CODE CURLINFO_HTTP_CODE
|
|
#endif
|
|
|
|
#endif
|
|
|
|
CPL_CVSID("$Id: cpl_http.cpp 29152 2015-05-04 11:45:44Z rouault $");
|
|
|
|
// list of named persistent http sessions
|
|
|
|
#ifdef HAVE_CURL
|
|
static std::map<CPLString,CURL*>* poSessionMap = NULL;
|
|
static CPLMutex *hSessionMapMutex = NULL;
|
|
#endif
|
|
|
|
/************************************************************************/
|
|
/* CPLWriteFct() */
|
|
/* */
|
|
/* Append incoming text to our collection buffer, reallocating */
|
|
/* it larger as needed. */
|
|
/************************************************************************/
|
|
|
|
#ifdef HAVE_CURL
|
|
static size_t
|
|
CPLWriteFct(void *buffer, size_t size, size_t nmemb, void *reqInfo)
|
|
|
|
{
|
|
CPLHTTPResult *psResult = (CPLHTTPResult *) reqInfo;
|
|
int nNewSize;
|
|
|
|
nNewSize = psResult->nDataLen + nmemb*size + 1;
|
|
if( nNewSize > psResult->nDataAlloc )
|
|
{
|
|
psResult->nDataAlloc = (int) (nNewSize * 1.25 + 100);
|
|
GByte* pabyNewData = (GByte *) VSIRealloc(psResult->pabyData,
|
|
psResult->nDataAlloc);
|
|
if( pabyNewData == NULL )
|
|
{
|
|
VSIFree(psResult->pabyData);
|
|
psResult->pabyData = NULL;
|
|
psResult->pszErrBuf = CPLStrdup(CPLString().Printf("Out of memory allocating %d bytes for HTTP data buffer.", psResult->nDataAlloc));
|
|
psResult->nDataAlloc = psResult->nDataLen = 0;
|
|
|
|
return 0;
|
|
}
|
|
psResult->pabyData = pabyNewData;
|
|
}
|
|
|
|
memcpy( psResult->pabyData + psResult->nDataLen, buffer,
|
|
nmemb * size );
|
|
|
|
psResult->nDataLen += nmemb * size;
|
|
psResult->pabyData[psResult->nDataLen] = 0;
|
|
|
|
return nmemb;
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLHdrWriteFct() */
|
|
/************************************************************************/
|
|
static size_t CPLHdrWriteFct(void *buffer, size_t size, size_t nmemb, void *reqInfo)
|
|
{
|
|
CPLHTTPResult *psResult = (CPLHTTPResult *) reqInfo;
|
|
// copy the buffer to a char* and initialize with zeros (zero terminate as well)
|
|
char* pszHdr = (char*)CPLCalloc(nmemb + 1, size);
|
|
CPLPrintString(pszHdr, (char *)buffer, nmemb * size);
|
|
char *pszKey = NULL;
|
|
const char *pszValue = CPLParseNameValue(pszHdr, &pszKey );
|
|
psResult->papszHeaders = CSLSetNameValue(psResult->papszHeaders, pszKey, pszValue);
|
|
CPLFree(pszHdr);
|
|
CPLFree(pszKey);
|
|
return nmemb;
|
|
}
|
|
|
|
#endif /* def HAVE_CURL */
|
|
|
|
/************************************************************************/
|
|
/* CPLHTTPFetch() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* \brief Fetch a document from an url and return in a string.
|
|
*
|
|
* @param pszURL valid URL recognized by underlying download library (libcurl)
|
|
* @param papszOptions option list as a NULL-terminated array of strings. May be NULL.
|
|
* The following options are handled :
|
|
* <ul>
|
|
* <li>TIMEOUT=val, where val is in seconds</li>
|
|
* <li>HEADERS=val, where val is an extra header to use when getting a web page.
|
|
* For example "Accept: application/x-ogcwkt"
|
|
* <li>HTTPAUTH=[BASIC/NTLM/GSSNEGOTIATE/ANY] to specify an authentication scheme to use.
|
|
* <li>USERPWD=userid:password to specify a user and password for authentication
|
|
* <li>POSTFIELDS=val, where val is a nul-terminated string to be passed to the server
|
|
* with a POST request.
|
|
* <li>PROXY=val, to make requests go through a proxy server, where val is of the
|
|
* form proxy.server.com:port_number
|
|
* <li>PROXYUSERPWD=val, where val is of the form username:password
|
|
* <li>PROXYAUTH=[BASIC/NTLM/DIGEST/ANY] to specify an proxy authentication scheme to use.
|
|
* <li>NETRC=[YES/NO] to enable or disable use of $HOME/.netrc, default YES.
|
|
* <li>CUSTOMREQUEST=val, where val is GET, PUT, POST, DELETE, etc.. (GDAL >= 1.9.0)
|
|
* <li>COOKIE=val, where val is formatted as COOKIE1=VALUE1; COOKIE2=VALUE2; ...
|
|
* <li>MAX_RETRY=val, where val is the maximum number of retry attempts if a 503 or
|
|
* 504 HTTP error occurs. Default is 0. (GDAL >= 2.0)
|
|
* <li>RETRY_DELAY=val, where val is the number of seconds between retry attempts.
|
|
* Default is 30. (GDAL >= 2.0)
|
|
* </ul>
|
|
*
|
|
* Alternatively, if not defined in the papszOptions arguments, the PROXY,
|
|
* PROXYUSERPWD, PROXYAUTH, NETRC, MAX_RETRY and RETRY_DELAY values are searched in the configuration
|
|
* options named GDAL_HTTP_PROXY, GDAL_HTTP_PROXYUSERPWD, GDAL_PROXY_AUTH,
|
|
* GDAL_HTTP_NETRC, GDAL_HTTP_MAX_RETRY and GDAL_HTTP_RETRY_DELAY.
|
|
*
|
|
* @return a CPLHTTPResult* structure that must be freed by
|
|
* CPLHTTPDestroyResult(), or NULL if libcurl support is disabled
|
|
*/
|
|
CPLHTTPResult *CPLHTTPFetch( const char *pszURL, char **papszOptions )
|
|
|
|
{
|
|
if( strncmp(pszURL, "/vsimem/", strlen("/vsimem/")) == 0 &&
|
|
/* Disabled by default for potential security issues */
|
|
CSLTestBoolean(CPLGetConfigOption("CPL_CURL_ENABLE_VSIMEM", "FALSE")) )
|
|
{
|
|
CPLString osURL(pszURL);
|
|
const char* pszPost = CSLFetchNameValue( papszOptions, "POSTFIELDS" );
|
|
if( pszPost != NULL ) /* Hack: we append post content to filename */
|
|
{
|
|
osURL += "&POSTFIELDS=";
|
|
osURL += pszPost;
|
|
}
|
|
vsi_l_offset nLength = 0;
|
|
CPLHTTPResult* psResult = (CPLHTTPResult* )CPLCalloc(1, sizeof(CPLHTTPResult));
|
|
GByte* pabyData = VSIGetMemFileBuffer( osURL, &nLength, FALSE );
|
|
if( pabyData == NULL )
|
|
{
|
|
CPLDebug("HTTP", "Cannot find %s", osURL.c_str());
|
|
psResult->nStatus = 1;
|
|
psResult->pszErrBuf = CPLStrdup(CPLSPrintf("HTTP error code : %d", 404));
|
|
CPLError( CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf );
|
|
}
|
|
else if( nLength != 0 )
|
|
{
|
|
psResult->nDataLen = (size_t)nLength;
|
|
psResult->pabyData = (GByte*) CPLMalloc((size_t)nLength + 1);
|
|
memcpy(psResult->pabyData, pabyData, (size_t)nLength);
|
|
psResult->pabyData[(size_t)nLength] = 0;
|
|
}
|
|
|
|
if( psResult->pabyData != NULL &&
|
|
strncmp((const char*)psResult->pabyData, "Content-Type: ",
|
|
strlen("Content-Type: ")) == 0 )
|
|
{
|
|
const char* pszContentType = (const char*)psResult->pabyData + strlen("Content-type: ");
|
|
const char* pszEOL = strchr(pszContentType, '\r');
|
|
if( pszEOL )
|
|
pszEOL = strchr(pszContentType, '\n');
|
|
if( pszEOL )
|
|
{
|
|
int nLength = pszEOL - pszContentType;
|
|
psResult->pszContentType = (char*)CPLMalloc(nLength + 1);
|
|
memcpy(psResult->pszContentType, pszContentType, nLength);
|
|
psResult->pszContentType[nLength] = 0;
|
|
}
|
|
}
|
|
|
|
return psResult;
|
|
}
|
|
|
|
#ifndef HAVE_CURL
|
|
(void) papszOptions;
|
|
(void) pszURL;
|
|
|
|
CPLError( CE_Failure, CPLE_NotSupported,
|
|
"GDAL/OGR not compiled with libcurl support, remote requests not supported." );
|
|
return NULL;
|
|
#else
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Are we using a persistent named session? If so, search for */
|
|
/* or create it. */
|
|
/* */
|
|
/* Currently this code does not attempt to protect against */
|
|
/* multiple threads asking for the same named session. If that */
|
|
/* occurs it will be in use in multiple threads at once which */
|
|
/* might have bad consequences depending on what guarantees */
|
|
/* libcurl gives - which I have not investigated. */
|
|
/* -------------------------------------------------------------------- */
|
|
CURL *http_handle = NULL;
|
|
|
|
const char *pszPersistent = CSLFetchNameValue( papszOptions, "PERSISTENT" );
|
|
const char *pszClosePersistent = CSLFetchNameValue( papszOptions, "CLOSE_PERSISTENT" );
|
|
if (pszPersistent)
|
|
{
|
|
CPLString osSessionName = pszPersistent;
|
|
CPLMutexHolder oHolder( &hSessionMapMutex );
|
|
|
|
if( poSessionMap == NULL )
|
|
poSessionMap = new std::map<CPLString,CURL*>;
|
|
if( poSessionMap->count( osSessionName ) == 0 )
|
|
{
|
|
(*poSessionMap)[osSessionName] = curl_easy_init();
|
|
CPLDebug( "HTTP", "Establish persistent session named '%s'.",
|
|
osSessionName.c_str() );
|
|
}
|
|
|
|
http_handle = (*poSessionMap)[osSessionName];
|
|
}
|
|
/* -------------------------------------------------------------------- */
|
|
/* Are we requested to close a persistent named session? */
|
|
/* -------------------------------------------------------------------- */
|
|
else if (pszClosePersistent)
|
|
{
|
|
CPLString osSessionName = pszClosePersistent;
|
|
CPLMutexHolder oHolder( &hSessionMapMutex );
|
|
|
|
if( poSessionMap )
|
|
{
|
|
std::map<CPLString,CURL*>::iterator oIter = poSessionMap->find( osSessionName );
|
|
if( oIter != poSessionMap->end() )
|
|
{
|
|
curl_easy_cleanup(oIter->second);
|
|
poSessionMap->erase(oIter);
|
|
if( poSessionMap->size() == 0 )
|
|
{
|
|
delete poSessionMap;
|
|
poSessionMap = NULL;
|
|
}
|
|
CPLDebug( "HTTP", "Ended persistent session named '%s'.",
|
|
osSessionName.c_str() );
|
|
}
|
|
else
|
|
{
|
|
CPLDebug( "HTTP", "Could not find persistent session named '%s'.",
|
|
osSessionName.c_str() );
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
else
|
|
http_handle = curl_easy_init();
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Setup the request. */
|
|
/* -------------------------------------------------------------------- */
|
|
char szCurlErrBuf[CURL_ERROR_SIZE+1];
|
|
CPLHTTPResult *psResult;
|
|
struct curl_slist *headers=NULL;
|
|
|
|
const char* pszArobase = strchr(pszURL, '@');
|
|
const char* pszSlash = strchr(pszURL, '/');
|
|
const char* pszColon = (pszSlash) ? strchr(pszSlash, ':') : NULL;
|
|
if (pszArobase != NULL && pszColon != NULL && pszArobase - pszColon > 0)
|
|
{
|
|
/* http://user:password@www.example.com */
|
|
char* pszSanitizedURL = CPLStrdup(pszURL);
|
|
pszSanitizedURL[pszColon-pszURL] = 0;
|
|
CPLDebug( "HTTP", "Fetch(%s:#password#%s)", pszSanitizedURL, pszArobase );
|
|
CPLFree(pszSanitizedURL);
|
|
}
|
|
else
|
|
{
|
|
CPLDebug( "HTTP", "Fetch(%s)", pszURL );
|
|
}
|
|
|
|
psResult = (CPLHTTPResult *) CPLCalloc(1,sizeof(CPLHTTPResult));
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_URL, pszURL );
|
|
|
|
CPLHTTPSetOptions(http_handle, papszOptions);
|
|
|
|
// turn off SSL verification, accept all servers with ssl
|
|
curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYPEER, FALSE);
|
|
|
|
/* Set Headers.*/
|
|
const char *pszHeaders = CSLFetchNameValue( papszOptions, "HEADERS" );
|
|
if( pszHeaders != NULL ) {
|
|
CPLDebug ("HTTP", "These HTTP headers were set: %s", pszHeaders);
|
|
headers = curl_slist_append(headers, pszHeaders);
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTPHEADER, headers);
|
|
}
|
|
|
|
// are we making a head request
|
|
const char* pszNoBody = NULL;
|
|
if ((pszNoBody = CSLFetchNameValue( papszOptions, "NO_BODY" )) != NULL)
|
|
{
|
|
if (CSLTestBoolean(pszNoBody))
|
|
{
|
|
CPLDebug ("HTTP", "HEAD Request: %s", pszURL);
|
|
curl_easy_setopt(http_handle, CURLOPT_NOBODY, 1L);
|
|
}
|
|
}
|
|
|
|
// capture response headers
|
|
curl_easy_setopt(http_handle, CURLOPT_HEADERDATA, psResult);
|
|
curl_easy_setopt(http_handle, CURLOPT_HEADERFUNCTION, CPLHdrWriteFct);
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_WRITEDATA, psResult );
|
|
curl_easy_setopt(http_handle, CURLOPT_WRITEFUNCTION, CPLWriteFct );
|
|
|
|
szCurlErrBuf[0] = '\0';
|
|
|
|
curl_easy_setopt(http_handle, CURLOPT_ERRORBUFFER, szCurlErrBuf );
|
|
|
|
static int bHasCheckVersion = FALSE;
|
|
static int bSupportGZip = FALSE;
|
|
if (!bHasCheckVersion)
|
|
{
|
|
bSupportGZip = strstr(curl_version(), "zlib/") != NULL;
|
|
bHasCheckVersion = TRUE;
|
|
}
|
|
int bGZipRequested = FALSE;
|
|
if (bSupportGZip && CSLTestBoolean(CPLGetConfigOption("CPL_CURL_GZIP", "YES")))
|
|
{
|
|
bGZipRequested = TRUE;
|
|
curl_easy_setopt(http_handle, CURLOPT_ENCODING, "gzip");
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* If 502, 503 or 504 status code retry this HTTP call until max */
|
|
/* retry has been rearched */
|
|
/* -------------------------------------------------------------------- */
|
|
const char *pszRetryDelay = CSLFetchNameValue( papszOptions, "RETRY_DELAY" );
|
|
if( pszRetryDelay == NULL )
|
|
pszRetryDelay = CPLGetConfigOption( "GDAL_HTTP_RETRY_DELAY", "30" );
|
|
const char *pszMaxRetries = CSLFetchNameValue( papszOptions, "MAX_RETRY" );
|
|
if( pszMaxRetries == NULL )
|
|
pszMaxRetries = CPLGetConfigOption( "GDAL_HTTP_MAX_RETRY", "0" );
|
|
int nRetryDelaySecs = atoi(pszRetryDelay);
|
|
int nMaxRetries = atoi(pszMaxRetries);
|
|
int nRetryCount = 0;
|
|
bool bRequestRetry;
|
|
|
|
do
|
|
{
|
|
bRequestRetry = FALSE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Execute the request, waiting for results. */
|
|
/* -------------------------------------------------------------------- */
|
|
psResult->nStatus = (int) curl_easy_perform( http_handle );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Fetch content-type if possible. */
|
|
/* -------------------------------------------------------------------- */
|
|
psResult->pszContentType = NULL;
|
|
curl_easy_getinfo( http_handle, CURLINFO_CONTENT_TYPE,
|
|
&(psResult->pszContentType) );
|
|
if( psResult->pszContentType != NULL )
|
|
psResult->pszContentType = CPLStrdup(psResult->pszContentType);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Have we encountered some sort of error? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( strlen(szCurlErrBuf) > 0 )
|
|
{
|
|
int bSkipError = FALSE;
|
|
|
|
/* Some servers such as http://115.113.193.14/cgi-bin/world/qgis_mapserv.fcgi?VERSION=1.1.1&SERVICE=WMS&REQUEST=GetCapabilities */
|
|
/* invalidly return Content-Length as the uncompressed size, with makes curl to wait for more data */
|
|
/* and time-out finally. If we got the expected data size, then we don't emit an error */
|
|
/* but turn off GZip requests */
|
|
if (bGZipRequested &&
|
|
strstr(szCurlErrBuf, "transfer closed with") &&
|
|
strstr(szCurlErrBuf, "bytes remaining to read"))
|
|
{
|
|
const char* pszContentLength =
|
|
CSLFetchNameValue(psResult->papszHeaders, "Content-Length");
|
|
if (pszContentLength && psResult->nDataLen != 0 &&
|
|
atoi(pszContentLength) == psResult->nDataLen)
|
|
{
|
|
const char* pszCurlGZIPOption = CPLGetConfigOption("CPL_CURL_GZIP", NULL);
|
|
if (pszCurlGZIPOption == NULL)
|
|
{
|
|
CPLSetConfigOption("CPL_CURL_GZIP", "NO");
|
|
CPLDebug("HTTP", "Disabling CPL_CURL_GZIP, because %s doesn't support it properly",
|
|
pszURL);
|
|
}
|
|
psResult->nStatus = 0;
|
|
bSkipError = TRUE;
|
|
}
|
|
}
|
|
if (!bSkipError)
|
|
{
|
|
psResult->pszErrBuf = CPLStrdup(szCurlErrBuf);
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"%s", szCurlErrBuf );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* HTTP errors do not trigger curl errors. But we need to */
|
|
/* propagate them to the caller though */
|
|
long response_code = 0;
|
|
curl_easy_getinfo(http_handle, CURLINFO_RESPONSE_CODE, &response_code);
|
|
|
|
if (response_code >= 400 && response_code < 600)
|
|
{
|
|
/* If HTTP 502, 503 or 504 gateway timeout error retry after a pause */
|
|
if ((response_code >= 502 && response_code <= 504) && nRetryCount < nMaxRetries)
|
|
{
|
|
CPLError(CE_Warning, CPLE_AppDefined,
|
|
"HTTP error code: %d - %s. Retrying again in %d secs",
|
|
(int)response_code, pszURL, nRetryDelaySecs);
|
|
CPLSleep(nRetryDelaySecs);
|
|
nRetryCount++;
|
|
|
|
CPLFree(psResult->pszContentType);
|
|
psResult->pszContentType = NULL;
|
|
CSLDestroy(psResult->papszHeaders);
|
|
psResult->papszHeaders = NULL;
|
|
CPLFree(psResult->pabyData);
|
|
psResult->pabyData = NULL;
|
|
psResult->nDataLen = 0;
|
|
psResult->nDataAlloc = 0;
|
|
|
|
bRequestRetry = TRUE;
|
|
}
|
|
else
|
|
{
|
|
psResult->pszErrBuf = CPLStrdup(CPLSPrintf("HTTP error code : %d", (int)response_code));
|
|
CPLError( CE_Failure, CPLE_AppDefined, "%s", psResult->pszErrBuf );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
while (bRequestRetry);
|
|
|
|
if (!pszPersistent)
|
|
curl_easy_cleanup( http_handle );
|
|
|
|
curl_slist_free_all(headers);
|
|
|
|
return psResult;
|
|
#endif /* def HAVE_CURL */
|
|
}
|
|
|
|
#ifdef HAVE_CURL
|
|
/************************************************************************/
|
|
/* CPLHTTPSetOptions() */
|
|
/************************************************************************/
|
|
|
|
void CPLHTTPSetOptions(CURL *http_handle, char** papszOptions)
|
|
{
|
|
if (CSLTestBoolean(CPLGetConfigOption("CPL_CURL_VERBOSE", "NO")))
|
|
curl_easy_setopt(http_handle, CURLOPT_VERBOSE, 1);
|
|
|
|
const char *pszHttpVersion = CSLFetchNameValue( papszOptions, "HTTP_VERSION");
|
|
if( pszHttpVersion && strcmp(pszHttpVersion, "1.0") == 0 )
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0 );
|
|
|
|
/* Support control over HTTPAUTH */
|
|
const char *pszHttpAuth = CSLFetchNameValue( papszOptions, "HTTPAUTH" );
|
|
if( pszHttpAuth == NULL )
|
|
pszHttpAuth = CPLGetConfigOption( "GDAL_HTTP_AUTH", NULL );
|
|
if( pszHttpAuth == NULL )
|
|
/* do nothing */;
|
|
|
|
/* CURLOPT_HTTPAUTH is defined in curl 7.11.0 or newer */
|
|
#if LIBCURL_VERSION_NUM >= 0x70B00
|
|
else if( EQUAL(pszHttpAuth,"BASIC") )
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
|
|
else if( EQUAL(pszHttpAuth,"NTLM") )
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM );
|
|
else if( EQUAL(pszHttpAuth,"ANY") )
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
|
|
#ifdef CURLAUTH_GSSNEGOTIATE
|
|
else if( EQUAL(pszHttpAuth,"NEGOTIATE") )
|
|
curl_easy_setopt(http_handle, CURLOPT_HTTPAUTH, CURLAUTH_GSSNEGOTIATE );
|
|
#endif
|
|
else
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Unsupported HTTPAUTH value '%s', ignored.",
|
|
pszHttpAuth );
|
|
}
|
|
#else
|
|
else
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"HTTPAUTH option needs curl >= 7.11.0" );
|
|
}
|
|
#endif
|
|
|
|
/* Support use of .netrc - default enabled */
|
|
const char *pszHttpNetrc = CSLFetchNameValue( papszOptions, "NETRC" );
|
|
if( pszHttpNetrc == NULL )
|
|
pszHttpNetrc = CPLGetConfigOption( "GDAL_HTTP_NETRC", "YES" );
|
|
if( pszHttpNetrc == NULL || CSLTestBoolean(pszHttpNetrc) )
|
|
curl_easy_setopt(http_handle, CURLOPT_NETRC, 1L);
|
|
|
|
/* Support setting userid:password */
|
|
const char *pszUserPwd = CSLFetchNameValue( papszOptions, "USERPWD" );
|
|
if (pszUserPwd == NULL)
|
|
pszUserPwd = CPLGetConfigOption("GDAL_HTTP_USERPWD", NULL);
|
|
if( pszUserPwd != NULL )
|
|
curl_easy_setopt(http_handle, CURLOPT_USERPWD, pszUserPwd );
|
|
|
|
/* Set Proxy parameters */
|
|
const char* pszProxy = CSLFetchNameValue( papszOptions, "PROXY" );
|
|
if (pszProxy == NULL)
|
|
pszProxy = CPLGetConfigOption("GDAL_HTTP_PROXY", NULL);
|
|
if (pszProxy)
|
|
curl_easy_setopt(http_handle,CURLOPT_PROXY,pszProxy);
|
|
|
|
const char* pszProxyUserPwd = CSLFetchNameValue( papszOptions, "PROXYUSERPWD" );
|
|
if (pszProxyUserPwd == NULL)
|
|
pszProxyUserPwd = CPLGetConfigOption("GDAL_HTTP_PROXYUSERPWD", NULL);
|
|
if (pszProxyUserPwd)
|
|
curl_easy_setopt(http_handle,CURLOPT_PROXYUSERPWD,pszProxyUserPwd);
|
|
|
|
/* Support control over PROXYAUTH */
|
|
const char *pszProxyAuth = CSLFetchNameValue( papszOptions, "PROXYAUTH" );
|
|
if( pszProxyAuth == NULL )
|
|
pszProxyAuth = CPLGetConfigOption( "GDAL_PROXY_AUTH", NULL );
|
|
if( pszProxyAuth == NULL )
|
|
/* do nothing */;
|
|
/* CURLOPT_PROXYAUTH is defined in curl 7.11.0 or newer */
|
|
#if LIBCURL_VERSION_NUM >= 0x70B00
|
|
else if( EQUAL(pszProxyAuth,"BASIC") )
|
|
curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH, CURLAUTH_BASIC );
|
|
else if( EQUAL(pszProxyAuth,"NTLM") )
|
|
curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH, CURLAUTH_NTLM );
|
|
else if( EQUAL(pszProxyAuth,"DIGEST") )
|
|
curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST );
|
|
else if( EQUAL(pszProxyAuth,"ANY") )
|
|
curl_easy_setopt(http_handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY );
|
|
else
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"Unsupported PROXYAUTH value '%s', ignored.",
|
|
pszProxyAuth );
|
|
}
|
|
#else
|
|
else
|
|
{
|
|
CPLError( CE_Warning, CPLE_AppDefined,
|
|
"PROXYAUTH option needs curl >= 7.11.0" );
|
|
}
|
|
#endif
|
|
|
|
/* Enable following redirections. Requires libcurl 7.10.1 at least */
|
|
curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1 );
|
|
curl_easy_setopt(http_handle, CURLOPT_MAXREDIRS, 10 );
|
|
|
|
/* Set timeout.*/
|
|
const char *pszTimeout = CSLFetchNameValue( papszOptions, "TIMEOUT" );
|
|
if (pszTimeout == NULL)
|
|
pszTimeout = CPLGetConfigOption("GDAL_HTTP_TIMEOUT", NULL);
|
|
if( pszTimeout != NULL )
|
|
curl_easy_setopt(http_handle, CURLOPT_TIMEOUT, atoi(pszTimeout) );
|
|
|
|
/* Disable some SSL verification */
|
|
const char *pszUnsafeSSL = CSLFetchNameValue( papszOptions, "UNSAFESSL" );
|
|
if (pszUnsafeSSL == NULL)
|
|
pszUnsafeSSL = CPLGetConfigOption("GDAL_HTTP_UNSAFESSL", NULL);
|
|
if (pszUnsafeSSL != NULL && CSLTestBoolean(pszUnsafeSSL))
|
|
{
|
|
curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
curl_easy_setopt(http_handle, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
}
|
|
|
|
/* Set Referer */
|
|
const char *pszReferer = CSLFetchNameValue(papszOptions, "REFERER");
|
|
if (pszReferer != NULL)
|
|
curl_easy_setopt(http_handle, CURLOPT_REFERER, pszReferer);
|
|
|
|
/* Set User-Agent */
|
|
const char *pszUserAgent = CSLFetchNameValue(papszOptions, "USERAGENT");
|
|
if (pszUserAgent == NULL)
|
|
pszUserAgent = CPLGetConfigOption("GDAL_HTTP_USERAGENT", NULL);
|
|
if (pszUserAgent != NULL)
|
|
curl_easy_setopt(http_handle, CURLOPT_USERAGENT, pszUserAgent);
|
|
|
|
/* NOSIGNAL should be set to true for timeout to work in multithread
|
|
* environments on Unix, requires libcurl 7.10 or more recent.
|
|
* (this force avoiding the use of signal handlers)
|
|
*/
|
|
#if LIBCURL_VERSION_NUM >= 0x070A00
|
|
curl_easy_setopt(http_handle, CURLOPT_NOSIGNAL, 1 );
|
|
#endif
|
|
|
|
/* Set POST mode */
|
|
const char* pszPost = CSLFetchNameValue( papszOptions, "POSTFIELDS" );
|
|
if( pszPost != NULL )
|
|
{
|
|
CPLDebug("HTTP", "These POSTFIELDS were sent:%.4000s", pszPost);
|
|
curl_easy_setopt(http_handle, CURLOPT_POST, 1 );
|
|
curl_easy_setopt(http_handle, CURLOPT_POSTFIELDS, pszPost );
|
|
}
|
|
|
|
const char* pszCustomRequest = CSLFetchNameValue( papszOptions, "CUSTOMREQUEST" );
|
|
if( pszCustomRequest != NULL )
|
|
{
|
|
curl_easy_setopt(http_handle, CURLOPT_CUSTOMREQUEST, pszCustomRequest );
|
|
}
|
|
|
|
const char* pszCookie = CSLFetchNameValue(papszOptions, "COOKIE");
|
|
if (pszCookie == NULL)
|
|
pszCookie = CPLGetConfigOption("GDAL_HTTP_COOKIE", NULL);
|
|
if (pszCookie != NULL)
|
|
curl_easy_setopt(http_handle, CURLOPT_COOKIE, pszCookie);
|
|
}
|
|
#endif /* def HAVE_CURL */
|
|
|
|
/************************************************************************/
|
|
/* CPLHTTPEnabled() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* \brief Return if CPLHTTP services can be useful
|
|
*
|
|
* Those services depend on GDAL being build with libcurl support.
|
|
*
|
|
* @return TRUE if libcurl support is enabled
|
|
*/
|
|
int CPLHTTPEnabled()
|
|
|
|
{
|
|
#ifdef HAVE_CURL
|
|
return TRUE;
|
|
#else
|
|
return FALSE;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLHTTPCleanup() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* \brief Cleanup function to call at application termination
|
|
*/
|
|
void CPLHTTPCleanup()
|
|
|
|
{
|
|
#ifdef HAVE_CURL
|
|
if( !hSessionMapMutex )
|
|
return;
|
|
|
|
{
|
|
CPLMutexHolder oHolder( &hSessionMapMutex );
|
|
std::map<CPLString,CURL*>::iterator oIt;
|
|
if( poSessionMap )
|
|
{
|
|
for( oIt=poSessionMap->begin(); oIt != poSessionMap->end(); oIt++ )
|
|
curl_easy_cleanup( oIt->second );
|
|
delete poSessionMap;
|
|
poSessionMap = NULL;
|
|
}
|
|
}
|
|
|
|
// not quite a safe sequence.
|
|
CPLDestroyMutex( hSessionMapMutex );
|
|
hSessionMapMutex = NULL;
|
|
#endif
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLHTTPDestroyResult() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* \brief Clean the memory associated with the return value of CPLHTTPFetch()
|
|
*
|
|
* @param psResult pointer to the return value of CPLHTTPFetch()
|
|
*/
|
|
void CPLHTTPDestroyResult( CPLHTTPResult *psResult )
|
|
|
|
{
|
|
if( psResult )
|
|
{
|
|
CPLFree( psResult->pabyData );
|
|
CPLFree( psResult->pszErrBuf );
|
|
CPLFree( psResult->pszContentType );
|
|
CSLDestroy( psResult->papszHeaders );
|
|
|
|
int i;
|
|
for(i=0;i<psResult->nMimePartCount;i++)
|
|
{
|
|
CSLDestroy( psResult->pasMimePart[i].papszHeaders );
|
|
}
|
|
CPLFree(psResult->pasMimePart);
|
|
|
|
CPLFree( psResult );
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* CPLHTTPParseMultipartMime() */
|
|
/************************************************************************/
|
|
|
|
/**
|
|
* \brief Parses a a MIME multipart message
|
|
*
|
|
* This function will iterate over each part and put it in a separate
|
|
* element of the pasMimePart array of the provided psResult structure.
|
|
*
|
|
* @param psResult pointer to the return value of CPLHTTPFetch()
|
|
* @return TRUE if the message contains MIME multipart message.
|
|
*/
|
|
int CPLHTTPParseMultipartMime( CPLHTTPResult *psResult )
|
|
|
|
{
|
|
/* -------------------------------------------------------------------- */
|
|
/* Is it already done? */
|
|
/* -------------------------------------------------------------------- */
|
|
if( psResult->nMimePartCount > 0 )
|
|
return TRUE;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Find the boundary setting in the content type. */
|
|
/* -------------------------------------------------------------------- */
|
|
const char *pszBound = NULL;
|
|
|
|
if( psResult->pszContentType != NULL )
|
|
pszBound = strstr(psResult->pszContentType,"boundary=");
|
|
|
|
if( pszBound == NULL )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to parse multi-part mime, no boundary setting." );
|
|
return FALSE;
|
|
}
|
|
|
|
CPLString osBoundary;
|
|
char **papszTokens =
|
|
CSLTokenizeStringComplex( pszBound + 9, "\n ;",
|
|
TRUE, FALSE );
|
|
|
|
if( CSLCount(papszTokens) == 0 || strlen(papszTokens[0]) == 0 )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined,
|
|
"Unable to parse multi-part mime, boundary not parsable." );
|
|
CSLDestroy( papszTokens );
|
|
return FALSE;
|
|
}
|
|
|
|
osBoundary = "--";
|
|
osBoundary += papszTokens[0];
|
|
CSLDestroy( papszTokens );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Find the start of the first chunk. */
|
|
/* -------------------------------------------------------------------- */
|
|
char *pszNext;
|
|
pszNext = (char *)
|
|
strstr((const char *) psResult->pabyData,osBoundary.c_str());
|
|
|
|
if( pszNext == NULL )
|
|
{
|
|
CPLError( CE_Failure, CPLE_AppDefined, "No parts found." );
|
|
return FALSE;
|
|
}
|
|
|
|
pszNext += strlen(osBoundary);
|
|
while( *pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0' )
|
|
pszNext++;
|
|
if( *pszNext == '\r' )
|
|
pszNext++;
|
|
if( *pszNext == '\n' )
|
|
pszNext++;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Loop over parts... */
|
|
/* -------------------------------------------------------------------- */
|
|
while( TRUE )
|
|
{
|
|
psResult->nMimePartCount++;
|
|
psResult->pasMimePart = (CPLMimePart *)
|
|
CPLRealloc(psResult->pasMimePart,
|
|
sizeof(CPLMimePart) * psResult->nMimePartCount );
|
|
|
|
CPLMimePart *psPart = psResult->pasMimePart+psResult->nMimePartCount-1;
|
|
|
|
memset( psPart, 0, sizeof(CPLMimePart) );
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Collect headers. */
|
|
/* -------------------------------------------------------------------- */
|
|
while( *pszNext != '\n' && *pszNext != '\r' && *pszNext != '\0' )
|
|
{
|
|
char *pszEOL = strstr(pszNext,"\n");
|
|
|
|
if( pszEOL == NULL )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Error while parsing multipart content (at line %d)", __LINE__);
|
|
return FALSE;
|
|
}
|
|
|
|
*pszEOL = '\0';
|
|
int bRestoreAntislashR = FALSE;
|
|
if (pszEOL - pszNext > 1 && pszEOL[-1] == '\r')
|
|
{
|
|
bRestoreAntislashR = TRUE;
|
|
pszEOL[-1] = '\0';
|
|
}
|
|
psPart->papszHeaders =
|
|
CSLAddString( psPart->papszHeaders, pszNext );
|
|
if (bRestoreAntislashR)
|
|
pszEOL[-1] = '\r';
|
|
*pszEOL = '\n';
|
|
|
|
pszNext = pszEOL + 1;
|
|
}
|
|
|
|
if( *pszNext == '\r' )
|
|
pszNext++;
|
|
if( *pszNext == '\n' )
|
|
pszNext++;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Work out the data block size. */
|
|
/* -------------------------------------------------------------------- */
|
|
psPart->pabyData = (GByte *) pszNext;
|
|
|
|
int nBytesAvail = psResult->nDataLen -
|
|
(pszNext - (const char *) psResult->pabyData);
|
|
|
|
while( nBytesAvail > 0
|
|
&& (*pszNext != '-'
|
|
|| strncmp(pszNext,osBoundary,strlen(osBoundary)) != 0) )
|
|
{
|
|
pszNext++;
|
|
nBytesAvail--;
|
|
}
|
|
|
|
if( nBytesAvail == 0 )
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Error while parsing multipart content (at line %d)", __LINE__);
|
|
return FALSE;
|
|
}
|
|
|
|
psPart->nDataLen = pszNext - (const char *) psPart->pabyData;
|
|
pszNext += strlen(osBoundary);
|
|
|
|
if( strncmp(pszNext,"--",2) == 0 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( *pszNext == '\r' )
|
|
pszNext++;
|
|
if( *pszNext == '\n' )
|
|
pszNext++;
|
|
else
|
|
{
|
|
CPLError(CE_Failure, CPLE_AppDefined,
|
|
"Error while parsing multipart content (at line %d)", __LINE__);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|