Compare commits
9 Commits
master
...
feature/ma
Author | SHA1 | Date | |
---|---|---|---|
ebd4adf22f | |||
3745a778e5 | |||
cec56d5a73 | |||
534d9f74e7 | |||
5f674cfe6f | |||
ea7476a882 | |||
ab9e5fb0bd | |||
7ca04fbf45 | |||
8b44dd4e94 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,8 +3,6 @@
|
||||
*.out
|
||||
*.gch
|
||||
*.user
|
||||
*.swp
|
||||
*.kate-swp
|
||||
qswiki
|
||||
wikiqs*
|
||||
data/*
|
||||
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -4,6 +4,9 @@
|
||||
[submodule "submodules/cpp-httplib"]
|
||||
path = submodules/cpp-httplib
|
||||
url = https://github.com/yhirose/cpp-httplib
|
||||
[submodule "submodules/exile.h"]
|
||||
path = submodules/exile.h
|
||||
url = https://gitea.quitesimple.org/crtxcr/exile.h.git
|
||||
[submodule "submodules/qssb.h"]
|
||||
path = submodules/qssb.h
|
||||
url = https://gitea.quitesimple.org/crtxcr/qssb.h.git
|
||||
[submodule "submodules/qsmaddy"]
|
||||
path = submodules/qsmaddy
|
||||
url = https://gitea.quitesimple.org/crtxcr/qsmaddy
|
||||
|
38
Makefile
38
Makefile
@ -1,14 +1,12 @@
|
||||
CPPSTD=c++20
|
||||
|
||||
#CFIFLAGS=-fsanitize=cfi -fvisibility=hidden -fsanitize=cfi -flto
|
||||
#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)
|
||||
CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra
|
||||
RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra
|
||||
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs -lseccomp
|
||||
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h -I submodules/qsmaddy/include/
|
||||
|
||||
CXX=g++
|
||||
|
||||
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 gateway/*.cpp)
|
||||
@ -16,7 +14,6 @@ SOURCES+=$(wildcard handlers/*.cpp)
|
||||
SOURCES+=$(wildcard database/*.cpp)
|
||||
SOURCES+=$(wildcard cache/*.cpp)
|
||||
SOURCES+=$(wildcard sandbox/*.cpp)
|
||||
SOURCES+=$(wildcard dynamic/*.cpp)
|
||||
|
||||
HEADERS=$(wildcard *.h)
|
||||
HEADERS+=$(wildcard gateway/*.h)
|
||||
@ -24,7 +21,7 @@ HEADERS+=$(wildcard handlers/*.h)
|
||||
HEADERS+=$(wildcard database/*.h)
|
||||
HEADERS+=$(wildcard cache/*.h)
|
||||
HEADERS+=$(wildcard sandbox/*.h)
|
||||
HEADERS+=$(wildcard dynamic/*.h)
|
||||
|
||||
|
||||
OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES))
|
||||
WIKIOBJECTS=$(filter-out test.o, $(OBJECTS))
|
||||
@ -38,25 +35,16 @@ GTEST_DIR = /home/data/SOURCES/gtest/googletest
|
||||
|
||||
GTESTS_TESTDIR = ./tests/
|
||||
|
||||
GTEST_CXXFLAGS=-std=$(CPPSTD) -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra
|
||||
GTEST_CXXFLAGS=-std=c++17 -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra
|
||||
GTEST_LDFLAGS=-lsqlite3 -g -O0 -lpthread -lcrypto -lstdc++fs
|
||||
GTEST_OBJECTS=$(filter-out qswiki.o, $(WIKIOBJECTS))
|
||||
|
||||
.DEFAULT_GOAL := qswiki
|
||||
|
||||
release: CXXFLAGS=$(RELEASE_CXXFLAGS)
|
||||
profile: CXXFLAGS=$(RELEASE_CXXFLAGS) -pg
|
||||
profile: LDFLAGS+= -pg
|
||||
|
||||
release: qswiki
|
||||
profile: qswiki
|
||||
|
||||
|
||||
exile.o: submodules/exile.h/exile.c
|
||||
$(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o
|
||||
|
||||
qswiki: $(WIKIOBJECTS) exile.o
|
||||
$(CXX) $(shell shuf -e $(WIKIOBJECTS) exile.o ) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
|
||||
qswiki: $(WIKIOBJECTS)
|
||||
$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
|
||||
|
||||
test: $(TESTOBJECTS)
|
||||
$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test
|
||||
@ -65,11 +53,9 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS)
|
||||
$(CXX) -o gtest $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS) $(GTEST_CXXFLAGS) $(GTEST_DIR)/src/gtest_main.cc $(GTEST_DIR)/src/gtest-all.cc $(GTEST_LDFLAGS)
|
||||
|
||||
%.o:%.cpp
|
||||
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -c -o $@ $<
|
||||
$(CXX) ${CXXFLAGS} ${LDFLAGS} ${INCLUDEFLAGS} -c -o $@ $<
|
||||
|
||||
version.o:version.cpp
|
||||
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
|
||||
clean:
|
||||
rm -f exile.o $(OBJECTS) $(DEPENDS)
|
||||
rm -f $(OBJECTS) $(DEPENDS)
|
||||
|
||||
|
||||
|
95
README.md
95
README.md
@ -1,84 +1,81 @@
|
||||
# qswiki
|
||||
|
||||
## About
|
||||
qswiki is a wiki software, intended for my needs. Originally implemented in C, it's now written in C++.
|
||||
About
|
||||
====
|
||||
qswiki is a wiki software, intended for small wikis. Originally
|
||||
implemented in C, it's now written in C++.
|
||||
|
||||
## Dude... why?
|
||||
|
||||
tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already
|
||||
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
|
||||
History
|
||||
====
|
||||
A couple of years ago, I wanted to setup a personal wiki on my raspberry
|
||||
pi. However, the distribution I used back then did not have a PHP package
|
||||
for ARM. So instead of switching distributions or searching for other
|
||||
wikis that I could use, I simply decided I would write one in C. Yes,
|
||||
that's an odd way to approach the problem and indeed, I may have had too
|
||||
much time back then. Also, I wanted to see how it's like to write a
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
But then I decided to play around and started using FastCGI (with the official
|
||||
Of course, it's pretty straightforward at first. No really: Just use CGI.
|
||||
And indeed, that would have been more than enough for my use cases.
|
||||
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.
|
||||
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.
|
||||
|
||||
C++
|
||||
---
|
||||
Eventually, since it was mostly a playground for me, the code became
|
||||
unmaintainable. Furthermore, I initially wanted something quick and given that
|
||||
it was CGI, I didn't bother taking care of memory leaks.
|
||||
After initiating a FastCGI interface, they became an issue and then the
|
||||
Eventually, since it was mostly a playground for me, the code became
|
||||
unmaintainable. Furthermore, I wanted something quick and given that
|
||||
it was CGI, I didn't bother taking care of memory leaks.
|
||||
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
|
||||
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.
|
||||
|
||||
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
|
||||
convenient even. Still, the standard library is lacking and
|
||||
I would hope for a some better built-in Unicode support in future C++
|
||||
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
|
||||
convenient even. Still, the standard library is lacking and
|
||||
I would hope for a some better built-in Unicode support in future C++
|
||||
standards.
|
||||
|
||||
|
||||
## Features
|
||||
Some essential features are lacking, such as a diff between revisions,
|
||||
user registration UI, etc.
|
||||
|
||||
It doesn't compete with any other software anyway.
|
||||
Features
|
||||
========
|
||||
To be fair, at this point it doesn't even have a "diff" between revisions
|
||||
yet and does not have features that would make you prefer it over other
|
||||
wikis.
|
||||
|
||||
- CGI
|
||||
- HTTP server using the header only library [cpp-httplib](https://github.com/yhirose/cpp-httplib). It's more
|
||||
portable and more "future-proof" than FastCGI (since the official website
|
||||
- HTTP server using the header only library cpp-httplib. It's more
|
||||
portable and more "future-proof" than FastCGI (since the official website
|
||||
disappeared, the library's future appears to be uncertain).
|
||||
- Support for user accounts. Passwords are stored using PBKDF2.
|
||||
sqlite database, but not too much of an effort to add other types of
|
||||
storage backends. sqlite is using the great header only library
|
||||
[sqlite_modern_cpp](https://github.com/SqliteModernCpp)
|
||||
sqlite database, but not too much of an effort to add other types of
|
||||
storage backends. sqlite is using the great header only library
|
||||
sqlite_modern_cpp
|
||||
- Relatively fine-grained permission system.
|
||||
- Categories
|
||||
- Templates
|
||||
- FTS search
|
||||
- Caching
|
||||
- Blog-like functionality
|
||||
- RSS/Atom feeds
|
||||
|
||||
## 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
|
||||
in the system and the system calls that the qswiki process can make are restricted.
|
||||
|
||||
As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS
|
||||
Security
|
||||
========
|
||||
On Linux namespaces are used to restrict the process to only access
|
||||
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
|
||||
can call. As for "web security", all POST requests are centrally
|
||||
protected against CSRF attacks and all input is escaped against XSS
|
||||
attacks.
|
||||
|
||||
## Building
|
||||
Building
|
||||
========
|
||||
Dependencies:
|
||||
- cpp-httplib: https://github.com/yhirose/cpp-httplib
|
||||
- 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
|
||||
|
||||
|
||||
The first three are header-only libraries that are included as a git submodule. The others must
|
||||
be installed, e. g. by using your distributions standard method.
|
||||
|
||||
|
@ -21,7 +21,7 @@ Authenticator::Authenticator(UserDao &userDao)
|
||||
// TODO: make failure counter configurable
|
||||
bool Authenticator::isBanned(std::string ip)
|
||||
{
|
||||
if(loginFails.contains(ip))
|
||||
if(utils::hasKey(loginFails, ip))
|
||||
{
|
||||
LoginFail &fl = loginFails[ip];
|
||||
std::lock_guard<std::mutex> lock(fl.mutex);
|
||||
@ -42,12 +42,11 @@ std::vector<char> Authenticator::pbkdf5(std::string password, const std::vector<
|
||||
unsigned char hash[32];
|
||||
const EVP_MD *sha256 = EVP_sha256();
|
||||
const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(salt.data());
|
||||
int ret =
|
||||
PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
|
||||
int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
|
||||
if(ret != 1)
|
||||
{
|
||||
Logger::error() << "Authenticator: pbkdf5: Failed to create hash";
|
||||
return {};
|
||||
return { };
|
||||
}
|
||||
std::vector<char> result;
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <variant>
|
||||
#include "database/userdao.h"
|
||||
|
||||
#define AUTH_DEFAULT_SALT_SIZE 32
|
||||
enum AuthenticationError
|
||||
{
|
||||
UserNotFound,
|
||||
|
2
cache/fscache.cpp
vendored
2
cache/fscache.cpp
vendored
@ -46,7 +46,7 @@ void FsCache::removePrefix(std::string_view prefix)
|
||||
// TODO: lock dir
|
||||
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(static_cast<std::string>(entry.path().filename()).find(prefix) == 0)
|
||||
{
|
||||
std::filesystem::remove_all(entry);
|
||||
}
|
||||
|
1
cache/mapcache.cpp
vendored
1
cache/mapcache.cpp
vendored
@ -1 +0,0 @@
|
||||
#include "mapcache.h"
|
87
cache/mapcache.h
vendored
87
cache/mapcache.h
vendored
@ -1,87 +0,0 @@
|
||||
#ifndef MAPCACHE_H
|
||||
#define MAPCACHE_H
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "icache.h"
|
||||
|
||||
/* Thread-Safe Key-Value store */
|
||||
template <class T> class MapCache
|
||||
{
|
||||
private:
|
||||
std::unordered_map<std::string, T> cache;
|
||||
mutable std::shared_mutex sharedMutex;
|
||||
|
||||
public:
|
||||
std::optional<T> find(const std::string &key) const
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(this->sharedMutex);
|
||||
auto it = this->cache.find(key);
|
||||
if(it != this->cache.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
void set(const std::string &key, const T &val)
|
||||
{
|
||||
std::lock_guard<std::shared_mutex> lock{sharedMutex};
|
||||
this->cache[key] = val;
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard<std::shared_mutex> lock{sharedMutex};
|
||||
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
|
30
cache/nocache.h
vendored
30
cache/nocache.h
vendored
@ -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;
|
||||
}
|
||||
};
|
278
cli.cpp
278
cli.cpp
@ -1,278 +0,0 @@
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
|
||||
#include "cli.h"
|
||||
#include "utils.h"
|
||||
#include "random.h"
|
||||
#include "authenticator.h"
|
||||
#include "config.h"
|
||||
#include "logger.h"
|
||||
#include "version.h"
|
||||
|
||||
CLIHandler::CLIHandler(Config &config, Database &db)
|
||||
{
|
||||
this->db = &db;
|
||||
this->conf = &config;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_add(const std::vector<std::string> &args)
|
||||
{
|
||||
std::string username = args.at(0);
|
||||
std::string password = args.at(1);
|
||||
|
||||
auto userDao = db->createUserDao();
|
||||
|
||||
Permissions perms = this->conf->handlersConfig.anon_permissions;
|
||||
int p = perms.getPermissions();
|
||||
p |= PERM_CAN_CREATE | PERM_CAN_SEARCH | PERM_CAN_EDIT;
|
||||
Permissions newPermissions = Permissions{p};
|
||||
|
||||
Random r;
|
||||
User user;
|
||||
user.enabled = true;
|
||||
user.login = username;
|
||||
user.salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
|
||||
user.permissions = newPermissions;
|
||||
|
||||
Authenticator auth{*userDao};
|
||||
std::vector<char> hashResult = auth.hash(password, user.salt);
|
||||
if(hashResult.empty())
|
||||
{
|
||||
return {false, "Error during hashing - Got empty hash"};
|
||||
}
|
||||
user.password = hashResult;
|
||||
|
||||
try
|
||||
{
|
||||
userDao->save(user);
|
||||
}
|
||||
catch(std::runtime_error &e)
|
||||
{
|
||||
return {false, "Exception: " + std::string(e.what())};
|
||||
}
|
||||
return {true, ""};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_change_pw([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
std::string username = args.at(0);
|
||||
std::string password = args.at(1);
|
||||
|
||||
auto userDao = db->createUserDao();
|
||||
|
||||
auto user = userDao->find(username);
|
||||
if(user)
|
||||
{
|
||||
Random r;
|
||||
Authenticator auth{*userDao};
|
||||
user->salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
|
||||
user->password = auth.hash(password, user->salt);
|
||||
if(user->password.empty())
|
||||
{
|
||||
return {false, "Error during hashing - Got empty hash"};
|
||||
}
|
||||
|
||||
userDao->save(*user);
|
||||
}
|
||||
return {false, "User not found"};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_rename([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
|
||||
return {true, ""};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_set_perms([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
auto userDao = this->db->createUserDao();
|
||||
std::string username = args.at(0);
|
||||
std::string permission_string = args.at(1);
|
||||
|
||||
Permissions perms{permission_string};
|
||||
|
||||
auto user = userDao->find(username);
|
||||
if(user)
|
||||
{
|
||||
user->permissions = perms;
|
||||
userDao->save(*user);
|
||||
user_show({username});
|
||||
return {true, ""};
|
||||
}
|
||||
|
||||
return {false, "User not found"};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_list([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
auto userDao = this->db->createUserDao();
|
||||
QueryOption o;
|
||||
auto result = userDao->list(o);
|
||||
std::stringstream stream;
|
||||
for(User &u : result)
|
||||
{
|
||||
stream << u.login << "\t" << std::string(u.enabled ? "enabled" : "disabled") << "\t" << u.permissions.toString()
|
||||
<< std::endl;
|
||||
}
|
||||
return {true, stream.str()};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::user_show(const std::vector<std::string> &args)
|
||||
{
|
||||
std::string username = args.at(0);
|
||||
auto userDao = this->db->createUserDao();
|
||||
auto user = userDao->find(username);
|
||||
std::stringstream stream;
|
||||
if(user)
|
||||
{
|
||||
stream << "Username: " << user->login << std::endl;
|
||||
|
||||
stream << "Enabled: " << std::string(user->enabled ? "yes" : "no") << std::endl;
|
||||
stream << "Permissions (general): " << user->permissions.toString() << std::endl;
|
||||
return {true, stream.str()};
|
||||
}
|
||||
return {false, "User not found"};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::page_list([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
auto pageDao = this->db->createPageDao();
|
||||
QueryOption o;
|
||||
auto result = pageDao->getPageList(o);
|
||||
std::stringstream stream;
|
||||
for(Page &page : result)
|
||||
{
|
||||
stream << page.name << " " << page.pageid << " " << std::string(page.listed ? "listed" : "unlisted") << std::endl;
|
||||
}
|
||||
return {true, stream.str()};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::pageperms_set_permissions(const std::vector<std::string> &args)
|
||||
{
|
||||
std::string page = args.at(0);
|
||||
std::string username = args.at(1);
|
||||
std::string perms = args.at(2);
|
||||
|
||||
auto permissionsDao = this->db->createPermissionsDao();
|
||||
permissionsDao->save(page, username, Permissions{perms});
|
||||
return {true, ""};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::attach([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
/* TODO: consider authentication */
|
||||
pid_t pid = getpid();
|
||||
return {true, "Hi, I am pid: " + std::to_string(pid)};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::cli_help(const std::vector<std::string> &args)
|
||||
{
|
||||
std::string command;
|
||||
if(args.size() > 0)
|
||||
command = args[0];
|
||||
std::stringstream stream;
|
||||
for(struct cmd &cmd : cmds)
|
||||
{
|
||||
if(command != "" && cmd.name != command)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stream << cmd.name << " - " << cmd.helptext << std::endl;
|
||||
for(struct cmd &subCmd : cmd.subCommands)
|
||||
{
|
||||
stream << "\t" << subCmd.name << " " << subCmd.helptext << std::endl;
|
||||
}
|
||||
stream << std::endl;
|
||||
}
|
||||
return {true, stream.str()};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd,
|
||||
const std::vector<std::string> &args)
|
||||
{
|
||||
auto c = std::find_if(commands.begin(), commands.end(),
|
||||
[&cmd](const struct CLIHandler::cmd &a) { return a.name == cmd; });
|
||||
if(c == commands.end())
|
||||
{
|
||||
std::cout << "No such command: " << cmd << std::endl;
|
||||
return cli_help({});
|
||||
}
|
||||
|
||||
if(!c->subCommands.empty() && args.size() >= c->required_args)
|
||||
{
|
||||
std::string newcmd = args[0];
|
||||
std::vector<std::string> newargs = args;
|
||||
newargs.erase(newargs.begin());
|
||||
return processCommand(c->subCommands, newcmd, newargs);
|
||||
}
|
||||
if(args.size() < c->required_args)
|
||||
{
|
||||
return {false, "not enough parameters passed"};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return c->func(this, args);
|
||||
}
|
||||
catch(std::runtime_error &e)
|
||||
{
|
||||
return {false, "Exception: " + std::string(e.what())};
|
||||
}
|
||||
return {false, ""};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::processCommand(std::string cmd, const std::vector<std::string> &args)
|
||||
{
|
||||
return processCommand(this->cmds, cmd, args);
|
||||
}
|
||||
|
||||
std::pair<std::string, std::vector<std::string>> CLIHandler::splitCommand(std::string input)
|
||||
{
|
||||
input = utils::trim(input);
|
||||
std::vector<std::string> splitted = utils::split(input, "\\s+");
|
||||
if(splitted.empty())
|
||||
{
|
||||
return {" ", splitted};
|
||||
}
|
||||
std::string cmd = splitted[0];
|
||||
splitted.erase(splitted.begin());
|
||||
return {cmd, splitted};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::version([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
return {true, get_version_string()};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::category_list([[maybe_unused]] const std::vector<std::string> &args)
|
||||
{
|
||||
auto categoryDao = this->db->createCategoryDao();
|
||||
auto categories = categoryDao->fetchList(QueryOption{});
|
||||
std::stringstream stream;
|
||||
for(std::string &cat : categories)
|
||||
{
|
||||
stream << cat << std::endl;
|
||||
}
|
||||
return {true, stream.str()};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::category_delete(const std::vector<std::string> &args)
|
||||
{
|
||||
auto categoryDao = this->db->createCategoryDao();
|
||||
categoryDao->deleteCategory(args.at(0));
|
||||
return {true, ""};
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIHandler::category_show(const std::vector<std::string> &args)
|
||||
{
|
||||
auto categoryDao = this->db->createCategoryDao();
|
||||
auto members = categoryDao->fetchMembers(args.at(0), QueryOption{});
|
||||
std::stringstream stream;
|
||||
for(Page &member : members)
|
||||
{
|
||||
stream << member.name << std::endl;
|
||||
}
|
||||
return {true, stream.str()};
|
||||
}
|
94
cli.h
94
cli.h
@ -1,94 +0,0 @@
|
||||
#ifndef CLI_H
|
||||
#define CLI_H
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "database/database.h"
|
||||
#include "config.h"
|
||||
|
||||
class CLIHandler
|
||||
{
|
||||
struct cmd
|
||||
{
|
||||
std::string name;
|
||||
std::string helptext;
|
||||
unsigned int required_args;
|
||||
std::vector<cmd> subCommands;
|
||||
std::function<std::pair<bool, std::string>(CLIHandler *, const std::vector<std::string> &)> func;
|
||||
};
|
||||
|
||||
private:
|
||||
Database *db;
|
||||
Config *conf;
|
||||
|
||||
protected:
|
||||
std::pair<bool, std::string> attach([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> cli_help([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_add([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_change_pw([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_rename([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_set_perms([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_list([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> user_show([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> page_list([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> pageperms_set_permissions([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> version([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> category_list([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> category_delete([[maybe_unused]] const std::vector<std::string> &args);
|
||||
std::pair<bool, std::string> category_show([[maybe_unused]] const std::vector<std::string> &args);
|
||||
|
||||
std::vector<struct cmd> cmds{
|
||||
{{"user",
|
||||
"user operations on the database",
|
||||
1,
|
||||
{{{"add", "[user] [password] - creates a user", 2, {}, &CLIHandler::user_add},
|
||||
{"changepw", "[user] [password] - changes the password of user", 2, {}, &CLIHandler::user_change_pw},
|
||||
{"rename", "[user] [new name] - renames a user", 2, {}, &CLIHandler::user_rename},
|
||||
{"setperms", "[user] [perms] - sets the permissions of the user", 2, {}, &CLIHandler::user_set_perms},
|
||||
{"list", "- lists users", 0, {}, &CLIHandler::user_list},
|
||||
{"show", "[user] - show detailed information about user", 1, {}, &CLIHandler::user_show}}},
|
||||
&CLIHandler::cli_help},
|
||||
{"page",
|
||||
"operation on pages",
|
||||
1,
|
||||
{{{"list", "- lists existing pages", 0, {}, &CLIHandler::page_list}}},
|
||||
&CLIHandler::cli_help},
|
||||
{"category",
|
||||
"operation on categories",
|
||||
1,
|
||||
{{{"list", "- lists existing categories", 0, {}, &CLIHandler::category_list},
|
||||
{"delete", " - deletes a category", 1, {}, &CLIHandler::category_delete},
|
||||
{"show", " - shows pages of a category", 1, {}, &CLIHandler::category_show}}},
|
||||
&CLIHandler::cli_help},
|
||||
{"pageperms",
|
||||
"set permissions on pages",
|
||||
1,
|
||||
{{{"set",
|
||||
"- [page] [username] [permissions] set permisisons on page",
|
||||
3,
|
||||
{},
|
||||
&CLIHandler::pageperms_set_permissions}}},
|
||||
&CLIHandler::cli_help},
|
||||
{"exit",
|
||||
"exit cli",
|
||||
0,
|
||||
{},
|
||||
[](CLIHandler *, [[maybe_unused]] const std::vector<std::string> &args) -> std::pair<bool, std::string>
|
||||
{
|
||||
exit(EXIT_SUCCESS);
|
||||
return {true, ""};
|
||||
}},
|
||||
{"help", "print this help", 0, {}, &CLIHandler::cli_help},
|
||||
{"attach", "attach to running instance", 0, {}, &CLIHandler::attach},
|
||||
{"version", "print verison info", 0, {}, &CLIHandler::version}}};
|
||||
|
||||
std::pair<bool, std::string> processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd,
|
||||
const std::vector<std::string> &args);
|
||||
|
||||
public:
|
||||
CLIHandler(Config &config, Database &d);
|
||||
std::pair<bool, std::string> processCommand(std::string cmd, const std::vector<std::string> &args);
|
||||
static std::pair<std::string, std::vector<std::string>> splitCommand(std::string input);
|
||||
};
|
||||
|
||||
#endif // CLI_H
|
147
cliconsole.cpp
147
cliconsole.cpp
@ -1,147 +0,0 @@
|
||||
#include "cliconsole.h"
|
||||
|
||||
CLIConsole::CLIConsole(CLIHandler &cliHandler, std::string socketPath)
|
||||
{
|
||||
this->handler = &cliHandler;
|
||||
this->socketPath = socketPath;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> CLIConsole::send(std::string input)
|
||||
{
|
||||
ssize_t ret =
|
||||
sendto(this->sock, input.c_str(), input.size(), 0, (const sockaddr *)&this->server, sizeof(this->server));
|
||||
if((size_t)ret != input.size())
|
||||
{
|
||||
return {false, "sendto failed: " + std::to_string(ret) + " " + std::string(strerror(errno))};
|
||||
}
|
||||
char buffer[1024] = {0};
|
||||
ret = recvfrom(this->sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
|
||||
if(ret == -1)
|
||||
{
|
||||
return {false, "recvfrom failed: " + std::string(strerror(errno))};
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
std::string_view view = buffer;
|
||||
if(view[0] == '1')
|
||||
{
|
||||
success = true;
|
||||
}
|
||||
view.remove_prefix(1);
|
||||
std::string msg = std::string{view};
|
||||
|
||||
return {success, msg};
|
||||
}
|
||||
|
||||
void CLIConsole::attach()
|
||||
{
|
||||
if(attached)
|
||||
{
|
||||
std::cout << "Already attached" << std::endl;
|
||||
return;
|
||||
}
|
||||
if(socketPath.size() > sizeof(this->server.sun_path) - 1)
|
||||
{
|
||||
std::cout << "Socket path too long" << std::endl;
|
||||
return;
|
||||
}
|
||||
memset(&this->server, 0, sizeof(this->server));
|
||||
this->server.sun_family = AF_UNIX;
|
||||
memcpy(&this->server.sun_path, socketPath.c_str(), socketPath.size());
|
||||
this->server.sun_path[socketPath.size()] = 0;
|
||||
|
||||
int s = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||
if(s == -1)
|
||||
{
|
||||
std::cout << "Failed to create socket" << strerror(errno) << std::endl;
|
||||
return;
|
||||
}
|
||||
this->sock = s;
|
||||
|
||||
struct sockaddr_un client;
|
||||
client.sun_family = AF_UNIX;
|
||||
client.sun_path[0] = '\0';
|
||||
|
||||
int ret = bind(this->sock, (struct sockaddr *)&client, sizeof(client));
|
||||
if(ret != 0)
|
||||
{
|
||||
std::cout << "bind() failed: " << strerror(errno) << std::endl;
|
||||
return;
|
||||
}
|
||||
auto result = this->send("attach");
|
||||
if(result.first)
|
||||
{
|
||||
std::cout << "Attached successfully: " << result.second << std::endl;
|
||||
this->attached = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Attached unsuccessfully: " << result.second << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void CLIConsole::startInteractive()
|
||||
{
|
||||
std::cout << "qswiki CLI" << std::endl;
|
||||
std::cout << "not attached - use 'attach' to connect to running instance" << std::endl;
|
||||
|
||||
while(true)
|
||||
{
|
||||
std::string input;
|
||||
std::cout << "> ";
|
||||
std::getline(std::cin, input);
|
||||
|
||||
if(std::cin.bad() || std::cin.eof())
|
||||
{
|
||||
std::cout << "Exiting" << std::endl;
|
||||
return;
|
||||
}
|
||||
if(input.empty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto pair = CLIHandler::splitCommand(input);
|
||||
if(pair.first == "exit")
|
||||
{
|
||||
if(attached)
|
||||
{
|
||||
std::cout << "You are attached. Quit attached instance too (y) or only this one(n)" << std::endl;
|
||||
char response;
|
||||
std::cin >> response;
|
||||
if(response == 'y')
|
||||
{
|
||||
this->send("exit");
|
||||
}
|
||||
}
|
||||
std::cout << "Exiting CLI" << std::endl;
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
if(pair.first == "attach")
|
||||
{
|
||||
attach();
|
||||
continue;
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> result;
|
||||
if(!attached)
|
||||
{
|
||||
result = handler->processCommand(pair.first, pair.second);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this->send(input);
|
||||
}
|
||||
|
||||
if(!result.second.empty())
|
||||
{
|
||||
std::cout << result.second << std::endl;
|
||||
}
|
||||
if(!result.first)
|
||||
{
|
||||
std::cout << "Command failed" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n";
|
||||
}
|
27
cliconsole.h
27
cliconsole.h
@ -1,27 +0,0 @@
|
||||
#ifndef CLICONSOLE_H
|
||||
#define CLICONSOLE_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include "cli.h"
|
||||
|
||||
class CLIConsole
|
||||
{
|
||||
private:
|
||||
struct sockaddr_un server;
|
||||
int sock;
|
||||
CLIHandler *handler;
|
||||
std::string socketPath;
|
||||
bool attached = false;
|
||||
std::pair<bool, std::string> send(std::string input);
|
||||
void attach();
|
||||
|
||||
public:
|
||||
CLIConsole(CLIHandler &cliHandler, std::string socketPath);
|
||||
void startInteractive();
|
||||
};
|
||||
|
||||
#endif // CLICONSOLE_H
|
@ -1,77 +0,0 @@
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include "cliserver.h"
|
||||
#include "logger.h"
|
||||
CLIServer::CLIServer(CLIHandler &handler)
|
||||
{
|
||||
this->handler = &handler;
|
||||
}
|
||||
|
||||
bool CLIServer::detachServer(std::string socketpath)
|
||||
{
|
||||
struct sockaddr_un name;
|
||||
const int max_socket_length = sizeof(name.sun_path) - 1;
|
||||
if(socketpath.size() > max_socket_length)
|
||||
{
|
||||
perror("socket path too long");
|
||||
return false;
|
||||
}
|
||||
int s = socket(AF_UNIX, SOCK_DGRAM, 0);
|
||||
if(s == -1)
|
||||
{
|
||||
perror("socket");
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&name, 0, sizeof(name));
|
||||
name.sun_family = AF_UNIX;
|
||||
memcpy(&name.sun_path, socketpath.c_str(), socketpath.size());
|
||||
|
||||
unlink(socketpath.c_str());
|
||||
int ret = bind(s, (const struct sockaddr *)&name, sizeof(name));
|
||||
if(ret == -1)
|
||||
{
|
||||
perror("bind");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto worker = [this, s]
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
char buffer[1024] = {0};
|
||||
struct sockaddr_un peer;
|
||||
socklen_t peerlen = sizeof(peer);
|
||||
|
||||
int ret = recvfrom(s, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peerlen);
|
||||
if(ret == -1)
|
||||
{
|
||||
Logger::error() << "Error during recvfrom in CLI server: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string input{buffer};
|
||||
|
||||
auto pair = CLIHandler::splitCommand(input);
|
||||
|
||||
auto result = handler->processCommand(pair.first, pair.second);
|
||||
char resultCode = '0';
|
||||
if(result.first)
|
||||
{
|
||||
resultCode = '1';
|
||||
}
|
||||
std::string resultString;
|
||||
resultString += resultCode;
|
||||
resultString += result.second;
|
||||
ret = sendto(s, resultString.c_str(), resultString.size(), 0, (struct sockaddr *)&peer, peerlen);
|
||||
if(ret == -1)
|
||||
{
|
||||
Logger::error() << "Error during sendto in CLI server: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
};
|
||||
std::thread t1{worker};
|
||||
t1.detach();
|
||||
return true;
|
||||
}
|
16
cliserver.h
16
cliserver.h
@ -1,16 +0,0 @@
|
||||
#ifndef CLISERVER_H
|
||||
#define CLISERVER_H
|
||||
#include <thread>
|
||||
#include "cli.h"
|
||||
|
||||
class CLIServer
|
||||
{
|
||||
private:
|
||||
CLIHandler *handler = nullptr;
|
||||
|
||||
public:
|
||||
CLIServer(CLIHandler &handler);
|
||||
bool detachServer(std::string socketpath);
|
||||
};
|
||||
|
||||
#endif // CLISERVER_H
|
@ -24,7 +24,6 @@ SOFTWARE.
|
||||
#include "config.h"
|
||||
#include "permissions.h"
|
||||
#include "varreplacer.h"
|
||||
|
||||
std::string Config::required(const std::string &key)
|
||||
{
|
||||
auto it = this->configmap.find(key);
|
||||
@ -72,22 +71,20 @@ Config::Config(const std::map<std::string, std::string> &map)
|
||||
|
||||
this->configmap = map;
|
||||
this->wikipath = optional("wikipath", "/");
|
||||
this->parser = optional("parser", "markdown");
|
||||
this->handlersConfig.anon_username = optional("anon_username", "anonymouse");
|
||||
this->handlersConfig.wikiname = required("wikiname");
|
||||
this->logfile = required("logfile");
|
||||
this->templatepath = required("templatepath");
|
||||
this->urls.linkallcats = required("linkallcats");
|
||||
this->urls.linkallpages = required("linkallpages");
|
||||
this->urls.linkallpagesrendertype = required ("linkallpagesrendertype");
|
||||
this->urls.linkcategory = required("linkcategory");
|
||||
this->urls.linkcategoryrendertype = required("linkcategoryrendertype");
|
||||
this->urls.linkdelete = required("linkdelete");
|
||||
this->urls.linkedit = required("linkedit");
|
||||
this->urls.linkhistory = required("linkhistory");
|
||||
this->urls.linkindex = required("linkindex");
|
||||
this->urls.linklogout = required("linklogout");
|
||||
this->urls.linkpage = required("linkpage");
|
||||
this->urls.linkpagebytitle = required("linkpagebytitle");
|
||||
this->urls.linkrecent = required("linkrecent");
|
||||
this->urls.linkrevision = required("linkrevision");
|
||||
this->urls.linksettings = required("linksettings");
|
||||
@ -100,8 +97,6 @@ Config::Config(const std::map<std::string, std::string> &map)
|
||||
this->urls.deletionurl = required("deletionurl");
|
||||
this->urls.adminregisterurl = required("adminregisterurl");
|
||||
this->urls.usersettingsurl = required("usersettingsurl");
|
||||
this->urls.rooturl = required("rooturl");
|
||||
this->urls.atomurl = required("atomurl");
|
||||
this->connectionstring = required("connectionstring");
|
||||
|
||||
this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256);
|
||||
@ -114,7 +109,7 @@ Config::Config(const std::map<std::string, std::string> &map)
|
||||
|
||||
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};
|
||||
this->configVarResolver = resolver;
|
||||
|
6
config.h
6
config.h
@ -23,11 +23,9 @@ struct ConfigUrls
|
||||
std::string linkindex;
|
||||
std::string linkrecent;
|
||||
std::string linkallpages;
|
||||
std::string linkallpagesrendertype;
|
||||
std::string linkallcats;
|
||||
std::string linkshere;
|
||||
std::string linkpage;
|
||||
std::string linkpagebytitle;
|
||||
std::string linkrevision;
|
||||
std::string linkhistory;
|
||||
std::string linkedit;
|
||||
@ -35,7 +33,6 @@ struct ConfigUrls
|
||||
std::string linkdelete;
|
||||
std::string linklogout;
|
||||
std::string linkcategory;
|
||||
std::string linkcategoryrendertype;
|
||||
std::string loginurl;
|
||||
std::string linkrecentsort;
|
||||
std::string actionurl;
|
||||
@ -44,8 +41,6 @@ struct ConfigUrls
|
||||
std::string linkhistorysort;
|
||||
std::string adminregisterurl;
|
||||
std::string usersettingsurl;
|
||||
std::string rooturl;
|
||||
std::string atomurl;
|
||||
};
|
||||
|
||||
class ConfigVariableResolver
|
||||
@ -91,6 +86,7 @@ class Config
|
||||
std::string templateprefix;
|
||||
std::string logfile;
|
||||
std::string connectionstring;
|
||||
std::string parser;
|
||||
int session_max_lifetime;
|
||||
int threadscount;
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <optional>
|
||||
#include "queryoption.h"
|
||||
#include "../category.h"
|
||||
#include "../page.h"
|
||||
|
||||
class CategoryDao
|
||||
{
|
||||
public:
|
||||
@ -14,8 +14,7 @@ class CategoryDao
|
||||
virtual std::vector<std::string> fetchList(QueryOption o) = 0;
|
||||
virtual std::optional<Category> find(std::string name) = 0;
|
||||
virtual void deleteCategory(std::string name) = 0;
|
||||
virtual std::vector<Page> fetchMembers(std::string name, QueryOption o) = 0;
|
||||
virtual ~CategoryDao() = default;
|
||||
virtual std::vector<std::string> fetchMembers(std::string name, QueryOption o) = 0;
|
||||
};
|
||||
|
||||
#endif // CATEGORYDAO_H
|
||||
|
@ -42,7 +42,6 @@ std::optional<Category> CategoryDaoSqlite::find(std::string name)
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void CategoryDaoSqlite::save(const Category &c)
|
||||
@ -65,14 +64,13 @@ void CategoryDaoSqlite::deleteCategory(std::string name)
|
||||
try
|
||||
{
|
||||
|
||||
*db << "BEGIN;";
|
||||
*db << "DELETE FROM categorymember WHERE category = (SELECT id FROM category WHERE name = ?);" << name;
|
||||
*db << "DELETE FROM category WHERE name = ?;" << name;
|
||||
*db << "BEGIN";
|
||||
*db << "DELETE FROM categorymember WHERE catid = (SELECT id FROM category WHERE name = ?)" << name;
|
||||
*db << "DELETE FROM category WHERE name = ?" << name;
|
||||
*db << "COMMIT;";
|
||||
}
|
||||
catch(sqlite::sqlite_exception &e)
|
||||
{
|
||||
*db << "ROLLBACK";
|
||||
throwFrom(e);
|
||||
}
|
||||
}
|
||||
@ -95,36 +93,21 @@ std::vector<std::string> CategoryDaoSqlite::fetchList(QueryOption o)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Page> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o)
|
||||
std::vector<std::string> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o)
|
||||
{
|
||||
std::vector<Page> result;
|
||||
std::vector<std::string> result;
|
||||
|
||||
SqliteQueryOption queryOption{o};
|
||||
std::string queryoptions =
|
||||
queryOption.setOrderByColumn("name").setListedColumnName("page.listed").setPrependWhere(false).build();
|
||||
queryOption.setOrderByColumn("name").setVisibleColumnName("page.visible").setPrependWhere(false).build();
|
||||
|
||||
try
|
||||
{
|
||||
auto query =
|
||||
*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 " +
|
||||
queryoptions
|
||||
<< name;
|
||||
query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool listed,
|
||||
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);
|
||||
};
|
||||
auto query = *db << "SELECT page.name AS name FROM categorymember INNER JOIN page ON page.id = "
|
||||
"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " +
|
||||
queryoptions
|
||||
<< name;
|
||||
query >> [&](std::string p) { result.push_back(p); };
|
||||
}
|
||||
catch(const sqlite::exceptions::no_rows &e)
|
||||
{
|
||||
|
@ -3,13 +3,12 @@
|
||||
|
||||
#include "categorydao.h"
|
||||
#include "sqlitedao.h"
|
||||
#include "../page.h"
|
||||
class CategoryDaoSqlite : public CategoryDao, protected SqliteDao
|
||||
{
|
||||
public:
|
||||
CategoryDaoSqlite();
|
||||
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 deleteCategory(std::string name) override;
|
||||
std::optional<Category> find(std::string name) override;
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "permissionsdao.h"
|
||||
class Database
|
||||
{
|
||||
protected:
|
||||
private:
|
||||
std::string connnectionstring;
|
||||
|
||||
public:
|
||||
|
@ -13,9 +13,8 @@ class PageDao
|
||||
virtual bool exists(std::string page) const = 0;
|
||||
virtual bool exists(unsigned int id) const = 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::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 void deletePage(std::string 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 std::vector<SearchResult> search(std::string query, QueryOption option) = 0;
|
||||
|
||||
virtual std::vector<std::string> getChildren(std::string pagename) = 0;
|
||||
|
||||
virtual ~PageDao()
|
||||
{
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
Page result;
|
||||
result.pageid = id;
|
||||
try
|
||||
{
|
||||
auto ps =
|
||||
*db
|
||||
<< "SELECT name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) FROM "
|
||||
"page WHERE id = ?";
|
||||
auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?";
|
||||
|
||||
ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed, result.feedlisted,
|
||||
result.parentpage);
|
||||
ps << id >> std::tie(result.name, result.current_revision, result.listed);
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &e)
|
||||
{
|
||||
@ -117,7 +89,6 @@ void PageDaoSqlite::deletePage(std::string page)
|
||||
}
|
||||
catch(sqlite::sqlite_exception &e)
|
||||
{
|
||||
*db << "ROLLBACK";
|
||||
throwFrom(e);
|
||||
}
|
||||
}
|
||||
@ -126,44 +97,30 @@ void PageDaoSqlite::save(const Page &page)
|
||||
{
|
||||
try
|
||||
{
|
||||
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, listed, feedlisted, parent) VALUES((SELECT "
|
||||
"id FROM page WHERE name = ? OR id = ?), ?, ?, ?, ?, ?, (SELECT id FROM page WHERE name = ?))"
|
||||
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed
|
||||
<< page.feedlisted << page.parentpage;
|
||||
*db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE name = "
|
||||
"? OR id = ?), ?, ?, ?)"
|
||||
<< page.name << page.pageid << page.name << page.current_revision << page.listed;
|
||||
}
|
||||
catch(sqlite::sqlite_exception &e)
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Page> PageDaoSqlite::getPageList(QueryOption option)
|
||||
std::vector<std::string> PageDaoSqlite::getPageList(QueryOption option)
|
||||
{
|
||||
std::vector<Page> result;
|
||||
std::vector<std::string> result;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
std::string queryOption = SqliteQueryOption(option)
|
||||
.setOrderByColumn("lower(name)")
|
||||
.setListedColumnName("listed")
|
||||
.setVisibleColumnName("visible")
|
||||
.setPrependWhere(true)
|
||||
.build();
|
||||
std::string query = "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE "
|
||||
"id = parent) FROM page " +
|
||||
queryOption;
|
||||
*db << query >> [&](unsigned int pageid, std::string name, std::string title, unsigned int current_revision,
|
||||
bool listed, bool feedlisted, std::string parent)
|
||||
{
|
||||
Page tmp;
|
||||
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);
|
||||
};
|
||||
std::string query = "SELECT name FROM page " + queryOption;
|
||||
|
||||
*db << query >> [&](std::string name) { result.push_back(name); };
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &e)
|
||||
{
|
||||
@ -226,8 +183,7 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
|
||||
auto query =
|
||||
*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? "
|
||||
<< ftsEscape(name);
|
||||
query >> [&](std::string pagename)
|
||||
{
|
||||
query >> [&](std::string pagename) {
|
||||
SearchResult sresult;
|
||||
sresult.pagename = pagename;
|
||||
sresult.query = name;
|
||||
@ -274,11 +230,3 @@ int PageDaoSqlite::fetchPageId(std::string pagename)
|
||||
auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename;
|
||||
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;
|
||||
}
|
||||
|
@ -20,16 +20,13 @@ class PageDaoSqlite : public PageDao, protected SqliteDao
|
||||
bool exists(std::string name) const override;
|
||||
void save(const Page &page) 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::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;
|
||||
using SqliteDao::SqliteDao;
|
||||
int fetchPageId(std::string pagename);
|
||||
std::vector<SearchResult> search(std::string query, QueryOption option) 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
|
||||
|
@ -1,6 +1,5 @@
|
||||
#ifndef PERMISSIONSDAO_H
|
||||
#define PERMISSIONSDAO_H
|
||||
#include <optional>
|
||||
#include "../permissions.h"
|
||||
#include "../user.h"
|
||||
class PermissionsDao
|
||||
@ -8,10 +7,6 @@ class PermissionsDao
|
||||
public:
|
||||
PermissionsDao();
|
||||
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 clearForPage(std::string pagename) = 0;
|
||||
|
||||
virtual ~PermissionsDao() = default;
|
||||
};
|
||||
|
||||
#endif // PERMISSIONSDAO_H
|
||||
|
@ -41,34 +41,3 @@ std::optional<Permissions> PermissionsDaoSqlite::find(std::string pagename, std:
|
||||
|
||||
return Permissions{permissions};
|
||||
}
|
||||
|
||||
void PermissionsDaoSqlite::save(std::string pagename, std::string username, Permissions perms)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto query =
|
||||
*db
|
||||
<< "INSERT OR REPLACE INTO permissions (id, permissions, userid, page) VALUES((SELECT id FROM permissions "
|
||||
"WHERE page = (SELECT id FROM page WHERE name = ?) AND userid = (SELECT id FROM user WHERE username = "
|
||||
"?)), ?, (SELECT id FROM user WHERE username = ?), (SELECT id FROM page WHERE name = ?))";
|
||||
query << pagename << username << perms.getPermissions() << username << pagename;
|
||||
query.execute();
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &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);
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,6 @@ class PermissionsDaoSqlite : public PermissionsDao, protected SqliteDao
|
||||
PermissionsDaoSqlite();
|
||||
|
||||
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 clearForPage(std::string pagename) override;
|
||||
using SqliteDao::SqliteDao;
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,7 @@ class QueryOption
|
||||
unsigned int offset = 0;
|
||||
unsigned int limit = 0;
|
||||
SORT_ORDER order = ASCENDING;
|
||||
bool includeUnlisted = true;
|
||||
bool includeInvisible = true;
|
||||
};
|
||||
|
||||
#endif // QUERYOPTION_H
|
||||
|
@ -52,7 +52,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
|
||||
{
|
||||
SqliteQueryOption queryOption{options};
|
||||
std::string queryOptionSql = queryOption.setPrependWhere(true)
|
||||
.setListedColumnName("page.listed")
|
||||
.setVisibleColumnName("page.visible")
|
||||
.setOrderByColumn("creationtime")
|
||||
.build();
|
||||
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 " +
|
||||
queryOptionSql;
|
||||
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;
|
||||
r.author = author;
|
||||
r.comment = comment;
|
||||
@ -92,7 +91,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
|
||||
{
|
||||
SqliteQueryOption queryOption{option};
|
||||
std::string queryOptionSql = queryOption.setPrependWhere(false)
|
||||
.setListedColumnName("page.listed")
|
||||
.setVisibleColumnName("page.visible")
|
||||
.setOrderByColumn("creationtime")
|
||||
.build();
|
||||
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;
|
||||
|
||||
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;
|
||||
r.author = author;
|
||||
r.comment = comment;
|
||||
@ -131,9 +129,9 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
|
||||
try
|
||||
{
|
||||
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
|
||||
"strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON "
|
||||
"revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid";
|
||||
query << pagename;
|
||||
"strftime('%s',creationtime), page, revisionid FROM revision WHERE page = (SELECT id FROM "
|
||||
"page WHERE name = ? ) AND revisionid = (SELECT lastrevision FROM page WHERE name = ?)";
|
||||
query << pagename << pagename;
|
||||
query >>
|
||||
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 =
|
||||
*db
|
||||
<< "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), "
|
||||
"page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND "
|
||||
"revisionid = ? ";
|
||||
"page, revisionid FROM revision WHERE page = (SELECT id FROM page WHERE name = ? ) AND revisionid = ?";
|
||||
query << pagename << revision;
|
||||
query >>
|
||||
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
|
||||
|
@ -10,7 +10,6 @@ class SessionDao
|
||||
virtual void save(const Session &session) = 0;
|
||||
virtual std::optional<Session> find(std::string token) = 0;
|
||||
virtual void deleteSession(std::string token) = 0;
|
||||
virtual std::vector<Session> fetch() = 0;
|
||||
virtual ~SessionDao()
|
||||
{
|
||||
}
|
||||
|
63
database/sessiondaosqlite.cpp
Executable file → Normal file
63
database/sessiondaosqlite.cpp
Executable file → Normal 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)
|
||||
{
|
||||
Session result;
|
||||
@ -85,7 +62,25 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
|
||||
int userid;
|
||||
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)
|
||||
{
|
||||
@ -97,23 +92,3 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -6,15 +6,11 @@
|
||||
|
||||
class SessionDaoSqlite : public SessionDao, protected SqliteDao
|
||||
{
|
||||
private:
|
||||
void fillSession(int userid, Session &sess);
|
||||
|
||||
public:
|
||||
SessionDaoSqlite();
|
||||
void save(const Session &session) override;
|
||||
std::optional<Session> find(std::string token) override;
|
||||
void deleteSession(std::string token) override;
|
||||
std::vector<Session> fetch() override;
|
||||
using SqliteDao::SqliteDao;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <atomic>
|
||||
#include "sqlite.h"
|
||||
#include "../logger.h"
|
||||
#include "pagedaosqlite.h"
|
||||
#include "revisiondaosqlite.h"
|
||||
#include "sessiondaosqlite.h"
|
||||
#include "sqlite_modern_cpp.h"
|
||||
#include "userdaosqlite.h"
|
||||
#include "categorydaosqlite.h"
|
||||
#include "exceptions.h"
|
||||
#include "permissionsdaosqlite.h"
|
||||
|
||||
thread_local sqlite::database *Sqlite::db = nullptr;
|
||||
|
||||
std::atomic<int> instances = 0;
|
||||
Sqlite::Sqlite(std::string path) : Database(path)
|
||||
{
|
||||
instances++;
|
||||
if(instances.load() > 1)
|
||||
{
|
||||
std::cerr << "temporal (yeah, right) HACK... only one instance allowed" << std::endl;
|
||||
abort();
|
||||
}
|
||||
this->db = std::make_shared<sqlite::database>(path);
|
||||
|
||||
*db << "PRAGMA journal_mode=WAL;";
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return create<RevisionDaoSqlite>();
|
||||
@ -87,20 +67,27 @@ std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
|
||||
|
||||
void Sqlite::beginTransaction()
|
||||
{
|
||||
*db << "begin;";
|
||||
if(!inTransaction)
|
||||
{
|
||||
*db << "begin;";
|
||||
inTransaction = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Sqlite::rollbackTransaction()
|
||||
{
|
||||
*db << "rollback;";
|
||||
if(inTransaction)
|
||||
{
|
||||
*db << "rollback;";
|
||||
inTransaction = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Sqlite::commitTransaction()
|
||||
{
|
||||
*db << "commit;";
|
||||
}
|
||||
|
||||
Sqlite::~Sqlite()
|
||||
{
|
||||
delete this->db;
|
||||
if(inTransaction)
|
||||
{
|
||||
*db << "commit;";
|
||||
inTransaction = false;
|
||||
}
|
||||
}
|
||||
|
@ -8,15 +8,14 @@
|
||||
class Sqlite : public Database
|
||||
{
|
||||
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
|
||||
{
|
||||
return std::make_unique<T>(database());
|
||||
return std::make_unique<T>(db);
|
||||
}
|
||||
|
||||
sqlite::database &database() const;
|
||||
|
||||
public:
|
||||
Sqlite(std::string path);
|
||||
std::unique_ptr<PageDao> createPageDao() const;
|
||||
@ -28,7 +27,6 @@ class Sqlite : public Database
|
||||
void beginTransaction();
|
||||
void commitTransaction();
|
||||
void rollbackTransaction();
|
||||
virtual ~Sqlite();
|
||||
};
|
||||
|
||||
#endif // SQLITE_H
|
||||
|
@ -22,17 +22,18 @@ SOFTWARE.
|
||||
|
||||
bool SqliteDao::execBool(sqlite::database_binder &binder) const
|
||||
{
|
||||
bool result = false;
|
||||
bool result;
|
||||
try
|
||||
{
|
||||
bool result;
|
||||
binder >> result;
|
||||
return result;
|
||||
}
|
||||
catch(sqlite::sqlite_exception &e)
|
||||
{
|
||||
// TODO: well, we may want to check whether rows have found or not and thus log this here
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int SqliteDao::execInt(sqlite::database_binder &binder) const
|
||||
@ -51,5 +52,4 @@ int SqliteDao::execInt(sqlite::database_binder &binder) const
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -12,20 +12,20 @@
|
||||
class SqliteDao
|
||||
{
|
||||
protected:
|
||||
sqlite::database *db = nullptr;
|
||||
std::shared_ptr<sqlite::database> db;
|
||||
|
||||
public:
|
||||
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
|
||||
@ -37,8 +37,6 @@ class SqliteDao
|
||||
|
||||
bool execBool(sqlite::database_binder &binder) const;
|
||||
int execInt(sqlite::database_binder &binder) const;
|
||||
|
||||
virtual ~SqliteDao() = default;
|
||||
};
|
||||
|
||||
#endif // SQLITEDAO_H
|
||||
|
@ -31,9 +31,9 @@ SqliteQueryOption &SqliteQueryOption::setOrderByColumn(std::string name)
|
||||
return *this;
|
||||
}
|
||||
|
||||
SqliteQueryOption &SqliteQueryOption::setListedColumnName(std::string name)
|
||||
SqliteQueryOption &SqliteQueryOption::setVisibleColumnName(std::string name)
|
||||
{
|
||||
this->listedColumnName = name;
|
||||
this->visibleColumnName = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -46,18 +46,15 @@ SqliteQueryOption &SqliteQueryOption::setPrependWhere(bool b)
|
||||
std::string SqliteQueryOption::build()
|
||||
{
|
||||
std::string result;
|
||||
if(this->prependWhere)
|
||||
if(!o.includeInvisible && !this->visibleColumnName.empty())
|
||||
{
|
||||
result += "WHERE ";
|
||||
}
|
||||
if(!o.includeUnlisted && !this->listedColumnName.empty())
|
||||
{
|
||||
result += this->listedColumnName + " = 1";
|
||||
}
|
||||
else
|
||||
{
|
||||
result += " 1 = 1";
|
||||
if(this->prependWhere)
|
||||
{
|
||||
result += "WHERE ";
|
||||
}
|
||||
result += this->visibleColumnName + " = 1";
|
||||
}
|
||||
|
||||
result += " ORDER BY " + orderByColumnName;
|
||||
if(o.order == ASCENDING)
|
||||
{
|
||||
@ -69,8 +66,7 @@ std::string SqliteQueryOption::build()
|
||||
}
|
||||
// TODO: limits for offset?
|
||||
if(o.limit > 0)
|
||||
{
|
||||
result += " LIMIT " + std::to_string(o.limit) + " OFFSET " + std::to_string(o.offset);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ class SqliteQueryOption
|
||||
{
|
||||
private:
|
||||
QueryOption o;
|
||||
std::string listedColumnName;
|
||||
std::string visibleColumnName;
|
||||
std::string orderByColumnName;
|
||||
|
||||
bool prependWhere;
|
||||
@ -17,7 +17,7 @@ class SqliteQueryOption
|
||||
|
||||
SqliteQueryOption &setOrderByColumn(std::string name);
|
||||
|
||||
SqliteQueryOption &setListedColumnName(std::string name);
|
||||
SqliteQueryOption &setVisibleColumnName(std::string name);
|
||||
|
||||
SqliteQueryOption &setPrependWhere(bool b);
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include "../user.h"
|
||||
#include "queryoption.h"
|
||||
class UserDao
|
||||
{
|
||||
public:
|
||||
@ -11,7 +10,6 @@ class UserDao
|
||||
virtual bool exists(std::string username) = 0;
|
||||
virtual std::optional<User> find(std::string username) = 0;
|
||||
virtual std::optional<User> find(int id) = 0;
|
||||
virtual std::vector<User> list(QueryOption o) = 0;
|
||||
virtual void deleteUser(std::string username) = 0;
|
||||
virtual void save(const User &u) = 0;
|
||||
virtual ~UserDao(){};
|
||||
|
@ -23,7 +23,6 @@ SOFTWARE.
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include "userdaosqlite.h"
|
||||
#include "sqlitequeryoption.h"
|
||||
|
||||
UserDaoSqlite::UserDaoSqlite()
|
||||
{
|
||||
@ -37,6 +36,7 @@ bool UserDaoSqlite::exists(std::string username)
|
||||
|
||||
std::optional<User> UserDaoSqlite::find(std::string username)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
User user;
|
||||
@ -47,7 +47,7 @@ std::optional<User> UserDaoSqlite::find(std::string username)
|
||||
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
|
||||
user.permissions = Permissions{perms};
|
||||
|
||||
return user;
|
||||
return std::move(user);
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &e)
|
||||
{
|
||||
@ -57,7 +57,6 @@ std::optional<User> UserDaoSqlite::find(std::string username)
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<User> UserDaoSqlite::find(int id)
|
||||
@ -71,7 +70,7 @@ std::optional<User> UserDaoSqlite::find(int id)
|
||||
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
|
||||
user.permissions = Permissions{perms};
|
||||
|
||||
return user;
|
||||
return std::move(user);
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &e)
|
||||
{
|
||||
@ -81,43 +80,9 @@ std::optional<User> UserDaoSqlite::find(int id)
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<User> UserDaoSqlite::list(QueryOption o)
|
||||
{
|
||||
std::vector<User> result;
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
std::string queryOption = SqliteQueryOption(o).setOrderByColumn("username").setPrependWhere(true).build();
|
||||
std::string query = "SELECT username, password, salt, permissions, enabled FROM user " + queryOption;
|
||||
|
||||
*db << query >>
|
||||
[&](std::string username, std::vector<char> pw, std::vector<char> salt, int permisisons, bool enabled)
|
||||
{
|
||||
User tmp;
|
||||
tmp.login = username;
|
||||
tmp.password = pw;
|
||||
tmp.salt = salt;
|
||||
tmp.permissions = Permissions{permisisons};
|
||||
tmp.enabled = enabled;
|
||||
result.push_back(tmp);
|
||||
};
|
||||
}
|
||||
catch(const sqlite::errors::no_rows &e)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
catch(sqlite::sqlite_exception &e)
|
||||
{
|
||||
throwFrom(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void UserDaoSqlite::deleteUser([[maybe_unused]] std::string username)
|
||||
void UserDaoSqlite::deleteUser(std::string username)
|
||||
{
|
||||
// What to do with the contributions of the user?
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ class UserDaoSqlite : public UserDao, protected SqliteDao
|
||||
std::optional<User> find(std::string username);
|
||||
std::optional<User> find(int id);
|
||||
|
||||
std::vector<User> list(QueryOption o);
|
||||
void deleteUser(std::string username);
|
||||
void save(const User &u);
|
||||
using SqliteDao::SqliteDao;
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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 = ↦
|
||||
}
|
@ -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
|
@ -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 {};
|
||||
}
|
@ -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
|
@ -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();
|
||||
}
|
@ -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
|
@ -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 = ↦
|
||||
}
|
@ -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
|
@ -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();
|
||||
}
|
@ -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
|
@ -41,7 +41,7 @@ Request HttpGateway::convertRequest(httplib::Request request)
|
||||
// TODO: this eats resources, where perhaps it does not need to. move it to request?
|
||||
for(auto &it : request.params)
|
||||
{
|
||||
it.second = utils::html_xss(it.second);
|
||||
it.second = utils::html_xss(std::string{it.second});
|
||||
}
|
||||
if(request.method == "GET")
|
||||
{
|
||||
@ -83,8 +83,7 @@ void HttpGateway::work(RequestWorker &worker)
|
||||
{
|
||||
httplib::Server server;
|
||||
server.set_payload_max_length(this->maxPayloadLength);
|
||||
auto handler = [&](const httplib::Request &req, httplib::Response &res)
|
||||
{
|
||||
auto handler = [&](const httplib::Request &req, httplib::Response &res) {
|
||||
Request wikiRequest = convertRequest(req);
|
||||
Logger::debug() << "httpgateway: received request " << wikiRequest;
|
||||
Response wikiresponse = worker.processRequest(wikiRequest);
|
||||
|
@ -1,8 +1,5 @@
|
||||
#ifndef HTTPGATEWAY_H
|
||||
#define HTTPGATEWAY_H
|
||||
|
||||
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 65536
|
||||
|
||||
#include <httplib.h>
|
||||
#include "gatewayinterface.h"
|
||||
#include "../requestworker.h"
|
||||
|
26
grouper.h
26
grouper.h
@ -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;
|
||||
}
|
||||
};
|
@ -34,7 +34,7 @@ void Handler::setGeneralVars(TemplatePage &page)
|
||||
}
|
||||
Response Handler::errorResponse(std::string errortitle, std::string errormessage, int status)
|
||||
{
|
||||
TemplatePage error = this->templ->getPage("error");
|
||||
TemplatePage &error = this->templ->getPage("error");
|
||||
error.setVar("title", createPageTitle(errortitle));
|
||||
error.setVar("errortitle", errortitle);
|
||||
error.setVar("errormessage", errormessage);
|
||||
@ -53,7 +53,7 @@ std::string Handler::createPageTitle(std::string title)
|
||||
QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const
|
||||
{
|
||||
QueryOption result;
|
||||
result.includeUnlisted = false;
|
||||
result.includeInvisible = false;
|
||||
try
|
||||
{
|
||||
result.limit = utils::toUInt(r.get("limit"));
|
||||
@ -98,10 +98,7 @@ Response Handler::handle(const Request &r)
|
||||
|
||||
Permissions Handler::effectivePermissions(std::string page)
|
||||
{
|
||||
Permissions &userPerms = this->userSession->user.permissions;
|
||||
if(userPerms.isAdmin())
|
||||
{
|
||||
return userPerms;
|
||||
}
|
||||
return this->database->createPermissionsDao()->find(page, this->userSession->user.login).value_or(userPerms);
|
||||
return this->database->createPermissionsDao()
|
||||
->find(page, this->userSession->user.login)
|
||||
.value_or(this->userSession->user.permissions);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
#ifndef HANDLER_H
|
||||
#define HANDLER_H
|
||||
#include <memory>
|
||||
#include "../config.h"
|
||||
#include "../response.h"
|
||||
#include "../request.h"
|
||||
@ -10,12 +9,13 @@
|
||||
#include "../database/queryoption.h"
|
||||
#include "../logger.h"
|
||||
#include "../cache/icache.h"
|
||||
#include "../dynamic/dynamiccontent.h"
|
||||
#include "../iparser.h"
|
||||
|
||||
class Handler
|
||||
{
|
||||
protected:
|
||||
ICache *cache;
|
||||
IParser *parser;
|
||||
Template *templ;
|
||||
Database *database;
|
||||
Session *userSession;
|
||||
@ -28,7 +28,7 @@ class Handler
|
||||
|
||||
public:
|
||||
Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider,
|
||||
ICache &cache)
|
||||
ICache &cache, IParser &parser)
|
||||
{
|
||||
this->handlersConfig = &handlersConfig;
|
||||
this->templ = &templ;
|
||||
@ -36,15 +36,16 @@ class Handler
|
||||
this->userSession = &userSession;
|
||||
this->urlProvider = &provider;
|
||||
this->cache = &cache;
|
||||
this->parser = &parser;
|
||||
}
|
||||
|
||||
virtual Response handle(const Request &r);
|
||||
virtual Response handleRequest([[maybe_unused]] const Request &r)
|
||||
virtual Response handleRequest(const Request &r)
|
||||
{
|
||||
return this->errorResponse("Invalid action", "This action is not implemented yet");
|
||||
}
|
||||
|
||||
virtual bool canAccess([[maybe_unused]] const Permissions &perms)
|
||||
virtual bool canAccess(const Permissions &perms)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -56,12 +57,6 @@ class Handler
|
||||
virtual ~Handler()
|
||||
{
|
||||
}
|
||||
|
||||
template <class T> inline std::shared_ptr<T> createDynamic()
|
||||
{
|
||||
return std::make_shared<T>(*this->templ, *this->database, *this->urlProvider);
|
||||
}
|
||||
|
||||
Response errorResponse(std::string errortitle, std::string errormessage, int status = 200);
|
||||
std::string createPageTitle(std::string append);
|
||||
};
|
||||
|
@ -32,7 +32,7 @@ Response HandlerAllCategories::handleRequest(const Request &r)
|
||||
"No categories",
|
||||
"This wiki does not have any categories defined yet or your query options did not yield any results");
|
||||
}
|
||||
TemplatePage searchPage = this->templ->getPage("allcategories");
|
||||
TemplatePage &searchPage = this->templ->getPage("allcategories");
|
||||
std::string body =
|
||||
this->templ->renderSearch(resultList, [&](std::string str) { return this->urlProvider->category(str); });
|
||||
searchPage.setVar("categorylist", body);
|
||||
|
@ -19,8 +19,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "handlerallpages.h"
|
||||
#include "../grouper.h"
|
||||
#include "../pagelistrenderer.h"
|
||||
|
||||
Response HandlerAllPages::handleRequest(const Request &r)
|
||||
{
|
||||
@ -29,21 +27,17 @@ Response HandlerAllPages::handleRequest(const Request &r)
|
||||
Response response;
|
||||
auto pageDao = this->database->createPageDao();
|
||||
QueryOption qo = queryOption(r);
|
||||
std::vector<Page> pageList = pageDao->getPageList(qo);
|
||||
if(pageList.size() == 0)
|
||||
auto resultList = pageDao->getPageList(qo);
|
||||
if(resultList.size() == 0)
|
||||
{
|
||||
return errorResponse("No pages", "This wiki does not have any pages yet");
|
||||
}
|
||||
|
||||
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database);
|
||||
TemplatePage allPages = this->templ->getPage("allpages");
|
||||
allPages.setVar("pagelistcontent", pagelistrender.render(pageList, r.get("rendertype")));
|
||||
allPages.setVar("pagelistletterlink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_LETTER));
|
||||
allPages.setVar("pagelistcreationdatelink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_CREATIONDATE));
|
||||
|
||||
allPages.setVar("title", createPageTitle("All pages"));
|
||||
setGeneralVars(allPages);
|
||||
response.setBody(allPages.render());
|
||||
TemplatePage &searchPage = this->templ->getPage("allpages");
|
||||
std::string body = this->templ->renderSearch(resultList);
|
||||
searchPage.setVar("pagelist", body);
|
||||
searchPage.setVar("title", createPageTitle("All pages"));
|
||||
setGeneralVars(searchPage);
|
||||
response.setBody(searchPage.render());
|
||||
response.setStatus(200);
|
||||
return response;
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include "handlercategory.h"
|
||||
#include "../pagelistrenderer.h"
|
||||
|
||||
Response HandlerCategory::handleRequest(const Request &r)
|
||||
{
|
||||
@ -34,13 +33,9 @@ Response HandlerCategory::handleRequest(const Request &r)
|
||||
}
|
||||
QueryOption qo = queryOption(r);
|
||||
auto resultList = categoryDao->fetchMembers(categoryname, qo);
|
||||
TemplatePage searchPage = this->templ->getPage("show_category");
|
||||
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database);
|
||||
|
||||
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));
|
||||
TemplatePage &searchPage = this->templ->getPage("show_category");
|
||||
std::string body = this->templ->renderSearch(resultList);
|
||||
searchPage.setVar("pagelist", body);
|
||||
searchPage.setVar("categoryname", categoryname);
|
||||
setGeneralVars(searchPage);
|
||||
searchPage.setVar("title", createPageTitle("Category: " + categoryname));
|
||||
|
@ -20,7 +20,7 @@ SOFTWARE.
|
||||
*/
|
||||
#include "handlerdefault.h"
|
||||
|
||||
Response HandlerDefault::handleRequest([[maybe_unused]] const Request &r)
|
||||
Response HandlerDefault::handleRequest(const Request &r)
|
||||
{
|
||||
return Response::redirectTemporarily(this->urlProvider->index());
|
||||
}
|
||||
@ -29,7 +29,7 @@ HandlerDefault::~HandlerDefault()
|
||||
{
|
||||
}
|
||||
|
||||
bool HandlerDefault::canAccess([[maybe_unused]] const Permissions &perms)
|
||||
bool HandlerDefault::canAccess(const Permissions &perms)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ SOFTWARE.
|
||||
#include "handlerhistory.h"
|
||||
#include "handlerpagedelete.h"
|
||||
#include "handlerusersettings.h"
|
||||
#include "handlerversion.h"
|
||||
#include "handlerfeedgenerator.h"
|
||||
|
||||
std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession)
|
||||
{
|
||||
if(action == "" || action == "index")
|
||||
@ -81,14 +80,6 @@ std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action
|
||||
{
|
||||
return produce<HandlerUserSettings>(userSession);
|
||||
}
|
||||
if(action == "version")
|
||||
{
|
||||
return produce<HandlerVersion>(userSession);
|
||||
}
|
||||
if(action == "feed")
|
||||
{
|
||||
return produce<HandlerFeedGenerator>(userSession);
|
||||
}
|
||||
|
||||
return produce<HandlerInvalidAction>(userSession);
|
||||
}
|
||||
|
@ -10,15 +10,16 @@ class HandlerFactory
|
||||
Database &db;
|
||||
UrlProvider &urlProvider;
|
||||
ICache &cache;
|
||||
IParser &parser;
|
||||
|
||||
template <class T> inline std::unique_ptr<T> produce(Session &userSession)
|
||||
{
|
||||
return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache);
|
||||
return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache, parser);
|
||||
}
|
||||
|
||||
public:
|
||||
HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache)
|
||||
: handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache)
|
||||
HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache, IParser &parser)
|
||||
: handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache), parser(parser)
|
||||
{
|
||||
}
|
||||
std::unique_ptr<Handler> createHandler(const std::string &action, Session &userSession);
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -50,8 +50,7 @@ Response HandlerHistory::handleRequest(const Request &r)
|
||||
std::vector<Revision> resultList;
|
||||
auto revisionDao = this->database->createRevisionDao();
|
||||
|
||||
auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order)
|
||||
{
|
||||
auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order) {
|
||||
if(!page.empty())
|
||||
{
|
||||
return this->urlProvider->pageHistorySort(page, limit, offset, order);
|
||||
@ -123,7 +122,7 @@ Response HandlerHistory::handleRequest(const Request &r)
|
||||
return response;
|
||||
}
|
||||
|
||||
bool HandlerHistory::canAccess([[maybe_unused]] const Permissions &perms)
|
||||
bool HandlerHistory::canAccess(const Permissions &perms)
|
||||
{
|
||||
return true; // This is a lie but we need to this a little more fine grained here, which we do in the handleRequest
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ SOFTWARE.
|
||||
*/
|
||||
#include "handlerinvalidaction.h"
|
||||
|
||||
Response HandlerInvalidAction::handleRequest([[maybe_unused]] const Request &r)
|
||||
Response HandlerInvalidAction::handleRequest(const Request &r)
|
||||
{
|
||||
return errorResponse("Invalid action", "No action defined for this action");
|
||||
}
|
||||
|
||||
bool HandlerInvalidAction::canAccess([[maybe_unused]] const Permissions &perms)
|
||||
bool HandlerInvalidAction::canAccess(const Permissions &perms)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ Response HandlerLogin::handleRequest(const Request &r)
|
||||
{
|
||||
page = "index";
|
||||
}
|
||||
TemplatePage loginTemplatePage = this->templ->getPage("login");
|
||||
TemplatePage &loginTemplatePage = this->templ->getPage("login");
|
||||
setGeneralVars(loginTemplatePage);
|
||||
loginTemplatePage.setVar("loginurl", urlProvider->login(page));
|
||||
loginTemplatePage.setVar("title", createPageTitle("Login"));
|
||||
@ -66,7 +66,7 @@ Response HandlerLogin::handleRequest(const Request &r)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HandlerLogin::canAccess([[maybe_unused]] const Permissions &perms)
|
||||
bool HandlerLogin::canAccess(const Permissions &perms)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -27,18 +27,7 @@ Response HandlerPage::handle(const Request &r)
|
||||
auto pageDao = this->database->createPageDao();
|
||||
if(pagename.empty())
|
||||
{
|
||||
std::string title = r.get("title");
|
||||
if(title.empty())
|
||||
{
|
||||
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;
|
||||
return errorResponse("No page given", "No page given to request");
|
||||
}
|
||||
|
||||
if(pageMustExist() && !pageDao->exists(pagename))
|
||||
@ -74,9 +63,8 @@ void HandlerPage::setPageVars(TemplatePage &page, std::string pagename)
|
||||
if(!pagename.empty())
|
||||
{
|
||||
std::string headerlinks;
|
||||
TemplatePage headerlink = this->templ->getPage("_headerlink");
|
||||
auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value)
|
||||
{
|
||||
TemplatePage &headerlink = this->templ->getPage("_headerlink");
|
||||
auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value) {
|
||||
headerlink.setVar("href", href);
|
||||
headerlink.setVar("value", value);
|
||||
headerlinks += headerlink.render();
|
||||
|
@ -45,7 +45,6 @@ Response HandlerPageDelete::handleRequest(PageDao &pageDao, std::string pagename
|
||||
{
|
||||
pageDao.deletePage(pagename);
|
||||
this->cache->removePrefix("page:"); // TODO: overkill?
|
||||
this->cache->removePrefix("feed:");
|
||||
return Response::redirectTemporarily(this->urlProvider->index());
|
||||
}
|
||||
TemplatePage delPage = this->templ->getPage("page_deletion");
|
||||
|
@ -21,13 +21,11 @@ SOFTWARE.
|
||||
#include "handlerpageedit.h"
|
||||
#include "../database/exceptions.h"
|
||||
#include "../request.h"
|
||||
#include "../parserlegacy.h"
|
||||
|
||||
#include "../parser.h"
|
||||
#include "../revisionrenderer.h"
|
||||
|
||||
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
|
||||
bool HandlerPageEdit::canAccess(std::string page)
|
||||
{
|
||||
return effectivePermissions(page).canEdit();
|
||||
return this->userSession->user.permissions.canEdit();
|
||||
}
|
||||
|
||||
bool HandlerPageEdit::pageMustExist()
|
||||
@ -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");
|
||||
}
|
||||
auto revisiondao = this->database->createRevisionDao();
|
||||
auto revision = revisiondao->getCurrentForPage(pagename);
|
||||
auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
|
||||
std::string body;
|
||||
|
||||
unsigned int current_revision = 0;
|
||||
@ -52,16 +50,6 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
body = revision->content;
|
||||
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.post("do") == "submit")
|
||||
@ -71,32 +59,13 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
|
||||
// TODO: must check, whether categories differ, and perhaps don't allow every user
|
||||
// to set categories
|
||||
Parser parser;
|
||||
ParserLegacy parser;
|
||||
std::vector<std::string> cats = parser.extractCategories(newContent);
|
||||
try
|
||||
{
|
||||
this->database->beginTransaction();
|
||||
|
||||
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 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;
|
||||
std::optional<Page> currentPage = pageDao.find(pagename);
|
||||
if(currentPage)
|
||||
@ -111,52 +80,9 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
}
|
||||
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.listed = !(listedcmd == "0");
|
||||
page.feedlisted = !(feedlistedcmd == "0");
|
||||
page.listed = !(visiblecmd == "0");
|
||||
page.name = pagename;
|
||||
page.title = customtitle;
|
||||
page.parentpage = parentpage;
|
||||
if(page.title.empty())
|
||||
{
|
||||
page.title = page.name;
|
||||
}
|
||||
pageDao.save(page);
|
||||
|
||||
Revision newRevision;
|
||||
@ -169,11 +95,9 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
pageDao.setCategories(pagename, cats);
|
||||
this->database->commitTransaction();
|
||||
this->cache->removePrefix("page:"); // TODO: overkill?
|
||||
this->cache->removePrefix("feed:");
|
||||
}
|
||||
catch(const DatabaseException &e)
|
||||
{
|
||||
this->database->rollbackTransaction();
|
||||
Logger::debug() << "Error saving revision: " << e.what();
|
||||
return errorResponse("Database error", "A database error occured while trying to save this revision");
|
||||
}
|
||||
@ -183,17 +107,13 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
if(r.post("do") == "preview")
|
||||
{
|
||||
std::string newContent = r.post("content");
|
||||
Parser parser;
|
||||
std::string title = parser.extractCommand("pagetitle", newContent);
|
||||
ParserLegacy parser;
|
||||
TemplatePage templatePage = this->templ->getPage("page_creation_preview");
|
||||
templatePage.setVar("actionurl", urlProvider->editPage(pagename));
|
||||
|
||||
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
|
||||
|
||||
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
|
||||
templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent));
|
||||
templatePage.setVar("content", newContent);
|
||||
setPageVars(templatePage, pagename);
|
||||
templatePage.setVar("title", createPageTitle("Preview: " + title));
|
||||
templatePage.setVar("title", createPageTitle("Preview: " + pagename));
|
||||
templatePage.setVar("comment", r.post("comment"));
|
||||
Response response;
|
||||
response.setBody(templatePage.render());
|
||||
@ -201,7 +121,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
}
|
||||
}
|
||||
|
||||
TemplatePage templatePage = this->templ->getPage("page_creation");
|
||||
TemplatePage &templatePage = this->templ->getPage("page_creation");
|
||||
templatePage.setVar("actionurl", urlProvider->editPage(pagename));
|
||||
templatePage.setVar("content", body);
|
||||
setPageVars(templatePage, pagename);
|
||||
|
@ -21,13 +21,8 @@ SOFTWARE.
|
||||
#include "handlerpageview.h"
|
||||
#include "../database/exceptions.h"
|
||||
#include "../logger.h"
|
||||
#include "../parser.h"
|
||||
#include "../parserlegacy.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)
|
||||
{
|
||||
@ -60,7 +55,7 @@ std::string HandlerPageView::createIndexContent(IParser &parser, std::string con
|
||||
}
|
||||
previous = h.level;
|
||||
HtmlLink link;
|
||||
link.href = "#" + utils::strreplace(h.title, " ", "");
|
||||
link.href = "#" + h.title;
|
||||
link.innervalue = h.title;
|
||||
link.cssclass = "indexlink";
|
||||
indexcontent += "<li>" + link.render() + "</li>";
|
||||
@ -91,20 +86,19 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
|
||||
std::optional<Revision> revision;
|
||||
std::string templatepartname;
|
||||
auto revisionDao = this->database->createRevisionDao();
|
||||
try
|
||||
{
|
||||
if(revisionid > 0)
|
||||
{
|
||||
if(!effectivePermissions(pagename).canSeePageHistory())
|
||||
{
|
||||
auto current = revisionDao->getCurrentForPage(pagename);
|
||||
auto current = this->database->createRevisionDao()->getCurrentForPage(pagename);
|
||||
if(current && current->revision > revisionid)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return errorResponse("Revision not found", "No such revision found");
|
||||
@ -124,7 +118,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
|
||||
return r;
|
||||
}
|
||||
}
|
||||
revision = revisionDao->getCurrentForPage(pagename);
|
||||
revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
|
||||
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");
|
||||
}
|
||||
|
||||
Parser parser;
|
||||
TemplatePage &page = this->templ->getPage(templatepartname);
|
||||
|
||||
|
||||
Response result;
|
||||
result.setStatus(200);
|
||||
std::string indexcontent;
|
||||
std::string parsedcontent;
|
||||
|
||||
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
|
||||
|
||||
std::string customtitle = parser.extractCommand("pagetitle", revision->content);
|
||||
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);
|
||||
/* TODO: Dynamic includes not considered, probably fine in practise */
|
||||
std::string indexcontent = createIndexContent(parser, revision->content);
|
||||
|
||||
if(revisionid > 0)
|
||||
{
|
||||
indexcontent = createIndexContent(*parser, revision->content);
|
||||
parsedcontent = parser->parse(pageDao, *this->urlProvider, 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);
|
||||
TemplatePage page = this->templ->getPage(templatepartname);
|
||||
page.setVar("content", parsedcontent);
|
||||
page.setVar("index", indexcontent);
|
||||
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("revision", revisionstr);
|
||||
setPageVars(page, pagename);
|
||||
if(!customtitle.empty())
|
||||
{
|
||||
page.setVar("title", createPageTitle(customtitle));
|
||||
}
|
||||
std::string body = page.render();
|
||||
if(revisionid == 0 && !this->userSession->loggedIn)
|
||||
{
|
||||
|
@ -25,11 +25,7 @@ Response HandlerSearch::handleRequest(const Request &r)
|
||||
std::string q = r.get("q");
|
||||
if(q.empty())
|
||||
{
|
||||
TemplatePage searchForm = this->templ->getPage("searchform");
|
||||
response.setBody(searchForm.render());
|
||||
response.setStatus(200);
|
||||
setGeneralVars(searchForm);
|
||||
return response;
|
||||
return errorResponse("Missing search term", "No search term supplied");
|
||||
}
|
||||
|
||||
auto pageDao = this->database->createPageDao();
|
||||
@ -41,7 +37,7 @@ Response HandlerSearch::handleRequest(const Request &r)
|
||||
{
|
||||
return errorResponse("No results", "Your search for " + q + " did not yield any results.");
|
||||
}
|
||||
TemplatePage searchPage = this->templ->getPage("search");
|
||||
TemplatePage &searchPage = this->templ->getPage("search");
|
||||
std::string body = this->templ->renderSearch(resultList);
|
||||
searchPage.setVar("pagelist", body);
|
||||
searchPage.setVar("searchterm", q);
|
||||
|
@ -15,20 +15,19 @@ Response HandlerUserSettings::handleRequest(const Request &r)
|
||||
|
||||
if(newpassword != newpasswordconfirm)
|
||||
{
|
||||
// TODO: is not nice, users has to hit the back button...
|
||||
//TODO: is not nice, users has to hit the back button...
|
||||
return this->errorResponse("Passwords don't match", "The entered new passwords don't match");
|
||||
}
|
||||
auto userDao = this->database->createUserDao();
|
||||
Authenticator authenticator(*userDao);
|
||||
|
||||
std::variant<User, AuthenticationError> authresult =
|
||||
authenticator.authenticate(this->userSession->user.login, oldpassword);
|
||||
std::variant<User, AuthenticationError> authresult = authenticator.authenticate(this->userSession->user.login, oldpassword);
|
||||
if(std::holds_alternative<AuthenticationError>(authresult))
|
||||
{
|
||||
return this->errorResponse("Invalid current password", "The old password you entered is invalid");
|
||||
}
|
||||
Random r;
|
||||
std::vector<char> salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
|
||||
std::vector<char> salt = r.getRandom(23);
|
||||
User user = std::get<User>(authresult);
|
||||
user.salt = salt;
|
||||
user.password = authenticator.hash(newpassword, user.salt);
|
||||
@ -51,7 +50,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
|
||||
}
|
||||
}
|
||||
|
||||
TemplatePage userSettingsPage = this->templ->getPage("usersettings");
|
||||
TemplatePage &userSettingsPage = this->templ->getPage("usersettings");
|
||||
setGeneralVars(userSettingsPage);
|
||||
userSettingsPage.setVar("usersettingsurl", urlProvider->userSettings());
|
||||
userSettingsPage.setVar("title", createPageTitle("User settings - " + this->userSession->user.login));
|
||||
@ -62,7 +61,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HandlerUserSettings::canAccess([[maybe_unused]] const Permissions &perms)
|
||||
bool HandlerUserSettings::canAccess(const Permissions &perms)
|
||||
{
|
||||
return this->userSession->loggedIn;
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
#include "handlerversion.h"
|
||||
#include "../version.h"
|
||||
Response HandlerVersion::handleRequest([[maybe_unused]] const Request &r)
|
||||
{
|
||||
Response response;
|
||||
response.setContentType("text/plain");
|
||||
response.setBody(get_version_string());
|
||||
return response;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
#ifndef HANDLERVERSION_H
|
||||
#define HANDLERVERSION_H
|
||||
#include "handler.h"
|
||||
class HandlerVersion : public Handler
|
||||
{
|
||||
public:
|
||||
using Handler::Handler;
|
||||
|
||||
public:
|
||||
Response handleRequest(const Request &r) override;
|
||||
|
||||
bool canAccess([[maybe_unused]] const Permissions &perms) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // HANDLERVERSION_H
|
31
iparser.h
31
iparser.h
@ -2,37 +2,22 @@
|
||||
#define IPARSER_H
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
#include "headline.h"
|
||||
#include "database/pagedao.h"
|
||||
#include "urlprovider.h"
|
||||
|
||||
|
||||
class IParser
|
||||
{
|
||||
protected:
|
||||
static std::string empty(std::string_view key, std::string_view content)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
public:
|
||||
virtual std::string extractFirstTag(std::string tagname, const std::string &content) const = 0;
|
||||
virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0;
|
||||
virtual std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const = 0;
|
||||
|
||||
virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0;
|
||||
virtual 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 std::string extractCommand(std::string cmdname, std::string content) const = 0;
|
||||
virtual std::vector<Headline> extractHeadlines(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 ~IParser(){};
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PARSER_H
|
||||
|
4
logger.h
4
logger.h
@ -7,8 +7,8 @@ class Logger
|
||||
private:
|
||||
class LogEntry
|
||||
{
|
||||
bool headerSent = false;
|
||||
std::ostream *out = nullptr;
|
||||
bool headerSent;
|
||||
std::ostream *out;
|
||||
std::string prefix;
|
||||
|
||||
public:
|
||||
|
3
page.h
3
page.h
@ -7,10 +7,7 @@ class Page
|
||||
public:
|
||||
Page();
|
||||
std::string name;
|
||||
std::string title;
|
||||
std::string parentpage;
|
||||
bool listed;
|
||||
bool feedlisted;
|
||||
unsigned int current_revision;
|
||||
unsigned int pageid;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
@ -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
|
245
parser.cpp
245
parser.cpp
@ -1,245 +0,0 @@
|
||||
/* Copyright (c) 2018 Albert S.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <regex>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "parser.h"
|
||||
#include "utils.h"
|
||||
#include "htmllink.h"
|
||||
std::vector<Headline> Parser::extractHeadlines(const std::string &content) const
|
||||
{
|
||||
std::vector<Headline> result;
|
||||
|
||||
std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])";
|
||||
std::regex headerfinder(reg);
|
||||
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
|
||||
auto end = std::sregex_iterator();
|
||||
|
||||
for(auto it = begin; it != end; it++)
|
||||
{
|
||||
auto smatch = *it;
|
||||
Headline h;
|
||||
h.level = utils::toUInt(smatch.str(1));
|
||||
h.title = smatch.str(3);
|
||||
result.push_back(h);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> Parser::extractCategories(const std::string &content) const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string reg = R"(\[category\](.*?)\[/category\])";
|
||||
std::regex headerfinder(reg);
|
||||
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
|
||||
auto end = std::sregex_iterator();
|
||||
|
||||
for(auto it = begin; it != end; it++)
|
||||
{
|
||||
auto smatch = *it;
|
||||
result.emplace_back(smatch.str(1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const
|
||||
{
|
||||
std::string cmd = "[" + tagname + "]";
|
||||
std::string cmdend = "[/" + tagname + "]";
|
||||
std::string_view view = content;
|
||||
size_t pos = 0;
|
||||
if((pos = view.find(cmd)) != std::string::npos)
|
||||
{
|
||||
view.remove_prefix(pos);
|
||||
view.remove_prefix(cmd.size());
|
||||
if((pos = view.find(cmdend)) != std::string::npos)
|
||||
{
|
||||
auto result = view.substr(0, pos);
|
||||
return std::string{result};
|
||||
}
|
||||
}
|
||||
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 linktag = match.str(1);
|
||||
std::string inside = match.str(2);
|
||||
|
||||
std::vector<std::string> splitted = utils::split(inside, '|');
|
||||
HtmlLink htmllink;
|
||||
if(splitted.size() == 2)
|
||||
{
|
||||
htmllink.innervalue = splitted[1];
|
||||
htmllink.href = splitted[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
htmllink.innervalue = inside;
|
||||
htmllink.href = inside;
|
||||
}
|
||||
|
||||
if(linktag == "wikilink")
|
||||
{
|
||||
if(pageDao.exists(htmllink.href))
|
||||
{
|
||||
htmllink.cssclass = "exists";
|
||||
}
|
||||
else
|
||||
{
|
||||
htmllink.cssclass = "notexists";
|
||||
}
|
||||
|
||||
htmllink.href = urlProvider.page(htmllink.href);
|
||||
}
|
||||
|
||||
return htmllink.render();
|
||||
}
|
||||
|
||||
std::string Parser::processImage(std::smatch &match) 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;
|
||||
// we don't care about commands, but we nevertheless replace them with empty strings
|
||||
std::regex tagfinder(
|
||||
R"(\[(b|i|u|s|li|p|br|ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:listed|cmd:feedlisted|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|cmd:parentpage|content|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1](\r\n)*)");
|
||||
|
||||
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
|
||||
|
||||
result = utils::regex_callback_replacer(
|
||||
tagfinder, content,
|
||||
[&](std::smatch &match)
|
||||
{
|
||||
std::string tag = match.str(1);
|
||||
std::string content = match.str(2);
|
||||
|
||||
std::string newlines = match.str(4);
|
||||
if(newlines == "\r\n")
|
||||
{
|
||||
newlines = "<br>";
|
||||
}
|
||||
if(tag == "br")
|
||||
{
|
||||
return std::string("<br>");
|
||||
}
|
||||
if(tag != "code" && tag != "blockquote")
|
||||
{
|
||||
content = parse(pagedao, provider, content, callback);
|
||||
}
|
||||
/* [content] just helps extracting the actual content of a page, pretty much noop otherwise */
|
||||
if(tag == "content")
|
||||
{
|
||||
return parse(pagedao, provider, content, callback);
|
||||
}
|
||||
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
|
||||
{
|
||||
if(tag == "p" || tag == "br")
|
||||
{
|
||||
newlines = "";
|
||||
}
|
||||
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
|
||||
}
|
||||
if(tag == "link" || tag == "wikilink")
|
||||
{
|
||||
return this->processLink(pagedao, provider,
|
||||
match) +
|
||||
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')
|
||||
{
|
||||
return "<" + tag + " id='" + utils::strreplace(content, " ", "") + "'>" + content + "</" + tag + ">";
|
||||
}
|
||||
if(tag == "code" || tag == "blockquote")
|
||||
{
|
||||
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
|
||||
}
|
||||
return callback(tag, content);
|
||||
});
|
||||
result = utils::strreplace(result, "\r\n", "<br>");
|
||||
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)); });
|
||||
}
|
28
parser.h
28
parser.h
@ -1,28 +0,0 @@
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
#include "iparser.h"
|
||||
|
||||
class Parser : public IParser
|
||||
{
|
||||
private:
|
||||
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
|
||||
std::string processImage(std::smatch &match) const;
|
||||
|
||||
public:
|
||||
std::string extractFirstTag(std::string tagname, const std::string &content) const override;
|
||||
std::string extractCommand(std::string cmdname, const std::string &content) const override;
|
||||
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override;
|
||||
std::vector<Headline> extractHeadlines(const std::string &content) const override;
|
||||
std::vector<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;
|
||||
};
|
||||
|
||||
#endif // PARSER_H
|
139
parserlegacy.cpp
Normal file
139
parserlegacy.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
/* Copyright (c) 2018 Albert S.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
#include <regex>
|
||||
#include <iostream>
|
||||
#include <regex>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "parserlegacy.h"
|
||||
#include "utils.h"
|
||||
#include "htmllink.h"
|
||||
std::vector<Headline> ParserLegacy::extractHeadlines(std::string content) const
|
||||
{
|
||||
std::vector<Headline> result;
|
||||
utils::regex_callback_extractor(std::regex(R"(\[h(1|2|3)\](.*?)\[/h\1\])"), content, [&](std::smatch &smatch) {
|
||||
Headline h;
|
||||
h.level = utils::toUInt(smatch.str(1));
|
||||
h.title = smatch.str(2);
|
||||
result.push_back(h);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> ParserLegacy::extractCategories(std::string content) const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string reg = R"(\[category\](.*?)\[/category\])";
|
||||
std::regex headerfinder(reg);
|
||||
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
|
||||
auto end = std::sregex_iterator();
|
||||
|
||||
for(auto it = begin; it != end; it++)
|
||||
{
|
||||
auto smatch = *it;
|
||||
result.emplace_back(smatch.str(1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ParserLegacy::extractCommand(std::string cmdname, std::string content) const
|
||||
{
|
||||
std::string cmd = "[cmd:" + cmdname + "]";
|
||||
std::string cmdend = "[/cmd:" + cmdname + "]";
|
||||
|
||||
std::string_view view = content;
|
||||
size_t pos = 0;
|
||||
if((pos = view.find(cmd)) != std::string::npos)
|
||||
{
|
||||
view.remove_prefix(pos);
|
||||
view.remove_prefix(cmd.size());
|
||||
if((pos = view.find(cmdend)) != std::string::npos)
|
||||
{
|
||||
auto result = view.substr(0, pos);
|
||||
return std::string{result};
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
|
||||
{
|
||||
std::string linktag = match.str(1);
|
||||
std::string inside = match.str(2);
|
||||
|
||||
std::vector<std::string> splitted = utils::split(inside, '|');
|
||||
HtmlLink htmllink;
|
||||
if(splitted.size() == 2)
|
||||
{
|
||||
htmllink.innervalue = splitted[1];
|
||||
htmllink.href = splitted[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
htmllink.innervalue = inside;
|
||||
htmllink.href = inside;
|
||||
}
|
||||
|
||||
if(linktag == "wikilink")
|
||||
{
|
||||
if(pageDao.exists(htmllink.href))
|
||||
{
|
||||
htmllink.cssclass = "exists";
|
||||
}
|
||||
else
|
||||
{
|
||||
htmllink.cssclass = "notexists";
|
||||
}
|
||||
|
||||
htmllink.href = urlProvider.page(htmllink.href);
|
||||
}
|
||||
|
||||
return htmllink.render();
|
||||
}
|
||||
|
||||
std::string ParserLegacy::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
|
||||
{
|
||||
std::string result;
|
||||
// we don't care about commands, but we nevertheless replace them with empty strings
|
||||
std::regex tagfinder(R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|category)*?\]((\s|\S)*?)\[/\1])");
|
||||
result = utils::regex_callback_replacer(tagfinder, content, [&](std::smatch &match) {
|
||||
std::string tag = match.str(1);
|
||||
std::string content = match.str(2);
|
||||
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
|
||||
content = parse(pagedao, provider, content);
|
||||
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
|
||||
{
|
||||
return "<" + tag + ">" + content + "</" + tag + ">";
|
||||
}
|
||||
if(tag == "link" || tag == "wikilink")
|
||||
{
|
||||
return this->processLink(pagedao, provider,
|
||||
match); // TODO: recreate this so we don't check inside the function stuff again
|
||||
}
|
||||
if(tag[0] == 'h')
|
||||
{
|
||||
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
|
||||
}
|
||||
return std::string("");
|
||||
});
|
||||
result = utils::strreplace(result, "\r\n", "<br>");
|
||||
return result;
|
||||
}
|
19
parserlegacy.h
Normal file
19
parserlegacy.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
#include "iparser.h"
|
||||
|
||||
class ParserLegacy : public IParser
|
||||
{
|
||||
private:
|
||||
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
|
||||
|
||||
public:
|
||||
std::string extractCommand(std::string cmdname, std::string content) const;
|
||||
std::vector<Headline> extractHeadlines(std::string content) const override;
|
||||
std::vector<std::string> extractCategories(std::string content) const override;
|
||||
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override;
|
||||
using IParser::IParser;
|
||||
~ParserLegacy(){};
|
||||
};
|
||||
|
||||
#endif // PARSER_H
|
71
parsermarkdown.cpp
Normal file
71
parsermarkdown.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include "parsermarkdown.h"
|
||||
#include "logger.h"
|
||||
#include "htmllink.h"
|
||||
|
||||
std::string ParserMarkdown::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
|
||||
{
|
||||
std::string inner = match.str(1);
|
||||
std::string link = match.str(2);
|
||||
HtmlLink htmllink;
|
||||
htmllink.href = link;
|
||||
htmllink.innervalue = inner;
|
||||
|
||||
if(link.find("http://") == 0 || link.find("https://") == 0)
|
||||
{
|
||||
return htmllink.render();
|
||||
}
|
||||
|
||||
if(pageDao.exists(link))
|
||||
{
|
||||
htmllink.cssclass = "exists";
|
||||
}
|
||||
else
|
||||
{
|
||||
htmllink.cssclass = "notexists";
|
||||
}
|
||||
|
||||
htmllink.href = urlProvider.page(htmllink.href);
|
||||
|
||||
return htmllink.render();
|
||||
}
|
||||
|
||||
ParserMarkdown::ParserMarkdown()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<Headline> ParserMarkdown::extractHeadlines(std::string content) const
|
||||
{
|
||||
std::vector<Headline> result;
|
||||
utils::regex_callback_extractor(std::regex(R"((#{1,6}) (.*))"), content, [&](std::smatch &smatch) {
|
||||
Headline h;
|
||||
h.level = smatch.str(1).length();
|
||||
h.title = smatch.str(2);
|
||||
result.push_back(h);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ParserMarkdown::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
|
||||
{
|
||||
std::shared_ptr<maddy::ParserConfig> config = std::make_shared<maddy::ParserConfig>();
|
||||
auto maddy = std::make_shared<maddy::Parser>(config);
|
||||
|
||||
auto linkParser = std::make_shared<maddy::LinkParser>();
|
||||
linkParser->setCallback([&](std::smatch &match) { return processLink(pagedao, provider, match); });
|
||||
maddy->setLinkParser(linkParser);
|
||||
// TODO: hack because the parser breaks if there is an \r
|
||||
content = utils::strreplace(content, "\r", "");
|
||||
std::stringstream s{content};
|
||||
std::string result = maddy->Parse(s);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ParserMarkdown::extractCommand(std::string cmdname, std::string content) const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<std::string> ParserMarkdown::extractCategories(std::string content) const
|
||||
{
|
||||
return { };
|
||||
}
|
21
parsermarkdown.h
Normal file
21
parsermarkdown.h
Normal file
@ -0,0 +1,21 @@
|
||||
#ifndef PARSER_MARKDOWN_H
|
||||
#define PARSER_MARKDOWN_H
|
||||
|
||||
#include "iparser.h"
|
||||
#include "maddy/parser.h"
|
||||
|
||||
class ParserMarkdown : public IParser
|
||||
{
|
||||
private:
|
||||
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
|
||||
|
||||
public:
|
||||
ParserMarkdown();
|
||||
std::vector<Headline> extractHeadlines(std::string content) const;
|
||||
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const;
|
||||
|
||||
std::string extractCommand(std::string cmdname, std::string content) const;
|
||||
std::vector<std::string> extractCategories(std::string content) const;
|
||||
};
|
||||
|
||||
#endif // PARSER_MARKDOWN_H
|
@ -20,19 +20,6 @@ SOFTWARE.
|
||||
*/
|
||||
#include "permissions.h"
|
||||
|
||||
static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING},
|
||||
{"can_read", PERM_CAN_READ},
|
||||
{"can_edit", PERM_CAN_EDIT},
|
||||
{"can_page_history", PERM_CAN_PAGE_HISTORY},
|
||||
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
|
||||
{"can_delete", PERM_CAN_DELETE},
|
||||
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
|
||||
{"can_create", PERM_CAN_CREATE},
|
||||
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
|
||||
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
|
||||
{"can_search", PERM_CAN_SEARCH},
|
||||
{"can_set_page_perms", PERM_CAN_SET_PAGE_PERMS}};
|
||||
|
||||
Permissions::Permissions(int permissions)
|
||||
{
|
||||
this->permissions = permissions;
|
||||
@ -49,20 +36,3 @@ Permissions::Permissions(const std::string &str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Permissions::toString(int perms)
|
||||
{
|
||||
std::string result;
|
||||
for(auto pair : permmap)
|
||||
{
|
||||
if(pair.second & perms)
|
||||
{
|
||||
result += pair.first + ",";
|
||||
}
|
||||
}
|
||||
if(result.size() > 0)
|
||||
{
|
||||
result.pop_back();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
#ifndef PERMISSIONS_H
|
||||
#define PERMISSIONS_H
|
||||
|
||||
#define PERM_CAN_NOTHING 0
|
||||
#define PERM_CAN_READ 1 << 0
|
||||
#define PERM_CAN_EDIT 1 << 1
|
||||
#define PERM_CAN_PAGE_HISTORY 1 << 2
|
||||
@ -12,17 +11,23 @@
|
||||
#define PERM_CAN_SEE_CATEGORY_LIST 1 << 7
|
||||
#define PERM_CAN_SEE_LINKS_HERE 1 << 8
|
||||
#define PERM_CAN_SEARCH 1 << 9
|
||||
#define PERM_CAN_SET_PAGE_PERMS 1 << 10
|
||||
#define PERM_IS_ADMIN (1L<<31)-1
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class Permissions
|
||||
|
||||
{
|
||||
private:
|
||||
int permissions = 0;
|
||||
const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
|
||||
{"can_edit", PERM_CAN_EDIT},
|
||||
{"can_page_history", PERM_CAN_PAGE_HISTORY},
|
||||
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
|
||||
{"can_delete", PERM_CAN_DELETE},
|
||||
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
|
||||
{"can_create", PERM_CAN_CREATE},
|
||||
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
|
||||
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
|
||||
{"can_search", PERM_CAN_SEARCH}};
|
||||
|
||||
public:
|
||||
Permissions()
|
||||
@ -57,16 +62,10 @@ class Permissions
|
||||
return this->permissions;
|
||||
}
|
||||
|
||||
bool canNothing() const
|
||||
{
|
||||
return this->permissions == PERM_CAN_NOTHING;
|
||||
}
|
||||
|
||||
bool canRead() const
|
||||
{
|
||||
return this->permissions & PERM_CAN_READ;
|
||||
}
|
||||
|
||||
bool canEdit() const
|
||||
{
|
||||
return this->permissions & PERM_CAN_EDIT;
|
||||
@ -103,28 +102,6 @@ class Permissions
|
||||
{
|
||||
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
|
||||
{
|
||||
return Permissions::toString(this->permissions);
|
||||
}
|
||||
|
||||
static std::string toString(int perms);
|
||||
|
||||
bool operator==(const Permissions &o) const
|
||||
{
|
||||
return this->permissions == o.permissions;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // PERMISSIONS_H
|
||||
|
149
qswiki.cpp
149
qswiki.cpp
@ -25,26 +25,23 @@ SOFTWARE.
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <filesystem>
|
||||
#include <getopt.h>
|
||||
#include "gateway/gatewayinterface.h"
|
||||
#include "gateway/gatewayfactory.h"
|
||||
#include "handlers/handlerfactory.h"
|
||||
#include "database/databasefactory.h"
|
||||
#include "config.h"
|
||||
#include "session.h"
|
||||
#include "template.h"
|
||||
#include "session.h"
|
||||
#include "logger.h"
|
||||
#include "urlprovider.h"
|
||||
#include "requestworker.h"
|
||||
#include "cache/fscache.h"
|
||||
#include "cache/nocache.h"
|
||||
#include "sandbox/sandboxfactory.h"
|
||||
#include "cli.h"
|
||||
#include "cliconsole.h"
|
||||
#include "cliserver.h"
|
||||
#include "version.h"
|
||||
#include "iparser.h"
|
||||
#include "parserlegacy.h"
|
||||
#include "parsermarkdown.h"
|
||||
|
||||
void sigterm_handler([[maybe_unused]] int arg)
|
||||
void sigterm_handler(int arg)
|
||||
{
|
||||
// TODO: proper shutdown.
|
||||
exit(EXIT_SUCCESS);
|
||||
@ -63,83 +60,31 @@ void setup_signal_handlers()
|
||||
}
|
||||
}
|
||||
|
||||
#define OPT_PRINT_VERSION 23
|
||||
|
||||
static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version", no_argument, 0, OPT_PRINT_VERSION}};
|
||||
|
||||
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
|
||||
{
|
||||
|
||||
std::string path = resolver.getConfig("cache_fs_dir");
|
||||
if(path == "")
|
||||
{
|
||||
return std::make_unique<StringCache>();
|
||||
}
|
||||
|
||||
return std::make_unique<FsCache>(path);
|
||||
}
|
||||
|
||||
std::thread background_worker;
|
||||
void start_background_worker(Database &database, Config &config)
|
||||
std::unique_ptr<IParser> createParser(const ConfigVariableResolver &resolver)
|
||||
{
|
||||
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));
|
||||
}
|
||||
});
|
||||
std::string parser = resolver.getConfig("parser");
|
||||
if(parser == "legacy")
|
||||
{
|
||||
return std::make_unique<ParserLegacy>();
|
||||
}
|
||||
return std::make_unique<ParserMarkdown>();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
|
||||
char *configfilepath = NULL;
|
||||
int option;
|
||||
int option_index;
|
||||
bool cli_mode = false;
|
||||
|
||||
if(geteuid() == 0)
|
||||
{
|
||||
std::cerr << "Do not run this as root!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
while((option = getopt_long(argc, argv, "cv", long_options, &option_index)) != -1)
|
||||
{
|
||||
switch(option)
|
||||
{
|
||||
case 'c':
|
||||
cli_mode = true;
|
||||
break;
|
||||
case OPT_PRINT_VERSION:
|
||||
std::cout << get_version_string() << std::endl;
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(optind == argc)
|
||||
{
|
||||
std::cerr << "Missing config path" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
configfilepath = argv[optind++];
|
||||
|
||||
auto sandbox = createSandbox();
|
||||
// TODO: do we want to keep it mandatory or configurable?
|
||||
if(!sandbox->supported())
|
||||
@ -147,18 +92,35 @@ int main(int argc, char **argv)
|
||||
Logger::error() << "Sandbox is not supported, exiting";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(!sandbox->enableForInit())
|
||||
{
|
||||
Logger::error() << "Sandboxing for init mode could not be activated.";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if(argc < 2)
|
||||
{
|
||||
std::cerr << "no path to config file provided" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string configpath = std::filesystem::absolute(configfilepath).string();
|
||||
|
||||
try
|
||||
{
|
||||
ConfigReader configreader(configpath);
|
||||
ConfigReader configreader(argv[1]);
|
||||
Config config = configreader.readConfig();
|
||||
|
||||
// TODO: config.connectiontring only works as long as we only support sqlite of course
|
||||
if(!sandbox->enablePreWorker({
|
||||
config.configVarResolver.getConfig("cache_fs_dir"),
|
||||
config.templatepath,
|
||||
std::filesystem::path(config.logfile).parent_path(),
|
||||
std::filesystem::path(config.connectionstring).parent_path(),
|
||||
}))
|
||||
{
|
||||
Logger::error() << "Sandboxing for pre worker stage could not be activated.";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
setup_signal_handlers();
|
||||
|
||||
std::fstream logstream;
|
||||
@ -167,37 +129,6 @@ int main(int argc, char **argv)
|
||||
|
||||
auto database = createDatabase(config);
|
||||
|
||||
std::string socketPath = config.configVarResolver.getConfig("socketpath");
|
||||
CLIHandler cliHandler(config, *database);
|
||||
|
||||
if(cli_mode)
|
||||
{
|
||||
CLIConsole console{cliHandler, socketPath};
|
||||
console.startInteractive();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: config.connectiontring only works as long as we only support sqlite of course
|
||||
if(!sandbox->enable({
|
||||
config.configVarResolver.getConfig("cache_fs_dir"),
|
||||
config.templatepath,
|
||||
std::filesystem::path(config.logfile).parent_path(),
|
||||
std::filesystem::path(config.connectionstring).parent_path(),
|
||||
}))
|
||||
{
|
||||
Logger::error() << "Sandboxing for worker could not be enabled!";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
start_background_worker(*database.get(), config);
|
||||
|
||||
CLIServer cliServer{cliHandler};
|
||||
if(!cliServer.detachServer(socketPath))
|
||||
{
|
||||
Logger::error() << "Error: Failed to detach unix socket server";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO: quite ugly, anon-handling must be rethought
|
||||
auto userdao = database->createUserDao();
|
||||
std::optional<User> anon = userdao->find(config.handlersConfig.anon_username);
|
||||
@ -213,19 +144,25 @@ int main(int argc, char **argv)
|
||||
userdao->save(anon.value());
|
||||
User::setAnon(anon.value());
|
||||
|
||||
MapCache<TemplatePage> mapCache;
|
||||
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver,
|
||||
mapCache};
|
||||
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver};
|
||||
UrlProvider urlProvider{config.urls};
|
||||
|
||||
auto cache = createCache(config.configVarResolver);
|
||||
cache->clear();
|
||||
|
||||
HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get()};
|
||||
auto parser = createParser(config.configVarResolver);
|
||||
|
||||
|
||||
HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get(), *parser.get()};
|
||||
RequestWorker requestWorker{handlerFactory, database->createSessionDao(), siteTemplate};
|
||||
|
||||
auto interface = createGateway(config);
|
||||
|
||||
if(!sandbox->enableForWorker())
|
||||
{
|
||||
Logger::error() << "Sandboxing for worker could not be enabled!";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
interface->work(requestWorker);
|
||||
}
|
||||
catch(const std::exception &e)
|
||||
|
@ -40,7 +40,7 @@ std::pair<std::string, std::string> Request::createPairFromVar(std::string var)
|
||||
else
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -75,7 +75,7 @@ void Request::initPostMap(const std::string &url)
|
||||
void Request::initCookies(const std::string &cookiestr)
|
||||
{
|
||||
// TODO: find out what it really should be, ";" or "; "?
|
||||
std::regex regex{";+\\s?"};
|
||||
std::regex regex { ";+\\s?" };
|
||||
auto cookiesplitted = utils::split(cookiestr, regex);
|
||||
for(const std::string &part : cookiesplitted)
|
||||
{
|
||||
|
@ -59,7 +59,7 @@ Response RequestWorker::processRequest(const Request &r)
|
||||
if(session.loggedIn && session.csrf_token != r.post("csrf_token"))
|
||||
{
|
||||
// TODO: this is code duplication
|
||||
TemplatePage error = this->templ->getPage("error");
|
||||
TemplatePage &error = this->templ->getPage("error");
|
||||
error.setVar("errortitle", "Invalid csrf token");
|
||||
error.setVar("errormessage", "Invalid csrf token");
|
||||
return {403, error.render()};
|
||||
|
@ -32,12 +32,12 @@ Response::Response(int http_status_code, std::string html)
|
||||
this->html = std::move(html);
|
||||
}
|
||||
|
||||
void Response::addHeader(std::string key, std::string value)
|
||||
void Response::addHeader(const std::string &key, const std::string &value)
|
||||
{
|
||||
this->responseHeaders.insert(std::make_pair(key, value));
|
||||
}
|
||||
|
||||
Response Response::redirectTemporarily(std::string url)
|
||||
Response Response::redirectTemporarily(const std::string &url)
|
||||
{
|
||||
Response result;
|
||||
result.addHeader("Location", url);
|
||||
|
@ -27,8 +27,8 @@ class Response
|
||||
return this->html;
|
||||
}
|
||||
|
||||
void addHeader(std::string key, std::string value);
|
||||
static Response redirectTemporarily(std::string url);
|
||||
void addHeader(const std::string &key, const std::string &value);
|
||||
static Response redirectTemporarily(const std::string &url);
|
||||
|
||||
void setStatus(int status)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user