/* * Copyright (c) 2001-2007 Peter Simons * * This software is provided 'as-is', without any express or * implied warranty. In no event will the authors be held liable * for any damages arising from the use of this software. * * Copying and distribution of this file, with or without * modification, are permitted in any medium without royalty * provided the copyright notice and this notice are preserved. */ #include "fastcgi.hpp" #include #include #ifndef _WIN32 #include #endif typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; enum message_type_t { TYPE_BEGIN_REQUEST = 1 , TYPE_ABORT_REQUEST = 2 , TYPE_END_REQUEST = 3 , TYPE_PARAMS = 4 , TYPE_STDIN = 5 , TYPE_STDOUT = 6 , TYPE_STDERR = 7 , TYPE_DATA = 8 , TYPE_GET_VALUES = 9 , TYPE_GET_VALUES_RESULT = 10 , TYPE_UNKNOWN = 11 }; struct Header { uint8_t version; uint8_t type; uint8_t requestIdB1; uint8_t requestIdB0; uint8_t contentLengthB1; uint8_t contentLengthB0; uint8_t paddingLength; uint8_t reserved; Header() { memset(this, 0, sizeof(*this)); } Header(message_type_t t, uint16_t id, uint16_t len) : version(1), type(t) , requestIdB1(id >> 8) , requestIdB0(id & 0xff) , contentLengthB1(len >> 8) , contentLengthB0(len & 0xff) , paddingLength(0), reserved(0) { } }; struct BeginRequest { uint8_t roleB1; uint8_t roleB0; uint8_t flags; uint8_t reserved[5]; }; static uint8_t const FLAG_KEEP_CONN = 1; struct EndRequestMsg : public Header { uint8_t appStatusB3; uint8_t appStatusB2; uint8_t appStatusB1; uint8_t appStatusB0; uint8_t protocolStatus; uint8_t reserved[3]; EndRequestMsg() { memset(this, 0, sizeof(*this)); } EndRequestMsg(uint16_t id, uint32_t appStatus, FCGIRequest::protocol_status_t protStatus) : Header(TYPE_END_REQUEST, id, sizeof(EndRequestMsg)-sizeof(Header)) , appStatusB3((appStatus >> 24) & 0xff) , appStatusB2((appStatus >> 16) & 0xff) , appStatusB1((appStatus >> 8) & 0xff) , appStatusB0((appStatus >> 0) & 0xff) , protocolStatus(protStatus) { memset(this->reserved, 0, sizeof(this->reserved)); } }; struct UnknownTypeMsg : public Header { uint8_t type; uint8_t reserved[7]; UnknownTypeMsg() { memset(this, 0, sizeof(*this)); } UnknownTypeMsg(uint8_t unknown_type) : Header(TYPE_UNKNOWN, 0, sizeof(UnknownTypeMsg) - sizeof(Header)) , type(unknown_type) { memset(this->reserved, 0, sizeof(this->reserved)); } }; void FCGIProtocolDriver::process_unknown(uint8_t type) { UnknownTypeMsg msg(type); output_cb(&msg, sizeof(UnknownTypeMsg)); } void FCGIProtocolDriver::process_begin_request(uint16_t id, uint8_t const * buf, uint16_t) { // Check whether we have an open request with that id already and // if, throw an exception. if (reqmap.find(id) != reqmap.end()) { char tmp[256]; #ifdef _WIN32 sprintf_s(tmp, "FCGIProtocolDriver received duplicate BEGIN_REQUEST id %u.", id); #else sprintf(tmp, "FCGIProtocolDriver received duplicate BEGIN_REQUEST id %u.", id); #endif throw duplicate_begin_request(tmp); } // Create a new request instance and store it away. The user may // get it after we've read all parameters. BeginRequest const * br = reinterpret_cast(buf); reqmap[id] = new FCGIRequest(*this, id, FCGIRequest::role_t((br->roleB1 << 8) + br->roleB0), (br->flags & FLAG_KEEP_CONN) == 1); } void FCGIProtocolDriver::process_abort_request(uint16_t id, uint8_t const *, uint16_t) { // Find request instance for this id. Ignore message if non // exists, set ignore flag otherwise. reqmap_t::iterator req = reqmap.find(id); if (req == reqmap.end()) std::cerr << "FCGIProtocolDriver received ABORT_REQUEST for non-existing id " << id << ". Ignoring." << std::endl; else { req->second->aborted = true; if (req->second->handler_cb) // Notify the handler associated with this request. (*req->second->handler_cb)(req->second); } } void FCGIProtocolDriver::process_params(uint16_t id, uint8_t const * buf, uint16_t len) { // Find request instance for this id. Ignore message if non // exists. reqmap_t::iterator req = reqmap.find(id); if (req == reqmap.end()) { std::cerr << "FCGIProtocolDriver received PARAMS for non-existing id " << id << ". Ignoring." << std::endl; return; } // Is this the last message to come? Then queue the request for // the user. if (len == 0) { new_request_queue.push(id); return; } // Process message. uint8_t const * const bufend(buf + len); uint32_t name_len; uint32_t data_len; while(buf != bufend) { if (*buf >> 7 == 0) name_len = *(buf++); else if (4 <= bufend - buf) { name_len = ((buf[0] & 0x7F) << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; buf += 4; } else break; if (*buf >> 7 == 0) data_len = *(buf++); else if (4 <= bufend - buf) { data_len = ((buf[0] & 0x7F) << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; buf += 4; } else break; if (name_len + data_len <= bufend - buf) { std::string const name(reinterpret_cast(buf), name_len); buf += name_len; std::string const data(reinterpret_cast(buf), data_len); buf += data_len; #ifdef DEBUG_FASTCGI std::cerr << "request #" << id << ": FCGIProtocolDriver received PARAM '" << name << "' = '" << data << "'" << std::endl; #endif req->second->params[name] = data; } else { break; } } } void FCGIProtocolDriver::process_stdin(uint16_t id, uint8_t const * buf, uint16_t len) { // Find request instance for this id. Ignore message if non // exists. reqmap_t::iterator req = reqmap.find(id); if (req == reqmap.end()) { std::cerr << "FCGIProtocolDriver received STDIN for non-existing id " << id << ". Ignoring." << std::endl; return; } // Is this the last message to come? Then set the eof flag. // Otherwise, add the data to the buffer in the request structure. if (len == 0) req->second->stdin_eof = true; else req->second->stdin_stream.append((char const *)buf, len); // Notify the handler associated with this request. if (req->second->handler_cb) (*req->second->handler_cb)(req->second); } FCGIProtocolDriver::FCGIProtocolDriver(OutputCallback& cb) : output_cb(cb) { } FCGIProtocolDriver::~FCGIProtocolDriver() { for(reqmap_t::iterator i = reqmap.begin(); i != reqmap.end(); ++i) { delete i->second; } } void FCGIProtocolDriver::process_input(void const * buf, size_t count) { // Copy data to our own buffer. InputBuffer.insert( InputBuffer.end() , static_cast(buf) , static_cast(buf) + count ); // If there is enough data in the input buffer to contain a // header, interpret it. while(InputBuffer.size() >= sizeof(Header)) { Header const * hp = reinterpret_cast
(&InputBuffer[0]); // Check whether our peer speaks the correct protocol version. if (hp->version != 1) { char buf[256]; #ifdef _WIN32 sprintf_s(buf, "FCGIProtocolDriver cannot handle protocol version %u.", hp->version); #else sprintf(buf, "FCGIProtocolDriver cannot handle protocol version %u.", hp->version); #endif throw unsupported_fcgi_version(buf); } // Check whether we have the whole message that follows the // headers in our buffer already. If not, we can't process it // yet. uint16_t msg_len = (hp->contentLengthB1 << 8) + hp->contentLengthB0; uint16_t msg_id = (hp->requestIdB1 << 8) + hp->requestIdB0; if (InputBuffer.size() < sizeof(Header)+msg_len+hp->paddingLength) return; // Process the message. In case an exception arrives here, // terminate the request. try { #ifdef DEBUG_FASTCGI std::cerr << "Received message: id = " << msg_id << ", " << "body len = " << msg_len << ", " << "type = " << (int)hp->type << std::endl; #endif switch (hp->type) { case TYPE_BEGIN_REQUEST: process_begin_request(msg_id, &InputBuffer[0]+sizeof(Header), msg_len); break; case TYPE_ABORT_REQUEST: process_abort_request(msg_id, &InputBuffer[0]+sizeof(Header), msg_len); break; case TYPE_PARAMS: process_params(msg_id, &InputBuffer[0]+sizeof(Header), msg_len); break; case TYPE_STDIN: process_stdin(msg_id, &InputBuffer[0]+sizeof(Header), msg_len); break; case TYPE_END_REQUEST: case TYPE_STDOUT: case TYPE_STDERR: case TYPE_DATA: case TYPE_GET_VALUES: case TYPE_GET_VALUES_RESULT: case TYPE_UNKNOWN: default: process_unknown(hp->type); } } catch(fcgi_io_callback_error const &) { throw; } catch(std::exception const & e) { std::cerr << "Caught exception while processing request #" << msg_id << ": " << e.what() << std::endl; terminate_request(msg_id); } catch(...) { std::cerr << "Caught unknown exception while processing request #" << msg_id << "." << std::endl; terminate_request(msg_id); } // Remove the message from our buffer and contine processing // if there if something left. InputBuffer.erase( InputBuffer.begin() , InputBuffer.begin()+sizeof(Header)+msg_len+hp->paddingLength ); } } FCGIRequest* FCGIProtocolDriver::get_request() { if (new_request_queue.empty()) return 0; FCGIRequest* r = reqmap[new_request_queue.front()]; new_request_queue.pop(); return r; } bool FCGIProtocolDriver::have_active_requests() { if (new_request_queue.empty() && reqmap.empty()) return false; else return true; } void FCGIProtocolDriver::terminate_request(uint16_t id) { reqmap_t::iterator req; req = reqmap.find(id); if (req != reqmap.end()) { delete req->second; reqmap.erase(req); } } // Pure virtual destructors must also exist somewhere. FCGIProtocolDriver::OutputCallback::~OutputCallback() { } FCGIRequest::FCGIRequest(FCGIProtocolDriver& driver_, uint16_t id_, role_t role_, bool kc) : id(id_), role(role_), keep_connection(kc), aborted(false), stdin_eof(false) , data_eof(false), handler_cb(0), driver(driver_) { } FCGIRequest::~FCGIRequest() { if (handler_cb) delete handler_cb; } void FCGIRequest::write(const std::string& buf, ostream_type_t stream) { write(buf.data(), buf.size(), stream); } void FCGIRequest::write(char const * buf, size_t count, ostream_type_t stream) { if (count > 0xffff) throw std::out_of_range("Can't send messages of that size."); else if (count == 0) return; // Construct message. Header h(stream == STDOUT ? TYPE_STDOUT : TYPE_STDERR, id, (uint16_t)count); driver.output_cb(&h, sizeof(Header)); driver.output_cb(buf, count); } void FCGIRequest::write(const std::string &tw) { write(tw.data(), tw.size(), STDOUT); } void FCGIRequest::end_request(uint32_t appStatus, FCGIRequest::protocol_status_t protStatus) { // Terminate the stdout and stderr stream, and send the // end-request message. uint8_t buf[64]; uint8_t * p = buf; new(p) Header(TYPE_STDOUT, id, 0); p += sizeof(Header); new(p) Header(TYPE_STDERR, id, 0); p += sizeof(Header); new(p) EndRequestMsg(id, appStatus, protStatus); p += sizeof(EndRequestMsg); driver.output_cb(buf, p - buf); driver.terminate_request(id); }