530 lines
14 KiB
C++
530 lines
14 KiB
C++
/**
|
|
* @file llhttpclient.cpp
|
|
* @brief Implementation of classes for making HTTP requests.
|
|
*
|
|
* $LicenseInfo:firstyear=2006&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2006-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llhttpclient.h"
|
|
|
|
#include "llassetstorage.h"
|
|
#include "lliopipe.h"
|
|
#include "llurlrequest.h"
|
|
#include "llbufferstream.h"
|
|
#include "llsdserialize.h"
|
|
#include "llvfile.h"
|
|
#include "llvfs.h"
|
|
#include "lluri.h"
|
|
|
|
#include "message.h"
|
|
#include <curl/curl.h>
|
|
|
|
const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Responder class moved to LLCurl
|
|
|
|
namespace
|
|
{
|
|
class LLHTTPClientURLAdaptor : public LLURLRequestComplete
|
|
{
|
|
public:
|
|
LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder)
|
|
: LLURLRequestComplete(), mResponder(responder), mStatus(499),
|
|
mReason("LLURLRequest complete w/no status")
|
|
{
|
|
}
|
|
|
|
~LLHTTPClientURLAdaptor()
|
|
{
|
|
}
|
|
|
|
virtual void httpStatus(U32 status, const std::string& reason)
|
|
{
|
|
LLURLRequestComplete::httpStatus(status,reason);
|
|
|
|
mStatus = status;
|
|
mReason = reason;
|
|
}
|
|
|
|
virtual void complete(const LLChannelDescriptors& channels,
|
|
const buffer_ptr_t& buffer)
|
|
{
|
|
if (mResponder.get())
|
|
{
|
|
mResponder->completedRaw(mStatus, mReason, channels, buffer);
|
|
mResponder->completedHeader(mStatus, mReason, mHeaderOutput);
|
|
}
|
|
}
|
|
virtual void header(const std::string& header, const std::string& value)
|
|
{
|
|
mHeaderOutput[header] = value;
|
|
}
|
|
|
|
private:
|
|
LLCurl::ResponderPtr mResponder;
|
|
U32 mStatus;
|
|
std::string mReason;
|
|
LLSD mHeaderOutput;
|
|
};
|
|
|
|
class Injector : public LLIOPipe
|
|
{
|
|
public:
|
|
virtual const char* contentType() = 0;
|
|
};
|
|
|
|
class LLSDInjector : public Injector
|
|
{
|
|
public:
|
|
LLSDInjector(const LLSD& sd) : mSD(sd) {}
|
|
virtual ~LLSDInjector() {}
|
|
|
|
const char* contentType() { return "application/llsd+xml"; }
|
|
|
|
virtual EStatus process_impl(const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
|
|
{
|
|
LLBufferStream ostream(channels, buffer.get());
|
|
LLSDSerialize::toXML(mSD, ostream);
|
|
eos = true;
|
|
return STATUS_DONE;
|
|
}
|
|
|
|
const LLSD mSD;
|
|
};
|
|
|
|
class RawInjector : public Injector
|
|
{
|
|
public:
|
|
RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {}
|
|
virtual ~RawInjector() {delete mData;}
|
|
|
|
const char* contentType() { return "application/octet-stream"; }
|
|
|
|
virtual EStatus process_impl(const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
|
|
{
|
|
LLBufferStream ostream(channels, buffer.get());
|
|
ostream.write((const char *)mData, mSize); // hopefully chars are always U8s
|
|
eos = true;
|
|
return STATUS_DONE;
|
|
}
|
|
|
|
const U8* mData;
|
|
S32 mSize;
|
|
};
|
|
|
|
class FileInjector : public Injector
|
|
{
|
|
public:
|
|
FileInjector(const std::string& filename) : mFilename(filename) {}
|
|
virtual ~FileInjector() {}
|
|
|
|
const char* contentType() { return "application/octet-stream"; }
|
|
|
|
virtual EStatus process_impl(const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
|
|
{
|
|
LLBufferStream ostream(channels, buffer.get());
|
|
|
|
llifstream fstream(mFilename, std::iostream::binary | std::iostream::out);
|
|
if(fstream.is_open())
|
|
{
|
|
fstream.seekg(0, std::ios::end);
|
|
U32 fileSize = fstream.tellg();
|
|
fstream.seekg(0, std::ios::beg);
|
|
char* fileBuffer;
|
|
fileBuffer = new char [fileSize];
|
|
fstream.read(fileBuffer, fileSize);
|
|
ostream.write(fileBuffer, fileSize);
|
|
fstream.close();
|
|
eos = true;
|
|
return STATUS_DONE;
|
|
}
|
|
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
const std::string mFilename;
|
|
};
|
|
|
|
class VFileInjector : public Injector
|
|
{
|
|
public:
|
|
VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {}
|
|
virtual ~VFileInjector() {}
|
|
|
|
const char* contentType() { return "application/octet-stream"; }
|
|
|
|
virtual EStatus process_impl(const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
|
|
{
|
|
LLBufferStream ostream(channels, buffer.get());
|
|
|
|
LLVFile vfile(gVFS, mUUID, mAssetType, LLVFile::READ);
|
|
S32 fileSize = vfile.getSize();
|
|
U8* fileBuffer;
|
|
fileBuffer = new U8 [fileSize];
|
|
vfile.read(fileBuffer, fileSize);
|
|
ostream.write((char*)fileBuffer, fileSize);
|
|
eos = true;
|
|
return STATUS_DONE;
|
|
}
|
|
|
|
const LLUUID mUUID;
|
|
LLAssetType::EType mAssetType;
|
|
};
|
|
|
|
|
|
LLPumpIO* theClientPump = NULL;
|
|
}
|
|
|
|
static void request(
|
|
const std::string& url,
|
|
LLURLRequest::ERequestAction method,
|
|
Injector* body_injector,
|
|
LLCurl::ResponderPtr responder,
|
|
const F32 timeout = HTTP_REQUEST_EXPIRY_SECS,
|
|
const LLSD& headers = LLSD())
|
|
{
|
|
if (!LLHTTPClient::hasPump())
|
|
{
|
|
responder->completed(U32_MAX, "No pump", LLSD());
|
|
return;
|
|
}
|
|
LLPumpIO::chain_t chain;
|
|
|
|
LLURLRequest* req = new LLURLRequest(method, url);
|
|
req->checkRootCertificate(true);
|
|
|
|
// Insert custom headers is the caller sent any
|
|
if (headers.isMap())
|
|
{
|
|
LLSD::map_const_iterator iter = headers.beginMap();
|
|
LLSD::map_const_iterator end = headers.endMap();
|
|
|
|
for (; iter != end; ++iter)
|
|
{
|
|
std::ostringstream header;
|
|
//if the header is "Pragma" with no value
|
|
//the caller intends to force libcurl to drop
|
|
//the Pragma header it so gratuitously inserts
|
|
//Before inserting the header, force libcurl
|
|
//to not use the proxy (read: llurlrequest.cpp)
|
|
static const std::string PRAGMA("Pragma");
|
|
if ((iter->first == PRAGMA) && (iter->second.asString().empty()))
|
|
{
|
|
req->useProxy(false);
|
|
}
|
|
header << iter->first << ": " << iter->second.asString() ;
|
|
lldebugs << "header = " << header.str() << llendl;
|
|
req->addHeader(header.str().c_str());
|
|
}
|
|
}
|
|
|
|
// Check to see if we have already set Accept or not. If no one
|
|
// set it, set it to application/llsd+xml since that's what we
|
|
// almost always want.
|
|
if( method != LLURLRequest::HTTP_PUT && method != LLURLRequest::HTTP_POST )
|
|
{
|
|
static const std::string ACCEPT("Accept");
|
|
if(!headers.has(ACCEPT))
|
|
{
|
|
req->addHeader("Accept: application/llsd+xml");
|
|
}
|
|
}
|
|
|
|
req->setCallback(new LLHTTPClientURLAdaptor(responder));
|
|
|
|
if (method == LLURLRequest::HTTP_POST && gMessageSystem)
|
|
{
|
|
req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d",
|
|
gMessageSystem->mPort).c_str());
|
|
}
|
|
|
|
if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST)
|
|
{
|
|
static const std::string CONTENT_TYPE("Content-Type");
|
|
if(!headers.has(CONTENT_TYPE))
|
|
{
|
|
// If the Content-Type header was passed in, it has
|
|
// already been added as a header through req->addHeader
|
|
// in the loop above. We defer to the caller's wisdom, but
|
|
// if they did not specify a Content-Type, then ask the
|
|
// injector.
|
|
req->addHeader(
|
|
llformat(
|
|
"Content-Type: %s",
|
|
body_injector->contentType()).c_str());
|
|
}
|
|
chain.push_back(LLIOPipe::ptr_t(body_injector));
|
|
}
|
|
|
|
chain.push_back(LLIOPipe::ptr_t(req));
|
|
|
|
theClientPump->addChain(chain, timeout);
|
|
}
|
|
|
|
|
|
void LLHTTPClient::getByteRange(
|
|
const std::string& url,
|
|
S32 offset,
|
|
S32 bytes,
|
|
ResponderPtr responder,
|
|
const LLSD& hdrs,
|
|
const F32 timeout)
|
|
{
|
|
LLSD headers = hdrs;
|
|
if(offset > 0 || bytes > 0)
|
|
{
|
|
std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1);
|
|
headers["Range"] = range;
|
|
}
|
|
request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::head(
|
|
const std::string& url,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers);
|
|
}
|
|
void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers);
|
|
}
|
|
void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout)
|
|
{
|
|
getHeaderOnly(url, responder, LLSD(), timeout);
|
|
}
|
|
|
|
void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout)
|
|
{
|
|
LLURI uri;
|
|
|
|
uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query);
|
|
get(uri.asString(), responder, headers, timeout);
|
|
}
|
|
|
|
// A simple class for managing data returned from a curl http request.
|
|
class LLHTTPBuffer
|
|
{
|
|
public:
|
|
LLHTTPBuffer() { }
|
|
|
|
static size_t curl_write( void *ptr, size_t size, size_t nmemb, void *user_data)
|
|
{
|
|
LLHTTPBuffer* self = (LLHTTPBuffer*)user_data;
|
|
|
|
size_t bytes = (size * nmemb);
|
|
self->mBuffer.append((char*)ptr,bytes);
|
|
return nmemb;
|
|
}
|
|
|
|
LLSD asLLSD()
|
|
{
|
|
LLSD content;
|
|
|
|
if (mBuffer.empty()) return content;
|
|
|
|
std::istringstream istr(mBuffer);
|
|
LLSDSerialize::fromXML(content, istr);
|
|
return content;
|
|
}
|
|
|
|
std::string asString()
|
|
{
|
|
return mBuffer;
|
|
}
|
|
|
|
private:
|
|
std::string mBuffer;
|
|
};
|
|
|
|
// *TODO: Deprecate (only used by dataserver)
|
|
// This call is blocking! This is probably usually bad. :(
|
|
LLSD LLHTTPClient::blockingGet(const std::string& url)
|
|
{
|
|
llinfos << "blockingGet of " << url << llendl;
|
|
|
|
// Returns an LLSD map: {status: integer, body: map}
|
|
char curl_error_buffer[CURL_ERROR_SIZE];
|
|
CURL* curlp = curl_easy_init();
|
|
|
|
LLHTTPBuffer http_buffer;
|
|
|
|
// Without this timeout, blockingGet() calls have been observed to take
|
|
// up to 90 seconds to complete. Users of blockingGet() already must
|
|
// check the HTTP return code for validity, so this will not introduce
|
|
// new errors. A 5 second timeout will succeed > 95% of the time (and
|
|
// probably > 99% of the time) based on my statistics. JC
|
|
curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts
|
|
curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds
|
|
|
|
curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write);
|
|
curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer);
|
|
curl_easy_setopt(curlp, CURLOPT_URL, url.c_str());
|
|
curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer);
|
|
curl_easy_setopt(curlp, CURLOPT_FAILONERROR, 1);
|
|
|
|
struct curl_slist *header_list = NULL;
|
|
header_list = curl_slist_append(header_list, "Accept: application/llsd+xml");
|
|
CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, header_list);
|
|
if ( curl_result != CURLE_OK )
|
|
{
|
|
llinfos << "Curl is hosed - can't add Accept header for llsd+xml" << llendl;
|
|
}
|
|
|
|
LLSD response = LLSD::emptyMap();
|
|
|
|
S32 curl_success = curl_easy_perform(curlp);
|
|
|
|
S32 http_status = 499;
|
|
curl_easy_getinfo(curlp,CURLINFO_RESPONSE_CODE, &http_status);
|
|
|
|
response["status"] = http_status;
|
|
|
|
if (curl_success != 0
|
|
&& http_status != 404) // We expect 404s, don't spam for them.
|
|
{
|
|
llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
|
|
|
|
response["body"] = http_buffer.asString();
|
|
}
|
|
else
|
|
{
|
|
response["body"] = http_buffer.asLLSD();
|
|
}
|
|
|
|
if(header_list)
|
|
{ // free the header list
|
|
curl_slist_free_all(header_list);
|
|
header_list = NULL;
|
|
}
|
|
|
|
curl_easy_cleanup(curlp);
|
|
|
|
return response;
|
|
}
|
|
|
|
void LLHTTPClient::put(
|
|
const std::string& url,
|
|
const LLSD& body,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::post(
|
|
const std::string& url,
|
|
const LLSD& body,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::postRaw(
|
|
const std::string& url,
|
|
const U8* data,
|
|
S32 size,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::postFile(
|
|
const std::string& url,
|
|
const std::string& filename,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout, headers);
|
|
}
|
|
|
|
void LLHTTPClient::postFile(
|
|
const std::string& url,
|
|
const LLUUID& uuid,
|
|
LLAssetType::EType asset_type,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout, headers);
|
|
}
|
|
|
|
// static
|
|
void LLHTTPClient::del(
|
|
const std::string& url,
|
|
ResponderPtr responder,
|
|
const LLSD& headers,
|
|
const F32 timeout)
|
|
{
|
|
request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout, headers);
|
|
}
|
|
|
|
// static
|
|
void LLHTTPClient::move(
|
|
const std::string& url,
|
|
const std::string& destination,
|
|
ResponderPtr responder,
|
|
const LLSD& hdrs,
|
|
const F32 timeout)
|
|
{
|
|
LLSD headers = hdrs;
|
|
headers["Destination"] = destination;
|
|
request(url, LLURLRequest::HTTP_MOVE, NULL, responder, timeout, headers);
|
|
}
|
|
|
|
|
|
void LLHTTPClient::setPump(LLPumpIO& pump)
|
|
{
|
|
theClientPump = &pump;
|
|
}
|
|
|
|
bool LLHTTPClient::hasPump()
|
|
{
|
|
return theClientPump != NULL;
|
|
}
|