47 Commits

Author SHA1 Message Date
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
c0049fc7b6 sqlite: Use per-thread connections 2023-07-29 10:00:27 +02:00
fe533a5076 Disable caching if no cache dir given 2023-07-29 09:52:07 +02:00
ec3cbe3f76 cache: Add dummy NoCache class 2023-07-29 09:45:51 +02:00
1095d38b02 Add [cmd:permissions] 2023-07-28 15:04:58 +02:00
234db99ef5 dynamic,HandlerFeedGenerator: Check for read permissions 2023-07-27 18:02:12 +02:00
32af0e2857 handlers: HandlerPageEdit: Add transaction rollback on exception 2023-07-26 20:21:44 +02:00
aa362331a5 submodules: cpp-httplib: Update 2023-05-31 21:16:58 +02:00
64b6e7e61c parser: Add [s] 2023-04-01 13:48:11 +02:00
3bc51b9d34 submodules: cpp-httplib: Update 2023-03-09 09:03:22 +01:00
7dee7bc06b Update .gitignore 2023-02-03 16:08:39 +01:00
afea31f231 revisionrenderer: renderContent(): Add modifydatetime var 2023-02-03 16:08:39 +01:00
004665e943 qswiki: Add background worker, currently to clean old sessions mainly 2023-02-03 16:08:39 +01:00
b9595bd513 database: Add fetch() for SessionDao 2023-02-03 16:08:28 +01:00
48e3614e78 Retire javascript session refresh 2023-02-03 16:07:11 +01:00
7c086e0d78 logger: Initialize members with default vals 2023-01-19 12:17:52 +01:00
24121a1618 Makefile: Add libstdc++ assertions + randomize link order 2023-01-16 20:43:34 +01:00
172129179e submodules: cpp-httplib: Update 2022-12-13 10:18:10 +01:00
8603e55c59 config: Adjust default payload length 2022-12-13 10:17:40 +01:00
e326e09a36 gateway: httpgateway: Adjust default payload length for urlencoded content 2022-12-13 10:16:30 +01:00
a71c3da129 template: Enable syntax highlighting using highlight.js 2022-11-08 08:48:22 +01:00
fbfe5510a1 parser: Render <code> and <blockquote> preformated 2022-11-08 08:47:53 +01:00
78b9e5e043 README: update 2022-10-30 11:30:27 +01:00
ef8eebdbaa database: Add missing virtual destructors for some classes 2022-10-24 15:17:04 +02:00
7ef9d7f020 sandbox: Use exile_vows_from_str() for seccomp policy 2022-10-23 21:36:58 +02:00
54 changed files with 710 additions and 211 deletions

2
.gitignore vendored
View File

@ -3,6 +3,8 @@
*.out *.out
*.gch *.gch
*.user *.user
*.swp
*.kate-swp
qswiki qswiki
wikiqs* wikiqs*
data/* data/*

View File

@ -1,13 +1,15 @@
CPPSTD=c++20 CPPSTD=c++20
CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra
RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra #CFIFLAGS=-fsanitize=cfi -fvisibility=hidden -fsanitize=cfi -flto
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs #Does not work reliably atm
CFIFLAGS=
CXXFLAGS=-std=$(CPPSTD) -O2 -g -no-pie -pipe -MMD -Wall -Wextra -DGLIBCXX_ASSERTIONS -D_LIBCPP_ENABLE_ASSERTIONS=1 $(CFIFLAGS)
RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra -DGLIBCXX_ASSERTIONS -D_LIBCPP_ENABLE_ASSERTIONS=1 $(CFIFLAGS)
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs $(CFIFLAGS)
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/exile.h INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/exile.h
CXX=g++
SOURCES=$(wildcard *.cpp) SOURCES=$(wildcard *.cpp)
SOURCES+=$(wildcard gateway/*.cpp) SOURCES+=$(wildcard gateway/*.cpp)
SOURCES+=$(wildcard handlers/*.cpp) SOURCES+=$(wildcard handlers/*.cpp)
@ -54,7 +56,7 @@ exile.o: submodules/exile.h/exile.c
$(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o $(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o
qswiki: $(WIKIOBJECTS) exile.o qswiki: $(WIKIOBJECTS) exile.o
$(CXX) $(WIKIOBJECTS) exile.o ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki $(CXX) $(shell shuf -e $(WIKIOBJECTS) exile.o ) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
test: $(TESTOBJECTS) test: $(TESTOBJECTS)
$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test $(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test

View File

@ -1,80 +1,84 @@
# qswiki # qswiki
About ## About
==== qswiki is a wiki software, intended for my needs. Originally implemented in C, it's now written in C++.
qswiki is a wiki software, intended for small wikis. Originally
implemented in C, it's now written in C++.
History ## Dude... why?
====
A couple of years ago, I wanted to setup a personal wiki on my raspberry tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already
pi. However, the distribution I used back then did not have a PHP package started.
### History
Several years ago, I wanted to setup a personal wiki on my raspberry
pi. However, the distribution I used back then did not have a PHP package
for ARM. So instead of switching distributions or searching for other for ARM. So instead of switching distributions or searching for other
wikis that I could use, I decided I would write one in C. Yes, wikis that I could use, I simply decided I would write one in C. Yes,
that's an odd way to approach the problem and indeed, I may have had too that's an odd way to approach the problem and indeed, I may have had too
much time back then. Also, I wanted to see how it's like to write a much time back then. Also, I wanted to see how it's like to write a
"web app" in C and wanted to sharpen my C skills a little bit. "web app" in C and wanted to sharpen my C skills a little bit.
Of course, it's pretty straightforward at first. No really: Just use CGI. Of course, it's pretty straightforward at first. No really: Just use CGI
And indeed, that would have been more than enough for my use cases. and print your HTML to stdout.And indeed, that would have been more than enough for my use cases.
Then I decided to play around and started using FastCGI (with the official
But then I decided to play around and started using FastCGI (with the official
library from now defunct fastcgi.com) and created a multi-threaded version. library from now defunct fastcgi.com) and created a multi-threaded version.
It initially used a "pile of files database", but that became too painful, It initially used a "pile of files database", but that became too painful,
so then I started using sqlite. so then I started using sqlite.
C++ C++
--- ---
Eventually, since it was mostly a playground for me, the code became Eventually, since it was mostly a playground for me, the code became
unmaintainable. Furthermore, I wanted something quick and given that unmaintainable. Furthermore, I initially wanted something quick and given that
it was CGI, I didn't bother taking care of memory leaks. it was CGI, I didn't bother taking care of memory leaks.
After initiating a FastCGI interface, they became an issue and then the After initiating a FastCGI interface, they became an issue and then the
task of avoiding memory leaks became too annoying. And of course, C does n task of avoiding memory leaks became too annoying. And of course, C does n
ot include any "batteries" and while I could manage, this too was another ot include any "batteries" and while I could manage, this too was another
good reason. good reason.
Overall, I am just continuing the experiment with C++17 now. It's not Overall, I am just continuing the experiment with >=C++17 now. It's not
nearly as bad as you would expect perhaps. Some things are surprisingly nearly as bad as you would expect perhaps. Some things are surprisingly
convenient even. Still, the standard library is lacking and convenient even. Still, the standard library is lacking and
I would hope for a some better built-in Unicode support in future C++ I would hope for a some better built-in Unicode support in future C++
standards. standards.
Features
======== ## Features
To be fair, at this point it doesn't even have a "diff" between revisions Some essential features are lacking, such as a diff between revisions,
yet and does not have features that would make you prefer it over other user registration UI, etc.
wikis.
It doesn't compete with any other software anyway.
- CGI - CGI
- HTTP server using the header only library cpp-httplib. It's more - HTTP server using the header only library [cpp-httplib](https://github.com/yhirose/cpp-httplib). It's more
portable and more "future-proof" than FastCGI (since the official website portable and more "future-proof" than FastCGI (since the official website
disappeared, the library's future appears to be uncertain). disappeared, the library's future appears to be uncertain).
- Support for user accounts. Passwords are stored using PBKDF2. - Support for user accounts. Passwords are stored using PBKDF2.
sqlite database, but not too much of an effort to add other types of sqlite database, but not too much of an effort to add other types of
storage backends. sqlite is using the great header only library storage backends. sqlite is using the great header only library
sqlite_modern_cpp [sqlite_modern_cpp](https://github.com/SqliteModernCpp)
- Relatively fine-grained permission system. - Relatively fine-grained permission system.
- Categories - Categories
- Templates - Templates
- FTS search - FTS search
- Caching - Caching
- Blog-like functionality
- RSS/Atom feeds
Security ## Security
======== [exile.h](https://github.com/quitesimpleorg/exile.h) is used
On Linux namespaces are used to restrict the process to only access to restrict access to the files the wiki needs. It doesn't have access to other paths
files it needs. It doesn't have access to other paths in the system. in the system and the system calls that the qswiki process can make are restricted.
In addition, Seccomp is used to restrict the syscalls the qswiki process
can call. As for "web security", all POST requests are centrally As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS
protected against CSRF attacks and all input is escaped against XSS
attacks. attacks.
Building ## Building
========
Dependencies: Dependencies:
- cpp-httplib: https://github.com/yhirose/cpp-httplib - cpp-httplib: https://github.com/yhirose/cpp-httplib
- SqliteModernCpp: https://github.com/SqliteModernCpp - SqliteModernCpp: https://github.com/SqliteModernCpp
- exile.h: https://gitea.quitesimple.org/crtxcr/exile.h - exile.h: https://gitea.quitesimple.org/crtxcr/exile.h
- sqlite3: https://sqlite.org/index.html - sqlite3: https://sqlite.org/index.html
The first three are header-only libraries that are included as a git submodule. The others must The first three are header-only libraries that are included as a git submodule. The others must
be installed, e. g. by using your distributions standard method. be installed, e. g. by using your distributions standard method.

51
cache/mapcache.h vendored
View File

@ -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

30
cache/nocache.h vendored Normal file
View File

@ -0,0 +1,30 @@
#include "icache.h"
class NoCache : public ICache
{
public:
NoCache(std::string p)
{
}
virtual std::optional<std::string> get(std::string_view key) const
{
return {};
}
virtual void put(std::string_view key, std::string val)
{
return;
}
virtual void remove(std::string_view key)
{
return;
}
virtual void removePrefix(std::string_view prefix)
{
return;
}
virtual void clear()
{
return;
}
};

View File

@ -114,7 +114,7 @@ Config::Config(const std::map<std::string, std::string> &map)
this->templateprefix = "{qswiki:"; this->templateprefix = "{qswiki:";
this->max_payload_length = optional("max_payload_length", 10 * 1024 * 1024); this->max_payload_length = optional("max_payload_length", 60 * 1024 * 1024);
ConfigVariableResolver resolver{this->configmap}; ConfigVariableResolver resolver{this->configmap};
this->configVarResolver = resolver; this->configVarResolver = resolver;

View File

@ -15,6 +15,7 @@ class CategoryDao
virtual std::optional<Category> find(std::string name) = 0; virtual std::optional<Category> find(std::string name) = 0;
virtual void deleteCategory(std::string name) = 0; virtual void deleteCategory(std::string name) = 0;
virtual std::vector<Page> fetchMembers(std::string name, QueryOption o) = 0; virtual std::vector<Page> fetchMembers(std::string name, QueryOption o) = 0;
virtual ~CategoryDao() = default;
}; };
#endif // CATEGORYDAO_H #endif // CATEGORYDAO_H

View File

@ -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)
{ {

View File

@ -13,7 +13,7 @@
#include "permissionsdao.h" #include "permissionsdao.h"
class Database class Database
{ {
private: protected:
std::string connnectionstring; std::string connnectionstring;
public: public:

View File

@ -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()
{ {
} }

View File

@ -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;
}

View File

@ -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

View File

@ -9,6 +9,9 @@ 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;
}; };
#endif // PERMISSIONSDAO_H #endif // PERMISSIONSDAO_H

View File

@ -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);
}
}

View File

@ -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;
}; };

View File

@ -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

View File

@ -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);

View File

@ -10,6 +10,7 @@ class SessionDao
virtual void save(const Session &session) = 0; virtual void save(const Session &session) = 0;
virtual std::optional<Session> find(std::string token) = 0; virtual std::optional<Session> find(std::string token) = 0;
virtual void deleteSession(std::string token) = 0; virtual void deleteSession(std::string token) = 0;
virtual std::vector<Session> fetch() = 0;
virtual ~SessionDao() virtual ~SessionDao()
{ {
} }

63
database/sessiondaosqlite.cpp Normal file → Executable file
View File

@ -50,6 +50,29 @@ void SessionDaoSqlite::deleteSession(std::string token)
} }
} }
void SessionDaoSqlite::fillSession(int userid, Session &sess)
{
if(userid > -1)
{
UserDaoSqlite userDao{*this->db};
auto u = userDao.find(userid);
if(u)
{
sess.user = *u;
}
else
{
Logger::error() << "Session for non existent user";
throw DatabaseQueryException("Session for non existent user");
}
}
else
{
sess.user = User::Anonymous();
}
sess.loggedIn = userid != -1;
}
std::optional<Session> SessionDaoSqlite::find(std::string token) std::optional<Session> SessionDaoSqlite::find(std::string token)
{ {
Session result; Session result;
@ -62,25 +85,7 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
int userid; int userid;
q >> std::tie(userid, result.token, result.csrf_token, result.creation_time); q >> std::tie(userid, result.token, result.csrf_token, result.creation_time);
if(userid > -1) fillSession(userid, result);
{
UserDaoSqlite userDao{this->db};
auto u = userDao.find(userid);
if(u)
{
result.user = *u;
}
else
{
Logger::error() << "Session for non existent user";
throw DatabaseQueryException("Session for non existent user");
}
}
else
{
result.user = User::Anonymous();
}
result.loggedIn = userid != -1;
} }
catch(const sqlite::exceptions::no_rows &e) catch(const sqlite::exceptions::no_rows &e)
{ {
@ -92,3 +97,23 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
} }
return result; return result;
} }
std::vector<Session> SessionDaoSqlite::fetch()
{
std::vector<Session> result;
*db << "SELECT userid, token, csrf_token, strftime('%s', creationtime) FROM session" >>
[this, &result](int userid, std::string token, std::string csrf_token, time_t creationtime)
{
Session tmp;
tmp.csrf_token = csrf_token;
tmp.token = token;
tmp.creation_time = creationtime;
fillSession(userid, tmp);
result.push_back(tmp);
};
return result;
}

View File

@ -6,11 +6,15 @@
class SessionDaoSqlite : public SessionDao, protected SqliteDao class SessionDaoSqlite : public SessionDao, protected SqliteDao
{ {
private:
void fillSession(int userid, Session &sess);
public: public:
SessionDaoSqlite(); SessionDaoSqlite();
void save(const Session &session) override; void save(const Session &session) override;
std::optional<Session> find(std::string token) override; std::optional<Session> find(std::string token) override;
void deleteSession(std::string token) override; void deleteSession(std::string token) override;
std::vector<Session> fetch() override;
using SqliteDao::SqliteDao; using SqliteDao::SqliteDao;
}; };

View File

@ -18,23 +18,43 @@ 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 <atomic>
#include "sqlite.h" #include "sqlite.h"
#include "../logger.h"
#include "pagedaosqlite.h" #include "pagedaosqlite.h"
#include "revisiondaosqlite.h" #include "revisiondaosqlite.h"
#include "sessiondaosqlite.h" #include "sessiondaosqlite.h"
#include "sqlite_modern_cpp.h"
#include "userdaosqlite.h" #include "userdaosqlite.h"
#include "categorydaosqlite.h" #include "categorydaosqlite.h"
#include "exceptions.h"
#include "permissionsdaosqlite.h" #include "permissionsdaosqlite.h"
thread_local sqlite::database *Sqlite::db = nullptr;
std::atomic<int> instances = 0;
Sqlite::Sqlite(std::string path) : Database(path) Sqlite::Sqlite(std::string path) : Database(path)
{ {
this->db = std::make_shared<sqlite::database>(path); instances++;
if(instances.load() > 1)
*db << "PRAGMA journal_mode=WAL;"; {
std::cerr << "temporal (yeah, right) HACK... only one instance allowed" << std::endl;
abort();
}
} }
std::mutex dbmutex;
sqlite::database &Sqlite::database() const
{
if(Sqlite::db == nullptr)
{
sqlite::sqlite_config config;
config.flags = config.flags | sqlite::OpenFlags::FULLMUTEX;
std::lock_guard<std::mutex> dbguard(dbmutex);
Sqlite::db = new sqlite::database(this->connnectionstring, config);
*Sqlite::db << "PRAGMA journal_mode=WAL;";
*Sqlite::db << "PRAGMA busy_timeout=10000;";
}
return *Sqlite::db;
}
std::unique_ptr<RevisionDao> Sqlite::createRevisionDao() const std::unique_ptr<RevisionDao> Sqlite::createRevisionDao() const
{ {
return create<RevisionDaoSqlite>(); return create<RevisionDaoSqlite>();
@ -67,27 +87,20 @@ std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
void Sqlite::beginTransaction() void Sqlite::beginTransaction()
{ {
if(!inTransaction) *db << "begin;";
{
*db << "begin;";
inTransaction = true;
}
} }
void Sqlite::rollbackTransaction() void Sqlite::rollbackTransaction()
{ {
if(inTransaction) *db << "rollback;";
{
*db << "rollback;";
inTransaction = false;
}
} }
void Sqlite::commitTransaction() void Sqlite::commitTransaction()
{ {
if(inTransaction) *db << "commit;";
{ }
*db << "commit;";
inTransaction = false; Sqlite::~Sqlite()
} {
delete this->db;
} }

View File

@ -8,14 +8,15 @@
class Sqlite : public Database class Sqlite : public Database
{ {
private: private:
bool inTransaction = false; static thread_local sqlite::database *db;
std::shared_ptr<sqlite::database> db;
template <class T> std::unique_ptr<T> create() const template <class T> std::unique_ptr<T> create() const
{ {
return std::make_unique<T>(db); return std::make_unique<T>(database());
} }
sqlite::database &database() const;
public: public:
Sqlite(std::string path); Sqlite(std::string path);
std::unique_ptr<PageDao> createPageDao() const; std::unique_ptr<PageDao> createPageDao() const;
@ -27,6 +28,7 @@ class Sqlite : public Database
void beginTransaction(); void beginTransaction();
void commitTransaction(); void commitTransaction();
void rollbackTransaction(); void rollbackTransaction();
virtual ~Sqlite();
}; };
#endif // SQLITE_H #endif // SQLITE_H

View File

@ -12,20 +12,20 @@
class SqliteDao class SqliteDao
{ {
protected: protected:
std::shared_ptr<sqlite::database> db; sqlite::database *db = nullptr;
public: public:
SqliteDao() SqliteDao()
{ {
} }
SqliteDao(std::shared_ptr<sqlite::database> db) SqliteDao(sqlite::database &db)
{ {
this->db = db; this->db = &db;
} }
void setDb(std::shared_ptr<sqlite::database> db) void setDb(sqlite::database &db)
{ {
this->db = db; this->db = &db;
} }
inline void throwFrom(const sqlite::sqlite_exception &e) const inline void throwFrom(const sqlite::sqlite_exception &e) const
@ -37,6 +37,8 @@ class SqliteDao
bool execBool(sqlite::database_binder &binder) const; bool execBool(sqlite::database_binder &binder) const;
int execInt(sqlite::database_binder &binder) const; int execInt(sqlite::database_binder &binder) const;
virtual ~SqliteDao() = default;
}; };
#endif // SQLITEDAO_H #endif // SQLITEDAO_H

View File

@ -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
{ {

View File

@ -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);

View File

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

View File

@ -10,11 +10,12 @@ class DynamicContent
Template *templ; Template *templ;
Database *database; Database *database;
UrlProvider *urlProvider; UrlProvider *urlProvider;
Session *userSession;
std::string argument; std::string argument;
public: public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider); DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider, Session &session);
virtual std::string render() = 0; virtual std::string render() = 0;
virtual void setArgument(std::string argument) virtual void setArgument(std::string argument)
{ {

View File

@ -9,18 +9,20 @@ private:
Template *templ; Template *templ;
Database *db; Database *db;
UrlProvider *urlProvider; UrlProvider *urlProvider;
Session *session;
public: public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider) DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider, Session &session)
{ {
this->templ = &templ; this->templ = &templ;
this->db = &db; this->db = &db;
this->urlProvider = &urlProvider; this->urlProvider = &urlProvider;
this->session = &session;
} }
template <class T> inline std::shared_ptr<T> createDynamicContent() template <class T> inline std::shared_ptr<T> createDynamicContent()
{ {
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider); return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider, *this->session);
} }

View File

@ -6,14 +6,21 @@ std::string DynamicContentPostList::render()
auto categoryDao = this->database->createCategoryDao(); auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
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)
{ {
auto revision = revisionDao->getRevisionForPage(member.name, 1); Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
pageList.push_back({member.name, revision->timestamp}); .value_or(this->userSession->user.permissions);
if(perms.canRead()) /* TODO: Maybe add canList() */
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, revision->timestamp});
}
} }
std::sort(pageList.begin(), pageList.end(), 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::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });

View File

@ -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();
}

View File

@ -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

View File

@ -1,5 +1,8 @@
#ifndef HTTPGATEWAY_H #ifndef HTTPGATEWAY_H
#define HTTPGATEWAY_H #define HTTPGATEWAY_H
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 65536
#include <httplib.h> #include <httplib.h>
#include "gatewayinterface.h" #include "gatewayinterface.h"
#include "../requestworker.h" #include "../requestworker.h"

View File

@ -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"));
@ -98,7 +98,10 @@ Response Handler::handle(const Request &r)
Permissions Handler::effectivePermissions(std::string page) Permissions Handler::effectivePermissions(std::string page)
{ {
return this->database->createPermissionsDao() Permissions &userPerms = this->userSession->user.permissions;
->find(page, this->userSession->user.login) if(userPerms.isAdmin())
.value_or(this->userSession->user.permissions); {
return userPerms;
}
return this->database->createPermissionsDao()->find(page, this->userSession->user.login).value_or(userPerms);
} }

View File

@ -1,19 +1,19 @@
#include "handlerfeedgenerator.h" #include "handlerfeedgenerator.h"
#include "../parser.h"
#include "../revisionrenderer.h" #include "../revisionrenderer.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries( std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories) std::vector<std::string> categories)
{ {
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
auto permissionDao = this->database->createPermissionsDao();
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; };
std::set<Page, decltype(comppage)> members (comppage); std::set<Page, decltype(comppage)> members(comppage);
if(categories.empty()) if(categories.empty())
{ {
auto pages = pageDao->getPageList(option); auto pages = pageDao->getPageList(option);
@ -34,8 +34,16 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch
} }
for(const Page &member : members) for(const Page &member : members)
{ {
auto revision = revisionDao->getRevisionForPage(member.name, 1).value(); if(member.feedlisted)
result.push_back({member, revision}); {
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).value();
result.push_back({member, revision});
}
}
} }
std::sort(result.begin(), result.end(), std::sort(result.begin(), result.end(),
[](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; }); [](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; });
@ -68,7 +76,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
subtitle = "All pages"; subtitle = "All pages";
} }
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
for(const EntryRevisionPair &entry : entries) for(const EntryRevisionPair &entry : entries)
{ {
@ -81,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");
@ -89,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();
} }

View File

@ -24,6 +24,7 @@ SOFTWARE.
#include "../parser.h" #include "../parser.h"
#include "../revisionrenderer.h" #include "../revisionrenderer.h"
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page) bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{ {
return effectivePermissions(page).canEdit(); return effectivePermissions(page).canEdit();
@ -56,7 +57,8 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{ {
if(!effectivePermissions(from).canRead()) if(!effectivePermissions(from).canRead())
{ {
return this->errorResponse("Permission denied", "No access permissions, so you can't use this page as a template"); return this->errorResponse("Permission denied",
"No access permissions, so you can't use this page as a template");
} }
body = revisiondao->getCurrentForPage(from)->content; body = revisiondao->getCurrentForPage(from)->content;
} }
@ -74,9 +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);
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)
@ -91,10 +111,48 @@ 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)
{
auto splitted = utils::split(perm, '|');
if(splitted.size() != 2)
{
return this->errorResponse("Invalid command", "permissions command is misformated");
}
auto currentPermission = permissionDao->find(pagename, splitted[0]);
Permissions newPermissions = Permissions{splitted[1]};
if(!currentPermission || newPermissions != currentPermission.value())
{
if(!this->userSession->user.permissions.canSetPagePerms())
{
this->database->rollbackTransaction();
return errorResponse("Permission denied",
"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;
@ -115,6 +173,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
} }
catch(const DatabaseException &e) catch(const DatabaseException &e)
{ {
this->database->rollbackTransaction();
Logger::debug() << "Error saving revision: " << e.what(); Logger::debug() << "Error saving revision: " << e.what();
return errorResponse("Database error", "A database error occured while trying to save this revision"); return errorResponse("Database error", "A database error occured while trying to save this revision");
} }
@ -129,7 +188,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
TemplatePage templatePage = this->templ->getPage("page_creation_preview"); TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("actionurl", urlProvider->editPage(pagename));
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent)); templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent); templatePage.setVar("content", newContent);

View File

@ -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>";
@ -138,8 +138,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
Response result; Response result;
result.setStatus(200); result.setStatus(200);
RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
std::string customtitle = parser.extractCommand("pagetitle", revision->content); std::string customtitle = parser.extractCommand("pagetitle", revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle); std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);

View File

@ -15,7 +15,10 @@ 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<Headline> extractHeadlines(const std::string &content) const = 0; virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0;
virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const
{ {

View File

@ -7,8 +7,8 @@ class Logger
private: private:
class LogEntry class LogEntry
{ {
bool headerSent; bool headerSent = false;
std::ostream *out; std::ostream *out = nullptr;
std::string prefix; std::string prefix;
public: public:

2
page.h
View File

@ -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;
}; };

View File

@ -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)
@ -82,6 +81,34 @@ 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> result;
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
std::string_view view = content;
size_t pos = 0;
while((pos = view.find(cmd)) != std::string::npos)
{
view.remove_prefix(pos);
view.remove_prefix(cmd.size());
if((pos = view.find(cmdend)) != std::string::npos)
{
result.emplace_back(view.substr(0, pos));
}
}
return result;
}
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{ {
std::string linktag = match.str(1); std::string linktag = match.str(1);
@ -148,24 +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|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|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", "code", "blockquote"};
content = parse(pagedao, provider, content, callback); std::string newlines = match.str(4);
if(newlines == "\r\n")
{
newlines = "<br>";
}
if(tag == "br")
{
return std::string("<br>");
}
if(tag != "code" && tag != "blockquote")
{
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")
{ {
@ -173,7 +224,11 @@ 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")
{
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
} }
return callback(tag, content); return callback(tag, content);
}); });

View File

@ -9,7 +9,9 @@ class Parser : public IParser
std::string processImage(std::smatch &match) const; std::string processImage(std::smatch &match) const;
public: public:
std::string extractCommand(std::string cmdname, const std::string &content) const; 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::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;
std::vector<std::string> extractCategories(const std::string &content) const override; std::vector<std::string> extractCategories(const std::string &content) const override;
using IParser::parse; using IParser::parse;

View File

@ -20,7 +20,8 @@ SOFTWARE.
*/ */
#include "permissions.h" #include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ}, static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING},
{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT}, {"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY}, {"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY}, {"can_global_history", PERM_CAN_GLOBAL_HISTORY},
@ -29,7 +30,8 @@ static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_create", PERM_CAN_CREATE}, {"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST}, {"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE}, {"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}}; {"can_search", PERM_CAN_SEARCH},
{"can_set_page_perms", PERM_CAN_SET_PAGE_PERMS}};
Permissions::Permissions(int permissions) Permissions::Permissions(int permissions)
{ {

View File

@ -1,6 +1,7 @@
#ifndef PERMISSIONS_H #ifndef PERMISSIONS_H
#define PERMISSIONS_H #define PERMISSIONS_H
#define PERM_CAN_NOTHING 0
#define PERM_CAN_READ 1 << 0 #define PERM_CAN_READ 1 << 0
#define PERM_CAN_EDIT 1 << 1 #define PERM_CAN_EDIT 1 << 1
#define PERM_CAN_PAGE_HISTORY 1 << 2 #define PERM_CAN_PAGE_HISTORY 1 << 2
@ -11,6 +12,8 @@
#define PERM_CAN_SEE_CATEGORY_LIST 1 << 7 #define PERM_CAN_SEE_CATEGORY_LIST 1 << 7
#define PERM_CAN_SEE_LINKS_HERE 1 << 8 #define PERM_CAN_SEE_LINKS_HERE 1 << 8
#define PERM_CAN_SEARCH 1 << 9 #define PERM_CAN_SEARCH 1 << 9
#define PERM_CAN_SET_PAGE_PERMS 1 << 10
#define PERM_IS_ADMIN (1L<<31)-1
#include <string> #include <string>
#include <map> #include <map>
@ -54,10 +57,16 @@ class Permissions
return this->permissions; return this->permissions;
} }
bool canNothing() const
{
return this->permissions == PERM_CAN_NOTHING;
}
bool canRead() const bool canRead() const
{ {
return this->permissions & PERM_CAN_READ; return this->permissions & PERM_CAN_READ;
} }
bool canEdit() const bool canEdit() const
{ {
return this->permissions & PERM_CAN_EDIT; return this->permissions & PERM_CAN_EDIT;
@ -95,12 +104,27 @@ class Permissions
return this->permissions & PERM_CAN_SEE_PAGE_LIST; return this->permissions & PERM_CAN_SEE_PAGE_LIST;
} }
bool canSetPagePerms() const
{
return this->permissions & PERM_CAN_SET_PAGE_PERMS;
}
bool isAdmin() const
{
return this->permissions == PERM_IS_ADMIN;
}
std::string toString() const std::string toString() const
{ {
return Permissions::toString(this->permissions); return Permissions::toString(this->permissions);
} }
static std::string toString(int perms); static std::string toString(int perms);
bool operator==(const Permissions &o) const
{
return this->permissions == o.permissions;
}
}; };
#endif // PERMISSIONS_H #endif // PERMISSIONS_H

View File

@ -31,12 +31,13 @@ SOFTWARE.
#include "handlers/handlerfactory.h" #include "handlers/handlerfactory.h"
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "config.h" #include "config.h"
#include "template.h"
#include "session.h" #include "session.h"
#include "template.h"
#include "logger.h" #include "logger.h"
#include "urlprovider.h" #include "urlprovider.h"
#include "requestworker.h" #include "requestworker.h"
#include "cache/fscache.h" #include "cache/fscache.h"
#include "cache/nocache.h"
#include "sandbox/sandboxfactory.h" #include "sandbox/sandboxfactory.h"
#include "cli.h" #include "cli.h"
#include "cliconsole.h" #include "cliconsole.h"
@ -68,12 +69,41 @@ static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version",
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver) 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 == "")
{
return std::make_unique<StringCache>();
}
return std::make_unique<FsCache>(path); return std::make_unique<FsCache>(path);
} }
std::thread background_worker;
void start_background_worker(Database &database, Config &config)
{
background_worker = std::thread(
[&database, &config]()
{
while(true)
{
Logger::log() << "Executing background worker";
auto sessionDao = database.createSessionDao();
auto sessionList = sessionDao->fetch();
time_t now = time(NULL);
for(Session &sess : sessionList)
{
if(now - sess.creation_time > config.session_max_lifetime)
{
sessionDao->deleteSession(sess.token);
}
}
std::this_thread::sleep_for(std::chrono::hours(1));
}
});
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
@ -136,6 +166,7 @@ int main(int argc, char **argv)
Logger::setStream(&logstream); Logger::setStream(&logstream);
auto database = createDatabase(config); auto database = createDatabase(config);
std::string socketPath = config.configVarResolver.getConfig("socketpath"); std::string socketPath = config.configVarResolver.getConfig("socketpath");
CLIHandler cliHandler(config, *database); CLIHandler cliHandler(config, *database);
@ -158,6 +189,8 @@ int main(int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
start_background_worker(*database.get(), config);
CLIServer cliServer{cliHandler}; CLIServer cliServer{cliHandler};
if(!cliServer.detachServer(socketPath)) if(!cliServer.detachServer(socketPath))
{ {

View File

@ -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{};
} }
@ -42,8 +50,10 @@ std::string RevisionRenderer::renderContent(std::string content)
{ {
dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content); dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content);
dynamicVarsMap["createdon"] = utils::toISODate(time(NULL)); dynamicVarsMap["createdon"] = utils::toISODate(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);
} }
@ -54,14 +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);
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);
} }

View File

@ -17,7 +17,7 @@ private:
Parser parser; Parser parser;
public: public:
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider) :dynamicContentFactory(templ, db, urlProvider) RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider, Session &session) :dynamicContentFactory(templ, db, urlProvider, session)
{ {
this->db = &db; this->db = &db;
this->urlProvider = &urlProvider; this->urlProvider = &urlProvider;

View File

@ -49,16 +49,17 @@ 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;
policy->no_new_privs = 1; policy->no_new_privs = 1;
policy->mount_path_policies_to_chroot = 1; policy->mount_path_policies_to_chroot = 1;
policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_CPATH | policy->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread");
EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_UNIX |
EXILE_SYSCALL_VOW_THREAD;
if(exile_enable_policy(policy) != 0) if(exile_enable_policy(policy) != 0)
{ {
Logger::error() << "Sandbox: Activation of exile failed!"; Logger::error() << "Sandbox: Activation of exile failed!";

View File

@ -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),

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

View File

@ -4,8 +4,5 @@
<li style="font-size: 10pt">Powered by qswiki</li> <li style="font-size: 10pt">Powered by qswiki</li>
</ul> </ul>
</footer> </footer>
<script>
{qswiki:include:js_session_refresh}
</script>
</body> </body>
</html> </html>

View File

@ -1,5 +0,0 @@
function refreshSession()
{
fetch(new Request("{qswiki:config:refreshsessionurl}"));
}
setInterval(refreshSession, 60*2*1000);

View File

@ -5,8 +5,9 @@
<li style="font-size: 10pt">Powered by qswiki</li> <li style="font-size: 10pt">Powered by qswiki</li>
</ul> </ul>
</footer> </footer>
<script src="{qswiki:config:highlightjspath}"></script>
<script> <script>
{qswiki:include:js_session_refresh} hljs.highlightAll();
</script> </script>
</body> </body>
</html> </html>

View File

@ -3,6 +3,8 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="{qswiki:config:csspath}"> <link rel="stylesheet" type="text/css" href="{qswiki:config:csspath}">
<link rel="stylesheet" href="{qswiki:config:highlightjsstyle}">
<title>{qswiki:var:title}</title> <title>{qswiki:var:title}</title>
<body> <body>
<nav> <nav>