Newer
Older
Monty Brandenberg
committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* @file _httpoprequest.cpp
* @brief Definitions for internal class HttpOpRequest
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012, 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"
Monty Brandenberg
committed
#include "_httplibcurl.h"
#include "_httpinternal.h"
Monty Brandenberg
committed
#include "llhttpstatuscodes.h"
#include "llproxy.h"
Monty Brandenberg
committed
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);
Monty Brandenberg
committed
// 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);
Monty Brandenberg
committed
static const char * const hdr_whitespace(" \t");
static const char * const hdr_separator(": \t");
} // end anonymous namespace
Monty Brandenberg
committed
namespace LLCore
{
HttpOpRequest::HttpOpRequest()
: HttpOperation(),
mProcFlags(0U),
mReqMethod(HOR_GET),
mReqBody(NULL),
mReqOffset(0),
mReqLength(0),
Monty Brandenberg
committed
mReqHeaders(NULL),
mReqOptions(NULL),
mCurlActive(false),
mCurlHandle(NULL),
mCurlService(NULL),
mCurlHeaders(NULL),
mCurlBodyPos(0),
Monty Brandenberg
committed
mReplyBody(NULL),
mReplyOffset(0),
mReplyLength(0),
Monty Brandenberg
committed
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().
}
Monty Brandenberg
committed
HttpOpRequest::~HttpOpRequest()
{
if (mReqBody)
{
mReqBody->release();
mReqBody = NULL;
}
if (mReqOptions)
{
mReqOptions->release();
mReqOptions = NULL;
}
Monty Brandenberg
committed
if (mReqHeaders)
{
mReqHeaders->release();
Monty Brandenberg
committed
mReqHeaders = NULL;
}
if (mCurlHandle)
Monty Brandenberg
committed
{
curl_easy_cleanup(mCurlHandle);
mCurlHandle = NULL;
}
mCurlService = NULL;
if (mCurlHeaders)
{
curl_slist_free_all(mCurlHeaders);
mCurlHeaders = NULL;
Monty Brandenberg
committed
}
if (mReplyBody)
{
mReplyBody->release();
mReplyBody = NULL;
}
if (mReplyHeaders)
{
mReplyHeaders->release();
mReplyHeaders = NULL;
}
}
void HttpOpRequest::stageFromRequest(HttpService * service)
{
addRef();
service->getPolicy().addOp(this); // transfers refcount
Monty Brandenberg
committed
}
void HttpOpRequest::stageFromReady(HttpService * service)
{
addRef();
service->getTransport().addOp(this); // transfers refcount
Monty Brandenberg
committed
}
void HttpOpRequest::stageFromActive(HttpService * service)
{
if (mReplyLength)
Monty Brandenberg
committed
{
// If non-zero, we received and processed a Content-Range
// header with the response. Verify that what it says
// is consistent with the received data.
if (mReplyLength != mReplyBody->size())
Monty Brandenberg
committed
{
// Not as expected, fail the request
mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR);
}
}
if (mCurlHeaders)
Monty Brandenberg
committed
{
// 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;
Monty Brandenberg
committed
}
Monty Brandenberg
committed
addAsReply();
}
void HttpOpRequest::visitNotifier(HttpRequest * request)
{
if (mUserHandler)
Monty Brandenberg
committed
{
HttpResponse * response = new HttpResponse();
response->setStatus(mStatus);
response->setBody(mReplyBody);
response->setHeaders(mReplyHeaders);
if (mReplyOffset || mReplyLength)
{
// Got an explicit offset/length in response
Monty Brandenberg
committed
response->setRange(mReplyOffset, mReplyLength, mReplyFullLength);
response->setContentType(mReplyConType);
mUserHandler->onCompleted(static_cast<HttpHandle>(this), response);
Monty Brandenberg
committed
response->release();
}
}
HttpStatus HttpOpRequest::cancel()
{
mStatus = HttpStatus(HttpStatus::LLCORE, HE_OP_CANCELED);
addAsReply();
return HttpStatus();
}
Monty Brandenberg
committed
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,
Monty Brandenberg
committed
const std::string & url,
size_t offset,
size_t len,
HttpOptions * options,
HttpHeaders * headers)
{
Monty Brandenberg
committed
setupCommon(policy_id, priority, url, NULL, options, headers);
Monty Brandenberg
committed
mReqMethod = HOR_GET;
mReqOffset = offset;
mReqLength = len;
Monty Brandenberg
committed
if (offset || len)
{
mProcFlags |= PF_SCAN_RANGE_HEADER;
}
Monty Brandenberg
committed
return HttpStatus();
Monty Brandenberg
committed
}
HttpStatus HttpOpRequest::setupPost(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
HttpOptions * options,
HttpHeaders * headers)
{
Monty Brandenberg
committed
setupCommon(policy_id, priority, url, body, options, headers);
mReqMethod = HOR_POST;
Monty Brandenberg
committed
return HttpStatus();
HttpStatus HttpOpRequest::setupPut(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
HttpOptions * options,
HttpHeaders * headers)
{
Monty Brandenberg
committed
setupCommon(policy_id, priority, url, body, options, headers);
mReqMethod = HOR_PUT;
return HttpStatus();
}
Monty Brandenberg
committed
void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
HttpRequest::priority_t priority,
const std::string & url,
BufferArray * body,
HttpOptions * options,
HttpHeaders * headers)
{
mReqPolicy = policy_id;
mReqPriority = priority;
mReqURL = url;
if (body)
{
body->addRef();
mReqBody = body;
}
if (headers && ! mReqHeaders)
{
headers->addRef();
mReqHeaders = headers;
}
if (options && ! mReqOptions)
{
Monty Brandenberg
committed
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)
Monty Brandenberg
committed
{
// 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;
Monty Brandenberg
committed
mReplyFullLength = 0;
if (mReplyHeaders)
{
mReplyHeaders->release();
mReplyHeaders = NULL;
}
mReplyConType.clear();
Monty Brandenberg
committed
// *FIXME: better error handling later
HttpStatus status;
// Get policy options
HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
Monty Brandenberg
committed
mCurlHandle = curl_easy_init();
curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
Monty Brandenberg
committed
curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this);
curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, "");
if (HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX)
// The Linksys WRT54G V5 router has an issue with frequent
// DNS lookups from LAN machines. If they happen too often,
// like for every HTTP request, the router gets annoyed after
// about 700 or so requests and starts issuing TCP RSTs to
// new connections. Reuse the DNS lookups for even a few
// seconds and no RSTs.
curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15);
// *TODO: Revisit this old DNS timeout setting - may no longer be valid
// I don't think this is valid anymore, the Multi shared DNS
// cache is working well. For the case of naked easy handles,
// consider using a shared DNS object.
curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0);
}
curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1);
Monty Brandenberg
committed
curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT);
Monty Brandenberg
committed
curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this);
curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback);
curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this);
curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0);
Monty Brandenberg
committed
const std::string * opt_value(NULL);
Monty Brandenberg
committed
long opt_long(0L);
policy.get(HttpRequest::GP_LLPROXY, &opt_long);
if (opt_long)
Monty Brandenberg
committed
// 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);
Monty Brandenberg
committed
else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value))
// *TODO: This is fine for now but get fuller socks5/
Monty Brandenberg
committed
// authentication thing going later....
curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str());
curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
Monty Brandenberg
committed
if (policy.get(HttpRequest::GP_CA_PATH, &opt_value))
Monty Brandenberg
committed
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;
Monty Brandenberg
committed
// Tracing
if (mTracing >= HTTP_TRACE_CURL_HEADERS)
Monty Brandenberg
committed
{
curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this);
Monty Brandenberg
committed
curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback);
}
// There's a CURLOPT for this now...
if ((mReqOffset || mReqLength) && HOR_GET == mReqMethod)
Monty Brandenberg
committed
{
Monty Brandenberg
committed
static const char * const fmt1("Range: bytes=%lu-%lu");
static const char * const fmt2("Range: bytes=%lu-");
Monty Brandenberg
committed
char range_line[64];
#if LL_WINDOWS
Monty Brandenberg
committed
_snprintf_s(range_line, sizeof(range_line), sizeof(range_line) - 1,
(mReqLength ? fmt1 : fmt2),
Monty Brandenberg
committed
(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
Monty Brandenberg
committed
#else
snprintf(range_line, sizeof(range_line),
(mReqLength ? fmt1 : fmt2),
Monty Brandenberg
committed
(unsigned long) mReqOffset, (unsigned long) (mReqOffset + mReqLength - 1));
#endif // LL_WINDOWS
Monty Brandenberg
committed
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))
Monty Brandenberg
committed
{
curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this);
Monty Brandenberg
committed
}
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));
Monty Brandenberg
committed
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;
Monty Brandenberg
committed
}
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)
{
LL_WARNS("HttpCore") << "Request body position beyond body size. Aborting request."
<< 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;
Monty Brandenberg
committed
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));
Monty Brandenberg
committed
const size_t hdr_size(size * nmemb);
const char * hdr_data(static_cast<const char *>(data)); // Not null terminated
Monty Brandenberg
committed
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.
Monty Brandenberg
committed
op->mReplyOffset = 0;
op->mReplyLength = 0;
Monty Brandenberg
committed
op->mReplyFullLength = 0;
Monty Brandenberg
committed
op->mStatus = HttpStatus();
if (op->mReplyHeaders)
{
op->mReplyHeaders->mHeaders.clear();
}
Monty Brandenberg
committed
}
// 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)
Monty Brandenberg
committed
{
char hdr_buffer[128]; // Enough for a reasonable header
size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1));
Monty Brandenberg
committed
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))
Monty Brandenberg
committed
{
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;
Monty Brandenberg
committed
op->mReplyFullLength = length;
Monty Brandenberg
committed
}
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;
Monty Brandenberg
committed
}
}
}
return hdr_size;
}
Monty Brandenberg
committed
int HttpOpRequest::debugCallback(CURL * handle, curl_infotype info, char * buffer, size_t len, void * userdata)
{
HttpOpRequest * op(static_cast<HttpOpRequest *>(userdata));
Monty Brandenberg
committed
std::string safe_line;
std::string tag;
bool logit(false);
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)
Monty Brandenberg
committed
{
tag = "TEXT";
escape_libcurl_debug_data(buffer, len, true, safe_line);
logit = true;
}
break;
case CURLINFO_HEADER_IN:
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
Monty Brandenberg
committed
{
tag = "HEADERIN";
escape_libcurl_debug_data(buffer, len, true, safe_line);
logit = true;
}
break;
case CURLINFO_HEADER_OUT:
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
Monty Brandenberg
committed
{
tag = "HEADEROUT";
escape_libcurl_debug_data(buffer, 2 * len, true, safe_line); // Goes out as one line
Monty Brandenberg
committed
logit = true;
}
break;
case CURLINFO_DATA_IN:
if (op->mTracing >= HTTP_TRACE_CURL_HEADERS)
Monty Brandenberg
committed
{
tag = "DATAIN";
logit = true;
if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
Monty Brandenberg
committed
{
escape_libcurl_debug_data(buffer, 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)
Monty Brandenberg
committed
{
tag = "DATAOUT";
logit = true;
if (op->mTracing >= HTTP_TRACE_CURL_BODIES)
Monty Brandenberg
committed
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
{
escape_libcurl_debug_data(buffer, 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;
}
Monty Brandenberg
committed
} // 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);
Monty Brandenberg
committed
bool match(true);
if (! os_strtok_r(buffer, hdr_separator, &tok_state))
Monty Brandenberg
committed
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)))
Monty Brandenberg
committed
match = false;
if (match)
{
unsigned int lcl_first(0), lcl_last(0), lcl_len(0);
#if LL_WINDOWS
Monty Brandenberg
committed
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
Monty Brandenberg
committed
{
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
Monty Brandenberg
committed
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
Monty Brandenberg
committed
{
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;
}
Monty Brandenberg
committed
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
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);
}
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
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
}
Monty Brandenberg
committed
} // end anonymous namespace