summaryrefslogtreecommitdiff
path: root/indra/llmessage/llhttpclient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage/llhttpclient.cpp')
-rw-r--r--indra/llmessage/llhttpclient.cpp381
1 files changed, 268 insertions, 113 deletions
diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp
index 859f3c1536..0e5206a520 100644
--- a/indra/llmessage/llhttpclient.cpp
+++ b/indra/llmessage/llhttpclient.cpp
@@ -1,36 +1,31 @@
- /**
+/**
* @file llhttpclient.cpp
* @brief Implementation of classes for making HTTP requests.
*
- * $LicenseInfo:firstyear=2006&license=viewergpl$
- *
- * Copyright (c) 2006-2007, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
* 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://secondlife.com/developers/opensource/gplv2
+ * Copyright (C) 2010, Linden Research, Inc.
*
- * 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://secondlife.com/developers/opensource/flossexception
+ * 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.
*
- * 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.
+ * 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.
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * 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 "linden_common.h"
-
+#include <openssl/x509_vfy.h>
#include "llhttpclient.h"
#include "llassetstorage.h"
@@ -45,7 +40,10 @@
#include "message.h"
#include <curl/curl.h>
+
const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f;
+LLURLRequest::SSLCertVerifyCallback LLHTTPClient::mCertVerifyCallback = NULL;
+
////////////////////////////////////////////////////////////////////////////
// Responder class moved to LLCurl
@@ -56,7 +54,7 @@ namespace
{
public:
LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder)
- : mResponder(responder), mStatus(499),
+ : LLURLRequestComplete(), mResponder(responder), mStatus(499),
mReason("LLURLRequest complete w/no status")
{
}
@@ -67,6 +65,8 @@ namespace
virtual void httpStatus(U32 status, const std::string& reason)
{
+ LLURLRequestComplete::httpStatus(status,reason);
+
mStatus = status;
mReason = reason;
}
@@ -76,8 +76,10 @@ namespace
{
if (mResponder.get())
{
- mResponder->completedRaw(mStatus, mReason, channels, buffer);
+ // Allow clients to parse headers before we attempt to parse
+ // the body and provide completed/result/error calls.
mResponder->completedHeader(mStatus, mReason, mHeaderOutput);
+ mResponder->completedRaw(mStatus, mReason, channels, buffer);
}
}
virtual void header(const std::string& header, const std::string& value)
@@ -104,7 +106,7 @@ namespace
LLSDInjector(const LLSD& sd) : mSD(sd) {}
virtual ~LLSDInjector() {}
- const char* contentType() { return "application/xml"; }
+ const char* contentType() { return "application/llsd+xml"; }
virtual EStatus process_impl(const LLChannelDescriptors& channels,
buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump)
@@ -122,7 +124,7 @@ namespace
{
public:
RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {}
- virtual ~RawInjector() {}
+ virtual ~RawInjector() {delete mData;}
const char* contentType() { return "application/octet-stream"; }
@@ -152,17 +154,21 @@ namespace
{
LLBufferStream ostream(channels, buffer.get());
- llifstream fstream(mFilename.c_str(), std::iostream::binary | std::iostream::out);
- 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;
+ 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);
+ std::vector<char> fileBuffer(fileSize);
+ fstream.read(&fileBuffer[0], fileSize);
+ ostream.write(&fileBuffer[0], fileSize);
+ fstream.close();
+ eos = true;
+ return STATUS_DONE;
+ }
+
+ return STATUS_ERROR;
}
const std::string mFilename;
@@ -187,6 +193,7 @@ namespace
fileBuffer = new U8 [fileSize];
vfile.read(fileBuffer, fileSize);
ostream.write((char*)fileBuffer, fileSize);
+ delete [] fileBuffer;
eos = true;
return STATUS_DONE;
}
@@ -199,15 +206,19 @@ namespace
LLPumpIO* theClientPump = NULL;
}
+void LLHTTPClient::setCertVerifyCallback(LLURLRequest::SSLCertVerifyCallback callback)
+{
+ LLHTTPClient::mCertVerifyCallback = callback;
+}
+
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(),
- S32 offset = 0,
- S32 bytes = 0)
+ const LLSD& headers = LLSD()
+ )
{
if (!LLHTTPClient::hasPump())
{
@@ -216,12 +227,21 @@ static void request(
}
LLPumpIO::chain_t chain;
- LLURLRequest *req = new LLURLRequest(method, url);
- req->checkRootCertificate(true);
+ LLURLRequest* req = new LLURLRequest(method, url);
+ req->setSSLVerifyCallback(LLHTTPClient::getCertVerifyCallback(), (void *)req);
+
+
+ lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " "
+ << headers << llendl;
+
+ // Insert custom headers if the caller sent any
+ if (headers.isMap())
+ {
+ if (headers.has("Cookie"))
+ {
+ req->allowCookies();
+ }
- // 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();
@@ -233,15 +253,34 @@ static void request(
//the Pragma header it so gratuitously inserts
//Before inserting the header, force libcurl
//to not use the proxy (read: llurlrequest.cpp)
- if ((iter->first == "Pragma") && (iter->second.asString() == ""))
+ static const std::string PRAGMA("Pragma");
+ if ((iter->first == PRAGMA) && (iter->second.asString().empty()))
{
- req->useProxy(FALSE);
+ 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");
+ }
+ }
+
+ if (responder)
+ {
+ responder->setURL(url);
+ }
+
req->setCallback(new LLHTTPClientURLAdaptor(responder));
if (method == LLURLRequest::HTTP_POST && gMessageSystem)
@@ -249,48 +288,55 @@ static void request(
req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d",
gMessageSystem->mPort).c_str());
}
-
+
if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST)
{
- req->addHeader(llformat("Content-Type: %s",
- body_injector->contentType()).c_str());
-
+ 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));
}
- if (method == LLURLRequest::HTTP_GET && (offset > 0 || bytes > 0))
- {
- std::string range = llformat("Range: bytes=%d-%d", offset,offset+bytes-1);
- req->addHeader(range.c_str());
- }
-
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& headers,
- const F32 timeout)
+void LLHTTPClient::getByteRange(
+ const std::string& url,
+ S32 offset,
+ S32 bytes,
+ ResponderPtr responder,
+ const LLSD& hdrs,
+ const F32 timeout)
{
- // *FIX: Why is the headers argument ignored? Phoenix 2008-04-28
- request(
- url,
- LLURLRequest::HTTP_GET,
- NULL,
- responder,
- timeout,
- LLSD(), // WTF? Shouldn't this be used?
- offset,
- bytes);
+ 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 F32 timeout)
+void LLHTTPClient::head(
+ const std::string& url,
+ ResponderPtr responder,
+ const LLSD& headers,
+ const F32 timeout)
{
- request(url, LLURLRequest::HTTP_HEAD, NULL, responder, 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)
@@ -349,91 +395,199 @@ 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)
+// These calls are blocking! This is usually bad, unless you're a dataserver. Then it's awesome.
+
+/**
+ @brief does a blocking request on the url, returning the data or bad status.
+
+ @param url URI to verb on.
+ @param method the verb to hit the URI with.
+ @param body the body of the call (if needed - for instance not used for GET and DELETE, but is for POST and PUT)
+ @param headers HTTP headers to use for the request.
+ @param timeout Curl timeout to use. Defaults to 5. Rationale:
+ 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
+
+ @returns an LLSD map: {status: integer, body: map}
+ */
+static LLSD blocking_request(
+ const std::string& url,
+ LLURLRequest::ERequestAction method,
+ const LLSD& body,
+ const LLSD& headers = LLSD(),
+ const F32 timeout = 5
+)
{
- llinfos << "blockingGet of " << url << llendl;
-
- // Returns an LLSD map: {status: integer, body: map}
- char curl_error_buffer[CURL_ERROR_SIZE];
+ lldebugs << "blockingRequest of " << url << llendl;
+ char curl_error_buffer[CURL_ERROR_SIZE] = "\0";
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
+ std::string body_str;
+
+ // other request method checks root cert first, we skip?
+
+ // * Set curl handle options
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_TIMEOUT, timeout); // seconds, see warning at top of function.
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);
+
+ // * Setup headers (don't forget to free them after the call!)
+ curl_slist* headers_list = NULL;
+ if (headers.isMap())
+ {
+ LLSD::map_const_iterator iter = headers.beginMap();
+ LLSD::map_const_iterator end = headers.endMap();
+ for (; iter != end; ++iter)
+ {
+ std::ostringstream header;
+ header << iter->first << ": " << iter->second.asString() ;
+ lldebugs << "header = " << header.str() << llendl;
+ headers_list = curl_slist_append(headers_list, header.str().c_str());
+ }
+ }
+
+ // * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy)
+ if (method == LLURLRequest::HTTP_GET)
+ {
+ curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1);
+ }
+ else if (method == LLURLRequest::HTTP_POST)
+ {
+ curl_easy_setopt(curlp, CURLOPT_POST, 1);
+ //serialize to ostr then copy to str - need to because ostr ptr is unstable :(
+ std::ostringstream ostr;
+ LLSDSerialize::toXML(body, ostr);
+ body_str = ostr.str();
+ curl_easy_setopt(curlp, CURLOPT_POSTFIELDS, body_str.c_str());
+ //copied from PHP libs, correct?
+ headers_list = curl_slist_append(headers_list, "Content-Type: application/llsd+xml");
+
+ // copied from llurlrequest.cpp
+ // it appears that apache2.2.3 or django in etch is busted. If
+ // we do not clear the expect header, we get a 500. May be
+ // limited to django/mod_wsgi.
+ headers_list = curl_slist_append(headers_list, "Expect:");
+ }
+
+ // * Do the action using curl, handle results
+ lldebugs << "HTTP body: " << body_str << llendl;
+ headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml");
+ CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, headers_list);
+ if ( curl_result != CURLE_OK )
+ {
+ llinfos << "Curl is hosed - can't add headers" << 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);
-
+ 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.
+ // if we get a non-404 and it's not a 200 OR maybe it is but you have error bits,
+ if ( http_status != 404 && (http_status != 200 || curl_success != 0) )
{
+ // We expect 404s, don't spam for them.
+ llwarns << "CURL REQ URL: " << url << llendl;
+ llwarns << "CURL REQ METHOD TYPE: " << method << llendl;
+ llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl;
+ llwarns << "CURL REQ BODY: " << body_str << llendl;
+ llwarns << "CURL HTTP_STATUS: " << http_status << llendl;
llwarns << "CURL ERROR: " << curl_error_buffer << llendl;
-
+ llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl;
response["body"] = http_buffer.asString();
}
else
{
response["body"] = http_buffer.asLLSD();
+ lldebugs << "CURL response: " << http_buffer.asString() << llendl;
}
- curl_easy_cleanup(curlp);
+ if(headers_list)
+ { // free the header list
+ curl_slist_free_all(headers_list);
+ }
+ // * Cleanup
+ curl_easy_cleanup(curlp);
return response;
}
-void LLHTTPClient::put(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
+LLSD LLHTTPClient::blockingGet(const std::string& url)
{
- request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout);
+ return blocking_request(url, LLURLRequest::HTTP_GET, LLSD());
}
-void LLHTTPClient::post(const std::string& url, const LLSD& body, ResponderPtr responder, const F32 timeout)
+LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body)
{
- request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout);
+ return blocking_request(url, LLURLRequest::HTTP_POST, body);
}
-void LLHTTPClient::post(const std::string& url, const U8* data, S32 size, ResponderPtr responder, const F32 timeout)
+void LLHTTPClient::put(
+ const std::string& url,
+ const LLSD& body,
+ ResponderPtr responder,
+ const LLSD& headers,
+ const F32 timeout)
{
- request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout);
+ request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout, headers);
}
-void LLHTTPClient::postFile(const std::string& url, const std::string& filename, ResponderPtr responder, const F32 timeout)
+void LLHTTPClient::post(
+ const std::string& url,
+ const LLSD& body,
+ ResponderPtr responder,
+ const LLSD& headers,
+ const F32 timeout)
{
- request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout);
+ request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout, headers);
}
-void LLHTTPClient::postFile(const std::string& url, const LLUUID& uuid,
- LLAssetType::EType asset_type, ResponderPtr responder, const F32 timeout)
+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);
+ 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);
+ request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout, headers);
}
// static
@@ -441,9 +595,10 @@ void LLHTTPClient::move(
const std::string& url,
const std::string& destination,
ResponderPtr responder,
+ const LLSD& hdrs,
const F32 timeout)
{
- LLSD headers;
+ LLSD headers = hdrs;
headers["Destination"] = destination;
request(url, LLURLRequest::HTTP_MOVE, NULL, responder, timeout, headers);
}