summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--TODO5
-rw-r--r--debian/webserver.conf53
-rw-r--r--debian/webserver.install1
-rw-r--r--plugins/weblog/weblog.cpp260
-rw-r--r--server.cpp2
-rw-r--r--webserver.conf1
6 files changed, 256 insertions, 66 deletions
diff --git a/TODO b/TODO
index 58fe21e..d727ebe 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,10 @@
-
+Integrate into Debian
+Ubuntu version
Request properties: Remote Address, e.g. [::1]:8081 -> ipv6 / ipv4
Speed up config.GetPath
read: The socket was closed due to a timeout
statistics
-index page
+file index page
webbox: Info if not selected: all
webbox: Copy function
wildcard ip listen
diff --git a/debian/webserver.conf b/debian/webserver.conf
new file mode 100644
index 0000000..55e7246
--- /dev/null
+++ b/debian/webserver.conf
@@ -0,0 +1,53 @@
+<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <plugin-directory>/usr/lib/webserver/plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>antcom.de</name>
+ <host>lists.antcom.de</host>
+ <host>antcom.de</host>
+ <host>www.antcom.de</host>
+ <path requested="/">
+ <plugin>static-files</plugin>
+ <target>/home/ernie/homepage/test</target>
+ </path>
+ <path requested="/webbox">
+ <plugin>static-files</plugin>
+ <target>/home/ernie/code/webserver/plugins/webbox/html</target>
+ </path>
+ <path requested="/webbox/bin">
+ <plugin>webbox</plugin>
+ <target>/home/ernie/testbox</target>
+ <WEBBOX_NAME>Testbox1</WEBBOX_NAME>
+ <WEBBOX_READONLY>1</WEBBOX_READONLY>
+ </path>
+ <certpath>/home/ernie/code/webserver/fullchain.pem</certpath>
+ <keypath>/home/ernie/code/webserver/privkey.pem</keypath>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>127.0.0.1</address>
+ <port>80</port>
+ <protocol>http</protocol>
+ </socket>
+ <socket>
+ <address>::1</address>
+ <port>80</port>
+ <protocol>http</protocol>
+ </socket>
+ <socket>
+ <address>127.0.0.1</address>
+ <port>443</port>
+ <protocol>https</protocol>
+ </socket>
+ <socket>
+ <address>::1</address>
+ <port>443</port>
+ <protocol>https</protocol>
+ </socket>
+ </sockets>
+</webserver>
+
diff --git a/debian/webserver.install b/debian/webserver.install
new file mode 100644
index 0000000..b4997f3
--- /dev/null
+++ b/debian/webserver.install
@@ -0,0 +1 @@
+debian/webserver.conf etc
diff --git a/plugins/weblog/weblog.cpp b/plugins/weblog/weblog.cpp
index 1c58b73..0c0ffcd 100644
--- a/plugins/weblog/weblog.cpp
+++ b/plugins/weblog/weblog.cpp
@@ -2,6 +2,7 @@
#include <boost/algorithm/string/predicate.hpp>
+#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -12,79 +13,203 @@ namespace fs = std::filesystem;
namespace {
-// 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]
+ const size_t number_of_articles_on_front_page {10};
+ const std::string article_filename{"article.data"};
+
+ // Return a reasonable mime type based on the extension of a file.
+ std::string mime_type(fs::path 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";
-}
+ 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";
+ }
-std::string getFile(const fs::path& filename)
-{
- std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate);
+ // 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)
+ {
+ SetResponseHeader("status", status);
+ SetResponseHeader("content_type", "text/html");
+ return status + " " + message;
+ }
- if (file.is_open()) {
- std::ifstream::pos_type fileSize = file.tellg();
- file.seekg(0, std::ios::beg);
+ std::string getFile(const fs::path& filename)
+ {
+ std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ if (file.is_open()) {
+ std::ifstream::pos_type fileSize = file.tellg();
+ file.seekg(0, std::ios::beg);
- std::string bytes(fileSize, ' ');
- file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
+ std::string bytes(fileSize, ' ');
+ file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
- return bytes;
+ return bytes;
- } else {
- throw std::runtime_error("Opening "s + filename.string() + " for reading");
+ } else {
+ throw std::runtime_error("Opening "s + filename.string() + " for reading");
+ }
}
-}
-bool is_index_page(std::string& path)
-{
- return (path.size() == 0 || path.back() == '/');
-}
+ bool is_index_page(std::string& rel_target)
+ {
+ return (rel_target.size() == 0 || rel_target == "/");
+ }
-std::string generateIndexPage(std::function<plugin_interface_setter_type>& SetResponseHeader)
-{
- return "<html><body><h1>Blog</h1></body></html>";
-}
+ bool is_article_page(std::string& rel_target, fs::path& path)
+ {
+ return (rel_target.size() >= 2 && rel_target.back() == '/' && fs::is_directory(path));
+ }
-// 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)
-{
- SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
- return status + " " + message;
-}
+ bool is_article_file(std::string& rel_target, fs::path& path)
+ {
+ return (fs::is_regular_file(path) && path.filename().string() != article_filename);
+ }
-}
+ struct ArticleInfo
+ {
+ fs::path path;
+ std::string subject;
+ std::string date;
+ };
+
+ // get article metadata from header lines
+ std::unordered_map<std::string, std::string> getMetaData(fs::path path)
+ {
+ if (path.string().size() > 0 && path.string().back() == '/') {
+ std::string s {path.string()};
+ path = s.substr(0, s.size() - 1);
+ }
+ std::unordered_map<std::string, std::string> result;
+
+ std::string pathname{path.filename().string()};
+ // ISO date
+ std::string date{pathname.substr(0, 4) + "-"s + pathname.substr(4, 2) + "-"s + pathname.substr(6, 2)};
+ std::string time{pathname.substr(9, 2) + ":"s + pathname.substr(11, 2)};
+ if (time != "00:00")
+ date += " " + time;
+
+ result["Date"] = date;
+
+ fs::path filepath {path / article_filename};
+
+ std::ifstream file(filepath.string(), std::ios::in);
+
+ if (file.is_open()) {
+ std::string line;
+ while (!file.eof()) {
+ std::getline(file, line);
+ if (line.empty()) // found header end
+ break;
+ size_t pos {line.find(": ")};
+ if (pos == line.npos) {
+ std::cerr << "Warning: Found bad header line in " << filepath << ": " << line << std::endl;
+ continue;
+ }
+ result[line.substr(0, pos)] = line.substr(pos + 2);
+ }
+ return result;
+
+ } else {
+ throw std::runtime_error("Opening "s + filepath.string() + " for reading");
+ }
+ }
+
+ std::vector<ArticleInfo> getArticleList(fs::path& path)
+ {
+ std::vector<ArticleInfo> result;
+
+ for (auto& year_entry: fs::directory_iterator(path)) {
+ for (auto& entry: fs::directory_iterator(year_entry.path())) {
+ auto metaData{getMetaData(entry.path())};
+ result.emplace_back(ArticleInfo{entry.path(), metaData.at("Subject"), metaData.at("Date")});
+ }
+ }
+
+ size_t size{std::min(number_of_articles_on_front_page, result.size())};
+ // sort backwards
+ std::partial_sort(result.begin(), result.begin() + size, result.end(), [](const ArticleInfo& a0, const ArticleInfo& a1){ return a0.date > a1.date;});
+
+ return {result.begin(), result.begin() + size};
+ }
+
+ std::string generateIndexPage(fs::path& path,
+ std::function<std::string(const std::string& key)>& GetRequestParam,
+ std::function<plugin_interface_setter_type>& SetResponseHeader)
+ {
+ try {
+ std::string result{"<html><body><h1>"s + GetRequestParam("WEBLOG_NAME") + "</h1>"s};
+
+ fs::path link{ GetRequestParam("rel_target")};
+
+ auto list{getArticleList(path)};
+ for (const auto& article: list) {
+ result += "<h2><a href=\"" + (link / article.path.filename()).string() + "/\">"s + article.subject + "</a></h2>"s + article.date + "<br/>"s;
+ }
+ result += "</body></html>";
+ return result;
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Reading Index page: "s + ex.what(), SetResponseHeader);
+ }
+ }
+
+ std::string generateArticlePage(fs::path& path,
+ std::function<plugin_interface_setter_type>& SetResponseHeader)
+ {
+ try {
+ auto metaData{getMetaData(path)};
+
+ std::string data { getFile(path / article_filename)};
+
+ size_t pos {data.find("\n\n")};
+ if (pos == data.npos)
+ throw std::runtime_error("Error parsing article");
+
+ std::string result { "<html><body><h1>"s + metaData.at("Subject") + "</h1>"s + metaData.at("Date") + "<br/><br/>"s + data.substr(pos + 2) + "</body></html>"s};
+
+ return result;
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Reading Article: "s + ex.what(), SetResponseHeader);
+ }
+ }
+
+ std::string generateArticleFile(fs::path& path, std::function<plugin_interface_setter_type>& SetResponseHeader)
+ {
+ try {
+ SetResponseHeader("content_type", mime_type(path));
+ return getFile(path);
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Reading Article file: "s + ex.what(), SetResponseHeader);
+ }
+ }
+
+} // anonymous namespace
std::string weblog_plugin::name()
{
@@ -122,6 +247,9 @@ std::string weblog_plugin::generate_page(
// Build the path to the requested file
std::string doc_root{GetRequestParam("doc_root")};
+ if (rel_target.size() >= 4 && std::for_each(rel_target.begin(), rel_target.begin() + 4, isdigit)) {
+ rel_target = rel_target.substr(0, 4) + "/" + rel_target;
+ }
fs::path path {fs::path{doc_root} / rel_target};
if (target.size() && target.back() != '/' && fs::is_directory(path)) {
std::string location{GetRequestParam("location") + "/"s};
@@ -132,7 +260,13 @@ std::string weblog_plugin::generate_page(
SetResponseHeader("content_type", "text/html");
if (is_index_page(rel_target))
- return generateIndexPage(SetResponseHeader);
+ return generateIndexPage(path, GetRequestParam, SetResponseHeader);
+
+ if (is_article_page(rel_target, path))
+ return generateArticlePage(path, SetResponseHeader);
+
+ if (is_article_file(rel_target, path))
+ return generateArticleFile(path, SetResponseHeader);
return HttpStatus("404", "Bad path specification: "s + rel_target, SetResponseHeader);
diff --git a/server.cpp b/server.cpp
index 81ee454..6e15466 100644
--- a/server.cpp
+++ b/server.cpp
@@ -35,7 +35,7 @@ namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
-const std::string Server::VersionString{ "Webserver "s + std::string{VERSION} };
+const std::string Server::VersionString{ "Reichwein.IT Webserver "s + std::string{VERSION} };
Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins)
: m_config(config)
diff --git a/webserver.conf b/webserver.conf
index 16ac3e0..4aa0daa 100644
--- a/webserver.conf
+++ b/webserver.conf
@@ -34,6 +34,7 @@
<path requested="/blog">
<plugin>weblog</plugin>
<target>/home/ernie/testblog</target>
+ <WEBLOG_NAME>Roland Reichweins Blog</WEBLOG_NAME>
</path>
<path requested="/cgi-bin">
<plugin>cgi</plugin>