mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
464 lines
12 KiB
C++
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);
|
|
}
|