Merge branch '2019-save-changes'

This commit is contained in:
Xuu 2020-10-30 11:09:44 -06:00
commit e27626d087
Signed by: xuu
GPG Key ID: 8B3B0604F164E04F
44 changed files with 32288 additions and 8871 deletions

49
.drone.yml Normal file
View File

@ -0,0 +1,49 @@
---
kind: pipeline
type: exec
name: default
platform:
os: linux
arch: amd64
steps:
- name: Setup
commands:
- git fetch origin --tags
- make setup
- name: Test
commands:
- make test
trigger:
event:
- push
- pull_request
---
kind: pipeline
type: exec
name: deploy
platform:
os: linux
arch: amd64
steps:
- name: Setup
commands:
- git fetch origin --tags
- make setup
- name: Build Release
commands:
- cd debian
- make tag
- make build
- make copy
- make deploy
trigger:
event:
- promote
...

2
.gitignore vendored
View File

@ -1,5 +1,3 @@
/paste /paste
/vendor /vendor
/public /public
/src/routes/bindata.go
/src/docs/bindata.go

4
.gitmodules vendored
View File

@ -1,4 +0,0 @@
[submodule "assets/src/vendor/sour-is"]
path = assets/src/vendor/sour-is
url = git@git.dn42.us:xuu/souris-react.git
branch = master

150
Gopkg.lock generated
View File

@ -1,150 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/cgilling/dbstats"
packages = ["."]
revision = "c9db8cf218e67da0c12f60591e9ac438d1573a85"
[[projects]]
name = "github.com/docopt/docopt.go"
packages = ["."]
revision = "784ddc588536785e7299f7272f39101f7faccc3f"
version = "0.6.2"
[[projects]]
name = "github.com/elazarl/go-bindata-assetfs"
packages = ["."]
revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43"
version = "v1.0.0"
[[projects]]
name = "github.com/fsnotify/fsnotify"
packages = ["."]
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
name = "github.com/gorilla/mux"
packages = ["."]
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
branch = "master"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token"
]
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
[[projects]]
name = "github.com/magiconair/properties"
packages = ["."]
revision = "c2353362d570a7bfa228149c62842019201cfb71"
version = "v1.8.0"
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
[[projects]]
name = "github.com/pelletier/go-toml"
packages = ["."]
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
name = "github.com/spf13/afero"
packages = [
".",
"mem"
]
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
version = "v1.1.1"
[[projects]]
name = "github.com/spf13/cast"
packages = ["."]
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
name = "github.com/spf13/viper"
packages = ["."]
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
version = "v1.0.2"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "e072cadbbdc8dd3d3ffa82b8b4b9304c261d9311"
[[projects]]
name = "golang.org/x/text"
packages = [
"internal/gen",
"internal/triegen",
"internal/ucd",
"transform",
"unicode/cldr",
"unicode/norm"
]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[[projects]]
branch = "master"
name = "sour.is/x/toolbox"
packages = [
"httpsrv",
"ident",
"log",
"stats",
"uuid"
]
revision = "c14dcd727823155806c6953e1186f8bb3505afc3"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "992f6e13bd909fa436a615718637dac016f185163cb35a6611b9c842c23ba756"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,54 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/docopt/docopt.go"
version = "0.6.2"
[[constraint]]
name = "github.com/elazarl/go-bindata-assetfs"
version = "1.0.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.1"
[[constraint]]
name = "github.com/spf13/viper"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "golang.org/x/sys"
[[constraint]]
branch = "master"
name = "sour.is/x/toolbox"
[prune]
go-tests = true
unused-packages = true

View File

@ -1,13 +1,15 @@
ROUTE_ASSET=src/routes/bindata.go ROUTE_ASSET=src/routes/bindata.go
ROUTE_FILES=$(wildcard assets/src/*) $(wildcard assets/public/*)
DOCS_ASSET=src/docs/bindata.go DOCS_ASSET=src/docs/bindata.go
DOCS_FILES=$(wildcard src/docs/ui/*)
VENDOR_FILES=$(shell find vendor -type f -name '*.go')
SOURCE=$(wildcard cmd/paste/*.go) $(filter-out src/routes/bindata.go, $(wildcard src/routes/*.go)) SOURCE=$(wildcard cmd/paste/*.go) $(filter-out src/routes/bindata.go, $(wildcard src/routes/*.go))
BINARY=paste BINARY=paste
PKG=./cmd/paste
VERSION=$(shell git describe --tags `git rev-list --tags --max-count=1`|cut -b2-)
VERSION_PAT=$(shell debian/inc_version.sh -p $(VERSION))
VERSION_MIN=$(shell debian/inc_version.sh -m $(VERSION))
VERSION_MAJ=$(shell debian/inc_version.sh -M $(VERSION))
DATE:=$(shell date -u +%FT%TZ)
define DUMMY_BINDATA define DUMMY_BINDATA
package docs package docs
@ -17,47 +19,43 @@ endef
export DUMMY_BINDATA export DUMMY_BINDATA
all: $(BINARY) all: $(BINARY)
clean: clean:
rm -rf $(BINARY) $(ROUTE_ASSET) $(DOCS_ASSET) rm -rf $(BINARY)
setup: setup:
dep ensure go mod download
go get github.com/jteeuwen/go-bindata/...
go get github.com/elazarl/go-bindata-assetfs/...
git submodule update
cd assets; npm i
ensure:
dep ensure -update
dep status
fmt: fmt:
go fmt ./... go fmt ./...
test: $(ROUTE_ASSET) $(DOCS_ASSET)
run: $(BINARY)
./$(BINARY) -vv serve
$(BINARY): $(SOURCE) $(ROUTE_ASSET) $(VENDOR_FILES) $(DOCS_ASSET)
go vet ./...
go test ./... go test ./...
go build "sour.is/x/paste/cmd/paste" go vet ./...
run:
go run \
-ldflags "-X main.AppVersion=$(VERSION_PAT) -X main.AppBuild=$(DATE)" \
$(PKG) -vv serve
$(ROUTE_ASSET): $(ROUTE_FILES) $(BINARY): $(SOURCE) $(ROUTE_ASSET) $(DOCS_ASSET)
go build -v \
-ldflags "-X main.AppVersion=$(VERSION) -X main.AppBuild=$(DATE)" \
"sour.is/x/paste/cmd/paste"
clean-ui:
rm -rf $(ROUTE_ASSET) $(DOCS_ASSET)
build-ui: clean-ui $(ROUTE_ASSET) $(DOCS_ASSET)
$(ROUTE_ASSET):
cd assets; \ cd assets; \
rm -rf build ../public; \ rm -rf build ../public; \
npm run build; \ npm i; npm run build; \
cp -r build ../public cp -r build ../public
go generate "sour.is/x/paste/src/routes"
export PATH=$$GOPATH/bin:$$PATH; go generate "sour.is/x/paste/src/routes" $(DOCS_ASSET):
$(DOCS_ASSET): $(DOCS_FILES) $(SOURCE)
echo "$$DUMMY_BINDATA" > src/docs/bindata.go echo "$$DUMMY_BINDATA" > src/docs/bindata.go
export PATH=$$GOPATH/bin:$$PATH; go generate "sour.is/x/paste/cmd/paste" go generate "sour.is/x/paste/cmd/paste"
export PATH=$$GOPATH/bin:$$PATH; go generate "sour.is/x/paste/src/docs" go generate "sour.is/x/paste/src/docs"
deploy: $(SOURCE) $(ROUTE_ASSET) deploy: $(SOURCE) $(ROUTE_ASSET)
cd debian && make cd debian && make
.PHONEY: all clean deploy run setup .PHONEY: all clean build deploy run setup
# DO NOT DELETE

34701
assets/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,35 @@
"name": "paste", "name": "paste",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "https://paste.dn42.us/ui",
"dependencies": { "dependencies": {
"react": "^16.2.0", "bootstrap": "^4.5.2",
"react-dom": "^16.2.0", "bootstrap-4-react": "0.0.59",
"react-scripts": "1.1.1", "crypto-js": "^4.0.0",
"sour-is": "file:src/vendor/sour-is" "react": "^16.9.0",
"react-dom": "^16.9.0",
"react-highlight": "^0.12.0",
"react-markdown": "^4.3.1",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.1.1",
"seedrandom": "^3.0.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --env=jsdom", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject", "eject": "react-scripts eject"
"postinstall": "npm install src/vendor/sour-is" },
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
} }
} }

View File

@ -8,7 +8,7 @@
"type": "image/x-icon" "type": "image/x-icon"
} }
], ],
"start_url": "./index.html", "start_url": "/ui/index.html",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#000000" "background_color": "#000000"

View File

@ -28,7 +28,7 @@ HASH=$((echo -e "exp:\t$(date +%s -d ${PASTE_DATE})"; \
[ "$PASTE_BURN" -eq "1" ] && echo -e "burn:\ttrue"; \ [ "$PASTE_BURN" -eq "1" ] && echo -e "burn:\ttrue"; \
[ "$PASTE_GZIP" -eq "1" ] && echo -e "zip:\ttrue"; \ [ "$PASTE_GZIP" -eq "1" ] && echo -e "zip:\ttrue"; \
echo; \ echo; \
cat /dev/stdin | $GZBIN | openssl aes-256-cbc -e -a -k $PASS) | \ cat /dev/stdin | $GZBIN | openssl aes-256-cbc -md md5 -e -a -k $PASS) | \
curl -s -X POST ${PASTE_URL}/paste --data-binary @-) curl -s -X POST ${PASTE_URL}/paste --data-binary @-)
HASH_OK=$(echo $HASH | cut -c1-2) HASH_OK=$(echo $HASH | cut -c1-2)
@ -37,7 +37,7 @@ if [ "$HASH_OK" = "OK" ]; then
HASH=$(echo $HASH | cut -f2 -d' ') HASH=$(echo $HASH | cut -f2 -d' ')
echo "url: ${PASTE_URL}/#/${HASH}!${PASS}" echo "url: ${PASTE_URL}/#/${HASH}!${PASS}"
echo -n "shell: curl -s ${PASTE_URL}/api/get/${HASH} | sed '1,/^\$/d' | openssl aes-256-cbc -d -a -k ${PASS}" echo -n "shell: curl -s ${PASTE_URL}/api/get/${HASH} | sed '1,/^\$/d' | openssl aes-256-cbc -md md5 -d -a -k ${PASS}"
[ "$PASTE_GZIP" -eq "1" ] && echo " | gzip -dc" || echo; [ "$PASTE_GZIP" -eq "1" ] && echo " | gzip -dc" || echo;
exit exit
fi fi

View File

@ -1,11 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom'; import { BrowserRouter, Route, Switch } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.css'; import './paste/Framework.css';
import 'sour-is/src/sour-is/Framework.css';
import Paste from './vendor/sour-is/src/paste/Paste'; import Paste from './paste/Paste';
import { remoteService } from "./vendor/sour-is/src/sour-is/RemoteService"; import { remoteService } from "./paste/RemoteService";
const req = remoteService(); const req = remoteService();
const APP_NAME = "DN42 Paste UI"; const APP_NAME = "DN42 Paste UI";
@ -21,7 +20,7 @@ class App extends Component {
} }
componentDidMount() { componentDidMount() {
req('/app-info').get() req('https://paste.dn42.us/app-info').get()
.then((response) => { .then((response) => {
if (response.ok) return response.text().catch(()=>""); if (response.ok) return response.text().catch(()=>"");
else return "Unknown API"; }) else return "Unknown API"; })
@ -31,11 +30,11 @@ componentDidMount() {
render() { render() {
const {api, name} = this.state; const {api, name} = this.state;
return ( return (
<BrowserRouter> <BrowserRouter basename={'/ui'}>
<React.Fragment key={1}> <React.Fragment key={1}>
<section className="container-responsive"> <section className="container-responsive">
<Switch> <Switch>
<Route path="/" component={Paste} /> <Route path={`/`} component={Paste} />
</Switch> </Switch>
</section> </section>

View File

@ -0,0 +1,70 @@
body,html {color: black !important; background-color: #272b30 !important; }
header + section { padding-top: 52px; padding-bottom: 2.1em }
footer { background-color: #3e444c; position:fixed; bottom:0; width: 100%; height: 2em; border-top: 1px solid #bbb; padding: 4px }
footer span.left { float: left; }
footer span.right { float: right; }
pre { color: black}
.breadcrumb label { color: black; }
.sidebar .list-group {
font-size: 75%;
}
.sidebar .list-group-item {
padding: 2px;
background:#111;
color: #62c462;
border:0;
font-family: Hack, monospace
}
.main {
font-family: Hack, monospace;
}
.sidebar li.list-group-item.active,.sidebar a.list-group-item.active {
color: #111;
background-color: #62c462;
}
@media (min-width: 768px) {
.hide-sm { display: none !important; }
}
@media (min-width: 768px) {
.sidebar {
position:fixed;
top: 52px;
bottom: 30px;
left: 0;
width: 24vw;
overflow: auto;
}
.main {
position: fixed;
top: 52px;
bottom: 30px;
left: 25vw;
right: 0;
overflow: auto;
}
}
@media (min-width: 992px) {
.sidebar {
position:fixed;
top: 52px;
bottom: 30px;
left: 0;
width: 24vw;
overflow: auto;
}
.main {
position: fixed;
top: 52px;
bottom: 30px;
left: 25vw;
right: 0;
overflow: auto;
}
}

411
assets/src/paste/Paste.js Normal file
View File

@ -0,0 +1,411 @@
import React, { Component } from "react";
import { Form, Button, Alert } from "bootstrap-4-react";
import "./paste.css";
import "highlight.js/styles/github.css";
import Highlight from "react-highlight";
import Markdown from 'react-markdown';
import pako from "pako";
import seedrandom from "seedrandom";
import { remoteService } from "./RemoteService"
import RIPEMD160 from "crypto-js/ripemd160";
import SHA256 from "crypto-js/sha256";
import AES from "crypto-js/aes";
import BASE64 from "crypto-js/enc-base64";
import HEX from "crypto-js/enc-hex";
import UTF8 from "crypto-js/enc-utf8"
const rng = seedrandom();
const req = remoteService();
const b64 = (s) => (!!s?BASE64.stringify(s).replace(/[=]+/, '').replace(/\//g, '_').replace(/\+/g, '-'):'');
const sha = (s) => b64(SHA256(s));
const chk = (s) => b64(RIPEMD160(SHA256(s)));
const enc = (t, p) => AES.encrypt(t, p).toString();
const u16a = (ua) => {
let s = '';
for (let i = 0; i < ua.length; i++) {
s += ('0' + ua[i].toString(16)).slice(-2);
}
return HEX.parse(s);
};
const u8a = (wa) => {
const w = wa.words;
let b = new Uint8Array(w.length * 4), v, i, j, k = 0;
for (i = 0; i < w.length; ++i) {
v = w[i];
for (j = 3; j >= 0; --j) {
b[k++] = ((v >> 8 * j) & 0xFF);
}
}
return b;
};
const str8 = (ua) => {
let s = '';
for (let i = 0; i < ua.byteLength; i++) {
if ((ua[i]&0x80) === 0) s += String.fromCharCode(ua[i]);
else if ((ua[i]&0xe0) === 0xc0 && (ua[i+1]&0xc0) === 0x80) {
s += String.fromCharCode(((ua[i]&0x1f)<<6) + (ua[i+1]&0x3f));
i += 1;
}
else if ((ua[i]&0xf0) === 0xe0 && (ua[i+1]&0xc0) === 0x80 && (ua[i+2]&0xc0) === 0x80){
s += String.fromCharCode(((ua[i]&0x0f)<<12) + ((ua[i+1]&0x3f)<<6) + (ua[i+2]&0x3f));
i += 2;
}
else if ((ua[i]&0xf8) === 0xf0 && (ua[i+1]&0xc0) === 0x80 && (ua[i+2]&0xc0) === 0x80 && (ua[i+3]&0xc0) === 0x80) {
s += String.fromCharCode(((ua[i]&0x0f)<<18) + ((ua[i+1]&0x3f)<<12) + ((ua[i+2]&0x3f)<<6) + (ua[i+3]&0x3f));
i += 3;
}
else { s += String.fromCharCode(65533); }
}
return s;
};
const zip = (text) => u16a(pako.gzip(text));
const dec = (c, p, z) => {
if (z) {
let tx = AES.decrypt(c, p);
tx = u8a(tx);
return str8(pako.inflate(tx));
} else {
return AES.decrypt(c, p).toString(UTF8);
}
}
const blength = (o) => {
if (!(typeof o === 'string' || o instanceof String)) return 0;
if (o === undefined || o.length === undefined) return 0;
var utf8length = 0;
for (var n = 0; n < o.length; n++) {
var c = o.charCodeAt(n);
if (c < 128) {
utf8length++;
}
else if ((c > 127) && (c < 2048)) {
utf8length = utf8length + 2;
} else {
utf8length = utf8length + 3;
}
}
return utf8length;
};
const PASTE_API = "https://paste.dn42.us/paste"
const syntaxItems = [
["text", "Plain Text"],
["markdown", "Markdown"],
["apache", "Apache"],
["bash", "Bash"],
["coffeescript", "CoffeeScript"],
["cpp", "C++"],
["cs", "C#"],
["css", "CSS"],
["diff", "Diff"],
["http", "HTTP"],
["ini", "Ini"],
["java", "Java"],
["javascript", "JavaScript"],
["json", "JSON"],
["makefile", "Makefile"],
["nginx", "Nginx"],
["objectivec", "Objective C"],
["perl", "Perl"],
["php", "PHP"],
["python", "Python"],
["ruby", "Ruby"],
["sql", "SQL"],
["xml", "HTML, XML"]
];
const expireItems = [
[3600, "1 Hour"],
[86400, "1 Day"],
[604800, "1 Week"],
[2419200, "4 Weeks"],
[15778463, "6 Months"],
[31556926, "1 Year"]
];
class Paste extends Component {
constructor(props) {
super(props);
const { location } = props;
const [ hash, key ] = location.hash.substring(2).split("!");
this.state = {
error: "",
syntax: "text",
expire: 604800,
expires: "",
burn: false,
plain: "",
cipher: "",
decryptKey: hash !== "new" ? key : "",
hash: hash !== "new" ? hash : "",
entropy: 0,
gzip: false,
syntaxItems,
expireItems
};
this.startEntropy = this.startEntropy.bind(this);
this.addEntropy = this.addEntropy.bind(this);
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.onNew = this.onNew.bind(this);
this.onCopy = this.onCopy.bind(this);
this.encrypt = this.encrypt.bind(this);
this.decrypt = this.decrypt.bind(this);
}
startEntropy(events, count) {
let t = [];
let fn = (e) => {
t.push([e.pageX, e.pageY, e.keyCode, +new Date()]);
if (t.length < count) {
return;
}
this.addEntropy(t);
t = [];
};
fn = fn.bind(this);
for (let i in events) {
if (events.hasOwnProperty(i))
document.addEventListener(events[i], fn);
}
};
addEntropy(s) {
this.setState(function(state, props) {
return {entropy: state.entropy + s.length};
} );
seedrandom(s, {entropy: true});
};
onChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
onSubmit(event) {
event.preventDefault();
const { plain } = this.state;
const { history } = this.props;
this.encrypt(plain).then(([hash, decryptKey]) => { history.push('/#/' + hash + '!' + decryptKey) });
}
onNew(event) {
const { history } = this.props;
this.setState({cipher:"", plain:"", hash: "", decryptKey: "", syntax: "text", expire: 604800, burn: false}, () => history.push('/'));
}
onCopy(event) {
const { history } = this.props;
this.setState({hash: "", decryptKey: ""}, () => history.push('/'))
}
decrypt(tx) {
if (tx === "") {
this.setState({hash: "", decryptKey: "", error: "Unable to retrieve paste."})
return;
}
let s = tx.split('\n');
let i = 0;
let header = {};
while (true) {
if (s[i] === "") break;
var l = s[i].trim().split(':\t');
header[l[0]] = l[1];
i++;
}
const cipher = s.splice(i).join('');
const zip = !!header.zip && header.zip === "true";
const { decryptKey } = this.state;
const plain = dec(cipher, decryptKey, zip);
const expires = !!header.exp ? (!!header.burn ? "Burn on Read" : ((d) => d.toLocaleDateString() + " " + d.toLocaleTimeString())(new Date(header.exp*1000))) : "Never";
this.setState({cipher: tx, plain: plain, expires, syntax: header.lang});
}
encrypt(input) {
const rnd = rng(40);
const decryptKey = sha(rnd);
const gzip = blength(input)>4000;
const plain = gzip?zip(input):input;
const { syntax, expire, burn } = this.state;
const header = {
chk: chk(rnd),
lang: syntax,
exp: parseInt(expire, 10) + (Date.now() / 1000 | 0),
zip: gzip,
burn: burn
}
let s = '', e = enc(plain, decryptKey);
while (e.length > 79) {
s += e.slice(0, 79) + "\n";
e = e.slice(79);
}
s += e + "\n";
const expires = !!header.exp ? (!!header.burn ? "Burn on Read" : ((d) => d.toLocaleDateString() + " " + d.toLocaleTimeString())(new Date(header.exp*1000))) : "Never";
const cipher = Object.entries(header).map(([k, v]) => !!v ? k + ":\t" + v + "\n" : "").join('') + "\n" + s;
return req(PASTE_API).post({}, cipher)
.then((r) => r.text())
.then((d) => {
console.log("Received:\n" + d);
const [ok='', hash=''] = d.split(' ', 2);
if (ok === "OK")
this.setState((state) => ({cipher, gzip, decryptKey, hash, expires}));
else console.log(d);
return [hash, decryptKey];
});
}
componentDidMount() {
this.startEntropy(['mousemove', 'keydown', 'keypress', 'click', 'scroll'], 16);
req(`${PASTE_API}/rng`).get().then((res)=>res.text()).then(this.addEntropy).catch();
const { hash } = this.state;
if (hash !== "")
req(`${PASTE_API}/${hash}`).get().then((res)=> res.ok ? res.text() : "").then(this.decrypt).catch();
}
componentWillReceiveProps(nextProps) {
const { location } = nextProps;
const { hash:nextHash } = location;
const [ hash='', key='' ] = nextHash.substring(2).split("!");
if (hash === this.state.hash) return;
if (hash === '') this.setState({cipher:"", plain:"", hash: "", decryptKey: "", syntax: "text", expire: 604800, burn: false})
else {
this.setState({hash: hash, decryptKey: key});
req(`${PASTE_API}/${hash}`).get().then((res)=> res.ok ? res.text() : "").then(this.decrypt).catch();
}
}
render() {
const { hash } = this.state;
return hash === '' ? <PasteCreate {...this.state} onChange={this.onChange} onSubmit={this.onSubmit}/> : <PasteView {...this.state} onNew={this.onNew} onCopy={this.onCopy}/> ;
}
}
function PasteCreate({error, onSubmit, onChange, syntax, syntaxItems, expire, expireItems, burn, entropy, plain}) {
return (
<section className="container">
<div>
{!!error &&
<Alert warning>
<strong>Holy guacamole!</strong> {error}
</Alert>}
<Form name='paste' onSubmit={onSubmit}>
<div className="form-inline">
<label className="my-1 mr-2" for="syntax">Syntax</label>
<select className='custom-select my-1 mr-sm-2' id="syntax" name="syntax" onChange={onChange} value={syntax}>
{syntaxItems.map((o) => <option key={o[0]} value={o[0]}>{o[1]}</option>)}
</select>
<label className="my-1 mr-2" for="expire">Expires</label>
<select className='custom-select my-1 mr-sm-2' id="expire" name="expire" onChange={onChange} value={expire}>
{expireItems.map((o) => <option key={o[0]} value={o[0]}>{o[1]}</option>)}
</select>
<div className="custom-control custom-checkbox my-1 mr-sm-2">
<input className="custom-control-input" type='checkbox' id="burn-on-read" name="burn" onChange={onChange} value={burn}/>
<label className="custom-control-label" for="burn-on-read">Burn on Read</label>
</div>
</div>
<textarea required className='form-control' rows='20' name="plain" onChange={onChange} value={plain}></textarea>
<pre className="grey">Additional Entropy: {entropy} bytes / Content size: {blength(plain)} bytes</pre>
<Button type='submit' className='btn btn-default btn-lg btn-block'>Encrypt</Button>
</Form>
<br/>
<div className="card">
<div className="card-body">
<p>Create pastes from the command line! <a href={`${window.location.origin}/ui/paste.sh`} download>paste.sh</a></p>
<pre>{`$ echo /etc/passwd | ./paste.sh
env options:
PASTE_URL - Set the url base for paste operations (default: HTTPS://paste.dn42.us)
PASTE_GZIP - 0 = No Compression, 1 = Use gzip compression (default: 0)
PASTE_BURN - 0 = No Burn on Read, 1 = Burn on read (default: 0)
PASTE_DATE - Value to be used when setting expire date. (default: next-week)`}
</pre>
</div>
</div>
</div>
</section>
);
}
function PasteView({hash, decryptKey, expires, burn, gzip, cipher, plain, syntax, onNew, onCopy}) {
const gzipOpts = gzip ? '| gzip -dc' : '';
return (
<section className="container">
<div className="input-group">
<span className="input-group-prepend">
<button className="btn btn-default" type="button" onClick={onNew}>New</button>
</span>
<input type='text' readOnly className='form-control' value={`${window.location.origin}/ui/#/${hash}!${decryptKey}`} onClick={(e) => e.target.select()}/>
<span className="input-group-append">
<button className="btn btn-default" type="button" onClick={onCopy}>Copy</button>
</span>
</div>
<br/>
<div className="card">
<div className='card-header'>
<b>Lang:</b> {syntax}
&nbsp;
<b>Expires:</b> {expires}
&nbsp;
{burn && (<b> BURN ON READ </b>)}
</div>
<div className="card-body">
{syntax==="markdown" ? (
<Markdown source={plain} />
) : (
<Highlight className={syntax}>{plain}</Highlight>
)}
</div>
<div className="card-footer">
<pre>{`# Command Line:
curl -s "${PASTE_API}/${hash}" \\
| sed "1,/^\\$/d" \\
| openssl aes-256-cbc -md md5 \\
-d -a -k "${decryptKey}" ${gzipOpts}
${cipher}`}</pre>
</div>
</div>
</section>
);
}
export default Paste;

View File

@ -0,0 +1,88 @@
const uuid = () =>
([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
((c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & 15)) >> c / 4).toString(16))
let COUNTER = 0, s
if (localStorage.getItem("session-id") === null) {
s = [uuid(), 0]
} else {
s = localStorage.getItem("session-id").split(":");
s[1] = (parseInt(s[1], 16) + 1).toString(36)
}
const SESSION = s.join(":");
localStorage.setItem("session-id", SESSION);
export function remoteService() {
return function(url, fn, config) {
if (config === undefined) config = {};
if (config.headers === undefined) config.headers = {};
const execute = function (fn, method, url, query, payload, headers) {
if (query === undefined) query = {};
for (var i in query) if (query.hasOwnProperty(i)) {
var needle = ':'+i;
if (url.search(needle)>0) {
url = url.replace(needle, query[i]);
delete(query[i]);
}
}
config.method = method;
if (payload !== undefined)
if (typeof payload === 'string' || payload instanceof String)
config.body = payload;
else config.body = JSON.stringify(payload);
if (fn !== undefined) config = fn(config);
config.headers["session-id"] = SESSION+":"+(COUNTER++).toString(36)
return fetch(url, config);
};
return {
get: function (query, headers) { return execute(fn, 'GET', url, query, undefined, headers); },
put: function (query, payload, headers) { return execute(fn, 'PUT', url, query, payload, headers); },
post: function (query, payload, headers) { return execute(fn, 'POST', url, query, payload, headers); },
patch: function (query, payload, headers) { return execute(fn, 'PATCH', url, query, payload, headers); },
del: function (query, headers) { return execute(fn, 'DELETE', url, query, undefined, headers); },
'delete': function (query, headers) { return execute(fn, 'DELETE', url, query, undefined, headers); },
'remove': function (query, headers) { return execute(fn, 'DELETE', url, query, undefined, headers); },
};
};
}
export function sessionRemoteService() {
const req = remoteService();
return function(url) {
var fn = function(config) {
if (localStorage.getItem("session") !== null)
config.headers['authorization'] = "session " + localStorage.getItem("session");
return config;
};
return req(url, fn);
};
}
export function sourisRemoteService() {
const req = remoteService();
return function(url, aspect) {
if (aspect === undefined) aspect = 'default';
var fn = function(config) {
if (localStorage.getItem('souris_token') !== undefined)
config.headers['authorization'] = "souris " + aspect + " " + localStorage.getItem('souris_token');
return config;
};
return req(url, fn);
};
}
export default remoteService;

105
assets/src/paste/paste.css Normal file
View File

@ -0,0 +1,105 @@
section.container { margin-bottom: 2em; }
textarea { font-family: "Fira Code",hack,"Anonymous Pro",monospace; }
pre { font-family: "Fira Code",hack,"Anonymous Pro",monospace; }
code { font-family: "Fira Code",hack,"Anonymous Pro",monospace; }
pre.grey { background-color: lightgrey; }
label { color: lightgrey; }
table a:link {
color: #666;
font-weight: bold;
text-decoration:none;
}
table a:visited {
color: #999999;
font-weight:bold;
text-decoration:none;
}
table a:active,
table a:hover {
color: #bd5a35;
text-decoration:underline;
}
table {
font-family:Arial, Helvetica, sans-serif;
color:#666;
font-size:12px;
text-shadow: 1px 1px 0px #fff;
background:#eaebec;
margin:20px;
border:#ccc 1px solid;
-moz-border-radius:3px;
-webkit-border-radius:3px;
border-radius:3px;
-moz-box-shadow: 0 1px 2px #d1d1d1;
-webkit-box-shadow: 0 1px 2px #d1d1d1;
box-shadow: 0 1px 2px #d1d1d1;
}
table th {
padding:21px 25px 22px 25px;
border-top:1px solid #fafafa;
border-bottom:1px solid #e0e0e0;
background: #ededed;
background: -webkit-gradient(linear, left top, left bottom, from(#ededed), to(#ebebeb));
background: -moz-linear-gradient(top, #ededed, #ebebeb);
}
table th:first-child {
text-align: left;
padding-left:20px;
}
table tr:first-child th:first-child {
-moz-border-radius-topleft:3px;
-webkit-border-top-left-radius:3px;
border-top-left-radius:3px;
}
table tr:first-child th:last-child {
-moz-border-radius-topright:3px;
-webkit-border-top-right-radius:3px;
border-top-right-radius:3px;
}
table tr {
text-align: center;
padding-left:20px;
}
table td:first-child {
text-align: left;
padding-left:20px;
border-left: 0;
}
table td {
padding:18px;
border-top: 1px solid #ffffff;
border-bottom:1px solid #e0e0e0;
border-left: 1px solid #e0e0e0;
background: #fafafa;
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#fafafa));
background: -moz-linear-gradient(top, #fbfbfb, #fafafa);
}
table tr.even td {
background: #f6f6f6;
background: -webkit-gradient(linear, left top, left bottom, from(#f8f8f8), to(#f6f6f6));
background: -moz-linear-gradient(top, #f8f8f8, #f6f6f6);
}
table tr:last-child td {
border-bottom:0;
}
table tr:last-child td:first-child {
-moz-border-radius-bottomleft:3px;
-webkit-border-bottom-left-radius:3px;
border-bottom-left-radius:3px;
}
table tr:last-child td:last-child {
-moz-border-radius-bottomright:3px;
-webkit-border-bottom-right-radius:3px;
border-bottom-right-radius:3px;
}
table tr:hover td {
background: #f2f2f2;
background: -webkit-gradient(linear, left top, left bottom, from(#f2f2f2), to(#f0f0f0));
background: -moz-linear-gradient(top, #f2f2f2, #f0f0f0);
}

@ -1 +0,0 @@
Subproject commit ba700a53ba68931451537ce5423c9c2823a39848

View File

@ -2,10 +2,11 @@ package main
import ( import (
"bytes" "bytes"
"github.com/docopt/docopt.go" "github.com/docopt/docopt.go"
"github.com/spf13/viper" "github.com/spf13/viper"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log" "sour.is/x/toolbox/log"
"sour.is/x/toolbox/log/event"
_ "sour.is/x/toolbox/stats" _ "sour.is/x/toolbox/stats"
) )
@ -42,10 +43,26 @@ Config:
var defaultConfig = ` var defaultConfig = `
[http] [http]
listen = ":9010" listen = ":9010"
base_url = "https//paste.dn42.us"
cors = [
"http://localhost",
"https://sour.is",
]
[module.paste] [module.paste]
random = "4096" random = "4096"
store = "data/" store = "data/paste"
[module.artifact]
store = "data/artifact"
[module.image]
store = "data/image"
[module.short]
index = "data/meta"
value = "data/meta"
` `
var args map[string]interface{} var args map[string]interface{}
@ -63,17 +80,17 @@ func init() {
viper.Set("app.build", AppBuild) viper.Set("app.build", AppBuild)
} }
if args, err = docopt.Parse(AppUsage, nil, true, AppVersion, false); err != nil { if args, err = docopt.ParseDoc(AppUsage); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if args["-v"].(int) == 1 { if args["-v"].(int) == 1 {
log.SetVerbose(log.Vinfo) log.SetVerbose(event.VerbInfo)
log.Info("Verbose Logging.")
} }
if args["-v"].(int) == 2 { if args["-v"].(int) == 2 {
log.SetVerbose(log.Vdebug) log.SetVerbose(event.VerbDebug)
log.SetFlags(log.LstdFlags | log.Lshortfile) log.Debug("Very Verbose Logging.")
log.Debug("Debug Logging.")
} }
log.Printf("%s (%s %s)\n", log.Printf("%s (%s %s)\n",
@ -92,7 +109,7 @@ func init() {
viper.AddConfigPath(".") viper.AddConfigPath(".")
viper.SetConfigType("toml") viper.SetConfigType("toml")
viper.ReadConfig(bytes.NewBuffer([]byte(defaultConfig))) _ = viper.ReadConfig(bytes.NewBuffer([]byte(defaultConfig)))
err = viper.MergeInConfig() err = viper.MergeInConfig()
if err != nil { // Handle errors reading the config file if err != nil { // Handle errors reading the config file
@ -104,8 +121,5 @@ func init() {
if args["--listen"] != nil { if args["--listen"] != nil {
viper.Set("http.listen", args["--listen"].(string)) viper.Set("http.listen", args["--listen"].(string))
} }
httpsrv.Config()
} }
} }

35
cmd/paste/cors.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"net/http"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/ident"
)
func init() {
httpsrv.NewMiddleware("cors", doCORS).
Register(httpsrv.EventPreAuth)
}
func doCORS(_ string, w httpsrv.ResponseWriter, r *http.Request, _ ident.Ident) bool {
origin := r.Header.Get("origin")
headers := r.Header.Get("access-control-request-headers")
if origin != "" {
w.Header().Add("access-control-allow-origin", origin)
}
w.Header().Add("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Add("access-control-allow-credentials", "true")
w.Header().Add("access-control-max-age", "3600")
if headers != "" {
w.Header().Add("access-control-allow-headers", headers)
}
if r.Method == "OPTIONS" {
return false
}
return true
}

View File

@ -29,11 +29,12 @@ package main // import "sour.is/x/paste/cmd/paste"
import ( import (
"os" "os"
"os/signal" "os/signal"
"syscall"
_ "sour.is/x/paste/src/docs" _ "sour.is/x/paste/src/docs"
_ "sour.is/x/paste/src/routes" _ "sour.is/x/paste/src/routes"
"sour.is/x/toolbox/httpsrv" "sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log" "sour.is/x/toolbox/log"
"syscall"
) )
func main() { func main() {
@ -51,4 +52,4 @@ func main() {
} }
} }
//go:generate swagger generate spec -o ../../src/docs/public/swagger.json //go:generate go run github.com/go-swagger/go-swagger/cmd/swagger generate spec -o ../../src/docs/public/swagger.json

View File

@ -0,0 +1,2 @@
[http]
listen = ":9015"

46
debian/Makefile vendored
View File

@ -1,45 +1,39 @@
NAME=sour.is-paste NAME=sour.is-paste
VERSION=$(shell cat VERSION) VERSION:=$(shell ./inc_version.sh -p $(shell git describe --tags `git rev-list --tags --max-count=1`)|cut -b2-)
RELEASE=$(shell cat RELEASE) DATE:=$(shell date -u +%FT%TZ)
REPO_HOST="kapha" REPO_HOST="xuu@kapha"
ANSIBLE_HOST="kapha"
REPO_PATH="/opt/web/pub/sour.is/debian/" REPO_PATH="/opt/web/pub/sour.is/debian/"
all: release build copy all: build copy tag
clean: clean:
rm -r BUILD/* rm -r BUILD/*
release: tag:
export RELEASE=`cat RELEASE`; \ git tag -a -m "release version $(VERSION)" "v$(VERSION)"
echo `expr $${RELEASE} + 1` > RELEASE git push --tags
build: build:
export VERSION=`cat VERSION`; \ export BUILD="BUILD/$(NAME)_$(VERSION)"; \
export RELEASE=`cat RELEASE`; \
export DATE=`date -u +%FT%TZ`; \
export BUILD="BUILD/$(NAME)_$${VERSION}-$${RELEASE}"; \
rm -rf ./$${BUILD}; \ rm -rf ./$${BUILD}; \
cp -r ROOT "$${BUILD}"; \ cp -r ROOT "$${BUILD}"; \
sed -i'.tmp' "s_Version:.*_Version: $${VERSION}-$${RELEASE}_" "$${BUILD}/DEBIAN/control" sed -i'.tmp' "s_Version:.*_Version: $(VERSION)_" "$${BUILD}/DEBIAN/control"
export VERSION=`cat VERSION`; \ export DATE=`date -u +%FT%TZ`; \
export RELEASE=`cat RELEASE`; \ export BUILD="BUILD/$(NAME)_$(VERSION)"; \
export DATE=`date -u +%FT%TZ`; \ env GOOS=linux GOARCH=amd64 go build -v -o $${BUILD}/opt/sour.is/bin/paste \
export BUILD="BUILD/$(NAME)_$${VERSION}-$${RELEASE}"; \ -ldflags "-X main.AppVersion=$(VERSION) -X main.AppBuild=$(DATE)"\
env GOOS=linux GOARCH=amd64 go build -v -o $${BUILD}/opt/sour.is/bin/paste \ sour.is/x/paste/cmd/paste && \
-ldflags "-X main.AppVersion=$${VERSION}-$${RELEASE} -X main.AppBuild=$${DATE}"\ dpkg -b $${BUILD}
sour.is/x/paste/cmd/paste; \
dpkg -b $${BUILD};
copy: copy:
export VERSION=`cat VERSION`; \ export BUILD="BUILD/$(NAME)_$(VERSION)"; \
export RELEASE=`cat RELEASE`; \
export BUILD="BUILD/$(NAME)_$${VERSION}-$${RELEASE}"; \
scp "$${BUILD}.deb" $(REPO_HOST):$(REPO_PATH); \ scp "$${BUILD}.deb" $(REPO_HOST):$(REPO_PATH); \
ssh $(REPO_HOST) -- $(REPO_PATH)scan.sh "$(REPO_PATH)$(NAME)_$${VERSION}-$${RELEASE}.deb"; ssh $(REPO_HOST) -- $(REPO_PATH)scan.sh "$(REPO_PATH)$(NAME)_$(VERSION).deb";
deploy: deploy:
ansible $(REPO_HOST) -b -m apt -a "name=sour.is-paste update_cache=yes state=latest" ansible $(ANSIBLE_HOST) -u xuu -b -m apt -a "name=sour.is-paste update_cache=yes state=latest"
.PHONY: clean release build copy # deploy .PHONY: clean tag build copy deploy

1
debian/RELEASE vendored
View File

@ -1 +0,0 @@
13

View File

@ -7,7 +7,7 @@ case "$1" in
if systemctl is-enabled sour.is-paste.service; then if systemctl is-enabled sour.is-paste.service; then
systemctl start sour.is-paste.service systemctl start sour.is-paste.service
fi fi
sudo chown -R root:root /opt/sour.is sudo chown -R root:root /opt/sour.is
sudo chown -R root:root /etc/opt/sour.is sudo chown -R root:root /etc/opt/sour.is

1
debian/VERSION vendored
View File

@ -1 +0,0 @@
2.2.0

52
debian/inc_version.sh vendored Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# Increment a version string using Semantic Versioning (SemVer) terminology.
# Parse command line options.
while getopts ":Mmp" Option
do
case $Option in
M ) major=true;;
m ) minor=true;;
p ) patch=true;;
esac
done
shift $(($OPTIND - 1))
version=$1
# Build array from version string.
a=( ${version//./ } )
# If version string is missing or has the wrong number of members, show usage message.
if [ ${#a[@]} -ne 3 ]
then
>&2 echo "usage: $(basename $0) [-Mmp] major.minor.patch"
exit 1
fi
# Increment version numbers as requested.
if [ ! -z $major ]
then
((a[0]++))
a[1]=0
a[2]=0
fi
if [ ! -z $minor ]
then
((a[1]++))
a[2]=0
fi
if [ ! -z $patch ]
then
((a[2]++))
fi
echo "${a[0]}.${a[1]}.${a[2]}"

32
go.mod Normal file
View File

@ -0,0 +1,32 @@
module sour.is/x/paste
go 1.14
require (
github.com/99designs/gqlgen v0.12.2
github.com/andybalholm/brotli v1.0.0
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-swagger/go-swagger v0.25.0
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167
github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.0
github.com/hashicorp/golang-lru v0.5.1
github.com/lucasb-eyer/go-colorful v1.0.3
github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96
github.com/sour-is/crypto v0.0.0-20201016232853-f42a24ba5a81
github.com/sour-is/go-assetfs v1.0.0
github.com/spf13/viper v1.7.1
github.com/timshannon/badgerhold v1.0.0
github.com/tv42/zbase32 v0.0.0-20190604154422-aacc64a8f915
github.com/vektah/dataloaden v0.3.0
go.uber.org/ratelimit v0.1.0
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
golang.org/x/net v0.0.0-20201029055024-942e2f445f3c // indirect
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418
sour.is/x/toolbox v0.12.17
)

793
go.sum Normal file
View File

@ -0,0 +1,793 @@
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.10.1/go.mod h1:IviubpnyI4gbBcj8IcxSSc/Q/+af5riwCmJmwF0uaPE=
github.com/99designs/gqlgen v0.12.2 h1:aOdpsiCycFtCnAv8CAI1exnKrIDHMqtMzQoXeTziY4o=
github.com/99designs/gqlgen v0.12.2/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Masterminds/squirrel v0.0.0-20190511014652-b4b75d10d7bf/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bouk/monkey v1.0.0 h1:k6z8fLlPhETfn5l9rlWVE7Q6B23DoaqosTdArvNQRdc=
github.com/bouk/monkey v1.0.0/go.mod h1:PG/63f4XEUlVyW1ttIeOJmJhhe1+t9EC/je3eTjvFhE=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cgilling/dbstats v0.0.0-20150427045024-c9db8cf218e6/go.mod h1:fsf3+k/VvGOE9sF2B9d6PBcZOzQIlDJhn2LhBqF/4VY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.0 h1:DshxFxZWXUcO0xX476VJC07Xsr6ZCBVRHKZ93Oh7Evo=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815 h1:HMAfwOa33y82IaQEKQDfUCiwNlxtM1iw7HLM9ru0RNc=
github.com/docopt/docopt.go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:l7JNRynTRuqe45tpIyItHNqZWTxywYjp87MWTOnU5cg=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE=
github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ=
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/errors v0.19.6 h1:xZMThgv5SQ7SMbWtKFkCf9bBdvR2iEyw9k3zGZONuys=
github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls=
github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY=
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.20 h1:J/t+QIjbcoq8WJvjGxRKiFBhqUE8slS9SbmD0Oi/raQ=
github.com/go-openapi/runtime v0.19.20/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg=
github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE=
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
github.com/go-openapi/validate v0.19.10 h1:tG3SZ5DC5KF4cyt7nqLVcQXGj5A7mpaYkAcNPlDK+Yk=
github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-swagger/go-swagger v0.25.0 h1:FxhyrWWV8V/A9P6GtI5szWordAdbb6Y0nqdY/y9So2w=
github.com/go-swagger/go-swagger v0.25.0/go.mod h1:9639ioXrPX9E6BbnbaDklGXjNz7upAXoNBwL4Ok11Vk=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/gddo v0.0.0-20190815223733-287de01127ef/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 h1:5vu7C+63KTbsSNnLhrgB98Sqy8MNVSW8FdhkcWA/3Rk=
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686/go.mod h1:sam69Hju0uq+5uvLJUMDlsKlQ21Vrs1Kd/1YFPNYdOU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167 h1:LP/6EfrZ/LyCc+SXvANDrIJ4sP9u2NAtqyv6QknetNQ=
github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f h1:KMlcu9X58lhTA/KrfX8Bi1LQSO4pzoVjTiL3h4Jk+Zk=
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/h2non/filetype v1.1.0 h1:Or/gjocJrJRNK/Cri/TDEKFjAR+cfG6eK65NGYB6gBA=
github.com/h2non/filetype v1.1.0/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmoiron/sqlx v0.0.0-20150110152746-69738bd20981/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96 h1:J8J/cgLDRuqXJnwIrRDBvtl+LLsdg7De74znW/BRRq4=
github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 h1:hBSHahWMEgzwRyS6dRpxY0XyjZsHyQ61s084wo5PJe0=
github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20170602164621-9e8dc3f972df h1:AawEzDdiSpy07QO9efSOHQ/BRincGLxilju4pOq3k8s=
github.com/smartystreets/goconvey v0.0.0-20170602164621-9e8dc3f972df/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sour-is/crypto v0.0.0-20201016232853-f42a24ba5a81 h1:7LadZJfye3tq1Dr5c46uy1ign6mQr2bAOlCJeAXpB1A=
github.com/sour-is/crypto v0.0.0-20201016232853-f42a24ba5a81/go.mod h1:7/Of5cnNodFyJ6PH2C3STkdCRvqbhj9yA3BhQ/E62wA=
github.com/sour-is/go-assetfs v1.0.0 h1:84Fd12qIAdZUOKjYIgsA1J27fcQF/JiSgiflz+2hqEA=
github.com/sour-is/go-assetfs v1.0.0/go.mod h1:y4ShXMTRymi5OMvwbtfT3sxcRE72sx1ycYymT46JbRE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/timshannon/badgerhold v1.0.0 h1:LtqnDRVP7294FWRiZCIfQa6Tt0bGmlzbO8c364QC2Y8=
github.com/timshannon/badgerhold v1.0.0/go.mod h1:Vv2Jj0PAfzqViEpGvJzLP8PY07x1iXLgKRuLY7bqPOE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/tv42/zbase32 v0.0.0-20190604154422-aacc64a8f915 h1:vX9DBbEHmrebYnVthUTzMO6Zc1vvConJdD2s0uvXrfw=
github.com/tv42/zbase32 v0.0.0-20190604154422-aacc64a8f915/go.mod h1:Y5DJgF9Eou+hSWetC39Mns8E0PU7DykCLNWiYeOINrE=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yosssi/gmq v0.0.1 h1:GhlDVaAQoi3Mvjul/qJXXGfL4JBeE0GQwbWp3eIsja8=
github.com/yosssi/gmq v0.0.1/go.mod h1:mReykazh0U1JabvuWh1PEbzzJftqOQWsjr0Lwg5jL1Y=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE=
go.mongodb.org/mongo-driver v1.3.5 h1:S0ZOruh4YGHjD7JoN7mIsTrNjnQbOjrmgrx6l6pZN7I=
go.mongodb.org/mongo-driver v1.3.5/go.mod h1:Ual6Gkco7ZGQw8wE1t4tLnvBsf6yVSM60qW6TgOeJ5c=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/ratelimit v0.1.0 h1:U2AruXqeTb4Eh9sYQSTrMhH8Cb7M0Ian2ibBOnBcnAw=
go.uber.org/ratelimit v0.1.0/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201029055024-942e2f445f3c h1:rpcgRPA7OvNEOdprt2Wx8/Re2cBTd8NPo/lvo3AyMqk=
golang.org/x/net v0.0.0-20201029055024-942e2f445f3c/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw=
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6 h1:nULzSsKgihxFGLnQFv2T7lE5vIhOtg8ZPpJHapEt7o0=
golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sour.is/x/toolbox v0.12.17 h1:m+8fAl5dkuu2HsmkOuefb6tDFzpPq93xAAtZZvBxySk=
sour.is/x/toolbox v0.12.17/go.mod h1:DMM+aEl38izskLKuH+nDwEHH4FznK6dEiDcPR3wAq5s=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

477
src/docs/bindata.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,4 +8,4 @@ func init() {
}) })
} }
//go:generate go-bindata-assetfs -pkg docs public/ //go:generate go run github.com/sour-is/go-assetfs/cmd/assetfs -pkg docs public/

77
src/pkg/cache/cache.go vendored Normal file
View File

@ -0,0 +1,77 @@
package cache
import (
"time"
lru "github.com/hashicorp/golang-lru"
)
type Key interface {
Key() interface{}
}
type Value interface {
Stale() bool
Value() interface{}
}
type item struct {
key interface{}
value interface{}
expireOn time.Time
}
func NewItem(key, value interface{}, expires time.Duration) *item {
return &item{
key: key,
value: value,
expireOn: time.Now().Add(expires),
}
}
func (e *item) Stale() bool {
if e == nil || e.value == nil {
return true
}
return time.Now().After(e.expireOn)
}
func (s *item) Value() interface{} {
return s.value
}
type Cacher interface {
Add(Key, Value)
Has(Key) bool
Get(Key) (Value, bool)
Remove(Key)
}
type arcCache struct {
cache *lru.ARCCache
}
func NewARC(size int) (Cacher, error) {
arc, err := lru.NewARC(size)
if err != nil {
return nil, err
}
return &arcCache{cache: arc}, nil
}
func (c *arcCache) Add(key Key, value Value) {
c.cache.Add(key.Key(), value)
}
func (c *arcCache) Get(key Key) (Value, bool) {
if v, ok := c.cache.Get(key.Key()); ok {
if value, ok := v.(Value); ok && !value.Stale() {
return value, true
}
c.cache.Remove(key.Key())
}
return nil, false
}
func (c *arcCache) Has(key Key) bool {
_, ok := c.Get(key)
return ok
}
func (c *arcCache) Remove(key Key) {
c.cache.Remove(key.Key())
}

225
src/pkg/promise/promise.go Normal file
View File

@ -0,0 +1,225 @@
package promise
import (
"context"
"fmt"
"sync"
"time"
"go.uber.org/ratelimit"
"sour.is/x/paste/src/pkg/cache"
"sour.is/x/toolbox/log"
)
type Q interface {
Key() interface{}
Context() context.Context
Resolve(interface{})
Reject(error)
Tasker
}
type Fn func(Q)
type Key interface {
Key() interface{}
}
type qTask struct {
key Key
fn Fn
ctx context.Context
cancel func()
done chan struct{}
result interface{}
err error
Tasker
}
func (t *qTask) Key() interface{} { return t.key }
func (t *qTask) Context() context.Context { return t.ctx }
func (t *qTask) Resolve(r interface{}) { t.result = r; t.finish() }
func (t *qTask) Reject(err error) { t.err = err; t.finish() }
func (t *qTask) Await() <-chan struct{} { return t.done }
func (t *qTask) Cancel() { t.err = fmt.Errorf("task cancelled"); t.finish() }
func (t *qTask) Result() interface{} { return t.result }
func (t *qTask) Err() error { return t.err }
func (t *qTask) finish() {
if t.done == nil {
return
}
t.cancel()
close(t.done)
t.done = nil
}
type Option interface {
Apply(*qTask)
}
type OptionFn func(*qTask)
func (fn OptionFn) Apply(t *qTask) { fn(t) }
type Tasker interface {
Run(Key, Fn, ...Option) *qTask
}
type Runner struct {
defaultOpts []Option
queue map[interface{}]*qTask
mu sync.RWMutex
ctx context.Context
cancel func()
pause chan struct{}
limiter ratelimit.Limiter
}
type Timeout time.Duration
func (d Timeout) Apply(task *qTask) {
task.ctx, task.cancel = context.WithTimeout(task.ctx, time.Duration(d))
}
func (tr *Runner) Run(key Key, fn Fn, opts ...Option) *qTask {
tr.mu.RLock()
log.Infos("task to run", fmt.Sprintf("%T", key), key.Key())
if task, ok := tr.queue[key.Key()]; ok {
tr.mu.RUnlock()
log.Infos("task found running", fmt.Sprintf("%T", key), key.Key())
return task
}
tr.mu.RUnlock()
task := &qTask{
key: key,
fn: fn,
cancel: func() {},
ctx: tr.ctx,
done: make(chan struct{}),
Tasker: tr,
}
for _, opt := range tr.defaultOpts {
opt.Apply(task)
}
for _, opt := range opts {
opt.Apply(task)
}
tr.mu.Lock()
tr.queue[key.Key()] = task
tr.mu.Unlock()
log.Debug("Waiting for limiter")
tr.limiter.Take()
log.Debug("Got tag from limiter")
go func() {
defer func() {
if r := recover(); r != nil {
task.err = fmt.Errorf("PANIC: %v", r)
}
if err := task.Err(); err == nil {
log.Infos("task complete", fmt.Sprintf("%T", task.Key()), task.Key())
} else {
log.Errors("task Failed", fmt.Sprintf("%T", task.Key()), task.Key(), "err", err)
}
}()
log.Infos("task Running", fmt.Sprintf("%T", task.Key()), task.Key())
task.fn(task)
tr.mu.Lock()
delete(tr.queue, task.Key())
tr.mu.Unlock()
}()
return task
}
func NewRunner(ctx context.Context, defaultOpts ...Option) *Runner {
ctx, cancel := context.WithCancel(ctx)
tr := &Runner{
defaultOpts: defaultOpts,
queue: make(map[interface{}]*qTask),
ctx: ctx,
cancel: cancel,
pause: make(chan struct{}),
limiter: ratelimit.New(10),
}
return tr
}
func (tr *Runner) List() []*qTask {
tr.mu.RLock()
defer tr.mu.RUnlock()
lis := make([]*qTask, 0, len(tr.queue))
for _, task := range tr.queue {
lis = append(lis, task)
}
return lis
}
func (tr *Runner) Stop() {
tr.cancel()
}
func (tr *Runner) Len() int {
tr.mu.RLock()
defer tr.mu.RUnlock()
return len(tr.queue)
}
func WithCache(c cache.Cacher, expireAfter time.Duration) OptionFn {
return func(task *qTask) {
innerFn := task.fn
task.fn = func(q Q) {
cacheKey, ok := q.Key().(cache.Key)
if !ok {
log.Infos("not a cache key", fmt.Sprintf("%T", q.Key()), q.Key())
innerFn(q)
return
}
if v, ok := c.Get(cacheKey); ok {
log.Infos("value in cache", fmt.Sprintf("%T", cacheKey), cacheKey.Key())
q.Resolve(v.Value())
return
}
log.Infos("not in cache", fmt.Sprintf("%T", cacheKey), cacheKey.Key())
innerFn(q)
if err := task.Err(); err != nil {
log.Error(err)
return
}
result := cache.NewItem(cacheKey, task.Result(), expireAfter)
log.Infos("result to cache", fmt.Sprintf("%T", cacheKey), cacheKey.Key())
c.Add(cacheKey, result)
}
}
}

View File

@ -0,0 +1,43 @@
package readutil
import (
"compress/bzip2"
"compress/gzip"
"errors"
"io"
"github.com/andybalholm/brotli"
xz "github.com/remyoudompheng/go-liblzma"
"sour.is/x/toolbox/log"
)
// Decompress varius types of compression types
func Decompress(in io.Reader) (io.Reader, error) {
rdr := NewPreviewReader(in)
mime, err := ReadMIMEWithSize(rdr, "", 32768)
if err != nil {
return rdr.Drain(), err
}
r := rdr.Drain()
switch mime {
case "application/gzip":
r, err = gzip.NewReader(r)
case "application/x-bzip2":
r = bzip2.NewReader(r)
case "application/x-xz":
r, err = xz.NewReader(r)
case "application/brotli":
r = brotli.NewReader(r)
default:
return r, ErrUnsupportedType
}
log.Debugs("Decompress:", "mime", mime)
return r, err
}
var ErrUnsupportedType = errors.New("Unsupported decompression type")

View File

@ -0,0 +1,89 @@
package readutil
import (
"bytes"
"errors"
"fmt"
"io"
"sour.is/x/toolbox/log"
)
// PreviewReader allows for seeking into a stream that does not support rewind
type PreviewReader struct {
r io.Reader
buf bytes.Buffer
}
// NewPreviewReader wrapps a reader with PreviewReader
func NewPreviewReader(r io.Reader) *PreviewReader {
return &PreviewReader{r: r}
}
// ErrReaderDrained if reading from a drained PreviewReader
var ErrReaderDrained = errors.New("io.Reader has been drained")
func (pr *PreviewReader) Read(p []byte) (n int, err error) {
if pr.r == nil {
return 0, ErrReaderDrained
}
i, err := pr.r.Read(p)
log.Debugf("PreviewReader: buffer %d bytes", i)
_, berr := pr.buf.Write(p[:i])
if berr != nil {
return i, berr
}
return i, err
}
// Drain returns reader that is reset
func (pr *PreviewReader) Drain() io.Reader {
dr := &drainReader{r: pr.r, buf: &pr.buf}
pr.r = nil
return dr
}
type drainReader struct {
r io.Reader
buf *bytes.Buffer
}
var _ io.Seeker = (*drainReader)(nil)
func (dr *drainReader) Read(p []byte) (n int, err error) {
i := 0
if dr.buf.Len() > 0 {
i, err = dr.buf.Read(p)
if err != nil && err != io.EOF {
return i, err
}
if err != nil && err == io.EOF {
err = nil
}
if err != nil {
return i, err
}
}
ri, err := dr.r.Read(p[i:])
// log.Debugs("drainReader:", "drain", i, "read", ri, "cap", len(p), "err", err)
return ri + i, err
}
// Seek attempt if the underlying reader supports it.
func (dr *drainReader) Seek(offset int64, whence int) (int64, error) {
if dr.buf.Len() > 0 {
return 0, fmt.Errorf("unable to seek")
}
if r, ok := dr.r.(io.Seeker); ok {
return r.Seek(offset, whence)
}
return 0, fmt.Errorf("unable to seek")
}

File diff suppressed because it is too large Load Diff

246
src/routes/artifact.go Normal file
View File

@ -0,0 +1,246 @@
package routes
import (
"archive/tar"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gorilla/mux"
"sour.is/x/paste/src/pkg/readutil"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
func init() {
a := &Artifact{}
httpsrv.RegisterModule("artifact", a.config)
httpsrv.HttpRegister("artifact", httpsrv.HttpRoutes{
{Name: "get-path", Method: "GET", Pattern: "/a/{name}/{path:.*}", HandlerFunc: a.get},
{Name: "get", Method: "GET", Pattern: "/a/{name}", HandlerFunc: a.get},
{Name: "put", Method: "PUT", Pattern: "/a", HandlerFunc: a.put},
{Name: "get", Method: "GET", Pattern: "/a", HandlerFunc: a.list},
})
}
// Artifact stores items to disk
type Artifact struct {
store string
maxSize int64
}
func (a *Artifact) config(config map[string]string) {
a.store = "data/"
if config["store"] != "" {
a.store = config["store"]
}
if !chkStore(a.store) {
log.Criticalf("Artifact Store location [%s] does not exist or is not writable.", a.store)
}
log.Noticef("Artifact Store location set to [%s]", a.store)
a.maxSize = 50 * 1024 * 1024
if s, ok := config["max-size"]; ok {
if i, err := strconv.Atoi(s); err == nil {
a.maxSize = int64(i)
}
}
log.Noticef("Artifact: store max-size set to [%d]", a.maxSize)
}
func (a *Artifact) get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
path, hasPath := vars["path"]
ext := filepath.Ext(name)
id := strings.TrimSuffix(name, ext)
fname := filepath.Join(a.store, id)
if !chkFile(fname) {
httpsrv.WriteError(w, http.StatusNotFound, "Not Found")
return
}
if chkGone(fname) {
httpsrv.WriteError(w, http.StatusGone, "Gone")
return
}
f, err := os.Open(fname)
if err != nil {
log.Error(err)
}
defer f.Close()
if !hasPath {
pr := readutil.NewPreviewReader(f)
mime, err := readutil.ReadMIME(pr, name)
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", mime)
w.Header().Set("X-Content-Type-Options", "nosniff")
_, _ = io.Copy(w, pr.Drain())
return
}
rdr, err := readutil.Decompress(f)
if err != nil && err != readutil.ErrUnsupportedType {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
if cl, ok := rdr.(io.ReadCloser); ok {
defer cl.Close()
}
tr := tar.NewReader(rdr)
if path == "@" {
var paths []string
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
log.Error(err)
}
paths = append(paths, hdr.Name)
}
httpsrv.WriteObject(w, http.StatusOK, paths)
return
}
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
log.Error(err)
}
if path == "~" && hdr.Name == "index.html" {
path = hdr.Name
}
if path == "~" && hdr.Name == "index.md" {
path = hdr.Name
}
if hdr.Name == path {
if strings.HasSuffix(hdr.Name, ".md") {
md, err := ioutil.ReadAll(tr)
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "text/html")
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
p := parser.NewWithExtensions(extensions)
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
b := markdown.ToHTML(md, p, renderer)
_, _ = w.Write(b)
w.WriteHeader(http.StatusOK)
return
}
pr := readutil.NewPreviewReader(tr)
mime, err := readutil.ReadMIME(pr, hdr.Name)
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", mime)
if _, err := io.Copy(w, pr.Drain()); err != nil {
log.Error(err)
}
w.WriteHeader(http.StatusOK)
return
}
}
httpsrv.WriteError(w, http.StatusNotFound, "Not Found in Archive")
}
func (a *Artifact) put(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if h := r.Header.Get("Content-Length"); h != "" {
if i, err := strconv.Atoi(h); err == nil {
if int64(i) > a.maxSize {
http.Error(w, "ERR Size Too Large", http.StatusRequestEntityTooLarge)
return
}
}
}
rdr := io.LimitReader(r.Body, 500*1024*1024)
pr := readutil.NewPreviewReader(rdr)
rdr = pr.Drain()
s256 := sha256.New()
tmp, err := ioutil.TempFile(a.store, "arch-")
if err != nil {
httpsrv.WriteError(w, 400, err.Error())
return
}
defer os.Remove(tmp.Name())
m := io.MultiWriter(s256, tmp)
if _, err := io.Copy(m, rdr); err != nil {
httpsrv.WriteError(w, 400, err.Error())
}
tmp.Close()
id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
fname := filepath.Join(a.store, id)
log.Debugs("Artifact: moving file", "src", tmp.Name(), "dst", fname)
_ = os.Rename(tmp.Name(), fname)
httpsrv.WriteText(w, http.StatusCreated, id)
}
func (a *Artifact) list(w http.ResponseWriter, r *http.Request) {
err := filepath.Walk(a.store, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
_, err = fmt.Fprintln(w, "FILE: ", info.Name())
return err
})
if err != nil {
httpsrv.WriteError(w, http.StatusInternalServerError, err.Error())
return
}
}

699
src/routes/bindata.go Normal file

File diff suppressed because one or more lines are too long

698
src/routes/identity.go Normal file
View File

@ -0,0 +1,698 @@
package routes
import (
"bytes"
"context"
"crypto/md5"
"crypto/sha1"
"fmt"
"io"
"net"
"net/http"
"net/mail"
"net/url"
"strings"
"text/template"
"time"
"github.com/gorilla/mux"
"github.com/lucasb-eyer/go-colorful"
"github.com/sour-is/crypto/openpgp"
"github.com/tv42/zbase32"
"golang.org/x/crypto/openpgp/armor"
"sour.is/x/paste/src/pkg/cache"
"sour.is/x/paste/src/pkg/promise"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log"
)
var expireAfter = 20 * time.Minute
func init() {
cache, err := cache.NewARC(2048)
if err != nil {
panic(err)
}
tasker := promise.NewRunner(context.TODO(), promise.Timeout(30*time.Second), promise.WithCache(cache, expireAfter))
s := &identity{
cache: cache,
tasker: tasker,
}
httpsrv.RegisterModule("identity", s.config)
httpsrv.HttpRegister("identity", httpsrv.HttpRoutes{
{Name: "get", Method: "GET", Pattern: "/id/{id}", HandlerFunc: s.get},
})
}
var pixl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
var defaultStyle = &Style{
Avatar: pixl,
Cover: pixl,
Background: pixl,
Palette: getPalette("#93CCEA"),
}
type identity struct {
cache cache.Cacher
tasker promise.Tasker
}
type page struct {
Entity *Entity
Style *Style
Proofs *Proofs
IsComplete bool
Err error
}
type Proofs map[string]*Proof
func (s *identity) config(config map[string]string) {}
// func (s *identity) runtoCache()
func (s *identity) get(w http.ResponseWriter, r *http.Request) {
secHeaders(w)
vars := mux.Vars(r)
id := vars["id"]
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
task := s.tasker.Run(EntityKey(id), func(q promise.Q) {
ctx := q.Context()
key := q.Key().(EntityKey)
log.Infos("start task", fmt.Sprintf("%T", key), key)
entity, err := s.getOpenPGPkey(ctx, string(key))
if err != nil {
q.Reject(err)
return
}
log.Infos("Scheduling Style", "email", entity.Primary.Address)
q.Run(StyleKey(entity.Primary.Address), func(q promise.Q) {
ctx := q.Context()
key := q.Key().(StyleKey)
log.Infos("start task", fmt.Sprintf("%T", key), key)
style, err := s.getStyle(ctx, string(key))
if err != nil {
q.Reject(err)
return
}
log.Notice("Resolving Style")
q.Resolve(style)
})
go func() {
log.Infos("Scheduling Proofs", "num", len(entity.Proofs))
for i := range entity.Proofs {
q.Run(ProofKey(entity.Proofs[i]), func(q promise.Q) {
key := q.Key().(ProofKey)
proof := NewProof(string(key))
proof.Checked = true
proof.Verified = true
log.Notice("Resolving Proof")
q.Resolve(proof)
})
}
}()
log.Notice("Resolving Entity")
q.Resolve(entity)
})
page := page{Style: defaultStyle}
select {
case <-task.Await():
log.Info("Tasks Competed")
if err := task.Err(); err != nil {
page.Err = err
page.IsComplete = true
break
}
page.Entity = task.Result().(*Entity)
case <-ctx.Done():
log.Info("Deadline Timeout")
if e, ok := s.cache.Get(EntityKey(id)); ok {
page.Entity = e.Value().(*Entity)
}
}
if page.Entity != nil {
var gotStyle, gotProofs bool
if s, ok := s.cache.Get(StyleKey(page.Entity.Primary.Address)); ok {
page.Style = s.Value().(*Style)
gotStyle = true
}
// TODO: Proofs
gotProofs = true
if len(page.Entity.Proofs) > 0 {
proofs := make(Proofs, len(page.Entity.Proofs))
for i := range page.Entity.Proofs {
p := page.Entity.Proofs[i]
proofs[p] = NewProof(p)
if s, ok := s.cache.Get(ProofKey(p)); ok {
proofs[p] = s.Value().(*Proof)
} else {
log.Info("Missing proof", p)
gotProofs = false
}
}
page.Proofs = &proofs
}
page.IsComplete = gotStyle && gotProofs
}
// e := json.NewEncoder(w)
// e.SetIndent("", " ")
// e.Encode(entity)
t, err := template.New("identity").Parse(identityTPL)
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
err = t.Execute(w, page)
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
}
func (s *identity) getOpenPGPkey(ctx context.Context, id string) (entity *Entity, err error) {
useArmored := false
addr := ""
if isFingerprint(id) {
addr = "https://keys.openpgp.org/vks/v1/by-fingerprint/" + strings.ToUpper(id)
useArmored = true
} else if email, err := mail.ParseAddress(id); err == nil {
addr = getWKDPubKeyAddr(email)
useArmored = false
} else {
return entity, fmt.Errorf("Parse address: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, addr, nil)
if err != nil {
return entity, err
}
cl := http.Client{}
resp, err := cl.Do(req)
if err != nil {
return entity, fmt.Errorf("Requesting key: %w\nRemote URL: %v", err, addr)
}
if resp.StatusCode != 200 {
return entity, fmt.Errorf("bad response from remote: %s\nRemote URL: %v", resp.Status, addr)
}
defer resp.Body.Close()
if resp.Header.Get("Content-Type") == "application/pgp-keys" {
useArmored = true
}
log.Infos("getIdentity", "id", id, "useArmored", useArmored, "status", resp.Status, "addr", addr)
entity, err = ReadKey(resp.Body, useArmored)
return entity, err
}
type EntityKey string
func (k EntityKey) Key() interface{} {
return k
}
type Entity struct {
Primary *mail.Address
Emails []*mail.Address
Fingerprint string
Proofs []string
ArmorText string
}
func getEntity(lis openpgp.EntityList) (*Entity, error) {
entity := &Entity{}
var err error
for _, e := range lis {
if e == nil {
continue
}
if e.PrimaryKey == nil {
continue
}
entity.Fingerprint = fmt.Sprintf("%X", e.PrimaryKey.Fingerprint)
for name, ident := range e.Identities {
// Pick first identity
if entity.Primary == nil {
entity.Primary, err = mail.ParseAddress(name)
if err != nil {
return entity, err
}
}
// If one is marked primary use that
if ident.SelfSignature != nil && ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
entity.Primary, err = mail.ParseAddress(name)
if err != nil {
return entity, err
}
} else {
var email *mail.Address
if email, err = mail.ParseAddress(name); err != nil {
return entity, err
}
entity.Emails = append(entity.Emails, email)
}
// If identity is self signed read notation data.
if ident.SelfSignature != nil && ident.SelfSignature.NotationData != nil {
// Get proofs and append to list.
if proofs, ok := ident.SelfSignature.NotationData["proof@metacode.biz"]; ok {
entity.Proofs = append(entity.Proofs, proofs...)
}
}
}
break
}
if entity.Primary == nil {
entity.Primary, _ = mail.ParseAddress("nobody@nodomain.xyz")
}
return entity, err
}
func ReadKey(r io.Reader, useArmored bool) (e *Entity, err error) {
var buf bytes.Buffer
var w io.Writer = &buf
if !useArmored {
var aw io.WriteCloser
aw, err = armor.Encode(&buf, "PGP PUBLIC KEY BLOCK", nil)
if err != nil {
return e, fmt.Errorf("Read key: %w", err)
}
defer func() { aw.Close(); e.ArmorText = buf.String() }()
w = aw
} else {
defer func() { e.ArmorText = buf.String() }()
}
r = io.TeeReader(r, w)
var lis openpgp.EntityList
if useArmored {
lis, err = openpgp.ReadArmoredKeyRing(r)
} else {
lis, err = openpgp.ReadKeyRing(r)
}
if err != nil {
return e, fmt.Errorf("Read key: %w", err)
}
e, err = getEntity(lis)
if err != nil {
return e, fmt.Errorf("Parse key: %w", err)
}
return
}
func isFingerprint(s string) bool {
for _, r := range s {
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F':
default:
return false
}
}
return true
}
func getWKDPubKeyAddr(email *mail.Address) string {
parts := strings.SplitN(email.Address, "@", 2)
hash := sha1.Sum([]byte(parts[0]))
lp := zbase32.EncodeToString(hash[:])
return fmt.Sprintf("https://%s/.well-known/openpgpkey/hu/%s", parts[1], lp)
}
type StyleKey string
func (s StyleKey) Key() interface{} {
return s
}
type Style struct {
Avatar,
Cover,
Background string
Palette []string
}
func (s *identity) getStyle(ctx context.Context, email string) (*Style, error) {
avatarHost, styleHost, err := styleSRV(ctx, email)
if err != nil {
return nil, err
}
log.Infos("getStyle", "avatar", avatarHost, "style", styleHost)
hash := md5.New()
email = strings.TrimSpace(strings.ToLower(email))
_, _ = hash.Write([]byte(email))
id := hash.Sum(nil)
style := &Style{}
style.Palette = getPalette(fmt.Sprintf("#%x", id[:3]))
style.Avatar = fmt.Sprintf("https://%s/avatar/%x", avatarHost, id)
style.Cover = pixl
style.Background = "https://lavana.sour.is/bg/52548b3dcb032882675afe1e4bcba0e9"
if styleHost != "" {
style.Cover = fmt.Sprintf("https://%s/cover/%x", styleHost, id)
style.Background = fmt.Sprintf("https://%s/bg/%x", styleHost, id)
}
return style, err
}
func styleSRV(ctx context.Context, email string) (avatar string, style string, err error) {
// Defaults
style = ""
avatar = "www.gravatar.com"
parts := strings.SplitN(email, "@", 2)
if _, srv, err := net.DefaultResolver.LookupSRV(ctx, "style-sec", "tcp", parts[1]); err == nil {
if len(srv) > 0 {
style = strings.TrimSuffix(srv[0].Target, ".")
avatar = strings.TrimSuffix(srv[0].Target, ".")
return avatar, style, err
}
}
if _, srv, err := net.DefaultResolver.LookupSRV(ctx, "avatars-sec", "tcp", parts[1]); err == nil {
if len(srv) > 0 {
avatar = strings.TrimSuffix(srv[0].Target, ".")
return avatar, style, err
}
}
return
}
// getPalette maes a complementary color palette. https://play.golang.org/p/nBXLUocGsU5
func getPalette(hex string) []string {
reference, _ := colorful.Hex(hex)
reference = sat(lum(reference, 0, .5), 0, .5)
white := colorful.Color{R: 1, G: 1, B: 1}
black := colorful.Color{R: 0, G: 0, B: 0}
accentA := hue(reference, 60)
accentB := hue(reference, -60)
accentC := hue(reference, -180)
return append(
[]string{},
white.Hex(),
lum(reference, .4, .6).Hex(),
reference.Hex(),
lum(reference, .4, 0).Hex(),
black.Hex(),
lum(accentA, .4, .6).Hex(),
accentA.Hex(),
lum(accentA, .4, 0).Hex(),
lum(accentB, .4, .6).Hex(),
accentB.Hex(),
lum(accentB, .4, 0).Hex(),
lum(accentC, .4, .6).Hex(),
accentC.Hex(),
lum(accentC, .4, 0).Hex(),
)
}
func hue(in colorful.Color, H float64) colorful.Color {
h, s, l := in.Hsl()
return colorful.Hsl(h+H, s, l)
}
func sat(in colorful.Color, S, V float64) colorful.Color {
h, s, l := in.Hsl()
return colorful.Hsl(h, V+s*S, l)
}
func lum(in colorful.Color, L, V float64) colorful.Color {
h, s, l := in.Hsl()
return colorful.Hsl(h, s, V+l*L)
}
type ProofKey string
func (k ProofKey) Key() interface{} {
return k
}
type Proof struct {
Icon string
Service string
Name string
URI string
Link string
Checked bool
Verified bool
}
func NewProof(uri string) *Proof {
p := &Proof{URI: uri}
u, err := url.Parse(uri)
if err != nil {
p.Icon = "exclamation-triangle"
p.Service = "error"
p.Name = err.Error()
return p
}
p.Service = u.Scheme
switch u.Scheme {
case "dns":
p.Icon = "fas fa-globe"
p.Name = u.Opaque
p.Link = fmt.Sprintf("https://%s", u.Hostname())
case "xmpp":
p.Icon = "fas fa-comments"
p.Name = u.Opaque
case "https":
p.Icon = "fas fa-atlas"
p.Name = u.Hostname()
p.Link = fmt.Sprintf("https://%s", u.Hostname())
switch {
case strings.HasPrefix(u.Host, "twitter.com"):
p.Icon = "fab fa-twitter"
p.Service = "Twitter"
case strings.HasPrefix(u.Host, "news.ycombinator.com"):
p.Icon = "fab fa-hacker-news"
p.Service = "HackerNews"
case strings.HasPrefix(u.Host, "dev.to"):
p.Icon = "fab fa-dev"
p.Service = "dev.to"
case strings.HasPrefix(u.Host, "reddit.com"), strings.HasPrefix(u.Host, "www.reddit.com"):
p.Icon = "fab fa-reddit"
p.Service = "Reddit"
case strings.HasPrefix(u.Host, "gist.github.com"):
p.Icon = "fab fa-github"
p.Service = "GitHub"
case strings.HasPrefix(u.Host, "lobste.rs"):
p.Icon = "fas fa-list-ul"
p.Service = "Lobsters"
case strings.HasSuffix(u.Host, "/gitlab_proof/"):
p.Icon = "fab fa-gitlab"
p.Service = "GetLab"
case strings.HasSuffix(u.Host, "/gitea_proof/"):
p.Icon = "fas fa-mug-hot"
p.Service = "Gitea"
default:
p.Icon = "fas fa-project-diagram"
p.Service = "fediverse"
}
}
return p
}
func secHeaders(w http.ResponseWriter) {
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff")
// w.Header().Set("Content-Security-Policy", "default-src 'self';")
w.Header().Set("X-Content-Type-Options", "nosniff")
}
var identityTPL = `
<html>
<head>
{{if not .IsComplete}}<meta http-equiv="refresh" content="1">{{end}}
<script src="https://pagecdn.io/lib/font-awesome/5.14.0/js/fontawesome.min.js" crossorigin="anonymous" integrity="sha256-dNZKI9qQEpJG03MLdR2Rg9Dva1o+50fN3zmlDP+3I+Y="></script>
<link href="https://pagecdn.io/lib/bootstrap/4.5.1/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha256-VoFZSlmyTXsegReQCNmbXrS4hBBUl/cexZvPmPWoJsY=" >
<link href="https://pagecdn.io/lib/font-awesome/5.14.0/css/fontawesome.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha256-7YMlwkILTJEm0TSengNDszUuNSeZu4KTN3z7XrhUQvc=" >
<link href="https://pagecdn.io/lib/font-awesome/5.14.0/css/solid.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha256-s0DhrAmIsT5gZ3X4f+9wIXUbH52CMiqFAwgqCmdPoec=" >
<link href="https://pagecdn.io/lib/font-awesome/5.14.0/css/regular.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha256-FAKIbnpfWhK6v5Re+NAi9n+5+dXanJvXVFohtH6WAuw=" >
<link href="https://pagecdn.io/lib/font-awesome/5.14.0/css/brands.min.css" rel="stylesheet" crossorigin="anonymous" integrity="sha256-xN44ju35FR+kTO/TP/UkqrVbM3LpqUI1VJCWDGbG1ew=" >
{{ with .Style }}
<style>
{{range $i, $val := .Palette}}.fg-color-{{$i}} { color: {{$val}}; }
{{end}}
{{range $i, $val := .Palette}}.bg-color-{{$i}} { background-color: {{$val}}; }
{{end}}
body {
background-image: url('{{.Background}}');
background-repeat: repeat;
background-color: {{index .Palette 7}};
padding-top: 1em;
}
.heading {
background-image: url('{{.Cover}}');
background-size: cover;
background-repeat: no-repeat;
background-color: {{index .Palette 3}};
}
.shade { background-color: {{index .Palette 3}}80; border-radius: .25rem;}
.lead { padding:0; margin:0; }
// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)
@media only screen and (max-width: 576px) {
.h1, .h2, .h3, .h4, .h5, h6 { font-size: 50% }
}
</style>
{{end}}
</head>
<body>
<div class="container">
<div class="card">
<div class="jumbotron heading">
<div class="container">
<div class="row shade">
{{ with .Err }}
<div class="col-xs">
<i class="fas fa-exclamation-triangle fa-4x fg-color-11"></i>
</div>
<div class="col-lg">
<h1 class="display-8 fg-color-8">Something went wrong...</h1>
<pre class="fg-color-11">{{.}}</pre>
</div>
{{else}}
{{ with .Style }}
<div class="col-xs">
<img src="{{.Avatar}}" class="img-thumbnail" alt="avatar" style="width:88px; height:88px">
</div>
{{end}}
{{with .Entity}}
<div class="col-lg">
<h1 class="display-8 fg-color-8">{{.Primary.Name}}</h1>
<p class="lead fg-color-11"><i class="fas fa-fingerprint"></i> {{.Fingerprint}}</p>
</div>
{{else}}
<div class="col-lg">
<h1 class="display-8 fg-color-8">Loading...</h1>
<p class="lead fg-color-11">Reading key from remote service.</p>
</div>
{{end}}
{{end}}
</div>
</div>
</div>
<div class="container">
{{ with .Entity }}
<div class="card">
<div class="card-header">Contact</div>
<div class="list-group list-group-flush">
{{with .Primary}}<a href="mailto:{{.Address}}" class="list-group-item list-group-item-action"><i class="fas fa-envelope"></i> <b>{{.Name}} &lt;{{.Address}}&gt;</b> <span class="badge badge-secondary">Primary</span></a>{{end}}
{{range .Emails}}<a href="mailto:{{.Address}}" class="list-group-item list-group-item-action"><i class="far fa-envelope"></i> {{.Name}} &lt;{{.Address}}&gt;</a>{{end}}
</div>
</div>
<br />
{{end}}
{{with .Proofs}}
<div class="card">
<div class="card-header">Proofs</div>
<ul class="list-group list-group-flush">
{{range .}}
<li class="list-group-item">
<i class="{{.Icon}}"></i>
<span class="badge badge-secondary">{{.Service}}</span>
{{.Name}}
{{if .Checked}}
{{if .Verified}}Verified{{else}}Invalid{{end}}
{{else}}Checking...{{end}}
</li>
{{end}}
</ul>
</div>
<br/>
{{else}}
<div class="card">
<div class="card-header">Proofs</div>
<div class="card-body">Loading...</div>
</div>
<br/>
{{end}}
</div>
<div class="card-footer text-muted text-center">
&copy; 2020 Sour.is
</div>
</div>
</div>
</body>
</html>
`

156
src/routes/image.go Normal file
View File

@ -0,0 +1,156 @@
package routes
import (
"crypto/sha256"
"encoding/base64"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/h2non/filetype"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log"
"sour.is/x/paste/src/pkg/readutil"
)
func init() {
a := &image{}
httpsrv.RegisterModule("image", a.config)
httpsrv.HttpRegister("image", httpsrv.HttpRoutes{
{Name: "getImage", Method: "GET", Pattern: "/i/{name}", HandlerFunc: a.get},
{Name: "putImage", Method: "PUT", Pattern: "/i", HandlerFunc: a.put},
{Name: "getStyle", Method: "GET", Pattern: "/{style:avatar|bg|cover}/", HandlerFunc: a.getStyle},
})
}
type image struct {
store string
maxSize int64
}
func (a *image) config(config map[string]string) {
a.store = "data/"
if config["store"] != "" {
a.store = config["store"]
}
if !chkStore(a.store) {
log.Criticalf("Image: store location [%s] does not exist or is not writable.", a.store)
}
log.Noticef("Image: store location set to [%s]", a.store)
a.maxSize = 10 * 1024 * 1024
if s, ok := config["max-size"]; ok {
if i, err := strconv.Atoi(s); err == nil {
a.maxSize = int64(i)
}
}
log.Noticef("Image: store max-size set to [%d]", a.maxSize)
}
func (a *image) get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
name := vars["name"]
ext := filepath.Ext(name)
id := strings.TrimSuffix(name, ext)
fname := filepath.Join(a.store, id)
if !chkFile(fname) {
httpsrv.WriteError(w, http.StatusNotFound, "Not Found")
return
}
if chkGone(fname) {
httpsrv.WriteError(w, http.StatusGone, "Gone")
return
}
f, err := os.Open(fname)
if err != nil {
log.Fatal(err)
}
defer f.Close()
pr := readutil.NewPreviewReader(f)
mime, _ := readutil.ReadMIME(pr, name)
w.Header().Set("Content-Type", mime)
w.Header().Set("X-Content-Type-Options", "nosniff")
_, _ = io.Copy(w, pr.Drain())
}
func (a *image) put(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if h := r.Header.Get("Content-Length"); h != "" {
log.Debugs("Artifact:", "content-length", h, "max-length", a.maxSize)
if i, err := strconv.Atoi(h); err == nil {
if int64(i) > a.maxSize {
log.Error("ERR Size Too Large")
http.Error(w, "ERR Size Too Large", http.StatusRequestEntityTooLarge)
return
}
}
}
rdr := io.LimitReader(r.Body, a.maxSize)
pr := readutil.NewPreviewReader(rdr)
if !isImageOrVideo(pr) {
httpsrv.WriteError(w, http.StatusUnsupportedMediaType, "ERR Not Image")
return
}
rdr = pr.Drain()
s256 := sha256.New()
tmp, err := ioutil.TempFile(a.store, "image-")
if err != nil {
httpsrv.WriteError(w, 400, err.Error())
return
}
defer os.Remove(tmp.Name())
m := io.MultiWriter(s256, tmp)
if _, err = io.Copy(m, rdr); err != nil {
httpsrv.WriteError(w, 400, err.Error())
return
}
tmp.Close()
id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
fname := filepath.Join(a.store, id)
log.Debugs("Image: moving file", "src", tmp.Name(), "dst", fname)
err = os.Rename(tmp.Name(), fname)
if err != nil {
httpsrv.WriteError(w, 400, err.Error())
return
}
httpsrv.WriteText(w, http.StatusCreated, id)
}
func isImageOrVideo(in io.Reader) bool {
buf := make([]byte, 320)
_, err := in.Read(buf)
if err != nil {
return false
}
return filetype.IsImage(buf) || filetype.IsVideo(buf)
}
func (a *image) getStyle(w http.ResponseWriter, r *http.Request) {
}

View File

@ -5,70 +5,83 @@ import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/json"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"sour.is/x/paste/src/pkg/readutil"
"sour.is/x/toolbox/httpsrv" "sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log" "sour.is/x/toolbox/log"
) )
var store string
var randBytes int
func init() { func init() {
httpsrv.RegisterModule("paste", setConfig) p := &Paste{}
httpsrv.RegisterModule("paste", p.setConfig)
httpsrv.HttpRegister("paste", httpsrv.HttpRoutes{ httpsrv.HttpRegister("paste", httpsrv.HttpRoutes{
{Name: "getRandom", Method: "GET", Pattern: "/paste/rng", HandlerFunc: getRandom}, {Name: "getRandom", Method: "GET", Pattern: "/paste/rng", HandlerFunc: p.getRandom},
{Name: "getPaste", Method: "GET", Pattern: "/paste/{id}", HandlerFunc: getPaste}, {Name: "getPaste", Method: "GET", Pattern: "/paste/{id}", HandlerFunc: p.getPaste},
{Name: "getPaste", Method: "GET", Pattern: "/paste/get/{id}", HandlerFunc: getPaste}, {Name: "getPaste", Method: "GET", Pattern: "/paste/get/{id}", HandlerFunc: p.getPaste},
{Name: "postPaste", Method: "POST", Pattern: "/paste", HandlerFunc: postPaste}, {Name: "postPaste", Method: "POST", Pattern: "/paste", HandlerFunc: p.postPaste},
{Name: "getRandom", Method: "GET", Pattern: "/api/rng", HandlerFunc: getRandom}, {Name: "getRandom", Method: "GET", Pattern: "/api/rng", HandlerFunc: p.getRandom},
{Name: "getPaste", Method: "GET", Pattern: "/api/{id}", HandlerFunc: getPaste}, {Name: "getPaste", Method: "GET", Pattern: "/api/{id}", HandlerFunc: p.getPaste},
{Name: "getPaste", Method: "GET", Pattern: "/api/get/{id}", HandlerFunc: getPaste}, {Name: "getPaste", Method: "GET", Pattern: "/api/get/{id}", HandlerFunc: p.getPaste},
{Name: "postPaste", Method: "POST", Pattern: "/api", HandlerFunc: postPaste}, {Name: "postPaste", Method: "POST", Pattern: "/api", HandlerFunc: p.postPaste},
}) })
} }
func setConfig(config map[string]string) { type Paste struct {
store string
randBytes int
}
store = "data/" func (p *Paste) setConfig(config map[string]string) {
p.store = "data/"
if config["store"] != "" { if config["store"] != "" {
store = config["store"] p.store = config["store"]
} }
if !chkStore(store) { if !chkStore(p.store) {
log.Criticalf("[routes::Paste] Store location [%s] does not exist or is not writable.", store) log.Criticalf("Paste: store location [%s] does not exist or is not writable.", p.store)
} }
log.Noticef("[paste::getPaste] Store location set to [%s]", store) log.Noticef("Paste: store location set to [%s]", p.store)
randBytes = 1024 p.randBytes = 1024
if config["random"] != "" { if config["random"] != "" {
randBytes, _ = strconv.Atoi(config["random"]) p.randBytes, _ = strconv.Atoi(config["random"])
log.Noticef("[paste:getRandom] set random size to %d bytes", randBytes) log.Noticef("Paste: set random size to %d bytes", p.randBytes)
} }
} }
func chkStore(path string) bool { func chkStore(path string) bool {
file, err := os.Stat(path) file, err := os.Stat(path)
if err == nil {
return true if err != nil && os.IsNotExist(err) {
err = os.MkdirAll(path, 0744)
if err != nil {
return false
}
file, err = os.Stat(path)
} }
if os.IsNotExist(err) {
if err != nil {
return false return false
} }
if !file.IsDir() { if !file.IsDir() {
return false return false
} }
if unix.Access(path, unix.W_OK&unix.R_OK) != nil { if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
return false return false
} }
@ -78,15 +91,14 @@ func chkStore(path string) bool {
func chkFile(path string) bool { func chkFile(path string) bool {
file, err := os.Stat(path) file, err := os.Stat(path)
if err == nil { if err != nil {
return true
}
if os.IsNotExist(err) {
return false return false
} }
if file.IsDir() { if file.IsDir() {
return false return false
} }
if unix.Access(path, unix.W_OK&unix.R_OK) != nil { if unix.Access(path, unix.W_OK&unix.R_OK) != nil {
return false return false
} }
@ -99,50 +111,55 @@ func chkGone(path string) bool {
if err != nil { if err != nil {
return true return true
} }
if file.Size() == 0 { if file.Size() == 0 {
return true return true
} }
return false return false
} }
func getRandom(w http.ResponseWriter, r *http.Request) { func (p *Paste) getRandom(w http.ResponseWriter, _ *http.Request) {
s := make([]byte, randBytes) s := make([]byte, p.randBytes)
rand.Read(s) _, _ = rand.Read(s)
w.Header().Set("content-type", "application/octet-stream") w.Header().Set("content-type", "application/octet-stream")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(s) _, _ = w.Write(s)
} }
func getPaste(w http.ResponseWriter, r *http.Request) { func (p *Paste) getPaste(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) vars := mux.Vars(r)
id := vars["id"] id := vars["id"]
if !chkFile(store + id) { fname := filepath.Join(p.store, id)
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("ERR Not Found")) if !chkFile(fname) {
httpsrv.WriteText(w, http.StatusNotFound, "ERR Not Found")
return return
} }
if chkGone(store + id) { if chkGone(fname) {
w.WriteHeader(http.StatusGone) httpsrv.WriteText(w, http.StatusGone, "ERR Gone")
w.Write([]byte("ERR Gone"))
return return
} }
head, err := os.Open(store + id) f, err := os.Open(fname)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
httpsrv.WriteText(w, http.StatusInternalServerError, "ERR Reading Content")
} }
defer head.Close() defer f.Close()
pr := readutil.NewPreviewReader(f)
keep := true keep := true
scanner := bufio.NewScanner(head) scanner := bufio.NewScanner(pr)
for scanner.Scan() { for scanner.Scan() {
txt := scanner.Text() txt := scanner.Text()
log.Debug(txt) log.Debug(txt)
// End of header found
if txt == "" { if txt == "" {
break break
} }
@ -157,9 +174,9 @@ func getPaste(w http.ResponseWriter, r *http.Request) {
log.Debugf("%d > %d", now, exp) log.Debugf("%d > %d", now, exp)
if now > exp { if now > exp {
w.WriteHeader(http.StatusGone) w.WriteHeader(http.StatusGone)
w.Write([]byte("ERR Gone")) _, _ = w.Write([]byte("ERR Gone"))
deleteFile(store + id) deleteFile(fname)
return return
} }
@ -174,47 +191,46 @@ func getPaste(w http.ResponseWriter, r *http.Request) {
} }
} }
file, _ := os.Open(store + id)
defer file.Close()
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, _ = io.Copy(w, pr.Drain())
scanner = bufio.NewScanner(file)
for scanner.Scan() {
w.Write(scanner.Bytes())
w.Write([]byte("\n"))
}
if !keep { if !keep {
deleteFile(store + id) deleteFile(fname)
} }
} }
func postPaste(w http.ResponseWriter, r *http.Request) { func (p *Paste) postPaste(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576)) rdr := io.LimitReader(r.Body, 1048576)
checkErr(err, w)
s256 := sha256.Sum256(body) s256 := sha256.New()
id := base64.RawURLEncoding.EncodeToString(s256[12:]) tmp, _ := ioutil.TempFile(p.store, "arch-")
defer os.Remove(tmp.Name())
ioutil.WriteFile(store+id, body, 0644) m := io.MultiWriter(s256, tmp)
if _, err := io.Copy(m, rdr); err != nil {
w.WriteHeader(http.StatusCreated) httpsrv.WriteError(w, 400, err.Error())
w.Write([]byte("OK " + id))
w.WriteHeader(http.StatusCreated)
}
func checkErr(err error, w http.ResponseWriter) {
if err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(err)
panic(err)
} }
tmp.Close()
id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:])
fname := filepath.Join(p.store, id)
_ = os.Rename(tmp.Name(), fname)
httpsrv.WriteText(w, http.StatusCreated, "OK "+id)
} }
// func checkErr(err error, w http.ResponseWriter) bool {
// if err != nil {
// httpsrv.WriteObject(w, http.StatusBadRequest, err)
// return true
// }
// return false
// }
func deleteFile(path string) { func deleteFile(path string) {
ioutil.WriteFile(path, []byte(""), 0644) _ = ioutil.WriteFile(path, nil, 0644)
} }

View File

@ -1,195 +0,0 @@
package routes
import (
"sour.is/x/httpsrv"
"os"
"golang.org/x/sys/unix"
"log"
"net/http"
"sour.is/x/ident"
"crypto/rand"
"encoding/base64"
"crypto/sha256"
"io/ioutil"
"io"
"bufio"
"github.com/gorilla/mux"
"strings"
"time"
"strconv"
)
var store string
var randBytes int
func init() {
httpsrv.RegisterModule("paste", SetConfig)
httpsrv.IdentRegister("paste", httpsrv.IdentRoutes{
{ "Paste", "GET", "/paste/rng", GetRandom, },
{ "Paste", "GET", "/paste/{id}", GetPaste, },
{ "Paste", "GET", "/paste/get/{id}", GetPaste, },
{ "Paste", "POST", "/paste", PostPaste, },
{ "Paste", "DELETE", "/paste/{id}", DeletePaste, },
{ "Paste", "GET", "/api/rng", GetRandom, },
{ "Paste", "GET", "/api/{id}", GetPaste, },
{ "Paste", "GET", "/api/get/{id}", GetPaste, },
{ "Paste", "POST", "/api", PostPaste, },
{ "Paste", "DELETE", "/api/{id}", DeletePaste, },
})
}
func SetConfig (config map[string]string) {
store = "data/"
if config["store"] != "" {
store = config["store"]
}
if !chkStore(store) {
log.Fatalf("[routes::Paste] Store location [%s] does not exist or is not writable.", store)
}
randBytes = 1024
if config["random"] != "" {
randBytes, _ = strconv.Atoi(config["random"])
}
}
func chkStore(path string) bool {
file, err := os.Stat(path)
if err == nil { return true }
if os.IsNotExist(err) { return false }
if !file.IsDir() { return false }
if unix.Access(path, unix.W_OK & unix.R_OK) != nil { return false }
return true
}
func chkFile(path string) bool {
file, err := os.Stat(path)
if err == nil { return true }
if os.IsNotExist(err) { return false }
if file.IsDir() { return false }
if unix.Access(path, unix.W_OK & unix.R_OK) != nil { return false }
return true
}
func chkGone(path string) bool {
file, err := os.Stat(path)
if err != nil { return true }
if file.Size() == 0 { return true }
return false
}
func GetRandom(w http.ResponseWriter, r *http.Request, i ident.Ident) {
w.WriteHeader(http.StatusOK)
w.Header().Set("content-type","application/octet-stream")
s := make([]byte, randBytes)
rand.Read(s)
w.Write(s)
}
func GetPaste(w http.ResponseWriter, r *http.Request, i ident.Ident) {
vars := mux.Vars(r)
id := vars["id"]
if !chkFile(store + id) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("ERR Not Found"))
return
}
if chkGone(store + id) {
w.WriteHeader(http.StatusGone)
w.Write([]byte("ERR Gone"))
return
}
head, err := os.Open(store + id)
if err != nil {
log.Fatal(err)
}
defer head.Close()
keep := true
scanner := bufio.NewScanner(head)
for scanner.Scan() {
txt := scanner.Text()
log.Println(txt)
if (txt == "") {
break
}
if strings.HasPrefix(txt, "exp:") {
now := time.Now().Unix()
exp, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(txt, "exp:")), 10, 64)
if err != nil {
log.Print(err)
}
if now > exp {
log.Printf("%d > %d", now, exp)
w.WriteHeader(http.StatusGone)
w.Write([]byte("ERR Gone"))
Delete(store + id)
return
}
}
if strings.HasPrefix(txt, "burn:") {
burn := strings.TrimSpace(strings.TrimPrefix(txt, "burn:"))
if burn == "true" {
keep = false
}
}
}
file, _ := os.Open(store + id)
defer file.Close()
w.WriteHeader(http.StatusOK)
scanner = bufio.NewScanner(file)
for scanner.Scan() {
w.Write(scanner.Bytes())
w.Write([]byte("\n"))
}
if !keep {
Delete(store + id)
}
}
func PostPaste(w http.ResponseWriter, r *http.Request, i ident.Ident) {
body, _ := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
s256 := sha256.Sum256(body)
id := base64.RawURLEncoding.EncodeToString(s256[12:])
ioutil.WriteFile(store + id, body, 0644)
w.WriteHeader(http.StatusCreated)
w.Write([]byte("OK " + id))
}
func DeletePaste(w http.ResponseWriter, r *http.Request, i ident.Ident) {
vars := mux.Vars(r)
id := vars["id"]
Delete(store + id)
w.WriteHeader(http.StatusNoContent)
w.Write([]byte("OK"))
}
func Delete(path string) {
ioutil.WriteFile(path, []byte(""), 0644)
}

View File

@ -1,11 +1,26 @@
package routes package routes
import "sour.is/x/toolbox/httpsrv" import (
"fmt"
"net/http"
"sour.is/x/toolbox/httpsrv"
)
func init() { func init() {
httpsrv.AssetRegister("paste", httpsrv.AssetRoutes{ asset := assetFS()
{Name: "Assets", Path: "/", HandlerFunc: httpsrv.FsHtml5(assetFS())}, fmt.Printf("%#v\n", asset)
httpsrv.HttpRegister("ui", httpsrv.HttpRoutes{
{
Name: "Assets",
Method: "GET",
Pattern: "/ui{path:.*}",
HandlerFunc: http.StripPrefix("/ui", http.FileServer(httpsrv.FsHtml5(asset))).ServeHTTP,
},
{Name: "Root", Method: "GET", Pattern: "/", HandlerFunc: http.RedirectHandler("/ui/", http.StatusFound).ServeHTTP},
}) })
} }
//go:generate go-bindata-assetfs -pkg routes -prefix ../../ ../../public/ ../../public/static/css/ ../../public/static/js/ ../../public/static/media/ //go:generate go run github.com/sour-is/go-assetfs/cmd/assetfs -pkg routes -prefix ../../ ../../public/...

167
src/routes/shorturl.go Normal file
View File

@ -0,0 +1,167 @@
package routes
import (
"net/http"
"net/url"
"regexp"
"github.com/gorilla/mux"
"github.com/timshannon/badgerhold"
"sour.is/x/toolbox/httpsrv"
"sour.is/x/toolbox/log"
"sour.is/x/toolbox/uuid"
)
type logger struct{}
func (logger) Errorf(s string, o ...interface{}) { log.Errorf(s, o...) }
func (logger) Warningf(s string, o ...interface{}) { log.Warningf(s, o...) }
func (logger) Infof(s string, o ...interface{}) { log.Infof(s, o...) }
func (logger) Debugf(s string, o ...interface{}) { log.Debugf(s, o...) }
func init() {
s := &shortDB{}
httpsrv.RegisterModule("short", s.config)
httpsrv.HttpRegister("short", httpsrv.HttpRoutes{
{Name: "get", Method: "GET", Pattern: "/s/{id}", HandlerFunc: s.get},
{Name: "put", Method: "PUT", Pattern: "/s/{id}", HandlerFunc: s.put},
})
}
type shortDB struct {
options badgerhold.Options
}
func (s *shortDB) config(config map[string]string) {
s.options = badgerhold.DefaultOptions
s.options.Options = s.options.WithLogger(logger{})
var ok bool
if s.options.Dir, ok = config["index"]; !ok {
s.options.Dir = "data"
}
if s.options.ValueDir, ok = config["value"]; !ok {
s.options.ValueDir = "data"
}
store, err := badgerhold.Open(s.options)
defer func() {
if err = store.Close(); err != nil {
log.Fatal(err)
}
}()
if err != nil {
log.Fatal(err)
}
}
func (s *shortDB) get(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
store, err := badgerhold.Open(s.options)
defer func() {
if err = store.Close(); err != nil {
log.Fatal(err)
}
}()
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
var url shortURL
err = store.Get(id, &url)
if err != nil && err != badgerhold.ErrNotFound {
httpsrv.WriteText(w, 500, err.Error())
return
}
if err == badgerhold.ErrNotFound {
httpsrv.WriteText(w, 404, "not found")
return
}
w.Header().Set("Location", url.URL)
w.WriteHeader(http.StatusFound)
}
func (s *shortDB) put(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
secret := r.FormValue("secret")
u, err := url.Parse(r.FormValue("url"))
if err != nil {
httpsrv.WriteText(w, 400, "bad url")
return
}
store, err := badgerhold.Open(s.options)
defer func() {
if err = store.Close(); err != nil {
log.Fatal(err)
}
}()
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
url := &shortURL{}
err = store.Get(id, url)
if err != nil && err != badgerhold.ErrNotFound {
httpsrv.WriteText(w, 500, err.Error())
return
}
if err == badgerhold.ErrNotFound {
url = newShortURL(id, secret, u.String())
err := store.Insert(url.ID, url)
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
httpsrv.WriteObject(w, 200, url)
return
}
if secret == "" {
httpsrv.WriteError(w, 401, "no auth")
return
}
if secret != url.Secret {
httpsrv.WriteError(w, 403, "forbidden")
return
}
err = store.Upsert(url.ID, url)
if err != nil {
httpsrv.WriteText(w, 500, err.Error())
return
}
httpsrv.WriteObject(w, 200, url)
}
type shortURL struct {
ID string `badgerhold:"unique"`
URL string
Secret string
}
func newShortURL(id, secret, u string) *shortURL {
m, err := regexp.MatchString("[a-z-]{1,64}", id)
if id == "" || !m || err != nil {
id = uuid.V4()
}
m, err = regexp.MatchString("[a-z-]{1,64}", secret)
if secret == "" || !m || err != nil {
secret = uuid.V4()
}
return &shortURL{ID: id, Secret: secret, URL: u}
}

15
tools/tools.go Normal file
View File

@ -0,0 +1,15 @@
// +build tools
package tools
import (
_ "github.com/99designs/gqlgen"
_ "github.com/go-swagger/go-swagger/cmd/swagger"
_ "github.com/sour-is/go-assetfs/cmd/assetfs"
_ "github.com/sour-is/go-assetfs/cmd/bindata"
_ "github.com/sour-is/go-assetfs/pkg/assetfs"
_ "github.com/vektah/dataloaden"
)