summaryrefslogtreecommitdiff
path: root/indra/llmessage/llurlrequest.cpp
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llmessage/llurlrequest.cpp
Print done when done.
Diffstat (limited to 'indra/llmessage/llurlrequest.cpp')
-rw-r--r--indra/llmessage/llurlrequest.cpp650
1 files changed, 650 insertions, 0 deletions
diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp
new file mode 100644
index 0000000000..ea0b13f703
--- /dev/null
+++ b/indra/llmessage/llurlrequest.cpp
@@ -0,0 +1,650 @@
+/**
+ * @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;
+}