比较提交

...

159 次代码提交

作者 SHA1 备注 提交日期
79d69f4b65 cache: Introduce StringCache, switch to unordered_map, default to memory cache if fs cache dir not given 2024-10-13 15:14:19 +02:00
bfeacb0510 submodules: cpp-httplib: Update 2024-09-20 20:25:55 +02:00
c6013338a9 HandlerFeedGenerator: Remove 'entryUpdated' as it requires another approach
Any small change on a page "updates" the feed, which is misleading to clients.
May need "minor edit" or something. For now, get rid of it.
2024-06-09 15:42:39 +02:00
dab0b94ec4 submodules: cpp-httplib: Update 2024-06-09 10:42:03 +02:00
2ebdbd0b6d parser: Consume superfluous newlines, add [br] and [p] 2024-06-09 10:32:50 +02:00
61e84a98c7 dynamic: Add dynamicpostrenderer 2024-03-16 22:07:37 +01:00
61f289625c RevisionRenderer: Add 'dynamicpostrenderer' 2024-03-16 22:07:37 +01:00
6a12070d0d add cmd:feedlisted and cmd:listed 2024-03-16 22:07:37 +01:00
03c6816528 tree: visible => listed 2024-03-16 22:07:37 +01:00
18f4ad9d51 setup: sqlite: Rename visible => listed, Add 'feedlisted' to indicate whether page should be listed in feeds 2024-03-16 22:07:37 +01:00
84adaa934a template: getPartPath(): Ensure return path isn't outside template dir
user-input to this function might become possible soon
2024-03-16 22:07:37 +01:00
579fadfb10 parser: Add [content] tag, add extractFirstTag() method 2024-03-16 22:07:37 +01:00
ff01a00217 cache: mapcache: Add <string> header 2024-03-16 22:07:37 +01:00
daed17848c handlers: handlerpageedit: Handle [cmd:parentpage] 2024-03-16 22:07:37 +01:00
0fb0457dbb setup: sqlite: Add "parent" refere to "page" 2024-03-16 22:07:37 +01:00
2d5d483790 database: pagedao: Add-support for subpages 2024-03-16 22:07:37 +01:00
f08e235d03 HandlerPageEdit: Use clearForPage() before setting Permissions 2023-08-11 09:22:04 +02:00
8998fb8793 PermissionsDao: Add clearForPage() 2023-08-11 09:21:02 +02:00
9088154372 submodules: cpp-httplib: Update 2023-08-09 13:56:04 +02:00
8a2d9fdc58 {page,categorydao}sqlite: Add missing ROLLBACK 2023-08-09 13:53:49 +02:00
c0049fc7b6 sqlite: Use per-thread connections 2023-07-29 10:00:27 +02:00
fe533a5076 Disable caching if no cache dir given 2023-07-29 09:52:07 +02:00
ec3cbe3f76 cache: Add dummy NoCache class 2023-07-29 09:45:51 +02:00
1095d38b02 Add [cmd:permissions] 2023-07-28 15:04:58 +02:00
234db99ef5 dynamic,HandlerFeedGenerator: Check for read permissions 2023-07-27 18:02:12 +02:00
32af0e2857 handlers: HandlerPageEdit: Add transaction rollback on exception 2023-07-26 20:21:44 +02:00
aa362331a5 submodules: cpp-httplib: Update 2023-05-31 21:16:58 +02:00
64b6e7e61c parser: Add [s] 2023-04-01 13:48:11 +02:00
3bc51b9d34 submodules: cpp-httplib: Update 2023-03-09 09:03:22 +01:00
7dee7bc06b Update .gitignore 2023-02-03 16:08:39 +01:00
afea31f231 revisionrenderer: renderContent(): Add modifydatetime var 2023-02-03 16:08:39 +01:00
004665e943 qswiki: Add background worker, currently to clean old sessions mainly 2023-02-03 16:08:39 +01:00
b9595bd513 database: Add fetch() for SessionDao 2023-02-03 16:08:28 +01:00
48e3614e78 Retire javascript session refresh 2023-02-03 16:07:11 +01:00
7c086e0d78 logger: Initialize members with default vals 2023-01-19 12:17:52 +01:00
24121a1618 Makefile: Add libstdc++ assertions + randomize link order 2023-01-16 20:43:34 +01:00
172129179e submodules: cpp-httplib: Update 2022-12-13 10:18:10 +01:00
8603e55c59 config: Adjust default payload length 2022-12-13 10:17:40 +01:00
e326e09a36 gateway: httpgateway: Adjust default payload length for urlencoded content 2022-12-13 10:16:30 +01:00
a71c3da129 template: Enable syntax highlighting using highlight.js 2022-11-08 08:48:22 +01:00
fbfe5510a1 parser: Render <code> and <blockquote> preformated 2022-11-08 08:47:53 +01:00
78b9e5e043 README: update 2022-10-30 11:30:27 +01:00
ef8eebdbaa database: Add missing virtual destructors for some classes 2022-10-24 15:17:04 +02:00
7ef9d7f020 sandbox: Use exile_vows_from_str() for seccomp policy 2022-10-23 21:36:58 +02:00
d3bd5f79cc HandlerFeedGenerator: Don't escape title again 2022-08-20 12:57:54 +02:00
995a980d49 HandlerPageEdit: Add 'frompage' GET parameter to use a page as a template 2022-08-20 12:41:30 +02:00
2ee760d9ca submodules: cpp-httplib: Update 2022-08-20 12:31:15 +02:00
ffeea8cfd1 submodules: exile.h: Update 2022-08-20 12:31:15 +02:00
a81963181a RevisionDaoSqlite: Fix cases where we got pageid instead of the page name 2022-08-20 12:31:15 +02:00
d18c0669ce handlers: HandlerPageEdit: Use RevisionRenderer 2022-08-20 12:31:15 +02:00
ecd45a61c8 HandlerPageView: Use RevisionRenderer 2022-08-20 12:30:47 +02:00
2b1c3c71b7 HandlerFeedGenerator: Use RevisionRenderer 2022-08-20 12:30:47 +02:00
a1042720a7 Add RevisionRenderer 2022-08-20 12:30:47 +02:00
6dbe8d34dc Add DynamicContentFactory 2022-08-20 10:24:23 +02:00
51b259f385 HandlerPageView: Set 'pagetitle' dynamic variable 2022-08-17 22:41:15 +02:00
0cad11004f HandlerPageView: Drop partial caches
Anonymous access is the common case, this is already cached.

For other cases this logic cannot be justified as there
is hardly a practical gain for that extra complexity.
2022-08-17 21:57:19 +02:00
2102cf4e6b Add [cmd:allowinclude]
Pages must specify whether they want to be included or not.

Otherwise there too many surprises possibe, enforcing access
restrictions will get more complicated
2022-08-17 21:54:34 +02:00
86890660f4 HandlerPageView: Set 'createdon' dynamic variable 2022-08-17 19:35:52 +02:00
0325cdf936 Parser: Add code,blockquote and begin img tag 2022-04-19 19:50:22 +02:00
b0c715c4ea Parser: Add cmd:visible, it's also a tag 2022-04-03 14:35:14 +02:00
63a4437de7 HandlerFeedGenerator: Fix comparator condition 2022-04-03 12:07:43 +02:00
c88889b10b Parser: Fix headline extraction for the default case broken by fbca85e5 2022-04-03 11:48:16 +02:00
634cb2d7ee Handlers: HandlerAllPages / HandlerCategory: Use PageListRenderer 2022-04-03 11:15:02 +02:00
1c1416934b UrlProvider: Add Links to specify rendertype in allpages / category view 2022-04-03 11:15:02 +02:00
622ef5af6a Database: PageDao/CategoryDao: Return 'Page' object, not pagename string 2022-04-03 11:15:02 +02:00
5f83981d68 utils: readCompleteFile(): Fix error string which is too generic without context 2022-04-03 11:07:26 +02:00
b5b2a42839 Add PageListRenderer: Allow rendering pagelist by creationdate and A-Z as before 2022-04-03 11:06:19 +02:00
e217218a3f Add Grouper: Maps a key to a vectors 2022-04-03 11:05:13 +02:00
82c081385b Request: createPairFromVar(): Explicitly decode value
May not be the case on POST requests.
2022-03-30 22:59:20 +02:00
91951abe9c Revert "dynamic: DynamicContentPostList: Link using UrlProvider::pageByTitle()"
This reverts commit 9b35e43161.
2022-03-29 22:45:17 +02:00
9b35e43161 dynamic: DynamicContentPostList: Link using UrlProvider::pageByTitle() 2022-03-29 22:37:45 +02:00
73a4e4c10f UrlProvider: Add pageByTitle() 2022-03-29 22:37:20 +02:00
1e224fdac6 HandlerPageView: First resolve all dynamics before parsing tags
Should make more sense this way, especially to extract headlines.
2022-03-29 22:36:05 +02:00
fbca85e5ed Parser: Seperate parseDynamcis(), fix headline extraction with tags inside them 2022-03-29 22:35:45 +02:00
15e4f081cc HandlerPage: Support lookup by title 2022-03-29 22:34:22 +02:00
e876b15c5d dynamic: Add DynamicContent{Get,Set}Var 2022-03-29 22:33:32 +02:00
3e736db0ef database: pagedao: Add findByTitle() 2022-03-29 22:30:20 +02:00
03c5646858 HandlerPageView: Parse dynamically included pages recursively 2022-03-28 21:25:37 +02:00
ba06d04a08 HandlerFeedGenerator: Error when cat does not exists (instead of empty feed) 2022-03-28 20:24:57 +02:00
5bb3f55945 HandlerFeedGenerator: Improvements to make feed vlaid 2022-03-28 20:06:42 +02:00
1ae5495e61 Dynamic: Add DynamicContentIncludePage to allow including pages 2022-03-27 21:36:53 +02:00
7bb7600d39 HandlerFeedGenerator: Add caching 2022-03-27 21:22:00 +02:00
f5eb36e7bb DynamicContentPostList: Ignore invisible entries 2022-03-27 20:03:28 +02:00
c891b36339 Makefile: Build dynamic content generators, adjust for exile update 2022-03-27 20:00:21 +02:00
d17e596563 sandbox-linux: include exile.hpp 2022-03-27 19:59:52 +02:00
761471f243 template: Add template for atom feed 2022-03-27 19:54:07 +02:00
9ac0ad0ccd template: Add template for dynamic postlist 2022-03-27 19:53:48 +02:00
c30e09d44d HandlerFactory: Wire up HandlerFeedGenerator 2022-03-27 19:52:45 +02:00
bcc3737d88 UrlProvider: Introduce combine(), rootUrl(), atomFeed() 2022-03-27 19:51:53 +02:00
9520aabe5c Config: Require rooturl,atomurl 2022-03-27 19:50:51 +02:00
4854ea85f2 Begin HandlerFeedGenerator: Generates Atom feeds for categories (or all pages) 2022-03-27 19:48:57 +02:00
16c352c6af utils: readCompleteFile(): Throw exception if file can't be opened 2022-03-27 19:47:52 +02:00
f7cf06cdd5 Page: Add 'title' column, storing title of last revision 2022-03-27 09:23:35 +02:00
ac793c6d39 handlers: HandlerPageView: Add '[dynamic:postlist]' tag by callback 2022-03-27 08:37:55 +02:00
a524674149 Begin dynamic content generators 2022-03-27 08:36:25 +02:00
a4a45d9add Parser: Add callback support for unknown "tags" 2022-03-27 08:31:59 +02:00
44c27ed8b4 Template: Make loadResolvedPart() public 2022-03-27 08:30:51 +02:00
433b5da2bb template: Adjust after renaming: Use utils::toISODateTime() 2022-03-27 08:30:20 +02:00
c5435c52f4 utils: Rename/Add date functions 2022-03-27 08:29:13 +02:00
b2a7ea4031 Parser: Take 'content' by const reference. 2022-01-23 10:12:37 +01:00
1d5bf80710 HandlerPageView: Add [cmd:pagetitle] to set custom per-page titles 2022-01-23 10:02:46 +01:00
ca0c8a94fb sandbox: Use exile.h vow promises 2021-12-29 11:13:47 +01:00
5870102aa9 submodules: cpp-httplib: Update to v0.10.1 2021-12-29 11:13:05 +01:00
32544c8f68 submodules: cpp-httplib: Update module 2021-12-02 10:15:36 +01:00
d0e7ff0a8c sandbox: Switch to exile.h (former qssb.h) 2021-12-02 10:15:11 +01:00
696ff9b7e7 sandbox: Allow TIME group 2021-12-02 10:06:21 +01:00
5570154113 fscache: Fix starts_with() broken by b41a5f4e5b 2021-11-30 19:14:59 +01:00
4f6bcd27b4 sandbox: Sync iwth qssb.h upstream: Use whitelisting and groups 2021-11-14 21:54:08 +01:00
bbe74a2c50 handlers: HandlerSearch: Add missing call to setGeneralVars() 2021-11-14 21:54:08 +01:00
5db9305408 template: display headers inline (backport from production) 2021-11-14 21:54:08 +01:00
c90e26a374 template: Remove space between header links 2021-10-26 23:22:05 +02:00
b297498ca9 template: Don't render searchbar in portrait. Show link instead
Issue: #18
2021-10-26 23:07:37 +02:00
fdcef18861 HandlerSearch: Render a form when no q= given 2021-10-26 23:07:37 +02:00
75268e0073 sandbox: Disable Landlock due to qssb.h issue #19 2021-10-26 23:07:37 +02:00
cc4506b918 submodules: sync with latest upstream 2021-10-26 10:47:02 +02:00
017932e673 Fix -wreturn-type warnings 2021-10-25 18:13:25 +02:00
ed61003636 C++20: Avoid implicit capture 2021-10-25 18:09:46 +02:00
ad42c0f046 handlers: HandlerPageEdit: canAccess(): Check with effectivePermisisons() 2021-10-25 18:07:23 +02:00
6304554358 Add [[maybe_unused]] to silence unhelpful warnings 2021-10-25 17:56:37 +02:00
0aa4bca6cc Fix gcc-11 etc. compilation errors 2021-10-25 13:31:40 +02:00
b41a5f4e5b fscache: removePrefix(): use starts_with() 2021-10-19 15:07:10 +02:00
873401694e Remove utils::hasKey(), as we now have .contains() 2021-10-19 12:48:45 +02:00
d035579da7 Makefile: Switch to C++20 2021-10-19 12:47:57 +02:00
eb292a7d79 CategoryDaoSqlite: deleteCategory(): Fix broken query 2021-10-12 20:10:35 +02:00
420e541e75 CLI: Implement category delete,show,list 2021-10-12 20:02:43 +02:00
c18178a50f SqliteQueryOption: build(): Handle includeInvisible = true properly 2021-10-12 20:02:03 +02:00
7a2f15cabe handlers: Introduce HandlerVersion to return the verison string 2021-10-10 22:39:35 +02:00
c9dc3416d7 TemplatePage: Change 'content' to shared_ptr 2021-10-10 22:32:13 +02:00
92be470545 Makefile: Remove LDFLAGS from .o compilation 2021-10-10 20:17:38 +02:00
d5485a833f CLIConsole: On 'exit', ask whether to quit attached instance 2021-10-10 20:15:28 +02:00
0bdb22c170 gateway: HttpGateway: convertRequest(): Don't convert param to std::string 2021-10-10 20:15:28 +02:00
eb49b013a7 Makefile: Add profile target 2021-10-10 20:15:28 +02:00
9593429f95 utils: trim(): Take string_view 2021-10-10 20:15:28 +02:00
86ac86b83f Response: addHeader(): Pass by value, not reference 2021-10-10 20:15:28 +02:00
92e7390056 utils: Pass by value where it makes sense 2021-10-10 20:15:28 +02:00
b1a8572eb6 utils: hasKey(), getKeyOrEmpty(), getAll(): Take params as references
Somehow, the fact that multimap was being copyied slipped through.
2021-10-10 20:15:28 +02:00
44ade88cae Template: createPage(): Take std::string_view 2021-10-10 20:15:28 +02:00
aadb623bf7 UserDaoSqlite: Remove redundant std::move 2021-10-08 23:38:22 +02:00
828d827c3d Adjust to new Template::getPage() returning value, not reference 2021-10-08 00:11:58 +02:00
8ffa64beea Template: Use MapCache, getPage(): Return value, not reference 2021-10-08 00:11:30 +02:00
e970ba1682 cache: MapCache: Introduce MapCache, thread-safe cache (key/value store) 2021-10-08 00:08:00 +02:00
b59e81a41d CLI: Begin 'page' and 'pageperms' commands 2021-10-05 21:57:20 +02:00
f002969cc1 PermissionsDao: Add save() 2021-10-05 21:51:31 +02:00
c4072a7e95 Sandbox: Remove multiple stages
While interesitng in theory, there is nothing to be gained here,
because we don't really have user input at those early stages.

As we are also not a privileged process, those early stage
sandboxes in the end are not worth it, since they increase
complexity while there is no benefit in practise.

So, reduce those 3 stages to a single one (enable()), which we
activate after CLI server has launched.
2021-10-03 23:53:56 +02:00
257675485d Template: Remove redundant debug output 2021-10-03 23:13:59 +02:00
94ade7238e CLI: Add 'version' command 2021-10-03 23:04:46 +02:00
fa5e75893f Add version.{h,cpp}: Returning version info 2021-10-03 23:01:19 +02:00
3d0fce590b Introduce CLI
main: Parse args using getopt_long() in main().

Begin implementation of a CLI. It can be launched
using ./qswiki config --cli.

Allow connecting to another instance using "attach" command.
This uses Unix domain sockets, and in the future can be used
to drop caches, reload template, etc.

Closes: #21
2021-10-03 17:05:46 +02:00
1082f8ac5a Permissions: Add toString()
Get a (reasonable) string representation of the permissions contained
in a Permissions object.
2021-10-03 17:01:48 +02:00
8b044d712b Authenticator: Introduce AUTH_DEFAULT_SALT_SIZE 2021-10-03 17:01:03 +02:00
5037a17fba utils: introduce trim() 2021-10-03 16:51:04 +02:00
164b2c19ee userDao: Implement list() 2021-10-03 16:51:04 +02:00
8d685dc581 Makefile: Remove -lseccomp as we don't need it anymore 2021-09-29 18:33:45 +02:00
ed43f5f700 submodules: update cpp-httplib 2021-09-29 18:28:18 +02:00
10f00aeb45 main: Pass absolute path of config file
As sandboxing code chroots and chdirs away,
2021-09-23 17:13:08 +02:00
67eb8b6428 sandbox: adjust to latest qssb.h 2021-09-23 17:13:08 +02:00
f26fd19fb4 submodules: sync with latest upstream 2021-09-23 17:13:08 +02:00
204a72da1f setup: Fix broken FTS DELETE op
Thie previous DELETE statement lead to strange
behaviours. It was pure luck this did not blow up
before all these years. It appears it may leave the index
in an undefined state, and the database recently started
to display strange behaviour in connection with newer sqlite
version.

Now, just remove the previous revision from the FTS index,
as for now, search only cares about the most recent revisions.

Also, remove redundant UPDATE trigger on revision table
We never update revisions, thus such trigger is simply
redundant.

Relevant: https://gitlab.gnome.org/GNOME/tracker/-/merge_requests/353
2021-09-23 17:13:08 +02:00
88816a4015 utils: html_xss(): Add ' and &
They REALLY should have been there from the beginning...
2021-06-15 18:37:52 +02:00
共有 134 个文件被更改,包括 2770 次插入575 次删除

2
.gitignore vendored
查看文件

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

6
.gitmodules vendored
查看文件

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

查看文件

@ -1,12 +1,14 @@
CPPSTD=c++20
#CFIFLAGS=-fsanitize=cfi -fvisibility=hidden -fsanitize=cfi -flto
#Does not work reliably atm
CFIFLAGS=
CXXFLAGS=-std=c++17 -O0 -g -no-pie -pipe -MMD -Wall -Wextra
RELEASE_CXXFLAGS=-std=c++17 -O3 -pipe -MMD -Wall -Wextra
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs -lseccomp
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/qssb.h
CXX=g++
CXXFLAGS=-std=$(CPPSTD) -O2 -g -no-pie -pipe -MMD -Wall -Wextra -DGLIBCXX_ASSERTIONS -D_LIBCPP_ENABLE_ASSERTIONS=1 $(CFIFLAGS)
RELEASE_CXXFLAGS=-std=$(CPPSTD) -O3 -pipe -MMD -Wall -Wextra -DGLIBCXX_ASSERTIONS -D_LIBCPP_ENABLE_ASSERTIONS=1 $(CFIFLAGS)
LDFLAGS=-lsqlite3 -lpthread -lcrypto -lstdc++fs $(CFIFLAGS)
INCLUDEFLAGS=-I submodules/sqlitemoderncpp/hdr -I submodules/cpp-httplib -I submodules/exile.h
SOURCES=$(wildcard *.cpp)
SOURCES+=$(wildcard gateway/*.cpp)
@ -14,6 +16,7 @@ SOURCES+=$(wildcard handlers/*.cpp)
SOURCES+=$(wildcard database/*.cpp)
SOURCES+=$(wildcard cache/*.cpp)
SOURCES+=$(wildcard sandbox/*.cpp)
SOURCES+=$(wildcard dynamic/*.cpp)
HEADERS=$(wildcard *.h)
HEADERS+=$(wildcard gateway/*.h)
@ -21,7 +24,7 @@ HEADERS+=$(wildcard handlers/*.h)
HEADERS+=$(wildcard database/*.h)
HEADERS+=$(wildcard cache/*.h)
HEADERS+=$(wildcard sandbox/*.h)
HEADERS+=$(wildcard dynamic/*.h)
OBJECTS=$(patsubst %.cpp, %.o, $(SOURCES))
WIKIOBJECTS=$(filter-out test.o, $(OBJECTS))
@ -35,16 +38,25 @@ GTEST_DIR = /home/data/SOURCES/gtest/googletest
GTESTS_TESTDIR = ./tests/
GTEST_CXXFLAGS=-std=c++17 -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra
GTEST_CXXFLAGS=-std=$(CPPSTD) -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -g -O0 -pipe -Wall -Wextra
GTEST_LDFLAGS=-lsqlite3 -g -O0 -lpthread -lcrypto -lstdc++fs
GTEST_OBJECTS=$(filter-out qswiki.o, $(WIKIOBJECTS))
.DEFAULT_GOAL := qswiki
release: CXXFLAGS=$(RELEASE_CXXFLAGS)
profile: CXXFLAGS=$(RELEASE_CXXFLAGS) -pg
profile: LDFLAGS+= -pg
release: qswiki
qswiki: $(WIKIOBJECTS)
$(CXX) $(WIKIOBJECTS) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
profile: 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) $(shell shuf -e $(WIKIOBJECTS) exile.o ) ${LDFLAGS} ${INCLUDEFLAGS} -o qswiki
test: $(TESTOBJECTS)
$(CXX) $(TESTOBJECTS) ${LDFLAGS} -o test
@ -53,9 +65,11 @@ gtest: $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS)
$(CXX) -o gtest $(GTESTS_TESTDIR)/*.cpp $(GTEST_OBJECTS) $(GTEST_CXXFLAGS) $(GTEST_DIR)/src/gtest_main.cc $(GTEST_DIR)/src/gtest-all.cc $(GTEST_LDFLAGS)
%.o:%.cpp
$(CXX) ${CXXFLAGS} ${LDFLAGS} ${INCLUDEFLAGS} -c -o $@ $<
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -c -o $@ $<
version.o:version.cpp
$(CXX) ${CXXFLAGS} ${INCLUDEFLAGS} -DGITCOMMIT=\"$(shell git rev-parse --short HEAD)\" -c -o $@ $<
clean:
rm -f $(OBJECTS) $(DEPENDS)
rm -f exile.o $(OBJECTS) $(DEPENDS)

查看文件

@ -1,81 +1,84 @@
# qswiki
About
====
qswiki is a wiki software, intended for small wikis. Originally
implemented in C, it's now written in C++.
## About
qswiki is a wiki software, intended for my needs. Originally implemented in C, it's now written in C++.
History
====
A couple of years ago, I wanted to setup a personal wiki on my raspberry
pi. However, the distribution I used back then did not have a PHP package
## Dude... why?
tl;dr: It was a playground, an experiment (taken too far). I guess at some point I couldn't stop, because I've already
started.
### History
Several years ago, I wanted to setup a personal wiki on my raspberry
pi. However, the distribution I used back then did not have a PHP package
for ARM. So instead of switching distributions or searching for other
wikis that I could use, I decided I would write one in C. Yes,
that's an odd way to approach the problem and indeed, I may have had too
much time back then. Also, I wanted to see how it's like to write a
wikis that I could use, I simply decided I would write one in C. Yes,
that's an odd way to approach the problem and indeed, I may have had too
much time back then. Also, I wanted to see how it's like to write a
"web app" in C and wanted to sharpen my C skills a little bit.
Of course, it's pretty straightforward at first. No really: Just use CGI.
And indeed, that would have been more than enough for my use cases.
Then I decided to play around and started using FastCGI (with the official
Of course, it's pretty straightforward at first. No really: Just use CGI
and print your HTML to stdout.And indeed, that would have been more than enough for my use cases.
But then I decided to play around and started using FastCGI (with the official
library from now defunct fastcgi.com) and created a multi-threaded version.
It initially used a "pile of files database", but that became too painful,
It initially used a "pile of files database", but that became too painful,
so then I started using sqlite.
C++
---
Eventually, since it was mostly a playground for me, the code became
unmaintainable. Furthermore, I wanted something quick and given that
it was CGI, I didn't bother taking care of memory leaks.
After initiating a FastCGI interface, they became an issue and then the
Eventually, since it was mostly a playground for me, the code became
unmaintainable. Furthermore, I initially wanted something quick and given that
it was CGI, I didn't bother taking care of memory leaks.
After initiating a FastCGI interface, they became an issue and then the
task of avoiding memory leaks became too annoying. And of course, C does n
ot include any "batteries" and while I could manage, this too was another
ot include any "batteries" and while I could manage, this too was another
good reason.
Overall, I am just continuing the experiment with C++17 now. It's not
nearly as bad as you would expect perhaps. Some things are surprisingly
convenient even. Still, the standard library is lacking and
I would hope for a some better built-in Unicode support in future C++
Overall, I am just continuing the experiment with >=C++17 now. It's not
nearly as bad as you would expect perhaps. Some things are surprisingly
convenient even. Still, the standard library is lacking and
I would hope for a some better built-in Unicode support in future C++
standards.
Features
========
To be fair, at this point it doesn't even have a "diff" between revisions
yet and does not have features that would make you prefer it over other
wikis.
## Features
Some essential features are lacking, such as a diff between revisions,
user registration UI, etc.
It doesn't compete with any other software anyway.
- CGI
- HTTP server using the header only library cpp-httplib. It's more
portable and more "future-proof" than FastCGI (since the official website
- HTTP server using the header only library [cpp-httplib](https://github.com/yhirose/cpp-httplib). It's more
portable and more "future-proof" than FastCGI (since the official website
disappeared, the library's future appears to be uncertain).
- Support for user accounts. Passwords are stored using PBKDF2.
sqlite database, but not too much of an effort to add other types of
storage backends. sqlite is using the great header only library
sqlite_modern_cpp
sqlite database, but not too much of an effort to add other types of
storage backends. sqlite is using the great header only library
[sqlite_modern_cpp](https://github.com/SqliteModernCpp)
- Relatively fine-grained permission system.
- Categories
- Templates
- FTS search
- Caching
- Blog-like functionality
- RSS/Atom feeds
Security
========
On Linux namespaces are used to restrict the process to only access
files it needs. It doesn't have access to other paths in the system.
In addition, Seccomp is used to restrict the syscalls the qswiki process
can call. As for "web security", all POST requests are centrally
protected against CSRF attacks and all input is escaped against XSS
## Security
[exile.h](https://github.com/quitesimpleorg/exile.h) is used
to restrict access to the files the wiki needs. It doesn't have access to other paths
in the system and the system calls that the qswiki process can make are restricted.
As for "web security", all POST requests are centrally protected against CSRF attacks and all input is escaped against XSS
attacks.
Building
========
## Building
Dependencies:
- cpp-httplib: https://github.com/yhirose/cpp-httplib
- SqliteModernCpp: https://github.com/SqliteModernCpp
- qssb.h: https://gitea.quitesimple.org/crtxcr/qssb.h
- libseccomp: https://github.com/seccomp/libseccomp
- exile.h: https://gitea.quitesimple.org/crtxcr/exile.h
- sqlite3: https://sqlite.org/index.html
The first three are header-only libraries that are included as a git submodule. The others must
be installed, e. g. by using your distributions standard method.

查看文件

@ -21,7 +21,7 @@ Authenticator::Authenticator(UserDao &userDao)
// TODO: make failure counter configurable
bool Authenticator::isBanned(std::string ip)
{
if(utils::hasKey(loginFails, ip))
if(loginFails.contains(ip))
{
LoginFail &fl = loginFails[ip];
std::lock_guard<std::mutex> lock(fl.mutex);
@ -42,11 +42,12 @@ std::vector<char> Authenticator::pbkdf5(std::string password, const std::vector<
unsigned char hash[32];
const EVP_MD *sha256 = EVP_sha256();
const unsigned char *rawsalt = reinterpret_cast<const unsigned char *>(salt.data());
int ret = PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
int ret =
PKCS5_PBKDF2_HMAC(password.c_str(), password.size(), rawsalt, salt.size(), 300000, sha256, sizeof(hash), hash);
if(ret != 1)
{
Logger::error() << "Authenticator: pbkdf5: Failed to create hash";
return { };
return {};
}
std::vector<char> result;

查看文件

@ -3,6 +3,7 @@
#include <variant>
#include "database/userdao.h"
#define AUTH_DEFAULT_SALT_SIZE 32
enum AuthenticationError
{
UserNotFound,

2
cache/fscache.cpp vendored
查看文件

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

1
cache/mapcache.cpp vendored 普通文件
查看文件

@ -0,0 +1 @@
#include "mapcache.h"

87
cache/mapcache.h vendored 普通文件
查看文件

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

30
cache/nocache.h vendored 普通文件
查看文件

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

278
cli.cpp 普通文件
查看文件

@ -0,0 +1,278 @@
#include <map>
#include <functional>
#include <iomanip>
#include "cli.h"
#include "utils.h"
#include "random.h"
#include "authenticator.h"
#include "config.h"
#include "logger.h"
#include "version.h"
CLIHandler::CLIHandler(Config &config, Database &db)
{
this->db = &db;
this->conf = &config;
}
std::pair<bool, std::string> CLIHandler::user_add(const std::vector<std::string> &args)
{
std::string username = args.at(0);
std::string password = args.at(1);
auto userDao = db->createUserDao();
Permissions perms = this->conf->handlersConfig.anon_permissions;
int p = perms.getPermissions();
p |= PERM_CAN_CREATE | PERM_CAN_SEARCH | PERM_CAN_EDIT;
Permissions newPermissions = Permissions{p};
Random r;
User user;
user.enabled = true;
user.login = username;
user.salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
user.permissions = newPermissions;
Authenticator auth{*userDao};
std::vector<char> hashResult = auth.hash(password, user.salt);
if(hashResult.empty())
{
return {false, "Error during hashing - Got empty hash"};
}
user.password = hashResult;
try
{
userDao->save(user);
}
catch(std::runtime_error &e)
{
return {false, "Exception: " + std::string(e.what())};
}
return {true, ""};
}
std::pair<bool, std::string> CLIHandler::user_change_pw([[maybe_unused]] const std::vector<std::string> &args)
{
std::string username = args.at(0);
std::string password = args.at(1);
auto userDao = db->createUserDao();
auto user = userDao->find(username);
if(user)
{
Random r;
Authenticator auth{*userDao};
user->salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
user->password = auth.hash(password, user->salt);
if(user->password.empty())
{
return {false, "Error during hashing - Got empty hash"};
}
userDao->save(*user);
}
return {false, "User not found"};
}
std::pair<bool, std::string> CLIHandler::user_rename([[maybe_unused]] const std::vector<std::string> &args)
{
return {true, ""};
}
std::pair<bool, std::string> CLIHandler::user_set_perms([[maybe_unused]] const std::vector<std::string> &args)
{
auto userDao = this->db->createUserDao();
std::string username = args.at(0);
std::string permission_string = args.at(1);
Permissions perms{permission_string};
auto user = userDao->find(username);
if(user)
{
user->permissions = perms;
userDao->save(*user);
user_show({username});
return {true, ""};
}
return {false, "User not found"};
}
std::pair<bool, std::string> CLIHandler::user_list([[maybe_unused]] const std::vector<std::string> &args)
{
auto userDao = this->db->createUserDao();
QueryOption o;
auto result = userDao->list(o);
std::stringstream stream;
for(User &u : result)
{
stream << u.login << "\t" << std::string(u.enabled ? "enabled" : "disabled") << "\t" << u.permissions.toString()
<< std::endl;
}
return {true, stream.str()};
}
std::pair<bool, std::string> CLIHandler::user_show(const std::vector<std::string> &args)
{
std::string username = args.at(0);
auto userDao = this->db->createUserDao();
auto user = userDao->find(username);
std::stringstream stream;
if(user)
{
stream << "Username: " << user->login << std::endl;
stream << "Enabled: " << std::string(user->enabled ? "yes" : "no") << std::endl;
stream << "Permissions (general): " << user->permissions.toString() << std::endl;
return {true, stream.str()};
}
return {false, "User not found"};
}
std::pair<bool, std::string> CLIHandler::page_list([[maybe_unused]] const std::vector<std::string> &args)
{
auto pageDao = this->db->createPageDao();
QueryOption o;
auto result = pageDao->getPageList(o);
std::stringstream stream;
for(Page &page : result)
{
stream << page.name << " " << page.pageid << " " << std::string(page.listed ? "listed" : "unlisted") << std::endl;
}
return {true, stream.str()};
}
std::pair<bool, std::string> CLIHandler::pageperms_set_permissions(const std::vector<std::string> &args)
{
std::string page = args.at(0);
std::string username = args.at(1);
std::string perms = args.at(2);
auto permissionsDao = this->db->createPermissionsDao();
permissionsDao->save(page, username, Permissions{perms});
return {true, ""};
}
std::pair<bool, std::string> CLIHandler::attach([[maybe_unused]] const std::vector<std::string> &args)
{
/* TODO: consider authentication */
pid_t pid = getpid();
return {true, "Hi, I am pid: " + std::to_string(pid)};
}
std::pair<bool, std::string> CLIHandler::cli_help(const std::vector<std::string> &args)
{
std::string command;
if(args.size() > 0)
command = args[0];
std::stringstream stream;
for(struct cmd &cmd : cmds)
{
if(command != "" && cmd.name != command)
{
continue;
}
stream << cmd.name << " - " << cmd.helptext << std::endl;
for(struct cmd &subCmd : cmd.subCommands)
{
stream << "\t" << subCmd.name << " " << subCmd.helptext << std::endl;
}
stream << std::endl;
}
return {true, stream.str()};
}
std::pair<bool, std::string> CLIHandler::processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd,
const std::vector<std::string> &args)
{
auto c = std::find_if(commands.begin(), commands.end(),
[&cmd](const struct CLIHandler::cmd &a) { return a.name == cmd; });
if(c == commands.end())
{
std::cout << "No such command: " << cmd << std::endl;
return cli_help({});
}
if(!c->subCommands.empty() && args.size() >= c->required_args)
{
std::string newcmd = args[0];
std::vector<std::string> newargs = args;
newargs.erase(newargs.begin());
return processCommand(c->subCommands, newcmd, newargs);
}
if(args.size() < c->required_args)
{
return {false, "not enough parameters passed"};
}
try
{
return c->func(this, args);
}
catch(std::runtime_error &e)
{
return {false, "Exception: " + std::string(e.what())};
}
return {false, ""};
}
std::pair<bool, std::string> CLIHandler::processCommand(std::string cmd, const std::vector<std::string> &args)
{
return processCommand(this->cmds, cmd, args);
}
std::pair<std::string, std::vector<std::string>> CLIHandler::splitCommand(std::string input)
{
input = utils::trim(input);
std::vector<std::string> splitted = utils::split(input, "\\s+");
if(splitted.empty())
{
return {" ", splitted};
}
std::string cmd = splitted[0];
splitted.erase(splitted.begin());
return {cmd, splitted};
}
std::pair<bool, std::string> CLIHandler::version([[maybe_unused]] const std::vector<std::string> &args)
{
return {true, get_version_string()};
}
std::pair<bool, std::string> CLIHandler::category_list([[maybe_unused]] const std::vector<std::string> &args)
{
auto categoryDao = this->db->createCategoryDao();
auto categories = categoryDao->fetchList(QueryOption{});
std::stringstream stream;
for(std::string &cat : categories)
{
stream << cat << std::endl;
}
return {true, stream.str()};
}
std::pair<bool, std::string> CLIHandler::category_delete(const std::vector<std::string> &args)
{
auto categoryDao = this->db->createCategoryDao();
categoryDao->deleteCategory(args.at(0));
return {true, ""};
}
std::pair<bool, std::string> CLIHandler::category_show(const std::vector<std::string> &args)
{
auto categoryDao = this->db->createCategoryDao();
auto members = categoryDao->fetchMembers(args.at(0), QueryOption{});
std::stringstream stream;
for(Page &member : members)
{
stream << member.name << std::endl;
}
return {true, stream.str()};
}

94
cli.h 普通文件
查看文件

@ -0,0 +1,94 @@
#ifndef CLI_H
#define CLI_H
#include <iostream>
#include <string>
#include <vector>
#include "database/database.h"
#include "config.h"
class CLIHandler
{
struct cmd
{
std::string name;
std::string helptext;
unsigned int required_args;
std::vector<cmd> subCommands;
std::function<std::pair<bool, std::string>(CLIHandler *, const std::vector<std::string> &)> func;
};
private:
Database *db;
Config *conf;
protected:
std::pair<bool, std::string> attach([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> cli_help([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_add([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_change_pw([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_rename([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_set_perms([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_list([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> user_show([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> page_list([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> pageperms_set_permissions([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> version([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> category_list([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> category_delete([[maybe_unused]] const std::vector<std::string> &args);
std::pair<bool, std::string> category_show([[maybe_unused]] const std::vector<std::string> &args);
std::vector<struct cmd> cmds{
{{"user",
"user operations on the database",
1,
{{{"add", "[user] [password] - creates a user", 2, {}, &CLIHandler::user_add},
{"changepw", "[user] [password] - changes the password of user", 2, {}, &CLIHandler::user_change_pw},
{"rename", "[user] [new name] - renames a user", 2, {}, &CLIHandler::user_rename},
{"setperms", "[user] [perms] - sets the permissions of the user", 2, {}, &CLIHandler::user_set_perms},
{"list", "- lists users", 0, {}, &CLIHandler::user_list},
{"show", "[user] - show detailed information about user", 1, {}, &CLIHandler::user_show}}},
&CLIHandler::cli_help},
{"page",
"operation on pages",
1,
{{{"list", "- lists existing pages", 0, {}, &CLIHandler::page_list}}},
&CLIHandler::cli_help},
{"category",
"operation on categories",
1,
{{{"list", "- lists existing categories", 0, {}, &CLIHandler::category_list},
{"delete", " - deletes a category", 1, {}, &CLIHandler::category_delete},
{"show", " - shows pages of a category", 1, {}, &CLIHandler::category_show}}},
&CLIHandler::cli_help},
{"pageperms",
"set permissions on pages",
1,
{{{"set",
"- [page] [username] [permissions] set permisisons on page",
3,
{},
&CLIHandler::pageperms_set_permissions}}},
&CLIHandler::cli_help},
{"exit",
"exit cli",
0,
{},
[](CLIHandler *, [[maybe_unused]] const std::vector<std::string> &args) -> std::pair<bool, std::string>
{
exit(EXIT_SUCCESS);
return {true, ""};
}},
{"help", "print this help", 0, {}, &CLIHandler::cli_help},
{"attach", "attach to running instance", 0, {}, &CLIHandler::attach},
{"version", "print verison info", 0, {}, &CLIHandler::version}}};
std::pair<bool, std::string> processCommand(const std::vector<CLIHandler::cmd> &commands, std::string cmd,
const std::vector<std::string> &args);
public:
CLIHandler(Config &config, Database &d);
std::pair<bool, std::string> processCommand(std::string cmd, const std::vector<std::string> &args);
static std::pair<std::string, std::vector<std::string>> splitCommand(std::string input);
};
#endif // CLI_H

147
cliconsole.cpp 普通文件
查看文件

@ -0,0 +1,147 @@
#include "cliconsole.h"
CLIConsole::CLIConsole(CLIHandler &cliHandler, std::string socketPath)
{
this->handler = &cliHandler;
this->socketPath = socketPath;
}
std::pair<bool, std::string> CLIConsole::send(std::string input)
{
ssize_t ret =
sendto(this->sock, input.c_str(), input.size(), 0, (const sockaddr *)&this->server, sizeof(this->server));
if((size_t)ret != input.size())
{
return {false, "sendto failed: " + std::to_string(ret) + " " + std::string(strerror(errno))};
}
char buffer[1024] = {0};
ret = recvfrom(this->sock, buffer, sizeof(buffer) - 1, 0, NULL, NULL);
if(ret == -1)
{
return {false, "recvfrom failed: " + std::string(strerror(errno))};
}
bool success = false;
std::string_view view = buffer;
if(view[0] == '1')
{
success = true;
}
view.remove_prefix(1);
std::string msg = std::string{view};
return {success, msg};
}
void CLIConsole::attach()
{
if(attached)
{
std::cout << "Already attached" << std::endl;
return;
}
if(socketPath.size() > sizeof(this->server.sun_path) - 1)
{
std::cout << "Socket path too long" << std::endl;
return;
}
memset(&this->server, 0, sizeof(this->server));
this->server.sun_family = AF_UNIX;
memcpy(&this->server.sun_path, socketPath.c_str(), socketPath.size());
this->server.sun_path[socketPath.size()] = 0;
int s = socket(AF_UNIX, SOCK_DGRAM, 0);
if(s == -1)
{
std::cout << "Failed to create socket" << strerror(errno) << std::endl;
return;
}
this->sock = s;
struct sockaddr_un client;
client.sun_family = AF_UNIX;
client.sun_path[0] = '\0';
int ret = bind(this->sock, (struct sockaddr *)&client, sizeof(client));
if(ret != 0)
{
std::cout << "bind() failed: " << strerror(errno) << std::endl;
return;
}
auto result = this->send("attach");
if(result.first)
{
std::cout << "Attached successfully: " << result.second << std::endl;
this->attached = true;
}
else
{
std::cout << "Attached unsuccessfully: " << result.second << std::endl;
}
}
void CLIConsole::startInteractive()
{
std::cout << "qswiki CLI" << std::endl;
std::cout << "not attached - use 'attach' to connect to running instance" << std::endl;
while(true)
{
std::string input;
std::cout << "> ";
std::getline(std::cin, input);
if(std::cin.bad() || std::cin.eof())
{
std::cout << "Exiting" << std::endl;
return;
}
if(input.empty())
{
continue;
}
auto pair = CLIHandler::splitCommand(input);
if(pair.first == "exit")
{
if(attached)
{
std::cout << "You are attached. Quit attached instance too (y) or only this one(n)" << std::endl;
char response;
std::cin >> response;
if(response == 'y')
{
this->send("exit");
}
}
std::cout << "Exiting CLI" << std::endl;
exit(EXIT_SUCCESS);
}
if(pair.first == "attach")
{
attach();
continue;
}
std::pair<bool, std::string> result;
if(!attached)
{
result = handler->processCommand(pair.first, pair.second);
}
else
{
result = this->send(input);
}
if(!result.second.empty())
{
std::cout << result.second << std::endl;
}
if(!result.first)
{
std::cout << "Command failed" << std::endl;
}
}
std::cout << "\n";
}

27
cliconsole.h 普通文件
查看文件

@ -0,0 +1,27 @@
#ifndef CLICONSOLE_H
#define CLICONSOLE_H
#include <iostream>
#include <string>
#include <vector>
#include <sys/socket.h>
#include <sys/un.h>
#include "cli.h"
class CLIConsole
{
private:
struct sockaddr_un server;
int sock;
CLIHandler *handler;
std::string socketPath;
bool attached = false;
std::pair<bool, std::string> send(std::string input);
void attach();
public:
CLIConsole(CLIHandler &cliHandler, std::string socketPath);
void startInteractive();
};
#endif // CLICONSOLE_H

77
cliserver.cpp 普通文件
查看文件

@ -0,0 +1,77 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include "cliserver.h"
#include "logger.h"
CLIServer::CLIServer(CLIHandler &handler)
{
this->handler = &handler;
}
bool CLIServer::detachServer(std::string socketpath)
{
struct sockaddr_un name;
const int max_socket_length = sizeof(name.sun_path) - 1;
if(socketpath.size() > max_socket_length)
{
perror("socket path too long");
return false;
}
int s = socket(AF_UNIX, SOCK_DGRAM, 0);
if(s == -1)
{
perror("socket");
return false;
}
memset(&name, 0, sizeof(name));
name.sun_family = AF_UNIX;
memcpy(&name.sun_path, socketpath.c_str(), socketpath.size());
unlink(socketpath.c_str());
int ret = bind(s, (const struct sockaddr *)&name, sizeof(name));
if(ret == -1)
{
perror("bind");
exit(EXIT_FAILURE);
}
auto worker = [this, s]
{
while(true)
{
char buffer[1024] = {0};
struct sockaddr_un peer;
socklen_t peerlen = sizeof(peer);
int ret = recvfrom(s, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &peerlen);
if(ret == -1)
{
Logger::error() << "Error during recvfrom in CLI server: " << strerror(errno);
return false;
}
std::string input{buffer};
auto pair = CLIHandler::splitCommand(input);
auto result = handler->processCommand(pair.first, pair.second);
char resultCode = '0';
if(result.first)
{
resultCode = '1';
}
std::string resultString;
resultString += resultCode;
resultString += result.second;
ret = sendto(s, resultString.c_str(), resultString.size(), 0, (struct sockaddr *)&peer, peerlen);
if(ret == -1)
{
Logger::error() << "Error during sendto in CLI server: " << strerror(errno);
}
}
};
std::thread t1{worker};
t1.detach();
return true;
}

16
cliserver.h 普通文件
查看文件

@ -0,0 +1,16 @@
#ifndef CLISERVER_H
#define CLISERVER_H
#include <thread>
#include "cli.h"
class CLIServer
{
private:
CLIHandler *handler = nullptr;
public:
CLIServer(CLIHandler &handler);
bool detachServer(std::string socketpath);
};
#endif // CLISERVER_H

查看文件

@ -24,6 +24,7 @@ SOFTWARE.
#include "config.h"
#include "permissions.h"
#include "varreplacer.h"
std::string Config::required(const std::string &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->urls.linkallcats = required("linkallcats");
this->urls.linkallpages = required("linkallpages");
this->urls.linkallpagesrendertype = required ("linkallpagesrendertype");
this->urls.linkcategory = required("linkcategory");
this->urls.linkcategoryrendertype = required("linkcategoryrendertype");
this->urls.linkdelete = required("linkdelete");
this->urls.linkedit = required("linkedit");
this->urls.linkhistory = required("linkhistory");
this->urls.linkindex = required("linkindex");
this->urls.linklogout = required("linklogout");
this->urls.linkpage = required("linkpage");
this->urls.linkpagebytitle = required("linkpagebytitle");
this->urls.linkrecent = required("linkrecent");
this->urls.linkrevision = required("linkrevision");
this->urls.linksettings = required("linksettings");
@ -96,6 +100,8 @@ Config::Config(const std::map<std::string, std::string> &map)
this->urls.deletionurl = required("deletionurl");
this->urls.adminregisterurl = required("adminregisterurl");
this->urls.usersettingsurl = required("usersettingsurl");
this->urls.rooturl = required("rooturl");
this->urls.atomurl = required("atomurl");
this->connectionstring = required("connectionstring");
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->max_payload_length = optional("max_payload_length", 10 * 1024 * 1024);
this->max_payload_length = optional("max_payload_length", 60 * 1024 * 1024);
ConfigVariableResolver resolver{this->configmap};
this->configVarResolver = resolver;

查看文件

@ -23,9 +23,11 @@ struct ConfigUrls
std::string linkindex;
std::string linkrecent;
std::string linkallpages;
std::string linkallpagesrendertype;
std::string linkallcats;
std::string linkshere;
std::string linkpage;
std::string linkpagebytitle;
std::string linkrevision;
std::string linkhistory;
std::string linkedit;
@ -33,6 +35,7 @@ struct ConfigUrls
std::string linkdelete;
std::string linklogout;
std::string linkcategory;
std::string linkcategoryrendertype;
std::string loginurl;
std::string linkrecentsort;
std::string actionurl;
@ -41,6 +44,8 @@ struct ConfigUrls
std::string linkhistorysort;
std::string adminregisterurl;
std::string usersettingsurl;
std::string rooturl;
std::string atomurl;
};
class ConfigVariableResolver

查看文件

@ -5,7 +5,7 @@
#include <optional>
#include "queryoption.h"
#include "../category.h"
#include "../page.h"
class CategoryDao
{
public:
@ -14,7 +14,8 @@ class CategoryDao
virtual std::vector<std::string> fetchList(QueryOption o) = 0;
virtual std::optional<Category> find(std::string name) = 0;
virtual void deleteCategory(std::string name) = 0;
virtual std::vector<std::string> fetchMembers(std::string name, QueryOption o) = 0;
virtual std::vector<Page> fetchMembers(std::string name, QueryOption o) = 0;
virtual ~CategoryDao() = default;
};
#endif // CATEGORYDAO_H

查看文件

@ -42,6 +42,7 @@ std::optional<Category> CategoryDaoSqlite::find(std::string name)
{
throwFrom(e);
}
return {};
}
void CategoryDaoSqlite::save(const Category &c)
@ -64,13 +65,14 @@ void CategoryDaoSqlite::deleteCategory(std::string name)
try
{
*db << "BEGIN";
*db << "DELETE FROM categorymember WHERE catid = (SELECT id FROM category WHERE name = ?)" << name;
*db << "DELETE FROM category WHERE name = ?" << name;
*db << "BEGIN;";
*db << "DELETE FROM categorymember WHERE category = (SELECT id FROM category WHERE name = ?);" << name;
*db << "DELETE FROM category WHERE name = ?;" << name;
*db << "COMMIT;";
}
catch(sqlite::sqlite_exception &e)
{
*db << "ROLLBACK";
throwFrom(e);
}
}
@ -93,21 +95,36 @@ std::vector<std::string> CategoryDaoSqlite::fetchList(QueryOption o)
}
return result;
}
std::vector<std::string> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o)
std::vector<Page> CategoryDaoSqlite::fetchMembers(std::string name, QueryOption o)
{
std::vector<std::string> result;
std::vector<Page> result;
SqliteQueryOption queryOption{o};
std::string queryoptions =
queryOption.setOrderByColumn("name").setVisibleColumnName("page.visible").setPrependWhere(false).build();
queryOption.setOrderByColumn("name").setListedColumnName("page.listed").setPrependWhere(false).build();
try
{
auto query = *db << "SELECT page.name AS name FROM categorymember INNER JOIN page ON page.id = "
"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " +
queryoptions
<< name;
query >> [&](std::string p) { result.push_back(p); };
auto query =
*db
<< "SELECT page.id, page.name AS name, page.title, page.lastrevision, page.listed, page.feedlisted FROM "
"categorymember INNER JOIN page ON page.id = "
"categorymember.page WHERE category = (SELECT id FROM category WHERE name = ? ) AND " +
queryoptions
<< name;
query >> [&](unsigned int id, std::string name, std::string title, unsigned int lastrevision, bool listed,
bool feedlisted)
{
Page p;
p.name = name;
p.pageid = id;
p.title = title;
p.current_revision = lastrevision;
p.listed = listed;
p.feedlisted = feedlisted;
result.push_back(p);
};
}
catch(const sqlite::exceptions::no_rows &e)
{

查看文件

@ -3,12 +3,13 @@
#include "categorydao.h"
#include "sqlitedao.h"
#include "../page.h"
class CategoryDaoSqlite : public CategoryDao, protected SqliteDao
{
public:
CategoryDaoSqlite();
std::vector<std::string> fetchList(QueryOption o) override;
std::vector<std::string> fetchMembers(std::string name, QueryOption o) override;
std::vector<Page> fetchMembers(std::string name, QueryOption o) override;
void save(const Category &c) override;
void deleteCategory(std::string name) override;
std::optional<Category> find(std::string name) override;

查看文件

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

查看文件

@ -13,8 +13,9 @@ class PageDao
virtual bool exists(std::string page) const = 0;
virtual bool exists(unsigned int id) const = 0;
virtual std::optional<Page> find(std::string name) = 0;
virtual std::optional<Page> findByTitle(std::string title) = 0;
virtual std::optional<Page> find(unsigned int id) = 0;
virtual std::vector<std::string> getPageList(QueryOption option) = 0;
virtual std::vector<Page> getPageList(QueryOption option) = 0;
virtual std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) = 0;
virtual void deletePage(std::string page) = 0;
virtual void save(const Page &page) = 0;
@ -22,6 +23,8 @@ class PageDao
virtual void setCategories(std::string pagename, const std::vector<std::string> &catnames) = 0;
virtual std::vector<SearchResult> search(std::string query, QueryOption option) = 0;
virtual std::vector<std::string> getChildren(std::string pagename) = 0;
virtual ~PageDao()
{
}

查看文件

@ -52,15 +52,43 @@ 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, listed, feedlisted, (SELECT name FROM page WHERE id = parent) "
"FROM page WHERE title = ?";
ps << title >> std::tie(result.pageid, result.name, result.title, result.current_revision, result.listed,
result.feedlisted, result.parentpage);
}
catch(const sqlite::errors::no_rows &e)
{
return {};
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return result;
}
std::optional<Page> PageDaoSqlite::find(unsigned int id)
{
Page result;
result.pageid = id;
try
{
auto ps = *db << "SELECT name, lastrevision, visible FROM page WHERE id = ?";
auto ps =
*db
<< "SELECT name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE id = parent) FROM "
"page WHERE id = ?";
ps << id >> std::tie(result.name, result.current_revision, result.listed);
ps << id >> std::tie(result.name, result.title, result.current_revision, result.listed, result.feedlisted,
result.parentpage);
}
catch(const sqlite::errors::no_rows &e)
{
@ -89,6 +117,7 @@ void PageDaoSqlite::deletePage(std::string page)
}
catch(sqlite::sqlite_exception &e)
{
*db << "ROLLBACK";
throwFrom(e);
}
}
@ -97,30 +126,44 @@ void PageDaoSqlite::save(const Page &page)
{
try
{
*db << "INSERT OR REPLACE INTO page (id, name, lastrevision, visible) VALUES((SELECT id FROM page WHERE name = "
"? OR id = ?), ?, ?, ?)"
<< page.name << page.pageid << page.name << page.current_revision << page.listed;
*db << "INSERT OR REPLACE INTO page (id, name, title, lastrevision, listed, feedlisted, parent) VALUES((SELECT "
"id FROM page WHERE name = ? OR id = ?), ?, ?, ?, ?, ?, (SELECT id FROM page WHERE name = ?))"
<< page.name << page.pageid << page.name << page.title << page.current_revision << page.listed
<< page.feedlisted << page.parentpage;
}
catch(sqlite::sqlite_exception &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
{
std::string queryOption = SqliteQueryOption(option)
.setOrderByColumn("lower(name)")
.setVisibleColumnName("visible")
.setListedColumnName("listed")
.setPrependWhere(true)
.build();
std::string query = "SELECT name FROM page " + queryOption;
*db << query >> [&](std::string name) { result.push_back(name); };
std::string query = "SELECT id, name, title, lastrevision, listed, feedlisted, (SELECT name FROM page WHERE "
"id = parent) FROM page " +
queryOption;
*db << query >> [&](unsigned int pageid, std::string name, std::string title, unsigned int current_revision,
bool listed, bool feedlisted, std::string parent)
{
Page tmp;
tmp.pageid = pageid;
tmp.name = name;
tmp.title = title;
tmp.current_revision = current_revision;
tmp.listed = listed;
tmp.feedlisted = feedlisted;
tmp.parentpage = parent;
result.push_back(tmp);
};
}
catch(const sqlite::errors::no_rows &e)
{
@ -183,7 +226,8 @@ std::vector<SearchResult> PageDaoSqlite::search(std::string name, QueryOption op
auto query =
*db << "SELECT page.name FROM search INNER JOIN page ON search.page = page.id WHERE search MATCH ? "
<< ftsEscape(name);
query >> [&](std::string pagename) {
query >> [&](std::string pagename)
{
SearchResult sresult;
sresult.pagename = pagename;
sresult.query = name;
@ -230,3 +274,11 @@ int PageDaoSqlite::fetchPageId(std::string pagename)
auto binder = *db << "SELECT id FROM page WHERE name = ?" << pagename;
return execInt(binder);
}
std::vector<std::string> PageDaoSqlite::getChildren(std::string pagename)
{
std::vector<std::string> result;
auto query = *db << "SELECT name FROM page WHERE parent = (SELECT id FROM page WHERE name = ?)" << pagename;
query >> [&](std::string page) { result.push_back(page); };
return result;
}

查看文件

@ -20,13 +20,16 @@ class PageDaoSqlite : public PageDao, protected SqliteDao
bool exists(std::string name) const override;
void save(const Page &page) override;
std::optional<Page> find(std::string name) override;
std::optional<Page> findByTitle(std::string title) override;
std::optional<Page> find(unsigned int id) override;
std::vector<std::string> getPageList(QueryOption option) override;
std::vector<Page> getPageList(QueryOption option) override;
std::vector<std::string> fetchCategories(std::string pagename, QueryOption option) override;
using SqliteDao::SqliteDao;
int fetchPageId(std::string pagename);
std::vector<SearchResult> search(std::string query, QueryOption option) override;
void setCategories(std::string pagename, const std::vector<std::string> &catnames) override;
std::vector<std::string> getChildren(std::string pagename) override;
};
#endif // PAGEDAOSQLITE_H

查看文件

@ -1,5 +1,6 @@
#ifndef PERMISSIONSDAO_H
#define PERMISSIONSDAO_H
#include <optional>
#include "../permissions.h"
#include "../user.h"
class PermissionsDao
@ -7,6 +8,10 @@ class PermissionsDao
public:
PermissionsDao();
virtual std::optional<Permissions> find(std::string pagename, std::string username) = 0;
virtual void save(std::string pagename, std::string username, Permissions perms) = 0;
virtual void clearForPage(std::string pagename) = 0;
virtual ~PermissionsDao() = default;
};
#endif // PERMISSIONSDAO_H

查看文件

@ -41,3 +41,34 @@ std::optional<Permissions> PermissionsDaoSqlite::find(std::string pagename, std:
return Permissions{permissions};
}
void PermissionsDaoSqlite::save(std::string pagename, std::string username, Permissions perms)
{
try
{
auto query =
*db
<< "INSERT OR REPLACE INTO permissions (id, permissions, userid, page) VALUES((SELECT id FROM permissions "
"WHERE page = (SELECT id FROM page WHERE name = ?) AND userid = (SELECT id FROM user WHERE username = "
"?)), ?, (SELECT id FROM user WHERE username = ?), (SELECT id FROM page WHERE name = ?))";
query << pagename << username << perms.getPermissions() << username << pagename;
query.execute();
}
catch(const sqlite::errors::no_rows &e)
{
throwFrom(e);
}
}
void PermissionsDaoSqlite::clearForPage(std::string pagename)
{
try
{
auto stmt = *db << "DELETE FROM permissions WHERE page = (SELECT id FROM page WHERE name = ?)" << pagename;
stmt.execute();
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
}

查看文件

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

查看文件

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

查看文件

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

查看文件

@ -10,6 +10,7 @@ class SessionDao
virtual void save(const Session &session) = 0;
virtual std::optional<Session> find(std::string token) = 0;
virtual void deleteSession(std::string token) = 0;
virtual std::vector<Session> fetch() = 0;
virtual ~SessionDao()
{
}

63
database/sessiondaosqlite.cpp 普通文件 -> 可执行文件
查看文件

@ -50,6 +50,29 @@ void SessionDaoSqlite::deleteSession(std::string token)
}
}
void SessionDaoSqlite::fillSession(int userid, Session &sess)
{
if(userid > -1)
{
UserDaoSqlite userDao{*this->db};
auto u = userDao.find(userid);
if(u)
{
sess.user = *u;
}
else
{
Logger::error() << "Session for non existent user";
throw DatabaseQueryException("Session for non existent user");
}
}
else
{
sess.user = User::Anonymous();
}
sess.loggedIn = userid != -1;
}
std::optional<Session> SessionDaoSqlite::find(std::string token)
{
Session result;
@ -62,25 +85,7 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
int userid;
q >> std::tie(userid, result.token, result.csrf_token, result.creation_time);
if(userid > -1)
{
UserDaoSqlite userDao{this->db};
auto u = userDao.find(userid);
if(u)
{
result.user = *u;
}
else
{
Logger::error() << "Session for non existent user";
throw DatabaseQueryException("Session for non existent user");
}
}
else
{
result.user = User::Anonymous();
}
result.loggedIn = userid != -1;
fillSession(userid, result);
}
catch(const sqlite::exceptions::no_rows &e)
{
@ -92,3 +97,23 @@ std::optional<Session> SessionDaoSqlite::find(std::string token)
}
return result;
}
std::vector<Session> SessionDaoSqlite::fetch()
{
std::vector<Session> result;
*db << "SELECT userid, token, csrf_token, strftime('%s', creationtime) FROM session" >>
[this, &result](int userid, std::string token, std::string csrf_token, time_t creationtime)
{
Session tmp;
tmp.csrf_token = csrf_token;
tmp.token = token;
tmp.creation_time = creationtime;
fillSession(userid, tmp);
result.push_back(tmp);
};
return result;
}

查看文件

@ -6,11 +6,15 @@
class SessionDaoSqlite : public SessionDao, protected SqliteDao
{
private:
void fillSession(int userid, Session &sess);
public:
SessionDaoSqlite();
void save(const Session &session) override;
std::optional<Session> find(std::string token) override;
void deleteSession(std::string token) override;
std::vector<Session> fetch() override;
using SqliteDao::SqliteDao;
};

查看文件

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

查看文件

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

查看文件

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

查看文件

@ -12,20 +12,20 @@
class SqliteDao
{
protected:
std::shared_ptr<sqlite::database> db;
sqlite::database *db = nullptr;
public:
SqliteDao()
{
}
SqliteDao(std::shared_ptr<sqlite::database> db)
SqliteDao(sqlite::database &db)
{
this->db = db;
this->db = &db;
}
void setDb(std::shared_ptr<sqlite::database> db)
void setDb(sqlite::database &db)
{
this->db = db;
this->db = &db;
}
inline void throwFrom(const sqlite::sqlite_exception &e) const
@ -37,6 +37,8 @@ class SqliteDao
bool execBool(sqlite::database_binder &binder) const;
int execInt(sqlite::database_binder &binder) const;
virtual ~SqliteDao() = default;
};
#endif // SQLITEDAO_H

查看文件

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

查看文件

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

查看文件

@ -3,6 +3,7 @@
#include <string>
#include <optional>
#include "../user.h"
#include "queryoption.h"
class UserDao
{
public:
@ -10,6 +11,7 @@ class UserDao
virtual bool exists(std::string username) = 0;
virtual std::optional<User> find(std::string username) = 0;
virtual std::optional<User> find(int id) = 0;
virtual std::vector<User> list(QueryOption o) = 0;
virtual void deleteUser(std::string username) = 0;
virtual void save(const User &u) = 0;
virtual ~UserDao(){};

查看文件

@ -23,6 +23,7 @@ SOFTWARE.
#include <memory>
#include <cstring>
#include "userdaosqlite.h"
#include "sqlitequeryoption.h"
UserDaoSqlite::UserDaoSqlite()
{
@ -36,7 +37,6 @@ bool UserDaoSqlite::exists(std::string username)
std::optional<User> UserDaoSqlite::find(std::string username)
{
try
{
User user;
@ -47,7 +47,7 @@ std::optional<User> UserDaoSqlite::find(std::string username)
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms};
return std::move(user);
return user;
}
catch(const sqlite::errors::no_rows &e)
{
@ -57,6 +57,7 @@ std::optional<User> UserDaoSqlite::find(std::string username)
{
throwFrom(e);
}
return {};
}
std::optional<User> UserDaoSqlite::find(int id)
@ -70,7 +71,7 @@ std::optional<User> UserDaoSqlite::find(int id)
stmt >> std::tie(user.login, user.password, user.salt, perms, user.enabled);
user.permissions = Permissions{perms};
return std::move(user);
return user;
}
catch(const sqlite::errors::no_rows &e)
{
@ -80,9 +81,43 @@ std::optional<User> UserDaoSqlite::find(int id)
{
throwFrom(e);
}
return {};
}
void UserDaoSqlite::deleteUser(std::string username)
std::vector<User> UserDaoSqlite::list(QueryOption o)
{
std::vector<User> result;
try
{
std::string queryOption = SqliteQueryOption(o).setOrderByColumn("username").setPrependWhere(true).build();
std::string query = "SELECT username, password, salt, permissions, enabled FROM user " + queryOption;
*db << query >>
[&](std::string username, std::vector<char> pw, std::vector<char> salt, int permisisons, bool enabled)
{
User tmp;
tmp.login = username;
tmp.password = pw;
tmp.salt = salt;
tmp.permissions = Permissions{permisisons};
tmp.enabled = enabled;
result.push_back(tmp);
};
}
catch(const sqlite::errors::no_rows &e)
{
return result;
}
catch(sqlite::sqlite_exception &e)
{
throwFrom(e);
}
return result;
}
void UserDaoSqlite::deleteUser([[maybe_unused]] std::string username)
{
// What to do with the contributions of the user?
}

查看文件

@ -13,6 +13,7 @@ class UserDaoSqlite : public UserDao, protected SqliteDao
std::optional<User> find(std::string username);
std::optional<User> find(int id);
std::vector<User> list(QueryOption o);
void deleteUser(std::string username);
void save(const User &u);
using SqliteDao::SqliteDao;

查看文件

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

29
dynamic/dynamiccontent.h 普通文件
查看文件

@ -0,0 +1,29 @@
#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;
Session *userSession;
std::string argument;
public:
DynamicContent(Template &templ, Database &database, UrlProvider &urlProvider, Session &session);
virtual std::string render() = 0;
virtual void setArgument(std::string argument)
{
this->argument = argument;
}
virtual ~DynamicContent()
{
}
};
#endif // DYNAMICCONTENT_H

查看文件

@ -0,0 +1,30 @@
#ifndef DYNAMICCONTENTFACTORY_H
#define DYNAMICCONTENTFACTORY_H
#include "dynamiccontent.h"
class DynamicContentFactory
{
private:
Template *templ;
Database *db;
UrlProvider *urlProvider;
Session *session;
public:
DynamicContentFactory(Template &templ, Database &db, UrlProvider &urlProvider, Session &session)
{
this->templ = &templ;
this->db = &db;
this->urlProvider = &urlProvider;
this->session = &session;
}
template <class T> inline std::shared_ptr<T> createDynamicContent()
{
return std::make_shared<T>(*this->templ, *this->db, *this->urlProvider, *this->session);
}
};
#endif // DYNAMICCONTENTFACTORY_H_INCLUDED

查看文件

@ -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 = &map;
}

查看文件

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

查看文件

@ -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

查看文件

@ -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 = &map;
}

查看文件

@ -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

查看文件

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

查看文件

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

查看文件

@ -41,7 +41,7 @@ Request HttpGateway::convertRequest(httplib::Request request)
// TODO: this eats resources, where perhaps it does not need to. move it to request?
for(auto &it : request.params)
{
it.second = utils::html_xss(std::string{it.second});
it.second = utils::html_xss(it.second);
}
if(request.method == "GET")
{
@ -83,7 +83,8 @@ void HttpGateway::work(RequestWorker &worker)
{
httplib::Server server;
server.set_payload_max_length(this->maxPayloadLength);
auto handler = [&](const httplib::Request &req, httplib::Response &res) {
auto handler = [&](const httplib::Request &req, httplib::Response &res)
{
Request wikiRequest = convertRequest(req);
Logger::debug() << "httpgateway: received request " << wikiRequest;
Response wikiresponse = worker.processRequest(wikiRequest);

查看文件

@ -1,5 +1,8 @@
#ifndef HTTPGATEWAY_H
#define HTTPGATEWAY_H
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 65536
#include <httplib.h>
#include "gatewayinterface.h"
#include "../requestworker.h"

0
grouper.cpp 普通文件
查看文件

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;
}
};

查看文件

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

查看文件

@ -1,5 +1,6 @@
#ifndef HANDLER_H
#define HANDLER_H
#include <memory>
#include "../config.h"
#include "../response.h"
#include "../request.h"
@ -9,6 +10,8 @@
#include "../database/queryoption.h"
#include "../logger.h"
#include "../cache/icache.h"
#include "../dynamic/dynamiccontent.h"
class Handler
{
protected:
@ -36,12 +39,12 @@ class Handler
}
virtual Response handle(const Request &r);
virtual Response handleRequest(const Request &r)
virtual Response handleRequest([[maybe_unused]] const Request &r)
{
return this->errorResponse("Invalid action", "This action is not implemented yet");
}
virtual bool canAccess(const Permissions &perms)
virtual bool canAccess([[maybe_unused]] const Permissions &perms)
{
return false;
}
@ -53,6 +56,12 @@ class 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);
std::string createPageTitle(std::string append);
};

查看文件

@ -32,7 +32,7 @@ Response HandlerAllCategories::handleRequest(const Request &r)
"No categories",
"This wiki does not have any categories defined yet or your query options did not yield any results");
}
TemplatePage &searchPage = this->templ->getPage("allcategories");
TemplatePage searchPage = this->templ->getPage("allcategories");
std::string body =
this->templ->renderSearch(resultList, [&](std::string str) { return this->urlProvider->category(str); });
searchPage.setVar("categorylist", body);

查看文件

@ -19,6 +19,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "handlerallpages.h"
#include "../grouper.h"
#include "../pagelistrenderer.h"
Response HandlerAllPages::handleRequest(const Request &r)
{
@ -27,17 +29,21 @@ Response HandlerAllPages::handleRequest(const Request &r)
Response response;
auto pageDao = this->database->createPageDao();
QueryOption qo = queryOption(r);
auto resultList = pageDao->getPageList(qo);
if(resultList.size() == 0)
std::vector<Page> pageList = pageDao->getPageList(qo);
if(pageList.size() == 0)
{
return errorResponse("No pages", "This wiki does not have any pages yet");
}
TemplatePage &searchPage = this->templ->getPage("allpages");
std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body);
searchPage.setVar("title", createPageTitle("All pages"));
setGeneralVars(searchPage);
response.setBody(searchPage.render());
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database);
TemplatePage allPages = this->templ->getPage("allpages");
allPages.setVar("pagelistcontent", pagelistrender.render(pageList, r.get("rendertype")));
allPages.setVar("pagelistletterlink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_LETTER));
allPages.setVar("pagelistcreationdatelink", this->urlProvider->allPages(PageListRenderer::RENDER_GROUP_BY_CREATIONDATE));
allPages.setVar("title", createPageTitle("All pages"));
setGeneralVars(allPages);
response.setBody(allPages.render());
response.setStatus(200);
return response;
}

查看文件

@ -19,6 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "handlercategory.h"
#include "../pagelistrenderer.h"
Response HandlerCategory::handleRequest(const Request &r)
{
@ -33,9 +34,13 @@ Response HandlerCategory::handleRequest(const Request &r)
}
QueryOption qo = queryOption(r);
auto resultList = categoryDao->fetchMembers(categoryname, qo);
TemplatePage &searchPage = this->templ->getPage("show_category");
std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body);
TemplatePage searchPage = this->templ->getPage("show_category");
PageListRenderer pagelistrender(*this->templ, *this->urlProvider, *this->database);
std::string body = pagelistrender.render(resultList, r.get("rendertype"));
searchPage.setVar("pagelistcontent", body);
searchPage.setVar("pagelistletterlink", this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_LETTER));
searchPage.setVar("pagelistcreationdatelink", this->urlProvider->category(categoryname, PageListRenderer::RENDER_GROUP_BY_CREATIONDATE));
searchPage.setVar("categoryname", categoryname);
setGeneralVars(searchPage);
searchPage.setVar("title", createPageTitle("Category: " + categoryname));

查看文件

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

查看文件

@ -33,7 +33,8 @@ SOFTWARE.
#include "handlerhistory.h"
#include "handlerpagedelete.h"
#include "handlerusersettings.h"
#include "handlerversion.h"
#include "handlerfeedgenerator.h"
std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action, Session &userSession)
{
if(action == "" || action == "index")
@ -80,6 +81,14 @@ std::unique_ptr<Handler> HandlerFactory::createHandler(const std::string &action
{
return produce<HandlerUserSettings>(userSession);
}
if(action == "version")
{
return produce<HandlerVersion>(userSession);
}
if(action == "feed")
{
return produce<HandlerFeedGenerator>(userSession);
}
return produce<HandlerInvalidAction>(userSession);
}

查看文件

@ -0,0 +1,157 @@
#include "handlerfeedgenerator.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();
auto permissionDao = this->database->createPermissionsDao();
std::vector<EntryRevisionPair> result;
QueryOption option;
option.includeUnlisted = true;
// 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)
{
if(member.feedlisted)
{
Permissions perms = permissionDao->find(member.name, this->userSession->user.login)
.value_or(this->userSession->user.permissions);
if(perms.canRead())
{
auto revision = revisionDao->getRevisionForPage(member.name, 1).value();
result.push_back({member, revision});
}
}
}
std::sort(result.begin(), result.end(),
[](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, *this->userSession};
for(const EntryRevisionPair &entry : entries)
{
const Page &page = entry.first;
const Revision &initialRevision = entry.second;
Revision current = revisionDao->getCurrentForPage(page.name).value();
std::string url = this->urlProvider->rootUrl() + this->urlProvider->page(page.name);
if(initialRevision.timestamp > newestPublished)
{
newestPublished = initialRevision.timestamp;
}
std::string entryPublished = utils::formatLocalDate(initialRevision.timestamp, dateformat) + "Z";
std::string entryurl =
this->urlProvider->combine({this->urlProvider->rootUrl(), this->urlProvider->page(page.name)});
TemplatePage atomentry = this->templ->getPage("feeds/atomentry");
atomentry.setVar("entrytitle", page.title);
atomentry.setVar("entryurl", utils::html_xss(entryurl));
atomentry.setVar("entryid", utils::html_xss(entryurl));
atomentry.setVar("entrypublished", entryPublished);
atomentry.setVar("entryupdated", entryPublished);
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;
}

查看文件

@ -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

查看文件

@ -50,7 +50,8 @@ Response HandlerHistory::handleRequest(const Request &r)
std::vector<Revision> resultList;
auto revisionDao = this->database->createRevisionDao();
auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order) {
auto makeSortedLink = [&](unsigned int limit, unsigned int offset, unsigned int order)
{
if(!page.empty())
{
return this->urlProvider->pageHistorySort(page, limit, offset, order);
@ -122,7 +123,7 @@ Response HandlerHistory::handleRequest(const Request &r)
return response;
}
bool HandlerHistory::canAccess(const Permissions &perms)
bool HandlerHistory::canAccess([[maybe_unused]] const Permissions &perms)
{
return true; // This is a lie but we need to this a little more fine grained here, which we do in the handleRequest
}

查看文件

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

查看文件

@ -55,7 +55,7 @@ Response HandlerLogin::handleRequest(const Request &r)
{
page = "index";
}
TemplatePage &loginTemplatePage = this->templ->getPage("login");
TemplatePage loginTemplatePage = this->templ->getPage("login");
setGeneralVars(loginTemplatePage);
loginTemplatePage.setVar("loginurl", urlProvider->login(page));
loginTemplatePage.setVar("title", createPageTitle("Login"));
@ -66,7 +66,7 @@ Response HandlerLogin::handleRequest(const Request &r)
return result;
}
bool HandlerLogin::canAccess(const Permissions &perms)
bool HandlerLogin::canAccess([[maybe_unused]] const Permissions &perms)
{
return true;
}

查看文件

@ -27,7 +27,18 @@ Response HandlerPage::handle(const Request &r)
auto pageDao = this->database->createPageDao();
if(pagename.empty())
{
return errorResponse("No page given", "No page given to request");
std::string title = r.get("title");
if(title.empty())
{
return errorResponse("No page given", "No page given to request");
}
title = utils::strreplace(title, "-", " ");
auto page = pageDao->findByTitle(title);
if(!page)
{
return errorResponse("No page by such title", "No page with such title exists");
}
pagename = page->name;
}
if(pageMustExist() && !pageDao->exists(pagename))
@ -63,8 +74,9 @@ void HandlerPage::setPageVars(TemplatePage &page, std::string pagename)
if(!pagename.empty())
{
std::string headerlinks;
TemplatePage &headerlink = this->templ->getPage("_headerlink");
auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value) {
TemplatePage headerlink = this->templ->getPage("_headerlink");
auto addHeaderLink = [&headerlinks, &headerlink](std::string href, std::string value)
{
headerlink.setVar("href", href);
headerlink.setVar("value", value);
headerlinks += headerlink.render();

查看文件

@ -45,6 +45,7 @@ Response HandlerPageDelete::handleRequest(PageDao &pageDao, std::string pagename
{
pageDao.deletePage(pagename);
this->cache->removePrefix("page:"); // TODO: overkill?
this->cache->removePrefix("feed:");
return Response::redirectTemporarily(this->urlProvider->index());
}
TemplatePage delPage = this->templ->getPage("page_deletion");

查看文件

@ -23,9 +23,11 @@ SOFTWARE.
#include "../request.h"
#include "../parser.h"
bool HandlerPageEdit::canAccess(std::string page)
#include "../revisionrenderer.h"
bool HandlerPageEdit::canAccess([[maybe_unused]] std::string page)
{
return this->userSession->user.permissions.canEdit();
return effectivePermissions(page).canEdit();
}
bool HandlerPageEdit::pageMustExist()
@ -41,7 +43,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("No permission", "You don't have permission to create new pages");
}
auto revisiondao = this->database->createRevisionDao();
auto revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
auto revision = revisiondao->getCurrentForPage(pagename);
std::string body;
unsigned int current_revision = 0;
@ -50,6 +52,16 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
body = revision->content;
current_revision = revision->revision;
}
std::string from = r.get("frompage");
if(!from.empty())
{
if(!effectivePermissions(from).canRead())
{
return this->errorResponse("Permission denied",
"No access permissions, so you can't use this page as a template");
}
body = revisiondao->getCurrentForPage(from)->content;
}
if(r.getRequestMethod() == "POST")
{
if(r.post("do") == "submit")
@ -64,8 +76,27 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
try
{
this->database->beginTransaction();
std::string visiblecmd = parser.extractCommand("visible", newContent);
std::string listedcmd = parser.extractCommand("listed", newContent);
/* Backwarts compatibility */
if(listedcmd.empty())
{
listedcmd = visiblecmd;
}
std::string feedlistedcmd = parser.extractCommand("feedlisted", newContent);
std::string rename = parser.extractCommand("rename", newContent);
std::string customtitle = parser.extractCommand("pagetitle", newContent);
std::string parentpage = parser.extractCommand("parentpage", newContent);
std::vector<std::string> perms = parser.extractCommands("permissions", newContent);
if(parentpage != "" && !pageDao.find(parentpage))
{
return this->errorResponse("Invalid parent",
"Specified parent page " + parentpage + " does not exist");
}
Page page;
std::optional<Page> currentPage = pageDao.find(pagename);
if(currentPage)
@ -80,9 +111,52 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
}
pagename = rename;
}
std::vector<std::pair<std::string, Permissions>> collectedPermissions;
auto permissionDao = this->database->createPermissionsDao();
for(const std::string &perm : perms)
{
auto splitted = utils::split(perm, '|');
if(splitted.size() != 2)
{
return this->errorResponse("Invalid command", "permissions command is misformated");
}
auto currentPermission = permissionDao->find(pagename, splitted[0]);
Permissions newPermissions = Permissions{splitted[1]};
if(!currentPermission || newPermissions != currentPermission.value())
{
if(!this->userSession->user.permissions.canSetPagePerms())
{
this->database->rollbackTransaction();
return errorResponse("Permission denied",
"You don't have permission to change permissions. Don't touch the "
"permission commands");
}
}
collectedPermissions.emplace_back(splitted[0], newPermissions);
}
if(this->userSession->user.permissions.canSetPagePerms())
{
permissionDao->clearForPage(pagename);
for(auto &perms : collectedPermissions)
{
permissionDao->save(pagename, perms.first, perms.second);
}
}
page.current_revision = current_revision;
page.listed = !(visiblecmd == "0");
page.listed = !(listedcmd == "0");
page.feedlisted = !(feedlistedcmd == "0");
page.name = pagename;
page.title = customtitle;
page.parentpage = parentpage;
if(page.title.empty())
{
page.title = page.name;
}
pageDao.save(page);
Revision newRevision;
@ -95,9 +169,11 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
pageDao.setCategories(pagename, cats);
this->database->commitTransaction();
this->cache->removePrefix("page:"); // TODO: overkill?
this->cache->removePrefix("feed:");
}
catch(const DatabaseException &e)
{
this->database->rollbackTransaction();
Logger::debug() << "Error saving revision: " << e.what();
return errorResponse("Database error", "A database error occured while trying to save this revision");
}
@ -108,12 +184,16 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
{
std::string newContent = r.post("content");
Parser parser;
std::string title = parser.extractCommand("pagetitle", newContent);
TemplatePage templatePage = this->templ->getPage("page_creation_preview");
templatePage.setVar("actionurl", urlProvider->editPage(pagename));
templatePage.setVar("preview_content", parser.parse(pageDao, *this->urlProvider, newContent));
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
templatePage.setVar("preview_content", revisionRenderer.renderContent(newContent));
templatePage.setVar("content", newContent);
setPageVars(templatePage, pagename);
templatePage.setVar("title", createPageTitle("Preview: " + pagename));
templatePage.setVar("title", createPageTitle("Preview: " + title));
templatePage.setVar("comment", r.post("comment"));
Response response;
response.setBody(templatePage.render());
@ -121,7 +201,7 @@ Response HandlerPageEdit::handleRequest(PageDao &pageDao, std::string pagename,
}
}
TemplatePage &templatePage = this->templ->getPage("page_creation");
TemplatePage templatePage = this->templ->getPage("page_creation");
templatePage.setVar("actionurl", urlProvider->editPage(pagename));
templatePage.setVar("content", body);
setPageVars(templatePage, pagename);

查看文件

@ -23,6 +23,11 @@ SOFTWARE.
#include "../logger.h"
#include "../parser.h"
#include "../htmllink.h"
#include "../dynamic/dynamiccontentpostlist.h"
#include "../dynamic/dynamiccontentincludepage.h"
#include "../dynamic/dynamiccontentsetvar.h"
#include "../dynamic/dynamiccontentgetvar.h"
#include "../revisionrenderer.h"
bool HandlerPageView::canAccess(std::string page)
{
@ -86,19 +91,20 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
std::optional<Revision> revision;
std::string templatepartname;
auto revisionDao = this->database->createRevisionDao();
try
{
if(revisionid > 0)
{
if(!effectivePermissions(pagename).canSeePageHistory())
{
auto current = this->database->createRevisionDao()->getCurrentForPage(pagename);
auto current = revisionDao->getCurrentForPage(pagename);
if(current && current->revision > revisionid)
{
return errorResponse("Error", "You are not allowed to view older revisions of this page");
}
}
revision = this->database->createRevisionDao()->getRevisionForPage(pagename, revisionid);
revision = revisionDao->getRevisionForPage(pagename, revisionid);
if(!revision)
{
return errorResponse("Revision not found", "No such revision found");
@ -118,7 +124,7 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return r;
}
}
revision = this->database->createRevisionDao()->getCurrentForPage(pagename);
revision = revisionDao->getCurrentForPage(pagename);
templatepartname = "page_view";
}
}
@ -128,52 +134,30 @@ Response HandlerPageView::handleRequest(PageDao &pageDao, std::string pagename,
return errorResponse("Database error", "While trying to fetch revision, a database error occured");
}
TemplatePage &page = this->templ->getPage(templatepartname);
Parser parser;
Response result;
result.setStatus(200);
std::string indexcontent;
std::string parsedcontent;
if(revisionid > 0)
{
indexcontent = createIndexContent(parser, revision->content);
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
}
else
{
std::string cachekeyindexcontent = "page:indexcontent:" + pagename;
std::string cachekeyparsedcontent = "page:parsedcontent:" + pagename;
auto cachedindexcontent = this->cache->get(cachekeyindexcontent);
auto cachedparsedcontent = this->cache->get(cachekeyparsedcontent);
if(cachedindexcontent)
{
indexcontent = *cachedindexcontent;
}
else
{
indexcontent = createIndexContent(parser, revision->content);
this->cache->put(cachekeyindexcontent, indexcontent);
}
if(cachedparsedcontent)
{
parsedcontent = *cachedparsedcontent;
}
else
{
parsedcontent = parser.parse(pageDao, *this->urlProvider, revision->content);
this->cache->put(cachekeyparsedcontent, parsedcontent);
}
}
RevisionRenderer revisionRenderer{*this->templ, *this->database, *this->urlProvider, *this->userSession};
std::string customtitle = parser.extractCommand("pagetitle", revision->content);
std::string parsedcontent = revisionRenderer.renderContent(revision.value(), customtitle);
/* TODO: Dynamic includes not considered, probably fine in practise */
std::string indexcontent = createIndexContent(parser, revision->content);
std::string revisionstr = std::to_string(revision->revision);
TemplatePage page = this->templ->getPage(templatepartname);
page.setVar("content", parsedcontent);
page.setVar("index", indexcontent);
page.setVar("editedby", revision->author);
page.setVar("editedon", utils::toISODate(revision->timestamp));
page.setVar("editedon", utils::toISODateTime(revision->timestamp));
page.setVar("historyurl", this->urlProvider->pageHistory(pagename));
page.setVar("revision", revisionstr);
setPageVars(page, pagename);
if(!customtitle.empty())
{
page.setVar("title", createPageTitle(customtitle));
}
std::string body = page.render();
if(revisionid == 0 && !this->userSession->loggedIn)
{

查看文件

@ -25,7 +25,11 @@ Response HandlerSearch::handleRequest(const Request &r)
std::string q = r.get("q");
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();
@ -37,7 +41,7 @@ Response HandlerSearch::handleRequest(const Request &r)
{
return errorResponse("No results", "Your search for " + q + " did not yield any results.");
}
TemplatePage &searchPage = this->templ->getPage("search");
TemplatePage searchPage = this->templ->getPage("search");
std::string body = this->templ->renderSearch(resultList);
searchPage.setVar("pagelist", body);
searchPage.setVar("searchterm", q);

查看文件

@ -15,19 +15,20 @@ Response HandlerUserSettings::handleRequest(const Request &r)
if(newpassword != newpasswordconfirm)
{
//TODO: is not nice, users has to hit the back button...
// TODO: is not nice, users has to hit the back button...
return this->errorResponse("Passwords don't match", "The entered new passwords don't match");
}
auto userDao = this->database->createUserDao();
Authenticator authenticator(*userDao);
std::variant<User, AuthenticationError> authresult = authenticator.authenticate(this->userSession->user.login, oldpassword);
std::variant<User, AuthenticationError> authresult =
authenticator.authenticate(this->userSession->user.login, oldpassword);
if(std::holds_alternative<AuthenticationError>(authresult))
{
return this->errorResponse("Invalid current password", "The old password you entered is invalid");
}
Random r;
std::vector<char> salt = r.getRandom(23);
std::vector<char> salt = r.getRandom(AUTH_DEFAULT_SALT_SIZE);
User user = std::get<User>(authresult);
user.salt = salt;
user.password = authenticator.hash(newpassword, user.salt);
@ -50,7 +51,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
}
}
TemplatePage &userSettingsPage = this->templ->getPage("usersettings");
TemplatePage userSettingsPage = this->templ->getPage("usersettings");
setGeneralVars(userSettingsPage);
userSettingsPage.setVar("usersettingsurl", urlProvider->userSettings());
userSettingsPage.setVar("title", createPageTitle("User settings - " + this->userSession->user.login));
@ -61,7 +62,7 @@ Response HandlerUserSettings::handleRequest(const Request &r)
return result;
}
bool HandlerUserSettings::canAccess(const Permissions &perms)
bool HandlerUserSettings::canAccess([[maybe_unused]] const Permissions &perms)
{
return this->userSession->loggedIn;
}

查看文件

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

18
handlers/handlerversion.h 普通文件
查看文件

@ -0,0 +1,18 @@
#ifndef HANDLERVERSION_H
#define HANDLERVERSION_H
#include "handler.h"
class HandlerVersion : public Handler
{
public:
using Handler::Handler;
public:
Response handleRequest(const Request &r) override;
bool canAccess([[maybe_unused]] const Permissions &perms) override
{
return true;
}
};
#endif // HANDLERVERSION_H

查看文件

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

查看文件

@ -7,8 +7,8 @@ class Logger
private:
class LogEntry
{
bool headerSent;
std::ostream *out;
bool headerSent = false;
std::ostream *out = nullptr;
std::string prefix;
public:

3
page.h
查看文件

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

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 普通文件
查看文件

@ -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

查看文件

@ -27,10 +27,11 @@ SOFTWARE.
#include "parser.h"
#include "utils.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::string reg = R"(\[h(1|2|3)\](.*?)\[/h\1\])";
std::string reg = R"(\[h(1|2|3)\](\[.*?\])*(.*?)(\[.*?\])*\[\/h\1\])";
std::regex headerfinder(reg);
auto begin = std::sregex_iterator(content.begin(), content.end(), headerfinder);
auto end = std::sregex_iterator();
@ -40,13 +41,13 @@ std::vector<Headline> Parser::extractHeadlines(std::string content) const
auto smatch = *it;
Headline h;
h.level = utils::toUInt(smatch.str(1));
h.title = smatch.str(2);
h.title = smatch.str(3);
result.push_back(h);
}
return result;
}
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::string reg = R"(\[category\](.*?)\[/category\])";
@ -62,11 +63,10 @@ std::vector<std::string> Parser::extractCategories(std::string content) const
return result;
}
std::string Parser::extractCommand(std::string cmdname, std::string content) const
std::string Parser::extractFirstTag(std::string tagname, const std::string &content) const
{
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
std::string cmd = "[" + tagname + "]";
std::string cmdend = "[/" + tagname + "]";
std::string_view view = content;
size_t pos = 0;
if((pos = view.find(cmd)) != std::string::npos)
@ -81,6 +81,34 @@ std::string Parser::extractCommand(std::string cmdname, std::string content) con
}
return "";
}
std::string Parser::extractCommand(std::string cmdname, const std::string &content) const
{
return extractFirstTag("cmd:" + cmdname, content);
}
std::vector<std::string> Parser::extractCommands(std::string cmdname, const std::string &content) const
{
std::vector<std::string> result;
std::string cmd = "[cmd:" + cmdname + "]";
std::string cmdend = "[/cmd:" + cmdname + "]";
std::string_view view = content;
size_t pos = 0;
while((pos = view.find(cmd)) != std::string::npos)
{
view.remove_prefix(pos);
view.remove_prefix(cmd.size());
if((pos = view.find(cmdend)) != std::string::npos)
{
result.emplace_back(view.substr(0, pos));
}
}
return result;
}
std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const
{
std::string linktag = match.str(1);
@ -116,31 +144,98 @@ std::string Parser::processLink(const PageDao &pageDao, UrlProvider &urlProvider
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;
// 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])");
result = utils::regex_callback_replacer(tagfinder, content, [&](std::smatch &match) {
std::string tag = match.str(1);
std::string content = match.str(2);
std::string justreplace[] = {"b", "i", "u", "ul", "li", "ol"};
content = parse(pagedao, provider, content);
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
std::regex tagfinder(
R"(\[(b|i|u|s|li|p|br|ul|ol|code|blockquote|img|link|wikilink|h\d|cmd:visible|cmd:listed|cmd:feedlisted|cmd:rename|cmd:redirect|cmd:pagetitle|cmd:allowinclude|cmd:permissions|cmd:parentpage|content|category|dynamic:postlist|dynamic:includepage|dynamic:getvar|dynamic:setvar)*?\]((\s|\S)*?)\[/\1](\r\n)*)");
const std::string justreplace[] = {"b", "i", "u", "p", "br", "ul", "li", "ol"};
result = utils::regex_callback_replacer(
tagfinder, content,
[&](std::smatch &match)
{
return "<" + tag + ">" + content + "</" + tag + ">";
}
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[0] == 'h')
{
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
}
return std::string("");
});
std::string tag = match.str(1);
std::string content = match.str(2);
std::string newlines = match.str(4);
if(newlines == "\r\n")
{
newlines = "<br>";
}
if(tag != "code" && tag != "blockquote")
{
content = parse(pagedao, provider, content, callback);
}
/* [content] just helps extracting the actual content of a page, pretty much noop otherwise */
if(tag == "content")
{
return parse(pagedao, provider, content, callback);
}
if(std::find(std::begin(justreplace), std::end(justreplace), tag) != std::end(justreplace))
{
if(tag == "p" || tag == "br")
{
newlines = "";
}
return "<" + tag + ">" + content + "</" + tag + ">" + newlines;
}
if(tag == "link" || tag == "wikilink")
{
return this->processLink(pagedao, provider,
match) +
newlines; // TODO: recreate this so we don't check inside the function stuff again
}
if(tag == "img")
{
return this->processImage(match);
}
if(tag[0] == 'h')
{
return "<" + tag + " id='" + content + "'>" + content + "</" + tag + ">";
}
if(tag == "code" || tag == "blockquote")
{
return "<pre><" + tag + ">" + utils::strreplace(content, "\r\n", "\n") + "</" + tag + "></pre>";
}
return callback(tag, content);
});
result = utils::strreplace(result, "\r\n", "<br>");
return result;
}
std::string Parser::parseDynamics(const std::string &content,
const std::function<std::string(std::string_view, std::string_view)> &callback) const
{
std::regex tagfinder(R"(\[(dynamic:\w+)*?\]((\s|\S)*?)\[/\1])");
return utils::regex_callback_replacer(tagfinder, content,
[&](std::smatch &match) { return callback(match.str(1), match.str(2)); });
}

查看文件

@ -6,14 +6,23 @@ class Parser : public IParser
{
private:
std::string processLink(const PageDao &pageDao, UrlProvider &urlProvider, std::smatch &match) const;
std::string processImage(std::smatch &match) const;
public:
std::string extractCommand(std::string cmdname, std::string content) const;
std::vector<Headline> extractHeadlines(std::string content) const override;
std::vector<std::string> extractCategories(std::string content) const override;
std::string parse(const PageDao &pagedao, UrlProvider &provider, std::string content) const override;
std::string extractFirstTag(std::string tagname, const std::string &content) const override;
std::string extractCommand(std::string cmdname, const std::string &content) const override;
std::vector<std::string> extractCommands(std::string cmdname, const std::string &content) const override;
std::vector<Headline> extractHeadlines(const std::string &content) const override;
std::vector<std::string> extractCategories(const 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;
~Parser(){};
};
#endif // PARSER_H

查看文件

@ -20,6 +20,19 @@ SOFTWARE.
*/
#include "permissions.h"
static const std::map<std::string, int> permmap = {{"can_nothing", PERM_CAN_NOTHING},
{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
{"can_delete", PERM_CAN_DELETE},
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
{"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH},
{"can_set_page_perms", PERM_CAN_SET_PAGE_PERMS}};
Permissions::Permissions(int permissions)
{
this->permissions = permissions;
@ -36,3 +49,20 @@ Permissions::Permissions(const std::string &str)
}
}
}
std::string Permissions::toString(int perms)
{
std::string result;
for(auto pair : permmap)
{
if(pair.second & perms)
{
result += pair.first + ",";
}
}
if(result.size() > 0)
{
result.pop_back();
}
return result;
}

查看文件

@ -1,6 +1,7 @@
#ifndef PERMISSIONS_H
#define PERMISSIONS_H
#define PERM_CAN_NOTHING 0
#define PERM_CAN_READ 1 << 0
#define PERM_CAN_EDIT 1 << 1
#define PERM_CAN_PAGE_HISTORY 1 << 2
@ -11,23 +12,17 @@
#define PERM_CAN_SEE_CATEGORY_LIST 1 << 7
#define PERM_CAN_SEE_LINKS_HERE 1 << 8
#define PERM_CAN_SEARCH 1 << 9
#define PERM_CAN_SET_PAGE_PERMS 1 << 10
#define PERM_IS_ADMIN (1L<<31)-1
#include <string>
#include <map>
class Permissions
{
private:
int permissions = 0;
const std::map<std::string, int> permmap = {{"can_read", PERM_CAN_READ},
{"can_edit", PERM_CAN_EDIT},
{"can_page_history", PERM_CAN_PAGE_HISTORY},
{"can_global_history", PERM_CAN_GLOBAL_HISTORY},
{"can_delete", PERM_CAN_DELETE},
{"can_see_page_list", PERM_CAN_SEE_PAGE_LIST},
{"can_create", PERM_CAN_CREATE},
{"can_see_category_list", PERM_CAN_SEE_CATEGORY_LIST},
{"can_see_links_here", PERM_CAN_SEE_LINKS_HERE},
{"can_search", PERM_CAN_SEARCH}};
public:
Permissions()
@ -62,10 +57,16 @@ class Permissions
return this->permissions;
}
bool canNothing() const
{
return this->permissions == PERM_CAN_NOTHING;
}
bool canRead() const
{
return this->permissions & PERM_CAN_READ;
}
bool canEdit() const
{
return this->permissions & PERM_CAN_EDIT;
@ -102,6 +103,28 @@ class Permissions
{
return this->permissions & PERM_CAN_SEE_PAGE_LIST;
}
bool canSetPagePerms() const
{
return this->permissions & PERM_CAN_SET_PAGE_PERMS;
}
bool isAdmin() const
{
return this->permissions == PERM_IS_ADMIN;
}
std::string toString() const
{
return Permissions::toString(this->permissions);
}
static std::string toString(int perms);
bool operator==(const Permissions &o) const
{
return this->permissions == o.permissions;
}
};
#endif // PERMISSIONS_H

查看文件

@ -25,19 +25,26 @@ SOFTWARE.
#include <unistd.h>
#include <sys/types.h>
#include <filesystem>
#include <getopt.h>
#include "gateway/gatewayinterface.h"
#include "gateway/gatewayfactory.h"
#include "handlers/handlerfactory.h"
#include "database/databasefactory.h"
#include "config.h"
#include "template.h"
#include "session.h"
#include "template.h"
#include "logger.h"
#include "urlprovider.h"
#include "requestworker.h"
#include "cache/fscache.h"
#include "cache/nocache.h"
#include "sandbox/sandboxfactory.h"
void sigterm_handler(int arg)
#include "cli.h"
#include "cliconsole.h"
#include "cliserver.h"
#include "version.h"
void sigterm_handler([[maybe_unused]] int arg)
{
// TODO: proper shutdown.
exit(EXIT_SUCCESS);
@ -56,20 +63,83 @@ void setup_signal_handlers()
}
}
#define OPT_PRINT_VERSION 23
static struct option long_options[] = {{"cli", no_argument, 0, 'c'}, {"version", no_argument, 0, OPT_PRINT_VERSION}};
std::unique_ptr<ICache> createCache(const ConfigVariableResolver &resolver)
{
std::string path = resolver.getConfig("cache_fs_dir");
if(path == "")
{
return std::make_unique<StringCache>();
}
return std::make_unique<FsCache>(path);
}
std::thread background_worker;
void start_background_worker(Database &database, Config &config)
{
background_worker = std::thread(
[&database, &config]()
{
while(true)
{
Logger::log() << "Executing background worker";
auto sessionDao = database.createSessionDao();
auto sessionList = sessionDao->fetch();
time_t now = time(NULL);
for(Session &sess : sessionList)
{
if(now - sess.creation_time > config.session_max_lifetime)
{
sessionDao->deleteSession(sess.token);
}
}
std::this_thread::sleep_for(std::chrono::hours(1));
}
});
}
int main(int argc, char **argv)
{
char *configfilepath = NULL;
int option;
int option_index;
bool cli_mode = false;
if(geteuid() == 0)
{
std::cerr << "Do not run this as root!" << std::endl;
return 1;
}
while((option = getopt_long(argc, argv, "cv", long_options, &option_index)) != -1)
{
switch(option)
{
case 'c':
cli_mode = true;
break;
case OPT_PRINT_VERSION:
std::cout << get_version_string() << std::endl;
exit(EXIT_SUCCESS);
break;
}
}
if(optind == argc)
{
std::cerr << "Missing config path" << std::endl;
return 1;
}
configfilepath = argv[optind++];
auto sandbox = createSandbox();
// TODO: do we want to keep it mandatory or configurable?
if(!sandbox->supported())
@ -77,35 +147,18 @@ int main(int argc, char **argv)
Logger::error() << "Sandbox is not supported, exiting";
exit(EXIT_FAILURE);
}
if(!sandbox->enableForInit())
{
Logger::error() << "Sandboxing for init mode could not be activated.";
exit(EXIT_FAILURE);
}
if(argc < 2)
{
std::cerr << "no path to config file provided" << std::endl;
return 1;
}
std::string configpath = std::filesystem::absolute(configfilepath).string();
try
{
ConfigReader configreader(argv[1]);
ConfigReader configreader(configpath);
Config config = configreader.readConfig();
// TODO: config.connectiontring only works as long as we only support sqlite of course
if(!sandbox->enablePreWorker({
config.configVarResolver.getConfig("cache_fs_dir"),
config.templatepath,
std::filesystem::path(config.logfile).parent_path(),
std::filesystem::path(config.connectionstring).parent_path(),
}))
{
Logger::error() << "Sandboxing for pre worker stage could not be activated.";
exit(EXIT_FAILURE);
}
setup_signal_handlers();
std::fstream logstream;
@ -114,6 +167,37 @@ int main(int argc, char **argv)
auto database = createDatabase(config);
std::string socketPath = config.configVarResolver.getConfig("socketpath");
CLIHandler cliHandler(config, *database);
if(cli_mode)
{
CLIConsole console{cliHandler, socketPath};
console.startInteractive();
return 0;
}
// TODO: config.connectiontring only works as long as we only support sqlite of course
if(!sandbox->enable({
config.configVarResolver.getConfig("cache_fs_dir"),
config.templatepath,
std::filesystem::path(config.logfile).parent_path(),
std::filesystem::path(config.connectionstring).parent_path(),
}))
{
Logger::error() << "Sandboxing for worker could not be enabled!";
exit(EXIT_FAILURE);
}
start_background_worker(*database.get(), config);
CLIServer cliServer{cliHandler};
if(!cliServer.detachServer(socketPath))
{
Logger::error() << "Error: Failed to detach unix socket server";
return 1;
}
// TODO: quite ugly, anon-handling must be rethought
auto userdao = database->createUserDao();
std::optional<User> anon = userdao->find(config.handlersConfig.anon_username);
@ -129,7 +213,9 @@ int main(int argc, char **argv)
userdao->save(anon.value());
User::setAnon(anon.value());
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver};
MapCache<TemplatePage> mapCache;
Template siteTemplate{config.templateprefix, config.templatepath, config.urls, config.configVarResolver,
mapCache};
UrlProvider urlProvider{config.urls};
auto cache = createCache(config.configVarResolver);
@ -140,11 +226,6 @@ int main(int argc, char **argv)
auto interface = createGateway(config);
if(!sandbox->enableForWorker())
{
Logger::error() << "Sandboxing for worker could not be enabled!";
exit(EXIT_FAILURE);
}
interface->work(requestWorker);
}
catch(const std::exception &e)

查看文件

@ -40,7 +40,7 @@ std::pair<std::string, std::string> Request::createPairFromVar(std::string var)
else
{
std::string key = var.substr(0, equal);
std::string val = utils::html_xss(var.substr(equal + 1));
std::string val = utils::html_xss(utils::urldecode(var.substr(equal + 1)));
return std::make_pair(std::move(key), std::move(val));
}
}
@ -75,7 +75,7 @@ void Request::initPostMap(const std::string &url)
void Request::initCookies(const std::string &cookiestr)
{
// TODO: find out what it really should be, ";" or "; "?
std::regex regex { ";+\\s?" };
std::regex regex{";+\\s?"};
auto cookiesplitted = utils::split(cookiestr, regex);
for(const std::string &part : cookiesplitted)
{

查看文件

@ -59,7 +59,7 @@ Response RequestWorker::processRequest(const Request &r)
if(session.loggedIn && session.csrf_token != r.post("csrf_token"))
{
// TODO: this is code duplication
TemplatePage &error = this->templ->getPage("error");
TemplatePage error = this->templ->getPage("error");
error.setVar("errortitle", "Invalid csrf token");
error.setVar("errormessage", "Invalid csrf token");
return {403, error.render()};

查看文件

@ -32,12 +32,12 @@ Response::Response(int http_status_code, std::string html)
this->html = std::move(html);
}
void Response::addHeader(const std::string &key, const std::string &value)
void Response::addHeader(std::string key, std::string value)
{
this->responseHeaders.insert(std::make_pair(key, value));
}
Response Response::redirectTemporarily(const std::string &url)
Response Response::redirectTemporarily(std::string url)
{
Response result;
result.addHeader("Location", url);

查看文件

@ -27,8 +27,8 @@ class Response
return this->html;
}
void addHeader(const std::string &key, const std::string &value);
static Response redirectTemporarily(const std::string &url);
void addHeader(std::string key, std::string value);
static Response redirectTemporarily(std::string url);
void setStatus(int status)
{

78
revisionrenderer.cpp 普通文件
查看文件

@ -0,0 +1,78 @@
#include "revisionrenderer.h"
#include "templatepage.h"
#include "dynamic/dynamiccontentpostlist.h"
#include "dynamic/dynamiccontentincludepage.h"
#include "dynamic/dynamiccontentgetvar.h"
#include "dynamic/dynamiccontentsetvar.h"
#include "dynamic/dynamicpostrenderer.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();
}
if(key == "dynamic:postrenderer")
{
auto renderer = this->dynamicContentFactory.createDynamicContent<DynamicPostRenderer>();
renderer->setArgument(std::string(value));
return renderer->render();
}
return std::string{};
}
std::string RevisionRenderer::renderContent(std::string content)
{
dynamicVarsMap["pagetitle"] = parser.extractCommand("pagetitle", content);
dynamicVarsMap["createdon"] = utils::toISODate(time(NULL));
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(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;
dynamicVarsMap["modifydatetime"] = utils::toISODateTime(r.timestamp);
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 普通文件
查看文件

@ -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, Session &session) :dynamicContentFactory(templ, db, urlProvider, session)
{
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,63 +12,13 @@
#include <filesystem>
#include <sys/mount.h>
#include <sys/capability.h>
#include <qssb.h>
#include <exile.hpp>
#include "../logger.h"
#include "../utils.h"
#include "../random.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::enableForInit()
{
umask(0027);
struct qssb_policy policy = {0};
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
policy.no_new_privs = 1;
int result = qssb_enable_policy(&policy);
if(result != 0)
{
Logger::error() << "Failed to install sandboxing policy (init): " << result;
return false;
}
return true;
}
bool SandboxLinux::enablePreWorker(std::vector<std::string> fsPaths)
{
std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); });
struct qssb_path_policy *policies = new qssb_path_policy[fsPaths.size()];
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
policies[i].next = policies + (i + 1);
policies[i].mountpoint = fsPaths[i].c_str();
policies[i].policy = QSSB_MOUNT_ALLOW_READ | QSSB_MOUNT_ALLOW_WRITE;
}
policies[fsPaths.size() - 1].next = NULL;
struct qssb_policy policy = {0};
policy.path_policies = policies;
policy.namespace_options |= QSSB_UNSHARE_MOUNT;
policy.namespace_options |= QSSB_UNSHARE_USER;
int blacklisted_syscalls[] = {QSSB_SYS(execveat), QSSB_SYS(execve), -1};
policy.blacklisted_syscalls = blacklisted_syscalls;
int result = qssb_enable_policy(&policy);
if(result != 0)
{
Logger::error() << "Failed to install sandboxing policy (preworker): %i" << result;
return false;
}
delete[] policies;
return true;
}
bool SandboxLinux::supported()
{
std::fstream stream;
@ -86,32 +36,36 @@ bool SandboxLinux::supported()
}
return true;
}
bool SandboxLinux::enableForWorker()
bool SandboxLinux::enable(std::vector<std::string> fsPaths)
{
struct qssb_policy policy = {0};
policy.drop_caps = 1;
policy.not_dumpable = 1;
policy.no_new_privs = 1;
std::sort(fsPaths.begin(), fsPaths.end(),
[](const std::string &a, const std::string &b) { return a.length() < b.length(); });
/* TODO: as said, a whitelist approach is better. As such, this list is bound to be incomplete in the
* sense that more could be listed here and some critical ones are probably missing */
int 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),
-1};
policy.blacklisted_syscalls = blacklisted_syscalls;
if(qssb_enable_policy(&policy) != 0)
struct exile_policy *policy = exile_init_policy();
if(policy == NULL)
{
Logger::error() << "Sandbox: Activation of seccomp blacklist failed!";
Logger::error() << "Failed to init sandboxing policy (worker) ";
return false;
}
for(unsigned int i = 0; i < fsPaths.size(); i++)
{
std::string &path = fsPaths[i];
if(path.size() > 0)
{
exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, path.c_str());
}
}
policy->drop_caps = 1;
policy->not_dumpable = 1;
policy->no_new_privs = 1;
policy->mount_path_policies_to_chroot = 1;
policy->vow_promises = exile_vows_from_str("stdio wpath cpath rpath inet unix thread");
if(exile_enable_policy(policy) != 0)
{
Logger::error() << "Sandbox: Activation of exile failed!";
exile_free_policy(policy);
return false;
}
exile_free_policy(policy);
return true;
}

查看文件

@ -8,8 +8,6 @@ class SandboxLinux : public Sandbox
public:
using Sandbox::Sandbox;
bool supported() override;
bool enableForInit() override;
bool enablePreWorker(std::vector<std::string> fsPaths) override;
bool enableForWorker() override;
bool enable(std::vector<std::string> fsPaths) override;
};
#endif

查看文件

@ -6,10 +6,6 @@ class SandboxOpenBSD : public Sandbox
{
public:
bool supported() override;
bool enableForInit() override;
bool enableForWorker() override;
private:
bool seccomp_blacklist(std::vector<int> syscalls);
bool enable(std::vector<std::string> fsPaths) override;
};
#endif

查看文件

@ -10,16 +10,7 @@ class Sandbox
/* Whether the platform has everything required to active all sandbnox modes */
virtual bool supported() = 0;
/* Activated early. At this point, we need more system calls
* than later on */
virtual bool enableForInit() = 0;
/* Activated after config has been read. Now we now which paths we need access to */
virtual bool enablePreWorker(std::vector<std::string> fsPaths) = 0;
/* Activated after we have acquired resources (bound to ports etc.)
*
* This should allow us to further restrcit the process */
virtual bool enableForWorker() = 0;
/* Activated after we have acquired resources (bound to ports etc.)*/
virtual bool enable(std::vector<std::string> fsPaths) = 0;
};
#endif

查看文件

@ -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, listed integer DEFAULT 1, parent integer REFERENCES page(id), feedlisted integer DEFAULT 1);
CREATE TABLE user(id INTEGER PRIMARY KEY,username varchar(64),
password blob, salt blob, permissions integer, enabled integer DEFAULT 1);
CREATE TABLE session(id INTEGER PRIMARY KEY, csrf_token varchar(32),
@ -25,24 +25,13 @@ count integer
CREATE TABLE category(id INTEGER PRIMARY KEY, name varchar(255));
CREATE TABLE categorymember(id INTEGER PRIMARY KEY, category REFERENCES category(id), page REFERENCES page (id));
CREATE INDEX revisionid ON revision (revisionid DESC);
CREATE INDEX pagename ON page (name)
;
CREATE INDEX token ON session (token)
;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
DELETE FROM search WHERE page = new.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE TRIGGER search_au AFTER UPDATE ON revision BEGIN
DELETE FROM search WHERE page = old.page;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;
CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id)
/* search(content,page) */;
CREATE TABLE IF NOT EXISTS 'search_data'(id INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'search_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS 'search_docsize'(id INTEGER PRIMARY KEY, sz BLOB);
CREATE TABLE IF NOT EXISTS 'search_config'(k PRIMARY KEY, v) WITHOUT ROWID;
CREATE INDEX pagename ON page (name);
CREATE INDEX token ON session (token);
CREATE VIRTUAL TABLE search USING fts5(content, page UNINDEXED, content=revision,content_rowid=id);
CREATE TRIGGER search_ad AFTER DELETE ON revision BEGIN
INSERT INTO search(search, rowid, content, page) VALUES('delete', old.id, old.content, old.page);
END;
CREATE TRIGGER search_ai AFTER INSERT ON revision BEGIN
INSERT INTO search(search, rowid, content, page) SELECT 'delete', id, content, page FROM revision WHERE page = new.page AND revisionid = new.revisionid - 1;
INSERT INTO search(rowid, content, page) VALUES (new.id, new.content, new.page);
END;

某些文件未显示,因为此 diff 中更改的文件太多 显示更多