mirror of
https://github.com/tomoko-dev9/nntpchan.git
synced 2026-05-09 23:04:27 +02:00
Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3899989f2e | |||
| ce7f112be8 | |||
| e6417b3bd7 | |||
| 8a72b29d45 | |||
| fcd5a97225 | |||
| 7c5546c0c0 | |||
| 7abd41eecd | |||
| c5479e2386 | |||
| b61c22898e | |||
| e2de5edd43 | |||
| 2449cb1adc | |||
| 2adcc73d92 | |||
| 55ba1e6c7c | |||
| 4bef3d8964 | |||
| aecd4ca291 | |||
| 222a905c3a | |||
| 777cb0941a | |||
| f06cb1d9a2 | |||
| ef1fc85a8a | |||
| f1d3c0a6b5 | |||
| e8e6812a25 | |||
| 6754947dc2 | |||
| 5683e6eba0 | |||
| 95e96db324 | |||
| 024f773a7c | |||
| 702ab469cd | |||
| 3eb2c0df0d | |||
| 6abc6f4021 | |||
| 6fbf3e9bd7 | |||
| 4af10d59a9 | |||
| ed833024f3 | |||
| ff4cb0a33a | |||
| c3426871d2 | |||
| 2bb4540118 | |||
| 0a77cf1a62 | |||
| e9507505af | |||
| b14a9f709d | |||
| ba74a79409 | |||
| 81653a5415 | |||
| 9f17b7add1 | |||
| 6b039265d9 | |||
| 78bb8577b4 | |||
| 9fba95b58d | |||
| 17e72ce097 | |||
| 8894cf6814 | |||
| 410ef6e430 | |||
| d752312868 | |||
| 3dab2ceb95 | |||
| 337a61dd7f | |||
| 685153f94e | |||
| 8cb044a5e3 | |||
| 91cfa9441e | |||
| f80acbecc2 | |||
| 53522b98eb | |||
| 517338264d | |||
| 2752676013 | |||
| aadb4ae230 | |||
| df021531cb | |||
| 37e6129261 | |||
| 0266deee2b | |||
| 92320aff4c | |||
| 3e6a80f58c | |||
| cdef33af7c | |||
| e0350ecb98 | |||
| 082430df55 | |||
| 63fe1ad1b5 | |||
| 8b35e7cf30 | |||
| cff6f50d63 | |||
| a4c8de953a | |||
| 9c89baf68e | |||
| 29ab733808 | |||
| 9e644eb004 | |||
| a7e72e2aff | |||
| f4640e82c4 | |||
| 95a9c1bda9 | |||
| 936782b616 | |||
| feb43b1ed8 | |||
| e093864ee7 | |||
| a7718e9a0a | |||
| 9230349b30 | |||
| 26d4f5dffb | |||
| a88334e985 | |||
| d3ca9dfa33 | |||
| 58cc8c2365 | |||
| 8cd93abe4f | |||
| a07f4a30d6 | |||
| 69359700b4 | |||
| d2552c5cd1 | |||
| 210a2109a7 | |||
| 2195c0a29c | |||
| 96a931de3e | |||
| c4d5ab431a | |||
| 3ce810775e | |||
| d89a811611 | |||
| c6dab2125e | |||
| b8fa8fff80 | |||
| 78606e95de | |||
| b919c095a8 | |||
| 666549e1e4 | |||
| 99a3403ed8 | |||
| e7c6100691 | |||
| bf357a3461 | |||
| c06b503efd | |||
| cda181e820 | |||
| ba16d5d717 | |||
| de253fb204 | |||
| 835c912053 | |||
| c53d4ba9ee | |||
| b25ce88232 | |||
| 9ef6d119c3 | |||
| 75670aa1b7 | |||
| 6d0ff936ce | |||
| 5367b53570 | |||
| f5fc7c0ff3 | |||
| 38a162e416 | |||
| 3c0122e8a2 | |||
| 55dc52daf2 | |||
| 62bb1b7b32 | |||
| 882da39b87 | |||
| f11f67190c | |||
| 75bc4df7f6 | |||
| 53330eef69 | |||
| f771c8c0d9 | |||
| 2aa1573bee | |||
| 09504ca363 | |||
| 3b83f184ea | |||
| 841c5c6afe | |||
| 85248787fc | |||
| 1f24f03cf5 | |||
| 53ba50541f | |||
| 7c94ff329a | |||
| 2740229d6b | |||
| 5834df6cf2 | |||
| 123057d608 | |||
| 24a25d5e20 | |||
| 311503884d | |||
| 8df0d9bbc2 | |||
| f17f865f79 | |||
| e9c88ffd28 | |||
| c583a03f81 | |||
| 12bb8c4936 | |||
| ec7a17a647 | |||
| c6cc0b17c0 | |||
| 529b1dd0f4 | |||
| 36243cb2eb | |||
| ce2e1eb7a8 | |||
| 78797c680f | |||
| 0848412aa0 | |||
| e25b84c686 | |||
| 579bf619f4 | |||
| e67e7a20bd | |||
| dc2de0fbc9 | |||
| 8cdb070723 | |||
| c54820a198 | |||
| 24fea24b03 | |||
| 93cc2ff803 | |||
| 8038663b0a | |||
| 334ac0e4f2 | |||
| b439795e04 | |||
| 6f00406f99 | |||
| a0495130cc | |||
| 54c0821339 | |||
| b3d65dc5b9 | |||
| 54fde6ae2e | |||
| 2284fac632 | |||
| 6ec930a54a | |||
| 394a4a65e1 | |||
| b823223bd1 | |||
| c951473310 | |||
| 23b4f11e6d | |||
| 93b4558b27 | |||
| 493565257e | |||
| d6bbe584de | |||
| 796d3480ba | |||
| ef024977df | |||
| cbe6af7349 | |||
| dcba36873e | |||
| 065e79026e | |||
| 1fd5588457 | |||
| 203e67a017 | |||
| 86896b6c52 | |||
| 9cc9609ef6 | |||
| c8563f5fb4 | |||
| 5a7eabc0d0 | |||
| d245462373 | |||
| 534e023526 | |||
| 6274fff05b | |||
| 0148aeb6af | |||
| 7371db736d | |||
| 42cc7f26c4 | |||
| be7efb24cd | |||
| 3ee449062e | |||
| 73cf6da65d | |||
| 2f86abe62b | |||
| 713cec5f45 | |||
| da7ad5a7fe | |||
| 5a2f3692cf | |||
| b634fa2665 | |||
| ab37624a7a | |||
| 61c35b7652 | |||
| 24b9076c65 | |||
| 69fac43124 | |||
| dee8c005fd | |||
| a85622854b | |||
| b61012fc43 | |||
| d2887a99b4 | |||
| e224ee7aab | |||
| b75d669f4e | |||
| 02edcaec3d | |||
| 5e40fe9c43 | |||
| e5b3027324 | |||
| 295b2b0362 | |||
| a1f8b35599 | |||
| 1aa6824fd7 | |||
| 1b55b4a213 | |||
| 3097cea3a4 | |||
| 693b399f10 | |||
| 7b5ac6602f | |||
| 683d7b7179 | |||
| d97f1332d6 | |||
| b8e862bbb6 | |||
| 3b0a58d24c | |||
| 2e1b934705 |
@@ -19,6 +19,7 @@ webroot
|
|||||||
|
|
||||||
# built binaries
|
# built binaries
|
||||||
go
|
go
|
||||||
|
gopherjs_go
|
||||||
./srndv2
|
./srndv2
|
||||||
|
|
||||||
# private key
|
# private key
|
||||||
@@ -36,5 +37,6 @@ contrib/static/nntpchan.js
|
|||||||
contrib/static/js/nntpchan.js
|
contrib/static/js/nntpchan.js
|
||||||
contrib/static/miner-js.js
|
contrib/static/miner-js.js
|
||||||
|
|
||||||
|
|
||||||
#docs trash
|
#docs trash
|
||||||
doc/.trash
|
doc/.trash
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Jeff Becker
|
Copyright (c) 2015-2018 Jeff Becker
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,26 +1,55 @@
|
|||||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
REPO_GOPATH=$(REPO)/go
|
REPO_GOPATH=$(REPO)/go
|
||||||
MINIFY=$(REPO_GOPATH)/bin/minify
|
MINIFY=$(REPO_GOPATH)/bin/minify
|
||||||
JS=$(REPO)/contrib/static/nntpchan.js
|
STATIC_DIR=$(REPO)/contrib/static
|
||||||
|
JS=$(STATIC_DIR)/nntpchan.js
|
||||||
|
MINER_JS=$(STATIC_DIR)/miner-js.js
|
||||||
CONTRIB_JS=$(REPO)/contrib/js/contrib
|
CONTRIB_JS=$(REPO)/contrib/js/contrib
|
||||||
LOCAL_JS=$(REPO)/contrib/js/nntpchan
|
LOCAL_JS=$(REPO)/contrib/js/nntpchan
|
||||||
VENDOR_JS=$(REPO)/contrib/js/vendor
|
VENDOR_JS=$(REPO)/contrib/js/vendor
|
||||||
SRND_DIR=$(REPO)/contrib/backends/srndv2
|
SRND_DIR=$(REPO)/contrib/backends/srndv2
|
||||||
|
NNTPCHAND_DIR=$(REPO)/contrib/backends/nntpchand
|
||||||
|
NNTPCHAN_DAEMON_DIR=$(REPO)/contrib/backends/nntpchan-daemon
|
||||||
SRND=$(REPO)/srndv2
|
SRND=$(REPO)/srndv2
|
||||||
|
NNTPCHAND=$(REPO)/nntpchand
|
||||||
|
NNTPD=$(REPO)/nntpd
|
||||||
|
|
||||||
|
GOROOT=$(shell go env GOROOT)
|
||||||
|
GO=$(GOROOT)/bin/go
|
||||||
|
|
||||||
|
GOPHERJS_GOROOT ?= $(GOROOT)
|
||||||
|
GOPHERJS_GO = $(GOPHERJS_GOROOT)/bin/go
|
||||||
|
|
||||||
|
GOPHERJS_GOPATH=$(REPO)/gopherjs_go
|
||||||
|
GOPHERJS=$(GOPHERJS_GOPATH)/bin/gopherjs
|
||||||
|
|
||||||
all: clean build
|
all: clean build
|
||||||
|
|
||||||
build: js srnd
|
build: js srnd
|
||||||
|
|
||||||
|
full: clean full-build
|
||||||
|
|
||||||
|
full-build: srnd beta native
|
||||||
|
|
||||||
js: $(JS)
|
js: $(JS)
|
||||||
|
|
||||||
srnd: $(SRND)
|
srnd: $(SRND)
|
||||||
|
|
||||||
$(MINIFY):
|
$(MINIFY):
|
||||||
GOPATH=$(REPO_GOPATH) go get -v github.com/tdewolff/minify/cmd/minify
|
GOPATH=$(REPO_GOPATH) $(GO) get -v github.com/tdewolff/minify/cmd/minify
|
||||||
|
|
||||||
|
$(GOPHERJS):
|
||||||
|
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS_GO) get -v github.com/gopherjs/gopherjs
|
||||||
|
|
||||||
js-deps: $(MINIFY)
|
js-deps: $(MINIFY)
|
||||||
|
|
||||||
|
$(MINER_JS): $(GOPHERJS) $(MINIFY)
|
||||||
|
rm -rf $(GOPHERJS_GOPATH)/pkg/
|
||||||
|
cp -rf $(SRND_DIR)/src/github.com $(GOPHERJS_GOPATH)/src/
|
||||||
|
GOROOT=$(GOPHERJS_GOROOT) GOPATH=$(GOPHERJS_GOPATH) $(GOPHERJS) -m -v build github.com/ZiRo-/cuckgo/miner_js -o miner.js
|
||||||
|
$(MINIFY) --mime=text/javascript > $(STATIC_DIR)/miner-js.js < miner.js
|
||||||
|
rm -f miner.js.map miner.js
|
||||||
|
|
||||||
$(JS): js-deps
|
$(JS): js-deps
|
||||||
rm -f $(JS)
|
rm -f $(JS)
|
||||||
for f in $(CONTRIB_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
for f in $(CONTRIB_JS)/*.js ; do $(MINIFY) --mime=text/javascript >> $(JS) < $$f ; done
|
||||||
@@ -30,12 +59,53 @@ $(JS): js-deps
|
|||||||
|
|
||||||
|
|
||||||
$(SRND):
|
$(SRND):
|
||||||
$(MAKE) -C $(SRND_DIR)
|
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR)
|
||||||
cp $(SRND_DIR)/srndv2 $(SRND)
|
cp $(SRND_DIR)/srndv2 $(SRND)
|
||||||
|
|
||||||
clean:
|
beta: $(NNTPCHAND)
|
||||||
rm -f $(SRND) $(JS)
|
|
||||||
$(MAKE) -C $(SRND_DIR) clean
|
$(NNTPCHAND):
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR)
|
||||||
|
cp $(NNTPCHAND_DIR)/nntpchand $(NNTPCHAND)
|
||||||
|
|
||||||
|
native: $(NNTPD)
|
||||||
|
|
||||||
|
$(NNTPD):
|
||||||
|
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR)
|
||||||
|
cp $(NNTPCHAN_DAEMON_DIR)/nntpd $(NNTPD)
|
||||||
|
|
||||||
|
test: test-srnd
|
||||||
|
|
||||||
|
test-full: test-srnd test-beta test-native
|
||||||
|
|
||||||
|
test-srnd:
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) test
|
||||||
|
|
||||||
|
test-beta:
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) test
|
||||||
|
|
||||||
|
test-native:
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAN_DAEMON_DIR) test
|
||||||
|
|
||||||
|
|
||||||
|
clean: clean-srnd clean-js
|
||||||
|
|
||||||
|
clean-full: clean clean-beta clean-native clean-js
|
||||||
|
|
||||||
|
clean-srnd:
|
||||||
|
rm -f $(SRND)
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(SRND_DIR) clean
|
||||||
|
|
||||||
|
clean-js:
|
||||||
|
rm -f $(JS) $(MINER_JS)
|
||||||
|
|
||||||
|
clean-beta:
|
||||||
|
rm -f $(NNTPCHAND)
|
||||||
|
GOROOT=$(GOROOT) $(MAKE) -C $(NNTPCHAND_DIR) clean
|
||||||
|
|
||||||
|
clean-native:
|
||||||
|
rm -f $(NNTPD)
|
||||||
|
$(MAKE) -C $(NNTPCHAN_DAEMON_DIR) clean
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -rf $(REPO_GOPATH)
|
rm -rf $(REPO_GOPATH)
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
[NNTPChan](https://nntpchan.info)
|
|
||||||
=================================
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
**NNTPChan** (previously known as overchan) is a decentralized imageboard that uses the [NNTP protocol](https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol) (network-news transfer protocol) to synchronize content between many different servers. It utilizes cryptographically signed posts to perform optional/opt-in decentralized moderation.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
|
[This](doc) is a step-by-step guide for getting up-and-running with NNTPChan as well as documentation for developers who want to either work on NNTPChan directly or use NNTPChan in their aplications with the API.
|
||||||
|
|
||||||
|
TL;DR edition:
|
||||||
|
|
||||||
|
|
||||||
|
$ sudo apt update
|
||||||
|
$ sudo apt install --no-install-recommends install imagemagick ffmpeg sox build-essential git ca-certificates postgresql postgresql-client
|
||||||
|
$ git clone https://github.com/majestrate/nntpchan
|
||||||
|
$ cd nntpchan
|
||||||
|
$ make
|
||||||
|
$ SRND_INSTALLER=0 ./srndv2 setup
|
||||||
|
|
||||||
## Bugs and issues
|
## Bugs and issues
|
||||||
|
|
||||||
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), the [issue tracker on tor](http://git.psii2pdloxelodts.onion/psi/nntpchan/), the [issue tracker on i2p](http://git.psi.i2p/psi/nntpchan/) or on the [GitGud issue tracker](https://gitgud.io/jeff/nntpchan/issues) so that the probelms can be resolved or discussed.
|
*PLEASE* report any bugs you find while building, setting-up or using NNTPChan on the [GitHub issue tracker](https://github.com/majestrate/nntpchan/issues), the [issue tracker on tor](http://git.psii2pdloxelodts.onion/psi/nntpchan/), the [issue tracker on i2p](http://git.psi.i2p/psi/nntpchan/) or on the [GitGud issue tracker](https://gitgud.io/jeff/nntpchan/issues) so that the probelms can be resolved or discussed.
|
||||||
@@ -41,11 +53,13 @@ This is a graph of the post flow of the `overchan.test` newsgroup over 4 years,
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
[source code for map generation](https://github.com/nilesr/nntpchan-mapper)
|
||||||
|
|
||||||
## Donations
|
## Donations
|
||||||
|
|
||||||
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
|
Like this project? Why not help by funding it? This address pays for the server that runs `2hu-ch.org`
|
||||||
|
|
||||||
Bitcoin: [15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE](bitcoin://15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE)
|
Bitcoin: 15yuMzuueV8y5vPQQ39ZqQVz5Ey98DNrjE
|
||||||
|
|
||||||
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
|
Monero: 46thSVXSPNhJkCgUsFD9WuCjW4K41DAHGL9khni2VEqmZZhfEZVvcukCp357rfhngZdviZMaeNdj5CLqhLyeK2qZRBCyL7Q
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
## TODO ##
|
## TODO ##
|
||||||
|
|
||||||
|
* imrpove frontend templates
|
||||||
* extra stylesheets
|
* extra stylesheets
|
||||||
* javascript free mod panel
|
|
||||||
* better mod panel
|
* better mod panel
|
||||||
* easier peering
|
* easier peering
|
||||||
* improve command line mod tools
|
* improve command line mod tools
|
||||||
|
* refactor srnd package
|
||||||
|
* configurable thumbnail size [issue #40](https://github.com/majestrate/nntpchan/issues/40)
|
||||||
|
* postgres password bug [issue #137](https://github.com/majestrate/nntpchan/issues/137)
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
TabWidth: 2
|
||||||
|
UseTab: Never
|
||||||
|
ColumnLimit: 120
|
||||||
|
IndentWidth: 2
|
||||||
|
Language: Cpp
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: true
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterStruct: true
|
||||||
|
BeforeElse: true
|
||||||
@@ -1,54 +1,75 @@
|
|||||||
|
|
||||||
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
REPO=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
|
||||||
SRC_PATH = $(REPO)/src
|
NNTPCHAN_PATH = $(REPO)/libnntpchan
|
||||||
|
|
||||||
SOURCES := $(wildcard $(SRC_PATH)/*.cpp)
|
NNTPCHAN_SRC := $(wildcard $(NNTPCHAN_PATH)/*.cpp)
|
||||||
HEADERS := $(wildcard $(SRC_PATH)/*.hpp)
|
NNTPCHAN_HDR := $(wildcard $(NNTPCHAN_PATH)/*.hpp)
|
||||||
OBJECTS := $(SOURCES:.cpp=.o)
|
NNTPCHAN_OBJ := $(NNTPCHAN_SRC:.cpp=.o)
|
||||||
|
|
||||||
TOOL_SRC_PATH := $(REPO)/tools
|
MUSTACHE_PATH = $(REPO)/libmustache
|
||||||
|
|
||||||
TOOL_SRC := $(wildcard $(TOOL_SRC_PATH)/*.cpp)
|
MUSTACHE_SRC := $(wildcard $(MUSTACHE_PATH)/*.cpp)
|
||||||
|
MUSTACHE_SRC += $(wildcard $(MUSTACHE_PATH)/*/*.cpp)
|
||||||
|
MUSTACHE_HDR := $(wildcard $(MUSTACHE_PATH)/*.hpp)
|
||||||
|
MUSTACHE_OBJ := $(MUSTACHE_SRC:.cpp=.o)
|
||||||
|
|
||||||
|
HEADERS_PATH=$(REPO)/include
|
||||||
|
|
||||||
|
TOOL_PATH := $(REPO)/tools
|
||||||
|
|
||||||
|
TOOL_SRC := $(wildcard $(TOOL_PATH)/*.cpp)
|
||||||
TOOLS := $(TOOL_SRC:.cpp=)
|
TOOLS := $(TOOL_SRC:.cpp=)
|
||||||
|
|
||||||
|
OBJ := $(NNTPCHAN_OBJ)
|
||||||
|
OBJ += $(MUSTACHE_OBJ)
|
||||||
|
|
||||||
|
TEST = $(REPO)/test
|
||||||
|
|
||||||
DAEMON_SRC = $(REPO)/daemon
|
DAEMON_SRC = $(REPO)/daemon
|
||||||
|
|
||||||
PKGS := libuv libsodium
|
PKGS := libuv libsodium
|
||||||
|
|
||||||
LD_FLAGS := $(shell pkg-config --libs $(PKGS))
|
LD_FLAGS := $(shell pkg-config --libs $(PKGS)) -lstdc++fs
|
||||||
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I $(REPO)/src
|
INC_FLAGS := $(shell pkg-config --cflags $(PKGS)) -I$(HEADERS_PATH)
|
||||||
CXXFLAGS := -std=c++11 -Wall -Wextra $(INC_FLAGS)
|
REQUIRED_CXXFLAGS = -std=c++17 -Wall -Wextra -Werror -pedantic $(INC_FLAGS)
|
||||||
|
|
||||||
|
DEBUG = 1
|
||||||
|
|
||||||
ifeq ($(DEBUG),1)
|
ifeq ($(DEBUG),1)
|
||||||
CXXFLAGS += -g
|
REQUIRED_CXXFLAGS += -g
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LIB = $(REPO)/libnntpchan.a
|
CXXFLAGS += $(REQUIRED_CXXFLAGS)
|
||||||
|
|
||||||
|
NNTPCHAN_LIB = $(REPO)/libnntpchan.a
|
||||||
|
MUSTACHE_LIB = $(REPO)/libmustache.a
|
||||||
|
|
||||||
|
LIBS = $(NNTPCHAN_LIB) $(MUSTACHE_LIB)
|
||||||
|
|
||||||
EXE = $(REPO)/nntpd
|
EXE = $(REPO)/nntpd
|
||||||
|
|
||||||
|
|
||||||
all: $(EXE) $(TOOLS)
|
all: build
|
||||||
|
|
||||||
$(LIB): $(OBJECTS)
|
build: $(EXE) $(TOOLS)
|
||||||
$(AR) -r $(LIB) $(OBJECTS)
|
|
||||||
|
|
||||||
$(EXE): $(LIB)
|
$(MUSTACHE_LIB): $(MUSTACHE_OBJ)
|
||||||
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIB) $(LD_FLAGS) -o $(EXE)
|
$(AR) -r $(MUSTACHE_LIB) $(MUSTACHE_OBJ)
|
||||||
|
|
||||||
$(TOOL_SRC): $(LIB)
|
$(NNTPCHAN_LIB): $(NNTPCHAN_OBJ)
|
||||||
|
$(AR) -r $(NNTPCHAN_LIB) $(NNTPCHAN_OBJ)
|
||||||
|
|
||||||
$(TOOLS): $(TOOL_SRC)
|
$(EXE): $(LIBS)
|
||||||
$(CXX) $(CXXFLAGS) $< $(LIB) $(LD_FLAGS) -o $@
|
$(CXX) $(CXXFLAGS) $(DAEMON_SRC)/main.cpp $(LIBS) $(LD_FLAGS) -o $(EXE)
|
||||||
|
|
||||||
build-test: $(LIB)
|
$(TOOLS): $(TOOL_SRC) $(LIBS)
|
||||||
$(CXX) -o test $(CXXFLAGS) test.cpp $(LIB) $(LD_FLAGS)
|
$(CXX) $(CXXFLAGS) $< $(LIBS) $(LD_FLAGS) -o $@
|
||||||
|
|
||||||
|
build-test: $(LIBS)
|
||||||
|
$(CXX) -o $(TEST) $(CXXFLAGS) test.cpp $(LIBS) $(LD_FLAGS)
|
||||||
|
|
||||||
test: build-test
|
test: build-test
|
||||||
./test
|
$(TEST)
|
||||||
|
|
||||||
%.o: src/%.cpp
|
|
||||||
$(CXX) $(CXXFLAGS) -c -o $@
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJECTS) $(LIB) $(EXE) $(TOOLS)
|
$(RM) $(OBJ) $(LIBS) $(EXE) $(TOOLS) $(TEST)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# nntpchan-daemon
|
||||||
|
|
||||||
|
C++ rewrite
|
||||||
|
|
||||||
|
requirements:
|
||||||
|
|
||||||
|
* g++ 7.2.0 >= or clang 5.x >=
|
||||||
|
|
||||||
|
* pkg-config
|
||||||
|
|
||||||
|
* libsodium 1.x
|
||||||
|
|
||||||
|
* libuv 1.x
|
||||||
|
|
||||||
|
* boost variant (for now)
|
||||||
|
|
||||||
|
* GNU Make
|
||||||
|
|
||||||
|
building:
|
||||||
|
|
||||||
|
$ make
|
||||||
+67
-56
@@ -26,20 +26,21 @@
|
|||||||
#define INI_HPP
|
#define INI_HPP
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <map>
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
namespace INI {
|
namespace INI
|
||||||
|
{
|
||||||
|
|
||||||
struct Level
|
struct Level
|
||||||
{
|
{
|
||||||
Level() : parent(NULL), depth(0) {}
|
Level() : parent(NULL), depth(0) {}
|
||||||
Level(Level* p) : parent(p), depth(0) {}
|
Level(Level *p) : parent(p), depth(0) {}
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> value_map_t;
|
typedef std::map<std::string, std::string> value_map_t;
|
||||||
typedef std::map<std::string, Level> section_map_t;
|
typedef std::map<std::string, Level> section_map_t;
|
||||||
@@ -49,57 +50,57 @@ struct Level
|
|||||||
section_map_t sections;
|
section_map_t sections;
|
||||||
values_t ordered_values; // original order in the ini file
|
values_t ordered_values; // original order in the ini file
|
||||||
sections_t ordered_sections;
|
sections_t ordered_sections;
|
||||||
Level* parent;
|
Level *parent;
|
||||||
size_t depth;
|
size_t depth;
|
||||||
|
|
||||||
const std::string& operator[](const std::string& name) { return values[name]; }
|
const std::string &operator[](const std::string &name) { return values[name]; }
|
||||||
Level& operator()(const std::string& name) { return sections[name]; }
|
Level &operator()(const std::string &name) { return sections[name]; }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Parser
|
class Parser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Parser(const char* fn);
|
Parser(const char *fn);
|
||||||
Parser(std::istream& f) : f_(&f), ln_(0) { parse(top_); }
|
Parser(std::istream &f) : f_(&f), ln_(0) { parse(top_); }
|
||||||
Level& top() { return top_; }
|
Level &top() { return top_; }
|
||||||
void dump(std::ostream& s) { dump(s, top(), ""); }
|
void dump(std::ostream &s) { dump(s, top(), ""); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void dump(std::ostream& s, const Level& l, const std::string& sname);
|
void dump(std::ostream &s, const Level &l, const std::string &sname);
|
||||||
void parse(Level& l);
|
void parse(Level &l);
|
||||||
void parseSLine(std::string& sname, size_t& depth);
|
void parseSLine(std::string &sname, size_t &depth);
|
||||||
void err(const char* s);
|
void err(const char *s);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Level top_;
|
Level top_;
|
||||||
std::ifstream f0_;
|
std::ifstream f0_;
|
||||||
std::istream* f_;
|
std::istream *f_;
|
||||||
std::string line_;
|
std::string line_;
|
||||||
size_t ln_;
|
size_t ln_;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void
|
inline void Parser::err(const char *s)
|
||||||
Parser::err(const char* s)
|
|
||||||
{
|
{
|
||||||
char buf[256];
|
char buf[256];
|
||||||
sprintf(buf, "%s on line #%ld", s, ln_);
|
sprintf(buf, "%s on line #%ld", s, ln_);
|
||||||
throw std::runtime_error(buf);
|
throw std::runtime_error(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string trim(const std::string& s)
|
inline std::string trim(const std::string &s)
|
||||||
{
|
{
|
||||||
char p[] = " \t\r\n";
|
char p[] = " \t\r\n";
|
||||||
long sp = 0;
|
long sp = 0;
|
||||||
long ep = s.length() - 1;
|
long ep = s.length() - 1;
|
||||||
for (; sp <= ep; ++sp)
|
for (; sp <= ep; ++sp)
|
||||||
if (!strchr(p, s[sp])) break;
|
if (!strchr(p, s[sp]))
|
||||||
|
break;
|
||||||
for (; ep >= 0; --ep)
|
for (; ep >= 0; --ep)
|
||||||
if (!strchr(p, s[ep])) break;
|
if (!strchr(p, s[ep]))
|
||||||
return s.substr(sp, ep-sp+1);
|
break;
|
||||||
|
return s.substr(sp, ep - sp + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline
|
inline Parser::Parser(const char *fn) : f0_(fn), f_(&f0_), ln_(0)
|
||||||
Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
|
|
||||||
{
|
{
|
||||||
if (!f0_)
|
if (!f0_)
|
||||||
throw std::runtime_error(std::string("failed to open file: ") + fn);
|
throw std::runtime_error(std::string("failed to open file: ") + fn);
|
||||||
@@ -107,56 +108,63 @@ Parser::Parser(const char* fn) : f0_(fn), f_(&f0_), ln_(0)
|
|||||||
parse(top_);
|
parse(top_);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void Parser::parseSLine(std::string &sname, size_t &depth)
|
||||||
Parser::parseSLine(std::string& sname, size_t& depth)
|
|
||||||
{
|
{
|
||||||
depth = 0;
|
depth = 0;
|
||||||
for (; depth < line_.length(); ++depth)
|
for (; depth < line_.length(); ++depth)
|
||||||
if (line_[depth] != '[') break;
|
if (line_[depth] != '[')
|
||||||
|
break;
|
||||||
|
|
||||||
sname = line_.substr(depth, line_.length() - 2*depth);
|
sname = line_.substr(depth, line_.length() - 2 * depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void Parser::parse(Level &l)
|
||||||
Parser::parse(Level& l)
|
|
||||||
{
|
{
|
||||||
while (std::getline(*f_, line_)) {
|
while (std::getline(*f_, line_))
|
||||||
|
{
|
||||||
++ln_;
|
++ln_;
|
||||||
if (line_[0] == '#' || line_[0] == ';') continue;
|
if (line_[0] == '#' || line_[0] == ';')
|
||||||
|
continue;
|
||||||
line_ = trim(line_);
|
line_ = trim(line_);
|
||||||
if (line_.empty()) continue;
|
if (line_.empty())
|
||||||
if (line_[0] == '[') {
|
continue;
|
||||||
|
if (line_[0] == '[')
|
||||||
|
{
|
||||||
size_t depth;
|
size_t depth;
|
||||||
std::string sname;
|
std::string sname;
|
||||||
parseSLine(sname, depth);
|
parseSLine(sname, depth);
|
||||||
Level* lp = NULL;
|
Level *lp = NULL;
|
||||||
Level* parent = &l;
|
Level *parent = &l;
|
||||||
if (depth > l.depth + 1)
|
if (depth > l.depth + 1)
|
||||||
err("section with wrong depth");
|
err("section with wrong depth");
|
||||||
if (l.depth == depth-1)
|
if (l.depth == depth - 1)
|
||||||
lp = &l.sections[sname];
|
lp = &l.sections[sname];
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
lp = l.parent;
|
lp = l.parent;
|
||||||
size_t n = l.depth - depth;
|
size_t n = l.depth - depth;
|
||||||
for (size_t i = 0; i < n; ++i) lp = lp->parent;
|
for (size_t i = 0; i < n; ++i)
|
||||||
|
lp = lp->parent;
|
||||||
parent = lp;
|
parent = lp;
|
||||||
lp = &lp->sections[sname];
|
lp = &lp->sections[sname];
|
||||||
}
|
}
|
||||||
if (lp->depth != 0)
|
if (lp->depth != 0)
|
||||||
err("duplicate section name on the same level");
|
err("duplicate section name on the same level");
|
||||||
if (!lp->parent) {
|
if (!lp->parent)
|
||||||
|
{
|
||||||
lp->depth = depth;
|
lp->depth = depth;
|
||||||
lp->parent = parent;
|
lp->parent = parent;
|
||||||
}
|
}
|
||||||
parent->ordered_sections.push_back(parent->sections.find(sname));
|
parent->ordered_sections.push_back(parent->sections.find(sname));
|
||||||
parse(*lp);
|
parse(*lp);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
size_t n = line_.find('=');
|
size_t n = line_.find('=');
|
||||||
if (n == std::string::npos)
|
if (n == std::string::npos)
|
||||||
err("no '=' found");
|
err("no '=' found");
|
||||||
std::pair<Level::value_map_t::const_iterator, bool> res =
|
std::pair<Level::value_map_t::const_iterator, bool> res =
|
||||||
l.values.insert(std::make_pair(trim(line_.substr(0, n)),
|
l.values.insert(std::make_pair(trim(line_.substr(0, n)), trim(line_.substr(n + 1, line_.length() - n - 1))));
|
||||||
trim(line_.substr(n+1, line_.length()-n-1))));
|
|
||||||
if (!res.second)
|
if (!res.second)
|
||||||
err("duplicated key found");
|
err("duplicated key found");
|
||||||
l.ordered_values.push_back(res.first);
|
l.ordered_values.push_back(res.first);
|
||||||
@@ -164,23 +172,26 @@ Parser::parse(Level& l)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void
|
inline void Parser::dump(std::ostream &s, const Level &l, const std::string &sname)
|
||||||
Parser::dump(std::ostream& s, const Level& l, const std::string& sname)
|
|
||||||
{
|
{
|
||||||
if (!sname.empty()) s << '\n';
|
if (!sname.empty())
|
||||||
for (size_t i = 0; i < l.depth; ++i) s << '[';
|
s << '\n';
|
||||||
if (!sname.empty()) s << sname;
|
for (size_t i = 0; i < l.depth; ++i)
|
||||||
for (size_t i = 0; i < l.depth; ++i) s << ']';
|
s << '[';
|
||||||
if (!sname.empty()) s << std::endl;
|
if (!sname.empty())
|
||||||
|
s << sname;
|
||||||
|
for (size_t i = 0; i < l.depth; ++i)
|
||||||
|
s << ']';
|
||||||
|
if (!sname.empty())
|
||||||
|
s << std::endl;
|
||||||
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
|
for (Level::values_t::const_iterator it = l.ordered_values.begin(); it != l.ordered_values.end(); ++it)
|
||||||
s << (*it)->first << '=' << (*it)->second << std::endl;
|
s << (*it)->first << '=' << (*it)->second << std::endl;
|
||||||
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it) {
|
for (Level::sections_t::const_iterator it = l.ordered_sections.begin(); it != l.ordered_sections.end(); ++it)
|
||||||
assert((*it)->second.depth == l.depth+1);
|
{
|
||||||
|
assert((*it)->second.depth == l.depth + 1);
|
||||||
dump(s, (*it)->second, (*it)->first);
|
dump(s, (*it)->second, (*it)->first);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // INI_HPP
|
#endif // INI_HPP
|
||||||
|
|
||||||
@@ -1,100 +1,135 @@
|
|||||||
#include "ini.hpp"
|
#include "ini.hpp"
|
||||||
|
|
||||||
#include "crypto.hpp"
|
#include <nntpchan/crypto.hpp>
|
||||||
#include "storage.hpp"
|
#include <nntpchan/event.hpp>
|
||||||
#include "nntp_server.hpp"
|
#include <nntpchan/exec_frontend.hpp>
|
||||||
#include "event.hpp"
|
#include <nntpchan/nntp_server.hpp>
|
||||||
#include "exec_frontend.hpp"
|
#include <nntpchan/staticfile_frontend.hpp>
|
||||||
|
#include <nntpchan/storage.hpp>
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
int main(int argc, char * argv[]) {
|
{
|
||||||
if (argc != 2) {
|
if (argc != 2)
|
||||||
|
{
|
||||||
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
std::cerr << "usage: " << argv[0] << " config.ini" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
nntpchan::Crypto crypto();
|
nntpchan::Crypto crypto;
|
||||||
|
|
||||||
nntpchan::Mainloop loop;
|
nntpchan::Mainloop loop;
|
||||||
|
|
||||||
nntpchan::NNTPServer nntp(loop);
|
nntpchan::NNTPServer nntp(loop);
|
||||||
|
|
||||||
|
|
||||||
std::string fname(argv[1]);
|
std::string fname(argv[1]);
|
||||||
|
|
||||||
std::ifstream i(fname);
|
std::ifstream i(fname);
|
||||||
|
|
||||||
if(i.is_open()) {
|
if (i.is_open())
|
||||||
|
{
|
||||||
INI::Parser conf(i);
|
INI::Parser conf(i);
|
||||||
|
|
||||||
std::vector<std::string> requiredSections = {"nntp", "articles"};
|
std::vector<std::string> requiredSections = {"nntp", "articles"};
|
||||||
|
|
||||||
auto & level = conf.top();
|
auto &level = conf.top();
|
||||||
|
|
||||||
for ( const auto & section : requiredSections ) {
|
for (const auto §ion : requiredSections)
|
||||||
if(level.sections.find(section) == level.sections.end()) {
|
{
|
||||||
|
if (level.sections.find(section) == level.sections.end())
|
||||||
|
{
|
||||||
std::cerr << "config file " << fname << " does not have required section: ";
|
std::cerr << "config file " << fname << " does not have required section: ";
|
||||||
std::cerr << section << std::endl;
|
std::cerr << section << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & storeconf = level.sections["articles"].values;
|
auto &storeconf = level.sections["articles"].values;
|
||||||
|
|
||||||
if (storeconf.find("store_path") == storeconf.end()) {
|
if (storeconf.find("store_path") == storeconf.end())
|
||||||
|
{
|
||||||
std::cerr << "storage section does not have 'store_path' value" << std::endl;
|
std::cerr << "storage section does not have 'store_path' value" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
nntp.SetStoragePath(storeconf["store_path"]);
|
nntp.SetStoragePath(storeconf["store_path"]);
|
||||||
|
|
||||||
auto & nntpconf = level.sections["nntp"].values;
|
auto &nntpconf = level.sections["nntp"].values;
|
||||||
|
|
||||||
if (nntpconf.find("bind") == nntpconf.end()) {
|
if (nntpconf.find("bind") == nntpconf.end())
|
||||||
|
{
|
||||||
std::cerr << "nntp section does not have 'bind' value" << std::endl;
|
std::cerr << "nntp section does not have 'bind' value" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nntpconf.find("instance_name") == nntpconf.end()) {
|
if (nntpconf.find("instance_name") == nntpconf.end())
|
||||||
|
{
|
||||||
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
|
std::cerr << "nntp section lacks 'instance_name' value" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
nntp.SetInstanceName(nntpconf["instance_name"]);
|
nntp.SetInstanceName(nntpconf["instance_name"]);
|
||||||
|
|
||||||
if (nntpconf.find("authdb") != nntpconf.end()) {
|
if (nntpconf.find("authdb") != nntpconf.end())
|
||||||
|
{
|
||||||
nntp.SetLoginDB(nntpconf["authdb"]);
|
nntp.SetLoginDB(nntpconf["authdb"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( level.sections.find("frontend") != level.sections.end()) {
|
if (level.sections.find("frontend") != level.sections.end())
|
||||||
|
{
|
||||||
// frontend enabled
|
// frontend enabled
|
||||||
auto & frontconf = level.sections["frontend"].values;
|
auto &frontconf = level.sections["frontend"].values;
|
||||||
if (frontconf.find("type") == frontconf.end()) {
|
if (frontconf.find("type") == frontconf.end())
|
||||||
|
{
|
||||||
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
|
std::cerr << "frontend section provided but 'type' value not provided" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
auto ftype = frontconf["type"];
|
auto ftype = frontconf["type"];
|
||||||
if (ftype == "exec") {
|
if (ftype == "exec")
|
||||||
if (frontconf.find("exec") == frontconf.end()) {
|
{
|
||||||
|
if (frontconf.find("exec") == frontconf.end())
|
||||||
|
{
|
||||||
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
|
std::cerr << "exec frontend specified but no 'exec' value provided" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
|
nntp.SetFrontend(new nntpchan::ExecFrontend(frontconf["exec"]));
|
||||||
} else {
|
}
|
||||||
|
else if (ftype == "staticfile")
|
||||||
|
{
|
||||||
|
auto required = {"template_dir", "out_dir", "template_dialect", "max_pages"};
|
||||||
|
for (const auto &opt : required)
|
||||||
|
{
|
||||||
|
if (frontconf.find(opt) == frontconf.end())
|
||||||
|
{
|
||||||
|
std::cerr << "staticfile frontend specified but no '" << opt << "' value provided" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto maxPages = std::stoi(frontconf["max_pages"]);
|
||||||
|
if (maxPages <= 0)
|
||||||
|
{
|
||||||
|
std::cerr << "max_pages invalid value '" << frontconf["max_pages"] << "'" << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
nntp.SetFrontend(new nntpchan::StaticFileFrontend(nntpchan::CreateTemplateEngine(frontconf["template_dialect"]),
|
||||||
|
frontconf["template_dir"], frontconf["out_dir"], maxPages));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
|
std::cerr << "unknown frontend type '" << ftype << "'" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & a = nntpconf["bind"];
|
auto &a = nntpconf["bind"];
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
nntp.Bind(a);
|
nntp.Bind(a);
|
||||||
} catch ( std::exception & ex ) {
|
} catch (std::exception &ex)
|
||||||
|
{
|
||||||
std::cerr << "failed to bind: " << ex.what() << std::endl;
|
std::cerr << "failed to bind: " << ex.what() << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -102,11 +137,10 @@ int main(int argc, char * argv[]) {
|
|||||||
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
|
std::cerr << "nntpd for " << nntp.InstanceName() << " bound to " << a << std::endl;
|
||||||
|
|
||||||
loop.Run();
|
loop.Run();
|
||||||
|
}
|
||||||
} else {
|
else
|
||||||
|
{
|
||||||
std::cerr << "failed to open " << fname << std::endl;
|
std::cerr << "failed to open " << fname << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
|
||||||
|
namespace mstch {
|
||||||
|
|
||||||
|
struct config {
|
||||||
|
static std::function<std::string(const std::string&)> escape;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
template<class N>
|
||||||
|
class object_t {
|
||||||
|
public:
|
||||||
|
const N& at(const std::string& name) const {
|
||||||
|
cache[name] = (methods.at(name))();
|
||||||
|
return cache[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has(const std::string name) const {
|
||||||
|
return methods.count(name) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<class S>
|
||||||
|
void register_methods(S* s, std::map<std::string,N(S::*)()> methods) {
|
||||||
|
for(auto& item: methods)
|
||||||
|
this->methods.insert({item.first, std::bind(item.second, s)});
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<std::string, std::function<N()>> methods;
|
||||||
|
mutable std::map<std::string, N> cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T, class N>
|
||||||
|
class is_fun {
|
||||||
|
private:
|
||||||
|
using not_fun = char;
|
||||||
|
using fun_without_args = char[2];
|
||||||
|
using fun_with_args = char[3];
|
||||||
|
template <typename U, U> struct really_has;
|
||||||
|
template <typename C> static fun_without_args& test(
|
||||||
|
really_has<N(C::*)() const, &C::operator()>*);
|
||||||
|
template <typename C> static fun_with_args& test(
|
||||||
|
really_has<N(C::*)(const std::string&) const,
|
||||||
|
&C::operator()>*);
|
||||||
|
template <typename> static not_fun& test(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool const no_args = sizeof(test<T>(0)) == sizeof(fun_without_args);
|
||||||
|
static bool const has_args = sizeof(test<T>(0)) == sizeof(fun_with_args);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class N>
|
||||||
|
using node_renderer = std::function<std::string(const N& n)>;
|
||||||
|
|
||||||
|
template<class N>
|
||||||
|
class lambda_t {
|
||||||
|
public:
|
||||||
|
template<class F>
|
||||||
|
lambda_t(F f, typename std::enable_if<is_fun<F, N>::no_args>::type* = 0):
|
||||||
|
fun([f](node_renderer<N> renderer, const std::string&) {
|
||||||
|
return renderer(f());
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
lambda_t(F f, typename std::enable_if<is_fun<F, N>::has_args>::type* = 0):
|
||||||
|
fun([f](node_renderer<N> renderer, const std::string& text) {
|
||||||
|
return renderer(f(text));
|
||||||
|
})
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(node_renderer<N> renderer,
|
||||||
|
const std::string& text = "") const
|
||||||
|
{
|
||||||
|
return fun(renderer, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<std::string(node_renderer<N> renderer, const std::string&)> fun;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using node = boost::make_recursive_variant<
|
||||||
|
std::nullptr_t, std::string, int, double, bool,
|
||||||
|
internal::lambda_t<boost::recursive_variant_>,
|
||||||
|
std::shared_ptr<internal::object_t<boost::recursive_variant_>>,
|
||||||
|
std::map<const std::string, boost::recursive_variant_>,
|
||||||
|
std::vector<boost::recursive_variant_>>::type;
|
||||||
|
using object = internal::object_t<node>;
|
||||||
|
using lambda = internal::lambda_t<node>;
|
||||||
|
using map = std::map<const std::string, node>;
|
||||||
|
using array = std::vector<node>;
|
||||||
|
|
||||||
|
std::string render(
|
||||||
|
const std::string& tmplt,
|
||||||
|
const node& root,
|
||||||
|
const std::map<std::string,std::string>& partials =
|
||||||
|
std::map<std::string,std::string>());
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef NNTPCHAN_BASE64_HPP
|
||||||
|
#define NNTPCHAN_BASE64_HPP
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
/** returns base64 encoded string */
|
||||||
|
std::string B64Encode(const uint8_t *data, const std::size_t l);
|
||||||
|
|
||||||
|
/** @brief returns true if decode was successful */
|
||||||
|
bool B64Decode(const std::string &data, std::vector<uint8_t> &out);
|
||||||
|
|
||||||
|
/** returns base32 encoded string */
|
||||||
|
std::string B32Encode(const uint8_t *data, const std::size_t l);
|
||||||
|
|
||||||
|
/** @brief returns true if decode was successful */
|
||||||
|
bool B32Decode(const std::string &data, std::vector<uint8_t> &out);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef NNTPCHAN_BUFFER_HPP
|
||||||
|
#define NNTPCHAN_BUFFER_HPP
|
||||||
|
#include <string>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
struct WriteBuffer
|
||||||
|
{
|
||||||
|
uv_write_t w;
|
||||||
|
uv_buf_t b;
|
||||||
|
|
||||||
|
WriteBuffer(const std::string &s);
|
||||||
|
WriteBuffer(const char *b, const size_t s);
|
||||||
|
~WriteBuffer();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef NNTPCHAN_CRYPTO_HPP
|
||||||
|
#define NNTPCHAN_CRYPTO_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <sodium/crypto_hash.h>
|
||||||
|
#include <sodium/crypto_generichash.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
|
||||||
|
|
||||||
|
void SHA512(const uint8_t *d, std::size_t l, SHA512Digest &h);
|
||||||
|
|
||||||
|
typedef std::array<uint8_t, crypto_generichash_BYTES> Blake2BDigest;
|
||||||
|
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h);
|
||||||
|
|
||||||
|
std::string Blake2B_base32(const std::string & str);
|
||||||
|
|
||||||
|
|
||||||
|
/** global crypto initializer */
|
||||||
|
struct Crypto
|
||||||
|
{
|
||||||
|
Crypto();
|
||||||
|
~Crypto();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef NNTPCHAN_EVENT_HPP
|
||||||
|
#define NNTPCHAN_EVENT_HPP
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
class Mainloop
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Mainloop();
|
||||||
|
~Mainloop();
|
||||||
|
|
||||||
|
operator uv_loop_t *() const { return m_loop; }
|
||||||
|
|
||||||
|
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uv_loop_t *m_loop;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
|
||||||
|
#define NNTPCHAN_EXEC_FRONTEND_HPP
|
||||||
|
#include "frontend.hpp"
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
class ExecFrontend : public Frontend
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExecFrontend(const std::string &exe);
|
||||||
|
|
||||||
|
~ExecFrontend();
|
||||||
|
|
||||||
|
void ProcessNewMessage(const fs::path &fpath);
|
||||||
|
bool AcceptsNewsgroup(const std::string &newsgroup);
|
||||||
|
bool AcceptsMessage(const std::string &msgid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int Exec(std::deque<std::string> args);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_exec;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef NNTPCHAN_FILE_HANDLE_HPP
|
||||||
|
#define NNTPCHAN_FILE_HANDLE_HPP
|
||||||
|
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
typedef std::unique_ptr<std::fstream> FileHandle_ptr;
|
||||||
|
|
||||||
|
enum FileMode
|
||||||
|
{
|
||||||
|
eRead,
|
||||||
|
eWrite
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
|
||||||
|
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef NNTPCHAN_FRONTEND_HPP
|
||||||
|
#define NNTPCHAN_FRONTEND_HPP
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
/** @brief nntpchan frontend ui interface */
|
||||||
|
class Frontend
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** @brief process an inbound message stored at fpath that we have accepted. */
|
||||||
|
virtual void ProcessNewMessage(const fs::path &fpath) = 0;
|
||||||
|
|
||||||
|
/** @brief return true if we take posts in a newsgroup */
|
||||||
|
virtual bool AcceptsNewsgroup(const std::string &newsgroup) = 0;
|
||||||
|
|
||||||
|
/** @brief return true if we will accept a message given its message-id */
|
||||||
|
virtual bool AcceptsMessage(const std::string &msgid) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unique_ptr<Frontend> Frontend_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
-2
@@ -1,6 +1,4 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_HPP
|
#ifndef NNTPCHAN_HTTP_HPP
|
||||||
#define NNTPCHAN_HTTP_HPP
|
#define NNTPCHAN_HTTP_HPP
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
-1
@@ -1,5 +1,4 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
|
#ifndef NNTPCHAN_HTTP_CLIENT_HPP
|
||||||
#define NNTPCHAN_HTTP_CLIENT_HPP
|
#define NNTPCHAN_HTTP_CLIENT_HPP
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
-2
@@ -1,6 +1,4 @@
|
|||||||
#ifndef NNTPCHAN_HTTP_SERVER_HPP
|
#ifndef NNTPCHAN_HTTP_SERVER_HPP
|
||||||
#define NNTPCHAN_HTTP_SERVER_HPP
|
#define NNTPCHAN_HTTP_SERVER_HPP
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef NNTPCHAN_IO_HANDLE_HPP
|
||||||
|
#define NNTPCHAN_IO_HANDLE_HPP
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
typedef std::unique_ptr<std::iostream> IOHandle_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#ifndef NNTPCHAN_LINE_HPP
|
||||||
|
#define NNTPCHAN_LINE_HPP
|
||||||
|
#include "server.hpp"
|
||||||
|
#include <stdint.h>
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @brief a buffered line reader */
|
||||||
|
class LineReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LineReader(size_t lineLimit);
|
||||||
|
|
||||||
|
/** @brief queue inbound data from connection */
|
||||||
|
void Data(const char *data, ssize_t s);
|
||||||
|
|
||||||
|
/** implements IConnHandler */
|
||||||
|
virtual bool ShouldClose();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** @brief handle a line from the client */
|
||||||
|
virtual void HandleLine(const std::string &line) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnLine(const char *d, const size_t l);
|
||||||
|
std::string m_leftovers;
|
||||||
|
bool m_close;
|
||||||
|
const size_t lineLimit;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef NNTPCHAN_MESSAGE_HPP
|
||||||
|
#define NNTPCHAN_MESSAGE_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <nntpchan/model.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
struct MessageDB
|
||||||
|
{
|
||||||
|
using BoardPage = nntpchan::model::BoardPage;
|
||||||
|
using Thread = nntpchan::model::Thread;
|
||||||
|
virtual bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const = 0;
|
||||||
|
virtual bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const = 0;
|
||||||
|
virtual bool LoadThread(Thread &thread, const std::string &rootmsgid) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unique_ptr<MessageDB> MessageDB_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef NNTPCHAN_MIME_HPP
|
||||||
|
#define NNTPCHAN_MIME_HPP
|
||||||
|
#include "file_handle.hpp"
|
||||||
|
#include "io_handle.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
typedef std::map<std::string, std::string> RawHeader;
|
||||||
|
|
||||||
|
bool ReadHeader(const FileHandle_ptr &f, RawHeader &h);
|
||||||
|
|
||||||
|
struct MimePart
|
||||||
|
{
|
||||||
|
virtual RawHeader &Header() = 0;
|
||||||
|
virtual IOHandle_ptr OpenPart() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unique_ptr<MimePart> MimePart_ptr;
|
||||||
|
|
||||||
|
typedef std::function<bool(MimePart_ptr)> PartReader;
|
||||||
|
|
||||||
|
bool ReadParts(const FileHandle_ptr &f, PartReader r);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef NNTPCHAN_MODEL_HPP
|
||||||
|
#define NNTPCHAN_MODEL_HPP
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
namespace model
|
||||||
|
{
|
||||||
|
// MIME Header
|
||||||
|
typedef std::map<std::string, std::vector<std::string>> PostHeader;
|
||||||
|
// text post contents
|
||||||
|
typedef std::string PostBody;
|
||||||
|
// single file attachment, (orig_filename, hexdigest, thumb_filename)
|
||||||
|
typedef std::tuple<std::string, std::string, std::string> PostAttachment;
|
||||||
|
// all attachments on a post
|
||||||
|
typedef std::vector<PostAttachment> Attachments;
|
||||||
|
// a post (header, Post Text, Attachments)
|
||||||
|
typedef std::tuple<PostHeader, PostBody, Attachments> Post;
|
||||||
|
// a thread (many posts in post order)
|
||||||
|
typedef std::vector<Post> Thread;
|
||||||
|
// a board page is many threads in bump order
|
||||||
|
typedef std::vector<Thread> BoardPage;
|
||||||
|
|
||||||
|
static inline const std::string &GetFilename(const PostAttachment &att) { return std::get<0>(att); }
|
||||||
|
|
||||||
|
static inline const std::string &GetHexDigest(const PostAttachment &att) { return std::get<1>(att); }
|
||||||
|
|
||||||
|
static inline const std::string &GetThumbnail(const PostAttachment &att) { return std::get<2>(att); }
|
||||||
|
|
||||||
|
static inline const PostHeader &GetHeader(const Post &post) { return std::get<0>(post); }
|
||||||
|
|
||||||
|
static inline const PostBody &GetBody(const Post &post) { return std::get<1>(post); }
|
||||||
|
|
||||||
|
static inline const Attachments &GetAttachments(const Post &post) { return std::get<2>(post); }
|
||||||
|
|
||||||
|
static inline const std::string &HeaderIFind(const PostHeader &header, const std::string &val,
|
||||||
|
const std::string &fallback)
|
||||||
|
{
|
||||||
|
std::string ival = ToLower(val);
|
||||||
|
auto itr = std::find_if(header.begin(), header.end(),
|
||||||
|
[ival](const auto &item) -> bool { return ToLower(item.first) == ival; });
|
||||||
|
if (itr == std::end(header))
|
||||||
|
return fallback;
|
||||||
|
else
|
||||||
|
return itr->second[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
using Model = std::variant<Thread, BoardPage>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#ifndef NNTPCHAN_NET_HPP
|
||||||
|
#define NNTPCHAN_NET_HPP
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
struct NetAddr
|
||||||
|
{
|
||||||
|
NetAddr();
|
||||||
|
|
||||||
|
sockaddr_in6 addr;
|
||||||
|
operator sockaddr *() { return (sockaddr *)&addr; }
|
||||||
|
operator const sockaddr *() const { return (sockaddr *)&addr; }
|
||||||
|
std::string to_string();
|
||||||
|
};
|
||||||
|
|
||||||
|
NetAddr ParseAddr(const std::string &addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef NNTPCHAN_NNTP_AUTH_HPP
|
||||||
|
#define NNTPCHAN_NNTP_AUTH_HPP
|
||||||
|
#include "line.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
/** @brief nntp credential db interface */
|
||||||
|
class NNTPCredentialDB
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** @brief open connection to database, return false on error otherwise return true */
|
||||||
|
virtual bool Open() = 0;
|
||||||
|
/** @brief close connection to database */
|
||||||
|
virtual void Close() = 0;
|
||||||
|
/** @brief return true if username password combo is correct */
|
||||||
|
virtual bool CheckLogin(const std::string &user, const std::string &passwd) = 0;
|
||||||
|
virtual ~NNTPCredentialDB() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::shared_ptr<NNTPCredentialDB> CredDB_ptr;
|
||||||
|
|
||||||
|
/** @brief nntp credential db using hashed+salted passwords */
|
||||||
|
class HashedCredDB : public NNTPCredentialDB, public LineReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HashedCredDB();
|
||||||
|
bool CheckLogin(const std::string &user, const std::string &passwd);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SetStream(std::istream *i);
|
||||||
|
|
||||||
|
std::string Hash(const std::string &data, const std::string &salt);
|
||||||
|
void HandleLine(const std::string &line);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ProcessLine(const std::string &line);
|
||||||
|
|
||||||
|
std::mutex m_access;
|
||||||
|
std::string m_user, m_passwd;
|
||||||
|
bool m_found;
|
||||||
|
/** return true if we have a line that matches this username / password combo */
|
||||||
|
std::istream *m_instream;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HashedFileDB : public HashedCredDB
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HashedFileDB(const std::string &fname);
|
||||||
|
~HashedFileDB();
|
||||||
|
bool Open();
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_fname;
|
||||||
|
std::ifstream f;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
|
||||||
|
#define NNTPCHAN_NNTP_HANDLER_HPP
|
||||||
|
#include "line.hpp"
|
||||||
|
#include "nntp_auth.hpp"
|
||||||
|
#include "storage.hpp"
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
class NNTPServerHandler : public LineReader, public IConnHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NNTPServerHandler(const fs::path &storage);
|
||||||
|
~NNTPServerHandler();
|
||||||
|
|
||||||
|
virtual bool ShouldClose();
|
||||||
|
|
||||||
|
void SetAuth(CredDB_ptr creds);
|
||||||
|
|
||||||
|
virtual void OnData(const char *, ssize_t);
|
||||||
|
|
||||||
|
void Greet();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void HandleLine(const std::string &line);
|
||||||
|
void HandleCommand(const std::deque<std::string> &command);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
eStateReadCommand,
|
||||||
|
eStateStoreArticle,
|
||||||
|
eStateQuit
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void EnterState(State st);
|
||||||
|
|
||||||
|
void ArticleObtained();
|
||||||
|
|
||||||
|
// handle quit command, this queues a reply
|
||||||
|
void Quit();
|
||||||
|
|
||||||
|
// switch nntp modes, this queues a reply
|
||||||
|
void SwitchMode(const std::string &mode);
|
||||||
|
|
||||||
|
bool PostingAllowed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_articleName;
|
||||||
|
FileHandle_ptr m_article;
|
||||||
|
CredDB_ptr m_auth;
|
||||||
|
ArticleStorage_ptr m_store;
|
||||||
|
std::string m_mode;
|
||||||
|
bool m_authed;
|
||||||
|
State m_state;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
||||||
|
#define NNTPCHAN_NNTP_SERVER_HPP
|
||||||
|
#include "frontend.hpp"
|
||||||
|
#include "server.hpp"
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
class NNTPServer : public Server
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NNTPServer(uv_loop_t *loop);
|
||||||
|
|
||||||
|
virtual ~NNTPServer();
|
||||||
|
|
||||||
|
void SetStoragePath(const std::string &path);
|
||||||
|
|
||||||
|
void SetLoginDB(const std::string path);
|
||||||
|
|
||||||
|
void SetInstanceName(const std::string &name);
|
||||||
|
|
||||||
|
std::string InstanceName() const;
|
||||||
|
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
virtual IServerConn *CreateConn(uv_stream_t *s);
|
||||||
|
|
||||||
|
virtual void OnAcceptError(int status);
|
||||||
|
|
||||||
|
void SetFrontend(Frontend *f);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_logindbpath;
|
||||||
|
std::string m_storagePath;
|
||||||
|
std::string m_servername;
|
||||||
|
|
||||||
|
Frontend_ptr m_frontend;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NNTPServerConn : public IServerConn
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NNTPServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h) : IServerConn(l, s, parent, h) {}
|
||||||
|
|
||||||
|
virtual bool IsTimedOut() { return false; };
|
||||||
|
|
||||||
|
/** @brief send next queued reply */
|
||||||
|
virtual void SendNextReply();
|
||||||
|
|
||||||
|
virtual void Greet();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#ifndef NNTPCHAN_SANITIZE_HPP
|
||||||
|
#define NNTPCHAN_SANITIZE_HPP
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
std::string NNTPSanitizeLine(const std::string &str);
|
||||||
|
std::string ToLower(const std::string &str);
|
||||||
|
std::string StripWhitespaces(const std::string &str);
|
||||||
|
bool IsValidMessageID(const std::string &msgid);
|
||||||
|
bool IsValidNewsgroup(const std::string &group);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#ifndef NNTPCHAN_SERVER_HPP
|
||||||
|
#define NNTPCHAN_SERVER_HPP
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
struct IConnHandler
|
||||||
|
{
|
||||||
|
|
||||||
|
virtual ~IConnHandler(){};
|
||||||
|
|
||||||
|
/** got inbound data */
|
||||||
|
virtual void OnData(const char *data, ssize_t s) = 0;
|
||||||
|
|
||||||
|
/** get next line of data to send */
|
||||||
|
std::string GetNextLine();
|
||||||
|
|
||||||
|
/** return true if we have a line to send */
|
||||||
|
bool HasNextLine();
|
||||||
|
|
||||||
|
/** return true if we should close this connection otherwise return false */
|
||||||
|
virtual bool ShouldClose() = 0;
|
||||||
|
|
||||||
|
/** queue a data send */
|
||||||
|
void QueueLine(const std::string &line);
|
||||||
|
|
||||||
|
virtual void Greet() = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::deque<std::string> m_sendlines;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** server connection handler interface */
|
||||||
|
struct IServerConn
|
||||||
|
{
|
||||||
|
IServerConn(uv_loop_t *l, uv_stream_t *s, Server *parent, IConnHandler *h);
|
||||||
|
virtual ~IServerConn();
|
||||||
|
virtual void Close();
|
||||||
|
virtual void Greet() = 0;
|
||||||
|
virtual void SendNextReply() = 0;
|
||||||
|
virtual bool IsTimedOut() = 0;
|
||||||
|
void SendString(const std::string &str);
|
||||||
|
Server *Parent() { return m_parent; };
|
||||||
|
IConnHandler *GetHandler() { return m_handler; };
|
||||||
|
uv_loop_t *GetLoop() { return m_loop; };
|
||||||
|
|
||||||
|
private:
|
||||||
|
uv_tcp_t m_conn;
|
||||||
|
uv_loop_t *m_loop;
|
||||||
|
Server *m_parent;
|
||||||
|
IConnHandler *m_handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Server
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Server(uv_loop_t *loop);
|
||||||
|
/** called after socket close, NEVER call directly */
|
||||||
|
virtual ~Server() {}
|
||||||
|
/** create connection handler from open stream */
|
||||||
|
virtual IServerConn *CreateConn(uv_stream_t *s) = 0;
|
||||||
|
/** close all sockets and stop */
|
||||||
|
void Close();
|
||||||
|
/** bind to address */
|
||||||
|
void Bind(const std::string &addr);
|
||||||
|
|
||||||
|
typedef std::function<void(IServerConn *)> ConnVisitor;
|
||||||
|
|
||||||
|
/** visit all open connections */
|
||||||
|
void VisitConns(ConnVisitor v);
|
||||||
|
|
||||||
|
/** remove connection from server, called after proper close */
|
||||||
|
void RemoveConn(IServerConn *conn);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uv_loop_t *GetLoop() { return m_loop; }
|
||||||
|
virtual void OnAcceptError(int status) = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
operator uv_handle_t *() { return (uv_handle_t *)&m_server; }
|
||||||
|
operator uv_tcp_t *() { return &m_server; }
|
||||||
|
operator uv_stream_t *() { return (uv_stream_t *)&m_server; }
|
||||||
|
|
||||||
|
void OnAccept(uv_stream_t *s, int status);
|
||||||
|
std::deque<IServerConn *> m_conns;
|
||||||
|
uv_tcp_t m_server;
|
||||||
|
uv_loop_t *m_loop;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef NNTPCHAN_SHA1_HPP
|
||||||
|
#define NNTPCHAN_SHA1_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
std::string sha1_hex(const std::string &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef NNTPCHAN_STATICFILE_FRONTEND_HPP
|
||||||
|
#define NNTPCHAN_STATICFILE_FRONTEND_HPP
|
||||||
|
#include "frontend.hpp"
|
||||||
|
#include "message.hpp"
|
||||||
|
#include "model.hpp"
|
||||||
|
#include "template_engine.hpp"
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
|
||||||
|
class StaticFileFrontend : public Frontend
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir, uint32_t pages);
|
||||||
|
|
||||||
|
~StaticFileFrontend();
|
||||||
|
|
||||||
|
void ProcessNewMessage(const fs::path &fpath);
|
||||||
|
bool AcceptsNewsgroup(const std::string &newsgroup);
|
||||||
|
bool AcceptsMessage(const std::string &msgid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MessageDB_ptr m_MessageDB;
|
||||||
|
TemplateEngine_ptr m_TemplateEngine;
|
||||||
|
fs::path m_TemplateDir;
|
||||||
|
fs::path m_OutDir;
|
||||||
|
uint32_t m_Pages;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef NNTPCHAN_STORAGE_HPP
|
||||||
|
#define NNTPCHAN_STORAGE_HPP
|
||||||
|
|
||||||
|
#include "file_handle.hpp"
|
||||||
|
#include "message.hpp"
|
||||||
|
#include <experimental/filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace fs = std::experimental::filesystem;
|
||||||
|
|
||||||
|
class ArticleStorage : public MessageDB
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArticleStorage(const fs::path &fpath);
|
||||||
|
~ArticleStorage();
|
||||||
|
|
||||||
|
FileHandle_ptr OpenWrite(const std::string &msgid) const;
|
||||||
|
FileHandle_ptr OpenRead(const std::string &msgid) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
return true if we should accept a new message give its message id
|
||||||
|
*/
|
||||||
|
bool Accept(const std::string &msgid) const;
|
||||||
|
|
||||||
|
bool LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage, uint32_t page) const;
|
||||||
|
bool FindThreadByHash(const std::string &hashhex, std::string &msgid) const;
|
||||||
|
bool LoadThread(Thread &thread, const std::string &rootmsgid) const;
|
||||||
|
|
||||||
|
/** ensure symlinks are formed for this article by message id */
|
||||||
|
void EnsureSymlinks(const std::string &msgid) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetPath(const fs::path &fpath);
|
||||||
|
|
||||||
|
fs::path MessagePath(const std::string &msgid) const;
|
||||||
|
|
||||||
|
bool init_skiplist(const std::string &subdir) const;
|
||||||
|
|
||||||
|
fs::path skiplist_root(const std::string &name) const;
|
||||||
|
fs::path skiplist_dir(const fs::path & root, const std::string & name) const;
|
||||||
|
|
||||||
|
fs::path basedir;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::unique_ptr<ArticleStorage> ArticleStorage_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef NNTPCHAN_TEMPLATE_ENGINE_HPP
|
||||||
|
#define NNTPCHAN_TEMPLATE_ENGINE_HPP
|
||||||
|
#include "file_handle.hpp"
|
||||||
|
#include "model.hpp"
|
||||||
|
#include <any>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TemplateEngine
|
||||||
|
{
|
||||||
|
typedef std::map<std::string, std::variant<nntpchan::model::Model, std::string>> Args_t;
|
||||||
|
virtual bool WriteTemplate(const fs::path &template_fpath, const Args_t &args, const FileHandle_ptr &out) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateEngine *CreateTemplateEngine(const std::string &dialect);
|
||||||
|
|
||||||
|
typedef std::unique_ptr<TemplateEngine> TemplateEngine_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
#include "render_context.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
std::function<std::string(const std::string&)> mstch::config::escape;
|
||||||
|
|
||||||
|
std::string mstch::render(
|
||||||
|
const std::string& tmplt,
|
||||||
|
const node& root,
|
||||||
|
const std::map<std::string,std::string>& partials)
|
||||||
|
{
|
||||||
|
std::map<std::string, template_type> partial_templates;
|
||||||
|
for (auto& partial: partials)
|
||||||
|
partial_templates.insert({partial.first, {partial.second}});
|
||||||
|
|
||||||
|
return render_context(root, partial_templates).render(tmplt);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include "render_context.hpp"
|
||||||
|
#include "state/outside_section.hpp"
|
||||||
|
#include "visitor/get_token.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
const mstch::node render_context::null_node;
|
||||||
|
|
||||||
|
render_context::push::push(render_context &context, const mstch::node &node) : m_context(context)
|
||||||
|
{
|
||||||
|
context.m_nodes.emplace_front(node);
|
||||||
|
context.m_node_ptrs.emplace_front(&node);
|
||||||
|
context.m_state.push(std::unique_ptr<render_state>(new outside_section));
|
||||||
|
}
|
||||||
|
|
||||||
|
render_context::push::~push()
|
||||||
|
{
|
||||||
|
m_context.m_nodes.pop_front();
|
||||||
|
m_context.m_node_ptrs.pop_front();
|
||||||
|
m_context.m_state.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string render_context::push::render(const template_type &templt) { return m_context.render(templt); }
|
||||||
|
|
||||||
|
render_context::render_context(const mstch::node &node, const std::map<std::string, template_type> &partials)
|
||||||
|
: m_partials(partials), m_nodes(1, node), m_node_ptrs(1, &node)
|
||||||
|
{
|
||||||
|
m_state.push(std::unique_ptr<render_state>(new outside_section));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mstch::node &render_context::find_node(const std::string &token, std::list<node const *> current_nodes)
|
||||||
|
{
|
||||||
|
if (token != "." && token.find('.') != std::string::npos)
|
||||||
|
return find_node(token.substr(token.rfind('.') + 1),
|
||||||
|
{&find_node(token.substr(0, token.rfind('.')), current_nodes)});
|
||||||
|
else
|
||||||
|
for (auto &node : current_nodes)
|
||||||
|
if (visit(has_token(token), *node))
|
||||||
|
return visit(get_token(token, *node), *node);
|
||||||
|
return null_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mstch::node &render_context::get_node(const std::string &token) { return find_node(token, m_node_ptrs); }
|
||||||
|
|
||||||
|
std::string render_context::render(const template_type &templt, const std::string &prefix)
|
||||||
|
{
|
||||||
|
std::string output;
|
||||||
|
bool prev_eol = true;
|
||||||
|
for (auto &token : templt)
|
||||||
|
{
|
||||||
|
if (prev_eol && prefix.length() != 0)
|
||||||
|
output += m_state.top()->render(*this, {prefix});
|
||||||
|
output += m_state.top()->render(*this, token);
|
||||||
|
prev_eol = token.eol();
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string render_context::render_partial(const std::string &partial_name, const std::string &prefix)
|
||||||
|
{
|
||||||
|
return m_partials.count(partial_name) ? render(m_partials.at(partial_name), prefix) : "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <list>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "state/render_state.hpp"
|
||||||
|
#include "template_type.hpp"
|
||||||
|
#include <mstch/mstch.hpp>
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class render_context
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class push
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
push(render_context &context, const mstch::node &node = {});
|
||||||
|
~push();
|
||||||
|
std::string render(const template_type &templt);
|
||||||
|
|
||||||
|
private:
|
||||||
|
render_context &m_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
render_context(const mstch::node &node, const std::map<std::string, template_type> &partials);
|
||||||
|
const mstch::node &get_node(const std::string &token);
|
||||||
|
std::string render(const template_type &templt, const std::string &prefix = "");
|
||||||
|
std::string render_partial(const std::string &partial_name, const std::string &prefix);
|
||||||
|
template <class T, class... Args> void set_state(Args &&... args)
|
||||||
|
{
|
||||||
|
m_state.top() = std::unique_ptr<render_state>(new T(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const mstch::node null_node;
|
||||||
|
const mstch::node &find_node(const std::string &token, std::list<node const *> current_nodes);
|
||||||
|
std::map<std::string, template_type> m_partials;
|
||||||
|
std::deque<mstch::node> m_nodes;
|
||||||
|
std::list<const mstch::node *> m_node_ptrs;
|
||||||
|
std::stack<std::unique_ptr<render_state>> m_state;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#include "in_section.hpp"
|
||||||
|
#include "../visitor/is_node_empty.hpp"
|
||||||
|
#include "../visitor/render_section.hpp"
|
||||||
|
#include "outside_section.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
in_section::in_section(type type, const token &start_token)
|
||||||
|
: m_type(type), m_start_token(start_token), m_skipped_openings(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string in_section::render(render_context &ctx, const token &token)
|
||||||
|
{
|
||||||
|
if (token.token_type() == token::type::section_close)
|
||||||
|
if (token.name() == m_start_token.name() && m_skipped_openings == 0)
|
||||||
|
{
|
||||||
|
auto &node = ctx.get_node(m_start_token.name());
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
if (m_type == type::normal && !visit(is_node_empty(), node))
|
||||||
|
out = visit(render_section(ctx, m_section, m_start_token.delims()), node);
|
||||||
|
else if (m_type == type::inverted && visit(is_node_empty(), node))
|
||||||
|
out = render_context::push(ctx).render(m_section);
|
||||||
|
|
||||||
|
ctx.set_state<outside_section>();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_skipped_openings--;
|
||||||
|
else if (token.token_type() == token::type::inverted_section_open || token.token_type() == token::type::section_open)
|
||||||
|
m_skipped_openings++;
|
||||||
|
|
||||||
|
m_section << token;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../template_type.hpp"
|
||||||
|
#include "render_state.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class in_section : public render_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class type
|
||||||
|
{
|
||||||
|
inverted,
|
||||||
|
normal
|
||||||
|
};
|
||||||
|
in_section(type type, const token &start_token);
|
||||||
|
std::string render(render_context &context, const token &token) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const type m_type;
|
||||||
|
const token &m_start_token;
|
||||||
|
template_type m_section;
|
||||||
|
int m_skipped_openings;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#include "outside_section.hpp"
|
||||||
|
|
||||||
|
#include "../render_context.hpp"
|
||||||
|
#include "../visitor/render_node.hpp"
|
||||||
|
#include "in_section.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
std::string outside_section::render(render_context &ctx, const token &token)
|
||||||
|
{
|
||||||
|
using flag = render_node::flag;
|
||||||
|
switch (token.token_type())
|
||||||
|
{
|
||||||
|
case token::type::section_open:
|
||||||
|
ctx.set_state<in_section>(in_section::type::normal, token);
|
||||||
|
break;
|
||||||
|
case token::type::inverted_section_open:
|
||||||
|
ctx.set_state<in_section>(in_section::type::inverted, token);
|
||||||
|
break;
|
||||||
|
case token::type::variable:
|
||||||
|
return visit(render_node(ctx, flag::escape_html), ctx.get_node(token.name()));
|
||||||
|
case token::type::unescaped_variable:
|
||||||
|
return visit(render_node(ctx, flag::none), ctx.get_node(token.name()));
|
||||||
|
case token::type::text:
|
||||||
|
return token.raw();
|
||||||
|
case token::type::partial:
|
||||||
|
return ctx.render_partial(token.name(), token.partial_prefix());
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "render_state.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class outside_section : public render_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string render(render_context &context, const token &token) override;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "../token.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class render_context;
|
||||||
|
|
||||||
|
class render_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~render_state() {}
|
||||||
|
virtual std::string render(render_context &context, const token &token) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
#include "template_type.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
template_type::template_type(const std::string &str, const delim_type &delims)
|
||||||
|
: m_open(delims.first), m_close(delims.second)
|
||||||
|
{
|
||||||
|
tokenize(str);
|
||||||
|
strip_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
template_type::template_type(const std::string &str) : m_open("{{"), m_close("}}")
|
||||||
|
{
|
||||||
|
tokenize(str);
|
||||||
|
strip_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
void template_type::process_text(citer begin, citer end)
|
||||||
|
{
|
||||||
|
if (begin == end)
|
||||||
|
return;
|
||||||
|
auto start = begin;
|
||||||
|
for (auto it = begin; it != end; ++it)
|
||||||
|
if (*it == '\n' || it == end - 1)
|
||||||
|
{
|
||||||
|
m_tokens.push_back({{start, it + 1}});
|
||||||
|
start = it + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void template_type::tokenize(const std::string &tmp)
|
||||||
|
{
|
||||||
|
citer beg = tmp.begin();
|
||||||
|
auto npos = std::string::npos;
|
||||||
|
|
||||||
|
for (std::size_t cur_pos = 0; cur_pos < tmp.size();)
|
||||||
|
{
|
||||||
|
auto open_pos = tmp.find(m_open, cur_pos);
|
||||||
|
auto close_pos = tmp.find(m_close, open_pos == npos ? open_pos : open_pos + 1);
|
||||||
|
|
||||||
|
if (close_pos != npos && open_pos != npos)
|
||||||
|
{
|
||||||
|
if (*(beg + open_pos + m_open.size()) == '{' && *(beg + close_pos + m_close.size()) == '}')
|
||||||
|
++close_pos;
|
||||||
|
|
||||||
|
process_text(beg + cur_pos, beg + open_pos);
|
||||||
|
cur_pos = close_pos + m_close.size();
|
||||||
|
m_tokens.push_back({{beg + open_pos, beg + close_pos + m_close.size()}, m_open.size(), m_close.size()});
|
||||||
|
|
||||||
|
if (cur_pos == tmp.size())
|
||||||
|
{
|
||||||
|
m_tokens.push_back({{""}});
|
||||||
|
m_tokens.back().eol(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*(beg + open_pos + m_open.size()) == '=' && *(beg + close_pos - 1) == '=')
|
||||||
|
{
|
||||||
|
auto tok_beg = beg + open_pos + m_open.size() + 1;
|
||||||
|
auto tok_end = beg + close_pos - 1;
|
||||||
|
auto front_skip = first_not_ws(tok_beg, tok_end);
|
||||||
|
auto back_skip = first_not_ws(reverse(tok_end), reverse(tok_beg));
|
||||||
|
m_open = {front_skip, beg + tmp.find(' ', front_skip - beg)};
|
||||||
|
m_close = {beg + tmp.rfind(' ', back_skip - beg) + 1, back_skip + 1};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
process_text(beg + cur_pos, tmp.end());
|
||||||
|
cur_pos = close_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void template_type::strip_whitespace()
|
||||||
|
{
|
||||||
|
auto line_begin = m_tokens.begin();
|
||||||
|
bool has_tag = false, non_space = false;
|
||||||
|
|
||||||
|
for (auto it = m_tokens.begin(); it != m_tokens.end(); ++it)
|
||||||
|
{
|
||||||
|
auto type = (*it).token_type();
|
||||||
|
if (type != token::type::text && type != token::type::variable && type != token::type::unescaped_variable)
|
||||||
|
has_tag = true;
|
||||||
|
else if (!(*it).ws_only())
|
||||||
|
non_space = true;
|
||||||
|
|
||||||
|
if ((*it).eol())
|
||||||
|
{
|
||||||
|
if (has_tag && !non_space)
|
||||||
|
{
|
||||||
|
store_prefixes(line_begin);
|
||||||
|
|
||||||
|
auto c = line_begin;
|
||||||
|
for (bool end = false; !end; (*c).ws_only() ? c = m_tokens.erase(c) : ++c)
|
||||||
|
if ((end = (*c).eol()))
|
||||||
|
it = c - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
non_space = has_tag = false;
|
||||||
|
line_begin = it + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void template_type::store_prefixes(std::vector<token>::iterator beg)
|
||||||
|
{
|
||||||
|
for (auto cur = beg; !(*cur).eol(); ++cur)
|
||||||
|
if ((*cur).token_type() == token::type::partial && cur != beg && (*(cur - 1)).ws_only())
|
||||||
|
(*cur).partial_prefix((*(cur - 1)).raw());
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "token.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class template_type
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template_type() = default;
|
||||||
|
template_type(const std::string &str);
|
||||||
|
template_type(const std::string &str, const delim_type &delims);
|
||||||
|
std::vector<token>::const_iterator begin() const { return m_tokens.begin(); }
|
||||||
|
std::vector<token>::const_iterator end() const { return m_tokens.end(); }
|
||||||
|
void operator<<(const token &token) { m_tokens.push_back(token); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<token> m_tokens;
|
||||||
|
std::string m_open;
|
||||||
|
std::string m_close;
|
||||||
|
void strip_whitespace();
|
||||||
|
void process_text(citer beg, citer end);
|
||||||
|
void tokenize(const std::string &tmp);
|
||||||
|
void store_prefixes(std::vector<token>::iterator beg);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#include "token.hpp"
|
||||||
|
#include "utils.hpp"
|
||||||
|
|
||||||
|
using namespace mstch;
|
||||||
|
|
||||||
|
token::type token::token_info(char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '>':
|
||||||
|
return type::partial;
|
||||||
|
case '^':
|
||||||
|
return type::inverted_section_open;
|
||||||
|
case '/':
|
||||||
|
return type::section_close;
|
||||||
|
case '&':
|
||||||
|
return type::unescaped_variable;
|
||||||
|
case '#':
|
||||||
|
return type::section_open;
|
||||||
|
case '!':
|
||||||
|
return type::comment;
|
||||||
|
default:
|
||||||
|
return type::variable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token::token(const std::string &str, std::size_t left, std::size_t right) : m_raw(str), m_eol(false), m_ws_only(false)
|
||||||
|
{
|
||||||
|
if (left != 0 && right != 0)
|
||||||
|
{
|
||||||
|
if (str[left] == '=' && str[str.size() - right - 1] == '=')
|
||||||
|
{
|
||||||
|
m_type = type::delimiter_change;
|
||||||
|
}
|
||||||
|
else if (str[left] == '{' && str[str.size() - right - 1] == '}')
|
||||||
|
{
|
||||||
|
m_type = type::unescaped_variable;
|
||||||
|
m_name = {first_not_ws(str.begin() + left + 1, str.end() - right),
|
||||||
|
first_not_ws(str.rbegin() + 1 + right, str.rend() - left) + 1};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto c = first_not_ws(str.begin() + left, str.end() - right);
|
||||||
|
m_type = token_info(*c);
|
||||||
|
if (m_type != type::variable)
|
||||||
|
c = first_not_ws(c + 1, str.end() - right);
|
||||||
|
m_name = {c, first_not_ws(str.rbegin() + right, str.rend() - left) + 1};
|
||||||
|
m_delims = {{str.begin(), str.begin() + left}, {str.end() - right, str.end()}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_type = type::text;
|
||||||
|
m_eol = (str.size() > 0 && str[str.size() - 1] == '\n');
|
||||||
|
m_ws_only = (str.find_first_not_of(" \r\n\t") == std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
using delim_type = std::pair<std::string, std::string>;
|
||||||
|
|
||||||
|
class token
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class type
|
||||||
|
{
|
||||||
|
text,
|
||||||
|
variable,
|
||||||
|
section_open,
|
||||||
|
section_close,
|
||||||
|
inverted_section_open,
|
||||||
|
unescaped_variable,
|
||||||
|
comment,
|
||||||
|
partial,
|
||||||
|
delimiter_change
|
||||||
|
};
|
||||||
|
token(const std::string &str, std::size_t left = 0, std::size_t right = 0);
|
||||||
|
type token_type() const { return m_type; };
|
||||||
|
const std::string &raw() const { return m_raw; };
|
||||||
|
const std::string &name() const { return m_name; };
|
||||||
|
const std::string &partial_prefix() const { return m_partial_prefix; };
|
||||||
|
const delim_type &delims() const { return m_delims; };
|
||||||
|
void partial_prefix(const std::string &p_partial_prefix) { m_partial_prefix = p_partial_prefix; };
|
||||||
|
bool eol() const { return m_eol; }
|
||||||
|
void eol(bool eol) { m_eol = eol; }
|
||||||
|
bool ws_only() const { return m_ws_only; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
type m_type;
|
||||||
|
std::string m_name;
|
||||||
|
std::string m_raw;
|
||||||
|
std::string m_partial_prefix;
|
||||||
|
delim_type m_delims;
|
||||||
|
bool m_eol;
|
||||||
|
bool m_ws_only;
|
||||||
|
type token_info(char c);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#include "utils.hpp"
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
|
||||||
|
mstch::citer mstch::first_not_ws(mstch::citer begin, mstch::citer end)
|
||||||
|
{
|
||||||
|
for (auto it = begin; it != end; ++it)
|
||||||
|
if (*it != ' ')
|
||||||
|
return it;
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
mstch::citer mstch::first_not_ws(mstch::criter begin, mstch::criter end)
|
||||||
|
{
|
||||||
|
for (auto rit = begin; rit != end; ++rit)
|
||||||
|
if (*rit != ' ')
|
||||||
|
return --(rit.base());
|
||||||
|
return --(end.base());
|
||||||
|
}
|
||||||
|
|
||||||
|
mstch::criter mstch::reverse(mstch::citer it) { return std::reverse_iterator<mstch::citer>(it); }
|
||||||
|
|
||||||
|
std::string mstch::html_escape(const std::string &str)
|
||||||
|
{
|
||||||
|
if (mstch::config::escape)
|
||||||
|
return mstch::config::escape(str);
|
||||||
|
|
||||||
|
std::string out;
|
||||||
|
citer start = str.begin();
|
||||||
|
|
||||||
|
auto add_escape = [&out, &start](const std::string &escaped, citer &it) {
|
||||||
|
out += std::string{start, it} + escaped;
|
||||||
|
start = it + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto it = str.begin(); it != str.end(); ++it)
|
||||||
|
switch (*it)
|
||||||
|
{
|
||||||
|
case '&':
|
||||||
|
add_escape("&", it);
|
||||||
|
break;
|
||||||
|
case '\'':
|
||||||
|
add_escape("'", it);
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
add_escape(""", it);
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
add_escape("<", it);
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
add_escape(">", it);
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
add_escape("/", it);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out + std::string{start, str.end()};
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/apply_visitor.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
using citer = std::string::const_iterator;
|
||||||
|
using criter = std::string::const_reverse_iterator;
|
||||||
|
|
||||||
|
citer first_not_ws(citer begin, citer end);
|
||||||
|
citer first_not_ws(criter begin, criter end);
|
||||||
|
std::string html_escape(const std::string &str);
|
||||||
|
criter reverse(citer it);
|
||||||
|
|
||||||
|
template <class... Args> auto visit(Args &&... args) -> decltype(boost::apply_visitor(std::forward<Args>(args)...))
|
||||||
|
{
|
||||||
|
return boost::apply_visitor(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
|
||||||
|
#include "has_token.hpp"
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class get_token : public boost::static_visitor<const mstch::node &>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
get_token(const std::string &token, const mstch::node &node) : m_token(token), m_node(node) {}
|
||||||
|
|
||||||
|
template <class T> const mstch::node &operator()(const T &) const { return m_node; }
|
||||||
|
|
||||||
|
const mstch::node &operator()(const map &map) const { return map.at(m_token); }
|
||||||
|
|
||||||
|
const mstch::node &operator()(const std::shared_ptr<object> &object) const { return object->at(m_token); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string &m_token;
|
||||||
|
const mstch::node &m_node;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class has_token : public boost::static_visitor<bool>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
has_token(const std::string &token) : m_token(token) {}
|
||||||
|
|
||||||
|
template <class T> bool operator()(const T &) const { return m_token == "."; }
|
||||||
|
|
||||||
|
bool operator()(const map &map) const { return map.count(m_token) == 1; }
|
||||||
|
|
||||||
|
bool operator()(const std::shared_ptr<object> &object) const { return object->has(m_token); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string &m_token;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class is_node_empty : public boost::static_visitor<bool>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <class T> bool operator()(const T &) const { return false; }
|
||||||
|
|
||||||
|
bool operator()(const std::nullptr_t &) const { return true; }
|
||||||
|
|
||||||
|
bool operator()(const int &value) const { return value == 0; }
|
||||||
|
|
||||||
|
bool operator()(const double &value) const { return value == 0; }
|
||||||
|
|
||||||
|
bool operator()(const bool &value) const { return !value; }
|
||||||
|
|
||||||
|
bool operator()(const std::string &value) const { return value == ""; }
|
||||||
|
|
||||||
|
bool operator()(const array &array) const { return array.size() == 0; }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "../render_context.hpp"
|
||||||
|
#include "../utils.hpp"
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class render_node : public boost::static_visitor<std::string>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class flag
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
escape_html
|
||||||
|
};
|
||||||
|
render_node(render_context &ctx, flag p_flag = flag::none) : m_ctx(ctx), m_flag(p_flag) {}
|
||||||
|
|
||||||
|
template <class T> std::string operator()(const T &) const { return ""; }
|
||||||
|
|
||||||
|
std::string operator()(const int &value) const { return std::to_string(value); }
|
||||||
|
|
||||||
|
std::string operator()(const double &value) const
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << value;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const bool &value) const { return value ? "true" : "false"; }
|
||||||
|
|
||||||
|
std::string operator()(const lambda &value) const
|
||||||
|
{
|
||||||
|
template_type interpreted{value([this](const mstch::node &n) { return visit(render_node(m_ctx), n); })};
|
||||||
|
auto rendered = render_context::push(m_ctx).render(interpreted);
|
||||||
|
return (m_flag == flag::escape_html) ? html_escape(rendered) : rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const std::string &value) const
|
||||||
|
{
|
||||||
|
return (m_flag == flag::escape_html) ? html_escape(value) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
render_context &m_ctx;
|
||||||
|
flag m_flag;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <boost/variant/static_visitor.hpp>
|
||||||
|
|
||||||
|
#include "../render_context.hpp"
|
||||||
|
#include "../utils.hpp"
|
||||||
|
#include "mstch/mstch.hpp"
|
||||||
|
#include "render_node.hpp"
|
||||||
|
|
||||||
|
namespace mstch
|
||||||
|
{
|
||||||
|
|
||||||
|
class render_section : public boost::static_visitor<std::string>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class flag
|
||||||
|
{
|
||||||
|
none,
|
||||||
|
keep_array
|
||||||
|
};
|
||||||
|
render_section(render_context &ctx, const template_type §ion, const delim_type &delims, flag p_flag = flag::none)
|
||||||
|
: m_ctx(ctx), m_section(section), m_delims(delims), m_flag(p_flag)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T> std::string operator()(const T &t) const
|
||||||
|
{
|
||||||
|
return render_context::push(m_ctx, t).render(m_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const lambda &fun) const
|
||||||
|
{
|
||||||
|
std::string section_str;
|
||||||
|
for (auto &token : m_section)
|
||||||
|
section_str += token.raw();
|
||||||
|
template_type interpreted{fun([this](const mstch::node &n) { return visit(render_node(m_ctx), n); }, section_str),
|
||||||
|
m_delims};
|
||||||
|
return render_context::push(m_ctx).render(interpreted);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const array &array) const
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
if (m_flag == flag::keep_array)
|
||||||
|
return render_context::push(m_ctx, array).render(m_section);
|
||||||
|
else
|
||||||
|
for (auto &item : array)
|
||||||
|
out += visit(render_section(m_ctx, m_section, m_delims, flag::keep_array), item);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
render_context &m_ctx;
|
||||||
|
const template_type &m_section;
|
||||||
|
const delim_type &m_delims;
|
||||||
|
flag m_flag;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,334 @@
|
|||||||
|
#include <nntpchan/base64.hpp>
|
||||||
|
|
||||||
|
// taken from i2pd
|
||||||
|
namespace i2p
|
||||||
|
{
|
||||||
|
namespace data
|
||||||
|
{
|
||||||
|
|
||||||
|
static const char T32[32] = {
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||||
|
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *GetBase32SubstitutionTable() { return T32; }
|
||||||
|
|
||||||
|
static void iT64Build(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* BASE64 Substitution Table
|
||||||
|
* -------------------------
|
||||||
|
*
|
||||||
|
* Direct Substitution Table
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const char T64[64] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||||
|
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||||
|
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '~'};
|
||||||
|
|
||||||
|
const char *GetBase64SubstitutionTable() { return T64; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reverse Substitution Table (built in run time)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char iT64[256];
|
||||||
|
static int isFirstTime = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Padding
|
||||||
|
*/
|
||||||
|
|
||||||
|
static char P64 = '=';
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* ByteStreamToBase64
|
||||||
|
* ------------------
|
||||||
|
*
|
||||||
|
* Converts binary encoded data to BASE64 format.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t /* Number of bytes in the encoded buffer */
|
||||||
|
ByteStreamToBase64(const uint8_t *InBuffer, /* Input buffer, binary data */
|
||||||
|
size_t InCount, /* Number of bytes in the input buffer */
|
||||||
|
char *OutBuffer, /* output buffer */
|
||||||
|
size_t len /* length of output buffer */
|
||||||
|
)
|
||||||
|
|
||||||
|
{
|
||||||
|
unsigned char *ps;
|
||||||
|
unsigned char *pd;
|
||||||
|
unsigned char acc_1;
|
||||||
|
unsigned char acc_2;
|
||||||
|
int i;
|
||||||
|
int n;
|
||||||
|
int m;
|
||||||
|
size_t outCount;
|
||||||
|
|
||||||
|
ps = (unsigned char *)InBuffer;
|
||||||
|
n = InCount / 3;
|
||||||
|
m = InCount % 3;
|
||||||
|
if (!m)
|
||||||
|
outCount = 4 * n;
|
||||||
|
else
|
||||||
|
outCount = 4 * (n + 1);
|
||||||
|
if (outCount > len)
|
||||||
|
return 0;
|
||||||
|
pd = (unsigned char *)OutBuffer;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1 << 4) & 0x30;
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
acc_1 &= 0x0f;
|
||||||
|
acc_1 <<= 2;
|
||||||
|
acc_2 = *ps++;
|
||||||
|
acc_1 |= acc_2 >> 6; /* base64 digit #3 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_2 &= 0x3f; /* base64 digit #4 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
}
|
||||||
|
if (m == 1)
|
||||||
|
{
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1 << 4) & 0x3f; /* base64 digit #2 */
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
*pd++ = P64;
|
||||||
|
*pd++ = P64;
|
||||||
|
}
|
||||||
|
else if (m == 2)
|
||||||
|
{
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 = (acc_1 << 4) & 0x3f;
|
||||||
|
acc_1 >>= 2; /* base64 digit #1 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
acc_1 = *ps++;
|
||||||
|
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
||||||
|
*pd++ = T64[acc_2];
|
||||||
|
acc_1 &= 0x0f;
|
||||||
|
acc_1 <<= 2; /* base64 digit #3 */
|
||||||
|
*pd++ = T64[acc_1];
|
||||||
|
*pd++ = P64;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Base64ToByteStream
|
||||||
|
* ------------------
|
||||||
|
*
|
||||||
|
* Converts BASE64 encoded data to binary format. If input buffer is
|
||||||
|
* not properly padded, buffer of negative length is returned
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t /* Number of output bytes */
|
||||||
|
Base64ToByteStream(const char *InBuffer, /* BASE64 encoded buffer */
|
||||||
|
size_t InCount, /* Number of input bytes */
|
||||||
|
uint8_t *OutBuffer, /* output buffer length */
|
||||||
|
size_t len /* length of output buffer */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
unsigned char *ps;
|
||||||
|
unsigned char *pd;
|
||||||
|
unsigned char acc_1;
|
||||||
|
unsigned char acc_2;
|
||||||
|
int i;
|
||||||
|
int n;
|
||||||
|
int m;
|
||||||
|
size_t outCount;
|
||||||
|
|
||||||
|
if (isFirstTime)
|
||||||
|
iT64Build();
|
||||||
|
n = InCount / 4;
|
||||||
|
m = InCount % 4;
|
||||||
|
if (InCount && !m)
|
||||||
|
outCount = 3 * n;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outCount = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ps = (unsigned char *)(InBuffer + InCount - 1);
|
||||||
|
while (*ps-- == P64)
|
||||||
|
outCount--;
|
||||||
|
ps = (unsigned char *)InBuffer;
|
||||||
|
|
||||||
|
if (outCount > len)
|
||||||
|
return -1;
|
||||||
|
pd = OutBuffer;
|
||||||
|
auto endOfOutBuffer = OutBuffer + outCount;
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
acc_1 = iT64[*ps++];
|
||||||
|
acc_2 = iT64[*ps++];
|
||||||
|
acc_1 <<= 2;
|
||||||
|
acc_1 |= acc_2 >> 4;
|
||||||
|
*pd++ = acc_1;
|
||||||
|
if (pd >= endOfOutBuffer)
|
||||||
|
break;
|
||||||
|
|
||||||
|
acc_2 <<= 4;
|
||||||
|
acc_1 = iT64[*ps++];
|
||||||
|
acc_2 |= acc_1 >> 2;
|
||||||
|
*pd++ = acc_2;
|
||||||
|
if (pd >= endOfOutBuffer)
|
||||||
|
break;
|
||||||
|
|
||||||
|
acc_2 = iT64[*ps++];
|
||||||
|
acc_2 |= acc_1 << 6;
|
||||||
|
*pd++ = acc_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Base64EncodingBufferSize(const size_t input_size)
|
||||||
|
{
|
||||||
|
auto d = div(input_size, 3);
|
||||||
|
if (d.rem)
|
||||||
|
d.quot++;
|
||||||
|
return 4 * d.quot;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Base32EncodingBufferSize(const size_t input_size)
|
||||||
|
{
|
||||||
|
auto d = div(input_size, 5);
|
||||||
|
if (d.rem)
|
||||||
|
d.quot++;
|
||||||
|
return 8 * d.quot;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* iT64
|
||||||
|
* ----
|
||||||
|
* Reverse table builder. P64 character is replaced with 0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void iT64Build()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
isFirstTime = 0;
|
||||||
|
for (i = 0; i < 256; i++)
|
||||||
|
iT64[i] = -1;
|
||||||
|
for (i = 0; i < 64; i++)
|
||||||
|
iT64[(int)T64[i]] = i;
|
||||||
|
iT64[(int)P64] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Base32ToByteStream(const char *inBuf, size_t len, uint8_t *outBuf, size_t outLen)
|
||||||
|
{
|
||||||
|
int tmp = 0, bits = 0;
|
||||||
|
size_t ret = 0;
|
||||||
|
for (size_t i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
char ch = inBuf[i];
|
||||||
|
if (ch >= '2' && ch <= '7') // digit
|
||||||
|
ch = (ch - '2') + 26; // 26 means a-z
|
||||||
|
else if (ch >= 'a' && ch <= 'z')
|
||||||
|
ch = ch - 'a'; // a = 0
|
||||||
|
else
|
||||||
|
return 0; // unexpected character
|
||||||
|
|
||||||
|
tmp |= ch;
|
||||||
|
bits += 5;
|
||||||
|
if (bits >= 8)
|
||||||
|
{
|
||||||
|
if (ret >= outLen)
|
||||||
|
return ret;
|
||||||
|
outBuf[ret] = tmp >> (bits - 8);
|
||||||
|
bits -= 8;
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
tmp <<= 5;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ByteStreamToBase32(const uint8_t *inBuf, size_t len, char *outBuf, size_t outLen)
|
||||||
|
{
|
||||||
|
size_t ret = 0, pos = 1;
|
||||||
|
int bits = 8, tmp = inBuf[0];
|
||||||
|
while (ret < outLen && (bits > 0 || pos < len))
|
||||||
|
{
|
||||||
|
if (bits < 5)
|
||||||
|
{
|
||||||
|
if (pos < len)
|
||||||
|
{
|
||||||
|
tmp <<= 8;
|
||||||
|
tmp |= inBuf[pos] & 0xFF;
|
||||||
|
pos++;
|
||||||
|
bits += 8;
|
||||||
|
}
|
||||||
|
else // last byte
|
||||||
|
{
|
||||||
|
tmp <<= (5 - bits);
|
||||||
|
bits = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bits -= 5;
|
||||||
|
int ind = (tmp >> bits) & 0x1F;
|
||||||
|
outBuf[ret] = (ind < 26) ? (ind + 'a') : ((ind - 26) + '2');
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
std::string B64Encode(const uint8_t *data, const std::size_t l)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
out.resize(i2p::data::Base64EncodingBufferSize(l));
|
||||||
|
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool B64Decode(const std::string &data, std::vector<uint8_t> &out)
|
||||||
|
{
|
||||||
|
out.resize(data.size());
|
||||||
|
if (i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
|
||||||
|
{
|
||||||
|
out.shrink_to_fit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string B32Encode(const uint8_t *data, const std::size_t l)
|
||||||
|
{
|
||||||
|
std::string out;
|
||||||
|
out.resize(i2p::data::Base32EncodingBufferSize(l));
|
||||||
|
i2p::data::ByteStreamToBase32(data, l, &out[0], out.size());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool B32Decode(const std::string &data, std::vector<uint8_t> &out)
|
||||||
|
{
|
||||||
|
out.resize(data.size());
|
||||||
|
if (i2p::data::Base32ToByteStream(data.c_str(), data.size(), &out[0], out.size()))
|
||||||
|
{
|
||||||
|
out.shrink_to_fit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <nntpchan/buffer.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
WriteBuffer::WriteBuffer(const char *b, const size_t s)
|
||||||
|
{
|
||||||
|
char *buf = new char[s];
|
||||||
|
std::memcpy(buf, b, s);
|
||||||
|
this->b = uv_buf_init(buf, s);
|
||||||
|
w.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteBuffer::WriteBuffer(const std::string &s) : WriteBuffer(s.c_str(), s.size()) {}
|
||||||
|
|
||||||
|
WriteBuffer::~WriteBuffer() { delete[] b.base; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <nntpchan/base64.hpp>
|
||||||
|
#include <nntpchan/crypto.hpp>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
void SHA512(const uint8_t *d, const std::size_t l, SHA512Digest &h) { crypto_hash(h.data(), d, l); }
|
||||||
|
|
||||||
|
void Blake2B(const uint8_t *d, std::size_t l, Blake2BDigest & h) { crypto_generichash(h.data(), h.size(), d, l, nullptr, 0); }
|
||||||
|
|
||||||
|
std::string Blake2B_base32(const std::string & str)
|
||||||
|
{
|
||||||
|
Blake2BDigest d;
|
||||||
|
Blake2B(reinterpret_cast<const uint8_t*>(str.c_str()), str.size(), d);
|
||||||
|
return B32Encode(d.data(), d.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Crypto::Crypto() { assert(sodium_init() == 0); }
|
||||||
|
|
||||||
|
Crypto::~Crypto() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#ifndef NNTPCHAN_CRYPTO_OLD_HPP
|
||||||
|
#define NNTPCHAN_CRYPTO_OLD_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint32_t state[5];
|
||||||
|
uint32_t count[2];
|
||||||
|
unsigned char buffer[64];
|
||||||
|
} SHA1_CTX;
|
||||||
|
|
||||||
|
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]);
|
||||||
|
|
||||||
|
void SHA1Init(SHA1_CTX *context);
|
||||||
|
|
||||||
|
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len);
|
||||||
|
|
||||||
|
void SHA1Final(unsigned char digest[20], SHA1_CTX *context);
|
||||||
|
|
||||||
|
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <nntpchan/event.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
Mainloop::Mainloop()
|
||||||
|
{
|
||||||
|
m_loop = uv_default_loop();
|
||||||
|
assert(uv_loop_init(m_loop) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mainloop::~Mainloop() { uv_loop_close(m_loop); }
|
||||||
|
|
||||||
|
void Mainloop::Stop() { uv_stop(m_loop); }
|
||||||
|
|
||||||
|
void Mainloop::Run(uv_run_mode mode) { assert(uv_run(m_loop, mode) == 0); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/exec_frontend.hpp>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
ExecFrontend::ExecFrontend(const std::string &fname) : m_exec(fname) {}
|
||||||
|
|
||||||
|
ExecFrontend::~ExecFrontend() {}
|
||||||
|
|
||||||
|
void ExecFrontend::ProcessNewMessage(const fs::path &fpath) { Exec({"post", fpath}); }
|
||||||
|
|
||||||
|
bool ExecFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return Exec({"newsgroup", newsgroup}) == 0; }
|
||||||
|
|
||||||
|
bool ExecFrontend::AcceptsMessage(const std::string &msgid) { return Exec({"msgid", msgid}) == 0; }
|
||||||
|
|
||||||
|
int ExecFrontend::Exec(std::deque<std::string> args)
|
||||||
|
{
|
||||||
|
// set up arguments
|
||||||
|
const char **cargs = new char const *[args.size() + 2];
|
||||||
|
std::size_t l = 0;
|
||||||
|
cargs[l++] = m_exec.c_str();
|
||||||
|
while (args.size())
|
||||||
|
{
|
||||||
|
cargs[l++] = args.front().c_str();
|
||||||
|
args.pop_front();
|
||||||
|
}
|
||||||
|
cargs[l] = 0;
|
||||||
|
int retcode = 0;
|
||||||
|
pid_t child = fork();
|
||||||
|
if (child)
|
||||||
|
{
|
||||||
|
waitpid(child, &retcode, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int r = execvpe(m_exec.c_str(), (char *const *)cargs, environ);
|
||||||
|
if (r == -1)
|
||||||
|
{
|
||||||
|
std::cout << strerror(errno) << std::endl;
|
||||||
|
exit(errno);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
exit(r);
|
||||||
|
}
|
||||||
|
return retcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#include <nntpchan/file_handle.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
FileHandle_ptr OpenFile(const fs::path &fname, FileMode mode)
|
||||||
|
{
|
||||||
|
std::fstream *f = new std::fstream;
|
||||||
|
if (mode == eRead)
|
||||||
|
{
|
||||||
|
f->open(fname, std::ios::in);
|
||||||
|
}
|
||||||
|
else if (mode == eWrite)
|
||||||
|
{
|
||||||
|
f->open(fname, std::ios::out);
|
||||||
|
}
|
||||||
|
if (f->is_open())
|
||||||
|
return FileHandle_ptr(f);
|
||||||
|
delete f;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include <nntpchan/line.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
LineReader::LineReader(size_t limit) : m_close(false), lineLimit(limit) {}
|
||||||
|
|
||||||
|
void LineReader::Data(const char *data, ssize_t l)
|
||||||
|
{
|
||||||
|
if (l <= 0)
|
||||||
|
return;
|
||||||
|
// process leftovers
|
||||||
|
std::size_t idx = 0;
|
||||||
|
std::size_t pos = 0;
|
||||||
|
while (l-- > 0)
|
||||||
|
{
|
||||||
|
char c = data[idx++];
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
OnLine(data, pos);
|
||||||
|
pos = 0;
|
||||||
|
data += idx;
|
||||||
|
}
|
||||||
|
else if (c == '\r' && data[idx] == '\n')
|
||||||
|
{
|
||||||
|
OnLine(data, pos);
|
||||||
|
data += idx + 1;
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LineReader::OnLine(const char *d, const size_t l)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
line += std::string(d, l);
|
||||||
|
HandleLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LineReader::ShouldClose() { return m_close; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include <nntpchan/mime.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
bool ReadHeader(const FileHandle_ptr &file, RawHeader &header)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(*file, line) && !(line == "\r" || line == ""))
|
||||||
|
{
|
||||||
|
std::string k, v;
|
||||||
|
auto idx = line.find(": ");
|
||||||
|
auto endidx = line.size() - 1;
|
||||||
|
|
||||||
|
while (line[endidx] == '\r')
|
||||||
|
--endidx;
|
||||||
|
|
||||||
|
if (idx != std::string::npos && idx + 2 < endidx)
|
||||||
|
{
|
||||||
|
k = line.substr(0, idx);
|
||||||
|
v = line.substr(idx + 2, endidx);
|
||||||
|
header[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file->good();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#include <cstring>
|
||||||
|
#include <nntpchan/net.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <uv.h>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
std::string NetAddr::to_string()
|
||||||
|
{
|
||||||
|
std::string str("invalid");
|
||||||
|
const size_t s = 128;
|
||||||
|
char *buff = new char[s];
|
||||||
|
if (uv_ip6_name(&addr, buff, s) == 0)
|
||||||
|
{
|
||||||
|
str = std::string(buff);
|
||||||
|
delete[] buff;
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
NetAddr::NetAddr() { std::memset(&addr, 0, sizeof(addr)); }
|
||||||
|
|
||||||
|
NetAddr ParseAddr(const std::string &addr)
|
||||||
|
{
|
||||||
|
NetAddr saddr;
|
||||||
|
auto n = addr.rfind("]:");
|
||||||
|
if (n == std::string::npos)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("invalid address: " + addr);
|
||||||
|
}
|
||||||
|
if (addr[0] != '[')
|
||||||
|
{
|
||||||
|
throw std::runtime_error("invalid address: " + addr);
|
||||||
|
}
|
||||||
|
auto p = addr.substr(n + 2);
|
||||||
|
int port = std::atoi(p.c_str());
|
||||||
|
auto a = addr.substr(0, n);
|
||||||
|
uv_ip6_addr(a.c_str(), port, &saddr.addr);
|
||||||
|
return saddr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/base64.hpp>
|
||||||
|
#include <nntpchan/crypto.hpp>
|
||||||
|
#include <nntpchan/nntp_auth.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
HashedCredDB::HashedCredDB() : LineReader(1024) {}
|
||||||
|
|
||||||
|
bool HashedCredDB::CheckLogin(const std::string &user, const std::string &passwd)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_access);
|
||||||
|
m_found = false;
|
||||||
|
m_user = user;
|
||||||
|
m_passwd = passwd;
|
||||||
|
m_instream->seekg(0, std::ios::end);
|
||||||
|
const auto l = m_instream->tellg();
|
||||||
|
m_instream->seekg(0, std::ios::beg);
|
||||||
|
char *buff = new char[l];
|
||||||
|
// read file
|
||||||
|
m_instream->read(buff, l);
|
||||||
|
Data(buff, l);
|
||||||
|
delete[] buff;
|
||||||
|
return m_found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HashedCredDB::ProcessLine(const std::string &line)
|
||||||
|
{
|
||||||
|
// strip comments
|
||||||
|
auto comment = line.find("#");
|
||||||
|
std::string part = line;
|
||||||
|
for (; comment != std::string::npos; comment = part.find("#"))
|
||||||
|
{
|
||||||
|
if (comment)
|
||||||
|
part = part.substr(0, comment);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!part.size())
|
||||||
|
return false; // empty line after comments
|
||||||
|
auto idx = part.find(":");
|
||||||
|
if (idx == std::string::npos)
|
||||||
|
return false; // bad format
|
||||||
|
if (m_user != part.substr(0, idx))
|
||||||
|
return false; // username mismatch
|
||||||
|
part = part.substr(idx + 1);
|
||||||
|
|
||||||
|
idx = part.find(":");
|
||||||
|
if (idx == std::string::npos)
|
||||||
|
return false; // bad format
|
||||||
|
std::string cred = part.substr(0, idx);
|
||||||
|
std::string salt = part.substr(idx + 1);
|
||||||
|
return Hash(m_passwd, salt) == cred;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HashedCredDB::HandleLine(const std::string &line)
|
||||||
|
{
|
||||||
|
if (m_found)
|
||||||
|
return;
|
||||||
|
if (ProcessLine(line))
|
||||||
|
m_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HashedCredDB::SetStream(std::istream *s) { m_instream = s; }
|
||||||
|
|
||||||
|
std::string HashedCredDB::Hash(const std::string &data, const std::string &salt)
|
||||||
|
{
|
||||||
|
SHA512Digest h;
|
||||||
|
std::string d = data + salt;
|
||||||
|
SHA512((const uint8_t *)d.c_str(), d.size(), h);
|
||||||
|
return B64Encode(h.data(), h.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
HashedFileDB::HashedFileDB(const std::string &fname) : m_fname(fname), f(nullptr) {}
|
||||||
|
|
||||||
|
HashedFileDB::~HashedFileDB() {}
|
||||||
|
|
||||||
|
void HashedFileDB::Close()
|
||||||
|
{
|
||||||
|
if (f.is_open())
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HashedFileDB::Open()
|
||||||
|
{
|
||||||
|
if (!f.is_open())
|
||||||
|
f.open(m_fname);
|
||||||
|
if (f.is_open())
|
||||||
|
{
|
||||||
|
SetStream(&f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/nntp_handler.hpp>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
NNTPServerHandler::NNTPServerHandler(const fs::path &storage)
|
||||||
|
: LineReader(1024), m_article(nullptr), m_auth(nullptr), m_store(std::make_unique<ArticleStorage>(storage)),
|
||||||
|
m_authed(false), m_state(eStateReadCommand)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NNTPServerHandler::~NNTPServerHandler() {}
|
||||||
|
|
||||||
|
void NNTPServerHandler::HandleLine(const std::string &line)
|
||||||
|
{
|
||||||
|
if (m_state == eStateReadCommand)
|
||||||
|
{
|
||||||
|
std::deque<std::string> command;
|
||||||
|
std::istringstream s;
|
||||||
|
s.str(line);
|
||||||
|
for (std::string part; std::getline(s, part, ' ');)
|
||||||
|
{
|
||||||
|
if (part.size())
|
||||||
|
command.push_back(part);
|
||||||
|
}
|
||||||
|
if (command.size())
|
||||||
|
HandleCommand(command);
|
||||||
|
else
|
||||||
|
QueueLine("501 Syntax error");
|
||||||
|
}
|
||||||
|
else if (m_state == eStateStoreArticle)
|
||||||
|
{
|
||||||
|
std::string l = line + "\r\n";
|
||||||
|
OnData(l.c_str(), l.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "invalid state" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::OnData(const char *data, ssize_t l)
|
||||||
|
{
|
||||||
|
if (l <= 0)
|
||||||
|
return;
|
||||||
|
if (m_state == eStateStoreArticle)
|
||||||
|
{
|
||||||
|
const char *end = strstr(data, "\r\n.\r\n");
|
||||||
|
if (end)
|
||||||
|
{
|
||||||
|
std::size_t diff = end - data;
|
||||||
|
if (m_article)
|
||||||
|
{
|
||||||
|
m_article->write(data, diff + 2);
|
||||||
|
m_article->flush();
|
||||||
|
}
|
||||||
|
ArticleObtained();
|
||||||
|
diff += 5;
|
||||||
|
Data(end + 5, l - diff);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_article)
|
||||||
|
{
|
||||||
|
m_article->write(data, l);
|
||||||
|
m_article->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Data(data, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::HandleCommand(const std::deque<std::string> &command)
|
||||||
|
{
|
||||||
|
auto cmd = command[0];
|
||||||
|
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
||||||
|
std::size_t cmdlen = command.size();
|
||||||
|
for (const auto &part : command)
|
||||||
|
std::cerr << " " << part;
|
||||||
|
std::cerr << std::endl;
|
||||||
|
if (cmd == "QUIT")
|
||||||
|
{
|
||||||
|
Quit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (cmd[0] == '5')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (cmd == "MODE")
|
||||||
|
{
|
||||||
|
if (cmdlen == 2)
|
||||||
|
{
|
||||||
|
// set mode
|
||||||
|
SwitchMode(command[1]);
|
||||||
|
}
|
||||||
|
else if (cmdlen)
|
||||||
|
{
|
||||||
|
// too many arguments
|
||||||
|
QueueLine("500 too many arguments");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// get mode
|
||||||
|
QueueLine("500 wrong arguments");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (cmd == "CAPABILITIES")
|
||||||
|
{
|
||||||
|
QueueLine("101 I support the following:");
|
||||||
|
QueueLine("READER");
|
||||||
|
QueueLine("IMPLEMENTATION nntpchan-daemon");
|
||||||
|
QueueLine("VERSION 2");
|
||||||
|
QueueLine("STREAMING");
|
||||||
|
QueueLine(".");
|
||||||
|
}
|
||||||
|
else if (cmd == "CHECK")
|
||||||
|
{
|
||||||
|
if (cmdlen >= 2)
|
||||||
|
{
|
||||||
|
const std::string &msgid = command[1];
|
||||||
|
if (IsValidMessageID(msgid) && m_store->Accept(msgid))
|
||||||
|
{
|
||||||
|
QueueLine("238 " + msgid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
QueueLine("438 " + msgid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
QueueLine("501 syntax error");
|
||||||
|
}
|
||||||
|
else if (cmd == "TAKETHIS")
|
||||||
|
{
|
||||||
|
if (cmdlen >= 2)
|
||||||
|
{
|
||||||
|
const std::string &msgid = command[1];
|
||||||
|
if (m_store->Accept(msgid))
|
||||||
|
{
|
||||||
|
m_article = m_store->OpenWrite(msgid);
|
||||||
|
}
|
||||||
|
m_articleName = msgid;
|
||||||
|
EnterState(eStateStoreArticle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QueueLine("501 invalid syntax");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// unknown command
|
||||||
|
QueueLine("500 Unknown Command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::ArticleObtained()
|
||||||
|
{
|
||||||
|
if (m_article)
|
||||||
|
{
|
||||||
|
m_article->close();
|
||||||
|
m_article = nullptr;
|
||||||
|
QueueLine("239 " + m_articleName);
|
||||||
|
std::cerr << "stored " << m_articleName << std::endl;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
QueueLine("439 " + m_articleName);
|
||||||
|
m_articleName = "";
|
||||||
|
EnterState(eStateReadCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::SwitchMode(const std::string &mode)
|
||||||
|
{
|
||||||
|
std::string m = mode;
|
||||||
|
std::transform(m.begin(), m.end(), m.begin(), ::toupper);
|
||||||
|
if (m == "READER")
|
||||||
|
{
|
||||||
|
m_mode = m;
|
||||||
|
if (PostingAllowed())
|
||||||
|
{
|
||||||
|
QueueLine("200 Posting is permitted yo");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueLine("201 Posting is not permitted yo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (m == "STREAM")
|
||||||
|
{
|
||||||
|
m_mode = m;
|
||||||
|
if (PostingAllowed())
|
||||||
|
{
|
||||||
|
QueueLine("203 Streaming enabled");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueLine("483 Streaming Denied");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// unknown mode
|
||||||
|
QueueLine("500 Unknown mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::EnterState(State st)
|
||||||
|
{
|
||||||
|
std::cerr << "enter state " << st << std::endl;
|
||||||
|
m_state = st;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::Quit()
|
||||||
|
{
|
||||||
|
EnterState(eStateQuit);
|
||||||
|
QueueLine("205 quitting");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NNTPServerHandler::ShouldClose() { return m_state == eStateQuit; }
|
||||||
|
|
||||||
|
bool NNTPServerHandler::PostingAllowed() { return m_authed || m_auth == nullptr; }
|
||||||
|
|
||||||
|
void NNTPServerHandler::Greet()
|
||||||
|
{
|
||||||
|
if (PostingAllowed())
|
||||||
|
QueueLine("200 Posting allowed");
|
||||||
|
else
|
||||||
|
QueueLine("201 Posting not allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerHandler::SetAuth(CredDB_ptr creds) { m_auth = creds; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/net.hpp>
|
||||||
|
#include <nntpchan/nntp_auth.hpp>
|
||||||
|
#include <nntpchan/nntp_handler.hpp>
|
||||||
|
#include <nntpchan/nntp_server.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
NNTPServer::NNTPServer(uv_loop_t *loop) : Server(loop), m_frontend(nullptr) {}
|
||||||
|
|
||||||
|
NNTPServer::~NNTPServer() {}
|
||||||
|
|
||||||
|
IServerConn *NNTPServer::CreateConn(uv_stream_t *s)
|
||||||
|
{
|
||||||
|
CredDB_ptr creds;
|
||||||
|
|
||||||
|
std::ifstream i;
|
||||||
|
i.open(m_logindbpath);
|
||||||
|
if (i.is_open())
|
||||||
|
creds = std::make_shared<HashedFileDB>(m_logindbpath);
|
||||||
|
|
||||||
|
NNTPServerHandler *handler = new NNTPServerHandler(m_storagePath);
|
||||||
|
if (creds)
|
||||||
|
handler->SetAuth(creds);
|
||||||
|
|
||||||
|
NNTPServerConn *conn = new NNTPServerConn(GetLoop(), s, this, handler);
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServer::SetLoginDB(const std::string path) { m_logindbpath = path; }
|
||||||
|
|
||||||
|
void NNTPServer::SetStoragePath(const std::string &path) { m_storagePath = path; }
|
||||||
|
|
||||||
|
void NNTPServer::SetInstanceName(const std::string &name) { m_servername = name; }
|
||||||
|
|
||||||
|
void NNTPServer::SetFrontend(Frontend *f) { m_frontend.reset(f); }
|
||||||
|
|
||||||
|
std::string NNTPServer::InstanceName() const { return m_servername; }
|
||||||
|
|
||||||
|
void NNTPServer::OnAcceptError(int status) { std::cerr << "nntpserver::accept() " << uv_strerror(status) << std::endl; }
|
||||||
|
|
||||||
|
void NNTPServerConn::SendNextReply()
|
||||||
|
{
|
||||||
|
IConnHandler *handler = GetHandler();
|
||||||
|
while (handler->HasNextLine())
|
||||||
|
{
|
||||||
|
auto line = handler->GetNextLine();
|
||||||
|
SendString(line + "\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NNTPServerConn::Greet()
|
||||||
|
{
|
||||||
|
IConnHandler *handler = GetHandler();
|
||||||
|
handler->Greet();
|
||||||
|
SendNextReply();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string NNTPSanitizeLine(const std::string &str)
|
||||||
|
{
|
||||||
|
if (str == ".")
|
||||||
|
return " .";
|
||||||
|
std::string sane;
|
||||||
|
sane += str;
|
||||||
|
const char ch = ' ';
|
||||||
|
std::replace_if(sane.begin(), sane.end(), [](unsigned char ch) -> bool { return iscntrl(ch); }, ch);
|
||||||
|
return sane;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToLower(const std::string &str)
|
||||||
|
{
|
||||||
|
std::string lower = str;
|
||||||
|
std::transform(lower.begin(), lower.end(), lower.begin(),
|
||||||
|
[](unsigned char ch) -> unsigned char { return std::tolower(ch); });
|
||||||
|
return lower;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::regex re_ValidMessageID("^<[a-zA-Z0-9$\\._]{2,128}@[a-zA-Z0-9\\-\\.]{2,63}>$");
|
||||||
|
|
||||||
|
bool IsValidMessageID(const std::string &msgid) { return std::regex_search(msgid, re_ValidMessageID) == 1; }
|
||||||
|
|
||||||
|
static const std::regex re_ValidNewsgroup("^[a-zA-Z][a-zA-Z0-9.]{1,128}$");
|
||||||
|
|
||||||
|
bool IsValidNewsgroup(const std::string &msgid) { return std::regex_search(msgid, re_ValidNewsgroup) == 1; }
|
||||||
|
|
||||||
|
std::string StripWhitespaces(const std::string &str)
|
||||||
|
{
|
||||||
|
std::string stripped;
|
||||||
|
for (const auto &ch : str)
|
||||||
|
if (!(std::isspace(ch) || std::iscntrl(ch)))
|
||||||
|
stripped += ch;
|
||||||
|
|
||||||
|
return stripped;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/buffer.hpp>
|
||||||
|
#include <nntpchan/net.hpp>
|
||||||
|
#include <nntpchan/server.hpp>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
Server::Server(uv_loop_t *loop)
|
||||||
|
{
|
||||||
|
m_loop = loop;
|
||||||
|
uv_tcp_init(m_loop, &m_server);
|
||||||
|
m_server.data = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::Close()
|
||||||
|
{
|
||||||
|
std::cout << "Close server" << std::endl;
|
||||||
|
uv_close((uv_handle_t *)&m_server, [](uv_handle_t *s) {
|
||||||
|
Server *self = (Server *)s->data;
|
||||||
|
if (self)
|
||||||
|
delete self;
|
||||||
|
s->data = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::Bind(const std::string &addr)
|
||||||
|
{
|
||||||
|
auto saddr = ParseAddr(addr);
|
||||||
|
assert(uv_tcp_bind(*this, saddr, 0) == 0);
|
||||||
|
auto cb = [](uv_stream_t *s, int status) {
|
||||||
|
Server *self = (Server *)s->data;
|
||||||
|
self->OnAccept(s, status);
|
||||||
|
};
|
||||||
|
assert(uv_listen(*this, 5, cb) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::OnAccept(uv_stream_t *s, int status)
|
||||||
|
{
|
||||||
|
if (status < 0)
|
||||||
|
{
|
||||||
|
OnAcceptError(status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IServerConn *conn = CreateConn(s);
|
||||||
|
assert(conn);
|
||||||
|
m_conns.push_back(conn);
|
||||||
|
conn->Greet();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Server::RemoveConn(IServerConn *conn)
|
||||||
|
{
|
||||||
|
auto itr = m_conns.begin();
|
||||||
|
while (itr != m_conns.end())
|
||||||
|
{
|
||||||
|
if (*itr == conn)
|
||||||
|
itr = m_conns.erase(itr);
|
||||||
|
else
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IConnHandler::QueueLine(const std::string &line) { m_sendlines.push_back(line); }
|
||||||
|
|
||||||
|
bool IConnHandler::HasNextLine() { return m_sendlines.size() > 0; }
|
||||||
|
|
||||||
|
std::string IConnHandler::GetNextLine()
|
||||||
|
{
|
||||||
|
std::string line = m_sendlines[0];
|
||||||
|
m_sendlines.pop_front();
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
IServerConn::IServerConn(uv_loop_t *l, uv_stream_t *st, Server *parent, IConnHandler *h)
|
||||||
|
{
|
||||||
|
m_loop = l;
|
||||||
|
m_parent = parent;
|
||||||
|
m_handler = h;
|
||||||
|
uv_tcp_init(l, &m_conn);
|
||||||
|
m_conn.data = this;
|
||||||
|
uv_accept(st, (uv_stream_t *)&m_conn);
|
||||||
|
uv_read_start((uv_stream_t *)&m_conn,
|
||||||
|
[](uv_handle_t *h, size_t s, uv_buf_t *b) {
|
||||||
|
IServerConn *self = (IServerConn *)h->data;
|
||||||
|
if (self == nullptr)
|
||||||
|
return;
|
||||||
|
b->base = new char[s];
|
||||||
|
},
|
||||||
|
[](uv_stream_t *s, ssize_t nread, const uv_buf_t *b) {
|
||||||
|
IServerConn *self = (IServerConn *)s->data;
|
||||||
|
if (self == nullptr)
|
||||||
|
{
|
||||||
|
if (b->base)
|
||||||
|
delete[] b->base;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nread > 0)
|
||||||
|
{
|
||||||
|
self->m_handler->OnData(b->base, nread);
|
||||||
|
self->SendNextReply();
|
||||||
|
if (self->m_handler->ShouldClose())
|
||||||
|
self->Close();
|
||||||
|
delete[] b->base;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (nread != UV_EOF)
|
||||||
|
{
|
||||||
|
std::cerr << "error in nntp server conn alloc: ";
|
||||||
|
std::cerr << uv_strerror(nread);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
}
|
||||||
|
// got eof or error
|
||||||
|
self->Close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
IServerConn::~IServerConn() { delete m_handler; }
|
||||||
|
|
||||||
|
void IServerConn::SendString(const std::string &str)
|
||||||
|
{
|
||||||
|
WriteBuffer *b = new WriteBuffer(str);
|
||||||
|
uv_write(&b->w, (uv_stream_t *)&m_conn, &b->b, 1, [](uv_write_t *w, int status) {
|
||||||
|
(void)status;
|
||||||
|
WriteBuffer *wb = (WriteBuffer *)w->data;
|
||||||
|
if (wb)
|
||||||
|
delete wb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IServerConn::Close()
|
||||||
|
{
|
||||||
|
m_parent->RemoveConn(this);
|
||||||
|
uv_close((uv_handle_t *)&m_conn, [](uv_handle_t *s) {
|
||||||
|
IServerConn *self = (IServerConn *)s->data;
|
||||||
|
if (self)
|
||||||
|
delete self;
|
||||||
|
s->data = nullptr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
SHA-1 in C
|
||||||
|
By Steve Reid <steve@edmweb.com>
|
||||||
|
100% Public Domain
|
||||||
|
|
||||||
|
Test Vectors (from FIPS PUB 180-1)
|
||||||
|
"abc"
|
||||||
|
A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||||
|
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
|
||||||
|
84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||||
|
A million repetitions of "a"
|
||||||
|
34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
|
||||||
|
/* #define SHA1HANDSOFF * Copies data before messing with it. */
|
||||||
|
|
||||||
|
#include "crypto_old.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#define SHA1HANDSOFF
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* for uint32_t */
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||||
|
|
||||||
|
/* blk0() and blk() perform the initial expand. */
|
||||||
|
/* I got the idea of expanding during the round function from SSLeay */
|
||||||
|
#if BYTE_ORDER == LITTLE_ENDIAN
|
||||||
|
#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF))
|
||||||
|
#elif BYTE_ORDER == BIG_ENDIAN
|
||||||
|
#define blk0(i) block->l[i]
|
||||||
|
#else
|
||||||
|
#error "Endianness not defined!"
|
||||||
|
#endif
|
||||||
|
#define blk(i) \
|
||||||
|
(block->l[i & 15] = \
|
||||||
|
rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
|
||||||
|
|
||||||
|
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
|
||||||
|
#define R0(v, w, x, y, z, i) \
|
||||||
|
z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
|
||||||
|
w = rol(w, 30);
|
||||||
|
#define R1(v, w, x, y, z, i) \
|
||||||
|
z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
|
||||||
|
w = rol(w, 30);
|
||||||
|
#define R2(v, w, x, y, z, i) \
|
||||||
|
z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \
|
||||||
|
w = rol(w, 30);
|
||||||
|
#define R3(v, w, x, y, z, i) \
|
||||||
|
z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
|
||||||
|
w = rol(w, 30);
|
||||||
|
#define R4(v, w, x, y, z, i) \
|
||||||
|
z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
|
||||||
|
w = rol(w, 30);
|
||||||
|
|
||||||
|
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||||
|
|
||||||
|
void SHA1Transform(uint32_t state[5], const unsigned char buffer[64])
|
||||||
|
{
|
||||||
|
uint32_t a, b, c, d, e;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
unsigned char c[64];
|
||||||
|
uint32_t l[16];
|
||||||
|
} CHAR64LONG16;
|
||||||
|
|
||||||
|
#ifdef SHA1HANDSOFF
|
||||||
|
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
|
||||||
|
|
||||||
|
memcpy(block, buffer, 64);
|
||||||
|
#else
|
||||||
|
/* The following had better never be used because it causes the
|
||||||
|
* pointer-to-const buffer to be cast into a pointer to non-const.
|
||||||
|
* And the result is written through. I threw a "const" in, hoping
|
||||||
|
* this will cause a diagnostic.
|
||||||
|
*/
|
||||||
|
CHAR64LONG16 *block = (const CHAR64LONG16 *)buffer;
|
||||||
|
#endif
|
||||||
|
/* Copy context->state[] to working vars */
|
||||||
|
a = state[0];
|
||||||
|
b = state[1];
|
||||||
|
c = state[2];
|
||||||
|
d = state[3];
|
||||||
|
e = state[4];
|
||||||
|
/* 4 rounds of 20 operations each. Loop unrolled. */
|
||||||
|
R0(a, b, c, d, e, 0);
|
||||||
|
R0(e, a, b, c, d, 1);
|
||||||
|
R0(d, e, a, b, c, 2);
|
||||||
|
R0(c, d, e, a, b, 3);
|
||||||
|
R0(b, c, d, e, a, 4);
|
||||||
|
R0(a, b, c, d, e, 5);
|
||||||
|
R0(e, a, b, c, d, 6);
|
||||||
|
R0(d, e, a, b, c, 7);
|
||||||
|
R0(c, d, e, a, b, 8);
|
||||||
|
R0(b, c, d, e, a, 9);
|
||||||
|
R0(a, b, c, d, e, 10);
|
||||||
|
R0(e, a, b, c, d, 11);
|
||||||
|
R0(d, e, a, b, c, 12);
|
||||||
|
R0(c, d, e, a, b, 13);
|
||||||
|
R0(b, c, d, e, a, 14);
|
||||||
|
R0(a, b, c, d, e, 15);
|
||||||
|
R1(e, a, b, c, d, 16);
|
||||||
|
R1(d, e, a, b, c, 17);
|
||||||
|
R1(c, d, e, a, b, 18);
|
||||||
|
R1(b, c, d, e, a, 19);
|
||||||
|
R2(a, b, c, d, e, 20);
|
||||||
|
R2(e, a, b, c, d, 21);
|
||||||
|
R2(d, e, a, b, c, 22);
|
||||||
|
R2(c, d, e, a, b, 23);
|
||||||
|
R2(b, c, d, e, a, 24);
|
||||||
|
R2(a, b, c, d, e, 25);
|
||||||
|
R2(e, a, b, c, d, 26);
|
||||||
|
R2(d, e, a, b, c, 27);
|
||||||
|
R2(c, d, e, a, b, 28);
|
||||||
|
R2(b, c, d, e, a, 29);
|
||||||
|
R2(a, b, c, d, e, 30);
|
||||||
|
R2(e, a, b, c, d, 31);
|
||||||
|
R2(d, e, a, b, c, 32);
|
||||||
|
R2(c, d, e, a, b, 33);
|
||||||
|
R2(b, c, d, e, a, 34);
|
||||||
|
R2(a, b, c, d, e, 35);
|
||||||
|
R2(e, a, b, c, d, 36);
|
||||||
|
R2(d, e, a, b, c, 37);
|
||||||
|
R2(c, d, e, a, b, 38);
|
||||||
|
R2(b, c, d, e, a, 39);
|
||||||
|
R3(a, b, c, d, e, 40);
|
||||||
|
R3(e, a, b, c, d, 41);
|
||||||
|
R3(d, e, a, b, c, 42);
|
||||||
|
R3(c, d, e, a, b, 43);
|
||||||
|
R3(b, c, d, e, a, 44);
|
||||||
|
R3(a, b, c, d, e, 45);
|
||||||
|
R3(e, a, b, c, d, 46);
|
||||||
|
R3(d, e, a, b, c, 47);
|
||||||
|
R3(c, d, e, a, b, 48);
|
||||||
|
R3(b, c, d, e, a, 49);
|
||||||
|
R3(a, b, c, d, e, 50);
|
||||||
|
R3(e, a, b, c, d, 51);
|
||||||
|
R3(d, e, a, b, c, 52);
|
||||||
|
R3(c, d, e, a, b, 53);
|
||||||
|
R3(b, c, d, e, a, 54);
|
||||||
|
R3(a, b, c, d, e, 55);
|
||||||
|
R3(e, a, b, c, d, 56);
|
||||||
|
R3(d, e, a, b, c, 57);
|
||||||
|
R3(c, d, e, a, b, 58);
|
||||||
|
R3(b, c, d, e, a, 59);
|
||||||
|
R4(a, b, c, d, e, 60);
|
||||||
|
R4(e, a, b, c, d, 61);
|
||||||
|
R4(d, e, a, b, c, 62);
|
||||||
|
R4(c, d, e, a, b, 63);
|
||||||
|
R4(b, c, d, e, a, 64);
|
||||||
|
R4(a, b, c, d, e, 65);
|
||||||
|
R4(e, a, b, c, d, 66);
|
||||||
|
R4(d, e, a, b, c, 67);
|
||||||
|
R4(c, d, e, a, b, 68);
|
||||||
|
R4(b, c, d, e, a, 69);
|
||||||
|
R4(a, b, c, d, e, 70);
|
||||||
|
R4(e, a, b, c, d, 71);
|
||||||
|
R4(d, e, a, b, c, 72);
|
||||||
|
R4(c, d, e, a, b, 73);
|
||||||
|
R4(b, c, d, e, a, 74);
|
||||||
|
R4(a, b, c, d, e, 75);
|
||||||
|
R4(e, a, b, c, d, 76);
|
||||||
|
R4(d, e, a, b, c, 77);
|
||||||
|
R4(c, d, e, a, b, 78);
|
||||||
|
R4(b, c, d, e, a, 79);
|
||||||
|
/* Add the working vars back into context.state[] */
|
||||||
|
state[0] += a;
|
||||||
|
state[1] += b;
|
||||||
|
state[2] += c;
|
||||||
|
state[3] += d;
|
||||||
|
state[4] += e;
|
||||||
|
/* Wipe variables */
|
||||||
|
a = b = c = d = e = 0;
|
||||||
|
#ifdef SHA1HANDSOFF
|
||||||
|
memset(block, '\0', sizeof(block));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SHA1Init - Initialize new context */
|
||||||
|
|
||||||
|
void SHA1Init(SHA1_CTX *context)
|
||||||
|
{
|
||||||
|
/* SHA1 initialization constants */
|
||||||
|
context->state[0] = 0x67452301;
|
||||||
|
context->state[1] = 0xEFCDAB89;
|
||||||
|
context->state[2] = 0x98BADCFE;
|
||||||
|
context->state[3] = 0x10325476;
|
||||||
|
context->state[4] = 0xC3D2E1F0;
|
||||||
|
context->count[0] = context->count[1] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run your data through this. */
|
||||||
|
|
||||||
|
void SHA1Update(SHA1_CTX *context, const unsigned char *data, uint32_t len)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
|
||||||
|
uint32_t j;
|
||||||
|
|
||||||
|
j = context->count[0];
|
||||||
|
if ((context->count[0] += len << 3) < j)
|
||||||
|
context->count[1]++;
|
||||||
|
context->count[1] += (len >> 29);
|
||||||
|
j = (j >> 3) & 63;
|
||||||
|
if ((j + len) > 63)
|
||||||
|
{
|
||||||
|
memcpy(&context->buffer[j], data, (i = 64 - j));
|
||||||
|
SHA1Transform(context->state, context->buffer);
|
||||||
|
for (; i + 63 < len; i += 64)
|
||||||
|
{
|
||||||
|
SHA1Transform(context->state, &data[i]);
|
||||||
|
}
|
||||||
|
j = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
i = 0;
|
||||||
|
memcpy(&context->buffer[j], &data[i], len - i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add padding and return the message digest. */
|
||||||
|
|
||||||
|
void SHA1Final(unsigned char digest[20], SHA1_CTX *context)
|
||||||
|
{
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
unsigned char finalcount[8];
|
||||||
|
|
||||||
|
unsigned char c;
|
||||||
|
|
||||||
|
#if 0 /* untested "improvement" by DHR */
|
||||||
|
/* Convert context->count to a sequence of bytes
|
||||||
|
* in finalcount. Second element first, but
|
||||||
|
* big-endian order within element.
|
||||||
|
* But we do it all backwards.
|
||||||
|
*/
|
||||||
|
unsigned char *fcp = &finalcount[8];
|
||||||
|
|
||||||
|
for (i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
uint32_t t = context->count[i];
|
||||||
|
|
||||||
|
int j;
|
||||||
|
|
||||||
|
for (j = 0; j < 4; t >>= 8, j++)
|
||||||
|
*--fcp = (unsigned char) t}
|
||||||
|
#else
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
finalcount[i] =
|
||||||
|
(unsigned char)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
c = 0200;
|
||||||
|
SHA1Update(context, &c, 1);
|
||||||
|
while ((context->count[0] & 504) != 448)
|
||||||
|
{
|
||||||
|
c = 0000;
|
||||||
|
SHA1Update(context, &c, 1);
|
||||||
|
}
|
||||||
|
SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
|
||||||
|
for (i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
digest[i] = (unsigned char)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
|
||||||
|
}
|
||||||
|
/* Wipe variables */
|
||||||
|
memset(context, '\0', sizeof(*context));
|
||||||
|
memset(&finalcount, '\0', sizeof(finalcount));
|
||||||
|
}
|
||||||
|
|
||||||
|
void sha1(uint8_t *hash_out, const uint8_t *str, size_t len)
|
||||||
|
{
|
||||||
|
SHA1_CTX ctx;
|
||||||
|
size_t ii;
|
||||||
|
|
||||||
|
SHA1Init(&ctx);
|
||||||
|
for (ii = 0; ii < len; ii += 1)
|
||||||
|
SHA1Update(&ctx, str + ii, 1);
|
||||||
|
SHA1Final(hash_out, &ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
static inline char nibble_to_char(uint8_t n)
|
||||||
|
{
|
||||||
|
if (n >= 10)
|
||||||
|
return n + 87;
|
||||||
|
else
|
||||||
|
return n + 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string sha1_hex(const std::string &data)
|
||||||
|
{
|
||||||
|
uint8_t digest[20];
|
||||||
|
const uint8_t *ptr = (uint8_t *)data.c_str();
|
||||||
|
sha1(digest, ptr, data.size());
|
||||||
|
std::string out;
|
||||||
|
std::size_t idx = 0;
|
||||||
|
while (idx < 20)
|
||||||
|
{
|
||||||
|
out += nibble_to_char((digest[idx] & 0xf0) >> 8) + nibble_to_char(digest[idx] & 0x0f);
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#include <any>
|
||||||
|
#include <iostream>
|
||||||
|
#include <nntpchan/file_handle.hpp>
|
||||||
|
#include <nntpchan/mime.hpp>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <nntpchan/sha1.hpp>
|
||||||
|
#include <nntpchan/staticfile_frontend.hpp>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
StaticFileFrontend::StaticFileFrontend(TemplateEngine *tmpl, const std::string &templateDir, const std::string &outDir,
|
||||||
|
uint32_t pages)
|
||||||
|
: m_TemplateEngine(tmpl), m_TemplateDir(templateDir), m_OutDir(outDir), m_Pages(pages)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileFrontend::ProcessNewMessage(const fs::path &fpath)
|
||||||
|
{
|
||||||
|
std::clog << "process message " << fpath << std::endl;
|
||||||
|
auto file = OpenFile(fpath, eRead);
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
// read header
|
||||||
|
RawHeader header;
|
||||||
|
if (!ReadHeader(file, header))
|
||||||
|
{
|
||||||
|
std::clog << "failed to read mime header" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read body
|
||||||
|
|
||||||
|
auto findMsgidFunc = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||||
|
auto lower = ToLower(item.first);
|
||||||
|
return (lower == "message-id") || (lower == "messageid");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto msgid_itr = std::find_if(header.begin(), header.end(), findMsgidFunc);
|
||||||
|
if (msgid_itr == std::end(header))
|
||||||
|
{
|
||||||
|
std::clog << "no message id for file " << fpath << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string msgid = StripWhitespaces(msgid_itr->second);
|
||||||
|
|
||||||
|
if (!IsValidMessageID(msgid))
|
||||||
|
{
|
||||||
|
std::clog << "invalid message-id: " << msgid << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rootmsgid;
|
||||||
|
|
||||||
|
auto findReferences = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||||
|
auto lower = ToLower(item.first);
|
||||||
|
return lower == "references";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto references_itr = std::find_if(header.begin(), header.end(), findReferences);
|
||||||
|
if (references_itr == std::end(header) || StripWhitespaces(references_itr->second).size() == 0)
|
||||||
|
{
|
||||||
|
rootmsgid = msgid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto &s = references_itr->second;
|
||||||
|
auto checkfunc = [](unsigned char ch) -> bool { return std::isspace(ch) || std::iscntrl(ch); };
|
||||||
|
if (std::count_if(s.begin(), s.end(), checkfunc))
|
||||||
|
{
|
||||||
|
/** split off first element */
|
||||||
|
auto idx = std::find_if(s.begin(), s.end(), checkfunc);
|
||||||
|
rootmsgid = s.substr(0, s.find(*idx));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rootmsgid = references_itr->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rootmsgid_hash = sha1_hex(rootmsgid);
|
||||||
|
|
||||||
|
std::set<std::string> newsgroups_list;
|
||||||
|
|
||||||
|
auto findNewsgroupsFunc = [](const std::pair<std::string, std::string> &item) -> bool {
|
||||||
|
return ToLower(item.first) == "newsgroups";
|
||||||
|
};
|
||||||
|
|
||||||
|
auto group = std::find_if(header.begin(), header.end(), findNewsgroupsFunc);
|
||||||
|
if (group == std::end(header))
|
||||||
|
{
|
||||||
|
std::clog << "no newsgroups header" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::istringstream input(group->second);
|
||||||
|
|
||||||
|
std::string newsgroup;
|
||||||
|
while (std::getline(input, newsgroup, ' '))
|
||||||
|
{
|
||||||
|
if (IsValidNewsgroup(newsgroup))
|
||||||
|
newsgroups_list.insert(newsgroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path threadFilePath = m_OutDir / fs::path("thread-" + rootmsgid_hash + ".html");
|
||||||
|
nntpchan::model::Thread thread;
|
||||||
|
|
||||||
|
if (!m_MessageDB)
|
||||||
|
{
|
||||||
|
std::clog << "no message database" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_MessageDB->LoadThread(thread, rootmsgid))
|
||||||
|
{
|
||||||
|
std::clog << "cannot find thread with root " << rootmsgid << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TemplateEngine::Args_t thread_args;
|
||||||
|
thread_args["posts"] = thread;
|
||||||
|
if (m_TemplateEngine)
|
||||||
|
{
|
||||||
|
FileHandle_ptr out = OpenFile(threadFilePath, eWrite);
|
||||||
|
if (!out || !m_TemplateEngine->WriteTemplate("thread.mustache", thread_args, out))
|
||||||
|
{
|
||||||
|
std::clog << "failed to write " << threadFilePath << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nntpchan::model::BoardPage page;
|
||||||
|
for (const auto &name : newsgroups_list)
|
||||||
|
{
|
||||||
|
uint32_t pageno = 0;
|
||||||
|
while (pageno < m_Pages)
|
||||||
|
{
|
||||||
|
page.clear();
|
||||||
|
if (!m_MessageDB->LoadBoardPage(page, name, 10, m_Pages))
|
||||||
|
{
|
||||||
|
std::clog << "cannot load board page " << pageno << " for " << name << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TemplateEngine::Args_t page_args;
|
||||||
|
page_args["group"] = name;
|
||||||
|
page_args["threads"] = page;
|
||||||
|
page_args["pageno"] = std::to_string(pageno);
|
||||||
|
if (pageno)
|
||||||
|
page_args["prev_pageno"] = std::to_string(pageno - 1);
|
||||||
|
if (pageno + 1 < m_Pages)
|
||||||
|
page_args["next_pageno"] = std::to_string(pageno + 1);
|
||||||
|
fs::path boardPageFilename(name + "-" + std::to_string(pageno) + ".html");
|
||||||
|
if (m_TemplateEngine)
|
||||||
|
{
|
||||||
|
fs::path outfile = m_OutDir / boardPageFilename;
|
||||||
|
FileHandle_ptr out = OpenFile(outfile, eWrite);
|
||||||
|
if (out)
|
||||||
|
m_TemplateEngine->WriteTemplate("board.mustache", page_args, out);
|
||||||
|
else
|
||||||
|
std::clog << "failed to open board page " << outfile << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
++pageno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StaticFileFrontend::AcceptsNewsgroup(const std::string &newsgroup) { return IsValidNewsgroup(newsgroup); }
|
||||||
|
|
||||||
|
bool StaticFileFrontend::AcceptsMessage(const std::string &msgid) { return IsValidMessageID(msgid); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <nntpchan/crypto.hpp>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <nntpchan/storage.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
const fs::path posts_skiplist_dir = "posts";
|
||||||
|
const fs::path threads_skiplist_dir = "threads";
|
||||||
|
|
||||||
|
ArticleStorage::ArticleStorage(const fs::path &fpath) { SetPath(fpath); }
|
||||||
|
|
||||||
|
ArticleStorage::~ArticleStorage() {}
|
||||||
|
|
||||||
|
void ArticleStorage::SetPath(const fs::path &fpath)
|
||||||
|
{
|
||||||
|
basedir = fpath;
|
||||||
|
fs::create_directories(basedir);
|
||||||
|
assert(init_skiplist(posts_skiplist_dir));
|
||||||
|
assert(init_skiplist(threads_skiplist_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ArticleStorage::init_skiplist(const std::string &subdir) const
|
||||||
|
{
|
||||||
|
fs::path skiplist = skiplist_root(subdir);
|
||||||
|
const auto subdirs = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||||
|
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||||
|
'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
|
||||||
|
'y', 'z', '2', '3', '4', '5', '6', '7',
|
||||||
|
};
|
||||||
|
for (const auto &s : subdirs)
|
||||||
|
fs::create_directories(skiplist / std::string(&s, 1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticleStorage::Accept(const std::string &msgid) const
|
||||||
|
{
|
||||||
|
if (!IsValidMessageID(msgid))
|
||||||
|
return false;
|
||||||
|
auto p = MessagePath(msgid);
|
||||||
|
return !fs::exists(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::path ArticleStorage::MessagePath(const std::string &msgid) const { return basedir / msgid; }
|
||||||
|
|
||||||
|
FileHandle_ptr ArticleStorage::OpenRead(const std::string &msgid) const { return OpenFile(MessagePath(msgid), eRead); }
|
||||||
|
|
||||||
|
FileHandle_ptr ArticleStorage::OpenWrite(const std::string &msgid) const
|
||||||
|
{
|
||||||
|
return OpenFile(MessagePath(msgid), eWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticleStorage::LoadBoardPage(BoardPage &board, const std::string &newsgroup, uint32_t perpage,
|
||||||
|
uint32_t page) const
|
||||||
|
{
|
||||||
|
(void)board;
|
||||||
|
(void)newsgroup;
|
||||||
|
(void)perpage;
|
||||||
|
(void)page;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ArticleStorage::FindThreadByHash(const std::string &hashhex, std::string &msgid) const
|
||||||
|
{
|
||||||
|
(void)hashhex;
|
||||||
|
(void)msgid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ArticleStorage::LoadThread(Thread &thread, const std::string &rootmsgid) const
|
||||||
|
{
|
||||||
|
(void)thread;
|
||||||
|
(void)rootmsgid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ensure symlinks are formed for this article by message id */
|
||||||
|
void ArticleStorage::EnsureSymlinks(const std::string &msgid) const
|
||||||
|
{
|
||||||
|
std::string msgidhash = Blake2B_base32(msgid);
|
||||||
|
skiplist_dir(posts_skiplist_dir, msgidhash);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fs::path ArticleStorage::skiplist_root(const std::string & name ) const
|
||||||
|
{
|
||||||
|
return basedir / name;
|
||||||
|
}
|
||||||
|
fs::path ArticleStorage::skiplist_dir(const fs::path & root, const std::string & name ) const
|
||||||
|
{
|
||||||
|
return root / name.substr(0, 1) ;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <mstch/mstch.hpp>
|
||||||
|
#include <nntpchan/sanitize.hpp>
|
||||||
|
#include <nntpchan/template_engine.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace nntpchan
|
||||||
|
{
|
||||||
|
|
||||||
|
template <class... Ts> struct overloaded : Ts...
|
||||||
|
{
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
template <class... Ts> overloaded(Ts...)->overloaded<Ts...>;
|
||||||
|
|
||||||
|
namespace mustache = mstch;
|
||||||
|
|
||||||
|
static mustache::map post_to_map(const nntpchan::model::Post &post)
|
||||||
|
{
|
||||||
|
mustache::map m;
|
||||||
|
mustache::array attachments;
|
||||||
|
mustache::map h;
|
||||||
|
|
||||||
|
for (const auto &att : nntpchan::model::GetAttachments(post))
|
||||||
|
{
|
||||||
|
mustache::map a;
|
||||||
|
a["filename"] = nntpchan::model::GetFilename(att);
|
||||||
|
a["hexdigest"] = nntpchan::model::GetHexDigest(att);
|
||||||
|
a["thumbnail"] = nntpchan::model::GetThumbnail(att);
|
||||||
|
attachments.push_back(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &item : nntpchan::model::GetHeader(post))
|
||||||
|
{
|
||||||
|
mustache::array vals;
|
||||||
|
for (const auto &v : item.second)
|
||||||
|
vals.push_back(v);
|
||||||
|
h[item.first] = vals;
|
||||||
|
}
|
||||||
|
|
||||||
|
m["attachments"] = attachments;
|
||||||
|
m["message"] = nntpchan::model::GetBody(post);
|
||||||
|
m["header"] = h;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mustache::map thread_to_map(const nntpchan::model::Thread &t)
|
||||||
|
{
|
||||||
|
mustache::map thread;
|
||||||
|
mustache::array posts;
|
||||||
|
for (const auto &post : t)
|
||||||
|
{
|
||||||
|
posts.push_back(post_to_map(post));
|
||||||
|
}
|
||||||
|
auto &opHeader = nntpchan::model::GetHeader(t[0]);
|
||||||
|
thread["title"] = nntpchan::model::HeaderIFind(opHeader, "subject", "None")[0];
|
||||||
|
thread["posts"] = posts;
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MustacheTemplateEngine : public TemplateEngine
|
||||||
|
{
|
||||||
|
struct Impl
|
||||||
|
{
|
||||||
|
|
||||||
|
Impl(const std::map<std::string, std::string> &partials) : m_partials(partials) {}
|
||||||
|
|
||||||
|
bool ParseTemplate(const FileHandle_ptr &in)
|
||||||
|
{
|
||||||
|
std::stringstream str;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(*in, line))
|
||||||
|
str << line << "\n";
|
||||||
|
m_tmplString = str.str();
|
||||||
|
return in->eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderFile(const Args_t &args, const FileHandle_ptr &out)
|
||||||
|
{
|
||||||
|
mustache::map obj;
|
||||||
|
for (const auto &item : args)
|
||||||
|
{
|
||||||
|
std::visit(overloaded{[&obj, item](const nntpchan::model::Model &m) {
|
||||||
|
std::visit(overloaded{[&obj, item](const nntpchan::model::BoardPage &p) {
|
||||||
|
mustache::array threads;
|
||||||
|
for (const auto &thread : p)
|
||||||
|
{
|
||||||
|
threads.push_back(thread_to_map(thread));
|
||||||
|
}
|
||||||
|
obj[item.first] = threads;
|
||||||
|
},
|
||||||
|
[&obj, item](const nntpchan::model::Thread &t) {
|
||||||
|
obj[item.first] = thread_to_map(t);
|
||||||
|
}},
|
||||||
|
m);
|
||||||
|
},
|
||||||
|
[&obj, item](const std::string &str) { obj[item.first] = str; }},
|
||||||
|
item.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string str = mustache::render(m_tmplString, obj);
|
||||||
|
out->write(str.c_str(), str.size());
|
||||||
|
out->flush();
|
||||||
|
return !out->fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string m_tmplString;
|
||||||
|
const std::map<std::string, std::string> &m_partials;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual bool WriteTemplate(const fs::path &fpath, const Args_t &args, const FileHandle_ptr &out)
|
||||||
|
{
|
||||||
|
auto templFile = OpenFile(fpath, eRead);
|
||||||
|
if (!templFile)
|
||||||
|
{
|
||||||
|
std::clog << "no such template at " << fpath << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, std::string> partials;
|
||||||
|
if (!LoadPartials(fpath.parent_path(), partials))
|
||||||
|
{
|
||||||
|
std::clog << "failed to load partials" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Impl impl(partials);
|
||||||
|
if (impl.ParseTemplate(templFile))
|
||||||
|
{
|
||||||
|
return impl.RenderFile(args, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::clog << "failed to parse template " << fpath << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadPartials(fs::path dir, std::map<std::string, std::string> &partials)
|
||||||
|
{
|
||||||
|
const auto partial_files = {"header", "footer"};
|
||||||
|
for (const auto &fname : partial_files)
|
||||||
|
{
|
||||||
|
auto file = OpenFile(dir / fs::path(fname + std::string(".html")), eRead);
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
std::clog << "no such partial: " << fname << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string line;
|
||||||
|
std::stringstream input;
|
||||||
|
while (std::getline(*file, line))
|
||||||
|
input << line << "\n";
|
||||||
|
partials[fname] = input.str();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TemplateEngine *CreateTemplateEngine(const std::string &dialect)
|
||||||
|
{
|
||||||
|
auto d = ToLower(dialect);
|
||||||
|
if (d == "mustache")
|
||||||
|
return new MustacheTemplateEngine;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
#include "base64.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
// taken from i2pd
|
|
||||||
namespace i2p
|
|
||||||
{
|
|
||||||
namespace data
|
|
||||||
{
|
|
||||||
static void iT64Build(void);
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* BASE64 Substitution Table
|
|
||||||
* -------------------------
|
|
||||||
*
|
|
||||||
* Direct Substitution Table
|
|
||||||
*/
|
|
||||||
|
|
||||||
static const char T64[64] = {
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
|
||||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
|
||||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
|
||||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
|
||||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
||||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
|
||||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
|
||||||
'4', '5', '6', '7', '8', '9', '+', '/'
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Reverse Substitution Table (built in run time)
|
|
||||||
*/
|
|
||||||
|
|
||||||
static char iT64[256];
|
|
||||||
static int isFirstTime = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Padding
|
|
||||||
*/
|
|
||||||
|
|
||||||
static char P64 = '=';
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* ByteStreamToBase64
|
|
||||||
* ------------------
|
|
||||||
*
|
|
||||||
* Converts binary encoded data to BASE64 format.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static size_t /* Number of bytes in the encoded buffer */
|
|
||||||
ByteStreamToBase64 (
|
|
||||||
const uint8_t * InBuffer, /* Input buffer, binary data */
|
|
||||||
size_t InCount, /* Number of bytes in the input buffer */
|
|
||||||
char * OutBuffer, /* output buffer */
|
|
||||||
size_t len /* length of output buffer */
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
|
||||||
unsigned char * ps;
|
|
||||||
unsigned char * pd;
|
|
||||||
unsigned char acc_1;
|
|
||||||
unsigned char acc_2;
|
|
||||||
int i;
|
|
||||||
int n;
|
|
||||||
int m;
|
|
||||||
size_t outCount;
|
|
||||||
|
|
||||||
ps = (unsigned char *)InBuffer;
|
|
||||||
n = InCount/3;
|
|
||||||
m = InCount%3;
|
|
||||||
if (!m)
|
|
||||||
outCount = 4*n;
|
|
||||||
else
|
|
||||||
outCount = 4*(n+1);
|
|
||||||
if (outCount > len) return 0;
|
|
||||||
pd = (unsigned char *)OutBuffer;
|
|
||||||
for ( i = 0; i<n; i++ ){
|
|
||||||
acc_1 = *ps++;
|
|
||||||
acc_2 = (acc_1<<4)&0x30;
|
|
||||||
acc_1 >>= 2; /* base64 digit #1 */
|
|
||||||
*pd++ = T64[acc_1];
|
|
||||||
acc_1 = *ps++;
|
|
||||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
|
||||||
*pd++ = T64[acc_2];
|
|
||||||
acc_1 &= 0x0f;
|
|
||||||
acc_1 <<=2;
|
|
||||||
acc_2 = *ps++;
|
|
||||||
acc_1 |= acc_2>>6; /* base64 digit #3 */
|
|
||||||
*pd++ = T64[acc_1];
|
|
||||||
acc_2 &= 0x3f; /* base64 digit #4 */
|
|
||||||
*pd++ = T64[acc_2];
|
|
||||||
}
|
|
||||||
if ( m == 1 ){
|
|
||||||
acc_1 = *ps++;
|
|
||||||
acc_2 = (acc_1<<4)&0x3f; /* base64 digit #2 */
|
|
||||||
acc_1 >>= 2; /* base64 digit #1 */
|
|
||||||
*pd++ = T64[acc_1];
|
|
||||||
*pd++ = T64[acc_2];
|
|
||||||
*pd++ = P64;
|
|
||||||
*pd++ = P64;
|
|
||||||
|
|
||||||
}
|
|
||||||
else if ( m == 2 ){
|
|
||||||
acc_1 = *ps++;
|
|
||||||
acc_2 = (acc_1<<4)&0x3f;
|
|
||||||
acc_1 >>= 2; /* base64 digit #1 */
|
|
||||||
*pd++ = T64[acc_1];
|
|
||||||
acc_1 = *ps++;
|
|
||||||
acc_2 |= acc_1 >> 4; /* base64 digit #2 */
|
|
||||||
*pd++ = T64[acc_2];
|
|
||||||
acc_1 &= 0x0f;
|
|
||||||
acc_1 <<=2; /* base64 digit #3 */
|
|
||||||
*pd++ = T64[acc_1];
|
|
||||||
*pd++ = P64;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* Base64ToByteStream
|
|
||||||
* ------------------
|
|
||||||
*
|
|
||||||
* Converts BASE64 encoded data to binary format. If input buffer is
|
|
||||||
* not properly padded, buffer of negative length is returned
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
static
|
|
||||||
ssize_t /* Number of output bytes */
|
|
||||||
Base64ToByteStream (
|
|
||||||
const char * InBuffer, /* BASE64 encoded buffer */
|
|
||||||
size_t InCount, /* Number of input bytes */
|
|
||||||
uint8_t * OutBuffer, /* output buffer length */
|
|
||||||
size_t len /* length of output buffer */
|
|
||||||
)
|
|
||||||
{
|
|
||||||
unsigned char * ps;
|
|
||||||
unsigned char * pd;
|
|
||||||
unsigned char acc_1;
|
|
||||||
unsigned char acc_2;
|
|
||||||
int i;
|
|
||||||
int n;
|
|
||||||
int m;
|
|
||||||
size_t outCount;
|
|
||||||
|
|
||||||
if (isFirstTime) iT64Build();
|
|
||||||
n = InCount/4;
|
|
||||||
m = InCount%4;
|
|
||||||
if (InCount && !m)
|
|
||||||
outCount = 3*n;
|
|
||||||
else {
|
|
||||||
outCount = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ps = (unsigned char *)(InBuffer + InCount - 1);
|
|
||||||
while ( *ps-- == P64 ) outCount--;
|
|
||||||
ps = (unsigned char *)InBuffer;
|
|
||||||
|
|
||||||
if (outCount > len) return -1;
|
|
||||||
pd = OutBuffer;
|
|
||||||
auto endOfOutBuffer = OutBuffer + outCount;
|
|
||||||
for ( i = 0; i < n; i++ ){
|
|
||||||
acc_1 = iT64[*ps++];
|
|
||||||
acc_2 = iT64[*ps++];
|
|
||||||
acc_1 <<= 2;
|
|
||||||
acc_1 |= acc_2>>4;
|
|
||||||
*pd++ = acc_1;
|
|
||||||
if (pd >= endOfOutBuffer) break;
|
|
||||||
|
|
||||||
acc_2 <<= 4;
|
|
||||||
acc_1 = iT64[*ps++];
|
|
||||||
acc_2 |= acc_1 >> 2;
|
|
||||||
*pd++ = acc_2;
|
|
||||||
if (pd >= endOfOutBuffer) break;
|
|
||||||
|
|
||||||
acc_2 = iT64[*ps++];
|
|
||||||
acc_2 |= acc_1 << 6;
|
|
||||||
*pd++ = acc_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return outCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t Base64EncodingBufferSize (const size_t input_size)
|
|
||||||
{
|
|
||||||
auto d = div (input_size, 3);
|
|
||||||
if (d.rem) d.quot++;
|
|
||||||
return 4*d.quot;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* iT64
|
|
||||||
* ----
|
|
||||||
* Reverse table builder. P64 character is replaced with 0
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void iT64Build()
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
isFirstTime = 0;
|
|
||||||
for ( i=0; i<256; i++ ) iT64[i] = -1;
|
|
||||||
for ( i=0; i<64; i++ ) iT64[(int)T64[i]] = i;
|
|
||||||
iT64[(int)P64] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
std::string B64Encode(const uint8_t * data, const std::size_t l)
|
|
||||||
{
|
|
||||||
std::string out;
|
|
||||||
out.resize(i2p::data::Base64EncodingBufferSize(l));
|
|
||||||
i2p::data::ByteStreamToBase64(data, l, &out[0], out.size());
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool B64Decode(const std::string & data, std::vector<uint8_t> & out)
|
|
||||||
{
|
|
||||||
out.resize(data.size());
|
|
||||||
if(i2p::data::Base64ToByteStream(data.c_str(), data.size(), &out[0], out.size()) == -1) return false;
|
|
||||||
out.shrink_to_fit();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_BASE64_HPP
|
|
||||||
#define NNTPCHAN_BASE64_HPP
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
/** returns base64 encoded string */
|
|
||||||
std::string B64Encode(const uint8_t * data, const std::size_t l);
|
|
||||||
|
|
||||||
/** @brief returns true if decode was successful */
|
|
||||||
bool B64Decode(const std::string & data, std::vector<uint8_t> & out);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "buffer.hpp"
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
WriteBuffer::WriteBuffer(const char * b, const size_t s)
|
|
||||||
{
|
|
||||||
char * buf = new char[s];
|
|
||||||
std::memcpy(buf, b, s);
|
|
||||||
this->b = uv_buf_init(buf, s);
|
|
||||||
w.data = this;
|
|
||||||
};
|
|
||||||
|
|
||||||
WriteBuffer::WriteBuffer(const std::string & s) : WriteBuffer(s.c_str(), s.size()) {}
|
|
||||||
|
|
||||||
WriteBuffer::~WriteBuffer()
|
|
||||||
{
|
|
||||||
delete [] b.base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_BUFFER_HPP
|
|
||||||
#define NNTPCHAN_BUFFER_HPP
|
|
||||||
#include <uv.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
struct WriteBuffer
|
|
||||||
{
|
|
||||||
uv_write_t w;
|
|
||||||
uv_buf_t b;
|
|
||||||
|
|
||||||
WriteBuffer(const std::string & s);
|
|
||||||
WriteBuffer(const char * b, const size_t s);
|
|
||||||
~WriteBuffer();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#include "crypto.hpp"
|
|
||||||
#include <sodium.h>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
void SHA512(const uint8_t * d, const std::size_t l, SHA512Digest & h)
|
|
||||||
{
|
|
||||||
crypto_hash(h.data(), d, l);
|
|
||||||
}
|
|
||||||
|
|
||||||
Crypto::Crypto()
|
|
||||||
{
|
|
||||||
assert(sodium_init() == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Crypto::~Crypto()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_CRYPTO_HPP
|
|
||||||
#define NNTPCHAN_CRYPTO_HPP
|
|
||||||
|
|
||||||
#include <sodium/crypto_hash.h>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
typedef std::array<uint8_t, crypto_hash_BYTES> SHA512Digest;
|
|
||||||
|
|
||||||
void SHA512(const uint8_t * d, std::size_t l, SHA512Digest & h);
|
|
||||||
|
|
||||||
/** global crypto initializer */
|
|
||||||
struct Crypto
|
|
||||||
{
|
|
||||||
Crypto();
|
|
||||||
~Crypto();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#include "event.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
Mainloop::Mainloop()
|
|
||||||
{
|
|
||||||
m_loop = uv_default_loop();
|
|
||||||
assert(uv_loop_init(m_loop) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mainloop::~Mainloop()
|
|
||||||
{
|
|
||||||
uv_loop_close(m_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mainloop::Stop()
|
|
||||||
{
|
|
||||||
uv_stop(m_loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mainloop::Run(uv_run_mode mode)
|
|
||||||
{
|
|
||||||
assert(uv_run(m_loop, mode) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_EVENT_HPP
|
|
||||||
#define NNTPCHAN_EVENT_HPP
|
|
||||||
#include <uv.h>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
class Mainloop
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
Mainloop();
|
|
||||||
~Mainloop();
|
|
||||||
|
|
||||||
operator uv_loop_t * () const { return m_loop; }
|
|
||||||
|
|
||||||
void Run(uv_run_mode mode = UV_RUN_DEFAULT);
|
|
||||||
void Stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
uv_loop_t * m_loop;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#include "exec_frontend.hpp"
|
|
||||||
#include <cstring>
|
|
||||||
#include <iostream>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/wait.h>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
ExecFrontend::ExecFrontend(const std::string & fname) :
|
|
||||||
m_exec(fname)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ExecFrontend::~ExecFrontend() {}
|
|
||||||
|
|
||||||
void ExecFrontend::ProcessNewMessage(const std::string & fpath)
|
|
||||||
{
|
|
||||||
Exec({"post", fpath});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExecFrontend::AcceptsNewsgroup(const std::string & newsgroup)
|
|
||||||
{
|
|
||||||
return Exec({"newsgroup", newsgroup}) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExecFrontend::AcceptsMessage(const std::string & msgid)
|
|
||||||
{
|
|
||||||
return Exec({"msgid", msgid}) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ExecFrontend::Exec(std::deque<std::string> args)
|
|
||||||
{
|
|
||||||
// set up arguments
|
|
||||||
const char ** cargs = new char const *[args.size() +2];
|
|
||||||
std::size_t l = 0;
|
|
||||||
cargs[l++] = m_exec.c_str();
|
|
||||||
while (args.size()) {
|
|
||||||
cargs[l++] = args.front().c_str();
|
|
||||||
args.pop_front();
|
|
||||||
}
|
|
||||||
cargs[l] = 0;
|
|
||||||
int retcode = 0;
|
|
||||||
pid_t child = fork();
|
|
||||||
if(child) {
|
|
||||||
waitpid(child, &retcode, 0);
|
|
||||||
} else {
|
|
||||||
int r = execvpe(m_exec.c_str(),(char * const *) cargs, environ);
|
|
||||||
if ( r == -1 ) {
|
|
||||||
std::cout << strerror(errno) << std::endl;
|
|
||||||
exit( errno );
|
|
||||||
} else
|
|
||||||
exit(r);
|
|
||||||
}
|
|
||||||
return retcode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_EXEC_FRONTEND_HPP
|
|
||||||
#define NNTPCHAN_EXEC_FRONTEND_HPP
|
|
||||||
#include "frontend.hpp"
|
|
||||||
#include <deque>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
class ExecFrontend : public Frontend
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
ExecFrontend(const std::string & exe);
|
|
||||||
|
|
||||||
~ExecFrontend();
|
|
||||||
|
|
||||||
void ProcessNewMessage(const std::string & fpath);
|
|
||||||
bool AcceptsNewsgroup(const std::string & newsgroup);
|
|
||||||
bool AcceptsMessage(const std::string & msgid);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
int Exec(std::deque<std::string> args);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_exec;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_FRONTEND_HPP
|
|
||||||
#define NNTPCHAN_FRONTEND_HPP
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
/** @brief nntpchan frontend ui interface */
|
|
||||||
class Frontend
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual ~Frontend() {}
|
|
||||||
|
|
||||||
/** @brief process an inbound message stored at fpath that we have accepted. */
|
|
||||||
virtual void ProcessNewMessage(const std::string & fpath) = 0;
|
|
||||||
|
|
||||||
/** @brief return true if we take posts in a newsgroup */
|
|
||||||
virtual bool AcceptsNewsgroup(const std::string & newsgroup) = 0;
|
|
||||||
|
|
||||||
/** @brief return true if we will accept a message given its message-id */
|
|
||||||
virtual bool AcceptsMessage(const std::string & msgid) = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include "line.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan {
|
|
||||||
|
|
||||||
LineReader::LineReader(size_t limit) : m_close(false), lineLimit(limit) {}
|
|
||||||
|
|
||||||
void LineReader::Data(const char * data, ssize_t l)
|
|
||||||
{
|
|
||||||
if(l <= 0) return;
|
|
||||||
// process leftovers
|
|
||||||
std::size_t idx = 0;
|
|
||||||
std::size_t pos = 0;
|
|
||||||
while(l-- > 0) {
|
|
||||||
char c = data[idx++];
|
|
||||||
if(c == '\n') {
|
|
||||||
OnLine(data, pos);
|
|
||||||
pos = 0;
|
|
||||||
data += idx;
|
|
||||||
} else if (c == '\r' && data[idx] == '\n') {
|
|
||||||
OnLine(data, pos);
|
|
||||||
data += idx + 1;
|
|
||||||
pos = 0;
|
|
||||||
} else {
|
|
||||||
pos ++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LineReader::OnLine(const char *d, const size_t l)
|
|
||||||
{
|
|
||||||
std::string line(d, l);
|
|
||||||
HandleLine(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LineReader::ShouldClose()
|
|
||||||
{
|
|
||||||
return m_close;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_LINE_HPP
|
|
||||||
#define NNTPCHAN_LINE_HPP
|
|
||||||
#include "server.hpp"
|
|
||||||
#include <stdint.h>
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
|
|
||||||
/** @brief a buffered line reader */
|
|
||||||
class LineReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
LineReader(size_t lineLimit);
|
|
||||||
|
|
||||||
/** @brief queue inbound data from connection */
|
|
||||||
void Data(const char * data, ssize_t s);
|
|
||||||
|
|
||||||
/** implements IConnHandler */
|
|
||||||
virtual bool ShouldClose();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/** @brief handle a line from the client */
|
|
||||||
virtual void HandleLine(const std::string & line) = 0;
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
void OnLine(const char * d, const size_t l);
|
|
||||||
std::string m_leftovers;
|
|
||||||
bool m_close;
|
|
||||||
const size_t lineLimit;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#include "message.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
bool IsValidMessageID(const std::string & msgid)
|
|
||||||
{
|
|
||||||
if(msgid[0] != '<') return false;
|
|
||||||
if(msgid[msgid.size()-1] != '>') return false;
|
|
||||||
auto itr = msgid.begin() + 1;
|
|
||||||
auto end = msgid.end() - 1;
|
|
||||||
bool atfound = false;
|
|
||||||
while(itr != end) {
|
|
||||||
auto c = *itr;
|
|
||||||
++itr;
|
|
||||||
if(atfound && c == '@') return false;
|
|
||||||
if(c == '@') {
|
|
||||||
atfound = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (c == '$' || c == '_' || c == '-' || c == '.') continue;
|
|
||||||
if (c >= '0' && c <= '9') continue;
|
|
||||||
if (c >= 'A' && c <= 'Z') continue;
|
|
||||||
if (c >= 'a' && c <= 'z') continue;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_MESSAGE_HPP
|
|
||||||
#define NNTPCHAN_MESSAGE_HPP
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
#include <map>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
bool IsValidMessageID(const std::string & msgid);
|
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> MessageHeader;
|
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> MIMEPartHeader;
|
|
||||||
|
|
||||||
typedef std::function<bool(const MessageHeader &)> MessageHeaderFilter;
|
|
||||||
|
|
||||||
typedef std::function<bool(const MIMEPartHeader &)> MIMEPartFilter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
read MIME message from i,
|
|
||||||
filter each header with h,
|
|
||||||
filter each part with p,
|
|
||||||
store result in o
|
|
||||||
|
|
||||||
return true if we read the whole message, return false if there is remaining
|
|
||||||
*/
|
|
||||||
bool StoreMIMEMessage(std::istream & i, MessageHeaderFilter h, MIMEPartHeader p, std::ostream & o);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
/*
|
|
||||||
Author: José Bollo <jobol@nonadev.net>
|
|
||||||
Author: José Bollo <jose.bollo@iot.bzh>
|
|
||||||
|
|
||||||
https://gitlab.com/jobol/mustach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "mustache.hpp"
|
|
||||||
|
|
||||||
#define NAME_LENGTH_MAX 1024
|
|
||||||
#define DEPTH_MAX 256
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
namespace mustache
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
static int getpartial(struct mustach_itf *itf, void *closure, const char *name, char **result)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
FILE *file;
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
*result = NULL;
|
|
||||||
file = open_memstream(result, &size);
|
|
||||||
if (file == NULL)
|
|
||||||
rc = MUSTACH_ERROR_SYSTEM;
|
|
||||||
else {
|
|
||||||
rc = itf->put(closure, name, 0, file);
|
|
||||||
if (rc == 0)
|
|
||||||
/* adds terminating null */
|
|
||||||
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
|
|
||||||
fclose(file);
|
|
||||||
if (rc < 0) {
|
|
||||||
free(*result);
|
|
||||||
*result = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int process(const char *templ, struct mustach_itf *itf, void *closure, FILE *file, const char *opstr, const char *clstr)
|
|
||||||
{
|
|
||||||
char name[NAME_LENGTH_MAX + 1], *partial, c;
|
|
||||||
const char *beg, *term;
|
|
||||||
struct { const char *name, *again; size_t length; int emit, entered; } stack[DEPTH_MAX];
|
|
||||||
size_t oplen, cllen, len, l;
|
|
||||||
int depth, rc, emit;
|
|
||||||
|
|
||||||
emit = 1;
|
|
||||||
oplen = strlen(opstr);
|
|
||||||
cllen = strlen(clstr);
|
|
||||||
depth = 0;
|
|
||||||
for(;;) {
|
|
||||||
beg = strstr(templ, opstr);
|
|
||||||
if (beg == NULL) {
|
|
||||||
/* no more mustach */
|
|
||||||
if (emit)
|
|
||||||
fwrite(templ, strlen(templ), 1, file);
|
|
||||||
return depth ? MUSTACH_ERROR_UNEXPECTED_END : 0;
|
|
||||||
}
|
|
||||||
if (emit)
|
|
||||||
fwrite(templ, (size_t)(beg - templ), 1, file);
|
|
||||||
beg += oplen;
|
|
||||||
term = strstr(beg, clstr);
|
|
||||||
if (term == NULL)
|
|
||||||
return MUSTACH_ERROR_UNEXPECTED_END;
|
|
||||||
templ = term + cllen;
|
|
||||||
len = (size_t)(term - beg);
|
|
||||||
c = *beg;
|
|
||||||
switch(c) {
|
|
||||||
case '!':
|
|
||||||
case '=':
|
|
||||||
break;
|
|
||||||
case '{':
|
|
||||||
for (l = 0 ; clstr[l] == '}' ; l++);
|
|
||||||
if (clstr[l]) {
|
|
||||||
if (!len || beg[len-1] != '}')
|
|
||||||
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
|
|
||||||
len--;
|
|
||||||
} else {
|
|
||||||
if (term[l] != '}')
|
|
||||||
return MUSTACH_ERROR_BAD_UNESCAPE_TAG;
|
|
||||||
templ++;
|
|
||||||
}
|
|
||||||
c = '&';
|
|
||||||
case '^':
|
|
||||||
case '#':
|
|
||||||
case '/':
|
|
||||||
case '&':
|
|
||||||
case '>':
|
|
||||||
#if !defined(NO_EXTENSION_FOR_MUSTACH) && !defined(NO_COLON_EXTENSION_FOR_MUSTACH)
|
|
||||||
case ':':
|
|
||||||
#endif
|
|
||||||
beg++; len--;
|
|
||||||
default:
|
|
||||||
while (len && isspace(beg[0])) { beg++; len--; }
|
|
||||||
while (len && isspace(beg[len-1])) len--;
|
|
||||||
if (len == 0)
|
|
||||||
return MUSTACH_ERROR_EMPTY_TAG;
|
|
||||||
if (len > NAME_LENGTH_MAX)
|
|
||||||
return MUSTACH_ERROR_TAG_TOO_LONG;
|
|
||||||
memcpy(name, beg, len);
|
|
||||||
name[len] = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch(c) {
|
|
||||||
case '!':
|
|
||||||
/* comment */
|
|
||||||
/* nothing to do */
|
|
||||||
break;
|
|
||||||
case '=':
|
|
||||||
/* defines separators */
|
|
||||||
if (len < 5 || beg[len - 1] != '=')
|
|
||||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
|
||||||
beg++;
|
|
||||||
len -= 2;
|
|
||||||
for (l = 0; l < len && !isspace(beg[l]) ; l++);
|
|
||||||
if (l == len)
|
|
||||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
|
||||||
opstr = strndupa(beg, l);
|
|
||||||
while (l < len && isspace(beg[l])) l++;
|
|
||||||
if (l == len)
|
|
||||||
return MUSTACH_ERROR_BAD_SEPARATORS;
|
|
||||||
clstr = strndupa(beg + l, len - l);
|
|
||||||
oplen = strlen(opstr);
|
|
||||||
cllen = strlen(clstr);
|
|
||||||
break;
|
|
||||||
case '^':
|
|
||||||
case '#':
|
|
||||||
/* begin section */
|
|
||||||
if (depth == DEPTH_MAX)
|
|
||||||
return MUSTACH_ERROR_TOO_DEPTH;
|
|
||||||
rc = emit;
|
|
||||||
if (rc) {
|
|
||||||
rc = itf->enter(closure, name);
|
|
||||||
if (rc < 0)
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
stack[depth].name = beg;
|
|
||||||
stack[depth].again = templ;
|
|
||||||
stack[depth].length = len;
|
|
||||||
stack[depth].emit = emit;
|
|
||||||
stack[depth].entered = rc;
|
|
||||||
if ((c == '#') == (rc == 0))
|
|
||||||
emit = 0;
|
|
||||||
depth++;
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
/* end section */
|
|
||||||
if (depth-- == 0 || len != stack[depth].length || memcmp(stack[depth].name, name, len))
|
|
||||||
return MUSTACH_ERROR_CLOSING;
|
|
||||||
rc = emit && stack[depth].entered ? itf->next(closure) : 0;
|
|
||||||
if (rc < 0)
|
|
||||||
return rc;
|
|
||||||
if (rc) {
|
|
||||||
templ = stack[depth++].again;
|
|
||||||
} else {
|
|
||||||
emit = stack[depth].emit;
|
|
||||||
if (emit && stack[depth].entered)
|
|
||||||
itf->leave(closure);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '>':
|
|
||||||
/* partials */
|
|
||||||
if (emit) {
|
|
||||||
rc = getpartial(itf, closure, name, &partial);
|
|
||||||
if (rc == 0) {
|
|
||||||
rc = process(partial, itf, closure, file, opstr, clstr);
|
|
||||||
free(partial);
|
|
||||||
}
|
|
||||||
if (rc < 0)
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* replacement */
|
|
||||||
if (emit) {
|
|
||||||
rc = itf->put(closure, name, c != '&', file);
|
|
||||||
if (rc < 0)
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file)
|
|
||||||
{
|
|
||||||
int rc = itf->start ? itf->start(closure) : 0;
|
|
||||||
if (rc == 0)
|
|
||||||
rc = process(templ, itf, closure, file, "{{", "}}");
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fdmustach(const char *templ, struct mustach_itf *itf, void *closure, int fd)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
FILE *file;
|
|
||||||
|
|
||||||
file = fdopen(fd, "w");
|
|
||||||
if (file == NULL) {
|
|
||||||
rc = MUSTACH_ERROR_SYSTEM;
|
|
||||||
errno = ENOMEM;
|
|
||||||
} else {
|
|
||||||
rc = fmustach(templ, itf, closure, file);
|
|
||||||
fclose(file);
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int mustach(const char *templ, struct mustach_itf *itf, void *closure, char **result, size_t *size)
|
|
||||||
{
|
|
||||||
int rc;
|
|
||||||
FILE *file;
|
|
||||||
size_t s;
|
|
||||||
|
|
||||||
*result = NULL;
|
|
||||||
if (size == NULL)
|
|
||||||
size = &s;
|
|
||||||
file = open_memstream(result, size);
|
|
||||||
if (file == NULL) {
|
|
||||||
rc = MUSTACH_ERROR_SYSTEM;
|
|
||||||
errno = ENOMEM;
|
|
||||||
} else {
|
|
||||||
rc = fmustach(templ, itf, closure, file);
|
|
||||||
if (rc == 0)
|
|
||||||
/* adds terminating null */
|
|
||||||
rc = fputc(0, file) ? MUSTACH_ERROR_SYSTEM : 0;
|
|
||||||
fclose(file);
|
|
||||||
if (rc >= 0)
|
|
||||||
/* removes terminating null of the length */
|
|
||||||
(*size)--;
|
|
||||||
else {
|
|
||||||
free(*result);
|
|
||||||
*result = NULL;
|
|
||||||
*size = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_MUSTACHE
|
|
||||||
#define NNTPCHAN_MUSTACHE
|
|
||||||
|
|
||||||
/*
|
|
||||||
Author: José Bollo <jobol@nonadev.net>
|
|
||||||
Author: José Bollo <jose.bollo@iot.bzh>
|
|
||||||
|
|
||||||
https://gitlab.com/jobol/mustach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define MUSTACH_OK 0
|
|
||||||
#define MUSTACH_ERROR_SYSTEM -1
|
|
||||||
#define MUSTACH_ERROR_UNEXPECTED_END -2
|
|
||||||
#define MUSTACH_ERROR_EMPTY_TAG -3
|
|
||||||
#define MUSTACH_ERROR_TAG_TOO_LONG -4
|
|
||||||
#define MUSTACH_ERROR_BAD_SEPARATORS -5
|
|
||||||
#define MUSTACH_ERROR_TOO_DEPTH -6
|
|
||||||
#define MUSTACH_ERROR_CLOSING -7
|
|
||||||
#define MUSTACH_ERROR_BAD_UNESCAPE_TAG -8
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
namespace mustache
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* mustach_itf - interface for callbacks
|
|
||||||
*
|
|
||||||
* All of this function should return a negative value to stop
|
|
||||||
* the mustache processing. The returned negative value will be
|
|
||||||
* then returned to the caller of mustach as is.
|
|
||||||
*
|
|
||||||
* The functions enter and next should return 0 or 1.
|
|
||||||
*
|
|
||||||
* All other functions should normally return 0.
|
|
||||||
*
|
|
||||||
* @start: Starts the mustach processing of the closure
|
|
||||||
* 'start' is optional (can be NULL)
|
|
||||||
*
|
|
||||||
* @put: Writes the value of 'name' to 'file' with 'escape' or not
|
|
||||||
*
|
|
||||||
* @enter: Enters the section of 'name' if possible.
|
|
||||||
* Musts return 1 if entered or 0 if not entered.
|
|
||||||
* When 1 is returned, the function 'leave' will always be called.
|
|
||||||
* Conversely 'leave' is never called when enter returns 0 or
|
|
||||||
* a negative value.
|
|
||||||
* When 1 is returned, the function must activate the first
|
|
||||||
* item of the section.
|
|
||||||
*
|
|
||||||
* @next: Activates the next item of the section if it exists.
|
|
||||||
* Musts return 1 when the next item is activated.
|
|
||||||
* Musts return 0 when there is no item to activate.
|
|
||||||
*
|
|
||||||
* @leave: Leaves the last entered section
|
|
||||||
*/
|
|
||||||
struct mustach_itf {
|
|
||||||
int (*start)(void *closure);
|
|
||||||
int (*put)(void *closure, const char *name, int escape, FILE *file);
|
|
||||||
int (*enter)(void *closure, const char *name);
|
|
||||||
int (*next)(void *closure);
|
|
||||||
int (*leave)(void *closure);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* fmustach - Renders the mustache 'template' in 'file' for 'itf' and 'closure'.
|
|
||||||
*
|
|
||||||
* @template: the template string to instanciate
|
|
||||||
* @itf: the interface to the functions that mustach calls
|
|
||||||
* @closure: the closure to pass to functions called
|
|
||||||
* @file: the file where to write the result
|
|
||||||
*
|
|
||||||
* Returns 0 in case of success, -1 with errno set in case of system error
|
|
||||||
* a other negative value in case of error.
|
|
||||||
*/
|
|
||||||
int fmustach(const char *templ, struct mustach_itf *itf, void *closure, FILE *file);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#include "net.hpp"
|
|
||||||
#include <uv.h>
|
|
||||||
#include <sstream>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
std::string NetAddr::to_string()
|
|
||||||
{
|
|
||||||
std::string str("invalid");
|
|
||||||
const size_t s = 128;
|
|
||||||
char * buff = new char[s];
|
|
||||||
if(uv_ip6_name(&addr, buff, s) == 0) {
|
|
||||||
str = std::string(buff);
|
|
||||||
delete [] buff;
|
|
||||||
}
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "[" << str << "]:" << ntohs(addr.sin6_port);
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
NetAddr::NetAddr()
|
|
||||||
{
|
|
||||||
std::memset(&addr, 0, sizeof(addr));
|
|
||||||
}
|
|
||||||
|
|
||||||
NetAddr ParseAddr(const std::string & addr)
|
|
||||||
{
|
|
||||||
NetAddr saddr;
|
|
||||||
auto n = addr.rfind("]:");
|
|
||||||
if (n == std::string::npos) {
|
|
||||||
throw std::runtime_error("invalid address: "+addr);
|
|
||||||
}
|
|
||||||
if (addr[0] != '[') {
|
|
||||||
throw std::runtime_error("invalid address: "+addr);
|
|
||||||
}
|
|
||||||
auto p = addr.substr(n+2);
|
|
||||||
int port = std::atoi(p.c_str());
|
|
||||||
auto a = addr.substr(0, n);
|
|
||||||
uv_ip6_addr(a.c_str(), port, &saddr.addr);
|
|
||||||
return saddr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_NET_HPP
|
|
||||||
#define NNTPCHAN_NET_HPP
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
struct NetAddr
|
|
||||||
{
|
|
||||||
NetAddr();
|
|
||||||
|
|
||||||
sockaddr_in6 addr;
|
|
||||||
operator sockaddr * () { return (sockaddr *) &addr; }
|
|
||||||
operator const sockaddr * () const { return (sockaddr *) &addr; }
|
|
||||||
std::string to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
NetAddr ParseAddr(const std::string & addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#include "nntp_auth.hpp"
|
|
||||||
#include "crypto.hpp"
|
|
||||||
#include "base64.hpp"
|
|
||||||
#include <array>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
HashedCredDB::HashedCredDB() : LineReader(1024) {}
|
|
||||||
|
|
||||||
bool HashedCredDB::CheckLogin(const std::string & user, const std::string & passwd)
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(m_access);
|
|
||||||
m_found = false;
|
|
||||||
m_user = user;
|
|
||||||
m_passwd = passwd;
|
|
||||||
m_instream->seekg(0, std::ios::end);
|
|
||||||
const auto l = m_instream->tellg();
|
|
||||||
m_instream->seekg(0, std::ios::beg);
|
|
||||||
char * buff = new char[l];
|
|
||||||
// read file
|
|
||||||
m_instream->read(buff, l);
|
|
||||||
Data(buff, l);
|
|
||||||
delete [] buff;
|
|
||||||
return m_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HashedCredDB::ProcessLine(const std::string & line)
|
|
||||||
{
|
|
||||||
// strip comments
|
|
||||||
auto comment = line.find("#");
|
|
||||||
std::string part = line;
|
|
||||||
for (; comment != std::string::npos; comment = part.find("#")) {
|
|
||||||
if(comment)
|
|
||||||
part = part.substr(0, comment);
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
if(!part.size()) return false; // empty line after comments
|
|
||||||
auto idx = part.find(":");
|
|
||||||
if (idx == std::string::npos) return false; // bad format
|
|
||||||
if (m_user != part.substr(0, idx)) return false; // username mismatch
|
|
||||||
part = part.substr(idx+1);
|
|
||||||
|
|
||||||
idx = part.find(":");
|
|
||||||
if (idx == std::string::npos) return false; // bad format
|
|
||||||
std::string cred = part.substr(0, idx);
|
|
||||||
std::string salt = part.substr(idx+1);
|
|
||||||
return Hash(m_passwd, salt) == cred;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HashedCredDB::HandleLine(const std::string &line)
|
|
||||||
{
|
|
||||||
if(m_found) return;
|
|
||||||
if(ProcessLine(line))
|
|
||||||
m_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void HashedCredDB::SetStream(std::istream * s)
|
|
||||||
{
|
|
||||||
m_instream = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string HashedCredDB::Hash(const std::string & data, const std::string & salt)
|
|
||||||
{
|
|
||||||
SHA512Digest h;
|
|
||||||
std::string d = data + salt;
|
|
||||||
SHA512((const uint8_t*)d.c_str(), d.size(), h);
|
|
||||||
return B64Encode(h.data(), h.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
HashedFileDB::HashedFileDB(const std::string & fname) :
|
|
||||||
m_fname(fname),
|
|
||||||
f(nullptr)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
HashedFileDB::~HashedFileDB()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void HashedFileDB::Close()
|
|
||||||
{
|
|
||||||
if(f.is_open())
|
|
||||||
f.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HashedFileDB::Open()
|
|
||||||
{
|
|
||||||
if(!f.is_open())
|
|
||||||
f.open(m_fname);
|
|
||||||
if(f.is_open()) {
|
|
||||||
SetStream(&f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_NNTP_AUTH_HPP
|
|
||||||
#define NNTPCHAN_NNTP_AUTH_HPP
|
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <mutex>
|
|
||||||
#include "line.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
/** @brief nntp credential db interface */
|
|
||||||
class NNTPCredentialDB
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/** @brief open connection to database, return false on error otherwise return true */
|
|
||||||
virtual bool Open() = 0;
|
|
||||||
/** @brief close connection to database */
|
|
||||||
virtual void Close() = 0;
|
|
||||||
/** @brief return true if username password combo is correct */
|
|
||||||
virtual bool CheckLogin(const std::string & user, const std::string & passwd) = 0;
|
|
||||||
virtual ~NNTPCredentialDB() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @brief nntp credential db using hashed+salted passwords */
|
|
||||||
class HashedCredDB : public NNTPCredentialDB, public LineReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HashedCredDB();
|
|
||||||
bool CheckLogin(const std::string & user, const std::string & passwd);
|
|
||||||
protected:
|
|
||||||
void SetStream(std::istream * i);
|
|
||||||
|
|
||||||
std::string Hash(const std::string & data, const std::string & salt);
|
|
||||||
void HandleLine(const std::string & line);
|
|
||||||
private:
|
|
||||||
bool ProcessLine(const std::string & line);
|
|
||||||
|
|
||||||
std::mutex m_access;
|
|
||||||
std::string m_user, m_passwd;
|
|
||||||
bool m_found;
|
|
||||||
/** return true if we have a line that matches this username / password combo */
|
|
||||||
std::istream * m_instream;
|
|
||||||
};
|
|
||||||
|
|
||||||
class HashedFileDB : public HashedCredDB
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
HashedFileDB(const std::string & fname);
|
|
||||||
~HashedFileDB();
|
|
||||||
bool Open();
|
|
||||||
void Close();
|
|
||||||
private:
|
|
||||||
std::string m_fname;
|
|
||||||
std::ifstream f;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
#include "nntp_handler.hpp"
|
|
||||||
#include "message.hpp"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
NNTPServerHandler::NNTPServerHandler(const std::string & storage) :
|
|
||||||
LineReader(1024),
|
|
||||||
m_article(nullptr),
|
|
||||||
m_auth(nullptr),
|
|
||||||
m_store(storage),
|
|
||||||
m_authed(false),
|
|
||||||
m_state(eStateReadCommand)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NNTPServerHandler::~NNTPServerHandler()
|
|
||||||
{
|
|
||||||
if(m_auth) delete m_auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::HandleLine(const std::string &line)
|
|
||||||
{
|
|
||||||
if(m_state == eStateReadCommand)
|
|
||||||
{
|
|
||||||
std::deque<std::string> command;
|
|
||||||
std::istringstream s;
|
|
||||||
s.str(line);
|
|
||||||
for (std::string part; std::getline(s, part, ' '); ) {
|
|
||||||
if(part.size()) command.push_back(std::string(part));
|
|
||||||
}
|
|
||||||
if(command.size())
|
|
||||||
HandleCommand(command);
|
|
||||||
else
|
|
||||||
QueueLine("501 Syntax error");
|
|
||||||
}
|
|
||||||
else if(m_state == eStateStoreArticle)
|
|
||||||
{
|
|
||||||
std::string l = line + "\r\n";
|
|
||||||
OnData(l.c_str(), l.size());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cerr << "invalid state" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::OnData(const char * data, ssize_t l)
|
|
||||||
{
|
|
||||||
if(l <= 0 ) return;
|
|
||||||
if(m_state == eStateStoreArticle)
|
|
||||||
{
|
|
||||||
const char * end = strstr(data, "\r\n.\r\n");
|
|
||||||
if(end)
|
|
||||||
{
|
|
||||||
std::size_t diff = end - data ;
|
|
||||||
if(m_article)
|
|
||||||
m_article->write(data, diff+2);
|
|
||||||
ArticleObtained();
|
|
||||||
diff += 5;
|
|
||||||
Data(end+5, l-diff);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(m_article)
|
|
||||||
m_article->write(data, l);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Data(data, l);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::HandleCommand(const std::deque<std::string> & command)
|
|
||||||
{
|
|
||||||
auto cmd = command[0];
|
|
||||||
std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper);
|
|
||||||
std::size_t cmdlen = command.size();
|
|
||||||
for(const auto & part : command)
|
|
||||||
std::cerr << " " << part;
|
|
||||||
std::cerr << std::endl;
|
|
||||||
if (cmd == "QUIT") {
|
|
||||||
Quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (cmd[0] == '5')
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (cmd == "MODE" ) {
|
|
||||||
if(cmdlen == 2) {
|
|
||||||
// set mode
|
|
||||||
SwitchMode(command[1]);
|
|
||||||
} else if(cmdlen) {
|
|
||||||
// too many arguments
|
|
||||||
QueueLine("500 too many arguments");
|
|
||||||
} else {
|
|
||||||
// get mode
|
|
||||||
QueueLine("500 wrong arguments");
|
|
||||||
}
|
|
||||||
} else if(cmd == "CAPABILITIES") {
|
|
||||||
QueueLine("101 I support the following:");
|
|
||||||
QueueLine("READER");
|
|
||||||
QueueLine("IMPLEMENTATION nntpchan-daemon");
|
|
||||||
QueueLine("VERSION 2");
|
|
||||||
QueueLine("STREAMING");
|
|
||||||
QueueLine(".");
|
|
||||||
} else if (cmd == "CHECK") {
|
|
||||||
if(cmdlen == 2) {
|
|
||||||
const std::string & msgid = command[1];
|
|
||||||
if(IsValidMessageID(msgid) && m_store.Accept(msgid))
|
|
||||||
{
|
|
||||||
QueueLine("238 "+msgid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QueueLine("438 "+msgid);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
QueueLine("501 syntax error");
|
|
||||||
} else if (cmd == "TAKETHIS") {
|
|
||||||
if (cmdlen == 2)
|
|
||||||
{
|
|
||||||
const std::string & msgid = command[1];
|
|
||||||
if(m_store.Accept(msgid))
|
|
||||||
{
|
|
||||||
m_article = m_store.OpenWrite(msgid);
|
|
||||||
}
|
|
||||||
m_articleName = msgid;
|
|
||||||
EnterState(eStateStoreArticle);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QueueLine("501 invalid syntax");
|
|
||||||
} else {
|
|
||||||
// unknown command
|
|
||||||
QueueLine("500 Unknown Command");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::ArticleObtained()
|
|
||||||
{
|
|
||||||
if(m_article)
|
|
||||||
{
|
|
||||||
m_article->flush();
|
|
||||||
m_article->close();
|
|
||||||
delete m_article;
|
|
||||||
m_article = nullptr;
|
|
||||||
QueueLine("239 "+m_articleName);
|
|
||||||
std::cerr << "stored " << m_articleName << std::endl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
QueueLine("439 "+m_articleName);
|
|
||||||
m_articleName = "";
|
|
||||||
EnterState(eStateReadCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::SwitchMode(const std::string & mode)
|
|
||||||
{
|
|
||||||
std::string m = mode;
|
|
||||||
std::transform(m.begin(), m.end(), m.begin(), ::toupper);
|
|
||||||
if (m == "READER") {
|
|
||||||
m_mode = m;
|
|
||||||
if(PostingAllowed()) {
|
|
||||||
QueueLine("200 Posting is permitted yo");
|
|
||||||
} else {
|
|
||||||
QueueLine("201 Posting is not permitted yo");
|
|
||||||
}
|
|
||||||
} else if (m == "STREAM") {
|
|
||||||
m_mode = m;
|
|
||||||
if (PostingAllowed()) {
|
|
||||||
QueueLine("203 Streaming enabled");
|
|
||||||
} else {
|
|
||||||
QueueLine("483 Streaming Denied");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// unknown mode
|
|
||||||
QueueLine("500 Unknown mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::EnterState(State st)
|
|
||||||
{
|
|
||||||
std::cerr << "enter state " << st << std::endl;
|
|
||||||
m_state = st;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::Quit()
|
|
||||||
{
|
|
||||||
EnterState(eStateQuit);
|
|
||||||
QueueLine("205 quitting");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NNTPServerHandler::ShouldClose()
|
|
||||||
{
|
|
||||||
return m_state == eStateQuit;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NNTPServerHandler::PostingAllowed()
|
|
||||||
{
|
|
||||||
return m_authed || m_auth == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::Greet()
|
|
||||||
{
|
|
||||||
if(PostingAllowed())
|
|
||||||
QueueLine("200 Posting allowed");
|
|
||||||
else
|
|
||||||
QueueLine("201 Posting not allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerHandler::SetAuth(NNTPCredentialDB *creds)
|
|
||||||
{
|
|
||||||
if(m_auth) delete m_auth;
|
|
||||||
m_auth = creds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_NNTP_HANDLER_HPP
|
|
||||||
#define NNTPCHAN_NNTP_HANDLER_HPP
|
|
||||||
#include <deque>
|
|
||||||
#include <string>
|
|
||||||
#include "line.hpp"
|
|
||||||
#include "nntp_auth.hpp"
|
|
||||||
#include "storage.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
class NNTPServerHandler : public LineReader, public IConnHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
NNTPServerHandler(const std::string & storage);
|
|
||||||
~NNTPServerHandler();
|
|
||||||
|
|
||||||
virtual bool ShouldClose();
|
|
||||||
|
|
||||||
void SetAuth(NNTPCredentialDB * creds);
|
|
||||||
|
|
||||||
virtual void OnData(const char *, ssize_t);
|
|
||||||
|
|
||||||
void Greet();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void HandleLine(const std::string & line);
|
|
||||||
void HandleCommand(const std::deque<std::string> & command);
|
|
||||||
private:
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
eStateReadCommand,
|
|
||||||
eStateStoreArticle,
|
|
||||||
eStateQuit
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
void EnterState(State st);
|
|
||||||
|
|
||||||
void ArticleObtained();
|
|
||||||
|
|
||||||
// handle quit command, this queues a reply
|
|
||||||
void Quit();
|
|
||||||
|
|
||||||
// switch nntp modes, this queues a reply
|
|
||||||
void SwitchMode(const std::string & mode);
|
|
||||||
|
|
||||||
bool PostingAllowed();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_articleName;
|
|
||||||
std::fstream * m_article;
|
|
||||||
NNTPCredentialDB * m_auth;
|
|
||||||
ArticleStorage m_store;
|
|
||||||
std::string m_mode;
|
|
||||||
bool m_authed;
|
|
||||||
State m_state;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
|
|
||||||
#include "nntp_server.hpp"
|
|
||||||
#include "nntp_auth.hpp"
|
|
||||||
#include "nntp_handler.hpp"
|
|
||||||
#include "net.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
|
|
||||||
NNTPServer::NNTPServer(uv_loop_t * loop) : Server(loop), m_frontend(nullptr) {}
|
|
||||||
|
|
||||||
NNTPServer::~NNTPServer()
|
|
||||||
{
|
|
||||||
if (m_frontend) delete m_frontend;
|
|
||||||
}
|
|
||||||
|
|
||||||
IServerConn * NNTPServer::CreateConn(uv_stream_t * s)
|
|
||||||
{
|
|
||||||
NNTPCredentialDB * creds = nullptr;
|
|
||||||
|
|
||||||
std::ifstream i;
|
|
||||||
i.open(m_logindbpath);
|
|
||||||
if(i.is_open()) creds = new HashedFileDB(m_logindbpath);
|
|
||||||
|
|
||||||
NNTPServerHandler * handler = new NNTPServerHandler(m_storagePath);
|
|
||||||
if(creds)
|
|
||||||
handler->SetAuth(creds);
|
|
||||||
|
|
||||||
NNTPServerConn * conn = new NNTPServerConn(GetLoop(), s, this, handler);
|
|
||||||
return conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServer::SetLoginDB(const std::string path)
|
|
||||||
{
|
|
||||||
m_logindbpath = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void NNTPServer::SetStoragePath(const std::string & path)
|
|
||||||
{
|
|
||||||
m_storagePath = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServer::SetInstanceName(const std::string & name)
|
|
||||||
{
|
|
||||||
m_servername = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string NNTPServer::InstanceName() const
|
|
||||||
{
|
|
||||||
return m_servername;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServer::SetFrontend(Frontend * f)
|
|
||||||
{
|
|
||||||
if(m_frontend) delete m_frontend;
|
|
||||||
m_frontend = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServer::OnAcceptError(int status)
|
|
||||||
{
|
|
||||||
std::cerr << "nntpserver::accept() " << uv_strerror(status) << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NNTPServerConn::SendNextReply()
|
|
||||||
{
|
|
||||||
IConnHandler * handler = GetHandler();
|
|
||||||
while(handler->HasNextLine()) {
|
|
||||||
auto line = handler->GetNextLine();
|
|
||||||
SendString(line + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void NNTPServerConn::Greet()
|
|
||||||
{
|
|
||||||
IConnHandler * handler = GetHandler();
|
|
||||||
handler->Greet();
|
|
||||||
SendNextReply();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
#ifndef NNTPCHAN_NNTP_SERVER_HPP
|
|
||||||
#define NNTPCHAN_NNTP_SERVER_HPP
|
|
||||||
#include <uv.h>
|
|
||||||
#include <string>
|
|
||||||
#include <deque>
|
|
||||||
#include "frontend.hpp"
|
|
||||||
#include "server.hpp"
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
|
|
||||||
class NNTPServer : public Server
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
NNTPServer(uv_loop_t * loop);
|
|
||||||
|
|
||||||
virtual ~NNTPServer();
|
|
||||||
|
|
||||||
void SetStoragePath(const std::string & path);
|
|
||||||
|
|
||||||
void SetLoginDB(const std::string path);
|
|
||||||
|
|
||||||
void SetInstanceName(const std::string & name);
|
|
||||||
|
|
||||||
std::string InstanceName() const;
|
|
||||||
|
|
||||||
void SetFrontend(Frontend * f);
|
|
||||||
|
|
||||||
void Close();
|
|
||||||
|
|
||||||
virtual IServerConn * CreateConn(uv_stream_t * s);
|
|
||||||
|
|
||||||
virtual void OnAcceptError(int status);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
std::string m_logindbpath;
|
|
||||||
std::string m_storagePath;
|
|
||||||
std::string m_servername;
|
|
||||||
|
|
||||||
Frontend * m_frontend;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class NNTPServerConn : public IServerConn
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
NNTPServerConn(uv_loop_t * l, uv_stream_t * s, Server * parent, IConnHandler * h) : IServerConn(l, s, parent, h) {}
|
|
||||||
|
|
||||||
virtual bool IsTimedOut() { return false; };
|
|
||||||
|
|
||||||
/** @brief send next queued reply */
|
|
||||||
virtual void SendNextReply();
|
|
||||||
|
|
||||||
virtual void Greet();
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#include "buffer.hpp"
|
|
||||||
#include "server.hpp"
|
|
||||||
#include "net.hpp"
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
namespace nntpchan
|
|
||||||
{
|
|
||||||
Server::Server(uv_loop_t * loop)
|
|
||||||
{
|
|
||||||
m_loop = loop;
|
|
||||||
uv_tcp_init(m_loop, &m_server);
|
|
||||||
m_server.data = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::Close()
|
|
||||||
{
|
|
||||||
std::cout << "Close server" << std::endl;
|
|
||||||
uv_close((uv_handle_t*)&m_server, [](uv_handle_t * s) {
|
|
||||||
Server * self = (Server*)s->data;
|
|
||||||
if (self) delete self;
|
|
||||||
s->data = nullptr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::Bind(const std::string & addr)
|
|
||||||
{
|
|
||||||
auto saddr = ParseAddr(addr);
|
|
||||||
assert(uv_tcp_bind(*this, saddr, 0) == 0);
|
|
||||||
auto cb = [] (uv_stream_t * s, int status) {
|
|
||||||
Server * self = (Server *) s->data;
|
|
||||||
self->OnAccept(s, status);
|
|
||||||
};
|
|
||||||
assert(uv_listen(*this, 5, cb) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::OnAccept(uv_stream_t * s, int status)
|
|
||||||
{
|
|
||||||
if(status < 0) {
|
|
||||||
OnAcceptError(status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
IServerConn * conn = CreateConn(s);
|
|
||||||
assert(conn);
|
|
||||||
m_conns.push_back(conn);
|
|
||||||
conn->Greet();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Server::RemoveConn(IServerConn * conn)
|
|
||||||
{
|
|
||||||
auto itr = m_conns.begin();
|
|
||||||
while(itr != m_conns.end())
|
|
||||||
{
|
|
||||||
if(*itr == conn)
|
|
||||||
itr = m_conns.erase(itr);
|
|
||||||
else
|
|
||||||
++itr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IConnHandler::QueueLine(const std::string & line)
|
|
||||||
{
|
|
||||||
m_sendlines.push_back(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IConnHandler::HasNextLine()
|
|
||||||
{
|
|
||||||
return m_sendlines.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string IConnHandler::GetNextLine()
|
|
||||||
{
|
|
||||||
std::string line = m_sendlines[0];
|
|
||||||
m_sendlines.pop_front();
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
IServerConn::IServerConn(uv_loop_t * l, uv_stream_t * st, Server * parent, IConnHandler * h)
|
|
||||||
{
|
|
||||||
m_loop = l;
|
|
||||||
m_parent = parent;
|
|
||||||
m_handler = h;
|
|
||||||
uv_tcp_init(l, &m_conn);
|
|
||||||
m_conn.data = this;
|
|
||||||
uv_accept(st, (uv_stream_t*) &m_conn);
|
|
||||||
uv_read_start((uv_stream_t*) &m_conn, [] (uv_handle_t * h, size_t s, uv_buf_t * b) {
|
|
||||||
IServerConn * self = (IServerConn*) h->data;
|
|
||||||
if(self == nullptr) return;
|
|
||||||
b->base = self->m_readbuff;
|
|
||||||
if (s > sizeof(self->m_readbuff))
|
|
||||||
b->len = sizeof(self->m_readbuff);
|
|
||||||
else
|
|
||||||
b->len = s;
|
|
||||||
}, [] (uv_stream_t * s, ssize_t nread, const uv_buf_t * b) {
|
|
||||||
IServerConn * self = (IServerConn*) s->data;
|
|
||||||
if(self == nullptr) return;
|
|
||||||
if(nread > 0) {
|
|
||||||
self->m_handler->OnData(b->base, nread);
|
|
||||||
self->SendNextReply();
|
|
||||||
if(self->m_handler->ShouldClose())
|
|
||||||
self->Close();
|
|
||||||
} else {
|
|
||||||
if (nread != UV_EOF) {
|
|
||||||
std::cerr << "error in nntp server conn alloc: ";
|
|
||||||
std::cerr << uv_strerror(nread);
|
|
||||||
std::cerr << std::endl;
|
|
||||||
}
|
|
||||||
// got eof or error
|
|
||||||
self->Close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
IServerConn::~IServerConn()
|
|
||||||
{
|
|
||||||
delete m_handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IServerConn::SendString(const std::string & str)
|
|
||||||
{
|
|
||||||
WriteBuffer * b = new WriteBuffer(str);
|
|
||||||
uv_write(&b->w, (uv_stream_t*)&m_conn, &b->b, 1, [](uv_write_t * w, int status) {
|
|
||||||
(void) status;
|
|
||||||
WriteBuffer * wb = (WriteBuffer *) w->data;
|
|
||||||
if(wb)
|
|
||||||
delete wb;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void IServerConn::Close()
|
|
||||||
{
|
|
||||||
m_parent->RemoveConn(this);
|
|
||||||
uv_close((uv_handle_t*)&m_conn, [] (uv_handle_t * s) {
|
|
||||||
IServerConn * self = (IServerConn*) s->data;
|
|
||||||
if(self)
|
|
||||||
delete self;
|
|
||||||
s->data = nullptr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user