diff options
Diffstat (limited to 'indra/llmessage/llhttpclient.cpp')
-rwxr-xr-x[-rw-r--r--] | indra/llmessage/llhttpclient.cpp | 325 |
1 files changed, 191 insertions, 134 deletions
diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index dd56e18caf..f8db3dded2 100644..100755 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -2,36 +2,30 @@ * @file llhttpclient.cpp * @brief Implementation of classes for making HTTP requests. * - * $LicenseInfo:firstyear=2006&license=viewergpl$ - * - * Copyright (c) 2006-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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" @@ -46,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 @@ -57,7 +54,7 @@ namespace { public: LLHTTPClientURLAdaptor(LLCurl::ResponderPtr responder) - : LLURLRequestComplete(), mResponder(responder), mStatus(499), + : LLURLRequestComplete(), mResponder(responder), mStatus(HTTP_INTERNAL_ERROR), mReason("LLURLRequest complete w/no status") { } @@ -66,7 +63,7 @@ namespace { } - virtual void httpStatus(U32 status, const std::string& reason) + virtual void httpStatus(S32 status, const std::string& reason) { LLURLRequestComplete::httpStatus(status,reason); @@ -77,28 +74,33 @@ namespace virtual void complete(const LLChannelDescriptors& channels, const buffer_ptr_t& buffer) { + // *TODO: Re-interpret mRequestStatus codes? + // Would like to detect curl errors, such as + // connection errors, write erros, etc. if (mResponder.get()) { - mResponder->completedRaw(mStatus, mReason, channels, buffer); - mResponder->completedHeader(mStatus, mReason, mHeaderOutput); + mResponder->setResult(mStatus, mReason); + mResponder->completedRaw(channels, buffer); } } virtual void header(const std::string& header, const std::string& value) { - mHeaderOutput[header] = value; + if (mResponder.get()) + { + mResponder->setResponseHeader(header, value); + } } private: LLCurl::ResponderPtr mResponder; - U32 mStatus; + S32 mStatus; std::string mReason; - LLSD mHeaderOutput; }; class Injector : public LLIOPipe { public: - virtual const char* contentType() = 0; + virtual const std::string& contentType() = 0; }; class LLSDInjector : public Injector @@ -107,7 +109,7 @@ namespace LLSDInjector(const LLSD& sd) : mSD(sd) {} virtual ~LLSDInjector() {} - const char* contentType() { return "application/llsd+xml"; } + const std::string& contentType() { return HTTP_CONTENT_LLSD_XML; } virtual EStatus process_impl(const LLChannelDescriptors& channels, buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) @@ -125,9 +127,9 @@ namespace { public: RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {} - virtual ~RawInjector() {delete mData;} + virtual ~RawInjector() {delete [] mData;} - const char* contentType() { return "application/octet-stream"; } + const std::string& contentType() { return HTTP_CONTENT_OCTET_STREAM; } virtual EStatus process_impl(const LLChannelDescriptors& channels, buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) @@ -148,18 +150,18 @@ namespace FileInjector(const std::string& filename) : mFilename(filename) {} virtual ~FileInjector() {} - const char* contentType() { return "application/octet-stream"; } + const std::string& contentType() { return HTTP_CONTENT_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); + llifstream fstream(mFilename.c_str(), std::iostream::binary | std::iostream::out); if(fstream.is_open()) { fstream.seekg(0, std::ios::end); - U32 fileSize = fstream.tellg(); + U32 fileSize = (U32)fstream.tellg(); fstream.seekg(0, std::ios::beg); std::vector<char> fileBuffer(fileSize); fstream.read(&fileBuffer[0], fileSize); @@ -181,7 +183,7 @@ namespace VFileInjector(const LLUUID& uuid, LLAssetType::EType asset_type) : mUUID(uuid), mAssetType(asset_type) {} virtual ~VFileInjector() {} - const char* contentType() { return "application/octet-stream"; } + const std::string& contentType() { return HTTP_CONTENT_OCTET_STREAM; } virtual EStatus process_impl(const LLChannelDescriptors& channels, buffer_ptr_t& buffer, bool& eos, LLSD& context, LLPumpIO* pump) @@ -194,6 +196,7 @@ namespace fileBuffer = new U8 [fileSize]; vfile.read(fileBuffer, fileSize); ostream.write((char*)fileBuffer, fileSize); + delete [] fileBuffer; eos = true; return STATUS_DONE; } @@ -206,94 +209,113 @@ namespace LLPumpIO* theClientPump = NULL; } +void LLHTTPClient::setCertVerifyCallback(LLURLRequest::SSLCertVerifyCallback callback) +{ + LLHTTPClient::mCertVerifyCallback = callback; +} + static void request( const std::string& url, - LLURLRequest::ERequestAction method, + EHTTPMethod method, Injector* body_injector, LLCurl::ResponderPtr responder, const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, - const LLSD& headers = LLSD()) + const LLSD& headers = LLSD(), + bool follow_redirects = true + ) { if (!LLHTTPClient::hasPump()) { - responder->completed(U32_MAX, "No pump", LLSD()); + if (responder) + { + responder->completeResult(HTTP_INTERNAL_ERROR, "No pump"); + } + delete body_injector; return; } LLPumpIO::chain_t chain; - LLURLRequest* req = new LLURLRequest(method, url); - req->checkRootCertificate(LLCurl::getSSLVerify()); + LLURLRequest* req = new LLURLRequest(method, url, follow_redirects); + if(!req->isValid())//failed + { + if (responder) + { + responder->completeResult(HTTP_INTERNAL_CURL_ERROR, "Internal Error - curl failure"); + } + delete req; + delete body_injector; + return; + } - - lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " " - << headers << llendl; - - // 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()); - } - } + req->setSSLVerifyCallback(LLHTTPClient::getCertVerifyCallback(), (void *)req); + + LL_DEBUGS("LLHTTPClient") << httpMethodAsVerb(method) << " " << url << " " << headers << LL_ENDL; + + // Insert custom headers if the caller sent any + if (headers.isMap()) + { + if (headers.has(HTTP_OUT_HEADER_COOKIE)) + { + req->allowCookies(); + } + + LLSD::map_const_iterator iter = headers.beginMap(); + LLSD::map_const_iterator end = headers.endMap(); + + for (; iter != end; ++iter) + { + //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) + if ((iter->first == HTTP_OUT_HEADER_PRAGMA) && (iter->second.asString().empty())) + { + req->useProxy(false); + } + LL_DEBUGS("LLHTTPClient") << "header = " << iter->first + << ": " << iter->second.asString() << LL_ENDL; + req->addHeader(iter->first, iter->second.asString()); + } + } // 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 ) + if( method != HTTP_PUT && method != HTTP_POST ) { - static const std::string ACCEPT("Accept"); - if(!headers.has(ACCEPT)) + if(!headers.has(HTTP_OUT_HEADER_ACCEPT)) { - req->addHeader("Accept: application/llsd+xml"); + req->addHeader(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); } } if (responder) { responder->setURL(url); + responder->setHTTPMethod(method); } req->setCallback(new LLHTTPClientURLAdaptor(responder)); - if (method == LLURLRequest::HTTP_POST && gMessageSystem) + if (method == HTTP_POST && gMessageSystem) { - req->addHeader(llformat("X-SecondLife-UDP-Listen-Port: %d", - gMessageSystem->mPort).c_str()); - } + req->addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", + gMessageSystem->mPort)); + } - if (method == LLURLRequest::HTTP_PUT || method == LLURLRequest::HTTP_POST) + if (method == HTTP_PUT || method == HTTP_POST || method == HTTP_PATCH) { - static const std::string CONTENT_TYPE("Content-Type"); - if(!headers.has(CONTENT_TYPE)) + if(!headers.has(HTTP_OUT_HEADER_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()); + req->addHeader(HTTP_OUT_HEADER_CONTENT_TYPE, body_injector->contentType()); } - chain.push_back(LLIOPipe::ptr_t(body_injector)); + chain.push_back(LLIOPipe::ptr_t(body_injector)); } chain.push_back(LLIOPipe::ptr_t(req)); @@ -308,45 +330,51 @@ void LLHTTPClient::getByteRange( S32 bytes, ResponderPtr responder, const LLSD& hdrs, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { LLSD headers = hdrs; if(offset > 0 || bytes > 0) { std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1); - headers["Range"] = range; + headers[HTTP_OUT_HEADER_RANGE] = range; } - request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url,HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } void LLHTTPClient::head( const std::string& url, ResponderPtr responder, const LLSD& headers, - const F32 timeout) + const F32 timeout, + bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); + request(url, HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout, + bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url, HTTP_GET, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { - request(url, LLURLRequest::HTTP_HEAD, NULL, responder, timeout, headers); + request(url, HTTP_HEAD, NULL, responder, timeout, headers, follow_redirects); } -void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout) +void LLHTTPClient::getHeaderOnly(const std::string& url, ResponderPtr responder, const F32 timeout, + bool follow_redirects /* = true */) { - getHeaderOnly(url, responder, LLSD(), timeout); + getHeaderOnly(url, responder, LLSD(), timeout, follow_redirects); } -void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, const F32 timeout) +void LLHTTPClient::get(const std::string& url, const LLSD& query, ResponderPtr responder, const LLSD& headers, + const F32 timeout, bool follow_redirects /* = true */) { LLURI uri; uri = LLURI::buildHTTP(url, LLSD::emptyArray(), query); - get(uri.asString(), responder, headers, timeout); + get(uri.asString(), responder, headers, timeout, follow_redirects); } // A simple class for managing data returned from a curl http request. @@ -375,7 +403,7 @@ public: return content; } - std::string asString() + const std::string& asString() { return mBuffer; } @@ -404,20 +432,24 @@ private: */ static LLSD blocking_request( const std::string& url, - LLURLRequest::ERequestAction method, + EHTTPMethod method, const LLSD& body, const LLSD& headers = LLSD(), const F32 timeout = 5 ) { - lldebugs << "blockingRequest of " << url << llendl; + LL_DEBUGS() << "blockingRequest of " << url << LL_ENDL; char curl_error_buffer[CURL_ERROR_SIZE] = "\0"; - CURL* curlp = curl_easy_init(); + CURL* curlp = LLCurl::newEasyHandle(); + llassert_always(curlp != NULL) ; + LLHTTPBuffer http_buffer; std::string body_str; // other request method checks root cert first, we skip? - //req->checkRootCertificate(true); + + // Apply configured proxy settings + LLProxy::getInstance()->applyProxySettings(curlp); // * Set curl handle options curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts @@ -426,7 +458,7 @@ static LLSD blocking_request( 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); - + // * Setup headers (don't forget to free them after the call!) curl_slist* headers_list = NULL; if (headers.isMap()) @@ -437,17 +469,17 @@ static LLSD blocking_request( { std::ostringstream header; header << iter->first << ": " << iter->second.asString() ; - lldebugs << "header = " << header.str() << llendl; + LL_DEBUGS() << "header = " << header.str() << LL_ENDL; 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) + if (method == HTTP_GET) { curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1); } - else if (method == LLURLRequest::HTTP_POST) + else if (method == HTTP_POST) { curl_easy_setopt(curlp, CURLOPT_POST, 1); //serialize to ostr then copy to str - need to because ostr ptr is unstable :( @@ -456,46 +488,48 @@ static LLSD blocking_request( 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"); + headers_list = curl_slist_append(headers_list, + llformat("%s: %s", HTTP_OUT_HEADER_CONTENT_TYPE.c_str(), HTTP_CONTENT_LLSD_XML.c_str()).c_str()); // 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:"); + headers_list = curl_slist_append(headers_list, llformat("%s:", HTTP_OUT_HEADER_EXPECT.c_str()).c_str()); } // * Do the action using curl, handle results - lldebugs << "HTTP body: " << body_str << llendl; - headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml"); + LL_DEBUGS() << "HTTP body: " << body_str << LL_ENDL; + headers_list = curl_slist_append(headers_list, + llformat("%s: %s", HTTP_OUT_HEADER_ACCEPT.c_str(), HTTP_CONTENT_LLSD_XML.c_str()).c_str()); 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; + LL_INFOS() << "Curl is hosed - can't add headers" << LL_ENDL; } LLSD response = LLSD::emptyMap(); S32 curl_success = curl_easy_perform(curlp); - S32 http_status = 499; + S32 http_status = HTTP_INTERNAL_ERROR; curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &http_status); response["status"] = http_status; // 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) ) + if ( http_status != HTTP_NOT_FOUND && (http_status != HTTP_OK || 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; + LL_WARNS() << "CURL REQ URL: " << url << LL_ENDL; + LL_WARNS() << "CURL REQ METHOD TYPE: " << method << LL_ENDL; + LL_WARNS() << "CURL REQ HEADERS: " << headers.asString() << LL_ENDL; + LL_WARNS() << "CURL REQ BODY: " << body_str << LL_ENDL; + LL_WARNS() << "CURL HTTP_STATUS: " << http_status << LL_ENDL; + LL_WARNS() << "CURL ERROR: " << curl_error_buffer << LL_ENDL; + LL_WARNS() << "CURL ERROR BODY: " << http_buffer.asString() << LL_ENDL; response["body"] = http_buffer.asString(); } else { response["body"] = http_buffer.asLLSD(); - lldebugs << "CURL response: " << http_buffer.asString() << llendl; + LL_DEBUGS() << "CURL response: " << http_buffer.asString() << LL_ENDL; } if(headers_list) @@ -504,18 +538,18 @@ static LLSD blocking_request( } // * Cleanup - curl_easy_cleanup(curlp); + LLCurl::deleteEasyHandle(curlp); return response; } LLSD LLHTTPClient::blockingGet(const std::string& url) { - return blocking_request(url, LLURLRequest::HTTP_GET, LLSD()); + return blocking_request(url, HTTP_GET, LLSD()); } LLSD LLHTTPClient::blockingPost(const std::string& url, const LLSD& body) { - return blocking_request(url, LLURLRequest::HTTP_POST, body); + return blocking_request(url, HTTP_POST, body); } void LLHTTPClient::put( @@ -525,7 +559,17 @@ void LLHTTPClient::put( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, timeout, headers); + request(url, HTTP_PUT, new LLSDInjector(body), responder, timeout, headers); +} + +void LLHTTPClient::patch( + const std::string& url, + const LLSD& body, + ResponderPtr responder, + const LLSD& headers, + const F32 timeout) +{ + request(url, HTTP_PATCH, new LLSDInjector(body), responder, timeout, headers); } void LLHTTPClient::post( @@ -535,7 +579,7 @@ void LLHTTPClient::post( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, timeout, headers); + request(url, HTTP_POST, new LLSDInjector(body), responder, timeout, headers); } void LLHTTPClient::postRaw( @@ -546,7 +590,7 @@ void LLHTTPClient::postRaw( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, timeout, headers); + request(url, HTTP_POST, new RawInjector(data, size), responder, timeout, headers); } void LLHTTPClient::postFile( @@ -556,7 +600,7 @@ void LLHTTPClient::postFile( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, timeout, headers); + request(url, HTTP_POST, new FileInjector(filename), responder, timeout, headers); } void LLHTTPClient::postFile( @@ -567,7 +611,7 @@ void LLHTTPClient::postFile( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout, headers); + request(url, HTTP_POST, new VFileInjector(uuid, asset_type), responder, timeout, headers); } // static @@ -577,7 +621,7 @@ void LLHTTPClient::del( const LLSD& headers, const F32 timeout) { - request(url, LLURLRequest::HTTP_DELETE, NULL, responder, timeout, headers); + request(url, HTTP_DELETE, NULL, responder, timeout, headers); } // static @@ -589,8 +633,21 @@ void LLHTTPClient::move( const F32 timeout) { LLSD headers = hdrs; - headers["Destination"] = destination; - request(url, LLURLRequest::HTTP_MOVE, NULL, responder, timeout, headers); + headers[HTTP_OUT_HEADER_DESTINATION] = destination; + request(url, HTTP_MOVE, NULL, responder, timeout, headers); +} + +// static +void LLHTTPClient::copy( + const std::string& url, + const std::string& destination, + ResponderPtr responder, + const LLSD& hdrs, + const F32 timeout) +{ + LLSD headers = hdrs; + headers[HTTP_OUT_HEADER_DESTINATION] = destination; + request(url, HTTP_COPY, NULL, responder, timeout, headers); } |