23 Incheckningar

Upphovsman SHA1 Meddelande Datum
22e3941f2c submodules: cpp-httplib: Update 2025-05-08 22:38:32 +02:00
168d24c545 submodules: cpp-httplib: Update 2025-03-09 15:21:14 +01:00
d1358f7e77 Remove whitespace from id links, fix <br> closing 2024-12-23 10:44:21 +01:00
79d69f4b65 cache: Introduce StringCache, switch to unordered_map, default to memory cache if fs cache dir not given 2024-10-13 15:14:19 +02:00
bfeacb0510 submodules: cpp-httplib: Update 2024-09-20 20:25:55 +02:00
c6013338a9 HandlerFeedGenerator: Remove 'entryUpdated' as it requires another approach
Any small change on a page "updates" the feed, which is misleading to clients.
May need "minor edit" or something. For now, get rid of it.
2024-06-09 15:42:39 +02:00
dab0b94ec4 submodules: cpp-httplib: Update 2024-06-09 10:42:03 +02:00
2ebdbd0b6d parser: Consume superfluous newlines, add [br] and [p] 2024-06-09 10:32:50 +02:00
61e84a98c7 dynamic: Add dynamicpostrenderer 2024-03-16 22:07:37 +01:00
61f289625c RevisionRenderer: Add 'dynamicpostrenderer' 2024-03-16 22:07:37 +01:00
6a12070d0d add cmd:feedlisted and cmd:listed 2024-03-16 22:07:37 +01:00
03c6816528 tree: visible => listed 2024-03-16 22:07:37 +01:00
18f4ad9d51 setup: sqlite: Rename visible => listed, Add 'feedlisted' to indicate whether page should be listed in feeds 2024-03-16 22:07:37 +01:00
84adaa934a template: getPartPath(): Ensure return path isn't outside template dir
user-input to this function might become possible soon
2024-03-16 22:07:37 +01:00
579fadfb10 parser: Add [content] tag, add extractFirstTag() method 2024-03-16 22:07:37 +01:00
ff01a00217 cache: mapcache: Add <string> header 2024-03-16 22:07:37 +01:00
daed17848c handlers: handlerpageedit: Handle [cmd:parentpage] 2024-03-16 22:07:37 +01:00
0fb0457dbb setup: sqlite: Add "parent" refere to "page" 2024-03-16 22:07:37 +01:00
2d5d483790 database: pagedao: Add-support for subpages 2024-03-16 22:07:37 +01:00
f08e235d03 HandlerPageEdit: Use clearForPage() before setting Permissions 2023-08-11 09:22:04 +02:00
8998fb8793 PermissionsDao: Add clearForPage() 2023-08-11 09:21:02 +02:00
9088154372 submodules: cpp-httplib: Update 2023-08-09 13:56:04 +02:00
8a2d9fdc58 {page,categorydao}sqlite: Add missing ROLLBACK 2023-08-09 13:53:49 +02:00
29 ändrade filer med 355 tillägg och 80 borttagningar

51
cache/mapcache.h vendored
Visa fil

@ -4,12 +4,14 @@
#include <set> #include <set>
#include <shared_mutex> #include <shared_mutex>
#include <optional> #include <optional>
#include <string>
#include "icache.h"
/* Thread-Safe Key-Value store */ /* Thread-Safe Key-Value store */
template <class T> class MapCache template <class T> class MapCache
{ {
private: private:
std::map<std::string, T> cache; std::unordered_map<std::string, T> cache;
mutable std::shared_mutex sharedMutex; mutable std::shared_mutex sharedMutex;
public: public:
@ -33,6 +35,53 @@ template <class T> class MapCache
std::lock_guard<std::shared_mutex> lock{sharedMutex}; std::lock_guard<std::shared_mutex> lock{sharedMutex};
this->cache.clear(); this->cache.clear();
} }
void remove(const std::string &key)
{
std::lock_guard<std::shared_mutex> lock{sharedMutex};
this->cache.erase(key);
}
void removePrefix(const std::string &key)
{
std::lock_guard<std::shared_mutex> lock{sharedMutex};
std::erase_if(this->cache, [key](const auto &item)
{
auto const& [k, v] = item;
return k.starts_with(std::string_view(key));
});
}
};
class StringCache : public MapCache<std::string>, public ICache
{
virtual std::optional<std::string> get(std::string_view key) const override
{
return MapCache<std::string>::find(std::string(key));
}
virtual void put(std::string_view key, std::string val) override
{
MapCache<std::string>::set(std::string(key), val);
}
virtual void remove(std::string_view key) override
{
MapCache<std::string>::remove(std::string(key));
}
virtual void removePrefix(std::string_view prefix)
{
MapCache<std::string>::removePrefix(std::string(prefix));
}
virtual void clear() override
{
MapCache<std::string>::clear();
}
}; };
#endif // MAPCACHE_H #endif // MAPCACHE_H

Visa fil

@ -72,6 +72,7 @@ void CategoryDaoSqlite::deleteCategory(std::string name)
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
*db << "ROLLBACK";
throwFrom(e); throwFrom(e);
} }
} }
@ -101,22 +102,29 @@ std::vector<Page> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption
SqliteQueryOption queryOption{o}; SqliteQueryOption queryOption{o};
std::string queryoptions = std::string queryoptions =
queryOption.setOrderByColumn("name").setVisibleColumnName("page.visible").setPrependWhere(false).build(); queryOption.setOrderByColumn("name").setListedColumnName("page.listed").setPrependWhere(false).build();
try try
{ {
auto query = *db << "SELECT page.id, page.name AS name, page.title, page.lastrevision, page.visible FROM categorymember INNER JOIN page ON page.id = " auto query =
"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " + *db
queryoptions << "SELECT page.id, page.name AS name, page.title, page.lastrevision, page.listed, page.feedlisted FROM "
<< name; "categorymember INNER JOIN page ON page.id = "
query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool visible) { "categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " +
queryoptions
<< name;
query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool listed,
bool feedlisted)
{
Page p; Page p;
p.name = name; p.name = name;
p.pageid = id; p.pageid = id;
p.title = title; p.title = title;
p.current_revision = lastrevision; p.current_revision = lastrevision;
p.listed = visible; p.listed = listed;
result.push_back(p); }; p.feedlisted = feedlisted;
result.push_back(p);
};
} }
catch(const sqlite::exceptions::no_rows &e) catch(const sqlite::exceptions::no_rows &e)
{ {

Visa fil

@ -23,6 +23,8 @@ class PageDao
virtual void setCategories(std::string pagename, const std::vector<std::string> &catnames) = 0; virtual void setCategories(std::string pagename, const std::vector<std::string> &catnames) = 0;
virtual std::vector<SearchResult> search(std::string query, QueryOption option) = 0; virtual std::vector<SearchResult> search(std::string query, QueryOption option) = 0;
virtual std::vector<std::string> getChildren(std::string pagename) = 0;
virtual ~PageDao() virtual ~PageDao()
{ {
} }

Visa fil

@ -57,8 +57,12 @@ std::optional<Page> PageDaoSqlite::findByTitle(std::string title)
Page result; Page result;
try try
{ {
auto ps = *db << "SELECT id, name, title, lastrevision, visible FROM page WHERE title = ?"; auto ps =
ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed); *db
<< "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) "
"FROM page WHERE title = ?";
ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed,
result.feedlisted, result.parentpage);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -78,9 +82,13 @@ std::optional<Page> PageDaoSqlite::find(unsigned int id)
result.pageid = id; result.pageid = id;
try try
{ {
auto ps = *db << "SELECT name, title, lastrevision, visible FROM page WHERE id = ?"; auto ps =
*db
<< "SELECT name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) FROM "
"page WHERE id = ?";
ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed); ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed, result.feedlisted,
result.parentpage);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -109,6 +117,7 @@ void PageDaoSqlite::deletePage(std::string page)
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
*db << "ROLLBACK";
throwFrom(e); throwFrom(e);
} }
} }
@ -117,10 +126,10 @@ void PageDaoSqlite::save(const Page &page)
{ {
try try
{ {
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, visible) VALUES((SELECT id FROM page WHERE " *db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, listed, feedlisted, parent) VALUES((SELECT "
"name = " "id FROM page WHERE name = ? OR id = ?), ?, ?, ?, ?, ?, (SELECT id FROM page WHERE name = ?))"
"? OR id = ?), ?, ?, ?, ?)" << page.name << page.pageid << page.name << page.title << page.current_revision << page.listed
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed; << page.feedlisted << page.parentpage;
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
@ -136,19 +145,25 @@ std::vector<Page> PageDaoSqlite::getPageList(QueryOption option)
{ {
std::string queryOption = SqliteQueryOption(option) std::string queryOption = SqliteQueryOption(option)
.setOrderByColumn("lower(name)") .setOrderByColumn("lower(name)")
.setVisibleColumnName("visible") .setListedColumnName("listed")
.setPrependWhere(true) .setPrependWhere(true)
.build(); .build();
std::string query = "SELECT id, name, title, lastrevision, visible FROM page " + queryOption; std::string query = "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE "
*db << query >> [&](unsigned int pageid, std::string name, std::string title,unsigned int current_revision, bool visible ) { "id = parent) FROM page " +
queryOption;
*db << query >> [&](unsigned int pageid, std::string name, std::string title, unsigned int current_revision,
bool listed, bool feedlisted, std::string parent)
{
Page tmp; Page tmp;
tmp.pageid = pageid; tmp.pageid = pageid;
tmp.name = name; tmp.name = name;
tmp.title = title; tmp.title = title;
tmp.current_revision = current_revision; tmp.current_revision = current_revision;
tmp.listed = visible; tmp.listed = listed;
result.push_back(tmp); }; tmp.feedlisted = feedlisted;
tmp.parentpage = parent;
result.push_back(tmp);
};
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -259,3 +274,11 @@ int PageDaoSqlite::fetchPageId(std::string pagename)
auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename; auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename;
return execInt(binder); return execInt(binder);
} }
std::vector<std::string> PageDaoSqlite::getChildren(std::string pagename)
{
std::vector<std::string> result;
auto query = *db << "SELECT name FROM page WHERE parent = (SELECT id FROM page WHERE name = ?)" << pagename;
query >> [&](std::string page) { result.push_back(page); };
return result;
}

Visa fil

@ -28,6 +28,8 @@ class PageDaoSqlite : public PageDao, protected SqliteDao
int fetchPageId(std::string pagename); int fetchPageId(std::string pagename);
std::vector<SearchResult> search(std::string query, QueryOption option) override; std::vector<SearchResult> search(std::string query, QueryOption option) override;
void setCategories(std::string pagename, const std::vector<std::string> &catnames) override; void setCategories(std::string pagename, const std::vector<std::string> &catnames) override;
std::vector<std::string> getChildren(std::string pagename) override;
}; };
#endif // PAGEDAOSQLITE_H #endif // PAGEDAOSQLITE_H

Visa fil

@ -9,6 +9,8 @@ class PermissionsDao
PermissionsDao(); PermissionsDao();
virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0; virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0;
virtual void save(std::string pagename, std::string username, Permissions perms) = 0; virtual void save(std::string pagename, std::string username, Permissions perms) = 0;
virtual void clearForPage(std::string pagename) = 0;
virtual ~PermissionsDao() = default; virtual ~PermissionsDao() = default;
}; };

Visa fil

@ -59,3 +59,16 @@ void PermissionsDaoSqlite::save(std::string pagename, std::string username, Perm
throwFrom(e); throwFrom(e);
} }
} }
void PermissionsDaoSqlite::clearForPage(std::string pagename)
{
try
{
auto stmt = *db << "DELETE FROM permissions WHERE page = (SELECT id FROM page WHERE name = ?)" << pagename;
stmt.execute();
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
}

Visa fil

@ -10,6 +10,7 @@ class PermissionsDaoSqlite : public PermissionsDao, protected SqliteDao
std::optional<Permissions> find(std::string pagename, std::string username) override; std::optional<Permissions> find(std::string pagename, std::string username) override;
virtual void save(std::string pagename, std::string username, Permissions perms) override; virtual void save(std::string pagename, std::string username, Permissions perms) override;
virtual void clearForPage(std::string pagename) override;
using SqliteDao::SqliteDao; using SqliteDao::SqliteDao;
}; };

Visa fil

@ -13,7 +13,7 @@ class QueryOption
unsigned int offset = 0; unsigned int offset = 0;
unsigned int limit = 0; unsigned int limit = 0;
SORT_ORDER order = ASCENDING; SORT_ORDER order = ASCENDING;
bool includeInvisible = true; bool includeUnlisted = true;
}; };
#endif // QUERYOPTION_H #endif // QUERYOPTION_H

Visa fil

@ -52,7 +52,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
{ {
SqliteQueryOption queryOption{options}; SqliteQueryOption queryOption{options};
std::string queryOptionSql = queryOption.setPrependWhere(true) std::string queryOptionSql = queryOption.setPrependWhere(true)
.setVisibleColumnName("page.visible") .setListedColumnName("page.listed")
.setOrderByColumn("creationtime") .setOrderByColumn("creationtime")
.build(); .build();
auto query = auto query =
@ -61,7 +61,8 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
"page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id " + "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id " +
queryOptionSql; queryOptionSql;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid) { std::string page, unsigned int revisionid)
{
Revision r; Revision r;
r.author = author; r.author = author;
r.comment = comment; r.comment = comment;
@ -91,7 +92,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
{ {
SqliteQueryOption queryOption{option}; SqliteQueryOption queryOption{option};
std::string queryOptionSql = queryOption.setPrependWhere(false) std::string queryOptionSql = queryOption.setPrependWhere(false)
.setVisibleColumnName("page.visible") .setListedColumnName("page.listed")
.setOrderByColumn("creationtime") .setOrderByColumn("creationtime")
.build(); .build();
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
@ -101,7 +102,8 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
<< pagename; << pagename;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid) { std::string page, unsigned int revisionid)
{
Revision r; Revision r;
r.author = author; r.author = author;
r.comment = comment; r.comment = comment;
@ -129,7 +131,8 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
try try
{ {
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
"strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid"; "strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON "
"revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid";
query << pagename; query << pagename;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
@ -154,7 +157,8 @@ std::optional<Revision> RevisionDaoSqlite::getRevisionForPage(std::string pagena
auto query = auto query =
*db *db
<< "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), " << "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), "
"page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND revisionid = ? "; "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND "
"revisionid = ? ";
query << pagename << revision; query << pagename << revision;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);

Visa fil

@ -31,9 +31,9 @@ SqliteQueryOption &SqliteQueryOption::setOrderByColumn(std::string name)
return *this; return *this;
} }
SqliteQueryOption &SqliteQueryOption::setVisibleColumnName(std::string name) SqliteQueryOption &SqliteQueryOption::setListedColumnName(std::string name)
{ {
this->visibleColumnName = name; this->listedColumnName = name;
return *this; return *this;
} }
@ -50,9 +50,9 @@ std::string SqliteQueryOption::build()
{ {
result += "WHERE "; result += "WHERE ";
} }
if(!o.includeInvisible && !this->visibleColumnName.empty()) if(!o.includeUnlisted && !this->listedColumnName.empty())
{ {
result += this->visibleColumnName + " = 1"; result += this->listedColumnName + " = 1";
} }
else else
{ {

Visa fil

@ -7,7 +7,7 @@ class SqliteQueryOption
{ {
private: private:
QueryOption o; QueryOption o;
std::string visibleColumnName; std::string listedColumnName;
std::string orderByColumnName; std::string orderByColumnName;
bool prependWhere; bool prependWhere;
@ -17,7 +17,7 @@ class SqliteQueryOption
SqliteQueryOption &setOrderByColumn(std::string name); SqliteQueryOption &setOrderByColumn(std::string name);
SqliteQueryOption &setVisibleColumnName(std::string name); SqliteQueryOption &setListedColumnName(std::string name);
SqliteQueryOption &setPrependWhere(bool b); SqliteQueryOption &setPrependWhere(bool b);

Visa fil

@ -9,7 +9,7 @@ std::string DynamicContentPostList::render()
auto permissionDao = this->database->createPermissionsDao(); auto permissionDao = this->database->createPermissionsDao();
QueryOption option; QueryOption option;
option.includeInvisible = false; option.includeUnlisted = false;
auto members = categoryDao->fetchMembers(this->argument, option); auto members = categoryDao->fetchMembers(this->argument, option);
std::vector<std::pair<std::string, time_t>> pageList; std::vector<std::pair<std::string, time_t>> pageList;
for(const Page &member : members) for(const Page &member : members)

Visa fil

@ -0,0 +1,73 @@
#include <chrono>
#include "dynamicpostrenderer.h"
#include "../parser.h"
#include "../utils.h"
void DynamicPostRenderer::setArgument(std::string argument)
{
auto splitted = utils::split(argument, '|');
this->category = splitted[0];
if(splitted.size() >= 2)
{
this->templatepartname = splitted[1];
}
if(splitted.size() >= 3)
{
this->customlinkurl = splitted[2];
}
}
std::string DynamicPostRenderer::linkToPage(std::string page)
{
if(this->customlinkurl.empty())
{
return this->urlProvider->page(page);
}
return utils::strreplace(this->customlinkurl, "{page}", page);
}
std::string DynamicPostRenderer::render()
{
auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao();
auto permissionDao = this->database->createPermissionsDao();
QueryOption option;
option.includeUnlisted = true;
auto members = categoryDao->fetchMembers(this->category, option);
std::vector<std::pair<std::string, time_t>> pageList;
for(const Page &member : members)
{
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead())
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, 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 entry = this->templ->loadResolvedPart(this->templatepartname);
std::stringstream stream;
for(auto &pair : pageList)
{
std::optional<Revision> revision = revisionDao->getCurrentForPage(pair.first);
if(revision)
{
std::string link = linkToPage(pair.first);
Parser parser;
std::string date = utils::toISODateTime(revision->timestamp);
Varreplacer replacer{"{"};
replacer.addKeyValue("url", link);
replacer.addKeyValue("date", date);
replacer.addKeyValue("content", parser.parse(*pageDao, *this->urlProvider,
parser.extractFirstTag("content", revision->content)));
stream << replacer.parse(entry);
}
}
return stream.str();
}

Visa fil

@ -0,0 +1,18 @@
#ifndef DYNAMICPOSTRENDERER_H
#define DYNAMICPOSTRENDERER_H
#include "dynamiccontent.h"
class DynamicPostRenderer : public DynamicContent
{
private:
std::string category;
std::string customlinkurl;
std::string templatepartname = "dynamic/categoryrendererentry";
public:
using DynamicContent::DynamicContent;
std::string render() override;
void setArgument(std::string argument) override;
std::string linkToPage(std::string page);
};
#endif // DYNAMICPOSTRENDERER_H

Visa fil

@ -53,7 +53,7 @@ std::string Handler::createPageTitle(std::string title)
QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const
{ {
QueryOption result; QueryOption result;
result.includeInvisible = false; result.includeUnlisted = false;
try try
{ {
result.limit = utils::toUInt(r.get("limit")); result.limit = utils::toUInt(r.get("limit"));

Visa fil

@ -9,7 +9,7 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch
std::vector<EntryRevisionPair> result; std::vector<EntryRevisionPair> result;
QueryOption option; QueryOption option;
option.includeInvisible = false; option.includeUnlisted = true;
// option.limit = 20; // option.limit = 20;
auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; }; auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; };
@ -34,12 +34,15 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch
} }
for(const Page &member : members) for(const Page &member : members)
{ {
Permissions perms = permissionDao->find(member.name, this->userSession->user.login) if(member.feedlisted)
.value_or(this->userSession->user.permissions);
if(perms.canRead())
{ {
auto revision = revisionDao->getRevisionForPage(member.name, 1).value(); Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
result.push_back({member, revision}); .value_or(this->userSession->user.permissions);
if(perms.canRead())
{
auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
result.push_back({member, revision});
}
} }
} }
std::sort(result.begin(), result.end(), std::sort(result.begin(), result.end(),
@ -86,7 +89,6 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
newestPublished = initialRevision.timestamp; newestPublished = initialRevision.timestamp;
} }
std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z"; std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z";
std::string entryUpdated = utils::formatLocalDate(current.timestamp, dateformat) + "Z";
std::string entryurl = std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)}); this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry"); TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
@ -94,7 +96,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
atomentry.setVar("entryurl", utils::html_xss(entryurl)); atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl)); atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished); atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryUpdated); atomentry.setVar("entryupdated", entryPublished);
atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title))); atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title)));
stream << atomentry.render(); stream << atomentry.render();
} }

Visa fil

@ -76,10 +76,27 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
try try
{ {
this->database->beginTransaction(); this->database->beginTransaction();
std::string visiblecmd = parser.extractCommand("visible", newContent); std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string listedcmd = parser.extractCommand("listed", newContent);
/* Backwarts compatibility */
if(listedcmd.empty())
{
listedcmd = visiblecmd;
}
std::string feedlistedcmd = parser.extractCommand("feedlisted", newContent);
std::string rename = parser.extractCommand("rename", newContent); std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent); std::string customtitle = parser.extractCommand("pagetitle", newContent);
std::string parentpage = parser.extractCommand("parentpage", newContent);
std::vector<std::string> perms = parser.extractCommands("permissions", newContent); std::vector<std::string> perms = parser.extractCommands("permissions", newContent);
if(parentpage != "" && !pageDao.find(parentpage))
{
return this->errorResponse("Invalid parent",
"Specified parent page " + parentpage + " does not exist");
}
Page page; Page page;
std::optional<Page> currentPage = pageDao.find(pagename); std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage) if(currentPage)
@ -95,6 +112,9 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
pagename = rename; pagename = rename;
} }
std::vector<std::pair<std::string, Permissions>> collectedPermissions;
auto permissionDao = this->database->createPermissionsDao();
for(const std::string &perm : perms) for(const std::string &perm : perms)
{ {
auto splitted = utils::split(perm, '|'); auto splitted = utils::split(perm, '|');
@ -102,29 +122,37 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{ {
return this->errorResponse("Invalid command", "permissions command is misformated"); return this->errorResponse("Invalid command", "permissions command is misformated");
} }
auto permissionDao = this->database->createPermissionsDao();
auto currentPermission = permissionDao->find(pagename, splitted[0]); auto currentPermission = permissionDao->find(pagename, splitted[0]);
Permissions newPermissions = Permissions{splitted[1]}; Permissions newPermissions = Permissions{splitted[1]};
if(!currentPermission || newPermissions != currentPermission.value()) if(!currentPermission || newPermissions != currentPermission.value())
{ {
if(this->userSession->user.permissions.canSetPagePerms()) if(!this->userSession->user.permissions.canSetPagePerms())
{
permissionDao->save(pagename, splitted[0], newPermissions);
}
else
{ {
this->database->rollbackTransaction(); this->database->rollbackTransaction();
return errorResponse("Invalid permissions", return errorResponse("Permission denied",
"You don't have permission to change page permissions"); "You don't have permission to change permissions. Don't touch the "
"permission commands");
} }
} }
collectedPermissions.emplace_back(splitted[0], newPermissions);
}
if(this->userSession->user.permissions.canSetPagePerms())
{
permissionDao->clearForPage(pagename);
for(auto &perms : collectedPermissions)
{
permissionDao->save(pagename, perms.first, perms.second);
}
} }
page.current_revision = current_revision; page.current_revision = current_revision;
page.listed = !(visiblecmd == "0"); page.listed = !(listedcmd == "0");
page.feedlisted = !(feedlistedcmd == "0");
page.name = pagename; page.name = pagename;
page.title = customtitle; page.title = customtitle;
page.parentpage = parentpage;
if(page.title.empty()) if(page.title.empty())
{ {
page.title = page.name; page.title = page.name;

Visa fil

@ -60,7 +60,7 @@ std::string HandlerPageView::createIndexContent(IParser &parser, std::string con
} }
previous = h.level; previous = h.level;
HtmlLink link; HtmlLink link;
link.href = "#" + h.title; link.href = "#" + utils::strreplace(h.title, " ", "");
link.innervalue = h.title; link.innervalue = h.title;
link.cssclass = "indexlink"; link.cssclass = "indexlink";
indexcontent += "<li>" + link.render() + "</li>"; indexcontent += "<li>" + link.render() + "</li>";

Visa fil

@ -15,6 +15,7 @@ class IParser
} }
public: public:
virtual std::string extractFirstTag(std::string tagname, const std::string &content) const = 0;
virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0; virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const = 0; virtual std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const = 0;

2
page.h
Visa fil

@ -8,7 +8,9 @@ class Page
Page(); Page();
std::string name; std::string name;
std::string title; std::string title;
std::string parentpage;
bool listed; bool listed;
bool feedlisted;
unsigned int current_revision; unsigned int current_revision;
unsigned int pageid; unsigned int pageid;
}; };

Visa fil

@ -63,11 +63,10 @@ std::vector<std::string> Parser::extractCategories(const std::string &content) c
return result; return result;
} }
std::string Parser::extractCommand(std::string cmdname, const std::string &content) const std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const
{ {
std::string cmd = "[cmd:" + cmdname + "]"; std::string cmd = "[" + tagname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]"; std::string cmdend = "[/" + tagname + "]";
std::string_view view = content; std::string_view view = content;
size_t pos = 0; size_t pos = 0;
if((pos = view.find(cmd)) != std::string::npos) if((pos = view.find(cmd)) != std::string::npos)
@ -83,6 +82,12 @@ std::string Parser::extractCommand(std::string cmdname, const std::string &conte
return ""; return "";
} }
std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
{
return extractFirstTag("cmd:" + cmdname, content);
}
std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const
{ {
std::vector<std::string> result; std::vector<std::string> result;
@ -170,27 +175,48 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
std::string result; std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings // we don't care about commands, but we nevertheless replace them with empty strings
std::regex tagfinder( std::regex tagfinder(
R"(\[(b|i|u|s|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])"); R"(\[(b|i|u|s|li|p|br|ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:listed|cmd:feedlisted|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|cmd:parentpage|content|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1](\r\n)*)");
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
result = utils::regex_callback_replacer( result = utils::regex_callback_replacer(
tagfinder, content, tagfinder, content,
[&](std::smatch &match) [&](std::smatch &match)
{ {
std::string tag = match.str(1); std::string tag = match.str(1);
std::string content = match.str(2); std::string content = match.str(2);
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol", "s"};
std::string newlines = match.str(4);
if(newlines == "\r\n")
{
newlines = "<br>";
}
if(tag == "br")
{
return std::string("<br>");
}
if(tag != "code" && tag != "blockquote") if(tag != "code" && tag != "blockquote")
{ {
content = parse(pagedao, provider, content, callback); content = parse(pagedao, provider, content, callback);
} }
/* [content] just helps extracting the actual content of a page, pretty much noop otherwise */
if(tag == "content")
{
return parse(pagedao, provider, content, callback);
}
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace)) if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
{ {
return "<" + tag + ">" + content + "</" + tag + ">"; if(tag == "p" || tag == "br")
{
newlines = "";
}
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
} }
if(tag == "link" || tag == "wikilink") if(tag == "link" || tag == "wikilink")
{ {
return this->processLink( return this->processLink(pagedao, provider,
pagedao, provider, match) +
match); // TODO: recreate this so we don't check inside the function stuff again newlines; // TODO: recreate this so we don't check inside the function stuff again
} }
if(tag == "img") if(tag == "img")
{ {
@ -198,7 +224,7 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
} }
if(tag[0] == 'h') if(tag[0] == 'h')
{ {
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">"; return "<" + tag + " id='" + utils::strreplace(content, " ", "") + "'>" + content + "</" + tag + ">";
} }
if(tag == "code" || tag == "blockquote") if(tag == "code" || tag == "blockquote")
{ {

Visa fil

@ -9,6 +9,7 @@ class Parser : public IParser
std::string processImage(std::smatch &match) const; std::string processImage(std::smatch &match) const;
public: public:
std::string extractFirstTag(std::string tagname, const std::string &content) const override;
std::string extractCommand(std::string cmdname, const std::string &content) const override; std::string extractCommand(std::string cmdname, const std::string &content) const override;
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override; std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override;
std::vector<Headline> extractHeadlines(const std::string &content) const override; std::vector<Headline> extractHeadlines(const std::string &content) const override;

Visa fil

@ -72,7 +72,7 @@ std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
std::string path = resolver.getConfig("cache_fs_dir"); std::string path = resolver.getConfig("cache_fs_dir");
if(path == "") if(path == "")
{ {
return std::make_unique<NoCache>(path); return std::make_unique<StringCache>();
} }
return std::make_unique<FsCache>(path); return std::make_unique<FsCache>(path);
} }

Visa fil

@ -1,9 +1,10 @@
#include "revisionrenderer.h" #include "revisionrenderer.h"
#include "templatepage.h" #include "templatepage.h"
#include "dynamic/dynamiccontentpostlist.h" #include "dynamic/dynamiccontentpostlist.h"
#include "dynamic/dynamiccontentincludepage.h" #include "dynamic/dynamiccontentincludepage.h"
#include "dynamic/dynamiccontentgetvar.h" #include "dynamic/dynamiccontentgetvar.h"
#include "dynamic/dynamiccontentsetvar.h" #include "dynamic/dynamiccontentsetvar.h"
#include "dynamic/dynamicpostrenderer.h"
#include "parser.h" #include "parser.h"
#include "htmllink.h" #include "htmllink.h"
@ -17,9 +18,10 @@ std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_
} }
if(key == "dynamic:includepage") if(key == "dynamic:includepage")
{ {
auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>(); auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>();
includePage->setArgument(std::string(value)); includePage->setArgument(std::string(value));
return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this,
std::placeholders::_1, std::placeholders::_2));
} }
if(key == "dynamic:setvar") if(key == "dynamic:setvar")
{ {
@ -35,6 +37,12 @@ std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_
getVar->setArgument(std::string(value)); getVar->setArgument(std::string(value));
return getVar->render(); return getVar->render();
} }
if(key == "dynamic:postrenderer")
{
auto renderer = this->dynamicContentFactory.createDynamicContent<DynamicPostRenderer>();
renderer->setArgument(std::string(value));
return renderer->render();
}
return std::string{}; return std::string{};
} }
@ -44,7 +52,8 @@ std::string RevisionRenderer::renderContent(std::string content)
dynamicVarsMap["createdon"] = utils::toISODate(time(NULL)); dynamicVarsMap["createdon"] = utils::toISODate(time(NULL));
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(time(NULL)); dynamicVarsMap["modifydatetime"] = utils::toISODateTime(time(NULL));
std::string resolvedContent = parser.parseDynamics(content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); std::string resolvedContent = parser.parseDynamics(
content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
} }
@ -55,15 +64,15 @@ std::string RevisionRenderer::renderContent(const Revision &r, std::string_view
auto firstRevision = revisionDao->getRevisionForPage(r.page, 1); auto firstRevision = revisionDao->getRevisionForPage(r.page, 1);
if(!firstRevision) if(!firstRevision)
{ {
throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?"); throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?");
} }
dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp); dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp);
dynamicVarsMap["pagetitle"] = customTitle; dynamicVarsMap["pagetitle"] = customTitle;
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(r.timestamp); dynamicVarsMap["modifydatetime"] = utils::toISODateTime(r.timestamp);
std::string resolvedContent = parser.parseDynamics(r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); std::string resolvedContent = parser.parseDynamics(
r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
} }

Visa fil

@ -49,7 +49,11 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
} }
for(unsigned int i = 0; i < fsPaths.size(); i++) for(unsigned int i = 0; i < fsPaths.size(); i++)
{ {
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str()); std::string &path = fsPaths[i];
if(path.size() > 0)
{
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, path.c_str());
}
} }
policy->drop_caps = 1; policy->drop_caps = 1;
policy->not_dumpable = 1; policy->not_dumpable = 1;

Visa fil

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

Visa fil

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include <filesystem>
#include "template.h" #include "template.h"
#include "varreplacer.h" #include "varreplacer.h"
#include "urlprovider.h" #include "urlprovider.h"
@ -47,9 +48,15 @@ TemplatePage Template::getPage(const std::string &pagename)
std::string Template::getPartPath(std::string_view partname) std::string Template::getPartPath(std::string_view partname)
{ {
// TODO: utils::concatPath? C++17 paths? auto absolute_path = std::filesystem::canonical(std::filesystem::path{this->templatepath} / partname);
return this->templatepath + "/" + std::string(partname); std::string result = absolute_path.string();
if(result.starts_with(this->templatepath))
{
return result;
}
return "";
} }
std::string Template::loadPartContent(std::string_view partname) std::string Template::loadPartContent(std::string_view partname)
{ {
std::string partpath = getPartPath(partname); std::string partpath = getPartPath(partname);