phoenix-firestorm/indra/llmessage/lliohttpserver.cpp

834 lines
19 KiB
C++

/**
* @file lliohttpserver.cpp
* @author Phoenix
* @date 2005-10-05
* @brief Implementation of the http server classes
*
* Copyright (c) 2005-$CurrentYear$, Linden Research, Inc.
* $License$
*/
#include "linden_common.h"
#include "lliohttpserver.h"
#include "boost/tokenizer.hpp"
#include "llapr.h"
#include "llbuffer.h"
#include "llbufferstream.h"
#include "llhttpnode.h"
#include "lliopipe.h"
#include "lliosocket.h"
#include "llioutil.h"
#include "llmemtype.h"
#include "llmemorystream.h"
#include "llpumpio.h"
#include "llsd.h"
#include "llsdserialize_xml.h"
#include "llstl.h"
static const char HTTP_VERSION_STR[] = "HTTP/1.0";
static const std::string CONTEXT_REQUEST("request");
static const std::string HTTP_VERB_GET("GET");
static const std::string HTTP_VERB_PUT("PUT");
static const std::string HTTP_VERB_POST("POST");
static const std::string HTTP_VERB_DELETE("DELETE");
class LLHTTPPipe : public LLIOPipe
{
public:
LLHTTPPipe(const LLHTTPNode& node)
: mNode(node), mResponse(NULL), mState(STATE_INVOKE), mChainLock(0), mStatusCode(0)
{ }
virtual ~LLHTTPPipe()
{
if (mResponse.notNull())
{
mResponse->nullPipe();
}
}
private:
// LLIOPipe API implementation.
virtual EStatus process_impl(
const LLChannelDescriptors& channels,
LLIOPipe::buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump);
const LLHTTPNode& mNode;
class Response : public LLHTTPNode::Response
{
public:
static LLPointer<Response> create(LLHTTPPipe* pipe);
virtual ~Response();
// from LLHTTPNode::Response
virtual void result(const LLSD&);
virtual void status(S32 code, const std::string& message);
void nullPipe();
private:
Response() {;} // Must be accessed through LLPointer.
LLHTTPPipe* mPipe;
};
friend class Response;
LLPointer<Response> mResponse;
enum State
{
STATE_INVOKE,
STATE_DELAYED,
STATE_LOCKED,
STATE_GOOD_RESULT,
STATE_STATUS_RESULT
};
State mState;
S32 mChainLock;
LLPumpIO* mLockedPump;
void lockChain(LLPumpIO*);
void unlockChain();
LLSD mGoodResult;
S32 mStatusCode;
std::string mStatusMessage;
};
LLIOPipe::EStatus LLHTTPPipe::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
PUMP_DEBUG;
lldebugs << "LLSDHTTPServer::process_impl" << llendl;
// Once we have all the data, We need to read the sd on
// the the in channel, and respond on the out channel
if(!eos) return STATUS_BREAK;
if(!pump || !buffer) return STATUS_PRECONDITION_NOT_MET;
PUMP_DEBUG;
if (mState == STATE_INVOKE)
{
PUMP_DEBUG;
mState = STATE_DELAYED;
// assume deferred unless mResponse does otherwise
mResponse = Response::create(this);
// TODO: Babbage: Parameterize parser?
LLBufferStream istr(channels, buffer.get());
std::string verb = context[CONTEXT_REQUEST]["verb"];
if(verb == HTTP_VERB_GET)
{
mNode.get(LLHTTPNode::ResponsePtr(mResponse), context);
}
else if(verb == HTTP_VERB_PUT)
{
LLSD input;
LLSDSerialize::fromXML(input, istr);
mNode.put(LLHTTPNode::ResponsePtr(mResponse), context, input);
}
else if(verb == HTTP_VERB_POST)
{
LLSD input;
LLSDSerialize::fromXML(input, istr);
mNode.post(LLHTTPNode::ResponsePtr(mResponse), context, input);
}
else if(verb == HTTP_VERB_DELETE)
{
mNode.del(LLHTTPNode::ResponsePtr(mResponse), context);
}
else
{
mResponse->methodNotAllowed();
}
// Log Internal Server Errors
if(mStatusCode == 500)
{
llwarns << "LLHTTPPipe::process_impl:500:Internal Server Error"
<< llendl;
}
}
PUMP_DEBUG;
switch (mState)
{
case STATE_DELAYED:
lockChain(pump);
mState = STATE_LOCKED;
return STATUS_BREAK;
case STATE_LOCKED:
// should never ever happen!
return STATUS_ERROR;
case STATE_GOOD_RESULT:
{
context["response"]["contentType"] = "application/xml";
LLBufferStream ostr(channels, buffer.get());
LLSDSerialize::toXML(mGoodResult, ostr);
return STATUS_DONE;
}
case STATE_STATUS_RESULT:
{
context["response"]["contentType"] = "text/plain";
context["response"]["statusCode"] = mStatusCode;
context["response"]["statusMessage"] = mStatusMessage;
LLBufferStream ostr(channels, buffer.get());
ostr << mStatusMessage << std::ends;
return STATUS_DONE;
}
default:
llwarns << "LLHTTPPipe::process_impl: unexpected state "
<< mState << llendl;
return STATUS_BREAK;
}
// PUMP_DEBUG; // unreachable
}
LLPointer<LLHTTPPipe::Response> LLHTTPPipe::Response::create(LLHTTPPipe* pipe)
{
LLPointer<Response> result = new Response();
result->mPipe = pipe;
return result;
}
// virtual
LLHTTPPipe::Response::~Response()
{
}
void LLHTTPPipe::Response::nullPipe()
{
mPipe = NULL;
}
// virtual
void LLHTTPPipe::Response::result(const LLSD& r)
{
if(! mPipe)
{
llwarns << "LLHTTPPipe::Response::result: NULL pipe" << llendl;
return;
}
mPipe->mStatusCode = 200;
mPipe->mStatusMessage = "OK";
mPipe->mGoodResult = r;
mPipe->mState = STATE_GOOD_RESULT;
mPipe->unlockChain();
}
// virtual
void LLHTTPPipe::Response::status(S32 code, const std::string& message)
{
if(! mPipe)
{
llwarns << "LLHTTPPipe::Response::status: NULL pipe" << llendl;
return;
}
mPipe->mStatusCode = code;
mPipe->mStatusMessage = message;
mPipe->mState = STATE_STATUS_RESULT;
mPipe->unlockChain();
}
void LLHTTPPipe::lockChain(LLPumpIO* pump)
{
if (mChainLock != 0) { return; }
mLockedPump = pump;
mChainLock = pump->setLock();
}
void LLHTTPPipe::unlockChain()
{
if (mChainLock == 0) { return; }
mLockedPump->clearLock(mChainLock);
mLockedPump = NULL;
mChainLock = 0;
}
/**
* @class LLHTTPResponseHeader
* @brief Class which correctly builds HTTP headers on a pipe
* @see LLIOPipe
*
* An instance of this class can be placed in a chain where it will
* wait for an end of stream. Once it gets that, it will count the
* bytes on CHANNEL_OUT (or the size of the buffer in io pipe versions
* prior to 2) prepend that data to the request in an HTTP format, and
* supply all normal HTTP response headers.
*/
class LLHTTPResponseHeader : public LLIOPipe
{
public:
LLHTTPResponseHeader() {}
virtual ~LLHTTPResponseHeader() {}
protected:
/* @name LLIOPipe virtual implementations
*/
//@{
/**
* @brief Process the data in buffer
*/
EStatus process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump);
//@}
protected:
S32 mCode;
};
/**
* LLHTTPResponseHeader
*/
// virtual
LLIOPipe::EStatus LLHTTPResponseHeader::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
PUMP_DEBUG;
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
if(eos)
{
PUMP_DEBUG;
//mGotEOS = true;
std::ostringstream ostr;
std::string message = context["response"]["statusMessage"];
int code = context["response"]["statusCode"];
if (code < 200)
{
code = 200;
message = "OK";
}
ostr << HTTP_VERSION_STR << " " << code << " " << message << "\r\n";
std::string type = context["response"]["contentType"].asString();
if (!type.empty())
{
ostr << "Content-Type: " << type << "\r\n";
}
S32 content_length = buffer->countAfter(channels.in(), NULL);
if(0 < content_length)
{
ostr << "Content-Length: " << content_length << "\r\n";
}
ostr << "\r\n";
LLChangeChannel change(channels.in(), channels.out());
std::for_each(buffer->beginSegment(), buffer->endSegment(), change);
std::string header = ostr.str();
buffer->prepend(channels.out(), (U8*)header.c_str(), header.size());
PUMP_DEBUG;
return STATUS_DONE;
}
PUMP_DEBUG;
return STATUS_OK;
}
/**
* @class LLHTTPResponder
* @brief This class
* @see LLIOPipe
*
* <b>NOTE:</b> You should not need to create or use one of these, the
* details are handled by the HTTPResponseFactory.
*/
class LLHTTPResponder : public LLIOPipe
{
public:
LLHTTPResponder(const LLHTTPNode& tree);
~LLHTTPResponder();
protected:
/**
* @brief Read data off of CHANNEL_IN keeping track of last read position.
*
* This is a quick little hack to read headers. It is not IO
* optimal, but it makes it easier for me to implement the header
* parsing. Plus, there should never be more than a few headers.
* This method will tend to read more than necessary, find the
* newline, make the front part of dest look like a c string, and
* move the read head back to where the newline was found. Thus,
* the next read will pick up on the next line.
* @param channel The channel to read in the buffer
* @param buffer The heap array of processed data
* @param dest Destination for the data to be read
* @param[in,out] len <b>in</b> The size of the buffer. <b>out</b> how
* much was read. This value is not useful for determining where to
* seek orfor string assignment.
* @returns Returns true if a line was found.
*/
bool readLine(
const LLChannelDescriptors& channels,
buffer_ptr_t buffer,
U8* dest,
S32& len);
/**
* @brief Mark the request as bad, and handle appropriately
*
* @param channels The channels to use in the buffer.
* @param buffer The heap array of processed data.
*/
void markBad(const LLChannelDescriptors& channels, buffer_ptr_t buffer);
protected:
/* @name LLIOPipe virtual implementations
*/
//@{
/**
* @brief Process the data in buffer
*/
EStatus process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump);
//@}
protected:
enum EState
{
STATE_NOTHING,
STATE_READING_HEADERS,
STATE_LOOKING_FOR_EOS,
STATE_DONE,
STATE_SHORT_CIRCUIT
};
EState mState;
U8* mLastRead;
std::string mVerb;
std::string mAbsPathAndQuery;
std::string mPath;
std::string mQuery;
std::string mVersion;
S32 mContentLength;
// handle the urls
const LLHTTPNode& mRootNode;
};
LLHTTPResponder::LLHTTPResponder(const LLHTTPNode& tree) :
mState(STATE_NOTHING),
mLastRead(NULL),
mContentLength(0),
mRootNode(tree)
{
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
}
// virtual
LLHTTPResponder::~LLHTTPResponder()
{
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
//lldebugs << "destroying LLHTTPResponder" << llendl;
}
bool LLHTTPResponder::readLine(
const LLChannelDescriptors& channels,
buffer_ptr_t buffer,
U8* dest,
S32& len)
{
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
--len;
U8* last = buffer->readAfter(channels.in(), mLastRead, dest, len);
dest[len] = '\0';
U8* newline = (U8*)strchr((char*)dest, '\n');
if(!newline)
{
if(len)
{
lldebugs << "readLine failed - too long maybe?" << llendl;
markBad(channels, buffer);
}
return false;
}
S32 offset = -((len - 1) - (newline - dest));
++newline;
*newline = '\0';
mLastRead = buffer->seek(channels.in(), last, offset);
return true;
}
void LLHTTPResponder::markBad(
const LLChannelDescriptors& channels,
buffer_ptr_t buffer)
{
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
mState = STATE_SHORT_CIRCUIT;
LLBufferStream out(channels, buffer.get());
out << HTTP_VERSION_STR << " 400 Bad Request\r\n\r\n<html>\n"
<< "<title>Bad Request</title>\n<body>\nBad Request.\n"
<< "</body>\n</html>\n";
}
// virtual
LLIOPipe::EStatus LLHTTPResponder::process_impl(
const LLChannelDescriptors& channels,
buffer_ptr_t& buffer,
bool& eos,
LLSD& context,
LLPumpIO* pump)
{
PUMP_DEBUG;
LLMemType m1(LLMemType::MTYPE_IO_HTTP_SERVER);
LLIOPipe::EStatus status = STATUS_OK;
// parsing headers
if((STATE_NOTHING == mState) || (STATE_READING_HEADERS == mState))
{
PUMP_DEBUG;
status = STATUS_BREAK;
mState = STATE_READING_HEADERS;
const S32 HEADER_BUFFER_SIZE = 1024;
char buf[HEADER_BUFFER_SIZE + 1]; /*Flawfinder: ignore*/
S32 len = HEADER_BUFFER_SIZE;
#if 0
if(true)
{
LLBufferArray::segment_iterator_t seg_iter = buffer->beginSegment();
char buf[1024]; /*Flawfinder: ignore*/
while(seg_iter != buffer->endSegment())
{
memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/
buf[(*seg_iter).size()] = '\0';
llinfos << (*seg_iter).getChannel() << ": " << buf
<< llendl;
++seg_iter;
}
}
#endif
PUMP_DEBUG;
if(readLine(channels, buffer, (U8*)buf, len))
{
bool read_next_line = false;
bool parse_all = true;
if(mVerb.empty())
{
read_next_line = true;
LLMemoryStream header((U8*)buf, len);
header >> mVerb;
if((HTTP_VERB_GET == mVerb)
|| (HTTP_VERB_POST == mVerb)
|| (HTTP_VERB_PUT == mVerb)
|| (HTTP_VERB_DELETE == mVerb))
{
header >> mAbsPathAndQuery;
header >> mVersion;
lldebugs << "http request: "
<< mVerb
<< " " << mAbsPathAndQuery
<< " " << mVersion << llendl;
std::string::size_type delimiter
= mAbsPathAndQuery.find('?');
if (delimiter == std::string::npos)
{
mPath = mAbsPathAndQuery;
mQuery = "";
}
else
{
mPath = mAbsPathAndQuery.substr(0, delimiter);
mQuery = mAbsPathAndQuery.substr(delimiter+1);
}
if(!mAbsPathAndQuery.empty())
{
if(mVersion.empty())
{
// simple request.
parse_all = false;
mState = STATE_DONE;
mVersion.assign("HTTP/1.0");
}
}
}
else
{
read_next_line = false;
parse_all = false;
lldebugs << "unknown http verb: " << mVerb << llendl;
markBad(channels, buffer);
}
}
if(parse_all)
{
bool keep_parsing = true;
while(keep_parsing)
{
if(read_next_line)
{
len = HEADER_BUFFER_SIZE;
readLine(channels, buffer, (U8*)buf, len);
}
if(0 == len)
{
return status;
}
if(buf[0] == '\r' && buf[1] == '\n')
{
// end-o-headers
keep_parsing = false;
mState = STATE_LOOKING_FOR_EOS;
break;
}
char* pos_colon = strchr(buf, ':');
if(NULL == pos_colon)
{
keep_parsing = false;
lldebugs << "bad header: " << buf << llendl;
markBad(channels, buffer);
break;
}
// we've found a header
read_next_line = true;
std::string name(buf, pos_colon - buf);
std::string value(pos_colon + 2);
LLString::toLower(name);
if("content-length" == name)
{
lldebugs << "Content-Length: " << value << llendl;
mContentLength = atoi(value.c_str());
}
}
}
}
}
PUMP_DEBUG;
// look for the end of stream based on
if(STATE_LOOKING_FOR_EOS == mState)
{
if(0 == mContentLength)
{
mState = STATE_DONE;
}
else if(buffer->countAfter(channels.in(), mLastRead) >= mContentLength)
{
mState = STATE_DONE;
}
// else more bytes should be coming.
}
PUMP_DEBUG;
if(STATE_DONE == mState)
{
// hey, hey, we should have everything now, so we pass it to
// a content handler.
context[CONTEXT_REQUEST]["verb"] = mVerb;
const LLHTTPNode* node = mRootNode.traverse(mPath, context);
if(node)
{
llinfos << "LLHTTPResponder::process_impl found node for "
<< mAbsPathAndQuery << llendl;
// Copy everything after mLast read to the out.
LLBufferArray::segment_iterator_t seg_iter;
seg_iter = buffer->splitAfter(mLastRead);
if(seg_iter != buffer->endSegment())
{
LLChangeChannel change(channels.in(), channels.out());
++seg_iter;
std::for_each(seg_iter, buffer->endSegment(), change);
#if 0
seg_iter = buffer->beginSegment();
char buf[1024]; /*Flawfinder: ignore*/
while(seg_iter != buffer->endSegment())
{
memcpy(buf, (*seg_iter).data(), (*seg_iter).size()); /*Flawfinder: ignore*/
buf[(*seg_iter).size()] = '\0';
llinfos << (*seg_iter).getChannel() << ": " << buf
<< llendl;
++seg_iter;
}
#endif
}
//
// *FIX: get rid of extra bytes off the end
//
// Set up a chain which will prepend a content length and
// HTTP headers.
LLPumpIO::chain_t chain;
chain.push_back(LLIOPipe::ptr_t(new LLIOFlush));
context[CONTEXT_REQUEST]["path"] = mPath;
context[CONTEXT_REQUEST]["query-string"] = mQuery;
const LLChainIOFactory* protocolHandler
= node->getProtocolHandler();
if (protocolHandler)
{
protocolHandler->build(chain, context);
}
else
{
// this is a simple LLHTTPNode, so use LLHTTPPipe
chain.push_back(LLIOPipe::ptr_t(new LLHTTPPipe(*node)));
}
// Add the header - which needs to have the same
// channel information as the link before it since it
// is part of the response.
LLIOPipe* header = new LLHTTPResponseHeader;
chain.push_back(LLIOPipe::ptr_t(header));
// We need to copy all of the pipes _after_ this so
// that the response goes out correctly.
LLPumpIO::links_t current_links;
pump->copyCurrentLinkInfo(current_links);
LLPumpIO::links_t::iterator link_iter = current_links.begin();
LLPumpIO::links_t::iterator links_end = current_links.end();
bool after_this = false;
for(; link_iter < links_end; ++link_iter)
{
if(after_this)
{
chain.push_back((*link_iter).mPipe);
}
else if(this == (*link_iter).mPipe.get())
{
after_this = true;
}
}
// Do the final build of the chain, and send it on
// it's way.
LLChannelDescriptors chnl = channels;
LLPumpIO::LLLinkInfo link;
LLPumpIO::links_t links;
LLPumpIO::chain_t::iterator it = chain.begin();
LLPumpIO::chain_t::iterator end = chain.end();
while(it != end)
{
link.mPipe = *it;
link.mChannels = chnl;
links.push_back(link);
chnl = LLBufferArray::makeChannelConsumer(chnl);
++it;
}
pump->addChain(
links,
buffer,
context,
DEFAULT_CHAIN_EXPIRY_SECS);
status = STATUS_STOP;
}
else
{
llinfos << "LLHTTPResponder::process_impl didn't find a node for "
<< mAbsPathAndQuery << llendl;
LLBufferStream str(channels, buffer.get());
mState = STATE_SHORT_CIRCUIT;
str << HTTP_VERSION_STR << " 404 Not Found\r\n\r\n<html>\n"
<< "<title>Not Found</title>\n<body>\nNode '" << mAbsPathAndQuery
<< "' not found.\n</body>\n</html>\n";
}
}
if(STATE_SHORT_CIRCUIT == mState)
{
//status = mNext->process(buffer, true, pump, context);
status = STATUS_DONE;
}
PUMP_DEBUG;
return status;
}
void LLCreateHTTPPipe(LLPumpIO::chain_t& chain, const LLHTTPNode& root)
{
chain.push_back(LLIOPipe::ptr_t(new LLHTTPResponder(root)));
}
class LLHTTPResponseFactory : public LLChainIOFactory
{
public:
bool build(LLPumpIO::chain_t& chain, LLSD ctx) const
{
LLCreateHTTPPipe(chain, mTree);
return true;
}
LLHTTPNode& getRootNode() { return mTree; }
private:
LLHTTPNode mTree;
};
LLHTTPNode& LLCreateHTTPServer(
apr_pool_t* pool, LLPumpIO& pump, U16 port)
{
LLSocket::ptr_t socket = LLSocket::create(
pool,
LLSocket::STREAM_TCP,
port);
if(!socket)
{
llerrs << "Unable to initialize socket" << llendl;
}
LLHTTPResponseFactory* factory = new LLHTTPResponseFactory;
boost::shared_ptr<LLChainIOFactory> factory_ptr(factory);
LLIOServerSocket* server = new LLIOServerSocket(pool, socket, factory_ptr);
LLPumpIO::chain_t chain;
chain.push_back(LLIOPipe::ptr_t(server));
pump.addChain(chain, NEVER_CHAIN_EXPIRY_SECS);
return factory->getRootNode();
}