sandboxing: First version using qssb.h #20

Closed
crtxcr wants to merge 0 commits from feature/qssb into master
196 changed files with 1440 additions and 3743 deletions

2
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

@ -4,6 +4,6 @@
[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://git.quitesimple.org/qssb.h

View File

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

103
README.md
View File

@ -1,93 +1,84 @@
# 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
- 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.
The first two are header-only libraries that are already included here.
If all dependencies are available, run:
```
git submodule init
git submodule update
make release
```
```make release```
Setup
=====

View File

@ -1,110 +0,0 @@
#include <atomic>
#include <openssl/evp.h>
#include <mutex>
#include "utils.h"
#include "authenticator.h"
#include "logger.h"
struct LoginFail
{
std::mutex mutex;
std::atomic<unsigned int> count;
time_t lastfail;
};
static std::map<std::string, LoginFail> loginFails;
Authenticator::Authenticator(UserDao &userDao)
{
this->userDao = &userDao;
}
// TODO: make failure counter configurable
bool Authenticator::isBanned(std::string ip)
{
if(loginFails.contains(ip))
{
LoginFail &fl = loginFails[ip];
std::lock_guard<std::mutex> lock(fl.mutex);
return fl.count > 5 && (time(nullptr) - fl.lastfail) < 1200;
}
return false;
}
void Authenticator::incFailureCount(std::string ip)
{
LoginFail &fl = loginFails[ip];
fl.count += 1;
fl.lastfail = time(nullptr);
}
std::vector<char> Authenticator::pbkdf5(std::string password, const std::vector<char> &salt) const
{
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);
if(ret != 1)
{
Logger::error() << "Authenticator: pbkdf5: Failed to create hash";
return {};
}
std::vector<char> result;
for(size_t i = 0; i < sizeof(hash); i++)
{
result.push_back(static_cast<char>(hash[i]));
}
return result;
}
std::variant<User, AuthenticationError> Authenticator::authenticate(std::string username, std::string password)
{
std::optional<User> user = userDao->find(username);
if(user)
{
if(user->enabled)
{
auto hashresult = pbkdf5(password, user.value().salt);
if(hashresult.size() == 0)
{
return AuthenticationError::GeneralError;
}
// TODO: timing attack (even though practical relevancy questionable)
if(hashresult == user.value().password)
{
return user.value();
}
return AuthenticationError::PasswordNotMatch;
}
return AuthenticationError::UserDisabled;
}
return AuthenticationError::UserNotFound;
}
std::variant<User, AuthenticationError> Authenticator::authenticate(std::string username, std::string password,
std::string ip)
{
if(isBanned(ip))
{
return AuthenticationError::BannedIP;
}
std::variant<User, AuthenticationError> authresult = authenticate(username, password);
if(std::holds_alternative<AuthenticationError>(authresult))
{
AuthenticationError error = std::get<AuthenticationError>(authresult);
if(error == AuthenticationError::PasswordNotMatch)
{
incFailureCount(ip);
}
return error;
}
return std::get<User>(authresult);
}
std::vector<char> Authenticator::hash(std::string password, const std::vector<char> &salt)
{
return this->pbkdf5(password, salt);
}

View File

@ -1,31 +0,0 @@
#ifndef AUTHENTICATOR_H
#define AUTHENTICATOR_H
#include <variant>
#include "database/userdao.h"
#define AUTH_DEFAULT_SALT_SIZE 32
enum AuthenticationError
{
UserNotFound,
UserDisabled,
PasswordNotMatch,
BannedIP,
GeneralError
};
class Authenticator
{
private:
UserDao *userDao;
bool isBanned(std::string ip);
void incFailureCount(std::string ip);
std::vector<char> pbkdf5(std::string password, const std::vector<char> &salt) const;
public:
Authenticator(UserDao &userDao);
std::variant<User, AuthenticationError> authenticate(std::string username, std::string password);
std::variant<User, AuthenticationError> authenticate(std::string username, std::string password, std::string ip);
std::vector<char> hash(std::string password, const std::vector<char> &salt);
};
#endif // AUTHENTICATOR_H

22
cache/fscache.cpp vendored
View File

@ -7,16 +7,16 @@ FsCache::FsCache(std::string path)
{
if(!std::filesystem::exists(path))
{
throw std::runtime_error{"Cache directory does not exist"};
throw std::runtime_error { "Cache directory does not exist" };
}
this->path = path;
}
std::string FsCache::getFilePath(std::string_view path) const
{
std::filesystem::path ps{path};
std::filesystem::path ps { path };
std::string name = ps.filename();
return std::filesystem::path{this->path} / name;
return std::filesystem::path { this->path } / name;
}
std::optional<std::string> FsCache::get(std::string_view key) const
{
@ -25,12 +25,12 @@ std::optional<std::string> FsCache::get(std::string_view key) const
{
return utils::readCompleteFile(path);
}
return {};
return { };
}
void FsCache::put(std::string_view key, std::string val)
{
std::string path = std::filesystem::path{this->path} / key;
std::string path = std::filesystem::path { this->path } / key;
std::fstream f1;
f1.open(path, std::ios::out);
f1 << val;
@ -38,24 +38,24 @@ void FsCache::put(std::string_view key, std::string val)
void FsCache::remove(std::string_view key)
{
std::filesystem::remove_all(std::filesystem::path{this->path} / key);
std::filesystem::remove_all(std::filesystem::path { this->path} / key);
}
void FsCache::removePrefix(std::string_view prefix)
{
// TODO: lock dir
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path}))
//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);
std::filesystem::remove_all(entry);
}
}
}
void FsCache::clear()
{
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path}))
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path { this->path }))
{
std::filesystem::remove_all(entry);
}

9
cache/fscache.h vendored
View File

@ -3,11 +3,10 @@
#include "icache.h"
class FsCache : public ICache
{
private:
private:
std::string path;
std::string getFilePath(std::string_view path) const;
public:
public:
FsCache(std::string directory);
std::optional<std::string> get(std::string_view key) const;
void put(std::string_view key, std::string val);
@ -15,9 +14,7 @@ class FsCache : public ICache
void removePrefix(std::string_view prefix);
void clear();
using ICache::ICache;
~FsCache()
{
}
~FsCache() { }
};
#endif // FSCACHE_H

6
cache/icache.h vendored
View File

@ -6,15 +6,13 @@
#include "../utils.h"
class ICache
{
public:
public:
virtual std::optional<std::string> get(std::string_view key) const = 0;
virtual void put(std::string_view key, std::string val) = 0;
virtual void remove(std::string_view key) = 0;
virtual void removePrefix(std::string_view prefix) = 0;
virtual void clear() = 0;
virtual ~ICache()
{
}
virtual ~ICache() { }
};
#endif // ICACHE_H

1
cache/mapcache.cpp vendored
View File

@ -1 +0,0 @@
#include "mapcache.h"

39
cache/mapcache.h vendored
View File

@ -1,39 +0,0 @@
#ifndef MAPCACHE_H
#define MAPCACHE_H
#include <map>
#include <set>
#include <shared_mutex>
#include <optional>
#include <string>
/* Thread-Safe Key-Value store */
template <class T> class MapCache
{
private:
std::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();
}
};
#endif // MAPCACHE_H

30
cache/nocache.h vendored
View File

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

View File

@ -22,4 +22,5 @@ SOFTWARE.
Category::Category()
{
}

View File

@ -4,10 +4,11 @@
#include <string>
class Category
{
public:
public:
Category();
unsigned int id;
std::string name;
};
#endif // CATEGORY_H

278
cli.cpp
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,15 +24,14 @@ SOFTWARE.
#include "config.h"
#include "permissions.h"
#include "varreplacer.h"
std::string Config::required(const std::string &key)
{
auto it = this->configmap.find(key);
if(it != this->configmap.end())
{
return it->second;
}
throw std::runtime_error("Required config key " + key + " not found");
auto it = this->configmap.find(key);
if(it != this->configmap.end())
{
return it->second;
}
throw std::runtime_error("Required config key " + key + " not found");
}
std::string Config::optional(const std::string &key, std::string defaultvalue)
@ -67,6 +66,8 @@ uint64_t Config::optional(const std::string &key, uint64_t defaultvalue)
return defaultvalue;
}
Config::Config(const std::map<std::string, std::string> &map)
{
@ -78,16 +79,13 @@ Config::Config(const std::map<std::string, std::string> &map)
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");
@ -99,11 +97,10 @@ Config::Config(const std::map<std::string, std::string> &map)
this->urls.settingsurl = required("settingsurl");
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->urls.userchangepwurl = required("userchangepwurl");
this->connectionstring = required("connectionstring");
this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256);
this->session_max_lifetime = optional("session_max_lifetime", 3600);
this->handlersConfig.query_limit = optional("query_limit", 200);
@ -114,14 +111,15 @@ 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};
ConfigVariableResolver resolver { this->configmap };
this->configVarResolver = resolver;
Varreplacer replacer("{");
replacer.addKeyValue("wikiname", this->handlersConfig.wikiname);
this->handlersConfig.page_title_template = replacer.parse(this->handlersConfig.page_title_template);
}
ConfigReader::ConfigReader(const std::string &file)
@ -136,16 +134,17 @@ Config ConfigReader::readConfig()
std::map<std::string, std::string> configmap;
while(getline(f1, line))
{
if(isspace(line[0]) || line[0] == '#')
{
if(isspace(line[0]) || line[0] == '#') {
continue;
}
}
std::stringstream s(line);
std::string key;
std::string value;
s >> key >> value;
configmap.insert(std::make_pair(std::move(key), std::move(value)));
}
return Config(configmap);
}

View File

@ -16,6 +16,7 @@ struct HandlerConfig
std::string page_title_template;
int max_pagename_length;
int query_limit;
};
struct ConfigUrls
@ -23,19 +24,16 @@ 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;
std::string linkrevision ;
std::string linkhistory ;
std::string linkedit ;
std::string linksettings;
std::string linkdelete;
std::string linklogout;
std::string linkdelete ;
std::string linklogout ;
std::string linkcategory;
std::string linkcategoryrendertype;
std::string loginurl;
std::string linkrecentsort;
std::string actionurl;
@ -43,19 +41,19 @@ struct ConfigUrls
std::string deletionurl;
std::string linkhistorysort;
std::string adminregisterurl;
std::string usersettingsurl;
std::string rooturl;
std::string atomurl;
std::string userchangepwurl;
};
class ConfigVariableResolver
{
private:
private:
const std::map<std::string, std::string> *configmap;
public:
public:
ConfigVariableResolver()
{
}
ConfigVariableResolver(const std::map<std::string, std::string> &configmap)
@ -67,20 +65,21 @@ class ConfigVariableResolver
{
return utils::getKeyOrEmpty(*configmap, key);
}
};
class Config
{
private:
private:
std::map<std::string, std::string> configmap;
std::string required(const std::string &key);
std::string optional(const std::string &key, std::string defaultvalue = "");
int optional(const std::string &key, int defaulvalue);
uint64_t optional(const std::string &key, uint64_t defaultvalue);
public:
Config(const std::map<std::string, std::string> &map);
public:
Config(const std::map<std::string, std::string> &map );
ConfigUrls urls;
ConfigVariableResolver configVarResolver;
@ -95,16 +94,21 @@ class Config
int threadscount;
uint64_t max_payload_length;
};
class ConfigReader
{
private:
private:
std::string path;
public:
public:
ConfigReader(const std::string &file);
Config readConfig();
};
#endif // CONFIG_H

View File

@ -4,7 +4,7 @@
#include <string>
class Cookie
{
public:
public:
std::string key;
std::string value;
int expires;

View File

@ -22,4 +22,5 @@ SOFTWARE.
CategoryDao::CategoryDao()
{
}

View File

@ -5,17 +5,17 @@
#include <optional>
#include "queryoption.h"
#include "../category.h"
#include "../page.h"
class CategoryDao
{
public:
public:
CategoryDao();
virtual void save(const Category &c) = 0;
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

View File

@ -24,12 +24,12 @@ SOFTWARE.
#include "sqlitequeryoption.h"
CategoryDaoSqlite::CategoryDaoSqlite()
{
}
std::optional<Category> CategoryDaoSqlite::find(std::string name)
{
try
{
try {
Category result;
*db << "SELECT id, name FROM category WHERE name = ?" << name >> std::tie(result.id, result.name);
return result;
@ -42,7 +42,6 @@ std::optional<Category> CategoryDaoSqlite::find(std::string name)
{
throwFrom(e);
}
return {};
}
void CategoryDaoSqlite::save(const Category &c)
@ -50,9 +49,7 @@ void CategoryDaoSqlite::save(const Category &c)
try
{
*db << "INSERT OR IGNORE INTO category (id, name) VALUES (SELECT id FROM category WHERE lower(name) = "
"lower(?), lower(?)"
<< c.name << c.name;
*db << "INSERT OR IGNORE INTO category (id, name) VALUES (SELECT id FROM category WHERE lower(name) = lower(?), lower(?)" <<c.name << c.name;
}
catch(sqlite::sqlite_exception &e)
{
@ -65,14 +62,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 << "COMMIT;";
*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);
}
}
@ -83,7 +79,8 @@ std::vector<std::string> CategoryDaoSqlite::fetchList(QueryOption o)
try
{
auto queryoption = SqliteQueryOption(o).setPrependWhere(true).setOrderByColumn("name").build();
*db << "SELECT name FROM category " + queryoption >> [&](std::string n) { result.push_back(n); };
*db << "SELECT name FROM category " + queryoption >> [&](std::string n) { result.push_back(n);};
}
catch(const sqlite::exceptions::no_rows &e)
{
@ -95,36 +92,18 @@ 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").setVisibleColumnName("page.visible").setPrependWhere(false).build();
SqliteQueryOption queryOption{o};
std::string queryoptions =
queryOption.setOrderByColumn("name").setListedColumnName("page.listed").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)
{

View File

@ -3,13 +3,12 @@
#include "categorydao.h"
#include "sqlitedao.h"
#include "../page.h"
class CategoryDaoSqlite : public CategoryDao, protected SqliteDao
class CategoryDaoSqlite : public CategoryDao, protected SqliteDao
{
public:
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;

View File

@ -13,19 +13,13 @@
#include "permissionsdao.h"
class Database
{
protected:
private:
std::string connnectionstring;
public:
Database() { }
Database(std::string connstring) { this->connnectionstring = connstring; }
public:
Database()
{
}
Database(std::string connstring)
{
this->connnectionstring = connstring;
}
virtual void beginTransaction() = 0;
virtual void beginTransaction() = 0;
virtual void rollbackTransaction() = 0;
virtual void commitTransaction() = 0;
virtual std::unique_ptr<PageDao> createPageDao() const = 0;
@ -34,9 +28,7 @@ class Database
virtual std::unique_ptr<UserDao> createUserDao() const = 0;
virtual std::unique_ptr<CategoryDao> createCategoryDao() const = 0;
virtual std::unique_ptr<PermissionsDao> createPermissionsDao() const = 0;
virtual ~Database()
{
}
virtual ~Database() { }
};
#endif

View File

@ -2,9 +2,10 @@
#define EXCEPTIONS_H
#include <stdexcept>
class DatabaseException : public std::runtime_error
class DatabaseException : public std::runtime_error
{
using std::runtime_error::runtime_error;
using std::runtime_error::runtime_error;
};
class DatabaseQueryException : public DatabaseException

View File

@ -22,4 +22,5 @@ SOFTWARE.
PageDao::PageDao()
{
}

View File

@ -8,26 +8,21 @@
#include "../searchresult.h"
class PageDao
{
public:
public:
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::optional<Page> find(unsigned int id) = 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;
// TODO: this may not be the correct place for this.
//TODO: this may not be the correct place for this.
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()
{
}
virtual ~PageDao() { }
};
#endif // PAGEDAO_H

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2018 Albert S.
/* 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
@ -23,14 +23,13 @@ SOFTWARE.
#include "exceptions.h"
#include "sqlitequeryoption.h"
#include "../logger.h"
#include "../utils.h"
/* TODO: copied from C version mostly, review whether access to table other than page is ok */
bool PageDaoSqlite::exists(unsigned int id) const
{
auto binder = *db << "SELECT 1 from page WHERE id = ?" << id;
return execBool(binder);
auto binder = *db << "SELECT 1 from page WHERE id = ?" << id ;
return execBool(binder);
}
bool PageDaoSqlite::exists(std::string name) const
@ -48,53 +47,25 @@ std::optional<Page> PageDaoSqlite::find(std::string name)
}
catch(const sqlite::errors::no_rows &e)
{
return {};
return { };
}
}
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)
{
return {};
return { };
}
catch(sqlite::sqlite_exception &e)
catch(sqlite::sqlite_exception& e)
{
throwFrom(e);
}
@ -105,7 +76,7 @@ std::optional<Page> PageDaoSqlite::find(unsigned int id)
void PageDaoSqlite::deletePage(std::string page)
{
int pageId = this->fetchPageId(page);
// TODO on delete cascade is better most certainly
//TODO on delete cascade is better most certainly
try
{
*db << "BEGIN;";
@ -114,55 +85,40 @@ void PageDaoSqlite::deletePage(std::string page)
*db << "DELETE FROM permissions WHERE page = ?;" << pageId;
*db << "DELETE FROM page WHERE id =?;" << pageId;
*db << "COMMIT;";
}
catch(sqlite::sqlite_exception &e)
{
*db << "ROLLBACK";
throwFrom(e);
}
}
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")
.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)
std::string queryOption = SqliteQueryOption(option).setOrderByColumn("lower(name)").setVisibleColumnName("visible").setPrependWhere(true).build();
std::string query = "SELECT name FROM page " + queryOption;
*db << query >> [&](std::string name)
{
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);
result.push_back(name);
};
}
catch(const sqlite::errors::no_rows &e)
@ -181,11 +137,9 @@ std::vector<std::string> PageDaoSqlite::fetchCategories(std::string pagename, Qu
std::vector<std::string> result;
try
{
auto query = *db << "SELECT name FROM categorymember INNNER JOIN category ON category = category.id WHERE page "
"= (SELECT id FROM page WHERE name = ?)"
<< pagename;
auto query = *db << "SELECT name FROM categorymember INNNER JOIN category ON category = category.id WHERE page = (SELECT id FROM page WHERE name = ?)" << pagename;
query << " AND " << SqliteQueryOption(option).setPrependWhere(false).setOrderByColumn("name").build();
query >> [&](std::string pagename) { result.push_back(pagename); };
query >> [&](std::string pagename) { result.push_back(pagename);};
}
catch(const sqlite::exceptions::no_rows &e)
{
@ -199,22 +153,6 @@ std::vector<std::string> PageDaoSqlite::fetchCategories(std::string pagename, Qu
return result;
}
std::string PageDaoSqlite::ftsEscape(std::string input)
{
std::string result = "";
for(auto &str : utils::split(input, ' '))
{
std::string tmp = utils::strreplace(str, "\"", "\"\"");
tmp = "\"" + tmp + "\"" + " ";
result += tmp;
}
if(!result.empty())
{
result.pop_back();
}
return result;
}
std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption option)
{
@ -222,12 +160,9 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
try
{
std::string qo = SqliteQueryOption(option).setPrependWhere(false).setOrderByColumn("rank").build();
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)
{
//TODO: what is passed here, simple gets thrown to the MATCH operator without escaping or anything and this is suboptimal
auto query = *db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? " << name;
query >> [&](std::string pagename) {
SearchResult sresult;
sresult.pagename = pagename;
sresult.query = name;
@ -254,12 +189,9 @@ void PageDaoSqlite::setCategories(std::string pagename, const std::vector<std::s
*db << "DELETE FROM categorymember WHERE page = ?" << pageid;
for(const std::string &cat : catnames)
{
*db << "INSERT OR IGNORE INTO category (id, name) VALUES( (SELECT id FROM category WHERE lower(name) = "
"lower(?)), lower(?))"
<< cat << cat;
*db << "INSERT INTO categorymember (category, page) VALUES ( (SELECT ID FROM category WHERE lower(name) = "
"lower(?)), ?)"
<< cat << pageid;
*db << "INSERT OR IGNORE INTO category (id, name) VALUES( (SELECT id FROM category WHERE lower(name) = lower(?)), lower(?))" << cat << cat;
*db << "INSERT INTO categorymember (category, page) VALUES ( (SELECT ID FROM category WHERE lower(name) = lower(?)), ?)" << cat << pageid;
}
*db << "release setcategories;";
}
@ -269,16 +201,9 @@ void PageDaoSqlite::setCategories(std::string pagename, const std::vector<std::s
}
}
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;
}

View File

@ -8,27 +8,20 @@
#include "sqlitedao.h"
class PageDaoSqlite : public PageDao, protected SqliteDao
{
private:
std::string ftsEscape(std::string input);
public:
PageDaoSqlite()
{
}
public:
PageDaoSqlite() { }
void deletePage(std::string page) override;
bool exists(unsigned int id) const override;
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::optional<Page> find(std::string name) override;
std::optional<Page> find(unsigned int id) 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;
};

View File

@ -22,4 +22,5 @@ SOFTWARE.
PermissionsDao::PermissionsDao()
{
}

View File

@ -1,17 +1,12 @@
#ifndef PERMISSIONSDAO_H
#define PERMISSIONSDAO_H
#include <optional>
#include "../permissions.h"
#include "../user.h"
class PermissionsDao
{
public:
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

View File

@ -22,12 +22,12 @@ SOFTWARE.
PermissionsDaoSqlite::PermissionsDaoSqlite()
{
}
std::optional<Permissions> PermissionsDaoSqlite::find(std::string pagename, std::string username)
{
auto query = *db << "SELECT permissions FROM permissions WHERE page = (SELECT id FROM page WHERE name = ?) AND "
"userid = (SELECT id FROM user WHERE username = ?)";
auto query = *db << "SELECT permissions FROM permissions WHERE page = (SELECT id FROM page WHERE name = ?) AND userid = (SELECT id FROM user WHERE username = ?)";
query << pagename << username;
int permissions = 0;
try
@ -36,39 +36,8 @@ std::optional<Permissions> PermissionsDaoSqlite::find(std::string pagename, std:
}
catch(const sqlite::errors::no_rows &e)
{
return {};
return { };
}
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);
}
return Permissions { permissions };
}

View File

@ -5,12 +5,10 @@
class PermissionsDaoSqlite : public PermissionsDao, protected SqliteDao
{
public:
public:
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;
};

View File

@ -19,3 +19,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "queryoption.h"

View File

@ -3,17 +3,17 @@
enum SORT_ORDER
{
ASCENDING = 0,
ASCENDING=0,
DESCENDING
};
class QueryOption
{
public:
public:
unsigned int offset = 0;
unsigned int limit = 0;
SORT_ORDER order = ASCENDING;
bool includeUnlisted = true;
bool includeInvisible = true;
};
#endif // QUERYOPTION_H

View File

@ -19,3 +19,4 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "revisiondao.h"

View File

@ -6,7 +6,7 @@
#include "queryoption.h"
class RevisionDao
{
public:
public:
virtual void save(const Revision &revision) = 0;
virtual std::vector<Revision> getAllRevisions(QueryOption &options) = 0;
virtual std::vector<Revision> getAllRevisionsForPage(std::string pagename, QueryOption &option) = 0;
@ -15,9 +15,8 @@ class RevisionDao
virtual unsigned int countTotalRevisions() = 0;
virtual unsigned int countTotalRevisions(std::string pagename) = 0;
virtual ~RevisionDao()
{
}
virtual ~RevisionDao() { }
};
#endif // REVISIONDAO_H

View File

@ -24,17 +24,18 @@ SOFTWARE.
#include "../utils.h"
RevisionDaoSqlite::RevisionDaoSqlite()
{
}
void RevisionDaoSqlite::save(const Revision &revision)
{
try
{
*db << "savepoint revisionsubmit;";
*db << "INSERT INTO revision(author, comment, content, creationtime, page, revisionid) VALUES((SELECT id FROM "
"user WHERE username = ?), ?, ?, DATETIME(), (SELECT id FROM page WHERE name = ?), (SELECT "
"lastrevision+1 FROM page WHERE id = (SELECT id FROM page WHERE name = ?)));"
<< revision.author << revision.comment << revision.content << revision.page << revision.page;
*db << "INSERT INTO revision(author, comment, content, creationtime, page, revisionid) VALUES((SELECT id FROM user WHERE username = ?), ?, ?, DATETIME(), (SELECT id FROM page WHERE name = ?), (SELECT lastrevision+1 FROM page WHERE id = (SELECT id FROM page WHERE name = ?)));" <<
revision.author << revision.comment << revision.content << revision.page << revision.page;
*db << "UPDATE page SET lastrevision=lastrevision+1 WHERE name = ?; " << revision.page;
*db << "release revisionsubmit;";
}
@ -42,6 +43,8 @@ void RevisionDaoSqlite::save(const Revision &revision)
{
throwFrom(e);
}
}
std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
@ -50,18 +53,10 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
try
{
SqliteQueryOption queryOption{options};
std::string queryOptionSql = queryOption.setPrependWhere(true)
.setListedColumnName("page.listed")
.setOrderByColumn("creationtime")
.build();
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 " +
queryOptionSql;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid)
SqliteQueryOption queryOption { options };
std::string queryOptionSql = queryOption.setPrependWhere(true).setVisibleColumnName("page.visible").setOrderByColumn("creationtime").build();
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 " + queryOptionSql;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, std::string page, unsigned int revisionid)
{
Revision r;
r.author = author;
@ -90,19 +85,11 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
try
{
SqliteQueryOption queryOption{option};
std::string queryOptionSql = queryOption.setPrependWhere(false)
.setListedColumnName("page.listed")
.setOrderByColumn("creationtime")
.build();
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 " +
queryOptionSql
<< pagename;
SqliteQueryOption queryOption { option };
std::string queryOptionSql = queryOption.setPrependWhere(false).setVisibleColumnName("page.visible").setOrderByColumn("creationtime").build();
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 " + queryOptionSql << pagename;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid)
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, std::string page, unsigned int revisionid)
{
Revision r;
r.author = author;
@ -123,6 +110,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
throwFrom(e);
}
return result;
}
std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagename)
@ -130,16 +118,13 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
Revision result;
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;
query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, 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);
}
catch(const sqlite::errors::no_rows &e)
{
return {};
return { };
}
catch(sqlite::sqlite_exception &e)
{
@ -148,24 +133,21 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
return result;
}
std::optional<Revision> RevisionDaoSqlite::getRevisionForPage(std::string pagename, unsigned int revision)
{
Revision result;
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 "
"revisionid = ? ";
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), 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);
query >> std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
}
catch(const sqlite::exceptions::no_rows &e)
{
return {};
return { };
}
return result;
}
@ -181,3 +163,4 @@ unsigned int RevisionDaoSqlite::countTotalRevisions(std::string page)
auto query = *db << "SELECT COUNT(ROWID) FROM revision WHERE page = (SELECT id FROM page WHERE name = ?)" << page;
return static_cast<unsigned int>(execInt(query));
}

View File

@ -6,16 +6,18 @@
class RevisionDaoSqlite : public RevisionDao, protected SqliteDao
{
public:
public:
RevisionDaoSqlite();
void save(const Revision &revision) override;
std::vector<Revision> getAllRevisions(QueryOption &options) override;
std::vector<Revision> getAllRevisionsForPage(std::string pagename, QueryOption &option) override;
std::optional<Revision> getCurrentForPage(std::string pagename) override;
std::optional<Revision> getRevisionForPage(std::string pagnename, unsigned int revision) override;
std::optional<Revision> getRevisionForPage(std::string pagnename, unsigned int revision) override;
unsigned int countTotalRevisions() override;
unsigned int countTotalRevisions(std::string pagename) override;
using SqliteDao::SqliteDao;
};
#endif // REVISIONDAOSQLITE_H

View File

@ -22,4 +22,5 @@ SOFTWARE.
SessionDao::SessionDao()
{
}

View File

@ -5,15 +5,12 @@
#include "../session.h"
class SessionDao
{
public:
public:
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()
{
}
virtual ~SessionDao() { }
};
#endif // SESSIONDAO_H

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

@ -25,9 +25,8 @@ void SessionDaoSqlite::save(const Session &session)
{
try
{
// TODO: we do not store creationtime
auto q = *db << "INSERT OR REPLACE INTO session(id, token, csrf_token, creationtime, userid) VALUES((SELECT id "
"FROM session WHERE token = ?), ?, ?, DATETIME(), (SELECT id FROM user WHERE username = ?))";
//TODO: we do not store creationtime
auto q = *db << "INSERT OR REPLACE INTO session(id, token, csrf_token, creationtime, userid) VALUES((SELECT id FROM session WHERE token = ?), ?, ?, DATETIME(), (SELECT id FROM user WHERE username = ?))";
q << session.token << session.token << session.csrf_token << session.user.login;
q.execute();
}
@ -48,29 +47,7 @@ void SessionDaoSqlite::deleteSession(std::string token)
{
throwFrom(e);
}
}
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)
@ -80,40 +57,41 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
try
{
std::string username;
auto q = *db << "SELECT userid, token, csrf_token, strftime('%s', creationtime) FROM session WHERE token = ?"
<< token;
auto q = *db << "SELECT userid, token, csrf_token, strftime('%s', creationtime) FROM session WHERE token = ?" << 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)
{
return {};
return { };
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return result;
}
std::vector<Session> SessionDaoSqlite::fetch()
{
std::vector<Session> result;
*db << "SELECT userid, token, csrf_token, strftime('%s', creationtime) FROM session" >>
[this, &result](int userid, std::string token, std::string csrf_token, time_t creationtime)
{
Session tmp;
tmp.csrf_token = csrf_token;
tmp.token = token;
tmp.creation_time = creationtime;
fillSession(userid, tmp);
result.push_back(tmp);
};
return result;
}

View File

@ -4,18 +4,17 @@
#include "../session.h"
#include "sqlitedao.h"
class SessionDaoSqlite : public SessionDao, protected SqliteDao
{
private:
void fillSession(int userid, Session &sess);
public:
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;
};
#endif // SESSIONDAOSQLITE_H

View File

@ -18,43 +18,26 @@ 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>();
@ -70,6 +53,7 @@ std::unique_ptr<UserDao> Sqlite::createUserDao() const
return create<UserDaoSqlite>();
}
std::unique_ptr<SessionDao> Sqlite::createSessionDao() const
{
return create<SessionDaoSqlite>();
@ -80,6 +64,7 @@ std::unique_ptr<CategoryDao> Sqlite::createCategoryDao() const
return create<CategoryDaoSqlite>();
}
std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
{
return create<PermissionsDaoSqlite>();
@ -87,20 +72,28 @@ std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
void Sqlite::beginTransaction()
{
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;
}
}

View File

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

View File

@ -20,19 +20,21 @@ SOFTWARE.
*/
#include "sqlitedao.h"
bool SqliteDao::execBool(sqlite::database_binder &binder) const
{
bool result = false;
bool result;
try
{
bool result;
binder >> 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 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
return false;
}
}
int SqliteDao::execInt(sqlite::database_binder &binder) const
@ -51,5 +53,4 @@ int SqliteDao::execInt(sqlite::database_binder &binder) const
{
throwFrom(e);
}
return 0;
}

View File

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

View File

@ -20,10 +20,8 @@ SOFTWARE.
*/
#include "sqlitequeryoption.h"
SqliteQueryOption::SqliteQueryOption(const QueryOption &o)
{
this->o = o;
}
SqliteQueryOption::SqliteQueryOption(const QueryOption &o) { this->o = o; }
SqliteQueryOption &SqliteQueryOption::setOrderByColumn(std::string name)
{
@ -31,33 +29,26 @@ 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;
}
SqliteQueryOption &SqliteQueryOption::setPrependWhere(bool b)
{
this->prependWhere = b;
return *this;
}
SqliteQueryOption &SqliteQueryOption::setPrependWhere(bool b) { this->prependWhere = b; return *this; }
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)
{
@ -67,10 +58,9 @@ std::string SqliteQueryOption::build()
{
result += " DESC";
}
// TODO: limits for offset?
if(o.limit > 0)
{
//TODO: limits for offset?
if(o.limit > 0 )
result += " LIMIT " + std::to_string(o.limit) + " OFFSET " + std::to_string(o.offset);
}
return result;
}

View File

@ -1,23 +1,22 @@
#ifndef SQLITEQUERYOPTION_H
#ifndef SQLITEQUERYOPTION_H
#define SQLITEQUERYOPTION_H
#include <string>
#include "queryoption.h"
class SqliteQueryOption
{
private:
private:
QueryOption o;
std::string listedColumnName;
std::string visibleColumnName;
std::string orderByColumnName;
bool prependWhere;
public:
public:
SqliteQueryOption(const QueryOption &o);
SqliteQueryOption &setOrderByColumn(std::string name);
SqliteQueryOption &setListedColumnName(std::string name);
SqliteQueryOption &setVisibleColumnName(std::string name);
SqliteQueryOption &setPrependWhere(bool b);

View File

@ -22,4 +22,5 @@ SOFTWARE.
UserDao::UserDao()
{
}

View File

@ -3,18 +3,18 @@
#include <string>
#include <optional>
#include "../user.h"
#include "queryoption.h"
class UserDao
{
public:
public:
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(){};
virtual ~UserDao() { };
};
#endif // USERDAO_H

View File

@ -23,10 +23,10 @@ SOFTWARE.
#include <memory>
#include <cstring>
#include "userdaosqlite.h"
#include "sqlitequeryoption.h"
UserDaoSqlite::UserDaoSqlite()
{
}
bool UserDaoSqlite::exists(std::string username)
@ -37,27 +37,26 @@ bool UserDaoSqlite::exists(std::string username)
std::optional<User> UserDaoSqlite::find(std::string username)
{
try
{
User user;
auto stmt = *db << "SELECT username, password, salt, permissions, enabled FROM user WHERE username = ?"
<< username;
auto stmt = *db << "SELECT username, password, salt, permissions, enabled FROM user WHERE username = ?" << username;
int perms = 0;
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms};
user.permissions = Permissions { perms };
return user;
return std::move(user);
}
catch(const sqlite::errors::no_rows &e)
{
return {};
return { };
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return {};
}
std::optional<User> UserDaoSqlite::find(int id)
@ -69,67 +68,33 @@ std::optional<User> UserDaoSqlite::find(int id)
int perms = 0;
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms};
user.permissions = Permissions { perms };
return user;
return std::move(user);
}
catch(const sqlite::errors::no_rows &e)
{
return {};
return { };
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return {};
}
std::vector<User> UserDaoSqlite::list(QueryOption o)
void UserDaoSqlite::deleteUser(std::string username)
{
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)
{
// What to do with the contributions of the user?
//What to do with the contributions of the user?
}
void UserDaoSqlite::save(const User &u)
{
try
{
auto q = *db << "INSERT OR REPLACE INTO user(id, username, password, salt, permissions, enabled) "
"VALUES((SELECT id FROM user WHERE username = ?), ?,?,?,?,?)";
auto q = *db << "INSERT OR REPLACE INTO user(id, username, password, salt, permissions, enabled) VALUES((SELECT id FROM user WHERE username = ?), ?,?,?,?,?)";
q << u.login << u.login << u.password << u.salt << u.permissions.getPermissions() << u.enabled;
q.execute();
}
catch(sqlite::sqlite_exception &e)
{

View File

@ -8,12 +8,11 @@
class UserDaoSqlite : public UserDao, protected SqliteDao
{
public:
public:
bool exists(std::string username);
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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,46 +0,0 @@
#include <chrono>
#include "dynamiccontentpostlist.h"
std::string DynamicContentPostList::render()
{
auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao();
auto permissionDao = this->database->createPermissionsDao();
QueryOption option;
option.includeUnlisted = false;
auto members = categoryDao->fetchMembers(this->argument, option);
std::vector<std::pair<std::string, time_t>> pageList;
for(const Page &member : members)
{
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead()) /* TODO: Maybe add canList() */
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, revision->timestamp});
}
}
std::sort(pageList.begin(), pageList.end(),
[](std::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });
std::string postListBegin = this->templ->loadResolvedPart("dynamic/postlistbegin");
std::string postListEnd = this->templ->loadResolvedPart("dynamic/postlistend");
std::string postLink = this->templ->loadResolvedPart("dynamic/postlistlink");
std::stringstream stream;
stream << postListBegin;
for(auto &pair : pageList)
{
std::string link = this->urlProvider->page(pair.first);
std::string date = utils::toISODate(pair.second);
Varreplacer replacer{"{"};
replacer.addKeyValue("url", link);
replacer.addKeyValue("date", date);
replacer.addKeyValue("title", pageDao->find(pair.first)->title);
stream << replacer.parse(postLink);
}
stream << postListEnd;
return stream.str();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -43,7 +43,7 @@ Request Cgi::readRequest()
throw std::runtime_error("REQUEST_URI is empty");
}
Request result{request_uri};
Request result { request_uri };
std::string method = utils::getenv("REQUEST_METHOD");
if(method == "POST")
@ -55,10 +55,14 @@ Request Cgi::readRequest()
}
std::string content_length = utils::getenv("CONTENT_LENGTH");
int cl = std::stoi(content_length);
std::unique_ptr<char[]> ptr(new char[cl + 1]);
std::cin.get(ptr.get(), cl + 1);
std::unique_ptr<char[]> ptr(new char[cl+1]);
std::cin.get(ptr.get(), cl+1);
std::string post_data { ptr.get() };
std::string post_data{ptr.get()};
}
result.initCookies(utils::getenv("HTTP_COOKIE"));
@ -66,6 +70,7 @@ Request Cgi::readRequest()
result.setUseragent(utils::getenv("HTTP_USER_AGENT"));
return result;
}
void Cgi::work(RequestWorker &worker)
@ -80,7 +85,7 @@ void Cgi::work(RequestWorker &worker)
void Cgi::sendResponse(const Response &r)
{
std::cout << "Status: " << r.getStatus() << "\r\n";
std::cout << "Content-Type: " << r.getContentType() << "\r\n";
std::cout << "Content-Type: " << r.getContentType() <<"\r\n";
for(auto header : r.getResponseHeaders())
{
std::string key = header.first;
@ -106,4 +111,5 @@ void Cgi::sendResponse(const Response &r)
Cgi::~Cgi()
{
}

View File

@ -5,13 +5,12 @@
#include "../requestworker.h"
class Cgi : public GatewayInterface
{
private:
private:
bool responseSent = false;
const Config *config;
Request readRequest();
void sendResponse(const Response &r);
public:
public:
Cgi(const Config &c);
bool keepReading() override;
void work(RequestWorker &worker) override;

View File

@ -35,5 +35,7 @@ std::unique_ptr<GatewayInterface> createGateway(const Config &c)
throw new std::runtime_error("No http.listenport in config file");
}
return std::make_unique<HttpGateway>(listenaddr, std::stoi(listenport), c.max_payload_length);
}

View File

@ -22,4 +22,5 @@ SOFTWARE.
GatewayInterface::GatewayInterface()
{
}

View File

@ -6,13 +6,11 @@
#include "../requestworker.h"
class GatewayInterface
{
public:
public:
GatewayInterface();
virtual bool keepReading() = 0;
virtual void work(RequestWorker &worker) = 0;
virtual ~GatewayInterface()
{
}
virtual ~GatewayInterface() { }
};
#endif // GATEWAYINTERFACE_H

View File

@ -38,10 +38,10 @@ Request HttpGateway::convertRequest(httplib::Request request)
result.setRequestMethod(request.method);
result.setUrl(request.target);
// TODO: this eats resources, where perhaps it does not need to. move it to request?
for(auto &it : request.params)
//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")
{
@ -69,7 +69,7 @@ httplib::Response HttpGateway::convertResponse(Response response)
result.status = response.getStatus();
for(auto &header : response.getResponseHeaders())
{
result.set_header(header.first.c_str(), header.second.c_str());
result.set_header(header.first.c_str(), header.second.c_str());
}
for(const Cookie &cookie : response.getCookies())
@ -79,12 +79,12 @@ httplib::Response HttpGateway::convertResponse(Response response)
return result;
}
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);

View File

@ -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"
@ -11,17 +8,16 @@
#include "../utils.h"
class HttpGateway : public GatewayInterface
{
private:
private:
Response convertResponse(httplib::Response response);
httplib::Response convertResponse(Response response);
Request convertRequest(httplib::Request request);
// void worker(const httplib::Request& req, httplib::Response& res);
// void worker(const httplib::Request& req, httplib::Response& res);
std::string listenaddr;
int listenport;
uint64_t maxPayloadLength;
public:
public:
HttpGateway(std::string listenaddr, int port, uint64_t maxPayloadLength);
bool keepReading() override;
void work(RequestWorker &worker) override;

View File

View File

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

View File

@ -20,6 +20,7 @@ SOFTWARE.
*/
#include "handler.h"
void Handler::setGeneralVars(TemplatePage &page)
{
if(userSession->loggedIn)
@ -28,19 +29,19 @@ void Handler::setGeneralVars(TemplatePage &page)
}
else
{
page.setVar("loginstatus", "not logged in");
page.setVar("loginstatus", "not logged in");
}
page.setVar("csrf_token", utils::toString(this->userSession->csrf_token));
}
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);
// TODO: log?
//TODO: log?
setGeneralVars(error);
return {status, error.render()};
return { status, error.render()};
}
std::string Handler::createPageTitle(std::string title)
@ -50,13 +51,14 @@ std::string Handler::createPageTitle(std::string title)
return replacer.parse(this->handlersConfig->page_title_template);
}
QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const
QueryOption Handler::queryOption(const Request &r) const
{
QueryOption result;
result.includeUnlisted = false;
result.includeInvisible = false;
try
{
result.limit = utils::toUInt(r.get("limit"));
}
catch(std::exception &e)
{
@ -65,23 +67,20 @@ QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const
try
{
result.offset = utils::toUInt(r.get("offset"));
}
catch(std::exception &e)
{
result.offset = 0;
}
std::string order = r.get("sort");
result.order = defaultSort;
if(order != "")
if(order == "0")
{
if(order == "1")
{
result.order = DESCENDING;
}
else
{
result.order = ASCENDING;
}
result.order = ASCENDING;
}
else
{
result.order = DESCENDING;
}
return result;
@ -98,10 +97,6 @@ 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);
}

View File

@ -1,6 +1,5 @@
#ifndef HANDLER_H
#define HANDLER_H
#include <memory>
#include "../config.h"
#include "../response.h"
#include "../request.h"
@ -10,11 +9,9 @@
#include "../database/queryoption.h"
#include "../logger.h"
#include "../cache/icache.h"
#include "../dynamic/dynamiccontent.h"
class Handler
{
protected:
protected:
ICache *cache;
Template *templ;
Database *database;
@ -22,13 +19,11 @@ class Handler
UrlProvider *urlProvider;
HandlerConfig *handlersConfig;
// TODO: may not to find a better place for this method
//TODO: may not to find a better place for this method
Permissions effectivePermissions(std::string page);
QueryOption queryOption(const Request &r, SORT_ORDER defaultSort = ASCENDING) const;
public:
Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider,
ICache &cache)
QueryOption queryOption(const Request &r) const;
public:
Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider, ICache &cache)
{
this->handlersConfig = &handlersConfig;
this->templ = &templ;
@ -39,29 +34,19 @@ class Handler
}
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;
}
virtual std::string accessErrorMessage()
{
virtual std::string accessErrorMessage() {
return "No permission for this action";
}
void setGeneralVars(TemplatePage &page);
virtual ~Handler()
{
}
template <class T> inline std::shared_ptr<T> createDynamic()
{
return std::make_shared<T>(*this->templ, *this->database, *this->urlProvider);
}
virtual ~Handler() { }
Response errorResponse(std::string errortitle, std::string errormessage, int status = 200);
std::string createPageTitle(std::string append);
};

View File

@ -28,13 +28,12 @@ Response HandlerAllCategories::handleRequest(const Request &r)
auto resultList = categoryDao->fetchList(qo);
if(resultList.size() == 0)
{
return errorResponse(
"No categories",
"This wiki does not have any categories defined yet or your query options did not yield any results");
return errorResponse("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");
std::string body =
this->templ->renderSearch(resultList, [&](std::string str) { return this->urlProvider->category(str); });
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);
searchPage.setVar("title", createPageTitle("All categories"));
setGeneralVars(searchPage);

View File

@ -1,11 +1,12 @@
#ifndef HANDLERALLCATEGORIES_H
#define HANDLERALLCATEGORIES_H
#include "handler.h"
class HandlerAllCategories : public Handler
{
public:
public:
HandlerAllCategories();
using Handler::Handler;
Response handleRequest(const Request &r) override;

View File

@ -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;
}
@ -52,6 +46,7 @@ Response HandlerAllPages::handleRequest(const Request &r)
Logger::error() << "Error during allpages Handler" << e.what();
return errorResponse("Error", "An unknown error occured");
}
}
std::string HandlerAllPages::accessErrorMessage()
@ -63,3 +58,4 @@ bool HandlerAllPages::canAccess(const Permissions &perms)
{
return perms.canSeePageList();
}

View File

@ -4,7 +4,7 @@
#include "handler.h"
class HandlerAllPages : public Handler
{
public:
public:
HandlerAllPages();
using Handler::Handler;
Response handleRequest(const Request &r) override;

View File

@ -19,7 +19,7 @@ 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)
{
@ -28,19 +28,15 @@ Response HandlerCategory::handleRequest(const Request &r)
Response response;
std::string categoryname = r.get("category");
auto categoryDao = this->database->createCategoryDao();
if(!categoryDao->find(categoryname))
if(! categoryDao->find(categoryname))
{
return this->errorResponse("No such category", "A category with the provided name does not exist", 404);
}
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));
@ -62,5 +58,5 @@ std::string HandlerCategory::accessErrorMessage()
bool HandlerCategory::canAccess(const Permissions &perms)
{
return perms.canRead(); // TODO: we may need a more specific permission
return perms.canRead(); //TODO: we may need a more specific permission
}

View File

@ -4,7 +4,7 @@
class HandlerCategory : public Handler
{
public:
public:
HandlerCategory();
using Handler::Handler;
Response handleRequest(const Request &r) override;

View File

@ -20,16 +20,18 @@ SOFTWARE.
*/
#include "handlerdefault.h"
Response HandlerDefault::handleRequest([[maybe_unused]] const Request &r)
Response HandlerDefault::handleRequest(const Request &r)
{
return Response::redirectTemporarily(this->urlProvider->index());
}
HandlerDefault::~HandlerDefault()
{
}
bool HandlerDefault::canAccess([[maybe_unused]] const Permissions &perms)
bool HandlerDefault::canAccess(const Permissions &perms)
{
return true;
}

View File

@ -4,7 +4,7 @@
#include "handler.h"
class HandlerDefault : public Handler
{
public:
public:
Response handleRequest(const Request &r) override;
~HandlerDefault() override;
using Handler::Handler;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2018 Albert S.
/* 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
@ -32,63 +32,51 @@ SOFTWARE.
#include "handlercategory.h"
#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")
{
return produce<HandlerDefault>(userSession);
}
if(action == "show")
{
return produce<HandlerPageView>(userSession);
}
if(action == "edit")
{
return produce<HandlerPageEdit>(userSession);
}
if(action == "login")
{
return produce<HandlerLogin>(userSession);
}
if(action == "search")
{
return produce<HandlerSearch>(userSession);
}
if(action == "delete")
{
return produce<HandlerPageDelete>(userSession);
}
if(action == "allpages")
{
return produce<HandlerAllPages>(userSession);
}
if(action == "allcategories")
{
return produce<HandlerAllCategories>(userSession);
}
if(action == "showcat")
{
return produce<HandlerCategory>(userSession);
}
if(action == "recent")
{
return produce<HandlerHistory>(userSession);
}
if(action == "usersettings")
{
return produce<HandlerUserSettings>(userSession);
}
if(action == "version")
{
return produce<HandlerVersion>(userSession);
}
if(action == "feed")
{
return produce<HandlerFeedGenerator>(userSession);
}
if(action == "" || action == "index")
{
return produce<HandlerDefault>(userSession);
}
if(action == "show")
{
return produce<HandlerPageView>(userSession);
}
if(action == "edit")
{
return produce<HandlerPageEdit>(userSession);
}
if(action == "login")
{
return produce<HandlerLogin>(userSession);
}
if(action == "search")
{
return produce<HandlerSearch>(userSession);
}
if(action == "delete")
{
return produce<HandlerPageDelete>(userSession);
}
if(action == "allpages")
{
return produce<HandlerAllPages>(userSession);
}
if(action == "allcategories")
{
return produce<HandlerAllCategories>(userSession);
}
if(action == "showcat")
{
return produce<HandlerCategory>(userSession);
}
if(action == "recent")
{
return produce<HandlerHistory>(userSession);
}
return produce<HandlerInvalidAction>(userSession);
return produce<HandlerInvalidAction>(userSession);
}

View File

@ -11,17 +11,18 @@ class HandlerFactory
UrlProvider &urlProvider;
ICache &cache;
template <class T> inline std::unique_ptr<T> produce(Session &userSession)
template<class T>
inline std::unique_ptr<T> produce( Session &userSession)
{
return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache);
}
public:
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) : handlerConfig(handlerConfig), templ(templ) ,db(db), urlProvider(urlprovider), cache(cache) { }
std::unique_ptr<Handler> createHandler(const std::string &action, Session &userSession);
};
#endif // HANDLERFACTORY_H

View File

@ -1,158 +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 entryUpdated = utils::formatLocalDate(current.timestamp, dateformat) + "Z";
std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
atomentry.setVar("entrytitle", page.title);
atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryUpdated);
atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title)));
stream << atomentry.render();
}
stream << atomfooter;
TemplatePage atomheader = this->templ->getPage("feeds/atomheader");
atomheader.setVar("subtitle", subtitle);
std::string selflink = utils::html_xss(this->urlProvider->atomFeed(filter));
atomheader.setVar("atomfeeduniqueid", selflink);
atomheader.setVar("atomselflink", selflink);
atomheader.setVar("atomfeedupdate", utils::formatLocalDate(newestPublished, dateformat) + "Z");
return atomheader.render() + stream.str();
}
Response HandlerFeedGenerator::handleRequest(const Request &r)
{
Response response;
try
{
std::string type = r.get("type");
std::string categories = r.get("cats");
if(type == "atom")
{
std::string filter = categories;
Response result;
result.setStatus(200);
result.setContentType("application/atom+xml");
std::string cacheKey = "feed:atom:" + filter;
auto cached = this->cache->get(cacheKey);
if(cached)
{
result.setBody(cached.value());
}
else
{
auto entries = fetchEntries(utils::split(categories, ','));
std::string feed = generateAtom(entries, filter);
result.setBody(feed);
this->cache->put(cacheKey, feed);
}
response = result;
}
else
{
return errorResponse("Invalid feed type", "Unknown feed type, try atom", 400);
}
}
catch(std::runtime_error &e)
{
Logger::error() << "Error while serving feed: " << e.what();
return errorResponse("Error", "An error occured while trying to serve the feed", 500);
}
return response;
}
bool HandlerFeedGenerator::canAccess(const Permissions &perms)
{
return true;
}

View File

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

View File

@ -25,7 +25,7 @@ SOFTWARE.
#include "../database/exceptions.h"
Response HandlerHistory::handleRequest(const Request &r)
{
QueryOption qo = queryOption(r, DESCENDING);
QueryOption qo = queryOption(r);
std::string page = r.get("page");
std::string title;
if(page.empty())
@ -46,6 +46,7 @@ Response HandlerHistory::handleRequest(const Request &r)
title = "History: " + page;
}
unsigned int count = 0;
std::vector<Revision> resultList;
auto revisionDao = this->database->createRevisionDao();
@ -71,6 +72,7 @@ Response HandlerHistory::handleRequest(const Request &r)
count = revisionDao->countTotalRevisions(page);
resultList = revisionDao->getAllRevisionsForPage(page, qo);
templatename = "page_history";
}
else
{
@ -90,7 +92,7 @@ Response HandlerHistory::handleRequest(const Request &r)
TemplatePage historyPage = this->templ->getPage(templatename);
setGeneralVars(historyPage);
if((qo.offset + (unsigned int)resultList.size()) < count)
if( (qo.offset + (unsigned int)resultList.size()) < count)
{
HtmlLink link;
link.href = makeSortedLink(qo.limit, qo.offset + qo.limit, qo.order);
@ -113,7 +115,7 @@ Response HandlerHistory::handleRequest(const Request &r)
historyPage.setVar("prevpage", link.render());
}
unsigned int neworder = (qo.order == DESCENDING) ? ASCENDING : DESCENDING;
unsigned int neworder = ( qo.order == DESCENDING ) ? ASCENDING : DESCENDING ;
historyPage.setVar("linkrecentsort", makeSortedLink(qo.limit, qo.offset, neworder));
historyPage.setVar("revisionlist", this->templ->renderRevisionList(resultList, page.empty()));
historyPage.setVar("title", createPageTitle(title));
@ -123,7 +125,9 @@ 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
return true; //This is a lie but we need to this a little more fine grained here, which we do in the handleRequest
}

View File

@ -5,7 +5,7 @@
class HandlerHistory : public Handler
{
public:
public:
HandlerHistory();
using Handler::Handler;
Response handleRequest(const Request &r) override;

View File

@ -20,12 +20,13 @@ 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;
}

View File

@ -4,13 +4,12 @@
class HandlerInvalidAction : public Handler
{
public:
public:
Response handleRequest(const Request &r) override;
~HandlerInvalidAction() override
{
}
~HandlerInvalidAction() override { }
bool canAccess(const Permissions &perms) override;
using Handler::Handler;
};
#endif // HANDLERINVALIDACTION_H

View File

@ -20,53 +20,118 @@ SOFTWARE.
*/
#include <atomic>
#include <mutex>
#include <openssl/evp.h>
#include "handlerlogin.h"
#include "../logger.h"
#include "../authenticator.h"
struct LoginFail
{
std::mutex mutex;
std::atomic<unsigned int> count;
time_t lastfail;
};
static std::map<std::string, LoginFail> loginFails;
//TODO: make configurable
bool HandlerLogin::isBanned(std::string ip)
{
if(utils::hasKey(loginFails, ip))
{
LoginFail &fl = loginFails[ip];
std::lock_guard<std::mutex> lock(fl.mutex);
return fl.count > 5 && (time(nullptr) - fl.lastfail) < 1200;
}
return false;
}
void HandlerLogin::incFailureCount(std::string ip)
{
LoginFail &fl = loginFails[ip];
fl.count+=1;
fl.lastfail = time(nullptr);
}
std::vector<char> HandlerLogin::pbkdf5(std::string password, const std::vector<char> &salt)
{
unsigned char hash[32];
const EVP_MD *sha256 = EVP_sha256();
const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(salt.data());
PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
std::vector<char> result;
for(size_t i = 0; i < sizeof(hash); i++)
{
result.push_back(static_cast<char>(hash[i]));
}
return result;
}
Response HandlerLogin::handleRequest(const Request &r)
{
auto createErrorReesponse = [&]() { return errorResponse("Login error", "The supplied credenetials are incorrect"); };
if(isBanned(r.getIp()))
{
return errorResponse("Banned", "You have been banned for too many login attempts. Try again later");
}
if(r.param("submit") == "1")
{
std::string password = r.post("password");
std::string username = r.post("user");
auto userDao = this->database->createUserDao();
Authenticator authenticator(*userDao);
std::variant<User, AuthenticationError> authresult = authenticator.authenticate(username, password, r.getIp());
if(std::holds_alternative<User>(authresult))
std::optional<User> user = userDao->find(username);
if(!user)
{
User user = std::get<User>(authresult);
*(this->userSession) = Session(user);
Response r = Response::redirectTemporarily(urlProvider->index());
return r;
return createErrorReesponse();
}
AuthenticationError error = std::get<AuthenticationError>(authresult);
if(error == AuthenticationError::UserDisabled)
if(!user->enabled)
{
return errorResponse("Login failed", "The user account has been disabled");
}
return errorResponse("Login failed", "Login with supplied credentials failed");
auto hashresult = pbkdf5(password, user.value().salt);
//TODO: timing attack
if(hashresult == user.value().password)
{
loginFails.erase(r.getIp());
Response r = Response::redirectTemporarily(urlProvider->index());
*(this->userSession) = Session(user.value());
return r;
}
else
{
//TODO: only if wanted by config
incFailureCount(r.getIp());
return createErrorReesponse();
}
// auto pbkdf5 = pbkdf5(password, user->)
}
std::string page = r.get("page");
if(page.empty())
{
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"));
Response result;
result.setStatus(200);
result.setBody(loginTemplatePage.render());
return result;
}
bool HandlerLogin::canAccess([[maybe_unused]] const Permissions &perms)
bool HandlerLogin::canAccess(const Permissions &perms)
{
return true;
}

View File

@ -5,12 +5,14 @@
class HandlerLogin : public Handler
{
public:
private:
bool isBanned(std::string ip);
void incFailureCount(std::string ip);
std::vector<char> pbkdf5(std::string password, const std::vector<char> &salt);
public:
HandlerLogin();
Response handleRequest(const Request &r) override;
~HandlerLogin() override
{
}
~HandlerLogin() override { }
bool canAccess(const Permissions &perms) override;
using Handler::Handler;
};

View File

@ -21,32 +21,20 @@ SOFTWARE.
#include <optional>
#include "handlerpage.h"
Response HandlerPage::handle(const Request &r)
{
std::string pagename = r.get("page");
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))
{
std::string createlink = this->urlProvider->editPage(pagename);
return errorResponse(
"Page not found",
"The requested page was not found. Do you want to <a href=\"" + createlink + "\">create</a> it?", 404);
return errorResponse("Page not found", "The requested page was not found. Do you want to <a href=\"" + createlink + "\">create</a> it?", 404);
}
if(!canAccess(pagename))
@ -74,7 +62,7 @@ void HandlerPage::setPageVars(TemplatePage &page, std::string pagename)
if(!pagename.empty())
{
std::string headerlinks;
TemplatePage headerlink = this->templ->getPage("_headerlink");
TemplatePage &headerlink = this->templ->getPage("_headerlink");
auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value)
{
headerlink.setVar("href", href);
@ -101,4 +89,10 @@ void HandlerPage::setPageVars(TemplatePage &page, std::string pagename)
page.setVar("page", pagename);
page.setVar("title", createPageTitle(pagename));
}
}

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