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

44 Коммитов

Автор SHA1 Сообщение Дата
f5eb36e7bb DynamicContentPostList: Ignore invisible entries 2022-03-27 20:03:28 +02:00
c891b36339 Makefile: Build dynamic content generators, adjust for exile update 2022-03-27 20:00:21 +02:00
d17e596563 sandbox-linux: include exile.hpp 2022-03-27 19:59:52 +02:00
761471f243 template: Add template for atom feed 2022-03-27 19:54:07 +02:00
9ac0ad0ccd template: Add template for dynamic postlist 2022-03-27 19:53:48 +02:00
c30e09d44d HandlerFactory: Wire up HandlerFeedGenerator 2022-03-27 19:52:45 +02:00
bcc3737d88 UrlProvider: Introduce combine(), rootUrl(), atomFeed() 2022-03-27 19:51:53 +02:00
9520aabe5c Config: Require rooturl,atomurl 2022-03-27 19:50:51 +02:00
4854ea85f2 Begin HandlerFeedGenerator: Generates Atom feeds for categories (or all pages) 2022-03-27 19:48:57 +02:00
16c352c6af utils: readCompleteFile(): Throw exception if file can't be opened 2022-03-27 19:47:52 +02:00
f7cf06cdd5 Page: Add 'title' column, storing title of last revision 2022-03-27 09:23:35 +02:00
ac793c6d39 handlers: HandlerPageView: Add '[dynamic:postlist]' tag by callback 2022-03-27 08:37:55 +02:00
a524674149 Begin dynamic content generators 2022-03-27 08:36:25 +02:00
a4a45d9add Parser: Add callback support for unknown "tags" 2022-03-27 08:31:59 +02:00
44c27ed8b4 Template: Make loadResolvedPart() public 2022-03-27 08:30:51 +02:00
433b5da2bb template: Adjust after renaming: Use utils::toISODateTime() 2022-03-27 08:30:20 +02:00
c5435c52f4 utils: Rename/Add date functions 2022-03-27 08:29:13 +02:00
b2a7ea4031 Parser: Take 'content' by const reference. 2022-01-23 10:12:37 +01:00
1d5bf80710 HandlerPageView: Add [cmd:pagetitle] to set custom per-page titles 2022-01-23 10:02:46 +01:00
ca0c8a94fb sandbox: Use exile.h vow promises 2021-12-29 11:13:47 +01:00
5870102aa9 submodules: cpp-httplib: Update to v0.10.1 2021-12-29 11:13:05 +01:00
32544c8f68 submodules: cpp-httplib: Update module 2021-12-02 10:15:36 +01:00
d0e7ff0a8c sandbox: Switch to exile.h (former qssb.h) 2021-12-02 10:15:11 +01:00
696ff9b7e7 sandbox: Allow TIME group 2021-12-02 10:06:21 +01:00
5570154113 fscache: Fix starts_with() broken by b41a5f4e5b 2021-11-30 19:14:59 +01:00
4f6bcd27b4 sandbox: Sync iwth qssb.h upstream: Use whitelisting and groups 2021-11-14 21:54:08 +01:00
bbe74a2c50 handlers: HandlerSearch: Add missing call to setGeneralVars() 2021-11-14 21:54:08 +01:00
5db9305408 template: display headers inline (backport from production) 2021-11-14 21:54:08 +01:00
c90e26a374 template: Remove space between header links 2021-10-26 23:22:05 +02:00
b297498ca9 template: Don't render searchbar in portrait. Show link instead
Issue: #18
2021-10-26 23:07:37 +02:00
fdcef18861 HandlerSearch: Render a form when no q= given 2021-10-26 23:07:37 +02:00
75268e0073 sandbox: Disable Landlock due to qssb.h issue #19 2021-10-26 23:07:37 +02:00
cc4506b918 submodules: sync with latest upstream 2021-10-26 10:47:02 +02:00
017932e673 Fix -wreturn-type warnings 2021-10-25 18:13:25 +02:00
ed61003636 C++20: Avoid implicit capture 2021-10-25 18:09:46 +02:00
ad42c0f046 handlers: HandlerPageEdit: canAccess(): Check with effectivePermisisons() 2021-10-25 18:07:23 +02:00
6304554358 Add [[maybe_unused]] to silence unhelpful warnings 2021-10-25 17:56:37 +02:00
0aa4bca6cc Fix gcc-11 etc. compilation errors 2021-10-25 13:31:40 +02:00
b41a5f4e5b fscache: removePrefix(): use starts_with() 2021-10-19 15:07:10 +02:00
873401694e Remove utils::hasKey(), as we now have .contains() 2021-10-19 12:48:45 +02:00
d035579da7 Makefile: Switch to C++20 2021-10-19 12:47:57 +02:00
eb292a7d79 CategoryDaoSqlite: deleteCategory(): Fix broken query 2021-10-12 20:10:35 +02:00
420e541e75 CLI: Implement category delete,show,list 2021-10-12 20:02:43 +02:00
c18178a50f SqliteQueryOption: build(): Handle includeInvisible = true properly 2021-10-12 20:02:03 +02:00
62 изменённых файлов: 620 добавлений и 201 удалений

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

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

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

@ -1,9 +1,9 @@
CPPSTD=c++20
CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra
RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/exile.h
CXX=g++ CXX=g++
@ -14,6 +14,7 @@ SOURCES+=$(wildcard handlers/*.cpp)
SOURCES+=$(wildcard database/*.cpp) SOURCES+=$(wildcard database/*.cpp)
SOURCES+=$(wildcard cache/*.cpp) SOURCES+=$(wildcard cache/*.cpp)
SOURCES+=$(wildcard sandbox/*.cpp) SOURCES+=$(wildcard sandbox/*.cpp)
SOURCES+=$(wildcard dynamic/*.cpp)
HEADERS=$(wildcard *.h) HEADERS=$(wildcard *.h)
HEADERS+=$(wildcard gateway/*.h) HEADERS+=$(wildcard gateway/*.h)
@ -21,7 +22,7 @@ HEADERS+=$(wildcard handlers/*.h)
HEADERS+=$(wildcard database/*.h) HEADERS+=$(wildcard database/*.h)
HEADERS+=$(wildcard cache/*.h) HEADERS+=$(wildcard cache/*.h)
HEADERS+=$(wildcard sandbox/*.h) HEADERS+=$(wildcard sandbox/*.h)
HEADERS+=$(wildcard dynamic/*.h)
OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES)) OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES))
WIKIOBJECTS=$(filter-out test.o, $(OBJECTS)) WIKIOBJECTS=$(filter-out test.o, $(OBJECTS))
@ -35,7 +36,7 @@ GTEST_DIR = /home/data/SOURCES/gtest/googletest
GTESTS_TESTDIR = ./tests/ GTESTS_TESTDIR = ./tests/
GTEST_CXXFLAGS=-std=c++17 -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra GTEST_CXXFLAGS=-std=$(CPPSTD) -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra
GTEST_LDFLAGS=-lsqlite3 -g -O0 -lpthread -lcrypto -lstdc++fs GTEST_LDFLAGS=-lsqlite3 -g -O0 -lpthread -lcrypto -lstdc++fs
GTEST_OBJECTS=$(filter-out qswiki.o, $(WIKIOBJECTS)) GTEST_OBJECTS=$(filter-out qswiki.o, $(WIKIOBJECTS))
@ -48,8 +49,12 @@ profile: LDFLAGS+= -pg
release: qswiki release: qswiki
profile: qswiki profile: qswiki
qswiki: $(WIKIOBJECTS)
$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o 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) $(WIKIOBJECTS) exile.o ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
test: $(TESTOBJECTS) test: $(TESTOBJECTS)
$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test $(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test
@ -63,6 +68,6 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS)
version.o:version.cpp version.o:version.cpp
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $< $(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
clean: clean:
rm -f $(OBJECTS) $(DEPENDS) rm -f exile.o $(OBJECTS) $(DEPENDS)

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

@ -72,8 +72,7 @@ Building
Dependencies: Dependencies:
- cpp-httplib: https://github.com/yhirose/cpp-httplib - cpp-httplib: https://github.com/yhirose/cpp-httplib
- SqliteModernCpp: https://github.com/SqliteModernCpp - SqliteModernCpp: https://github.com/SqliteModernCpp
- qssb.h: https://gitea.quitesimple.org/crtxcr/qssb.h - exile.h: https://gitea.quitesimple.org/crtxcr/exile.h
- libseccomp: https://github.com/seccomp/libseccomp
- sqlite3: https://sqlite.org/index.html - sqlite3: https://sqlite.org/index.html
The first three are header-only libraries that are included as a git submodule. The others must The first three are header-only libraries that are included as a git submodule. The others must

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

@ -21,7 +21,7 @@ Authenticator::Authenticator(UserDao &userDao)
// TODO: make failure counter configurable // TODO: make failure counter configurable
bool Authenticator::isBanned(std::string ip) bool Authenticator::isBanned(std::string ip)
{ {
if(utils::hasKey(loginFails, ip)) if(loginFails.contains(ip))
{ {
LoginFail &fl = loginFails[ip]; LoginFail &fl = loginFails[ip];
std::lock_guard<std::mutex> lock(fl.mutex); std::lock_guard<std::mutex> lock(fl.mutex);

2
cache/fscache.cpp поставляемый
Просмотреть файл

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

1
cache/mapcache.h поставляемый
Просмотреть файл

@ -3,6 +3,7 @@
#include <map> #include <map>
#include <set> #include <set>
#include <shared_mutex> #include <shared_mutex>
#include <optional>
/* Thread-Safe Key-Value store */ /* Thread-Safe Key-Value store */
template <class T> class MapCache template <class T> class MapCache

50
cli.cpp
Просмотреть файл

@ -54,7 +54,7 @@ std::pair<bool, std::string> CLIHandler::user_add(const std::vector<std::string>
return {true, ""}; return {true, ""};
} }
std::pair<bool, std::string> CLIHandler::user_change_pw(const std::vector<std::string> &args) 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 username = args.at(0);
std::string password = args.at(1); std::string password = args.at(1);
@ -75,19 +75,16 @@ std::pair<bool, std::string> CLIHandler::user_change_pw(const std::vector<std::s
userDao->save(*user); userDao->save(*user);
} }
else return {false, "User not found"};
{
return {false, "User not found"};
}
} }
std::pair<bool, std::string> CLIHandler::user_rename(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::user_rename([[maybe_unused]] const std::vector<std::string> &args)
{ {
return {true, ""}; return {true, ""};
} }
std::pair<bool, std::string> CLIHandler::user_set_perms(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::user_set_perms([[maybe_unused]] const std::vector<std::string> &args)
{ {
auto userDao = this->db->createUserDao(); auto userDao = this->db->createUserDao();
std::string username = args.at(0); std::string username = args.at(0);
@ -107,7 +104,7 @@ std::pair<bool, std::string> CLIHandler::user_set_perms(const std::vector<std::s
return {false, "User not found"}; return {false, "User not found"};
} }
std::pair<bool, std::string> CLIHandler::user_list(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::user_list([[maybe_unused]] const std::vector<std::string> &args)
{ {
auto userDao = this->db->createUserDao(); auto userDao = this->db->createUserDao();
QueryOption o; QueryOption o;
@ -138,7 +135,7 @@ std::pair<bool, std::string> CLIHandler::user_show(const std::vector<std::string
return {false, "User not found"}; return {false, "User not found"};
} }
std::pair<bool, std::string> CLIHandler::page_list(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::page_list([[maybe_unused]] const std::vector<std::string> &args)
{ {
auto pageDao = this->db->createPageDao(); auto pageDao = this->db->createPageDao();
QueryOption o; QueryOption o;
@ -163,7 +160,7 @@ std::pair<bool, std::string> CLIHandler::pageperms_set_permissions(const std::ve
return {true, ""}; return {true, ""};
} }
std::pair<bool, std::string> CLIHandler::attach(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::attach([[maybe_unused]] const std::vector<std::string> &args)
{ {
/* TODO: consider authentication */ /* TODO: consider authentication */
pid_t pid = getpid(); pid_t pid = getpid();
@ -245,7 +242,38 @@ std::pair<std::string, std::vector<std::string>> CLIHandler::splitCommand(std::s
return {cmd, splitted}; return {cmd, splitted};
} }
std::pair<bool, std::string> CLIHandler::version(const std::vector<std::string> &args) std::pair<bool, std::string> CLIHandler::version([[maybe_unused]] const std::vector<std::string> &args)
{ {
return {true, get_version_string()}; 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(std::string &member : members)
{
stream << member << std::endl;
}
return {true, stream.str()};
}

34
cli.h
Просмотреть файл

@ -22,17 +22,20 @@ class CLIHandler
Config *conf; Config *conf;
protected: protected:
std::pair<bool, std::string> attach(const std::vector<std::string> &args); std::pair<bool, std::string> attach([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> cli_help(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(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(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(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(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(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(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(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(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(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{ std::vector<struct cmd> cmds{
{{"user", {{"user",
@ -50,6 +53,13 @@ class CLIHandler
1, 1,
{{{"list", "- lists existing pages", 0, {}, &CLIHandler::page_list}}}, {{{"list", "- lists existing pages", 0, {}, &CLIHandler::page_list}}},
&CLIHandler::cli_help}, &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", {"pageperms",
"set permissions on pages", "set permissions on pages",
1, 1,
@ -63,7 +73,7 @@ class CLIHandler
"exit cli", "exit cli",
0, 0,
{}, {},
[](CLIHandler *, const std::vector<std::string> &args) -> std::pair<bool, std::string> [](CLIHandler *, [[maybe_unused]] const std::vector<std::string> &args) -> std::pair<bool, std::string>
{ {
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
return {true, ""}; return {true, ""};

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

@ -36,7 +36,7 @@ bool CLIServer::detachServer(std::string socketpath)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
auto worker = [=] auto worker = [this, s]
{ {
while(true) while(true)
{ {

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

@ -24,6 +24,7 @@ SOFTWARE.
#include "config.h" #include "config.h"
#include "permissions.h" #include "permissions.h"
#include "varreplacer.h" #include "varreplacer.h"
std::string Config::required(const std::string &key) std::string Config::required(const std::string &key)
{ {
auto it = this->configmap.find(key); auto it = this->configmap.find(key);
@ -96,6 +97,8 @@ Config::Config(const std::map<std::string, std::string> &map)
this->urls.deletionurl = required("deletionurl"); this->urls.deletionurl = required("deletionurl");
this->urls.adminregisterurl = required("adminregisterurl"); this->urls.adminregisterurl = required("adminregisterurl");
this->urls.usersettingsurl = required("usersettingsurl"); this->urls.usersettingsurl = required("usersettingsurl");
this->urls.rooturl = required("rooturl");
this->urls.atomurl = required("atomurl");
this->connectionstring = required("connectionstring"); this->connectionstring = required("connectionstring");
this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256); this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256);

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

@ -41,6 +41,8 @@ struct ConfigUrls
std::string linkhistorysort; std::string linkhistorysort;
std::string adminregisterurl; std::string adminregisterurl;
std::string usersettingsurl; std::string usersettingsurl;
std::string rooturl;
std::string atomurl;
}; };
class ConfigVariableResolver class ConfigVariableResolver

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

@ -42,6 +42,7 @@ std::optional<Category> CategoryDaoSqlite::find(std::string name)
{ {
throwFrom(e); throwFrom(e);
} }
return {};
} }
void CategoryDaoSqlite::save(const Category &c) void CategoryDaoSqlite::save(const Category &c)
@ -64,9 +65,9 @@ void CategoryDaoSqlite::deleteCategory(std::string name)
try try
{ {
*db << "BEGIN"; *db << "BEGIN;";
*db << "DELETE FROM categorymember WHERE catid = (SELECT id FROM category WHERE name = ?)" << name; *db << "DELETE FROM categorymember WHERE category = (SELECT id FROM category WHERE name = ?);" << name;
*db << "DELETE FROM category WHERE name = ?" << name; *db << "DELETE FROM category WHERE name = ?;" << name;
*db << "COMMIT;"; *db << "COMMIT;";
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)

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

@ -58,9 +58,9 @@ std::optional<Page> PageDaoSqlite::find(unsigned int id)
result.pageid = id; result.pageid = id;
try try
{ {
auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?"; auto ps = *db << "SELECT name, title, lastrevision, visible FROM page WHERE id = ?";
ps << id >> std::tie(result.name, result.current_revision, result.listed); ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@ -97,9 +97,10 @@ void PageDaoSqlite::save(const Page &page)
{ {
try try
{ {
*db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE name = " *db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, visible) VALUES((SELECT id FROM page WHERE "
"? OR id = ?), ?, ?, ?)" "name = "
<< page.name << page.pageid << page.name << page.current_revision << page.listed; "? OR id = ?), ?, ?, ?, ?)"
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed;
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
@ -183,7 +184,8 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
auto query = auto query =
*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? " *db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? "
<< ftsEscape(name); << ftsEscape(name);
query >> [&](std::string pagename) { query >> [&](std::string pagename)
{
SearchResult sresult; SearchResult sresult;
sresult.pagename = pagename; sresult.pagename = pagename;
sresult.query = name; sresult.query = name;

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

@ -1,5 +1,6 @@
#ifndef PERMISSIONSDAO_H #ifndef PERMISSIONSDAO_H
#define PERMISSIONSDAO_H #define PERMISSIONSDAO_H
#include <optional>
#include "../permissions.h" #include "../permissions.h"
#include "../user.h" #include "../user.h"
class PermissionsDao class PermissionsDao

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

@ -22,18 +22,17 @@ SOFTWARE.
bool SqliteDao::execBool(sqlite::database_binder &binder) const bool SqliteDao::execBool(sqlite::database_binder &binder) const
{ {
bool result; bool result = false;
try try
{ {
bool result;
binder >> result; binder >> result;
return result;
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
// TODO: well, we may want to check whether rows have found or not and thus log this here // TODO: well, we may want to check whether rows have found or not and thus log this here
return false; result = false;
} }
return result;
} }
int SqliteDao::execInt(sqlite::database_binder &binder) const int SqliteDao::execInt(sqlite::database_binder &binder) const
@ -52,4 +51,5 @@ int SqliteDao::execInt(sqlite::database_binder &binder) const
{ {
throwFrom(e); throwFrom(e);
} }
return 0;
} }

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

@ -46,15 +46,18 @@ SqliteQueryOption &SqliteQueryOption::setPrependWhere(bool b)
std::string SqliteQueryOption::build() std::string SqliteQueryOption::build()
{ {
std::string result; std::string result;
if(this->prependWhere)
{
result += "WHERE ";
}
if(!o.includeInvisible && !this->visibleColumnName.empty()) if(!o.includeInvisible && !this->visibleColumnName.empty())
{ {
if(this->prependWhere)
{
result += "WHERE ";
}
result += this->visibleColumnName + " = 1"; result += this->visibleColumnName + " = 1";
} }
else
{
result += " 1 = 1";
}
result += " ORDER BY " + orderByColumnName; result += " ORDER BY " + orderByColumnName;
if(o.order == ASCENDING) if(o.order == ASCENDING)
{ {
@ -66,7 +69,8 @@ std::string SqliteQueryOption::build()
} }
// TODO: limits for offset? // TODO: limits for offset?
if(o.limit > 0) if(o.limit > 0)
{
result += " LIMIT " + std::to_string(o.limit) + " OFFSET " + std::to_string(o.offset); result += " LIMIT " + std::to_string(o.limit) + " OFFSET " + std::to_string(o.offset);
}
return result; return result;
} }

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

@ -37,7 +37,6 @@ bool UserDaoSqlite::exists(std::string username)
std::optional<User> UserDaoSqlite::find(std::string username) std::optional<User> UserDaoSqlite::find(std::string username)
{ {
try try
{ {
User user; User user;
@ -58,6 +57,7 @@ std::optional<User> UserDaoSqlite::find(std::string username)
{ {
throwFrom(e); throwFrom(e);
} }
return {};
} }
std::optional<User> UserDaoSqlite::find(int id) std::optional<User> UserDaoSqlite::find(int id)
@ -81,6 +81,7 @@ std::optional<User> UserDaoSqlite::find(int id)
{ {
throwFrom(e); throwFrom(e);
} }
return {};
} }
std::vector<User> UserDaoSqlite::list(QueryOption o) std::vector<User> UserDaoSqlite::list(QueryOption o)
@ -116,7 +117,7 @@ std::vector<User> UserDaoSqlite::list(QueryOption o)
return result; return result;
} }
void UserDaoSqlite::deleteUser(std::string username) 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?
} }

8
dynamic/dynamiccontent.cpp Обычный файл
Просмотреть файл

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

22
dynamic/dynamiccontent.h Обычный файл
Просмотреть файл

@ -0,0 +1,22 @@
#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;
public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider);
virtual std::string render() = 0;
virtual ~DynamicContent()
{
}
};
#endif // DYNAMICCONTENT_H

44
dynamic/dynamiccontentpostlist.cpp Обычный файл
Просмотреть файл

@ -0,0 +1,44 @@
#include <chrono>
#include "dynamiccontentpostlist.h"
void DynamicContentPostList::setCategory(std::string catname)
{
this->catname = catname;
}
std::string DynamicContentPostList::render()
{
auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao();
QueryOption option;
option.includeInvisible = false;
auto members = categoryDao->fetchMembers(this->catname, option);
std::vector<std::pair<std::string, time_t>> pageList;
for(std::string &member : members)
{
auto revision = revisionDao->getRevisionForPage(member, 1);
pageList.push_back({member, 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();
}

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

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

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

@ -1,5 +1,6 @@
#ifndef HANDLER_H #ifndef HANDLER_H
#define HANDLER_H #define HANDLER_H
#include <memory>
#include "../config.h" #include "../config.h"
#include "../response.h" #include "../response.h"
#include "../request.h" #include "../request.h"
@ -9,6 +10,8 @@
#include "../database/queryoption.h" #include "../database/queryoption.h"
#include "../logger.h" #include "../logger.h"
#include "../cache/icache.h" #include "../cache/icache.h"
#include "../dynamic/dynamiccontent.h"
class Handler class Handler
{ {
protected: protected:
@ -36,12 +39,12 @@ class Handler
} }
virtual Response handle(const Request &r); virtual Response handle(const Request &r);
virtual Response handleRequest(const Request &r) virtual Response handleRequest([[maybe_unused]] const Request &r)
{ {
return this->errorResponse("Invalid action", "This action is not implemented yet"); return this->errorResponse("Invalid action", "This action is not implemented yet");
} }
virtual bool canAccess(const Permissions &perms) virtual bool canAccess([[maybe_unused]] const Permissions &perms)
{ {
return false; return false;
} }
@ -53,6 +56,12 @@ class Handler
virtual ~Handler() virtual ~Handler()
{ {
} }
template <class T> inline std::shared_ptr<T> createDynamic()
{
return std::make_shared<T>(*this->templ, *this->database, *this->urlProvider);
}
Response errorResponse(std::string errortitle, std::string errormessage, int status = 200); Response errorResponse(std::string errortitle, std::string errormessage, int status = 200);
std::string createPageTitle(std::string append); std::string createPageTitle(std::string append);
}; };

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

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

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

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

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

@ -0,0 +1,129 @@
#include "handlerfeedgenerator.h"
#include "../parser.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories)
{
auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao();
std::vector<EntryRevisionPair> result;
QueryOption option;
option.includeInvisible = false;
// option.limit = 20;
std::set<std::string> members;
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)
{
auto catmembers = categoryDao->fetchMembers(cat, option);
std::copy(catmembers.begin(), catmembers.end(), std::inserter(members, members.end()));
}
}
for(const std::string &member : members)
{
auto page = pageDao->find(member).value();
auto revision = revisionDao->getRevisionForPage(page.name, 1).value();
result.push_back({page, 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;
}
Response 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();
if(utils::trim(filter).empty())
{
filter = "All pages";
}
for(const EntryRevisionPair &entry : entries)
{
const Page &page = entry.first;
const Revision &initialRevision = entry.second;
Revision current = revisionDao->getCurrentForPage(page.name).value();
std::string url = this->urlProvider->rootUrl() + this->urlProvider->page(page.name);
if(initialRevision.timestamp > newestPublished)
{
newestPublished = initialRevision.timestamp;
}
std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z";
std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
atomentry.setVar("entrytitle", utils::html_xss(page.title));
atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished);
Parser parser;
atomentry.setVar("entrycontent", utils::html_xss(parser.parse(*pageDao, *this->urlProvider, current.content)));
stream << atomentry.render();
}
stream << atomfooter;
TemplatePage atomheader = this->templ->getPage("feeds/atomheader");
atomheader.setVar("subtitle", filter);
atomheader.setVar("atomfeeduniqueid", utils::html_xss(this->urlProvider->atomFeed(filter)));
atomheader.setVar("atomfeedupdate", utils::formatLocalDate(newestPublished, dateformat) + "Z");
Response result;
result.setStatus(200);
result.setContentType("application/atom+xml");
result.setBody(atomheader.render() + stream.str());
return result;
}
Response HandlerFeedGenerator::handleRequest(const Request &r)
{
Response response;
try
{
std::string type = r.get("type");
std::string categories = r.get("cats");
auto entries = fetchEntries(utils::split(categories, ','));
if(type == "atom")
{
std::string filter = categories;
response = generateAtom(entries, filter);
}
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;
}

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

@ -0,0 +1,21 @@
#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);
Response generateAtom(const std::vector<EntryRevisionPair> &entries, std::string atomtitle);
Response generateRss(const std::vector<EntryRevisionPair> &entries);
public:
using Handler::Handler;
Response handleRequest(const Request &r) override;
bool canAccess(const Permissions &perms) override;
};
#endif // HANDLERFEEDGENERATOR_H

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

@ -50,7 +50,8 @@ Response HandlerHistory::handleRequest(const Request &r)
std::vector<Revision> resultList; std::vector<Revision> resultList;
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order) { auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order)
{
if(!page.empty()) if(!page.empty())
{ {
return this->urlProvider->pageHistorySort(page, limit, offset, order); return this->urlProvider->pageHistorySort(page, limit, offset, order);
@ -122,7 +123,7 @@ Response HandlerHistory::handleRequest(const Request &r)
return response; return response;
} }
bool HandlerHistory::canAccess(const Permissions &perms) bool HandlerHistory::canAccess([[maybe_unused]] 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
} }

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

@ -20,12 +20,12 @@ SOFTWARE.
*/ */
#include "handlerinvalidaction.h" #include "handlerinvalidaction.h"
Response HandlerInvalidAction::handleRequest(const Request &r) Response HandlerInvalidAction::handleRequest([[maybe_unused]] const Request &r)
{ {
return errorResponse("Invalid action", "No action defined for this action"); return errorResponse("Invalid action", "No action defined for this action");
} }
bool HandlerInvalidAction::canAccess(const Permissions &perms) bool HandlerInvalidAction::canAccess([[maybe_unused]] const Permissions &perms)
{ {
return true; return true;
} }

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

@ -66,7 +66,7 @@ Response HandlerLogin::handleRequest(const Request &r)
return result; return result;
} }
bool HandlerLogin::canAccess(const Permissions &perms) bool HandlerLogin::canAccess([[maybe_unused]] const Permissions &perms)
{ {
return true; return true;
} }

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

@ -23,9 +23,9 @@ SOFTWARE.
#include "../request.h" #include "../request.h"
#include "../parser.h" #include "../parser.h"
bool HandlerPageEdit::canAccess(std::string page) bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{ {
return this->userSession->user.permissions.canEdit(); return effectivePermissions(page).canEdit();
} }
bool HandlerPageEdit::pageMustExist() bool HandlerPageEdit::pageMustExist()
@ -66,6 +66,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
this->database->beginTransaction(); this->database->beginTransaction();
std::string visiblecmd = parser.extractCommand("visible", newContent); std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string rename = parser.extractCommand("rename", newContent); std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent);
Page page; Page page;
std::optional<Page> currentPage = pageDao.find(pagename); std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage) if(currentPage)
@ -83,6 +84,11 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
page.current_revision = current_revision; page.current_revision = current_revision;
page.listed = !(visiblecmd == "0"); page.listed = !(visiblecmd == "0");
page.name = pagename; page.name = pagename;
page.title = customtitle;
if(page.title.empty())
{
page.title = page.name;
}
pageDao.save(page); pageDao.save(page);
Revision newRevision; Revision newRevision;

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

@ -23,7 +23,7 @@ SOFTWARE.
#include "../logger.h" #include "../logger.h"
#include "../parser.h" #include "../parser.h"
#include "../htmllink.h" #include "../htmllink.h"
#include "../dynamic/dynamiccontentpostlist.h"
bool HandlerPageView::canAccess(std::string page) bool HandlerPageView::canAccess(std::string page)
{ {
return effectivePermissions(page).canRead(); return effectivePermissions(page).canRead();
@ -136,10 +136,21 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
std::string indexcontent; std::string indexcontent;
std::string parsedcontent; std::string parsedcontent;
std::function<std::string(std::string_view, std::string_view)> dynamicParseCallback =
[&](std::string_view key, std::string_view value) -> std::string
{
if(key == "dynamic:postlist")
{
std::shared_ptr<DynamicContentPostList> postlist = createDynamic<DynamicContentPostList>();
postlist->setCategory(std::string(value));
return postlist->render();
}
return std::string{};
};
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, dynamicParseCallback);
} }
else else
{ {
@ -162,18 +173,23 @@ 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, dynamicParseCallback);
this->cache->put(cachekeyparsedcontent, parsedcontent); this->cache->put(cachekeyparsedcontent, parsedcontent);
} }
} }
std::string revisionstr = std::to_string(revision->revision); std::string revisionstr = std::to_string(revision->revision);
std::string customtitle = parser.extractCommand("pagetitle", revision->content);
page.setVar("content", parsedcontent); page.setVar("content", parsedcontent);
page.setVar("index", indexcontent); page.setVar("index", indexcontent);
page.setVar("editedby", revision->author); page.setVar("editedby", revision->author);
page.setVar("editedon", utils::toISODate(revision->timestamp)); page.setVar("editedon", utils::toISODateTime(revision->timestamp));
page.setVar("historyurl", this->urlProvider->pageHistory(pagename)); page.setVar("historyurl", this->urlProvider->pageHistory(pagename));
page.setVar("revision", revisionstr); page.setVar("revision", revisionstr);
setPageVars(page, pagename); setPageVars(page, pagename);
if(!customtitle.empty())
{
page.setVar("title", createPageTitle(customtitle));
}
std::string body = page.render(); std::string body = page.render();
if(revisionid == 0 && !this->userSession->loggedIn) if(revisionid == 0 && !this->userSession->loggedIn)
{ {

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

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

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

@ -62,7 +62,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
return result; return result;
} }
bool HandlerUserSettings::canAccess(const Permissions &perms) bool HandlerUserSettings::canAccess([[maybe_unused]] const Permissions &perms)
{ {
return this->userSession->loggedIn; return this->userSession->loggedIn;
} }

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

@ -1,6 +1,6 @@
#include "handlerversion.h" #include "handlerversion.h"
#include "../version.h" #include "../version.h"
Response HandlerVersion::handleRequest(const Request &r) Response HandlerVersion::handleRequest([[maybe_unused]] const Request &r)
{ {
Response response; Response response;
response.setContentType("text/plain"); response.setContentType("text/plain");

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

@ -9,7 +9,7 @@ class HandlerVersion : public Handler
public: public:
Response handleRequest(const Request &r) override; Response handleRequest(const Request &r) override;
bool canAccess(const Permissions &perms) override bool canAccess([[maybe_unused]] const Permissions &perms) override
{ {
return true; return true;
} }

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

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

1
page.h
Просмотреть файл

@ -7,6 +7,7 @@ class Page
public: public:
Page(); Page();
std::string name; std::string name;
std::string title;
bool listed; bool listed;
unsigned int current_revision; unsigned int current_revision;
unsigned int pageid; unsigned int pageid;

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

@ -27,7 +27,7 @@ SOFTWARE.
#include "parser.h" #include "parser.h"
#include "utils.h" #include "utils.h"
#include "htmllink.h" #include "htmllink.h"
std::vector<Headline> Parser::extractHeadlines(std::string content) const std::vector<Headline> Parser::extractHeadlines(const std::string &content) const
{ {
std::vector<Headline> result; std::vector<Headline> result;
std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])"; std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])";
@ -46,7 +46,7 @@ std::vector<Headline> Parser::extractHeadlines(std::string content) const
return result; return result;
} }
std::vector<std::string> Parser::extractCategories(std::string content) const std::vector<std::string> Parser::extractCategories(const 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\])";
@ -62,7 +62,7 @@ std::vector<std::string> Parser::extractCategories(std::string content) const
return result; return result;
} }
std::string Parser::extractCommand(std::string cmdname, std::string content) const std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
{ {
std::string cmd = "[cmd:" + cmdname + "]"; std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]"; std::string cmdend = "[/cmd:" + cmdname + "]";
@ -116,31 +116,37 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider
return htmllink.render(); return htmllink.render();
} }
std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const
{ {
std::string result; std::string result;
// we don't care about commands, but we nevertheless replace them with empty strings // we don't care about commands, but we nevertheless replace them with empty strings
std::regex tagfinder(R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|category)*?\]((\s|\S)*?)\[/\1])"); std::regex tagfinder(
result = utils::regex_callback_replacer(tagfinder, content, [&](std::smatch &match) { R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|cmd:pagetitle|category|dynamic:postlist)*?\]((\s|\S)*?)\[/\1])");
std::string tag = match.str(1); result = utils::regex_callback_replacer(
std::string content = match.str(2); tagfinder, content,
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"}; [&](std::smatch &match)
content = parse(pagedao, provider, content);
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
{ {
return "<" + tag + ">" + content + "</" + tag + ">"; std::string tag = match.str(1);
} std::string content = match.str(2);
if(tag == "link" || tag == "wikilink") std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
{ content = parse(pagedao, provider, content, callback);
return this->processLink(pagedao, provider, if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
match); // TODO: recreate this so we don't check inside the function stuff again {
} return "<" + tag + ">" + content + "</" + tag + ">";
if(tag[0] == 'h') }
{ if(tag == "link" || tag == "wikilink")
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">"; {
} return this->processLink(
return std::string(""); pagedao, provider,
}); match); // TODO: recreate this so we don't check inside the function stuff again
}
if(tag[0] == 'h')
{
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
}
return callback(tag, content);
});
result = utils::strreplace(result, "\r\n", "<br>"); result = utils::strreplace(result, "\r\n", "<br>");
return result; return result;
} }

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

@ -8,12 +8,15 @@ class Parser : public IParser
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
public: public:
std::string extractCommand(std::string cmdname, std::string content) const; std::string extractCommand(std::string cmdname, const std::string &content) const;
std::vector<Headline> extractHeadlines(std::string content) const override; std::vector<Headline> extractHeadlines(const std::string &content) const override;
std::vector<std::string> extractCategories(std::string content) const override; std::vector<std::string> extractCategories(const std::string &content) const override;
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override; using IParser::parse;
virtual std::string parse(
const PageDao &pagedao, UrlProvider &provider, const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const override;
using IParser::IParser; using IParser::IParser;
~Parser(){};
}; };
#endif // PARSER_H #endif // PARSER_H

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

@ -43,7 +43,7 @@ SOFTWARE.
#include "cliserver.h" #include "cliserver.h"
#include "version.h" #include "version.h"
void sigterm_handler(int arg) void sigterm_handler([[maybe_unused]] int arg)
{ {
// TODO: proper shutdown. // TODO: proper shutdown.
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);

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

@ -12,17 +12,13 @@
#include <filesystem> #include <filesystem>
#include <sys/mount.h> #include <sys/mount.h>
#include <sys/capability.h> #include <sys/capability.h>
#include <qssb.h> #include <exile.hpp>
#include "../logger.h" #include "../logger.h"
#include "../utils.h" #include "../utils.h"
#include "../random.h" #include "../random.h"
#include "sandbox-linux.h" #include "sandbox-linux.h"
/* TODO: make a whitelist approach. So far we simply blacklist
* obvious systemcalls. To whitelist, we need to analyse our
* dependencies (http library, sqlite wrapper, sqlite lib etc.) */
bool SandboxLinux::supported() bool SandboxLinux::supported()
{ {
std::fstream stream; std::fstream stream;
@ -45,7 +41,7 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
std::sort(fsPaths.begin(), fsPaths.end(), std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); }); [](const std::string &a, const std::string &b) { return a.length() < b.length(); });
struct qssb_policy *policy = qssb_init_policy(); struct exile_policy *policy = exile_init_policy();
if(policy == NULL) if(policy == NULL)
{ {
Logger::error() << "Failed to init sandboxing policy (worker) "; Logger::error() << "Failed to init sandboxing policy (worker) ";
@ -53,30 +49,22 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
} }
for(unsigned int i = 0; i < fsPaths.size(); i++) for(unsigned int i = 0; i < fsPaths.size(); i++)
{ {
qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ | QSSB_FS_ALLOW_WRITE, fsPaths[i].c_str()); exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str());
} }
policy->drop_caps = 1; policy->drop_caps = 1;
policy->not_dumpable = 1; policy->not_dumpable = 1;
policy->no_new_privs = 1; policy->no_new_privs = 1;
policy->mount_path_policies_to_chroot = 1; policy->mount_path_policies_to_chroot = 1;
/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_CPATH |
* sense that more could be listed here and some critical ones are probably missing */ EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_UNIX |
EXILE_SYSCALL_VOW_THREAD;
/* TODO: use qssb groups */ if(exile_enable_policy(policy) != 0)
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!"; Logger::error() << "Sandbox: Activation of exile failed!";
qssb_free_policy(policy); exile_free_policy(policy);
return false; return false;
} }
qssb_free_policy(policy); exile_free_policy(policy);
return true; return true;
} }

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

@ -1,4 +1,4 @@
CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), lastrevision integer, visible integer DEFAULT 1); CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), title varchar(1024), lastrevision integer, visible integer DEFAULT 1);
CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64), CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64),
password blob, salt blob, permissions integer, enabled integer DEFAULT 1); password blob, salt blob, permissions integer, enabled integer DEFAULT 1);
CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32), CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32),

1
submodules/exile.h Подмодуль

Submodule submodules/exile.h added at f2ca26010a

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

@ -141,7 +141,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< revision.revision << "</a></td>" << revision.revision << "</a></td>"
<< "<td>" << revision.author << "</td>" << "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>" << "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>"; << "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>";
} }
}; };
@ -155,7 +155,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions,
<< "<td>" << revision.revision << "</td>" << "<td>" << revision.revision << "</td>"
<< "<td>" << revision.author << "</td>" << "<td>" << revision.author << "</td>"
<< "<td>" << revision.comment << "</td>" << "<td>" << revision.comment << "</td>"
<< "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>"; << "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>";
} }
}; };

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

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

1
template/quitesimple/dynamic/postlistbegin Обычный файл
Просмотреть файл

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

1
template/quitesimple/dynamic/postlistend Обычный файл
Просмотреть файл

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

1
template/quitesimple/dynamic/postlistlink Обычный файл
Просмотреть файл

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

7
template/quitesimple/feeds/atomentry Обычный файл
Просмотреть файл

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

1
template/quitesimple/feeds/atomfooter Обычный файл
Просмотреть файл

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

8
template/quitesimple/feeds/atomheader Обычный файл
Просмотреть файл

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

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

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

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

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

7
template/quitesimple/searchform Обычный файл
Просмотреть файл

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

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

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

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

@ -18,6 +18,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include <string_view>
#include "urlprovider.h" #include "urlprovider.h"
std::string replaceSingleVar(std::string where, std::string varname, std::string replacement) std::string replaceSingleVar(std::string where, std::string varname, std::string replacement)
@ -119,3 +120,28 @@ std::string UrlProvider::login(std::string page)
{ {
return replaceOnlyPage(config->loginurl, page); return replaceOnlyPage(config->loginurl, page);
} }
std::string UrlProvider::rootUrl()
{
return config->rooturl;
}
std::string UrlProvider::atomFeed(std::string filter)
{
return combine({config->rooturl, replaceSingleVar(config->atomurl, "filter", filter)});
}
std::string UrlProvider::combine(std::initializer_list<std::string> urls)
{
std::string result;
for(const std::string &url : urls)
{
std::string_view urlview{url};
if(result.back() == '/' && urlview.front() == '/')
{
urlview.remove_prefix(1);
}
result += urlview;
}
return result;
}

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

@ -48,6 +48,12 @@ class UrlProvider
std::string category(std::string catname); std::string category(std::string catname);
std::string login(std::string page); std::string login(std::string page);
std::string rootUrl();
std::string atomFeed(std::string filter);
std::string combine(std::initializer_list<std::string> urls);
}; };
#endif // LINKCREATOR_H #endif // LINKCREATOR_H

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

@ -138,6 +138,10 @@ std::string utils::readCompleteFile(std::string_view filepath)
{ {
std::fstream stream(std::string{filepath}); std::fstream stream(std::string{filepath});
if(!stream.is_open())
{
throw std::runtime_error("stream is not open");
}
std::stringstream ss; std::stringstream ss;
ss << stream.rdbuf(); ss << stream.rdbuf();
std::string content = ss.str(); std::string content = ss.str();
@ -166,7 +170,10 @@ std::string utils::regex_callback_replacer(std::regex regex, const std::string &
return result; return result;
} }
std::string utils::toISODate(time_t t) /* TODO: Convert to C++20, but currently the state is rather poor and would
* require workarounds, so keep it this way for now, and do it properly
* once compiler support gets there */
std::string utils::formatLocalDate(time_t t, std::string format)
{ {
struct tm lt; struct tm lt;
if(localtime_r(&t, &lt) == nullptr) if(localtime_r(&t, &lt) == nullptr)
@ -174,7 +181,7 @@ std::string utils::toISODate(time_t t)
return {}; return {};
} }
char result[20]; char result[20];
size_t x = strftime(result, sizeof(result), "%Y-%m-%d %H:%M:%S", &lt); size_t x = strftime(result, sizeof(result), format.c_str(), &lt);
if(x == 0) if(x == 0)
{ {
return {}; return {};
@ -182,6 +189,16 @@ std::string utils::toISODate(time_t t)
return std::string{result}; return std::string{result};
} }
std::string utils::toISODateTime(time_t t)
{
return utils::formatLocalDate(t, "%Y-%m-%d %H:%M:%S");
}
std::string utils::toISODate(time_t t)
{
return utils::formatLocalDate(t, "%Y-%m-%d");
}
std::string utils::trim(std::string_view view) std::string utils::trim(std::string_view view)
{ {
std::string_view chars = " \t\n\r"; std::string_view chars = " \t\n\r";

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

@ -8,6 +8,7 @@
#include <map> #include <map>
#include <regex> #include <regex>
#include <ctime> #include <ctime>
#include <limits>
namespace utils namespace utils
{ {
@ -20,12 +21,6 @@ std::string strreplace(std::string str, const std::string &search, const std::st
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, const T &key)
{
auto k = map.find(key);
return k != map.end();
}
template <class T, class U> U getKeyOrEmpty(const std::map<T, U> &map, const 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);
@ -86,7 +81,9 @@ inline unsigned int toUInt(const std::string &str)
return result; return result;
} }
std::string formatLocalDate(time_t t, std::string format);
std::string toISODate(time_t t); std::string toISODate(time_t t);
std::string toISODateTime(time_t t);
template <class T> inline std::string toString(const T &v) template <class T> inline std::string toString(const T &v)
{ {

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

@ -60,12 +60,12 @@ std::string Varreplacer::makeReplacement(std::string_view varkeyvalue)
std::string_view value; std::string_view value;
std::tie(key, value) = extractKeyAndValue(varkeyvalue); std::tie(key, value) = extractKeyAndValue(varkeyvalue);
if(utils::hasKey(keyValues, key)) if(keyValues.contains(key))
{ {
std::string replacementContent = keyValues[key]; std::string replacementContent = keyValues[key];
return replacementContent; return replacementContent;
} }
else if(utils::hasKey(resolverFunctionsMap, key)) else if(resolverFunctionsMap.contains(key))
{ {
auto resolver = this->resolverFunctionsMap[key]; auto resolver = this->resolverFunctionsMap[key];