diff options
Diffstat (limited to 'plugins/weblog')
-rw-r--r-- | plugins/weblog/Makefile | 1 | ||||
-rw-r--r-- | plugins/weblog/stringutil.cpp | 66 | ||||
-rw-r--r-- | plugins/weblog/stringutil.h | 10 | ||||
-rw-r--r-- | plugins/weblog/weblog.cpp | 101 |
4 files changed, 163 insertions, 15 deletions
diff --git a/plugins/weblog/Makefile b/plugins/weblog/Makefile index ffd31e1..58d1801 100644 --- a/plugins/weblog/Makefile +++ b/plugins/weblog/Makefile @@ -57,6 +57,7 @@ LIBS+= \ endif PROGSRC=\ + stringutil.cpp \ weblog.cpp TESTSRC=\ diff --git a/plugins/weblog/stringutil.cpp b/plugins/weblog/stringutil.cpp new file mode 100644 index 0000000..f87fa00 --- /dev/null +++ b/plugins/weblog/stringutil.cpp @@ -0,0 +1,66 @@ +#include "stringutil.h" + +#include <boost/algorithm/string/predicate.hpp> +#include <boost/algorithm/string/case_conv.hpp> + +#include <cstdarg> + +std::string strfmt(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + int size = std::vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + std::string result(size, ' '); + + va_start(args, fmt); + std::vsnprintf(result.data(), size + 1, fmt, args); + va_end(args); + + return result; +} + +std::vector<std::string> split(std::string value, const std::string separators) +{ + std::vector<std::string> result; + + size_t pos0 = 0; + size_t pos1 = 0; + while (pos0 < value.size()) { + pos1 = value.find_first_of(separators, pos0); + if (pos1 == std::string::npos) + pos1 = value.size(); + std::string part = value.substr(pos0, pos1 - pos0); + //std::cout << "DEBUG: " << part << std::endl << std::flush; + if (part != "") + result.push_back(part); + pos0 = value.find_first_not_of(separators, pos1); + if (pos0 == std::string::npos) + pos0 = value.size(); + } + + return result; +} + +std::string join(std::vector<std::string> vs, std::string separator) +{ + std::string s; + for (const auto& line : vs) { + if (s.size() > 0) + s += separator; + s += line; + } + + return s; +} + +bool startsWithAnyOfLower(const std::string &s, const std::vector<std::string> &list) { + for (const std::string& element : list) { + if (boost::algorithm::starts_with(boost::algorithm::to_lower_copy(s), boost::algorithm::to_lower_copy(element))) + return true; + } + return false; +} + diff --git a/plugins/weblog/stringutil.h b/plugins/weblog/stringutil.h new file mode 100644 index 0000000..5110e2e --- /dev/null +++ b/plugins/weblog/stringutil.h @@ -0,0 +1,10 @@ +#pragma once + +#include <string> +#include <vector> + +std::string strfmt(const char* fmt, ...); + +std::vector<std::string> split(std::string value, const std::string separators = "\r\n "); +std::string join(std::vector<std::string> vs, std::string separator = "\n"); +bool startsWithAnyOfLower(const std::string &s, const std::vector<std::string> &list); diff --git a/plugins/weblog/weblog.cpp b/plugins/weblog/weblog.cpp index 07b6447..a69fdb6 100644 --- a/plugins/weblog/weblog.cpp +++ b/plugins/weblog/weblog.cpp @@ -1,5 +1,7 @@ #include "weblog.h" +#include "stringutil.h" + #include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/replace.hpp> #include <boost/property_tree/ptree.hpp> @@ -152,7 +154,7 @@ namespace { } } - std::vector<ArticleInfo> getArticleList(fs::path& path) + std::vector<ArticleInfo> getArticleList(fs::path& path, size_t page) { std::vector<ArticleInfo> result; @@ -166,11 +168,12 @@ namespace { } } - size_t size{std::min(number_of_articles_on_front_page, result.size())}; + size_t index0{std::min(number_of_articles_on_front_page * (page), result.size())}; + size_t index1{std::min(number_of_articles_on_front_page * (page + 1), 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;}); + std::partial_sort(result.begin(), result.begin() + index1, result.end(), [](const ArticleInfo& a0, const ArticleInfo& a1){ return a0.date > a1.date;}); - return {result.begin(), result.begin() + size}; + return {result.begin() + index0, result.begin() + index1}; } std::string plainTextFromPTree(const pt::ptree& tree) @@ -271,25 +274,35 @@ namespace { std::string generateIndexPage(fs::path& path, std::function<std::string(const std::string& key)>& GetRequestParam, - std::function<plugin_interface_setter_type>& SetResponseHeader) + std::function<plugin_interface_setter_type>& SetResponseHeader, + size_t page) { try { HtmlPage htmlPage{GetRequestParam, "<h1>"s + GetRequestParam("WEBLOG_NAME") + "</h1>"s}; fs::path link{ GetRequestParam("rel_target")}; - auto list{getArticleList(path)}; + auto list{getArticleList(path, page)}; if (list.empty()) htmlPage += "(no articles found.)"; - for (const auto& article: list) { - std::string linkstart{"<a href=\"" + (link / article.path.filename()).string() + "/\">"}; - std::string linkend{"</a>"}; - htmlPage += "<h2>"s + linkstart + article.subject + linkend + "</h2>"s + article.date + "<br/>"s; - - auto sv{shortVersion(article.path)}; - if (sv.size()) { - htmlPage += sv + " "s + linkstart + "more..." + linkend; + else { + for (const auto& article: list) { + std::string linkstart{"<a href=\"" + (link / article.path.filename()).string() + "/\">"}; + std::string linkend{"</a>"}; + htmlPage += "<h2>"s + linkstart + article.subject + linkend + "</h2>"s + article.date + "<br/>"s; + + auto sv{shortVersion(article.path)}; + if (sv.size()) { + htmlPage += sv + " "s + linkstart + "more..." + linkend; + } } + htmlPage += "<br/><br/><br/>"; + if (page > 0) + htmlPage += "<a href=\"?page="s + std::to_string(page - 1) + "\"><<newer</a> "s; + htmlPage += "page "s + std::to_string(page + 1); + if (list.size() == number_of_articles_on_front_page) + htmlPage += " <a href=\"?page="s + std::to_string(page + 1) + "\">older>></a>"s; + htmlPage += "<br/>"; } return htmlPage; } catch (const std::exception& ex) { @@ -334,6 +347,56 @@ namespace { } } + std::string urlDecode(std::string s) + { + std::string result; + + size_t pos = 0; + while (pos < s.size()) { + char c {s[pos]}; + if (c == '+') { + result += ' '; + } else if (c == '%' && pos + 2 < s.size()) { + try { + int i = stoi(s.substr(pos + 1, 2), 0, 16); + if (i < 0 || i > 255) + return result; + + result += static_cast<char>(i); + } catch (...) { + return result; + } + + pos += 2; + } else { + result += c; + } + pos++; + } + + return result; + } + + std::unordered_map<std::string, std::string> SplitQueryString(std::string& s) + { + std::unordered_map<std::string, std::string> result; + + size_t qpos = s.find('?'); + if (qpos != s.npos) { + auto list {split(s.substr(qpos + 1), "&")}; + for (auto i: list) { + size_t apos = i.find('='); + if (apos != i.npos) { + result[urlDecode(i.substr(0, apos))] = urlDecode(i.substr(apos + 1)); + } + } + } + + s = s.substr(0, qpos); + + return result; + } + } // anonymous namespace std::string weblog_plugin::name() @@ -370,6 +433,8 @@ std::string weblog_plugin::generate_page( return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader); } + std::unordered_map<std::string, std::string> query { SplitQueryString(rel_target) }; + // Build the path to the requested file std::string doc_root{GetRequestParam("doc_root")}; if (rel_target.size() >= 4 && std::all_of(rel_target.begin(), rel_target.begin() + 4, isdigit)) { @@ -384,8 +449,14 @@ std::string weblog_plugin::generate_page( SetResponseHeader("content_type", "text/html"); + size_t page {0}; + auto it {query.find("page")}; + if (it != query.end()) { + page = stoul(it->second); + } + if (is_index_page(rel_target)) - return generateIndexPage(path, GetRequestParam, SetResponseHeader); + return generateIndexPage(path, GetRequestParam, SetResponseHeader, page); if (is_article_page(rel_target, path)) return generateArticlePage(path, GetRequestParam, SetResponseHeader); |