summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2020-05-02 18:12:37 +0200
committerRoland Reichwein <mail@reichwein.it>2020-05-02 18:12:37 +0200
commit6ab2d11a72041e22eee42b0f582377b148e4c348 (patch)
tree4fcffab342dfc5a60777ad45230fc22bb1c4ba4f
parent5dd4766490e4b29634ea0b3ff35e5d124f657f9c (diff)
Fixed FCGI
-rw-r--r--plugins/fcgi/fcgi.cpp211
1 files changed, 143 insertions, 68 deletions
diff --git a/plugins/fcgi/fcgi.cpp b/plugins/fcgi/fcgi.cpp
index eb9abe2..23fa91e 100644
--- a/plugins/fcgi/fcgi.cpp
+++ b/plugins/fcgi/fcgi.cpp
@@ -43,41 +43,6 @@ namespace {
const std::string gateway_interface{"CGI/1.1"};
- // Return a reasonable mime type based on the extension of a file.
- std::string mime_type(fs::path path)
- {
- using boost::algorithm::iequals;
- auto const ext = [&path]
- {
- size_t pos = path.string().rfind(".");
- if (pos == std::string::npos)
- return std::string{};
- return path.string().substr(pos);
- }();
- if(iequals(ext, ".htm")) return "text/html"; // TODO: unordered_map
- if(iequals(ext, ".html")) return "text/html";
- if(iequals(ext, ".php")) return "text/html";
- if(iequals(ext, ".css")) return "text/css";
- if(iequals(ext, ".txt")) return "text/plain";
- if(iequals(ext, ".js")) return "application/javascript";
- if(iequals(ext, ".json")) return "application/json";
- if(iequals(ext, ".xml")) return "application/xml";
- if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
- if(iequals(ext, ".flv")) return "video/x-flv";
- if(iequals(ext, ".png")) return "image/png";
- if(iequals(ext, ".jpe")) return "image/jpeg";
- if(iequals(ext, ".jpeg")) return "image/jpeg";
- if(iequals(ext, ".jpg")) return "image/jpeg";
- if(iequals(ext, ".gif")) return "image/gif";
- if(iequals(ext, ".bmp")) return "image/bmp";
- if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
- if(iequals(ext, ".tiff")) return "image/tiff";
- if(iequals(ext, ".tif")) return "image/tiff";
- if(iequals(ext, ".svg")) return "image/svg+xml";
- if(iequals(ext, ".svgz")) return "image/svg+xml";
- return "application/text";
- }
-
typedef boost::coroutines2::coroutine<std::string> coro_t;
// returns true iff std::string is empty or contains newline
@@ -180,14 +145,14 @@ namespace {
{
if (type == FCGI_BEGIN_REQUEST) {
size_t size {sizeof(FCGI_BeginRequestRecord)};
- m_data.resize(size);
+ m_data.resize(size, 0);
FCGI_BeginRequestRecord& r{*reinterpret_cast<FCGI_BeginRequestRecord*>(m_data.data())};
r.header.version = FCGI_VERSION_1;
r.header.type = type;
r.header.requestIdB1 = id >> 8;
r.header.requestIdB0 = id & 0xFF;
- r.header.contentLengthB1 = 0;
- r.header.contentLengthB0 = sizeof(r.body);
+ r.header.contentLengthB1 = sizeof(r.body) >> 8;
+ r.header.contentLengthB0 = sizeof(r.body) & 0xFF;
r.body.roleB1 = 0;
r.body.roleB0 = arg1;
r.body.flags = arg2;
@@ -198,16 +163,16 @@ namespace {
// create record to send
FCGI_Record(unsigned char type, uint16_t id, const std::string& data)
{
- if (type == FCGI_PARAMS || type == FCGI_STDIN) {
+ if (type == FCGI_PARAMS || type == FCGI_STDIN || type == FCGI_GET_VALUES || type == FCGI_DATA) {
size_t size {sizeof(FCGI_Header) + data.size()};
m_data.resize(size);
- FCGI_Header& r{*reinterpret_cast<FCGI_Header*>(m_data.data())};
- r.version = FCGI_VERSION_1;
- r.type = type;
- r.requestIdB1 = id >> 8;
- r.requestIdB0 = id & 0xFF;
- r.contentLengthB1 = 0;
- r.contentLengthB0 = data.size();
+ FCGI_Header& h{*reinterpret_cast<FCGI_Header*>(m_data.data())};
+ h.version = FCGI_VERSION_1;
+ h.type = type;
+ h.requestIdB1 = id >> 8;
+ h.requestIdB0 = id & 0xFF;
+ h.contentLengthB1 = data.size() >> 8;
+ h.contentLengthB0 = data.size() & 0xFF;
memcpy((void*)&m_data[sizeof(FCGI_Header)], (void*)data.data(), data.size());
} else
throw std::runtime_error("Bad FCGI type: "s + std::to_string(type));
@@ -222,11 +187,11 @@ namespace {
FCGI_Header& r{*reinterpret_cast<FCGI_Header*>(v.data())};
size_t content_length {((size_t)r.contentLengthB1) << 8 | r.contentLengthB0};
- size_t record_size {sizeof(FCGI_Header) + content_length};
+ size_t record_size {sizeof(FCGI_Header) + content_length + r.paddingLength};
if (v.size() < record_size)
throw std::length_error("No full FCGI record available");
- m_data = std::vector(v.begin(), v.begin() + record_size);
+ m_data = std::vector(v.begin(), v.begin() + record_size - r.paddingLength);
v.erase(v.begin(), v.begin() + record_size);
}
@@ -255,6 +220,7 @@ namespace {
std::string encode_u32(size_t v)
{
uint32_t x {static_cast<uint32_t>(v)};
+ x |= 0x80000000;
boost::endian::native_to_big_inplace(x);
return std::string(reinterpret_cast<char*>(&x), sizeof(x));
}
@@ -279,6 +245,67 @@ namespace {
}
}
+ bool decode_size(const std::string& s, size_t& pos, size_t& size)
+ {
+ uint8_t byte {static_cast<uint8_t>(s[pos])};
+ if (byte <= 127) {
+ size = byte;
+ pos += 1;
+
+ } else {
+ if (s.size() < pos + 4) {
+ std::cerr << "FCGI Error: Broken FCGI_GET_VALUES data (size)" << std::endl;
+ return false;
+ }
+
+ uint32_t v {*reinterpret_cast<const uint32_t*>(&s[pos])};
+ boost::endian::big_to_native_inplace(v);
+ v &= ~0x80000000;
+ size = static_cast<size_t>(v);
+
+ pos += 4;
+ }
+
+ return true;
+ }
+
+ void FCGI_DecodeEnv(const std::string& s, std::unordered_map<std::string, std::string>& map)
+ {
+ map.clear();
+
+ size_t pos{0};
+
+ while (pos < s.size()) {
+ size_t keysize{};
+ size_t valuesize{};
+
+ if (!decode_size(s, pos, keysize))
+ return;
+ if (!decode_size(s, pos, valuesize))
+ return;
+
+ if (s.size() < pos + keysize) {
+ std::cerr << "FCGI Error: Broken FCGI_GET_VALUES data (key)" << std::endl;
+ return;
+ }
+ std::string key{s.substr(pos, keysize)};
+ pos += keysize;
+
+ if (s.size() < pos + valuesize) {
+ std::cerr << "FCGI Error: Broken FCGI_GET_VALUES data (value) for key " << key << std::endl;
+ return;
+ }
+ std::string value{s.substr(pos, valuesize)};
+ pos += valuesize;
+
+ if (map.find(key) != map.end()) {
+ std::cerr << "FCGI Warning: Double key in FCGI_GET_VALUES: " << key << std::endl;
+ } else {
+ map[key] = value;
+ }
+ }
+ }
+
// Used to return errors by generating response page and HTTP status code
std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)
{
@@ -287,6 +314,16 @@ namespace {
return status + " " + message;
}
+ void DumpAppValues(const std::unordered_map<std::string, std::string>& app_values)
+ {
+ std::cout << "App properties:" << std::endl;
+ if (app_values.size() == 0)
+ std::cout << " (empty)" << std::endl;
+ else for (auto&[key, value]: app_values) {
+ std::cout << " " << key << "=" << value << std::endl;
+ }
+ }
+
} // anonymous namespace
std::string fcgi_plugin::fcgiQuery(FCGIContext& context)
@@ -296,14 +333,21 @@ std::string fcgi_plugin::fcgiQuery(FCGIContext& context)
std::string output_data;
+ std::unordered_map<std::string, std::string> system_config{{"FCGI_MAX_CONNS", ""}, {"FCGI_MAX_REQS", ""}, {"FCGI_MPXS_CONNS", ""}};
+ std::string system_config_bytes;
+ FCGI_EncodeEnv(system_config, system_config_bytes);
+
std::unordered_map<std::string, std::string> env;
setFCGIEnvironment(env, context);
std::string env_bytes;
FCGI_EncodeEnv(env, env_bytes);
+ std::unordered_map<std::string, std::string> app_values; // will be read by FCGI_GET_VALUES
+
size_t pos { app_addr.find(':') };
if (pos != app_addr.npos) { // host:port
boost::asio::io_context io_context; // TODO: member?
+
tcp::resolver resolver(io_context);
auto endpoints{resolver.resolve(app_addr.substr(0, pos), app_addr.substr(pos + 1))};
tcp::socket socket(io_context);
@@ -313,31 +357,60 @@ std::string fcgi_plugin::fcgiQuery(FCGIContext& context)
return HttpStatus("500", "FCGI connection", context.SetResponseHeader);
}
+ FCGI_Record get_values{FCGI_GET_VALUES, 0, system_config_bytes};
+ if (socket.write_some(boost::asio::buffer(get_values.getBuffer())) != get_values.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 1" << std::endl;
+
FCGI_ID_Guard id_guard(m_fcgi_id);
- uint16_t id{id_guard.getID()};
+ uint16_t id {id_guard.getID()};
- FCGI_Record begin_request{FCGI_BEGIN_REQUEST, id, FCGI_RESPONDER, FCGI_KEEP_CONN};
- socket.write_some(boost::asio::buffer(begin_request.getBuffer()));
+ FCGI_Record begin_request{FCGI_BEGIN_REQUEST, id, FCGI_RESPONDER, 0/*FCGI_KEEP_CONN*/};
+ if (socket.write_some(boost::asio::buffer(begin_request.getBuffer())) != begin_request.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 3" << std::endl;
FCGI_Record params{FCGI_PARAMS, id, env_bytes};
- socket.write_some(boost::asio::buffer(params.getBuffer()));
+ if (socket.write_some(boost::asio::buffer(params.getBuffer())) != params.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 4" << std::endl;
+
+ if (env_bytes.size()) {
+ FCGI_Record params_end{FCGI_PARAMS, id, std::string{}};
+ if (socket.write_some(boost::asio::buffer(params_end.getBuffer())) != params_end.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 5" << std::endl;
+ }
+
+ std::string body {context.GetRequestParam("body")};
+ FCGI_Record stdin_{FCGI_STDIN, id, body};
+ if (socket.write_some(boost::asio::buffer(stdin_.getBuffer())) != stdin_.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 6" << std::endl;
- FCGI_Record params_end{FCGI_PARAMS, id, std::string{}};
- socket.write_some(boost::asio::buffer(params_end.getBuffer()));
+ if (body.size()) {
+ FCGI_Record stdin_end{FCGI_STDIN, id, std::string{}};
+ if (socket.write_some(boost::asio::buffer(stdin_end.getBuffer())) != stdin_end.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 7" << std::endl;
+ }
- FCGI_Record stdin_{FCGI_PARAMS, id, context.GetRequestParam("body")};
- socket.write_some(boost::asio::buffer(stdin_.getBuffer()));
-
- FCGI_Record stdin_end{FCGI_PARAMS, id, std::string{}};
- socket.write_some(boost::asio::buffer(stdin_end.getBuffer()));
+#if 0
+ FCGI_Record data{FCGI_DATA, id, std::string{}};
+ if (socket.write_some(boost::asio::buffer(data.getBuffer())) != data.getBuffer().size())
+ std::cerr << "Warning: Not all bytes written 8" << std::endl;
+#endif
bool ended{false};
std::vector<char> inbuf;
+ std::vector<char> inbuf_part(1024);
while (!ended) {
- std::vector<char> inbuf_part(1024);
- size_t got {socket.read_some(boost::asio::buffer(inbuf_part))};
- inbuf.insert(inbuf.end(), inbuf_part.begin(), inbuf_part.begin() + got);
-
+ try {
+ size_t got {socket.read_some(boost::asio::buffer(inbuf_part))};
+ inbuf.insert(inbuf.end(), inbuf_part.begin(), inbuf_part.begin() + got);
+ } catch (const boost::system::system_error& ex) {
+ if (ex.code() == boost::asio::error::eof) {
+ //std::cerr << "FCGI Warning: Early EOF" << std::endl; // seems to be ok here
+ } else {
+ std::cerr << "FCGI Warning: Expected EOF, got " << ex.code() << ", " << ex.what() << std::endl;
+ ended = true;
+ }
+ }
+
try {
FCGI_Record r{inbuf};
if (r.getType() == FCGI_END_REQUEST) {
@@ -345,7 +418,10 @@ std::string fcgi_plugin::fcgiQuery(FCGIContext& context)
} else if (r.getType() == FCGI_STDOUT) {
output_data += r.getContent();
} else if (r.getType() == FCGI_STDERR) {
- std::cerr << "FCGI Error: " << r.getContent();
+ std::cerr << "FCGI STDERR: " << r.getContent();
+ } else if (r.getType() == FCGI_GET_VALUES_RESULT) {
+ FCGI_DecodeEnv(r.getContent(), app_values);
+ DumpAppValues(app_values);
} else
throw std::runtime_error("Unhandled FCGI type: "s + std::to_string(r.getType()));
} catch (const std::length_error& ex) {
@@ -386,9 +462,10 @@ std::string fcgi_plugin::fcgiQuery(FCGIContext& context)
throw std::runtime_error("Input missing on processing CGI body");
});
- while (std::getline(is_out, line)) {
+ do {
+ std::getline(is_out, line);
processLine(line);
- }
+ } while (!is_out.eof());
return output;
}
@@ -432,8 +509,6 @@ std::string fcgi_plugin::generate_page(
try {
return fcgiQuery(context);
- } catch (const std::runtime_error& ex) {
- return HttpStatus("404", "Not found: "s + GetRequestParam("target"), SetResponseHeader);
} catch (const std::exception& ex) {
return HttpStatus("500", "Internal Server Error: "s + ex.what(), SetResponseHeader);
}