aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFeuerfuchs <git@feuerfuchs.dev>2020-05-18 12:12:43 +0200
committerFeuerfuchs <git@feuerfuchs.dev>2020-05-18 12:12:43 +0200
commit4bf44b16562335b3d09b6df0150521bb5b5f776f (patch)
tree576723e6dc9f9db48d0892f7ec354a11b973aef4
parentAdded 2 more glyphs (diff)
downloadgopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.tar.gz
gopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.tar.bz2
gopherproxy-4bf44b16562335b3d09b6df0150521bb5b5f776f.zip
WIP: Refactoring
-rw-r--r--.gitignore2
-rw-r--r--Dockerfile10
-rw-r--r--Makefile14
-rw-r--r--README.md8
-rw-r--r--assets/iosevka-aile-regular.woffbin13588 -> 187604 bytes
-rw-r--r--assets/iosevka-aile-regular.woff2bin11036 -> 130736 bytes
-rw-r--r--assets/iosevka-term-ss03-regular.woffbin13864 -> 155916 bytes
-rw-r--r--assets/iosevka-term-ss03-regular.woff2bin11384 -> 111644 bytes
-rw-r--r--assets/startpage.txt6
-rw-r--r--cmd/port/main.go (renamed from main.go)4
-rw-r--r--fonts/glyphs.txt2
-rw-r--r--go.mod13
-rw-r--r--go.sum217
-rwxr-xr-xgopherproxy.binbin0 -> 14744336 bytes
-rw-r--r--gopherproxy/gopherproxy.go705
-rw-r--r--internal/port/gemini.go205
-rw-r--r--internal/port/gopher.go217
-rw-r--r--internal/port/main.go301
-rw-r--r--internal/port/tpl/gemini.html0
-rw-r--r--internal/port/tpl/gopher.html0
-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
diff --git a/.gitignore b/.gitignore
index fe92af3..f02946e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,6 @@ dist
3*.bak 3*.bak
4coverage.txt 4coverage.txt
5 5
6gopherproxy.bin 6port.bin
7 7
8.vscode 8.vscode
diff --git a/Dockerfile b/Dockerfile
index 8072728..5db4ab0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,16 +2,16 @@ FROM golang:alpine
2 2
3EXPOSE 80/tcp 3EXPOSE 80/tcp
4 4
5ENTRYPOINT ["gopherproxy"] 5ENTRYPOINT ["port"]
6 6
7RUN \ 7RUN \
8 apk add --update git && \ 8 apk add --update git && \
9 rm -rf /var/cache/apk/* 9 rm -rf /var/cache/apk/*
10 10
11RUN mkdir -p /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy 11RUN mkdir -p /go/src/git.vulpes.one/Feuerfuchs/port
12WORKDIR /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy 12WORKDIR /go/src/git.vulpes.one/Feuerfuchs/port
13 13
14COPY . /go/src/git.feuerfuchs.dev/Feuerfuchs/gopherproxy 14COPY . /go/src/git.vulpes.one/Feuerfuchs/port
15 15
16RUN go get -v -d 16RUN go get -v -d
17RUN go install -v git.feuerfuchs.dev/Feuerfuchs/gopherproxy/... 17RUN go install -v git.vulpes.one/Feuerfuchs/port/...
diff --git a/Makefile b/Makefile
index 75a8f2e..8513d69 100644
--- a/Makefile
+++ b/Makefile
@@ -3,16 +3,16 @@
3all: dev 3all: dev
4 4
5dev: build 5dev: build
6 ./gopherproxy.bin -bind 127.0.0.1:8000 6 ./port.bin -bind 127.0.0.1:8000
7 7
8build: clean 8build: 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
17profile: 17profile:
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
26clean: 26clean:
27 @git clean -f -d -X 27 #@git clean -f -d -X
diff --git a/README.md b/README.md
index e4f5d97..40ed18a 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
1# Gopher (RFC 1436) Web Proxy 1# Gopher (RFC 1436) Web Proxy
2 2
3gopherproxy 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. 3port 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
5gopherproxy is a fork of [https://github.com/prologic/gopherproxy](https://github.com/prologic/gopherproxy). 5port is a fork of [https://github.com/prologic/port](https://github.com/prologic/port).
6 6
7Demo: https://proxy.vulpes.one/ 7Demo: 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
28Arguments: 28Arguments:
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
6GETTING STARTED -- 6GETTING 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
10var ( 10var (
@@ -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{|}~äöüÄÖÜßẞ↓↙←↖↑↗→↘€»«„“”·…°’‾█▓▒░ ▀▄‐╭╮─│╰╯┌┐└┘├╱╲╳ʻ‘
diff --git a/go.mod b/go.mod
index 6cde9a1..f6759d3 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,12 @@
1module git.feuerfuchs.dev/Feuerfuchs/gopherproxy 1module git.vulpes.one/Feuerfuchs/port
2 2
3require ( 3require (
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
13go 1.13 12go 1.13
diff --git a/go.sum b/go.sum
index 7f255e0..171b051 100644
--- a/go.sum
+++ b/go.sum
@@ -1,92 +1,309 @@
1cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
1github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= 3github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
2github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 4github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
5github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
6github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
7github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
8github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
9github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
10github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
11github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
12github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
13github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
14github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
15github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
16github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
17github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
18github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
19github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
20github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
3github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea h1:ZtETbJTO1R3qVLdVbpjrDhD5fR8bYVhhq2RMi7rOlH4= 24github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea h1:ZtETbJTO1R3qVLdVbpjrDhD5fR8bYVhhq2RMi7rOlH4=
7github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk= 25github.com/davidbyttow/govips v0.0.0-20190304175058-d272f04c0fea/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk=
26github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a h1:g1X1c43wACmPtvFo+szBy/0coLkRwVJ0+i2f6YJITto=
27github.com/davidbyttow/govips v0.0.0-20200412130214-cbefdd8c639a/go.mod h1:a3qO525EPfJNYa0NXBcNtXzJvyQsJAxphEDa7OOHPBk=
28github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
29github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
30github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
31github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
32github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
33github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
34github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
35github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
36github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
8github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= 37github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
38github.com/gobuffalo/attrs v1.0.0 h1:aAK8D55L0GNVbVRlaCwvAGE2BcGdEoVoK86vgQpuUZ8=
39github.com/gobuffalo/attrs v1.0.0/go.mod h1:YWU+sjOr7V05rNzvCoVc5qQTC2IgEz9434TFW5GhNaw=
9github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= 40github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
41github.com/gobuffalo/depgen v0.2.0 h1:CYuqsR8sq+L9G9+A6uUcTEuaK8AGenAjtYOm238fN3M=
42github.com/gobuffalo/depgen v0.2.0/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
10github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ= 43github.com/gobuffalo/envy v1.6.15 h1:OsV5vOpHYUpP7ZLS6sem1y40/lNX1BZj+ynMiRi21lQ=
11github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 44github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
12github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= 45github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
13github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= 46github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
47github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
48github.com/gobuffalo/envy v1.8.1 h1:RUr68liRvs0TS1D5qdW3mQv2SjAsu1QWMCx1tG4kDjs=
49github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
14github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= 50github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
15github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= 51github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
52github.com/gobuffalo/flect v0.2.0 h1:EWCvMGGxOjsgwlWaP+f4+Hh6yrrte7JeFL2S6b+0hdM=
53github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80=
16github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= 54github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
17github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e h1:JbHBQOMhE0wmpSuejnSkdnL2rULqQTwEGgVe85o7+No= 55github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e h1:JbHBQOMhE0wmpSuejnSkdnL2rULqQTwEGgVe85o7+No=
18github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= 56github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
57github.com/gobuffalo/genny v0.6.0 h1:d7c6d66ZrTHHty01hDX1/TcTWvAJQxRZl885KWX5kHY=
58github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA=
19github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= 59github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
60github.com/gobuffalo/github_flavored_markdown v1.1.0/go.mod h1:TSpTKWcRTI0+v7W3x8dkSKMLJSUpuVitlptCkpeY8ic=
20github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA= 61github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA=
21github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= 62github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
63github.com/gobuffalo/gogen v0.2.0 h1:Xx7NCe+/y++eII2aWAFZ09/81MhDCsZwvMzIFJoQRnU=
64github.com/gobuffalo/gogen v0.2.0/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
65github.com/gobuffalo/helpers v0.5.0/go.mod h1:stpgxJ2C7T99NLyAxGUnYMM2zAtBk5NKQR0SIbd05j4=
66github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
22github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= 67github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs=
23github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= 68github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
69github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
70github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc=
71github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM=
24github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 72github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
25github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= 73github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk=
26github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 74github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
75github.com/gobuffalo/mapi v1.2.1 h1:TyfbtQaW7GvS4DXdF1KQOSGrW6L0uiFmGDz+JgEIMbM=
76github.com/gobuffalo/mapi v1.2.1/go.mod h1:giGJ2AUESRepOFYAzWpq8Gf/s/QDryxoEHisQtFN3cY=
27github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI= 77github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI=
28github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= 78github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
79github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
80github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM=
81github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
29github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY= 82github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY=
30github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U= 83github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U=
31github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= 84github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
32github.com/gobuffalo/packr/v2 v2.1.0 h1:nWGTgGtZrR4yBQvmAKF4AthraObjRMzx6lJa9e+JbLQ= 85github.com/gobuffalo/packr/v2 v2.1.0 h1:nWGTgGtZrR4yBQvmAKF4AthraObjRMzx6lJa9e+JbLQ=
33github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= 86github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM=
87github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc=
88github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY=
89github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g=
90github.com/gobuffalo/plush v3.8.3+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI=
34github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= 91github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4=
35github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= 92github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
93github.com/gobuffalo/syncx v0.1.0 h1://CNTQ/+VFQizkW24DrBtTBvj8c2+chz5Y7kbboQ2qk=
94github.com/gobuffalo/syncx v0.1.0/go.mod h1:Mg/s+5pv7IgxEp6sA+NFpqS4o2x+R9dQNwbwT0iuOGQ=
95github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY=
96github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE=
97github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
98github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
99github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
100github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
101github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
102github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
103github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
104github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
105github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
106github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
107github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
108github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
109github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
110github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
111github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
112github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
113github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
36github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 114github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
37github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 115github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
38github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 116github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
117github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
118github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
39github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA= 119github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA=
40github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= 120github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
121github.com/karrick/godirwalk v1.15.3 h1:0a2pXOgtB16CqIqXTiT7+K9L73f74n/aNQUnH6Ortew=
122github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
123github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
124github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
41github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 125github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
126github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
127github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
128github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
42github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 129github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
43github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 130github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
44github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 131github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
132github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
133github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI=
134github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
45github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= 135github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k=
46github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 136github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
137github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY=
138github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI=
47github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= 139github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
48github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 140github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
141github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
142github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
143github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
144github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
145github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
49github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 146github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
50github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 147github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
148github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
149github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
150github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
151github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
152github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
51github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
52github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 154github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
53github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 155github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
156github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
157github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
54github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 158github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
55github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 159github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
56github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0 h1:10LO/S8HVjIuEHsHea//Cena1Ztgy23f/e8HFC0w5ow= 160github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0 h1:10LO/S8HVjIuEHsHea//Cena1Ztgy23f/e8HFC0w5ow=
57github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0/go.mod h1:LiuwIXz4es4YIUOD6yRv8mES9n9dFbe4z0+TcrLkhXg= 161github.com/prologic/go-gopher v0.0.0-20181230133552-0c68ed5f58b0/go.mod h1:LiuwIXz4es4YIUOD6yRv8mES9n9dFbe4z0+TcrLkhXg=
162github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
163github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
164github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
165github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
166github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
167github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
168github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
169github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
170github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
171github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
58github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 172github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
59github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 173github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
60github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= 174github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
61github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 175github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
176github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
177github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
178github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w=
179github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
180github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
181github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
182github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
183github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
184github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
185github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
62github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 186github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
63github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= 187github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
64github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 188github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
189github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
190github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
191github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
192github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
193github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
194github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
195github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
196github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
65github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 197github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
198github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
199github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
200github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
66github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 201github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
202github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
203github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
67github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 204github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
68github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
69github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 206github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
70github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 207github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
71github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 208github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
209github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
210github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
72github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea h1:hH8P1IiDpzRU6ZDbDh/RDnVuezi2oOXJpApa06M0zyI= 211github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea h1:hH8P1IiDpzRU6ZDbDh/RDnVuezi2oOXJpApa06M0zyI=
73github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea/go.mod h1:aOux3gHPCftJ3KHq6Pz/AlDjYJ7Y+yKfm1gU/3B0u04= 212github.com/temoto/robotstxt v0.0.0-20180810133444-97ee4a9ee6ea/go.mod h1:aOux3gHPCftJ3KHq6Pz/AlDjYJ7Y+yKfm1gU/3B0u04=
213github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA=
214github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo=
215github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
216github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
217github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
218github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
219github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
220go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
221go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
222go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
223go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
74golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 224golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
225golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
75golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= 226golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
76golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 227golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
228golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
229golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
230golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
231golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
232golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
233golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
234golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
235golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
236golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
237golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
238golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
239golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
77golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis= 240golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
78golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 241golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
79golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 242golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
80golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
244golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
245golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
246golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
247golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
248golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
249golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY=
250golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
251golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
252golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
253golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
254golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
81golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 255golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
82golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 256golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
257golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
258golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
259golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
83golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 260golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
261golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
262golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
263golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
264golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
84golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 265golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
85golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 266golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
267golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
268golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
269golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
270golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
271golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
86golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 272golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
87golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 273golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
274golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
275golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
276golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
277golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
278golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
279golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
280golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
88golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 281golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
89golang.org/x/tools v0.0.0-20190404132500-923d25813098 h1:MtqjsZmyGRgMmLUgxnmMJ6RYdvd2ib8ipiayHhqSxs4= 282golang.org/x/tools v0.0.0-20190404132500-923d25813098 h1:MtqjsZmyGRgMmLUgxnmMJ6RYdvd2ib8ipiayHhqSxs4=
90golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 283golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
284golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
285golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
286golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
287golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE=
288golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
289golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
290golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
291golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
292golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
293google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
294google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
295google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
296google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
297gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
298gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
91gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 299gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
92gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 300gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
301gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
302gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
303gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
304gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
305gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
306gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
307gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
308gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
309honnef.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 @@
1package gopherproxy
2
3import (
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
32var (
33 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m")
34)
35
36const (
37 ITEM_TYPE_GEMINI_LINE = ""
38 ITEM_TYPE_GEMINI_LINK = " =>"
39)
40
41type Item struct {
42 Link template.URL
43 Type string
44 Text string
45}
46
47type AssetList struct {
48 Style string
49 JS string
50 FontW string
51 FontW2 string
52 PropFontW string
53 PropFontW2 string
54}
55
56type 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
66func 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
95func 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
170func 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
207func 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.
225func 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
338func 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.
491func 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
503func 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
516func 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
524func 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
532func 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.
557func 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 @@
1package port
2
3import (
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
23var (
24 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m")
25)
26
27func 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
56func 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 @@
1package port
2
3import (
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
20type Item struct {
21 Link template.URL
22 Type string
23 Text string
24}
25
26func 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.
106func 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 @@
1package port
2
3import (
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
20type AssetList struct {
21 Style string
22 JS string
23 FontW string
24 FontW2 string
25 PropFontW string
26 PropFontW2 string
27}
28
29type 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
39func 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.
54func 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
66func 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
79func 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
87func 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
95func 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.
120func 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 @@
1package gopherproxy 1<!doctype html>
2
3var 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