urbackup_backend/libfastcgi/fastcgi.cpp

464 lines
12 KiB
C++

/*
* Copyright (c) 2001-2007 Peter Simons <simons@cryp.to>
*
* 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 <iostream>
#include <stdio.h>
#ifndef _WIN32
#include <memory.h>
#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<BeginRequest const *>(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<char const *>(buf), name_len);
buf += name_len;
std::string const data(reinterpret_cast<char const *>(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<uint8_t const *>(buf)
, static_cast<uint8_t const *>(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<Header const *>(&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);
}