diff options
| -rw-r--r-- | plugins/fcgi/fcgi.cpp | 303 | ||||
| -rw-r--r-- | plugins/fcgi/fcgi.h | 53 | ||||
| -rw-r--r-- | plugins/weblog/html/blog.css | 76 | ||||
| -rw-r--r-- | plugins/weblog/html/favicon.ico | bin | 0 -> 2238 bytes | 
4 files changed, 361 insertions, 71 deletions
diff --git a/plugins/fcgi/fcgi.cpp b/plugins/fcgi/fcgi.cpp index d301579..eb9abe2 100644 --- a/plugins/fcgi/fcgi.cpp +++ b/plugins/fcgi/fcgi.cpp @@ -1,9 +1,11 @@ -// WIP!  #include "fcgi.h"  #include "fastcgi.h"  #include <boost/algorithm/string/predicate.hpp> +#include <boost/array.hpp> +#include <boost/asio.hpp> +#include <boost/endian/conversion.hpp>  #include <boost/coroutine2/coroutine.hpp>  #include <boost/process.hpp> @@ -18,27 +20,29 @@ using namespace std::string_literals;  namespace bp = boost::process;  namespace fs = std::filesystem; +using boost::asio::ip::tcp; + +struct FCGIContext +{ +  std::function<std::string(const std::string& key)>& GetServerParam; +  std::function<std::string(const std::string& key)>& GetRequestParam; // request including body (POST...) +  std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader; // to be added to result string + +  FCGIContext(std::function<std::string(const std::string& key)>& p_GetServerParam, +             std::function<std::string(const std::string& key)>& p_GetRequestParam, +             std::function<void(const std::string& key, const std::string& value)>& p_SetResponseHeader +             ) +   : GetServerParam(p_GetServerParam) +   , GetRequestParam(p_GetRequestParam) +   , SetResponseHeader(p_SetResponseHeader) + { + } +}; +  namespace {   const std::string gateway_interface{"CGI/1.1"}; - struct FCGIContext - { -   std::function<std::string(const std::string& key)>& GetServerParam; -   std::function<std::string(const std::string& key)>& GetRequestParam; // request including body (POST...) -   std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader; // to be added to result string - -   FCGIContext(std::function<std::string(const std::string& key)>& p_GetServerParam, -              std::function<std::string(const std::string& key)>& p_GetRequestParam, -              std::function<void(const std::string& key, const std::string& value)>& p_SetResponseHeader -              ) -    : GetServerParam(p_GetServerParam) -    , GetRequestParam(p_GetRequestParam) -    , SetResponseHeader(p_SetResponseHeader) -  { -  } - }; -   // Return a reasonable mime type based on the extension of a file.   std::string mime_type(fs::path path)   { @@ -122,7 +126,7 @@ namespace {     it->second(value, context);   } - void setCGIEnvironment(bp::environment& env, FCGIContext& c) + void setFCGIEnvironment(std::unordered_map<std::string, std::string>& env, FCGIContext& c)   {    std::string authorization {c.GetRequestParam("authorization")};    if (!authorization.empty()) @@ -140,8 +144,8 @@ namespace {     target = target.substr(0, query_pos);    } -  //TODO: env["PATH_INFO"] = c.path_info.string(); -  //TODO: env["PATH_TRANSLATED"] = c.path.string(); +  env["PATH_INFO"] = c.GetRequestParam("rel_target"); +  env["PATH_TRANSLATED"] = fs::path{c.GetRequestParam("rel_target")} / c.GetRequestParam("rel_target");    env["QUERY_STRING"] = query;    env["REMOTE_ADDR"] = "";    env["REMOTE_HOST"] = ""; @@ -149,7 +153,7 @@ namespace {    env["REMOTE_USER"] = "";    env["REQUEST_METHOD"] = c.GetRequestParam("method");    env["REQUEST_URI"] = target; -  //TODO: env["SCRIPT_NAME"] = c.file_path; +  env["SCRIPT_NAME"] = c.GetRequestParam("target");    env["SERVER_NAME"] = c.GetRequestParam("host");    env["SERVER_PORT"] = c.GetServerParam("port");    env["SERVER_PROTOCOL"] = c.GetRequestParam("http_version"); @@ -167,56 +171,112 @@ namespace {    env["HTTPS"] = c.GetRequestParam("https");   } - std::string fcgiQuery(FCGIContext& context) + class FCGI_Record   { -  bp::pipe is_in; -  bp::ipstream is_out; - -  bp::environment env {boost::this_process::environment()}; -  setCGIEnvironment(env, context); - -  bp::child child("", env, bp::std_out > is_out, bp::std_err > stderr, bp::std_in < is_in); - -  std::string body{ context.GetRequestParam("body") }; -  is_in.write(body.data(), body.size()); -  is_in.close(); - -  std::string output; -  std::string line; - -  // TODO: C++20 coroutine  -  coro_t::push_type processLine( [&](coro_t::pull_type& in){ -                                std::string line; -                                // read header lines -                                while (in && !isEmpty(line = in.get())) { -                                 trimLinebreak(line); -                                 handleHeader(line, context); -                                 in(); -                                } -                                 -                                // read empty line -                                if (!isEmpty(line)) -                                 throw std::runtime_error("Missing empty line between CGI header and body"); -                                if (in) -                                 in(); - -                                // read remainder -                                while (in) { -                                 line = in.get(); -                                 output += line + '\n'; -                                 in(); -                                } - -                                throw std::runtime_error("Input missing on processing CGI body"); -  }); - -  while (child.running() && std::getline(is_out, line)) { -   processLine(line); +  std::vector<char> m_data; + public: +  // create record to send +  FCGI_Record(unsigned char type, uint16_t id, unsigned char arg1, unsigned char arg2) +  { +   if (type == FCGI_BEGIN_REQUEST) { +    size_t size {sizeof(FCGI_BeginRequestRecord)}; +    m_data.resize(size); +    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.body.roleB1 = 0; +    r.body.roleB0 = arg1; +    r.body.flags = arg2; +   } else +    throw std::runtime_error("Bad FCGI type: "s + std::to_string(type)); +  } +   +  // create record to send +  FCGI_Record(unsigned char type, uint16_t id, const std::string& data) +  { +   if (type == FCGI_PARAMS || type == FCGI_STDIN) { +    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(); +    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)); +  } + +  // parse record +  FCGI_Record(std::vector<char>& v) +  { +   if (v.size() < sizeof(FCGI_Header)) +    throw std::length_error("No full FCGI header available"); + +   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}; +   if (v.size() < record_size) +    throw std::length_error("No full FCGI record available"); + +   m_data = std::vector(v.begin(), v.begin() + record_size); + +   v.erase(v.begin(), v.begin() + record_size); +  } + +  std::vector<char>& getBuffer() +  { +   return m_data; +  } + +  unsigned char getType() { return reinterpret_cast<FCGI_Header*>(m_data.data())->type; } + +  std::string getContent() +  { +   if (m_data.size() < sizeof(FCGI_Header)) +    throw std::runtime_error("No data available in FCGI_Record: "s + std::to_string(m_data.size()) + " of "s + std::to_string(sizeof(FCGI_Header)) + " bytes"s); +   return std::string(m_data.data() + sizeof(FCGI_Header), m_data.size() - sizeof(FCGI_Header));    } + }; -  child.wait(); + std::string encode_u8(size_t v) + { +  unsigned char c {static_cast<unsigned char>(v)}; +  return std::string(reinterpret_cast<char*>(&c), 1); + } -  return output; + std::string encode_u32(size_t v) + { +  uint32_t x {static_cast<uint32_t>(v)}; +  boost::endian::native_to_big_inplace(x); +  return std::string(reinterpret_cast<char*>(&x), sizeof(x)); + } + + void FCGI_EncodeEnv(const std::unordered_map<std::string, std::string>& map, std::string& s) + { +  s.clear(); + +  for (auto&[key, value]: map) { +   if (key.size() > 127) +    s += encode_u32(key.size()); +   else +    s += encode_u8(key.size()); +    +   if (value.size() > 127) +    s += encode_u32(value.size()); +   else +    s += encode_u8(value.size()); + +   s += key; +   s += value; +  }   }   // Used to return errors by generating response page and HTTP status code @@ -229,6 +289,110 @@ namespace {  } // anonymous namespace +std::string fcgi_plugin::fcgiQuery(FCGIContext& context) +{ + // host:port or unix domain socket + std::string app_addr{context.GetRequestParam("doc_root")}; + + std::string output_data; + + std::unordered_map<std::string, std::string> env; + setFCGIEnvironment(env, context); + std::string env_bytes; + FCGI_EncodeEnv(env, env_bytes); + + 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); +  boost::asio::connect(socket, endpoints); + +  if (!socket.is_open()) { +   return HttpStatus("500", "FCGI connection", context.SetResponseHeader); +  } + +  FCGI_ID_Guard id_guard(m_fcgi_id); +  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 params{FCGI_PARAMS, id, env_bytes}; +  socket.write_some(boost::asio::buffer(params.getBuffer())); +  +  FCGI_Record params_end{FCGI_PARAMS, id, std::string{}}; +  socket.write_some(boost::asio::buffer(params_end.getBuffer())); + +  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())); + +  bool ended{false}; +  std::vector<char> inbuf; +  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 { +    FCGI_Record r{inbuf}; +    if (r.getType() == FCGI_END_REQUEST) { +     ended = true; +    } else if (r.getType() == FCGI_STDOUT) { +     output_data += r.getContent(); +    } else if (r.getType() == FCGI_STDERR) { +     std::cerr << "FCGI Error: " << r.getContent(); +    } else +     throw std::runtime_error("Unhandled FCGI type: "s + std::to_string(r.getType())); +   } catch (const std::length_error& ex) { +    // ignore if not enough data available yet +   } +  } + } else { // Unix domain socket, or file to start +  // TODO + } + + std::istringstream is_out{output_data}; + std::string output; + std::string line; + + // TODO: C++20 coroutine  + coro_t::push_type processLine( [&](coro_t::pull_type& in){ +                               std::string line; +                               // read header lines +                               while (in && !isEmpty(line = in.get())) { +                                trimLinebreak(line); +                                handleHeader(line, context); +                                in(); +                               } +                                +                               // read empty line +                               if (!isEmpty(line)) +                                throw std::runtime_error("Missing empty line between CGI header and body"); +                               if (in) +                                in(); + +                               // read remainder +                               while (in) { +                                line = in.get(); +                                output += line + '\n'; +                                in(); +                               } + +                               throw std::runtime_error("Input missing on processing CGI body"); + }); + + while (std::getline(is_out, line)) { +  processLine(line); + } + + return output; +} +  std::string fcgi_plugin::name()  {   return "fcgi"; @@ -262,9 +426,6 @@ std::string fcgi_plugin::generate_page(     return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader);    } -  // Build the path to the requested file -  std::string app_addr{GetRequestParam("doc_root")}; -    SetResponseHeader("content_type", "text/html");    FCGIContext context(GetServerParam, GetRequestParam, SetResponseHeader); diff --git a/plugins/fcgi/fcgi.h b/plugins/fcgi/fcgi.h index 7edfe91..164fecb 100644 --- a/plugins/fcgi/fcgi.h +++ b/plugins/fcgi/fcgi.h @@ -2,8 +2,60 @@  #include "../../plugin_interface.h" +#include <set> +#include <cstdint> + +// TODO: multithreading +class FCGI_ID +{ + std::set<uint16_t >m_unused; + uint16_t m_current_max{}; + +public: + FCGI_ID(){} + + // starting at 1 + uint16_t getID(){ +  if (m_unused.empty()) { +   m_current_max++; +   return m_current_max; +  } else { +   uint16_t result{*m_unused.begin()}; +   m_unused.erase(m_unused.begin()); +   return result; +  } + } + + void putID(uint16_t id){ +  m_unused.insert(id); + } +}; + +// automatically reserves ID, and releases it via RAII +class FCGI_ID_Guard +{ + FCGI_ID& m_fcgi_id; + uint16_t m_id; + +public: + FCGI_ID_Guard(FCGI_ID& fcgi_id): m_fcgi_id(fcgi_id), m_id(fcgi_id.getID()) + { + } + + ~FCGI_ID_Guard() + { +  m_fcgi_id.putID(m_id); + } + + uint16_t getID() const { return m_id; } +}; + +struct FCGIContext; +  class fcgi_plugin: public webserver_plugin_interface   { + FCGI_ID m_fcgi_id; +  public:   fcgi_plugin();   ~fcgi_plugin(); @@ -15,6 +67,7 @@ public:    std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader // to be added to result string   ); + std::string fcgiQuery(FCGIContext& context);  };  extern "C" BOOST_SYMBOL_EXPORT fcgi_plugin webserver_plugin; diff --git a/plugins/weblog/html/blog.css b/plugins/weblog/html/blog.css new file mode 100644 index 0000000..5277980 --- /dev/null +++ b/plugins/weblog/html/blog.css @@ -0,0 +1,76 @@ +body { +	font-family: "sans-serif"; +} + +figcaption { +	text-align: center; +	font-size: 8px; +	color: #808080; +} + +figure { +	display: inline-block; +} + +h2 { +	margin: 25px 0px 3px 0px; +} + +div.date { +	font-size: 8px; +	color: #808080; +	margin: 0px 0px 10px 0px; +} + +div.impressum { +	margin: 500px 0px 0px 0px; +} + +.citation { +	font-style:italic; +	margin-left: 50px; +	margin-right: 50px; +} + +.reference { +	text-align: right; +	margin-bottom: 30px; +} + + +.mobile { +	width: 300px; +	border-width: 80px 15px 80px 15px; +       	border-style: solid; +	border-radius: 30px; +	border-color: #000000; +} + +.logo { +	display: block; +	margin: 0 auto; +} + +.screenshot { +	width: 400px; +	border: 2px solid; +	border-color: #8888AA; +} + +img.banner { +	vertical-align: -5px; +} + +@media only screen and (min-width: 1px) and (max-width: 630px) { +} + +@media only screen and (min-width: 631px) and (max-width: 950px) { +} + +@media only screen and (min-width: 951px) { +	div.page { +		max-width: 950px; +		width: 100%; +		margin: 0 auto; +	} +} diff --git a/plugins/weblog/html/favicon.ico b/plugins/weblog/html/favicon.ico Binary files differnew file mode 100644 index 0000000..e8cbddb --- /dev/null +++ b/plugins/weblog/html/favicon.ico  | 
