887 lines
22 KiB
C++
Executable File
887 lines
22 KiB
C++
Executable File
/**
|
|
* @file _httpoprequest.cpp
|
|
* @brief Definitions for internal class HttpOpRequest
|
|
*
|
|
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2012-2013, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "_httpoprequest.h"
|
|
|
|
#include <cstdio>
|
|
#include <algorithm>
|
|
|
|
#include "httpcommon.h"
|
|
#include "httphandler.h"
|
|
#include "httpresponse.h"
|
|
#include "bufferarray.h"
|
|
#include "httpheaders.h"
|
|
#include "httpoptions.h"
|
|
|
|
#include "_httprequestqueue.h"
|
|
#include "_httpreplyqueue.h"
|
|
#include "_httpservice.h"
|
|
#include "_httppolicy.h"
|
|
#include "_httppolicyglobal.h"
|
|
#include "_httplibcurl.h"
|
|
#include "_httpinternal.h"
|
|
|
|
#include "llhttpstatuscodes.h"
|
|
#include "llproxy.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
// Attempts to parse a 'Content-Range:' header. Caller must already
|
|
// have verified that the header tag is present. The 'buffer' argument
|
|
// will be processed by strtok_r calls which will modify the buffer.
|
|
//
|
|
// @return -1 if invalid and response should be dropped, 0 if valid an
|
|
// correct, 1 if couldn't be parsed. If 0, the first, last,
|
|
// and length arguments are also written. 'length' may be
|
|
// 0 if the length wasn't available to the server.
|
|
//
|
|
int parse_content_range_header(char * buffer,
|
|
unsigned int * first,
|
|
unsigned int * last,
|
|
unsigned int * length);
|
|
|
|
|
|
// Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and
|
|
// escape and format it for a tracing line in logging. Absolutely
|
|
// anything including NULs can be in the data. If @scrub is true,
|
|
// non-printing or non-ascii characters are replaced with spaces
|
|
// otherwise a %XX form of escaping is used.
|
|
void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub,
|
|
std::string & safe_line);
|
|
|
|
|
|
// OS-neutral string comparisons of various types
|
|
int os_strncasecmp(const char *s1, const char *s2, size_t n);
|
|
int os_strcasecmp(const char *s1, const char *s2);
|
|
char * os_strtok_r(char *str, const char *delim, char **saveptr);
|
|
|
|
|
|
static const char * const hdr_whitespace(" \t");
|
|
static const char * const hdr_separator(": \t");
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
|
namespace LLCore
|
|
{
|
|
|
|
|
|
HttpOpRequest::HttpOpRequest()
|
|
: HttpOperation(),
|
|
mProcFlags(0U),
|
|
mReqMethod(HOR_GET),
|
|
mReqBody(NULL),
|
|
mReqOffset(0),
|
|
mReqLength(0),
|
|
mReqHeaders(NULL),
|
|
mReqOptions(NULL),
|
|
mCurlActive(false),
|
|
mCurlHandle(NULL),
|
|
mCurlService(NULL),
|
|
mCurlHeaders(NULL),
|
|
mCurlBodyPos(0),
|
|
mReplyBody(NULL),
|
|
mReplyOffset(0),
|
|
mReplyLength(0),
|
|
mReplyFullLength(0),
|
|
mReplyHeaders(NULL),
|
|
mPolicyRetries(0),
|
|
mPolicyRetryAt(HttpTime(0)),
|
|
mPolicyRetryLimit(HTTP_RETRY_COUNT_DEFAULT)
|
|
{
|
|
// *NOTE: As members are added, retry initialization/cleanup
|
|
// may need to be extended in @see prepareRequest().
|
|
}
|
|
|
|
|
|
|
|
HttpOpRequest::~HttpOpRequest()
|
|
{
|
|
if (mReqBody)
|
|
{
|
|
mReqBody->release();
|
|
mReqBody = NULL;
|
|
}
|
|
|
|
if (mReqOptions)
|
|
{
|
|
mReqOptions->release();
|
|
mReqOptions = NULL;
|
|
}
|
|
|
|
if (mReqHeaders)
|
|
{
|
|
mReqHeaders->release();
|
|
mReqHeaders = NULL;
|
|
}
|
|
|
|
if (mCurlHandle)
|
|
{
|
|
curl_easy_cleanup(mCurlHandle);
|
|
mCurlHandle = NULL;
|
|
}
|
|
|
|
mCurlService = NULL;
|
|
|
|
if (mCurlHeaders)
|
|
{
|
|
curl_slist_free_all(mCurlHeaders);
|
|
mCurlHeaders = NULL;
|
|
}
|
|
|
|
if (mReplyBody)
|
|
{
|
|
mReplyBody->release();
|
|
mReplyBody = NULL;
|
|
}
|
|
|
|
if (mReplyHeaders)
|
|
{
|
|
mReplyHeaders->release();
|
|
mReplyHeaders = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void HttpOpRequest::stageFromRequest(HttpService * service)
|
|
{
|
|
addRef();
|
|
service->getPolicy().addOp(this); // transfers refcount
|
|
}
|
|
|
|
|
|
void HttpOpRequest::stageFromReady(HttpService * service)
|
|
{
|
|
addRef();
|
|
service->getTransport().addOp(this); // transfers refcount
|
|
}
|
|
|
|
|
|
void HttpOpRequest::stageFromActive(HttpService * service)
|
|
{
|
|
if (mReplyLength)
|
|
{
|
|
// If non-zero, we received and processed a Content-Range
|
|
// header with the response. If there is received data
|
|
// (and there may not be due to protocol violations,
|
|
// HEAD requests, etc., see BUG-2295) Verify that what it
|
|
// says is consistent with the received data.
|
|
if (mReplyBody && mReplyBody->size() && mReplyLength != mReplyBody->size())
|
|
{
|
|
// Not as expected, fail the request
|
|
mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
|
|
}
|
|
}
|
|
|
|
if (mCurlHeaders)
|
|
{
|
|
// We take these headers out of the request now as they were
|
|
// allocated originally in this thread and the notifier doesn't
|
|
// need them. This eliminates one source of heap moving across
|
|
// threads.
|
|
|
|
curl_slist_free_all(mCurlHeaders);
|
|
mCurlHeaders = NULL;
|
|
}
|
|
|
|
addAsReply();
|
|
}
|
|
|
|
|
|
void HttpOpRequest::visitNotifier(HttpRequest * request)
|
|
{
|
|
if (mUserHandler)
|
|
{
|
|
HttpResponse * response = new HttpResponse();
|
|
response->setStatus(mStatus);
|
|
response->setBody(mReplyBody);
|
|
response->setHeaders(mReplyHeaders);
|
|
if (mReplyOffset || mReplyLength)
|
|
{
|
|
// Got an explicit offset/length in response
|
|
response->setRange(mReplyOffset, mReplyLength, mReplyFullLength);
|
|
}
|
|
response->setContentType(mReplyConType);
|
|
|
|
mUserHandler->onCompleted(static_cast<HttpHandle>(this), response);
|
|
|
|
response->release();
|
|
}
|
|
}
|
|
|
|
|
|
HttpStatus HttpOpRequest::cancel()
|
|
{
|
|
mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);
|
|
|
|
addAsReply();
|
|
|
|
return HttpStatus();
|
|
}
|
|
|
|
|
|
HttpStatus HttpOpRequest::setupGet(HttpRequest::policy_t policy_id,
|
|
HttpRequest::priority_t priority,
|
|
const std::string & url,
|
|
HttpOptions * options,
|
|
HttpHeaders * headers)
|
|
{
|
|
setupCommon(policy_id, priority, url, NULL, options, headers);
|
|
mReqMethod = HOR_GET;
|
|
|
|
return HttpStatus();
|
|
}
|
|
|
|
|
|
HttpStatus HttpOpRequest::setupGetByteRange(HttpRequest::policy_t policy_id,
|
|
HttpRequest::priority_t priority,
|
|
const std::string & url,
|
|
size_t offset,
|
|
size_t len,
|
|
HttpOptions * options,
|
|
HttpHeaders * headers)
|
|
{
|
|
setupCommon(policy_id, priority, url, NULL, options, headers);
|
|
mReqMethod = HOR_GET;
|
|
mReqOffset = offset;
|
|
mReqLength = len;
|
|
if (offset || len)
|
|
{
|
|
mProcFlags |= PF_SCAN_RANGE_HEADER;
|
|
}
|
|
|
|
return HttpStatus();
|
|
}
|
|
|
|
|
|
HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
|
|
HttpRequest::priority_t priority,
|
|
const std::string & url,
|
|
BufferArray * body,
|
|
HttpOptions * options,
|
|
HttpHeaders * headers)
|
|
{
|
|
setupCommon(policy_id, priority, url, body, options, headers);
|
|
mReqMethod = HOR_POST;
|
|
|
|
return HttpStatus();
|
|
}
|
|
|
|
|
|
HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
|
|
HttpRequest::priority_t priority,
|
|
const std::string & url,
|
|
BufferArray * body,
|
|
HttpOptions * options,
|
|
HttpHeaders * headers)
|
|
{
|
|
setupCommon(policy_id, priority, url, body, options, headers);
|
|
mReqMethod = HOR_PUT;
|
|
|
|
return HttpStatus();
|
|
}
|
|
|
|
|
|
void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
|
|
HttpRequest::priority_t priority,
|
|
const std::string & url,
|
|
BufferArray * body,
|
|
HttpOptions * options,
|
|
HttpHeaders * headers)
|
|
{
|
|
mProcFlags = 0U;
|
|
mReqPolicy = policy_id;
|
|
mReqPriority = priority;
|
|
mReqURL = url;
|
|
if (body)
|
|
{
|
|
body->addRef();
|
|
mReqBody = body;
|
|
}
|
|
if (headers && ! mReqHeaders)
|
|
{
|
|
headers->addRef();
|
|
mReqHeaders = headers;
|
|
}
|
|
if (options && ! mReqOptions)
|
|
{
|
|
options->addRef();
|
|
mReqOptions = options;
|
|
if (options->getWantHeaders())
|
|
{
|
|
mProcFlags |= PF_SAVE_HEADERS;
|
|
}
|
|
mPolicyRetryLimit = options->getRetries();
|
|
mPolicyRetryLimit = llclamp(mPolicyRetryLimit, HTTP_RETRY_COUNT_MIN, HTTP_RETRY_COUNT_MAX);
|
|
mTracing = (std::max)(mTracing, llclamp(options->getTrace(), HTTP_TRACE_MIN, HTTP_TRACE_MAX));
|
|
}
|
|
}
|
|
|
|
// Sets all libcurl options and data for a request.
|
|
//
|
|
// Used both for initial requests and to 'reload' for
|
|
// a retry, generally with a different CURL handle.
|
|
// Junk may be left around from a failed request and that
|
|
// needs to be cleaned out.
|
|
//
|
|
HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
|
|
{
|
|
// Scrub transport and result data for retried op case
|
|
mCurlActive = false;
|
|
mCurlHandle = NULL;
|
|
mCurlService = NULL;
|
|
if (mCurlHeaders)
|
|
{
|
|
curl_slist_free_all(mCurlHeaders);
|
|
mCurlHeaders = NULL;
|
|
}
|
|
mCurlBodyPos = 0;
|
|
|
|
if (mReplyBody)
|
|
{
|
|
mReplyBody->release();
|
|
mReplyBody = NULL;
|
|
}
|
|
mReplyOffset = 0;
|
|
mReplyLength = 0;
|
|
mReplyFullLength = 0;
|
|
if (mReplyHeaders)
|
|
{
|
|
mReplyHeaders->release();
|
|
mReplyHeaders = NULL;
|
|
}
|
|
mReplyConType.clear();
|
|
|
|
// *FIXME: better error handling later
|
|
HttpStatus status;
|
|
|
|
// Get policy options
|
|
HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
|
|
|
|
mCurlHandle = LLCurl::createStandardCurlHandle();
|
|
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
|
|
|
|
const std::string * opt_value(NULL);
|
|
long opt_long(0L);
|
|
policy.get(HttpRequest::GP_LLPROXY, &opt_long);
|
|
if (opt_long)
|
|
{
|
|
// Use the viewer-based thread-safe API which has a
|
|
// fast/safe check for proxy enable. Would like to
|
|
// encapsulate this someway...
|
|
LLProxy::getInstance()->applyProxySettings(mCurlHandle);
|
|
}
|
|
else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value))
|
|
{
|
|
// *TODO: This is fine for now but get fuller socks5/
|
|
// authentication thing going later....
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str());
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
|
|
}
|
|
if (policy.get(HttpRequest::GP_CA_PATH, &opt_value))
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str());
|
|
}
|
|
if (policy.get(HttpRequest::GP_CA_FILE, &opt_value))
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str());
|
|
}
|
|
|
|
switch (mReqMethod)
|
|
{
|
|
case HOR_GET:
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1);
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
|
|
break;
|
|
|
|
case HOR_POST:
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
|
|
long data_size(0);
|
|
if (mReqBody)
|
|
{
|
|
data_size = mReqBody->size();
|
|
}
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast<void *>(NULL));
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size);
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
|
|
}
|
|
break;
|
|
|
|
case HOR_PUT:
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1);
|
|
long data_size(0);
|
|
if (mReqBody)
|
|
{
|
|
data_size = mReqBody->size();
|
|
}
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL);
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:");
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive");
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
LL_ERRS("CoreHttp") << "Invalid HTTP method in request: "
|
|
<< int(mReqMethod) << ". Can't recover."
|
|
<< LL_ENDL;
|
|
break;
|
|
}
|
|
|
|
// Tracing
|
|
if (mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
|
|
}
|
|
|
|
// There's a CURLOPT for this now...
|
|
if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod)
|
|
{
|
|
static const char * const fmt1("Range: bytes=%lu-%lu");
|
|
static const char * const fmt2("Range: bytes=%lu-");
|
|
|
|
char range_line[64];
|
|
|
|
#if LL_WINDOWS
|
|
_snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1,
|
|
(mReqLength ? fmt1 : fmt2),
|
|
(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
|
|
#else
|
|
snprintf(range_line, sizeof(range_line),
|
|
(mReqLength ? fmt1 : fmt2),
|
|
(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
|
|
#endif // LL_WINDOWS
|
|
range_line[sizeof(range_line) - 1] = '\0';
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, range_line);
|
|
}
|
|
|
|
mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:");
|
|
|
|
// Request options
|
|
long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT);
|
|
if (mReqOptions)
|
|
{
|
|
timeout = mReqOptions->getTimeout();
|
|
timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX);
|
|
}
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
|
|
|
|
// Request headers
|
|
if (mReqHeaders)
|
|
{
|
|
// Caller's headers last to override
|
|
mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
|
|
}
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
|
|
|
|
if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS))
|
|
{
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
|
|
curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
|
|
}
|
|
|
|
if (status)
|
|
{
|
|
mCurlService = service;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
size_t HttpOpRequest::writeCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
|
{
|
|
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
|
|
|
|
if (! op->mReplyBody)
|
|
{
|
|
op->mReplyBody = new BufferArray();
|
|
}
|
|
const size_t req_size(size * nmemb);
|
|
const size_t write_size(op->mReplyBody->append(static_cast<char *>(data), req_size));
|
|
return write_size;
|
|
}
|
|
|
|
|
|
size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
|
{
|
|
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
|
|
|
|
if (! op->mReqBody)
|
|
{
|
|
return 0;
|
|
}
|
|
const size_t req_size(size * nmemb);
|
|
const size_t body_size(op->mReqBody->size());
|
|
if (body_size <= op->mCurlBodyPos)
|
|
{
|
|
if (body_size < op->mCurlBodyPos)
|
|
{
|
|
// Warn but continue if the read position moves beyond end-of-body
|
|
// for some reason.
|
|
LL_WARNS("HttpCore") << "Request body position beyond body size. Truncating request body."
|
|
<< LL_ENDL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const size_t do_size((std::min)(req_size, body_size - op->mCurlBodyPos));
|
|
const size_t read_size(op->mReqBody->read(op->mCurlBodyPos, static_cast<char *>(data), do_size));
|
|
op->mCurlBodyPos += read_size;
|
|
return read_size;
|
|
}
|
|
|
|
|
|
size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, void * userdata)
|
|
{
|
|
static const char status_line[] = "HTTP/";
|
|
static const size_t status_line_len = sizeof(status_line) - 1;
|
|
|
|
static const char con_ran_line[] = "content-range:";
|
|
static const size_t con_ran_line_len = sizeof(con_ran_line) - 1;
|
|
|
|
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
|
|
|
|
const size_t hdr_size(size * nmemb);
|
|
const char * hdr_data(static_cast<const char *>(data)); // Not null terminated
|
|
|
|
if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len))
|
|
{
|
|
// One of possibly several status lines. Reset what we know and start over
|
|
// taking results from the last header stanza we receive.
|
|
op->mReplyOffset = 0;
|
|
op->mReplyLength = 0;
|
|
op->mReplyFullLength = 0;
|
|
op->mStatus = HttpStatus();
|
|
if (op->mReplyHeaders)
|
|
{
|
|
op->mReplyHeaders->mHeaders.clear();
|
|
}
|
|
}
|
|
|
|
// Nothing in here wants a final CR/LF combination. Remove
|
|
// it as much as possible.
|
|
size_t wanted_hdr_size(hdr_size);
|
|
if (wanted_hdr_size && '\n' == hdr_data[wanted_hdr_size - 1])
|
|
{
|
|
if (--wanted_hdr_size && '\r' == hdr_data[wanted_hdr_size - 1])
|
|
{
|
|
--wanted_hdr_size;
|
|
}
|
|
}
|
|
|
|
// Save header if caller wants them in the response
|
|
if (op->mProcFlags & PF_SAVE_HEADERS)
|
|
{
|
|
// Save headers in response
|
|
if (! op->mReplyHeaders)
|
|
{
|
|
op->mReplyHeaders = new HttpHeaders;
|
|
}
|
|
op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size));
|
|
}
|
|
|
|
// Detect and parse 'Content-Range' headers
|
|
if (op->mProcFlags & PF_SCAN_RANGE_HEADER)
|
|
{
|
|
char hdr_buffer[128]; // Enough for a reasonable header
|
|
size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1));
|
|
|
|
memcpy(hdr_buffer, hdr_data, frag_size);
|
|
hdr_buffer[frag_size] = '\0';
|
|
if (frag_size > con_ran_line_len &&
|
|
! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len))
|
|
{
|
|
unsigned int first(0), last(0), length(0);
|
|
int status;
|
|
|
|
if (! (status = parse_content_range_header(hdr_buffer, &first, &last, &length)))
|
|
{
|
|
// Success, record the fragment position
|
|
op->mReplyOffset = first;
|
|
op->mReplyLength = last - first + 1;
|
|
op->mReplyFullLength = length;
|
|
}
|
|
else if (-1 == status)
|
|
{
|
|
// Response is badly formed and shouldn't be accepted
|
|
op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
|
|
}
|
|
else
|
|
{
|
|
// Ignore the unparsable.
|
|
LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '"
|
|
<< std::string(hdr_data, frag_size)
|
|
<< "'. Ignoring."
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return hdr_size;
|
|
}
|
|
|
|
|
|
int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata)
|
|
{
|
|
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
|
|
|
|
std::string safe_line;
|
|
std::string tag;
|
|
bool logit(false);
|
|
const size_t log_len((std::min)(len, size_t(256))); // Keep things reasonable in all cases
|
|
|
|
switch (info)
|
|
{
|
|
case CURLINFO_TEXT:
|
|
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
tag = "TEXT";
|
|
escape_libcurl_debug_data(buffer, log_len, true, safe_line);
|
|
logit = true;
|
|
}
|
|
break;
|
|
|
|
case CURLINFO_HEADER_IN:
|
|
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
tag = "HEADERIN";
|
|
escape_libcurl_debug_data(buffer, log_len, true, safe_line);
|
|
logit = true;
|
|
}
|
|
break;
|
|
|
|
case CURLINFO_HEADER_OUT:
|
|
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
tag = "HEADEROUT";
|
|
escape_libcurl_debug_data(buffer, log_len, true, safe_line); // Goes out as one line unlike header_in
|
|
logit = true;
|
|
}
|
|
break;
|
|
|
|
case CURLINFO_DATA_IN:
|
|
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
tag = "DATAIN";
|
|
logit = true;
|
|
if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
|
|
{
|
|
escape_libcurl_debug_data(buffer, log_len, false, safe_line);
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream out;
|
|
out << len << " Bytes";
|
|
safe_line = out.str();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CURLINFO_DATA_OUT:
|
|
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
|
|
{
|
|
tag = "DATAOUT";
|
|
logit = true;
|
|
if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
|
|
{
|
|
escape_libcurl_debug_data(buffer, log_len, false, safe_line);
|
|
}
|
|
else
|
|
{
|
|
std::ostringstream out;
|
|
out << len << " Bytes";
|
|
safe_line = out.str();
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
logit = false;
|
|
break;
|
|
}
|
|
|
|
if (logit)
|
|
{
|
|
LL_INFOS("CoreHttp") << "TRACE, LibcurlDebug, Handle: "
|
|
<< static_cast<HttpHandle>(op)
|
|
<< ", Type: " << tag
|
|
<< ", Data: " << safe_line
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
} // end namespace LLCore
|
|
|
|
|
|
// =======================================
|
|
// Anonymous Namespace
|
|
// =======================================
|
|
|
|
namespace
|
|
{
|
|
|
|
int parse_content_range_header(char * buffer,
|
|
unsigned int * first,
|
|
unsigned int * last,
|
|
unsigned int * length)
|
|
{
|
|
char * tok_state(NULL), * tok(NULL);
|
|
bool match(true);
|
|
|
|
if (! os_strtok_r(buffer, hdr_separator, &tok_state))
|
|
match = false;
|
|
if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state)))
|
|
match = 0 == os_strcasecmp("bytes", tok);
|
|
if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state)))
|
|
match = false;
|
|
if (match)
|
|
{
|
|
unsigned int lcl_first(0), lcl_last(0), lcl_len(0);
|
|
|
|
#if LL_WINDOWS
|
|
if (3 == sscanf_s(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len))
|
|
#else
|
|
if (3 == sscanf(tok, "%u-%u/%u", &lcl_first, &lcl_last, &lcl_len))
|
|
#endif // LL_WINDOWS
|
|
{
|
|
if (lcl_first > lcl_last || lcl_last >= lcl_len)
|
|
return -1;
|
|
*first = lcl_first;
|
|
*last = lcl_last;
|
|
*length = lcl_len;
|
|
return 0;
|
|
}
|
|
#if LL_WINDOWS
|
|
if (2 == sscanf_s(tok, "%u-%u/*", &lcl_first, &lcl_last))
|
|
#else
|
|
if (2 == sscanf(tok, "%u-%u/*", &lcl_first, &lcl_last))
|
|
#endif // LL_WINDOWS
|
|
{
|
|
if (lcl_first > lcl_last)
|
|
return -1;
|
|
*first = lcl_first;
|
|
*last = lcl_last;
|
|
*length = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Header is there but badly/unexpectedly formed, try to ignore it.
|
|
return 1;
|
|
}
|
|
|
|
|
|
void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line)
|
|
{
|
|
std::string out;
|
|
len = (std::min)(len, size_t(200));
|
|
out.reserve(3 * len);
|
|
for (int i(0); i < len; ++i)
|
|
{
|
|
unsigned char uc(static_cast<unsigned char>(buffer[i]));
|
|
|
|
if (uc < 32 || uc > 126)
|
|
{
|
|
if (scrub)
|
|
{
|
|
out.append(1, ' ');
|
|
}
|
|
else
|
|
{
|
|
static const char hex[] = "0123456789ABCDEF";
|
|
char convert[4];
|
|
|
|
convert[0] = '%';
|
|
convert[1] = hex[(uc >> 4) % 16];
|
|
convert[2] = hex[uc % 16];
|
|
convert[3] = '\0';
|
|
out.append(convert);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
out.append(1, buffer[i]);
|
|
}
|
|
}
|
|
safe_line.swap(out);
|
|
}
|
|
|
|
|
|
int os_strncasecmp(const char *s1, const char *s2, size_t n)
|
|
{
|
|
#if LL_WINDOWS
|
|
return _strnicmp(s1, s2, n);
|
|
#else
|
|
return strncasecmp(s1, s2, n);
|
|
#endif // LL_WINDOWS
|
|
}
|
|
|
|
|
|
int os_strcasecmp(const char *s1, const char *s2)
|
|
{
|
|
#if LL_WINDOWS
|
|
return _stricmp(s1, s2);
|
|
#else
|
|
return strcasecmp(s1, s2);
|
|
#endif // LL_WINDOWS
|
|
}
|
|
|
|
|
|
char * os_strtok_r(char *str, const char *delim, char ** savestate)
|
|
{
|
|
#if LL_WINDOWS
|
|
return strtok_s(str, delim, savestate);
|
|
#else
|
|
return strtok_r(str, delim, savestate);
|
|
#endif
|
|
}
|
|
|
|
|
|
} // end anonymous namespace
|
|
|
|
|