diff options
| -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); | 
