651 lines
15 KiB
C++
651 lines
15 KiB
C++
/**
|
|
* @file llurlrequest.cpp
|
|
* @author Phoenix
|
|
* @date 2005-04-28
|
|
* @brief Implementation of the URLRequest class and related classes.
|
|
*
|
|
* Copyright (c) 2005-$CurrentYear$, Linden Research, Inc.
|
|
* $License$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "llurlrequest.h"
|
|
|
|
#include <curl/curl.h>
|
|
#include <algorithm>
|
|
|
|
#include "llioutil.h"
|
|
#include "llmemtype.h"
|
|
#include "llpumpio.h"
|
|
#include "llsd.h"
|
|
#include "llstring.h"
|
|
|
|
static const U32 HTTP_STATUS_PIPE_ERROR = 499;
|
|
|
|
/**
|
|
* String constants
|
|
*/
|
|
const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri");
|
|
|
|
|
|
static
|
|
size_t headerCallback(void* data, size_t size, size_t nmemb, void* user);
|
|
|
|
/**
|
|
* class LLURLRequestDetail
|
|
*/
|
|
class LLURLRequestDetail
|
|
{
|
|
public:
|
|
LLURLRequestDetail();
|
|
~LLURLRequestDetail();
|
|
CURLM* mCurlMulti;
|
|
CURL* mCurl;
|
|
struct curl_slist* mHeaders;
|
|
char* mURL;
|
|
char mCurlErrorBuf[CURL_ERROR_SIZE + 1]; /* Flawfinder: ignore */
|
|
bool mNeedToRemoveEasyHandle;
|
|
LLBufferArray* mResponseBuffer;
|
|
LLChannelDescriptors mChannels;
|
|
U8* mLastRead;
|
|
U32 mBodyLimit;
|
|
bool mIsBodyLimitSet;
|
|
};
|
|
|
|
LLURLRequestDetail::LLURLRequestDetail() :
|
|
mCurlMulti(NULL),
|
|
mCurl(NULL),
|
|
mHeaders(NULL),
|
|
mURL(NULL),
|
|
mNeedToRemoveEasyHandle(false),
|
|
mResponseBuffer(NULL),
|
|
mLastRead(NULL),
|
|
mBodyLimit(0),
|
|
mIsBodyLimitSet(false)
|
|
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
mCurlErrorBuf[0] = '\0';
|
|
}
|
|
|
|
LLURLRequestDetail::~LLURLRequestDetail()
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
if(mCurl)
|
|
{
|
|
if(mNeedToRemoveEasyHandle && mCurlMulti)
|
|
{
|
|
curl_multi_remove_handle(mCurlMulti, mCurl);
|
|
mNeedToRemoveEasyHandle = false;
|
|
}
|
|
curl_easy_cleanup(mCurl);
|
|
mCurl = NULL;
|
|
}
|
|
if(mCurlMulti)
|
|
{
|
|
curl_multi_cleanup(mCurlMulti);
|
|
mCurlMulti = NULL;
|
|
}
|
|
if(mHeaders)
|
|
{
|
|
curl_slist_free_all(mHeaders);
|
|
mHeaders = NULL;
|
|
}
|
|
delete[] mURL;
|
|
mURL = NULL;
|
|
mResponseBuffer = NULL;
|
|
mLastRead = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* class LLURLRequest
|
|
*/
|
|
|
|
static std::string sCAFile("");
|
|
static std::string sCAPath("");
|
|
|
|
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) :
|
|
mAction(action)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
initialize();
|
|
}
|
|
|
|
LLURLRequest::LLURLRequest(
|
|
LLURLRequest::ERequestAction action,
|
|
const std::string& url) :
|
|
mAction(action)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
initialize();
|
|
setURL(url);
|
|
}
|
|
|
|
LLURLRequest::~LLURLRequest()
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
delete mDetail;
|
|
}
|
|
|
|
void LLURLRequest::setURL(const std::string& url)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
if(mDetail->mURL)
|
|
{
|
|
// *NOTE: if any calls to set the url have been made to curl,
|
|
// this will probably lead to a crash.
|
|
delete[] mDetail->mURL;
|
|
mDetail->mURL = NULL;
|
|
}
|
|
if(!url.empty())
|
|
{
|
|
mDetail->mURL = new char[url.size() + 1];
|
|
url.copy(mDetail->mURL, url.size());
|
|
mDetail->mURL[url.size()] = '\0';
|
|
}
|
|
}
|
|
|
|
void LLURLRequest::addHeader(const char* header)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
mDetail->mHeaders = curl_slist_append(mDetail->mHeaders, header);
|
|
}
|
|
|
|
void LLURLRequest::requestEncoding(const char* encoding)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_ENCODING, encoding);
|
|
}
|
|
|
|
void LLURLRequest::setBodyLimit(U32 size)
|
|
{
|
|
mDetail->mBodyLimit = size;
|
|
mDetail->mIsBodyLimitSet = true;
|
|
}
|
|
|
|
void LLURLRequest::checkRootCertificate(bool check, const char* caBundle)
|
|
{
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE));
|
|
if (caBundle)
|
|
{
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, caBundle);
|
|
}
|
|
}
|
|
|
|
void LLURLRequest::setCallback(LLURLRequestComplete* callback)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
mCompletionCallback = callback;
|
|
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_HEADERFUNCTION, &headerCallback);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEHEADER, callback);
|
|
}
|
|
|
|
// virtual
|
|
LLIOPipe::EStatus LLURLRequest::handleError(
|
|
LLIOPipe::EStatus status,
|
|
LLPumpIO* pump)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
if(mCompletionCallback && pump)
|
|
{
|
|
LLURLRequestComplete* complete = NULL;
|
|
complete = (LLURLRequestComplete*)mCompletionCallback.get();
|
|
complete->httpStatus(
|
|
HTTP_STATUS_PIPE_ERROR,
|
|
LLIOPipe::lookupStatusString(status));
|
|
complete->responseStatus(status);
|
|
pump->respond(complete);
|
|
mCompletionCallback = NULL;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
// virtual
|
|
LLIOPipe::EStatus LLURLRequest::process_impl(
|
|
const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer,
|
|
bool& eos,
|
|
LLSD& context,
|
|
LLPumpIO* pump)
|
|
{
|
|
PUMP_DEBUG;
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
//llinfos << "LLURLRequest::process_impl()" << llendl;
|
|
if(!buffer) return STATUS_ERROR;
|
|
switch(mState)
|
|
{
|
|
case STATE_INITIALIZED:
|
|
{
|
|
PUMP_DEBUG;
|
|
// We only need to wait for input if we are uploading
|
|
// something.
|
|
if(((HTTP_PUT == mAction) || (HTTP_POST == mAction)) && !eos)
|
|
{
|
|
// we're waiting to get all of the information
|
|
return STATUS_BREAK;
|
|
}
|
|
|
|
// *FIX: bit of a hack, but it should work. The configure and
|
|
// callback method expect this information to be ready.
|
|
mDetail->mResponseBuffer = buffer.get();
|
|
mDetail->mChannels = channels;
|
|
if(!configure())
|
|
{
|
|
return STATUS_ERROR;
|
|
}
|
|
mState = STATE_WAITING_FOR_RESPONSE;
|
|
|
|
// *FIX: Maybe we should just go to the next state now...
|
|
return STATUS_BREAK;
|
|
}
|
|
case STATE_WAITING_FOR_RESPONSE:
|
|
case STATE_PROCESSING_RESPONSE:
|
|
{
|
|
PUMP_DEBUG;
|
|
const S32 MAX_CALLS = 5;
|
|
S32 count = MAX_CALLS;
|
|
CURLMcode code;
|
|
LLIOPipe::EStatus status = STATUS_BREAK;
|
|
S32 queue;
|
|
do
|
|
{
|
|
code = curl_multi_perform(mDetail->mCurlMulti, &queue);
|
|
}while((CURLM_CALL_MULTI_PERFORM == code) && (queue > 0) && count--);
|
|
CURLMsg* curl_msg;
|
|
do
|
|
{
|
|
curl_msg = curl_multi_info_read(mDetail->mCurlMulti, &queue);
|
|
if(curl_msg && (curl_msg->msg == CURLMSG_DONE))
|
|
{
|
|
mState = STATE_HAVE_RESPONSE;
|
|
|
|
CURLcode result = curl_msg->data.result;
|
|
switch(result)
|
|
{
|
|
case CURLE_OK:
|
|
case CURLE_WRITE_ERROR:
|
|
// NB: The error indication means that we stopped the
|
|
// writing due the body limit being reached
|
|
if(mCompletionCallback && pump)
|
|
{
|
|
LLURLRequestComplete* complete = NULL;
|
|
complete = (LLURLRequestComplete*)
|
|
mCompletionCallback.get();
|
|
complete->responseStatus(
|
|
result == CURLE_OK
|
|
? STATUS_OK : STATUS_STOP);
|
|
LLPumpIO::links_t chain;
|
|
LLPumpIO::LLLinkInfo link;
|
|
link.mPipe = mCompletionCallback;
|
|
link.mChannels = LLBufferArray::makeChannelConsumer(
|
|
channels);
|
|
chain.push_back(link);
|
|
pump->respond(chain, buffer, context);
|
|
mCompletionCallback = NULL;
|
|
}
|
|
break;
|
|
case CURLE_COULDNT_CONNECT:
|
|
status = STATUS_NO_CONNECTION;
|
|
break;
|
|
default:
|
|
llwarns << "URLRequest Error: " << curl_msg->data.result
|
|
<< ", "
|
|
#if LL_DARWIN
|
|
// curl_easy_strerror was added in libcurl 7.12.0. Unfortunately, the version in the Mac OS X 10.3.9 SDK is 7.10.2...
|
|
// There's a problem with the custom curl headers in our build that keeps me from #ifdefing this on the libcurl version number
|
|
// (the correct check would be #if LIBCURL_VERSION_NUM >= 0x070c00). We'll fix the header problem soon, but for now
|
|
// just punt and print the numeric error code on the Mac.
|
|
<< curl_msg->data.result
|
|
#else // LL_DARWIN
|
|
<< curl_easy_strerror(curl_msg->data.result)
|
|
#endif // LL_DARWIN
|
|
<< ", "
|
|
<< (mDetail->mURL ? mDetail->mURL : "<EMPTY URL>")
|
|
<< llendl;
|
|
status = STATUS_ERROR;
|
|
break;
|
|
}
|
|
curl_multi_remove_handle(mDetail->mCurlMulti, mDetail->mCurl);
|
|
mDetail->mNeedToRemoveEasyHandle = false;
|
|
}
|
|
}while(curl_msg && (queue > 0));
|
|
return status;
|
|
}
|
|
case STATE_HAVE_RESPONSE:
|
|
PUMP_DEBUG;
|
|
// we already stuffed everything into channel in in the curl
|
|
// callback, so we are done.
|
|
eos = true;
|
|
return STATUS_DONE;
|
|
|
|
default:
|
|
PUMP_DEBUG;
|
|
return STATUS_ERROR;
|
|
}
|
|
}
|
|
|
|
void LLURLRequest::initialize()
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
mState = STATE_INITIALIZED;
|
|
mDetail = new LLURLRequestDetail;
|
|
mDetail->mCurl = curl_easy_init();
|
|
mDetail->mCurlMulti = curl_multi_init();
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_NOSIGNAL, 1);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEFUNCTION, &downCallback);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_WRITEDATA, this);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_READFUNCTION, &upCallback);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_READDATA, this);
|
|
curl_easy_setopt(
|
|
mDetail->mCurl,
|
|
CURLOPT_ERRORBUFFER,
|
|
mDetail->mCurlErrorBuf);
|
|
|
|
if(sCAPath != std::string(""))
|
|
{
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_CAPATH, sCAPath.c_str());
|
|
}
|
|
if(sCAFile != std::string(""))
|
|
{
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_CAINFO, sCAFile.c_str());
|
|
}
|
|
}
|
|
|
|
bool LLURLRequest::configure()
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
bool rv = false;
|
|
S32 bytes = mDetail->mResponseBuffer->countAfter(
|
|
mDetail->mChannels.in(),
|
|
NULL);
|
|
switch(mAction)
|
|
{
|
|
case HTTP_GET:
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_HTTPGET, 1);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_FOLLOWLOCATION, 1);
|
|
rv = true;
|
|
break;
|
|
|
|
case HTTP_PUT:
|
|
// Disable the expect http 1.1 extension. POST and PUT default
|
|
// to turning this on, and I am not too sure what it means.
|
|
addHeader("Expect:");
|
|
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_UPLOAD, 1);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_INFILESIZE, bytes);
|
|
rv = true;
|
|
break;
|
|
|
|
case HTTP_POST:
|
|
// Disable the expect http 1.1 extension. POST and PUT default
|
|
// to turning this on, and I am not too sure what it means.
|
|
addHeader("Expect:");
|
|
|
|
// Disable the content type http header.
|
|
// *FIX: what should it be?
|
|
addHeader("Content-Type:");
|
|
|
|
// Set the handle for an http post
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_POST, 1);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDS, NULL);
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_POSTFIELDSIZE, bytes);
|
|
rv = true;
|
|
break;
|
|
|
|
case HTTP_DELETE:
|
|
// Set the handle for an http post
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
rv = true;
|
|
break;
|
|
|
|
default:
|
|
llwarns << "Unhandled URLRequest action: " << mAction << llendl;
|
|
break;
|
|
}
|
|
if(rv)
|
|
{
|
|
if(mDetail->mHeaders)
|
|
{
|
|
curl_easy_setopt(
|
|
mDetail->mCurl,
|
|
CURLOPT_HTTPHEADER,
|
|
mDetail->mHeaders);
|
|
}
|
|
curl_easy_setopt(mDetail->mCurl, CURLOPT_URL, mDetail->mURL);
|
|
curl_multi_add_handle(mDetail->mCurlMulti, mDetail->mCurl);
|
|
mDetail->mNeedToRemoveEasyHandle = true;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
size_t LLURLRequest::downCallback(
|
|
void* data,
|
|
size_t size,
|
|
size_t nmemb,
|
|
void* user)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
LLURLRequest* req = (LLURLRequest*)user;
|
|
if(STATE_WAITING_FOR_RESPONSE == req->mState)
|
|
{
|
|
req->mState = STATE_PROCESSING_RESPONSE;
|
|
}
|
|
U32 bytes = size * nmemb;
|
|
if (req->mDetail->mIsBodyLimitSet)
|
|
{
|
|
if (bytes > req->mDetail->mBodyLimit)
|
|
{
|
|
bytes = req->mDetail->mBodyLimit;
|
|
req->mDetail->mBodyLimit = 0;
|
|
}
|
|
else
|
|
{
|
|
req->mDetail->mBodyLimit -= bytes;
|
|
}
|
|
}
|
|
|
|
req->mDetail->mResponseBuffer->append(
|
|
req->mDetail->mChannels.out(),
|
|
(U8*)data,
|
|
bytes);
|
|
return bytes;
|
|
}
|
|
|
|
// static
|
|
size_t LLURLRequest::upCallback(
|
|
void* data,
|
|
size_t size,
|
|
size_t nmemb,
|
|
void* user)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
LLURLRequest* req = (LLURLRequest*)user;
|
|
S32 bytes = llmin(
|
|
(S32)(size * nmemb),
|
|
req->mDetail->mResponseBuffer->countAfter(
|
|
req->mDetail->mChannels.in(),
|
|
req->mDetail->mLastRead));
|
|
req->mDetail->mLastRead = req->mDetail->mResponseBuffer->readAfter(
|
|
req->mDetail->mChannels.in(),
|
|
req->mDetail->mLastRead,
|
|
(U8*)data,
|
|
bytes);
|
|
return bytes;
|
|
}
|
|
|
|
static
|
|
size_t headerCallback(void* data, size_t size, size_t nmemb, void* user)
|
|
{
|
|
const char* headerLine = (const char*)data;
|
|
size_t headerLen = size * nmemb;
|
|
LLURLRequestComplete* complete = (LLURLRequestComplete*)user;
|
|
|
|
// FIXME: This should be a utility in llstring.h: isascii()
|
|
for (size_t i = 0; i < headerLen; ++i)
|
|
{
|
|
if (headerLine[i] < 0)
|
|
{
|
|
return headerLen;
|
|
}
|
|
}
|
|
|
|
size_t sep;
|
|
for (sep = 0; sep < headerLen && headerLine[sep] != ':'; ++sep) { }
|
|
|
|
if (sep < headerLen && complete)
|
|
{
|
|
std::string key(headerLine, sep);
|
|
std::string value(headerLine + sep + 1, headerLen - sep - 1);
|
|
|
|
key = utf8str_tolower(utf8str_trim(key));
|
|
value = utf8str_trim(value);
|
|
|
|
complete->header(key, value);
|
|
}
|
|
else
|
|
{
|
|
std::string s(headerLine, headerLen);
|
|
|
|
std::string::iterator end = s.end();
|
|
std::string::iterator pos1 = std::find(s.begin(), end, ' ');
|
|
if (pos1 != end) ++pos1;
|
|
std::string::iterator pos2 = std::find(pos1, end, ' ');
|
|
if (pos2 != end) ++pos2;
|
|
std::string::iterator pos3 = std::find(pos2, end, '\r');
|
|
|
|
std::string version(s.begin(), pos1);
|
|
std::string status(pos1, pos2);
|
|
std::string reason(pos2, pos3);
|
|
|
|
int statusCode = atoi(status.c_str());
|
|
if (statusCode > 0)
|
|
{
|
|
complete->httpStatus((U32)statusCode, reason);
|
|
}
|
|
}
|
|
|
|
return headerLen;
|
|
}
|
|
|
|
//static
|
|
void LLURLRequest::setCertificateAuthorityFile(const std::string& file_name)
|
|
{
|
|
sCAFile = file_name;
|
|
}
|
|
|
|
//static
|
|
void LLURLRequest::setCertificateAuthorityPath(const std::string& path)
|
|
{
|
|
sCAPath = path;
|
|
}
|
|
|
|
/**
|
|
* LLContextURLExtractor
|
|
*/
|
|
// virtual
|
|
LLIOPipe::EStatus LLContextURLExtractor::process_impl(
|
|
const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer,
|
|
bool& eos,
|
|
LLSD& context,
|
|
LLPumpIO* pump)
|
|
{
|
|
PUMP_DEBUG;
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
// The destination host is in the context.
|
|
if(context.isUndefined() || !mRequest)
|
|
{
|
|
return STATUS_PRECONDITION_NOT_MET;
|
|
}
|
|
|
|
// copy in to out, since this just extract the URL and does not
|
|
// actually change the data.
|
|
LLChangeChannel change(channels.in(), channels.out());
|
|
std::for_each(buffer->beginSegment(), buffer->endSegment(), change);
|
|
|
|
// find the context url
|
|
if(context.has(CONTEXT_DEST_URI_SD_LABEL))
|
|
{
|
|
mRequest->setURL(context[CONTEXT_DEST_URI_SD_LABEL]);
|
|
return STATUS_DONE;
|
|
}
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
|
|
/**
|
|
* LLURLRequestComplete
|
|
*/
|
|
LLURLRequestComplete::LLURLRequestComplete() :
|
|
mRequestStatus(LLIOPipe::STATUS_ERROR)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
}
|
|
|
|
// virtual
|
|
LLURLRequestComplete::~LLURLRequestComplete()
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
}
|
|
|
|
//virtual
|
|
void LLURLRequestComplete::header(const std::string& header, const std::string& value)
|
|
{
|
|
}
|
|
|
|
//virtual
|
|
void LLURLRequestComplete::httpStatus(U32 status, const std::string& reason)
|
|
{
|
|
}
|
|
|
|
//virtual
|
|
void LLURLRequestComplete::complete(const LLChannelDescriptors& channels,
|
|
const buffer_ptr_t& buffer)
|
|
{
|
|
if(STATUS_OK == mRequestStatus)
|
|
{
|
|
response(channels, buffer);
|
|
}
|
|
else
|
|
{
|
|
noResponse();
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
void LLURLRequestComplete::response(const LLChannelDescriptors& channels,
|
|
const buffer_ptr_t& buffer)
|
|
{
|
|
llwarns << "LLURLRequestComplete::response default implementation called"
|
|
<< llendl;
|
|
}
|
|
|
|
//virtual
|
|
void LLURLRequestComplete::noResponse()
|
|
{
|
|
llwarns << "LLURLRequestComplete::noResponse default implementation called"
|
|
<< llendl;
|
|
}
|
|
|
|
void LLURLRequestComplete::responseStatus(LLIOPipe::EStatus status)
|
|
{
|
|
LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST);
|
|
mRequestStatus = status;
|
|
}
|
|
|
|
// virtual
|
|
LLIOPipe::EStatus LLURLRequestComplete::process_impl(
|
|
const LLChannelDescriptors& channels,
|
|
buffer_ptr_t& buffer,
|
|
bool& eos,
|
|
LLSD& context,
|
|
LLPumpIO* pump)
|
|
{
|
|
PUMP_DEBUG;
|
|
complete(channels, buffer);
|
|
return STATUS_OK;
|
|
}
|