diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Dockerfile | 10 | ||||
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | assets/iosevka-aile-regular.woff | bin | 13588 -> 187604 bytes | |||
-rw-r--r-- | assets/iosevka-aile-regular.woff2 | bin | 11036 -> 130736 bytes | |||
-rw-r--r-- | assets/iosevka-term-ss03-regular.woff | bin | 13864 -> 155916 bytes | |||
-rw-r--r-- | assets/iosevka-term-ss03-regular.woff2 | bin | 11384 -> 111644 bytes | |||
-rw-r--r-- | assets/startpage.txt | 6 | ||||
-rw-r--r-- | cmd/port/main.go (renamed from main.go) | 4 | ||||
-rw-r--r-- | fonts/glyphs.txt | 2 | ||||
-rw-r--r-- | go.mod | 13 | ||||
-rw-r--r-- | go.sum | 217 | ||||
-rwxr-xr-x | gopherproxy.bin | bin | 0 -> 14744336 bytes | |||
-rw-r--r-- | gopherproxy/gopherproxy.go | 705 | ||||
-rw-r--r-- | internal/port/gemini.go | 205 | ||||
-rw-r--r-- | internal/port/gopher.go | 217 | ||||
-rw-r--r-- | internal/port/main.go | 301 | ||||
-rw-r--r-- | internal/port/tpl/gemini.html | 0 | ||||
-rw-r--r-- | internal/port/tpl/gopher.html | 0 | ||||
-rw-r--r-- | internal/port/tpl/startpage.html (renamed from gopherproxy/template.go) | 6 | ||||
-rw-r--r-- | pkg/libgemini/libgemini.go (renamed from gopherproxy/libgemini/libgemini.go) | 0 | ||||
-rw-r--r-- | pkg/libgopher/libgopher.go (renamed from gopherproxy/libgopher/libgopher.go) | 0 |
23 files changed, 971 insertions, 739 deletions
@@ -3,6 +3,6 @@ dist | |||
3 | *.bak | 3 | *.bak |
4 | coverage.txt | 4 | coverage.txt |
5 | 5 | ||
6 | gopherproxy.bin | 6 | port.bin |
7 | 7 | ||
8 | .vscode | 8 | .vscode |
@@ -2,16 +2,16 @@ FROM golang:alpine | |||
2 | 2 | ||
3 | EXPOSE 80/tcp | 3 | EXPOSE 80/tcp |
4 | 4 | ||
5 | ENTRYPOINT ["gopherproxy"] | 5 | ENTRYPOINT ["port"] |
6 | 6 | ||
7 | RUN \ | 7 | RUN \ |
8 | apk add --update git && \ | 8 | apk add --update git && \ |
9 | rm -rf /var/cache/apk/* | 9 | rm -rf /var/cache/apk/* |
10 | 10 | ||
11 | RUN mkdir -p /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy | 11 | RUN mkdir -p /go/src/git.vulpes.one/Feuerfuchs/port |
12 | WORKDIR /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy | 12 | WORKDIR /go/src/git.vulpes.one/Feuerfuchs/port |
13 | 13 | ||
14 | COPY . /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy | 14 | COPY . /go/src/git.vulpes.one/Feuerfuchs/port |
15 | 15 | ||
16 | RUN go get -v -d | 16 | RUN go get -v -d |
17 | RUN go install -v git.feuerfuchs.dev/Feuerfuchs/gopherproxy/... | 17 | RUN go install -v git.vulpes.one/Feuerfuchs/port/... |
@@ -3,16 +3,16 @@ | |||
3 | all: dev | 3 | all: dev |
4 | 4 | ||
5 | dev: build | 5 | dev: build |
6 | ./gopherproxy.bin -bind 127.0.0.1:8000 | 6 | ./port.bin -bind 127.0.0.1:8000 |
7 | 7 | ||
8 | build: clean | 8 | build: clean |
9 | sassc -t compressed css/main.scss assets/style.css | 9 | sassc -t compressed css/main.scss assets/style.css |
10 | tsc --strict --module none --outFile /dev/stdout js/* | terser --compress --mangle -o assets/main.js -- | 10 | tsc --strict --module none --outFile /dev/stdout js/* | terser --compress --mangle -o assets/main.js -- |
11 | pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=fonts/glyphs.txt --flavor='woff' --with-zopfli --output-file='assets/iosevka-term-ss03-regular.woff' | 11 | pyftsubset fonts/iosevka-term-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-term-ss03-regular.woff' |
12 | pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=fonts/glyphs.txt --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2' | 12 | pyftsubset fonts/iosevka-term-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2' |
13 | pyftsubset fonts/iosevka-aile-regular.ttf --name-IDs+=0,4,6 --text-file=fonts/glyphs.txt --flavor='woff' --with-zopfli --output-file='assets/iosevka-aile-regular.woff' | 13 | pyftsubset fonts/iosevka-aile-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-aile-regular.woff' |
14 | pyftsubset fonts/iosevka-aile-regular.ttf --name-IDs+=0,4,6 --text-file=fonts/glyphs.txt --flavor='woff2' --output-file='assets/iosevka-aile-regular.woff2' | 14 | pyftsubset fonts/iosevka-aile-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-aile-regular.woff2' |
15 | go build -o ./gopherproxy.bin ./main.go | 15 | go build -o ./port.bin ./cmd/port |
16 | 16 | ||
17 | profile: | 17 | profile: |
18 | @go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench . | 18 | @go test -cpuprofile cpu.prof -memprofile mem.prof -v -bench . |
@@ -24,4 +24,4 @@ test: | |||
24 | @go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic . | 24 | @go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic . |
25 | 25 | ||
26 | clean: | 26 | clean: |
27 | @git clean -f -d -X | 27 | #@git clean -f -d -X |
@@ -1,8 +1,8 @@ | |||
1 | # Gopher (RFC 1436) Web Proxy | 1 | # Gopher (RFC 1436) Web Proxy |
2 | 2 | ||
3 | gopherproxy is a Gopher (RFC 1436) and Gemini (gopher://zaibatsu.circumlunar.space/1/~solderpunk/gemini/docs/) Web Proxy that acts as a gateway into Gopherspace/Geminispace by proxying standard Web HTTP requests to Gopher/Gemini requests of the target server. | 3 | port is a Gopher (RFC 1436) and Gemini (gopher://zaibatsu.circumlunar.space/1/~solderpunk/gemini/docs/) Web Proxy that acts as a gateway into Gopherspace/Geminispace by proxying standard Web HTTP requests to Gopher/Gemini requests of the target server. |
4 | 4 | ||
5 | gopherproxy is a fork of [https://github.com/prologic/gopherproxy](https://github.com/prologic/gopherproxy). | 5 | port is a fork of [https://github.com/prologic/port](https://github.com/prologic/port). |
6 | 6 | ||
7 | Demo: https://proxy.vulpes.one/ | 7 | Demo: https://proxy.vulpes.one/ |
8 | 8 | ||
@@ -15,14 +15,14 @@ Demo: https://proxy.vulpes.one/ | |||
15 | ## Installation | 15 | ## Installation |
16 | 16 | ||
17 | ```#!bash | 17 | ```#!bash |
18 | $ go install git.feuerfuchs.dev/Feuerfuchs/gopherproxy/... | 18 | $ go install git.vulpes.one/Feuerfuchs/port/... |
19 | ``` | 19 | ``` |
20 | 20 | ||
21 | 21 | ||
22 | ## Usage | 22 | ## Usage |
23 | 23 | ||
24 | ```#!bash | 24 | ```#!bash |
25 | $ gopherproxy | 25 | $ port |
26 | ``` | 26 | ``` |
27 | 27 | ||
28 | Arguments: | 28 | Arguments: |
diff --git a/assets/iosevka-aile-regular.woff b/assets/iosevka-aile-regular.woff index a23b09c..97b42ea 100644 --- a/assets/iosevka-aile-regular.woff +++ b/assets/iosevka-aile-regular.woff | |||
Binary files differ | |||
diff --git a/assets/iosevka-aile-regular.woff2 b/assets/iosevka-aile-regular.woff2 index 2a4375e..fea3967 100644 --- a/assets/iosevka-aile-regular.woff2 +++ b/assets/iosevka-aile-regular.woff2 | |||
Binary files differ | |||
diff --git a/assets/iosevka-term-ss03-regular.woff b/assets/iosevka-term-ss03-regular.woff index 18d4b27..eb2568a 100644 --- a/assets/iosevka-term-ss03-regular.woff +++ b/assets/iosevka-term-ss03-regular.woff | |||
Binary files differ | |||
diff --git a/assets/iosevka-term-ss03-regular.woff2 b/assets/iosevka-term-ss03-regular.woff2 index af3e978..957b020 100644 --- a/assets/iosevka-term-ss03-regular.woff2 +++ b/assets/iosevka-term-ss03-regular.woff2 | |||
Binary files differ | |||
diff --git a/assets/startpage.txt b/assets/startpage.txt index 53c12ab..46b22c1 100644 --- a/assets/startpage.txt +++ b/assets/startpage.txt | |||
@@ -1,6 +1,6 @@ | |||
1 | P R O X Y | 1 | P R O X Y |
2 | - - - - - - for - - - - - - | 2 | - - - - - - for - - - - - - |
3 | GOPHER + GEMINI | 3 | G O P H E R + G E M I N I |
4 | 4 | ||
5 | 5 | ||
6 | GETTING STARTED -- | 6 | GETTING STARTED -- |
diff --git a/main.go b/cmd/port/main.go index f5a82fe..6cdccc6 100644 --- a/main.go +++ b/cmd/port/main.go | |||
@@ -4,7 +4,7 @@ import ( | |||
4 | "flag" | 4 | "flag" |
5 | "log" | 5 | "log" |
6 | 6 | ||
7 | "git.feuerfuchs.dev/Feuerfuchs/gopherproxy/gopherproxy" | 7 | port "git.vulpes.one/Feuerfuchs/port/internal/port" |
8 | ) | 8 | ) |
9 | 9 | ||
10 | var ( | 10 | var ( |
@@ -21,5 +21,5 @@ func main() { | |||
21 | flag.Parse() | 21 | flag.Parse() |
22 | 22 | ||
23 | // Use a config struct | 23 | // Use a config struct |
24 | log.Fatal(gopherproxy.ListenAndServe(*bind, *startpagefile, *robotsfile, *robotsdebug, *vipsconcurrency)) | 24 | log.Fatal(port.ListenAndServe(*bind, *startpagefile, *robotsfile, *robotsdebug, *vipsconcurrency)) |
25 | } | 25 | } |
diff --git a/fonts/glyphs.txt b/fonts/glyphs.txt index 5acadec..ac0461a 100644 --- a/fonts/glyphs.txt +++ b/fonts/glyphs.txt | |||
@@ -1 +1 @@ | |||
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`´abcdefghijklmnopqrstuvwxyz{|}~äöüÄÖÜßẞ↓↙←↖↑↗→↘€»«„“”·…°’‾█▓▒░ ▀▄‐╭╮─│╰╯╱╲╳ʻ‘ | !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`´abcdefghijklmnopqrstuvwxyz{|}~äöüÄÖÜßẞ↓↙←↖↑↗→↘€»«„“”·…°’‾█▓▒░ ▀▄‐╭╮─│╰╯┌┐└┘├╱╲╳ʻ‘ | ||
@@ -1,13 +1,12 @@ | |||
1 | module git.feuerfuchs.dev/Feuerfuchs/gopherproxy | 1 | module git.vulpes.one/Feuerfuchs/port |
2 | 2 | ||
3 | require ( | 3 | require ( |
4 | github.com/NYTimes/gziphandler v1.1.1 | 4 | github.com/NYTimes/gziphandler v1.1.1 |
5 | github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea | 5 | github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a |
6 | github.com/gobuffalo/packr/v2 v2.1.0 | 6 | github.com/gobuffalo/packr/v2 v2.8.0 |
7 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 | 7 | github.com/temoto/robotstxt v1.1.1 |
8 | github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea | 8 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 |
9 | golang.org/x/net v0.0.0-20190311183353-d8887717615a | 9 | golang.org/x/text v0.3.2 |
10 | golang.org/x/text v0.3.0 | ||
11 | ) | 10 | ) |
12 | 11 | ||
13 | go 1.13 | 12 | go 1.13 |
@@ -1,92 +1,309 @@ | |||
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= | ||
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||
1 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= | 3 | github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= |
2 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= | 4 | github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= |
5 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= | ||
6 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= | ||
7 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= | ||
8 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= | ||
9 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | ||
10 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= | ||
11 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= | ||
12 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||
13 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= | ||
14 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= | ||
15 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= | ||
16 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= | ||
17 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | ||
18 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= | ||
19 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= | ||
20 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | ||
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 21 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | 22 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 23 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
6 | github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea h1:ZtETbJTO1R3qVLdVbpjrDhD5fR8bYVhhq2RMi7rOlH4= | 24 | github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea h1:ZtETbJTO1R3qVLdVbpjrDhD5fR8bYVhhq2RMi7rOlH4= |
7 | github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk= | 25 | github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk= |
26 | github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a h1:g1X1c43wACmPtvFo+szBy/0coLkRwVJ0+i2f6YJITto= | ||
27 | github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk= | ||
28 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||
29 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | ||
30 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= | ||
31 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||
32 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||
33 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= | ||
34 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= | ||
35 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= | ||
36 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= | ||
8 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= | 37 | github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= |
38 | github.com/gobuffalo/attrs v1.0.0 h1:aAK8D55L0GNVbVRlaCwvAGE2BcGdEoVoK86vgQpuUZ8= | ||
39 | github.com/gobuffalo/attrs v1.0.0/go.mod h1:YWU+sjOr7V05rNzvCoVc5qQTC2IgEz9434TFW5GhNaw= | ||
9 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= | 40 | github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= |
41 | github.com/gobuffalo/depgen v0.2.0 h1:CYuqsR8sq+L9G9+A6uUcTEuaK8AGenAjtYOm238fN3M= | ||
42 | github.com/gobuffalo/depgen v0.2.0/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= | ||
10 | github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ= | 43 | github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ= |
11 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= | 44 | github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= |
12 | github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= | 45 | github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= |
13 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= | 46 | github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= |
47 | github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= | ||
48 | github.com/gobuffalo/envy v1.8.1 h1:RUr68liRvs0TS1D5qdW3mQv2SjAsu1QWMCx1tG4kDjs= | ||
49 | github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= | ||
14 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= | 50 | github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= |
15 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= | 51 | github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= |
52 | github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM= | ||
53 | github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= | ||
16 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= | 54 | github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= |
17 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e h1:JbHBQOMhE0wmpSuejnSkdnL2rULqQTwEGgVe85o7+No= | 55 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e h1:JbHBQOMhE0wmpSuejnSkdnL2rULqQTwEGgVe85o7+No= |
18 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= | 56 | github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= |
57 | github.com/gobuffalo/genny v0.6.0 h1:d7c6d66ZrTHHty01hDX1/TcTWvAJQxRZl885KWX5kHY= | ||
58 | github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA= | ||
19 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= | 59 | github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= |
60 | github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic= | ||
20 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA= | 61 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA= |
21 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= | 62 | github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= |
63 | github.com/gobuffalo/gogen v0.2.0 h1:Xx7NCe+/y++eII2aWAFZ09/81MhDCsZwvMzIFJoQRnU= | ||
64 | github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= | ||
65 | github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4= | ||
66 | github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= | ||
22 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= | 67 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= |
23 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= | 68 | github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= |
69 | github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= | ||
70 | github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= | ||
71 | github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= | ||
24 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= | 72 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= |
25 | github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= | 73 | github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= |
26 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= | 74 | github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= |
75 | github.com/gobuffalo/mapi v1.2.1 h1:TyfbtQaW7GvS4DXdF1KQOSGrW6L0uiFmGDz+JgEIMbM= | ||
76 | github.com/gobuffalo/mapi v1.2.1/go.mod h1:giGJ2AUESRepOFYAzWpq8Gf/s/QDryxoEHisQtFN3cY= | ||
27 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI= | 77 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI= |
28 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= | 78 | github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= |
79 | github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= | ||
80 | github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= | ||
81 | github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= | ||
29 | github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY= | 82 | github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY= |
30 | github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U= | 83 | github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U= |
31 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= | 84 | github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= |
32 | github.com/gobuffalo/packr/v2 v2.1.0 h1:nWGTgGtZrR4yBQvmAKF4AthraObjRMzx6lJa9e+JbLQ= | 85 | github.com/gobuffalo/packr/v2 v2.1.0 h1:nWGTgGtZrR4yBQvmAKF4AthraObjRMzx6lJa9e+JbLQ= |
33 | github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= | 86 | github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= |
87 | github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= | ||
88 | github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY= | ||
89 | github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g= | ||
90 | github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= | ||
34 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= | 91 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= |
35 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= | 92 | github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= |
93 | github.com/gobuffalo/syncx v0.1.0 h1://CNTQ/+VFQizkW24DrBtTBvj8c2+chz5Y7kbboQ2qk= | ||
94 | github.com/gobuffalo/syncx v0.1.0/go.mod h1:Mg/s+5pv7IgxEp6sA+NFpqS4o2x+R9dQNwbwT0iuOGQ= | ||
95 | github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= | ||
96 | github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= | ||
97 | github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= | ||
98 | github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= | ||
99 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||
100 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= | ||
101 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||
102 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||
103 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||
104 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
105 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
106 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= | ||
107 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= | ||
108 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= | ||
109 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= | ||
110 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= | ||
111 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||
112 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= | ||
113 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= | ||
36 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= | 114 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= |
37 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= | 115 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= |
38 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= | 116 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= |
117 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= | ||
118 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||
39 | github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA= | 119 | github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA= |
40 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= | 120 | github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= |
121 | github.com/karrick/godirwalk v1.15.3 h1:0a2pXOgtB16CqIqXTiT7+K9L73f74n/aNQUnH6Ortew= | ||
122 | github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= | ||
123 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | ||
124 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||
41 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | 125 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
126 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= | ||
127 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
128 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= | ||
42 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | 129 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
43 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | 130 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
44 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | 131 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
132 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||
133 | github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= | ||
134 | github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= | ||
45 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= | 135 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= |
46 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= | 136 | github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= |
137 | github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= | ||
138 | github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= | ||
47 | github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= | 139 | github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= |
48 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= | 140 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= |
141 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= | ||
142 | github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= | ||
143 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= | ||
144 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= | ||
145 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= | ||
49 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= | 146 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= |
50 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= | 147 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= |
148 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= | ||
149 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||
150 | github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= | ||
151 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= | ||
152 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||
51 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | 153 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
52 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | 154 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |
53 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | 155 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
156 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
157 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
54 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | 158 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
55 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | 159 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
56 | github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0 h1:10LO/S8HVjIuEHsHea//Cena1Ztgy23f/e8HFC0w5ow= | 160 | github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0 h1:10LO/S8HVjIuEHsHea//Cena1Ztgy23f/e8HFC0w5ow= |
57 | github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0/go.mod h1:LiuwIXz4es4YIUOD6yRv8mES9n9dFbe4z0+TcrLkhXg= | 161 | github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0/go.mod h1:LiuwIXz4es4YIUOD6yRv8mES9n9dFbe4z0+TcrLkhXg= |
162 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | ||
163 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= | ||
164 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||
165 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||
166 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= | ||
167 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||
168 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||
169 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||
170 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= | ||
171 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= | ||
58 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | 172 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= |
59 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | 173 | github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= |
60 | github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= | 174 | github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= |
61 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | 175 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= |
176 | github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= | ||
177 | github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= | ||
178 | github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= | ||
179 | github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= | ||
180 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= | ||
181 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||
182 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= | ||
183 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= | ||
184 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= | ||
185 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | ||
62 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= | 186 | github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= |
63 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= | 187 | github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= |
64 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= | 188 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= |
189 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= | ||
190 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||
191 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= | ||
192 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= | ||
193 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= | ||
194 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= | ||
195 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= | ||
196 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= | ||
65 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | 197 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= |
198 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= | ||
199 | github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= | ||
200 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||
66 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | 201 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= |
202 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= | ||
203 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= | ||
67 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | 204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
68 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | 205 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
69 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | 206 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
70 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= | 207 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= |
71 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | 208 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
209 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
210 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||
72 | github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea h1:hH8P1IiDpzRU6ZDbDh/RDnVuezi2oOXJpApa06M0zyI= | 211 | github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea h1:hH8P1IiDpzRU6ZDbDh/RDnVuezi2oOXJpApa06M0zyI= |
73 | github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea/go.mod h1:aOux3gHPCftJ3KHq6Pz/AlDjYJ7Y+yKfm1gU/3B0u04= | 212 | github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea/go.mod h1:aOux3gHPCftJ3KHq6Pz/AlDjYJ7Y+yKfm1gU/3B0u04= |
213 | github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA= | ||
214 | github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= | ||
215 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= | ||
216 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= | ||
217 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= | ||
218 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= | ||
219 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= | ||
220 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= | ||
221 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||
222 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||
223 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||
74 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | 224 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
225 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
75 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= | 226 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= |
76 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | 227 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
228 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
229 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||
230 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= | ||
231 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
232 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||
233 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= | ||
234 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= | ||
235 | golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= | ||
236 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||
237 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
238 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
239 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
77 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= | 240 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= |
78 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | 241 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |
79 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= | 242 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= |
80 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | 243 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
244 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
245 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= | ||
246 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
247 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
248 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||
249 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= | ||
250 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= | ||
251 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||
252 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
253 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
254 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
81 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | 255 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
82 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | 256 | golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
257 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
258 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
259 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
83 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | 260 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
261 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
262 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
263 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
264 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
84 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= | 265 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= |
85 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | 266 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
267 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
268 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
269 | golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
270 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= | ||
271 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
86 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= | 272 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= |
87 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | 273 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
274 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= | ||
275 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||
276 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
277 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
278 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
279 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
280 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | ||
88 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | 281 | golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
89 | golang.org/x/tools v0.0.0-20190404132500-923d25813098 h1:MtqjsZmyGRgMmLUgxnmMJ6RYdvd2ib8ipiayHhqSxs4= | 282 | golang.org/x/tools v0.0.0-20190404132500-923d25813098 h1:MtqjsZmyGRgMmLUgxnmMJ6RYdvd2ib8ipiayHhqSxs4= |
90 | golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= | 283 | golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
284 | golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
285 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||
286 | golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= | ||
287 | golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE= | ||
288 | golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= | ||
289 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
290 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
291 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||
292 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||
293 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= | ||
294 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= | ||
295 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||
296 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= | ||
297 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||
298 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
91 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | 299 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
92 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | 300 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= |
301 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= | ||
302 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= | ||
303 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= | ||
304 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= | ||
305 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
306 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
307 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
308 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
309 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||
diff --git a/gopherproxy.bin b/gopherproxy.bin new file mode 100755 index 0000000..e8f94c1 --- /dev/null +++ b/gopherproxy.bin | |||
Binary files differ | |||
diff --git a/gopherproxy/gopherproxy.go b/gopherproxy/gopherproxy.go deleted file mode 100644 index 453ee9c..0000000 --- a/gopherproxy/gopherproxy.go +++ /dev/null | |||
@@ -1,705 +0,0 @@ | |||
1 | package gopherproxy | ||
2 | |||
3 | import ( | ||
4 | "bufio" | ||
5 | "bytes" | ||
6 | "crypto/md5" | ||
7 | "fmt" | ||
8 | "html" | ||
9 | "html/template" | ||
10 | "io" | ||
11 | "io/ioutil" | ||
12 | "log" | ||
13 | "mime" | ||
14 | "net" | ||
15 | "net/http" | ||
16 | "net/url" | ||
17 | "regexp" | ||
18 | "strings" | ||
19 | |||
20 | "golang.org/x/net/html/charset" | ||
21 | "golang.org/x/text/transform" | ||
22 | |||
23 | "git.feuerfuchs.dev/Feuerfuchs/gopherproxy/gopherproxy/libgemini" | ||
24 | "git.feuerfuchs.dev/Feuerfuchs/gopherproxy/gopherproxy/libgopher" | ||
25 | |||
26 | "github.com/NYTimes/gziphandler" | ||
27 | "github.com/davidbyttow/govips/pkg/vips" | ||
28 | "github.com/gobuffalo/packr/v2" | ||
29 | "github.com/temoto/robotstxt" | ||
30 | ) | ||
31 | |||
32 | var ( | ||
33 | TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") | ||
34 | ) | ||
35 | |||
36 | const ( | ||
37 | ITEM_TYPE_GEMINI_LINE = "" | ||
38 | ITEM_TYPE_GEMINI_LINK = " =>" | ||
39 | ) | ||
40 | |||
41 | type Item struct { | ||
42 | Link template.URL | ||
43 | Type string | ||
44 | Text string | ||
45 | } | ||
46 | |||
47 | type AssetList struct { | ||
48 | Style string | ||
49 | JS string | ||
50 | FontW string | ||
51 | FontW2 string | ||
52 | PropFontW string | ||
53 | PropFontW2 string | ||
54 | } | ||
55 | |||
56 | type TemplateVariables struct { | ||
57 | Title string | ||
58 | URI string | ||
59 | Assets AssetList | ||
60 | RawText string | ||
61 | Lines []Item | ||
62 | Error bool | ||
63 | Protocol string | ||
64 | } | ||
65 | |||
66 | func resolveURI(uri string, baseURL *url.URL) (resolvedURI string) { | ||
67 | if strings.HasPrefix(uri, "//") { | ||
68 | resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "//") | ||
69 | } else if strings.HasPrefix(uri, "gemini://") { | ||
70 | resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "gemini://") | ||
71 | } else if strings.HasPrefix(uri, "gopher://") { | ||
72 | resolvedURI = "/gopher/" + strings.TrimPrefix(uri, "gopher://") | ||
73 | } else { | ||
74 | url, err := url.Parse(uri) | ||
75 | if err != nil { | ||
76 | return "" | ||
77 | } | ||
78 | adjustedURI := baseURL.ResolveReference(url) | ||
79 | path := adjustedURI.Path | ||
80 | if !strings.HasPrefix(path, "/") { | ||
81 | path = "/" + path | ||
82 | } | ||
83 | if adjustedURI.Scheme == "gemini" { | ||
84 | resolvedURI = "/gemini/" + adjustedURI.Host + path | ||
85 | } else if adjustedURI.Scheme == "gopher" { | ||
86 | resolvedURI = "/gopher/" + adjustedURI.Host + path | ||
87 | } else { | ||
88 | resolvedURI = adjustedURI.String() | ||
89 | } | ||
90 | } | ||
91 | |||
92 | return | ||
93 | } | ||
94 | |||
95 | func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error { | ||
96 | var title string | ||
97 | |||
98 | out := make([]Item, len(d.Items)) | ||
99 | |||
100 | for i, x := range d.Items { | ||
101 | if x.Type == libgopher.INFO && x.Selector == "TITLE" { | ||
102 | title = x.Description | ||
103 | continue | ||
104 | } | ||
105 | |||
106 | tr := Item{ | ||
107 | Text: x.Description, | ||
108 | Type: x.Type.String(), | ||
109 | } | ||
110 | |||
111 | if x.Type == libgopher.INFO { | ||
112 | out[i] = tr | ||
113 | continue | ||
114 | } | ||
115 | |||
116 | if strings.HasPrefix(x.Selector, "URL:") || strings.HasPrefix(x.Selector, "/URL:") { | ||
117 | link := strings.TrimPrefix(strings.TrimPrefix(x.Selector, "/"), "URL:") | ||
118 | if strings.HasPrefix(link, "gemini://") { | ||
119 | link = fmt.Sprintf( | ||
120 | "/gemini/%s", | ||
121 | strings.TrimPrefix(link, "gemini://"), | ||
122 | ) | ||
123 | } else if strings.HasPrefix(link, "gopher://") { | ||
124 | link = fmt.Sprintf( | ||
125 | "/gopher/%s", | ||
126 | strings.TrimPrefix(link, "gopher://"), | ||
127 | ) | ||
128 | } | ||
129 | tr.Link = template.URL(link) | ||
130 | } else { | ||
131 | var linkHostport string | ||
132 | if x.Port != "70" { | ||
133 | linkHostport = net.JoinHostPort(x.Host, x.Port) | ||
134 | } else { | ||
135 | linkHostport = x.Host | ||
136 | } | ||
137 | |||
138 | path := url.PathEscape(x.Selector) | ||
139 | path = strings.Replace(path, "%2F", "/", -1) | ||
140 | tr.Link = template.URL( | ||
141 | fmt.Sprintf( | ||
142 | "/gopher/%s/%s%s", | ||
143 | linkHostport, | ||
144 | string(byte(x.Type)), | ||
145 | path, | ||
146 | ), | ||
147 | ) | ||
148 | } | ||
149 | |||
150 | out[i] = tr | ||
151 | } | ||
152 | |||
153 | if title == "" { | ||
154 | if uri != "" { | ||
155 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
156 | } else { | ||
157 | title = hostport | ||
158 | } | ||
159 | } | ||
160 | |||
161 | return tpl.Execute(w, TemplateVariables{ | ||
162 | Title: title, | ||
163 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
164 | Assets: assetList, | ||
165 | Lines: out, | ||
166 | Protocol: "gopher", | ||
167 | }) | ||
168 | } | ||
169 | |||
170 | func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (items []Item) { | ||
171 | baseURL, err := url.Parse(fmt.Sprintf( | ||
172 | "gemini://%s/%s", | ||
173 | hostport, | ||
174 | uri, | ||
175 | )) | ||
176 | if err != nil { | ||
177 | return []Item{} | ||
178 | } | ||
179 | |||
180 | scanner := bufio.NewScanner(body) | ||
181 | |||
182 | for scanner.Scan() { | ||
183 | line := strings.Trim(scanner.Text(), "\r\n") | ||
184 | line = TermEscapeSGRPattern.ReplaceAllString(line, "") | ||
185 | |||
186 | item := Item{ | ||
187 | Type: ITEM_TYPE_GEMINI_LINE, | ||
188 | Text: line, | ||
189 | } | ||
190 | |||
191 | linkMatch := libgemini.LinkPattern.FindStringSubmatch(line) | ||
192 | if len(linkMatch) != 0 && linkMatch[0] != "" { | ||
193 | item.Type = ITEM_TYPE_GEMINI_LINK | ||
194 | item.Link = template.URL(resolveURI(linkMatch[1], baseURL)) | ||
195 | item.Text = linkMatch[2] | ||
196 | if item.Text == "" { | ||
197 | item.Text = linkMatch[1] | ||
198 | } | ||
199 | } | ||
200 | |||
201 | items = append(items, item) | ||
202 | } | ||
203 | |||
204 | return | ||
205 | } | ||
206 | |||
207 | func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc { | ||
208 | return func(w http.ResponseWriter, req *http.Request) { | ||
209 | if err := tpl.Execute(w, TemplateVariables{ | ||
210 | Title: "Gopher/Gemini proxy", | ||
211 | Assets: assetList, | ||
212 | RawText: startpagetext, | ||
213 | Protocol: "startpage", | ||
214 | }); err != nil { | ||
215 | log.Println("Template error: " + err.Error()) | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | // GopherHandler returns a Handler that proxies requests | ||
221 | // to the specified Gopher server as denoated by the first argument | ||
222 | // to the request path and renders the content using the provided template. | ||
223 | // The optional robots parameters points to a robotstxt.RobotsData struct | ||
224 | // to test user agents against a configurable robotst.txt file. | ||
225 | func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { | ||
226 | return func(w http.ResponseWriter, req *http.Request) { | ||
227 | agent := req.UserAgent() | ||
228 | path := strings.TrimPrefix(req.URL.Path, "/gopher/") | ||
229 | |||
230 | if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { | ||
231 | log.Printf("UserAgent %s ignored robots.txt", agent) | ||
232 | } | ||
233 | |||
234 | parts := strings.Split(path, "/") | ||
235 | hostport := parts[0] | ||
236 | |||
237 | if len(hostport) == 0 { | ||
238 | http.Redirect(w, req, "/", http.StatusFound) | ||
239 | return | ||
240 | } | ||
241 | |||
242 | title := hostport | ||
243 | |||
244 | var qs string | ||
245 | |||
246 | if req.URL.RawQuery != "" { | ||
247 | qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) | ||
248 | } | ||
249 | |||
250 | uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) | ||
251 | if err != nil { | ||
252 | if e := tpl.Execute(w, TemplateVariables{ | ||
253 | Title: title, | ||
254 | URI: hostport, | ||
255 | Assets: assetList, | ||
256 | RawText: fmt.Sprintf("Error: %s", err), | ||
257 | Error: true, | ||
258 | Protocol: "gopher", | ||
259 | }); e != nil { | ||
260 | log.Println("Template error: " + e.Error()) | ||
261 | log.Println(err.Error()) | ||
262 | } | ||
263 | return | ||
264 | } | ||
265 | |||
266 | if uri != "" { | ||
267 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
268 | } | ||
269 | |||
270 | res, err := libgopher.Get( | ||
271 | fmt.Sprintf( | ||
272 | "gopher://%s/%s%s", | ||
273 | hostport, | ||
274 | uri, | ||
275 | qs, | ||
276 | ), | ||
277 | ) | ||
278 | |||
279 | if err != nil { | ||
280 | if e := tpl.Execute(w, TemplateVariables{ | ||
281 | Title: title, | ||
282 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
283 | Assets: assetList, | ||
284 | RawText: fmt.Sprintf("Error: %s", err), | ||
285 | Error: true, | ||
286 | Protocol: "gopher", | ||
287 | }); e != nil { | ||
288 | log.Println("Template error: " + e.Error()) | ||
289 | } | ||
290 | return | ||
291 | } | ||
292 | |||
293 | if res.Body != nil { | ||
294 | if len(parts) < 2 { | ||
295 | io.Copy(w, res.Body) | ||
296 | } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") { | ||
297 | buf := new(bytes.Buffer) | ||
298 | buf.ReadFrom(res.Body) | ||
299 | |||
300 | if err := tpl.Execute(w, TemplateVariables{ | ||
301 | Title: title, | ||
302 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
303 | Assets: assetList, | ||
304 | RawText: buf.String(), | ||
305 | Protocol: "gopher", | ||
306 | }); err != nil { | ||
307 | log.Println("Template error: " + err.Error()) | ||
308 | } | ||
309 | } else if strings.HasPrefix(parts[1], "T") { | ||
310 | _, _, err = vips.NewTransform(). | ||
311 | Load(res.Body). | ||
312 | ResizeStrategy(vips.ResizeStrategyAuto). | ||
313 | ResizeWidth(160). | ||
314 | Quality(75). | ||
315 | Output(w). | ||
316 | Apply() | ||
317 | } else { | ||
318 | io.Copy(w, res.Body) | ||
319 | } | ||
320 | } else { | ||
321 | if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { | ||
322 | if e := tpl.Execute(w, TemplateVariables{ | ||
323 | Title: title, | ||
324 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
325 | Assets: assetList, | ||
326 | RawText: fmt.Sprintf("Error: %s", err), | ||
327 | Error: true, | ||
328 | Protocol: "gopher", | ||
329 | }); e != nil { | ||
330 | log.Println("Template error: " + e.Error()) | ||
331 | log.Println(e.Error()) | ||
332 | } | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | } | ||
337 | |||
338 | func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { | ||
339 | return func(w http.ResponseWriter, req *http.Request) { | ||
340 | agent := req.UserAgent() | ||
341 | path := strings.TrimPrefix(req.URL.Path, "/gemini/") | ||
342 | |||
343 | if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { | ||
344 | log.Printf("UserAgent %s ignored robots.txt", agent) | ||
345 | } | ||
346 | |||
347 | parts := strings.Split(path, "/") | ||
348 | hostport := parts[0] | ||
349 | |||
350 | if len(hostport) == 0 { | ||
351 | http.Redirect(w, req, "/", http.StatusFound) | ||
352 | return | ||
353 | } | ||
354 | |||
355 | title := hostport | ||
356 | |||
357 | var qs string | ||
358 | |||
359 | if req.URL.RawQuery != "" { | ||
360 | qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) | ||
361 | } | ||
362 | |||
363 | uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) | ||
364 | if err != nil { | ||
365 | if e := tpl.Execute(w, TemplateVariables{ | ||
366 | Title: title, | ||
367 | URI: hostport, | ||
368 | Assets: assetList, | ||
369 | RawText: fmt.Sprintf("Error: %s", err), | ||
370 | Error: true, | ||
371 | Protocol: "gemini", | ||
372 | }); e != nil { | ||
373 | log.Println("Template error: " + e.Error()) | ||
374 | log.Println(err.Error()) | ||
375 | } | ||
376 | return | ||
377 | } | ||
378 | |||
379 | if uri != "" { | ||
380 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
381 | } | ||
382 | |||
383 | res, err := libgemini.Get( | ||
384 | fmt.Sprintf( | ||
385 | "gemini://%s/%s%s", | ||
386 | hostport, | ||
387 | uri, | ||
388 | qs, | ||
389 | ), | ||
390 | ) | ||
391 | |||
392 | if err != nil { | ||
393 | if e := tpl.Execute(w, TemplateVariables{ | ||
394 | Title: title, | ||
395 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
396 | Assets: assetList, | ||
397 | RawText: fmt.Sprintf("Error: %s", err), | ||
398 | Error: true, | ||
399 | Protocol: "gemini", | ||
400 | }); e != nil { | ||
401 | log.Println("Template error: " + e.Error()) | ||
402 | log.Println(err.Error()) | ||
403 | } | ||
404 | return | ||
405 | } | ||
406 | |||
407 | if int(res.Header.Status/10) == 3 { | ||
408 | baseURL, err := url.Parse(fmt.Sprintf( | ||
409 | "gemini://%s/%s", | ||
410 | hostport, | ||
411 | uri, | ||
412 | )) | ||
413 | if err != nil { | ||
414 | if e := tpl.Execute(w, TemplateVariables{ | ||
415 | Title: title, | ||
416 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
417 | Assets: assetList, | ||
418 | RawText: fmt.Sprintf("Error: %s", err), | ||
419 | Error: true, | ||
420 | Protocol: "gemini", | ||
421 | }); e != nil { | ||
422 | log.Println("Template error: " + e.Error()) | ||
423 | log.Println(err.Error()) | ||
424 | } | ||
425 | return | ||
426 | } | ||
427 | |||
428 | http.Redirect(w, req, resolveURI(res.Header.Meta, baseURL), http.StatusFound) | ||
429 | return | ||
430 | } | ||
431 | |||
432 | if int(res.Header.Status/10) != 2 { | ||
433 | if err := tpl.Execute(w, TemplateVariables{ | ||
434 | Title: title, | ||
435 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
436 | Assets: assetList, | ||
437 | RawText: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), | ||
438 | Error: true, | ||
439 | Protocol: "gemini", | ||
440 | }); err != nil { | ||
441 | log.Println("Template error: " + err.Error()) | ||
442 | } | ||
443 | return | ||
444 | } | ||
445 | |||
446 | if strings.HasPrefix(res.Header.Meta, "text/") { | ||
447 | buf := new(bytes.Buffer) | ||
448 | |||
449 | _, params, err := mime.ParseMediaType(res.Header.Meta) | ||
450 | if err != nil { | ||
451 | buf.ReadFrom(res.Body) | ||
452 | } else { | ||
453 | encoding, _ := charset.Lookup(params["charset"]) | ||
454 | readbuf := new(bytes.Buffer) | ||
455 | readbuf.ReadFrom(res.Body) | ||
456 | |||
457 | writer := transform.NewWriter(buf, encoding.NewDecoder()) | ||
458 | writer.Write(readbuf.Bytes()) | ||
459 | writer.Close() | ||
460 | } | ||
461 | |||
462 | var ( | ||
463 | rawText string | ||
464 | items []Item | ||
465 | ) | ||
466 | |||
467 | if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { | ||
468 | items = parseGeminiDocument(buf, uri, hostport) | ||
469 | } else { | ||
470 | rawText = buf.String() | ||
471 | } | ||
472 | |||
473 | if err := tpl.Execute(w, TemplateVariables{ | ||
474 | Title: title, | ||
475 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
476 | Assets: assetList, | ||
477 | Lines: items, | ||
478 | RawText: rawText, | ||
479 | Protocol: "gemini", | ||
480 | }); err != nil { | ||
481 | log.Println("Template error: " + err.Error()) | ||
482 | } | ||
483 | } else { | ||
484 | io.Copy(w, res.Body) | ||
485 | } | ||
486 | } | ||
487 | } | ||
488 | |||
489 | // RobotsTxtHandler returns the contents of the robots.txt file | ||
490 | // if configured and valid. | ||
491 | func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc { | ||
492 | return func(w http.ResponseWriter, req *http.Request) { | ||
493 | if robotstxtdata == nil { | ||
494 | http.Error(w, "Not Found", http.StatusNotFound) | ||
495 | return | ||
496 | } | ||
497 | |||
498 | w.Header().Set("Content-Type", "text/plain") | ||
499 | w.Write(robotstxtdata) | ||
500 | } | ||
501 | } | ||
502 | |||
503 | func FaviconHandler(favicondata []byte) http.HandlerFunc { | ||
504 | return func(w http.ResponseWriter, req *http.Request) { | ||
505 | if favicondata == nil { | ||
506 | http.Error(w, "Not Found", http.StatusNotFound) | ||
507 | return | ||
508 | } | ||
509 | |||
510 | w.Header().Set("Content-Type", "image/vnd.microsoft.icon") | ||
511 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
512 | w.Write(favicondata) | ||
513 | } | ||
514 | } | ||
515 | |||
516 | func StyleHandler(styledata []byte) http.HandlerFunc { | ||
517 | return func(w http.ResponseWriter, req *http.Request) { | ||
518 | w.Header().Set("Content-Type", "text/css") | ||
519 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
520 | w.Write(styledata) | ||
521 | } | ||
522 | } | ||
523 | |||
524 | func JavaScriptHandler(jsdata []byte) http.HandlerFunc { | ||
525 | return func(w http.ResponseWriter, req *http.Request) { | ||
526 | w.Header().Set("Content-Type", "text/javascript") | ||
527 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
528 | w.Write(jsdata) | ||
529 | } | ||
530 | } | ||
531 | |||
532 | func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { | ||
533 | return func(w http.ResponseWriter, req *http.Request) { | ||
534 | if fontdata == nil { | ||
535 | http.Error(w, "Not Found", http.StatusNotFound) | ||
536 | return | ||
537 | } | ||
538 | |||
539 | if woff2 { | ||
540 | w.Header().Set("Content-Type", "font/woff2") | ||
541 | } else { | ||
542 | w.Header().Set("Content-Type", "font/woff") | ||
543 | } | ||
544 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
545 | |||
546 | w.Write(fontdata) | ||
547 | } | ||
548 | } | ||
549 | |||
550 | // ListenAndServe creates a listening HTTP server bound to | ||
551 | // the interface specified by bind and sets up a Gopher to HTTP | ||
552 | // proxy proxying requests as requested and by default will prozy | ||
553 | // to a Gopher server address specified by uri if no servers is | ||
554 | // specified by the request. The robots argument is a pointer to | ||
555 | // a robotstxt.RobotsData struct for testing user agents against | ||
556 | // a configurable robots.txt file. | ||
557 | func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error { | ||
558 | var ( | ||
559 | tpl *template.Template | ||
560 | robotsdata *robotstxt.RobotsData | ||
561 | ) | ||
562 | |||
563 | robotstxtdata, err := ioutil.ReadFile(robotsfile) | ||
564 | if err != nil { | ||
565 | log.Printf("error reading robots.txt: %s", err) | ||
566 | robotstxtdata = nil | ||
567 | } else { | ||
568 | robotsdata, err = robotstxt.FromBytes(robotstxtdata) | ||
569 | if err != nil { | ||
570 | log.Printf("error reading robots.txt: %s", err) | ||
571 | robotstxtdata = nil | ||
572 | } | ||
573 | } | ||
574 | |||
575 | box := packr.New("assets", "../assets") | ||
576 | |||
577 | fontdataw, err := box.Find("iosevka-term-ss03-regular.woff") | ||
578 | if err != nil { | ||
579 | fontdataw = []byte{} | ||
580 | } | ||
581 | fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw)) | ||
582 | |||
583 | fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") | ||
584 | if err != nil { | ||
585 | fontdataw2 = []byte{} | ||
586 | } | ||
587 | fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2)) | ||
588 | |||
589 | propfontdataw, err := box.Find("iosevka-aile-regular.woff") | ||
590 | if err != nil { | ||
591 | propfontdataw = []byte{} | ||
592 | } | ||
593 | propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw)) | ||
594 | |||
595 | propfontdataw2, err := box.Find("iosevka-aile-regular.woff2") | ||
596 | if err != nil { | ||
597 | propfontdataw2 = []byte{} | ||
598 | } | ||
599 | propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2)) | ||
600 | |||
601 | styledata, err := box.Find("style.css") | ||
602 | if err != nil { | ||
603 | styledata = []byte{} | ||
604 | } | ||
605 | styleAsset := fmt.Sprintf("/style-%x.css", md5.Sum(styledata)) | ||
606 | |||
607 | jsdata, err := box.Find("main.js") | ||
608 | if err != nil { | ||
609 | jsdata = []byte{} | ||
610 | } | ||
611 | jsAsset := fmt.Sprintf("/main-%x.js", md5.Sum(jsdata)) | ||
612 | |||
613 | favicondata, err := box.Find("favicon.ico") | ||
614 | if err != nil { | ||
615 | favicondata = []byte{} | ||
616 | } | ||
617 | |||
618 | startpagedata, err := ioutil.ReadFile(startpagefile) | ||
619 | if err != nil { | ||
620 | startpagedata, err = box.Find("startpage.txt") | ||
621 | if err != nil { | ||
622 | startpagedata = []byte{} | ||
623 | } | ||
624 | } | ||
625 | startpagetext := string(startpagedata) | ||
626 | |||
627 | tpldata, err := ioutil.ReadFile(".template") | ||
628 | if err == nil { | ||
629 | tpltext = string(tpldata) | ||
630 | } | ||
631 | |||
632 | funcMap := template.FuncMap{ | ||
633 | "safeHtml": func(s string) template.HTML { | ||
634 | return template.HTML(s) | ||
635 | }, | ||
636 | "safeCss": func(s string) template.CSS { | ||
637 | return template.CSS(s) | ||
638 | }, | ||
639 | "safeJs": func(s string) template.JS { | ||
640 | return template.JS(s) | ||
641 | }, | ||
642 | "HTMLEscape": func(s string) string { | ||
643 | return html.EscapeString(s) | ||
644 | }, | ||
645 | "split": strings.Split, | ||
646 | "last": func(s []string) string { | ||
647 | return s[len(s)-1] | ||
648 | }, | ||
649 | "pop": func(s []string) []string { | ||
650 | return s[:len(s)-1] | ||
651 | }, | ||
652 | "replace": func(pattern, output string, input interface{}) string { | ||
653 | var re = regexp.MustCompile(pattern) | ||
654 | var inputStr = fmt.Sprintf("%v", input) | ||
655 | return re.ReplaceAllString(inputStr, output) | ||
656 | }, | ||
657 | "trimLeftChar": func(s string) string { | ||
658 | for i := range s { | ||
659 | if i > 0 { | ||
660 | return s[i:] | ||
661 | } | ||
662 | } | ||
663 | return s[:0] | ||
664 | }, | ||
665 | "hasPrefix": func(s string, prefix string) bool { | ||
666 | return strings.HasPrefix(s, prefix) | ||
667 | }, | ||
668 | "title": func(s string) string { | ||
669 | return strings.Title(s) | ||
670 | }, | ||
671 | } | ||
672 | |||
673 | tpl, err = template.New("gophermenu").Funcs(funcMap).Parse(tpltext) | ||
674 | if err != nil { | ||
675 | log.Fatal(err) | ||
676 | } | ||
677 | |||
678 | vips.Startup(&vips.Config{ | ||
679 | ConcurrencyLevel: vipsconcurrency, | ||
680 | }) | ||
681 | |||
682 | assets := AssetList{ | ||
683 | Style: styleAsset, | ||
684 | JS: jsAsset, | ||
685 | FontW: fontwAsset, | ||
686 | FontW2: fontw2Asset, | ||
687 | PropFontW: propfontwAsset, | ||
688 | PropFontW2: propfontw2Asset, | ||
689 | } | ||
690 | |||
691 | http.Handle("/", gziphandler.GzipHandler(DefaultHandler(tpl, startpagetext, assets))) | ||
692 | http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(tpl, robotsdata, assets, robotsdebug))) | ||
693 | http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(tpl, robotsdata, assets, robotsdebug))) | ||
694 | http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata))) | ||
695 | http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) | ||
696 | http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) | ||
697 | http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata))) | ||
698 | http.HandleFunc(fontwAsset, FontHandler(false, fontdataw)) | ||
699 | http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2)) | ||
700 | http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw)) | ||
701 | http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2)) | ||
702 | //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) | ||
703 | |||
704 | return http.ListenAndServe(bind, nil) | ||
705 | } | ||
diff --git a/internal/port/gemini.go b/internal/port/gemini.go new file mode 100644 index 0000000..f9b0b97 --- /dev/null +++ b/internal/port/gemini.go | |||
@@ -0,0 +1,205 @@ | |||
1 | package port | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "html/template" | ||
7 | "io" | ||
8 | "log" | ||
9 | "mime" | ||
10 | "net/http" | ||
11 | "net/url" | ||
12 | "regexp" | ||
13 | "strings" | ||
14 | |||
15 | "golang.org/x/net/html/charset" | ||
16 | "golang.org/x/text/transform" | ||
17 | |||
18 | "git.vulpes.one/Feuerfuchs/port/port/libgemini" | ||
19 | |||
20 | "github.com/temoto/robotstxt" | ||
21 | ) | ||
22 | |||
23 | var ( | ||
24 | TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") | ||
25 | ) | ||
26 | |||
27 | func resolveURI(uri string, baseURL *url.URL) (resolvedURI string) { | ||
28 | if strings.HasPrefix(uri, "//") { | ||
29 | resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "//") | ||
30 | } else if strings.HasPrefix(uri, "gemini://") { | ||
31 | resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "gemini://") | ||
32 | } else if strings.HasPrefix(uri, "gopher://") { | ||
33 | resolvedURI = "/gopher/" + strings.TrimPrefix(uri, "gopher://") | ||
34 | } else { | ||
35 | url, err := url.Parse(uri) | ||
36 | if err != nil { | ||
37 | return "" | ||
38 | } | ||
39 | adjustedURI := baseURL.ResolveReference(url) | ||
40 | path := adjustedURI.Path | ||
41 | if !strings.HasPrefix(path, "/") { | ||
42 | path = "/" + path | ||
43 | } | ||
44 | if adjustedURI.Scheme == "gemini" { | ||
45 | resolvedURI = "/gemini/" + adjustedURI.Host + path | ||
46 | } else if adjustedURI.Scheme == "gopher" { | ||
47 | resolvedURI = "/gopher/" + adjustedURI.Host + path | ||
48 | } else { | ||
49 | resolvedURI = adjustedURI.String() | ||
50 | } | ||
51 | } | ||
52 | |||
53 | return | ||
54 | } | ||
55 | |||
56 | func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { | ||
57 | return func(w http.ResponseWriter, req *http.Request) { | ||
58 | agent := req.UserAgent() | ||
59 | path := strings.TrimPrefix(req.URL.Path, "/gemini/") | ||
60 | |||
61 | if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { | ||
62 | log.Printf("UserAgent %s ignored robots.txt", agent) | ||
63 | } | ||
64 | |||
65 | parts := strings.Split(path, "/") | ||
66 | hostport := parts[0] | ||
67 | |||
68 | if len(hostport) == 0 { | ||
69 | http.Redirect(w, req, "/", http.StatusFound) | ||
70 | return | ||
71 | } | ||
72 | |||
73 | title := hostport | ||
74 | |||
75 | var qs string | ||
76 | |||
77 | if req.URL.RawQuery != "" { | ||
78 | qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) | ||
79 | } | ||
80 | |||
81 | uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) | ||
82 | if err != nil { | ||
83 | if e := tpl.Execute(w, TemplateVariables{ | ||
84 | Title: title, | ||
85 | URI: hostport, | ||
86 | Assets: assetList, | ||
87 | RawText: fmt.Sprintf("Error: %s", err), | ||
88 | Error: true, | ||
89 | Protocol: "gemini", | ||
90 | }); e != nil { | ||
91 | log.Println("Template error: " + e.Error()) | ||
92 | log.Println(err.Error()) | ||
93 | } | ||
94 | return | ||
95 | } | ||
96 | |||
97 | if uri != "" { | ||
98 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
99 | } | ||
100 | |||
101 | res, err := libgemini.Get( | ||
102 | fmt.Sprintf( | ||
103 | "gemini://%s/%s%s", | ||
104 | hostport, | ||
105 | uri, | ||
106 | qs, | ||
107 | ), | ||
108 | ) | ||
109 | |||
110 | if err != nil { | ||
111 | if e := tpl.Execute(w, TemplateVariables{ | ||
112 | Title: title, | ||
113 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
114 | Assets: assetList, | ||
115 | RawText: fmt.Sprintf("Error: %s", err), | ||
116 | Error: true, | ||
117 | Protocol: "gemini", | ||
118 | }); e != nil { | ||
119 | log.Println("Template error: " + e.Error()) | ||
120 | log.Println(err.Error()) | ||
121 | } | ||
122 | return | ||
123 | } | ||
124 | |||
125 | if int(res.Header.Status/10) == 3 { | ||
126 | baseURL, err := url.Parse(fmt.Sprintf( | ||
127 | "gemini://%s/%s", | ||
128 | hostport, | ||
129 | uri, | ||
130 | )) | ||
131 | if err != nil { | ||
132 | if e := tpl.Execute(w, TemplateVariables{ | ||
133 | Title: title, | ||
134 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
135 | Assets: assetList, | ||
136 | RawText: fmt.Sprintf("Error: %s", err), | ||
137 | Error: true, | ||
138 | Protocol: "gemini", | ||
139 | }); e != nil { | ||
140 | log.Println("Template error: " + e.Error()) | ||
141 | log.Println(err.Error()) | ||
142 | } | ||
143 | return | ||
144 | } | ||
145 | |||
146 | http.Redirect(w, req, resolveURI(res.Header.Meta, baseURL), http.StatusFound) | ||
147 | return | ||
148 | } | ||
149 | |||
150 | if int(res.Header.Status/10) != 2 { | ||
151 | if err := tpl.Execute(w, TemplateVariables{ | ||
152 | Title: title, | ||
153 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
154 | Assets: assetList, | ||
155 | RawText: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), | ||
156 | Error: true, | ||
157 | Protocol: "gemini", | ||
158 | }); err != nil { | ||
159 | log.Println("Template error: " + err.Error()) | ||
160 | } | ||
161 | return | ||
162 | } | ||
163 | |||
164 | if strings.HasPrefix(res.Header.Meta, "text/") { | ||
165 | buf := new(bytes.Buffer) | ||
166 | |||
167 | _, params, err := mime.ParseMediaType(res.Header.Meta) | ||
168 | if err != nil { | ||
169 | buf.ReadFrom(res.Body) | ||
170 | } else { | ||
171 | encoding, _ := charset.Lookup(params["charset"]) | ||
172 | readbuf := new(bytes.Buffer) | ||
173 | readbuf.ReadFrom(res.Body) | ||
174 | |||
175 | writer := transform.NewWriter(buf, encoding.NewDecoder()) | ||
176 | writer.Write(readbuf.Bytes()) | ||
177 | writer.Close() | ||
178 | } | ||
179 | |||
180 | var ( | ||
181 | rawText string | ||
182 | items []Item | ||
183 | ) | ||
184 | |||
185 | if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { | ||
186 | items = parseGeminiDocument(buf, uri, hostport) | ||
187 | } else { | ||
188 | rawText = buf.String() | ||
189 | } | ||
190 | |||
191 | if err := tpl.Execute(w, TemplateVariables{ | ||
192 | Title: title, | ||
193 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
194 | Assets: assetList, | ||
195 | Lines: items, | ||
196 | RawText: rawText, | ||
197 | Protocol: "gemini", | ||
198 | }); err != nil { | ||
199 | log.Println("Template error: " + err.Error()) | ||
200 | } | ||
201 | } else { | ||
202 | io.Copy(w, res.Body) | ||
203 | } | ||
204 | } | ||
205 | } | ||
diff --git a/internal/port/gopher.go b/internal/port/gopher.go new file mode 100644 index 0000000..ebeb213 --- /dev/null +++ b/internal/port/gopher.go | |||
@@ -0,0 +1,217 @@ | |||
1 | package port | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "html/template" | ||
7 | "io" | ||
8 | "log" | ||
9 | "net" | ||
10 | "net/http" | ||
11 | "net/url" | ||
12 | "strings" | ||
13 | |||
14 | "git.vulpes.one/Feuerfuchs/port/port/libgopher" | ||
15 | |||
16 | "github.com/davidbyttow/govips/pkg/vips" | ||
17 | "github.com/temoto/robotstxt" | ||
18 | ) | ||
19 | |||
20 | type Item struct { | ||
21 | Link template.URL | ||
22 | Type string | ||
23 | Text string | ||
24 | } | ||
25 | |||
26 | func renderGopherDirectory(w http.ResponseWriter, tpl *template.Template, assetList AssetList, uri string, hostport string, d libgopher.Directory) error { | ||
27 | var title string | ||
28 | |||
29 | out := make([]Item, len(d.Items)) | ||
30 | |||
31 | for i, x := range d.Items { | ||
32 | if x.Type == libgopher.INFO && x.Selector == "TITLE" { | ||
33 | title = x.Description | ||
34 | continue | ||
35 | } | ||
36 | |||
37 | tr := Item{ | ||
38 | Text: x.Description, | ||
39 | Type: x.Type.String(), | ||
40 | } | ||
41 | |||
42 | if x.Type == libgopher.INFO { | ||
43 | out[i] = tr | ||
44 | continue | ||
45 | } | ||
46 | |||
47 | if strings.HasPrefix(x.Selector, "URL:") || strings.HasPrefix(x.Selector, "/URL:") { | ||
48 | link := strings.TrimPrefix(strings.TrimPrefix(x.Selector, "/"), "URL:") | ||
49 | if strings.HasPrefix(link, "gemini://") { | ||
50 | link = fmt.Sprintf( | ||
51 | "/gemini/%s", | ||
52 | strings.TrimPrefix(link, "gemini://"), | ||
53 | ) | ||
54 | } else if strings.HasPrefix(link, "gopher://") { | ||
55 | link = fmt.Sprintf( | ||
56 | "/gopher/%s", | ||
57 | strings.TrimPrefix(link, "gopher://"), | ||
58 | ) | ||
59 | } | ||
60 | tr.Link = template.URL(link) | ||
61 | } else { | ||
62 | var linkHostport string | ||
63 | if x.Port != "70" { | ||
64 | linkHostport = net.JoinHostPort(x.Host, x.Port) | ||
65 | } else { | ||
66 | linkHostport = x.Host | ||
67 | } | ||
68 | |||
69 | path := url.PathEscape(x.Selector) | ||
70 | path = strings.Replace(path, "%2F", "/", -1) | ||
71 | tr.Link = template.URL( | ||
72 | fmt.Sprintf( | ||
73 | "/gopher/%s/%s%s", | ||
74 | linkHostport, | ||
75 | string(byte(x.Type)), | ||
76 | path, | ||
77 | ), | ||
78 | ) | ||
79 | } | ||
80 | |||
81 | out[i] = tr | ||
82 | } | ||
83 | |||
84 | if title == "" { | ||
85 | if uri != "" { | ||
86 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
87 | } else { | ||
88 | title = hostport | ||
89 | } | ||
90 | } | ||
91 | |||
92 | return tpl.Execute(w, TemplateVariables{ | ||
93 | Title: title, | ||
94 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
95 | Assets: assetList, | ||
96 | Lines: out, | ||
97 | Protocol: "gopher", | ||
98 | }) | ||
99 | } | ||
100 | |||
101 | // GopherHandler returns a Handler that proxies requests | ||
102 | // to the specified Gopher server as denoated by the first argument | ||
103 | // to the request path and renders the content using the provided template. | ||
104 | // The optional robots parameters points to a robotstxt.RobotsData struct | ||
105 | // to test user agents against a configurable robotst.txt file. | ||
106 | func GopherHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { | ||
107 | return func(w http.ResponseWriter, req *http.Request) { | ||
108 | agent := req.UserAgent() | ||
109 | path := strings.TrimPrefix(req.URL.Path, "/gopher/") | ||
110 | |||
111 | if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { | ||
112 | log.Printf("UserAgent %s ignored robots.txt", agent) | ||
113 | } | ||
114 | |||
115 | parts := strings.Split(path, "/") | ||
116 | hostport := parts[0] | ||
117 | |||
118 | if len(hostport) == 0 { | ||
119 | http.Redirect(w, req, "/", http.StatusFound) | ||
120 | return | ||
121 | } | ||
122 | |||
123 | title := hostport | ||
124 | |||
125 | var qs string | ||
126 | |||
127 | if req.URL.RawQuery != "" { | ||
128 | qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) | ||
129 | } | ||
130 | |||
131 | uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) | ||
132 | if err != nil { | ||
133 | if e := tpl.Execute(w, TemplateVariables{ | ||
134 | Title: title, | ||
135 | URI: hostport, | ||
136 | Assets: assetList, | ||
137 | RawText: fmt.Sprintf("Error: %s", err), | ||
138 | Error: true, | ||
139 | Protocol: "gopher", | ||
140 | }); e != nil { | ||
141 | log.Println("Template error: " + e.Error()) | ||
142 | log.Println(err.Error()) | ||
143 | } | ||
144 | return | ||
145 | } | ||
146 | |||
147 | if uri != "" { | ||
148 | title = fmt.Sprintf("%s/%s", hostport, uri) | ||
149 | } | ||
150 | |||
151 | res, err := libgopher.Get( | ||
152 | fmt.Sprintf( | ||
153 | "gopher://%s/%s%s", | ||
154 | hostport, | ||
155 | uri, | ||
156 | qs, | ||
157 | ), | ||
158 | ) | ||
159 | |||
160 | if err != nil { | ||
161 | if e := tpl.Execute(w, TemplateVariables{ | ||
162 | Title: title, | ||
163 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
164 | Assets: assetList, | ||
165 | RawText: fmt.Sprintf("Error: %s", err), | ||
166 | Error: true, | ||
167 | Protocol: "gopher", | ||
168 | }); e != nil { | ||
169 | log.Println("Template error: " + e.Error()) | ||
170 | } | ||
171 | return | ||
172 | } | ||
173 | |||
174 | if res.Body != nil { | ||
175 | if len(parts) < 2 { | ||
176 | io.Copy(w, res.Body) | ||
177 | } else if strings.HasPrefix(parts[1], "0") && !strings.HasSuffix(uri, ".xml") && !strings.HasSuffix(uri, ".asc") { | ||
178 | buf := new(bytes.Buffer) | ||
179 | buf.ReadFrom(res.Body) | ||
180 | |||
181 | if err := tpl.Execute(w, TemplateVariables{ | ||
182 | Title: title, | ||
183 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
184 | Assets: assetList, | ||
185 | RawText: buf.String(), | ||
186 | Protocol: "gopher", | ||
187 | }); err != nil { | ||
188 | log.Println("Template error: " + err.Error()) | ||
189 | } | ||
190 | } else if strings.HasPrefix(parts[1], "T") { | ||
191 | _, _, err = vips.NewTransform(). | ||
192 | Load(res.Body). | ||
193 | ResizeStrategy(vips.ResizeStrategyAuto). | ||
194 | ResizeWidth(160). | ||
195 | Quality(75). | ||
196 | Output(w). | ||
197 | Apply() | ||
198 | } else { | ||
199 | io.Copy(w, res.Body) | ||
200 | } | ||
201 | } else { | ||
202 | if err := renderGopherDirectory(w, tpl, assetList, uri, hostport, res.Dir); err != nil { | ||
203 | if e := tpl.Execute(w, TemplateVariables{ | ||
204 | Title: title, | ||
205 | URI: fmt.Sprintf("%s/%s", hostport, uri), | ||
206 | Assets: assetList, | ||
207 | RawText: fmt.Sprintf("Error: %s", err), | ||
208 | Error: true, | ||
209 | Protocol: "gopher", | ||
210 | }); e != nil { | ||
211 | log.Println("Template error: " + e.Error()) | ||
212 | log.Println(e.Error()) | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | } | ||
217 | } | ||
diff --git a/internal/port/main.go b/internal/port/main.go new file mode 100644 index 0000000..5cdd794 --- /dev/null +++ b/internal/port/main.go | |||
@@ -0,0 +1,301 @@ | |||
1 | package port | ||
2 | |||
3 | import ( | ||
4 | "crypto/md5" | ||
5 | "fmt" | ||
6 | "html" | ||
7 | "html/template" | ||
8 | "io/ioutil" | ||
9 | "log" | ||
10 | "net/http" | ||
11 | "regexp" | ||
12 | "strings" | ||
13 | |||
14 | "github.com/NYTimes/gziphandler" | ||
15 | "github.com/davidbyttow/govips/pkg/vips" | ||
16 | "github.com/gobuffalo/packr/v2" | ||
17 | "github.com/temoto/robotstxt" | ||
18 | ) | ||
19 | |||
20 | type AssetList struct { | ||
21 | Style string | ||
22 | JS string | ||
23 | FontW string | ||
24 | FontW2 string | ||
25 | PropFontW string | ||
26 | PropFontW2 string | ||
27 | } | ||
28 | |||
29 | type TemplateVariables struct { | ||
30 | Title string | ||
31 | URI string | ||
32 | Assets AssetList | ||
33 | RawText string | ||
34 | Lines []Item | ||
35 | Error bool | ||
36 | Protocol string | ||
37 | } | ||
38 | |||
39 | func DefaultHandler(tpl *template.Template, startpagetext string, assetList AssetList) http.HandlerFunc { | ||
40 | return func(w http.ResponseWriter, req *http.Request) { | ||
41 | if err := tpl.Execute(w, TemplateVariables{ | ||
42 | Title: "Gopher/Gemini proxy", | ||
43 | Assets: assetList, | ||
44 | RawText: startpagetext, | ||
45 | Protocol: "startpage", | ||
46 | }); err != nil { | ||
47 | log.Println("Template error: " + err.Error()) | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | |||
52 | // RobotsTxtHandler returns the contents of the robots.txt file | ||
53 | // if configured and valid. | ||
54 | func RobotsTxtHandler(robotstxtdata []byte) http.HandlerFunc { | ||
55 | return func(w http.ResponseWriter, req *http.Request) { | ||
56 | if robotstxtdata == nil { | ||
57 | http.Error(w, "Not Found", http.StatusNotFound) | ||
58 | return | ||
59 | } | ||
60 | |||
61 | w.Header().Set("Content-Type", "text/plain") | ||
62 | w.Write(robotstxtdata) | ||
63 | } | ||
64 | } | ||
65 | |||
66 | func FaviconHandler(favicondata []byte) http.HandlerFunc { | ||
67 | return func(w http.ResponseWriter, req *http.Request) { | ||
68 | if favicondata == nil { | ||
69 | http.Error(w, "Not Found", http.StatusNotFound) | ||
70 | return | ||
71 | } | ||
72 | |||
73 | w.Header().Set("Content-Type", "image/vnd.microsoft.icon") | ||
74 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
75 | w.Write(favicondata) | ||
76 | } | ||
77 | } | ||
78 | |||
79 | func StyleHandler(styledata []byte) http.HandlerFunc { | ||
80 | return func(w http.ResponseWriter, req *http.Request) { | ||
81 | w.Header().Set("Content-Type", "text/css") | ||
82 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
83 | w.Write(styledata) | ||
84 | } | ||
85 | } | ||
86 | |||
87 | func JavaScriptHandler(jsdata []byte) http.HandlerFunc { | ||
88 | return func(w http.ResponseWriter, req *http.Request) { | ||
89 | w.Header().Set("Content-Type", "text/javascript") | ||
90 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
91 | w.Write(jsdata) | ||
92 | } | ||
93 | } | ||
94 | |||
95 | func FontHandler(woff2 bool, fontdata []byte) http.HandlerFunc { | ||
96 | return func(w http.ResponseWriter, req *http.Request) { | ||
97 | if fontdata == nil { | ||
98 | http.Error(w, "Not Found", http.StatusNotFound) | ||
99 | return | ||
100 | } | ||
101 | |||
102 | if woff2 { | ||
103 | w.Header().Set("Content-Type", "font/woff2") | ||
104 | } else { | ||
105 | w.Header().Set("Content-Type", "font/woff") | ||
106 | } | ||
107 | w.Header().Set("Cache-Control", "max-age=2592000") | ||
108 | |||
109 | w.Write(fontdata) | ||
110 | } | ||
111 | } | ||
112 | |||
113 | // ListenAndServe creates a listening HTTP server bound to | ||
114 | // the interface specified by bind and sets up a Gopher to HTTP | ||
115 | // proxy proxying requests as requested and by default will prozy | ||
116 | // to a Gopher server address specified by uri if no servers is | ||
117 | // specified by the request. The robots argument is a pointer to | ||
118 | // a robotstxt.RobotsData struct for testing user agents against | ||
119 | // a configurable robots.txt file. | ||
120 | func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug bool, vipsconcurrency int) error { | ||
121 | box := packr.New("assets", "../assets") | ||
122 | |||
123 | // | ||
124 | // Robots | ||
125 | |||
126 | var robotsdata *robotstxt.RobotsData | ||
127 | |||
128 | robotstxtdata, err := ioutil.ReadFile(robotsfile) | ||
129 | if err != nil { | ||
130 | log.Printf("error reading robots.txt: %s", err) | ||
131 | robotstxtdata = nil | ||
132 | } else { | ||
133 | robotsdata, err = robotstxt.FromBytes(robotstxtdata) | ||
134 | if err != nil { | ||
135 | log.Printf("error reading robots.txt: %s", err) | ||
136 | robotstxtdata = nil | ||
137 | } | ||
138 | } | ||
139 | |||
140 | // | ||
141 | // Fonts | ||
142 | |||
143 | fontdataw, err := box.Find("iosevka-term-ss03-regular.woff") | ||
144 | if err != nil { | ||
145 | fontdataw = []byte{} | ||
146 | } | ||
147 | fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw)) | ||
148 | |||
149 | fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") | ||
150 | if err != nil { | ||
151 | fontdataw2 = []byte{} | ||
152 | } | ||
153 | fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2)) | ||
154 | |||
155 | propfontdataw, err := box.Find("iosevka-aile-regular.woff") | ||
156 | if err != nil { | ||
157 | propfontdataw = []byte{} | ||
158 | } | ||
159 | propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw)) | ||
160 | |||
161 | propfontdataw2, err := box.Find("iosevka-aile-regular.woff2") | ||
162 | if err != nil { | ||
163 | propfontdataw2 = []byte{} | ||
164 | } | ||
165 | propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2)) | ||
166 | |||
167 | // | ||
168 | // Stylesheet | ||
169 | |||
170 | styledata, err := box.Find("style.css") | ||
171 | if err != nil { | ||
172 | styledata = []byte{} | ||
173 | } | ||
174 | styleAsset := fmt.Sprintf("/style-%x.css", md5.Sum(styledata)) | ||
175 | |||
176 | // | ||
177 | // JavaScript | ||
178 | |||
179 | jsdata, err := box.Find("main.js") | ||
180 | if err != nil { | ||
181 | jsdata = []byte{} | ||
182 | } | ||
183 | jsAsset := fmt.Sprintf("/main-%x.js", md5.Sum(jsdata)) | ||
184 | |||
185 | // | ||
186 | // Favicon | ||
187 | |||
188 | favicondata, err := box.Find("favicon.ico") | ||
189 | if err != nil { | ||
190 | favicondata = []byte{} | ||
191 | } | ||
192 | |||
193 | // | ||
194 | // Start page text | ||
195 | |||
196 | startpagedata, err := ioutil.ReadFile(startpagefile) | ||
197 | if err != nil { | ||
198 | startpagedata, err = box.Find("startpage.txt") | ||
199 | if err != nil { | ||
200 | startpagedata = []byte{} | ||
201 | } | ||
202 | } | ||
203 | startpagetext := string(startpagedata) | ||
204 | |||
205 | // | ||
206 | // | ||
207 | |||
208 | var allFiles []string | ||
209 | files, err := ioutil.ReadDir("./tpl") | ||
210 | if err != nil { | ||
211 | fmt.Println(err) | ||
212 | } | ||
213 | for _, file := range files { | ||
214 | filename := file.Name() | ||
215 | if strings.HasSuffix(filename, ".html") { | ||
216 | allFiles = append(allFiles, "./tpl/"+filename) | ||
217 | } | ||
218 | } | ||
219 | |||
220 | templates, err = template.ParseFiles(allFiles...) | ||
221 | |||
222 | // | ||
223 | |||
224 | funcMap := template.FuncMap{ | ||
225 | "safeHtml": func(s string) template.HTML { | ||
226 | return template.HTML(s) | ||
227 | }, | ||
228 | "safeCss": func(s string) template.CSS { | ||
229 | return template.CSS(s) | ||
230 | }, | ||
231 | "safeJs": func(s string) template.JS { | ||
232 | return template.JS(s) | ||
233 | }, | ||
234 | "HTMLEscape": func(s string) string { | ||
235 | return html.EscapeString(s) | ||
236 | }, | ||
237 | "split": strings.Split, | ||
238 | "last": func(s []string) string { | ||
239 | return s[len(s)-1] | ||
240 | }, | ||
241 | "pop": func(s []string) []string { | ||
242 | return s[:len(s)-1] | ||
243 | }, | ||
244 | "replace": func(pattern, output string, input interface{}) string { | ||
245 | var re = regexp.MustCompile(pattern) | ||
246 | var inputStr = fmt.Sprintf("%v", input) | ||
247 | return re.ReplaceAllString(inputStr, output) | ||
248 | }, | ||
249 | "trimLeftChar": func(s string) string { | ||
250 | for i := range s { | ||
251 | if i > 0 { | ||
252 | return s[i:] | ||
253 | } | ||
254 | } | ||
255 | return s[:0] | ||
256 | }, | ||
257 | "hasPrefix": func(s string, prefix string) bool { | ||
258 | return strings.HasPrefix(s, prefix) | ||
259 | }, | ||
260 | "title": func(s string) string { | ||
261 | return strings.Title(s) | ||
262 | }, | ||
263 | } | ||
264 | |||
265 | // | ||
266 | |||
267 | startpageTpl := templates.Lookup("startpage.html").Funcs(funcMap) | ||
268 | geminiTpl := templates.Lookup("gemini.html").Funcs(funcMap) | ||
269 | gopherTpl := templates.Lookup("gopher.html").Funcs(funcMap) | ||
270 | |||
271 | // | ||
272 | // | ||
273 | |||
274 | vips.Startup(&vips.Config{ | ||
275 | ConcurrencyLevel: vipsconcurrency, | ||
276 | }) | ||
277 | |||
278 | assets := AssetList{ | ||
279 | Style: styleAsset, | ||
280 | JS: jsAsset, | ||
281 | FontW: fontwAsset, | ||
282 | FontW2: fontw2Asset, | ||
283 | PropFontW: propfontwAsset, | ||
284 | PropFontW2: propfontw2Asset, | ||
285 | } | ||
286 | |||
287 | http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets))) | ||
288 | http.Handle("/gopher/", gziphandler.GzipHandler(GopherHandler(gopherTpl, robotsdata, assets, robotsdebug))) | ||
289 | http.Handle("/gemini/", gziphandler.GzipHandler(GeminiHandler(geminiTpl, robotsdata, assets, robotsdebug))) | ||
290 | http.Handle("/robots.txt", gziphandler.GzipHandler(RobotsTxtHandler(robotstxtdata))) | ||
291 | http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) | ||
292 | http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) | ||
293 | http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata))) | ||
294 | http.HandleFunc(fontwAsset, FontHandler(false, fontdataw)) | ||
295 | http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2)) | ||
296 | http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw)) | ||
297 | http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2)) | ||
298 | //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) | ||
299 | |||
300 | return http.ListenAndServe(bind, nil) | ||
301 | } | ||
diff --git a/internal/port/tpl/gemini.html b/internal/port/tpl/gemini.html new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/internal/port/tpl/gemini.html | |||
diff --git a/internal/port/tpl/gopher.html b/internal/port/tpl/gopher.html new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/internal/port/tpl/gopher.html | |||
diff --git a/gopherproxy/template.go b/internal/port/tpl/startpage.html index 6b90cc0..8482a6f 100644 --- a/gopherproxy/template.go +++ b/internal/port/tpl/startpage.html | |||
@@ -1,6 +1,4 @@ | |||
1 | package gopherproxy | 1 | <!doctype html> |
2 | |||
3 | var tpltext = `<!doctype html> | ||
4 | <html> | 2 | <html> |
5 | <head> | 3 | <head> |
6 | <meta charset="utf-8"> | 4 | <meta charset="utf-8"> |
@@ -119,4 +117,4 @@ var tpltext = `<!doctype html> | |||
119 | </aside> | 117 | </aside> |
120 | <script src="{{ .Assets.JS }}"></script> | 118 | <script src="{{ .Assets.JS }}"></script> |
121 | </body> | 119 | </body> |
122 | </html>` | 120 | </html> |
diff --git a/gopherproxy/libgemini/libgemini.go b/pkg/libgemini/libgemini.go index 303490c..303490c 100644 --- a/gopherproxy/libgemini/libgemini.go +++ b/pkg/libgemini/libgemini.go | |||
diff --git a/gopherproxy/libgopher/libgopher.go b/pkg/libgopher/libgopher.go index 86d58ff..86d58ff 100644 --- a/gopherproxy/libgopher/libgopher.go +++ b/pkg/libgopher/libgopher.go | |||