Déan comparáid idir tiomáintí
	
		
			46 Tiomáintí
		
	
	
		
			next
			...
			172129179e
		
	
	| Údar | SHA1 | Dáta | |
|---|---|---|---|
| 172129179e | |||
| 8603e55c59 | |||
| e326e09a36 | |||
| a71c3da129 | |||
| fbfe5510a1 | |||
| 78b9e5e043 | |||
| ef8eebdbaa | |||
| 7ef9d7f020 | |||
| d3bd5f79cc | |||
| 995a980d49 | |||
| 2ee760d9ca | |||
| ffeea8cfd1 | |||
| a81963181a | |||
| d18c0669ce | |||
| ecd45a61c8 | |||
| 2b1c3c71b7 | |||
| a1042720a7 | |||
| 6dbe8d34dc | |||
| 51b259f385 | |||
| 0cad11004f | |||
| 2102cf4e6b | |||
| 86890660f4 | |||
| 0325cdf936 | |||
| b0c715c4ea | |||
| 63a4437de7 | |||
| c88889b10b | |||
| 634cb2d7ee | |||
| 1c1416934b | |||
| 622ef5af6a | |||
| 5f83981d68 | |||
| b5b2a42839 | |||
| e217218a3f | |||
| 82c081385b | |||
| 91951abe9c | |||
| 9b35e43161 | |||
| 73a4e4c10f | |||
| 1e224fdac6 | |||
| fbca85e5ed | |||
| 15e4f081cc | |||
| e876b15c5d | |||
| 3e736db0ef | |||
| 03c5646858 | |||
| ba06d04a08 | |||
| 5bb3f55945 | |||
| 1ae5495e61 | |||
| 7bb7600d39 | 
							
								
								
									
										92
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,80 +1,84 @@ | ||||
| # qswiki | ||||
|  | ||||
| About | ||||
| ==== | ||||
| qswiki is a wiki software, intended for small wikis. Originally  | ||||
| implemented in C, it's now written in C++. | ||||
| ## About | ||||
| qswiki is a wiki software, intended for my needs. Originally  implemented in C, it's now written in C++. | ||||
|  | ||||
| History | ||||
| ==== | ||||
| A couple of years ago, I wanted to setup a personal wiki on my raspberry  | ||||
| pi. However, the distribution I used back then did not have a PHP package  | ||||
| ## Dude... why? | ||||
|  | ||||
| tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already | ||||
| started. | ||||
|  | ||||
| ### History | ||||
| Several years ago, I wanted to setup a personal wiki on my raspberry | ||||
| pi. However, the distribution I used back then did not have a PHP package | ||||
| for ARM. So instead of switching distributions or searching for other | ||||
| wikis that I could use, I decided I would write one in C. Yes,  | ||||
| that's an odd way  to approach the problem and indeed, I may have had too  | ||||
| much time back  then. Also, I wanted to see how it's like to write a  | ||||
| wikis that I could use, I simply decided I would write one in C. Yes, | ||||
| that's an odd way  to approach the problem and indeed, I may have had too | ||||
| much time back  then. Also, I wanted to see how it's like to write a | ||||
| "web app" in C and wanted to sharpen my C skills a little bit. | ||||
|  | ||||
| Of course, it's pretty straightforward at first. No really: Just use CGI.  | ||||
| And indeed, that would have been more than enough for my use cases.  | ||||
| Then I decided to play around and started using FastCGI (with the official  | ||||
| Of course, it's pretty straightforward at first. No really: Just use CGI | ||||
| and print your HTML to stdout.And indeed, that would have been more than enough for my use cases. | ||||
|  | ||||
| But then I decided to play around and started using FastCGI (with the official | ||||
| library from now  defunct fastcgi.com) and created a multi-threaded version. | ||||
| It initially  used a "pile of files database", but that became too painful,  | ||||
| It initially  used a "pile of files database", but that became too painful, | ||||
| so then I started using sqlite. | ||||
|  | ||||
| C++ | ||||
| --- | ||||
| Eventually, since it was mostly a playground for me, the code became  | ||||
| unmaintainable. Furthermore, I wanted something quick and given that  | ||||
| it was CGI, I didn't bother taking care of memory leaks.  | ||||
| After initiating a FastCGI interface, they became an issue and then the  | ||||
| Eventually, since it was mostly a playground for me, the code became | ||||
| unmaintainable. Furthermore, I initially wanted something quick and given that | ||||
| it was CGI, I didn't bother taking care of memory leaks. | ||||
| After initiating a FastCGI interface, they became an issue and then the | ||||
| task of avoiding memory leaks became too annoying. And of course, C does n | ||||
| ot include any "batteries" and while I could manage, this too was another  | ||||
| ot include any "batteries" and while I could manage, this too was another | ||||
| good reason. | ||||
|  | ||||
| Overall, I am just continuing the experiment with C++17 now. It's not  | ||||
| nearly as bad as you would expect perhaps. Some things are surprisingly  | ||||
| convenient even. Still, the standard library is lacking and  | ||||
| I would hope for a some better built-in Unicode support in future C++  | ||||
| Overall, I am just continuing the experiment with >=C++17 now. It's not | ||||
| nearly as bad as you would expect perhaps. Some things are surprisingly | ||||
| convenient even. Still, the standard library is lacking and | ||||
| I would hope for a some better built-in Unicode support in future C++ | ||||
| standards. | ||||
|  | ||||
| Features | ||||
| ======== | ||||
| To be fair, at this point it doesn't even have a "diff" between revisions  | ||||
| yet and does not have features that would make you prefer it over other  | ||||
| wikis. | ||||
|  | ||||
| ## Features | ||||
| Some essential features are lacking, such as a diff between revisions, | ||||
| user registration UI, etc. | ||||
|  | ||||
| It doesn't compete with any other software anyway. | ||||
|  | ||||
|  - CGI | ||||
|  - HTTP server using the header only library cpp-httplib. It's more  | ||||
|  portable and more "future-proof" than FastCGI (since the official website  | ||||
|  - HTTP server using the header only library [cpp-httplib](https://github.com/yhirose/cpp-httplib). It's more | ||||
|  portable and more "future-proof" than FastCGI (since the official website | ||||
|  disappeared, the library's future appears to be uncertain). | ||||
|  - Support for user accounts. Passwords are stored using PBKDF2. | ||||
|   sqlite database, but not too much of an effort to add other types of  | ||||
|   storage backends. sqlite is using the great header only library  | ||||
|   sqlite_modern_cpp | ||||
|   sqlite database, but not too much of an effort to add other types of | ||||
|   storage backends. sqlite is using the great header only library | ||||
|   [sqlite_modern_cpp](https://github.com/SqliteModernCpp) | ||||
|  - Relatively fine-grained permission system. | ||||
|  - Categories | ||||
|  - Templates | ||||
|  - FTS search | ||||
|  - Caching | ||||
|  - Blog-like functionality | ||||
|  - RSS/Atom feeds | ||||
|  | ||||
| Security | ||||
| ======== | ||||
| On Linux namespaces are used to restrict the process to only access | ||||
| files it needs. It doesn't have access to other paths in the system. | ||||
| In addition, Seccomp is used to restrict the syscalls the qswiki process | ||||
| can call.  As for "web security", all POST requests are centrally | ||||
| protected against CSRF attacks and all input is escaped against XSS  | ||||
| ## Security | ||||
| [exile.h](https://github.com/quitesimpleorg/exile.h) is used | ||||
| to restrict access to the files the wiki needs.  It doesn't have access to other paths | ||||
| in the system and the system calls that the qswiki process can make are restricted. | ||||
|  | ||||
| As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS | ||||
| attacks. | ||||
|  | ||||
| Building | ||||
| ======== | ||||
| ## Building | ||||
| Dependencies: | ||||
|   - cpp-httplib: https://github.com/yhirose/cpp-httplib | ||||
|   - SqliteModernCpp: https://github.com/SqliteModernCpp | ||||
|   - exile.h: https://gitea.quitesimple.org/crtxcr/exile.h | ||||
|   - sqlite3: https://sqlite.org/index.html | ||||
|      | ||||
|  | ||||
| The first three are header-only libraries that are included as a git submodule. The others must | ||||
| be installed, e. g. by using your distributions standard method. | ||||
|  | ||||
|   | ||||
							
								
								
									
										9
									
								
								cli.cpp
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								cli.cpp
									
									
									
									
									
								
							| @@ -141,10 +141,9 @@ std::pair<bool, std::string> CLIHandler::page_list([[maybe_unused]] const std::v | ||||
| 	QueryOption o; | ||||
| 	auto result = pageDao->getPageList(o); | ||||
| 	std::stringstream stream; | ||||
| 	for(std::string pagename : result) | ||||
| 	for(Page &page : result) | ||||
| 	{ | ||||
| 		Page p = pageDao->find(pagename).value(); | ||||
| 		stream << p.name << " " << p.pageid << " " << std::string(p.listed ? "listed" : "unlisted") << std::endl; | ||||
| 		stream << page.name << " " << page.pageid << " " << std::string(page.listed ? "listed" : "unlisted") << std::endl; | ||||
| 	} | ||||
| 	return {true, stream.str()}; | ||||
| } | ||||
| @@ -271,9 +270,9 @@ std::pair<bool, std::string> CLIHandler::category_show(const std::vector<std::st | ||||
| 	auto categoryDao = this->db->createCategoryDao(); | ||||
| 	auto members = categoryDao->fetchMembers(args.at(0), QueryOption{}); | ||||
| 	std::stringstream stream; | ||||
| 	for(std::string &member : members) | ||||
| 	for(Page &member : members) | ||||
| 	{ | ||||
| 		stream << member << std::endl; | ||||
| 		stream << member.name << std::endl; | ||||
| 	} | ||||
| 	return {true, stream.str()}; | ||||
| } | ||||
|   | ||||
| @@ -78,13 +78,16 @@ Config::Config(const std::map<std::string, std::string> &map) | ||||
| 	this->templatepath = required("templatepath"); | ||||
| 	this->urls.linkallcats = required("linkallcats"); | ||||
| 	this->urls.linkallpages = required("linkallpages"); | ||||
| 	this->urls.linkallpagesrendertype = required ("linkallpagesrendertype"); | ||||
| 	this->urls.linkcategory = required("linkcategory"); | ||||
| 	this->urls.linkcategoryrendertype = required("linkcategoryrendertype"); | ||||
| 	this->urls.linkdelete = required("linkdelete"); | ||||
| 	this->urls.linkedit = required("linkedit"); | ||||
| 	this->urls.linkhistory = required("linkhistory"); | ||||
| 	this->urls.linkindex = required("linkindex"); | ||||
| 	this->urls.linklogout = required("linklogout"); | ||||
| 	this->urls.linkpage = required("linkpage"); | ||||
| 	this->urls.linkpagebytitle = required("linkpagebytitle"); | ||||
| 	this->urls.linkrecent = required("linkrecent"); | ||||
| 	this->urls.linkrevision = required("linkrevision"); | ||||
| 	this->urls.linksettings = required("linksettings"); | ||||
| @@ -111,7 +114,7 @@ Config::Config(const std::map<std::string, std::string> &map) | ||||
|  | ||||
| 	this->templateprefix = "{qswiki:"; | ||||
|  | ||||
| 	this->max_payload_length = optional("max_payload_length", 10 * 1024 * 1024); | ||||
| 	this->max_payload_length = optional("max_payload_length", 60 * 1024 * 1024); | ||||
|  | ||||
| 	ConfigVariableResolver resolver{this->configmap}; | ||||
| 	this->configVarResolver = resolver; | ||||
|   | ||||
							
								
								
									
										3
									
								
								config.h
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								config.h
									
									
									
									
									
								
							| @@ -23,9 +23,11 @@ struct ConfigUrls | ||||
| 	std::string linkindex; | ||||
| 	std::string linkrecent; | ||||
| 	std::string linkallpages; | ||||
| 	std::string linkallpagesrendertype; | ||||
| 	std::string linkallcats; | ||||
| 	std::string linkshere; | ||||
| 	std::string linkpage; | ||||
| 	std::string linkpagebytitle; | ||||
| 	std::string linkrevision; | ||||
| 	std::string linkhistory; | ||||
| 	std::string linkedit; | ||||
| @@ -33,6 +35,7 @@ struct ConfigUrls | ||||
| 	std::string linkdelete; | ||||
| 	std::string linklogout; | ||||
| 	std::string linkcategory; | ||||
| 	std::string linkcategoryrendertype; | ||||
| 	std::string loginurl; | ||||
| 	std::string linkrecentsort; | ||||
| 	std::string actionurl; | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| #include <optional> | ||||
| #include "queryoption.h" | ||||
| #include "../category.h" | ||||
|  | ||||
| #include "../page.h" | ||||
| class CategoryDao | ||||
| { | ||||
|   public: | ||||
| @@ -14,7 +14,8 @@ class CategoryDao | ||||
| 	virtual std::vector<std::string> fetchList(QueryOption o) = 0; | ||||
| 	virtual std::optional<Category> find(std::string name) = 0; | ||||
| 	virtual void deleteCategory(std::string name) = 0; | ||||
| 	virtual std::vector<std::string> fetchMembers(std::string name, QueryOption o) = 0; | ||||
| 	virtual std::vector<Page> fetchMembers(std::string name, QueryOption o) = 0; | ||||
| 	virtual ~CategoryDao() = default; | ||||
| }; | ||||
|  | ||||
| #endif // CATEGORYDAO_H | ||||
|   | ||||
| @@ -94,9 +94,10 @@ std::vector<std::string> CategoryDaoSqlite::fetchList(QueryOption o) | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| std::vector<std::string> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o) | ||||
|  | ||||
| std::vector<Page> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o) | ||||
| { | ||||
| 	std::vector<std::string> result; | ||||
| 	std::vector<Page> result; | ||||
|  | ||||
| 	SqliteQueryOption queryOption{o}; | ||||
| 	std::string queryoptions = | ||||
| @@ -104,11 +105,18 @@ std::vector<std::string> CategoryDaoSqlite::fetchMembers(std::string name, Query | ||||
|  | ||||
| 	try | ||||
| 	{ | ||||
| 		auto query = *db << "SELECT page.name AS name FROM categorymember INNER JOIN page ON page.id = " | ||||
| 		auto query = *db << "SELECT  page.id, page.name AS name, page.title, page.lastrevision, page.visible FROM categorymember INNER JOIN page ON page.id = " | ||||
| 							"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " + | ||||
| 								queryoptions | ||||
| 						 << name; | ||||
| 		query >> [&](std::string p) { result.push_back(p); }; | ||||
| 		query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool visible) { | ||||
| 			Page p; | ||||
| 			p.name = name; | ||||
| 			p.pageid = id; | ||||
| 			p.title = title; | ||||
| 			p.current_revision = lastrevision; | ||||
| 			p.listed = visible; | ||||
| 			result.push_back(p); }; | ||||
| 	} | ||||
| 	catch(const sqlite::exceptions::no_rows &e) | ||||
| 	{ | ||||
|   | ||||
| @@ -3,12 +3,13 @@ | ||||
|  | ||||
| #include "categorydao.h" | ||||
| #include "sqlitedao.h" | ||||
| #include "../page.h" | ||||
| class CategoryDaoSqlite : public CategoryDao, protected SqliteDao | ||||
| { | ||||
|   public: | ||||
| 	CategoryDaoSqlite(); | ||||
| 	std::vector<std::string> fetchList(QueryOption o) override; | ||||
| 	std::vector<std::string> fetchMembers(std::string name, QueryOption o) override; | ||||
| 	std::vector<Page> fetchMembers(std::string name, QueryOption o) override; | ||||
| 	void save(const Category &c) override; | ||||
| 	void deleteCategory(std::string name) override; | ||||
| 	std::optional<Category> find(std::string name) override; | ||||
|   | ||||
| @@ -13,8 +13,9 @@ class PageDao | ||||
| 	virtual bool exists(std::string page) const = 0; | ||||
| 	virtual bool exists(unsigned int id) const = 0; | ||||
| 	virtual std::optional<Page> find(std::string name) = 0; | ||||
| 	virtual std::optional<Page> findByTitle(std::string title) = 0; | ||||
| 	virtual std::optional<Page> find(unsigned int id) = 0; | ||||
| 	virtual std::vector<std::string> getPageList(QueryOption option) = 0; | ||||
| 	virtual std::vector<Page> getPageList(QueryOption option) = 0; | ||||
| 	virtual std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) = 0; | ||||
| 	virtual void deletePage(std::string page) = 0; | ||||
| 	virtual void save(const Page &page) = 0; | ||||
|   | ||||
| @@ -52,6 +52,26 @@ std::optional<Page> PageDaoSqlite::find(std::string name) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::optional<Page> PageDaoSqlite::findByTitle(std::string title) | ||||
| { | ||||
| 	Page result; | ||||
| 	try | ||||
| 	{ | ||||
| 		auto ps = *db << "SELECT id, name, title, lastrevision, visible FROM page WHERE title = ?"; | ||||
| 		ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed); | ||||
| 	} | ||||
| 	catch(const sqlite::errors::no_rows &e) | ||||
| 	{ | ||||
| 		return {}; | ||||
| 	} | ||||
| 	catch(sqlite::sqlite_exception &e) | ||||
| 	{ | ||||
| 		throwFrom(e); | ||||
| 	} | ||||
|  | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::optional<Page> PageDaoSqlite::find(unsigned int id) | ||||
| { | ||||
| 	Page result; | ||||
| @@ -107,21 +127,28 @@ void PageDaoSqlite::save(const Page &page) | ||||
| 		throwFrom(e); | ||||
| 	} | ||||
| } | ||||
| std::vector<std::string> PageDaoSqlite::getPageList(QueryOption option) | ||||
|  | ||||
| std::vector<Page> PageDaoSqlite::getPageList(QueryOption option) | ||||
| { | ||||
| 	std::vector<std::string> result; | ||||
| 	std::vector<Page> result; | ||||
|  | ||||
| 	try | ||||
| 	{ | ||||
|  | ||||
| 		std::string queryOption = SqliteQueryOption(option) | ||||
| 									  .setOrderByColumn("lower(name)") | ||||
| 									  .setVisibleColumnName("visible") | ||||
| 									  .setPrependWhere(true) | ||||
| 									  .build(); | ||||
| 		std::string query = "SELECT name FROM page " + queryOption; | ||||
| 		std::string query = "SELECT id, name, title, lastrevision, visible FROM page " + queryOption; | ||||
| 		*db << query >> [&](unsigned int pageid, std::string name, std::string title,unsigned int current_revision, bool visible ) { | ||||
|  | ||||
| 		*db << query >> [&](std::string name) { result.push_back(name); }; | ||||
| 			Page tmp; | ||||
| 			tmp.pageid = pageid; | ||||
| 			tmp.name = name; | ||||
| 			tmp.title = title; | ||||
| 			tmp.current_revision = current_revision; | ||||
| 			tmp.listed = visible; | ||||
| 			result.push_back(tmp); }; | ||||
| 	} | ||||
| 	catch(const sqlite::errors::no_rows &e) | ||||
| 	{ | ||||
|   | ||||
| @@ -20,8 +20,9 @@ class PageDaoSqlite : public PageDao, protected SqliteDao | ||||
| 	bool exists(std::string name) const override; | ||||
| 	void save(const Page &page) override; | ||||
| 	std::optional<Page> find(std::string name) override; | ||||
| 	std::optional<Page> findByTitle(std::string title) override; | ||||
| 	std::optional<Page> find(unsigned int id) override; | ||||
| 	std::vector<std::string> getPageList(QueryOption option) override; | ||||
| 	std::vector<Page> getPageList(QueryOption option) override; | ||||
| 	std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) override; | ||||
| 	using SqliteDao::SqliteDao; | ||||
| 	int fetchPageId(std::string pagename); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ class PermissionsDao | ||||
| 	PermissionsDao(); | ||||
| 	virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0; | ||||
| 	virtual void save(std::string pagename, std::string username, Permissions perms) = 0; | ||||
| 	virtual ~PermissionsDao() = default; | ||||
| }; | ||||
|  | ||||
| #endif // PERMISSIONSDAO_H | ||||
|   | ||||
| @@ -129,9 +129,8 @@ std::optional<Revision> RevisionDaoSqlite::getCurrentForPage(std::string pagenam | ||||
| 	try | ||||
| 	{ | ||||
| 		auto query = *db << "SELECT (SELECT username FROM user WHERE id = author), comment, content, " | ||||
| 							"strftime('%s',creationtime), page, revisionid FROM revision WHERE page = (SELECT id FROM " | ||||
| 							"page WHERE name = ? ) AND revisionid = (SELECT lastrevision FROM page WHERE name = ?)"; | ||||
| 		query << pagename << pagename; | ||||
| 							"strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid"; | ||||
| 		query << pagename; | ||||
| 		query >> | ||||
| 			std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); | ||||
| 	} | ||||
| @@ -155,7 +154,7 @@ std::optional<Revision> RevisionDaoSqlite::getRevisionForPage(std::string pagena | ||||
| 		auto query = | ||||
| 			*db | ||||
| 			<< "SELECT (SELECT username FROM user WHERE id = author), comment, content, strftime('%s',creationtime), " | ||||
| 			   "page, revisionid FROM revision WHERE page = (SELECT id FROM page WHERE name = ? ) AND revisionid = ?"; | ||||
| 			   "page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND revisionid = ?  "; | ||||
| 		query << pagename << revision; | ||||
| 		query >> | ||||
| 			std::tie(result.author, result.comment, result.content, result.timestamp, result.page, result.revision); | ||||
|   | ||||
| @@ -37,6 +37,8 @@ class SqliteDao | ||||
|  | ||||
| 	bool execBool(sqlite::database_binder &binder) const; | ||||
| 	int execInt(sqlite::database_binder &binder) const; | ||||
|  | ||||
| 	virtual ~SqliteDao() = default; | ||||
| }; | ||||
|  | ||||
| #endif // SQLITEDAO_H | ||||
|   | ||||
| @@ -11,9 +11,15 @@ class DynamicContent | ||||
| 	Database *database; | ||||
| 	UrlProvider *urlProvider; | ||||
|  | ||||
| 	std::string argument; | ||||
|  | ||||
|   public: | ||||
| 	DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider); | ||||
| 	virtual std::string render() = 0; | ||||
| 	virtual void setArgument(std::string argument) | ||||
| 	{ | ||||
| 		this->argument = argument; | ||||
| 	} | ||||
| 	virtual ~DynamicContent() | ||||
| 	{ | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										28
									
								
								dynamic/dynamiccontentfactory.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										28
									
								
								dynamic/dynamiccontentfactory.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #ifndef DYNAMICCONTENTFACTORY_H | ||||
| #define DYNAMICCONTENTFACTORY_H | ||||
|  | ||||
| #include "dynamiccontent.h" | ||||
|  | ||||
| class DynamicContentFactory | ||||
| { | ||||
| private: | ||||
| 	Template *templ; | ||||
| 	Database *db; | ||||
| 	UrlProvider *urlProvider; | ||||
| 	 | ||||
| public: | ||||
| 	DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider) | ||||
| 	{ | ||||
| 		this->templ = &templ; | ||||
| 		this->db = &db; | ||||
| 		this->urlProvider = &urlProvider; | ||||
| 	} | ||||
| 	 | ||||
| 	template <class T> inline std::shared_ptr<T> createDynamicContent() | ||||
| 	{ | ||||
| 		return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider); | ||||
| 	} | ||||
| 	 | ||||
| 	 | ||||
| }; | ||||
| #endif // DYNAMICCONTENTFACTORY_H_INCLUDED | ||||
							
								
								
									
										11
									
								
								dynamic/dynamiccontentgetvar.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										11
									
								
								dynamic/dynamiccontentgetvar.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,11 @@ | ||||
| #include "dynamiccontentgetvar.h" | ||||
|  | ||||
| std::string DynamicContentGetVar::render() | ||||
| { | ||||
| 	return (*this->map)[this->argument]; | ||||
| } | ||||
|  | ||||
| void DynamicContentGetVar::setMap(std::map<std::string, std::string> &map) | ||||
| { | ||||
| 	this->map = ↦ | ||||
| } | ||||
							
								
								
									
										19
									
								
								dynamic/dynamiccontentgetvar.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										19
									
								
								dynamic/dynamiccontentgetvar.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #ifndef DYNAMICCONTENTGETVAR_H | ||||
| #define DYNAMICCONTENTGETVAR_H | ||||
|  | ||||
| #include "dynamiccontent.h" | ||||
| class DynamicContentGetVar : public DynamicContent | ||||
| { | ||||
|   private: | ||||
| 	std::map<std::string, std::string> *map; | ||||
|  | ||||
|   public: | ||||
| 	using DynamicContent::DynamicContent; | ||||
|  | ||||
| 	// DynamicContent interface | ||||
|   public: | ||||
| 	std::string render(); | ||||
| 	void setMap(std::map<std::string, std::string> &map); | ||||
| }; | ||||
|  | ||||
| #endif // DYNAMICCONTENTGETVAR_H | ||||
							
								
								
									
										16
									
								
								dynamic/dynamiccontentincludepage.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										16
									
								
								dynamic/dynamiccontentincludepage.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #include "dynamiccontentincludepage.h" | ||||
| #include "../parser.h" | ||||
| std::string DynamicContentIncludePage::render() | ||||
| { | ||||
| 	auto revisionDao = this->database->createRevisionDao(); | ||||
| 	auto rev = revisionDao->getCurrentForPage(this->argument); | ||||
| 	if(rev) | ||||
| 	{ | ||||
| 		/* Quite dirty */ | ||||
| 		if(rev->content.find("[cmd:allowinclude]1[") != std::string::npos) | ||||
| 		{ | ||||
| 			return rev->content; | ||||
| 		} | ||||
| 	} | ||||
| 	return {}; | ||||
| } | ||||
							
								
								
									
										12
									
								
								dynamic/dynamiccontentincludepage.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										12
									
								
								dynamic/dynamiccontentincludepage.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #ifndef DYNAMICCONTENTINCLUDEPAGE_H | ||||
| #define DYNAMICCONTENTINCLUDEPAGE_H | ||||
| #include "dynamiccontent.h" | ||||
| class DynamicContentIncludePage : public DynamicContent | ||||
| { | ||||
|   public: | ||||
| 	using DynamicContent::DynamicContent; | ||||
|  | ||||
| 	std::string render(); | ||||
| }; | ||||
|  | ||||
| #endif // DYNAMICCONTENTINCLUDEPAGE_H | ||||
| @@ -1,11 +1,6 @@ | ||||
| #include <chrono> | ||||
| #include "dynamiccontentpostlist.h" | ||||
|  | ||||
| void DynamicContentPostList::setCategory(std::string catname) | ||||
| { | ||||
| 	this->catname = catname; | ||||
| } | ||||
|  | ||||
| std::string DynamicContentPostList::render() | ||||
| { | ||||
| 	auto categoryDao = this->database->createCategoryDao(); | ||||
| @@ -13,12 +8,12 @@ std::string DynamicContentPostList::render() | ||||
| 	auto revisionDao = this->database->createRevisionDao(); | ||||
| 	QueryOption option; | ||||
| 	option.includeInvisible = false; | ||||
| 	auto members = categoryDao->fetchMembers(this->catname, option); | ||||
| 	auto members = categoryDao->fetchMembers(this->argument, option); | ||||
| 	std::vector<std::pair<std::string, time_t>> pageList; | ||||
| 	for(std::string &member : members) | ||||
| 	for(const Page &member : members) | ||||
| 	{ | ||||
| 		auto revision = revisionDao->getRevisionForPage(member, 1); | ||||
| 		pageList.push_back({member, revision->timestamp}); | ||||
| 		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; }); | ||||
|   | ||||
| @@ -4,12 +4,8 @@ | ||||
| #include "dynamiccontent.h" | ||||
| class DynamicContentPostList : public DynamicContent | ||||
| { | ||||
|   private: | ||||
| 	std::string catname; | ||||
|  | ||||
|   public: | ||||
| 	using DynamicContent::DynamicContent; | ||||
| 	void setCategory(std::string catname); | ||||
| 	std::string render() override; | ||||
| }; | ||||
|  | ||||
|   | ||||
							
								
								
									
										21
									
								
								dynamic/dynamiccontentsetvar.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										21
									
								
								dynamic/dynamiccontentsetvar.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #include "dynamiccontentsetvar.h" | ||||
|  | ||||
| std::string DynamicContentSetVar::render() | ||||
| { | ||||
| 	auto result = utils::split(this->argument, '='); | ||||
| 	if(result.size() == 2) | ||||
| 	{ | ||||
| 		this->map->emplace(std::make_pair(result[0], result[1])); | ||||
| 	} | ||||
| 	return {}; | ||||
| } | ||||
|  | ||||
| void DynamicContentSetVar::setArgument(std::string argument) | ||||
| { | ||||
| 	this->argument = argument; | ||||
| } | ||||
|  | ||||
| void DynamicContentSetVar::setMap(std::map<std::string, std::string> &map) | ||||
| { | ||||
| 	this->map = ↦ | ||||
| } | ||||
							
								
								
									
										17
									
								
								dynamic/dynamiccontentsetvar.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										17
									
								
								dynamic/dynamiccontentsetvar.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,17 @@ | ||||
| #ifndef DYNAMCCONTENTPUSHVAR_H | ||||
| #define DYNAMCCONTENTPUSHVAR_H | ||||
| #include "dynamiccontent.h" | ||||
| class DynamicContentSetVar : public DynamicContent | ||||
| { | ||||
|   private: | ||||
| 	std::map<std::string, std::string> *map; | ||||
|  | ||||
|   public: | ||||
| 	using DynamicContent::DynamicContent; | ||||
|  | ||||
| 	std::string render(); | ||||
| 	void setArgument(std::string argument); | ||||
| 	void setMap(std::map<std::string, std::string> &map); | ||||
| }; | ||||
|  | ||||
| #endif // DYNAMCCONTENTPUSHVAR_H | ||||
| @@ -1,5 +1,8 @@ | ||||
| #ifndef HTTPGATEWAY_H | ||||
| #define HTTPGATEWAY_H | ||||
|  | ||||
| #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 65536 | ||||
|  | ||||
| #include <httplib.h> | ||||
| #include "gatewayinterface.h" | ||||
| #include "../requestworker.h" | ||||
|   | ||||
							
								
								
									
										0
									
								
								grouper.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										0
									
								
								grouper.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
								
								
									
										26
									
								
								grouper.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										26
									
								
								grouper.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #include "utils.h" | ||||
|  | ||||
| template<class G, class V, class C> | ||||
| class Grouper | ||||
| { | ||||
| 	std::map<G, std::vector<const V*>, C> results; | ||||
| public: | ||||
|  | ||||
| 	Grouper(C c) | ||||
| 	{ | ||||
| 		results = std::map<G, std::vector<const V*>, C>(c); | ||||
| 	} | ||||
|  | ||||
| 	void group(const std::function<G(const V&)> &map, const std::vector<V> &values) | ||||
| 	{ | ||||
| 		for(const V &v : values) | ||||
| 		{ | ||||
| 			results[map(v)].push_back(&v); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	std::map<G, std::vector<const V*>, C> &getResults() | ||||
| 	{ | ||||
| 		return this->results; | ||||
| 	} | ||||
| }; | ||||
| @@ -19,6 +19,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| */ | ||||
| #include "handlerallpages.h" | ||||
| #include "../grouper.h" | ||||
| #include "../pagelistrenderer.h" | ||||
|  | ||||
| Response HandlerAllPages::handleRequest(const Request &r) | ||||
| { | ||||
| @@ -27,17 +29,21 @@ Response HandlerAllPages::handleRequest(const Request &r) | ||||
| 		Response response; | ||||
| 		auto pageDao = this->database->createPageDao(); | ||||
| 		QueryOption qo = queryOption(r); | ||||
| 		auto resultList = pageDao->getPageList(qo); | ||||
| 		if(resultList.size() == 0) | ||||
| 		std::vector<Page> pageList = pageDao->getPageList(qo); | ||||
| 		if(pageList.size() == 0) | ||||
| 		{ | ||||
| 			return errorResponse("No pages", "This wiki does not have any pages yet"); | ||||
| 		} | ||||
| 		TemplatePage searchPage = this->templ->getPage("allpages"); | ||||
| 		std::string body = this->templ->renderSearch(resultList); | ||||
| 		searchPage.setVar("pagelist", body); | ||||
| 		searchPage.setVar("title", createPageTitle("All pages")); | ||||
| 		setGeneralVars(searchPage); | ||||
| 		response.setBody(searchPage.render()); | ||||
| 		 | ||||
| 		PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); | ||||
| 		TemplatePage allPages = this->templ->getPage("allpages"); | ||||
| 		allPages.setVar("pagelistcontent", pagelistrender.render(pageList, r.get("rendertype"))); | ||||
| 		allPages.setVar("pagelistletterlink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_LETTER)); | ||||
| 		allPages.setVar("pagelistcreationdatelink",  this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_CREATIONDATE)); | ||||
| 				 | ||||
| 		allPages.setVar("title", createPageTitle("All pages")); | ||||
| 		setGeneralVars(allPages); | ||||
| 		response.setBody(allPages.render()); | ||||
| 		response.setStatus(200); | ||||
| 		return response; | ||||
| 	} | ||||
|   | ||||
| @@ -19,6 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
| */ | ||||
| #include "handlercategory.h" | ||||
| #include "../pagelistrenderer.h" | ||||
|  | ||||
| Response HandlerCategory::handleRequest(const Request &r) | ||||
| { | ||||
| @@ -34,8 +35,12 @@ Response HandlerCategory::handleRequest(const Request &r) | ||||
| 		QueryOption qo = queryOption(r); | ||||
| 		auto resultList = categoryDao->fetchMembers(categoryname, qo); | ||||
| 		TemplatePage searchPage = this->templ->getPage("show_category"); | ||||
| 		std::string body = this->templ->renderSearch(resultList); | ||||
| 		searchPage.setVar("pagelist", body); | ||||
| 		PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); | ||||
|  | ||||
| 		std::string body = pagelistrender.render(resultList, r.get("rendertype")); | ||||
| 		searchPage.setVar("pagelistcontent", body); | ||||
| 		searchPage.setVar("pagelistletterlink", this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_LETTER)); | ||||
| 		searchPage.setVar("pagelistcreationdatelink",  this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_CREATIONDATE)); | ||||
| 		searchPage.setVar("categoryname", categoryname); | ||||
| 		setGeneralVars(searchPage); | ||||
| 		searchPage.setVar("title", createPageTitle("Category: " + categoryname)); | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #include "handlerfeedgenerator.h" | ||||
| #include "../parser.h" | ||||
| #include "../revisionrenderer.h" | ||||
| std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries( | ||||
| 	std::vector<std::string> categories) | ||||
| { | ||||
| @@ -10,7 +11,9 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch | ||||
| 	QueryOption option; | ||||
| 	option.includeInvisible = false; | ||||
| 	// option.limit = 20; | ||||
| 	std::set<std::string> members; | ||||
| 	 | ||||
| 	auto comppage = [](const Page &a, const Page &b) { return a.name < b.name; }; | ||||
| 	std::set<Page, decltype(comppage)> members (comppage); | ||||
| 	if(categories.empty()) | ||||
| 	{ | ||||
| 		auto pages = pageDao->getPageList(option); | ||||
| @@ -21,15 +24,18 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch | ||||
| 		auto categoryDao = this->database->createCategoryDao(); | ||||
| 		for(std::string cat : categories) | ||||
| 		{ | ||||
| 			if(!categoryDao->find(cat)) | ||||
| 			{ | ||||
| 				throw std::runtime_error("No such category"); | ||||
| 			} | ||||
| 			auto catmembers = categoryDao->fetchMembers(cat, option); | ||||
| 			std::copy(catmembers.begin(), catmembers.end(), std::inserter(members, members.end())); | ||||
| 		} | ||||
| 	} | ||||
| 	for(const std::string &member : members) | ||||
| 	for(const Page &member : members) | ||||
| 	{ | ||||
| 		auto page = pageDao->find(member).value(); | ||||
| 		auto revision = revisionDao->getRevisionForPage(page.name, 1).value(); | ||||
| 		result.push_back({page, revision}); | ||||
| 		auto revision = revisionDao->getRevisionForPage(member.name, 1).value(); | ||||
| 		result.push_back({member, revision}); | ||||
| 	} | ||||
| 	std::sort(result.begin(), result.end(), | ||||
| 			  [](EntryRevisionPair &a, EntryRevisionPair &b) { return a.second.timestamp > b.second.timestamp; }); | ||||
| @@ -43,8 +49,8 @@ std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetch | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| Response HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerator::EntryRevisionPair> &entries, | ||||
| 											std::string filter) | ||||
| std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerator::EntryRevisionPair> &entries, | ||||
| 											   std::string filter) | ||||
| { | ||||
|  | ||||
| 	std::stringstream stream; | ||||
| @@ -56,11 +62,14 @@ Response HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerat | ||||
| 	auto revisionDao = this->database->createRevisionDao(); | ||||
| 	auto pageDao = this->database->createPageDao(); | ||||
|  | ||||
| 	std::string subtitle = filter; | ||||
| 	if(utils::trim(filter).empty()) | ||||
| 	{ | ||||
| 		filter = "All pages"; | ||||
| 		subtitle = "All pages"; | ||||
| 	} | ||||
|  | ||||
| 	RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||
|  | ||||
| 	for(const EntryRevisionPair &entry : entries) | ||||
| 	{ | ||||
| 		const Page &page = entry.first; | ||||
| @@ -72,28 +81,26 @@ Response HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerat | ||||
| 			newestPublished = initialRevision.timestamp; | ||||
| 		} | ||||
| 		std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z"; | ||||
| 		std::string entryUpdated = utils::formatLocalDate(current.timestamp, dateformat) + "Z"; | ||||
| 		std::string entryurl = | ||||
| 			this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)}); | ||||
| 		TemplatePage atomentry = this->templ->getPage("feeds/atomentry"); | ||||
| 		atomentry.setVar("entrytitle", utils::html_xss(page.title)); | ||||
| 		atomentry.setVar("entrytitle", 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))); | ||||
| 		atomentry.setVar("entryupdated", entryUpdated); | ||||
| 		atomentry.setVar("entrycontent", utils::html_xss(revisionRenderer.renderContent(current, page.title))); | ||||
| 		stream << atomentry.render(); | ||||
| 	} | ||||
| 	stream << atomfooter; | ||||
| 	TemplatePage atomheader = this->templ->getPage("feeds/atomheader"); | ||||
| 	atomheader.setVar("subtitle", filter); | ||||
| 	atomheader.setVar("atomfeeduniqueid", utils::html_xss(this->urlProvider->atomFeed(filter))); | ||||
| 	atomheader.setVar("subtitle", subtitle); | ||||
| 	std::string selflink = utils::html_xss(this->urlProvider->atomFeed(filter)); | ||||
| 	atomheader.setVar("atomfeeduniqueid", selflink); | ||||
| 	atomheader.setVar("atomselflink", selflink); | ||||
| 	atomheader.setVar("atomfeedupdate", utils::formatLocalDate(newestPublished, dateformat) + "Z"); | ||||
|  | ||||
| 	Response result; | ||||
| 	result.setStatus(200); | ||||
| 	result.setContentType("application/atom+xml"); | ||||
| 	result.setBody(atomheader.render() + stream.str()); | ||||
| 	return result; | ||||
| 	return atomheader.render() + stream.str(); | ||||
| } | ||||
|  | ||||
| Response HandlerFeedGenerator::handleRequest(const Request &r) | ||||
| @@ -103,12 +110,26 @@ Response HandlerFeedGenerator::handleRequest(const Request &r) | ||||
| 	{ | ||||
| 		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); | ||||
| 			Response result; | ||||
| 			result.setStatus(200); | ||||
| 			result.setContentType("application/atom+xml"); | ||||
| 			std::string cacheKey = "feed:atom:" + filter; | ||||
| 			auto cached = this->cache->get(cacheKey); | ||||
| 			if(cached) | ||||
| 			{ | ||||
| 				result.setBody(cached.value()); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				auto entries = fetchEntries(utils::split(categories, ',')); | ||||
| 				std::string feed = generateAtom(entries, filter); | ||||
| 				result.setBody(feed); | ||||
| 				this->cache->put(cacheKey, feed); | ||||
| 			} | ||||
| 			response = result; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
|   | ||||
| @@ -9,7 +9,7 @@ class HandlerFeedGenerator : public Handler | ||||
|  | ||||
|   protected: | ||||
| 	std::vector<EntryRevisionPair> fetchEntries(std::vector<std::string> categories); | ||||
| 	Response generateAtom(const std::vector<EntryRevisionPair> &entries, std::string atomtitle); | ||||
| 	std::string generateAtom(const std::vector<EntryRevisionPair> &entries, std::string atomtitle); | ||||
| 	Response generateRss(const std::vector<EntryRevisionPair> &entries); | ||||
|  | ||||
|   public: | ||||
|   | ||||
| @@ -27,7 +27,18 @@ Response HandlerPage::handle(const Request &r) | ||||
| 	auto pageDao = this->database->createPageDao(); | ||||
| 	if(pagename.empty()) | ||||
| 	{ | ||||
| 		return errorResponse("No page given", "No page given to request"); | ||||
| 		std::string title = r.get("title"); | ||||
| 		if(title.empty()) | ||||
| 		{ | ||||
| 			return errorResponse("No page given", "No page given to request"); | ||||
| 		} | ||||
| 		title = utils::strreplace(title, "-", " "); | ||||
| 		auto page = pageDao->findByTitle(title); | ||||
| 		if(!page) | ||||
| 		{ | ||||
| 			return errorResponse("No page by such title", "No page with such title exists"); | ||||
| 		} | ||||
| 		pagename = page->name; | ||||
| 	} | ||||
|  | ||||
| 	if(pageMustExist() && !pageDao->exists(pagename)) | ||||
|   | ||||
| @@ -45,6 +45,7 @@ Response HandlerPageDelete::handleRequest(PageDao &pageDao, std::string pagename | ||||
| 		{ | ||||
| 			pageDao.deletePage(pagename); | ||||
| 			this->cache->removePrefix("page:"); // TODO: overkill? | ||||
| 			this->cache->removePrefix("feed:"); | ||||
| 			return Response::redirectTemporarily(this->urlProvider->index()); | ||||
| 		} | ||||
| 		TemplatePage delPage = this->templ->getPage("page_deletion"); | ||||
|   | ||||
| @@ -23,6 +23,7 @@ SOFTWARE. | ||||
| #include "../request.h" | ||||
|  | ||||
| #include "../parser.h" | ||||
| #include "../revisionrenderer.h" | ||||
| bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page) | ||||
| { | ||||
| 	return effectivePermissions(page).canEdit(); | ||||
| @@ -41,7 +42,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 		return errorResponse("No permission", "You don't have permission to create new pages"); | ||||
| 	} | ||||
| 	auto revisiondao = this->database->createRevisionDao(); | ||||
| 	auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename); | ||||
| 	auto revision = revisiondao->getCurrentForPage(pagename); | ||||
| 	std::string body; | ||||
|  | ||||
| 	unsigned int current_revision = 0; | ||||
| @@ -50,6 +51,15 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 		body = revision->content; | ||||
| 		current_revision = revision->revision; | ||||
| 	} | ||||
| 	std::string from = r.get("frompage"); | ||||
| 	if(!from.empty()) | ||||
| 	{ | ||||
| 		if(!effectivePermissions(from).canRead()) | ||||
| 		{ | ||||
| 			return this->errorResponse("Permission denied", "No access permissions, so you can't use this page as a template"); | ||||
| 		} | ||||
| 		body = revisiondao->getCurrentForPage(from)->content; | ||||
| 	} | ||||
| 	if(r.getRequestMethod() == "POST") | ||||
| 	{ | ||||
| 		if(r.post("do") == "submit") | ||||
| @@ -101,6 +111,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 				pageDao.setCategories(pagename, cats); | ||||
| 				this->database->commitTransaction(); | ||||
| 				this->cache->removePrefix("page:"); // TODO: overkill? | ||||
| 				this->cache->removePrefix("feed:"); | ||||
| 			} | ||||
| 			catch(const DatabaseException &e) | ||||
| 			{ | ||||
| @@ -114,12 +125,16 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 		{ | ||||
| 			std::string newContent = r.post("content"); | ||||
| 			Parser parser; | ||||
| 			std::string title = parser.extractCommand("pagetitle", newContent); | ||||
| 			TemplatePage templatePage = this->templ->getPage("page_creation_preview"); | ||||
| 			templatePage.setVar("actionurl", urlProvider->editPage(pagename)); | ||||
| 			templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent)); | ||||
|  | ||||
| 			RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||
|  | ||||
| 			templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent)); | ||||
| 			templatePage.setVar("content", newContent); | ||||
| 			setPageVars(templatePage, pagename); | ||||
| 			templatePage.setVar("title", createPageTitle("Preview: " + pagename)); | ||||
| 			templatePage.setVar("title", createPageTitle("Preview: " + title)); | ||||
| 			templatePage.setVar("comment", r.post("comment")); | ||||
| 			Response response; | ||||
| 			response.setBody(templatePage.render()); | ||||
|   | ||||
| @@ -24,6 +24,11 @@ SOFTWARE. | ||||
| #include "../parser.h" | ||||
| #include "../htmllink.h" | ||||
| #include "../dynamic/dynamiccontentpostlist.h" | ||||
| #include "../dynamic/dynamiccontentincludepage.h" | ||||
| #include "../dynamic/dynamiccontentsetvar.h" | ||||
| #include "../dynamic/dynamiccontentgetvar.h" | ||||
| #include "../revisionrenderer.h" | ||||
|  | ||||
| bool HandlerPageView::canAccess(std::string page) | ||||
| { | ||||
| 	return effectivePermissions(page).canRead(); | ||||
| @@ -86,19 +91,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | ||||
|  | ||||
| 	std::optional<Revision> revision; | ||||
| 	std::string templatepartname; | ||||
| 	auto revisionDao = this->database->createRevisionDao(); | ||||
| 	try | ||||
| 	{ | ||||
| 		if(revisionid > 0) | ||||
| 		{ | ||||
| 			if(!effectivePermissions(pagename).canSeePageHistory()) | ||||
| 			{ | ||||
| 				auto current = this->database->createRevisionDao()->getCurrentForPage(pagename); | ||||
| 				auto current = revisionDao->getCurrentForPage(pagename); | ||||
| 				if(current && current->revision > revisionid) | ||||
| 				{ | ||||
| 					return errorResponse("Error", "You are not allowed to view older revisions of this page"); | ||||
| 				} | ||||
| 			} | ||||
| 			revision = this->database->createRevisionDao()->getRevisionForPage(pagename, revisionid); | ||||
| 			revision = revisionDao->getRevisionForPage(pagename, revisionid); | ||||
| 			if(!revision) | ||||
| 			{ | ||||
| 				return errorResponse("Revision not found", "No such revision found"); | ||||
| @@ -118,7 +124,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 					return r; | ||||
| 				} | ||||
| 			} | ||||
| 			revision = this->database->createRevisionDao()->getCurrentForPage(pagename); | ||||
| 			revision = revisionDao->getCurrentForPage(pagename); | ||||
| 			templatepartname = "page_view"; | ||||
| 		} | ||||
| 	} | ||||
| @@ -128,57 +134,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | ||||
| 		return errorResponse("Database error", "While trying to fetch revision, a database error occured"); | ||||
| 	} | ||||
|  | ||||
| 	TemplatePage page = this->templ->getPage(templatepartname); | ||||
|  | ||||
| 	Parser parser; | ||||
| 	Response result; | ||||
| 	result.setStatus(200); | ||||
| 	std::string indexcontent; | ||||
| 	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) | ||||
| 	{ | ||||
| 		indexcontent = createIndexContent(parser, revision->content); | ||||
| 		parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content, dynamicParseCallback); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		std::string cachekeyindexcontent = "page:indexcontent:" + pagename; | ||||
| 		std::string cachekeyparsedcontent = "page:parsedcontent:" + pagename; | ||||
| 		auto cachedindexcontent = this->cache->get(cachekeyindexcontent); | ||||
| 		auto cachedparsedcontent = this->cache->get(cachekeyparsedcontent); | ||||
| 		if(cachedindexcontent) | ||||
| 		{ | ||||
| 			indexcontent = *cachedindexcontent; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			indexcontent = createIndexContent(parser, revision->content); | ||||
| 			this->cache->put(cachekeyindexcontent, indexcontent); | ||||
| 		} | ||||
| 		if(cachedparsedcontent) | ||||
| 		{ | ||||
| 			parsedcontent = *cachedparsedcontent; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content, dynamicParseCallback); | ||||
| 			this->cache->put(cachekeyparsedcontent, parsedcontent); | ||||
| 		} | ||||
| 	} | ||||
| 	std::string revisionstr = std::to_string(revision->revision); | ||||
| 	RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||
|  | ||||
|  | ||||
| 	std::string customtitle = parser.extractCommand("pagetitle", revision->content); | ||||
| 	std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle); | ||||
| 	/* TODO: Dynamic includes not considered, probably fine in practise */ | ||||
| 	std::string indexcontent = createIndexContent(parser, revision->content); | ||||
|  | ||||
| 	std::string revisionstr = std::to_string(revision->revision); | ||||
| 	TemplatePage page = this->templ->getPage(templatepartname); | ||||
| 	page.setVar("content", parsedcontent); | ||||
| 	page.setVar("index", indexcontent); | ||||
| 	page.setVar("editedby", revision->author); | ||||
|   | ||||
| @@ -23,6 +23,10 @@ class IParser | ||||
| 	} | ||||
| 	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::string parseDynamics( | ||||
| 		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(){}; | ||||
|   | ||||
							
								
								
									
										66
									
								
								pagelistrenderer.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										66
									
								
								pagelistrenderer.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,66 @@ | ||||
| #include "pagelistrenderer.h" | ||||
| #include "grouper.h" | ||||
|  | ||||
| PageListRenderer::PageListRenderer(Template &templ, UrlProvider &provider, Database &database) | ||||
| { | ||||
| 	this->templ = &templ; | ||||
| 	this->urlProvider = &provider; | ||||
| 	this->database = &database; | ||||
| } | ||||
|  | ||||
| std::string PageListRenderer::render(const std::vector<Page> &pagelist, std::string type) const | ||||
| { | ||||
| 	TemplatePage pagelistrendergroup = this->templ->loadResolvedPart("pagelistrender_group"); | ||||
| 	TemplatePage pagelistlink = this->templ->loadResolvedPart("pagelistrender_link"); | ||||
|  | ||||
|  | ||||
| 	std::function<bool(const std::string &, const std::string &)> lesser = [](const std::string &a, const std::string &b) -> bool { | ||||
| 		return a < b; | ||||
| 	}; | ||||
|  | ||||
| 	std::function<bool(const std::string &, const std::string &)> greater = [](const std::string &a, const std::string &b) -> bool { | ||||
| 		return a > b; | ||||
| 	}; | ||||
|  | ||||
| 	using Grouper = Grouper<std::string, Page, std::function<bool(const std::string &,const std::string &)>>; | ||||
|  | ||||
| 	Grouper grouper = (type == "letter") ? Grouper(lesser) : Grouper(greater); | ||||
| 	if(type == "letter") | ||||
| 	{ | ||||
| 		auto az = [&](const Page &p) -> std::string { | ||||
| 			int upper = toupper(p.title[0]); // TODO: this is not unicode safe. | ||||
| 			return { (char)upper }; | ||||
| 		}; | ||||
| 		grouper.group(az, pagelist); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		auto revisionDao = this->database->createRevisionDao(); | ||||
| 		auto creationdate = [&revisionDao](const Page &p) -> std::string { | ||||
| 			return utils::formatLocalDate(revisionDao->getRevisionForPage(p.name, 1).value().timestamp, "%Y-%m"); | ||||
| 		}; | ||||
| 		grouper.group(creationdate, pagelist); | ||||
| 	} | ||||
|  | ||||
| 	std::stringstream stream; | ||||
| 	std::string lastGroup = ""; | ||||
|  | ||||
| 	for(auto &entry : grouper.getResults()) | ||||
| 	{ | ||||
| 		std::string groupname = entry.first; | ||||
| 		const std::vector<const Page*> &pages = entry.second; | ||||
| 		if(lastGroup != groupname) | ||||
| 		{ | ||||
| 			lastGroup = groupname; | ||||
| 			pagelistrendergroup.setVar("groupname", groupname); | ||||
| 			stream << pagelistrendergroup.render(); | ||||
| 		} | ||||
| 		for(const Page *p : pages) | ||||
| 		{ | ||||
| 			pagelistlink.setVar("inner", p->title); | ||||
| 			pagelistlink.setVar("href", this->urlProvider->page(p->name)); | ||||
| 			stream << pagelistlink.render(); | ||||
| 		} | ||||
| 	} | ||||
| 	return stream.str(); | ||||
| } | ||||
							
								
								
									
										27
									
								
								pagelistrenderer.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										27
									
								
								pagelistrenderer.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,27 @@ | ||||
| #ifndef PAGELISTRENDERER_H | ||||
| #define PAGELISTRENDERER_H | ||||
| #include "utils.h" | ||||
| #include "page.h" | ||||
| #include "template.h" | ||||
| #include "htmllink.h" | ||||
| #include "urlprovider.h" | ||||
| #include "database/database.h" | ||||
|  | ||||
| class PageListRenderer | ||||
| { | ||||
| private: | ||||
| 	Template *templ; | ||||
| 	UrlProvider *urlProvider; | ||||
| 	Database *database; | ||||
|  | ||||
| public: | ||||
| 	PageListRenderer(Template &templ, UrlProvider &provider, Database &database); | ||||
|  | ||||
| 	std::string render(const std::vector<Page> &pages, std::string type) const; | ||||
|  | ||||
| 	inline static const std::string RENDER_GROUP_BY_LETTER { "letter" }; | ||||
| 	inline static const std::string RENDER_GROUP_BY_CREATIONDATE { "creationdate" }; | ||||
|  | ||||
|  | ||||
| }; | ||||
| #endif | ||||
							
								
								
									
										53
									
								
								parser.cpp
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								parser.cpp
									
									
									
									
									
								
							| @@ -30,7 +30,8 @@ SOFTWARE. | ||||
| std::vector<Headline> Parser::extractHeadlines(const std::string &content) const | ||||
| { | ||||
| 	std::vector<Headline> result; | ||||
| 	std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])"; | ||||
|  | ||||
| 	std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])"; | ||||
| 	std::regex headerfinder(reg); | ||||
| 	auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder); | ||||
| 	auto end = std::sregex_iterator(); | ||||
| @@ -40,7 +41,7 @@ std::vector<Headline> Parser::extractHeadlines(const std::string &content) const | ||||
| 		auto smatch = *it; | ||||
| 		Headline h; | ||||
| 		h.level = utils::toUInt(smatch.str(1)); | ||||
| 		h.title = smatch.str(2); | ||||
| 		h.title = smatch.str(3); | ||||
| 		result.push_back(h); | ||||
| 	} | ||||
| 	return result; | ||||
| @@ -116,13 +117,38 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider | ||||
| 	return htmllink.render(); | ||||
| } | ||||
|  | ||||
| std::string Parser::processImage(std::smatch &match) const | ||||
| { | ||||
| 	std::string tag = match.str(1); | ||||
| 	std::string inside = match.str(2); | ||||
| 	std::vector<std::string> splitted = utils::split(inside, '|'); | ||||
| 	std::string width; | ||||
| 	std::string height; | ||||
| 	std::string src; | ||||
| 	if(splitted.size() == 3) | ||||
| 	{ | ||||
| 		width = splitted[0]; | ||||
| 		height = splitted[1]; | ||||
| 		src = splitted[2]; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		src = splitted[0]; | ||||
| 	} | ||||
| 	if(!width.empty() && !height.empty()) | ||||
| 	{ | ||||
| 		return "<img src=\"" + src + "\" width=\"" + width + "\" height=\"" + height + "\"/>"; | ||||
| 	} | ||||
| 	return "<img src=\"" + src + "\"/>"; | ||||
| } | ||||
|  | ||||
| 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; | ||||
| 	// 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|cmd:pagetitle|category|dynamic:postlist)*?\]((\s|\S)*?)\[/\1])"); | ||||
| 		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])"); | ||||
| 	result = utils::regex_callback_replacer( | ||||
| 		tagfinder, content, | ||||
| 		[&](std::smatch &match) | ||||
| @@ -130,7 +156,10 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s | ||||
| 			std::string tag = match.str(1); | ||||
| 			std::string content = match.str(2); | ||||
| 			std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"}; | ||||
| 			content = parse(pagedao, provider, content, callback); | ||||
| 			if(tag != "code" && tag != "blockquote") | ||||
| 			{ | ||||
| 				content = parse(pagedao, provider, content, callback); | ||||
| 			} | ||||
| 			if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace)) | ||||
| 			{ | ||||
| 				return "<" + tag + ">" + content + "</" + tag + ">"; | ||||
| @@ -141,12 +170,28 @@ std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, const s | ||||
| 					pagedao, provider, | ||||
| 					match); // TODO: recreate this so we don't check inside the function stuff again | ||||
| 			} | ||||
| 			if(tag == "img") | ||||
| 			{ | ||||
| 				return this->processImage(match); | ||||
| 			} | ||||
| 			if(tag[0] == 'h') | ||||
| 			{ | ||||
| 				return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">"; | ||||
| 			} | ||||
| 			if(tag == "code" || tag == "blockquote") | ||||
| 			{ | ||||
| 				return "<pre><" + tag + ">"+ utils::strreplace(content, "\r\n", "\n") + "</"+tag+"></pre>"; | ||||
| 			} | ||||
| 			return callback(tag, content); | ||||
| 		}); | ||||
| 	result = utils::strreplace(result, "\r\n", "<br>"); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| std::string Parser::parseDynamics(const std::string &content, | ||||
| 								  const std::function<std::string(std::string_view, std::string_view)> &callback) const | ||||
| { | ||||
| 	std::regex tagfinder(R"(\[(dynamic:\w+)*?\]((\s|\S)*?)\[/\1])"); | ||||
| 	return utils::regex_callback_replacer(tagfinder, content, | ||||
| 										  [&](std::smatch &match) { return callback(match.str(1), match.str(2)); }); | ||||
| } | ||||
|   | ||||
							
								
								
									
										4
									
								
								parser.h
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								parser.h
									
									
									
									
									
								
							| @@ -6,6 +6,7 @@ class Parser : public IParser | ||||
| { | ||||
|   private: | ||||
| 	std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; | ||||
| 	std::string processImage(std::smatch &match) const; | ||||
|  | ||||
|   public: | ||||
| 	std::string extractCommand(std::string cmdname, const std::string &content) const; | ||||
| @@ -15,6 +16,9 @@ class Parser : public IParser | ||||
| 	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; | ||||
| 	std::string parseDynamics( | ||||
| 		const std::string &content, | ||||
| 		const std::function<std::string(std::string_view, std::string_view)> &callback) const override; | ||||
|  | ||||
| 	using IParser::IParser; | ||||
| }; | ||||
|   | ||||
| @@ -40,7 +40,7 @@ std::pair<std::string, std::string> Request::createPairFromVar(std::string var) | ||||
| 	else | ||||
| 	{ | ||||
| 		std::string key = var.substr(0, equal); | ||||
| 		std::string val = utils::html_xss(var.substr(equal + 1)); | ||||
| 		std::string val = utils::html_xss(utils::urldecode(var.substr(equal + 1))); | ||||
| 		return std::make_pair(std::move(key), std::move(val)); | ||||
| 	} | ||||
| } | ||||
| @@ -75,7 +75,7 @@ void Request::initPostMap(const std::string &url) | ||||
| void Request::initCookies(const std::string &cookiestr) | ||||
| { | ||||
| 	// TODO: find out what it really should be, ";" or "; "? | ||||
| 	std::regex regex { ";+\\s?" }; | ||||
| 	std::regex regex{";+\\s?"}; | ||||
| 	auto cookiesplitted = utils::split(cookiestr, regex); | ||||
| 	for(const std::string &part : cookiesplitted) | ||||
| 	{ | ||||
|   | ||||
							
								
								
									
										67
									
								
								revisionrenderer.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										67
									
								
								revisionrenderer.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,67 @@ | ||||
|  #include "revisionrenderer.h" | ||||
| #include "templatepage.h" | ||||
| #include "dynamic/dynamiccontentpostlist.h" | ||||
| #include "dynamic/dynamiccontentincludepage.h" | ||||
| #include "dynamic/dynamiccontentgetvar.h" | ||||
| #include "dynamic/dynamiccontentsetvar.h" | ||||
| #include "parser.h" | ||||
| #include "htmllink.h" | ||||
|  | ||||
| std::string RevisionRenderer::dynamicCallback(std::string_view key, std::string_view value) | ||||
| { | ||||
| 	if(key == "dynamic:postlist") | ||||
| 	{ | ||||
| 		auto postlist = this->dynamicContentFactory.createDynamicContent<DynamicContentPostList>(); | ||||
| 		postlist->setArgument(std::string(value)); | ||||
| 		return postlist->render(); | ||||
| 	} | ||||
| 	if(key == "dynamic:includepage") | ||||
| 	{ | ||||
| 		auto includePage =  this->dynamicContentFactory.createDynamicContent<DynamicContentIncludePage>(); | ||||
| 		includePage->setArgument(std::string(value)); | ||||
| 		return parser.parseDynamics(includePage->render(), std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); | ||||
| 	} | ||||
| 	if(key == "dynamic:setvar") | ||||
| 	{ | ||||
| 		auto setVar = this->dynamicContentFactory.createDynamicContent<DynamicContentSetVar>(); | ||||
| 		setVar->setMap(dynamicVarsMap); | ||||
| 		setVar->setArgument(std::string(value)); | ||||
| 		return setVar->render(); | ||||
| 	} | ||||
| 	if(key == "dynamic:getvar") | ||||
| 	{ | ||||
| 		auto getVar = this->dynamicContentFactory.createDynamicContent<DynamicContentGetVar>(); | ||||
| 		getVar->setMap(dynamicVarsMap); | ||||
| 		getVar->setArgument(std::string(value)); | ||||
| 		return getVar->render(); | ||||
| 	} | ||||
| 	return std::string{}; | ||||
| } | ||||
|  | ||||
| std::string RevisionRenderer::renderContent(std::string content) | ||||
| { | ||||
| 	dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content); | ||||
| 	dynamicVarsMap["createdon"] = utils::toISODate(time(NULL)); | ||||
|  | ||||
| 	std::string resolvedContent = parser.parseDynamics(content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); | ||||
|  | ||||
| 	return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); | ||||
| } | ||||
|  | ||||
| std::string RevisionRenderer::renderContent(const Revision &r, std::string_view customTitle) | ||||
| { | ||||
| 	auto revisionDao = this->db->createRevisionDao(); | ||||
| 	auto firstRevision = revisionDao->getRevisionForPage(r.page, 1); | ||||
| 	if(!firstRevision) | ||||
| 	{ | ||||
|  		throw std::runtime_error("Could not get first revision for page, which is odd. Solar flares?"); | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	dynamicVarsMap["createdon"] = utils::toISODate(firstRevision.value().timestamp); | ||||
| 	dynamicVarsMap["pagetitle"] = customTitle; | ||||
|  | ||||
| 	std::string resolvedContent = parser.parseDynamics(r.content, std::bind(&RevisionRenderer::dynamicCallback, this, std::placeholders::_1, std::placeholders::_2)); | ||||
|  | ||||
| 	return parser.parse(*this->db->createPageDao(), *this->urlProvider, resolvedContent); | ||||
| } | ||||
							
								
								
									
										29
									
								
								revisionrenderer.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										29
									
								
								revisionrenderer.h
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #ifndef REVISIONRENDERER_H | ||||
| #define REVISIONRENDERER_H | ||||
| #include "revision.h" | ||||
| #include "templatepage.h" | ||||
| #include "dynamic/dynamiccontentfactory.h" | ||||
| #include "iparser.h" | ||||
| #include "parser.h" | ||||
| class RevisionRenderer | ||||
| { | ||||
| private: | ||||
| 	DynamicContentFactory dynamicContentFactory; | ||||
| 	Database *db; | ||||
| 	UrlProvider *urlProvider; | ||||
| 	std::map<std::string, std::string> dynamicVarsMap; | ||||
| 	 | ||||
| 	std::string dynamicCallback(std::string_view key, std::string_view value); | ||||
| 	Parser parser; | ||||
| 	 | ||||
| public: | ||||
| 	RevisionRenderer(Template &templ, Database &db, UrlProvider &urlProvider) :dynamicContentFactory(templ, db, urlProvider) | ||||
| 	{ | ||||
| 		this->db = &db; | ||||
| 		this->urlProvider = &urlProvider; | ||||
| 	} | ||||
| 	std::string renderContent(std::string content); | ||||
| 	std::string renderContent(const Revision &r, std::string_view customTitle); | ||||
| }; | ||||
|  | ||||
| #endif // REVISIONRENDERER_H | ||||
| @@ -55,10 +55,7 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths) | ||||
| 	policy->not_dumpable = 1; | ||||
| 	policy->no_new_privs = 1; | ||||
| 	policy->mount_path_policies_to_chroot = 1; | ||||
| 	policy->vow_promises = EXILE_SYSCALL_VOW_STDIO | EXILE_SYSCALL_VOW_WPATH | EXILE_SYSCALL_VOW_CPATH | | ||||
| 						   EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_INET | EXILE_SYSCALL_VOW_UNIX | | ||||
| 						   EXILE_SYSCALL_VOW_THREAD; | ||||
|  | ||||
| 	policy->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread"); | ||||
| 	if(exile_enable_policy(policy) != 0) | ||||
| 	{ | ||||
| 		Logger::error() << "Sandbox: Activation of exile failed!"; | ||||
|   | ||||
 Nuashonraíodh fomhodúl submodules/cpp-httplib: b324921c1a...c0b461a3b7
									
								
							 Nuashonraíodh fomhodúl submodules/exile.h: f2ca26010a...e711a1d53a
									
								
							| @@ -1,6 +1,6 @@ | ||||
| {qswiki:include:general_header} | ||||
| <div id="content" style="margin-top: 10px;"> | ||||
| <h2>All pages</h2> | ||||
| {qswiki:var:pagelist} | ||||
| {qswiki:include:pagelistrender} | ||||
| </div> | ||||
| {qswiki:include:general_footer} | ||||
| {qswiki:include:general_footer} | ||||
|   | ||||
| @@ -3,5 +3,6 @@ | ||||
|     <link href="{qswiki:var:entryurl}"/> | ||||
|     <id>{qswiki:var:entryid}</id> | ||||
|     <published>{qswiki:var:entrypublished}</published> | ||||
|     <updated>{qswiki:var:entryupdated}</updated> | ||||
|     <content type="html">{qswiki:var:entrycontent}</content> | ||||
| </entry> | ||||
|   | ||||
| @@ -5,4 +5,5 @@ | ||||
|   </author> | ||||
|   <title>{qswiki:config:wikiname} - {qswiki:var:subtitle}</title> | ||||
|   <id>{qswiki:var:atomfeeduniqueid}</id> | ||||
|   <link rel="self" href="{qswiki:var:atomselflink}"/> | ||||
|   <updated>{qswiki:var:atomfeedupdate}</updated> | ||||
|   | ||||
| @@ -5,8 +5,10 @@ | ||||
| 		<li style="font-size: 10pt">Powered by qswiki</li> | ||||
| 	</ul> | ||||
| </footer> | ||||
| <script src="{qswiki:config:highlightjspath}"></script> | ||||
| <script> | ||||
| {qswiki:include:js_session_refresh} | ||||
| hljs.highlightAll(); | ||||
| </script> | ||||
| </body> | ||||
| </html> | ||||
| </html> | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| <meta http-equiv="content-type" content="text/html; charset=UTF-8"> | ||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| <link rel="stylesheet" type="text/css" href="{qswiki:config:csspath}"> | ||||
| <link rel="stylesheet" href="{qswiki:config:highlightjsstyle}"> | ||||
|  | ||||
| <title>{qswiki:var:title}</title> | ||||
| <body> | ||||
| <nav> | ||||
|   | ||||
							
								
								
									
										3
									
								
								template/quitesimple/pagelistrender
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										3
									
								
								template/quitesimple/pagelistrender
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1,3 @@ | ||||
| {qswiki:include:pagelistrender_header} | ||||
| {qswiki:var:pagelistcontent} | ||||
| {qswiki:include:pagelistrender_footer} | ||||
							
								
								
									
										0
									
								
								template/quitesimple/pagelistrender_footer
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										0
									
								
								template/quitesimple/pagelistrender_footer
									
									
									
									
									
										Comhad gnáth
									
								
							
							
								
								
									
										1
									
								
								template/quitesimple/pagelistrender_group
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										1
									
								
								template/quitesimple/pagelistrender_group
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1 @@ | ||||
| <div class="letter_search_result">{qswiki:var:groupname}</div> | ||||
							
								
								
									
										1
									
								
								template/quitesimple/pagelistrender_header
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										1
									
								
								template/quitesimple/pagelistrender_header
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1 @@ | ||||
| Sort by: <a href="{qswiki:var:pagelistletterlink}">A-Z</a> - <a href="{qswiki:var:pagelistcreationdatelink}">Creation date</a> | ||||
							
								
								
									
										1
									
								
								template/quitesimple/pagelistrender_link
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										1
									
								
								template/quitesimple/pagelistrender_link
									
									
									
									
									
										Comhad gnáth
									
								
							| @@ -0,0 +1 @@ | ||||
| <a href="{qswiki:var:href}">{qswiki:var:inner}</a><br> | ||||
| @@ -1,6 +1,6 @@ | ||||
| {qswiki:include:general_header} | ||||
| <main id="content"> | ||||
| <h2>Category: {qswiki:var:categoryname}</h2> | ||||
| {qswiki:var:pagelist} | ||||
| {qswiki:include:pagelistrender} | ||||
| </main> | ||||
| {qswiki:include:general_footer} | ||||
| {qswiki:include:general_footer} | ||||
|   | ||||
| @@ -53,6 +53,11 @@ std::string UrlProvider::allPages() | ||||
| 	return config->linkallpages; | ||||
| } | ||||
|  | ||||
| std::string UrlProvider::allPages(std::string rendertype) | ||||
| { | ||||
| 	return replaceSingleVar(config->linkallpagesrendertype, "type", rendertype); | ||||
| } | ||||
|  | ||||
| std::string UrlProvider::allCats() | ||||
| { | ||||
| 	return config->linkallcats; | ||||
| @@ -63,6 +68,11 @@ std::string UrlProvider::page(std::string pagename) | ||||
| 	return replaceOnlyPage(config->linkpage, pagename); | ||||
| } | ||||
|  | ||||
| std::string UrlProvider::pageByTitle(std::string title) | ||||
| { | ||||
| 	return replaceSingleVar(config->linkpagebytitle, "title", utils::strreplace(title, " ", "-")); | ||||
| } | ||||
|  | ||||
| std::string UrlProvider::linksHere(std::string pagename) | ||||
| { | ||||
| 	return replaceOnlyPage(config->linkshere, pagename); | ||||
| @@ -116,6 +126,16 @@ std::string UrlProvider::category(std::string catname) | ||||
| { | ||||
| 	return replaceSingleVar(config->linkcategory, "category", catname); | ||||
| } | ||||
|  | ||||
| std::string UrlProvider::category(std::string catname, std::string rendertype) | ||||
| { | ||||
| 	Varreplacer replace("{"); | ||||
| 	replace.addKeyValue("category", catname); | ||||
| 	replace.addKeyValue("type", rendertype); | ||||
| 	return replace.parse(config->linkcategoryrendertype); | ||||
| } | ||||
|  | ||||
|  | ||||
| std::string UrlProvider::login(std::string page) | ||||
| { | ||||
| 	return replaceOnlyPage(config->loginurl, page); | ||||
|   | ||||
| @@ -22,11 +22,14 @@ class UrlProvider | ||||
| 	std::string recentSorted(unsigned int limit, unsigned int offset, unsigned int sort); | ||||
|  | ||||
| 	std::string allPages(); | ||||
|  | ||||
| 	std::string allPages(std::string rendertype); | ||||
| 	 | ||||
| 	std::string allCats(); | ||||
|  | ||||
| 	std::string page(std::string pagename); | ||||
|  | ||||
| 	std::string pageByTitle(std::string title); | ||||
|  | ||||
| 	std::string linksHere(std::string pagename); | ||||
|  | ||||
| 	std::string pageHistory(std::string pagename); | ||||
| @@ -46,7 +49,8 @@ class UrlProvider | ||||
| 	std::string refreshSession(); | ||||
|  | ||||
| 	std::string category(std::string catname); | ||||
|  | ||||
| 	std::string category(std::string catname, std::string rendertype); | ||||
| 	 | ||||
| 	std::string login(std::string page); | ||||
|  | ||||
| 	std::string rootUrl(); | ||||
|   | ||||
| @@ -136,11 +136,10 @@ std::string utils::getenv(const std::string &key) | ||||
|  | ||||
| std::string utils::readCompleteFile(std::string_view filepath) | ||||
| { | ||||
|  | ||||
| 	std::fstream stream(std::string{filepath}); | ||||
| 	if(!stream.is_open()) | ||||
| 	{ | ||||
| 		throw std::runtime_error("stream is not open"); | ||||
| 		throw std::runtime_error("utils::readCompleteFile(): stream is not open"); | ||||
| 	} | ||||
| 	std::stringstream ss; | ||||
| 	ss << stream.rdbuf(); | ||||
|   | ||||
		Tagairt in Eagrán Nua
	
	Cuir bac ar úsáideoir