Compare commits

...

20 Commits

Author SHA1 Message Date
Albert S. 5bb3f55945 HandlerFeedGenerator: Improvements to make feed vlaid 2022-03-28 20:06:42 +02:00
Albert S. 1ae5495e61 Dynamic: Add DynamicContentIncludePage to allow including pages 2022-03-27 21:36:53 +02:00
Albert S. 7bb7600d39 HandlerFeedGenerator: Add caching 2022-03-27 21:22:00 +02:00
Albert S. f5eb36e7bb DynamicContentPostList: Ignore invisible entries 2022-03-27 20:03:28 +02:00
Albert S. c891b36339 Makefile: Build dynamic content generators, adjust for exile update 2022-03-27 20:00:21 +02:00
Albert S. d17e596563 sandbox-linux: include exile.hpp 2022-03-27 19:59:52 +02:00
Albert S. 761471f243 template: Add template for atom feed 2022-03-27 19:54:07 +02:00
Albert S. 9ac0ad0ccd template: Add template for dynamic postlist 2022-03-27 19:53:48 +02:00
Albert S. c30e09d44d HandlerFactory: Wire up HandlerFeedGenerator 2022-03-27 19:52:45 +02:00
Albert S. bcc3737d88 UrlProvider: Introduce combine(), rootUrl(), atomFeed() 2022-03-27 19:51:53 +02:00
Albert S. 9520aabe5c Config: Require rooturl,atomurl 2022-03-27 19:50:51 +02:00
Albert S. 4854ea85f2 Begin HandlerFeedGenerator: Generates Atom feeds for categories (or all pages) 2022-03-27 19:48:57 +02:00
Albert S. 16c352c6af utils: readCompleteFile(): Throw exception if file can't be opened 2022-03-27 19:47:52 +02:00
Albert S. f7cf06cdd5 Page: Add 'title' column, storing title of last revision 2022-03-27 09:23:35 +02:00
Albert S. ac793c6d39 handlers: HandlerPageView: Add '[dynamic:postlist]' tag by callback 2022-03-27 08:37:55 +02:00
Albert S. a524674149 Begin dynamic content generators 2022-03-27 08:36:25 +02:00
Albert S. a4a45d9add Parser: Add callback support for unknown "tags" 2022-03-27 08:31:59 +02:00
Albert S. 44c27ed8b4 Template: Make loadResolvedPart() public 2022-03-27 08:30:51 +02:00
Albert S. 433b5da2bb template: Adjust after renaming: Use utils::toISODateTime() 2022-03-27 08:30:20 +02:00
Albert S. c5435c52f4 utils: Rename/Add date functions 2022-03-27 08:29:13 +02:00
36 changed files with 451 additions and 38 deletions

View File

@ -14,6 +14,7 @@ SOURCES+=$(wildcard handlers/*.cpp)
SOURCES+=$(wildcard database/*.cpp)
SOURCES+=$(wildcard cache/*.cpp)
SOURCES+=$(wildcard sandbox/*.cpp)
SOURCES+=$(wildcard dynamic/*.cpp)
HEADERS=$(wildcard *.h)
HEADERS+=$(wildcard gateway/*.h)
@ -21,7 +22,7 @@ HEADERS+=$(wildcard handlers/*.h)
HEADERS+=$(wildcard database/*.h)
HEADERS+=$(wildcard cache/*.h)
HEADERS+=$(wildcard sandbox/*.h)
HEADERS+=$(wildcard dynamic/*.h)
OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES))
WIKIOBJECTS=$(filter-out test.o, $(OBJECTS))
@ -48,8 +49,12 @@ profile: LDFLAGS+= -pg
release: qswiki
profile: qswiki
qswiki: $(WIKIOBJECTS)
$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
exile.o: submodules/exile.h/exile.c
$(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o
qswiki: $(WIKIOBJECTS) exile.o
$(CXX) $(WIKIOBJECTS) exile.o ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
test: $(TESTOBJECTS)
$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test
@ -63,6 +68,6 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS)
version.o:version.cpp
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
clean:
rm -f $(OBJECTS) $(DEPENDS)
rm -f exile.o $(OBJECTS) $(DEPENDS)

View File

@ -24,6 +24,7 @@ SOFTWARE.
#include "config.h"
#include "permissions.h"
#include "varreplacer.h"
std::string Config::required(const std::string &key)
{
auto it = this->configmap.find(key);
@ -96,6 +97,8 @@ Config::Config(const std::map<std::string, std::string> &map)
this->urls.deletionurl = required("deletionurl");
this->urls.adminregisterurl = required("adminregisterurl");
this->urls.usersettingsurl = required("usersettingsurl");
this->urls.rooturl = required("rooturl");
this->urls.atomurl = required("atomurl");
this->connectionstring = required("connectionstring");
this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256);

View File

@ -41,6 +41,8 @@ struct ConfigUrls
std::string linkhistorysort;
std::string adminregisterurl;
std::string usersettingsurl;
std::string rooturl;
std::string atomurl;
};
class ConfigVariableResolver

View File

@ -58,9 +58,9 @@ std::optional<Page> PageDaoSqlite::find(unsigned int id)
result.pageid = id;
try
{
auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?";
auto ps = *db << "SELECT name, title, lastrevision, visible FROM page WHERE id = ?";
ps << id >> std::tie(result.name, result.current_revision, result.listed);
ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed);
}
catch(const sqlite::errors::no_rows &e)
{
@ -97,9 +97,10 @@ void PageDaoSqlite::save(const Page &page)
{
try
{
*db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE name = "
"? OR id = ?), ?, ?, ?)"
<< page.name << page.pageid << page.name << page.current_revision << page.listed;
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, visible) VALUES((SELECT id FROM page WHERE "
"name = "
"? OR id = ?), ?, ?, ?, ?)"
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed;
}
catch(sqlite::sqlite_exception &e)
{
@ -183,7 +184,8 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
auto query =
*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? "
<< ftsEscape(name);
query >> [&](std::string pagename) {
query >> [&](std::string pagename)
{
SearchResult sresult;
sresult.pagename = pagename;
sresult.query = name;

View File

@ -0,0 +1,8 @@
#include "dynamiccontent.h"
DynamicContent::DynamicContent(Template &templ, Database &database, UrlProvider &provider)
{
this->templ = &templ;
this->database = &database;
this->urlProvider = &provider;
}

28
dynamic/dynamiccontent.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef DYNAMICCONTENT_H
#define DYNAMICCONTENT_H
#include <string>
#include "../database/database.h"
#include "../template.h"
#include "../urlprovider.h"
class DynamicContent
{
protected:
Template *templ;
Database *database;
UrlProvider *urlProvider;
std::string argument;
public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider);
virtual std::string render() = 0;
virtual void setArgument(std::string argument)
{
this->argument = argument;
}
virtual ~DynamicContent()
{
}
};
#endif // DYNAMICCONTENT_H

View File

@ -0,0 +1,14 @@
#include "dynamiccontentincludepage.h"
#include "../parser.h"
std::string DynamicContentIncludePage::render()
{
auto revisionDao = this->database->createRevisionDao();
auto rev = revisionDao->getCurrentForPage(this->argument);
if(rev)
{
Parser parser;
auto result = parser.parse(*this->database->createPageDao(), *this->urlProvider, rev->content);
return result;
}
return {};
}

View File

@ -0,0 +1,12 @@
#ifndef DYNAMICCONTENTINCLUDEPAGE_H
#define DYNAMICCONTENTINCLUDEPAGE_H
#include "dynamiccontent.h"
class DynamicContentIncludePage : public DynamicContent
{
public:
using DynamicContent::DynamicContent;
std::string render();
};
#endif // DYNAMICCONTENTINCLUDEPAGE_H

View File

@ -0,0 +1,39 @@
#include <chrono>
#include "dynamiccontentpostlist.h"
std::string DynamicContentPostList::render()
{
auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao();
QueryOption option;
option.includeInvisible = false;
auto members = categoryDao->fetchMembers(this->argument, option);
std::vector<std::pair<std::string, time_t>> pageList;
for(std::string &member : members)
{
auto revision = revisionDao->getRevisionForPage(member, 1);
pageList.push_back({member, revision->timestamp});
}
std::sort(pageList.begin(), pageList.end(),
[](std::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });
std::string postListBegin = this->templ->loadResolvedPart("dynamic/postlistbegin");
std::string postListEnd = this->templ->loadResolvedPart("dynamic/postlistend");
std::string postLink = this->templ->loadResolvedPart("dynamic/postlistlink");
std::stringstream stream;
stream << postListBegin;
for(auto &pair : pageList)
{
std::string link = this->urlProvider->page(pair.first);
std::string date = utils::toISODate(pair.second);
Varreplacer replacer{"{"};
replacer.addKeyValue("url", link);
replacer.addKeyValue("date", date);
replacer.addKeyValue("title", pageDao->find(pair.first)->title);
stream << replacer.parse(postLink);
}
stream << postListEnd;
return stream.str();
}

View File

@ -0,0 +1,12 @@
#ifndef DYNAMICCONTENTPOSTLIST_H
#define DYNAMICCONTENTPOSTLIST_H
#include "dynamiccontent.h"
class DynamicContentPostList : public DynamicContent
{
public:
using DynamicContent::DynamicContent;
std::string render() override;
};
#endif // DYNAMICCONTENTPOSTLIST_H

View File

@ -1,5 +1,6 @@
#ifndef HANDLER_H
#define HANDLER_H
#include <memory>
#include "../config.h"
#include "../response.h"
#include "../request.h"
@ -9,6 +10,8 @@
#include "../database/queryoption.h"
#include "../logger.h"
#include "../cache/icache.h"
#include "../dynamic/dynamiccontent.h"
class Handler
{
protected:
@ -53,6 +56,12 @@ class Handler
virtual ~Handler()
{
}
template <class T> inline std::shared_ptr<T> createDynamic()
{
return std::make_shared<T>(*this->templ, *this->database, *this->urlProvider);
}
Response errorResponse(std::string errortitle, std::string errormessage, int status = 200);
std::string createPageTitle(std::string append);
};

View File

@ -34,6 +34,7 @@ SOFTWARE.
#include "handlerpagedelete.h"
#include "handlerusersettings.h"
#include "handlerversion.h"
#include "handlerfeedgenerator.h"
std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession)
{
if(action == "" || action == "index")
@ -84,6 +85,10 @@ std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action
{
return produce<HandlerVersion>(userSession);
}
if(action == "feed")
{
return produce<HandlerFeedGenerator>(userSession);
}
return produce<HandlerInvalidAction>(userSession);
}

View File

@ -0,0 +1,143 @@
#include "handlerfeedgenerator.h"
#include "../parser.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories)
{
auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao();
std::vector<EntryRevisionPair> result;
QueryOption option;
option.includeInvisible = false;
// option.limit = 20;
std::set<std::string> members;
if(categories.empty())
{
auto pages = pageDao->getPageList(option);
std::copy(pages.begin(), pages.end(), std::inserter(members, members.end()));
}
else
{
auto categoryDao = this->database->createCategoryDao();
for(std::string cat : categories)
{
auto catmembers = categoryDao->fetchMembers(cat, option);
std::copy(catmembers.begin(), catmembers.end(), std::inserter(members, members.end()));
}
}
for(const std::string &member : members)
{
auto page = pageDao->find(member).value();
auto revision = revisionDao->getRevisionForPage(page.name, 1).value();
result.push_back({page, revision});
}
std::sort(result.begin(), result.end(),
[](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; });
const int maxResults = 20;
if(result.size() > maxResults)
{
result.erase(result.begin() + maxResults - 1, result.end());
}
return result;
}
std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerator::EntryRevisionPair> &entries,
std::string filter)
{
std::stringstream stream;
// don't care about offset for now especially since "%z" does not return what we need exactly (with ':')
const std::string dateformat = "%Y-%m-%dT%T";
time_t newestPublished = 0;
std::string atomfooter = this->templ->loadResolvedPart("feeds/atomfooter");
auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao();
std::string subtitle = filter;
if(utils::trim(filter).empty())
{
subtitle = "All pages";
}
for(const EntryRevisionPair &entry : entries)
{
const Page &page = entry.first;
const Revision &initialRevision = entry.second;
Revision current = revisionDao->getCurrentForPage(page.name).value();
std::string url = this->urlProvider->rootUrl() + this->urlProvider->page(page.name);
if(initialRevision.timestamp > newestPublished)
{
newestPublished = initialRevision.timestamp;
}
std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z";
std::string entryUpdated = utils::formatLocalDate(current.timestamp, dateformat) + "Z";
std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
atomentry.setVar("entrytitle", utils::html_xss(page.title));
atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryUpdated);
Parser parser;
atomentry.setVar("entrycontent", utils::html_xss(parser.parse(*pageDao, *this->urlProvider, current.content)));
stream << atomentry.render();
}
stream << atomfooter;
TemplatePage atomheader = this->templ->getPage("feeds/atomheader");
atomheader.setVar("subtitle", subtitle);
std::string selflink = utils::html_xss(this->urlProvider->atomFeed(filter));
atomheader.setVar("atomfeeduniqueid", selflink);
atomheader.setVar("atomselflink", selflink);
atomheader.setVar("atomfeedupdate", utils::formatLocalDate(newestPublished, dateformat) + "Z");
return atomheader.render() + stream.str();
}
Response HandlerFeedGenerator::handleRequest(const Request &r)
{
Response response;
try
{
std::string type = r.get("type");
std::string categories = r.get("cats");
if(type == "atom")
{
std::string filter = categories;
Response result;
result.setStatus(200);
result.setContentType("application/atom+xml");
std::string cacheKey = "feed:atom:" + filter;
auto cached = this->cache->get(cacheKey);
if(cached)
{
result.setBody(cached.value());
}
else
{
auto entries = fetchEntries(utils::split(categories, ','));
std::string feed = generateAtom(entries, filter);
result.setBody(feed);
this->cache->put(cacheKey, feed);
}
response = result;
}
else
{
return errorResponse("Invalid feed type", "Unknown feed type, try atom", 400);
}
}
catch(std::runtime_error &e)
{
Logger::error() << "Error while serving feed: " << e.what();
return errorResponse("Error", "An error occured while trying to serve the feed", 500);
}
return response;
}
bool HandlerFeedGenerator::canAccess(const Permissions &perms)
{
return true;
}

View File

@ -0,0 +1,21 @@
#ifndef HANDLERFEEDGENERATOR_H
#define HANDLERFEEDGENERATOR_H
#include "handler.h"
#include "../page.h"
#include "../revision.h"
class HandlerFeedGenerator : public Handler
{
typedef std::pair<Page, Revision> EntryRevisionPair;
protected:
std::vector<EntryRevisionPair> fetchEntries(std::vector<std::string> categories);
std::string generateAtom(const std::vector<EntryRevisionPair> &entries, std::string atomtitle);
Response generateRss(const std::vector<EntryRevisionPair> &entries);
public:
using Handler::Handler;
Response handleRequest(const Request &r) override;
bool canAccess(const Permissions &perms) override;
};
#endif // HANDLERFEEDGENERATOR_H

View File

@ -45,6 +45,7 @@ Response HandlerPageDelete::handleRequest(PageDao &pageDao, std::string pagename
{
pageDao.deletePage(pagename);
this->cache->removePrefix("page:"); // TODO: overkill?
this->cache->removePrefix("feed:");
return Response::redirectTemporarily(this->urlProvider->index());
}
TemplatePage delPage = this->templ->getPage("page_deletion");

View File

@ -66,6 +66,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
this->database->beginTransaction();
std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent);
Page page;
std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage)
@ -83,6 +84,11 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
page.current_revision = current_revision;
page.listed = !(visiblecmd == "0");
page.name = pagename;
page.title = customtitle;
if(page.title.empty())
{
page.title = page.name;
}
pageDao.save(page);
Revision newRevision;
@ -95,6 +101,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
pageDao.setCategories(pagename, cats);
this->database->commitTransaction();
this->cache->removePrefix("page:"); // TODO: overkill?
this->cache->removePrefix("feed:");
}
catch(const DatabaseException &e)
{

View File

@ -23,7 +23,8 @@ SOFTWARE.
#include "../logger.h"
#include "../parser.h"
#include "../htmllink.h"
#include "../dynamic/dynamiccontentpostlist.h"
#include "../dynamic/dynamiccontentincludepage.h"
bool HandlerPageView::canAccess(std::string page)
{
return effectivePermissions(page).canRead();
@ -136,10 +137,27 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
std::string indexcontent;
std::string parsedcontent;
std::function<std::string(std::string_view, std::string_view)> dynamicParseCallback =
[&](std::string_view key, std::string_view value) -> std::string
{
if(key == "dynamic:postlist")
{
std::shared_ptr<DynamicContentPostList> postlist = createDynamic<DynamicContentPostList>();
postlist->setArgument(std::string(value));
return postlist->render();
}
if(key == "dynamic:includepage")
{
std::shared_ptr<DynamicContentIncludePage> includePage = createDynamic<DynamicContentIncludePage>();
includePage->setArgument(std::string(value));
return includePage->render();
}
return std::string{};
};
if(revisionid > 0)
{
indexcontent = createIndexContent(parser, revision->content);
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content, dynamicParseCallback);
}
else
{
@ -162,7 +180,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
}
else
{
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content, dynamicParseCallback);
this->cache->put(cachekeyparsedcontent, parsedcontent);
}
}
@ -171,7 +189,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
page.setVar("content", parsedcontent);
page.setVar("index", indexcontent);
page.setVar("editedby", revision->author);
page.setVar("editedon", utils::toISODate(revision->timestamp));
page.setVar("editedon", utils::toISODateTime(revision->timestamp));
page.setVar("historyurl", this->urlProvider->pageHistory(pagename));
page.setVar("revision", revisionstr);
setPageVars(page, pagename);

View File

@ -2,15 +2,27 @@
#define IPARSER_H
#include <vector>
#include <string_view>
#include <functional>
#include "headline.h"
#include "database/pagedao.h"
#include "urlprovider.h"
class IParser
{
protected:
static std::string empty(std::string_view key, std::string_view content)
{
return "";
}
public:
virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0;
virtual std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const = 0;
virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const
{
return parse(pagedao, provider, content, empty);
}
virtual std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const = 0;
virtual std::vector<std::string> extractCategories(const std::string &content) const = 0;
virtual ~IParser(){};

1
page.h
View File

@ -7,6 +7,7 @@ class Page
public:
Page();
std::string name;
std::string title;
bool listed;
unsigned int current_revision;
unsigned int pageid;

View File

@ -116,12 +116,13 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider
return htmllink.render();
}
std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const
std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const
{
std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings
std::regex tagfinder(
R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|cmd:pagetitle|category)*?\]((\s|\S)*?)\[/\1])");
R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|cmd:pagetitle|category|dynamic:postlist|dynamic:includepage)*?\]((\s|\S)*?)\[/\1])");
result = utils::regex_callback_replacer(
tagfinder, content,
[&](std::smatch &match)
@ -129,7 +130,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
std::string tag = match.str(1);
std::string content = match.str(2);
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
content = parse(pagedao, provider, content);
content = parse(pagedao, provider, content, callback);
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
{
return "<" + tag + ">" + content + "</" + tag + ">";
@ -144,7 +145,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
{
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
}
return std::string("");
return callback(tag, content);
});
result = utils::strreplace(result, "\r\n", "<br>");
return result;

View File

@ -11,9 +11,12 @@ class Parser : public IParser
std::string extractCommand(std::string cmdname, const std::string &content) const;
std::vector<Headline> extractHeadlines(const std::string &content) const override;
std::vector<std::string> extractCategories(const std::string &content) const override;
std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const override;
using IParser::parse;
virtual std::string parse(
const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const override;
using IParser::IParser;
~Parser(){};
};
#endif // PARSER_H

View File

@ -12,18 +12,13 @@
#include <filesystem>
#include <sys/mount.h>
#include <sys/capability.h>
#define HAVE_LANDLOCK 0
#include <exile.h>
#include <exile.hpp>
#include "../logger.h"
#include "../utils.h"
#include "../random.h"
#include "sandbox-linux.h"
/* TODO: make a whitelist approach. So far we simply blacklist
* obvious systemcalls. To whitelist, we need to analyse our
* dependencies (http library, sqlite wrapper, sqlite lib etc.) */
bool SandboxLinux::supported()
{
std::fstream stream;
@ -54,19 +49,19 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
}
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str());
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str());
}
policy->drop_caps = 1;
policy->not_dumpable = 1;
policy->no_new_privs = 1;
policy->mount_path_policies_to_chroot = 1;
policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_CPATH |
EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_UNIX |
EXILE_SYSCALL_VOW_THREAD;
EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_UNIX |
EXILE_SYSCALL_VOW_THREAD;
if(exile_enable_policy(policy) != 0)
{
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
Logger::error() << "Sandbox: Activation of exile failed!";
exile_free_policy(policy);
return false;
}

View File

@ -1,4 +1,4 @@
CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), lastrevision integer, visible integer DEFAULT 1);
CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), title varchar(1024), lastrevision integer, visible integer DEFAULT 1);
CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64),
password blob, salt blob, permissions integer, enabled integer DEFAULT 1);
CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32),

@ -1 +1 @@
Subproject commit 4824c6eaa9043878daaba7b3778338f5bf913f06
Subproject commit f2ca26010a2bb6d9e270d6ade2e8789c02ac3b31

View File

@ -141,7 +141,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< revision.revision << "</a></td>"
<< "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>";
<< "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>";
}
};
@ -155,7 +155,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< "<td>" << revision.revision << "</td>"
<< "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>";
<< "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>";
}
};

View File

@ -22,7 +22,6 @@ class Template
std::string resolveIncludes(std::string_view content);
std::string getPartPath(std::string_view partname);
std::string loadResolvedPart(std::string_view partname);
std::string loadPartContent(std::string_view partname);
TemplatePage createPage(std::string_view name);
@ -31,6 +30,7 @@ class Template
ConfigVariableResolver &configVarsResolver, MapCache<TemplatePage> &pageCache);
TemplatePage getPage(const std::string &pagename);
std::string loadResolvedPart(std::string_view partname);
std::string renderSearch(const std::vector<std::string> &results,
std::function<std::string(std::string)> callback) const;

View File

@ -0,0 +1 @@
<ul>

View File

@ -0,0 +1 @@
</ul>

View File

@ -0,0 +1 @@
<li>{date}: <a href="{url}">{title}</a></li>

View File

@ -0,0 +1,8 @@
<entry>
<title>{qswiki:var:entrytitle}</title>
<link href="{qswiki:var:entryurl}"/>
<id>{qswiki:var:entryid}</id>
<published>{qswiki:var:entrypublished}</published>
<updated>{qswiki:var:entryupdated}</updated>
<content type="html">{qswiki:var:entrycontent}</content>
</entry>

View File

@ -0,0 +1 @@
</feed>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<author>
<name>{qswiki:config:wikiownername}</name>
</author>
<title>{qswiki:config:wikiname} - {qswiki:var:subtitle}</title>
<id>{qswiki:var:atomfeeduniqueid}</id>
<link rel="self" href="{qswiki:var:atomselflink}"/>
<updated>{qswiki:var:atomfeedupdate}</updated>

View File

@ -18,6 +18,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <string_view>
#include "urlprovider.h"
std::string replaceSingleVar(std::string where, std::string varname, std::string replacement)
@ -119,3 +120,28 @@ std::string UrlProvider::login(std::string page)
{
return replaceOnlyPage(config->loginurl, page);
}
std::string UrlProvider::rootUrl()
{
return config->rooturl;
}
std::string UrlProvider::atomFeed(std::string filter)
{
return combine({config->rooturl, replaceSingleVar(config->atomurl, "filter", filter)});
}
std::string UrlProvider::combine(std::initializer_list<std::string> urls)
{
std::string result;
for(const std::string &url : urls)
{
std::string_view urlview{url};
if(result.back() == '/' && urlview.front() == '/')
{
urlview.remove_prefix(1);
}
result += urlview;
}
return result;
}

View File

@ -48,6 +48,12 @@ class UrlProvider
std::string category(std::string catname);
std::string login(std::string page);
std::string rootUrl();
std::string atomFeed(std::string filter);
std::string combine(std::initializer_list<std::string> urls);
};
#endif // LINKCREATOR_H

View File

@ -138,6 +138,10 @@ std::string utils::readCompleteFile(std::string_view filepath)
{
std::fstream stream(std::string{filepath});
if(!stream.is_open())
{
throw std::runtime_error("stream is not open");
}
std::stringstream ss;
ss << stream.rdbuf();
std::string content = ss.str();
@ -166,7 +170,10 @@ std::string utils::regex_callback_replacer(std::regex regex, const std::string &
return result;
}
std::string utils::toISODate(time_t t)
/* TODO: Convert to C++20, but currently the state is rather poor and would
* require workarounds, so keep it this way for now, and do it properly
* once compiler support gets there */
std::string utils::formatLocalDate(time_t t, std::string format)
{
struct tm lt;
if(localtime_r(&t, &lt) == nullptr)
@ -174,7 +181,7 @@ std::string utils::toISODate(time_t t)
return {};
}
char result[20];
size_t x = strftime(result, sizeof(result), "%Y-%m-%d %H:%M:%S", &lt);
size_t x = strftime(result, sizeof(result), format.c_str(), &lt);
if(x == 0)
{
return {};
@ -182,6 +189,16 @@ std::string utils::toISODate(time_t t)
return std::string{result};
}
std::string utils::toISODateTime(time_t t)
{
return utils::formatLocalDate(t, "%Y-%m-%d %H:%M:%S");
}
std::string utils::toISODate(time_t t)
{
return utils::formatLocalDate(t, "%Y-%m-%d");
}
std::string utils::trim(std::string_view view)
{
std::string_view chars = " \t\n\r";

View File

@ -81,7 +81,9 @@ inline unsigned int toUInt(const std::string &str)
return result;
}
std::string formatLocalDate(time_t t, std::string format);
std::string toISODate(time_t t);
std::string toISODateTime(time_t t);
template <class T> inline std::string toString(const T &v)
{