Сравнить коммиты

33 Коммитов

Автор SHA1 Сообщение Дата
7a2f15cabe handlers: Introduce HandlerVersion to return the verison string 2021-10-10 22:39:35 +02:00
c9dc3416d7 TemplatePage: Change 'content' to shared_ptr 2021-10-10 22:32:13 +02:00
92be470545 Makefile: Remove LDFLAGS from .o compilation 2021-10-10 20:17:38 +02:00
d5485a833f CLIConsole: On 'exit', ask whether to quit attached instance 2021-10-10 20:15:28 +02:00
0bdb22c170 gateway: HttpGateway: convertRequest(): Don't convert param to std::string 2021-10-10 20:15:28 +02:00
eb49b013a7 Makefile: Add profile target 2021-10-10 20:15:28 +02:00
9593429f95 utils: trim(): Take string_view 2021-10-10 20:15:28 +02:00
86ac86b83f Response: addHeader(): Pass by value, not reference 2021-10-10 20:15:28 +02:00
92e7390056 utils: Pass by value where it makes sense 2021-10-10 20:15:28 +02:00
b1a8572eb6 utils: hasKey(), getKeyOrEmpty(), getAll(): Take params as references
Somehow, the fact that multimap was being copyied slipped through.
2021-10-10 20:15:28 +02:00
44ade88cae Template: createPage(): Take std::string_view 2021-10-10 20:15:28 +02:00
aadb623bf7 UserDaoSqlite: Remove redundant std::move 2021-10-08 23:38:22 +02:00
828d827c3d Adjust to new Template::getPage() returning value, not reference 2021-10-08 00:11:58 +02:00
8ffa64beea Template: Use MapCache, getPage(): Return value, not reference 2021-10-08 00:11:30 +02:00
e970ba1682 cache: MapCache: Introduce MapCache, thread-safe cache (key/value store) 2021-10-08 00:08:00 +02:00
b59e81a41d CLI: Begin 'page' and 'pageperms' commands 2021-10-05 21:57:20 +02:00
f002969cc1 PermissionsDao: Add save() 2021-10-05 21:51:31 +02:00
c4072a7e95 Sandbox: Remove multiple stages
While interesitng in theory, there is nothing to be gained here,
because we don't really have user input at those early stages.

As we are also not a privileged process, those early stage
sandboxes in the end are not worth it, since they increase
complexity while there is no benefit in practise.

So, reduce those 3 stages to a single one (enable()), which we
activate after CLI server has launched.
2021-10-03 23:53:56 +02:00
257675485d Template: Remove redundant debug output 2021-10-03 23:13:59 +02:00
94ade7238e CLI: Add 'version' command 2021-10-03 23:04:46 +02:00
fa5e75893f Add version.{h,cpp}: Returning version info 2021-10-03 23:01:19 +02:00
3d0fce590b Introduce CLI
main: Parse args using getopt_long() in main().

Begin implementation of a CLI. It can be launched
using ./qswiki config --cli.

Allow connecting to another instance using "attach" command.
This uses Unix domain sockets, and in the future can be used
to drop caches, reload template, etc.

Closes: #21
2021-10-03 17:05:46 +02:00
1082f8ac5a Permissions: Add toString()
Get a (reasonable) string representation of the permissions contained
in a Permissions object.
2021-10-03 17:01:48 +02:00
8b044d712b Authenticator: Introduce AUTH_DEFAULT_SALT_SIZE 2021-10-03 17:01:03 +02:00
5037a17fba utils: introduce trim() 2021-10-03 16:51:04 +02:00
164b2c19ee userDao: Implement list() 2021-10-03 16:51:04 +02:00
8d685dc581 Makefile: Remove -lseccomp as we don't need it anymore 2021-09-29 18:33:45 +02:00
ed43f5f700 submodules: update cpp-httplib 2021-09-29 18:28:18 +02:00
10f00aeb45 main: Pass absolute path of config file
As sandboxing code chroots and chdirs away,
2021-09-23 17:13:08 +02:00
67eb8b6428 sandbox: adjust to latest qssb.h 2021-09-23 17:13:08 +02:00
f26fd19fb4 submodules: sync with latest upstream 2021-09-23 17:13:08 +02:00
204a72da1f setup: Fix broken FTS DELETE op
Thie previous DELETE statement lead to strange
behaviours. It was pure luck this did not blow up
before all these years. It appears it may leave the index
in an undefined state, and the database recently started
to display strange behaviour in connection with newer sqlite
version.

Now, just remove the previous revision from the FTS index,
as for now, search only cares about the most recent revisions.

Also, remove redundant UPDATE trigger on revision table
We never update revisions, thus such trigger is simply
redundant.

Relevant: https://gitlab.gnome.org/GNOME/tracker/-/merge_requests/353
2021-09-23 17:13:08 +02:00
88816a4015 utils: html_xss(): Add ' and &
They REALLY should have been there from the beginning...
2021-06-15 18:37:52 +02:00
63 изменённых файлов: 1030 добавлений и 362 удалений

3
.gitmodules поставляемый
Просмотреть файл

@ -7,6 +7,3 @@
[submodule "submodules/qssb.h"] [submodule "submodules/qssb.h"]
path = submodules/qssb.h path = submodules/qssb.h
url = https://gitea.quitesimple.org/crtxcr/qssb.h.git url = https://gitea.quitesimple.org/crtxcr/qssb.h.git
[submodule "submodules/qsmaddy"]
path = submodules/qsmaddy
url = https://gitea.quitesimple.org/crtxcr/qsmaddy

Просмотреть файл

@ -2,8 +2,8 @@
CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra
RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs -lseccomp LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h -I submodules/qsmaddy/include/ INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h
CXX=g++ CXX=g++
@ -42,7 +42,12 @@ GTEST_OBJECTS=$(filter-out qswiki.o, $(WIKIOBJECTS))
.DEFAULT_GOAL := qswiki .DEFAULT_GOAL := qswiki
release: CXXFLAGS=$(RELEASE_CXXFLAGS) release: CXXFLAGS=$(RELEASE_CXXFLAGS)
profile: CXXFLAGS=$(RELEASE_CXXFLAGS) -pg
profile: LDFLAGS+= -pg
release: qswiki release: qswiki
profile: qswiki
qswiki: $(WIKIOBJECTS) qswiki: $(WIKIOBJECTS)
$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki $(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
@ -53,8 +58,10 @@ 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) $(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 %.o:%.cpp
$(CXX) ${CXXFLAGS} ${LDFLAGS} ${INCLUDEFLAGS} -c -o $@ $< $(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -c -o $@ $<
version.o:version.cpp
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
clean: clean:
rm -f $(OBJECTS) $(DEPENDS) rm -f $(OBJECTS) $(DEPENDS)

Просмотреть файл

@ -42,11 +42,12 @@ std::vector<char> Authenticator::pbkdf5(std::string password, const std::vector<
unsigned char hash[32]; unsigned char hash[32];
const EVP_MD *sha256 = EVP_sha256(); const EVP_MD *sha256 = EVP_sha256();
const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(salt.data()); const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(salt.data());
int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash); int ret =
PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
if(ret != 1) if(ret != 1)
{ {
Logger::error() << "Authenticator: pbkdf5: Failed to create hash"; Logger::error() << "Authenticator: pbkdf5: Failed to create hash";
return { }; return {};
} }
std::vector<char> result; std::vector<char> result;

Просмотреть файл

@ -3,6 +3,7 @@
#include <variant> #include <variant>
#include "database/userdao.h" #include "database/userdao.h"
#define AUTH_DEFAULT_SALT_SIZE 32
enum AuthenticationError enum AuthenticationError
{ {
UserNotFound, UserNotFound,

1
cache/mapcache.cpp поставляемый Обычный файл
Просмотреть файл

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

37
cache/mapcache.h поставляемый Обычный файл
Просмотреть файл

@ -0,0 +1,37 @@
#ifndef MAPCACHE_H
#define MAPCACHE_H
#include <map>
#include <set>
#include <shared_mutex>
/* 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

251
cli.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,251 @@
#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(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);
}
else
{
return {false, "User not found"};
}
}
std::pair<bool, std::string> CLIHandler::user_rename(const std::vector<std::string> &args)
{
return {true, ""};
}
std::pair<bool, std::string> CLIHandler::user_set_perms(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(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(const std::vector<std::string> &args)
{
auto pageDao = this->db->createPageDao();
QueryOption o;
auto result = pageDao->getPageList(o);
std::stringstream stream;
for(std::string pagename : result)
{
Page p = pageDao->find(pagename).value();
stream << p.name << " " << p.pageid << " " << std::string(p.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(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(const std::vector<std::string> &args)
{
return {true, get_version_string()};
}

84
cli.h Обычный файл
Просмотреть файл

@ -0,0 +1,84 @@
#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(const std::vector<std::string> &args);
std::pair<bool, std::string> cli_help(const std::vector<std::string> &args);
std::pair<bool, std::string> user_add(const std::vector<std::string> &args);
std::pair<bool, std::string> user_change_pw(const std::vector<std::string> &args);
std::pair<bool, std::string> user_rename(const std::vector<std::string> &args);
std::pair<bool, std::string> user_set_perms(const std::vector<std::string> &args);
std::pair<bool, std::string> user_list(const std::vector<std::string> &args);
std::pair<bool, std::string> user_show(const std::vector<std::string> &args);
std::pair<bool, std::string> page_list(const std::vector<std::string> &args);
std::pair<bool, std::string> pageperms_set_permissions(const std::vector<std::string> &args);
std::pair<bool, std::string> version(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},
{"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 *, const std::vector<std::string> &args) -> std::pair<bool, std::string>
{
exit(EXIT_SUCCESS);
return {true, ""};
}},
{"help", "print this help", 0, {}, &CLIHandler::cli_help},
{"attach", "attach to running instance", 0, {}, &CLIHandler::attach},
{"version", "print verison info", 0, {}, &CLIHandler::version}}};
std::pair<bool, std::string> processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd,
const std::vector<std::string> &args);
public:
CLIHandler(Config &config, Database &d);
std::pair<bool, std::string> processCommand(std::string cmd, const std::vector<std::string> &args);
static std::pair<std::string, std::vector<std::string>> splitCommand(std::string input);
};
#endif // CLI_H

147
cliconsole.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,147 @@
#include "cliconsole.h"
CLIConsole::CLIConsole(CLIHandler &cliHandler, std::string socketPath)
{
this->handler = &cliHandler;
this->socketPath = socketPath;
}
std::pair<bool, std::string> CLIConsole::send(std::string input)
{
ssize_t ret =
sendto(this->sock, input.c_str(), input.size(), 0, (const sockaddr *)&this->server, sizeof(this->server));
if((size_t)ret != input.size())
{
return {false, "sendto failed: " + std::to_string(ret) + " " + std::string(strerror(errno))};
}
char buffer[1024] = {0};
ret = recvfrom(this->sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
if(ret == -1)
{
return {false, "recvfrom failed: " + std::string(strerror(errno))};
}
bool success = false;
std::string_view view = buffer;
if(view[0] == '1')
{
success = true;
}
view.remove_prefix(1);
std::string msg = std::string{view};
return {success, msg};
}
void CLIConsole::attach()
{
if(attached)
{
std::cout << "Already attached" << std::endl;
return;
}
if(socketPath.size() > sizeof(this->server.sun_path) - 1)
{
std::cout << "Socket path too long" << std::endl;
return;
}
memset(&this->server, 0, sizeof(this->server));
this->server.sun_family = AF_UNIX;
memcpy(&this->server.sun_path, socketPath.c_str(), socketPath.size());
this->server.sun_path[socketPath.size()] = 0;
int s = socket(AF_UNIX, SOCK_DGRAM, 0);
if(s == -1)
{
std::cout << "Failed to create socket" << strerror(errno) << std::endl;
return;
}
this->sock = s;
struct sockaddr_un client;
client.sun_family = AF_UNIX;
client.sun_path[0] = '\0';
int ret = bind(this->sock, (struct sockaddr *)&client, sizeof(client));
if(ret != 0)
{
std::cout << "bind() failed: " << strerror(errno) << std::endl;
return;
}
auto result = this->send("attach");
if(result.first)
{
std::cout << "Attached successfully: " << result.second << std::endl;
this->attached = true;
}
else
{
std::cout << "Attached unsuccessfully: " << result.second << std::endl;
}
}
void CLIConsole::startInteractive()
{
std::cout << "qswiki CLI" << std::endl;
std::cout << "not attached - use 'attach' to connect to running instance" << std::endl;
while(true)
{
std::string input;
std::cout << "> ";
std::getline(std::cin, input);
if(std::cin.bad() || std::cin.eof())
{
std::cout << "Exiting" << std::endl;
return;
}
if(input.empty())
{
continue;
}
auto pair = CLIHandler::splitCommand(input);
if(pair.first == "exit")
{
if(attached)
{
std::cout << "You are attached. Quit attached instance too (y) or only this one(n)" << std::endl;
char response;
std::cin >> response;
if(response == 'y')
{
this->send("exit");
}
}
std::cout << "Exiting CLI" << std::endl;
exit(EXIT_SUCCESS);
}
if(pair.first == "attach")
{
attach();
continue;
}
std::pair<bool, std::string> result;
if(!attached)
{
result = handler->processCommand(pair.first, pair.second);
}
else
{
result = this->send(input);
}
if(!result.second.empty())
{
std::cout << result.second << std::endl;
}
if(!result.first)
{
std::cout << "Command failed" << std::endl;
}
}
std::cout << "\n";
}

27
cliconsole.h Обычный файл
Просмотреть файл

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

77
cliserver.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,77 @@
#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 = [=]
{
while(true)
{
char buffer[1024] = {0};
struct sockaddr_un peer;
socklen_t peerlen = sizeof(peer);
int ret = recvfrom(s, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peerlen);
if(ret == -1)
{
Logger::error() << "Error during recvfrom in CLI server: " << strerror(errno);
return false;
}
std::string input{buffer};
auto pair = CLIHandler::splitCommand(input);
auto result = handler->processCommand(pair.first, pair.second);
char resultCode = '0';
if(result.first)
{
resultCode = '1';
}
std::string resultString;
resultString += resultCode;
resultString += result.second;
ret = sendto(s, resultString.c_str(), resultString.size(), 0, (struct sockaddr *)&peer, peerlen);
if(ret == -1)
{
Logger::error() << "Error during sendto in CLI server: " << strerror(errno);
}
}
};
std::thread t1{worker};
t1.detach();
return true;
}

16
cliserver.h Обычный файл
Просмотреть файл

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

Просмотреть файл

@ -71,7 +71,6 @@ Config::Config(const std::map<std::string, std::string> &map)
this->configmap = map; this->configmap = map;
this->wikipath = optional("wikipath", "/"); this->wikipath = optional("wikipath", "/");
this->parser = optional("parser", "markdown");
this->handlersConfig.anon_username = optional("anon_username", "anonymouse"); this->handlersConfig.anon_username = optional("anon_username", "anonymouse");
this->handlersConfig.wikiname = required("wikiname"); this->handlersConfig.wikiname = required("wikiname");
this->logfile = required("logfile"); this->logfile = required("logfile");

Просмотреть файл

@ -86,7 +86,6 @@ class Config
std::string templateprefix; std::string templateprefix;
std::string logfile; std::string logfile;
std::string connectionstring; std::string connectionstring;
std::string parser;
int session_max_lifetime; int session_max_lifetime;
int threadscount; int threadscount;

Просмотреть файл

@ -7,6 +7,7 @@ class PermissionsDao
public: public:
PermissionsDao(); PermissionsDao();
virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0; virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0;
virtual void save(std::string pagename, std::string username, Permissions perms) = 0;
}; };
#endif // PERMISSIONSDAO_H #endif // PERMISSIONSDAO_H

Просмотреть файл

@ -41,3 +41,21 @@ std::optional<Permissions> PermissionsDaoSqlite::find(std::string pagename, std:
return Permissions{permissions}; 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);
}
}

Просмотреть файл

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

Просмотреть файл

@ -3,6 +3,7 @@
#include <string> #include <string>
#include <optional> #include <optional>
#include "../user.h" #include "../user.h"
#include "queryoption.h"
class UserDao class UserDao
{ {
public: public:
@ -10,6 +11,7 @@ class UserDao
virtual bool exists(std::string username) = 0; virtual bool exists(std::string username) = 0;
virtual std::optional<User> find(std::string username) = 0; virtual std::optional<User> find(std::string username) = 0;
virtual std::optional<User> find(int id) = 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 deleteUser(std::string username) = 0;
virtual void save(const User &u) = 0; virtual void save(const User &u) = 0;
virtual ~UserDao(){}; virtual ~UserDao(){};

Просмотреть файл

@ -23,6 +23,7 @@ SOFTWARE.
#include <memory> #include <memory>
#include <cstring> #include <cstring>
#include "userdaosqlite.h" #include "userdaosqlite.h"
#include "sqlitequeryoption.h"
UserDaoSqlite::UserDaoSqlite() UserDaoSqlite::UserDaoSqlite()
{ {
@ -47,7 +48,7 @@ std::optional<User> UserDaoSqlite::find(std::string username)
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled); stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms}; user.permissions = Permissions{perms};
return std::move(user); return user;
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -70,7 +71,7 @@ std::optional<User> UserDaoSqlite::find(int id)
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled); stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms}; user.permissions = Permissions{perms};
return std::move(user); return user;
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -82,6 +83,39 @@ std::optional<User> UserDaoSqlite::find(int id)
} }
} }
std::vector<User> UserDaoSqlite::list(QueryOption o)
{
std::vector<User> result;
try
{
std::string queryOption = SqliteQueryOption(o).setOrderByColumn("username").setPrependWhere(true).build();
std::string query = "SELECT username, password, salt, permissions, enabled FROM user " + queryOption;
*db << query >>
[&](std::string username, std::vector<char> pw, std::vector<char> salt, int permisisons, bool enabled)
{
User tmp;
tmp.login = username;
tmp.password = pw;
tmp.salt = salt;
tmp.permissions = Permissions{permisisons};
tmp.enabled = enabled;
result.push_back(tmp);
};
}
catch(const sqlite::errors::no_rows &e)
{
return result;
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return result;
}
void UserDaoSqlite::deleteUser(std::string username) void UserDaoSqlite::deleteUser(std::string username)
{ {
// What to do with the contributions of the user? // What to do with the contributions of the user?

Просмотреть файл

@ -13,6 +13,7 @@ class UserDaoSqlite : public UserDao, protected SqliteDao
std::optional<User> find(std::string username); std::optional<User> find(std::string username);
std::optional<User> find(int id); std::optional<User> find(int id);
std::vector<User> list(QueryOption o);
void deleteUser(std::string username); void deleteUser(std::string username);
void save(const User &u); void save(const User &u);
using SqliteDao::SqliteDao; using SqliteDao::SqliteDao;

Просмотреть файл

@ -41,7 +41,7 @@ Request HttpGateway::convertRequest(httplib::Request request)
// TODO: this eats resources, where perhaps it does not need to. move it to request? // TODO: this eats resources, where perhaps it does not need to. move it to request?
for(auto &it : request.params) for(auto &it : request.params)
{ {
it.second = utils::html_xss(std::string{it.second}); it.second = utils::html_xss(it.second);
} }
if(request.method == "GET") if(request.method == "GET")
{ {
@ -83,7 +83,8 @@ void HttpGateway::work(RequestWorker &worker)
{ {
httplib::Server server; httplib::Server server;
server.set_payload_max_length(this->maxPayloadLength); 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); Request wikiRequest = convertRequest(req);
Logger::debug() << "httpgateway: received request " << wikiRequest; Logger::debug() << "httpgateway: received request " << wikiRequest;
Response wikiresponse = worker.processRequest(wikiRequest); Response wikiresponse = worker.processRequest(wikiRequest);

Просмотреть файл

@ -34,7 +34,7 @@ void Handler::setGeneralVars(TemplatePage &page)
} }
Response Handler::errorResponse(std::string errortitle, std::string errormessage, int status) 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("title", createPageTitle(errortitle));
error.setVar("errortitle", errortitle); error.setVar("errortitle", errortitle);
error.setVar("errormessage", errormessage); error.setVar("errormessage", errormessage);

Просмотреть файл

@ -9,13 +9,10 @@
#include "../database/queryoption.h" #include "../database/queryoption.h"
#include "../logger.h" #include "../logger.h"
#include "../cache/icache.h" #include "../cache/icache.h"
#include "../iparser.h"
class Handler class Handler
{ {
protected: protected:
ICache *cache; ICache *cache;
IParser *parser;
Template *templ; Template *templ;
Database *database; Database *database;
Session *userSession; Session *userSession;
@ -28,7 +25,7 @@ class Handler
public: public:
Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider, Handler(HandlerConfig &handlersConfig, Template &templ, Database &db, Session &userSession, UrlProvider &provider,
ICache &cache, IParser &parser) ICache &cache)
{ {
this->handlersConfig = &handlersConfig; this->handlersConfig = &handlersConfig;
this->templ = &templ; this->templ = &templ;
@ -36,7 +33,6 @@ class Handler
this->userSession = &userSession; this->userSession = &userSession;
this->urlProvider = &provider; this->urlProvider = &provider;
this->cache = &cache; this->cache = &cache;
this->parser = &parser;
} }
virtual Response handle(const Request &r); virtual Response handle(const Request &r);

Просмотреть файл

@ -32,7 +32,7 @@ Response HandlerAllCategories::handleRequest(const Request &r)
"No categories", "No categories",
"This wiki does not have any categories defined yet or your query options did not yield any results"); "This wiki does not have any categories defined yet or your query options did not yield any results");
} }
TemplatePage &searchPage = this->templ->getPage("allcategories"); TemplatePage searchPage = this->templ->getPage("allcategories");
std::string body = std::string body =
this->templ->renderSearch(resultList, [&](std::string str) { return this->urlProvider->category(str); }); this->templ->renderSearch(resultList, [&](std::string str) { return this->urlProvider->category(str); });
searchPage.setVar("categorylist", body); searchPage.setVar("categorylist", body);

Просмотреть файл

@ -32,7 +32,7 @@ Response HandlerAllPages::handleRequest(const Request &r)
{ {
return errorResponse("No pages", "This wiki does not have any pages yet"); return errorResponse("No pages", "This wiki does not have any pages yet");
} }
TemplatePage &searchPage = this->templ->getPage("allpages"); TemplatePage searchPage = this->templ->getPage("allpages");
std::string body = this->templ->renderSearch(resultList); std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body); searchPage.setVar("pagelist", body);
searchPage.setVar("title", createPageTitle("All pages")); searchPage.setVar("title", createPageTitle("All pages"));

Просмотреть файл

@ -33,7 +33,7 @@ Response HandlerCategory::handleRequest(const Request &r)
} }
QueryOption qo = queryOption(r); QueryOption qo = queryOption(r);
auto resultList = categoryDao->fetchMembers(categoryname, qo); auto resultList = categoryDao->fetchMembers(categoryname, qo);
TemplatePage &searchPage = this->templ->getPage("show_category"); TemplatePage searchPage = this->templ->getPage("show_category");
std::string body = this->templ->renderSearch(resultList); std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body); searchPage.setVar("pagelist", body);
searchPage.setVar("categoryname", categoryname); searchPage.setVar("categoryname", categoryname);

Просмотреть файл

@ -33,7 +33,7 @@ SOFTWARE.
#include "handlerhistory.h" #include "handlerhistory.h"
#include "handlerpagedelete.h" #include "handlerpagedelete.h"
#include "handlerusersettings.h" #include "handlerusersettings.h"
#include "handlerversion.h"
std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession) std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession)
{ {
if(action == "" || action == "index") if(action == "" || action == "index")
@ -80,6 +80,10 @@ std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action
{ {
return produce<HandlerUserSettings>(userSession); return produce<HandlerUserSettings>(userSession);
} }
if(action == "version")
{
return produce<HandlerVersion>(userSession);
}
return produce<HandlerInvalidAction>(userSession); return produce<HandlerInvalidAction>(userSession);
} }

Просмотреть файл

@ -10,16 +10,15 @@ class HandlerFactory
Database &db; Database &db;
UrlProvider &urlProvider; UrlProvider &urlProvider;
ICache &cache; ICache &cache;
IParser &parser;
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, parser); return std::make_unique<T>(handlerConfig, templ, db, userSession, urlProvider, cache);
} }
public: public:
HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache, IParser &parser) HandlerFactory(HandlerConfig &handlerConfig, Template &templ, Database &db, UrlProvider &urlprovider, ICache &cache)
: handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache), parser(parser) : handlerConfig(handlerConfig), templ(templ), db(db), urlProvider(urlprovider), cache(cache)
{ {
} }
std::unique_ptr<Handler> createHandler(const std::string &action, Session &userSession); std::unique_ptr<Handler> createHandler(const std::string &action, Session &userSession);

Просмотреть файл

@ -55,7 +55,7 @@ Response HandlerLogin::handleRequest(const Request &r)
{ {
page = "index"; page = "index";
} }
TemplatePage &loginTemplatePage = this->templ->getPage("login"); TemplatePage loginTemplatePage = this->templ->getPage("login");
setGeneralVars(loginTemplatePage); setGeneralVars(loginTemplatePage);
loginTemplatePage.setVar("loginurl", urlProvider->login(page)); loginTemplatePage.setVar("loginurl", urlProvider->login(page));
loginTemplatePage.setVar("title", createPageTitle("Login")); loginTemplatePage.setVar("title", createPageTitle("Login"));

Просмотреть файл

@ -63,8 +63,9 @@ void HandlerPage::setPageVars(TemplatePage &page, std::string pagename)
if(!pagename.empty()) if(!pagename.empty())
{ {
std::string headerlinks; 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) { auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value)
{
headerlink.setVar("href", href); headerlink.setVar("href", href);
headerlink.setVar("value", value); headerlink.setVar("value", value);
headerlinks += headerlink.render(); headerlinks += headerlink.render();

Просмотреть файл

@ -21,8 +21,8 @@ SOFTWARE.
#include "handlerpageedit.h" #include "handlerpageedit.h"
#include "../database/exceptions.h" #include "../database/exceptions.h"
#include "../request.h" #include "../request.h"
#include "../parserlegacy.h"
#include "../parser.h"
bool HandlerPageEdit::canAccess(std::string page) bool HandlerPageEdit::canAccess(std::string page)
{ {
return this->userSession->user.permissions.canEdit(); return this->userSession->user.permissions.canEdit();
@ -59,7 +59,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
// TODO: must check, whether categories differ, and perhaps don't allow every user // TODO: must check, whether categories differ, and perhaps don't allow every user
// to set categories // to set categories
ParserLegacy parser; Parser parser;
std::vector<std::string> cats = parser.extractCategories(newContent); std::vector<std::string> cats = parser.extractCategories(newContent);
try try
{ {
@ -107,7 +107,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
if(r.post("do") == "preview") if(r.post("do") == "preview")
{ {
std::string newContent = r.post("content"); std::string newContent = r.post("content");
ParserLegacy parser; Parser parser;
TemplatePage templatePage = this->templ->getPage("page_creation_preview"); TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("actionurl", urlProvider->editPage(pagename));
templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent)); templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent));
@ -121,7 +121,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
} }
} }
TemplatePage &templatePage = this->templ->getPage("page_creation"); TemplatePage templatePage = this->templ->getPage("page_creation");
templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("actionurl", urlProvider->editPage(pagename));
templatePage.setVar("content", body); templatePage.setVar("content", body);
setPageVars(templatePage, pagename); setPageVars(templatePage, pagename);

Просмотреть файл

@ -21,7 +21,7 @@ SOFTWARE.
#include "handlerpageview.h" #include "handlerpageview.h"
#include "../database/exceptions.h" #include "../database/exceptions.h"
#include "../logger.h" #include "../logger.h"
#include "../parserlegacy.h" #include "../parser.h"
#include "../htmllink.h" #include "../htmllink.h"
bool HandlerPageView::canAccess(std::string page) bool HandlerPageView::canAccess(std::string page)
@ -128,9 +128,9 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("Database error", "While trying to fetch revision, a database error occured"); return errorResponse("Database error", "While trying to fetch revision, a database error occured");
} }
TemplatePage &page = this->templ->getPage(templatepartname); TemplatePage page = this->templ->getPage(templatepartname);
Parser parser;
Response result; Response result;
result.setStatus(200); result.setStatus(200);
std::string indexcontent; std::string indexcontent;
@ -138,8 +138,8 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
if(revisionid > 0) if(revisionid > 0)
{ {
indexcontent = createIndexContent(*parser, revision->content); indexcontent = createIndexContent(parser, revision->content);
parsedcontent = parser->parse(pageDao, *this->urlProvider, revision->content); parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
} }
else else
{ {
@ -153,7 +153,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
} }
else else
{ {
indexcontent = createIndexContent(*parser, revision->content); indexcontent = createIndexContent(parser, revision->content);
this->cache->put(cachekeyindexcontent, indexcontent); this->cache->put(cachekeyindexcontent, indexcontent);
} }
if(cachedparsedcontent) if(cachedparsedcontent)
@ -162,7 +162,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
} }
else else
{ {
parsedcontent = parser->parse(pageDao, *this->urlProvider, revision->content); parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
this->cache->put(cachekeyparsedcontent, parsedcontent); this->cache->put(cachekeyparsedcontent, parsedcontent);
} }
} }

Просмотреть файл

@ -37,7 +37,7 @@ Response HandlerSearch::handleRequest(const Request &r)
{ {
return errorResponse("No results", "Your search for " + q + " did not yield any results."); return errorResponse("No results", "Your search for " + q + " did not yield any results.");
} }
TemplatePage &searchPage = this->templ->getPage("search"); TemplatePage searchPage = this->templ->getPage("search");
std::string body = this->templ->renderSearch(resultList); std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body); searchPage.setVar("pagelist", body);
searchPage.setVar("searchterm", q); searchPage.setVar("searchterm", q);

Просмотреть файл

@ -15,19 +15,20 @@ Response HandlerUserSettings::handleRequest(const Request &r)
if(newpassword != newpasswordconfirm) if(newpassword != newpasswordconfirm)
{ {
//TODO: is not nice, users has to hit the back button... // TODO: is not nice, users has to hit the back button...
return this->errorResponse("Passwords don't match", "The entered new passwords don't match"); return this->errorResponse("Passwords don't match", "The entered new passwords don't match");
} }
auto userDao = this->database->createUserDao(); auto userDao = this->database->createUserDao();
Authenticator authenticator(*userDao); Authenticator authenticator(*userDao);
std::variant<User, AuthenticationError> authresult = authenticator.authenticate(this->userSession->user.login, oldpassword); std::variant<User, AuthenticationError> authresult =
authenticator.authenticate(this->userSession->user.login, oldpassword);
if(std::holds_alternative<AuthenticationError>(authresult)) if(std::holds_alternative<AuthenticationError>(authresult))
{ {
return this->errorResponse("Invalid current password", "The old password you entered is invalid"); return this->errorResponse("Invalid current password", "The old password you entered is invalid");
} }
Random r; Random r;
std::vector<char> salt = r.getRandom(23); std::vector<char> salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
User user = std::get<User>(authresult); User user = std::get<User>(authresult);
user.salt = salt; user.salt = salt;
user.password = authenticator.hash(newpassword, user.salt); user.password = authenticator.hash(newpassword, user.salt);
@ -50,7 +51,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
} }
} }
TemplatePage &userSettingsPage = this->templ->getPage("usersettings"); TemplatePage userSettingsPage = this->templ->getPage("usersettings");
setGeneralVars(userSettingsPage); setGeneralVars(userSettingsPage);
userSettingsPage.setVar("usersettingsurl", urlProvider->userSettings()); userSettingsPage.setVar("usersettingsurl", urlProvider->userSettings());
userSettingsPage.setVar("title", createPageTitle("User settings - " + this->userSession->user.login)); userSettingsPage.setVar("title", createPageTitle("User settings - " + this->userSession->user.login));

9
handlers/handlerversion.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,9 @@
#include "handlerversion.h"
#include "../version.h"
Response HandlerVersion::handleRequest(const Request &r)
{
Response response;
response.setContentType("text/plain");
response.setBody(get_version_string());
return response;
}

18
handlers/handlerversion.h Обычный файл
Просмотреть файл

@ -0,0 +1,18 @@
#ifndef HANDLERVERSION_H
#define HANDLERVERSION_H
#include "handler.h"
class HandlerVersion : public Handler
{
public:
using Handler::Handler;
public:
Response handleRequest(const Request &r) override;
bool canAccess(const Permissions &perms) override
{
return true;
}
};
#endif // HANDLERVERSION_H

Просмотреть файл

@ -5,8 +5,6 @@
#include "headline.h" #include "headline.h"
#include "database/pagedao.h" #include "database/pagedao.h"
#include "urlprovider.h" #include "urlprovider.h"
class IParser class IParser
{ {
public: public:
@ -18,6 +16,4 @@ class IParser
virtual ~IParser(){}; virtual ~IParser(){};
}; };
#endif // PARSER_H #endif // PARSER_H

Просмотреть файл

@ -24,22 +24,29 @@ SOFTWARE.
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
#include "parserlegacy.h" #include "parser.h"
#include "utils.h" #include "utils.h"
#include "htmllink.h" #include "htmllink.h"
std::vector<Headline> ParserLegacy::extractHeadlines(std::string content) const std::vector<Headline> Parser::extractHeadlines(std::string content) const
{ {
std::vector<Headline> result; std::vector<Headline> result;
utils::regex_callback_extractor(std::regex(R"(\[h(1|2|3)\](.*?)\[/h\1\])"), content, [&](std::smatch &smatch) { std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])";
std::regex headerfinder(reg);
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
auto end = std::sregex_iterator();
for(auto it = begin; it != end; it++)
{
auto smatch = *it;
Headline h; Headline h;
h.level = utils::toUInt(smatch.str(1)); h.level = utils::toUInt(smatch.str(1));
h.title = smatch.str(2); h.title = smatch.str(2);
result.push_back(h); result.push_back(h);
}); }
return result; return result;
} }
std::vector<std::string> ParserLegacy::extractCategories(std::string content) const std::vector<std::string> Parser::extractCategories(std::string content) const
{ {
std::vector<std::string> result; std::vector<std::string> result;
std::string reg = R"(\[category\](.*?)\[/category\])"; std::string reg = R"(\[category\](.*?)\[/category\])";
@ -55,7 +62,7 @@ std::vector<std::string> ParserLegacy::extractCategories(std::string content) co
return result; return result;
} }
std::string ParserLegacy::extractCommand(std::string cmdname, std::string content) const std::string Parser::extractCommand(std::string cmdname, std::string content) const
{ {
std::string cmd = "[cmd:" + cmdname + "]"; std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]"; std::string cmdend = "[/cmd:" + cmdname + "]";
@ -74,7 +81,7 @@ std::string ParserLegacy::extractCommand(std::string cmdname, std::string conten
} }
return ""; return "";
} }
std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{ {
std::string linktag = match.str(1); std::string linktag = match.str(1);
std::string inside = match.str(2); std::string inside = match.str(2);
@ -109,7 +116,7 @@ std::string ParserLegacy::processLink(const PageDao &pageDao, UrlProvider &urlPr
return htmllink.render(); return htmllink.render();
} }
std::string ParserLegacy::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
{ {
std::string result; std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings // we don't care about commands, but we nevertheless replace them with empty strings

Просмотреть файл

@ -2,7 +2,7 @@
#define PARSER_H #define PARSER_H
#include "iparser.h" #include "iparser.h"
class ParserLegacy : public IParser class Parser : public IParser
{ {
private: private:
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
@ -13,7 +13,7 @@ class ParserLegacy : public IParser
std::vector<std::string> extractCategories(std::string content) const override; std::vector<std::string> extractCategories(std::string content) const override;
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override; std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override;
using IParser::IParser; using IParser::IParser;
~ParserLegacy(){}; ~Parser(){};
}; };
#endif // PARSER_H #endif // PARSER_H

Просмотреть файл

@ -1,71 +0,0 @@
#include "parsermarkdown.h"
#include "logger.h"
#include "htmllink.h"
std::string ParserMarkdown::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{
std::string inner = match.str(1);
std::string link = match.str(2);
HtmlLink htmllink;
htmllink.href = link;
htmllink.innervalue = inner;
if(link.find("http://") == 0 || link.find("https://") == 0)
{
return htmllink.render();
}
if(pageDao.exists(link))
{
htmllink.cssclass = "exists";
}
else
{
htmllink.cssclass = "notexists";
}
htmllink.href = urlProvider.page(htmllink.href);
return htmllink.render();
}
ParserMarkdown::ParserMarkdown()
{
}
std::vector<Headline> ParserMarkdown::extractHeadlines(std::string content) const
{
std::vector<Headline> result;
utils::regex_callback_extractor(std::regex(R"((#{1,6}) (.*))"), content, [&](std::smatch &smatch) {
Headline h;
h.level = smatch.str(1).length();
h.title = smatch.str(2);
result.push_back(h);
});
return result;
}
std::string ParserMarkdown::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const
{
std::shared_ptr<maddy::ParserConfig> config = std::make_shared<maddy::ParserConfig>();
auto maddy = std::make_shared<maddy::Parser>(config);
auto linkParser = std::make_shared<maddy::LinkParser>();
linkParser->setCallback([&](std::smatch &match) { return processLink(pagedao, provider, match); });
maddy->setLinkParser(linkParser);
// TODO: hack because the parser breaks if there is an \r
content = utils::strreplace(content, "\r", "");
std::stringstream s{content};
std::string result = maddy->Parse(s);
return result;
}
std::string ParserMarkdown::extractCommand(std::string cmdname, std::string content) const
{
return "";
}
std::vector<std::string> ParserMarkdown::extractCategories(std::string content) const
{
return { };
}

Просмотреть файл

@ -1,21 +0,0 @@
#ifndef PARSER_MARKDOWN_H
#define PARSER_MARKDOWN_H
#include "iparser.h"
#include "maddy/parser.h"
class ParserMarkdown : public IParser
{
private:
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
public:
ParserMarkdown();
std::vector<Headline> extractHeadlines(std::string content) const;
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const;
std::string extractCommand(std::string cmdname, std::string content) const;
std::vector<std::string> extractCategories(std::string content) const;
};
#endif // PARSER_MARKDOWN_H

Просмотреть файл

@ -20,6 +20,17 @@ SOFTWARE.
*/ */
#include "permissions.h" #include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
{"can_delete", PERM_CAN_DELETE},
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
{"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}};
Permissions::Permissions(int permissions) Permissions::Permissions(int permissions)
{ {
this->permissions = permissions; this->permissions = permissions;
@ -36,3 +47,20 @@ Permissions::Permissions(const std::string &str)
} }
} }
} }
std::string Permissions::toString(int perms)
{
std::string result;
for(auto pair : permmap)
{
if(pair.second & perms)
{
result += pair.first + ",";
}
}
if(result.size() > 0)
{
result.pop_back();
}
return result;
}

Просмотреть файл

@ -14,20 +14,12 @@
#include <string> #include <string>
#include <map> #include <map>
class Permissions class Permissions
{ {
private: private:
int permissions = 0; int permissions = 0;
const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
{"can_delete", PERM_CAN_DELETE},
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
{"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}};
public: public:
Permissions() Permissions()
@ -102,6 +94,13 @@ class Permissions
{ {
return this->permissions & PERM_CAN_SEE_PAGE_LIST; return this->permissions & PERM_CAN_SEE_PAGE_LIST;
} }
std::string toString() const
{
return Permissions::toString(this->permissions);
}
static std::string toString(int perms);
}; };
#endif // PERMISSIONS_H #endif // PERMISSIONS_H

Просмотреть файл

@ -25,6 +25,7 @@ SOFTWARE.
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <filesystem> #include <filesystem>
#include <getopt.h>
#include "gateway/gatewayinterface.h" #include "gateway/gatewayinterface.h"
#include "gateway/gatewayfactory.h" #include "gateway/gatewayfactory.h"
#include "handlers/handlerfactory.h" #include "handlers/handlerfactory.h"
@ -37,9 +38,10 @@ SOFTWARE.
#include "requestworker.h" #include "requestworker.h"
#include "cache/fscache.h" #include "cache/fscache.h"
#include "sandbox/sandboxfactory.h" #include "sandbox/sandboxfactory.h"
#include "iparser.h" #include "cli.h"
#include "parserlegacy.h" #include "cliconsole.h"
#include "parsermarkdown.h" #include "cliserver.h"
#include "version.h"
void sigterm_handler(int arg) void sigterm_handler(int arg)
{ {
@ -60,6 +62,10 @@ void setup_signal_handlers()
} }
} }
#define OPT_PRINT_VERSION 23
static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version", no_argument, 0, OPT_PRINT_VERSION}};
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver) std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
{ {
@ -68,23 +74,42 @@ std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
return std::make_unique<FsCache>(path); return std::make_unique<FsCache>(path);
} }
std::unique_ptr<IParser> createParser(const ConfigVariableResolver &resolver)
{
std::string parser = resolver.getConfig("parser");
if(parser == "legacy")
{
return std::make_unique<ParserLegacy>();
}
return std::make_unique<ParserMarkdown>();
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
char *configfilepath = NULL;
int option;
int option_index;
bool cli_mode = false;
if(geteuid() == 0) if(geteuid() == 0)
{ {
std::cerr << "Do not run this as root!" << std::endl; std::cerr << "Do not run this as root!" << std::endl;
return 1; return 1;
} }
while((option = getopt_long(argc, argv, "cv", long_options, &option_index)) != -1)
{
switch(option)
{
case 'c':
cli_mode = true;
break;
case OPT_PRINT_VERSION:
std::cout << get_version_string() << std::endl;
exit(EXIT_SUCCESS);
break;
}
}
if(optind == argc)
{
std::cerr << "Missing config path" << std::endl;
return 1;
}
configfilepath = argv[optind++];
auto sandbox = createSandbox(); auto sandbox = createSandbox();
// TODO: do we want to keep it mandatory or configurable? // TODO: do we want to keep it mandatory or configurable?
if(!sandbox->supported()) if(!sandbox->supported())
@ -92,35 +117,18 @@ int main(int argc, char **argv)
Logger::error() << "Sandbox is not supported, exiting"; Logger::error() << "Sandbox is not supported, exiting";
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
if(!sandbox->enableForInit())
{
Logger::error() << "Sandboxing for init mode could not be activated.";
exit(EXIT_FAILURE);
}
if(argc < 2) if(argc < 2)
{ {
std::cerr << "no path to config file provided" << std::endl; std::cerr << "no path to config file provided" << std::endl;
return 1; return 1;
} }
std::string configpath = std::filesystem::absolute(configfilepath).string();
try try
{ {
ConfigReader configreader(argv[1]); ConfigReader configreader(configpath);
Config config = configreader.readConfig(); Config config = configreader.readConfig();
// TODO: config.connectiontring only works as long as we only support sqlite of course
if(!sandbox->enablePreWorker({
config.configVarResolver.getConfig("cache_fs_dir"),
config.templatepath,
std::filesystem::path(config.logfile).parent_path(),
std::filesystem::path(config.connectionstring).parent_path(),
}))
{
Logger::error() << "Sandboxing for pre worker stage could not be activated.";
exit(EXIT_FAILURE);
}
setup_signal_handlers(); setup_signal_handlers();
std::fstream logstream; std::fstream logstream;
@ -128,6 +136,34 @@ int main(int argc, char **argv)
Logger::setStream(&logstream); Logger::setStream(&logstream);
auto database = createDatabase(config); auto database = createDatabase(config);
std::string socketPath = config.configVarResolver.getConfig("socketpath");
CLIHandler cliHandler(config, *database);
if(cli_mode)
{
CLIConsole console{cliHandler, socketPath};
console.startInteractive();
return 0;
}
// TODO: config.connectiontring only works as long as we only support sqlite of course
if(!sandbox->enable({
config.configVarResolver.getConfig("cache_fs_dir"),
config.templatepath,
std::filesystem::path(config.logfile).parent_path(),
std::filesystem::path(config.connectionstring).parent_path(),
}))
{
Logger::error() << "Sandboxing for worker could not be enabled!";
exit(EXIT_FAILURE);
}
CLIServer cliServer{cliHandler};
if(!cliServer.detachServer(socketPath))
{
Logger::error() << "Error: Failed to detach unix socket server";
return 1;
}
// TODO: quite ugly, anon-handling must be rethought // TODO: quite ugly, anon-handling must be rethought
auto userdao = database->createUserDao(); auto userdao = database->createUserDao();
@ -144,25 +180,19 @@ int main(int argc, char **argv)
userdao->save(anon.value()); userdao->save(anon.value());
User::setAnon(anon.value()); User::setAnon(anon.value());
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver}; MapCache<TemplatePage> mapCache;
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver,
mapCache};
UrlProvider urlProvider{config.urls}; UrlProvider urlProvider{config.urls};
auto cache = createCache(config.configVarResolver); auto cache = createCache(config.configVarResolver);
cache->clear(); cache->clear();
auto parser = createParser(config.configVarResolver); HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get()};
HandlerFactory handlerFactory{config.handlersConfig, siteTemplate, *database.get(), urlProvider, *cache.get(), *parser.get()};
RequestWorker requestWorker{handlerFactory, database->createSessionDao(), siteTemplate}; RequestWorker requestWorker{handlerFactory, database->createSessionDao(), siteTemplate};
auto interface = createGateway(config); auto interface = createGateway(config);
if(!sandbox->enableForWorker())
{
Logger::error() << "Sandboxing for worker could not be enabled!";
exit(EXIT_FAILURE);
}
interface->work(requestWorker); interface->work(requestWorker);
} }
catch(const std::exception &e) catch(const std::exception &e)

Просмотреть файл

@ -59,7 +59,7 @@ Response RequestWorker::processRequest(const Request &r)
if(session.loggedIn && session.csrf_token != r.post("csrf_token")) if(session.loggedIn && session.csrf_token != r.post("csrf_token"))
{ {
// TODO: this is code duplication // TODO: this is code duplication
TemplatePage &error = this->templ->getPage("error"); TemplatePage error = this->templ->getPage("error");
error.setVar("errortitle", "Invalid csrf token"); error.setVar("errortitle", "Invalid csrf token");
error.setVar("errormessage", "Invalid csrf token"); error.setVar("errormessage", "Invalid csrf token");
return {403, error.render()}; return {403, error.render()};

Просмотреть файл

@ -32,12 +32,12 @@ Response::Response(int http_status_code, std::string html)
this->html = std::move(html); this->html = std::move(html);
} }
void Response::addHeader(const std::string &key, const std::string &value) void Response::addHeader(std::string key, std::string value)
{ {
this->responseHeaders.insert(std::make_pair(key, value)); this->responseHeaders.insert(std::make_pair(key, value));
} }
Response Response::redirectTemporarily(const std::string &url) Response Response::redirectTemporarily(std::string url)
{ {
Response result; Response result;
result.addHeader("Location", url); result.addHeader("Location", url);

Просмотреть файл

@ -27,8 +27,8 @@ class Response
return this->html; return this->html;
} }
void addHeader(const std::string &key, const std::string &value); void addHeader(std::string key, std::string value);
static Response redirectTemporarily(const std::string &url); static Response redirectTemporarily(std::string url);
void setStatus(int status) void setStatus(int status)
{ {

Просмотреть файл

@ -23,52 +23,6 @@
* obvious systemcalls. To whitelist, we need to analyse our * obvious systemcalls. To whitelist, we need to analyse our
* dependencies (http library, sqlite wrapper, sqlite lib etc.) */ * dependencies (http library, sqlite wrapper, sqlite lib etc.) */
bool SandboxLinux::enableForInit()
{
umask(0027);
struct qssb_policy policy = {0};
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
policy.no_new_privs = 1;
int result = qssb_enable_policy(&policy);
if(result != 0)
{
Logger::error() << "Failed to install sandboxing policy (init): " << result;
return false;
}
return true;
}
bool SandboxLinux::enablePreWorker(std::vector<std::string> fsPaths)
{
std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); });
struct qssb_path_policy *policies = new qssb_path_policy[fsPaths.size()];
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
policies[i].next = policies + (i + 1);
policies[i].mountpoint = fsPaths[i].c_str();
policies[i].policy = QSSB_MOUNT_ALLOW_READ | QSSB_MOUNT_ALLOW_WRITE;
}
policies[fsPaths.size() - 1].next = NULL;
struct qssb_policy policy = {0};
policy.path_policies = policies;
policy.namespace_options |= QSSB_UNSHARE_MOUNT;
policy.namespace_options |= QSSB_UNSHARE_USER;
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
int result = qssb_enable_policy(&policy);
if(result != 0)
{
Logger::error() << "Failed to install sandboxing policy (preworker): %i" << result;
return false;
}
delete[] policies;
return true;
}
bool SandboxLinux::supported() bool SandboxLinux::supported()
{ {
std::fstream stream; std::fstream stream;
@ -86,32 +40,43 @@ bool SandboxLinux::supported()
} }
return true; return true;
} }
bool SandboxLinux::enableForWorker() bool SandboxLinux::enable(std::vector<std::string> fsPaths)
{ {
struct qssb_policy policy = {0}; std::sort(fsPaths.begin(), fsPaths.end(),
policy.drop_caps = 1; [](const std::string &a, const std::string &b) { return a.length() < b.length(); });
policy.not_dumpable = 1;
policy.no_new_privs = 1;
/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the struct qssb_policy *policy = qssb_init_policy();
* sense that more could be listed here and some critical ones are probably missing */ if(policy == NULL)
int blacklisted_syscalls[] = {QSSB_SYS(setuid),
QSSB_SYS(connect),
QSSB_SYS(chroot),
QSSB_SYS(pivot_root),
QSSB_SYS(mount),
QSSB_SYS(setns),
QSSB_SYS(unshare),
QSSB_SYS(ptrace),
QSSB_SYS(personality),
QSSB_SYS(prctl),
-1};
policy.blacklisted_syscalls = blacklisted_syscalls;
if(qssb_enable_policy(&policy) != 0)
{ {
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!"; Logger::error() << "Failed to init sandboxing policy (worker) ";
return false; return false;
} }
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ | QSSB_FS_ALLOW_WRITE, fsPaths[i].c_str());
}
policy->drop_caps = 1;
policy->not_dumpable = 1;
policy->no_new_privs = 1;
policy->mount_path_policies_to_chroot = 1;
/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the
* sense that more could be listed here and some critical ones are probably missing */
/* TODO: use qssb groups */
long blacklisted_syscalls[] = {QSSB_SYS(setuid), QSSB_SYS(connect), QSSB_SYS(chroot), QSSB_SYS(pivot_root),
QSSB_SYS(mount), QSSB_SYS(setns), QSSB_SYS(unshare), QSSB_SYS(ptrace),
QSSB_SYS(personality), QSSB_SYS(prctl), QSSB_SYS(execveat), QSSB_SYS(execve),
QSSB_SYS(fork)};
qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, blacklisted_syscalls,
sizeof(blacklisted_syscalls) / sizeof(blacklisted_syscalls[0]));
qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW);
if(qssb_enable_policy(policy) != 0)
{
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
qssb_free_policy(policy);
return false;
}
qssb_free_policy(policy);
return true; return true;
} }

Просмотреть файл

@ -8,8 +8,6 @@ class SandboxLinux : public Sandbox
public: public:
using Sandbox::Sandbox; using Sandbox::Sandbox;
bool supported() override; bool supported() override;
bool enableForInit() override; bool enable(std::vector<std::string> fsPaths) override;
bool enablePreWorker(std::vector<std::string> fsPaths) override;
bool enableForWorker() override;
}; };
#endif #endif

Просмотреть файл

@ -6,10 +6,6 @@ class SandboxOpenBSD : public Sandbox
{ {
public: public:
bool supported() override; bool supported() override;
bool enableForInit() override; bool enable(std::vector<std::string> fsPaths) override;
bool enableForWorker() override;
private:
bool seccomp_blacklist(std::vector<int> syscalls);
}; };
#endif #endif

Просмотреть файл

@ -10,16 +10,7 @@ class Sandbox
/* Whether the platform has everything required to active all sandbnox modes */ /* Whether the platform has everything required to active all sandbnox modes */
virtual bool supported() = 0; virtual bool supported() = 0;
/* Activated early. At this point, we need more system calls /* Activated after we have acquired resources (bound to ports etc.)*/
* than later on */ virtual bool enable(std::vector<std::string> fsPaths) = 0;
virtual bool enableForInit() = 0;
/* Activated after config has been read. Now we now which paths we need access to */
virtual bool enablePreWorker(std::vector<std::string> fsPaths) = 0;
/* Activated after we have acquired resources (bound to ports etc.)
*
* This should allow us to further restrcit the process */
virtual bool enableForWorker() = 0;
}; };
#endif #endif

Просмотреть файл

@ -25,24 +25,13 @@ count integer
CREATE TABLE category(id INTEGER PRIMARY KEY, name varchar(255)); CREATE TABLE category(id INTEGER PRIMARY KEY, name varchar(255));
CREATE TABLE categorymember(id INTEGER PRIMARY KEY, category REFERENCES category(id), page REFERENCES page (id)); CREATE TABLE categorymember(id INTEGER PRIMARY KEY, category REFERENCES category(id), page REFERENCES page (id));
CREATE INDEX revisionid ON revision (revisionid DESC); CREATE INDEX revisionid ON revision (revisionid DESC);
CREATE INDEX pagename ON page (name) CREATE INDEX pagename ON page (name);
; CREATE INDEX token ON session (token);
CREATE INDEX token ON session (token) CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id);
;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
DELETE FROM search WHERE page = new.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE TRIGGER search_au AFTER UPDATE ON revision BEGIN
DELETE FROM search WHERE page = old.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id)
/* search(content,page) */;
CREATE TABLE IF NOT EXISTS 'search_data'(id INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'search_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS 'search_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
CREATE TABLE IF NOT EXISTS 'search_config'(k PRIMARY KEY, v) WITHOUT ROWID;
CREATE TRIGGER search_ad AFTER DELETE ON revision BEGIN CREATE TRIGGER search_ad AFTER DELETE ON revision BEGIN
INSERT INTO search(search, rowid, content, page) VALUES('delete', old.id, old.content, old.page); INSERT INTO search(search, rowid, content, page) VALUES('delete', old.id, old.content, old.page);
END; END;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
INSERT INTO search(search, rowid, content, page) SELECT 'delete', id, content, page FROM revision WHERE page = new.page AND revisionid = new.revisionid - 1;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;

Просмотреть файл

@ -24,12 +24,25 @@ SOFTWARE.
#include "htmllink.h" #include "htmllink.h"
#include "logger.h" #include "logger.h"
Template::Template(std::string templateprefix, std::string templatepath, ConfigUrls &configUrls, Template::Template(std::string templateprefix, std::string templatepath, ConfigUrls &configUrls,
ConfigVariableResolver &configVarsResolver) ConfigVariableResolver &configVarsResolver, MapCache<TemplatePage> &pageCache)
{ {
this->templateprefix = templateprefix; this->templateprefix = templateprefix;
this->templatepath = templatepath; this->templatepath = templatepath;
this->configUrls = &configUrls; this->configUrls = &configUrls;
this->configVarResolver = &configVarsResolver; this->configVarResolver = &configVarsResolver;
this->pageCache = &pageCache;
}
TemplatePage Template::getPage(const std::string &pagename)
{
auto result = this->pageCache->find(pagename);
if(result)
{
return *result;
}
auto page = createPage(pagename);
this->pageCache->set(pagename, page);
return page;
} }
std::string Template::getPartPath(std::string_view partname) std::string Template::getPartPath(std::string_view partname)
@ -54,7 +67,7 @@ std::string Template::resolveIncludes(std::string_view content)
return replacer.parse(content); return replacer.parse(content);
} }
TemplatePage Template::createPage(std::string name) TemplatePage Template::createPage(std::string_view name)
{ {
std::string content = loadResolvedPart(name); std::string content = loadResolvedPart(name);
Varreplacer replacer(this->templateprefix); Varreplacer replacer(this->templateprefix);
@ -65,16 +78,6 @@ TemplatePage Template::createPage(std::string name)
return TemplatePage(replacer.parse(content)); return TemplatePage(replacer.parse(content));
} }
TemplatePage &Template::getPage(const std::string &pagename)
{
if(utils::hasKey(pagesMap, pagename))
{
return pagesMap[pagename];
}
pagesMap.insert(std::make_pair(pagename, createPage(pagename)));
return pagesMap[pagename];
}
// TODO: this restricts template a bit // TODO: this restricts template a bit
std::string Template::renderSearch(const std::vector<std::string> &results, std::string Template::renderSearch(const std::vector<std::string> &results,
std::function<std::string(std::string)> callback) const std::function<std::string(std::string)> callback) const
@ -130,11 +133,10 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
std::stringstream stream; std::stringstream stream;
UrlProvider urlprovider(*this->configUrls); UrlProvider urlprovider(*this->configUrls);
auto genwithoutpage = [&] { auto genwithoutpage = [&]
{
for(const Revision &revision : revisions) for(const Revision &revision : revisions)
{ {
Logger::debug() << "processing: " << revision.revision;
stream << "<tr><td><a href=\"" << urlprovider.pageRevision(revision.page, revision.revision) << "\">" stream << "<tr><td><a href=\"" << urlprovider.pageRevision(revision.page, revision.revision) << "\">"
<< revision.revision << "</a></td>" << revision.revision << "</a></td>"
<< "<td>" << revision.author << "</td>" << "<td>" << revision.author << "</td>"
@ -143,7 +145,8 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
} }
}; };
auto genwithpage = [&] { auto genwithpage = [&]
{
for(const Revision &revision : revisions) for(const Revision &revision : revisions)
{ {

Просмотреть файл

@ -8,31 +8,29 @@
#include "response.h" #include "response.h"
#include "searchresult.h" #include "searchresult.h"
#include "revision.h" #include "revision.h"
#include "cache/mapcache.h"
class Template class Template
{ {
private: private:
ConfigVariableResolver *configVarResolver; ConfigVariableResolver *configVarResolver;
ConfigUrls *configUrls; ConfigUrls *configUrls;
MapCache<TemplatePage> *pageCache;
std::string templateprefix; std::string templateprefix;
std::string templatepath; std::string templatepath;
std::map<std::string, TemplatePage> pagesMap;
std::string resolveIncludes(std::string_view content); std::string resolveIncludes(std::string_view content);
std::string getPartPath(std::string_view partname); std::string getPartPath(std::string_view partname);
std::string loadResolvedPart(std::string_view partname); std::string loadResolvedPart(std::string_view partname);
std::string loadPartContent(std::string_view partname); std::string loadPartContent(std::string_view partname);
TemplatePage createPage(std::string name); TemplatePage createPage(std::string_view name);
public: public:
Template(std::string templateprefix, std::string templatepath, ConfigUrls &configUrls, Template(std::string templateprefix, std::string templatepath, ConfigUrls &configUrls,
ConfigVariableResolver &configVarsResolver); ConfigVariableResolver &configVarsResolver, MapCache<TemplatePage> &pageCache);
/* TODO: returning this as a reference is by no means a risk free business,
because between requests, different vars can be set conditionally, TemplatePage getPage(const std::string &pagename);
thus creating a mess
*/
TemplatePage &getPage(const std::string &pagename);
std::string renderSearch(const std::vector<std::string> &results, std::string renderSearch(const std::vector<std::string> &results,
std::function<std::string(std::string)> callback) const; std::function<std::string(std::string)> callback) const;

Просмотреть файл

@ -27,7 +27,7 @@ TemplatePage::TemplatePage()
TemplatePage::TemplatePage(std::string content) TemplatePage::TemplatePage(std::string content)
{ {
this->content = content; this->content = std::make_shared<std::string>(content);
} }
void TemplatePage::setVar(const std::string &key, std::string value) void TemplatePage::setVar(const std::string &key, std::string value)
@ -40,5 +40,5 @@ std::string TemplatePage::render() const
Varreplacer replacer("{qswiki:"); Varreplacer replacer("{qswiki:");
replacer.addResolver("var", replacer.addResolver("var",
[&](std::string_view key) { return utils::getKeyOrEmpty(this->varsMap, std::string(key)); }); [&](std::string_view key) { return utils::getKeyOrEmpty(this->varsMap, std::string(key)); });
return replacer.parse(this->content); return replacer.parse(*this->content);
} }

Просмотреть файл

@ -3,10 +3,11 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <map> #include <map>
#include <memory>
class TemplatePage class TemplatePage
{ {
private: private:
std::string content; std::shared_ptr<const std::string> content;
std::map<std::string, std::string> varsMap; std::map<std::string, std::string> varsMap;
public: public:

Просмотреть файл

@ -46,6 +46,12 @@ std::string utils::html_xss(std::string_view str)
case '%': case '%':
result += "&#37;"; result += "&#37;";
break; break;
case '\'':
result += "&#x27;";
break;
case '&':
result += "&amp;";
break;
default: default:
result += c; result += c;
} }
@ -78,7 +84,7 @@ std::string utils::urldecode(std::string_view str)
return result; return result;
} }
std::vector<std::string> utils::split(const std::string &str, char delim) std::vector<std::string> utils::split(std::string str, char delim)
{ {
std::vector<std::string> result; std::vector<std::string> result;
std::stringstream stream(str); std::stringstream stream(str);
@ -91,9 +97,9 @@ std::vector<std::string> utils::split(const std::string &str, char delim)
} }
// TODO: can easily break if we pass a regex here // TODO: can easily break if we pass a regex here
std::vector<std::string> utils::split(const std::string &str, const std::string &delim) std::vector<std::string> utils::split(std::string str, const std::string &delim)
{ {
std::regex regex { delim + "+" }; std::regex regex{delim + "+"};
return split(str, regex); return split(str, regex);
} }
@ -106,7 +112,7 @@ std::vector<std::string> utils::split(const std::string &str, std::regex &regex)
return result; return result;
} }
std::string utils::strreplace(const std::string &str, const std::string &search, const std::string &replace) std::string utils::strreplace(std::string str, const std::string &search, const std::string &replace)
{ {
std::string result = str; std::string result = str;
auto searchlength = search.length(); auto searchlength = search.length();
@ -176,13 +182,18 @@ std::string utils::toISODate(time_t t)
return std::string{result}; return std::string{result};
} }
void utils::regex_callback_extractor(std::regex regex, const std::string &input, std::function<void (std::smatch &)> callback) std::string utils::trim(std::string_view view)
{ {
auto begin = std::sregex_iterator(input.begin(), input.end(), regex); std::string_view chars = " \t\n\r";
auto end = std::sregex_iterator(); auto n = view.find_first_not_of(chars);
for(auto it = begin; it != end; it++) if(n != std::string_view::npos)
{ {
std::smatch smatch = *it; view.remove_prefix(n);
callback(smatch);
} }
n = view.find_last_not_of(chars);
if(n != std::string_view::npos)
{
view.remove_suffix(view.size() - n - 1);
}
return std::string{view};
} }

17
utils.h
Просмотреть файл

@ -11,22 +11,22 @@
namespace utils namespace utils
{ {
std::vector<std::string> split(const std::string &str, char delim); std::vector<std::string> split(std::string str, char delim);
std::vector<std::string> split(const std::string &str, const std::string &delim); std::vector<std::string> split(std::string str, const std::string &delim);
std::vector<std::string> split(const std::string &str, std::regex &regex); std::vector<std::string> split(const std::string &str, std::regex &regex);
std::string urldecode(std::string_view str); std::string urldecode(std::string_view str);
std::string strreplace(const std::string &str, const std::string &search, const std::string &replace); std::string strreplace(std::string str, const std::string &search, const std::string &replace);
std::string html_xss(std::string_view str); std::string html_xss(std::string_view str);
std::string getenv(const std::string &key); std::string getenv(const std::string &key);
template <class T, class U> bool hasKey(const std::map<T, U> &map, T key) template <class T, class U> bool hasKey(const std::map<T, U> &map, const T &key)
{ {
auto k = map.find(key); auto k = map.find(key);
return k != map.end(); return k != map.end();
} }
template <class T, class U> U getKeyOrEmpty(const std::map<T, U> &map, T key) template <class T, class U> U getKeyOrEmpty(const std::map<T, U> &map, const T &key)
{ {
auto k = map.find(key); auto k = map.find(key);
if(k != map.end()) if(k != map.end())
@ -36,7 +36,7 @@ template <class T, class U> U getKeyOrEmpty(const std::map<T, U> &map, T key)
return U(); return U();
} }
template <class T, class U> U getKeyOrEmpty(std::multimap<T, U> map, T key) template <class T, class U> U getKeyOrEmpty(const std::multimap<T, U> &map, const T &key)
{ {
auto k = map.find(key); auto k = map.find(key);
if(k != map.end()) if(k != map.end())
@ -46,7 +46,7 @@ template <class T, class U> U getKeyOrEmpty(std::multimap<T, U> map, T key)
return U(); return U();
} }
template <class T, class U> std::vector<U> getAll(std::multimap<T, U> map, T key) template <class T, class U> std::vector<U> getAll(const std::multimap<T, U> &map, const T &key)
{ {
std::vector<U> result; std::vector<U> result;
auto range = map.equal_range(key); auto range = map.equal_range(key);
@ -60,7 +60,6 @@ template <class T, class U> std::vector<U> getAll(std::multimap<T, U> map, T key
std::string regex_callback_replacer(std::regex regex, const std::string &input, std::string regex_callback_replacer(std::regex regex, const std::string &input,
std::function<std::string(std::smatch &)> callback); std::function<std::string(std::smatch &)> callback);
void regex_callback_extractor(std::regex regex, const std::string &input, std::function<void(std::smatch &)> callback);
std::string readCompleteFile(std::string_view filepath); std::string readCompleteFile(std::string_view filepath);
inline std::string nz(const char *s) inline std::string nz(const char *s)
@ -94,5 +93,7 @@ template <class T> inline std::string toString(const T &v)
return std::string(v.begin(), v.end()); return std::string(v.begin(), v.end());
} }
std::string trim(std::string_view view);
} // namespace utils } // namespace utils
#endif #endif

11
version.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,11 @@
#include "version.h"
std::string git_commit_id()
{
return std::string(GITCOMMIT);
}
std::string get_version_string()
{
return git_commit_id() + " Built: " + __DATE__ + " " + __TIME__;
}

7
version.h Обычный файл
Просмотреть файл

@ -0,0 +1,7 @@
#ifndef VERSION_H
#define VERSION_H
#include <string>
std::string git_commit_id();
std::string get_version_string();
#endif // VERSION_H