Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
f5eb36e7bb | |||
c891b36339 | |||
d17e596563 | |||
761471f243 | |||
9ac0ad0ccd | |||
c30e09d44d | |||
bcc3737d88 | |||
9520aabe5c | |||
4854ea85f2 | |||
16c352c6af | |||
f7cf06cdd5 | |||
ac793c6d39 | |||
a524674149 | |||
a4a45d9add | |||
44c27ed8b4 | |||
433b5da2bb | |||
c5435c52f4 | |||
b2a7ea4031 | |||
1d5bf80710 | |||
ca0c8a94fb | |||
5870102aa9 | |||
32544c8f68 | |||
d0e7ff0a8c | |||
696ff9b7e7 | |||
5570154113 | |||
4f6bcd27b4 | |||
bbe74a2c50 | |||
5db9305408 | |||
c90e26a374 | |||
b297498ca9 | |||
fdcef18861 | |||
75268e0073 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -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
|
||||||
|
15
Makefile
15
Makefile
@ -3,7 +3,7 @@ CPPSTD=c++20
|
|||||||
CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra
|
CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra
|
||||||
RELEASE_CXXFLAGS=-std=$(CPPSTD) -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))
|
||||||
@ -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
|
||||||
|
2
cache/fscache.cpp
vendored
2
cache/fscache.cpp
vendored
@ -46,7 +46,7 @@ void FsCache::removePrefix(std::string_view prefix)
|
|||||||
// TODO: lock dir
|
// TODO: lock dir
|
||||||
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path}))
|
for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path}))
|
||||||
{
|
{
|
||||||
if(std::string_view(entry.path().filename().c_str()).starts_with(prefix) == 0)
|
if(std::string_view(entry.path().filename().c_str()).starts_with(prefix))
|
||||||
{
|
{
|
||||||
std::filesystem::remove_all(entry);
|
std::filesystem::remove_all(entry);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
2
config.h
2
config.h
@ -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
|
||||||
|
@ -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;
|
||||||
|
8
dynamic/dynamiccontent.cpp
Normal file
8
dynamic/dynamiccontent.cpp
Normal file
@ -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
Normal file
22
dynamic/dynamiccontent.h
Normal file
@ -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
Normal file
44
dynamic/dynamiccontentpostlist.cpp
Normal file
@ -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
Normal file
16
dynamic/dynamiccontentpostlist.h
Normal file
@ -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:
|
||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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
Normal file
129
handlers/handlerfeedgenerator.cpp
Normal file
@ -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
Normal file
21
handlers/handlerfeedgenerator.h
Normal file
@ -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
|
@ -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();
|
||||||
|
20
iparser.h
20
iparser.h
@ -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
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;
|
||||||
|
24
parser.cpp
24
parser.cpp
@ -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,30 +116,36 @@ 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])");
|
||||||
|
result = utils::regex_callback_replacer(
|
||||||
|
tagfinder, content,
|
||||||
|
[&](std::smatch &match)
|
||||||
|
{
|
||||||
std::string tag = match.str(1);
|
std::string tag = match.str(1);
|
||||||
std::string content = match.str(2);
|
std::string content = match.str(2);
|
||||||
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
|
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
|
||||||
content = parse(pagedao, provider, content);
|
content = parse(pagedao, provider, content, callback);
|
||||||
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
|
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
|
||||||
{
|
{
|
||||||
return "<" + tag + ">" + content + "</" + tag + ">";
|
return "<" + tag + ">" + content + "</" + tag + ">";
|
||||||
}
|
}
|
||||||
if(tag == "link" || tag == "wikilink")
|
if(tag == "link" || tag == "wikilink")
|
||||||
{
|
{
|
||||||
return this->processLink(pagedao, provider,
|
return this->processLink(
|
||||||
|
pagedao, provider,
|
||||||
match); // TODO: recreate this so we don't check inside the function stuff again
|
match); // TODO: recreate this so we don't check inside the function stuff again
|
||||||
}
|
}
|
||||||
if(tag[0] == 'h')
|
if(tag[0] == 'h')
|
||||||
{
|
{
|
||||||
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
|
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
|
||||||
}
|
}
|
||||||
return std::string("");
|
return callback(tag, content);
|
||||||
});
|
});
|
||||||
result = utils::strreplace(result, "\r\n", "<br>");
|
result = utils::strreplace(result, "\r\n", "<br>");
|
||||||
return result;
|
return result;
|
||||||
|
13
parser.h
13
parser.h
@ -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
|
||||||
|
@ -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),
|
||||||
|
Submodule submodules/cpp-httplib updated: 4f8fcdbaf7...b324921c1a
1
submodules/exile.h
Submodule
1
submodules/exile.h
Submodule
Submodule submodules/exile.h added at f2ca26010a
Submodule submodules/qssb.h deleted from 0d7c5bd6d4
@ -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
Normal file
1
template/quitesimple/dynamic/postlistbegin
Normal file
@ -0,0 +1 @@
|
|||||||
|
<ul>
|
1
template/quitesimple/dynamic/postlistend
Normal file
1
template/quitesimple/dynamic/postlistend
Normal file
@ -0,0 +1 @@
|
|||||||
|
</ul>
|
1
template/quitesimple/dynamic/postlistlink
Normal file
1
template/quitesimple/dynamic/postlistlink
Normal file
@ -0,0 +1 @@
|
|||||||
|
<li>{date}: <a href="{url}">{title}</a></li>
|
7
template/quitesimple/feeds/atomentry
Normal file
7
template/quitesimple/feeds/atomentry
Normal file
@ -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
Normal file
1
template/quitesimple/feeds/atomfooter
Normal file
@ -0,0 +1 @@
|
|||||||
|
</feed>
|
8
template/quitesimple/feeds/atomheader
Normal file
8
template/quitesimple/feeds/atomheader
Normal file
@ -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: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>
|
||||||
|
<li id="searchlink"><a href="{qswiki:config:linksearch}">Search</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<ul id="right" class="search">
|
<ul id="right" class="search">
|
||||||
<li><div><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
Normal file
7
template/quitesimple/searchform
Normal file
@ -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}
|
@ -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,7 +86,6 @@ a:hover
|
|||||||
{
|
{
|
||||||
background-color: #062463;
|
background-color: #062463;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#content
|
#content
|
||||||
@ -107,19 +100,16 @@ 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
|
||||||
@ -137,9 +127,8 @@ list-style-type: none;
|
|||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
21
utils.cpp
21
utils.cpp
@ -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, <) == nullptr)
|
if(localtime_r(&t, <) == 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", <);
|
size_t x = strftime(result, sizeof(result), format.c_str(), <);
|
||||||
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";
|
||||||
|
2
utils.h
2
utils.h
@ -81,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)
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user