4 Commitit

53 muutettua tiedostoa jossa 832 lisäystä ja 658 poistoa

2
.gitignore vendored
Näytä tiedosto

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

Näytä tiedosto

@@ -53,7 +53,7 @@ profile: qswiki
exile.o: submodules/exile.h/exile.c exile.o: submodules/exile.h/exile.c
$(CC) -std=c99 -c submodules/exile.h/exile.c -o exile.o $(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o
qswiki: $(WIKIOBJECTS) exile.o qswiki: $(WIKIOBJECTS) exile.o
$(CXX) $(shell shuf -e $(WIKIOBJECTS) exile.o ) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki $(CXX) $(shell shuf -e $(WIKIOBJECTS) exile.o ) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki

51
cache/mapcache.h vendored
Näytä tiedosto

@@ -4,14 +4,12 @@
#include <set> #include <set>
#include <shared_mutex> #include <shared_mutex>
#include <optional> #include <optional>
#include <string>
#include "icache.h"
/* Thread-Safe Key-Value store */ /* Thread-Safe Key-Value store */
template <class T> class MapCache template <class T> class MapCache
{ {
private: private:
std::unordered_map<std::string, T> cache; std::map<std::string, T> cache;
mutable std::shared_mutex sharedMutex; mutable std::shared_mutex sharedMutex;
public: public:
@@ -35,53 +33,6 @@ template <class T> class MapCache
std::lock_guard<std::shared_mutex> lock{sharedMutex}; std::lock_guard<std::shared_mutex> lock{sharedMutex};
this->cache.clear(); this->cache.clear();
} }
void remove(const std::string &key)
{
std::lock_guard<std::shared_mutex> lock{sharedMutex};
this->cache.erase(key);
}
void removePrefix(const std::string &key)
{
std::lock_guard<std::shared_mutex> lock{sharedMutex};
std::erase_if(this->cache, [key](const auto &item)
{
auto const& [k, v] = item;
return k.starts_with(std::string_view(key));
});
}
};
class StringCache : public MapCache<std::string>, public ICache
{
virtual std::optional<std::string> get(std::string_view key) const override
{
return MapCache<std::string>::find(std::string(key));
}
virtual void put(std::string_view key, std::string val) override
{
MapCache<std::string>::set(std::string(key), val);
}
virtual void remove(std::string_view key) override
{
MapCache<std::string>::remove(std::string(key));
}
virtual void removePrefix(std::string_view prefix)
{
MapCache<std::string>::removePrefix(std::string(prefix));
}
virtual void clear() override
{
MapCache<std::string>::clear();
}
}; };
#endif // MAPCACHE_H #endif // MAPCACHE_H

30
cache/nocache.h vendored
Näytä tiedosto

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

Binary file not shown.

Näytä tiedosto

@@ -72,7 +72,6 @@ void CategoryDaoSqlite::deleteCategory(std::string name)
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
*db << "ROLLBACK";
throwFrom(e); throwFrom(e);
} }
} }
@@ -102,29 +101,22 @@ std::vector<Page> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption
SqliteQueryOption queryOption{o}; SqliteQueryOption queryOption{o};
std::string queryoptions = std::string queryoptions =
queryOption.setOrderByColumn("name").setListedColumnName("page.listed").setPrependWhere(false).build(); queryOption.setOrderByColumn("name").setVisibleColumnName("page.visible").setPrependWhere(false).build();
try try
{ {
auto query = auto query = *db << "SELECT page.id, page.name AS name, page.title, page.lastrevision, page.visible FROM categorymember INNER JOIN page ON page.id = "
*db "categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " +
<< "SELECT page.id, page.name AS name, page.title, page.lastrevision, page.listed, page.feedlisted FROM " queryoptions
"categorymember INNER JOIN page ON page.id = " << name;
"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " + query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool visible) {
queryoptions
<< name;
query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool listed,
bool feedlisted)
{
Page p; Page p;
p.name = name; p.name = name;
p.pageid = id; p.pageid = id;
p.title = title; p.title = title;
p.current_revision = lastrevision; p.current_revision = lastrevision;
p.listed = listed; p.listed = visible;
p.feedlisted = feedlisted; result.push_back(p); };
result.push_back(p);
};
} }
catch(const sqlite::exceptions::no_rows &e) catch(const sqlite::exceptions::no_rows &e)
{ {

Näytä tiedosto

@@ -13,7 +13,7 @@
#include "permissionsdao.h" #include "permissionsdao.h"
class Database class Database
{ {
protected: private:
std::string connnectionstring; std::string connnectionstring;
public: public:

Näytä tiedosto

@@ -23,8 +23,6 @@ class PageDao
virtual void setCategories(std::string pagename, const std::vector<std::string> &catnames) = 0; virtual void setCategories(std::string pagename, const std::vector<std::string> &catnames) = 0;
virtual std::vector<SearchResult> search(std::string query, QueryOption option) = 0; virtual std::vector<SearchResult> search(std::string query, QueryOption option) = 0;
virtual std::vector<std::string> getChildren(std::string pagename) = 0;
virtual ~PageDao() virtual ~PageDao()
{ {
} }

Näytä tiedosto

@@ -57,12 +57,8 @@ std::optional<Page> PageDaoSqlite::findByTitle(std::string title)
Page result; Page result;
try try
{ {
auto ps = auto ps = *db << "SELECT id, name, title, lastrevision, visible FROM page WHERE title = ?";
*db ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed);
<< "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) "
"FROM page WHERE title = ?";
ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed,
result.feedlisted, result.parentpage);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@@ -82,13 +78,9 @@ std::optional<Page> PageDaoSqlite::find(unsigned int id)
result.pageid = id; result.pageid = id;
try try
{ {
auto ps = auto ps = *db << "SELECT name, title, lastrevision, visible FROM page WHERE id = ?";
*db
<< "SELECT name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) FROM "
"page WHERE id = ?";
ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed, result.feedlisted, ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed);
result.parentpage);
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@@ -117,7 +109,6 @@ void PageDaoSqlite::deletePage(std::string page)
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
*db << "ROLLBACK";
throwFrom(e); throwFrom(e);
} }
} }
@@ -126,10 +117,10 @@ void PageDaoSqlite::save(const Page &page)
{ {
try try
{ {
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, listed, feedlisted, parent) VALUES((SELECT " *db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, visible) VALUES((SELECT id FROM page WHERE "
"id FROM page WHERE name = ? OR id = ?), ?, ?, ?, ?, ?, (SELECT id FROM page WHERE name = ?))" "name = "
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed "? OR id = ?), ?, ?, ?, ?)"
<< page.feedlisted << page.parentpage; << page.name << page.pageid << page.name << page.title << page.current_revision << page.listed;
} }
catch(sqlite::sqlite_exception &e) catch(sqlite::sqlite_exception &e)
{ {
@@ -145,25 +136,19 @@ std::vector<Page> PageDaoSqlite::getPageList(QueryOption option)
{ {
std::string queryOption = SqliteQueryOption(option) std::string queryOption = SqliteQueryOption(option)
.setOrderByColumn("lower(name)") .setOrderByColumn("lower(name)")
.setListedColumnName("listed") .setVisibleColumnName("visible")
.setPrependWhere(true) .setPrependWhere(true)
.build(); .build();
std::string query = "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE " std::string query = "SELECT id, name, title, lastrevision, visible FROM page " + queryOption;
"id = parent) FROM page " + *db << query >> [&](unsigned int pageid, std::string name, std::string title,unsigned int current_revision, bool visible ) {
queryOption;
*db << query >> [&](unsigned int pageid, std::string name, std::string title, unsigned int current_revision,
bool listed, bool feedlisted, std::string parent)
{
Page tmp; Page tmp;
tmp.pageid = pageid; tmp.pageid = pageid;
tmp.name = name; tmp.name = name;
tmp.title = title; tmp.title = title;
tmp.current_revision = current_revision; tmp.current_revision = current_revision;
tmp.listed = listed; tmp.listed = visible;
tmp.feedlisted = feedlisted; result.push_back(tmp); };
tmp.parentpage = parent;
result.push_back(tmp);
};
} }
catch(const sqlite::errors::no_rows &e) catch(const sqlite::errors::no_rows &e)
{ {
@@ -274,11 +259,3 @@ int PageDaoSqlite::fetchPageId(std::string pagename)
auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename; auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename;
return execInt(binder); return execInt(binder);
} }
std::vector<std::string> PageDaoSqlite::getChildren(std::string pagename)
{
std::vector<std::string> result;
auto query = *db << "SELECT name FROM page WHERE parent = (SELECT id FROM page WHERE name = ?)" << pagename;
query >> [&](std::string page) { result.push_back(page); };
return result;
}

Näytä tiedosto

@@ -28,8 +28,6 @@ class PageDaoSqlite : public PageDao, protected SqliteDao
int fetchPageId(std::string pagename); int fetchPageId(std::string pagename);
std::vector<SearchResult> search(std::string query, QueryOption option) override; std::vector<SearchResult> search(std::string query, QueryOption option) override;
void setCategories(std::string pagename, const std::vector<std::string> &catnames) override; void setCategories(std::string pagename, const std::vector<std::string> &catnames) override;
std::vector<std::string> getChildren(std::string pagename) override;
}; };
#endif // PAGEDAOSQLITE_H #endif // PAGEDAOSQLITE_H

Näytä tiedosto

@@ -9,8 +9,6 @@ class PermissionsDao
PermissionsDao(); PermissionsDao();
virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0; virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0;
virtual void save(std::string pagename, std::string username, Permissions perms) = 0; virtual void save(std::string pagename, std::string username, Permissions perms) = 0;
virtual void clearForPage(std::string pagename) = 0;
virtual ~PermissionsDao() = default; virtual ~PermissionsDao() = default;
}; };

Näytä tiedosto

@@ -59,16 +59,3 @@ void PermissionsDaoSqlite::save(std::string pagename, std::string username, Perm
throwFrom(e); throwFrom(e);
} }
} }
void PermissionsDaoSqlite::clearForPage(std::string pagename)
{
try
{
auto stmt = *db << "DELETE FROM permissions WHERE page = (SELECT id FROM page WHERE name = ?)" << pagename;
stmt.execute();
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
}

Näytä tiedosto

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

Näytä tiedosto

@@ -13,7 +13,7 @@ class QueryOption
unsigned int offset = 0; unsigned int offset = 0;
unsigned int limit = 0; unsigned int limit = 0;
SORT_ORDER order = ASCENDING; SORT_ORDER order = ASCENDING;
bool includeUnlisted = true; bool includeInvisible = true;
}; };
#endif // QUERYOPTION_H #endif // QUERYOPTION_H

Näytä tiedosto

@@ -52,7 +52,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
{ {
SqliteQueryOption queryOption{options}; SqliteQueryOption queryOption{options};
std::string queryOptionSql = queryOption.setPrependWhere(true) std::string queryOptionSql = queryOption.setPrependWhere(true)
.setListedColumnName("page.listed") .setVisibleColumnName("page.visible")
.setOrderByColumn("creationtime") .setOrderByColumn("creationtime")
.build(); .build();
auto query = auto query =
@@ -61,8 +61,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisions(QueryOption &options)
"page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id " + "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id " +
queryOptionSql; queryOptionSql;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid) std::string page, unsigned int revisionid) {
{
Revision r; Revision r;
r.author = author; r.author = author;
r.comment = comment; r.comment = comment;
@@ -92,7 +91,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
{ {
SqliteQueryOption queryOption{option}; SqliteQueryOption queryOption{option};
std::string queryOptionSql = queryOption.setPrependWhere(false) std::string queryOptionSql = queryOption.setPrependWhere(false)
.setListedColumnName("page.listed") .setVisibleColumnName("page.visible")
.setOrderByColumn("creationtime") .setOrderByColumn("creationtime")
.build(); .build();
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
@@ -102,8 +101,7 @@ std::vector<Revision> RevisionDaoSqlite::getAllRevisionsForPage(std::string page
<< pagename; << pagename;
query >> [&](std::string author, std::string comment, std::string content, time_t creationtime, query >> [&](std::string author, std::string comment, std::string content, time_t creationtime,
std::string page, unsigned int revisionid) std::string page, unsigned int revisionid) {
{
Revision r; Revision r;
r.author = author; r.author = author;
r.comment = comment; r.comment = comment;
@@ -131,8 +129,7 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam
try try
{ {
auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, "
"strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON " "strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid";
"revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid";
query << pagename; query << pagename;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);
@@ -157,8 +154,7 @@ std::optional<Revision> RevisionDaoSqlite::getRevisionForPage(std::string pagena
auto query = auto query =
*db *db
<< "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), " << "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), "
"page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND " "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND revisionid = ? ";
"revisionid = ? ";
query << pagename << revision; query << pagename << revision;
query >> query >>
std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision);

Näytä tiedosto

@@ -54,7 +54,7 @@ void SessionDaoSqlite::fillSession(int userid, Session &sess)
{ {
if(userid > -1) if(userid > -1)
{ {
UserDaoSqlite userDao{*this->db}; UserDaoSqlite userDao{this->db};
auto u = userDao.find(userid); auto u = userDao.find(userid);
if(u) if(u)
{ {

Näytä tiedosto

@@ -18,43 +18,23 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
*/ */
#include <atomic>
#include "sqlite.h" #include "sqlite.h"
#include "../logger.h"
#include "pagedaosqlite.h" #include "pagedaosqlite.h"
#include "revisiondaosqlite.h" #include "revisiondaosqlite.h"
#include "sessiondaosqlite.h" #include "sessiondaosqlite.h"
#include "sqlite_modern_cpp.h"
#include "userdaosqlite.h" #include "userdaosqlite.h"
#include "categorydaosqlite.h" #include "categorydaosqlite.h"
#include "exceptions.h"
#include "permissionsdaosqlite.h" #include "permissionsdaosqlite.h"
thread_local sqlite::database *Sqlite::db = nullptr;
std::atomic<int> instances = 0;
Sqlite::Sqlite(std::string path) : Database(path) Sqlite::Sqlite(std::string path) : Database(path)
{ {
instances++; this->db = std::make_shared<sqlite::database>(path);
if(instances.load() > 1)
{ *db << "PRAGMA journal_mode=WAL;";
std::cerr << "temporal (yeah, right) HACK... only one instance allowed" << std::endl;
abort();
}
} }
std::mutex dbmutex;
sqlite::database &Sqlite::database() const
{
if(Sqlite::db == nullptr)
{
sqlite::sqlite_config config;
config.flags = config.flags | sqlite::OpenFlags::FULLMUTEX;
std::lock_guard<std::mutex> dbguard(dbmutex);
Sqlite::db = new sqlite::database(this->connnectionstring, config);
*Sqlite::db << "PRAGMA journal_mode=WAL;";
*Sqlite::db << "PRAGMA busy_timeout=10000;";
}
return *Sqlite::db;
}
std::unique_ptr<RevisionDao> Sqlite::createRevisionDao() const std::unique_ptr<RevisionDao> Sqlite::createRevisionDao() const
{ {
return create<RevisionDaoSqlite>(); return create<RevisionDaoSqlite>();
@@ -87,20 +67,27 @@ std::unique_ptr<PermissionsDao> Sqlite::createPermissionsDao() const
void Sqlite::beginTransaction() void Sqlite::beginTransaction()
{ {
*db << "begin;"; if(!inTransaction)
{
*db << "begin;";
inTransaction = true;
}
} }
void Sqlite::rollbackTransaction() void Sqlite::rollbackTransaction()
{ {
*db << "rollback;"; if(inTransaction)
{
*db << "rollback;";
inTransaction = false;
}
} }
void Sqlite::commitTransaction() void Sqlite::commitTransaction()
{ {
*db << "commit;"; if(inTransaction)
} {
*db << "commit;";
Sqlite::~Sqlite() inTransaction = false;
{ }
delete this->db;
} }

Näytä tiedosto

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

Näytä tiedosto

@@ -12,20 +12,20 @@
class SqliteDao class SqliteDao
{ {
protected: protected:
sqlite::database *db = nullptr; std::shared_ptr<sqlite::database> db;
public: public:
SqliteDao() SqliteDao()
{ {
} }
SqliteDao(sqlite::database &db) SqliteDao(std::shared_ptr<sqlite::database> db)
{ {
this->db = &db; this->db = db;
} }
void setDb(sqlite::database &db) void setDb(std::shared_ptr<sqlite::database> db)
{ {
this->db = &db; this->db = db;
} }
inline void throwFrom(const sqlite::sqlite_exception &e) const inline void throwFrom(const sqlite::sqlite_exception &e) const

Näytä tiedosto

@@ -31,9 +31,9 @@ SqliteQueryOption &SqliteQueryOption::setOrderByColumn(std::string name)
return *this; return *this;
} }
SqliteQueryOption &SqliteQueryOption::setListedColumnName(std::string name) SqliteQueryOption &SqliteQueryOption::setVisibleColumnName(std::string name)
{ {
this->listedColumnName = name; this->visibleColumnName = name;
return *this; return *this;
} }
@@ -50,9 +50,9 @@ std::string SqliteQueryOption::build()
{ {
result += "WHERE "; result += "WHERE ";
} }
if(!o.includeUnlisted && !this->listedColumnName.empty()) if(!o.includeInvisible && !this->visibleColumnName.empty())
{ {
result += this->listedColumnName + " = 1"; result += this->visibleColumnName + " = 1";
} }
else else
{ {

Näytä tiedosto

@@ -7,7 +7,7 @@ class SqliteQueryOption
{ {
private: private:
QueryOption o; QueryOption o;
std::string listedColumnName; std::string visibleColumnName;
std::string orderByColumnName; std::string orderByColumnName;
bool prependWhere; bool prependWhere;
@@ -17,7 +17,7 @@ class SqliteQueryOption
SqliteQueryOption &setOrderByColumn(std::string name); SqliteQueryOption &setOrderByColumn(std::string name);
SqliteQueryOption &setListedColumnName(std::string name); SqliteQueryOption &setVisibleColumnName(std::string name);
SqliteQueryOption &setPrependWhere(bool b); SqliteQueryOption &setPrependWhere(bool b);

Näytä tiedosto

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

Näytä tiedosto

@@ -10,12 +10,11 @@ class DynamicContent
Template *templ; Template *templ;
Database *database; Database *database;
UrlProvider *urlProvider; UrlProvider *urlProvider;
Session *userSession;
std::string argument; std::string argument;
public: public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider, Session &session); DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider);
virtual std::string render() = 0; virtual std::string render() = 0;
virtual void setArgument(std::string argument) virtual void setArgument(std::string argument)
{ {

Näytä tiedosto

@@ -9,20 +9,18 @@ private:
Template *templ; Template *templ;
Database *db; Database *db;
UrlProvider *urlProvider; UrlProvider *urlProvider;
Session *session;
public: public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider, Session &session) DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider)
{ {
this->templ = &templ; this->templ = &templ;
this->db = &db; this->db = &db;
this->urlProvider = &urlProvider; this->urlProvider = &urlProvider;
this->session = &session;
} }
template <class T> inline std::shared_ptr<T> createDynamicContent() template <class T> inline std::shared_ptr<T> createDynamicContent()
{ {
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider, *this->session); return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider);
} }

Näytä tiedosto

@@ -6,21 +6,14 @@ std::string DynamicContentPostList::render()
auto categoryDao = this->database->createCategoryDao(); auto categoryDao = this->database->createCategoryDao();
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
auto permissionDao = this->database->createPermissionsDao();
QueryOption option; QueryOption option;
option.includeUnlisted = false; option.includeInvisible = false;
auto members = categoryDao->fetchMembers(this->argument, option); auto members = categoryDao->fetchMembers(this->argument, option);
std::vector<std::pair<std::string, time_t>> pageList; std::vector<std::pair<std::string, time_t>> pageList;
for(const Page &member : members) for(const Page &member : members)
{ {
Permissions perms = permissionDao->find(member.name, this->userSession->user.login) auto revision = revisionDao->getRevisionForPage(member.name, 1);
.value_or(this->userSession->user.permissions); pageList.push_back({member.name, revision->timestamp});
if(perms.canRead()) /* TODO: Maybe add canList() */
{
auto revision = revisionDao->getRevisionForPage(member.name, 1);
pageList.push_back({member.name, revision->timestamp});
}
} }
std::sort(pageList.begin(), pageList.end(), std::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::pair<std::string, time_t> &a, std::pair<std::string, time_t> &b) { return a.second > b.second; });

Näytä tiedosto

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

Näytä tiedosto

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

Näytä tiedosto

@@ -20,7 +20,6 @@ SOFTWARE.
*/ */
#include "httpgateway.h" #include "httpgateway.h"
#include "../logger.h" #include "../logger.h"
#include <stdexcept>
HttpGateway::HttpGateway(std::string listenaddr, int port, uint64_t maxPayloadLength) HttpGateway::HttpGateway(std::string listenaddr, int port, uint64_t maxPayloadLength)
{ {
this->listenaddr = listenaddr; this->listenaddr = listenaddr;
@@ -35,11 +34,6 @@ bool HttpGateway::keepReading()
Request HttpGateway::convertRequest(httplib::Request request) Request HttpGateway::convertRequest(httplib::Request request)
{ {
if(!utils::is_printable_ascii(request.target))
{
throw std::runtime_error("Invalid chars in URI: " + utils::catv(request.target));
}
Request result; Request result;
result.setRequestMethod(request.method); result.setRequestMethod(request.method);
result.setUrl(request.target); result.setUrl(request.target);
@@ -61,15 +55,9 @@ Request HttpGateway::convertRequest(httplib::Request request)
if(request.has_header("COOKIE")) if(request.has_header("COOKIE"))
{ {
std::string cookie = request.get_header_value("COOKIE");
if(!utils::is_printable_ascii(cookie))
{
/* We better bail */
throw std::runtime_error("Cookie with non printable chars sent");
}
result.initCookies(request.get_header_value("COOKIE")); result.initCookies(request.get_header_value("COOKIE"));
} }
result.setIp("127.0.0.1"); result.setIp(request.get_header_value("REMOTE_ADDR"));
return result; return result;
} }
@@ -94,27 +82,6 @@ httplib::Response HttpGateway::convertResponse(Response response)
void HttpGateway::work(RequestWorker &worker) void HttpGateway::work(RequestWorker &worker)
{ {
httplib::Server server; httplib::Server server;
server.set_exception_handler([](const httplib::Request& req, httplib::Response& res, std::exception_ptr ep) {
auto fmt = "<h1>Error 500</h1><p>%s</p>";
char buf[BUFSIZ];
try
{
std::rethrow_exception(ep);
}
catch (std::exception &e)
{
std::string exception = utils::html_xss(e.what());
snprintf(buf, sizeof(buf), fmt, exception.c_str());
Logger::error() << "Exception caught in Httpgateway::work():" << utils::html_xss(utils::catv(e.what()));
}
catch (...)
{
snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
Logger::error() << "Unknown exception caught in Httpgateway::work()";
}
res.set_content(buf, "text/html");
res.status = 500;
});
server.set_payload_max_length(this->maxPayloadLength); server.set_payload_max_length(this->maxPayloadLength);
auto handler = [&](const httplib::Request &req, httplib::Response &res) auto handler = [&](const httplib::Request &req, httplib::Response &res)
{ {
@@ -127,5 +94,4 @@ void HttpGateway::work(RequestWorker &worker)
server.Get("/(.*)", handler); server.Get("/(.*)", handler);
server.Post("/(.*)", handler); server.Post("/(.*)", handler);
server.listen(this->listenaddr.c_str(), this->listenport); server.listen(this->listenaddr.c_str(), this->listenport);
} }

Näytä tiedosto

@@ -53,7 +53,7 @@ std::string Handler::createPageTitle(std::string title)
QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const QueryOption Handler::queryOption(const Request &r, SORT_ORDER defaultSort) const
{ {
QueryOption result; QueryOption result;
result.includeUnlisted = false; result.includeInvisible = false;
try try
{ {
result.limit = utils::toUInt(r.get("limit")); result.limit = utils::toUInt(r.get("limit"));
@@ -98,10 +98,7 @@ Response Handler::handle(const Request &r)
Permissions Handler::effectivePermissions(std::string page) Permissions Handler::effectivePermissions(std::string page)
{ {
Permissions &userPerms = this->userSession->user.permissions; return this->database->createPermissionsDao()
if(userPerms.isAdmin()) ->find(page, this->userSession->user.login)
{ .value_or(this->userSession->user.permissions);
return userPerms;
}
return this->database->createPermissionsDao()->find(page, this->userSession->user.login).value_or(userPerms);
} }

Näytä tiedosto

@@ -1,19 +1,19 @@
#include "handlerfeedgenerator.h" #include "handlerfeedgenerator.h"
#include "../parser.h"
#include "../revisionrenderer.h" #include "../revisionrenderer.h"
std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries( std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries(
std::vector<std::string> categories) std::vector<std::string> categories)
{ {
auto revisionDao = this->database->createRevisionDao(); auto revisionDao = this->database->createRevisionDao();
auto pageDao = this->database->createPageDao(); auto pageDao = this->database->createPageDao();
auto permissionDao = this->database->createPermissionsDao();
std::vector<EntryRevisionPair> result; std::vector<EntryRevisionPair> result;
QueryOption option; QueryOption option;
option.includeUnlisted = true; option.includeInvisible = false;
// option.limit = 20; // option.limit = 20;
auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; }; auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; };
std::set<Page, decltype(comppage)> members(comppage); std::set<Page, decltype(comppage)> members (comppage);
if(categories.empty()) if(categories.empty())
{ {
auto pages = pageDao->getPageList(option); auto pages = pageDao->getPageList(option);
@@ -34,16 +34,8 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch
} }
for(const Page &member : members) for(const Page &member : members)
{ {
if(member.feedlisted) auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
{ result.push_back({member, revision});
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead())
{
auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
result.push_back({member, revision});
}
}
} }
std::sort(result.begin(), result.end(), std::sort(result.begin(), result.end(),
[](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; }); [](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; });
@@ -76,7 +68,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
subtitle = "All pages"; subtitle = "All pages";
} }
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession}; RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
for(const EntryRevisionPair &entry : entries) for(const EntryRevisionPair &entry : entries)
{ {
@@ -89,6 +81,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
newestPublished = initialRevision.timestamp; newestPublished = initialRevision.timestamp;
} }
std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z"; std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z";
std::string entryUpdated = utils::formatLocalDate(current.timestamp, dateformat) + "Z";
std::string entryurl = std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)}); this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry"); TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
@@ -96,7 +89,7 @@ std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGene
atomentry.setVar("entryurl", utils::html_xss(entryurl)); atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl)); atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished); atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryPublished); atomentry.setVar("entryupdated", entryUpdated);
atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title))); atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title)));
stream << atomentry.render(); stream << atomentry.render();
} }

Näytä tiedosto

@@ -24,7 +24,6 @@ SOFTWARE.
#include "../parser.h" #include "../parser.h"
#include "../revisionrenderer.h" #include "../revisionrenderer.h"
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page) bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{ {
return effectivePermissions(page).canEdit(); return effectivePermissions(page).canEdit();
@@ -57,8 +56,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{ {
if(!effectivePermissions(from).canRead()) if(!effectivePermissions(from).canRead())
{ {
return this->errorResponse("Permission denied", return this->errorResponse("Permission denied", "No access permissions, so you can't use this page as a template");
"No access permissions, so you can't use this page as a template");
} }
body = revisiondao->getCurrentForPage(from)->content; body = revisiondao->getCurrentForPage(from)->content;
} }
@@ -76,27 +74,9 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
try try
{ {
this->database->beginTransaction(); this->database->beginTransaction();
std::string visiblecmd = parser.extractCommand("visible", newContent); std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string listedcmd = parser.extractCommand("listed", newContent);
/* Backwarts compatibility */
if(listedcmd.empty())
{
listedcmd = visiblecmd;
}
std::string feedlistedcmd = parser.extractCommand("feedlisted", newContent);
std::string rename = parser.extractCommand("rename", newContent); std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent); std::string customtitle = parser.extractCommand("pagetitle", newContent);
std::string parentpage = parser.extractCommand("parentpage", newContent);
std::vector<std::string> perms = parser.extractCommands("permissions", newContent);
if(parentpage != "" && !pageDao.find(parentpage))
{
return this->errorResponse("Invalid parent",
"Specified parent page " + parentpage + " does not exist");
}
Page page; Page page;
std::optional<Page> currentPage = pageDao.find(pagename); std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage) if(currentPage)
@@ -111,48 +91,10 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
} }
pagename = rename; pagename = rename;
} }
std::vector<std::pair<std::string, Permissions>> collectedPermissions;
auto permissionDao = this->database->createPermissionsDao();
for(const std::string &perm : perms)
{
auto splitted = utils::split(perm, '|');
if(splitted.size() != 2)
{
return this->errorResponse("Invalid command", "permissions command is misformated");
}
auto currentPermission = permissionDao->find(pagename, splitted[0]);
Permissions newPermissions = Permissions{splitted[1]};
if(!currentPermission || newPermissions != currentPermission.value())
{
if(!this->userSession->user.permissions.canSetPagePerms())
{
this->database->rollbackTransaction();
return errorResponse("Permission denied",
"You don't have permission to change permissions. Don't touch the "
"permission commands");
}
}
collectedPermissions.emplace_back(splitted[0], newPermissions);
}
if(this->userSession->user.permissions.canSetPagePerms())
{
permissionDao->clearForPage(pagename);
for(auto &perms : collectedPermissions)
{
permissionDao->save(pagename, perms.first, perms.second);
}
}
page.current_revision = current_revision; page.current_revision = current_revision;
page.listed = !(listedcmd == "0"); page.listed = !(visiblecmd == "0");
page.feedlisted = !(feedlistedcmd == "0");
page.name = pagename; page.name = pagename;
page.title = customtitle; page.title = customtitle;
page.parentpage = parentpage;
if(page.title.empty()) if(page.title.empty())
{ {
page.title = page.name; page.title = page.name;
@@ -173,7 +115,6 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
} }
catch(const DatabaseException &e) catch(const DatabaseException &e)
{ {
this->database->rollbackTransaction();
Logger::debug() << "Error saving revision: " << e.what(); Logger::debug() << "Error saving revision: " << e.what();
return errorResponse("Database error", "A database error occured while trying to save this revision"); return errorResponse("Database error", "A database error occured while trying to save this revision");
} }
@@ -188,7 +129,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
TemplatePage templatePage = this->templ->getPage("page_creation_preview"); TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename)); templatePage.setVar("actionurl", urlProvider->editPage(pagename));
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession}; RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent)); templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent); templatePage.setVar("content", newContent);

Näytä tiedosto

@@ -60,7 +60,7 @@ std::string HandlerPageView::createIndexContent(IParser &parser, std::string con
} }
previous = h.level; previous = h.level;
HtmlLink link; HtmlLink link;
link.href = "#" + utils::strreplace(h.title, " ", ""); link.href = "#" + h.title;
link.innervalue = h.title; link.innervalue = h.title;
link.cssclass = "indexlink"; link.cssclass = "indexlink";
indexcontent += "<li>" + link.render() + "</li>"; indexcontent += "<li>" + link.render() + "</li>";
@@ -138,7 +138,8 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
Response result; Response result;
result.setStatus(200); result.setStatus(200);
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession}; RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider };
std::string customtitle = parser.extractCommand("pagetitle", revision->content); std::string customtitle = parser.extractCommand("pagetitle", revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle); std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);

Näytä tiedosto

@@ -15,10 +15,7 @@ class IParser
} }
public: public:
virtual std::string extractFirstTag(std::string tagname, const std::string &content) const = 0;
virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0; virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const = 0;
virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0; virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0;
virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const
{ {

Näytä tiedosto

@@ -2,7 +2,6 @@
#define LOGGER_H #define LOGGER_H
#include <iostream> #include <iostream>
#include <chrono> #include <chrono>
#include "utils.h"
class Logger class Logger
{ {
private: private:
@@ -25,15 +24,7 @@ class Logger
{ {
(*out) << time(0) << " " << prefix; (*out) << time(0) << " " << prefix;
} }
(*out) << val;
if constexpr (std::is_convertible_v<T, std::string_view>)
{
(*out) << utils::catv(val);
}
else
{
(*out) << val;
}
headerSent = true; headerSent = true;
return *this; // or maybe out itself? probably not. return *this; // or maybe out itself? probably not.
} }

2
page.h
Näytä tiedosto

@@ -8,9 +8,7 @@ class Page
Page(); Page();
std::string name; std::string name;
std::string title; std::string title;
std::string parentpage;
bool listed; bool listed;
bool feedlisted;
unsigned int current_revision; unsigned int current_revision;
unsigned int pageid; unsigned int pageid;
}; };

Näytä tiedosto

@@ -63,10 +63,11 @@ std::vector<std::string> Parser::extractCategories(const std::string &content) c
return result; return result;
} }
std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
{ {
std::string cmd = "[" + tagname + "]"; std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/" + tagname + "]"; std::string cmdend = "[/cmd:" + cmdname + "]";
std::string_view view = content; std::string_view view = content;
size_t pos = 0; size_t pos = 0;
if((pos = view.find(cmd)) != std::string::npos) if((pos = view.find(cmd)) != std::string::npos)
@@ -81,34 +82,6 @@ std::string Parser::extractFirstTag(std::string tagname, const std::string &cont
} }
return ""; return "";
} }
std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
{
return extractFirstTag("cmd:" + cmdname, content);
}
std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const
{
std::vector<std::string> result;
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
std::string_view view = content;
size_t pos = 0;
while((pos = view.find(cmd)) != std::string::npos)
{
view.remove_prefix(pos);
view.remove_prefix(cmd.size());
if((pos = view.find(cmdend)) != std::string::npos)
{
result.emplace_back(view.substr(0, pos));
}
}
return result;
}
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{ {
std::string linktag = match.str(1); std::string linktag = match.str(1);
@@ -175,48 +148,27 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
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( std::regex tagfinder(
R"(\[(b|i|u|s|li|p|br|ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:listed|cmd:feedlisted|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|cmd:parentpage|content|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1](\r\n)*)"); R"(\[(b|i|u|li||ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1])");
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
result = utils::regex_callback_replacer( result = utils::regex_callback_replacer(
tagfinder, content, tagfinder, content,
[&](std::smatch &match) [&](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 newlines = match.str(4);
if(newlines == "\r\n")
{
newlines = "<br>";
}
if(tag == "br")
{
return std::string("<br>");
}
if(tag != "code" && tag != "blockquote") if(tag != "code" && tag != "blockquote")
{ {
content = parse(pagedao, provider, content, callback); content = parse(pagedao, provider, content, callback);
} }
/* [content] just helps extracting the actual content of a page, pretty much noop otherwise */
if(tag == "content")
{
return parse(pagedao, provider, content, callback);
}
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace)) if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
{ {
if(tag == "p" || tag == "br") return "<" + tag + ">" + content + "</" + tag + ">";
{
newlines = "";
}
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
} }
if(tag == "link" || tag == "wikilink") if(tag == "link" || tag == "wikilink")
{ {
return this->processLink(pagedao, provider, return this->processLink(
match) + pagedao, provider,
newlines; // 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 == "img") if(tag == "img")
{ {
@@ -224,11 +176,11 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s
} }
if(tag[0] == 'h') if(tag[0] == 'h')
{ {
return "<" + tag + " id='" + utils::strreplace(content, " ", "") + "'>" + content + "</" + tag + ">"; return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
} }
if(tag == "code" || tag == "blockquote") if(tag == "code" || tag == "blockquote")
{ {
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>"; return "<pre><" + tag + ">"+ utils::strreplace(content, "\r\n", "\n") + "</"+tag+"></pre>";
} }
return callback(tag, content); return callback(tag, content);
}); });

Näytä tiedosto

@@ -9,9 +9,7 @@ class Parser : public IParser
std::string processImage(std::smatch &match) const; std::string processImage(std::smatch &match) const;
public: public:
std::string extractFirstTag(std::string tagname, const std::string &content) const override; std::string extractCommand(std::string cmdname, const std::string &content) const;
std::string extractCommand(std::string cmdname, const std::string &content) const override;
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override;
std::vector<Headline> extractHeadlines(const std::string &content) const override; std::vector<Headline> extractHeadlines(const std::string &content) const override;
std::vector<std::string> extractCategories(const std::string &content) const override; std::vector<std::string> extractCategories(const std::string &content) const override;
using IParser::parse; using IParser::parse;

Näytä tiedosto

@@ -20,8 +20,7 @@ SOFTWARE.
*/ */
#include "permissions.h" #include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING}, static const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT}, {"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY}, {"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY}, {"can_global_history", PERM_CAN_GLOBAL_HISTORY},
@@ -30,8 +29,7 @@ static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTH
{"can_create", PERM_CAN_CREATE}, {"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST}, {"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE}, {"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}, {"can_search", PERM_CAN_SEARCH}};
{"can_set_page_perms", PERM_CAN_SET_PAGE_PERMS}};
Permissions::Permissions(int permissions) Permissions::Permissions(int permissions)
{ {

Näytä tiedosto

@@ -1,7 +1,6 @@
#ifndef PERMISSIONS_H #ifndef PERMISSIONS_H
#define PERMISSIONS_H #define PERMISSIONS_H
#define PERM_CAN_NOTHING 0
#define PERM_CAN_READ 1 << 0 #define PERM_CAN_READ 1 << 0
#define PERM_CAN_EDIT 1 << 1 #define PERM_CAN_EDIT 1 << 1
#define PERM_CAN_PAGE_HISTORY 1 << 2 #define PERM_CAN_PAGE_HISTORY 1 << 2
@@ -12,8 +11,6 @@
#define PERM_CAN_SEE_CATEGORY_LIST 1 << 7 #define PERM_CAN_SEE_CATEGORY_LIST 1 << 7
#define PERM_CAN_SEE_LINKS_HERE 1 << 8 #define PERM_CAN_SEE_LINKS_HERE 1 << 8
#define PERM_CAN_SEARCH 1 << 9 #define PERM_CAN_SEARCH 1 << 9
#define PERM_CAN_SET_PAGE_PERMS 1 << 10
#define PERM_IS_ADMIN (1L<<31)-1
#include <string> #include <string>
#include <map> #include <map>
@@ -57,16 +54,10 @@ class Permissions
return this->permissions; return this->permissions;
} }
bool canNothing() const
{
return this->permissions == PERM_CAN_NOTHING;
}
bool canRead() const bool canRead() const
{ {
return this->permissions & PERM_CAN_READ; return this->permissions & PERM_CAN_READ;
} }
bool canEdit() const bool canEdit() const
{ {
return this->permissions & PERM_CAN_EDIT; return this->permissions & PERM_CAN_EDIT;
@@ -104,27 +95,12 @@ class Permissions
return this->permissions & PERM_CAN_SEE_PAGE_LIST; return this->permissions & PERM_CAN_SEE_PAGE_LIST;
} }
bool canSetPagePerms() const
{
return this->permissions & PERM_CAN_SET_PAGE_PERMS;
}
bool isAdmin() const
{
return this->permissions == PERM_IS_ADMIN;
}
std::string toString() const std::string toString() const
{ {
return Permissions::toString(this->permissions); return Permissions::toString(this->permissions);
} }
static std::string toString(int perms); static std::string toString(int perms);
bool operator==(const Permissions &o) const
{
return this->permissions == o.permissions;
}
}; };
#endif // PERMISSIONS_H #endif // PERMISSIONS_H

Näytä tiedosto

@@ -37,7 +37,6 @@ SOFTWARE.
#include "urlprovider.h" #include "urlprovider.h"
#include "requestworker.h" #include "requestworker.h"
#include "cache/fscache.h" #include "cache/fscache.h"
#include "cache/nocache.h"
#include "sandbox/sandboxfactory.h" #include "sandbox/sandboxfactory.h"
#include "cli.h" #include "cli.h"
#include "cliconsole.h" #include "cliconsole.h"
@@ -69,11 +68,9 @@ static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version",
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver) std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
{ {
std::string path = resolver.getConfig("cache_fs_dir"); std::string path = resolver.getConfig("cache_fs_dir");
if(path == "")
{
return std::make_unique<StringCache>();
}
return std::make_unique<FsCache>(path); return std::make_unique<FsCache>(path);
} }
@@ -230,8 +227,8 @@ int main(int argc, char **argv)
} }
catch(const std::exception &e) catch(const std::exception &e)
{ {
Logger::error() << utils::catv(e.what()); Logger::error() << e.what();
std::cerr << utils::catv(e.what()) << std::endl; std::cerr << e.what() << std::endl;
} }
return 0; return 0;
} }

Näytä tiedosto

@@ -86,15 +86,7 @@ void Request::initCookies(const std::string &cookiestr)
std::string Request::get(const std::string &key) const std::string Request::get(const std::string &key) const
{ {
std::string value = utils::getKeyOrEmpty(this->getVars, key); return utils::getKeyOrEmpty(this->getVars, key);
/* In general all our expected GET values are printable and, for now, ascii.
* If not, it's not a normal request. So just return an empty string then.
* Exceptions are probably a bit too much */
if(!utils::is_printable_ascii(value))
{
return "";
}
return value;
} }
std::string Request::post(const std::string &key) const std::string Request::post(const std::string &key) const
@@ -113,18 +105,23 @@ std::string Request::param(const std::string &key) const
} }
std::string Request::cookie(const std::string &key) const std::string Request::cookie(const std::string &key) const
{ {
std::string value;
for(const Cookie &c : cookies) for(const Cookie &c : cookies)
{ {
if(c.key == key) if(c.key == key)
{ {
value = c.value; return c.value;
break;
} }
} }
if(utils::is_printable_ascii(value))
{
return value;
}
return ""; return "";
} }
std::vector<std::string> Request::allGet(const std::string &key)
{
return utils::getAll(this->getVars, key);
}
std::vector<std::string> Request::allPost(const std::string &key)
{
return utils::getAll(this->postVars, key);
}

Näytä tiedosto

@@ -34,6 +34,9 @@ class Request
std::string post(const std::string &key) const; std::string post(const std::string &key) const;
std::string cookie(const std::string &key) const; std::string cookie(const std::string &key) const;
std::string param(const std::string &key) const; std::string param(const std::string &key) const;
std::vector<std::string> allGet(const std::string &key);
std::vector<std::string> allPost(const std::string &key);
const std::vector<Cookie> &getCookies() const const std::vector<Cookie> &getCookies() const
{ {
return this->cookies; return this->cookies;

Näytä tiedosto

@@ -1,10 +1,9 @@
#include "revisionrenderer.h" #include "revisionrenderer.h"
#include "templatepage.h" #include "templatepage.h"
#include "dynamic/dynamiccontentpostlist.h" #include "dynamic/dynamiccontentpostlist.h"
#include "dynamic/dynamiccontentincludepage.h" #include "dynamic/dynamiccontentincludepage.h"
#include "dynamic/dynamiccontentgetvar.h" #include "dynamic/dynamiccontentgetvar.h"
#include "dynamic/dynamiccontentsetvar.h" #include "dynamic/dynamiccontentsetvar.h"
#include "dynamic/dynamicpostrenderer.h"
#include "parser.h" #include "parser.h"
#include "htmllink.h" #include "htmllink.h"
@@ -18,10 +17,9 @@ std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_
} }
if(key == "dynamic:includepage") if(key == "dynamic:includepage")
{ {
auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>(); auto includePage = this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>();
includePage->setArgument(std::string(value)); includePage->setArgument(std::string(value));
return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this, return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
std::placeholders::_1, std::placeholders::_2));
} }
if(key == "dynamic:setvar") if(key == "dynamic:setvar")
{ {
@@ -37,12 +35,6 @@ std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_
getVar->setArgument(std::string(value)); getVar->setArgument(std::string(value));
return getVar->render(); return getVar->render();
} }
if(key == "dynamic:postrenderer")
{
auto renderer = this->dynamicContentFactory.createDynamicContent<DynamicPostRenderer>();
renderer->setArgument(std::string(value));
return renderer->render();
}
return std::string{}; return std::string{};
} }
@@ -52,8 +44,7 @@ std::string RevisionRenderer::renderContent(std::string content)
dynamicVarsMap["createdon"] = utils::toISODate(time(NULL)); dynamicVarsMap["createdon"] = utils::toISODate(time(NULL));
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(time(NULL)); dynamicVarsMap["modifydatetime"] = utils::toISODateTime(time(NULL));
std::string resolvedContent = parser.parseDynamics( std::string resolvedContent = parser.parseDynamics(content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
} }
@@ -64,15 +55,15 @@ std::string RevisionRenderer::renderContent(const Revision &r, std::string_view
auto firstRevision = revisionDao->getRevisionForPage(r.page, 1); auto firstRevision = revisionDao->getRevisionForPage(r.page, 1);
if(!firstRevision) if(!firstRevision)
{ {
throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?"); throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?");
} }
dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp); dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp);
dynamicVarsMap["pagetitle"] = customTitle; dynamicVarsMap["pagetitle"] = customTitle;
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(r.timestamp); dynamicVarsMap["modifydatetime"] = utils::toISODateTime(r.timestamp);
std::string resolvedContent = parser.parseDynamics( std::string resolvedContent = parser.parseDynamics(r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2));
return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent);
} }

Näytä tiedosto

@@ -17,7 +17,7 @@ private:
Parser parser; Parser parser;
public: public:
RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider, Session &session) :dynamicContentFactory(templ, db, urlProvider, session) RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider) :dynamicContentFactory(templ, db, urlProvider)
{ {
this->db = &db; this->db = &db;
this->urlProvider = &urlProvider; this->urlProvider = &urlProvider;

Näytä tiedosto

@@ -44,19 +44,17 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths)
struct exile_policy *policy = exile_init_policy(); struct exile_policy *policy = exile_init_policy();
if(policy == NULL) if(policy == NULL)
{ {
Logger::error() << "Failed to init sandboxing policy"; Logger::error() << "Failed to init sandboxing policy (worker) ";
return false; return false;
} }
for(unsigned int i = 0; i < fsPaths.size(); i++) for(unsigned int i = 0; i < fsPaths.size(); i++)
{ {
std::string &path = fsPaths[i]; exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str());
if(path.size() > 0)
{
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, path.c_str());
}
} }
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->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread"); policy->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread");
if(exile_enable_policy(policy) != 0) if(exile_enable_policy(policy) != 0)
{ {

Näytä tiedosto

@@ -1,4 +1,4 @@
CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), title varchar(1024), lastrevision integer, listed integer DEFAULT 1, parent integer REFERENCES page(id), feedlisted 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),

Näytä tiedosto

@@ -18,7 +18,6 @@ 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 <filesystem>
#include "template.h" #include "template.h"
#include "varreplacer.h" #include "varreplacer.h"
#include "urlprovider.h" #include "urlprovider.h"
@@ -48,15 +47,9 @@ TemplatePage Template::getPage(const std::string &pagename)
std::string Template::getPartPath(std::string_view partname) std::string Template::getPartPath(std::string_view partname)
{ {
auto absolute_path = std::filesystem::canonical(std::filesystem::path{this->templatepath} / partname); // TODO: utils::concatPath? C++17 paths?
std::string result = absolute_path.string(); return this->templatepath + "/" + std::string(partname);
if(result.starts_with(this->templatepath))
{
return result;
}
return "";
} }
std::string Template::loadPartContent(std::string_view partname) std::string Template::loadPartContent(std::string_view partname)
{ {
std::string partpath = getPartPath(partname); std::string partpath = getPartPath(partname);

Näytä tiedosto

@@ -0,0 +1,446 @@
body
{
padding: 0;
margin: 0;
font-family: Verdana;
background-color: white;
display: grid;
min-height: 100vh;
grid-template-rows: auto 1fr auto;
grid-template-areas: "nav nav"
"main side"
"footer footer";
grid-template-columns: 1fr auto;
}
header
{
margin: 0;
paddin: 0;
}
h1, h2, h3
{
margin: 0;
padding: 0;
display: inline;
}
nav
{
padding: 0px;
margin: 0px;
display: flex;
background-color: #062463;
justify-content: space-between;
flex-wrap: wrap;
grid-area: nav;
}
nav ul
{
background-color: #062463;
color: white;
margin: 0;
padding: 0;
list-style-type: none;
display: flex;
align-items: center;
flex-wrap: wrap;
}
nav li
{
margin: 0;
padding: 0;
}
nav a, nav a:visited
{
padding: 10px;
text-decoration: none;
color: white;
display: block;
font-weight: bold;
text-align: center;
line-height: 100%;
}
nav a:hover, nav a:focus
{
padding: 10px;
text-decoration: none;
background-color: white;
color: #062463;
display: block;
font-weight: bold;
}
a, a:visited
{
color: #062463;;
}
a:hover
{
background-color: #062463;
color: white;
}
#content
{
padding: 15px;
font-family: monospace;
font-size: 14pt;
flex: 1;
grid-area: main
}
#sidebar
{
grid-area: side;
}
#sidebar ul
{
list-style-type: none;
}
#sidebar a, a:visited
{
color: #062463;
}
#sidebar a:hover
{
background-color: #062463;
color: white;
}
#content a, a:visited
{
color: #062463;
}
#content a:hover
{
background-color: #062463;
color: white;
}
footer
{
width: 100%;
display: block;
color: white;
background-color: #062463;
font-weight: bold;
grid-area: footer;
}
footer ul
{
background-color: #062463;
margin: 0px;
padding: 0px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: center;
}
footer li
{
margin: 0;
padding: 0;
display: inline-block;
line-height: 45px;
color: white;
font-weight: bold;
//flex: 1 1 0;
text-align: center;
}
footer a, a:visited
{
text-decoration: none;
color: white;
display: inline-block;
}
footer a:hover, ul#nav a:focus
{
text-decoration: none;
color: #062463;
background-color: white;
display: inline-block;
}
#cats
{
background-color: #062463;
}
.letter_search_result
{
text-decoration: underline;
font-weight: bold;
}
ol
{
counter-reset: item;
}
.indexlink
{
display: block;
}
.notexists
{
color: red !important;
font-weight: bold;
}
body
{
padding: 0;
margin: 0;
font-family: Verdana;
background-color: white;
display: grid;
min-height: 100vh;
grid-template-rows: auto 1fr auto;
grid-template-areas: "nav nav"
"main side"
"footer footer";
grid-template-columns: 1fr auto;
}
header
{
margin: 0;
paddin: 0;
}
h1, h2, h3
{
margin: 0;
padding: 0;
display: inline;
}
nav
{
padding: 0px;
margin: 0px;
display: flex;
background-color: #062463;
justify-content: space-between;
flex-wrap: wrap;
grid-area: nav;
}
nav ul
{
background-color: #062463;
color: white;
margin: 0;
padding: 0;
list-style-type: none;
display: flex;
align-items: center;
flex-wrap: wrap;
}
nav li
{
margin: 0;
padding: 0;
}
nav a, nav a:visited
{
padding: 10px;
text-decoration: none;
color: white;
display: block;
font-weight: bold;
text-align: center;
line-height: 100%;
}
nav a:hover, nav a:focus
{
padding: 10px;
text-decoration: none;
background-color: white;
color: #062463;
display: block;
font-weight: bold;
}
a, a:visited
{
color: #062463;;
}
a:hover
{
background-color: #062463;
color: white;
}
#content
{
padding: 15px;
font-family: monospace;
font-size: 14pt;
flex: 1;
grid-area: main
}
#sidebar
{
grid-area: side;
}
#sidebar ul
{
list-style-type: none;
}
#sidebar a, a:visited
{
color: #062463;
}
#sidebar a:hover
{
background-color: #062463;
color: white;
}
#content a, a:visited
{
color: #062463;
}
#content a:hover
{
background-color: #062463;
color: white;
}
footer
{
width: 100%;
display: block;
color: white;
background-color: #062463;
font-weight: bold;
grid-area: footer;
}
footer ul
{
background-color: #062463;
margin: 0px;
padding: 0px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: center;
}
footer li
{
margin: 0;
padding: 0;
display: inline-block;
line-height: 45px;
color: white;
font-weight: bold;
//flex: 1 1 0;
text-align: center;
}
footer a, a:visited
{
text-decoration: none;
color: white;
display: inline-block;
}
footer a:hover, ul#nav a:focus
{
text-decoration: none;
color: #062463;
background-color: white;
display: inline-block;
}
#cats
{
background-color: #062463;
}
.letter_search_result
{
text-decoration: underline;
font-weight: bold;
}
ol
{
counter-reset: item;
}
.indexlink
{
display: block;
}
.notexists
{
color: red !important;
font-weight: bold;
}
@media screen and (max-width: 768px)
{
#sidebar
{
display: none;
}
#footer li:nth-child(-n+2)
{
display: none;
}
#footer li:nth-of-type(3)
{
text-align: center;
width: 100%;
}
}

Näytä tiedosto

@@ -0,0 +1,236 @@
body
{
padding: 0;
margin: 0;
font-family: Verdana;
background-color: white;
display: grid;
min-height: 100vh;
grid-template-rows: auto 1fr auto;
grid-template-areas: "nav nav"
"main side"
"footer footer";
grid-template-columns: 1fr auto;
}
header
{
margin: 0;
paddin: 0;
}
h1, h2, h3
{
margin: 0;
padding: 0;
display: inline;
}
nav
{
padding: 0px;
margin: 0px;
display: flex;
background-color: #062463;
justify-content: space-between;
flex-wrap: wrap;
grid-area: nav;
}
nav ul
{
background-color: #062463;
color: white;
margin: 0;
padding: 0;
list-style-type: none;
display: flex;
align-items: center;
flex-wrap: wrap;
}
nav li
{
margin: 0;
padding: 0;
}
nav a, nav a:visited
{
padding: 10px;
text-decoration: none;
color: white;
display: block;
font-weight: bold;
text-align: center;
line-height: 100%;
}
nav a:hover, nav a:focus
{
padding: 10px;
text-decoration: none;
background-color: white;
color: #062463;
display: block;
font-weight: bold;
}
a, a:visited
{
color: #062463;;
}
a:hover
{
background-color: #062463;
color: white;
}
#content
{
padding: 15px;
font-family: monospace;
font-size: 14pt;
flex: 1;
grid-area: main
}
#sidebar
{
grid-area: side;
}
#sidebar ul
{
list-style-type: none;
}
#sidebar a, a:visited
{
color: #062463;
}
#sidebar a:hover
{
background-color: #062463;
color: white;
}
#content a, a:visited
{
color: #062463;
}
#content a:hover
{
background-color: #062463;
color: white;
}
footer
{
width: 100%;
display: block;
color: white;
background-color: #062463;
font-weight: bold;
grid-area: footer;
}
footer ul
{
background-color: #062463;
margin: 0px;
padding: 0px;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: center;
}
footer li
{
margin: 0;
padding: 0;
display: inline-block;
line-height: 45px;
color: white;
font-weight: bold;
text-align: center;
}
footer a, a:visited
{
text-decoration: none;
color: white;
display: inline-block;
}
footer a:hover, ul#nav a:focus
{
text-decoration: none;
color: #062463;
background-color: white;
display: inline-block;
}
#cats
{
background-color: #062463;
}
.letter_search_result
{
text-decoration: underline;
font-weight: bold;
}
ol
{
counter-reset: item;
}
.indexlink
{
display: block;
}
.notexists
{
color: red !important;
font-weight: bold;
}
#searchlink
{
display: none;
}
@media screen and (orientation: portrait)
{
#sidebar
{
display: none;
}
#footer li:nth-child(-n+2)
{
display: none;
}
#footer li:nth-of-type(3)
{
text-align: center;
width: 100%;
}
#searchlink {
display: inline;
}
#searchbar {
display: none;
}
}

Näytä tiedosto

@@ -63,12 +63,11 @@ std::string utils::html_xss(std::string_view str)
std::string utils::urldecode(std::string_view str) std::string utils::urldecode(std::string_view str)
{ {
std::string result; std::string result;
size_t size = str.length(); int size = str.length();
for(size_t i = 0; i < size; i++) for(int i = 0; i < size; i++)
{ {
char c = str[i]; char c = str[i];
if(c == '%' && (size - i > 1))
if(c == '%' && i + 2 < size)
{ {
char h[3]; char h[3];
h[0] = str[i + 1]; h[0] = str[i + 1];
@@ -214,40 +213,3 @@ std::string utils::trim(std::string_view view)
} }
return std::string{view}; return std::string{view};
} }
std::string utils::catv(std::string_view view)
{
std::string result;
result.reserve(view.length());
for(auto c : view)
{
if (!isascii(c))
{
result += "M-";
result += toascii(c);
}
else if(iscntrl(c))
{
result += '^';
result += c == '\177' ? '?': c | 0100;
}
else
{
result += c;
}
}
return result;
}
bool utils::is_printable_ascii(std::string view)
{
for(char c : view)
{
if( !(c >= ' ' && c <= '~'))
{
return false;
}
}
return true;
}

Näytä tiedosto

@@ -91,10 +91,6 @@ template <class T> inline std::string toString(const T &v)
} }
std::string trim(std::string_view view); std::string trim(std::string_view view);
std::string catv(std::string_view view);
bool is_printable_ascii(std::string view);
} // namespace utils } // namespace utils
#endif #endif