Compare commits

..

1 commit

Author SHA1 Message Date
e47a874711 initial db refactoring 2019-09-09 08:27:51 +02:00
1201 changed files with 154751 additions and 228029 deletions

3
.gitignore vendored
View file

@ -1,5 +1,4 @@
/docstore docstore
/docstore_*
tmp tmp
run.sh run.sh
TODO.txt TODO.txt

View file

@ -1,4 +1,4 @@
Copyright (C) 2021 Marius Schellenberger Copyright (C) 2019 Marius Schellenberger
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View file

@ -1,55 +1,44 @@
CC=go CC=go
BUILD=build -v -trimpath BUILD=build -v
VERSION=$(shell cat VERSION) VERSION=$(shell cat VERSION)
GCFLAGS=-gcflags '-e' GCFLAGS=-gcflags 'all=-e'
LDFLAGS=-ldflags '-buildid=$(VERSION) -X main.version=$(VERSION) -s -w' LDFLAGS=-ldflags '-X main.version=$(VERSION) -s -w'
PROGRAM=docstore PROGRAM=docstore
CMD=-o $(PROGRAM) cmd/docstore/main.go ENV=CGO_ENABLED=0 GO111MODULE=on
ENV=GOGC=0 CGO_ENABLED=0 GO111MODULE=on
arch:
GOOS=openbsd make release PROGRAM=docstore_openbsd_amd64
GOOS=linux make release PROGRAM=docstore_linux_amd64
GOOS=linux GOARCH=arm64 make release PROGRAM=docstore_linux_arm64
all: $(PROGRAM) all: $(PROGRAM)
setup: setup:
$(CC) install github.com/client9/misspell/cmd/misspell@latest $(CC) get github.com/client9/misspell
$(CC) install honnef.co/go/tools/cmd/staticcheck@latest
release: clean gofmt codeqa release: clean generate gofmt codeqa
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD) $(ENV) $(CC) $(BUILD) $(LDFLAGS)
release-vendor: clean gofmt codeqa release-vendor: clean generate gofmt codeqa
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD) $(ENV) $(CC) $(BUILD) -mod=vendor $(LDFLAGS)
vendor: clean gofmt vendor: clean generate gofmt
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD) $(ENV) $(CC) $(BUILD) -mod=vendor $(LDFLAGS)
$(PROGRAM): clean gofmt $(PROGRAM): clean generate gofmt
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD) $(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS)
clean: clean:
$(CC) clean -x $(CC) clean -x
codeqa: misspell golint test
generate:
$(CC) generate
gofmt: gofmt:
gofmt -w pkg/ cmd/ gofmt -w pkg/ main.go
codeqa: govet misspell staticcheck test golint:
$(GOPATH)/bin/golint .
govet:
$(CC) vet ./...
update:
$(ENV) $(CC) get -u ./...
misspell: misspell:
$(GOPATH)/bin/misspell cmd/* pkg/* Makefile README.md sri.sh $(GOPATH)/bin/misspell pkg/* templates/* main.go Makefile README.md sri.sh templates.sh
staticcheck:
/bin/true
# $(GOPATH)/bin/staticcheck ./...
test: test:
ifeq ($(shell go env GOARCH), $(shell go env GOHOSTARCH)) ifeq ($(shell go env GOARCH), $(shell go env GOHOSTARCH))
@ -62,4 +51,4 @@ else
$(info skipping tests of other platforms) $(info skipping tests of other platforms)
endif endif
.PHONY: arch setup release release-vendor vendor build clean codeqa gofmt govet misspell staticcheck test .PHONY: setup release release-vendor vendor build clean codeqa generate gofmt golint misspell test

View file

@ -4,7 +4,7 @@
**Requirements:** **Requirements:**
* Go (golang) >= 1.16 * Go (golang) >= 1.12
Linux: Linux:

3
TODO
View file

@ -1,3 +0,0 @@
implement pdftoppm to make pdfs readable by tesseract
pdftoppm -tiff -r 300 "$pdf_in" "$temp_dir/pg"

67
go.mod
View file

@ -1,44 +1,37 @@
module git.giftfish.de/ston1th/docstore module git.giftfish.de/ston1th/docstore
go 1.24.0 go 1.12
require ( require (
git.giftfish.de/ston1th/authdav v1.1.0 git.giftfish.de/ston1th/authdav v1.1.0
git.giftfish.de/ston1th/godrop/v2 v2.2.0 git.giftfish.de/ston1th/godrop/v2 v2.1.0
git.giftfish.de/ston1th/jwt/v3 v3.3.0 git.giftfish.de/ston1th/jwt/v3 v3.2.0
github.com/RoaringBitmap/roaring v0.4.17 // indirect
github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01 github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01
github.com/blevesearch/bleve v1.0.14 github.com/blevesearch/bleve v0.8.0
github.com/go-logr/logr v1.4.3 github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 // indirect
github.com/gorilla/mux v1.8.1 github.com/blevesearch/go-porterstemmer v1.0.2 // indirect
github.com/pquerna/otp v1.5.0 github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f // indirect
github.com/urfave/cli v1.22.17 github.com/boltdb/bolt v1.3.1
go.etcd.io/bbolt v1.4.3 github.com/couchbase/vellum v0.0.0-20190328134517-462e86d8716b // indirect
golang.org/x/crypto v0.43.0 github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
golang.org/x/net v0.46.0 github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
k8s.io/klog/v2 v2.130.1 github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
) github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/etcd-io/bbolt v1.3.3 // indirect
require ( github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/RoaringBitmap/roaring v1.9.4 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/bits-and-blooms/bitset v1.24.1 // indirect github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/golang/protobuf v1.3.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect github.com/gorilla/mux v1.7.1
github.com/blevesearch/segment v0.9.1 // indirect github.com/jmhodges/levigo v1.0.0 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect github.com/pquerna/otp v1.2.0
github.com/blevesearch/zap/v11 v11.0.14 // indirect github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect
github.com/blevesearch/zap/v12 v12.0.14 // indirect github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 // indirect
github.com/blevesearch/zap/v13 v13.0.6 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/blevesearch/zap/v14 v14.0.5 // indirect github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect
github.com/blevesearch/zap/v15 v15.0.3 // indirect github.com/urfave/cli v1.20.0
github.com/boombuler/barcode v1.1.0 // indirect go.etcd.io/bbolt v1.3.3 // indirect
github.com/couchbase/vellum v1.0.2 // indirect golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/steveyen/gtreap v0.1.0 // indirect
github.com/willf/bitset v1.1.11 // indirect
golang.org/x/sys v0.37.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
) )

218
go.sum
View file

@ -1,182 +1,124 @@
git.giftfish.de/ston1th/authdav v1.1.0 h1:hGNmaw/bNKETcV02JaS6NQaf3cKVFc3iyQ32QaAwXeo= git.giftfish.de/ston1th/authdav v1.1.0 h1:hGNmaw/bNKETcV02JaS6NQaf3cKVFc3iyQ32QaAwXeo=
git.giftfish.de/ston1th/authdav v1.1.0/go.mod h1:UAVoOXY6jAVh0rOnIwy4wVWpLIPAom/EoptPkAhX1nQ= git.giftfish.de/ston1th/authdav v1.1.0/go.mod h1:UAVoOXY6jAVh0rOnIwy4wVWpLIPAom/EoptPkAhX1nQ=
git.giftfish.de/ston1th/godrop/v2 v2.2.0 h1:jdcPJeotiuug3tC4zXvwnauen1Y7kfF9CbFKQbGttYU= git.giftfish.de/ston1th/godrop/v2 v2.1.0 h1:vR4aWVeMGJ+tHQH0p44esZrSl5dYJzuCmZqhKWP/vwI=
git.giftfish.de/ston1th/godrop/v2 v2.2.0/go.mod h1:gDAD0cWURkN0cMAjjSKB6a6jqIXvOrY8Th25HdoJAyg= git.giftfish.de/ston1th/godrop/v2 v2.1.0/go.mod h1:z8KA3HP/vB04wddLXZfYVKURYDxBGau9hlHtV61x9mM=
git.giftfish.de/ston1th/jwt/v3 v3.3.0 h1:lSBVbk1D+KmLHBlxhS7snK33+U975cNbx/Z4Md6S+0A= git.giftfish.de/ston1th/jwt/v3 v3.2.0 h1:V6Lh7OjXn4D3HPd/LoDf7w5N7cyiSXEPAQCIfSVZ05c=
git.giftfish.de/ston1th/jwt/v3 v3.3.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw= git.giftfish.de/ston1th/jwt/v3 v3.2.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/RoaringBitmap/roaring v0.4.17 h1:oCYFIFEMSQZrLHpywH7919esI1VSrQZ0pJXkZPGIJ78=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv2QzDdQ=
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01 h1:ZkEYngtmNszw0BJt4GoWW5rdn5U6MaFlLAUD4S3KEas= github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01 h1:ZkEYngtmNszw0BJt4GoWW5rdn5U6MaFlLAUD4S3KEas=
github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01/go.mod h1:ZgSGz2VRprXTqsfbn6HAH1ePnGiVCrlA3kflJ6Sjxqg= github.com/bcampbell/qs v0.0.0-20161004022730-15caa99abd01/go.mod h1:ZgSGz2VRprXTqsfbn6HAH1ePnGiVCrlA3kflJ6Sjxqg=
github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blevesearch/bleve v0.8.0 h1:DCoCrxscCXrlzVWK92k7Vq4d28lTAFuigVmcgIX0VCo=
github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c= github.com/blevesearch/bleve v0.8.0/go.mod h1:Y2lmIkzV6mcNfAnAdOd+ZxHkHchhBfU/xroGIp61wfw=
github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 h1:U6vnxZrTfItfiUiYx0lf/LgHjRSfaKK5QHSom3lEbnA=
github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4= github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ=
github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ= github.com/blevesearch/go-porterstemmer v1.0.2 h1:qe7n69gBd1OLY5sHKnxQHIbzn0LNJA4hpAf+5XDxV2I=
github.com/blevesearch/blevex v1.0.0 h1:pnilj2Qi3YSEGdWgLj1Pn9Io7ukfXPoQcpAI1Bv8n/o= github.com/blevesearch/go-porterstemmer v1.0.2/go.mod h1:haWQqFT3RdOGz7PJuM3or/pWNJS1pKkoZJWCkWu0DVA=
github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc= github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f h1:kqbi9lqXLLs+zfWlgo1PIiRQ86n33K1JKotjj4rSYOg=
github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc= github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f/go.mod h1:IInt5XRvpiGE09KOk9mmCMLjHhydIhNPKPPFLFBB7L8=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
github.com/blevesearch/zap/v11 v11.0.14 h1:IrDAvtlzDylh6H2QCmS0OGcN9Hpf6mISJlfKjcwJs7k=
github.com/blevesearch/zap/v11 v11.0.14/go.mod h1:MUEZh6VHGXv1PKx3WnCbdP404LGG2IZVa/L66pyFwnY=
github.com/blevesearch/zap/v12 v12.0.14 h1:2o9iRtl1xaRjsJ1xcqTyLX414qPAwykHNV7wNVmbp3w=
github.com/blevesearch/zap/v12 v12.0.14/go.mod h1:rOnuZOiMKPQj18AEKEHJxuI14236tTQ1ZJz4PAnWlUg=
github.com/blevesearch/zap/v13 v13.0.6 h1:r+VNSVImi9cBhTNNR+Kfl5uiGy8kIbb0JMz/h8r6+O4=
github.com/blevesearch/zap/v13 v13.0.6/go.mod h1:L89gsjdRKGyGrRN6nCpIScCvvkyxvmeDCwZRcjjPCrw=
github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67njR0NU=
github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= github.com/couchbase/vellum v0.0.0-20190328134517-462e86d8716b h1:WgO2yt0pCXyPgU3K/pnY1qO/8FP8TxBAaYXqa+BQpx0=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/couchbase/vellum v0.0.0-20190328134517-462e86d8716b/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/couchbase/vellum v1.0.2 h1:BrbP0NKiyDdndMPec8Jjhy0U47CZ0Lgx3xUC2r9rZqw=
github.com/couchbase/vellum v1.0.2/go.mod h1:FcwrEivFpNi24R3jLOs3n+fs5RnuQnQqCLBJ1uAg1W4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d h1:SwD98825d6bdB+pEuTxWOXiSjBrHdOl/UVp75eI7JT8=
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 h1:MZRmHqDBd0vxNwenEbKSQqRVT24d3C05ft8kduSwlqM=
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 h1:OTanQnFt0bi5iLFSdbEVA/idR6Q2WhCm+deb7ir2CcM=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ikawaha/kagome.ipadic v1.1.2/go.mod h1:DPSBbU0czaJhAb/5uKQZHMc9MTVRpDugJfX+HddPHHg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 h1:YDeskXpkNDhPdWN3REluVa46HQOVuVkjkd2sWnrABNQ=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 h1:JNEGSiWg6D3lcBCMCBqN3ELniXujt+0QNHLhNnO0w3s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM=
github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 h1:HOxvxvnntLiPn123Fk+twfUhCQdMDaqmb0cclArW0T0=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026144532-2772b66316d2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=

View file

@ -1,7 +1,8 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
//go:build go1.16 //go:generate ./templates.sh
// +build go1.16
// +build go1.12
package main package main

View file

@ -1,28 +1,24 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package cmd package cmd
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/db"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/docstore/pkg/scan"
"git.giftfish.de/ston1th/docstore/pkg/server"
"git.giftfish.de/ston1th/godrop/v2"
"github.com/urfave/cli"
stdlog "log"
"net" "net"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"syscall" "syscall"
"time"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/db"
"git.giftfish.de/ston1th/docstore/pkg/scan"
"git.giftfish.de/ston1th/docstore/pkg/server"
"git.giftfish.de/ston1th/godrop/v2"
"github.com/go-logr/logr"
"github.com/urfave/cli"
"k8s.io/klog/v2"
"k8s.io/klog/v2/klogr"
//"github.com/go-logr/zerologr"
//"github.com/rs/zerolog"
) )
const ( const (
@ -30,6 +26,9 @@ const (
defListen = "127.0.0.1:8080" defListen = "127.0.0.1:8080"
defLogFile = "docstore.log" defLogFile = "docstore.log"
defRunUser = "docstore"
defRunGroup = "docstore"
defTimeout = 600 defTimeout = 600
tesseractThreads = 1 tesseractThreads = 1
@ -42,12 +41,23 @@ var (
configFile string configFile string
) )
func initServer(cfg *core.Config) (err error) { func initServer(cfg core.Config) (err error) {
// remove initial pledge due to violation with go1.12 // remove initial pledge due to violation with go1.12
// sysctl kern.somaxconn // sysctl kern.somaxconn
// if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil { //if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil {
// return // return
// } //}
dropCfg := godrop.Config{
User: cfg.User,
Group: cfg.Group,
Foreground: cfg.Foreground,
}
err = godrop.Drop(dropCfg, func() (net.Listener, error) {
return net.Listen("tcp", cfg.ListenAddr)
})
if err != nil {
return
}
if err = os.Chdir(cfg.RunDir); err != nil { if err = os.Chdir(cfg.RunDir); err != nil {
return return
} }
@ -56,25 +66,19 @@ func initServer(cfg *core.Config) (err error) {
return return
} }
l, err := net.Listen("tcp", cfg.ListenAddr) l, err := godrop.GetListener()
if err != nil { if err != nil {
return return
} }
ll, err := logFileLogger(cfg) log.InitLogger(cfg)
if err != nil {
return
}
log := ll.WithName("main")
log.Info("starting docstore", "version", cfg.Version)
cfg.DataDir = filepath.Join(cfg.RunDir, core.DataDir) cfg.DataDir = filepath.Join(cfg.RunDir, core.DataDir)
debugCfg := new(core.Config) debugCfg := cfg
*debugCfg = *cfg debugCfg.Secret = "***hidden***"
debugCfg.Secret = "[redacted]" log.Debugf("docstore config: %+v", debugCfg)
log.V(2).Info("docstore config", "cfg", debugCfg)
scanner, err := scan.New(ll.WithName("scanner"), cfg) scanner, err := scan.New(cfg)
if err != nil { if err != nil {
return errors.New("scanner: " + err.Error()) return errors.New("scanner: " + err.Error())
} }
@ -87,20 +91,25 @@ func initServer(cfg *core.Config) (err error) {
return return
} }
srv := server.NewHTTPServer(ll, cfg, l, scanner) srv := server.NewHTTPServer(cfg, l, scanner)
err = srv.Start() err = srv.Start()
if err != nil { if err != nil {
return return
} }
sigs := make(chan os.Signal, 1) log.Println("docstore " + cfg.Version + " started")
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs sig := <-sigs
log.Info("docstore shutdown initiated") log.Println("signal: " + sig.String())
go func() {
time.Sleep(time.Second * 20)
log.Fatal("stop timed out: killing")
}()
err = srv.Stop() err = srv.Stop()
if err != nil { if err != nil {
log.Error(err, "shutdown") log.Println(err)
} }
log.Info("docstore shutdown completed") log.Println("docstore stopped")
return return
} }
@ -116,13 +125,15 @@ func loadConfig() error {
if err != nil { if err != nil {
return err return err
} }
if config.Debug {
new(klog.Level).Set("2")
//zerolog.SetGlobalLevel(zerolog.TraceLevel)
}
if config.RunDir == "" { if config.RunDir == "" {
config.RunDir = defRunDir config.RunDir = defRunDir
} }
if config.User == "" {
config.User = defRunUser
}
if config.Group == "" {
config.Group = defRunGroup
}
if config.ListenAddr == "" { if config.ListenAddr == "" {
config.ListenAddr = defListen config.ListenAddr = defListen
} }
@ -141,48 +152,6 @@ func loadConfig() error {
return file.Close() return file.Close()
} }
func fatal(err error) {
log.Error(err, "init failed")
os.Exit(1)
}
var (
log logr.Logger
//zl zerolog.Logger
)
func logFileLogger(cfg *core.Config) (l logr.Logger, err error) {
f, err := os.OpenFile(filepath.Join(cfg.DataDir, cfg.LogFile), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o640)
if err != nil {
return
}
klog.LogToStderr(false)
klog.SetOutput(f)
l = klogr.New()
//zln := zl.Output(f)
//l = zerologr.New(&zln)
return
}
func init() {
//zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
// short := file
// for i := len(file) - 1; i > 0; i-- {
// if file[i] == '/' {
// short = file[i+1:]
// break
// }
// }
// file = short
// return file + ":" + strconv.Itoa(line)
//}
//zerologr.VerbosityFieldName = ""
//zl = zerolog.New(os.Stderr)
//zl = zl.With().Caller().Timestamp().Logger()
//log = zerologr.New(&zl)
log = klogr.New()
}
func Run(version string) { func Run(version string) {
app := cli.NewApp() app := cli.NewApp()
app.Name = "docstore" app.Name = "docstore"
@ -196,52 +165,18 @@ func Run(version string) {
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
config.Langs = c.StringSlice("langs") config.Langs = c.StringSlice("langs")
if err := loadConfig(); err != nil { if err := loadConfig(); err != nil {
fatal(err) stdlog.Fatal("server: ", err)
} }
config.Version = app.Version config.Version = app.Version
if err := initServer(&config); err != nil { if err := initServer(config); err != nil {
fatal(err) stdlog.Fatal("server: ", err)
}
return nil
},
},
{
Name: "reset",
Usage: "reset the users password and TOTP token (docstore must be stopped)",
Flags: defaultFlags(nil),
Action: func(c *cli.Context) error {
if err := loadConfig(); err != nil {
fatal(err)
}
DB, err := db.NewPlain(log.WithName("db"), &config)
if err != nil {
fatal(err)
}
u, err := DB.GetUser()
if err != nil {
fatal(err)
}
fmt.Println("username:", u.Username)
pw, err := password()
if err != nil {
fatal(err)
}
if err = DB.UpdateUserPassword(pw); err != nil {
fatal(err)
}
fmt.Println("password:", pw)
if err = DB.UpdateUserSecret(""); err != nil {
fatal(err)
}
if err = DB.Close(); err != nil {
fatal(err)
} }
return nil return nil
}, },
}, },
{ {
Name: "dump", Name: "dump",
Usage: "create a json dump of all users (docstore must be stopped)", Usage: "create a json dump of all users (docstore needs to be stopped)",
Flags: defaultFlags(dumpRestoreFlags()), Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
var ( var (
@ -249,32 +184,32 @@ func Run(version string) {
err error err error
) )
if err = loadConfig(); err != nil { if err = loadConfig(); err != nil {
fatal(err) stdlog.Fatal("dump: ", err)
} }
if dumpFile != "-" { if dumpFile != "-" {
file, err = os.OpenFile(dumpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o640) file, err = os.OpenFile(dumpFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("dump: ", err)
} }
} }
DB, err := db.NewPlain(log.WithName("db"), &config) DB, err := db.New(config)
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("dump: ", err)
} }
if err = DB.Dump(file); err != nil { if err = DB.Dump(file); err != nil {
fatal(err) stdlog.Fatal("dump: ", err)
} }
file.Close() file.Close()
err = DB.Close() err = DB.Close()
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("dump: ", err)
} }
return nil return nil
}, },
}, },
{ {
Name: "restore", Name: "restore",
Usage: "restore a json dump (docstore must be stopped)", Usage: "restore a json dump (docstore needs to be stopped)",
Flags: defaultFlags(dumpRestoreFlags()), Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
var ( var (
@ -282,25 +217,25 @@ func Run(version string) {
err error err error
) )
if err = loadConfig(); err != nil { if err = loadConfig(); err != nil {
fatal(err) stdlog.Fatal("restore: ", err)
} }
if dumpFile != "-" { if dumpFile != "-" {
file, err = os.Open(dumpFile) file, err = os.Open(dumpFile)
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("restore: ", err)
} }
} }
DB, err := db.NewPlain(log.WithName("db"), &config) DB, err := db.New(config)
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("restore: ", err)
} }
if err = DB.Restore(file); err != nil { if err = DB.Restore(file); err != nil {
fatal(err) stdlog.Fatal("restore: ", err)
} }
file.Close() file.Close()
err = DB.Close() err = DB.Close()
if err != nil { if err != nil {
fatal(err) stdlog.Fatal("restore: ", err)
} }
return nil return nil
}, },
@ -311,6 +246,18 @@ func Run(version string) {
func serverFlags() []cli.Flag { func serverFlags() []cli.Flag {
return []cli.Flag{ return []cli.Flag{
cli.StringFlag{
Name: "user, u",
Value: defRunUser,
Usage: "drop privileges to user",
Destination: &config.User,
},
cli.StringFlag{
Name: "group, g",
Value: defRunGroup,
Usage: "drop privileges to group",
Destination: &config.Group,
},
cli.StringFlag{ cli.StringFlag{
Name: "listen, l", Name: "listen, l",
Value: defListen, Value: defListen,
@ -350,6 +297,11 @@ func serverFlags() []cli.Flag {
Usage: "tesseract oem value (1-4)", Usage: "tesseract oem value (1-4)",
Destination: &config.TesseractOEM, Destination: &config.TesseractOEM,
}, },
cli.BoolFlag{
Name: "foreground, f",
Usage: "do not fork into the background",
Destination: &config.Foreground,
},
cli.BoolFlag{ cli.BoolFlag{
Name: "secure, s", Name: "secure, s",
Usage: "enable secure cookie flag", Usage: "enable secure cookie flag",

View file

@ -1,14 +0,0 @@
// Copyright (C) 2021 Marius Schellenberger
package cmd
import (
"crypto/rand"
"encoding/hex"
)
func password() (string, error) {
b := make([]byte, 16)
_, err := rand.Read(b)
return hex.EncodeToString(b), err
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package core package core
@ -10,7 +10,6 @@ const (
SearchURI = "/search" SearchURI = "/search"
LogsURI = "/logs" LogsURI = "/logs"
TagsURI = "/tags" TagsURI = "/tags"
StatsURI = "/stats"
UserURI = "/user" UserURI = "/user"
RawPrefix = "/raw" RawPrefix = "/raw"
@ -22,7 +21,10 @@ const (
TagsPrefix = "/tags" TagsPrefix = "/tags"
DeletePrefix = "/delete" DeletePrefix = "/delete"
WebDavPrefix = "/webdav" WebDavPrefix = "/webdav"
StaticPrefix = "/static"
Favicon = "/favicon.ico"
BootstrapCSS = "/bootstrap.css"
CustomCSS = "/custom.css"
DataDir = "data" DataDir = "data"
) )

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package core package core
@ -10,7 +10,6 @@ import (
const ( const (
fileFmt = "20060102_150405" fileFmt = "20060102_150405"
logFmt = "2006/01/02 15:04:05: " logFmt = "2006/01/02 15:04:05: "
createdFmt = "02.01.2006 15:04:05"
) )
var m sync.Mutex var m sync.Mutex
@ -27,7 +26,3 @@ func FileName() string {
func LogTime() string { func LogTime() string {
return time.Now().Format(logFmt) return time.Now().Format(logFmt)
} }
func CreatedTime(t time.Time) string {
return t.Format(createdFmt)
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package core package core
@ -64,17 +64,11 @@ func (p Paths) Len() int { return len(p) }
func (p Paths) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p Paths) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Paths) Less(i, j int) bool { return p[i].Name < p[j].Name } func (p Paths) Less(i, j int) bool { return p[i].Name < p[j].Name }
type Stats struct {
Goroutines int
Alloc string
Sys string
Docs uint64
GoVersion string
}
type Config struct { type Config struct {
RunDir string `json:"run_dir,omitempty"` RunDir string `json:"run_dir,omitempty"`
DataDir string `json:"-"` DataDir string `json:"-"`
User string `json:"user,omitempty"`
Group string `json:"group,omitempty"`
ListenAddr string `json:"listen_addr,omitempty"` ListenAddr string `json:"listen_addr,omitempty"`
LogFile string `json:"log_file,omitempty"` LogFile string `json:"log_file,omitempty"`
Version string `json:"-"` Version string `json:"-"`
@ -83,6 +77,7 @@ type Config struct {
CMDTimeout int `json:"cmd_timeout,omitempty"` CMDTimeout int `json:"cmd_timeout,omitempty"`
TesseractThreads int `json:"tesseract_threads,omitempty"` TesseractThreads int `json:"tesseract_threads,omitempty"`
TesseractOEM int `json:"tesseract_oem,omitempty"` TesseractOEM int `json:"tesseract_oem,omitempty"`
Foreground bool `json:"foreground,omitempty"`
SecureCookie bool `json:"secure_cookie,omitempty"` SecureCookie bool `json:"secure_cookie,omitempty"`
Debug bool `json:"debug,omitempty"` Debug bool `json:"debug,omitempty"`
WebDav bool `json:"webdav,omitempty"` WebDav bool `json:"webdav,omitempty"`

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
@ -15,7 +15,10 @@ func (db *DB) Remove(sig string) error {
} }
func (db *DB) Check(sig string) (ok bool) { func (db *DB) Check(sig string) (ok bool) {
return db.store.Get(blacklistPrefix+sig, nil) == nil if db.store.Get(blacklistPrefix+sig, nil) == nil {
return true
}
return false
} }
func (db *DB) Map() (list jwt.BlacklistMap, err error) { func (db *DB) Map() (list jwt.BlacklistMap, err error) {

View file

@ -1,15 +1,13 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
import ( import (
"io"
"path/filepath"
"git.giftfish.de/ston1th/docstore/pkg/core" "git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/index" "git.giftfish.de/ston1th/docstore/pkg/index"
"git.giftfish.de/ston1th/docstore/pkg/store" "git.giftfish.de/ston1th/docstore/pkg/store"
"github.com/go-logr/logr" "io"
"path/filepath"
) )
const ( const (
@ -20,15 +18,16 @@ const (
const blevePath = "bleve" const blevePath = "bleve"
type DB struct { type DB struct {
log logr.Logger
rm countMutex rm countMutex
IndexMutex countMutex IndexMutex countMutex
store store.Store store store.Store
Index *index.Index Index *index.Index
} }
func New(log logr.Logger, cfg *core.Config) (db *DB, err error) { func New(cfg core.Config) (db *DB, err error) {
db, err = NewPlain(log, cfg) db = new(DB)
dbFile := filepath.Join(cfg.RunDir, storeFile)
db.store, err = store.NewBoltStore(dbFile, nil)
if err != nil { if err != nil {
return return
} }
@ -51,13 +50,6 @@ func New(log logr.Logger, cfg *core.Config) (db *DB, err error) {
return return
} }
func NewPlain(log logr.Logger, cfg *core.Config) (db *DB, err error) {
db = &DB{log: log}
dbFile := filepath.Join(cfg.RunDir, storeFile)
db.store, err = store.NewBoltStore(dbFile, nil)
return
}
func (db *DB) Dump(w io.Writer) error { func (db *DB) Dump(w io.Writer) error {
return db.store.Dump(w) return db.store.Dump(w)
} }
@ -71,8 +63,5 @@ func (db *DB) Close() (err error) {
if err != nil { if err != nil {
return return
} }
if db.Index != nil {
return db.Index.Close() return db.Index.Close()
}
return nil
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
@ -16,82 +16,131 @@ const (
) )
func (db *DB) GetPaths(res core.Results) { func (db *DB) GetPaths(res core.Results) {
db.store.Tx(false, func(tx store.KVStore) error {
for i, v := range res { for i, v := range res {
err := db.store.Get(pathPrefix+v.ID, &res[i].Path) err := tx.Get(pathPrefix+v.ID, &res[i].Path)
if err != nil { if err != nil {
res[i].Path = err.Error() res[i].Path = err.Error()
} }
} }
})
} }
func (db *DB) GetIndexed(p core.Paths) { func (db *DB) GetIndexed(p core.Paths) {
db.store.Tx(false, func(tx store.KVStore) error {
for i, v := range p { for i, v := range p {
p[i].Flag = db.IsIndexed(v.Abs) p[i].Flag = db.IsIndexed(tx, v.Abs)
p[i].Tags, _ = db.GetTag(v.Abs) p[i].Tags, _ = db.GetTag(tx, v.Abs)
} }
})
} }
func (db *DB) IsIndexed(path string) bool { func (db *DB) IsIndexed(tx store.KVStore, path string) bool {
return db.store.Get(idPrefix+path, nil) == nil f := func(tx store.KVStore) error {
return tx.Get(idPrefix+path, nil)
}
if tx != nil {
return f(tx) == nil
}
return f(db.store) == nil
} }
func (db *DB) GetPath(id string) (path string, err error) { func (db *DB) GetPath(tx store.KVStore, id string) (path string, err error) {
err = db.store.Get(pathPrefix+id, &path) f := func(tx store.KVStore) error {
return tx.Get(pathPrefix+id, &path)
}
if tx != nil {
err = f(tx)
return
}
err = f(db.store)
return return
} }
func (db *DB) GetID(path string) (id string, err error) { func (db *DB) GetID(tx store.KVStore, path string) (id string, err error) {
err = db.store.Get(idPrefix+path, &id) f := func(tx store.KVStore) error {
return tx.Get(idPrefix+path, &id)
}
if tx != nil {
err = f(tx)
return
}
err = f(db.store)
return return
} }
func (db *DB) DeletePath(id string) error { func (db *DB) DeletePath(tx store.KVStore, id string) error {
return db.store.Delete(pathPrefix + id) f := func(tx store.KVStore) error {
return tx.Delete(pathPrefix + id)
}
if tx != nil {
return f(tx)
}
return f(db.store)
} }
func (db *DB) DeleteID(path string) error { func (db *DB) DeleteID(tx store.KVStore, path string) error {
return db.store.Delete(idPrefix + path) f := func(tx store.KVStore) error {
return tx.Delete(idPrefix + path)
}
if tx != nil {
return f(tx)
}
return f(db.store)
} }
func (db *DB) NewFile(id, path string, tag []string) (err error) { func (db *DB) NewFile(tx store.KVStore, id, path string, tag []string) (err error) {
err = db.store.Set(idPrefix+path, id) f := func(tx store.KVStore) error {
err = tx.Set(idPrefix+path, id)
if err != nil { if err != nil {
return return
} }
err = db.store.Set(pathPrefix+id, path) err = tx.Set(pathPrefix+id, path)
if err != nil { if err != nil {
return return
} }
return db.SetTag(path, tag) return tx.Set(tagPrefix+path, tag)
}
if tx != nil {
return f(tx)
}
return db.store.Tx(true, f)
} }
func (db *DB) MoveFile(op, np string) (err error) { func (db *DB) MoveFile(op, np string) (err error) {
if !db.IsIndexed(op) { return db.store.Tx(true, func(tx store.KVStore) error {
return nil id, tag, err := db.DeleteFile(tx, op)
}
id, tag, err := db.DeleteFile(op)
if err != nil { if err != nil {
return return
} }
return db.NewFile(id, np, tag) return db.NewFile(tx, id, np, tag)
})
} }
func (db *DB) DeleteFile(path string) (id string, tag []string, err error) { func (db *DB) DeleteFile(tx store.KVStore, path string) (id string, tags []string, err error) {
if !db.IsIndexed(path) { f := func(tx store.KVStore) error {
return err = tx.Get(idPrefix+path, &id)
}
id, err = db.GetID(path)
if err != nil { if err != nil {
return return
} }
err = db.DeleteID(path) err = tx.Delete(idPrefix + path)
if err != nil { if err != nil {
return return
} }
tag, err = db.UnsetTag(path) err = tx.Get(tagPrefix+path, &tags)
if err != nil { if err != nil {
return return
} }
err = db.DeletePath(id) err = tx.Delete(tagPrefix + path)
if err != nil {
return
}
return tx.Delete(pathPrefix + id)
}
if tx != nil {
err = f(tx)
return
}
err = db.store.Tx(true, f)
return return
} }

View file

@ -1,8 +1,9 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
import ( import (
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/docstore/pkg/store" "git.giftfish.de/ston1th/docstore/pkg/store"
) )
@ -31,7 +32,6 @@ var migrators = []migrator{
} }
func (db *DB) runMigrations() (err error) { func (db *DB) runMigrations() (err error) {
log := db.log
var version int var version int
err = db.store.Get(versionKey, &version) err = db.store.Get(versionKey, &version)
if err == store.ErrKeyNotFound { if err == store.ErrKeyNotFound {
@ -44,10 +44,10 @@ func (db *DB) runMigrations() (err error) {
return return
} }
} }
log.Info("database version", "version", version) log.Printf("db: database version %d", version)
for version < currentVersion { for version < currentVersion {
v := version + 1 v := version + 1
log.Info("running migration", "version", v) log.Printf("db: running migration version %d", v)
err = migrators[v](db, v) err = migrators[v](db, v)
if err != nil { if err != nil {
return return
@ -57,7 +57,7 @@ func (db *DB) runMigrations() (err error) {
return return
} }
version = v version = v
log.Info("database version", "version", version) log.Printf("db: database version %d", version)
} }
return return
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
@ -10,7 +10,7 @@ type countMutex struct {
c uint64 c uint64
} }
func (cm *countMutex) Lock() (max, c uint64, ok bool) { func (cm *countMutex) Lock() (uint64, uint64, bool) {
if atomic.CompareAndSwapUint64(&cm.mu, 0, 1) { if atomic.CompareAndSwapUint64(&cm.mu, 0, 1) {
return 0, 0, true return 0, 0, true
} }

View file

@ -1,14 +1,14 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
import ( import (
"errors" "errors"
"fmt" "fmt"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/log"
"regexp" "regexp"
"sort" "sort"
"git.giftfish.de/ston1th/docstore/pkg/core"
) )
const ( const (
@ -17,10 +17,10 @@ const (
) )
var defaultTags = core.Tags{ var defaultTags = core.Tags{
{Name: "invoice", Regex: `(?i)[^e]rechnung|quittung|beleg|invoice|bill|check`}, {"invoice", `(?i)[^e]rechnung|quittung|beleg|invoice|bill|check`},
{Name: "tax", Regex: `(?i)steuer|lohn|tax|salary`}, {"tax", `(?i)steuer|lohn|tax|salary`},
{Name: "contract", Regex: `(?i)vertrag|kündigung|vereinbarung|contract|termination|agreement`}, {"contract", `(?i)vertrag|kündigung|vereinbarung|contract|termination|agreement`},
{Name: "insurance", Regex: `(?i)versicherung|versichert|insurance|insured`}, {"insurance", `(?i)versicherung|versichert|insurance|insured`},
} }
func (db *DB) addDefaultTags() (err error) { func (db *DB) addDefaultTags() (err error) {
@ -34,7 +34,6 @@ func (db *DB) addDefaultTags() (err error) {
} }
func (db *DB) Retag(path string, tags core.RTags) (err error) { func (db *DB) Retag(path string, tags core.RTags) (err error) {
log := db.log.WithValues("file", path)
if tags == nil { if tags == nil {
tags, err = db.GetAllRTags() tags, err = db.GetAllRTags()
if err != nil { if err != nil {
@ -49,14 +48,14 @@ func (db *DB) Retag(path string, tags core.RTags) (err error) {
if err != nil { if err != nil {
return return
} }
log.V(2).Info("retagging") log.Debugf("db: retagging: %s", path)
doc, err := db.Index.Get(id) doc, err := db.Index.Get(id)
if err != nil { if err != nil {
return return
} }
doc.Tags = tags.Match(doc.Text) doc.Tags = tags.Match(doc.Text)
if eq(t, doc.Tags) { if eq(t, doc.Tags) {
log.V(2).Info("retagging skipped") log.Debugf("db: retagging: skipped %s", path)
return return
} }
err = db.Index.Update(id, doc) err = db.Index.Update(id, doc)
@ -67,7 +66,6 @@ func (db *DB) Retag(path string, tags core.RTags) (err error) {
} }
func (db *DB) RetagAll() (err error) { func (db *DB) RetagAll() (err error) {
log := db.log
if m, c, ok := db.rm.Lock(); !ok { if m, c, ok := db.rm.Lock(); !ok {
return fmt.Errorf("retagging already in progress: %d/%d", c, m) return fmt.Errorf("retagging already in progress: %d/%d", c, m)
} }
@ -91,7 +89,7 @@ func (db *DB) RetagAll() (err error) {
if err == nil { if err == nil {
err = errors.New("retagAll had errors") err = errors.New("retagAll had errors")
} }
log.Error(e, "error retagging file", "file", p) log.Printf("db: error retagging %s: %s", p, e)
} }
db.rm.Inc() db.rm.Inc()
} }
@ -117,10 +115,7 @@ func (db *DB) GetAllRTags() (rtags core.RTags, err error) {
return return
} }
for _, v := range tags { for _, v := range tags {
rtags = append(rtags, core.RTag{ rtags = append(rtags, core.RTag{v.Name, regexp.MustCompile(v.Regex)})
Name: v.Name,
Regex: regexp.MustCompile(v.Regex),
})
} }
return return
} }
@ -140,10 +135,7 @@ func (db *DB) NewTag(name, regex string) (err error) {
return return
} }
name = nameRe.ReplaceAllString(name, "") name = nameRe.ReplaceAllString(name, "")
err = db.store.Set(tagsPrefix+name, core.Tag{ err = db.store.Set(tagsPrefix+name, core.Tag{name, regex})
Name: name,
Regex: regex,
})
return return
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package db package db
@ -66,7 +66,10 @@ func (db *DB) CreateUser(username, password string) error {
func (db *DB) BasicAuth(username, password string) bool { func (db *DB) BasicAuth(username, password string) bool {
_, err := db.Login(username, password) _, err := db.Login(username, password)
return err == nil if err == nil {
return true
}
return false
} }
func (db *DB) Login(username, password string) (u core.User, err error) { func (db *DB) Login(username, password string) (u core.User, err error) {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package fs package fs

View file

@ -1,22 +1,21 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package fs package fs
import ( import (
"errors" "errors"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/log"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"git.giftfish.de/ston1th/docstore/pkg/core"
"github.com/go-logr/logr"
) )
const ( const (
DirMode = 0o750 DirMode = 0750
FileMode = 0o640 FileMode = 0640
) )
// Directory represents a directory and its contents // Directory represents a directory and its contents
@ -33,7 +32,7 @@ type Filesystem struct {
scan map[string]struct{} scan map[string]struct{}
} }
func NewFilesystem(log logr.Logger, base string) (fs *Filesystem, err error) { func NewFilesystem(base string) (fs *Filesystem, err error) {
if !filepath.IsAbs(base) { if !filepath.IsAbs(base) {
err = errors.New("base dir '" + base + "' is not an absolute path") err = errors.New("base dir '" + base + "' is not an absolute path")
return return
@ -44,7 +43,7 @@ func NewFilesystem(log logr.Logger, base string) (fs *Filesystem, err error) {
if err != nil { if err != nil {
return return
} }
log.Info("created data directory", "dir", base) log.Printf("fs: created data directory '%s'", base)
fi, err = os.Stat(base) fi, err = os.Stat(base)
if err != nil { if err != nil {
return return

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package fs package fs
@ -27,7 +27,6 @@ var reserved = []string{
core.SearchURI, core.SearchURI,
core.LogsURI, core.LogsURI,
core.TagsURI, core.TagsURI,
core.StatsURI,
core.UserURI, core.UserURI,
core.RawPrefix, core.RawPrefix,
core.IndexPrefix, core.IndexPrefix,
@ -37,7 +36,9 @@ var reserved = []string{
core.MovePrefix, core.MovePrefix,
core.DeletePrefix, core.DeletePrefix,
core.WebDavPrefix, core.WebDavPrefix,
core.StaticPrefix, core.Favicon,
core.BootstrapCSS,
core.CustomCSS,
} }
func checkPath(path string) error { func checkPath(path string) error {

View file

@ -1,5 +1,3 @@
// Copyright (C) 2021 Marius Schellenberger
package fs package fs
import "errors" import "errors"

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package index package index

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package index package index
@ -66,11 +66,6 @@ func (i *Index) Close() error {
return i.i.Close() return i.i.Close()
} }
func (i *Index) DocCount() (c uint64) {
c, _ = i.i.DocCount()
return
}
func (i *Index) Add(text string, tags []string) (id string, err error) { func (i *Index) Add(text string, tags []string) (id string, err error) {
id = newID() id = newID()
for { for {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package index package index

52
pkg/log/log.go Normal file
View file

@ -0,0 +1,52 @@
// Copyright (C) 2019 Marius Schellenberger
package log
import (
"git.giftfish.de/ston1th/docstore/pkg/core"
stdlog "log"
"os"
"path/filepath"
)
var (
log = stdlog.New(os.Stdout, "", stdlog.LstdFlags)
debug = false
)
func InitLogger(cfg core.Config) {
debug = cfg.Debug
if cfg.LogFile == "-" {
return
}
f, err := os.OpenFile(filepath.Join(cfg.DataDir, cfg.LogFile), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0640)
if err != nil {
stdlog.Fatal(err)
}
stdlog.SetOutput(f)
log = stdlog.New(f, "", stdlog.LstdFlags)
}
func Println(v ...interface{}) {
log.Println(v...)
}
func Printf(fmt string, v ...interface{}) {
log.Printf(fmt, v...)
}
func Fatal(v ...interface{}) {
log.Fatal(v...)
}
func Debug(v ...interface{}) {
if debug {
log.Println(append([]interface{}{"debug:"}, v...)...)
}
}
func Debugf(fmt string, v ...interface{}) {
if debug {
log.Printf("debug: "+fmt, v...)
}
}

70
pkg/log/scanlog.go Normal file
View file

@ -0,0 +1,70 @@
package log
import (
"fmt"
"git.giftfish.de/ston1th/docstore/pkg/core"
"strings"
"sync"
)
const defaultSize = 500
type ScanLog struct {
// protects logs
sync.RWMutex
size int
logs []string
}
func NewScanLog(size int) *ScanLog {
if size <= 0 {
size = defaultSize
}
return &ScanLog{size: size}
}
func (l *ScanLog) Printf(f string, v ...interface{}) {
s := fmt.Sprintf(f, v...)
Println(s)
s = core.LogTime() + s
l.Lock()
llen := len(l.logs)
if llen < l.size {
l.logs = append(l.logs, s)
} else if llen == l.size {
l.logs = append(l.logs[1:], s)
}
l.Unlock()
}
func (l *ScanLog) Logs() (logs string) {
l.RLock()
logs = reverseJoin(l.logs, "\n")
l.RUnlock()
return
}
// reverseJoin is a reverse version of strings.Join
// code taken from strings.Join
func reverseJoin(a []string, sep string) string {
switch len(a) {
case 0:
return ""
case 1:
return a[0]
}
n := len(sep) * (len(a) - 1)
for i := 0; i < len(a); i++ {
n += len(a[i])
}
var b strings.Builder
b.Grow(n)
b.WriteString(a[len(a)-1])
for i := len(a) - 2; i >= 0; i-- {
b.WriteString(sep)
b.WriteString(a[i])
}
return b.String()
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package otp package otp

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package scan package scan
@ -6,7 +6,6 @@ import (
"bytes" "bytes"
"net/http" "net/http"
"os" "os"
"strings"
) )
const ( const (
@ -19,8 +18,6 @@ const (
pnmT = "image/x-portable-anymap" pnmT = "image/x-portable-anymap"
jp2T = "image/jp2" jp2T = "image/jp2"
tiffT = "image/tiff" tiffT = "image/tiff"
textT = "text/"
rawT = "application/octet-stream"
maxSniffSize = 512 maxSniffSize = 512
) )
@ -48,11 +45,35 @@ func extendedContentType(buf []byte) string {
return sig.ct return sig.ct
} }
} }
return rawT return "application/octet-stream"
} }
func isImage(ct string, buf []byte) bool { func isPDF(path string) bool {
switch ct { f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, maxSniffSize)
_, err = f.Read(buf)
if err != nil {
return false
}
return http.DetectContentType(buf) == pdfT
}
func isImage(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
buf := make([]byte, maxSniffSize)
_, err = f.Read(buf)
if err != nil {
return false
}
switch http.DetectContentType(buf) {
case jpegT, pngT, bmpT, gifT, webpT: case jpegT, pngT, bmpT, gifT, webpT:
return true return true
default: default:
@ -63,29 +84,3 @@ func isImage(ct string, buf []byte) bool {
} }
return false return false
} }
type filetype struct {
text bool
pdf bool
image bool
}
func (s *Scanner) newFileType(path string) (ft *filetype) {
ft = new(filetype)
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
buf := make([]byte, maxSniffSize)
n, err := f.Read(buf)
if err != nil {
return
}
buf = buf[:n]
ct := http.DetectContentType(buf)
ft.text = strings.HasPrefix(ct, textT)
ft.pdf = ct == pdfT
ft.image = isImage(ct, buf)
return
}

View file

@ -1,20 +1,19 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package scan package scan
import ( import (
"errors" "errors"
"fmt" "fmt"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/godrop/v2"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"time" "time"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/godrop/v2"
"github.com/go-logr/logr"
) )
const ( const (
@ -39,20 +38,19 @@ type Tesseract struct {
var ( var (
wsr = regexp.MustCompile(`[\t\f ]+`) wsr = regexp.MustCompile(`[\t\f ]+`)
nlr = regexp.MustCompile(`\n{3,}`) nlr = regexp.MustCompile(`[\n]{3,}`)
ws = []byte(" ") ws = []byte(" ")
nl = []byte("\n") nl = []byte("\n")
) )
type Scanner struct { type Scanner struct {
log logr.Logger
base string base string
timeout time.Duration timeout time.Duration
t Tesseract t Tesseract
p PDF p PDF
} }
func New(log logr.Logger, c *core.Config) (s *Scanner, err error) { func New(c core.Config) (s *Scanner, err error) {
var ( var (
tcmd string tcmd string
pcmd string pcmd string
@ -94,7 +92,6 @@ func New(log logr.Logger, c *core.Config) (s *Scanner, err error) {
} }
args := []string{"-l", lang, "--oem", fmt.Sprintf("%d", c.TesseractOEM)} args := []string{"-l", lang, "--oem", fmt.Sprintf("%d", c.TesseractOEM)}
s = &Scanner{ s = &Scanner{
log,
c.DataDir, c.DataDir,
time.Second * time.Duration(c.CMDTimeout), time.Second * time.Duration(c.CMDTimeout),
Tesseract{tcmd, args, []string{fmt.Sprintf("OMP_THREAD_LIMIT=%d", threads)}}, Tesseract{tcmd, args, []string{fmt.Sprintf("OMP_THREAD_LIMIT=%d", threads)}},
@ -104,28 +101,15 @@ func New(log logr.Logger, c *core.Config) (s *Scanner, err error) {
} }
func (s *Scanner) Scan(file string) (filename, text string, err error) { func (s *Scanner) Scan(file string) (filename, text string, err error) {
log := s.log
file = fs.Clean(file) file = fs.Clean(file)
scanfile := filepath.Join(s.base, file) scanfile := filepath.Join(s.base, file)
var pdffile string var pdffile string
ft := s.newFileType(scanfile) pdf := isPDF(scanfile)
if !ft.pdf { if !pdf {
if !ft.image { if !isImage(scanfile) {
if ft.text { err = errors.New("error: input file is not an image")
log.V(2).Info("indexing plaintext file", "file", scanfile)
var buf []byte
buf, err = os.ReadFile(scanfile)
if err != nil {
return return
} }
filename = file
text = string(nlr.ReplaceAll(wsr.ReplaceAll(buf, ws), nl))
return
} else {
err = errors.New("error: input: no image, pdf or text file")
return
}
}
var ( var (
pdf []byte pdf []byte
f *os.File f *os.File
@ -133,7 +117,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
p, _ := filepath.Split(file) p, _ := filepath.Split(file)
pdfCmd := exec.Command(s.t.cmd, append(s.t.args, []string{scanfile, "stdout", "pdf"}...)...) pdfCmd := exec.Command(s.t.cmd, append(s.t.args, []string{scanfile, "stdout", "pdf"}...)...)
pdfCmd.Env = s.t.env pdfCmd.Env = s.t.env
log.V(2).Info("tesseract command", "env", pdfCmd.Env, "path", pdfCmd.Path, "args", pdfCmd.Args) log.Debugf("%v %s %v", pdfCmd.Env, pdfCmd.Path, pdfCmd.Args)
pdf, err = timeout(pdfCmd, s.timeout) pdf, err = timeout(pdfCmd, s.timeout)
if err != nil { if err != nil {
return return
@ -156,7 +140,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
filename = file filename = file
} }
txtCmd := exec.Command(s.p.cmd, append(s.p.args, []string{pdffile, "-"}...)...) txtCmd := exec.Command(s.p.cmd, append(s.p.args, []string{pdffile, "-"}...)...)
log.V(2).Info("text2pdf command", "path", txtCmd.Path, "args", txtCmd.Args) log.Debugf("%s %v", txtCmd.Path, txtCmd.Args)
txt, err := timeout(txtCmd, s.timeout) txt, err := timeout(txtCmd, s.timeout)
if err != nil { if err != nil {
return return
@ -166,7 +150,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
return return
} }
text = string(nlr.ReplaceAll(wsr.ReplaceAll(txt, ws), nl)) text = string(nlr.ReplaceAll(wsr.ReplaceAll(txt, ws), nl))
if !ft.pdf { if !pdf {
err = os.Remove(scanfile) err = os.Remove(scanfile)
} }
return return

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package scan package scan

View file

@ -1,9 +1,13 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server
import ( import (
"errors" "errors"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/jwt/v3"
"github.com/gorilla/mux"
"html/template" "html/template"
"io" "io"
"net/http" "net/http"
@ -11,10 +15,6 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"time" "time"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/jwt/v3"
"github.com/gorilla/mux"
) )
const ( const (
@ -77,7 +77,6 @@ type webData struct {
Path string Path string
Paths core.Paths Paths core.Paths
Tags []string Tags []string
Created string
Data interface{} Data interface{}
} }
@ -93,7 +92,7 @@ func (c *Context) Exec() {
c.Err = errors.New("template is nil") c.Err = errors.New("template is nil")
return return
} }
c.Data.Token = newXsrf(c.Token.RawSig()) c.Data.Token = newXsrf(c.Token.RawSig()[:keySize])
c.Data.Version = c.Srv.Config.Version c.Data.Version = c.Srv.Config.Version
if c.Data.BodyTitle == "" { if c.Data.BodyTitle == "" {
@ -123,20 +122,10 @@ func (c *Context) Log() {
c.HTTPStatus = int(reflect.Indirect(reflect.ValueOf(c.Response)).FieldByName("status").Int()) c.HTTPStatus = int(reflect.Indirect(reflect.ValueOf(c.Response)).FieldByName("status").Int())
} }
if c.Err != nil { if c.Err != nil {
c.Srv.Log.Error(c.Err, "access", log.Printf("%s %s %s %d error: %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.HTTPStatus, c.Err)
"client", c.Request.RemoteAddr,
"method", c.Request.Method,
"status", c.HTTPStatus,
"uri", c.Request.URL.Path,
)
return return
} }
c.Srv.Log.Info("access", log.Printf("%s %s %s %d\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, c.HTTPStatus)
"client", c.Request.RemoteAddr,
"method", c.Request.Method,
"status", c.HTTPStatus,
"uri", c.Request.URL.Path,
)
} }
func (c *Context) Error(i interface{}) { func (c *Context) Error(i interface{}) {
@ -164,7 +153,7 @@ func (c *Context) Forbidden() {
} }
func (c *Context) SwitchHandler() { func (c *Context) SwitchHandler() {
c.Srv.Log.V(2).Info("switching to normal handler") log.Debug("server: switching to normal handler")
c.Srv.srv.Handler = c.Srv.handler c.Srv.srv.Handler = c.Srv.handler
c.Redirect(core.IndexURI, http.StatusFound) c.Redirect(core.IndexURI, http.StatusFound)
} }
@ -191,13 +180,7 @@ func (c *Context) Redirect(uri string, code int) {
if len(uri) > 0 && uri[0] != '/' { if len(uri) > 0 && uri[0] != '/' {
uri = "/" + uri uri = "/" + uri
} }
c.Srv.Log.Info("access", log.Printf("%s %s %s %d %s\n", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, code, uri)
"client", c.Request.RemoteAddr,
"method", c.Request.Method,
"status", code,
"uri", c.Request.URL.Path,
"redirect", uri,
)
http.Redirect(c.Response, c.Request, uri, code) http.Redirect(c.Response, c.Request, uri, code)
} }
@ -232,7 +215,7 @@ func (c *Context) GetForm(name string) string {
} }
func (c *Context) Var(name string) (ret string) { func (c *Context) Var(name string) (ret string) {
ret = mux.Vars(c.Request)[name] ret, _ = mux.Vars(c.Request)[name]
return return
} }
@ -247,7 +230,7 @@ func (c *Context) File(name string) (io.Reader, string, error) {
// CheckXsrf validates the xsrf token // CheckXsrf validates the xsrf token
func (c *Context) CheckXsrf() (ok bool) { func (c *Context) CheckXsrf() (ok bool) {
ok = checkXsrf(c.Form("token"), c.Token.RawSig()) ok = checkXsrf(c.Form("token"), c.Token.RawSig()[:keySize])
if !ok { if !ok {
c.Error("wrong csrf token") c.Error("wrong csrf token")
} }
@ -260,7 +243,7 @@ func (c *Context) LoggedOn() (ok bool) {
} }
func (c *Context) LogSetCookie(msg string, err error) { func (c *Context) LogSetCookie(msg string, err error) {
c.Srv.Log.Error(err, msg) log.Printf("ctx: %s %s\n", msg, err)
c.SetCookie(nil, 0) c.SetCookie(nil, 0)
} }
@ -282,7 +265,7 @@ func (c *Context) setCookie(t *jwt.Token, d time.Duration) {
} }
t.Claims[jwt.ExpClaim] = jwt.NewExp(d) t.Claims[jwt.ExpClaim] = jwt.NewExp(d)
if err := c.Srv.JWT.Sign(t); err != nil { if err := c.Srv.JWT.Sign(t); err != nil {
c.Srv.Log.Error(err, "jwt sign") log.Println(err)
return return
} }
c.Token = *t c.Token = *t

View file

@ -1,18 +1,18 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server
import ( import (
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/docstore/pkg/otp"
"git.giftfish.de/ston1th/jwt/v3"
"html/template" "html/template"
"net/http" "net/http"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/docstore/pkg/otp"
"git.giftfish.de/ston1th/jwt/v3"
) )
const noSuchFile = "no such file or directory" const noSuchFile = "no such file or directory"
@ -97,6 +97,24 @@ func authHandler(h ctxHandler) ctxHandler {
} }
} }
func staticHandler(ctx *Context) {
var err error
ctx.SetHeader("Content-Type", "text/css; charset=utf-8")
ctx.SetHeader("Expires", time.Now().UTC().Add(max).Format(http.TimeFormat))
switch ctx.Path() {
case core.BootstrapCSS:
err = ctx.Write(ctx.Srv.res["bootstrap.css"])
case core.CustomCSS:
err = ctx.Write(ctx.Srv.res["custom.css"])
case core.Favicon:
ctx.SetHeader("Content-Type", "image/x-icon")
err = ctx.Write(ctx.Srv.res["favicon.ico"])
}
if err != nil {
log.Println("static:", err)
}
}
type registerNotFoundHandler struct { type registerNotFoundHandler struct {
s *HTTPServer s *HTTPServer
} }
@ -146,46 +164,69 @@ func rawHandler(ctx *Context) {
f, fi, err := ctx.Srv.FS.GetFile(path) f, fi, err := ctx.Srv.FS.GetFile(path)
if err != nil { if err != nil {
ctx.Status(http.StatusNotFound) ctx.Status(http.StatusNotFound)
ctx.Err = ctx.Write([]byte(noSuchFile)) err = ctx.Write([]byte(noSuchFile))
if err != nil {
log.Println("file:", err)
}
return return
} }
ctx.Response.Header().Del("Content-Security-Policy")
http.ServeContent(ctx.Response, ctx.Request, fi.Name(), fi.ModTime(), f) http.ServeContent(ctx.Response, ctx.Request, fi.Name(), fi.ModTime(), f)
} }
func scanFile(srv *HTTPServer, path string) (err error) {
err = srv.FS.AddScan(path)
if err != nil {
return
}
go func() {
defer srv.FS.RemoveScan(path)
l := srv.Log
file, txt, err := srv.Scanner.Scan(path)
if err != nil {
l.Printf("scanner: %s: %s", path, err)
return
}
tags, err := srv.DB.GetAllRTags()
if err != nil {
l.Printf("getTags: %s: %s", path, err)
}
found := tags.Match(txt)
id, err := srv.DB.Index.Add(txt, found)
if err != nil {
l.Printf("index: %s: %s", path, err)
return
}
err = srv.DB.NewFile(id, file, found)
if err != nil {
l.Printf("newFile: %s: %s", path, err)
}
}()
return
}
func indexHandler(ctx *Context) { func indexHandler(ctx *Context) {
log := ctx.Srv.Log
switch ctx.Method() { switch ctx.Method() {
case "GET": case "GET":
var ( var (
err error err error
ok bool
fpath string fpath string
) )
path := ctx.Path() path := ctx.Path()
if fpath, ok = hasTrimPrefix(path, core.IndexPrefix); ok { if strings.HasPrefix(path, core.IndexPrefix) {
log = log.WithValues("file", fpath) fpath = strings.TrimPrefix(path, core.IndexPrefix)
log.V(2).Info("started indexing file") err = scanFile(ctx.Srv, fpath)
err = ctx.Srv.SQ.Add(fpath, nil)
if err != nil {
log.Error(err, "error indexing file")
} }
} else if fpath, ok = hasTrimPrefix(path, core.RetagPrefix); ok { if strings.HasPrefix(path, core.RetagPrefix) {
fpath = strings.TrimPrefix(path, core.RetagPrefix)
if fpath == "" { if fpath == "" {
log.V(2).Info("started retagging all files")
go func() { go func() {
err := ctx.Srv.DB.RetagAll() err := ctx.Srv.DB.RetagAll()
if err != nil { if err != nil {
log.Error(err, "error retagging all files") ctx.Srv.Log.Printf("retagAll: %s", err)
} }
}() }()
} else { } else {
log = log.WithValues("file", fpath)
log.V(2).Info("started retagging file")
err = ctx.Srv.DB.Retag(fpath, nil) err = ctx.Srv.DB.Retag(fpath, nil)
if err != nil {
log.Error(err, "error retagging file")
}
} }
} }
p, _ := filepath.Split(fpath) p, _ := filepath.Split(fpath)
@ -207,22 +248,19 @@ func indexHandler(ctx *Context) {
return return
} }
if m, c, ok := ctx.Srv.DB.IndexMutex.Lock(); ok { if m, c, ok := ctx.Srv.DB.IndexMutex.Lock(); ok {
log.V(2).Info("started indexing all files")
go func() { go func() {
defer ctx.Srv.DB.IndexMutex.Unlock() defer ctx.Srv.DB.IndexMutex.Unlock()
files := ctx.Srv.FS.RecursiveFiles("/") files := ctx.Srv.FS.RecursiveFiles("/")
ctx.Srv.DB.IndexMutex.Set(uint64(len(files))) ctx.Srv.DB.IndexMutex.Set(uint64(len(files)))
for _, f := range files { for _, f := range files {
if !ctx.Srv.DB.IsIndexed(f) { if !ctx.Srv.DB.IsIndexed(f) {
err := ctx.Srv.SQ.Add(f, ctx.Srv.DB.IndexMutex.Inc) scanFile(ctx.Srv, f)
if err != nil {
log.Error(err, "error indexing file", "file", f)
}
} }
ctx.Srv.DB.IndexMutex.Inc()
} }
}() }()
} else { } else {
log.Info("indexing of all files is already in progress", "indexed", c, "remaning", m) ctx.Srv.Log.Printf("indexAll: indexing already in progress: %d/%d", c, m)
} }
ctx.Redirect(core.IndexURI, http.StatusFound) ctx.Redirect(core.IndexURI, http.StatusFound)
} }
@ -282,7 +320,6 @@ func newDirHandler(ctx *Context) {
} }
func moveHandler(ctx *Context) { func moveHandler(ctx *Context) {
log := ctx.Srv.Log
path := strings.TrimPrefix(ctx.Path(), core.MovePrefix) path := strings.TrimPrefix(ctx.Path(), core.MovePrefix)
mode, _, err := ctx.Srv.FS.Mode(path) mode, _, err := ctx.Srv.FS.Mode(path)
if err != nil { if err != nil {
@ -327,19 +364,18 @@ func moveHandler(ctx *Context) {
return return
} }
for _, m := range moved { for _, m := range moved {
log.V(2).Info("moving file", "old", m.Old, "new", m.New)
err = ctx.Srv.DB.MoveFile(m.Old, m.New) err = ctx.Srv.DB.MoveFile(m.Old, m.New)
if err != nil { if err != nil {
log.Error(err, "moving file failed", "old", m.Old, "new", m.New) log.Printf("move: %s: %s", m, err)
continue continue
} }
log.Debugf("move: %s to %s", m.Old, m.New)
} }
ctx.Redirect(newfile, http.StatusFound) ctx.Redirect(newfile, http.StatusFound)
} }
} }
func deleteHandler(ctx *Context) { func deleteHandler(ctx *Context) {
log := ctx.Srv.Log
path := strings.TrimPrefix(ctx.Path(), core.DeletePrefix) path := strings.TrimPrefix(ctx.Path(), core.DeletePrefix)
mode, enoent, err := ctx.Srv.FS.Mode(path) mode, enoent, err := ctx.Srv.FS.Mode(path)
if !enoent && err != nil { if !enoent && err != nil {
@ -382,21 +418,18 @@ func deleteHandler(ctx *Context) {
clean := ctx.Form("clean") clean := ctx.Form("clean")
if clean == "true" { if clean == "true" {
f := fs.Clean(path) f := fs.Clean(path)
log = log.WithValues("file", f)
log.V(2).Info("deleting file from DB")
id, _, err := ctx.Srv.DB.DeleteFile(f) id, _, err := ctx.Srv.DB.DeleteFile(f)
if err != nil { if err != nil {
log.Error(err, "delete from DB failed") log.Printf("db: delete: %s %s", f, err)
} }
if id == "" { if id == "" {
ctx.Redirect(p, http.StatusFound) ctx.Redirect(p, http.StatusFound)
return return
} }
log = log.WithValues("id", id) log.Debugf("delete %s id: %s from index", f, id)
log.V(2).Info("deleting file from index")
err = ctx.Srv.DB.Index.Delete(id) err = ctx.Srv.DB.Index.Delete(id)
if err != nil { if err != nil {
log.Error(err, "delete from index failed") log.Printf("index: delete: %s %s", f, err)
} }
ctx.Redirect(p, http.StatusFound) ctx.Redirect(p, http.StatusFound)
return return
@ -407,18 +440,17 @@ func deleteHandler(ctx *Context) {
return return
} }
for _, f := range files { for _, f := range files {
log.V(2).Info("deleting file from DB", "file", f)
id, _, err := ctx.Srv.DB.DeleteFile(f) id, _, err := ctx.Srv.DB.DeleteFile(f)
if err != nil { if err != nil {
log.Error(err, "delete from DB failed", "file", f) log.Printf("db: delete: %s %s", f, err)
} }
if id == "" { if id == "" {
continue continue
} }
log.V(2).Info("deleting file from index", "file", f, "id", id) log.Debugf("delete %s id: %s from index", f, id)
err = ctx.Srv.DB.Index.Delete(id) err = ctx.Srv.DB.Index.Delete(id)
if err != nil { if err != nil {
log.Error(err, "delete from index failed", "file", f, "id", id) log.Printf("index: delete: %s %s", f, err)
} }
} }
ctx.Redirect(p, http.StatusFound) ctx.Redirect(p, http.StatusFound)
@ -428,7 +460,8 @@ func deleteHandler(ctx *Context) {
func dirHandler(ctx *Context) { func dirHandler(ctx *Context) {
ctx.Template("dirHandler") ctx.Template("dirHandler")
path := ctx.Path() path := ctx.Path()
if ctx.Method() == "GET" { switch ctx.Method() {
case "GET":
mode, enoent, err := ctx.Srv.FS.Mode(path) mode, enoent, err := ctx.Srv.FS.Mode(path)
if !enoent && err != nil { if !enoent && err != nil {
ctx.Error(err) ctx.Error(err)
@ -450,15 +483,7 @@ func dirHandler(ctx *Context) {
Title: "Document Viewer | " + path, Title: "Document Viewer | " + path,
} }
ctx.Template("fileHandler") ctx.Template("fileHandler")
// f := ctx.Srv.DB.IsIndexed(path) f := ctx.Srv.DB.IsIndexed(path)
id, err := ctx.Srv.DB.GetID(path)
f := err == nil
if id != "" {
d, err := ctx.Srv.DB.Index.Get(id)
if err == nil {
ctx.Data.Created = core.CreatedTime(d.Created)
}
}
ctx.Data.Data = core.Path{ ctx.Data.Data = core.Path{
Name: path, Name: path,
Abs: core.RawPrefix + path, Abs: core.RawPrefix + path,
@ -500,7 +525,6 @@ func loginHandler(ctx *Context) {
Title: "Login", Title: "Login",
BodyTitle: "Login", BodyTitle: "Login",
} }
log := ctx.Srv.Log
var data loginData var data loginData
switch ctx.Method() { switch ctx.Method() {
case "GET": case "GET":
@ -524,12 +548,12 @@ func loginHandler(ctx *Context) {
} }
u, err := ctx.Srv.DB.Login(data.User, password) u, err := ctx.Srv.DB.Login(data.User, password)
if err != nil { if err != nil {
log.Error(err, "login failed", "user", data.User) log.Println("login:", data.User, err)
ctx.Error("bad username or password") ctx.Error("bad username or password")
return return
} }
if u.Secret == "" { if u.Secret == "" {
log.Info("login successful", "user", u.Username) log.Println("login:", u.Username)
ctx.Login(u, ctx.Form("remember")) ctx.Login(u, ctx.Form("remember"))
ctx.Redirect(data.Referer, http.StatusFound) ctx.Redirect(data.Referer, http.StatusFound)
return return
@ -553,7 +577,6 @@ func loginTotpHandler(ctx *Context) {
Title: "TOTP Verification", Title: "TOTP Verification",
BodyTitle: "TOTP Veriftcation", BodyTitle: "TOTP Veriftcation",
} }
log := ctx.Srv.Log
switch ctx.Method() { switch ctx.Method() {
case "GET": case "GET":
ctx.Exec() ctx.Exec()
@ -572,7 +595,7 @@ func loginTotpHandler(ctx *Context) {
return return
} }
ref := ctx.Referer() ref := ctx.Referer()
log.Info("login successful", "user", u.Username) log.Println("login:", u.Username)
ctx.Login(u, ctx.Remember()) ctx.Login(u, ctx.Remember())
ctx.Redirect(ref, http.StatusFound) ctx.Redirect(ref, http.StatusFound)
} }
@ -612,7 +635,6 @@ func userEditHandler(ctx *Context) {
Title: "Edit User", Title: "Edit User",
BodyTitle: "Edit User", BodyTitle: "Edit User",
} }
log := ctx.Srv.Log
u, err := ctx.Srv.DB.GetUserWithoutPassword() u, err := ctx.Srv.DB.GetUserWithoutPassword()
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
@ -637,7 +659,7 @@ func userEditHandler(ctx *Context) {
ctx.Error(err) ctx.Error(err)
return return
} }
log.Info("user updated", "user", ctx.User()) log.Printf("update: %s updated\n", ctx.User())
ctx.Redirect(core.IndexURI, http.StatusFound) ctx.Redirect(core.IndexURI, http.StatusFound)
} }
} }
@ -648,7 +670,6 @@ func userTotpHandler(ctx *Context) {
Title: "TOTP", Title: "TOTP",
BodyTitle: "TOTP", BodyTitle: "TOTP",
} }
log := ctx.Srv.Log
req := otp.Request{Username: ctx.User()} req := otp.Request{Username: ctx.User()}
u, err := ctx.Srv.DB.GetUser() u, err := ctx.Srv.DB.GetUser()
if err != nil { if err != nil {
@ -702,9 +723,9 @@ func userTotpHandler(ctx *Context) {
return return
} }
if req.Secret == "" { if req.Secret == "" {
log.Info("totp disabled", "user", user) log.Printf("totp: disabled for %s\n", user)
} else { } else {
log.Info("totp enabled", "user", user) log.Printf("totp: enabled for %s\n", user)
} }
ctx.Redirect("/user/edit", http.StatusFound) ctx.Redirect("/user/edit", http.StatusFound)
} }
@ -716,9 +737,9 @@ func logsHandler(ctx *Context) {
Title: "Logs", Title: "Logs",
BodyTitle: "Logs", BodyTitle: "Logs",
} }
if ctx.Method() == "GET" { switch ctx.Method() {
// TODO remove case "GET":
//ctx.Data.Data = ctx.Srv.Log.Logs() ctx.Data.Data = ctx.Srv.Log.Logs()
ctx.Exec() ctx.Exec()
} }
} }
@ -729,7 +750,8 @@ func tagsHandler(ctx *Context) {
Title: "Tags", Title: "Tags",
BodyTitle: "Tags", BodyTitle: "Tags",
} }
if ctx.Method() == "GET" { switch ctx.Method() {
case "GET":
t, err := ctx.Srv.DB.GetAllTags() t, err := ctx.Srv.DB.GetAllTags()
if err != nil { if err != nil {
ctx.Error(err) ctx.Error(err)
@ -746,7 +768,6 @@ func tagsNewHandler(ctx *Context) {
Title: "New Tag", Title: "New Tag",
BodyTitle: "New Tag", BodyTitle: "New Tag",
} }
log := ctx.Srv.Log
switch ctx.Method() { switch ctx.Method() {
case "GET": case "GET":
ctx.Data.Data = core.Tag{} ctx.Data.Data = core.Tag{}
@ -771,7 +792,7 @@ func tagsNewHandler(ctx *Context) {
ctx.Error(err) ctx.Error(err)
return return
} }
log.Info("new tag created", "user", ctx.User(), "tag", t.Name) log.Printf("tag %s created by: %s\n", t.Name, ctx.User())
ctx.Redirect(core.TagsURI, http.StatusFound) ctx.Redirect(core.TagsURI, http.StatusFound)
} }
} }
@ -782,7 +803,6 @@ func tagsEditHandler(ctx *Context) {
Title: "Edit Tag", Title: "Edit Tag",
BodyTitle: "Edit Tag", BodyTitle: "Edit Tag",
} }
log := ctx.Srv.Log
name := ctx.Var("tag") name := ctx.Var("tag")
switch ctx.Method() { switch ctx.Method() {
case "GET": case "GET":
@ -809,7 +829,7 @@ func tagsEditHandler(ctx *Context) {
ctx.Error(err) ctx.Error(err)
return return
} }
log.Info("tag updated", "user", ctx.User(), "tag", t.Name) log.Printf("tag %s updated by: %s\n", t.Name, ctx.User())
ctx.Redirect(core.TagsURI, http.StatusFound) ctx.Redirect(core.TagsURI, http.StatusFound)
} }
} }
@ -820,7 +840,6 @@ func tagsDelHandler(ctx *Context) {
Title: "Delete Tag", Title: "Delete Tag",
BodyTitle: "Delete Tag", BodyTitle: "Delete Tag",
} }
log := ctx.Srv.Log
name := ctx.Var("tag") name := ctx.Var("tag")
ctx.Data.Data = name ctx.Data.Data = name
switch ctx.Method() { switch ctx.Method() {
@ -834,30 +853,17 @@ func tagsDelHandler(ctx *Context) {
ctx.Error(err) ctx.Error(err)
return return
} }
log.Info("tag deleted", "user", ctx.User(), "tag", name) log.Printf("tag %s deleted by: %s\n", name, ctx.User())
ctx.Redirect(core.TagsURI, http.StatusFound) ctx.Redirect(core.TagsURI, http.StatusFound)
} }
} }
func statsHandler(ctx *Context) {
ctx.Template("statsHandler")
ctx.Data = webData{
Title: "Stats",
BodyTitle: "Stats",
}
if ctx.Method() == "GET" {
ctx.Data.Data = getStats(ctx.Srv.DB.Index)
ctx.Exec()
}
}
func logoutHandler(ctx *Context) { func logoutHandler(ctx *Context) {
log := ctx.Srv.Log
user := ctx.User() user := ctx.User()
if user == "" { if user == "" {
user = ctx.Totp() + " (totp)" user = ctx.Totp() + " (totp)"
} }
log.Info("user logged out", "user", user) log.Println("logout:", user)
ctx.Srv.JWT.Invalidate(&ctx.Token) ctx.Srv.JWT.Invalidate(&ctx.Token)
ctx.SetCookie(nil, 0) ctx.SetCookie(nil, 0)
ctx.Redirect(core.LoginURI, http.StatusFound) ctx.Redirect(core.LoginURI, http.StatusFound)

View file

@ -1,37 +0,0 @@
// Copyright (C) 2021 Marius Schellenberger
package server
import (
"runtime"
"strconv"
"git.giftfish.de/ston1th/docstore/pkg/core"
bleve "git.giftfish.de/ston1th/docstore/pkg/index"
)
const div = 1048576
func fmtMem(i uint64) string {
return strconv.FormatFloat(float64(i)/div, 'f', 2, 64)
}
func getStats(i *bleve.Index) *core.Stats {
mem := new(runtime.MemStats)
runtime.ReadMemStats(mem)
return &core.Stats{
Goroutines: runtime.NumGoroutine(),
Alloc: fmtMem(mem.Alloc),
Sys: fmtMem(mem.Sys),
Docs: i.DocCount(),
GoVersion: runtime.Version(),
}
}
// Parts taken from strings.TrimPrefix
func hasTrimPrefix(s, prefix string) (string, bool) {
if len(s) >= len(prefix) && s[0:len(prefix)] == prefix {
return s[len(prefix):], true
}
return s, false
}

View file

@ -1,80 +0,0 @@
// Copyright (C) 2022 Marius Schellenberger
package server
import "context"
type ScanQueue struct {
ctx context.Context
srv *HTTPServer
jobs chan job
}
type job struct {
path string
callback func()
}
func NewScanQueue(ctx context.Context, srv *HTTPServer, buffer int) *ScanQueue {
return &ScanQueue{
ctx: ctx,
srv: srv,
jobs: make(chan job, buffer),
}
}
func (sq *ScanQueue) Add(path string, callback func()) (err error) {
err = sq.srv.FS.AddScan(path)
if err != nil {
return
}
go func() {
select {
case <-sq.ctx.Done():
return
case sq.jobs <- job{path, callback}:
}
}()
return
}
func (sq *ScanQueue) Scan() {
for {
select {
case <-sq.ctx.Done():
//close(sq.jobs)
return
case j := <-sq.jobs:
sq.scanFile(j)
}
}
}
func (sq *ScanQueue) scanFile(j job) {
srv := sq.srv
path := j.path
if j.callback != nil {
defer j.callback()
}
defer srv.FS.RemoveScan(path)
log := srv.Log.WithValues("file", path)
file, txt, err := srv.Scanner.Scan(path)
if err != nil {
log.Error(err, "error scanning file")
return
}
tags, err := srv.DB.GetAllRTags()
if err != nil {
log.Error(err, "error getting tags")
}
found := tags.Match(txt)
id, err := srv.DB.Index.Add(txt, found)
if err != nil {
log.Error(err, "error adding file to index")
return
}
err = srv.DB.NewFile(id, file, found)
if err != nil {
log.Error(err, "error adding file to DB")
}
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server
@ -10,14 +10,30 @@ type route struct {
Methods []string Methods []string
} }
var registerRoutes = []route{ var static = []route{
{ {
core.Favicon,
staticHandler,
[]string{"GET"},
},
{
core.BootstrapCSS,
staticHandler,
[]string{"GET"},
},
{
core.CustomCSS,
staticHandler,
[]string{"GET"},
},
}
var registerRoutes = append(static, route{
core.IndexURI, core.IndexURI,
jwtRegisterHandler( jwtRegisterHandler(
registerHandler), registerHandler),
[]string{"GET", "POST"}, []string{"GET", "POST"},
}, })
}
var prefixRoutes = []route{ var prefixRoutes = []route{
{ {
@ -78,7 +94,7 @@ var prefixRoutes = []route{
}, },
} }
var routes = []route{ var routes = append(static, []route{
{ {
core.SearchURI, core.SearchURI,
jwtHandler( jwtHandler(
@ -127,13 +143,6 @@ var routes = []route{
tagsHandler)), tagsHandler)),
[]string{"GET"}, []string{"GET"},
}, },
{
core.StatsURI,
jwtHandler(
authHandler(
statsHandler)),
[]string{"GET"},
},
{ {
"/tags/new", "/tags/new",
jwtHandler( jwtHandler(
@ -161,4 +170,4 @@ var routes = []route{
logoutHandler), logoutHandler),
[]string{"GET", "POST"}, []string{"GET", "POST"},
}, },
} }...)

View file

@ -1,27 +1,25 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server
import ( import (
"bytes" "bytes"
"context" "context"
"embed"
"errors" "errors"
"html/template"
"net"
"net/http"
"time"
"git.giftfish.de/ston1th/authdav" "git.giftfish.de/ston1th/authdav"
"git.giftfish.de/ston1th/docstore/pkg/core" "git.giftfish.de/ston1th/docstore/pkg/core"
"git.giftfish.de/ston1th/docstore/pkg/db" "git.giftfish.de/ston1th/docstore/pkg/db"
"git.giftfish.de/ston1th/docstore/pkg/fs" "git.giftfish.de/ston1th/docstore/pkg/fs"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/docstore/pkg/scan" "git.giftfish.de/ston1th/docstore/pkg/scan"
"git.giftfish.de/ston1th/godrop/v2" "git.giftfish.de/ston1th/godrop/v2"
"git.giftfish.de/ston1th/jwt/v3" "git.giftfish.de/ston1th/jwt/v3"
"github.com/go-logr/logr"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/webdav" "golang.org/x/net/webdav"
"html/template"
"net"
"net/http"
"time"
) )
const ( const (
@ -29,7 +27,7 @@ const (
) )
type HTTPServer struct { type HTTPServer struct {
Config *core.Config Config core.Config
listener net.Listener listener net.Listener
srv *http.Server srv *http.Server
@ -39,39 +37,30 @@ type HTTPServer struct {
JWT *jwt.JWT JWT *jwt.JWT
Scanner *scan.Scanner Scanner *scan.Scanner
FS *fs.Filesystem FS *fs.Filesystem
SQ *ScanQueue Log *log.ScanLog
Log logr.Logger
plain logr.Logger
cancel func()
templ map[string]*template.Template templ map[string]*template.Template
res map[string][]byte res map[string][]byte
} }
func NewHTTPServer(log logr.Logger, cfg *core.Config, l net.Listener, s *scan.Scanner) (srv *HTTPServer) { func NewHTTPServer(cfg core.Config, l net.Listener, s *scan.Scanner) (srv *HTTPServer) {
ctx, cancel := context.WithCancel(context.Background())
srv = &HTTPServer{ srv = &HTTPServer{
Config: cfg, Config: cfg,
listener: l, listener: l,
Scanner: s, Scanner: s,
Log: log.WithName("server"), Log: log.NewScanLog(0),
plain: log,
cancel: cancel,
} }
srv.SQ = NewScanQueue(ctx, srv, 100)
srv.loadTemplates() srv.loadTemplates()
srv.srv = &http.Server{} srv.srv = &http.Server{}
return return
} }
func (s *HTTPServer) Start() (err error) { func (s *HTTPServer) Start() (err error) {
log := s.Log s.FS, err = fs.NewFilesystem(s.Config.DataDir)
s.FS, err = fs.NewFilesystem(s.plain.WithName("fs"), s.Config.DataDir)
if err != nil { if err != nil {
return errors.New("fs: " + err.Error()) return errors.New("fs: " + err.Error())
} }
s.DB, err = db.New(s.plain.WithName("db"), s.Config) s.DB, err = db.New(s.Config)
if err != nil { if err != nil {
return errors.New("db: " + err.Error()) return errors.New("db: " + err.Error())
} }
@ -79,33 +68,28 @@ func (s *HTTPServer) Start() (err error) {
if err != nil { if err != nil {
return return
} }
opts := []jwt.JWTOption{jwt.WithNonce(), jwt.WithBlacklist(s.DB)}
if s.Config.Secret != "" { if s.Config.Secret != "" {
opts = append(opts, jwt.WithSecret(bytes.NewBufferString(s.Config.Secret))) s.JWT, err = jwt.New(jwt.DefaultExpiry, s.DB, bytes.NewBufferString(s.Config.Secret))
} else {
s.JWT, err = jwt.New(jwt.DefaultExpiry, s.DB, nil)
} }
s.JWT, err = jwt.New(opts...)
if err != nil { if err != nil {
return errors.New("server: " + err.Error()) return errors.New("server: " + err.Error())
} }
s.handler = s.buildRoutes() s.handler = s.buildRoutes()
if !s.DB.UserExists() { if !s.DB.UserExists() {
log.V(2).Info("starting register handler") log.Debug("server: starting register handler")
s.srv.Handler = s.register() s.srv.Handler = s.register()
} else { } else {
s.srv.Handler = s.handler s.srv.Handler = s.handler
} }
go s.SQ.Scan()
go func() { go func() {
err = s.srv.Serve(s.listener) log.Println(s.srv.Serve(s.listener))
if err != nil && err != http.ErrServerClosed {
log.Error(err, "http server error")
}
}() }()
return return
} }
func (s *HTTPServer) Stop() error { func (s *HTTPServer) Stop() error {
s.cancel()
s.DB.Close() s.DB.Close()
s.JWT.Stop() s.JWT.Stop()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
@ -116,33 +100,21 @@ func (s *HTTPServer) Stop() error {
func (s *HTTPServer) register() http.Handler { func (s *HTTPServer) register() http.Handler {
r := mux.NewRouter() r := mux.NewRouter()
r.NotFoundHandler = &registerNotFoundHandler{s} r.NotFoundHandler = &registerNotFoundHandler{s}
r.Handle("/static/{file}", http.StripPrefix("/", http.FileServer(http.FS(static))))
for _, v := range registerRoutes { for _, v := range registerRoutes {
r.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...) r.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
} }
return r return r
} }
//go:embed static/*
var static embed.FS
func (s *HTTPServer) buildRoutes() http.Handler { func (s *HTTPServer) buildRoutes() http.Handler {
r := mux.NewRouter() r := mux.NewRouter()
r.NotFoundHandler = &notFoundHandler{s} r.NotFoundHandler = &notFoundHandler{s}
if s.Config.WebDav { if s.Config.WebDav {
dav := authdav.NewWriteOnlyOnceFileSystem(webdav.Dir(s.Config.DataDir)) fs := authdav.NewWriteOnlyOnceFileSystem(webdav.Dir(s.Config.DataDir))
dav.Filters = []authdav.Filter{authdav.NewMacOSFilter()} fs.Filters = []authdav.Filter{authdav.NewMacOSFilter()}
h := authdav.NewWebdavBasicAuth( h := authdav.NewWebdavBasicAuth(core.WebDavPrefix, fs, nil, webdavLogger, s.DB, "DocStore WebDav")
core.WebDavPrefix,
dav,
nil,
webdavLogger(s.plain.WithName("webdav")),
s.DB,
"DocStore WebDav",
)
r.PathPrefix(core.WebDavPrefix).Handler(h) r.PathPrefix(core.WebDavPrefix).Handler(h)
} }
r.Handle("/static/{file}", http.StripPrefix("/", http.FileServer(http.FS(static))))
for _, v := range routes { for _, v := range routes {
r.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...) r.HandleFunc(v.Path, s.contextWrapper(v.Handler)).Methods(v.Methods...)
} }

File diff suppressed because one or more lines are too long

View file

@ -1,33 +0,0 @@
{{define "body"}}
<div class="page-header">
<div class="row">
<h2>{{.BodyTitle}}</h2>
</div>
</div>
<div class="row">
<table class="table table-stripped table-hover">
<tbody>
<tr>
<td><b>Goroutines</b></td>
<td>{{.Data.Goroutines}}</td>
</tr>
<tr>
<td><b>Memory usage</b></td>
<td>{{.Data.Alloc}} MiB</td>
</tr>
<tr>
<td><b>Memory allocated</b></td>
<td>{{.Data.Sys}} MiB</td>
</tr>
<tr>
<td><b>Documents</b></td>
<td>{{.Data.Docs}}</td>
</tr>
<tr>
<td><b>Go Version</b></td>
<td>{{.Data.GoVersion}}</td>
</tr>
</tbody>
</table>
</div>
{{end}}

View file

@ -1,27 +1,14 @@
// Copyright (C) 2022 Marius Schellenberger
package server package server
import ( import (
"git.giftfish.de/ston1th/docstore/pkg/log"
"net/http" "net/http"
"github.com/go-logr/logr"
) )
func webdavLogger(log logr.Logger) func(r *http.Request, err error) { func webdavLogger(r *http.Request, err error) {
return func(r *http.Request, err error) {
if err != nil { if err != nil {
log.Error(err, "access", log.Printf("webdav: %s %s %s error: %s\n", r.RemoteAddr, r.Method, r.URL, err)
"client", r.RemoteAddr,
"method", r.Method,
"uri", r.URL.Path,
)
return return
} }
log.Info("access", log.Printf("webdav: %s %s %s\n", r.RemoteAddr, r.Method, r.URL)
"client", r.RemoteAddr,
"method", r.Method,
"uri", r.URL.Path,
)
}
} }

View file

@ -1,30 +1,28 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server
import ( import (
"crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/subtle"
"encoding/base64" "encoding/base64"
"git.giftfish.de/ston1th/docstore/pkg/log"
"git.giftfish.de/ston1th/jwt/v3"
) )
const ( const keySize = jwt.KeySize / 2
keySize = 16
fullSize = keySize * 2
)
func genKey(length int) (bytes []byte) { func genKey(length int) (bytes []byte) {
bytes = make([]byte, length) bytes = make([]byte, length)
rand.Read(bytes) if _, err := rand.Reader.Read(bytes); err != nil {
log.Println("genKey:", err)
}
return return
} }
func newXsrf(secret []byte) string { func newXsrf(secret []byte) string {
mac := hmac.New(sha256.New, secret)
rnd := genKey(keySize) rnd := genKey(keySize)
mac.Write(rnd) return base64.RawURLEncoding.EncodeToString(append(rnd, xor(rnd, secret)...))
return base64.RawURLEncoding.EncodeToString(append(rnd, mac.Sum(nil)[:keySize]...))
} }
func checkXsrf(xsrf string, secret []byte) bool { func checkXsrf(xsrf string, secret []byte) bool {
@ -32,10 +30,20 @@ func checkXsrf(xsrf string, secret []byte) bool {
if err != nil { if err != nil {
return false return false
} }
if len(t) != fullSize { if len(t) != jwt.KeySize {
return false return false
} }
mac := hmac.New(sha256.New, secret) return subtle.ConstantTimeCompare(secret, xor(t[keySize:], t[:keySize])) == 1
mac.Write(t[:keySize]) }
return hmac.Equal(t[keySize:], mac.Sum(nil)[:keySize])
func xor(a, b []byte) (c []byte) {
n := len(a)
if len(b) < n {
return nil
}
c = make([]byte, n)
for i := 0; i < n; i++ {
c[i] = a[i] ^ b[i]
}
return
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package server package server

View file

@ -1,16 +1,16 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store
import ( import (
"encoding/json" "encoding/json"
bolt "go.etcd.io/bbolt" "github.com/boltdb/bolt"
"io" "io"
) )
const ( const (
defaultBoltBucket = "default" defaultBoltBucket = "default"
fileMode = 0o640 fileMode = 0640
) )
type BoltStore struct { type BoltStore struct {
@ -26,7 +26,7 @@ func NewBoltStore(file string, m Marshaler) (Store, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := db.Update(func(tx *bolt.Tx) (err error) { if err = db.Update(func(tx *bolt.Tx) (err error) {
_, err = tx.CreateBucketIfNotExists([]byte(defaultBoltBucket)) _, err = tx.CreateBucketIfNotExists([]byte(defaultBoltBucket))
return return
}); err != nil { }); err != nil {
@ -84,6 +84,19 @@ func (bs *BoltStore) Restore(r io.Reader) (err error) {
}) })
} }
func (bs *BoltStore) Tx(writeable bool, f func(tx KVStore) error) error {
tx, err := db.db.Begin(writeable)
if err != nil {
return err
}
err = f(&BoltStoreTx{tx, bs})
if err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func (bs *BoltStore) Get(key string, v interface{}) (err error) { func (bs *BoltStore) Get(key string, v interface{}) (err error) {
err = bs.db.View(func(tx *bolt.Tx) error { err = bs.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key)) b := tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key))
@ -120,12 +133,23 @@ func (bs *BoltStore) ForEach(f func(string, []byte) error) error {
} }
func (bs *BoltStore) ForEachPrefix(prefix string, f func(string, []byte) error) error { func (bs *BoltStore) ForEachPrefix(prefix string, f func(string, []byte) error) error {
return bs.ForEach(func(k string, v []byte) error { return bs.db.View(func(tx *bolt.Tx) error {
if trim, ok := hasTrimPrefix(k, prefix); ok { c := tx.Bucket([]byte(defaultBoltBucket)).Cursor()
return f(trim, v) p := []byte(prefix)
for k, v := c.Seek(p); k != nil && bytes.HasPrefix(k, p); k, v = c.Next() {
err := f(trimPrefix(k, p), v)
if err != nil {
return err
}
} }
return nil return nil
}) })
//return bs.ForEach(func(k string, v []byte) error {
// if trim, ok := hasTrimPrefix(k, prefix); ok {
// return f(trim, v)
// }
// return nil
//})
} }
func (bs *BoltStore) Delete(key string) error { func (bs *BoltStore) Delete(key string) error {
@ -137,3 +161,52 @@ func (bs *BoltStore) Delete(key string) error {
func (bs *BoltStore) Close() error { func (bs *BoltStore) Close() error {
return bs.db.Close() return bs.db.Close()
} }
type BoltStoreTx struct {
tx *bolt.Tx
bs *BoltStore
}
func (tx *BoltStoreTx) Get(key string, v interface{}) (err error) {
b := tx.tx.Bucket([]byte(defaultBoltBucket)).Get([]byte(key))
if b == nil {
return ErrKeyNotFound
}
if v != nil {
return tx.bs.Unmarshal(b, v)
}
return nil
}
func (tx *BoltStoreTx) Set(key string, v interface{}) error {
var b []byte
if v != nil {
b, err = tx.bs.Marshal(v)
if err != nil {
return
}
}
return tx.tx.Bucket([]byte(defaultBoltBucket)).Put([]byte(key), b)
}
func (tx *BoltStoreTx) ForEach(f func(string, []byte) error) error {
return tx.tx.Bucket([]byte(defaultBoltBucket)).ForEach(func(k, v []byte) error {
return f(string(k), v)
})
}
func (tx *BoltStoreTx) ForEachPrefix(prefix string, f func(string, []byte) error) error {
c := tx.tx.Bucket([]byte(defaultBoltBucket)).Cursor()
p := []byte(prefix)
for k, v := c.Seek(p); k != nil && bytes.HasPrefix(k, p); k, v = c.Next() {
err := f(trimPrefix(k, p), v)
if err != nil {
return err
}
}
return nil
}
func (tx *BoltStoreTx) Delete(key string) error {
return tx.tx.Bucket([]byte(defaultBoltBucket)).Delete([]byte(key))
}

View file

@ -1,10 +1,11 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store
import ( import (
"bytes" "bytes"
"errors" "errors"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -19,7 +20,7 @@ func TestBoltStore(t *testing.T) {
{"123"}, {"123"},
{"hello"}, {"hello"},
} }
dir, err := os.MkdirTemp("", "") dir, err := ioutil.TempDir("", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store
@ -9,3 +9,8 @@ func hasTrimPrefix(s, prefix string) (string, bool) {
} }
return s, false return s, false
} }
// Parts taken from bytes.TrimPrefix
func trimPrefix(b, prefix []byte) string {
return string(b[len(prefix):])
}

11
pkg/store/kvstore.go Normal file
View file

@ -0,0 +1,11 @@
// Copyright (C) 2019 Marius Schellenberger
package store
type KVStore interface {
Get(string, interface{}) error
Set(string, interface{}) error
ForEach(func(string, []byte) error) error
ForEachPrefix(string, func(string, []byte) error) error
Delete(string) error
}

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger // Copyright (C) 2019 Marius Schellenberger
package store package store
@ -14,10 +14,7 @@ type Store interface {
Marshaler Marshaler
Dumper Dumper
Restorer Restorer
Get(string, interface{}) error KVStore
Set(string, interface{}) error Tx(writeable bool, f func(tx KVStore) error) error
ForEach(func(string, []byte) error) error
ForEachPrefix(string, func(string, []byte) error) error
Delete(string) error
Close() error Close() error
} }

View file

@ -5,18 +5,12 @@ pkg_add poppler-utils tesseract tesseract-deu tesseract-eng
# rc script # rc script
cat <<'EOF'> /etc/rc.d/docstore cat <<'EOF'> /etc/rc.d/docstore
#!/bin/ksh #!/bin/sh
daemon="/usr/local/sbin/docstore" daemon="/usr/local/sbin/docstore"
daemon_user="docstore"
daemon_logger="daemon.info"
. /etc/rc.d/rc.subr . /etc/rc.d/rc.subr
pexp=".*${daemon}.*"
rc_bg=YES
rc_reload=NO
rc_cmd $1 rc_cmd $1
EOF EOF
chmod 555 /etc/rc.d/docstore chmod 555 /etc/rc.d/docstore

4
sri.sh
View file

@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
# Copyright (C) 2021 Marius Schellenberger # Copyright (C) 2019 Marius Schellenberger
HASH="sha256" HASH="sha256"
DIR="pkg/server/static" DIR="templates"
FILES="*.css *.js" FILES="*.css *.js"
BINS="*.ico" BINS="*.ico"
echo "SRI (subresource integrity) hashes" echo "SRI (subresource integrity) hashes"

111
templates.sh Executable file
View file

@ -0,0 +1,111 @@
#!/bin/bash
# Copyright (C) 2019 Marius Schellenberger
# this file generates Go source code for the gowiki
# it contains html and css and uses the contents
# of the templates directory
templates_go="pkg/server/templates.go"
echo "Generating ${templates_go}"
if [ ! -d templates ]; then
echo "error: templates directory not found" 1>&2
exit 2
fi
cat << EOF > ${templates_go}
// Copyright (C) 2019 Marius Schellenberger
// This file is auto-generated by templates.sh
// using the contents of the templates directory
package server
import (
"encoding/base64"
"errors"
"git.giftfish.de/ston1th/docstore/pkg/log"
"html/template"
)
EOF
echo "const (" >> ${templates_go}
for f in $(ls templates/*.html); do
fname=${f/templates\//}
fname=${fname/.html/}
echo " ${fname} = \`$(cat ${f})\`" >> ${templates_go}
done
for f in $(ls templates/*.css); do
fname=${f/templates\//}
fname=${fname/.css/CSS}
echo " ${fname} = \`$(cat ${f})\`" >> ${templates_go}
done
echo " favicon = \"$(base64 -w 0 templates/favicon.ico)\"" >> ${templates_go}
cat << EOF >> ${templates_go}
)
func (s *HTTPServer) loadTemplates() {
var fatal bool
s.templ = make(map[string]*template.Template)
s.res = make(map[string][]byte)
parse := func(html ...string) (temp *template.Template) {
var err error
temp = template.New("")
for _, s := range html {
temp, err = temp.Parse(s)
if err != nil {
log.Println("parse:", err)
fatal = true
return
}
}
return
}
// resources
s.res["bootstrap.css"] = []byte(bootstrapCSS)
s.res["custom.css"] = []byte(customCSS)
fav, err := base64.StdEncoding.DecodeString(favicon)
if err != nil {
log.Println("favicon:", err)
fatal = true
}
s.res["favicon.ico"] = fav
// register
s.templ["registerHandler"] = parse(index, menu, register)
s.templ["dirHandler"] = parse(index, menu, dir)
s.templ["fileHandler"] = parse(index, menu, file)
s.templ["uploadHandler"] = parse(index, menu, upload)
s.templ["newDirHandler"] = parse(index, menu, newDir)
s.templ["moveHandler"] = parse(index, menu, move)
s.templ["enoentHandler"] = parse(index, menu, enoent)
s.templ["deleteHandler"] = parse(index, menu, delete)
s.templ["notFoundHandler"] = parse(index, menu, notFound)
s.templ["forbiddenHandler"] = parse(index, menu, forbidden)
s.templ["iseHandler"] = parse(index, menu, ise)
s.templ["loginHandler"] = parse(index, menu, login)
s.templ["loginTotpHandler"] = parse(index, menu, loginTotp)
s.templ["searchHandler"] = parse(index, menu, search)
s.templ["userEditHandler"] = parse(index, menu, userEdit)
// totp
s.templ["userTotpHandler"] = parse(index, menu, userTotp)
// tags
s.templ["tagsHandler"] = parse(index, menu, tagsList)
s.templ["tagsNewHandler"] = parse(index, menu, tagsNew)
s.templ["tagsEditHandler"] = parse(index, menu, tagsEdit)
s.templ["tagsDelHandler"] = parse(index, menu, tagsDel)
// logs
s.templ["logsHandler"] = parse(index, menu, logs)
if fatal {
log.Fatal("parse: ", errors.New("parsing templates failed"))
}
}
EOF

View file

@ -106,8 +106,6 @@ code {
} }
embed { embed {
background-color: #999999; background-color: #999999;
width: 100%;
height: 700px;
} }
textarea,input,select { textarea,input,select {
color: #e2e2e2 !important; color: #e2e2e2 !important;

View file

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

View file

@ -24,18 +24,17 @@
</div> </div>
</div> </div>
</div> </div>
{{if .Tags}}
<div class="row"> <div class="row">
<div class="alert alert-secondary full-width"> <div class="alert alert-secondary full-width">
{{if .Tags}}
Tags Tags
{{range $t := .Tags}} {{range $t := .Tags}}
<a href="/search?query=tags:{{$t}}" class="btn badge btn-primary tag">{{$t}}</a> <a href="/search?query=tags:{{$t}}" class="btn badge btn-primary tag">{{$t}}</a>
{{end}} {{end}}
{{end}}
<span class="float-right">{{.Created}}</span>
</div> </div>
</div> </div>
{{end}}
<div class="row"> <div class="row">
<embed src="{{.Data.Abs}}" toolbar="1" statusbar="1" navpanes="1"></embed> <embed src="{{.Data.Abs}}" width="100%" height="700px" toolbar="1" statusbar="1" navpanes="1"></embed>
</div> </div>
{{end}} {{end}}

View file

@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>DocStore | {{.Title}}</title> <title>DocStore | {{.Title}}</title>
<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico" integrity="sha256-bgotk/IPq4Yvt6s8AQGPTM5ZjPjQ6rrBRa2EyxpnFSc="></link> <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" integrity="sha256-bgotk/IPq4Yvt6s8AQGPTM5ZjPjQ6rrBRa2EyxpnFSc="></link>
<link rel="stylesheet" type="text/css" href="/static/bootstrap.css" media="screen" integrity="sha256-3YeveuySvBgp2GItUS8eV1R/9T1zcPMyvw22SyVf8qo="></link> <link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-3YeveuySvBgp2GItUS8eV1R/9T1zcPMyvw22SyVf8qo="></link>
<link rel="stylesheet" type="text/css" href="/static/custom.css" media="screen" integrity="sha256-wLiyjr/D8ZaaIPLjUmZ3zdm45/HW/cy4kCVTViwkPeI="></link> <link rel="stylesheet" type="text/css" href="/custom.css" media="screen" integrity="sha256-6jxgK5/GbgoDJ3zQ2bcYXgSUsOvQ1IF18Krslbwlk7w="></link>
<meta name="theme-color" content="#375a7f"> <meta name="theme-color" content="#375a7f">
<meta name="description" content="{{.Title}}"> <meta name="description" content="{{.Title}}">
</head> </head>

View file

@ -15,7 +15,6 @@
{{end}} {{end}}
<ul class="nav navbar-nav ml-auto"> <ul class="nav navbar-nav ml-auto">
{{if .Login}} {{if .Login}}
<li class="nav-item"><a class="nav-link" href="/stats">Stats</a></li>
<li class="nav-item"><a class="nav-link" href="/user/edit">Profile</a></li> <li class="nav-item"><a class="nav-link" href="/user/edit">Profile</a></li>
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li> <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
{{else}} {{else}}

View file

@ -8,7 +8,7 @@
<div class="card-body"> <div class="card-body">
<div class="alert alert-dismissible alert-primary"> <div class="alert alert-dismissible alert-primary">
<strong>Note</strong> Editing a tag does not update tags already assigned to a document. <strong>Note</strong> Editing a tag does not update tags already assigned to a document.
You need to retag all documents to avoid false-positive searches. You need to reindex all documents to avoid false-positive searches.
</div> </div>
<form class="form-horizontal" action="/tags/edit/{{.Data.Name}}" method="post"> <form class="form-horizontal" action="/tags/edit/{{.Data.Name}}" method="post">
<input type="hidden" name="token" value="{{.Token}}"> <input type="hidden" name="token" value="{{.Token}}">

View file

@ -14,7 +14,7 @@
<form class="form-horizontal" action="/user/edit" method="post"> <form class="form-horizontal" action="/user/edit" method="post">
<input type="hidden" name="token" value="{{.Token}}"> <input type="hidden" name="token" value="{{.Token}}">
<div class="form-group"> <div class="form-group">
<label class="col-form-label" for="password">New Password</label> <label class="col-form-label" for="password">Password</label>
<input class="form-control input-sm" type="password" id="password" name="password" autofocus> <input class="form-control input-sm" type="password" id="password" name="password" autofocus>
</div> </div>
<div class="form-group"> <div class="form-group">

View file

@ -1,24 +0,0 @@
Copyright (C) 2019 Marius Schellenberger
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of the authors and/or contributors may not be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,59 +0,0 @@
# authdav - a x/net/webdav wrapper
Wrappers:
* HTTP Basic Auth wrapper for `webdav.Handler`
* write only once FileSystem wrapper for `webdav.FileSystem`
## Usage
### HTTP Basic Auth
```
package main
import (
"git.giftfish.de/ston1th/authdav"
"golang.org/x/net/webdav"
"log"
"net/http"
)
type exampleAuthenticator struct {}
func (exampleAuthenticator) BasicAuth(user, password string) bool {
if user == "user" && password == "password" {
return true
}
return false
}
func main() {
auth := exampleAuthenticator{}
handler := authdav.NewWebdavBasicAuth("", webdav.Dir("."), nil, nil, auth, "webdav with basic auth")
log.Fatal(http.ListenAndServe(":8080", handler))
}
```
### Write Only
```
package main
import (
"git.giftfish.de/ston1th/authdav"
"golang.org/x/net/webdav"
"log"
"net/http"
)
func main() {
fs := authdav.NewWriteOnlyOnceFileSystem(webdav.Dir("."))
handler := &webdav.Handler{
Prefix: "",
FileSystem: fs,
LockSystem: webdav.NewMemLS(),
Logger: nil,
}
log.Fatal(http.ListenAndServe(":8080", handler))
}
```

View file

@ -1,57 +0,0 @@
// Copyright (C) 2019 Marius Schellenberger
package authdav
import (
"golang.org/x/net/webdav"
"net/http"
)
// DefaultRealm is the HTTP Basic Auth default realm
const DefaultRealm = "WebDav"
// Authenticator is the interface for Webdav HTTP Basic Auth
type Authenticator interface {
BasicAuth(username string, password string) bool
}
// WebdavBasicAuth is a WebDav wrapper for HTTP Basic Auth and implements the http.Handler interface
type WebdavBasicAuth struct {
dav *webdav.Handler
auth Authenticator
realm string
}
// NewWebdavBasicAuth returns a new WebdavBasicAuth wrapper
// prefix can be empty
// ls and log can be nil
// an empty authenticator disables HTTP Basic Auth
// realm can be empty and defaults to authdav.DefaultRealm
func NewWebdavBasicAuth(prefix string, fs webdav.FileSystem, ls webdav.LockSystem, log func(*http.Request, error), auth Authenticator, realm string) *WebdavBasicAuth {
if ls == nil {
ls = webdav.NewMemLS()
}
if realm == "" {
realm = DefaultRealm
}
dav := &webdav.Handler{
Prefix: prefix,
FileSystem: fs,
LockSystem: ls,
Logger: log,
}
return &WebdavBasicAuth{dav, auth, `Basic realm="` + realm + `"`}
}
func (h *WebdavBasicAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.auth != nil {
u, p, ok := r.BasicAuth()
if !ok || !h.auth.BasicAuth(u, p) {
w.Header().Set("WWW-Authenticate", h.realm)
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorised\n"))
return
}
}
h.dav.ServeHTTP(w, r)
}

View file

@ -1,46 +0,0 @@
// Copyright (C) 2019 Marius Schellenberger
package authdav
import (
"golang.org/x/net/webdav"
"io"
"os"
)
// File is a wrapper for webdav.File providing write only functionality
type File struct {
f webdav.File
}
func (f *File) Close() error {
return f.f.Close()
}
func (f *File) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (f *File) Seek(offset int64, whence int) (int64, error) {
return 0, nil
}
func (f *File) Readdir(count int) (fi []os.FileInfo, err error) {
ffi, err := f.f.Readdir(count)
for _, f := range ffi {
fi = append(fi, &FileInfo{f})
}
return
}
func (f *File) Stat() (os.FileInfo, error) {
fi, err := f.f.Stat()
if err != nil {
return nil, err
}
return &FileInfo{fi}, nil
}
func (f *File) Write(p []byte) (int, error) {
return f.f.Write(p)
}

View file

@ -1,44 +0,0 @@
// Copyright (C) 2019 Marius Schellenberger
package authdav
import (
"os"
"time"
)
// FileInfo is a wrapper for os.FileInfo which always raturns a zero length for regular files
type FileInfo struct {
fi os.FileInfo
}
func (fi *FileInfo) Name() string {
return fi.fi.Name()
}
func (fi *FileInfo) RealSize() int64 {
return fi.fi.Size()
}
func (fi *FileInfo) Size() int64 {
if fi.Mode().IsRegular() {
return 0
}
return fi.fi.Size()
}
func (fi *FileInfo) Mode() os.FileMode {
return fi.fi.Mode()
}
func (fi *FileInfo) ModTime() time.Time {
return fi.fi.ModTime()
}
func (fi *FileInfo) IsDir() bool {
return fi.fi.IsDir()
}
func (fi *FileInfo) Sys() interface{} {
return fi.fi.Sys()
}

View file

@ -1,79 +0,0 @@
// Copyright (C) 2019 Marius Schellenberger
package authdav
import (
"context"
"errors"
"golang.org/x/net/webdav"
"os"
)
// ErrWonly indicates forbidden operation
var ErrWonly = errors.New("authdav: only file write once operations permitted")
const writeFlag = os.O_RDWR | os.O_CREATE | os.O_TRUNC
// WriteOnlyOnceFileSystem is a file write only wrapper for a webdav.FileSystem
type WriteOnlyOnceFileSystem struct {
Filters []Filter
fs webdav.FileSystem
}
// NewWriteOnlyOnceFileSystem returns a new WriteOnlyOnceFileSystem wrapping a webdav.FileSystem
func NewWriteOnlyOnceFileSystem(fs webdav.FileSystem) *WriteOnlyOnceFileSystem {
return &WriteOnlyOnceFileSystem{fs: fs}
}
// Mkdir is not permitted
func (fs *WriteOnlyOnceFileSystem) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
return ErrWonly
}
// OpenFile allows write only once operations
func (fs *WriteOnlyOnceFileSystem) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
fi, err := fs.Stat(ctx, name)
if err == nil && fi.Mode().IsRegular() {
f, ok := fi.(*FileInfo)
if ok && flag == writeFlag && f.RealSize() > 0 {
return nil, ErrWonly
}
}
for _, filter := range fs.Filters {
err = filter.Filter(name)
if err != nil {
return nil, err
}
}
f, err := fs.fs.OpenFile(ctx, name, flag, perm)
if err != nil {
return nil, err
}
return &File{f}, nil
}
// RemoveAll only permits deleting empty files
func (fs *WriteOnlyOnceFileSystem) RemoveAll(ctx context.Context, name string) error {
fi, err := fs.fs.Stat(ctx, name)
if err != nil {
return err
}
if fi.Mode().IsRegular() && fi.Size() == 0 {
return fs.fs.RemoveAll(ctx, name)
}
return ErrWonly
}
// Rename is not permitted
func (fs *WriteOnlyOnceFileSystem) Rename(ctx context.Context, oldName, newName string) error {
return ErrWonly
}
// Stat returns os.FileInfo wrapped in FileInfo
func (fs *WriteOnlyOnceFileSystem) Stat(ctx context.Context, name string) (os.FileInfo, error) {
fi, err := fs.fs.Stat(ctx, name)
if err != nil {
return nil, err
}
return &FileInfo{fi}, nil
}

View file

@ -1,50 +0,0 @@
// Copyright (C) 2019 Marius Schellenberger
package authdav
import (
"errors"
"path/filepath"
"strings"
)
// Matcher is the matcher function type
type Matcher func(path string) bool
// Filter defines an interface to filter paths or files
type Filter interface {
Filter(path string) error
}
// ErrMacOSFilter error returned by the MacOSFilter
var ErrMacOSFilter = errors.New("authdav: MacOS filter: access denied")
// MacOSFilter implements a filter for MacOS specific files
type MacOSFilter struct {
match []Matcher
}
// NewMacOSFilter returns a new MacOSFilter
func NewMacOSFilter() *MacOSFilter {
return &MacOSFilter{
match: []Matcher{
func(p string) bool {
return p == ".DS_Store"
},
func(p string) bool {
return strings.HasPrefix(p, "._")
},
},
}
}
// Filter implements the Filter interface
func (f *MacOSFilter) Filter(path string) error {
_, file := filepath.Split(path)
for _, m := range f.match {
if m(file) {
return ErrMacOSFilter
}
}
return nil
}

View file

@ -1,4 +1,4 @@
Copyright (C) 2022 Marius Schellenberger Copyright (C) 2017 Marius Schellenberger
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -0,0 +1,3 @@
module git.giftfish.de/ston1th/godrop/v2
require golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 // indirect

View file

@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20181026144532-2772b66316d2 h1:W7CqTdBJ1CmxLKe7LptKDnBYV6PHrVLiGnoyBjaG/JQ=
golang.org/x/sys v0.0.0-20181026144532-2772b66316d2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build go1.11
// +build go1.11 // +build go1.11
// Package godrop provides a simple library to drop privileges on Linux and OpenBSD. // Package godrop provides a simple library to drop privileges on Linux and OpenBSD.

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build go1.11
// +build go1.11 // +build go1.11
package godrop package godrop

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build !openbsd
// +build !openbsd // +build !openbsd
package godrop package godrop

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build openbsd
// +build openbsd // +build openbsd
package godrop package godrop

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build !openbsd
// +build !openbsd // +build !openbsd
package godrop package godrop

View file

@ -1,6 +1,5 @@
// Copyright (C) 2022 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
//go:build openbsd
// +build openbsd // +build openbsd
package godrop package godrop

View file

@ -1,4 +1,4 @@
Copyright (C) 2025 Marius Schellenberger Copyright (C) 2018 Marius Schellenberger
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View file

@ -1,4 +1,4 @@
// Copyright (C) 2025 Marius Schellenberger // Copyright (C) 2018 Marius Schellenberger
package jwt package jwt

Some files were not shown because too many files have changed in this diff Show more