From 6ab2d11a72041e22eee42b0f582377b148e4c348 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sat, 2 May 2020 18:12:37 +0200 Subject: Fixed FCGI --- plugins/fcgi/fcgi.cpp | 211 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 68 deletions(-) (limited to 'plugins') 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 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(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(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(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(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(v)}; + x |= 0x80000000; boost::endian::native_to_big_inplace(x); return std::string(reinterpret_cast(&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(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(&s[pos])}; + boost::endian::big_to_native_inplace(v); + v &= ~0x80000000; + size = static_cast(v); + + pos += 4; + } + + return true; + } + + void FCGI_DecodeEnv(const std::string& s, std::unordered_map& 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& SetResponseHeader) { @@ -287,6 +314,16 @@ namespace { return status + " " + message; } + void DumpAppValues(const std::unordered_map& 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 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 env; setFCGIEnvironment(env, context); std::string env_bytes; FCGI_EncodeEnv(env, env_bytes); + std::unordered_map 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 inbuf; + std::vector inbuf_part(1024); while (!ended) { - std::vector 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); } -- cgit v1.2.3