/** * @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 "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" #include "lltimer.h" #include #include 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"); static LLIOHTTPServer::timing_callback_t sTimingCallback = NULL; static void* sTimingCallbackData = NULL; 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 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 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()); static LLTimer timer; if (sTimingCallback) { timer.reset(); } 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(); } if (sTimingCallback) { LLHTTPNode::Description desc; mNode.describe(desc); LLSD info = desc.getInfo(); std::string timing_name = info["description"]; timing_name += " "; timing_name += verb; F32 delta = timer.getElapsedTimeF32(); sTimingCallback(timing_name.c_str(), delta, sTimingCallbackData); } // 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::create(LLHTTPPipe* pipe) { LLPointer 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 * * NOTE: 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, const LLSD& ctx); ~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 in The size of the buffer. out 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 }; LLSD mBuildContext; EState mState; U8* mLastRead; std::string mVerb; std::string mAbsPathAndQuery; std::string mPath; std::string mQuery; std::string mVersion; S32 mContentLength; LLSD mHeaders; // handle the urls const LLHTTPNode& mRootNode; }; LLHTTPResponder::LLHTTPResponder(const LLHTTPNode& tree, const LLSD& ctx) : mBuildContext(ctx), 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\n" << "Bad Request\n\nBad Request.\n" << "\n\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()); } else { LLString::trimTail(value); mHeaders[name] = value; } } } } } 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; context[CONTEXT_REQUEST]["remote-host"] = mBuildContext["remote-host"]; context[CONTEXT_REQUEST]["remote-port"] = mBuildContext["remote-port"]; context[CONTEXT_REQUEST]["headers"] = mHeaders; 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\n" << "Not Found\n\nNode '" << mAbsPathAndQuery << "' not found.\n\n\n"; } } if(STATE_SHORT_CIRCUIT == mState) { //status = mNext->process(buffer, true, pump, context); status = STATUS_DONE; } PUMP_DEBUG; return status; } // static void LLIOHTTPServer::createPipe(LLPumpIO::chain_t& chain, const LLHTTPNode& root, const LLSD& ctx) { chain.push_back(LLIOPipe::ptr_t(new LLHTTPResponder(root, ctx))); } class LLHTTPResponseFactory : public LLChainIOFactory { public: bool build(LLPumpIO::chain_t& chain, LLSD ctx) const { LLIOHTTPServer::createPipe(chain, mTree, ctx); return true; } LLHTTPNode& getRootNode() { return mTree; } private: LLHTTPNode mTree; }; // static LLHTTPNode& LLIOHTTPServer::create( 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 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(); } // static void LLIOHTTPServer::setTimingCallback(timing_callback_t callback, void* data) { sTimingCallback = callback; sTimingCallbackData = data; }