比较提交
	
		
			78 次代码提交
		
	
	
		
			WIP/cpp20
			...
			172129179e
		
	
	| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
| 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 | |||
| f5eb36e7bb | |||
| c891b36339 | |||
| d17e596563 | |||
| 761471f243 | |||
| 9ac0ad0ccd | |||
| c30e09d44d | |||
| bcc3737d88 | |||
| 9520aabe5c | |||
| 4854ea85f2 | |||
| 16c352c6af | |||
| f7cf06cdd5 | |||
| ac793c6d39 | |||
| a524674149 | |||
| a4a45d9add | |||
| 44c27ed8b4 | |||
| 433b5da2bb | |||
| c5435c52f4 | |||
| b2a7ea4031 | |||
| 1d5bf80710 | |||
| ca0c8a94fb | |||
| 5870102aa9 | |||
| 32544c8f68 | |||
| d0e7ff0a8c | |||
| 696ff9b7e7 | |||
| 5570154113 | |||
| 4f6bcd27b4 | |||
| bbe74a2c50 | |||
| 5db9305408 | |||
| c90e26a374 | |||
| b297498ca9 | |||
| fdcef18861 | |||
| 75268e0073 | 
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -4,6 +4,6 @@ | |||||||
| [submodule "submodules/cpp-httplib"] | [submodule "submodules/cpp-httplib"] | ||||||
| 	path = submodules/cpp-httplib | 	path = submodules/cpp-httplib | ||||||
| 	url = https://github.com/yhirose/cpp-httplib | 	url = https://github.com/yhirose/cpp-httplib | ||||||
| [submodule "submodules/qssb.h"] | [submodule "submodules/exile.h"] | ||||||
| 	path = submodules/qssb.h | 	path = submodules/exile.h | ||||||
| 	url = https://gitea.quitesimple.org/crtxcr/qssb.h.git | 	url = https://gitea.quitesimple.org/crtxcr/exile.h.git | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Makefile
									
									
									
									
									
								
							| @@ -3,7 +3,7 @@ CPPSTD=c++20 | |||||||
| CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra | CXXFLAGS=-std=$(CPPSTD) -O0 -g -no-pie -pipe -MMD -Wall -Wextra | ||||||
| RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra | RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra | ||||||
| LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs | LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs | ||||||
| INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h | INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/exile.h | ||||||
|  |  | ||||||
| CXX=g++ | CXX=g++ | ||||||
|  |  | ||||||
| @@ -14,6 +14,7 @@ SOURCES+=$(wildcard handlers/*.cpp) | |||||||
| SOURCES+=$(wildcard database/*.cpp) | SOURCES+=$(wildcard database/*.cpp) | ||||||
| SOURCES+=$(wildcard cache/*.cpp) | SOURCES+=$(wildcard cache/*.cpp) | ||||||
| SOURCES+=$(wildcard sandbox/*.cpp) | SOURCES+=$(wildcard sandbox/*.cpp) | ||||||
|  | SOURCES+=$(wildcard dynamic/*.cpp) | ||||||
|  |  | ||||||
| HEADERS=$(wildcard *.h) | HEADERS=$(wildcard *.h) | ||||||
| HEADERS+=$(wildcard gateway/*.h) | HEADERS+=$(wildcard gateway/*.h) | ||||||
| @@ -21,7 +22,7 @@ HEADERS+=$(wildcard handlers/*.h) | |||||||
| HEADERS+=$(wildcard database/*.h) | HEADERS+=$(wildcard database/*.h) | ||||||
| HEADERS+=$(wildcard cache/*.h) | HEADERS+=$(wildcard cache/*.h) | ||||||
| HEADERS+=$(wildcard sandbox/*.h) | HEADERS+=$(wildcard sandbox/*.h) | ||||||
|  | HEADERS+=$(wildcard dynamic/*.h) | ||||||
|  |  | ||||||
| OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES)) | OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES)) | ||||||
| WIKIOBJECTS=$(filter-out test.o, $(OBJECTS)) | WIKIOBJECTS=$(filter-out test.o, $(OBJECTS)) | ||||||
| @@ -48,8 +49,12 @@ profile: LDFLAGS+= -pg | |||||||
| release: qswiki | release: qswiki | ||||||
| profile: qswiki | profile: qswiki | ||||||
|  |  | ||||||
| qswiki: $(WIKIOBJECTS) |  | ||||||
| 	$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS}  -o qswiki | exile.o: submodules/exile.h/exile.c | ||||||
|  | 	$(CC) -std=c99 -DHAVE_LANDLOCK=0 -c submodules/exile.h/exile.c -o exile.o | ||||||
|  |  | ||||||
|  | qswiki: $(WIKIOBJECTS) exile.o | ||||||
|  | 	$(CXX) $(WIKIOBJECTS) exile.o ${LDFLAGS} ${INCLUDEFLAGS}  -o qswiki | ||||||
|  |  | ||||||
| test: $(TESTOBJECTS) | test: $(TESTOBJECTS) | ||||||
| 	$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test | 	$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test | ||||||
| @@ -63,6 +68,6 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS) | |||||||
| version.o:version.cpp | version.o:version.cpp | ||||||
| 	$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $< | 	$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $< | ||||||
| clean: | clean: | ||||||
| 	rm -f $(OBJECTS) $(DEPENDS) | 	rm -f exile.o $(OBJECTS) $(DEPENDS) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										95
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										95
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,81 +1,84 @@ | |||||||
| # qswiki | # qswiki | ||||||
|  |  | ||||||
| About | ## About | ||||||
| ==== | qswiki is a wiki software, intended for my needs. Originally  implemented in C, it's now written in C++. | ||||||
| qswiki is a wiki software, intended for small wikis. Originally  |  | ||||||
| implemented in C, it's now written in C++. |  | ||||||
|  |  | ||||||
| History | ## Dude... why? | ||||||
| ==== |  | ||||||
| A couple of years ago, I wanted to setup a personal wiki on my raspberry  | tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already | ||||||
| pi. However, the distribution I used back then did not have a PHP package  | 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 | 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,  | 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  | 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  | 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. | "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.  | 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.  | and print your HTML to stdout.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  |  | ||||||
|  | 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. | 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. | so then I started using sqlite. | ||||||
|  |  | ||||||
| C++ | C++ | ||||||
| --- | --- | ||||||
| Eventually, since it was mostly a playground for me, the code became  | Eventually, since it was mostly a playground for me, the code became | ||||||
| unmaintainable. Furthermore, I wanted something quick and given that  | unmaintainable. Furthermore, I initially wanted something quick and given that | ||||||
| it was CGI, I didn't bother taking care of memory leaks.  | it was CGI, I didn't bother taking care of memory leaks. | ||||||
| After initiating a FastCGI interface, they became an issue and then the  | 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 | 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. | good reason. | ||||||
|  |  | ||||||
| Overall, I am just continuing the experiment with C++17 now. It's not  | 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  | nearly as bad as you would expect perhaps. Some things are surprisingly | ||||||
| convenient even. Still, the standard library is lacking and  | convenient even. Still, the standard library is lacking and | ||||||
| I would hope for a some better built-in Unicode support in future C++  | I would hope for a some better built-in Unicode support in future C++ | ||||||
| standards. | standards. | ||||||
|  |  | ||||||
| Features |  | ||||||
| ======== | ## Features | ||||||
| To be fair, at this point it doesn't even have a "diff" between revisions  | Some essential features are lacking, such as a diff between revisions, | ||||||
| yet and does not have features that would make you prefer it over other  | user registration UI, etc. | ||||||
| wikis. |  | ||||||
|  | It doesn't compete with any other software anyway. | ||||||
|  |  | ||||||
|  - CGI |  - CGI | ||||||
|  - HTTP server using the header only library cpp-httplib. It's more  |  - 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  |  portable and more "future-proof" than FastCGI (since the official website | ||||||
|  disappeared, the library's future appears to be uncertain). |  disappeared, the library's future appears to be uncertain). | ||||||
|  - Support for user accounts. Passwords are stored using PBKDF2. |  - Support for user accounts. Passwords are stored using PBKDF2. | ||||||
|   sqlite database, but not too much of an effort to add other types of  |   sqlite database, but not too much of an effort to add other types of | ||||||
|   storage backends. sqlite is using the great header only library  |   storage backends. sqlite is using the great header only library | ||||||
|   sqlite_modern_cpp |   [sqlite_modern_cpp](https://github.com/SqliteModernCpp) | ||||||
|  - Relatively fine-grained permission system. |  - Relatively fine-grained permission system. | ||||||
|  - Categories |  - Categories | ||||||
|  - Templates |  - Templates | ||||||
|  - FTS search |  - FTS search | ||||||
|  - Caching |  - Caching | ||||||
|  |  - Blog-like functionality | ||||||
|  |  - RSS/Atom feeds | ||||||
|  |  | ||||||
| Security | ## Security | ||||||
| ======== | [exile.h](https://github.com/quitesimpleorg/exile.h) is used | ||||||
| On Linux namespaces are used to restrict the process to only access | to restrict access to the files the wiki needs.  It doesn't have access to other paths | ||||||
| files it needs. It doesn't have access to other paths in the system. | in the system and the system calls that the qswiki process can make are restricted. | ||||||
| In addition, Seccomp is used to restrict the syscalls the qswiki process |  | ||||||
| can call.  As for "web security", all POST requests are centrally | As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS | ||||||
| protected against CSRF attacks and all input is escaped against XSS  |  | ||||||
| attacks. | attacks. | ||||||
|  |  | ||||||
| Building | ## Building | ||||||
| ======== |  | ||||||
| Dependencies: | Dependencies: | ||||||
|   - cpp-httplib: https://github.com/yhirose/cpp-httplib |   - cpp-httplib: https://github.com/yhirose/cpp-httplib | ||||||
|   - SqliteModernCpp: https://github.com/SqliteModernCpp |   - SqliteModernCpp: https://github.com/SqliteModernCpp | ||||||
|   - qssb.h: https://gitea.quitesimple.org/crtxcr/qssb.h |   - exile.h: https://gitea.quitesimple.org/crtxcr/exile.h | ||||||
|   - libseccomp: https://github.com/seccomp/libseccomp |  | ||||||
|   - sqlite3: https://sqlite.org/index.html |   - sqlite3: https://sqlite.org/index.html | ||||||
|      |  | ||||||
| The first three are header-only libraries that are included as a git submodule. The others must | The first three are header-only libraries that are included as a git submodule. The others must | ||||||
| be installed, e. g. by using your distributions standard method. | be installed, e. g. by using your distributions standard method. | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								cache/fscache.cpp
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								cache/fscache.cpp
									
									
									
									
										vendored
									
									
								
							| @@ -46,7 +46,7 @@ void FsCache::removePrefix(std::string_view prefix) | |||||||
| 	// TODO: lock dir | 	// TODO: lock dir | ||||||
| 	for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path})) | 	for(auto &entry : std::filesystem::directory_iterator(std::filesystem::path{this->path})) | ||||||
| 	{ | 	{ | ||||||
| 		if(std::string_view(entry.path().filename().c_str()).starts_with(prefix) == 0) | 		if(std::string_view(entry.path().filename().c_str()).starts_with(prefix)) | ||||||
| 		{ | 		{ | ||||||
| 			std::filesystem::remove_all(entry); | 			std::filesystem::remove_all(entry); | ||||||
| 		} | 		} | ||||||
|   | |||||||
							
								
								
									
										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; | 	QueryOption o; | ||||||
| 	auto result = pageDao->getPageList(o); | 	auto result = pageDao->getPageList(o); | ||||||
| 	std::stringstream stream; | 	std::stringstream stream; | ||||||
| 	for(std::string pagename : result) | 	for(Page &page : result) | ||||||
| 	{ | 	{ | ||||||
| 		Page p = pageDao->find(pagename).value(); | 		stream << page.name << " " << page.pageid << " " << std::string(page.listed ? "listed" : "unlisted") << std::endl; | ||||||
| 		stream << p.name << " " << p.pageid << " " << std::string(p.listed ? "listed" : "unlisted") << std::endl; |  | ||||||
| 	} | 	} | ||||||
| 	return {true, stream.str()}; | 	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 categoryDao = this->db->createCategoryDao(); | ||||||
| 	auto members = categoryDao->fetchMembers(args.at(0), QueryOption{}); | 	auto members = categoryDao->fetchMembers(args.at(0), QueryOption{}); | ||||||
| 	std::stringstream stream; | 	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()}; | 	return {true, stream.str()}; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ SOFTWARE. | |||||||
| #include "config.h" | #include "config.h" | ||||||
| #include "permissions.h" | #include "permissions.h" | ||||||
| #include "varreplacer.h" | #include "varreplacer.h" | ||||||
|  |  | ||||||
| std::string Config::required(const std::string &key) | std::string Config::required(const std::string &key) | ||||||
| { | { | ||||||
| 	auto it = this->configmap.find(key); | 	auto it = this->configmap.find(key); | ||||||
| @@ -77,13 +78,16 @@ Config::Config(const std::map<std::string, std::string> &map) | |||||||
| 	this->templatepath = required("templatepath"); | 	this->templatepath = required("templatepath"); | ||||||
| 	this->urls.linkallcats = required("linkallcats"); | 	this->urls.linkallcats = required("linkallcats"); | ||||||
| 	this->urls.linkallpages = required("linkallpages"); | 	this->urls.linkallpages = required("linkallpages"); | ||||||
|  | 	this->urls.linkallpagesrendertype = required ("linkallpagesrendertype"); | ||||||
| 	this->urls.linkcategory = required("linkcategory"); | 	this->urls.linkcategory = required("linkcategory"); | ||||||
|  | 	this->urls.linkcategoryrendertype = required("linkcategoryrendertype"); | ||||||
| 	this->urls.linkdelete = required("linkdelete"); | 	this->urls.linkdelete = required("linkdelete"); | ||||||
| 	this->urls.linkedit = required("linkedit"); | 	this->urls.linkedit = required("linkedit"); | ||||||
| 	this->urls.linkhistory = required("linkhistory"); | 	this->urls.linkhistory = required("linkhistory"); | ||||||
| 	this->urls.linkindex = required("linkindex"); | 	this->urls.linkindex = required("linkindex"); | ||||||
| 	this->urls.linklogout = required("linklogout"); | 	this->urls.linklogout = required("linklogout"); | ||||||
| 	this->urls.linkpage = required("linkpage"); | 	this->urls.linkpage = required("linkpage"); | ||||||
|  | 	this->urls.linkpagebytitle = required("linkpagebytitle"); | ||||||
| 	this->urls.linkrecent = required("linkrecent"); | 	this->urls.linkrecent = required("linkrecent"); | ||||||
| 	this->urls.linkrevision = required("linkrevision"); | 	this->urls.linkrevision = required("linkrevision"); | ||||||
| 	this->urls.linksettings = required("linksettings"); | 	this->urls.linksettings = required("linksettings"); | ||||||
| @@ -96,6 +100,8 @@ Config::Config(const std::map<std::string, std::string> &map) | |||||||
| 	this->urls.deletionurl = required("deletionurl"); | 	this->urls.deletionurl = required("deletionurl"); | ||||||
| 	this->urls.adminregisterurl = required("adminregisterurl"); | 	this->urls.adminregisterurl = required("adminregisterurl"); | ||||||
| 	this->urls.usersettingsurl = required("usersettingsurl"); | 	this->urls.usersettingsurl = required("usersettingsurl"); | ||||||
|  | 	this->urls.rooturl = required("rooturl"); | ||||||
|  | 	this->urls.atomurl = required("atomurl"); | ||||||
| 	this->connectionstring = required("connectionstring"); | 	this->connectionstring = required("connectionstring"); | ||||||
|  |  | ||||||
| 	this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256); | 	this->handlersConfig.max_pagename_length = optional("max_pagename_length", 256); | ||||||
| @@ -108,7 +114,7 @@ Config::Config(const std::map<std::string, std::string> &map) | |||||||
|  |  | ||||||
| 	this->templateprefix = "{qswiki:"; | 	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}; | 	ConfigVariableResolver resolver{this->configmap}; | ||||||
| 	this->configVarResolver = resolver; | 	this->configVarResolver = resolver; | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								config.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								config.h
									
									
									
									
									
								
							| @@ -23,9 +23,11 @@ struct ConfigUrls | |||||||
| 	std::string linkindex; | 	std::string linkindex; | ||||||
| 	std::string linkrecent; | 	std::string linkrecent; | ||||||
| 	std::string linkallpages; | 	std::string linkallpages; | ||||||
|  | 	std::string linkallpagesrendertype; | ||||||
| 	std::string linkallcats; | 	std::string linkallcats; | ||||||
| 	std::string linkshere; | 	std::string linkshere; | ||||||
| 	std::string linkpage; | 	std::string linkpage; | ||||||
|  | 	std::string linkpagebytitle; | ||||||
| 	std::string linkrevision; | 	std::string linkrevision; | ||||||
| 	std::string linkhistory; | 	std::string linkhistory; | ||||||
| 	std::string linkedit; | 	std::string linkedit; | ||||||
| @@ -33,6 +35,7 @@ struct ConfigUrls | |||||||
| 	std::string linkdelete; | 	std::string linkdelete; | ||||||
| 	std::string linklogout; | 	std::string linklogout; | ||||||
| 	std::string linkcategory; | 	std::string linkcategory; | ||||||
|  | 	std::string linkcategoryrendertype; | ||||||
| 	std::string loginurl; | 	std::string loginurl; | ||||||
| 	std::string linkrecentsort; | 	std::string linkrecentsort; | ||||||
| 	std::string actionurl; | 	std::string actionurl; | ||||||
| @@ -41,6 +44,8 @@ struct ConfigUrls | |||||||
| 	std::string linkhistorysort; | 	std::string linkhistorysort; | ||||||
| 	std::string adminregisterurl; | 	std::string adminregisterurl; | ||||||
| 	std::string usersettingsurl; | 	std::string usersettingsurl; | ||||||
|  | 	std::string rooturl; | ||||||
|  | 	std::string atomurl; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class ConfigVariableResolver | class ConfigVariableResolver | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| #include <optional> | #include <optional> | ||||||
| #include "queryoption.h" | #include "queryoption.h" | ||||||
| #include "../category.h" | #include "../category.h" | ||||||
|  | #include "../page.h" | ||||||
| class CategoryDao | class CategoryDao | ||||||
| { | { | ||||||
|   public: |   public: | ||||||
| @@ -14,7 +14,8 @@ class CategoryDao | |||||||
| 	virtual std::vector<std::string> fetchList(QueryOption o) = 0; | 	virtual std::vector<std::string> fetchList(QueryOption o) = 0; | ||||||
| 	virtual std::optional<Category> find(std::string name) = 0; | 	virtual std::optional<Category> find(std::string name) = 0; | ||||||
| 	virtual void deleteCategory(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 | #endif // CATEGORYDAO_H | ||||||
|   | |||||||
| @@ -94,9 +94,10 @@ std::vector<std::string> CategoryDaoSqlite::fetchList(QueryOption o) | |||||||
| 	} | 	} | ||||||
| 	return result; | 	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}; | 	SqliteQueryOption queryOption{o}; | ||||||
| 	std::string queryoptions = | 	std::string queryoptions = | ||||||
| @@ -104,11 +105,18 @@ std::vector<std::string> CategoryDaoSqlite::fetchMembers(std::string name, Query | |||||||
|  |  | ||||||
| 	try | 	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 " + | 							"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " + | ||||||
| 								queryoptions | 								queryoptions | ||||||
| 						 << name; | 						 << 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) | 	catch(const sqlite::exceptions::no_rows &e) | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
| @@ -3,12 +3,13 @@ | |||||||
|  |  | ||||||
| #include "categorydao.h" | #include "categorydao.h" | ||||||
| #include "sqlitedao.h" | #include "sqlitedao.h" | ||||||
|  | #include "../page.h" | ||||||
| class CategoryDaoSqlite : public CategoryDao, protected SqliteDao | class CategoryDaoSqlite : public CategoryDao, protected SqliteDao | ||||||
| { | { | ||||||
|   public: |   public: | ||||||
| 	CategoryDaoSqlite(); | 	CategoryDaoSqlite(); | ||||||
| 	std::vector<std::string> fetchList(QueryOption o) override; | 	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 save(const Category &c) override; | ||||||
| 	void deleteCategory(std::string name) override; | 	void deleteCategory(std::string name) override; | ||||||
| 	std::optional<Category> find(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(std::string page) const = 0; | ||||||
| 	virtual bool exists(unsigned int id) const = 0; | 	virtual bool exists(unsigned int id) const = 0; | ||||||
| 	virtual std::optional<Page> find(std::string name) = 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::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 std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) = 0; | ||||||
| 	virtual void deletePage(std::string page) = 0; | 	virtual void deletePage(std::string page) = 0; | ||||||
| 	virtual void save(const Page &page) = 0; | 	virtual void save(const Page &page) = 0; | ||||||
|   | |||||||
| @@ -52,15 +52,35 @@ 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) | std::optional<Page> PageDaoSqlite::find(unsigned int id) | ||||||
| { | { | ||||||
| 	Page result; | 	Page result; | ||||||
| 	result.pageid = id; | 	result.pageid = id; | ||||||
| 	try | 	try | ||||||
| 	{ | 	{ | ||||||
| 		auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?"; | 		auto ps = *db << "SELECT name, title, lastrevision, visible FROM page WHERE id = ?"; | ||||||
|  |  | ||||||
| 		ps << id >> std::tie(result.name, result.current_revision, result.listed); | 		ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed); | ||||||
| 	} | 	} | ||||||
| 	catch(const sqlite::errors::no_rows &e) | 	catch(const sqlite::errors::no_rows &e) | ||||||
| 	{ | 	{ | ||||||
| @@ -97,30 +117,38 @@ void PageDaoSqlite::save(const Page &page) | |||||||
| { | { | ||||||
| 	try | 	try | ||||||
| 	{ | 	{ | ||||||
| 		*db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE name = " | 		*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, visible) VALUES((SELECT id FROM page WHERE " | ||||||
| 			   "? OR id = ?), ?, ?, ?)" | 			   "name = " | ||||||
| 			<< page.name << page.pageid << page.name << page.current_revision << page.listed; | 			   "? OR id = ?), ?, ?, ?, ?)" | ||||||
|  | 			<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed; | ||||||
| 	} | 	} | ||||||
| 	catch(sqlite::sqlite_exception &e) | 	catch(sqlite::sqlite_exception &e) | ||||||
| 	{ | 	{ | ||||||
| 		throwFrom(e); | 		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 | 	try | ||||||
| 	{ | 	{ | ||||||
|  |  | ||||||
| 		std::string queryOption = SqliteQueryOption(option) | 		std::string queryOption = SqliteQueryOption(option) | ||||||
| 									  .setOrderByColumn("lower(name)") | 									  .setOrderByColumn("lower(name)") | ||||||
| 									  .setVisibleColumnName("visible") | 									  .setVisibleColumnName("visible") | ||||||
| 									  .setPrependWhere(true) | 									  .setPrependWhere(true) | ||||||
| 									  .build(); | 									  .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) | 	catch(const sqlite::errors::no_rows &e) | ||||||
| 	{ | 	{ | ||||||
| @@ -183,7 +211,8 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op | |||||||
| 		auto query = | 		auto query = | ||||||
| 			*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? " | 			*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? " | ||||||
| 				<< ftsEscape(name); | 				<< ftsEscape(name); | ||||||
| 		query >> [&](std::string pagename) { | 		query >> [&](std::string pagename) | ||||||
|  | 		{ | ||||||
| 			SearchResult sresult; | 			SearchResult sresult; | ||||||
| 			sresult.pagename = pagename; | 			sresult.pagename = pagename; | ||||||
| 			sresult.query = name; | 			sresult.query = name; | ||||||
|   | |||||||
| @@ -20,8 +20,9 @@ class PageDaoSqlite : public PageDao, protected SqliteDao | |||||||
| 	bool exists(std::string name) const override; | 	bool exists(std::string name) const override; | ||||||
| 	void save(const Page &page) override; | 	void save(const Page &page) override; | ||||||
| 	std::optional<Page> find(std::string name) 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::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; | 	std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) override; | ||||||
| 	using SqliteDao::SqliteDao; | 	using SqliteDao::SqliteDao; | ||||||
| 	int fetchPageId(std::string pagename); | 	int fetchPageId(std::string pagename); | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ 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 ~PermissionsDao() = default; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // PERMISSIONSDAO_H | #endif // PERMISSIONSDAO_H | ||||||
|   | |||||||
| @@ -129,9 +129,8 @@ 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, revisionid FROM revision WHERE page = (SELECT id FROM " | 							"strftime('%s',creationtime), page.name, revisionid FROM revision INNER JOIN page ON revision.page = page.id WHERE page.name = ? AND page.lastrevision = revision.revisionid"; | ||||||
| 							"page WHERE name = ? ) AND revisionid = (SELECT lastrevision FROM page WHERE name = ?)"; | 		query << pagename; | ||||||
| 		query << pagename << 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); | ||||||
| 	} | 	} | ||||||
| @@ -155,7 +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, 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 << 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); | ||||||
|   | |||||||
| @@ -37,6 +37,8 @@ class SqliteDao | |||||||
|  |  | ||||||
| 	bool execBool(sqlite::database_binder &binder) const; | 	bool execBool(sqlite::database_binder &binder) const; | ||||||
| 	int execInt(sqlite::database_binder &binder) const; | 	int execInt(sqlite::database_binder &binder) const; | ||||||
|  |  | ||||||
|  | 	virtual ~SqliteDao() = default; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // SQLITEDAO_H | #endif // SQLITEDAO_H | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								dynamic/dynamiccontent.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										8
									
								
								dynamic/dynamiccontent.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | #include "dynamiccontent.h" | ||||||
|  |  | ||||||
|  | DynamicContent::DynamicContent(Template &templ, Database &database, UrlProvider &provider) | ||||||
|  | { | ||||||
|  | 	this->templ = &templ; | ||||||
|  | 	this->database = &database; | ||||||
|  | 	this->urlProvider = &provider; | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								dynamic/dynamiccontent.h
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										28
									
								
								dynamic/dynamiccontent.h
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #ifndef DYNAMICCONTENT_H | ||||||
|  | #define DYNAMICCONTENT_H | ||||||
|  | #include <string> | ||||||
|  | #include "../database/database.h" | ||||||
|  | #include "../template.h" | ||||||
|  | #include "../urlprovider.h" | ||||||
|  | class DynamicContent | ||||||
|  | { | ||||||
|  |   protected: | ||||||
|  | 	Template *templ; | ||||||
|  | 	Database *database; | ||||||
|  | 	UrlProvider *urlProvider; | ||||||
|  |  | ||||||
|  | 	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() | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // DYNAMICCONTENT_H | ||||||
							
								
								
									
										28
									
								
								dynamic/dynamiccontentfactory.h
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										28
									
								
								dynamic/dynamiccontentfactory.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										11
									
								
								dynamic/dynamiccontentgetvar.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -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
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										19
									
								
								dynamic/dynamiccontentgetvar.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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 | ||||||
| @@ -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 {}; | ||||||
|  | } | ||||||
| @@ -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 | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | #include <chrono> | ||||||
|  | #include "dynamiccontentpostlist.h" | ||||||
|  |  | ||||||
|  | std::string DynamicContentPostList::render() | ||||||
|  | { | ||||||
|  | 	auto categoryDao = this->database->createCategoryDao(); | ||||||
|  | 	auto pageDao = this->database->createPageDao(); | ||||||
|  | 	auto revisionDao = this->database->createRevisionDao(); | ||||||
|  | 	QueryOption option; | ||||||
|  | 	option.includeInvisible = false; | ||||||
|  | 	auto members = categoryDao->fetchMembers(this->argument, option); | ||||||
|  | 	std::vector<std::pair<std::string, time_t>> pageList; | ||||||
|  | 	for(const Page &member : members) | ||||||
|  | 	{ | ||||||
|  | 		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 postListBegin = this->templ->loadResolvedPart("dynamic/postlistbegin"); | ||||||
|  | 	std::string postListEnd = this->templ->loadResolvedPart("dynamic/postlistend"); | ||||||
|  | 	std::string postLink = this->templ->loadResolvedPart("dynamic/postlistlink"); | ||||||
|  | 	std::stringstream stream; | ||||||
|  | 	stream << postListBegin; | ||||||
|  | 	for(auto &pair : pageList) | ||||||
|  | 	{ | ||||||
|  | 		std::string link = this->urlProvider->page(pair.first); | ||||||
|  | 		std::string date = utils::toISODate(pair.second); | ||||||
|  | 		Varreplacer replacer{"{"}; | ||||||
|  | 		replacer.addKeyValue("url", link); | ||||||
|  | 		replacer.addKeyValue("date", date); | ||||||
|  | 		replacer.addKeyValue("title", pageDao->find(pair.first)->title); | ||||||
|  |  | ||||||
|  | 		stream << replacer.parse(postLink); | ||||||
|  | 	} | ||||||
|  | 	stream << postListEnd; | ||||||
|  | 	return stream.str(); | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								dynamic/dynamiccontentpostlist.h
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										12
									
								
								dynamic/dynamiccontentpostlist.h
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | #ifndef DYNAMICCONTENTPOSTLIST_H | ||||||
|  | #define DYNAMICCONTENTPOSTLIST_H | ||||||
|  |  | ||||||
|  | #include "dynamiccontent.h" | ||||||
|  | class DynamicContentPostList : public DynamicContent | ||||||
|  | { | ||||||
|  |   public: | ||||||
|  | 	using DynamicContent::DynamicContent; | ||||||
|  | 	std::string render() override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // DYNAMICCONTENTPOSTLIST_H | ||||||
							
								
								
									
										21
									
								
								dynamic/dynamiccontentsetvar.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										21
									
								
								dynamic/dynamiccontentsetvar.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -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
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										17
									
								
								dynamic/dynamiccontentsetvar.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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 | #ifndef HTTPGATEWAY_H | ||||||
| #define HTTPGATEWAY_H | #define HTTPGATEWAY_H | ||||||
|  |  | ||||||
|  | #define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 65536 | ||||||
|  |  | ||||||
| #include <httplib.h> | #include <httplib.h> | ||||||
| #include "gatewayinterface.h" | #include "gatewayinterface.h" | ||||||
| #include "../requestworker.h" | #include "../requestworker.h" | ||||||
|   | |||||||
							
								
								
									
										0
									
								
								grouper.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										0
									
								
								grouper.cpp
									
									
									
									
									
										普通文件
									
								
							
							
								
								
									
										26
									
								
								grouper.h
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										26
									
								
								grouper.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| #ifndef HANDLER_H | #ifndef HANDLER_H | ||||||
| #define HANDLER_H | #define HANDLER_H | ||||||
|  | #include <memory> | ||||||
| #include "../config.h" | #include "../config.h" | ||||||
| #include "../response.h" | #include "../response.h" | ||||||
| #include "../request.h" | #include "../request.h" | ||||||
| @@ -9,6 +10,8 @@ | |||||||
| #include "../database/queryoption.h" | #include "../database/queryoption.h" | ||||||
| #include "../logger.h" | #include "../logger.h" | ||||||
| #include "../cache/icache.h" | #include "../cache/icache.h" | ||||||
|  | #include "../dynamic/dynamiccontent.h" | ||||||
|  |  | ||||||
| class Handler | class Handler | ||||||
| { | { | ||||||
|   protected: |   protected: | ||||||
| @@ -53,6 +56,12 @@ class Handler | |||||||
| 	virtual ~Handler() | 	virtual ~Handler() | ||||||
| 	{ | 	{ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	template <class T> inline std::shared_ptr<T> createDynamic() | ||||||
|  | 	{ | ||||||
|  | 		return std::make_shared<T>(*this->templ, *this->database, *this->urlProvider); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	Response errorResponse(std::string errortitle, std::string errormessage, int status = 200); | 	Response errorResponse(std::string errortitle, std::string errormessage, int status = 200); | ||||||
| 	std::string createPageTitle(std::string append); | 	std::string createPageTitle(std::string append); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||||
| SOFTWARE. | SOFTWARE. | ||||||
| */ | */ | ||||||
| #include "handlerallpages.h" | #include "handlerallpages.h" | ||||||
|  | #include "../grouper.h" | ||||||
|  | #include "../pagelistrenderer.h" | ||||||
|  |  | ||||||
| Response HandlerAllPages::handleRequest(const Request &r) | Response HandlerAllPages::handleRequest(const Request &r) | ||||||
| { | { | ||||||
| @@ -27,17 +29,21 @@ Response HandlerAllPages::handleRequest(const Request &r) | |||||||
| 		Response response; | 		Response response; | ||||||
| 		auto pageDao = this->database->createPageDao(); | 		auto pageDao = this->database->createPageDao(); | ||||||
| 		QueryOption qo = queryOption(r); | 		QueryOption qo = queryOption(r); | ||||||
| 		auto resultList = pageDao->getPageList(qo); | 		std::vector<Page> pageList = pageDao->getPageList(qo); | ||||||
| 		if(resultList.size() == 0) | 		if(pageList.size() == 0) | ||||||
| 		{ | 		{ | ||||||
| 			return errorResponse("No pages", "This wiki does not have any pages yet"); | 			return errorResponse("No pages", "This wiki does not have any pages yet"); | ||||||
| 		} | 		} | ||||||
| 		TemplatePage searchPage = this->templ->getPage("allpages"); | 		 | ||||||
| 		std::string body = this->templ->renderSearch(resultList); | 		PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); | ||||||
| 		searchPage.setVar("pagelist", body); | 		TemplatePage allPages = this->templ->getPage("allpages"); | ||||||
| 		searchPage.setVar("title", createPageTitle("All pages")); | 		allPages.setVar("pagelistcontent", pagelistrender.render(pageList, r.get("rendertype"))); | ||||||
| 		setGeneralVars(searchPage); | 		allPages.setVar("pagelistletterlink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_LETTER)); | ||||||
| 		response.setBody(searchPage.render()); | 		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); | 		response.setStatus(200); | ||||||
| 		return response; | 		return response; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||||||
| SOFTWARE. | SOFTWARE. | ||||||
| */ | */ | ||||||
| #include "handlercategory.h" | #include "handlercategory.h" | ||||||
|  | #include "../pagelistrenderer.h" | ||||||
|  |  | ||||||
| Response HandlerCategory::handleRequest(const Request &r) | Response HandlerCategory::handleRequest(const Request &r) | ||||||
| { | { | ||||||
| @@ -34,8 +35,12 @@ Response HandlerCategory::handleRequest(const Request &r) | |||||||
| 		QueryOption qo = queryOption(r); | 		QueryOption qo = queryOption(r); | ||||||
| 		auto resultList = categoryDao->fetchMembers(categoryname, qo); | 		auto resultList = categoryDao->fetchMembers(categoryname, qo); | ||||||
| 		TemplatePage searchPage = this->templ->getPage("show_category"); | 		TemplatePage searchPage = this->templ->getPage("show_category"); | ||||||
| 		std::string body = this->templ->renderSearch(resultList); | 		PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database); | ||||||
| 		searchPage.setVar("pagelist", body); |  | ||||||
|  | 		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); | 		searchPage.setVar("categoryname", categoryname); | ||||||
| 		setGeneralVars(searchPage); | 		setGeneralVars(searchPage); | ||||||
| 		searchPage.setVar("title", createPageTitle("Category: " + categoryname)); | 		searchPage.setVar("title", createPageTitle("Category: " + categoryname)); | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ SOFTWARE. | |||||||
| #include "handlerpagedelete.h" | #include "handlerpagedelete.h" | ||||||
| #include "handlerusersettings.h" | #include "handlerusersettings.h" | ||||||
| #include "handlerversion.h" | #include "handlerversion.h" | ||||||
|  | #include "handlerfeedgenerator.h" | ||||||
| std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession) | std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession) | ||||||
| { | { | ||||||
| 	if(action == "" || action == "index") | 	if(action == "" || action == "index") | ||||||
| @@ -84,6 +85,10 @@ std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action | |||||||
| 	{ | 	{ | ||||||
| 		return produce<HandlerVersion>(userSession); | 		return produce<HandlerVersion>(userSession); | ||||||
| 	} | 	} | ||||||
|  | 	if(action == "feed") | ||||||
|  | 	{ | ||||||
|  | 		return produce<HandlerFeedGenerator>(userSession); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return produce<HandlerInvalidAction>(userSession); | 	return produce<HandlerInvalidAction>(userSession); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										150
									
								
								handlers/handlerfeedgenerator.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										150
									
								
								handlers/handlerfeedgenerator.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | #include "handlerfeedgenerator.h" | ||||||
|  | #include "../parser.h" | ||||||
|  | #include "../revisionrenderer.h" | ||||||
|  | std::vector<HandlerFeedGenerator::EntryRevisionPair> HandlerFeedGenerator::fetchEntries( | ||||||
|  | 	std::vector<std::string> categories) | ||||||
|  | { | ||||||
|  | 	auto revisionDao = this->database->createRevisionDao(); | ||||||
|  | 	auto pageDao = this->database->createPageDao(); | ||||||
|  |  | ||||||
|  | 	std::vector<EntryRevisionPair> result; | ||||||
|  | 	QueryOption option; | ||||||
|  | 	option.includeInvisible = false; | ||||||
|  | 	// option.limit = 20; | ||||||
|  | 	 | ||||||
|  | 	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); | ||||||
|  | 		std::copy(pages.begin(), pages.end(), std::inserter(members, members.end())); | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		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 Page &member : members) | ||||||
|  | 	{ | ||||||
|  | 		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; }); | ||||||
|  |  | ||||||
|  | 	const int maxResults = 20; | ||||||
|  | 	if(result.size() > maxResults) | ||||||
|  | 	{ | ||||||
|  | 		result.erase(result.begin() + maxResults - 1, result.end()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string HandlerFeedGenerator::generateAtom(const std::vector<HandlerFeedGenerator::EntryRevisionPair> &entries, | ||||||
|  | 											   std::string filter) | ||||||
|  | { | ||||||
|  |  | ||||||
|  | 	std::stringstream stream; | ||||||
|  | 	// don't care about offset for now especially since "%z" does not return what we need exactly (with ':') | ||||||
|  | 	const std::string dateformat = "%Y-%m-%dT%T"; | ||||||
|  |  | ||||||
|  | 	time_t newestPublished = 0; | ||||||
|  | 	std::string atomfooter = this->templ->loadResolvedPart("feeds/atomfooter"); | ||||||
|  | 	auto revisionDao = this->database->createRevisionDao(); | ||||||
|  | 	auto pageDao = this->database->createPageDao(); | ||||||
|  |  | ||||||
|  | 	std::string subtitle = filter; | ||||||
|  | 	if(utils::trim(filter).empty()) | ||||||
|  | 	{ | ||||||
|  | 		subtitle = "All pages"; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||||
|  |  | ||||||
|  | 	for(const EntryRevisionPair &entry : entries) | ||||||
|  | 	{ | ||||||
|  | 		const Page &page = entry.first; | ||||||
|  | 		const Revision &initialRevision = entry.second; | ||||||
|  | 		Revision current = revisionDao->getCurrentForPage(page.name).value(); | ||||||
|  | 		std::string url = this->urlProvider->rootUrl() + this->urlProvider->page(page.name); | ||||||
|  | 		if(initialRevision.timestamp > newestPublished) | ||||||
|  | 		{ | ||||||
|  | 			newestPublished = initialRevision.timestamp; | ||||||
|  | 		} | ||||||
|  | 		std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z"; | ||||||
|  | 		std::string 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", page.title); | ||||||
|  | 		atomentry.setVar("entryurl", utils::html_xss(entryurl)); | ||||||
|  | 		atomentry.setVar("entryid", utils::html_xss(entryurl)); | ||||||
|  | 		atomentry.setVar("entrypublished", entryPublished); | ||||||
|  | 		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", 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"); | ||||||
|  | 	return atomheader.render() + stream.str(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Response HandlerFeedGenerator::handleRequest(const Request &r) | ||||||
|  | { | ||||||
|  | 	Response response; | ||||||
|  | 	try | ||||||
|  | 	{ | ||||||
|  | 		std::string type = r.get("type"); | ||||||
|  | 		std::string categories = r.get("cats"); | ||||||
|  | 		if(type == "atom") | ||||||
|  | 		{ | ||||||
|  | 			std::string filter = categories; | ||||||
|  | 			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 | ||||||
|  | 		{ | ||||||
|  | 			return errorResponse("Invalid feed type", "Unknown feed type, try atom", 400); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	catch(std::runtime_error &e) | ||||||
|  | 	{ | ||||||
|  | 		Logger::error() << "Error while serving feed: " << e.what(); | ||||||
|  | 		return errorResponse("Error", "An error occured while trying to serve the feed", 500); | ||||||
|  | 	} | ||||||
|  | 	return response; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool HandlerFeedGenerator::canAccess(const Permissions &perms) | ||||||
|  | { | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								handlers/handlerfeedgenerator.h
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										21
									
								
								handlers/handlerfeedgenerator.h
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | #ifndef HANDLERFEEDGENERATOR_H | ||||||
|  | #define HANDLERFEEDGENERATOR_H | ||||||
|  | #include "handler.h" | ||||||
|  | #include "../page.h" | ||||||
|  | #include "../revision.h" | ||||||
|  | class HandlerFeedGenerator : public Handler | ||||||
|  | { | ||||||
|  | 	typedef std::pair<Page, Revision> EntryRevisionPair; | ||||||
|  |  | ||||||
|  |   protected: | ||||||
|  | 	std::vector<EntryRevisionPair> fetchEntries(std::vector<std::string> categories); | ||||||
|  | 	std::string generateAtom(const std::vector<EntryRevisionPair> &entries, std::string atomtitle); | ||||||
|  | 	Response generateRss(const std::vector<EntryRevisionPair> &entries); | ||||||
|  |  | ||||||
|  |   public: | ||||||
|  | 	using Handler::Handler; | ||||||
|  | 	Response handleRequest(const Request &r) override; | ||||||
|  | 	bool canAccess(const Permissions &perms) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // HANDLERFEEDGENERATOR_H | ||||||
| @@ -27,7 +27,18 @@ Response HandlerPage::handle(const Request &r) | |||||||
| 	auto pageDao = this->database->createPageDao(); | 	auto pageDao = this->database->createPageDao(); | ||||||
| 	if(pagename.empty()) | 	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)) | 	if(pageMustExist() && !pageDao->exists(pagename)) | ||||||
|   | |||||||
| @@ -45,6 +45,7 @@ Response HandlerPageDelete::handleRequest(PageDao &pageDao, std::string pagename | |||||||
| 		{ | 		{ | ||||||
| 			pageDao.deletePage(pagename); | 			pageDao.deletePage(pagename); | ||||||
| 			this->cache->removePrefix("page:"); // TODO: overkill? | 			this->cache->removePrefix("page:"); // TODO: overkill? | ||||||
|  | 			this->cache->removePrefix("feed:"); | ||||||
| 			return Response::redirectTemporarily(this->urlProvider->index()); | 			return Response::redirectTemporarily(this->urlProvider->index()); | ||||||
| 		} | 		} | ||||||
| 		TemplatePage delPage = this->templ->getPage("page_deletion"); | 		TemplatePage delPage = this->templ->getPage("page_deletion"); | ||||||
|   | |||||||
| @@ -23,6 +23,7 @@ SOFTWARE. | |||||||
| #include "../request.h" | #include "../request.h" | ||||||
|  |  | ||||||
| #include "../parser.h" | #include "../parser.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(); | ||||||
| @@ -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"); | 		return errorResponse("No permission", "You don't have permission to create new pages"); | ||||||
| 	} | 	} | ||||||
| 	auto revisiondao = this->database->createRevisionDao(); | 	auto revisiondao = this->database->createRevisionDao(); | ||||||
| 	auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename); | 	auto revision = revisiondao->getCurrentForPage(pagename); | ||||||
| 	std::string body; | 	std::string body; | ||||||
|  |  | ||||||
| 	unsigned int current_revision = 0; | 	unsigned int current_revision = 0; | ||||||
| @@ -50,6 +51,15 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 		body = revision->content; | 		body = revision->content; | ||||||
| 		current_revision = revision->revision; | 		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.getRequestMethod() == "POST") | ||||||
| 	{ | 	{ | ||||||
| 		if(r.post("do") == "submit") | 		if(r.post("do") == "submit") | ||||||
| @@ -66,6 +76,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 				this->database->beginTransaction(); | 				this->database->beginTransaction(); | ||||||
| 				std::string visiblecmd = parser.extractCommand("visible", newContent); | 				std::string visiblecmd = parser.extractCommand("visible", newContent); | ||||||
| 				std::string rename = parser.extractCommand("rename", newContent); | 				std::string rename = parser.extractCommand("rename", newContent); | ||||||
|  | 				std::string customtitle = parser.extractCommand("pagetitle", newContent); | ||||||
| 				Page page; | 				Page page; | ||||||
| 				std::optional<Page> currentPage = pageDao.find(pagename); | 				std::optional<Page> currentPage = pageDao.find(pagename); | ||||||
| 				if(currentPage) | 				if(currentPage) | ||||||
| @@ -83,6 +94,11 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 				page.current_revision = current_revision; | 				page.current_revision = current_revision; | ||||||
| 				page.listed = !(visiblecmd == "0"); | 				page.listed = !(visiblecmd == "0"); | ||||||
| 				page.name = pagename; | 				page.name = pagename; | ||||||
|  | 				page.title = customtitle; | ||||||
|  | 				if(page.title.empty()) | ||||||
|  | 				{ | ||||||
|  | 					page.title = page.name; | ||||||
|  | 				} | ||||||
| 				pageDao.save(page); | 				pageDao.save(page); | ||||||
|  |  | ||||||
| 				Revision newRevision; | 				Revision newRevision; | ||||||
| @@ -95,6 +111,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 				pageDao.setCategories(pagename, cats); | 				pageDao.setCategories(pagename, cats); | ||||||
| 				this->database->commitTransaction(); | 				this->database->commitTransaction(); | ||||||
| 				this->cache->removePrefix("page:"); // TODO: overkill? | 				this->cache->removePrefix("page:"); // TODO: overkill? | ||||||
|  | 				this->cache->removePrefix("feed:"); | ||||||
| 			} | 			} | ||||||
| 			catch(const DatabaseException &e) | 			catch(const DatabaseException &e) | ||||||
| 			{ | 			{ | ||||||
| @@ -108,12 +125,16 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 		{ | 		{ | ||||||
| 			std::string newContent = r.post("content"); | 			std::string newContent = r.post("content"); | ||||||
| 			Parser parser; | 			Parser parser; | ||||||
|  | 			std::string title = parser.extractCommand("pagetitle", newContent); | ||||||
| 			TemplatePage templatePage = this->templ->getPage("page_creation_preview"); | 			TemplatePage templatePage = this->templ->getPage("page_creation_preview"); | ||||||
| 			templatePage.setVar("actionurl", urlProvider->editPage(pagename)); | 			templatePage.setVar("actionurl", urlProvider->editPage(pagename)); | ||||||
| 			templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent)); |  | ||||||
|  | 			RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||||
|  |  | ||||||
|  | 			templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent)); | ||||||
| 			templatePage.setVar("content", newContent); | 			templatePage.setVar("content", newContent); | ||||||
| 			setPageVars(templatePage, pagename); | 			setPageVars(templatePage, pagename); | ||||||
| 			templatePage.setVar("title", createPageTitle("Preview: " + pagename)); | 			templatePage.setVar("title", createPageTitle("Preview: " + title)); | ||||||
| 			templatePage.setVar("comment", r.post("comment")); | 			templatePage.setVar("comment", r.post("comment")); | ||||||
| 			Response response; | 			Response response; | ||||||
| 			response.setBody(templatePage.render()); | 			response.setBody(templatePage.render()); | ||||||
|   | |||||||
| @@ -23,6 +23,11 @@ SOFTWARE. | |||||||
| #include "../logger.h" | #include "../logger.h" | ||||||
| #include "../parser.h" | #include "../parser.h" | ||||||
| #include "../htmllink.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) | bool HandlerPageView::canAccess(std::string page) | ||||||
| { | { | ||||||
| @@ -86,19 +91,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
|  |  | ||||||
| 	std::optional<Revision> revision; | 	std::optional<Revision> revision; | ||||||
| 	std::string templatepartname; | 	std::string templatepartname; | ||||||
|  | 	auto revisionDao = this->database->createRevisionDao(); | ||||||
| 	try | 	try | ||||||
| 	{ | 	{ | ||||||
| 		if(revisionid > 0) | 		if(revisionid > 0) | ||||||
| 		{ | 		{ | ||||||
| 			if(!effectivePermissions(pagename).canSeePageHistory()) | 			if(!effectivePermissions(pagename).canSeePageHistory()) | ||||||
| 			{ | 			{ | ||||||
| 				auto current = this->database->createRevisionDao()->getCurrentForPage(pagename); | 				auto current = revisionDao->getCurrentForPage(pagename); | ||||||
| 				if(current && current->revision > revisionid) | 				if(current && current->revision > revisionid) | ||||||
| 				{ | 				{ | ||||||
| 					return errorResponse("Error", "You are not allowed to view older revisions of this page"); | 					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) | 			if(!revision) | ||||||
| 			{ | 			{ | ||||||
| 				return errorResponse("Revision not found", "No such revision found"); | 				return errorResponse("Revision not found", "No such revision found"); | ||||||
| @@ -118,7 +124,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 					return r; | 					return r; | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			revision = this->database->createRevisionDao()->getCurrentForPage(pagename); | 			revision = revisionDao->getCurrentForPage(pagename); | ||||||
| 			templatepartname = "page_view"; | 			templatepartname = "page_view"; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @@ -128,52 +134,31 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename, | |||||||
| 		return errorResponse("Database error", "While trying to fetch revision, a database error occured"); | 		return errorResponse("Database error", "While trying to fetch revision, a database error occured"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	TemplatePage page = this->templ->getPage(templatepartname); |  | ||||||
|  |  | ||||||
| 	Parser parser; | 	Parser parser; | ||||||
| 	Response result; | 	Response result; | ||||||
| 	result.setStatus(200); | 	result.setStatus(200); | ||||||
| 	std::string indexcontent; |  | ||||||
| 	std::string parsedcontent; |  | ||||||
|  |  | ||||||
| 	if(revisionid > 0) | 	RevisionRenderer revisionRenderer { *this->templ, *this->database, *this->urlProvider }; | ||||||
| 	{ |  | ||||||
| 		indexcontent = createIndexContent(parser, revision->content); |  | ||||||
| 		parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content); | 	std::string customtitle = parser.extractCommand("pagetitle", revision->content); | ||||||
| 	} | 	std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle); | ||||||
| 	else | 	/* TODO: Dynamic includes not considered, probably fine in practise */ | ||||||
| 	{ | 	std::string indexcontent = createIndexContent(parser, revision->content); | ||||||
| 		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); |  | ||||||
| 			this->cache->put(cachekeyparsedcontent, parsedcontent); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	std::string revisionstr = std::to_string(revision->revision); | 	std::string revisionstr = std::to_string(revision->revision); | ||||||
|  | 	TemplatePage page = this->templ->getPage(templatepartname); | ||||||
| 	page.setVar("content", parsedcontent); | 	page.setVar("content", parsedcontent); | ||||||
| 	page.setVar("index", indexcontent); | 	page.setVar("index", indexcontent); | ||||||
| 	page.setVar("editedby", revision->author); | 	page.setVar("editedby", revision->author); | ||||||
| 	page.setVar("editedon", utils::toISODate(revision->timestamp)); | 	page.setVar("editedon", utils::toISODateTime(revision->timestamp)); | ||||||
| 	page.setVar("historyurl", this->urlProvider->pageHistory(pagename)); | 	page.setVar("historyurl", this->urlProvider->pageHistory(pagename)); | ||||||
| 	page.setVar("revision", revisionstr); | 	page.setVar("revision", revisionstr); | ||||||
| 	setPageVars(page, pagename); | 	setPageVars(page, pagename); | ||||||
|  | 	if(!customtitle.empty()) | ||||||
|  | 	{ | ||||||
|  | 		page.setVar("title", createPageTitle(customtitle)); | ||||||
|  | 	} | ||||||
| 	std::string body = page.render(); | 	std::string body = page.render(); | ||||||
| 	if(revisionid == 0 && !this->userSession->loggedIn) | 	if(revisionid == 0 && !this->userSession->loggedIn) | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
| @@ -25,7 +25,11 @@ Response HandlerSearch::handleRequest(const Request &r) | |||||||
| 	std::string q = r.get("q"); | 	std::string q = r.get("q"); | ||||||
| 	if(q.empty()) | 	if(q.empty()) | ||||||
| 	{ | 	{ | ||||||
| 		return errorResponse("Missing search term", "No search term supplied"); | 		TemplatePage searchForm = this->templ->getPage("searchform"); | ||||||
|  | 		response.setBody(searchForm.render()); | ||||||
|  | 		response.setStatus(200); | ||||||
|  | 		setGeneralVars(searchForm); | ||||||
|  | 		return response; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auto pageDao = this->database->createPageDao(); | 	auto pageDao = this->database->createPageDao(); | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								iparser.h
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								iparser.h
									
									
									
									
									
								
							| @@ -2,16 +2,32 @@ | |||||||
| #define IPARSER_H | #define IPARSER_H | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <string_view> | #include <string_view> | ||||||
|  | #include <functional> | ||||||
| #include "headline.h" | #include "headline.h" | ||||||
| #include "database/pagedao.h" | #include "database/pagedao.h" | ||||||
| #include "urlprovider.h" | #include "urlprovider.h" | ||||||
| class IParser | class IParser | ||||||
| { | { | ||||||
|  |   protected: | ||||||
|  | 	static std::string empty(std::string_view key, std::string_view content) | ||||||
|  | 	{ | ||||||
|  | 		return ""; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	virtual std::string extractCommand(std::string cmdname, std::string content) const = 0; | 	virtual std::string extractCommand(std::string cmdname, const std::string &content) const = 0; | ||||||
| 	virtual std::vector<Headline> extractHeadlines(std::string content) const = 0; | 	virtual std::vector<Headline> extractHeadlines(const std::string &content) const = 0; | ||||||
| 	virtual std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const = 0; | 	virtual inline std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content) const | ||||||
| 	virtual std::vector<std::string> extractCategories(std::string content) const = 0; | 	{ | ||||||
|  | 		return parse(pagedao, provider, content, empty); | ||||||
|  | 	} | ||||||
|  | 	virtual std::string parse(const PageDao &pagedao, UrlProvider &provider, const std::string &content, | ||||||
|  | 							  const std::function<std::string(std::string_view, std::string_view)> &callback) const = 0; | ||||||
|  |  | ||||||
|  | 	virtual std::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(){}; | 	virtual ~IParser(){}; | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								page.h
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								page.h
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ class Page | |||||||
|   public: |   public: | ||||||
| 	Page(); | 	Page(); | ||||||
| 	std::string name; | 	std::string name; | ||||||
|  | 	std::string title; | ||||||
| 	bool listed; | 	bool listed; | ||||||
| 	unsigned int current_revision; | 	unsigned int current_revision; | ||||||
| 	unsigned int pageid; | 	unsigned int pageid; | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								pagelistrenderer.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										66
									
								
								pagelistrenderer.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -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
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										27
									
								
								pagelistrenderer.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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 | ||||||
							
								
								
									
										103
									
								
								parser.cpp
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								parser.cpp
									
									
									
									
									
								
							| @@ -27,10 +27,11 @@ SOFTWARE. | |||||||
| #include "parser.h" | #include "parser.h" | ||||||
| #include "utils.h" | #include "utils.h" | ||||||
| #include "htmllink.h" | #include "htmllink.h" | ||||||
| std::vector<Headline> Parser::extractHeadlines(std::string content) const | std::vector<Headline> Parser::extractHeadlines(const std::string &content) const | ||||||
| { | { | ||||||
| 	std::vector<Headline> result; | 	std::vector<Headline> result; | ||||||
| 	std::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])"; |  | ||||||
|  | 	std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])"; | ||||||
| 	std::regex headerfinder(reg); | 	std::regex headerfinder(reg); | ||||||
| 	auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder); | 	auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder); | ||||||
| 	auto end = std::sregex_iterator(); | 	auto end = std::sregex_iterator(); | ||||||
| @@ -40,13 +41,13 @@ std::vector<Headline> Parser::extractHeadlines(std::string content) const | |||||||
| 		auto smatch = *it; | 		auto smatch = *it; | ||||||
| 		Headline h; | 		Headline h; | ||||||
| 		h.level = utils::toUInt(smatch.str(1)); | 		h.level = utils::toUInt(smatch.str(1)); | ||||||
| 		h.title = smatch.str(2); | 		h.title = smatch.str(3); | ||||||
| 		result.push_back(h); | 		result.push_back(h); | ||||||
| 	} | 	} | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::vector<std::string> Parser::extractCategories(std::string content) const | std::vector<std::string> Parser::extractCategories(const std::string &content) const | ||||||
| { | { | ||||||
| 	std::vector<std::string> result; | 	std::vector<std::string> result; | ||||||
| 	std::string reg = R"(\[category\](.*?)\[/category\])"; | 	std::string reg = R"(\[category\](.*?)\[/category\])"; | ||||||
| @@ -62,7 +63,7 @@ std::vector<std::string> Parser::extractCategories(std::string content) const | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Parser::extractCommand(std::string cmdname, std::string content) const | std::string Parser::extractCommand(std::string cmdname, const std::string &content) const | ||||||
| { | { | ||||||
| 	std::string cmd = "[cmd:" + cmdname + "]"; | 	std::string cmd = "[cmd:" + cmdname + "]"; | ||||||
| 	std::string cmdend = "[/cmd:" + cmdname + "]"; | 	std::string cmdend = "[/cmd:" + cmdname + "]"; | ||||||
| @@ -116,31 +117,81 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider | |||||||
| 	return htmllink.render(); | 	return htmllink.render(); | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string Parser::parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const | std::string Parser::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; | 	std::string result; | ||||||
| 	// we don't care about commands, but we nevertheless replace them with empty strings | 	// we don't care about commands, but we nevertheless replace them with empty strings | ||||||
| 	std::regex tagfinder(R"(\[(b|i|u|li||ul|ol|link|wikilink|h\d|cmd:rename|cmd:redirect|category)*?\]((\s|\S)*?)\[/\1])"); | 	std::regex tagfinder( | ||||||
| 	result = utils::regex_callback_replacer(tagfinder, content, [&](std::smatch &match) { | 		R"(\[(b|i|u|li||ul|ol|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])"); | ||||||
| 		std::string tag = match.str(1); | 	result = utils::regex_callback_replacer( | ||||||
| 		std::string content = match.str(2); | 		tagfinder, content, | ||||||
| 		std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"}; | 		[&](std::smatch &match) | ||||||
| 		content = parse(pagedao, provider, content); |  | ||||||
| 		if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace)) |  | ||||||
| 		{ | 		{ | ||||||
| 			return "<" + tag + ">" + content + "</" + tag + ">"; | 			std::string tag = match.str(1); | ||||||
| 		} | 			std::string content = match.str(2); | ||||||
| 		if(tag == "link" || tag == "wikilink") | 			std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"}; | ||||||
| 		{ | 			if(tag != "code" && tag != "blockquote") | ||||||
| 			return this->processLink(pagedao, provider, | 			{ | ||||||
| 									 match); // TODO: recreate this so we don't check inside the function stuff again | 				content = parse(pagedao, provider, content, callback); | ||||||
| 		} | 			} | ||||||
| 		if(tag[0] == 'h') | 			if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace)) | ||||||
| 		{ | 			{ | ||||||
| 			return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">"; | 				return "<" + tag + ">" + content + "</" + tag + ">"; | ||||||
| 		} | 			} | ||||||
| 		return std::string(""); | 			if(tag == "link" || tag == "wikilink") | ||||||
| 	}); | 			{ | ||||||
|  | 				return this->processLink( | ||||||
|  | 					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>"); | 	result = utils::strreplace(result, "\r\n", "<br>"); | ||||||
| 	return result; | 	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)); }); | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								parser.h
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								parser.h
									
									
									
									
									
								
							| @@ -6,14 +6,21 @@ class Parser : public IParser | |||||||
| { | { | ||||||
|   private: |   private: | ||||||
| 	std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; | 	std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const; | ||||||
|  | 	std::string processImage(std::smatch &match) const; | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	std::string extractCommand(std::string cmdname, std::string content) const; | 	std::string extractCommand(std::string cmdname, const std::string &content) const; | ||||||
| 	std::vector<Headline> extractHeadlines(std::string content) const override; | 	std::vector<Headline> extractHeadlines(const std::string &content) const override; | ||||||
| 	std::vector<std::string> extractCategories(std::string content) const override; | 	std::vector<std::string> extractCategories(const std::string &content) const override; | ||||||
| 	std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override; | 	using IParser::parse; | ||||||
|  | 	virtual std::string parse( | ||||||
|  | 		const PageDao &pagedao, UrlProvider &provider, const std::string &content, | ||||||
|  | 		const std::function<std::string(std::string_view, std::string_view)> &callback) const override; | ||||||
|  | 	std::string parseDynamics( | ||||||
|  | 		const std::string &content, | ||||||
|  | 		const std::function<std::string(std::string_view, std::string_view)> &callback) const override; | ||||||
|  |  | ||||||
| 	using IParser::IParser; | 	using IParser::IParser; | ||||||
| 	~Parser(){}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // PARSER_H | #endif // PARSER_H | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ std::pair<std::string, std::string> Request::createPairFromVar(std::string var) | |||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		std::string key = var.substr(0, equal); | 		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)); | 		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) | void Request::initCookies(const std::string &cookiestr) | ||||||
| { | { | ||||||
| 	// TODO: find out what it really should be, ";" or "; "? | 	// TODO: find out what it really should be, ";" or "; "? | ||||||
| 	std::regex regex { ";+\\s?" }; | 	std::regex regex{";+\\s?"}; | ||||||
| 	auto cookiesplitted = utils::split(cookiestr, regex); | 	auto cookiesplitted = utils::split(cookiestr, regex); | ||||||
| 	for(const std::string &part : cookiesplitted) | 	for(const std::string &part : cookiesplitted) | ||||||
| 	{ | 	{ | ||||||
|   | |||||||
							
								
								
									
										67
									
								
								revisionrenderer.cpp
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										67
									
								
								revisionrenderer.cpp
									
									
									
									
									
										普通文件
									
								
							| @@ -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
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										29
									
								
								revisionrenderer.h
									
									
									
									
									
										普通文件
									
								
							| @@ -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 | ||||||
| @@ -12,17 +12,13 @@ | |||||||
| #include <filesystem> | #include <filesystem> | ||||||
| #include <sys/mount.h> | #include <sys/mount.h> | ||||||
| #include <sys/capability.h> | #include <sys/capability.h> | ||||||
| #include <qssb.h> | #include <exile.hpp> | ||||||
| #include "../logger.h" | #include "../logger.h" | ||||||
| #include "../utils.h" | #include "../utils.h" | ||||||
| #include "../random.h" | #include "../random.h" | ||||||
|  |  | ||||||
| #include "sandbox-linux.h" | #include "sandbox-linux.h" | ||||||
|  |  | ||||||
| /* TODO: make a whitelist approach. So far we simply blacklist |  | ||||||
|  * obvious systemcalls. To whitelist, we need to analyse our |  | ||||||
|  * dependencies (http library, sqlite wrapper, sqlite lib etc.) */ |  | ||||||
|  |  | ||||||
| bool SandboxLinux::supported() | bool SandboxLinux::supported() | ||||||
| { | { | ||||||
| 	std::fstream stream; | 	std::fstream stream; | ||||||
| @@ -45,7 +41,7 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths) | |||||||
| 	std::sort(fsPaths.begin(), fsPaths.end(), | 	std::sort(fsPaths.begin(), fsPaths.end(), | ||||||
| 			  [](const std::string &a, const std::string &b) { return a.length() < b.length(); }); | 			  [](const std::string &a, const std::string &b) { return a.length() < b.length(); }); | ||||||
|  |  | ||||||
| 	struct qssb_policy *policy = qssb_init_policy(); | 	struct exile_policy *policy = exile_init_policy(); | ||||||
| 	if(policy == NULL) | 	if(policy == NULL) | ||||||
| 	{ | 	{ | ||||||
| 		Logger::error() << "Failed to init sandboxing policy (worker) "; | 		Logger::error() << "Failed to init sandboxing policy (worker) "; | ||||||
| @@ -53,30 +49,19 @@ bool SandboxLinux::enable(std::vector<std::string> fsPaths) | |||||||
| 	} | 	} | ||||||
| 	for(unsigned int i = 0; i < fsPaths.size(); i++) | 	for(unsigned int i = 0; i < fsPaths.size(); i++) | ||||||
| 	{ | 	{ | ||||||
| 		qssb_append_path_policy(policy, QSSB_FS_ALLOW_READ | QSSB_FS_ALLOW_WRITE, fsPaths[i].c_str()); | 		exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, fsPaths[i].c_str()); | ||||||
| 	} | 	} | ||||||
| 	policy->drop_caps = 1; | 	policy->drop_caps = 1; | ||||||
| 	policy->not_dumpable = 1; | 	policy->not_dumpable = 1; | ||||||
| 	policy->no_new_privs = 1; | 	policy->no_new_privs = 1; | ||||||
| 	policy->mount_path_policies_to_chroot = 1; | 	policy->mount_path_policies_to_chroot = 1; | ||||||
| 	/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the | 	policy->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread"); | ||||||
| 	 * sense that more could be listed here and some critical ones are probably missing */ | 	if(exile_enable_policy(policy) != 0) | ||||||
|  |  | ||||||
| 	/* TODO: use qssb groups */ |  | ||||||
| 	long blacklisted_syscalls[] = {QSSB_SYS(setuid),	  QSSB_SYS(connect), QSSB_SYS(chroot),	 QSSB_SYS(pivot_root), |  | ||||||
| 								   QSSB_SYS(mount),		  QSSB_SYS(setns),	 QSSB_SYS(unshare),	 QSSB_SYS(ptrace), |  | ||||||
| 								   QSSB_SYS(personality), QSSB_SYS(prctl),	 QSSB_SYS(execveat), QSSB_SYS(execve), |  | ||||||
| 								   QSSB_SYS(fork)}; |  | ||||||
| 	qssb_append_syscalls_policy(policy, QSSB_SYSCALL_DENY_KILL_PROCESS, blacklisted_syscalls, |  | ||||||
| 								sizeof(blacklisted_syscalls) / sizeof(blacklisted_syscalls[0])); |  | ||||||
| 	qssb_append_syscall_default_policy(policy, QSSB_SYSCALL_ALLOW); |  | ||||||
|  |  | ||||||
| 	if(qssb_enable_policy(policy) != 0) |  | ||||||
| 	{ | 	{ | ||||||
| 		Logger::error() << "Sandbox: Activation of seccomp blacklist failed!"; | 		Logger::error() << "Sandbox: Activation of exile failed!"; | ||||||
| 		qssb_free_policy(policy); | 		exile_free_policy(policy); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 	qssb_free_policy(policy); | 	exile_free_policy(policy); | ||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), lastrevision integer, visible integer DEFAULT 1); | CREATE TABLE page(id INTEGER PRIMARY KEY, name varchar(256), title varchar(1024), lastrevision integer, visible integer DEFAULT 1); | ||||||
| CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64),  | CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64),  | ||||||
| password blob, salt blob, permissions integer, enabled integer DEFAULT 1); | password blob, salt blob, permissions integer, enabled integer DEFAULT 1); | ||||||
| CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32),  | CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32),  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								submodules/exile.h
									
									
									
									
									
										子模块
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								submodules/exile.h
									
									
									
									
									
										子模块
									
								
							 子模块 submodules/exile.h 已添加到 e711a1d53a
									
								
							 子模块 submodules/qssb.h 已从 0d7c5bd6d4 中删除
									
								
							| @@ -141,7 +141,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions, | |||||||
| 				   << revision.revision << "</a></td>" | 				   << revision.revision << "</a></td>" | ||||||
| 				   << "<td>" << revision.author << "</td>" | 				   << "<td>" << revision.author << "</td>" | ||||||
| 				   << "<td>" << revision.comment << "</td>" | 				   << "<td>" << revision.comment << "</td>" | ||||||
| 				   << "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>"; | 				   << "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>"; | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| @@ -155,7 +155,7 @@ std::string Template::renderRevisionList(const std::vector<Revision> &revisions, | |||||||
| 				   << "<td>" << revision.revision << "</td>" | 				   << "<td>" << revision.revision << "</td>" | ||||||
| 				   << "<td>" << revision.author << "</td>" | 				   << "<td>" << revision.author << "</td>" | ||||||
| 				   << "<td>" << revision.comment << "</td>" | 				   << "<td>" << revision.comment << "</td>" | ||||||
| 				   << "<td>" << utils::toISODate(revision.timestamp) << "</td></tr>"; | 				   << "<td>" << utils::toISODateTime(revision.timestamp) << "</td></tr>"; | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,7 +22,6 @@ class Template | |||||||
| 	std::string resolveIncludes(std::string_view content); | 	std::string resolveIncludes(std::string_view content); | ||||||
|  |  | ||||||
| 	std::string getPartPath(std::string_view partname); | 	std::string getPartPath(std::string_view partname); | ||||||
| 	std::string loadResolvedPart(std::string_view partname); |  | ||||||
| 	std::string loadPartContent(std::string_view partname); | 	std::string loadPartContent(std::string_view partname); | ||||||
| 	TemplatePage createPage(std::string_view name); | 	TemplatePage createPage(std::string_view name); | ||||||
|  |  | ||||||
| @@ -31,6 +30,7 @@ class Template | |||||||
| 			 ConfigVariableResolver &configVarsResolver, MapCache<TemplatePage> &pageCache); | 			 ConfigVariableResolver &configVarsResolver, MapCache<TemplatePage> &pageCache); | ||||||
|  |  | ||||||
| 	TemplatePage getPage(const std::string &pagename); | 	TemplatePage getPage(const std::string &pagename); | ||||||
|  | 	std::string loadResolvedPart(std::string_view partname); | ||||||
|  |  | ||||||
| 	std::string renderSearch(const std::vector<std::string> &results, | 	std::string renderSearch(const std::vector<std::string> &results, | ||||||
| 							 std::function<std::string(std::string)> callback) const; | 							 std::function<std::string(std::string)> callback) const; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| {qswiki:include:general_header} | {qswiki:include:general_header} | ||||||
| <div id="content" style="margin-top: 10px;"> | <div id="content" style="margin-top: 10px;"> | ||||||
| <h2>All pages</h2> | <h2>All pages</h2> | ||||||
| {qswiki:var:pagelist} | {qswiki:include:pagelistrender} | ||||||
| </div> | </div> | ||||||
| {qswiki:include:general_footer} | {qswiki:include:general_footer} | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | <ul> | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | </ul> | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | <li>{date}: <a href="{url}">{title}</a></li> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | <entry> | ||||||
|  |     <title>{qswiki:var:entrytitle}</title> | ||||||
|  |     <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> | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | </feed> | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <feed xmlns="http://www.w3.org/2005/Atom"> | ||||||
|  |   <author> | ||||||
|  |     <name>{qswiki:config:wikiownername}</name> | ||||||
|  |   </author> | ||||||
|  |   <title>{qswiki:config:wikiname} - {qswiki:var:subtitle}</title> | ||||||
|  |   <id>{qswiki:var:atomfeeduniqueid}</id> | ||||||
|  |   <link rel="self" href="{qswiki:var:atomselflink}"/> | ||||||
|  |   <updated>{qswiki:var:atomfeedupdate}</updated> | ||||||
| @@ -6,16 +6,15 @@ | |||||||
| <title>{qswiki:var:title}</title> | <title>{qswiki:var:title}</title> | ||||||
| <body> | <body> | ||||||
| <nav> | <nav> | ||||||
| 	<ul> |  | ||||||
| 	<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> |  | ||||||
| 	</ul> |  | ||||||
| 	<ul id="nav"> | 	<ul id="nav"> | ||||||
|     <li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> | 	<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> | ||||||
|     <li><a href="{qswiki:config:linkallpages}">All pages</a></li> | 	<li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> | ||||||
|     <li><a href="{qswiki:config:linkallcats}">All categories</a></li> | 	<li><a href="{qswiki:config:linkallpages}">All pages</a></li> | ||||||
|     </ul> | 	<li><a href="{qswiki:config:linkallcats}">All categories</a></li> | ||||||
|  | 	<li id="searchlink"><a href="{qswiki:config:linksearch}">Search</a></li> | ||||||
|  | 	</ul> | ||||||
|  |  | ||||||
|     <ul id="right" class="search"> | 	<ul id="right" class="search"> | ||||||
| 	<li><div><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form></div></li> | 	<li><div id="searchbar"><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form></div></li> | ||||||
| 	</ul> | 	</ul> | ||||||
| </nav> | </nav> | ||||||
|   | |||||||
| @@ -5,8 +5,10 @@ | |||||||
| 		<li style="font-size: 10pt">Powered by qswiki</li> | 		<li style="font-size: 10pt">Powered by qswiki</li> | ||||||
| 	</ul> | 	</ul> | ||||||
| </footer> | </footer> | ||||||
|  | <script src="{qswiki:config:highlightjspath}"></script> | ||||||
| <script> | <script> | ||||||
| {qswiki:include:js_session_refresh} | {qswiki:include:js_session_refresh} | ||||||
|  | hljs.highlightAll(); | ||||||
| </script> | </script> | ||||||
| </body> | </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -3,23 +3,20 @@ | |||||||
| <meta http-equiv="content-type" content="text/html; charset=UTF-8"> | <meta http-equiv="content-type" content="text/html; charset=UTF-8"> | ||||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
| <link rel="stylesheet" type="text/css" href="{qswiki:config:csspath}"> | <link rel="stylesheet" type="text/css" href="{qswiki:config:csspath}"> | ||||||
|  | <link rel="stylesheet" href="{qswiki:config:highlightjsstyle}"> | ||||||
|  |  | ||||||
| <title>{qswiki:var:title}</title> | <title>{qswiki:var:title}</title> | ||||||
| <body> | <body> | ||||||
| <nav> | <nav> | ||||||
| 	<ul> |  | ||||||
| 	<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> |  | ||||||
| 	</ul> |  | ||||||
| 	<ul id="nav"> | 	<ul id="nav"> | ||||||
|  | 	<li><a href="{qswiki:config:linkindex}"><h2>{qswiki:config:wikiname}</h2></a></li> | ||||||
|     <li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> |     <li><a href="{qswiki:config:linkrecent}">Recent changes</a></li> | ||||||
|     <li><a href="{qswiki:config:linkallpages}">All pages</a></li> |     <li><a href="{qswiki:config:linkallpages}">All pages</a></li> | ||||||
|     <li><a href="{qswiki:config:linkallcats}">All categories</a></li> |     <li><a href="{qswiki:config:linkallcats}">All categories</a></li> | ||||||
|     </ul> |     <li id="searchlink"><a href="{qswiki:config:linksearch}">Search</a></li> | ||||||
|  |  | ||||||
|     <ul> |  | ||||||
| 	{qswiki:var:headerlinks} | 	{qswiki:var:headerlinks} | ||||||
| 	</ul> |     </ul> | ||||||
|  |  | ||||||
|     <ul id="right" class="search"> |     <ul id="right" class="search"> | ||||||
| 	<li><div><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" value="search here" onfocus="this.value=''" name="q"/></form></div></li> | 	<li><div id="searchbar"><form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" value="search here" onfocus="this.value=''" name="q"/></form></div></li> | ||||||
| 	</ul> | 	</ul> | ||||||
| </nav> | </nav> | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | {qswiki:include:pagelistrender_header} | ||||||
|  | {qswiki:var:pagelistcontent} | ||||||
|  | {qswiki:include:pagelistrender_footer} | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | <div class="letter_search_result">{qswiki:var:groupname}</div> | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | Sort by: <a href="{qswiki:var:pagelistletterlink}">A-Z</a> - <a href="{qswiki:var:pagelistcreationdatelink}">Creation date</a> | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | <a href="{qswiki:var:href}">{qswiki:var:inner}</a><br> | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | {qswiki:include:general_header} | ||||||
|  | <main id="content"> | ||||||
|  | <h2>Search</h2><br> | ||||||
|  | Search content of pages: | ||||||
|  | <form action="{qswiki:config:wikipath}" method="GET"><input type="hidden" name="action" value="search"/><input type="text" name="q" value="search here" onfocus="this.value=''"></form> | ||||||
|  | </main> | ||||||
|  | {qswiki:include:general_footer} | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| {qswiki:include:general_header} | {qswiki:include:general_header} | ||||||
| <main id="content"> | <main id="content"> | ||||||
| <h2>Category: {qswiki:var:categoryname}</h2> | <h2>Category: {qswiki:var:categoryname}</h2> | ||||||
| {qswiki:var:pagelist} | {qswiki:include:pagelistrender} | ||||||
| </main> | </main> | ||||||
| {qswiki:include:general_footer} | {qswiki:include:general_footer} | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ h1, h2, h3 | |||||||
| { | { | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| 	padding: 0; | 	padding: 0; | ||||||
|         display: inline; | 	display: inline; | ||||||
| } | } | ||||||
|  |  | ||||||
| nav | nav | ||||||
| @@ -37,6 +37,7 @@ nav | |||||||
| 	grid-area: nav; | 	grid-area: nav; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| nav ul | nav ul | ||||||
| { | { | ||||||
| 	background-color: #062463; | 	background-color: #062463; | ||||||
| @@ -47,16 +48,12 @@ nav ul | |||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: center; | 	align-items: center; | ||||||
| 	flex-wrap: wrap; | 	flex-wrap: wrap; | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| nav li | nav li | ||||||
| { | { | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| 	padding: 0; | 	padding: 0; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| nav a, nav a:visited | nav a, nav a:visited | ||||||
| @@ -68,7 +65,6 @@ nav a, nav a:visited | |||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| 	line-height: 100%; | 	line-height: 100%; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| nav a:hover, nav a:focus | nav a:hover, nav a:focus | ||||||
| @@ -81,8 +77,6 @@ nav a:hover, nav a:focus | |||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| a, a:visited | a, a:visited | ||||||
| { | { | ||||||
| 	color: #062463;; | 	color: #062463;; | ||||||
| @@ -92,40 +86,36 @@ a:hover | |||||||
| { | { | ||||||
| 	background-color: #062463; | 	background-color: #062463; | ||||||
| 	color: white; | 	color: white; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #content | #content | ||||||
| { | { | ||||||
| padding: 15px; | 	padding: 15px; | ||||||
| font-family: monospace; | 	font-family: monospace; | ||||||
| font-size: 14pt; | 	font-size: 14pt; | ||||||
| flex: 1; | 	flex: 1; | ||||||
| grid-area: main | 	grid-area: main | ||||||
| } | } | ||||||
|  |  | ||||||
| #sidebar | #sidebar | ||||||
| { | { | ||||||
| grid-area: side; | 	grid-area: side; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #sidebar ul | #sidebar ul | ||||||
| { | { | ||||||
| list-style-type: none; | 	list-style-type: none; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #sidebar a, a:visited | #sidebar a, a:visited | ||||||
| { | { | ||||||
| 	color: #062463; | 	color: #062463; | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #sidebar a:hover | #sidebar a:hover | ||||||
| { | { | ||||||
|    background-color: #062463; | 	background-color: #062463; | ||||||
|    color: white; | 	color: white; | ||||||
| } | } | ||||||
|  |  | ||||||
| #content a, a:visited | #content a, a:visited | ||||||
| @@ -135,11 +125,10 @@ list-style-type: none; | |||||||
|  |  | ||||||
| #content a:hover | #content a:hover | ||||||
| { | { | ||||||
|    background-color: #062463; | 	background-color: #062463; | ||||||
|    color: white; | 	color: white; | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| footer | footer | ||||||
| { | { | ||||||
| 	width: 100%; | 	width: 100%; | ||||||
| @@ -160,6 +149,7 @@ footer ul | |||||||
| 	flex-wrap: wrap; | 	flex-wrap: wrap; | ||||||
| 	align-items: center; | 	align-items: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| footer li | footer li | ||||||
| { | { | ||||||
| 	margin: 0; | 	margin: 0; | ||||||
| @@ -168,14 +158,12 @@ footer li | |||||||
| 	line-height: 45px; | 	line-height: 45px; | ||||||
| 	color: white; | 	color: white; | ||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| 	//flex: 1 1 0; |  | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
| } | } | ||||||
|  |  | ||||||
| footer a, a:visited | footer a, a:visited | ||||||
| { | { | ||||||
| 	text-decoration: none; | 	text-decoration: none; | ||||||
|  |  | ||||||
| 	color: white; | 	color: white; | ||||||
| 	display: inline-block; | 	display: inline-block; | ||||||
| } | } | ||||||
| @@ -190,7 +178,7 @@ footer a:hover, ul#nav a:focus | |||||||
|  |  | ||||||
| #cats | #cats | ||||||
| { | { | ||||||
| background-color: #062463; | 	background-color: #062463; | ||||||
| } | } | ||||||
|  |  | ||||||
| .letter_search_result | .letter_search_result | ||||||
| @@ -198,20 +186,27 @@ background-color: #062463; | |||||||
| 	text-decoration: underline; | 	text-decoration: underline; | ||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
| ol | ol | ||||||
| { | { | ||||||
| 	counter-reset: item; | 	counter-reset: item; | ||||||
| } | } | ||||||
|  |  | ||||||
| .indexlink | .indexlink | ||||||
| { | { | ||||||
| display: block; | 	display: block; | ||||||
| } | } | ||||||
|  |  | ||||||
| .notexists | .notexists | ||||||
| { | { | ||||||
| 	color: red !important; | 	color: red !important; | ||||||
| 	font-weight: bold; | 	font-weight: bold; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #searchlink | ||||||
|  | { | ||||||
|  | 	display: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| @media screen and (orientation: portrait) | @media screen and (orientation: portrait) | ||||||
| { | { | ||||||
| @@ -219,13 +214,23 @@ display: block; | |||||||
| 	{ | 	{ | ||||||
| 		display: none; | 		display: none; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	#footer li:nth-child(-n+2) | 	#footer li:nth-child(-n+2) | ||||||
| 	{ | 	{ | ||||||
| 		display: none; | 		display: none; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	#footer li:nth-of-type(3) | 	#footer li:nth-of-type(3) | ||||||
| 	{ | 	{ | ||||||
| 		text-align: center; | 		text-align: center; | ||||||
| 		width: 100%; | 		width: 100%; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	#searchlink { | ||||||
|  | 		display: inline; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	#searchbar { | ||||||
|  | 		display: none; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
| SOFTWARE. | SOFTWARE. | ||||||
| */ | */ | ||||||
|  | #include <string_view> | ||||||
| #include "urlprovider.h" | #include "urlprovider.h" | ||||||
|  |  | ||||||
| std::string replaceSingleVar(std::string where, std::string varname, std::string replacement) | std::string replaceSingleVar(std::string where, std::string varname, std::string replacement) | ||||||
| @@ -52,6 +53,11 @@ std::string UrlProvider::allPages() | |||||||
| 	return config->linkallpages; | 	return config->linkallpages; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string UrlProvider::allPages(std::string rendertype) | ||||||
|  | { | ||||||
|  | 	return replaceSingleVar(config->linkallpagesrendertype, "type", rendertype); | ||||||
|  | } | ||||||
|  |  | ||||||
| std::string UrlProvider::allCats() | std::string UrlProvider::allCats() | ||||||
| { | { | ||||||
| 	return config->linkallcats; | 	return config->linkallcats; | ||||||
| @@ -62,6 +68,11 @@ std::string UrlProvider::page(std::string pagename) | |||||||
| 	return replaceOnlyPage(config->linkpage, 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) | std::string UrlProvider::linksHere(std::string pagename) | ||||||
| { | { | ||||||
| 	return replaceOnlyPage(config->linkshere, pagename); | 	return replaceOnlyPage(config->linkshere, pagename); | ||||||
| @@ -115,7 +126,42 @@ std::string UrlProvider::category(std::string catname) | |||||||
| { | { | ||||||
| 	return replaceSingleVar(config->linkcategory, "category", 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) | std::string UrlProvider::login(std::string page) | ||||||
| { | { | ||||||
| 	return replaceOnlyPage(config->loginurl, page); | 	return replaceOnlyPage(config->loginurl, page); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string UrlProvider::rootUrl() | ||||||
|  | { | ||||||
|  | 	return config->rooturl; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string UrlProvider::atomFeed(std::string filter) | ||||||
|  | { | ||||||
|  | 	return combine({config->rooturl, replaceSingleVar(config->atomurl, "filter", filter)}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string UrlProvider::combine(std::initializer_list<std::string> urls) | ||||||
|  | { | ||||||
|  | 	std::string result; | ||||||
|  | 	for(const std::string &url : urls) | ||||||
|  | 	{ | ||||||
|  | 		std::string_view urlview{url}; | ||||||
|  | 		if(result.back() == '/' && urlview.front() == '/') | ||||||
|  | 		{ | ||||||
|  | 			urlview.remove_prefix(1); | ||||||
|  | 		} | ||||||
|  | 		result += urlview; | ||||||
|  | 	} | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -22,11 +22,14 @@ class UrlProvider | |||||||
| 	std::string recentSorted(unsigned int limit, unsigned int offset, unsigned int sort); | 	std::string recentSorted(unsigned int limit, unsigned int offset, unsigned int sort); | ||||||
|  |  | ||||||
| 	std::string allPages(); | 	std::string allPages(); | ||||||
|  | 	std::string allPages(std::string rendertype); | ||||||
|  | 	 | ||||||
| 	std::string allCats(); | 	std::string allCats(); | ||||||
|  |  | ||||||
| 	std::string page(std::string pagename); | 	std::string page(std::string pagename); | ||||||
|  |  | ||||||
|  | 	std::string pageByTitle(std::string title); | ||||||
|  |  | ||||||
| 	std::string linksHere(std::string pagename); | 	std::string linksHere(std::string pagename); | ||||||
|  |  | ||||||
| 	std::string pageHistory(std::string pagename); | 	std::string pageHistory(std::string pagename); | ||||||
| @@ -46,8 +49,15 @@ class UrlProvider | |||||||
| 	std::string refreshSession(); | 	std::string refreshSession(); | ||||||
|  |  | ||||||
| 	std::string category(std::string catname); | 	std::string category(std::string catname); | ||||||
|  | 	std::string category(std::string catname, std::string rendertype); | ||||||
|  | 	 | ||||||
| 	std::string login(std::string page); | 	std::string login(std::string page); | ||||||
|  |  | ||||||
|  | 	std::string rootUrl(); | ||||||
|  |  | ||||||
|  | 	std::string atomFeed(std::string filter); | ||||||
|  |  | ||||||
|  | 	std::string combine(std::initializer_list<std::string> urls); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // LINKCREATOR_H | #endif // LINKCREATOR_H | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								utils.cpp
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								utils.cpp
									
									
									
									
									
								
							| @@ -136,8 +136,11 @@ std::string utils::getenv(const std::string &key) | |||||||
|  |  | ||||||
| std::string utils::readCompleteFile(std::string_view filepath) | std::string utils::readCompleteFile(std::string_view filepath) | ||||||
| { | { | ||||||
|  |  | ||||||
| 	std::fstream stream(std::string{filepath}); | 	std::fstream stream(std::string{filepath}); | ||||||
|  | 	if(!stream.is_open()) | ||||||
|  | 	{ | ||||||
|  | 		throw std::runtime_error("utils::readCompleteFile(): stream is not open"); | ||||||
|  | 	} | ||||||
| 	std::stringstream ss; | 	std::stringstream ss; | ||||||
| 	ss << stream.rdbuf(); | 	ss << stream.rdbuf(); | ||||||
| 	std::string content = ss.str(); | 	std::string content = ss.str(); | ||||||
| @@ -166,7 +169,10 @@ std::string utils::regex_callback_replacer(std::regex regex, const std::string & | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| std::string utils::toISODate(time_t t) | /* TODO: Convert to C++20, but currently the state is rather poor and would | ||||||
|  |  * require workarounds, so keep it this way for now, and do it properly | ||||||
|  |  * once compiler support gets there */ | ||||||
|  | std::string utils::formatLocalDate(time_t t, std::string format) | ||||||
| { | { | ||||||
| 	struct tm lt; | 	struct tm lt; | ||||||
| 	if(localtime_r(&t, <) == nullptr) | 	if(localtime_r(&t, <) == nullptr) | ||||||
| @@ -174,7 +180,7 @@ std::string utils::toISODate(time_t t) | |||||||
| 		return {}; | 		return {}; | ||||||
| 	} | 	} | ||||||
| 	char result[20]; | 	char result[20]; | ||||||
| 	size_t x = strftime(result, sizeof(result), "%Y-%m-%d %H:%M:%S", <); | 	size_t x = strftime(result, sizeof(result), format.c_str(), <); | ||||||
| 	if(x == 0) | 	if(x == 0) | ||||||
| 	{ | 	{ | ||||||
| 		return {}; | 		return {}; | ||||||
| @@ -182,6 +188,16 @@ std::string utils::toISODate(time_t t) | |||||||
| 	return std::string{result}; | 	return std::string{result}; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string utils::toISODateTime(time_t t) | ||||||
|  | { | ||||||
|  | 	return utils::formatLocalDate(t, "%Y-%m-%d %H:%M:%S"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::string utils::toISODate(time_t t) | ||||||
|  | { | ||||||
|  | 	return utils::formatLocalDate(t, "%Y-%m-%d"); | ||||||
|  | } | ||||||
|  |  | ||||||
| std::string utils::trim(std::string_view view) | std::string utils::trim(std::string_view view) | ||||||
| { | { | ||||||
| 	std::string_view chars = " \t\n\r"; | 	std::string_view chars = " \t\n\r"; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								utils.h
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								utils.h
									
									
									
									
									
								
							| @@ -81,7 +81,9 @@ inline unsigned int toUInt(const std::string &str) | |||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | std::string formatLocalDate(time_t t, std::string format); | ||||||
| std::string toISODate(time_t t); | std::string toISODate(time_t t); | ||||||
|  | std::string toISODateTime(time_t t); | ||||||
|  |  | ||||||
| template <class T> inline std::string toString(const T &v) | template <class T> inline std::string toString(const T &v) | ||||||
| { | { | ||||||
|   | |||||||
		在新工单中引用
	
	屏蔽一个用户