Compare commits

..

No commits in common. "master" and "WIP/cpp20" have entirely different histories.

102 changed files with 361 additions and 1716 deletions

2
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

@ -4,6 +4,6 @@
[submodule "submodules/cpp-httplib"] [submodule "submodules/cpp-httplib"]
path = submodules/cpp-httplib path = submodules/cpp-httplib
url = https://github.com/yhirose/cpp-httplib url = https://github.com/yhirose/cpp-httplib
[submodule "submodules/exile.h"] [submodule "submodules/qssb.h"]
path = submodules/exile.h path = submodules/qssb.h
url = https://gitea.quitesimple.org/crtxcr/exile.h.git url = https://gitea.quitesimple.org/crtxcr/qssb.h.git

View File

@ -1,14 +1,12 @@
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
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h
#CFIFLAGS=-fsanitize=cfi -fvisibility=hidden -fsanitize=cfi -flto CXX=g++
#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
SOURCES=$(wildcard *.cpp) SOURCES=$(wildcard *.cpp)
SOURCES+=$(wildcard gateway/*.cpp) SOURCES+=$(wildcard gateway/*.cpp)
@ -16,7 +14,6 @@ SOURCES+=$(wildcard handlers/*.cpp)
SOURCES+=$(wildcard database/*.cpp) SOURCES+=$(wildcard database/*.cpp)
SOURCES+=$(wildcard cache/*.cpp) SOURCES+=$(wildcard cache/*.cpp)
SOURCES+=$(wildcard sandbox/*.cpp) SOURCES+=$(wildcard sandbox/*.cpp)
SOURCES+=$(wildcard dynamic/*.cpp)
HEADERS=$(wildcard *.h) HEADERS=$(wildcard *.h)
HEADERS+=$(wildcard gateway/*.h) HEADERS+=$(wildcard gateway/*.h)
@ -24,7 +21,7 @@ HEADERS+=$(wildcard handlers/*.h)
HEADERS+=$(wildcard database/*.h) HEADERS+=$(wildcard database/*.h)
HEADERS+=$(wildcard cache/*.h) HEADERS+=$(wildcard cache/*.h)
HEADERS+=$(wildcard sandbox/*.h) HEADERS+=$(wildcard sandbox/*.h)
HEADERS+=$(wildcard dynamic/*.h)
OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES)) OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES))
WIKIOBJECTS=$(filter-out test.o, $(OBJECTS)) WIKIOBJECTS=$(filter-out test.o, $(OBJECTS))
@ -51,12 +48,8 @@ profile: LDFLAGS+= -pg
release: qswiki release: qswiki
profile: qswiki profile: qswiki
qswiki: $(WIKIOBJECTS)
exile.o: submodules/exile.h/exile.c $(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
$(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o
qswiki: $(WIKIOBJECTS) exile.o
$(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
@ -70,6 +63,6 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS)
version.o:version.cpp version.o:version.cpp
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $< $(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
clean: clean:
rm -f exile.o $(OBJECTS) $(DEPENDS) rm -f $(OBJECTS) $(DEPENDS)

View File

@ -1,26 +1,23 @@
# 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++.
## Dude... why? History
====
tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already A couple of years ago, I wanted to setup a personal wiki on my raspberry
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 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 simply decided I would write one in C. Yes, wikis that I could use, I 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 print your HTML to stdout.And indeed, that would have been more than enough for my use cases. 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.
@ -28,55 +25,55 @@ 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 initially wanted something quick and given that unmaintainable. Furthermore, I 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 ========
Some essential features are lacking, such as a diff between revisions, To be fair, at this point it doesn't even have a "diff" between revisions
user registration UI, etc. yet and does not have features that would make you prefer it over other
wikis.
It doesn't compete with any other software anyway.
- CGI - CGI
- HTTP server using the header only library [cpp-httplib](https://github.com/yhirose/cpp-httplib). It's more - HTTP server using the header only library 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](https://github.com/SqliteModernCpp) sqlite_modern_cpp
- 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 ========
to restrict access to the files the wiki needs. It doesn't have access to other paths On Linux namespaces are used to restrict the process to only access
in the system and the system calls that the qswiki process can make are restricted. files it needs. It doesn't have access to other paths in the system.
In addition, Seccomp is used to restrict the syscalls the qswiki process
As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS can call. As for "web security", all POST requests are centrally
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 - qssb.h: https://gitea.quitesimple.org/crtxcr/qssb.h
- libseccomp: https://github.com/seccomp/libseccomp
- 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

2
cache/fscache.cpp vendored
View File

@ -46,7 +46,7 @@ void FsCache::removePrefix(std::string_view prefix)
// TODO: lock dir // TODO: lock dir
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path})) for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path}))
{ {
if(std::string_view(entry.path().filename().c_str()).starts_with(prefix)) if(std::string_view(entry.path().filename().c_str()).starts_with(prefix) == 0)
{ {
std::filesystem::remove_all(entry); std::filesystem::remove_all(entry);
} }

51
cache/mapcache.h vendored
View File

@ -4,14 +4,12 @@
#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::unordered_map<std::string, T> cache; std::map<std::string, T> cache;
mutable std::shared_mutex sharedMutex; mutable std::shared_mutex sharedMutex;
public: public:
@ -35,53 +33,6 @@ 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
View File

@ -1,30 +0,0 @@
#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

@ -141,9 +141,10 @@ std::pair<bool, std::string> CLIHandler::page_list([[maybe_unused]] const std::v
QueryOption o; QueryOption o;
auto result = pageDao->getPageList(o); auto result = pageDao->getPageList(o);
std::stringstream stream; std::stringstream stream;
for(Page &page : result) for(std::string pagename : result)
{ {
stream << page.name << " " << page.pageid << " " << std::string(page.listed ? "listed" : "unlisted") << std::endl; Page p = pageDao->find(pagename).value();
stream << p.name << " " << p.pageid << " " << std::string(p.listed ? "listed" : "unlisted") << std::endl;
} }
return {true, stream.str()}; return {true, stream.str()};
} }
@ -270,9 +271,9 @@ std::pair<bool, std::string> CLIHandler::category_show(const std::vector<std::st
auto categoryDao = this->db->createCategoryDao(); auto categoryDao = this->db->createCategoryDao();
auto members = categoryDao->fetchMembers(args.at(0), QueryOption{}); auto members = categoryDao->fetchMembers(args.at(0), QueryOption{});
std::stringstream stream; std::stringstream stream;
for(Page &member : members) for(std::string &member : members)
{ {
stream << member.name << std::endl; stream << member << std::endl;
} }
return {true, stream.str()}; return {true, stream.str()};
} }

View File

@ -24,7 +24,6 @@ SOFTWARE.
#include "config.h" #include "config.h"
#include "permissions.h" #include "permissions.h"
#include "varreplacer.h" #include "varreplacer.h"
std::string Config::required(const std::string &key) std::string Config::required(const std::string &key)
{ {
auto it = this->configmap.find(key); auto it = this->configmap.find(key);
@ -78,16 +77,13 @@ Config::Config(const std::map<std::string, std::string> &map)
this->templatepath = required("templatepath"); this->templatepath = required("templatepath");
this->urls.linkallcats = required("linkallcats"); this->urls.linkallcats = required("linkallcats");
this->urls.linkallpages = required("linkallpages"); this->urls.linkallpages = required("linkallpages");
this->urls.linkallpagesrendertype = required ("linkallpagesrendertype");
this->urls.linkcategory = required("linkcategory"); this->urls.linkcategory = required("linkcategory");
this->urls.linkcategoryrendertype = required("linkcategoryrendertype");
this->urls.linkdelete = required("linkdelete"); this->urls.linkdelete = required("linkdelete");
this->urls.linkedit = required("linkedit"); this->urls.linkedit = required("linkedit");
this->urls.linkhistory = required("linkhistory"); this->urls.linkhistory = required("linkhistory");
this->urls.linkindex = required("linkindex"); this->urls.linkindex = required("linkindex");
this->urls.linklogout = required("linklogout"); this->urls.linklogout = required("linklogout");
this->urls.linkpage = required("linkpage"); this->urls.linkpage = required("linkpage");
this->urls.linkpagebytitle = required("linkpagebytitle");
this->urls.linkrecent = required("linkrecent"); this->urls.linkrecent = required("linkrecent");
this->urls.linkrevision = required("linkrevision"); this->urls.linkrevision = required("linkrevision");
this->urls.linksettings = required("linksettings"); this->urls.linksettings = required("linksettings");
@ -100,8 +96,6 @@ Config::Config(const std::map<std::string, std::string> &map)
this->urls.deletionurl = required("deletionurl"); this->urls.deletionurl = required("deletionurl");
this->urls.adminregisterurl = required("adminregisterurl"); this->urls.adminregisterurl = required("adminregisterurl");
this->urls.usersettingsurl = required("usersettingsurl"); this->urls.usersettingsurl = required("usersettingsurl");
this->urls.rooturl = required("rooturl");
this->urls.atomurl = required("atomurl");
this->connectionstring = required("connectionstring"); this->connectionstring = required("connectionstring");
this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256); this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256);
@ -114,7 +108,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", 60 * 1024 * 1024); this->max_payload_length = optional("max_payload_length", 10 * 1024 * 1024);
ConfigVariableResolver resolver{this->configmap}; ConfigVariableResolver resolver{this->configmap};
this->configVarResolver = resolver; this->configVarResolver = resolver;

View File

@ -23,11 +23,9 @@ struct ConfigUrls
std::string linkindex; std::string linkindex;
std::string linkrecent; std::string linkrecent;
std::string linkallpages; std::string linkallpages;
std::string linkallpagesrendertype;
std::string linkallcats; std::string linkallcats;
std::string linkshere; std::string linkshere;
std::string linkpage; std::string linkpage;
std::string linkpagebytitle;
std::string linkrevision; std::string linkrevision;
std::string linkhistory; std::string linkhistory;
std::string linkedit; std::string linkedit;
@ -35,7 +33,6 @@ struct ConfigUrls
std::string linkdelete; std::string linkdelete;
std::string linklogout; std::string linklogout;
std::string linkcategory; std::string linkcategory;
std::string linkcategoryrendertype;
std::string loginurl; std::string loginurl;
std::string linkrecentsort; std::string linkrecentsort;
std::string actionurl; std::string actionurl;
@ -44,8 +41,6 @@ struct ConfigUrls
std::string linkhistorysort; std::string linkhistorysort;
std::string adminregisterurl; std::string adminregisterurl;
std::string usersettingsurl; std::string usersettingsurl;
std::string rooturl;
std::string atomurl;
}; };
class ConfigVariableResolver class ConfigVariableResolver

View File

@ -5,7 +5,7 @@
#include <optional> #include <optional>
#include "queryoption.h" #include "queryoption.h"
#include "../category.h" #include "../category.h"
#include "../page.h"
class CategoryDao class CategoryDao
{ {
public: public:
@ -14,8 +14,7 @@ class CategoryDao
virtual std::vector<std::string> fetchList(QueryOption o) = 0; virtual std::vector<std::string> fetchList(QueryOption o) = 0;
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<std::string> fetchMembers(std::string name, QueryOption o) = 0;
virtual ~CategoryDao() = default;
}; };
#endif // CATEGORYDAO_H #endif // CATEGORYDAO_H

View File

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

View File

@ -3,13 +3,12 @@
#include "categorydao.h" #include "categorydao.h"
#include "sqlitedao.h" #include "sqlitedao.h"
#include "../page.h"
class CategoryDaoSqlite : public CategoryDao, protected SqliteDao class CategoryDaoSqlite : public CategoryDao, protected SqliteDao
{ {
public: public:
CategoryDaoSqlite(); CategoryDaoSqlite();
std::vector<std::string> fetchList(QueryOption o) override; std::vector<std::string> fetchList(QueryOption o) override;
std::vector<Page> fetchMembers(std::string name, QueryOption o) override; std::vector<std::string> fetchMembers(std::string name, QueryOption o) override;
void save(const Category &c) override; void save(const Category &c) override;
void deleteCategory(std::string name) override; void deleteCategory(std::string name) override;
std::optional<Category> find(std::string name) override; std::optional<Category> find(std::string name) override;

View File

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

View File

@ -13,9 +13,8 @@ class PageDao
virtual bool exists(std::string page) const = 0; virtual bool exists(std::string page) const = 0;
virtual bool exists(unsigned int id) const = 0; virtual bool exists(unsigned int id) const = 0;
virtual std::optional<Page> find(std::string name) = 0; virtual std::optional<Page> find(std::string name) = 0;
virtual std::optional<Page> findByTitle(std::string title) = 0;
virtual std::optional<Page> find(unsigned int id) = 0; virtual std::optional<Page> find(unsigned int id) = 0;
virtual std::vector<Page> getPageList(QueryOption option) = 0; virtual std::vector<std::string> getPageList(QueryOption option) = 0;
virtual std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) = 0; virtual std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) = 0;
virtual void deletePage(std::string page) = 0; virtual void deletePage(std::string page) = 0;
virtual void save(const Page &page) = 0; virtual void save(const Page &page) = 0;
@ -23,8 +22,6 @@ 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

@ -52,43 +52,15 @@ std::optional<Page> PageDaoSqlite::find(std::string name)
} }
} }
std::optional<Page> PageDaoSqlite::findByTitle(std::string title)
{
Page result;
try
{
auto ps =
*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)
{
return {};
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return result;
}
std::optional<Page> PageDaoSqlite::find(unsigned int id) std::optional<Page> PageDaoSqlite::find(unsigned int id)
{ {
Page result; Page result;
result.pageid = id; result.pageid = id;
try try
{ {
auto ps = auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?";
*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, result.feedlisted, ps << id >> std::tie(result.name, result.current_revision, result.listed);
result.parentpage);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -117,7 +89,6 @@ void PageDaoSqlite::deletePage(std::string page)
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
*db << "ROLLBACK";
throwFrom(e); throwFrom(e);
} }
} }
@ -126,44 +97,30 @@ void PageDaoSqlite::save(const Page &page)
{ {
try try
{ {
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, listed, feedlisted, parent) VALUES((SELECT " *db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE 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.current_revision << page.listed;
<< page.feedlisted << page.parentpage;
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
throwFrom(e); throwFrom(e);
} }
} }
std::vector<std::string> PageDaoSqlite::getPageList(QueryOption option)
std::vector<Page> PageDaoSqlite::getPageList(QueryOption option)
{ {
std::vector<Page> result; std::vector<std::string> result;
try try
{ {
std::string queryOption = SqliteQueryOption(option) std::string queryOption = SqliteQueryOption(option)
.setOrderByColumn("lower(name)") .setOrderByColumn("lower(name)")
.setListedColumnName("listed") .setVisibleColumnName("visible")
.setPrependWhere(true) .setPrependWhere(true)
.build(); .build();
std::string query = "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE " std::string query = "SELECT name FROM page " + queryOption;
"id = parent) FROM page " +
queryOption; *db << query >> [&](std::string name) { result.push_back(name); };
*db << query >> [&](unsigned int pageid, std::string name, std::string title, unsigned int current_revision,
bool listed, bool feedlisted, std::string parent)
{
Page tmp;
tmp.pageid = pageid;
tmp.name = name;
tmp.title = title;
tmp.current_revision = current_revision;
tmp.listed = listed;
tmp.feedlisted = feedlisted;
tmp.parentpage = parent;
result.push_back(tmp);
};
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -226,8 +183,7 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
auto query = auto query =
*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? " *db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? "
<< ftsEscape(name); << ftsEscape(name);
query >> [&](std::string pagename) query >> [&](std::string pagename) {
{
SearchResult sresult; SearchResult sresult;
sresult.pagename = pagename; sresult.pagename = pagename;
sresult.query = name; sresult.query = name;
@ -274,11 +230,3 @@ 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

@ -20,16 +20,13 @@ class PageDaoSqlite : public PageDao, protected SqliteDao
bool exists(std::string name) const override; bool exists(std::string name) const override;
void save(const Page &page) override; void save(const Page &page) override;
std::optional<Page> find(std::string name) override; std::optional<Page> find(std::string name) override;
std::optional<Page> findByTitle(std::string title) override;
std::optional<Page> find(unsigned int id) override; std::optional<Page> find(unsigned int id) override;
std::vector<Page> getPageList(QueryOption option) override; std::vector<std::string> getPageList(QueryOption option) override;
std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) override; std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) override;
using SqliteDao::SqliteDao; using SqliteDao::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,9 +9,6 @@ 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,16 +59,3 @@ 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,7 +10,6 @@ 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 includeUnlisted = true; bool includeInvisible = 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)
.setListedColumnName("page.listed") .setVisibleColumnName("page.visible")
.setOrderByColumn("creationtime") .setOrderByColumn("creationtime")
.build(); .build();
auto query = auto query =
@ -61,8 +61,7 @@ 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;
@ -92,7 +91,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)
.setListedColumnName("page.listed") .setVisibleColumnName("page.visible")
.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, "
@ -102,8 +101,7 @@ 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;
@ -131,9 +129,9 @@ 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 " "strftime('%s',creationtime), page, revisionid FROM revision WHERE page = (SELECT id FROM "
"revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid"; "page WHERE name = ? ) AND revisionid = (SELECT lastrevision FROM page WHERE name = ?)";
query << pagename; query << pagename << 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);
} }
@ -157,8 +155,7 @@ 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 " "page, revisionid FROM revision WHERE page = (SELECT id FROM page WHERE name = ? ) AND revisionid = ?";
"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,7 +10,6 @@ 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 Executable file → Normal file
View File

@ -50,29 +50,6 @@ 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;
@ -85,7 +62,25 @@ 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);
fillSession(userid, result); if(userid > -1)
{
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)
{ {
@ -97,23 +92,3 @@ 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,15 +6,11 @@
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,43 +18,23 @@ 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)
{ {
instances++; this->db = std::make_shared<sqlite::database>(path);
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>();
@ -86,21 +66,28 @@ 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()
{ {
*db << "commit;"; if(inTransaction)
}
Sqlite::~Sqlite()
{ {
delete this->db; *db << "commit;";
inTransaction = false;
}
} }

View File

@ -8,15 +8,14 @@
class Sqlite : public Database class Sqlite : public Database
{ {
private: private:
static thread_local sqlite::database *db; bool inTransaction = false;
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>(database()); return std::make_unique<T>(db);
} }
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;
@ -28,7 +27,6 @@ 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:
sqlite::database *db = nullptr; std::shared_ptr<sqlite::database> db;
public: public:
SqliteDao() SqliteDao()
{ {
} }
SqliteDao(sqlite::database &db) SqliteDao(std::shared_ptr<sqlite::database> db)
{ {
this->db = &db; this->db = db;
} }
void setDb(sqlite::database &db) void setDb(std::shared_ptr<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,8 +37,6 @@ 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::setListedColumnName(std::string name) SqliteQueryOption &SqliteQueryOption::setVisibleColumnName(std::string name)
{ {
this->listedColumnName = name; this->visibleColumnName = name;
return *this; return *this;
} }
@ -50,9 +50,9 @@ std::string SqliteQueryOption::build()
{ {
result += "WHERE "; result += "WHERE ";
} }
if(!o.includeUnlisted && !this->listedColumnName.empty()) if(!o.includeInvisible && !this->visibleColumnName.empty())
{ {
result += this->listedColumnName + " = 1"; result += this->visibleColumnName + " = 1";
} }
else else
{ {

View File

@ -7,7 +7,7 @@ class SqliteQueryOption
{ {
private: private:
QueryOption o; QueryOption o;
std::string listedColumnName; std::string visibleColumnName;
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 &setListedColumnName(std::string name); SqliteQueryOption &setVisibleColumnName(std::string name);
SqliteQueryOption &setPrependWhere(bool b); SqliteQueryOption &setPrependWhere(bool b);

View File

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

View File

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

View File

@ -1,30 +0,0 @@
#ifndef DYNAMICCONTENTFACTORY_H
#define DYNAMICCONTENTFACTORY_H
#include "dynamiccontent.h"
class DynamicContentFactory
{
private:
Template *templ;
Database *db;
UrlProvider *urlProvider;
Session *session;
public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider, Session &session)
{
this->templ = &templ;
this->db = &db;
this->urlProvider = &urlProvider;
this->session = &session;
}
template <class T> inline std::shared_ptr<T> createDynamicContent()
{
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider, *this->session);
}
};
#endif // DYNAMICCONTENTFACTORY_H_INCLUDED

View File

@ -1,11 +0,0 @@
#include "dynamiccontentgetvar.h"
std::string DynamicContentGetVar::render()
{
return (*this->map)[this->argument];
}
void DynamicContentGetVar::setMap(std::map<std::string, std::string> &map)
{
this->map = &map;
}

View File

@ -1,19 +0,0 @@
#ifndef DYNAMICCONTENTGETVAR_H
#define DYNAMICCONTENTGETVAR_H
#include "dynamiccontent.h"
class DynamicContentGetVar : public DynamicContent
{
private:
std::map<std::string, std::string> *map;
public:
using DynamicContent::DynamicContent;
// DynamicContent interface
public:
std::string render();
void setMap(std::map<std::string, std::string> &map);
};
#endif // DYNAMICCONTENTGETVAR_H

View File

@ -1,16 +0,0 @@
#include "dynamiccontentincludepage.h"
#include "../parser.h"
std::string DynamicContentIncludePage::render()
{
auto revisionDao = this->database->createRevisionDao();
auto rev = revisionDao->getCurrentForPage(this->argument);
if(rev)
{
/* Quite dirty */
if(rev->content.find("[cmd:allowinclude]1[") != std::string::npos)
{
return rev->content;
}
}
return {};
}

View File

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

View File

@ -1,46 +0,0 @@
#include <chrono>
#include "dynamiccontentpostlist.h"
std::string DynamicContentPostList::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 = false;
auto members = categoryDao->fetchMembers(this->argument, 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()) /* 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::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });
std::string postListBegin = this->templ->loadResolvedPart("dynamic/postlistbegin");
std::string postListEnd = this->templ->loadResolvedPart("dynamic/postlistend");
std::string postLink = this->templ->loadResolvedPart("dynamic/postlistlink");
std::stringstream stream;
stream << postListBegin;
for(auto &pair : pageList)
{
std::string link = this->urlProvider->page(pair.first);
std::string date = utils::toISODate(pair.second);
Varreplacer replacer{"{"};
replacer.addKeyValue("url", link);
replacer.addKeyValue("date", date);
replacer.addKeyValue("title", pageDao->find(pair.first)->title);
stream << replacer.parse(postLink);
}
stream << postListEnd;
return stream.str();
}

View File

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

View File

@ -1,21 +0,0 @@
#include "dynamiccontentsetvar.h"
std::string DynamicContentSetVar::render()
{
auto result = utils::split(this->argument, '=');
if(result.size() == 2)
{
this->map->emplace(std::make_pair(result[0], result[1]));
}
return {};
}
void DynamicContentSetVar::setArgument(std::string argument)
{
this->argument = argument;
}
void DynamicContentSetVar::setMap(std::map<std::string, std::string> &map)
{
this->map = &map;
}

View File

@ -1,17 +0,0 @@
#ifndef DYNAMCCONTENTPUSHVAR_H
#define DYNAMCCONTENTPUSHVAR_H
#include "dynamiccontent.h"
class DynamicContentSetVar : public DynamicContent
{
private:
std::map<std::string, std::string> *map;
public:
using DynamicContent::DynamicContent;
std::string render();
void setArgument(std::string argument);
void setMap(std::map<std::string, std::string> &map);
};
#endif // DYNAMCCONTENTPUSHVAR_H

View File

@ -1,73 +0,0 @@
#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

@ -1,18 +0,0 @@
#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,8 +1,5 @@
#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

View File

@ -1,26 +0,0 @@
#include "utils.h"
template<class G, class V, class C>
class Grouper
{
std::map<G, std::vector<const V*>, C> results;
public:
Grouper(C c)
{
results = std::map<G, std::vector<const V*>, C>(c);
}
void group(const std::function<G(const V&)> &map, const std::vector<V> &values)
{
for(const V &v : values)
{
results[map(v)].push_back(&v);
}
}
std::map<G, std::vector<const V*>, C> &getResults()
{
return this->results;
}
};

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

View File

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

View File

@ -19,8 +19,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "handlerallpages.h" #include "handlerallpages.h"
#include "../grouper.h"
#include "../pagelistrenderer.h"
Response HandlerAllPages::handleRequest(const Request &r) Response HandlerAllPages::handleRequest(const Request &r)
{ {
@ -29,21 +27,17 @@ Response HandlerAllPages::handleRequest(const Request &r)
Response response; Response response;
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
QueryOption qo = queryOption(r); QueryOption qo = queryOption(r);
std::vector<Page> pageList = pageDao->getPageList(qo); auto resultList = pageDao->getPageList(qo);
if(pageList.size() == 0) if(resultList.size() == 0)
{ {
return errorResponse("No pages", "This wiki does not have any pages yet"); return errorResponse("No pages", "This wiki does not have any pages yet");
} }
TemplatePage searchPage = this->templ->getPage("allpages");
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); std::string body = this->templ->renderSearch(resultList);
TemplatePage allPages = this->templ->getPage("allpages"); searchPage.setVar("pagelist", body);
allPages.setVar("pagelistcontent", pagelistrender.render(pageList, r.get("rendertype"))); searchPage.setVar("title", createPageTitle("All pages"));
allPages.setVar("pagelistletterlink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_LETTER)); setGeneralVars(searchPage);
allPages.setVar("pagelistcreationdatelink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_CREATIONDATE)); response.setBody(searchPage.render());
allPages.setVar("title", createPageTitle("All pages"));
setGeneralVars(allPages);
response.setBody(allPages.render());
response.setStatus(200); response.setStatus(200);
return response; return response;
} }

View File

@ -19,7 +19,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include "handlercategory.h" #include "handlercategory.h"
#include "../pagelistrenderer.h"
Response HandlerCategory::handleRequest(const Request &r) Response HandlerCategory::handleRequest(const Request &r)
{ {
@ -35,12 +34,8 @@ Response HandlerCategory::handleRequest(const Request &r)
QueryOption qo = queryOption(r); QueryOption qo = queryOption(r);
auto resultList = categoryDao->fetchMembers(categoryname, qo); auto resultList = categoryDao->fetchMembers(categoryname, qo);
TemplatePage searchPage = this->templ->getPage("show_category"); TemplatePage searchPage = this->templ->getPage("show_category");
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body);
std::string body = pagelistrender.render(resultList, r.get("rendertype"));
searchPage.setVar("pagelistcontent", body);
searchPage.setVar("pagelistletterlink", this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_LETTER));
searchPage.setVar("pagelistcreationdatelink", this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_CREATIONDATE));
searchPage.setVar("categoryname", categoryname); searchPage.setVar("categoryname", categoryname);
setGeneralVars(searchPage); setGeneralVars(searchPage);
searchPage.setVar("title", createPageTitle("Category: " + categoryname)); searchPage.setVar("title", createPageTitle("Category: " + categoryname));

View File

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

View File

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

View File

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

View File

@ -26,20 +26,9 @@ Response HandlerPage::handle(const Request &r)
std::string pagename = r.get("page"); std::string pagename = r.get("page");
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
if(pagename.empty()) if(pagename.empty())
{
std::string title = r.get("title");
if(title.empty())
{ {
return errorResponse("No page given", "No page given to request"); return errorResponse("No page given", "No page given to request");
} }
title = utils::strreplace(title, "-", " ");
auto page = pageDao->findByTitle(title);
if(!page)
{
return errorResponse("No page by such title", "No page with such title exists");
}
pagename = page->name;
}
if(pageMustExist() && !pageDao->exists(pagename)) if(pageMustExist() && !pageDao->exists(pagename))
{ {

View File

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

View File

@ -23,8 +23,6 @@ SOFTWARE.
#include "../request.h" #include "../request.h"
#include "../parser.h" #include "../parser.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();
@ -43,7 +41,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("No permission", "You don't have permission to create new pages"); return errorResponse("No permission", "You don't have permission to create new pages");
} }
auto revisiondao = this->database->createRevisionDao(); auto revisiondao = this->database->createRevisionDao();
auto revision = revisiondao->getCurrentForPage(pagename); auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
std::string body; std::string body;
unsigned int current_revision = 0; unsigned int current_revision = 0;
@ -52,16 +50,6 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
body = revision->content; body = revision->content;
current_revision = revision->revision; current_revision = revision->revision;
} }
std::string from = r.get("frompage");
if(!from.empty())
{
if(!effectivePermissions(from).canRead())
{
return this->errorResponse("Permission denied",
"No access permissions, so you can't use this page as a template");
}
body = revisiondao->getCurrentForPage(from)->content;
}
if(r.getRequestMethod() == "POST") if(r.getRequestMethod() == "POST")
{ {
if(r.post("do") == "submit") if(r.post("do") == "submit")
@ -76,27 +64,8 @@ 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 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)
@ -111,52 +80,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)
{
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 = !(listedcmd == "0"); page.listed = !(visiblecmd == "0");
page.feedlisted = !(feedlistedcmd == "0");
page.name = pagename; page.name = pagename;
page.title = customtitle;
page.parentpage = parentpage;
if(page.title.empty())
{
page.title = page.name;
}
pageDao.save(page); pageDao.save(page);
Revision newRevision; Revision newRevision;
@ -169,11 +95,9 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
pageDao.setCategories(pagename, cats); pageDao.setCategories(pagename, cats);
this->database->commitTransaction(); this->database->commitTransaction();
this->cache->removePrefix("page:"); // TODO: overkill? this->cache->removePrefix("page:"); // TODO: overkill?
this->cache->removePrefix("feed:");
} }
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");
} }
@ -184,16 +108,12 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{ {
std::string newContent = r.post("content"); std::string newContent = r.post("content");
Parser parser; Parser parser;
std::string title = parser.extractCommand("pagetitle", newContent);
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));
templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent));
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent); templatePage.setVar("content", newContent);
setPageVars(templatePage, pagename); setPageVars(templatePage, pagename);
templatePage.setVar("title", createPageTitle("Preview: " + title)); templatePage.setVar("title", createPageTitle("Preview: " + pagename));
templatePage.setVar("comment", r.post("comment")); templatePage.setVar("comment", r.post("comment"));
Response response; Response response;
response.setBody(templatePage.render()); response.setBody(templatePage.render());

View File

@ -23,11 +23,6 @@ SOFTWARE.
#include "../logger.h" #include "../logger.h"
#include "../parser.h" #include "../parser.h"
#include "../htmllink.h" #include "../htmllink.h"
#include "../dynamic/dynamiccontentpostlist.h"
#include "../dynamic/dynamiccontentincludepage.h"
#include "../dynamic/dynamiccontentsetvar.h"
#include "../dynamic/dynamiccontentgetvar.h"
#include "../revisionrenderer.h"
bool HandlerPageView::canAccess(std::string page) bool HandlerPageView::canAccess(std::string page)
{ {
@ -91,20 +86,19 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
std::optional<Revision> revision; std::optional<Revision> revision;
std::string templatepartname; std::string templatepartname;
auto revisionDao = this->database->createRevisionDao();
try try
{ {
if(revisionid > 0) if(revisionid > 0)
{ {
if(!effectivePermissions(pagename).canSeePageHistory()) if(!effectivePermissions(pagename).canSeePageHistory())
{ {
auto current = revisionDao->getCurrentForPage(pagename); auto current = this->database->createRevisionDao()->getCurrentForPage(pagename);
if(current && current->revision > revisionid) if(current && current->revision > revisionid)
{ {
return errorResponse("Error", "You are not allowed to view older revisions of this page"); return errorResponse("Error", "You are not allowed to view older revisions of this page");
} }
} }
revision = revisionDao->getRevisionForPage(pagename, revisionid); revision = this->database->createRevisionDao()->getRevisionForPage(pagename, revisionid);
if(!revision) if(!revision)
{ {
return errorResponse("Revision not found", "No such revision found"); return errorResponse("Revision not found", "No such revision found");
@ -124,7 +118,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return r; return r;
} }
} }
revision = revisionDao->getCurrentForPage(pagename); revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
templatepartname = "page_view"; templatepartname = "page_view";
} }
} }
@ -134,30 +128,52 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("Database error", "While trying to fetch revision, a database error occured"); return errorResponse("Database error", "While trying to fetch revision, a database error occured");
} }
TemplatePage page = this->templ->getPage(templatepartname);
Parser parser; Parser parser;
Response result; Response result;
result.setStatus(200); result.setStatus(200);
std::string indexcontent;
std::string parsedcontent;
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession}; if(revisionid > 0)
{
std::string customtitle = parser.extractCommand("pagetitle", revision->content); indexcontent = createIndexContent(parser, revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle); parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
/* TODO: Dynamic includes not considered, probably fine in practise */ }
std::string indexcontent = createIndexContent(parser, revision->content); else
{
std::string cachekeyindexcontent = "page:indexcontent:" + pagename;
std::string cachekeyparsedcontent = "page:parsedcontent:" + pagename;
auto cachedindexcontent = this->cache->get(cachekeyindexcontent);
auto cachedparsedcontent = this->cache->get(cachekeyparsedcontent);
if(cachedindexcontent)
{
indexcontent = *cachedindexcontent;
}
else
{
indexcontent = createIndexContent(parser, revision->content);
this->cache->put(cachekeyindexcontent, indexcontent);
}
if(cachedparsedcontent)
{
parsedcontent = *cachedparsedcontent;
}
else
{
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
this->cache->put(cachekeyparsedcontent, parsedcontent);
}
}
std::string revisionstr = std::to_string(revision->revision); std::string revisionstr = std::to_string(revision->revision);
TemplatePage page = this->templ->getPage(templatepartname);
page.setVar("content", parsedcontent); page.setVar("content", parsedcontent);
page.setVar("index", indexcontent); page.setVar("index", indexcontent);
page.setVar("editedby", revision->author); page.setVar("editedby", revision->author);
page.setVar("editedon", utils::toISODateTime(revision->timestamp)); page.setVar("editedon", utils::toISODate(revision->timestamp));
page.setVar("historyurl", this->urlProvider->pageHistory(pagename)); page.setVar("historyurl", this->urlProvider->pageHistory(pagename));
page.setVar("revision", revisionstr); page.setVar("revision", revisionstr);
setPageVars(page, pagename); setPageVars(page, pagename);
if(!customtitle.empty())
{
page.setVar("title", createPageTitle(customtitle));
}
std::string body = page.render(); std::string body = page.render();
if(revisionid == 0 && !this->userSession->loggedIn) if(revisionid == 0 && !this->userSession->loggedIn)
{ {

View File

@ -25,11 +25,7 @@ Response HandlerSearch::handleRequest(const Request &r)
std::string q = r.get("q"); std::string q = r.get("q");
if(q.empty()) if(q.empty())
{ {
TemplatePage searchForm = this->templ->getPage("searchform"); return errorResponse("Missing search term", "No search term supplied");
response.setBody(searchForm.render());
response.setStatus(200);
setGeneralVars(searchForm);
return response;
} }
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();

View File

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

View File

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

3
page.h
View File

@ -7,10 +7,7 @@ class Page
public: public:
Page(); Page();
std::string name; std::string name;
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

@ -1,66 +0,0 @@
#include "pagelistrenderer.h"
#include "grouper.h"
PageListRenderer::PageListRenderer(Template &templ, UrlProvider &provider, Database &database)
{
this->templ = &templ;
this->urlProvider = &provider;
this->database = &database;
}
std::string PageListRenderer::render(const std::vector<Page> &pagelist, std::string type) const
{
TemplatePage pagelistrendergroup = this->templ->loadResolvedPart("pagelistrender_group");
TemplatePage pagelistlink = this->templ->loadResolvedPart("pagelistrender_link");
std::function<bool(const std::string &, const std::string &)> lesser = [](const std::string &a, const std::string &b) -> bool {
return a < b;
};
std::function<bool(const std::string &, const std::string &)> greater = [](const std::string &a, const std::string &b) -> bool {
return a > b;
};
using Grouper = Grouper<std::string, Page, std::function<bool(const std::string &,const std::string &)>>;
Grouper grouper = (type == "letter") ? Grouper(lesser) : Grouper(greater);
if(type == "letter")
{
auto az = [&](const Page &p) -> std::string {
int upper = toupper(p.title[0]); // TODO: this is not unicode safe.
return { (char)upper };
};
grouper.group(az, pagelist);
}
else
{
auto revisionDao = this->database->createRevisionDao();
auto creationdate = [&revisionDao](const Page &p) -> std::string {
return utils::formatLocalDate(revisionDao->getRevisionForPage(p.name, 1).value().timestamp, "%Y-%m");
};
grouper.group(creationdate, pagelist);
}
std::stringstream stream;
std::string lastGroup = "";
for(auto &entry : grouper.getResults())
{
std::string groupname = entry.first;
const std::vector<const Page*> &pages = entry.second;
if(lastGroup != groupname)
{
lastGroup = groupname;
pagelistrendergroup.setVar("groupname", groupname);
stream << pagelistrendergroup.render();
}
for(const Page *p : pages)
{
pagelistlink.setVar("inner", p->title);
pagelistlink.setVar("href", this->urlProvider->page(p->name));
stream << pagelistlink.render();
}
}
return stream.str();
}

View File

@ -1,27 +0,0 @@
#ifndef PAGELISTRENDERER_H
#define PAGELISTRENDERER_H
#include "utils.h"
#include "page.h"
#include "template.h"
#include "htmllink.h"
#include "urlprovider.h"
#include "database/database.h"
class PageListRenderer
{
private:
Template *templ;
UrlProvider *urlProvider;
Database *database;
public:
PageListRenderer(Template &templ, UrlProvider &provider, Database &database);
std::string render(const std::vector<Page> &pages, std::string type) const;
inline static const std::string RENDER_GROUP_BY_LETTER { "letter" };
inline static const std::string RENDER_GROUP_BY_CREATIONDATE { "creationdate" };
};
#endif

View File

@ -27,11 +27,10 @@ SOFTWARE.
#include "parser.h" #include "parser.h"
#include "utils.h" #include "utils.h"
#include "htmllink.h" #include "htmllink.h"
std::vector<Headline> Parser::extractHeadlines(const std::string &content) const std::vector<Headline> Parser::extractHeadlines(std::string content) const
{ {
std::vector<Headline> result; std::vector<Headline> result;
std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])";
std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])";
std::regex headerfinder(reg); std::regex headerfinder(reg);
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder); auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
auto end = std::sregex_iterator(); auto end = std::sregex_iterator();
@ -41,13 +40,13 @@ std::vector<Headline> Parser::extractHeadlines(const std::string &content) const
auto smatch = *it; auto smatch = *it;
Headline h; Headline h;
h.level = utils::toUInt(smatch.str(1)); h.level = utils::toUInt(smatch.str(1));
h.title = smatch.str(3); h.title = smatch.str(2);
result.push_back(h); result.push_back(h);
} }
return result; return result;
} }
std::vector<std::string> Parser::extractCategories(const std::string &content) const std::vector<std::string> Parser::extractCategories(std::string content) const
{ {
std::vector<std::string> result; std::vector<std::string> result;
std::string reg = R"(\[category\](.*?)\[/category\])"; std::string reg = R"(\[category\](.*?)\[/category\])";
@ -63,10 +62,11 @@ std::vector<std::string> Parser::extractCategories(const std::string &content) c
return result; return result;
} }
std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const std::string Parser::extractCommand(std::string cmdname, std::string content) const
{ {
std::string cmd = "[" + tagname + "]"; std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/" + tagname + "]"; std::string cmdend = "[/cmd:" + cmdname + "]";
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)
@ -81,34 +81,6 @@ std::string Parser::extractFirstTag(std::string tagname, const std::string &cont
} }
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);
@ -144,98 +116,31 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider
return htmllink.render(); return htmllink.render();
} }
std::string Parser::processImage(std::smatch &match) const std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
{
std::string tag = match.str(1);
std::string inside = match.str(2);
std::vector<std::string> splitted = utils::split(inside, '|');
std::string width;
std::string height;
std::string src;
if(splitted.size() == 3)
{
width = splitted[0];
height = splitted[1];
src = splitted[2];
}
else
{
src = splitted[0];
}
if(!width.empty() && !height.empty())
{
return "<img src=\"" + src + "\" width=\"" + width + "\" height=\"" + height + "\"/>";
}
return "<img src=\"" + src + "\"/>";
}
std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const
{ {
std::string result; 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|link|wikilink|h\d|cmd:rename|cmd:redirect|category)*?\]((\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)*)"); result = utils::regex_callback_replacer(tagfinder, content, [&](std::smatch &match) {
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
result = utils::regex_callback_replacer(
tagfinder, content,
[&](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"};
std::string newlines = match.str(4); content = parse(pagedao, provider, content);
if(newlines == "\r\n")
{
newlines = "<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))
{ {
if(tag == "p" || tag == "br") return "<" + tag + ">" + content + "</" + tag + ">";
{
newlines = "";
}
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
} }
if(tag == "link" || tag == "wikilink") if(tag == "link" || tag == "wikilink")
{ {
return this->processLink(pagedao, provider, return this->processLink(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")
{
return this->processImage(match);
} }
if(tag[0] == 'h') if(tag[0] == 'h')
{ {
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">"; return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
} }
if(tag == "code" || tag == "blockquote") return std::string("");
{
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
}
return callback(tag, content);
}); });
result = utils::strreplace(result, "\r\n", "<br>"); result = utils::strreplace(result, "\r\n", "<br>");
return result; return result;
} }
std::string Parser::parseDynamics(const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const
{
std::regex tagfinder(R"(\[(dynamic:\w+)*?\]((\s|\S)*?)\[/\1])");
return utils::regex_callback_replacer(tagfinder, content,
[&](std::smatch &match) { return callback(match.str(1), match.str(2)); });
}

View File

@ -6,23 +6,14 @@ class Parser : public IParser
{ {
private: private:
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, 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, std::string content) const;
std::string extractCommand(std::string cmdname, const std::string &content) const override; std::vector<Headline> extractHeadlines(std::string content) const override;
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override; std::vector<std::string> extractCategories(std::string content) const override;
std::vector<Headline> extractHeadlines(const std::string &content) const override; std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override;
std::vector<std::string> extractCategories(const std::string &content) const override;
using IParser::parse;
virtual std::string parse(
const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const override;
std::string parseDynamics(
const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const override;
using IParser::IParser; using IParser::IParser;
~Parser(){};
}; };
#endif // PARSER_H #endif // PARSER_H

View File

@ -20,8 +20,7 @@ SOFTWARE.
*/ */
#include "permissions.h" #include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING}, static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"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},
@ -30,8 +29,7 @@ static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTH
{"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,7 +1,6 @@
#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
@ -12,8 +11,6 @@
#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>
@ -57,16 +54,10 @@ 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;
@ -104,27 +95,12 @@ 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,13 +31,12 @@ SOFTWARE.
#include "handlers/handlerfactory.h" #include "handlers/handlerfactory.h"
#include "database/databasefactory.h" #include "database/databasefactory.h"
#include "config.h" #include "config.h"
#include "session.h"
#include "template.h" #include "template.h"
#include "session.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"
@ -69,41 +68,12 @@ 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)
{ {
@ -166,7 +136,6 @@ 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);
@ -189,8 +158,6 @@ 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

@ -40,7 +40,7 @@ std::pair<std::string, std::string> Request::createPairFromVar(std::string var)
else else
{ {
std::string key = var.substr(0, equal); std::string key = var.substr(0, equal);
std::string val = utils::html_xss(utils::urldecode(var.substr(equal + 1))); std::string val = utils::html_xss(var.substr(equal + 1));
return std::make_pair(std::move(key), std::move(val)); return std::make_pair(std::move(key), std::move(val));
} }
} }

View File

@ -1,78 +0,0 @@
#include "revisionrenderer.h"
#include "templatepage.h"
#include "dynamic/dynamiccontentpostlist.h"
#include "dynamic/dynamiccontentincludepage.h"
#include "dynamic/dynamiccontentgetvar.h"
#include "dynamic/dynamiccontentsetvar.h"
#include "dynamic/dynamicpostrenderer.h"
#include "parser.h"
#include "htmllink.h"
std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_view value)
{
if(key == "dynamic:postlist")
{
auto postlist = this->dynamicContentFactory.createDynamicContent<DynamicContentPostList>();
postlist->setArgument(std::string(value));
return postlist->render();
}
if(key == "dynamic:includepage")
{
auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>();
includePage->setArgument(std::string(value));
return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this,
std::placeholders::_1, std::placeholders::_2));
}
if(key == "dynamic:setvar")
{
auto setVar = this->dynamicContentFactory.createDynamicContent<DynamicContentSetVar>();
setVar->setMap(dynamicVarsMap);
setVar->setArgument(std::string(value));
return setVar->render();
}
if(key == "dynamic:getvar")
{
auto getVar = this->dynamicContentFactory.createDynamicContent<DynamicContentGetVar>();
getVar->setMap(dynamicVarsMap);
getVar->setArgument(std::string(value));
return getVar->render();
}
if(key == "dynamic:postrenderer")
{
auto renderer = this->dynamicContentFactory.createDynamicContent<DynamicPostRenderer>();
renderer->setArgument(std::string(value));
return renderer->render();
}
return std::string{};
}
std::string RevisionRenderer::renderContent(std::string content)
{
dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content);
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));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
}
std::string RevisionRenderer::renderContent(const Revision &r, std::string_view customTitle)
{
auto revisionDao = this->db->createRevisionDao();
auto firstRevision = revisionDao->getRevisionForPage(r.page, 1);
if(!firstRevision)
{
throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?");
}
dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp);
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));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
}

View File

@ -1,29 +0,0 @@
#ifndef REVISIONRENDERER_H
#define REVISIONRENDERER_H
#include "revision.h"
#include "templatepage.h"
#include "dynamic/dynamiccontentfactory.h"
#include "iparser.h"
#include "parser.h"
class RevisionRenderer
{
private:
DynamicContentFactory dynamicContentFactory;
Database *db;
UrlProvider *urlProvider;
std::map<std::string, std::string> dynamicVarsMap;
std::string dynamicCallback(std::string_view key, std::string_view value);
Parser parser;
public:
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider, Session &session) :dynamicContentFactory(templ, db, urlProvider, session)
{
this->db = &db;
this->urlProvider = &urlProvider;
}
std::string renderContent(std::string content);
std::string renderContent(const Revision &r, std::string_view customTitle);
};
#endif // REVISIONRENDERER_H

View File

@ -12,13 +12,17 @@
#include <filesystem> #include <filesystem>
#include <sys/mount.h> #include <sys/mount.h>
#include <sys/capability.h> #include <sys/capability.h>
#include <exile.hpp> #include <qssb.h>
#include "../logger.h" #include "../logger.h"
#include "../utils.h" #include "../utils.h"
#include "../random.h" #include "../random.h"
#include "sandbox-linux.h" #include "sandbox-linux.h"
/* TODO: make a whitelist approach. So far we simply blacklist
* obvious systemcalls. To whitelist, we need to analyse our
* dependencies (http library, sqlite wrapper, sqlite lib etc.) */
bool SandboxLinux::supported() bool SandboxLinux::supported()
{ {
std::fstream stream; std::fstream stream;
@ -41,7 +45,7 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
std::sort(fsPaths.begin(), fsPaths.end(), std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); }); [](const std::string &a, const std::string &b) { return a.length() < b.length(); });
struct exile_policy *policy = exile_init_policy(); struct qssb_policy *policy = qssb_init_policy();
if(policy == NULL) if(policy == NULL)
{ {
Logger::error() << "Failed to init sandboxing policy (worker) "; Logger::error() << "Failed to init sandboxing policy (worker) ";
@ -49,23 +53,30 @@ 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++)
{ {
std::string &path = fsPaths[i]; qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ | QSSB_FS_ALLOW_WRITE, fsPaths[i].c_str());
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_vows_from_str("stdio wpath cpath rpath inet unix thread"); /* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the
if(exile_enable_policy(policy) != 0) * sense that more could be listed here and some critical ones are probably missing */
/* TODO: use qssb groups */
long blacklisted_syscalls[] = {QSSB_SYS(setuid), QSSB_SYS(connect), QSSB_SYS(chroot), QSSB_SYS(pivot_root),
QSSB_SYS(mount), QSSB_SYS(setns), QSSB_SYS(unshare), QSSB_SYS(ptrace),
QSSB_SYS(personality), QSSB_SYS(prctl), QSSB_SYS(execveat), QSSB_SYS(execve),
QSSB_SYS(fork)};
qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, blacklisted_syscalls,
sizeof(blacklisted_syscalls) / sizeof(blacklisted_syscalls[0]));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
if(qssb_enable_policy(policy) != 0)
{ {
Logger::error() << "Sandbox: Activation of exile failed!"; Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
exile_free_policy(policy); qssb_free_policy(policy);
return false; return false;
} }
exile_free_policy(policy); qssb_free_policy(policy);
return true; return true;
} }

View File

@ -1,4 +1,4 @@
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 page(id INTEGER PRIMARY KEY, name varchar(256), lastrevision integer, visible 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),

@ -1 +1 @@
Subproject commit e64379c3d71ccf3f62e4e4853bfd1316901564b3 Subproject commit 4f8fcdbaf7696a17c407cdd498819a7c7200c73b

@ -1 +0,0 @@
Subproject commit e711a1d53a9210f8f562f774901e5e044d20e67a

1
submodules/qssb.h Submodule

@ -0,0 +1 @@
Subproject commit 0d7c5bd6d437ae95a4900aab6b7b6cc207acbd1b

View File

@ -18,7 +18,6 @@ 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"
@ -48,15 +47,9 @@ TemplatePage Template::getPage(const std::string &pagename)
std::string Template::getPartPath(std::string_view partname) std::string Template::getPartPath(std::string_view partname)
{ {
auto absolute_path = std::filesystem::canonical(std::filesystem::path{this->templatepath} / partname); // TODO: utils::concatPath? C++17 paths?
std::string result = absolute_path.string(); return this->templatepath + "/" + std::string(partname);
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);
@ -148,7 +141,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< revision.revision << "</a></td>" << revision.revision << "</a></td>"
<< "<td>" << revision.author << "</td>" << "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>" << "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>"; << "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>";
} }
}; };
@ -162,7 +155,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< "<td>" << revision.revision << "</td>" << "<td>" << revision.revision << "</td>"
<< "<td>" << revision.author << "</td>" << "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>" << "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>"; << "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>";
} }
}; };

View File

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

View File

@ -1,6 +1,6 @@
{qswiki:include:general_header} {qswiki:include:general_header}
<div id="content" style="margin-top: 10px;"> <div id="content" style="margin-top: 10px;">
<h2>All pages</h2> <h2>All pages</h2>
{qswiki:include:pagelistrender} {qswiki:var:pagelist}
</div> </div>
{qswiki:include:general_footer} {qswiki:include:general_footer}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,5 +4,8 @@
<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

@ -6,15 +6,16 @@
<title>{qswiki:var:title}</title> <title>{qswiki:var:title}</title>
<body> <body>
<nav> <nav>
<ul id="nav"> <ul>
<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> <li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li>
</ul>
<ul id="nav">
<li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> <li><a href="{qswiki:config:linkrecent}">Recent changes</a></li>
<li><a href="{qswiki:config:linkallpages}">All pages</a></li> <li><a href="{qswiki:config:linkallpages}">All pages</a></li>
<li><a href="{qswiki:config:linkallcats}">All categories</a></li> <li><a href="{qswiki:config:linkallcats}">All categories</a></li>
<li id="searchlink"><a href="{qswiki:config:linksearch}">Search</a></li>
</ul> </ul>
<ul id="right" class="search"> <ul id="right" class="search">
<li><div id="searchbar"><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form></div></li> <li><div><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form></div></li>
</ul> </ul>
</nav> </nav>

View File

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

View File

@ -5,9 +5,8 @@
<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>
hljs.highlightAll(); {qswiki:include:js_session_refresh}
</script> </script>
</body> </body>
</html> </html>

View File

@ -3,20 +3,23 @@
<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>
<ul id="nav"> <ul>
<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> <li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li>
</ul>
<ul id="nav">
<li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> <li><a href="{qswiki:config:linkrecent}">Recent changes</a></li>
<li><a href="{qswiki:config:linkallpages}">All pages</a></li> <li><a href="{qswiki:config:linkallpages}">All pages</a></li>
<li><a href="{qswiki:config:linkallcats}">All categories</a></li> <li><a href="{qswiki:config:linkallcats}">All categories</a></li>
<li id="searchlink"><a href="{qswiki:config:linksearch}">Search</a></li> </ul>
<ul>
{qswiki:var:headerlinks} {qswiki:var:headerlinks}
</ul> </ul>
<ul id="right" class="search"> <ul id="right" class="search">
<li><div id="searchbar"><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" value="search here" onfocus="this.value=''" name="q"/></form></div></li> <li><div><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" value="search here" onfocus="this.value=''" name="q"/></form></div></li>
</ul> </ul>
</nav> </nav>

View File

@ -1,3 +0,0 @@
{qswiki:include:pagelistrender_header}
{qswiki:var:pagelistcontent}
{qswiki:include:pagelistrender_footer}

View File

@ -1 +0,0 @@
<div class="letter_search_result">{qswiki:var:groupname}</div>

View File

@ -1 +0,0 @@
Sort by: <a href="{qswiki:var:pagelistletterlink}">A-Z</a> - <a href="{qswiki:var:pagelistcreationdatelink}">Creation date</a>

View File

@ -1 +0,0 @@
<a href="{qswiki:var:href}">{qswiki:var:inner}</a><br>

View File

@ -1,7 +0,0 @@
{qswiki:include:general_header}
<main id="content">
<h2>Search</h2><br>
Search content of pages:
<form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form>
</main>
{qswiki:include:general_footer}

View File

@ -1,6 +1,6 @@
{qswiki:include:general_header} {qswiki:include:general_header}
<main id="content"> <main id="content">
<h2>Category: {qswiki:var:categoryname}</h2> <h2>Category: {qswiki:var:categoryname}</h2>
{qswiki:include:pagelistrender} {qswiki:var:pagelist}
</main> </main>
{qswiki:include:general_footer} {qswiki:include:general_footer}

View File

@ -37,7 +37,6 @@ nav
grid-area: nav; grid-area: nav;
} }
nav ul nav ul
{ {
background-color: #062463; background-color: #062463;
@ -48,12 +47,16 @@ nav ul
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
}
}
nav li nav li
{ {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
nav a, nav a:visited nav a, nav a:visited
@ -65,6 +68,7 @@ nav a, nav a:visited
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
line-height: 100%; line-height: 100%;
} }
nav a:hover, nav a:focus nav a:hover, nav a:focus
@ -77,6 +81,8 @@ nav a:hover, nav a:focus
font-weight: bold; font-weight: bold;
} }
a, a:visited a, a:visited
{ {
color: #062463;; color: #062463;;
@ -86,6 +92,7 @@ a:hover
{ {
background-color: #062463; background-color: #062463;
color: white; color: white;
} }
#content #content
@ -100,16 +107,19 @@ a:hover
#sidebar #sidebar
{ {
grid-area: side; grid-area: side;
} }
#sidebar ul #sidebar ul
{ {
list-style-type: none; list-style-type: none;
} }
#sidebar a, a:visited #sidebar a, a:visited
{ {
color: #062463; color: #062463;
} }
#sidebar a:hover #sidebar a:hover
@ -127,8 +137,9 @@ a:hover
{ {
background-color: #062463; background-color: #062463;
color: white; color: white;
}
}
footer footer
{ {
width: 100%; width: 100%;
@ -149,7 +160,6 @@ footer ul
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
} }
footer li footer li
{ {
margin: 0; margin: 0;
@ -158,12 +168,14 @@ footer li
line-height: 45px; line-height: 45px;
color: white; color: white;
font-weight: bold; font-weight: bold;
//flex: 1 1 0;
text-align: center; text-align: center;
} }
footer a, a:visited footer a, a:visited
{ {
text-decoration: none; text-decoration: none;
color: white; color: white;
display: inline-block; display: inline-block;
} }
@ -186,27 +198,20 @@ footer a:hover, ul#nav a:focus
text-decoration: underline; text-decoration: underline;
font-weight: bold; font-weight: bold;
} }
ol ol
{ {
counter-reset: item; counter-reset: item;
} }
.indexlink .indexlink
{ {
display: block; display: block;
} }
.notexists .notexists
{ {
color: red !important; color: red !important;
font-weight: bold; font-weight: bold;
} }
#searchlink
{
display: none;
}
@media screen and (orientation: portrait) @media screen and (orientation: portrait)
{ {
@ -214,23 +219,13 @@ ol
{ {
display: none; display: none;
} }
#footer li:nth-child(-n+2) #footer li:nth-child(-n+2)
{ {
display: none; display: none;
} }
#footer li:nth-of-type(3) #footer li:nth-of-type(3)
{ {
text-align: center; text-align: center;
width: 100%; width: 100%;
} }
#searchlink {
display: inline;
}
#searchbar {
display: none;
}
} }

View File

@ -18,7 +18,6 @@ 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 <string_view>
#include "urlprovider.h" #include "urlprovider.h"
std::string replaceSingleVar(std::string where, std::string varname, std::string replacement) std::string replaceSingleVar(std::string where, std::string varname, std::string replacement)
@ -53,11 +52,6 @@ std::string UrlProvider::allPages()
return config->linkallpages; return config->linkallpages;
} }
std::string UrlProvider::allPages(std::string rendertype)
{
return replaceSingleVar(config->linkallpagesrendertype, "type", rendertype);
}
std::string UrlProvider::allCats() std::string UrlProvider::allCats()
{ {
return config->linkallcats; return config->linkallcats;
@ -68,11 +62,6 @@ std::string UrlProvider::page(std::string pagename)
return replaceOnlyPage(config->linkpage, pagename); return replaceOnlyPage(config->linkpage, pagename);
} }
std::string UrlProvider::pageByTitle(std::string title)
{
return replaceSingleVar(config->linkpagebytitle, "title", utils::strreplace(title, " ", "-"));
}
std::string UrlProvider::linksHere(std::string pagename) std::string UrlProvider::linksHere(std::string pagename)
{ {
return replaceOnlyPage(config->linkshere, pagename); return replaceOnlyPage(config->linkshere, pagename);
@ -126,42 +115,7 @@ std::string UrlProvider::category(std::string catname)
{ {
return replaceSingleVar(config->linkcategory, "category", catname); return replaceSingleVar(config->linkcategory, "category", catname);
} }
std::string UrlProvider::category(std::string catname, std::string rendertype)
{
Varreplacer replace("{");
replace.addKeyValue("category", catname);
replace.addKeyValue("type", rendertype);
return replace.parse(config->linkcategoryrendertype);
}
std::string UrlProvider::login(std::string page) std::string UrlProvider::login(std::string page)
{ {
return replaceOnlyPage(config->loginurl, page); return replaceOnlyPage(config->loginurl, page);
} }
std::string UrlProvider::rootUrl()
{
return config->rooturl;
}
std::string UrlProvider::atomFeed(std::string filter)
{
return combine({config->rooturl, replaceSingleVar(config->atomurl, "filter", filter)});
}
std::string UrlProvider::combine(std::initializer_list<std::string> urls)
{
std::string result;
for(const std::string &url : urls)
{
std::string_view urlview{url};
if(result.back() == '/' && urlview.front() == '/')
{
urlview.remove_prefix(1);
}
result += urlview;
}
return result;
}

View File

@ -22,14 +22,11 @@ class UrlProvider
std::string recentSorted(unsigned int limit, unsigned int offset, unsigned int sort); std::string recentSorted(unsigned int limit, unsigned int offset, unsigned int sort);
std::string allPages(); std::string allPages();
std::string allPages(std::string rendertype);
std::string allCats(); std::string allCats();
std::string page(std::string pagename); std::string page(std::string pagename);
std::string pageByTitle(std::string title);
std::string linksHere(std::string pagename); std::string linksHere(std::string pagename);
std::string pageHistory(std::string pagename); std::string pageHistory(std::string pagename);
@ -49,15 +46,8 @@ class UrlProvider
std::string refreshSession(); std::string refreshSession();
std::string category(std::string catname); std::string category(std::string catname);
std::string category(std::string catname, std::string rendertype);
std::string login(std::string page); std::string login(std::string page);
std::string rootUrl();
std::string atomFeed(std::string filter);
std::string combine(std::initializer_list<std::string> urls);
}; };
#endif // LINKCREATOR_H #endif // LINKCREATOR_H

Some files were not shown because too many files have changed in this diff Show More