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
run.sh
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
it under the terms of the GNU General Public License as published by

View file

@ -1,55 +1,44 @@
CC=go
BUILD=build -v -trimpath
BUILD=build -v
VERSION=$(shell cat VERSION)
GCFLAGS=-gcflags '-e'
LDFLAGS=-ldflags '-buildid=$(VERSION) -X main.version=$(VERSION) -s -w'
GCFLAGS=-gcflags 'all=-e'
LDFLAGS=-ldflags '-X main.version=$(VERSION) -s -w'
PROGRAM=docstore
CMD=-o $(PROGRAM) cmd/docstore/main.go
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
ENV=CGO_ENABLED=0 GO111MODULE=on
all: $(PROGRAM)
setup:
$(CC) install github.com/client9/misspell/cmd/misspell@latest
$(CC) install honnef.co/go/tools/cmd/staticcheck@latest
$(CC) get github.com/client9/misspell
release: clean gofmt codeqa
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD)
release: clean generate gofmt codeqa
$(ENV) $(CC) $(BUILD) $(LDFLAGS)
release-vendor: clean gofmt codeqa
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD)
release-vendor: clean generate gofmt codeqa
$(ENV) $(CC) $(BUILD) -mod=vendor $(LDFLAGS)
vendor: clean gofmt
$(ENV) $(CC) $(BUILD) -mod=vendor $(GCFLAGS) $(LDFLAGS) $(CMD)
vendor: clean generate gofmt
$(ENV) $(CC) $(BUILD) -mod=vendor $(LDFLAGS)
$(PROGRAM): clean gofmt
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS) $(CMD)
$(PROGRAM): clean generate gofmt
$(ENV) $(CC) $(BUILD) $(GCFLAGS) $(LDFLAGS)
clean:
$(CC) clean -x
codeqa: misspell golint test
generate:
$(CC) generate
gofmt:
gofmt -w pkg/ cmd/
gofmt -w pkg/ main.go
codeqa: govet misspell staticcheck test
govet:
$(CC) vet ./...
update:
$(ENV) $(CC) get -u ./...
golint:
$(GOPATH)/bin/golint .
misspell:
$(GOPATH)/bin/misspell cmd/* pkg/* Makefile README.md sri.sh
staticcheck:
/bin/true
# $(GOPATH)/bin/staticcheck ./...
$(GOPATH)/bin/misspell pkg/* templates/* main.go Makefile README.md sri.sh templates.sh
test:
ifeq ($(shell go env GOARCH), $(shell go env GOHOSTARCH))
@ -62,4 +51,4 @@ else
$(info skipping tests of other platforms)
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:**
* Go (golang) >= 1.16
* Go (golang) >= 1.12
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
go 1.24.0
go 1.12
require (
git.giftfish.de/ston1th/authdav v1.1.0
git.giftfish.de/ston1th/godrop/v2 v2.2.0
git.giftfish.de/ston1th/jwt/v3 v3.3.0
git.giftfish.de/ston1th/godrop/v2 v2.1.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/blevesearch/bleve v1.0.14
github.com/go-logr/logr v1.4.3
github.com/gorilla/mux v1.8.1
github.com/pquerna/otp v1.5.0
github.com/urfave/cli v1.22.17
go.etcd.io/bbolt v1.4.3
golang.org/x/crypto v0.43.0
golang.org/x/net v0.46.0
k8s.io/klog/v2 v2.130.1
)
require (
github.com/RoaringBitmap/roaring v1.9.4 // indirect
github.com/bits-and-blooms/bitset v1.24.1 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/zap/v11 v11.0.14 // indirect
github.com/blevesearch/zap/v12 v12.0.14 // indirect
github.com/blevesearch/zap/v13 v13.0.6 // indirect
github.com/blevesearch/zap/v14 v14.0.5 // indirect
github.com/blevesearch/zap/v15 v15.0.3 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/couchbase/vellum v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
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
github.com/blevesearch/bleve v0.8.0
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 // indirect
github.com/blevesearch/go-porterstemmer v1.0.2 // indirect
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f // indirect
github.com/boltdb/bolt v1.3.1
github.com/couchbase/vellum v0.0.0-20190328134517-462e86d8716b // indirect
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
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
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/gorilla/mux v1.7.1
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/pquerna/otp v1.2.0
github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 // indirect
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 // indirect
github.com/urfave/cli v1.20.0
go.etcd.io/bbolt v1.3.3 // indirect
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
)

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/go.mod h1:UAVoOXY6jAVh0rOnIwy4wVWpLIPAom/EoptPkAhX1nQ=
git.giftfish.de/ston1th/godrop/v2 v2.2.0 h1:jdcPJeotiuug3tC4zXvwnauen1Y7kfF9CbFKQbGttYU=
git.giftfish.de/ston1th/godrop/v2 v2.2.0/go.mod h1:gDAD0cWURkN0cMAjjSKB6a6jqIXvOrY8Th25HdoJAyg=
git.giftfish.de/ston1th/jwt/v3 v3.3.0 h1:lSBVbk1D+KmLHBlxhS7snK33+U975cNbx/Z4Md6S+0A=
git.giftfish.de/ston1th/jwt/v3 v3.3.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
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=
git.giftfish.de/ston1th/godrop/v2 v2.1.0 h1:vR4aWVeMGJ+tHQH0p44esZrSl5dYJzuCmZqhKWP/vwI=
git.giftfish.de/ston1th/godrop/v2 v2.1.0/go.mod h1:z8KA3HP/vB04wddLXZfYVKURYDxBGau9hlHtV61x9mM=
git.giftfish.de/ston1th/jwt/v3 v3.2.0 h1:V6Lh7OjXn4D3HPd/LoDf7w5N7cyiSXEPAQCIfSVZ05c=
git.giftfish.de/ston1th/jwt/v3 v3.2.0/go.mod h1:jDALrW7OKeIaqYuMxs3GWyPRnQZmk/R+Lu9p/Em3Ziw=
github.com/RoaringBitmap/roaring v0.4.17 h1:oCYFIFEMSQZrLHpywH7919esI1VSrQZ0pJXkZPGIJ78=
github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI=
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/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.1 h1:hqnfFbjjk3pxGa5E9Ho3hjoU7odtUuNmJ9Ao+Bo8s1c=
github.com/bits-and-blooms/bitset v1.24.1/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/blevesearch/bleve v1.0.14 h1:Q8r+fHTt35jtGXJUM0ULwM3Tzg+MRfyai4ZkWDy2xO4=
github.com/blevesearch/bleve v1.0.14/go.mod h1:e/LJTr+E7EaoVdkQZTfoz7dt4KoDNvDbLb8MSKuNTLQ=
github.com/blevesearch/blevex v1.0.0 h1:pnilj2Qi3YSEGdWgLj1Pn9Io7ukfXPoQcpAI1Bv8n/o=
github.com/blevesearch/blevex v1.0.0/go.mod h1:2rNVqoG2BZI8t1/P1awgTKnGlx5MP9ZbtEciQaNhswc=
github.com/blevesearch/cld2 v0.0.0-20200327141045-8b5f551d37f5/go.mod h1:PN0QNTLs9+j1bKy3d/GB/59wsNBFC4sWLWG3k69lWbc=
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
github.com/blevesearch/mmap-go v1.0.2/go.mod h1:ol2qBqYaOUsGdm7aRMRrYGgPvnwLe6Y+7LMvAB5IbSA=
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/blevesearch/bleve v0.8.0 h1:DCoCrxscCXrlzVWK92k7Vq4d28lTAFuigVmcgIX0VCo=
github.com/blevesearch/bleve v0.8.0/go.mod h1:Y2lmIkzV6mcNfAnAdOd+ZxHkHchhBfU/xroGIp61wfw=
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3 h1:U6vnxZrTfItfiUiYx0lf/LgHjRSfaKK5QHSom3lEbnA=
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ=
github.com/blevesearch/go-porterstemmer v1.0.2 h1:qe7n69gBd1OLY5sHKnxQHIbzn0LNJA4hpAf+5XDxV2I=
github.com/blevesearch/go-porterstemmer v1.0.2/go.mod h1:haWQqFT3RdOGz7PJuM3or/pWNJS1pKkoZJWCkWu0DVA=
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f h1:kqbi9lqXLLs+zfWlgo1PIiRQ86n33K1JKotjj4rSYOg=
github.com/blevesearch/segment v0.0.0-20160915185041-762005e7a34f/go.mod h1:IInt5XRvpiGE09KOk9mmCMLjHhydIhNPKPPFLFBB7L8=
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
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/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
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/couchbase/vellum v0.0.0-20190328134517-462e86d8716b h1:WgO2yt0pCXyPgU3K/pnY1qO/8FP8TxBAaYXqa+BQpx0=
github.com/couchbase/vellum v0.0.0-20190328134517-462e86d8716b/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g=
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/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=
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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
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/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/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/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493 h1:OTanQnFt0bi5iLFSdbEVA/idR6Q2WhCm+deb7ir2CcM=
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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 v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU=
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
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/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/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw=
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 h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
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.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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.5.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-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
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/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001 h1:YDeskXpkNDhPdWN3REluVa46HQOVuVkjkd2sWnrABNQ=
github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys=
github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs=
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2 h1:JNEGSiWg6D3lcBCMCBqN3ELniXujt+0QNHLhNnO0w3s=
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw=
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/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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
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/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tebeka/snowball v0.4.2/go.mod h1:4IfL14h1lvwZcp1sfXuuc7/7yCsvVffTWxWxCLfFpYg=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481 h1:HOxvxvnntLiPn123Fk+twfUhCQdMDaqmb0cclArW0T0=
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/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/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
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.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
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=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
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.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
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-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.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.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-20181205085412-a5c9d58dba9a/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-20181026144532-2772b66316d2/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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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/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/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.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
// +build go1.16
//go:generate ./templates.sh
// +build go1.12
package main

View file

@ -1,28 +1,24 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package cmd
import (
"encoding/json"
"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"
"os"
"os/signal"
"path/filepath"
"syscall"
"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"
"time"
)
const (
@ -30,6 +26,9 @@ const (
defListen = "127.0.0.1:8080"
defLogFile = "docstore.log"
defRunUser = "docstore"
defRunGroup = "docstore"
defTimeout = 600
tesseractThreads = 1
@ -42,12 +41,23 @@ var (
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
// sysctl kern.somaxconn
// if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil {
// return
// }
//if err = godrop.PledgePromises("stdio rpath wpath cpath inet fattr flock proc exec id unveil"); err != nil {
// 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 {
return
}
@ -56,25 +66,19 @@ func initServer(cfg *core.Config) (err error) {
return
}
l, err := net.Listen("tcp", cfg.ListenAddr)
l, err := godrop.GetListener()
if err != nil {
return
}
ll, err := logFileLogger(cfg)
if err != nil {
return
}
log := ll.WithName("main")
log.Info("starting docstore", "version", cfg.Version)
log.InitLogger(cfg)
cfg.DataDir = filepath.Join(cfg.RunDir, core.DataDir)
debugCfg := new(core.Config)
*debugCfg = *cfg
debugCfg.Secret = "[redacted]"
log.V(2).Info("docstore config", "cfg", debugCfg)
debugCfg := cfg
debugCfg.Secret = "***hidden***"
log.Debugf("docstore config: %+v", debugCfg)
scanner, err := scan.New(ll.WithName("scanner"), cfg)
scanner, err := scan.New(cfg)
if err != nil {
return errors.New("scanner: " + err.Error())
}
@ -87,20 +91,25 @@ func initServer(cfg *core.Config) (err error) {
return
}
srv := server.NewHTTPServer(ll, cfg, l, scanner)
srv := server.NewHTTPServer(cfg, l, scanner)
err = srv.Start()
if err != nil {
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)
<-sigs
log.Info("docstore shutdown initiated")
sig := <-sigs
log.Println("signal: " + sig.String())
go func() {
time.Sleep(time.Second * 20)
log.Fatal("stop timed out: killing")
}()
err = srv.Stop()
if err != nil {
log.Error(err, "shutdown")
log.Println(err)
}
log.Info("docstore shutdown completed")
log.Println("docstore stopped")
return
}
@ -116,13 +125,15 @@ func loadConfig() error {
if err != nil {
return err
}
if config.Debug {
new(klog.Level).Set("2")
//zerolog.SetGlobalLevel(zerolog.TraceLevel)
}
if config.RunDir == "" {
config.RunDir = defRunDir
}
if config.User == "" {
config.User = defRunUser
}
if config.Group == "" {
config.Group = defRunGroup
}
if config.ListenAddr == "" {
config.ListenAddr = defListen
}
@ -141,48 +152,6 @@ func loadConfig() error {
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) {
app := cli.NewApp()
app.Name = "docstore"
@ -196,52 +165,18 @@ func Run(version string) {
Action: func(c *cli.Context) error {
config.Langs = c.StringSlice("langs")
if err := loadConfig(); err != nil {
fatal(err)
stdlog.Fatal("server: ", err)
}
config.Version = app.Version
if err := initServer(&config); err != nil {
fatal(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)
if err := initServer(config); err != nil {
stdlog.Fatal("server: ", err)
}
return nil
},
},
{
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()),
Action: func(c *cli.Context) error {
var (
@ -249,32 +184,32 @@ func Run(version string) {
err error
)
if err = loadConfig(); err != nil {
fatal(err)
stdlog.Fatal("dump: ", err)
}
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 {
fatal(err)
stdlog.Fatal("dump: ", err)
}
}
DB, err := db.NewPlain(log.WithName("db"), &config)
DB, err := db.New(config)
if err != nil {
fatal(err)
stdlog.Fatal("dump: ", err)
}
if err = DB.Dump(file); err != nil {
fatal(err)
stdlog.Fatal("dump: ", err)
}
file.Close()
err = DB.Close()
if err != nil {
fatal(err)
stdlog.Fatal("dump: ", err)
}
return nil
},
},
{
Name: "restore",
Usage: "restore a json dump (docstore must be stopped)",
Usage: "restore a json dump (docstore needs to be stopped)",
Flags: defaultFlags(dumpRestoreFlags()),
Action: func(c *cli.Context) error {
var (
@ -282,25 +217,25 @@ func Run(version string) {
err error
)
if err = loadConfig(); err != nil {
fatal(err)
stdlog.Fatal("restore: ", err)
}
if dumpFile != "-" {
file, err = os.Open(dumpFile)
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 {
fatal(err)
stdlog.Fatal("restore: ", err)
}
if err = DB.Restore(file); err != nil {
fatal(err)
stdlog.Fatal("restore: ", err)
}
file.Close()
err = DB.Close()
if err != nil {
fatal(err)
stdlog.Fatal("restore: ", err)
}
return nil
},
@ -311,6 +246,18 @@ func Run(version string) {
func serverFlags() []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{
Name: "listen, l",
Value: defListen,
@ -350,6 +297,11 @@ func serverFlags() []cli.Flag {
Usage: "tesseract oem value (1-4)",
Destination: &config.TesseractOEM,
},
cli.BoolFlag{
Name: "foreground, f",
Usage: "do not fork into the background",
Destination: &config.Foreground,
},
cli.BoolFlag{
Name: "secure, s",
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
@ -10,7 +10,6 @@ const (
SearchURI = "/search"
LogsURI = "/logs"
TagsURI = "/tags"
StatsURI = "/stats"
UserURI = "/user"
RawPrefix = "/raw"
@ -22,7 +21,10 @@ const (
TagsPrefix = "/tags"
DeletePrefix = "/delete"
WebDavPrefix = "/webdav"
StaticPrefix = "/static"
Favicon = "/favicon.ico"
BootstrapCSS = "/bootstrap.css"
CustomCSS = "/custom.css"
DataDir = "data"
)

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package core
@ -8,9 +8,8 @@ import (
)
const (
fileFmt = "20060102_150405"
logFmt = "2006/01/02 15:04:05: "
createdFmt = "02.01.2006 15:04:05"
fileFmt = "20060102_150405"
logFmt = "2006/01/02 15:04:05: "
)
var m sync.Mutex
@ -27,7 +26,3 @@ func FileName() string {
func LogTime() string {
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
@ -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) 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 {
RunDir string `json:"run_dir,omitempty"`
DataDir string `json:"-"`
User string `json:"user,omitempty"`
Group string `json:"group,omitempty"`
ListenAddr string `json:"listen_addr,omitempty"`
LogFile string `json:"log_file,omitempty"`
Version string `json:"-"`
@ -83,6 +77,7 @@ type Config struct {
CMDTimeout int `json:"cmd_timeout,omitempty"`
TesseractThreads int `json:"tesseract_threads,omitempty"`
TesseractOEM int `json:"tesseract_oem,omitempty"`
Foreground bool `json:"foreground,omitempty"`
SecureCookie bool `json:"secure_cookie,omitempty"`
Debug bool `json:"debug,omitempty"`
WebDav bool `json:"webdav,omitempty"`

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package db
@ -15,7 +15,10 @@ func (db *DB) Remove(sig string) error {
}
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) {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package db
@ -10,7 +10,7 @@ type countMutex struct {
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) {
return 0, 0, true
}

View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package db
@ -66,7 +66,10 @@ func (db *DB) CreateUser(username, password string) error {
func (db *DB) BasicAuth(username, password string) bool {
_, 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) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package index
@ -66,11 +66,6 @@ func (i *Index) Close() error {
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) {
id = newID()
for {

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package scan
@ -6,7 +6,6 @@ import (
"bytes"
"net/http"
"os"
"strings"
)
const (
@ -19,8 +18,6 @@ const (
pnmT = "image/x-portable-anymap"
jp2T = "image/jp2"
tiffT = "image/tiff"
textT = "text/"
rawT = "application/octet-stream"
maxSniffSize = 512
)
@ -48,11 +45,35 @@ func extendedContentType(buf []byte) string {
return sig.ct
}
}
return rawT
return "application/octet-stream"
}
func isImage(ct string, buf []byte) bool {
switch ct {
func isPDF(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
}
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:
return true
default:
@ -63,29 +84,3 @@ func isImage(ct string, buf []byte) bool {
}
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
import (
"errors"
"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/exec"
"path/filepath"
"regexp"
"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 (
@ -39,20 +38,19 @@ type Tesseract struct {
var (
wsr = regexp.MustCompile(`[\t\f ]+`)
nlr = regexp.MustCompile(`\n{3,}`)
nlr = regexp.MustCompile(`[\n]{3,}`)
ws = []byte(" ")
nl = []byte("\n")
)
type Scanner struct {
log logr.Logger
base string
timeout time.Duration
t Tesseract
p PDF
}
func New(log logr.Logger, c *core.Config) (s *Scanner, err error) {
func New(c core.Config) (s *Scanner, err error) {
var (
tcmd 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)}
s = &Scanner{
log,
c.DataDir,
time.Second * time.Duration(c.CMDTimeout),
Tesseract{tcmd, args, []string{fmt.Sprintf("OMP_THREAD_LIMIT=%d", threads)}},
@ -104,27 +101,14 @@ func New(log logr.Logger, c *core.Config) (s *Scanner, err error) {
}
func (s *Scanner) Scan(file string) (filename, text string, err error) {
log := s.log
file = fs.Clean(file)
scanfile := filepath.Join(s.base, file)
var pdffile string
ft := s.newFileType(scanfile)
if !ft.pdf {
if !ft.image {
if ft.text {
log.V(2).Info("indexing plaintext file", "file", scanfile)
var buf []byte
buf, err = os.ReadFile(scanfile)
if err != nil {
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
}
pdf := isPDF(scanfile)
if !pdf {
if !isImage(scanfile) {
err = errors.New("error: input file is not an image")
return
}
var (
pdf []byte
@ -133,7 +117,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
p, _ := filepath.Split(file)
pdfCmd := exec.Command(s.t.cmd, append(s.t.args, []string{scanfile, "stdout", "pdf"}...)...)
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)
if err != nil {
return
@ -156,7 +140,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
filename = file
}
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)
if err != nil {
return
@ -166,7 +150,7 @@ func (s *Scanner) Scan(file string) (filename, text string, err error) {
return
}
text = string(nlr.ReplaceAll(wsr.ReplaceAll(txt, ws), nl))
if !ft.pdf {
if !pdf {
err = os.Remove(scanfile)
}
return

View file

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

View file

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

View file

@ -1,18 +1,18 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package server
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"
"net/http"
"path/filepath"
"strings"
"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"
@ -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 {
s *HTTPServer
}
@ -146,46 +164,69 @@ func rawHandler(ctx *Context) {
f, fi, err := ctx.Srv.FS.GetFile(path)
if err != nil {
ctx.Status(http.StatusNotFound)
ctx.Err = ctx.Write([]byte(noSuchFile))
err = ctx.Write([]byte(noSuchFile))
if err != nil {
log.Println("file:", err)
}
return
}
ctx.Response.Header().Del("Content-Security-Policy")
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) {
log := ctx.Srv.Log
switch ctx.Method() {
case "GET":
var (
err error
ok bool
fpath string
)
path := ctx.Path()
if fpath, ok = hasTrimPrefix(path, core.IndexPrefix); ok {
log = log.WithValues("file", fpath)
log.V(2).Info("started indexing file")
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.IndexPrefix) {
fpath = strings.TrimPrefix(path, core.IndexPrefix)
err = scanFile(ctx.Srv, fpath)
}
if strings.HasPrefix(path, core.RetagPrefix) {
fpath = strings.TrimPrefix(path, core.RetagPrefix)
if fpath == "" {
log.V(2).Info("started retagging all files")
go func() {
err := ctx.Srv.DB.RetagAll()
if err != nil {
log.Error(err, "error retagging all files")
ctx.Srv.Log.Printf("retagAll: %s", err)
}
}()
} else {
log = log.WithValues("file", fpath)
log.V(2).Info("started retagging file")
err = ctx.Srv.DB.Retag(fpath, nil)
if err != nil {
log.Error(err, "error retagging file")
}
}
}
p, _ := filepath.Split(fpath)
@ -207,22 +248,19 @@ func indexHandler(ctx *Context) {
return
}
if m, c, ok := ctx.Srv.DB.IndexMutex.Lock(); ok {
log.V(2).Info("started indexing all files")
go func() {
defer ctx.Srv.DB.IndexMutex.Unlock()
files := ctx.Srv.FS.RecursiveFiles("/")
ctx.Srv.DB.IndexMutex.Set(uint64(len(files)))
for _, f := range files {
if !ctx.Srv.DB.IsIndexed(f) {
err := ctx.Srv.SQ.Add(f, ctx.Srv.DB.IndexMutex.Inc)
if err != nil {
log.Error(err, "error indexing file", "file", f)
}
scanFile(ctx.Srv, f)
}
ctx.Srv.DB.IndexMutex.Inc()
}
}()
} 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)
}
@ -282,7 +320,6 @@ func newDirHandler(ctx *Context) {
}
func moveHandler(ctx *Context) {
log := ctx.Srv.Log
path := strings.TrimPrefix(ctx.Path(), core.MovePrefix)
mode, _, err := ctx.Srv.FS.Mode(path)
if err != nil {
@ -327,19 +364,18 @@ func moveHandler(ctx *Context) {
return
}
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)
if err != nil {
log.Error(err, "moving file failed", "old", m.Old, "new", m.New)
log.Printf("move: %s: %s", m, err)
continue
}
log.Debugf("move: %s to %s", m.Old, m.New)
}
ctx.Redirect(newfile, http.StatusFound)
}
}
func deleteHandler(ctx *Context) {
log := ctx.Srv.Log
path := strings.TrimPrefix(ctx.Path(), core.DeletePrefix)
mode, enoent, err := ctx.Srv.FS.Mode(path)
if !enoent && err != nil {
@ -382,21 +418,18 @@ func deleteHandler(ctx *Context) {
clean := ctx.Form("clean")
if clean == "true" {
f := fs.Clean(path)
log = log.WithValues("file", f)
log.V(2).Info("deleting file from DB")
id, _, err := ctx.Srv.DB.DeleteFile(f)
if err != nil {
log.Error(err, "delete from DB failed")
log.Printf("db: delete: %s %s", f, err)
}
if id == "" {
ctx.Redirect(p, http.StatusFound)
return
}
log = log.WithValues("id", id)
log.V(2).Info("deleting file from index")
log.Debugf("delete %s id: %s from index", f, id)
err = ctx.Srv.DB.Index.Delete(id)
if err != nil {
log.Error(err, "delete from index failed")
log.Printf("index: delete: %s %s", f, err)
}
ctx.Redirect(p, http.StatusFound)
return
@ -407,18 +440,17 @@ func deleteHandler(ctx *Context) {
return
}
for _, f := range files {
log.V(2).Info("deleting file from DB", "file", f)
id, _, err := ctx.Srv.DB.DeleteFile(f)
if err != nil {
log.Error(err, "delete from DB failed", "file", f)
log.Printf("db: delete: %s %s", f, err)
}
if id == "" {
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)
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)
@ -428,7 +460,8 @@ func deleteHandler(ctx *Context) {
func dirHandler(ctx *Context) {
ctx.Template("dirHandler")
path := ctx.Path()
if ctx.Method() == "GET" {
switch ctx.Method() {
case "GET":
mode, enoent, err := ctx.Srv.FS.Mode(path)
if !enoent && err != nil {
ctx.Error(err)
@ -450,15 +483,7 @@ func dirHandler(ctx *Context) {
Title: "Document Viewer | " + path,
}
ctx.Template("fileHandler")
// 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)
}
}
f := ctx.Srv.DB.IsIndexed(path)
ctx.Data.Data = core.Path{
Name: path,
Abs: core.RawPrefix + path,
@ -500,7 +525,6 @@ func loginHandler(ctx *Context) {
Title: "Login",
BodyTitle: "Login",
}
log := ctx.Srv.Log
var data loginData
switch ctx.Method() {
case "GET":
@ -524,12 +548,12 @@ func loginHandler(ctx *Context) {
}
u, err := ctx.Srv.DB.Login(data.User, password)
if err != nil {
log.Error(err, "login failed", "user", data.User)
log.Println("login:", data.User, err)
ctx.Error("bad username or password")
return
}
if u.Secret == "" {
log.Info("login successful", "user", u.Username)
log.Println("login:", u.Username)
ctx.Login(u, ctx.Form("remember"))
ctx.Redirect(data.Referer, http.StatusFound)
return
@ -553,7 +577,6 @@ func loginTotpHandler(ctx *Context) {
Title: "TOTP Verification",
BodyTitle: "TOTP Veriftcation",
}
log := ctx.Srv.Log
switch ctx.Method() {
case "GET":
ctx.Exec()
@ -572,7 +595,7 @@ func loginTotpHandler(ctx *Context) {
return
}
ref := ctx.Referer()
log.Info("login successful", "user", u.Username)
log.Println("login:", u.Username)
ctx.Login(u, ctx.Remember())
ctx.Redirect(ref, http.StatusFound)
}
@ -612,7 +635,6 @@ func userEditHandler(ctx *Context) {
Title: "Edit User",
BodyTitle: "Edit User",
}
log := ctx.Srv.Log
u, err := ctx.Srv.DB.GetUserWithoutPassword()
if err != nil {
ctx.Error(err)
@ -637,7 +659,7 @@ func userEditHandler(ctx *Context) {
ctx.Error(err)
return
}
log.Info("user updated", "user", ctx.User())
log.Printf("update: %s updated\n", ctx.User())
ctx.Redirect(core.IndexURI, http.StatusFound)
}
}
@ -648,7 +670,6 @@ func userTotpHandler(ctx *Context) {
Title: "TOTP",
BodyTitle: "TOTP",
}
log := ctx.Srv.Log
req := otp.Request{Username: ctx.User()}
u, err := ctx.Srv.DB.GetUser()
if err != nil {
@ -702,9 +723,9 @@ func userTotpHandler(ctx *Context) {
return
}
if req.Secret == "" {
log.Info("totp disabled", "user", user)
log.Printf("totp: disabled for %s\n", user)
} else {
log.Info("totp enabled", "user", user)
log.Printf("totp: enabled for %s\n", user)
}
ctx.Redirect("/user/edit", http.StatusFound)
}
@ -716,9 +737,9 @@ func logsHandler(ctx *Context) {
Title: "Logs",
BodyTitle: "Logs",
}
if ctx.Method() == "GET" {
// TODO remove
//ctx.Data.Data = ctx.Srv.Log.Logs()
switch ctx.Method() {
case "GET":
ctx.Data.Data = ctx.Srv.Log.Logs()
ctx.Exec()
}
}
@ -729,7 +750,8 @@ func tagsHandler(ctx *Context) {
Title: "Tags",
BodyTitle: "Tags",
}
if ctx.Method() == "GET" {
switch ctx.Method() {
case "GET":
t, err := ctx.Srv.DB.GetAllTags()
if err != nil {
ctx.Error(err)
@ -746,7 +768,6 @@ func tagsNewHandler(ctx *Context) {
Title: "New Tag",
BodyTitle: "New Tag",
}
log := ctx.Srv.Log
switch ctx.Method() {
case "GET":
ctx.Data.Data = core.Tag{}
@ -771,7 +792,7 @@ func tagsNewHandler(ctx *Context) {
ctx.Error(err)
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)
}
}
@ -782,7 +803,6 @@ func tagsEditHandler(ctx *Context) {
Title: "Edit Tag",
BodyTitle: "Edit Tag",
}
log := ctx.Srv.Log
name := ctx.Var("tag")
switch ctx.Method() {
case "GET":
@ -809,7 +829,7 @@ func tagsEditHandler(ctx *Context) {
ctx.Error(err)
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)
}
}
@ -820,7 +840,6 @@ func tagsDelHandler(ctx *Context) {
Title: "Delete Tag",
BodyTitle: "Delete Tag",
}
log := ctx.Srv.Log
name := ctx.Var("tag")
ctx.Data.Data = name
switch ctx.Method() {
@ -834,30 +853,17 @@ func tagsDelHandler(ctx *Context) {
ctx.Error(err)
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)
}
}
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) {
log := ctx.Srv.Log
user := ctx.User()
if user == "" {
user = ctx.Totp() + " (totp)"
}
log.Info("user logged out", "user", user)
log.Println("logout:", user)
ctx.Srv.JWT.Invalidate(&ctx.Token)
ctx.SetCookie(nil, 0)
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
@ -10,15 +10,31 @@ type route struct {
Methods []string
}
var registerRoutes = []route{
var static = []route{
{
core.IndexURI,
jwtRegisterHandler(
registerHandler),
[]string{"GET", "POST"},
core.Favicon,
staticHandler,
[]string{"GET"},
},
{
core.BootstrapCSS,
staticHandler,
[]string{"GET"},
},
{
core.CustomCSS,
staticHandler,
[]string{"GET"},
},
}
var registerRoutes = append(static, route{
core.IndexURI,
jwtRegisterHandler(
registerHandler),
[]string{"GET", "POST"},
})
var prefixRoutes = []route{
{
core.RawPrefix,
@ -78,7 +94,7 @@ var prefixRoutes = []route{
},
}
var routes = []route{
var routes = append(static, []route{
{
core.SearchURI,
jwtHandler(
@ -127,13 +143,6 @@ var routes = []route{
tagsHandler)),
[]string{"GET"},
},
{
core.StatsURI,
jwtHandler(
authHandler(
statsHandler)),
[]string{"GET"},
},
{
"/tags/new",
jwtHandler(
@ -161,4 +170,4 @@ var routes = []route{
logoutHandler),
[]string{"GET", "POST"},
},
}
}...)

View file

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

View file

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

View file

@ -1,16 +1,16 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package store
import (
"encoding/json"
bolt "go.etcd.io/bbolt"
"github.com/boltdb/bolt"
"io"
)
const (
defaultBoltBucket = "default"
fileMode = 0o640
fileMode = 0640
)
type BoltStore struct {
@ -26,7 +26,7 @@ func NewBoltStore(file string, m Marshaler) (Store, error) {
if err != nil {
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))
return
}); 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) {
err = bs.db.View(func(tx *bolt.Tx) error {
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 {
return bs.ForEach(func(k string, v []byte) error {
if trim, ok := hasTrimPrefix(k, prefix); ok {
return f(trim, v)
return bs.db.View(func(tx *bolt.Tx) error {
c := 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
})
//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 {
@ -137,3 +161,52 @@ func (bs *BoltStore) Delete(key string) error {
func (bs *BoltStore) Close() error {
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
import (
"bytes"
"errors"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -19,7 +20,7 @@ func TestBoltStore(t *testing.T) {
{"123"},
{"hello"},
}
dir, err := os.MkdirTemp("", "")
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}

View file

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

View file

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

View file

@ -1,4 +1,4 @@
// Copyright (C) 2021 Marius Schellenberger
// Copyright (C) 2019 Marius Schellenberger
package store
@ -9,3 +9,8 @@ func hasTrimPrefix(s, prefix string) (string, bool) {
}
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

View file

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

View file

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

4
sri.sh
View file

@ -1,8 +1,8 @@
#!/bin/bash
# Copyright (C) 2021 Marius Schellenberger
# Copyright (C) 2019 Marius Schellenberger
HASH="sha256"
DIR="pkg/server/static"
DIR="templates"
FILES="*.css *.js"
BINS="*.ico"
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 {
background-color: #999999;
width: 100%;
height: 700px;
}
textarea,input,select {
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>
{{if .Tags}}
<div class="row">
<div class="alert alert-secondary full-width">
{{if .Tags}}
Tags
{{range $t := .Tags}}
<a href="/search?query=tags:{{$t}}" class="btn badge btn-primary tag">{{$t}}</a>
{{end}}
{{end}}
<span class="float-right">{{.Created}}</span>
</div>
</div>
{{end}}
<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>
{{end}}

View file

@ -2,9 +2,9 @@
<html lang="en">
<head>
<title>DocStore | {{.Title}}</title>
<link rel="shortcut icon" type="image/x-icon" href="/static/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="/static/custom.css" media="screen" integrity="sha256-wLiyjr/D8ZaaIPLjUmZ3zdm45/HW/cy4kCVTViwkPeI="></link>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" integrity="sha256-bgotk/IPq4Yvt6s8AQGPTM5ZjPjQ6rrBRa2EyxpnFSc="></link>
<link rel="stylesheet" type="text/css" href="/bootstrap.css" media="screen" integrity="sha256-3YeveuySvBgp2GItUS8eV1R/9T1zcPMyvw22SyVf8qo="></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="description" content="{{.Title}}">
</head>

View file

@ -15,7 +15,6 @@
{{end}}
<ul class="nav navbar-nav ml-auto">
{{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="/logout">Logout</a></li>
{{else}}

View file

@ -8,7 +8,7 @@
<div class="card-body">
<div class="alert alert-dismissible alert-primary">
<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>
<form class="form-horizontal" action="/tags/edit/{{.Data.Name}}" method="post">
<input type="hidden" name="token" value="{{.Token}}">

View file

@ -14,7 +14,7 @@
<form class="form-horizontal" action="/user/edit" method="post">
<input type="hidden" name="token" value="{{.Token}}">
<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>
</div>
<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.
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
// 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
package godrop

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
Copyright (C) 2025 Marius Schellenberger
Copyright (C) 2018 Marius Schellenberger
All rights reserved.
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

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