Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7587b8067a | ||
|
|
2b7fb84a5e | ||
|
|
963f2ddb32 | ||
|
|
3ff2620ba5 | ||
|
|
22c83d8f2d | ||
|
|
cab407a098 | ||
|
|
4d21cd5dd8 | ||
|
|
26cb73de1c | ||
|
|
caef8c0fbd | ||
|
|
ea3d3fdcef | ||
|
|
4d2c5587d6 | ||
|
|
6b3f0f0ae6 | ||
|
|
28e9a2c0ca | ||
|
|
e5677bec86 | ||
|
|
a1b16115df | ||
|
|
5439149b09 | ||
|
|
332276f6db | ||
|
|
66c45e1c6e | ||
|
|
68654f8bd3 | ||
|
|
a95e38802d | ||
|
|
e063187656 | ||
|
|
892546343a | ||
|
|
dc3b5695d4 | ||
|
|
f8ee0c0459 | ||
|
|
947bbe9efa | ||
|
|
9af2b5b5f6 | ||
|
|
7e1387a787 | ||
|
|
ff1f38c494 | ||
|
|
de1d2ba243 | ||
|
|
2ab2c553b6 | ||
|
|
6889309af7 | ||
|
|
61758ec3ce | ||
|
|
7a1831c6ec | ||
|
|
31cafdfd16 | ||
|
|
859574f1e0 | ||
|
|
b4efbd1b6b | ||
|
|
a7fc7054c5 | ||
|
|
9dd2d6da30 | ||
|
|
ad85705599 | ||
|
|
b94703f6b9 | ||
|
|
acad061682 | ||
|
|
d89a841bfb | ||
|
|
b6a2908b68 | ||
|
|
37b71cb7bf | ||
|
|
644cbbcfd6 | ||
|
|
f9b2518734 |
38
.goreleaser.yml
Normal file
38
.goreleaser.yml
Normal file
@@ -0,0 +1,38 @@
|
||||
project_name: gohttpserver
|
||||
release:
|
||||
github:
|
||||
owner: codeskyblue
|
||||
name: gohttpserver
|
||||
brew:
|
||||
install: bin.install "gohttpserver"
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
goarm:
|
||||
- "6"
|
||||
main: .
|
||||
ldflags: -s -w -X main.VERSION={{.Version}}
|
||||
flags: -tags bindata
|
||||
binary: gohttpserver
|
||||
archive:
|
||||
format: tar.gz
|
||||
name_template: '{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
|
||||
.Arm }}{{ end }}'
|
||||
files:
|
||||
- licence*
|
||||
- LICENCE*
|
||||
- license*
|
||||
- LICENSE*
|
||||
- readme*
|
||||
- README*
|
||||
- changelog*
|
||||
- CHANGELOG*
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt'
|
||||
@@ -1,9 +1,14 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
env:
|
||||
global:
|
||||
- GO15VENDOREXPERIMENT=1
|
||||
script:
|
||||
- go test -v
|
||||
# .travis.yml
|
||||
after_success:
|
||||
- go get github.com/jteeuwen/go-bindata/...
|
||||
- go get github.com/elazarl/go-bindata-assetfs/...
|
||||
- test -n "$TRAVIS_TAG" && go-bindata-assetfs -tags bindata res/...
|
||||
- test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
|
||||
|
||||
59
Godeps/Godeps.json
generated
59
Godeps/Godeps.json
generated
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"ImportPath": "github.com/codeskyblue/gohttpserver",
|
||||
"GoVersion": "go1.6",
|
||||
"GoVersion": "go1.7",
|
||||
"GodepVersion": "v74",
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/DHowett/go-plist",
|
||||
"Rev": "f4bf55d2395500aacc17eedcebc5f139336b4312"
|
||||
"Rev": "fd61394c0abc85ba629ff8471fa35d4309cd6609"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/kingpin",
|
||||
"Comment": "v2.1.3",
|
||||
"Rev": "aef28d186e59d39ed537473dfce4472108ea1045"
|
||||
"Comment": "v2.2.0",
|
||||
"Rev": "92b2733684bc5f1971162349ce098fe0d070e2a9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/template",
|
||||
"Rev": "b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0"
|
||||
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/template/parse",
|
||||
"Rev": "b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0"
|
||||
"Rev": "a0175ee3bccc567396460bf5acd36800cb10c49c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alecthomas/units",
|
||||
@@ -28,6 +28,10 @@
|
||||
"ImportPath": "github.com/codeskyblue/dockerignore",
|
||||
"Rev": "de82dee623d9207f906d327172149cba50427a88"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codeskyblue/openid-go",
|
||||
"Rev": "0d30842b2fb4704849cd07f6fba862a7a9f4b403"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-yaml/yaml",
|
||||
"Rev": "e4d366fc3c7938e2958e662b4258c7a89e1f0e3e"
|
||||
@@ -38,22 +42,53 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/context",
|
||||
"Comment": "v1.1-4-gaed02d1",
|
||||
"Rev": "aed02d124ae4a0e94fea4541c8effd05bf0c8296"
|
||||
"Comment": "v1.1-8-g0b6087c",
|
||||
"Rev": "0b6087c0035ecc2ecddef2451329bca7f36192ab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/handlers",
|
||||
"Comment": "v1.1-10-g801d6e3",
|
||||
"Rev": "801d6e3b008914ee888c9ab9b1b379b9a56fbf44"
|
||||
"Comment": "v1.1-14-g3a5767c",
|
||||
"Rev": "3a5767ca75ece5f7f1440b1d16975247f8d8b221"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/mux",
|
||||
"Comment": "v1.1-15-gd391bea",
|
||||
"Rev": "d391bea3118c9fc17a88d62c9189bb791255e0ef"
|
||||
"Comment": "v1.1-27-g757bef9",
|
||||
"Rev": "757bef944d0f21880861c2dd9c871ca543023cba"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/securecookie",
|
||||
"Comment": "v1.1-5-gfa5329f",
|
||||
"Rev": "fa5329f913702981df43dcb2a380bac429c810b5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/gorilla/sessions",
|
||||
"Comment": "v1.1",
|
||||
"Rev": "ca9ada44574153444b00d3fd9c8559e4cc95f896"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mash/go-accesslog",
|
||||
"Rev": "9ba8e13f36087d6cb83d9a9f17f9e8da137d5ee9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/pkg/errors",
|
||||
"Comment": "v0.8.0-1-g839d9e9",
|
||||
"Rev": "839d9e913e063e28dfd0e6c7b7512793e0a48be9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/shogo82148/androidbinary",
|
||||
"Rev": "2fb750de4ec6a45853fcc5f85694eab47e1007f9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/shogo82148/androidbinary/apk",
|
||||
"Rev": "2fb750de4ec6a45853fcc5f85694eab47e1007f9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html",
|
||||
"Rev": "a625e3953219464fdd5611bb48bf87c927717295"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/html/atom",
|
||||
"Rev": "a625e3953219464fdd5611bb48bf87c927717295"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
70
README.md
70
README.md
@@ -1,20 +1,19 @@
|
||||
# gohttpserver
|
||||
[](https://travis-ci.org/codeskyblue/gohttpserver)
|
||||
|
||||
Make the best HTTP File Server. Better UI, upload support, apple&android install package qrcode generate.
|
||||
- Goal: Make the best HTTP File Server.
|
||||
- Features: Human-friendly UI, file uploading support, direct QR-code generation for Apple & Android install package.
|
||||
|
||||
[Demo site](https://gohttpserver.herokuapp.com/)
|
||||
|
||||
- 目标: 做最好的HTTP文件服务器
|
||||
- 功能: 人性化的UI体验,文件的上传支持,安卓和苹果安装包的二维码直接生成。
|
||||
|
||||
**Binary** can be download from [github releases](https://github.com/codeskyblue/gohttpserver/releases/)
|
||||
**Binaries** can be downloaded from [this repo releases](https://github.com/codeskyblue/gohttpserver/releases/)
|
||||
|
||||
## Notes
|
||||
If using go1.5, ensure you set GO15VENDOREXPERIMENT=1
|
||||
|
||||
Upload size now limited to 1G
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
@@ -47,9 +46,12 @@ Upload size now limited to 1G
|
||||
1. [ ] Support sort by size or modified time
|
||||
1. [x] Add version info into index page
|
||||
1. [ ] Add api `/-/info/some.(apk|ipa)` to get detail info
|
||||
1. [x] Add api `/-/apk/info/some.apk` to get android package info
|
||||
1. [x] Auto tag version
|
||||
1. [x] Custom title support
|
||||
1. [x] Support setting from conf file
|
||||
1. [x] Quick copy download link
|
||||
1. [x] Show folder size
|
||||
|
||||
## Installation
|
||||
```
|
||||
@@ -59,21 +61,43 @@ go build && ./gohttpserver
|
||||
```
|
||||
|
||||
## Usage
|
||||
Listen port 8000 on all interface, and enable upload
|
||||
Listen on port 8000 of all interfaces, and enable file uploading.
|
||||
|
||||
```
|
||||
./gohttpserver -r ./ --addr :8000 --upload
|
||||
```
|
||||
|
||||
## Authentication options
|
||||
- Enable basic http authentication
|
||||
|
||||
```sh
|
||||
$ gohttpserver --auth-type http --auth-http username:password
|
||||
```
|
||||
|
||||
- Use openid auth
|
||||
|
||||
```sh
|
||||
$ gohttpserver --auth-type openid --auth-openid https://login.example-hostname.com/openid/
|
||||
```
|
||||
|
||||
The openid returns url using "http" instead of "https", but I am not planing to fix this currently.
|
||||
|
||||
## Advanced usage
|
||||
Support update access rule if there is a file named `.ghs.yml` under directory. `.ghs.yml` example
|
||||
Add access rule by creating a `.ghs.yml` file under a sub-directory. An example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
upload: false
|
||||
delete: false
|
||||
users:
|
||||
- email: "codeskyblue@codeskyblue.com"
|
||||
delete: true
|
||||
upload: true
|
||||
```
|
||||
|
||||
For example, if there is such file under directory `foo`, directory `foo` can not be uploaded, while `bar` can.
|
||||
In this case, if openid auth is enabled and user "codeskyblue@codeskyblue.com" has logged in, he/she can delete/upload files under the directory where the `.ghs.yml` file exits.
|
||||
|
||||
For example, in the following directory hierarchy, users can delete/uploade files in directory `foo`, but he/she cannot do this in directory `bar`.
|
||||
|
||||
```
|
||||
root -
|
||||
@@ -84,16 +108,26 @@ root -
|
||||
`-- hello.txt
|
||||
```
|
||||
|
||||
Use config file. specfied with `--conf`, see [example config.yml](testdata/config.yml). Note that command line option can overwrite conf in `config.yml`
|
||||
User can specify config file name with `--conf`, see [example config.yml](testdata/config.yml).
|
||||
|
||||
To specify which files is hidden and which file is visible, add the following lines to `.ghs.yml`
|
||||
|
||||
```yaml
|
||||
accessTables:
|
||||
- regex: block.file
|
||||
allow: false
|
||||
- regex: visual.file
|
||||
allow: true
|
||||
```
|
||||
|
||||
### ipa plist proxy
|
||||
This is used for server which not https enabled. default use <https://plistproxy.herokuapp.com/plist>
|
||||
This is used for server on which https is enabled. default use <https://plistproxy.herokuapp.com/plist>
|
||||
|
||||
```
|
||||
./gohttpserver --plistproxy=https://someproxyhost.com/
|
||||
```
|
||||
|
||||
Proxy web site should have ability
|
||||
Test if proxy works:
|
||||
|
||||
```sh
|
||||
$ http POST https://proxyhost.com/plist < app.plist
|
||||
@@ -107,8 +141,6 @@ $ http GET https://proxyhost.com/plist/18f99211
|
||||
### Upload with CURL
|
||||
For example, upload a file named `foo.txt` to directory `somedir`
|
||||
|
||||
PS: max upload size limited to 1G (hard coded)
|
||||
|
||||
```sh
|
||||
$ curl -F file=@foo.txt localhost:8000/somedir
|
||||
```
|
||||
@@ -116,8 +148,8 @@ $ curl -F file=@foo.txt localhost:8000/somedir
|
||||
## FAQ
|
||||
- [How to generate self signed certificate with openssl](http://stackoverflow.com/questions/10175812/how-to-create-a-self-signed-certificate-with-openssl)
|
||||
|
||||
### How the search works
|
||||
The search algorithm follow the search engine google. keywords are seperated with space, words with prefix `-` will be excluded.
|
||||
### How the query is formated
|
||||
The search query follows common format rules just like Google. Keywords are seperated with space(s), keywords with prefix `-` will be excluded in search results.
|
||||
|
||||
1. `hello world` means must contains `hello` and `world`
|
||||
1. `hello -world` means must contains `hello` but not contains `world`
|
||||
@@ -131,7 +163,7 @@ go get github.com/jteeuwen/go-bindata/...
|
||||
go get github.com/elazarl/go-bindata-assetfs/...
|
||||
```
|
||||
|
||||
Theme are all defined in [res/themes](res/themes) directory. Now only two, black and green.
|
||||
Theme are all defined in [res/themes](res/themes) directory. Now only two themes are available, "black" and "green".
|
||||
|
||||
## How to build single binary release
|
||||
```sh
|
||||
@@ -139,8 +171,6 @@ go-bindata-assetfs -tags bindata res/...
|
||||
go build -tags bindata
|
||||
```
|
||||
|
||||
That's all. ^_^
|
||||
|
||||
## Reference Web sites
|
||||
|
||||
* Core lib Vue <https://vuejs.org.cn/>
|
||||
@@ -150,6 +180,8 @@ That's all. ^_^
|
||||
* Markdown CSS <https://github.com/sindresorhus/github-markdown-css>
|
||||
* Upload support <http://www.dropzonejs.com/>
|
||||
* ScrollUp <https://markgoodyear.com/2013/01/scrollup-jquery-plugin/>
|
||||
* Clipboard <https://clipboardjs.com/>
|
||||
* Underscore <http://underscorejs.org/>
|
||||
|
||||
**Go Libraries**
|
||||
|
||||
@@ -157,7 +189,7 @@ That's all. ^_^
|
||||
* <http://www.gorillatoolkit.org/pkg/handlers>
|
||||
|
||||
## History
|
||||
The first version is <https://github.com/codeskyblue/gohttp>
|
||||
The old version is hosted at <https://github.com/codeskyblue/gohttp>
|
||||
|
||||
## LICENSE
|
||||
This project is under license [MIT](LICENSE)
|
||||
This project is licensed under [MIT](LICENSE).
|
||||
|
||||
@@ -15,10 +15,22 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"regexp"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/shogo82148/androidbinary/apk"
|
||||
)
|
||||
|
||||
type ApkInfo struct {
|
||||
PackageName string `json:"packageName"`
|
||||
MainActivity string `json:"mainActivity"`
|
||||
Version struct {
|
||||
Code int `json:"code"`
|
||||
Name string `json:"name"`
|
||||
} `json:"version"`
|
||||
}
|
||||
|
||||
type IndexFileItem struct {
|
||||
Path string
|
||||
Info os.FileInfo
|
||||
@@ -32,6 +44,7 @@ type HTTPStaticServer struct {
|
||||
Theme string
|
||||
PlistProxy string
|
||||
GoogleTrackerId string
|
||||
AuthType string
|
||||
|
||||
indexes []IndexFileItem
|
||||
m *mux.Router
|
||||
@@ -72,9 +85,11 @@ func NewHTTPStaticServer(root string) *HTTPStaticServer {
|
||||
// routers for Apple *.ipa
|
||||
m.HandleFunc("/-/ipa/plist/{path:.*}", s.hPlist)
|
||||
m.HandleFunc("/-/ipa/link/{path:.*}", s.hIpaLink)
|
||||
// TODO: /ipa/info
|
||||
|
||||
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET")
|
||||
// TODO: /ipa/info
|
||||
m.HandleFunc("/-/info/{path:.*}", s.hInfo)
|
||||
|
||||
m.HandleFunc("/{path:.*}", s.hIndex).Methods("GET", "HEAD")
|
||||
m.HandleFunc("/{path:.*}", s.hUpload).Methods("POST")
|
||||
m.HandleFunc("/{path:.*}", s.hDelete).Methods("DELETE")
|
||||
return s
|
||||
@@ -88,8 +103,10 @@ func (s *HTTPStaticServer) hIndex(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
|
||||
finfo, err := os.Stat(relPath)
|
||||
if err == nil && finfo.IsDir() {
|
||||
if r.FormValue("raw") == "false" || isDir(relPath) {
|
||||
if r.Method == "HEAD" {
|
||||
return
|
||||
}
|
||||
tmpl.ExecuteTemplate(w, "index", s)
|
||||
} else {
|
||||
if r.FormValue("download") == "true" {
|
||||
@@ -110,7 +127,7 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
auth := s.readAccessConf(path)
|
||||
log.Printf("%#v", auth)
|
||||
if !auth.Delete {
|
||||
if !auth.canDelete(req) {
|
||||
http.Error(w, "Delete forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -124,46 +141,104 @@ func (s *HTTPStaticServer) hDelete(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
func (s *HTTPStaticServer) hUpload(w http.ResponseWriter, req *http.Request) {
|
||||
path := mux.Vars(req)["path"]
|
||||
dirpath := filepath.Join(s.Root, path)
|
||||
|
||||
// check auth
|
||||
auth := s.readAccessConf(path)
|
||||
if !auth.Upload {
|
||||
if !auth.canUpload(req) {
|
||||
http.Error(w, "Upload forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
err := req.ParseMultipartForm(1 << 30) // max memory 1G
|
||||
file, header, err := req.FormFile("file")
|
||||
if err != nil {
|
||||
log.Println("Parse form file:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(req.MultipartForm.File["file"]) == 0 {
|
||||
http.Error(w, "Need multipart file", http.StatusInternalServerError)
|
||||
defer func() {
|
||||
file.Close()
|
||||
req.MultipartForm.RemoveAll() // Seen from go source code, req.MultipartForm not nil after call FormFile(..)
|
||||
}()
|
||||
dstPath := filepath.Join(dirpath, header.Filename)
|
||||
dst, err := os.Create(dstPath)
|
||||
if err != nil {
|
||||
log.Println("Create file:", err)
|
||||
http.Error(w, "File create "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
dirpath := filepath.Join(s.Root, path)
|
||||
|
||||
for _, mfile := range req.MultipartForm.File["file"] {
|
||||
file, err := mfile.Open()
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
dst, err := os.Create(filepath.Join(dirpath, mfile.Filename)) // BUG(ssx): There is a leak here
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
log.Println("Handle upload file:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err := io.Copy(dst, file); err != nil {
|
||||
log.Println("Handle upload file:", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write([]byte("Upload success"))
|
||||
w.Header().Set("Content-Type", "application/json;charset=utf-8")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"destination": dstPath,
|
||||
})
|
||||
}
|
||||
|
||||
type FileJSONInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
Path string `json:"path"`
|
||||
ModTime int64 `json:"mtime"`
|
||||
Extra interface{} `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// path should be absolute
|
||||
func parseApkInfo(path string) (ai *ApkInfo) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Println("parse-apk-info panic:", err)
|
||||
}
|
||||
}()
|
||||
apkf, err := apk.OpenFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ai = &ApkInfo{}
|
||||
ai.MainActivity, _ = apkf.MainAcitivty()
|
||||
ai.PackageName = apkf.PackageName()
|
||||
ai.Version.Code = apkf.Manifest().VersionCode
|
||||
ai.Version.Name = apkf.Manifest().VersionName
|
||||
return
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hInfo(w http.ResponseWriter, r *http.Request) {
|
||||
path := mux.Vars(r)["path"]
|
||||
relPath := filepath.Join(s.Root, path)
|
||||
if !isFile(relPath) {
|
||||
http.Error(w, "Not a file", 403)
|
||||
return
|
||||
}
|
||||
fi, err := os.Stat(relPath)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
fji := &FileJSONInfo{
|
||||
Name: fi.Name(),
|
||||
Size: fi.Size(),
|
||||
Path: path,
|
||||
ModTime: fi.ModTime().UnixNano() / 1e6,
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
switch ext {
|
||||
case ".md":
|
||||
fji.Type = "markdown"
|
||||
case ".apk":
|
||||
fji.Type = "apk"
|
||||
fji.Extra = parseApkInfo(relPath)
|
||||
default:
|
||||
fji.Type = "text"
|
||||
}
|
||||
data, _ := json.Marshal(fji)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hZip(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -286,23 +361,96 @@ func (s *HTTPStaticServer) hFileOrDirectory(w http.ResponseWriter, r *http.Reque
|
||||
http.ServeFile(w, r, filepath.Join(s.Root, path))
|
||||
}
|
||||
|
||||
type ListResponse struct {
|
||||
type HTTPFileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Type string `json:"type"`
|
||||
Size string `json:"size"`
|
||||
Size int64 `json:"size"`
|
||||
ModTime int64 `json:"mtime"`
|
||||
}
|
||||
|
||||
type AccessTable struct {
|
||||
Regex string `yaml:"regex"`
|
||||
Allow bool `yaml:"allow"`
|
||||
}
|
||||
|
||||
type UserControl struct {
|
||||
Email string
|
||||
// Access bool
|
||||
Upload bool
|
||||
Delete bool
|
||||
}
|
||||
|
||||
type AccessConf struct {
|
||||
Upload bool `yaml:"upload" json:"upload"`
|
||||
Delete bool `yaml:"delete" json:"delete"`
|
||||
Upload bool `yaml:"upload" json:"upload"`
|
||||
Delete bool `yaml:"delete" json:"delete"`
|
||||
Users []UserControl `yaml:"users" json:"users"`
|
||||
AccessTables []AccessTable `yaml:"accessTables"`
|
||||
}
|
||||
|
||||
var reCache = make(map[string]*regexp.Regexp)
|
||||
|
||||
func (c *AccessConf) canAccess(fileName string) bool {
|
||||
for _, table := range c.AccessTables {
|
||||
pattern, ok := reCache[table.Regex]
|
||||
if !ok {
|
||||
pattern, _ = regexp.Compile(table.Regex)
|
||||
reCache[table.Regex] = pattern
|
||||
}
|
||||
// skip wrong format regex
|
||||
if pattern == nil {
|
||||
continue
|
||||
}
|
||||
if pattern.MatchString(fileName) {
|
||||
return table.Allow
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *AccessConf) canDelete(r *http.Request) bool {
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
return c.Delete
|
||||
}
|
||||
val := session.Values["user"]
|
||||
if val == nil {
|
||||
return c.Delete
|
||||
}
|
||||
userInfo := val.(*UserInfo)
|
||||
for _, rule := range c.Users {
|
||||
if rule.Email == userInfo.Email {
|
||||
return rule.Delete
|
||||
}
|
||||
}
|
||||
return c.Delete
|
||||
}
|
||||
|
||||
func (c *AccessConf) canUpload(r *http.Request) bool {
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
return c.Upload
|
||||
}
|
||||
val := session.Values["user"]
|
||||
if val == nil {
|
||||
return c.Upload
|
||||
}
|
||||
userInfo := val.(*UserInfo)
|
||||
for _, rule := range c.Users {
|
||||
if rule.Email == userInfo.Email {
|
||||
return rule.Upload
|
||||
}
|
||||
}
|
||||
return c.Upload
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath := mux.Vars(r)["path"]
|
||||
localPath := filepath.Join(s.Root, requestPath)
|
||||
search := r.FormValue("search")
|
||||
auth := s.readAccessConf(requestPath)
|
||||
auth.Upload = auth.canUpload(r)
|
||||
auth.Delete = auth.canDelete(r)
|
||||
|
||||
// path string -> info os.FileInfo
|
||||
fileInfoMap := make(map[string]os.FileInfo, 0)
|
||||
@@ -329,9 +477,12 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// turn file list -> json
|
||||
lrs := make([]ListResponse, 0)
|
||||
lrs := make([]HTTPFileInfo, 0)
|
||||
for path, info := range fileInfoMap {
|
||||
lr := ListResponse{
|
||||
if !auth.canAccess(info.Name()) {
|
||||
continue
|
||||
}
|
||||
lr := HTTPFileInfo{
|
||||
Name: info.Name(),
|
||||
Path: path,
|
||||
ModTime: info.ModTime().UnixNano() / 1e6,
|
||||
@@ -348,39 +499,60 @@ func (s *HTTPStaticServer) hJSONList(w http.ResponseWriter, r *http.Request) {
|
||||
lr.Name = name
|
||||
lr.Path = filepath.Join(filepath.Dir(path), name)
|
||||
lr.Type = "dir"
|
||||
lr.Size = "-"
|
||||
lr.Size = s.historyDirSize(lr.Path)
|
||||
} else {
|
||||
lr.Type = "file"
|
||||
lr.Size = formatSize(info)
|
||||
lr.Size = info.Size() // formatSize(info)
|
||||
}
|
||||
lrs = append(lrs, lr)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"files": lrs,
|
||||
"auth": s.readAccessConf(requestPath),
|
||||
"auth": auth,
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
var dirSizeMap = make(map[string]int64)
|
||||
|
||||
func (s *HTTPStaticServer) makeIndex() error {
|
||||
var indexes = make([]IndexFileItem, 0)
|
||||
var err = filepath.Walk(s.Root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
log.Printf("WARN: Visit path: %s error: %v", strconv.Quote(path), err)
|
||||
return filepath.SkipDir
|
||||
// return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if filepath.IsAbs(path) {
|
||||
path, _ = filepath.Rel(s.Root, path)
|
||||
}
|
||||
|
||||
path, _ = filepath.Rel(s.Root, path)
|
||||
path = filepath.ToSlash(path)
|
||||
indexes = append(indexes, IndexFileItem{path, info})
|
||||
return nil
|
||||
})
|
||||
s.indexes = indexes
|
||||
dirSizeMap = make(map[string]int64)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) historyDirSize(dir string) int64 {
|
||||
var size int64
|
||||
if size, ok := dirSizeMap[dir]; ok {
|
||||
return size
|
||||
}
|
||||
for _, fitem := range s.indexes {
|
||||
if filepath.HasPrefix(fitem.Path, dir) {
|
||||
size += fitem.Info.Size()
|
||||
}
|
||||
}
|
||||
dirSizeMap[dir] = size
|
||||
return size
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) findIndex(text string) []IndexFileItem {
|
||||
ret := make([]IndexFileItem, 0)
|
||||
for _, item := range s.indexes {
|
||||
@@ -410,11 +582,18 @@ func (s *HTTPStaticServer) findIndex(text string) []IndexFileItem {
|
||||
func (s *HTTPStaticServer) defaultAccessConf() AccessConf {
|
||||
return AccessConf{
|
||||
Upload: s.Upload,
|
||||
Delete: s.Delete,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HTTPStaticServer) readAccessConf(requestPath string) (ac AccessConf) {
|
||||
ac = s.defaultAccessConf()
|
||||
requestPath = filepath.Clean(requestPath)
|
||||
if requestPath == "/" || requestPath == "" || requestPath == "." {
|
||||
ac = s.defaultAccessConf()
|
||||
} else {
|
||||
parentPath := filepath.Dir(requestPath)
|
||||
ac = s.readAccessConf(parentPath)
|
||||
}
|
||||
relPath := filepath.Join(s.Root, requestPath)
|
||||
if isFile(relPath) {
|
||||
relPath = filepath.Dir(relPath)
|
||||
@@ -456,3 +635,8 @@ func isFile(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsRegular()
|
||||
}
|
||||
|
||||
func isDir(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.Mode().IsDir()
|
||||
}
|
||||
|
||||
34
main.go
34
main.go
@@ -25,21 +25,26 @@ type Configure struct {
|
||||
Conf *os.File `yaml:"-"`
|
||||
Addr string `yaml:"addr"`
|
||||
Root string `yaml:"root"`
|
||||
HttpAuth string `yaml:"httpauth"`
|
||||
HTTPAuth string `yaml:"httpauth"`
|
||||
Cert string `yaml:"cert"`
|
||||
Key string `yaml:"key"`
|
||||
Cors bool `yaml:"cors"`
|
||||
Theme string `yaml:"theme"`
|
||||
XHeaders bool `yaml:"xheaders"`
|
||||
Upload bool `yaml:"upload"`
|
||||
Delete bool `yaml:"delete"`
|
||||
PlistProxy string `yaml:"plistproxy"`
|
||||
Title string `yaml:"title"`
|
||||
Debug bool `yaml:"debug"`
|
||||
GoogleTrackerId string `yaml:"google-tracker-id"`
|
||||
Auth struct {
|
||||
Type string `yaml:"type"`
|
||||
OpenID string `yaml:"openid"`
|
||||
HTTP string `yaml:"http"`
|
||||
} `yaml:"auth"`
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
}
|
||||
type logger struct{}
|
||||
|
||||
func (l logger) Log(record accesslog.LogRecord) {
|
||||
log.Printf("%s - %s %d %s", record.Ip, record.Method, record.Status, record.Uri)
|
||||
@@ -47,6 +52,7 @@ func (l logger) Log(record accesslog.LogRecord) {
|
||||
|
||||
var (
|
||||
defaultPlistProxy = "https://plistproxy.herokuapp.com/plist"
|
||||
defaultOpenID = "https://some-hostname.com/openid/"
|
||||
gcfg = Configure{}
|
||||
l = logger{}
|
||||
|
||||
@@ -82,6 +88,7 @@ func parseFlags() error {
|
||||
gcfg.Addr = ":8000"
|
||||
gcfg.Theme = "black"
|
||||
gcfg.PlistProxy = defaultPlistProxy
|
||||
gcfg.Auth.OpenID = defaultOpenID
|
||||
gcfg.GoogleTrackerId = "UA-81205425-2"
|
||||
gcfg.Title = "Go HTTP File Server"
|
||||
|
||||
@@ -92,9 +99,12 @@ func parseFlags() error {
|
||||
kingpin.Flag("addr", "listen address, default :8000").Short('a').StringVar(&gcfg.Addr)
|
||||
kingpin.Flag("cert", "tls cert.pem path").StringVar(&gcfg.Cert)
|
||||
kingpin.Flag("key", "tls key.pem path").StringVar(&gcfg.Key)
|
||||
kingpin.Flag("httpauth", "HTTP basic auth (ex: user:pass)").StringVar(&gcfg.HttpAuth)
|
||||
kingpin.Flag("auth-type", "Auth type <http|openid>").StringVar(&gcfg.Auth.Type)
|
||||
kingpin.Flag("auth-http", "HTTP basic auth (ex: user:pass)").StringVar(&gcfg.Auth.HTTP)
|
||||
kingpin.Flag("auth-openid", "OpenID auth identity url").StringVar(&gcfg.Auth.OpenID)
|
||||
kingpin.Flag("theme", "web theme, one of <black|green>").StringVar(&gcfg.Theme)
|
||||
kingpin.Flag("upload", "enable upload support").BoolVar(&gcfg.Upload)
|
||||
kingpin.Flag("delete", "enable delete support").BoolVar(&gcfg.Delete)
|
||||
kingpin.Flag("xheaders", "used when behide nginx").BoolVar(&gcfg.XHeaders)
|
||||
kingpin.Flag("cors", "enable cross-site HTTP request").BoolVar(&gcfg.Cors)
|
||||
kingpin.Flag("debug", "enable debug mode").BoolVar(&gcfg.Debug)
|
||||
@@ -125,12 +135,15 @@ func main() {
|
||||
data, _ := yaml.Marshal(gcfg)
|
||||
fmt.Printf("--- config ---\n%s\n", string(data))
|
||||
}
|
||||
log.SetFlags(log.Lshortfile | log.LstdFlags)
|
||||
|
||||
ss := NewHTTPStaticServer(gcfg.Root)
|
||||
ss.Theme = gcfg.Theme
|
||||
ss.Title = gcfg.Title
|
||||
ss.GoogleTrackerId = gcfg.GoogleTrackerId
|
||||
ss.Upload = gcfg.Upload
|
||||
ss.Delete = gcfg.Delete
|
||||
ss.AuthType = gcfg.Auth.Type
|
||||
|
||||
if gcfg.PlistProxy != "" {
|
||||
u, err := url.Parse(gcfg.PlistProxy)
|
||||
@@ -146,10 +159,15 @@ func main() {
|
||||
hdlr = accesslog.NewLoggingHandler(hdlr, l)
|
||||
|
||||
// HTTP Basic Authentication
|
||||
userpass := strings.SplitN(gcfg.HttpAuth, ":", 2)
|
||||
if len(userpass) == 2 {
|
||||
user, pass := userpass[0], userpass[1]
|
||||
hdlr = httpauth.SimpleBasicAuth(user, pass)(hdlr)
|
||||
userpass := strings.SplitN(gcfg.Auth.HTTP, ":", 2)
|
||||
switch gcfg.Auth.Type {
|
||||
case "http":
|
||||
if len(userpass) == 2 {
|
||||
user, pass := userpass[0], userpass[1]
|
||||
hdlr = httpauth.SimpleBasicAuth(user, pass)(hdlr)
|
||||
}
|
||||
case "openid":
|
||||
handleOpenID(false) // FIXME(ssx): set secure default to false
|
||||
}
|
||||
// CORS
|
||||
if gcfg.Cors {
|
||||
|
||||
111
openid-login.go
Normal file
111
openid-login.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
openid "github.com/codeskyblue/openid-go"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
var (
|
||||
nonceStore = openid.NewSimpleNonceStore()
|
||||
discoveryCache = openid.NewSimpleDiscoveryCache()
|
||||
store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
defaultSessionName = "ghs-session"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
NickName string `json:"nickName"`
|
||||
}
|
||||
|
||||
type M map[string]interface{}
|
||||
|
||||
func init() {
|
||||
gob.Register(&UserInfo{})
|
||||
gob.Register(&M{})
|
||||
}
|
||||
|
||||
func handleOpenID(secure bool) {
|
||||
http.HandleFunc("/-/login", func(w http.ResponseWriter, r *http.Request) {
|
||||
nextUrl := r.FormValue("next")
|
||||
referer := r.Referer()
|
||||
if nextUrl == "" && strings.Contains(referer, "://"+r.Host) {
|
||||
nextUrl = referer
|
||||
}
|
||||
scheme := "http"
|
||||
if secure {
|
||||
scheme = "https"
|
||||
}
|
||||
if url, err := openid.RedirectURL("https://login.netease.com/openid",
|
||||
scheme+"://"+r.Host+"/-/openidcallback?next="+nextUrl, ""); err == nil {
|
||||
http.Redirect(w, r, url, 303)
|
||||
} else {
|
||||
log.Println("Should not got error here:", err)
|
||||
}
|
||||
})
|
||||
|
||||
http.HandleFunc("/-/openidcallback", func(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := openid.Verify("http://"+r.Host+r.URL.String(), discoveryCache, nonceStore)
|
||||
if err != nil {
|
||||
io.WriteString(w, "Authentication check failed.")
|
||||
return
|
||||
}
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
user := &UserInfo{
|
||||
Id: id,
|
||||
Email: r.FormValue("openid.sreg.email"),
|
||||
Name: r.FormValue("openid.sreg.fullname"),
|
||||
NickName: r.FormValue("openid.sreg.nickname"),
|
||||
}
|
||||
session.Values["user"] = user
|
||||
if err := session.Save(r, w); err != nil {
|
||||
log.Println("session save error:", err)
|
||||
}
|
||||
|
||||
nextUrl := r.FormValue("next")
|
||||
if nextUrl == "" {
|
||||
nextUrl = "/"
|
||||
}
|
||||
http.Redirect(w, r, nextUrl, 302)
|
||||
})
|
||||
|
||||
http.HandleFunc("/-/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
val := session.Values["user"]
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
data, _ := json.Marshal(val)
|
||||
w.Write(data)
|
||||
})
|
||||
|
||||
http.HandleFunc("/-/logout", func(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := store.Get(r, defaultSessionName)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
delete(session.Values, "user")
|
||||
session.Options.MaxAge = -1
|
||||
nextUrl := r.FormValue("next")
|
||||
_ = session.Save(r, w)
|
||||
if nextUrl == "" {
|
||||
nextUrl = r.Referer()
|
||||
}
|
||||
http.Redirect(w, r, nextUrl, 302)
|
||||
})
|
||||
}
|
||||
@@ -24,3 +24,11 @@ div.dropzone {
|
||||
.qrcode-title {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#qrcodeCanvas {
|
||||
padding-right: 20px;
|
||||
}
|
||||
@@ -2,218 +2,262 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>gohttp server</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/themes/[[.Theme]].css">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>gohttp server</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/bootstrap-3.3.5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/font-awesome-4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/github-markdown.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/dropzone.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/scrollUp-image.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/-/res/themes/[[.Theme]].css">
|
||||
</head>
|
||||
|
||||
<body id="app">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-2">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">[[.Title]]</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-2">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="hidden-xs">
|
||||
<a href="javascript:void(0)" v-on:click='genQrcode("/", location.origin)'>
|
||||
View in Phone
|
||||
<span class="glyphicon glyphicon-qrcode"></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search text" v-bind:value="search" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<ul id="nav-right-bar" class="nav navbar-nav navbar-right">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
<li>
|
||||
<a v-on:click='changePath("/", $event)' href="/"><i class="fa fa-home"></i></a>
|
||||
</li>
|
||||
<li v-for="bc in breadcrumb.slice(0, breadcrumb.length-1)">
|
||||
<a v-on:click='changePath(bc.path, $event)' href="{{bc.path}}">{{bc.name}}</a>
|
||||
</li>
|
||||
<li v-if="breadcrumb.length >= 1">
|
||||
{{breadcrumb.slice(-1)[0].name}}
|
||||
</li>
|
||||
</ol>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan=4>
|
||||
<!-- <button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-2">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">[[.Title]]</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-2">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="hidden-xs">
|
||||
<a href="javascript:void(0)" v-on:click='genQrcode(location.pathname)'>
|
||||
View in Phone
|
||||
<span class="glyphicon glyphicon-qrcode"></span>
|
||||
</a>
|
||||
</li>
|
||||
[[if eq .AuthType "openid"]]
|
||||
<li class="hidden-xs">
|
||||
<a href="/-/logout" v-if="user.email">
|
||||
<span v-text="user.name"></span>
|
||||
<span class="glyphicon glyphicon-indent-left"></span>
|
||||
</a>
|
||||
<a href="/-/login" v-else>
|
||||
Login
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
</a>
|
||||
</li>
|
||||
[[end]]
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right">
|
||||
<div class="input-group">
|
||||
<input type="text" name="search" class="form-control" placeholder="Search text" v-bind:value="search" autofocus>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">
|
||||
<span class="glyphicon glyphicon-search"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<ul id="nav-right-bar" class="nav navbar-nav navbar-right">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<div class="col-md-12">
|
||||
<ol class="breadcrumb">
|
||||
<li>
|
||||
<a v-on:click='changePath("/", $event)' href="/"><i class="fa fa-home"></i></a>
|
||||
</li>
|
||||
<li v-for="bc in breadcrumb.slice(0, breadcrumb.length-1)">
|
||||
<a v-on:click='changePath(bc.path, $event)' href="{{bc.path}}">{{bc.name}}</a>
|
||||
</li>
|
||||
<li v-if="breadcrumb.length >= 1">
|
||||
{{breadcrumb.slice(-1)[0].name}}
|
||||
</li>
|
||||
</ol>
|
||||
<table class="table table-hover" v-if="!previewMode">
|
||||
<thead>
|
||||
<tr>
|
||||
<td colspan=4>
|
||||
<!-- <button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Back <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button> -->
|
||||
<button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Hidden <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-if="auth.upload" data-toggle="modal" data-target="#upload-modal">
|
||||
Upload <i class="fa fa-upload"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th class="hidden-xs">
|
||||
<span style="cursor: pointer" v-on:click='mtimeTypeFromNow = !mtimeTypeFromNow'>ModTime</span>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="f in computedFiles">
|
||||
<td>
|
||||
<a v-on:click='clickFileOrDir(f, $event)' href="/{{f.path}}">
|
||||
<i style="padding-right: 0.5em" class="fa" v-bind:class='genFileClass(f)'></i> {{f.name}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{f.size}}</td>
|
||||
<td class="hidden-xs">{{formatTime(f.mtime)}}</td>
|
||||
<td style="text-align: left">
|
||||
<template v-if="f.type == 'dir'">
|
||||
<a class="btn btn-default btn-xs" href="/-/zip/{{f.path}}">
|
||||
<span class="hidden-xs">Archive</span> Zip
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-if="f.type == 'file'">
|
||||
<a class="btn btn-default btn-xs hidden-xs" href="/{{f.path}}?download=true">
|
||||
<span class="hidden-xs">Download</span>
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-xs hidden-xs" v-on:click="genQrcode(f.name)" href="javascript:void(0)">
|
||||
<span v-if="shouldHaveQrcode(f.name)">QRCode</span>
|
||||
<span class="glyphicon glyphicon-qrcode"></span>
|
||||
</a>
|
||||
<a class="btn btn-default btn-xs visible-xs" v-if="shouldHaveQrcode(f.name)" href="{{genInstallURL(f.name)}}">
|
||||
Install <i class="fa fa-cube"></i>
|
||||
</a>
|
||||
<a class="btn btn-default btn-xs" v-if="auth.delete" v-on:click="deletePathConfirm(f, $event)" href="javascript:void(0)">
|
||||
<span style="color:#CC3300" class="glyphicon glyphicon-trash"></span>
|
||||
</a>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-12" id="preview" v-if="previewFile">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title" style="font-weight: normal">
|
||||
<i class="fa" v-bind:class='genFileClass(previewFile)'></i>
|
||||
{{previewFile.name}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<article class="markdown-body">{{{previewFile.contentHTML }}}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12" id="content">
|
||||
<!-- Small qrcode modal -->
|
||||
<div id="qrcode-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<span id="qrcode-title"></span>
|
||||
<a style="font-size: 0.6em" href="#" id="qrcode-link">[view]</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="qrcodeCanvas"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upload modal-->
|
||||
<div id="upload-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-upload"></i> File upload
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="#" class="dropzone" id="my-dropzone"></form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div id="footer" class="pull-right" style="margin: 2em 1em">
|
||||
<a href="https://github.com/codeskyblue/gohttpserver">gohttpserver (ver:{{version}})</a>, written by <a href="https://github.com/codeskyblue">codeskyblue</a>. 2016. go1.6
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-xs btn-default" onclick="history.back()">
|
||||
Back <i class="fa fa-arrow-left"></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-on:click='toggleHidden()'>
|
||||
Hidden <i class="fa" v-bind:class='showHidden ? "fa-eye" : "fa-eye-slash"'></i>
|
||||
</button>
|
||||
<button class="btn btn-xs btn-default" v-if="auth.upload" data-toggle="modal" data-target="#upload-modal">
|
||||
Upload <i class="fa fa-upload"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Size</th>
|
||||
<th class="hidden-xs">
|
||||
<span style="cursor: pointer" v-on:click='mtimeTypeFromNow = !mtimeTypeFromNow'>ModTime</span>
|
||||
</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="f in computedFiles">
|
||||
<td>
|
||||
<a v-on:click='clickFileOrDir(f, $event)' href="/{{f.path + (f.type == 'dir' ? '' : '')}}">
|
||||
<!-- ?raw=false -->
|
||||
<i style="padding-right: 0.5em" class="fa" v-bind:class='genFileClass(f)'></i> {{f.name}}
|
||||
</a>
|
||||
</td>
|
||||
<td><span v-if="f.type == 'dir'">~</span> {{f.size | formatBytes}}</td>
|
||||
<td class="hidden-xs">{{formatTime(f.mtime)}}</td>
|
||||
<td style="text-align: left">
|
||||
<template v-if="f.type == 'dir'">
|
||||
<a class="btn btn-default btn-xs" href="/-/zip/{{f.path}}">
|
||||
<span class="hidden-xs">Archive</span> Zip
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-if="f.type == 'file'">
|
||||
<a class="btn btn-default btn-xs hidden-xs" href="/{{f.path}}?download=true">
|
||||
<span class="hidden-xs">Download</span>
|
||||
<span class="glyphicon glyphicon-download-alt"></span>
|
||||
</a>
|
||||
<button class="btn btn-default btn-xs bstooltip" data-trigger="manual" data-title="Copied!" data-clipboard-text="{{genDownloadURL(f)}}">
|
||||
<i class="fa fa-copy"></i>
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs" v-on:click="showInfo(f)">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
</button>
|
||||
<button class="btn btn-default btn-xs hidden-xs" v-on:click="genQrcode(f.name)">
|
||||
<span v-if="shouldHaveQrcode(f.name)">QRCode</span>
|
||||
<span class="glyphicon glyphicon-qrcode"></span>
|
||||
</button>
|
||||
<a class="btn btn-default btn-xs visible-xs" v-if="shouldHaveQrcode(f.name)" href="{{genInstallURL(f.name)}}">
|
||||
Install <i class="fa fa-cube"></i>
|
||||
</a>
|
||||
<button class="btn btn-default btn-xs" v-if="auth.delete" v-on:click="deletePathConfirm(f, $event)">
|
||||
<span style="color:#CC3300" class="glyphicon glyphicon-trash"></span>
|
||||
</button>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script src="/-/res/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="/-/res/js/jquery.qrcode.js"></script>
|
||||
<script src="/-/res/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="/-/res/js/qrcode.js"></script>
|
||||
<script src="/-/res/js/vue-1.0.min.js"></script>
|
||||
<script src="/-/res/js/showdown-1.4.2.min.js"></script>
|
||||
<script src="/-/res/js/moment.min.js"></script>
|
||||
<script src="/-/res/js/dropzone.js"></script>
|
||||
<script src="/-/res/js/underscore-min.js"></script>
|
||||
<script src="/-/res/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src="/-/res/js/index.js"></script>
|
||||
[[if .GoogleTrackerId ]]
|
||||
<script>
|
||||
<div class="col-md-12" id="preview" v-if="preview.filename">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title" style="font-weight: normal">
|
||||
<i class="fa" v-bind:class='genFileClass(previewFile)'></i> {{preview.filename}}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<article class="markdown-body">{{{preview.contentHTML }}}
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12" id="content">
|
||||
<!-- Small qrcode modal -->
|
||||
<div id="qrcode-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<span id="qrcode-title"></span>
|
||||
<a style="font-size: 0.6em" href="#" id="qrcode-link">[view]</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body clearfix">
|
||||
<div id="qrcodeCanvas" class="pull-left"></div>
|
||||
<div id="qrcodeRight" class="pull-left">
|
||||
<p>
|
||||
<a href="#">下载链接</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Upload modal-->
|
||||
<div id="upload-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<i class="fa fa-upload"></i> File upload
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="#" class="dropzone" id="upload-form"></form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" @click="removeAllUploads">RemoveAll</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- File info modal -->
|
||||
<div id="file-info-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title">
|
||||
<span id="file-info-title"></span>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre id="file-info-content"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div id="footer" class="pull-right" style="margin: 2em 1em">
|
||||
<a href="https://github.com/codeskyblue/gohttpserver">gohttpserver (ver:{{version}})</a>, written by <a href="https://github.com/codeskyblue">codeskyblue</a>. Copyright 2016-2017. go1.7
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/-/res/js/jquery-3.1.0.min.js"></script>
|
||||
<script src="/-/res/js/jquery.qrcode.js"></script>
|
||||
<script src="/-/res/js/jquery.scrollUp.min.js"></script>
|
||||
<script src="/-/res/js/qrcode.js"></script>
|
||||
<script src="/-/res/js/vue-1.0.min.js"></script>
|
||||
<script src="/-/res/js/showdown-1.6.4.min.js"></script>
|
||||
<script src="/-/res/js/moment.min.js"></script>
|
||||
<script src="/-/res/js/dropzone.js"></script>
|
||||
<script src="/-/res/js/underscore-min.js"></script>
|
||||
<script src="/-/res/js/clipboard-1.5.12.min.js"></script>
|
||||
<script src="/-/res/bootstrap-3.3.5/js/bootstrap.min.js"></script>
|
||||
<script src="/-/res/js/index.js"></script>
|
||||
[[if .GoogleTrackerId ]]
|
||||
<script>
|
||||
(function(i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function() {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
i[r] = i[r] || function() {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date();
|
||||
a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0];
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', '[[.GoogleTrackerId]]', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
[[ end ]]
|
||||
</script>
|
||||
[[ end ]]
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,71 +1,71 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>[[.Name]] install</title>
|
||||
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<script type="text/javascript" src="/-/res/js/ua-parser.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
<title>[[.Name]] install</title>
|
||||
<meta http-equiv="Content-Type" content="text/HTML; charset=utf-8">
|
||||
<meta content="target-densitydpi=device-dpi,width=640" name="viewport" id="viewport">
|
||||
<link rel="shortcut icon" type="image/png" href="/-/res/favicon.png" />
|
||||
<script type="text/javascript" src="/-/res/js/ua-parser.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
function showById(name) {
|
||||
document.getElementById(name).style.display = 'block';
|
||||
document.getElementById(name).style.display = 'block';
|
||||
}
|
||||
|
||||
function checkBrowerAndDownload() {
|
||||
var parser = new UAParser();
|
||||
var os_info = parser.getOS();
|
||||
console.log(os_info)
|
||||
var parser = new UAParser();
|
||||
var os_info = parser.getOS();
|
||||
console.log(os_info)
|
||||
|
||||
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||
showById('wechat');
|
||||
return;
|
||||
}
|
||||
var plistLink = "[[.PlistLink]]";
|
||||
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||
var plistLink = "[[.PlistLink]]";
|
||||
var ipaInstallLink = 'itms-services://?action=download-manifest&url=' + plistLink;
|
||||
document.getElementById('itms-link').href = ipaInstallLink;
|
||||
|
||||
document.getElementById('itms-link').href = ipaInstallLink;
|
||||
|
||||
if (os_info.name == 'Android') {
|
||||
return;
|
||||
} else if (os_info.name == 'iOS') {
|
||||
showById('safari');
|
||||
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else {
|
||||
showById('browser');
|
||||
return;
|
||||
}
|
||||
// wechat is support AppStore link now.
|
||||
if (navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == "micromessenger") {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else if (os_info.name == 'Android') {
|
||||
showById("android");
|
||||
return;
|
||||
} else if (os_info.name == 'iOS') {
|
||||
showById('safari');
|
||||
location.href = ipaInstallLink;
|
||||
return;
|
||||
} else {
|
||||
showById('browser');
|
||||
return;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<style>
|
||||
<style>
|
||||
#wechat {
|
||||
position: relative;
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 777px;
|
||||
position: relative;
|
||||
width: 640px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 777px;
|
||||
}
|
||||
</style>
|
||||
<div id="wechat" style="display: none">
|
||||
<img style='width: 100%;position: relative;' src='/-/res/imgs/wx.png' />
|
||||
</div>
|
||||
<div id="browser" style="display: none">
|
||||
This is IPA install page, you should open this link with your iPhone.
|
||||
</div>
|
||||
<div id="safari" style="display: none">
|
||||
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||
</div>
|
||||
<div id="android" style="display: none">
|
||||
This is IPA install page, not for android.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
</style>
|
||||
<div id="wechat" style="display: none">
|
||||
<img style='width: 100%;position: relative;' src='/-/res/imgs/wx.png' />
|
||||
</div>
|
||||
<div id="browser" style="display: none">
|
||||
This is IPA install page, you should open this link with your iPhone.
|
||||
</div>
|
||||
<div id="safari" style="display: none">
|
||||
If install not started soon, click <a id="itms-link" href="#">here</a>
|
||||
</div>
|
||||
<div id="android" style="display: none">
|
||||
This is IPA install page, not for android.
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
checkBrowerAndDownload();
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
7
res/js/clipboard-1.5.12.min.js
vendored
Normal file
7
res/js/clipboard-1.5.12.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
582
res/js/index.js
582
res/js/index.js
@@ -1,271 +1,377 @@
|
||||
jQuery('#qrcodeCanvas').qrcode({
|
||||
text: "http://jetienne.com/"
|
||||
text: "http://jetienne.com/"
|
||||
});
|
||||
|
||||
Dropzone.autoDiscover = false;
|
||||
|
||||
function getExtention(fname) {
|
||||
return fname.slice((fname.lastIndexOf(".") - 1 >>> 0) + 2);
|
||||
return fname.slice((fname.lastIndexOf(".") - 1 >>> 0) + 2);
|
||||
}
|
||||
|
||||
function pathJoin(parts, sep) {
|
||||
var separator = sep || '/';
|
||||
var replace = new RegExp(separator + '{1,}', 'g');
|
||||
return parts.join(separator).replace(replace, separator);
|
||||
var separator = sep || '/';
|
||||
var replace = new RegExp(separator + '{1,}', 'g');
|
||||
return parts.join(separator).replace(replace, separator);
|
||||
}
|
||||
|
||||
function getQueryString(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
var r = decodeURI(window.location.search).substr(1).match(reg);
|
||||
if (r != null) return r[2].replace(/\+/g, ' ');
|
||||
return null;
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
||||
var r = decodeURI(window.location.search).substr(1).match(reg);
|
||||
if (r != null) return r[2].replace(/\+/g, ' ');
|
||||
return null;
|
||||
}
|
||||
|
||||
var vm = new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
message: "Hello vue.js",
|
||||
location: window.location,
|
||||
breadcrumb: [],
|
||||
showHidden: false,
|
||||
previewFile: null,
|
||||
version: "loading",
|
||||
mtimeTypeFromNow: false, // or fromNow
|
||||
auth: {},
|
||||
search: getQueryString("search"),
|
||||
files: [{
|
||||
name: "loading ...",
|
||||
path: "",
|
||||
size: "...",
|
||||
type: "dir",
|
||||
}]
|
||||
el: "#app",
|
||||
data: {
|
||||
user: {
|
||||
email: "",
|
||||
name: "",
|
||||
},
|
||||
computed: {
|
||||
computedFiles: function() {
|
||||
var that = this;
|
||||
this.previewFile = null;
|
||||
|
||||
var files = this.files.filter(function(f) {
|
||||
if (f.name == 'README.md') {
|
||||
that.previewFile = {
|
||||
name: f.name,
|
||||
path: f.path,
|
||||
size: f.size,
|
||||
type: 'markdown',
|
||||
contentHTML: '',
|
||||
}
|
||||
}
|
||||
if (!that.showHidden && f.name.slice(0, 1) === '.') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// console.log(this.previewFile)
|
||||
if (this.previewFile) {
|
||||
var name = this.previewFile.name; // For now only README.md
|
||||
console.log(pathJoin([location.pathname, 'README.md']))
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, 'README.md']),
|
||||
method: 'GET',
|
||||
success: function(res) {
|
||||
var converter = new showdown.Converter({
|
||||
tables: true,
|
||||
omitExtraWLInCodeBlocks: true,
|
||||
parseImgDimensions: true,
|
||||
simplifiedAutoLink: true,
|
||||
literalMidWordUnderscores: true,
|
||||
tasklists: true,
|
||||
ghCodeBlocks: true,
|
||||
smoothLivePreview: true,
|
||||
});
|
||||
|
||||
var html = converter.makeHtml(res);
|
||||
that.previewFile.contentHTML = html;
|
||||
},
|
||||
error: function(err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return files;
|
||||
},
|
||||
location: window.location,
|
||||
breadcrumb: [],
|
||||
showHidden: false,
|
||||
previewMode: false,
|
||||
preview: {
|
||||
filename: '',
|
||||
filetype: '',
|
||||
filesize: 0,
|
||||
contentHTML: '',
|
||||
},
|
||||
methods: {
|
||||
formatTime: function(timestamp) {
|
||||
var m = moment(timestamp);
|
||||
if (this.mtimeTypeFromNow) {
|
||||
return m.fromNow();
|
||||
}
|
||||
return m.format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
toggleHidden: function() {
|
||||
this.showHidden = !this.showHidden;
|
||||
},
|
||||
genInstallURL: function(name) {
|
||||
if (getExtention(name) == "ipa") {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, "/-/ipa/link", location.pathname, name]);
|
||||
return urlPath;
|
||||
}
|
||||
return location.protocol + "//" + pathJoin([location.host, location.pathname, name]);
|
||||
},
|
||||
genQrcode: function(text, title) {
|
||||
var urlPath = this.genInstallURL(text);
|
||||
$("#qrcode-title").html(title || text);
|
||||
$("#qrcode-link").attr("href", urlPath);
|
||||
$('#qrcodeCanvas').empty().qrcode({
|
||||
text: urlPath
|
||||
});
|
||||
$("#qrcode-modal").modal("show");
|
||||
},
|
||||
shouldHaveQrcode: function(name) {
|
||||
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
|
||||
},
|
||||
genFileClass: function(f) {
|
||||
if (f.type == "dir") {
|
||||
if (f.name == '.git') {
|
||||
return 'fa-git-square';
|
||||
}
|
||||
return "fa-folder-open";
|
||||
}
|
||||
var ext = getExtention(f.name);
|
||||
switch (ext) {
|
||||
case "go":
|
||||
case "py":
|
||||
case "js":
|
||||
case "java":
|
||||
case "c":
|
||||
case "cpp":
|
||||
case "h":
|
||||
return "fa-file-code-o";
|
||||
case "pdf":
|
||||
return "fa-file-pdf-o";
|
||||
case "zip":
|
||||
return "fa-file-zip-o";
|
||||
case "mp3":
|
||||
case "wav":
|
||||
return "fa-file-audio-o";
|
||||
case "jpg":
|
||||
case "png":
|
||||
case "gif":
|
||||
case "jpeg":
|
||||
case "tiff":
|
||||
return "fa-file-picture-o";
|
||||
case "ipa":
|
||||
case "dmg":
|
||||
return "fa-apple";
|
||||
case "apk":
|
||||
return "fa-android";
|
||||
case "exe":
|
||||
return "fa-windows";
|
||||
}
|
||||
return "fa-file-text-o"
|
||||
},
|
||||
clickFileOrDir: function(f, e) {
|
||||
if (f.type == "file") {
|
||||
return true;
|
||||
}
|
||||
var reqPath = pathJoin([location.pathname, f.name]);
|
||||
loadDirectory(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
changePath: function(reqPath, e) {
|
||||
loadDirectory(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
deletePathConfirm: function(f, e) {
|
||||
// confirm
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, f.name]),
|
||||
method: 'DELETE',
|
||||
success: function(res) {
|
||||
loadFileList()
|
||||
},
|
||||
error: function(err) {
|
||||
alert(err.responseText);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateBreadcrumb: function() {
|
||||
var pathname = decodeURI(location.pathname || "/");
|
||||
var parts = pathname.split('/');
|
||||
this.breadcrumb = [];
|
||||
if (pathname == "/") {
|
||||
return this.breadcrumb;
|
||||
}
|
||||
var i = 2;
|
||||
for (; i <= parts.length; i += 1) {
|
||||
var name = parts[i - 1];
|
||||
var path = parts.slice(0, i).join('/');
|
||||
this.breadcrumb.push({
|
||||
name: name + (i == parts.length ? ' /' : ''),
|
||||
path: path
|
||||
})
|
||||
}
|
||||
return this.breadcrumb;
|
||||
version: "loading",
|
||||
mtimeTypeFromNow: false, // or fromNow
|
||||
auth: {},
|
||||
search: getQueryString("search"),
|
||||
files: [{
|
||||
name: "loading ...",
|
||||
path: "",
|
||||
size: "...",
|
||||
type: "dir",
|
||||
}],
|
||||
myDropzone: null,
|
||||
},
|
||||
computed: {
|
||||
computedFiles: function() {
|
||||
var that = this;
|
||||
that.preview.filename = null;
|
||||
|
||||
var files = this.files.filter(function(f) {
|
||||
if (f.name == 'README.md') {
|
||||
that.preview.filename = f.name;
|
||||
}
|
||||
}
|
||||
if (!that.showHidden && f.name.slice(0, 1) === '.') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// console.log(this.previewFile)
|
||||
if (this.preview.filename) {
|
||||
var name = this.preview.filename; // For now only README.md
|
||||
console.log(pathJoin([location.pathname, 'README.md']))
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, 'README.md']),
|
||||
method: 'GET',
|
||||
success: function(res) {
|
||||
var converter = new showdown.Converter({
|
||||
tables: true,
|
||||
omitExtraWLInCodeBlocks: true,
|
||||
parseImgDimensions: true,
|
||||
simplifiedAutoLink: true,
|
||||
literalMidWordUnderscores: true,
|
||||
tasklists: true,
|
||||
ghCodeBlocks: true,
|
||||
smoothLivePreview: true,
|
||||
simplifiedAutoLink: true,
|
||||
strikethrough: true,
|
||||
});
|
||||
|
||||
var html = converter.makeHtml(res);
|
||||
that.preview.contentHTML = html;
|
||||
},
|
||||
error: function(err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return files;
|
||||
},
|
||||
},
|
||||
created: function() {
|
||||
$.ajax({
|
||||
url: "/-/user",
|
||||
method: "get",
|
||||
dataType: "json",
|
||||
success: function(ret) {
|
||||
if (ret) {
|
||||
this.user.email = ret.email;
|
||||
this.user.name = ret.name;
|
||||
}
|
||||
}.bind(this)
|
||||
})
|
||||
this.myDropzone = new Dropzone("#upload-form", {
|
||||
paramName: "file",
|
||||
maxFilesize: 1024,
|
||||
addRemoveLinks: true,
|
||||
init: function() {
|
||||
this.on("uploadprogress", function(file, progress) {
|
||||
// console.log("File progress", progress);
|
||||
});
|
||||
this.on("complete", function(file) {
|
||||
console.log("reload file list")
|
||||
loadFileList()
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
formatTime: function(timestamp) {
|
||||
var m = moment(timestamp);
|
||||
if (this.mtimeTypeFromNow) {
|
||||
return m.fromNow();
|
||||
}
|
||||
return m.format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
toggleHidden: function() {
|
||||
this.showHidden = !this.showHidden;
|
||||
},
|
||||
removeAllUploads: function() {
|
||||
this.myDropzone.removeAllFiles();
|
||||
},
|
||||
genInstallURL: function(name) {
|
||||
var urlPath;
|
||||
if (getExtention(name) == "ipa") {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, "/-/ipa/link", location.pathname, name]);
|
||||
} else {
|
||||
urlPath = location.protocol + "//" + pathJoin([location.host, location.pathname, name]);
|
||||
}
|
||||
return encodeURI(urlPath);
|
||||
},
|
||||
genQrcode: function(name, title) {
|
||||
var installURL = this.genInstallURL(name);
|
||||
$("#qrcode-title").html(title || name);
|
||||
$("#qrcode-link").attr("href", installURL);
|
||||
$('#qrcodeCanvas').empty().qrcode({
|
||||
text: installURL
|
||||
});
|
||||
$("#qrcodeRight a").attr("href", encodeURI(location.origin + pathJoin([location.pathname, name])));
|
||||
$("#qrcode-modal").modal("show");
|
||||
},
|
||||
genDownloadURL: function(f) {
|
||||
return location.origin + "/" + f.path;
|
||||
},
|
||||
shouldHaveQrcode: function(name) {
|
||||
return ['apk', 'ipa'].indexOf(getExtention(name)) !== -1;
|
||||
},
|
||||
genFileClass: function(f) {
|
||||
if (f.type == "dir") {
|
||||
if (f.name == '.git') {
|
||||
return 'fa-git-square';
|
||||
}
|
||||
return "fa-folder-open";
|
||||
}
|
||||
var ext = getExtention(f.name);
|
||||
switch (ext) {
|
||||
case "go":
|
||||
case "py":
|
||||
case "js":
|
||||
case "java":
|
||||
case "c":
|
||||
case "cpp":
|
||||
case "h":
|
||||
return "fa-file-code-o";
|
||||
case "pdf":
|
||||
return "fa-file-pdf-o";
|
||||
case "zip":
|
||||
return "fa-file-zip-o";
|
||||
case "mp3":
|
||||
case "wav":
|
||||
return "fa-file-audio-o";
|
||||
case "jpg":
|
||||
case "png":
|
||||
case "gif":
|
||||
case "jpeg":
|
||||
case "tiff":
|
||||
return "fa-file-picture-o";
|
||||
case "ipa":
|
||||
case "dmg":
|
||||
return "fa-apple";
|
||||
case "apk":
|
||||
return "fa-android";
|
||||
case "exe":
|
||||
return "fa-windows";
|
||||
}
|
||||
return "fa-file-text-o"
|
||||
},
|
||||
clickFileOrDir: function(f, e) {
|
||||
// TODO: fix here tomorrow
|
||||
if (f.type == "file") {
|
||||
return true;
|
||||
}
|
||||
var reqPath = pathJoin([location.pathname, f.name]);
|
||||
loadFileOrDir(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
changePath: function(reqPath, e) {
|
||||
loadFileOrDir(reqPath);
|
||||
e.preventDefault()
|
||||
},
|
||||
showInfo: function(f) {
|
||||
console.log(f);
|
||||
$.ajax({
|
||||
url: pathJoin(["/-/info", location.pathname, f.name]),
|
||||
method: "GET",
|
||||
success: function(res) {
|
||||
$("#file-info-title").text(f.name);
|
||||
$("#file-info-content").text(JSON.stringify(res, null, 4));
|
||||
$("#file-info-modal").modal("show");
|
||||
// console.log(JSON.stringify(res, null, 4));
|
||||
}
|
||||
})
|
||||
},
|
||||
deletePathConfirm: function(f, e) {
|
||||
// confirm
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: pathJoin([location.pathname, f.name]),
|
||||
method: 'DELETE',
|
||||
success: function(res) {
|
||||
loadFileList()
|
||||
},
|
||||
error: function(err) {
|
||||
alert(err.responseText);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateBreadcrumb: function() {
|
||||
var pathname = decodeURI(location.pathname || "/");
|
||||
var parts = pathname.split('/');
|
||||
this.breadcrumb = [];
|
||||
if (pathname == "/") {
|
||||
return this.breadcrumb;
|
||||
}
|
||||
var i = 2;
|
||||
for (; i <= parts.length; i += 1) {
|
||||
var name = parts[i - 1];
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
var path = parts.slice(0, i).join('/');
|
||||
this.breadcrumb.push({
|
||||
name: name + (i == parts.length ? ' /' : ''),
|
||||
path: path
|
||||
})
|
||||
}
|
||||
return this.breadcrumb;
|
||||
},
|
||||
loadPreviewFile: function(filepath, e) {
|
||||
if (e) {
|
||||
e.preventDefault() // may be need a switch
|
||||
}
|
||||
var that = this;
|
||||
$.getJSON(pathJoin(['/-/info', location.pathname]))
|
||||
.then(function(res) {
|
||||
console.log(res);
|
||||
that.preview.filename = res.name;
|
||||
that.preview.filesize = res.size;
|
||||
return $.ajax({
|
||||
url: '/' + res.path,
|
||||
dataType: 'text',
|
||||
});
|
||||
})
|
||||
.then(function(res) {
|
||||
console.log(res)
|
||||
that.preview.contentHTML = '<pre>' + res + '</pre>';
|
||||
console.log("Finally")
|
||||
})
|
||||
.done(function(res) {
|
||||
console.log("done", res)
|
||||
});
|
||||
},
|
||||
loadAll: function() {
|
||||
// TODO: move loadFileList here
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
window.onpopstate = function(event) {
|
||||
var pathname = decodeURI(location.pathname)
|
||||
loadFileList()
|
||||
var pathname = decodeURI(location.pathname)
|
||||
loadFileList()
|
||||
}
|
||||
|
||||
function loadDirectory(reqPath) {
|
||||
window.history.pushState({}, "", reqPath);
|
||||
loadFileList(reqPath)
|
||||
function loadFileOrDir(reqPath) {
|
||||
window.history.pushState({}, "", reqPath);
|
||||
loadFileList(reqPath)
|
||||
}
|
||||
|
||||
function loadFileList(pathname) {
|
||||
var pathname = pathname || location.pathname;
|
||||
// console.log("load filelist:", pathname)
|
||||
var pathname = pathname || location.pathname;
|
||||
// console.log("load filelist:", pathname)
|
||||
if (getQueryString("raw") !== "false") { // not a file preview
|
||||
$.ajax({
|
||||
url: pathJoin(["/-/json", pathname]),
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(res) {
|
||||
res.files = _.sortBy(res.files, function(f) {
|
||||
return [f.type, f.name];
|
||||
})
|
||||
url: pathJoin(["/-/json", pathname]),
|
||||
dataType: "json",
|
||||
cache: false,
|
||||
success: function(res) {
|
||||
res.files = _.sortBy(res.files, function(f) {
|
||||
var weight = f.type == 'dir' ? 1000 : 1;
|
||||
return -weight * f.mtime;
|
||||
})
|
||||
|
||||
vm.files = res.files;
|
||||
vm.auth = res.auth;
|
||||
},
|
||||
error: function(err) {
|
||||
console.error(err)
|
||||
},
|
||||
vm.files = res.files;
|
||||
vm.auth = res.auth;
|
||||
},
|
||||
error: function(err) {
|
||||
console.error(err)
|
||||
},
|
||||
});
|
||||
vm.updateBreadcrumb();
|
||||
|
||||
}
|
||||
|
||||
vm.updateBreadcrumb();
|
||||
vm.previewMode = getQueryString("raw") == "false";
|
||||
if (vm.previewMode) {
|
||||
vm.loadPreviewFile();
|
||||
}
|
||||
}
|
||||
|
||||
// For page first loading
|
||||
loadFileList(location.pathname + location.search)
|
||||
|
||||
// update version
|
||||
$.getJSON("/-/sysinfo", function(res) {
|
||||
vm.version = res.version;
|
||||
Vue.filter('fromNow', function(value) {
|
||||
return moment(value).fromNow();
|
||||
})
|
||||
|
||||
Dropzone.options.myDropzone = {
|
||||
paramName: "file",
|
||||
maxFilesize: 1024,
|
||||
addRemoveLinks: true,
|
||||
init: function() {
|
||||
this.on("uploadprogress", function(file, progress) {
|
||||
console.log("File progress", progress);
|
||||
});
|
||||
this.on("complete", function(file) {
|
||||
console.log("reload file list")
|
||||
loadFileList()
|
||||
})
|
||||
}
|
||||
}
|
||||
Vue.filter('formatBytes', function(value) {
|
||||
var bytes = parseFloat(value);
|
||||
if (bytes < 0) return "-";
|
||||
else if (bytes < 1024) return bytes + " B";
|
||||
else if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
|
||||
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
|
||||
else return (bytes / 1073741824).toFixed(1) + " GB";
|
||||
})
|
||||
|
||||
$(function() {
|
||||
$.scrollUp({
|
||||
scrollText: '', // text are defined in css
|
||||
});
|
||||
});
|
||||
Vue.filter('fromNow', function(value) {
|
||||
return moment(value).fromNow();
|
||||
})
|
||||
$.scrollUp({
|
||||
scrollText: '', // text are defined in css
|
||||
});
|
||||
|
||||
// For page first loading
|
||||
loadFileList(location.pathname + location.search)
|
||||
|
||||
// update version
|
||||
$.getJSON("/-/sysinfo", function(res) {
|
||||
vm.version = res.version;
|
||||
})
|
||||
|
||||
var clipboard = new Clipboard('.btn');
|
||||
clipboard.on('success', function(e) {
|
||||
console.info('Action:', e.action);
|
||||
console.info('Text:', e.text);
|
||||
console.info('Trigger:', e.trigger);
|
||||
$(e.trigger)
|
||||
.tooltip('show')
|
||||
.mouseleave(function() {
|
||||
$(this).tooltip('hide');
|
||||
})
|
||||
|
||||
e.clearSelection();
|
||||
});
|
||||
});
|
||||
4
res/js/showdown-1.4.2.min.js
vendored
4
res/js/showdown-1.4.2.min.js
vendored
File diff suppressed because one or more lines are too long
5
res/js/showdown-1.6.4.min.js
vendored
Normal file
5
res/js/showdown-1.6.4.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
testdata/deletable/.ghs.yml
vendored
5
testdata/deletable/.ghs.yml
vendored
@@ -1,2 +1,7 @@
|
||||
upload: true
|
||||
delete: true
|
||||
accessTables:
|
||||
- regex: block.file
|
||||
allow: false
|
||||
- regex: visual.file
|
||||
allow: true
|
||||
0
testdata/deletable/block.file
vendored
Normal file
0
testdata/deletable/block.file
vendored
Normal file
0
testdata/deletable/other.file
vendored
Normal file
0
testdata/deletable/other.file
vendored
Normal file
0
testdata/deletable/visual.file
vendored
Normal file
0
testdata/deletable/visual.file
vendored
Normal file
4
testdata/uploadable/.ghs.yml
vendored
4
testdata/uploadable/.ghs.yml
vendored
@@ -1,2 +1,6 @@
|
||||
---
|
||||
upload: true
|
||||
users:
|
||||
- email: "user@example.com"
|
||||
upload: true
|
||||
delete: true
|
||||
0
testdata/uploadable/sub-upload/.gitkeep
vendored
Normal file
0
testdata/uploadable/sub-upload/.gitkeep
vendored
Normal file
33
utils.go
33
utils.go
@@ -1,28 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func formatSize(file os.FileInfo) string {
|
||||
if file.IsDir() {
|
||||
return "-"
|
||||
}
|
||||
size := file.Size()
|
||||
switch {
|
||||
case size > 1024*1024:
|
||||
return fmt.Sprintf("%.1f MB", float64(size)/1024/1024)
|
||||
case size > 1024:
|
||||
return fmt.Sprintf("%.1f KB", float64(size)/1024)
|
||||
default:
|
||||
return strconv.Itoa(int(size)) + " B"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// func formatSize(file os.FileInfo) string {
|
||||
// if file.IsDir() {
|
||||
// return "-"
|
||||
// }
|
||||
// size := file.Size()
|
||||
// switch {
|
||||
// case size > 1024*1024:
|
||||
// return fmt.Sprintf("%.1f MB", float64(size)/1024/1024)
|
||||
// case size > 1024:
|
||||
// return fmt.Sprintf("%.1f KB", float64(size)/1024)
|
||||
// default:
|
||||
// return strconv.Itoa(int(size)) + " B"
|
||||
// }
|
||||
// return ""
|
||||
// }
|
||||
|
||||
func getRealIP(req *http.Request) string {
|
||||
xip := req.Header.Get("X-Real-IP")
|
||||
|
||||
81
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
81
vendor/github.com/DHowett/go-plist/bplist.go
generated
vendored
@@ -327,12 +327,13 @@ func newBplistGenerator(w io.Writer) *bplistGenerator {
|
||||
}
|
||||
|
||||
type bplistParser struct {
|
||||
reader io.ReadSeeker
|
||||
version int
|
||||
buf []byte
|
||||
objrefs map[uint64]*plistValue
|
||||
offtable []uint64
|
||||
trailer bplistTrailer
|
||||
reader io.ReadSeeker
|
||||
version int
|
||||
buf []byte
|
||||
objrefs map[uint64]*plistValue
|
||||
offtable []uint64
|
||||
trailer bplistTrailer
|
||||
trailerOffset int64
|
||||
}
|
||||
|
||||
func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
@@ -370,7 +371,7 @@ func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
}
|
||||
|
||||
p.objrefs = make(map[uint64]*plistValue)
|
||||
_, err = p.reader.Seek(-32, 2)
|
||||
p.trailerOffset, err = p.reader.Seek(-32, 2)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
@@ -380,6 +381,13 @@ func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if p.trailer.NumObjects > uint64(math.Pow(2, 8*float64(p.trailer.ObjectRefSize))) {
|
||||
panic(fmt.Errorf("binary property list contains more objects (%v) than its object ref size (%v bytes) can support", p.trailer.NumObjects, p.trailer.ObjectRefSize))
|
||||
}
|
||||
|
||||
if p.trailer.TopObject >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("top object index %v is out of range (only %v objects exist)", p.trailer.TopObject, p.trailer.NumObjects))
|
||||
}
|
||||
p.offtable = make([]uint64, p.trailer.NumObjects)
|
||||
|
||||
// SEEK_SET
|
||||
@@ -390,6 +398,9 @@ func (p *bplistParser) parseDocument() (pval *plistValue, parseError error) {
|
||||
|
||||
for i := uint64(0); i < p.trailer.NumObjects; i++ {
|
||||
off := p.readSizedInt(int(p.trailer.OffsetIntSize))
|
||||
if off >= uint64(p.trailerOffset) {
|
||||
panic(fmt.Errorf("object %v starts beyond end of plist trailer (%v vs %v)", i, off, p.trailerOffset))
|
||||
}
|
||||
p.offtable[i] = off
|
||||
}
|
||||
|
||||
@@ -450,8 +461,14 @@ func (p *bplistParser) valueAtOffset(off uint64) *plistValue {
|
||||
|
||||
func (p *bplistParser) parseTagAtOffset(off int64) *plistValue {
|
||||
var tag uint8
|
||||
p.reader.Seek(off, 0)
|
||||
binary.Read(p.reader, binary.BigEndian, &tag)
|
||||
_, err := p.reader.Seek(off, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = binary.Read(p.reader, binary.BigEndian, &tag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
switch tag & 0xF0 {
|
||||
case bpTagNull:
|
||||
@@ -489,12 +506,18 @@ func (p *bplistParser) parseTagAtOffset(off int64) *plistValue {
|
||||
return &plistValue{Date, time}
|
||||
case bpTagData:
|
||||
cnt := p.countForTag(tag)
|
||||
if int64(cnt) > p.trailerOffset-int64(off) {
|
||||
panic(fmt.Errorf("data at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off)))
|
||||
}
|
||||
|
||||
bytes := make([]byte, cnt)
|
||||
binary.Read(p.reader, binary.BigEndian, bytes)
|
||||
return &plistValue{Data, bytes}
|
||||
case bpTagASCIIString, bpTagUTF16String:
|
||||
cnt := p.countForTag(tag)
|
||||
if int64(cnt) > p.trailerOffset-int64(off) {
|
||||
panic(fmt.Errorf("string at %x longer than file (%v bytes, max is %v)", off, cnt, p.trailerOffset-int64(off)))
|
||||
}
|
||||
|
||||
if tag&0xF0 == bpTagASCIIString {
|
||||
bytes := make([]byte, cnt)
|
||||
@@ -516,11 +539,33 @@ func (p *bplistParser) parseTagAtOffset(off int64) *plistValue {
|
||||
indices := make([]uint64, cnt*2)
|
||||
for i := uint64(0); i < cnt*2; i++ {
|
||||
idx := p.readSizedInt(int(p.trailer.ObjectRefSize))
|
||||
|
||||
if idx >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("dictionary contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects))
|
||||
}
|
||||
|
||||
indices[i] = idx
|
||||
}
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
kval := p.valueAtOffset(p.offtable[indices[i]])
|
||||
subvalues[kval.value.(string)] = p.valueAtOffset(p.offtable[indices[i+cnt]])
|
||||
keyOffset := p.offtable[indices[i]]
|
||||
valueOffset := p.offtable[indices[i+cnt]]
|
||||
if keyOffset == uint64(off) {
|
||||
panic(fmt.Errorf("dictionary contains self-referential key %x (index %d)", off, i))
|
||||
}
|
||||
if valueOffset == uint64(off) {
|
||||
panic(fmt.Errorf("dictionary contains self-referential value %x (index %d)", off, i))
|
||||
}
|
||||
|
||||
kval := p.valueAtOffset(keyOffset)
|
||||
if kval == nil || kval.kind != String {
|
||||
panic(fmt.Errorf("dictionary contains non-string key at index %d", i))
|
||||
}
|
||||
|
||||
key, ok := kval.value.(string)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("string-type plist value contains non-string at index %d", i))
|
||||
}
|
||||
subvalues[key] = p.valueAtOffset(valueOffset)
|
||||
}
|
||||
|
||||
return &plistValue{Dictionary, &dictionary{m: subvalues}}
|
||||
@@ -530,10 +575,20 @@ func (p *bplistParser) parseTagAtOffset(off int64) *plistValue {
|
||||
arr := make([]*plistValue, cnt)
|
||||
indices := make([]uint64, cnt)
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
indices[i] = p.readSizedInt(int(p.trailer.ObjectRefSize))
|
||||
idx := p.readSizedInt(int(p.trailer.ObjectRefSize))
|
||||
|
||||
if idx >= p.trailer.NumObjects {
|
||||
panic(fmt.Errorf("array contains invalid entry index %d (max %d)", idx, p.trailer.NumObjects))
|
||||
}
|
||||
|
||||
indices[i] = idx
|
||||
}
|
||||
for i := uint64(0); i < cnt; i++ {
|
||||
arr[i] = p.valueAtOffset(p.offtable[indices[i]])
|
||||
valueOffset := p.offtable[indices[i]]
|
||||
if valueOffset == uint64(off) {
|
||||
panic(fmt.Errorf("array contains self-referential value %x (index %d)", off, i))
|
||||
}
|
||||
arr[i] = p.valueAtOffset(valueOffset)
|
||||
}
|
||||
|
||||
return &plistValue{Array, arr}
|
||||
|
||||
17
vendor/github.com/DHowett/go-plist/fuzz.go
generated
vendored
Normal file
17
vendor/github.com/DHowett/go-plist/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build gofuzz
|
||||
|
||||
package plist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
buf := bytes.NewReader(data)
|
||||
|
||||
var obj interface{}
|
||||
if err := NewDecoder(buf).Decode(&obj); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
6
vendor/github.com/DHowett/go-plist/text.go
generated
vendored
6
vendor/github.com/DHowett/go-plist/text.go
generated
vendored
@@ -477,7 +477,7 @@ func (p *textPlistParser) parseArray() *plistValue {
|
||||
}
|
||||
|
||||
func (p *textPlistParser) parseGNUStepValue(v []byte) *plistValue {
|
||||
if len(v) < 2 {
|
||||
if len(v) < 3 {
|
||||
panic(errors.New("invalid GNUStep extended value"))
|
||||
}
|
||||
typ := v[1]
|
||||
@@ -525,6 +525,10 @@ func (p *textPlistParser) parsePlistValue() *plistValue {
|
||||
}
|
||||
bytes = bytes[:len(bytes)-1]
|
||||
|
||||
if len(bytes) == 0 {
|
||||
panic(errors.New("invalid empty angle-bracketed element"))
|
||||
}
|
||||
|
||||
if bytes[0] == '*' {
|
||||
p.format = GNUStepFormat
|
||||
return p.parseGNUStepValue(bytes)
|
||||
|
||||
7
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
7
vendor/github.com/DHowett/go-plist/util.go
generated
vendored
@@ -16,3 +16,10 @@ func (w *countedWriter) Write(p []byte) (int, error) {
|
||||
func (w *countedWriter) BytesWritten() int {
|
||||
return w.nbytes
|
||||
}
|
||||
|
||||
func unsignedGetBase(s string) (string, int) {
|
||||
if len(s) > 1 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X') {
|
||||
return s[2:], 16
|
||||
}
|
||||
return s, 10
|
||||
}
|
||||
|
||||
10
vendor/github.com/DHowett/go-plist/xml.go
generated
vendored
10
vendor/github.com/DHowett/go-plist/xml.go
generated
vendored
@@ -198,11 +198,17 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) *plistValue {
|
||||
}
|
||||
|
||||
s := string(charData)
|
||||
if len(s) == 0 {
|
||||
panic(errors.New("invalid empty <integer/>"))
|
||||
}
|
||||
|
||||
if s[0] == '-' {
|
||||
n := mustParseInt(string(charData), 10, 64)
|
||||
s, base := unsignedGetBase(s[1:])
|
||||
n := mustParseInt("-"+s, base, 64)
|
||||
return &plistValue{Integer, signedInt{uint64(n), true}}
|
||||
} else {
|
||||
n := mustParseUint(string(charData), 10, 64)
|
||||
s, base := unsignedGetBase(s)
|
||||
n := mustParseUint(s, base, 64)
|
||||
return &plistValue{Integer, signedInt{n, false}}
|
||||
}
|
||||
case "real":
|
||||
|
||||
139
vendor/github.com/alecthomas/kingpin/README.md
generated
vendored
139
vendor/github.com/alecthomas/kingpin/README.md
generated
vendored
@@ -1,4 +1,4 @@
|
||||
# Kingpin - A Go (golang) command line and flag parser [](https://travis-ci.org/alecthomas/kingpin)
|
||||
# Kingpin - A Go (golang) command line and flag parser [](http://godoc.org/github.com/alecthomas/kingpin) [](https://travis-ci.org/alecthomas/kingpin)
|
||||
|
||||
<!-- MarkdownTOC -->
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
- [Displaying errors and usage information](#displaying-errors-and-usage-information)
|
||||
- [Sub-commands](#sub-commands)
|
||||
- [Custom Parsers](#custom-parsers)
|
||||
- [Repeatable flags](#repeatable-flags)
|
||||
- [Boolean Values](#boolean-values)
|
||||
- [Default Values](#default-values)
|
||||
- [Place-holders in Help](#place-holders-in-help)
|
||||
- [Consuming all remaining arguments](#consuming-all-remaining-arguments)
|
||||
- [Bash/ZSH Shell Completion](#bashzsh-shell-completion)
|
||||
- [Supporting -h for help](#supporting--h-for-help)
|
||||
- [Custom help](#custom-help)
|
||||
|
||||
@@ -51,7 +54,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
More [examples](https://github.com/alecthomas/kingpin/tree/master/examples) are available.
|
||||
More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available.
|
||||
|
||||
Second to parsing, providing the user with useful help is probably the most
|
||||
important thing a command-line parser does. Kingpin tries to provide detailed
|
||||
@@ -71,7 +74,7 @@ contextual help if `--help` is encountered at any point in the command line
|
||||
- POSIX-style short flag combining (`-a -b` -> `-ab`).
|
||||
- Short-flag+parameter combining (`-a parm` -> `-aparm`).
|
||||
- Read command-line from files (`@<file>`).
|
||||
- Automatically generate man pages (`--man-page`).
|
||||
- Automatically generate man pages (`--help-man`).
|
||||
|
||||
## User-visible changes between v1 and v2
|
||||
|
||||
@@ -135,7 +138,7 @@ $ go get gopkg.in/alecthomas/kingpin.v1
|
||||
- *2015-09-19* -- Stable v2.1.0 release.
|
||||
- Added `command.Default()` to specify a default command to use if no other
|
||||
command matches. This allows for convenient user shortcuts.
|
||||
- Exposed `HelpFlag` and `VersionFlag` for further cusomisation.
|
||||
- Exposed `HelpFlag` and `VersionFlag` for further customisation.
|
||||
- `Action()` and `PreAction()` added and both now support an arbitrary
|
||||
number of callbacks.
|
||||
- `kingpin.SeparateOptionalFlagsUsageTemplate`.
|
||||
@@ -421,12 +424,26 @@ You would use it like so:
|
||||
headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H'))
|
||||
```
|
||||
|
||||
### Repeatable flags
|
||||
|
||||
Depending on the `Value` they hold, some flags may be repeated. The
|
||||
`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()`
|
||||
multiple times or if an error should be raised if several values are passed.
|
||||
|
||||
The built-in `Value`s returning slices and maps, as well as `Counter` are
|
||||
examples of `Value`s that make a flag repeatable.
|
||||
|
||||
### Boolean values
|
||||
|
||||
Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement:
|
||||
`--<name>` and `--no-<name>`.
|
||||
|
||||
### Default Values
|
||||
|
||||
The default value is the zero value for a type. This can be overridden with
|
||||
the `Default(value)` function on flags and arguments. This function accepts a
|
||||
string, which is parsed by the value itself, so it *must* be compliant with
|
||||
the format expected.
|
||||
the `Default(value...)` function on flags and arguments. This function accepts
|
||||
one or several strings, which are parsed by the value itself, so they *must*
|
||||
be compliant with the format expected.
|
||||
|
||||
### Place-holders in Help
|
||||
|
||||
@@ -451,13 +468,13 @@ IP addresses as positional arguments:
|
||||
|
||||
./cmd ping 10.1.1.1 192.168.1.1
|
||||
|
||||
Kingpin supports this by having `Value` provide a `IsCumulative() bool`
|
||||
function. If this function exists and returns true, the value parser will be
|
||||
called repeatedly for every remaining argument.
|
||||
Such arguments are similar to [repeatable flags](#repeatable-flags), but for
|
||||
arguments. Therefore they use the same `IsCumulative() bool` function on the
|
||||
underlying `Value`, so the built-in `Value`s for which the `Set()` function
|
||||
can be called several times will consume multiple arguments.
|
||||
|
||||
Examples of this are the `Strings()` and `StringMap()` values.
|
||||
|
||||
To implement the above example we might do something like this:
|
||||
To implement the above example with a custom `Value`, we might do something
|
||||
like this:
|
||||
|
||||
```go
|
||||
type ipList []net.IP
|
||||
@@ -492,9 +509,103 @@ And use it like so:
|
||||
ips := IPList(kingpin.Arg("ips", "IP addresses to ping."))
|
||||
```
|
||||
|
||||
### Bash/ZSH Shell Completion
|
||||
|
||||
By default, all flags and commands/subcommands generate completions
|
||||
internally.
|
||||
|
||||
Out of the box, CLI tools using kingpin should be able to take advantage
|
||||
of completion hinting for flags and commands. By specifying
|
||||
`--completion-bash` as the first argument, your CLI tool will show
|
||||
possible subcommands. By ending your argv with `--`, hints for flags
|
||||
will be shown.
|
||||
|
||||
To allow your end users to take advantage you must package a
|
||||
`/etc/bash_completion.d` script with your distribution (or the equivalent
|
||||
for your target platform/shell). An alternative is to instruct your end
|
||||
user to source a script from their `bash_profile` (or equivalent).
|
||||
|
||||
Fortunately Kingpin makes it easy to generate or source a script for use
|
||||
with end users shells. `./yourtool --completion-script-bash` and
|
||||
`./yourtool --completion-script-zsh` will generate these scripts for you.
|
||||
|
||||
**Installation by Package**
|
||||
|
||||
For the best user experience, you should bundle your pre-created
|
||||
completion script with your CLI tool and install it inside
|
||||
`/etc/bash_completion.d` (or equivalent). A good suggestion is to add
|
||||
this as an automated step to your build pipeline, in the implementation
|
||||
is improved for bug fixed.
|
||||
|
||||
**Installation by `bash_profile`**
|
||||
|
||||
Alternatively, instruct your users to add an additional statement to
|
||||
their `bash_profile` (or equivalent):
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-bash)"
|
||||
```
|
||||
|
||||
Or for ZSH
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-zsh)"
|
||||
```
|
||||
|
||||
#### Additional API
|
||||
To provide more flexibility, a completion option API has been
|
||||
exposed for flags to allow user defined completion options, to extend
|
||||
completions further than just EnumVar/Enum.
|
||||
|
||||
|
||||
**Provide Static Options**
|
||||
|
||||
When using an `Enum` or `EnumVar`, users are limited to only the options
|
||||
given. Maybe we wish to hint possible options to the user, but also
|
||||
allow them to provide their own custom option. `HintOptions` gives
|
||||
this functionality to flags.
|
||||
|
||||
```
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("port", "Provide a port to connect to").
|
||||
Required().
|
||||
HintOptions("80", "443", "8080").
|
||||
IntVar(&c.port)
|
||||
```
|
||||
|
||||
**Provide Dynamic Options**
|
||||
Consider the case that you needed to read a local database or a file to
|
||||
provide suggestions. You can dynamically generate the options
|
||||
|
||||
```
|
||||
func listHosts(args []string) []string {
|
||||
// Provide a dynamic list of hosts from a hosts file or otherwise
|
||||
// for bash completion. In this example we simply return static slice.
|
||||
|
||||
// You could use this functionality to reach into a hosts file to provide
|
||||
// completion for a list of known hosts.
|
||||
return []string{"sshhost.example", "webhost.example", "ftphost.example"}
|
||||
}
|
||||
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("flag-1", "").HintAction(listHosts).String()
|
||||
```
|
||||
|
||||
**EnumVar/Enum**
|
||||
When using `Enum` or `EnumVar`, any provided options will be automatically
|
||||
used for bash autocompletion. However, if you wish to provide a subset or
|
||||
different options, you can use `HintOptions` or `HintAction` which will override
|
||||
the default completion options for `Enum`/`EnumVar`.
|
||||
|
||||
|
||||
**Examples**
|
||||
You can see an in depth example of the completion API within
|
||||
`examples/completion/main.go`
|
||||
|
||||
|
||||
### Supporting -h for help
|
||||
|
||||
`kingpin.CommandLine.HelpFlag.Short('-h')`
|
||||
`kingpin.CommandLine.HelpFlag.Short('h')`
|
||||
|
||||
### Custom help
|
||||
|
||||
|
||||
237
vendor/github.com/alecthomas/kingpin/app.go
generated
vendored
237
vendor/github.com/alecthomas/kingpin/app.go
generated
vendored
@@ -21,10 +21,7 @@ type ApplicationValidator func(*Application) error
|
||||
// An Application contains the definitions of flags, arguments and commands
|
||||
// for an application.
|
||||
type Application struct {
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
actionMixin
|
||||
cmdMixin
|
||||
initialized bool
|
||||
|
||||
Name string
|
||||
@@ -38,6 +35,7 @@ type Application struct {
|
||||
terminate func(status int) // See Terminate()
|
||||
noInterspersed bool // can flags be interspersed with args (or must they come first)
|
||||
defaultEnvars bool
|
||||
completion bool
|
||||
|
||||
// Help flag. Exposed for user customisation.
|
||||
HelpFlag *FlagClause
|
||||
@@ -50,19 +48,23 @@ type Application struct {
|
||||
// New creates a new Kingpin application instance.
|
||||
func New(name, help string) *Application {
|
||||
a := &Application{
|
||||
flagGroup: newFlagGroup(),
|
||||
argGroup: newArgGroup(),
|
||||
Name: name,
|
||||
Help: help,
|
||||
writer: os.Stderr,
|
||||
usageTemplate: DefaultUsageTemplate,
|
||||
terminate: os.Exit,
|
||||
}
|
||||
a.flagGroup = newFlagGroup()
|
||||
a.argGroup = newArgGroup()
|
||||
a.cmdGroup = newCmdGroup(a)
|
||||
a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
|
||||
a.HelpFlag.Bool()
|
||||
a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool()
|
||||
a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool()
|
||||
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion)
|
||||
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool()
|
||||
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -84,6 +86,24 @@ func (a *Application) generateManPage(c *ParseContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateZSHCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultEnvars configures all flags (that do not already have an associated
|
||||
// envar) to use a default environment variable in the form "<app>_<flag>".
|
||||
//
|
||||
@@ -145,17 +165,48 @@ func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseCon
|
||||
// This will populate all flag and argument values, call all callbacks, and so
|
||||
// on.
|
||||
func (a *Application) Parse(args []string) (command string, err error) {
|
||||
context, err := a.ParseContext(args)
|
||||
if err != nil {
|
||||
|
||||
context, parseErr := a.ParseContext(args)
|
||||
selected := []string{}
|
||||
var setValuesErr error
|
||||
|
||||
if context == nil {
|
||||
// Since we do not throw error immediately, there could be a case
|
||||
// where a context returns nil. Protect against that.
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
if err = a.setDefaults(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.maybeHelp(context)
|
||||
if !context.EOL() {
|
||||
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||
|
||||
selected, setValuesErr = a.setValues(context)
|
||||
|
||||
if err = a.applyPreActions(context, !a.completion); err != nil {
|
||||
return "", err
|
||||
}
|
||||
command, err = a.execute(context)
|
||||
if err == ErrCommandNotSpecified {
|
||||
a.writeUsage(context, nil)
|
||||
|
||||
if a.completion {
|
||||
a.generateBashCompletion(context)
|
||||
a.terminate(0)
|
||||
} else {
|
||||
if parseErr != nil {
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
a.maybeHelp(context)
|
||||
if !context.EOL() {
|
||||
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||
}
|
||||
|
||||
if setValuesErr != nil {
|
||||
return "", setValuesErr
|
||||
}
|
||||
|
||||
command, err = a.execute(context, selected)
|
||||
if err == ErrCommandNotSpecified {
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
@@ -173,34 +224,13 @@ func (a *Application) writeUsage(context *ParseContext, err error) {
|
||||
func (a *Application) maybeHelp(context *ParseContext) {
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag {
|
||||
// Re-parse the command-line ignoring defaults, so that help works correctly.
|
||||
context, _ = a.parseContext(true, context.rawArgs)
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findCommandFromArgs finds a command (if any) from the given command line arguments.
|
||||
func (a *Application) findCommandFromArgs(args []string) (command string, err error) {
|
||||
if err := a.init(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
context := tokenize(args, false)
|
||||
if _, err := a.parse(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return a.findCommandFromContext(context), nil
|
||||
}
|
||||
|
||||
// findCommandFromContext finds a command (if any) from a parsed context.
|
||||
func (a *Application) findCommandFromContext(context *ParseContext) string {
|
||||
commands := []string{}
|
||||
for _, element := range context.Elements {
|
||||
if c, ok := element.Clause.(*CmdClause); ok {
|
||||
commands = append(commands, c.name)
|
||||
}
|
||||
}
|
||||
return strings.Join(commands, " ")
|
||||
}
|
||||
|
||||
// Version adds a --version flag for displaying the application version.
|
||||
func (a *Application) Version(version string) *Application {
|
||||
a.version = version
|
||||
@@ -325,22 +355,8 @@ func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) execute(context *ParseContext) (string, error) {
|
||||
func (a *Application) execute(context *ParseContext, selected []string) (string, error) {
|
||||
var err error
|
||||
selected := []string{}
|
||||
|
||||
if err = a.setDefaults(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
selected, err = a.setValues(context)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyPreActions(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.validateRequired(context); err != nil {
|
||||
return "", err
|
||||
@@ -379,22 +395,16 @@ func (a *Application) setDefaults(context *ParseContext) error {
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
// Set defaults, if any.
|
||||
if flag.defaultValue != "" {
|
||||
if err := flag.value.Set(flag.defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := flag.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
// Set defaults, if any.
|
||||
if arg.defaultValue != "" {
|
||||
if err := arg.value.Set(arg.defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := arg.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -429,7 +439,7 @@ func (a *Application) validateRequired(context *ParseContext) error {
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if arg.required {
|
||||
if arg.needsValue() {
|
||||
return fmt.Errorf("required argument '%s' not provided", arg.name)
|
||||
}
|
||||
}
|
||||
@@ -439,13 +449,22 @@ func (a *Application) validateRequired(context *ParseContext) error {
|
||||
|
||||
func (a *Application) setValues(context *ParseContext) (selected []string, err error) {
|
||||
// Set all arg and flag values.
|
||||
var lastCmd *CmdClause
|
||||
var (
|
||||
lastCmd *CmdClause
|
||||
flagSet = map[string]struct{}{}
|
||||
)
|
||||
for _, element := range context.Elements {
|
||||
switch clause := element.Clause.(type) {
|
||||
case *FlagClause:
|
||||
if _, ok := flagSet[clause.name]; ok {
|
||||
if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name)
|
||||
}
|
||||
}
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
flagSet[clause.name] = struct{}{}
|
||||
|
||||
case *ArgClause:
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
@@ -486,18 +505,21 @@ func (a *Application) applyValidators(context *ParseContext) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Application) applyPreActions(context *ParseContext) error {
|
||||
func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error {
|
||||
if err := a.actionMixin.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyPreActions(context); err != nil {
|
||||
return err
|
||||
if dispatch {
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -558,6 +580,83 @@ func (a *Application) FatalIfError(err error, format string, args ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) completionOptions(context *ParseContext) []string {
|
||||
args := context.rawArgs
|
||||
|
||||
var (
|
||||
currArg string
|
||||
prevArg string
|
||||
target cmdMixin
|
||||
)
|
||||
|
||||
numArgs := len(args)
|
||||
if numArgs > 1 {
|
||||
args = args[1:]
|
||||
currArg = args[len(args)-1]
|
||||
}
|
||||
if numArgs > 2 {
|
||||
prevArg = args[len(args)-2]
|
||||
}
|
||||
|
||||
target = a.cmdMixin
|
||||
if context.SelectedCommand != nil {
|
||||
// A subcommand was in use. We will use it as the target
|
||||
target = context.SelectedCommand.cmdMixin
|
||||
}
|
||||
|
||||
if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") {
|
||||
// Perform completion for A flag. The last/current argument started with "-"
|
||||
var (
|
||||
flagName string // The name of a flag if given (could be half complete)
|
||||
flagValue string // The value assigned to a flag (if given) (could be half complete)
|
||||
)
|
||||
|
||||
if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag value
|
||||
// Wont Match: ./myApp --flag --
|
||||
flagName = prevArg[2:] // Strip the "--"
|
||||
flagValue = currArg
|
||||
} else if strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag --
|
||||
// Matches: ./myApp --flag somevalue --
|
||||
// Matches: ./myApp --
|
||||
flagName = currArg[2:] // Strip the "--"
|
||||
}
|
||||
|
||||
options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue)
|
||||
if valueMatched {
|
||||
// Value Matched. Show cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
// Add top level flags if we're not at the top level and no match was found.
|
||||
if context.SelectedCommand != nil && flagMatched == false {
|
||||
topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue)
|
||||
if topValueMatched {
|
||||
// Value Matched. Back to cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
if topFlagMatched {
|
||||
// Top level had a flag which matched the input. Return it's options.
|
||||
options = topOptions
|
||||
} else {
|
||||
// Add top level flags
|
||||
options = append(options, topOptions...)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Perform completion for sub commands and arguments.
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletion(context *ParseContext) {
|
||||
options := a.completionOptions(context)
|
||||
fmt.Printf("%s", strings.Join(options, "\n"))
|
||||
}
|
||||
|
||||
func envarTransform(name string) string {
|
||||
return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_"))
|
||||
}
|
||||
|
||||
98
vendor/github.com/alecthomas/kingpin/args.go
generated
vendored
98
vendor/github.com/alecthomas/kingpin/args.go
generated
vendored
@@ -1,6 +1,8 @@
|
||||
package kingpin
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type argGroup struct {
|
||||
args []*ArgClause
|
||||
@@ -14,6 +16,19 @@ func (a *argGroup) have() bool {
|
||||
return len(a.args) > 0
|
||||
}
|
||||
|
||||
// GetArg gets an argument definition.
|
||||
//
|
||||
// This allows existing arguments to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (a *argGroup) GetArg(name string) *ArgClause {
|
||||
for _, arg := range a.args {
|
||||
if arg.name == name {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argGroup) Arg(name, help string) *ArgClause {
|
||||
arg := newArg(name, help)
|
||||
a.args = append(a.args, arg)
|
||||
@@ -51,10 +66,12 @@ func (a *argGroup) init() error {
|
||||
type ArgClause struct {
|
||||
actionMixin
|
||||
parserMixin
|
||||
name string
|
||||
help string
|
||||
defaultValue string
|
||||
required bool
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
help string
|
||||
defaultValues []string
|
||||
required bool
|
||||
}
|
||||
|
||||
func newArg(name, help string) *ArgClause {
|
||||
@@ -65,6 +82,38 @@ func newArg(name, help string) *ArgClause {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) setDefault() error {
|
||||
if a.HasEnvarValue() {
|
||||
if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return a.value.Set(a.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range a.GetSplitEnvarValue() {
|
||||
if err := a.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(a.defaultValues) > 0 {
|
||||
for _, defaultValue := range a.defaultValues {
|
||||
if err := a.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ArgClause) needsValue() bool {
|
||||
haveDefault := len(a.defaultValues) > 0
|
||||
return a.required && !(haveDefault || a.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (a *ArgClause) consumesRemainder() bool {
|
||||
if r, ok := a.value.(remainderArg); ok {
|
||||
return r.IsCumulative()
|
||||
@@ -78,9 +127,26 @@ func (a *ArgClause) Required() *ArgClause {
|
||||
return a
|
||||
}
|
||||
|
||||
// Default value for this argument. It *must* be parseable by the value of the argument.
|
||||
func (a *ArgClause) Default(value string) *ArgClause {
|
||||
a.defaultValue = value
|
||||
// Default values for this argument. They *must* be parseable by the value of the argument.
|
||||
func (a *ArgClause) Default(values ...string) *ArgClause {
|
||||
a.defaultValues = values
|
||||
return a
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (a *ArgClause) Envar(name string) *ArgClause {
|
||||
a.envar = name
|
||||
a.noEnvar = false
|
||||
return a
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (a *ArgClause) NoEnvar() *ArgClause {
|
||||
a.envar = ""
|
||||
a.noEnvar = true
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -94,8 +160,22 @@ func (a *ArgClause) PreAction(action Action) *ArgClause {
|
||||
return a
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the arg to provide completions
|
||||
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) init() error {
|
||||
if a.required && a.defaultValue != "" {
|
||||
if a.required && len(a.defaultValues) > 0 {
|
||||
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
|
||||
}
|
||||
if a.value == nil {
|
||||
|
||||
145
vendor/github.com/alecthomas/kingpin/cmd.go
generated
vendored
145
vendor/github.com/alecthomas/kingpin/cmd.go
generated
vendored
@@ -5,6 +5,92 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type cmdMixin struct {
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
actionMixin
|
||||
}
|
||||
|
||||
// CmdCompletion returns completion options for arguments, if that's where
|
||||
// parsing left off, or commands if there aren't any unsatisfied args.
|
||||
func (c *cmdMixin) CmdCompletion(context *ParseContext) []string {
|
||||
var options []string
|
||||
|
||||
// Count args already satisfied - we won't complete those, and add any
|
||||
// default commands' alternatives, since they weren't listed explicitly
|
||||
// and the user may want to explicitly list something else.
|
||||
argsSatisfied := 0
|
||||
for _, el := range context.Elements {
|
||||
switch clause := el.Clause.(type) {
|
||||
case *ArgClause:
|
||||
if el.Value != nil && *el.Value != "" {
|
||||
argsSatisfied++
|
||||
}
|
||||
case *CmdClause:
|
||||
options = append(options, clause.completionAlts...)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if argsSatisfied < len(c.argGroup.args) {
|
||||
// Since not all args have been satisfied, show options for the current one
|
||||
options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...)
|
||||
} else {
|
||||
// If all args are satisfied, then go back to completing commands
|
||||
for _, cmd := range c.cmdGroup.commandOrder {
|
||||
if !cmd.hidden {
|
||||
options = append(options, cmd.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) {
|
||||
// Check if flagName matches a known flag.
|
||||
// If it does, show the options for the flag
|
||||
// Otherwise, show all flags
|
||||
|
||||
options := []string{}
|
||||
|
||||
for _, flag := range c.flagGroup.flagOrder {
|
||||
// Loop through each flag and determine if a match exists
|
||||
if flag.name == flagName {
|
||||
// User typed entire flag. Need to look for flag options.
|
||||
options = flag.resolveCompletions()
|
||||
if len(options) == 0 {
|
||||
// No Options to Choose From, Assume Match.
|
||||
return options, true, true
|
||||
}
|
||||
|
||||
// Loop options to find if the user specified value matches
|
||||
isPrefix := false
|
||||
matched := false
|
||||
|
||||
for _, opt := range options {
|
||||
if flagValue == opt {
|
||||
matched = true
|
||||
} else if strings.HasPrefix(opt, flagValue) {
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
|
||||
// Matched Flag Directly
|
||||
// Flag Value Not Prefixed, and Matched Directly
|
||||
return options, true, !isPrefix && matched
|
||||
}
|
||||
|
||||
if !flag.hidden {
|
||||
options = append(options, "--"+flag.name)
|
||||
}
|
||||
}
|
||||
// No Flag directly matched.
|
||||
return options, false, false
|
||||
|
||||
}
|
||||
|
||||
type cmdGroup struct {
|
||||
app *Application
|
||||
parent *CmdClause
|
||||
@@ -21,6 +107,22 @@ func (c *cmdGroup) defaultSubcommand() *CmdClause {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) cmdNames() []string {
|
||||
names := make([]string, 0, len(c.commandOrder))
|
||||
for _, cmd := range c.commandOrder {
|
||||
names = append(names, cmd.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GetArg gets a command definition.
|
||||
//
|
||||
// This allows existing commands to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (c *cmdGroup) GetCommand(name string) *CmdClause {
|
||||
return c.commands[name]
|
||||
}
|
||||
|
||||
func newCmdGroup(app *Application) *cmdGroup {
|
||||
return &cmdGroup{
|
||||
app: app,
|
||||
@@ -59,6 +161,12 @@ func (c *cmdGroup) init() error {
|
||||
return fmt.Errorf("duplicate command %q", cmd.name)
|
||||
}
|
||||
seen[cmd.name] = true
|
||||
for _, alias := range cmd.aliases {
|
||||
if seen[alias] {
|
||||
return fmt.Errorf("alias duplicates existing command %q", alias)
|
||||
}
|
||||
c.commands[alias] = cmd
|
||||
}
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,27 +186,32 @@ type CmdClauseValidator func(*CmdClause) error
|
||||
// A CmdClause is a single top-level command. It encapsulates a set of flags
|
||||
// and either subcommands or positional arguments.
|
||||
type CmdClause struct {
|
||||
actionMixin
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
app *Application
|
||||
name string
|
||||
help string
|
||||
isDefault bool
|
||||
validator CmdClauseValidator
|
||||
hidden bool
|
||||
cmdMixin
|
||||
app *Application
|
||||
name string
|
||||
aliases []string
|
||||
help string
|
||||
isDefault bool
|
||||
validator CmdClauseValidator
|
||||
hidden bool
|
||||
completionAlts []string
|
||||
}
|
||||
|
||||
func newCommand(app *Application, name, help string) *CmdClause {
|
||||
c := &CmdClause{
|
||||
flagGroup: newFlagGroup(),
|
||||
argGroup: newArgGroup(),
|
||||
cmdGroup: newCmdGroup(app),
|
||||
app: app,
|
||||
name: name,
|
||||
help: help,
|
||||
app: app,
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
c.flagGroup = newFlagGroup()
|
||||
c.argGroup = newArgGroup()
|
||||
c.cmdGroup = newCmdGroup(app)
|
||||
return c
|
||||
}
|
||||
|
||||
// Add an Alias for this command.
|
||||
func (c *CmdClause) Alias(name string) *CmdClause {
|
||||
c.aliases = append(c.aliases, name)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
33
vendor/github.com/alecthomas/kingpin/completions.go
generated
vendored
Normal file
33
vendor/github.com/alecthomas/kingpin/completions.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package kingpin
|
||||
|
||||
// HintAction is a function type who is expected to return a slice of possible
|
||||
// command line arguments.
|
||||
type HintAction func() []string
|
||||
type completionsMixin struct {
|
||||
hintActions []HintAction
|
||||
builtinHintActions []HintAction
|
||||
}
|
||||
|
||||
func (a *completionsMixin) addHintAction(action HintAction) {
|
||||
a.hintActions = append(a.hintActions, action)
|
||||
}
|
||||
|
||||
// Allow adding of HintActions which are added internally, ie, EnumVar
|
||||
func (a *completionsMixin) addHintActionBuiltin(action HintAction) {
|
||||
a.builtinHintActions = append(a.builtinHintActions, action)
|
||||
}
|
||||
|
||||
func (a *completionsMixin) resolveCompletions() []string {
|
||||
var hints []string
|
||||
|
||||
options := a.builtinHintActions
|
||||
if len(a.hintActions) > 0 {
|
||||
// User specified their own hintActions. Use those instead.
|
||||
options = a.hintActions
|
||||
}
|
||||
|
||||
for _, hintAction := range options {
|
||||
hints = append(hints, hintAction()...)
|
||||
}
|
||||
return hints
|
||||
}
|
||||
45
vendor/github.com/alecthomas/kingpin/envar.go
generated
vendored
Normal file
45
vendor/github.com/alecthomas/kingpin/envar.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envVarValuesSeparator = "\r?\n"
|
||||
envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$")
|
||||
envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator)
|
||||
)
|
||||
|
||||
type envarMixin struct {
|
||||
envar string
|
||||
noEnvar bool
|
||||
}
|
||||
|
||||
func (e *envarMixin) HasEnvarValue() bool {
|
||||
return e.GetEnvarValue() != ""
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetEnvarValue() string {
|
||||
if e.noEnvar || e.envar == "" {
|
||||
return ""
|
||||
}
|
||||
return os.Getenv(e.envar)
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetSplitEnvarValue() []string {
|
||||
values := make([]string, 0)
|
||||
|
||||
envarValue := e.GetEnvarValue()
|
||||
if envarValue == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
// Split by new line to extract multiple values, if any.
|
||||
trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "")
|
||||
for _, value := range envVarValuesSplitter.Split(trimmed, -1) {
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
165
vendor/github.com/alecthomas/kingpin/flags.go
generated
vendored
165
vendor/github.com/alecthomas/kingpin/flags.go
generated
vendored
@@ -2,7 +2,6 @@ package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -14,19 +13,17 @@ type flagGroup struct {
|
||||
|
||||
func newFlagGroup() *flagGroup {
|
||||
return &flagGroup{
|
||||
short: make(map[string]*FlagClause),
|
||||
long: make(map[string]*FlagClause),
|
||||
short: map[string]*FlagClause{},
|
||||
long: map[string]*FlagClause{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *flagGroup) merge(o *flagGroup) {
|
||||
for _, flag := range o.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
f.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
f.long[flag.name] = flag
|
||||
f.flagOrder = append(f.flagOrder, flag)
|
||||
}
|
||||
// GetFlag gets a flag definition.
|
||||
//
|
||||
// This allows existing flags to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
||||
return f.long[name]
|
||||
}
|
||||
|
||||
// Flag defines a new flag with the given long name and help.
|
||||
@@ -38,6 +35,9 @@ func (f *flagGroup) Flag(name, help string) *FlagClause {
|
||||
}
|
||||
|
||||
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||
if err := f.checkDuplicates(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, flag := range f.long {
|
||||
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
||||
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
||||
@@ -52,6 +52,24 @@ func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) checkDuplicates() error {
|
||||
seenShort := map[byte]bool{}
|
||||
seenLong := map[string]bool{}
|
||||
for _, flag := range f.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := seenShort[flag.shorthand]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
seenShort[flag.shorthand] = true
|
||||
}
|
||||
if _, ok := seenLong[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
seenLong[flag.name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
||||
var token *Token
|
||||
|
||||
@@ -71,11 +89,14 @@ loop:
|
||||
|
||||
name := token.Value
|
||||
if token.Type == TokenLong {
|
||||
if strings.HasPrefix(name, "no-") {
|
||||
name = name[3:]
|
||||
invert = true
|
||||
}
|
||||
flag, ok = f.long[name]
|
||||
if !ok {
|
||||
if strings.HasPrefix(name, "no-") {
|
||||
name = name[3:]
|
||||
invert = true
|
||||
}
|
||||
flag, ok = f.long[name]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
@@ -119,28 +140,18 @@ loop:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) visibleFlags() int {
|
||||
count := 0
|
||||
for _, flag := range f.long {
|
||||
if !flag.hidden {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// FlagClause is a fluid interface used to build flags.
|
||||
type FlagClause struct {
|
||||
parserMixin
|
||||
actionMixin
|
||||
name string
|
||||
shorthand byte
|
||||
help string
|
||||
envar string
|
||||
noEnvar bool
|
||||
defaultValue string
|
||||
placeholder string
|
||||
hidden bool
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
shorthand byte
|
||||
help string
|
||||
defaultValues []string
|
||||
placeholder string
|
||||
hidden bool
|
||||
}
|
||||
|
||||
func newFlag(name, help string) *FlagClause {
|
||||
@@ -151,34 +162,47 @@ func newFlag(name, help string) *FlagClause {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) needsValue() bool {
|
||||
return f.required && f.defaultValue == ""
|
||||
func (f *FlagClause) setDefault() error {
|
||||
if f.HasEnvarValue() {
|
||||
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return f.value.Set(f.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range f.GetSplitEnvarValue() {
|
||||
if err := f.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.defaultValues) > 0 {
|
||||
for _, defaultValue := range f.defaultValues {
|
||||
if err := f.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagClause) formatPlaceHolder() string {
|
||||
if f.placeholder != "" {
|
||||
return f.placeholder
|
||||
}
|
||||
if f.defaultValue != "" {
|
||||
if _, ok := f.value.(*stringValue); ok {
|
||||
return fmt.Sprintf("%q", f.defaultValue)
|
||||
}
|
||||
return f.defaultValue
|
||||
}
|
||||
return strings.ToUpper(f.name)
|
||||
func (f *FlagClause) needsValue() bool {
|
||||
haveDefault := len(f.defaultValues) > 0
|
||||
return f.required && !(haveDefault || f.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (f *FlagClause) init() error {
|
||||
if f.required && f.defaultValue != "" {
|
||||
if f.required && len(f.defaultValues) > 0 {
|
||||
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
||||
}
|
||||
if f.value == nil {
|
||||
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
||||
}
|
||||
if !f.noEnvar && f.envar != "" {
|
||||
if v := os.Getenv(f.envar); v != "" {
|
||||
f.defaultValue = v
|
||||
}
|
||||
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
||||
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -194,9 +218,37 @@ func (f *FlagClause) PreAction(action Action) *FlagClause {
|
||||
return f
|
||||
}
|
||||
|
||||
// Default value for this flag. It *must* be parseable by the value of the flag.
|
||||
func (f *FlagClause) Default(value string) *FlagClause {
|
||||
f.defaultValue = value
|
||||
// HintAction registers a HintAction (function) for the flag to provide completions
|
||||
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
||||
a.parserMixin.EnumVar(target, options...)
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FlagClause) Enum(options ...string) (target *string) {
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
return a.parserMixin.Enum(options...)
|
||||
}
|
||||
|
||||
// Default values for this flag. They *must* be parseable by the value of the flag.
|
||||
func (f *FlagClause) Default(values ...string) *FlagClause {
|
||||
f.defaultValues = values
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -205,8 +257,9 @@ func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
||||
return f.Envar(envar)
|
||||
}
|
||||
|
||||
// Envar overrides the default value for a flag from an environment variable,
|
||||
// if it is set.
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (f *FlagClause) Envar(name string) *FlagClause {
|
||||
f.envar = name
|
||||
f.noEnvar = false
|
||||
|
||||
2
vendor/github.com/alecthomas/kingpin/guesswidth.go
generated
vendored
2
vendor/github.com/alecthomas/kingpin/guesswidth.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// +build !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
|
||||
2
vendor/github.com/alecthomas/kingpin/guesswidth_unix.go
generated
vendored
2
vendor/github.com/alecthomas/kingpin/guesswidth_unix.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
// +build linux freebsd darwin dragonfly netbsd openbsd
|
||||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
|
||||
24
vendor/github.com/alecthomas/kingpin/model.go
generated
vendored
24
vendor/github.com/alecthomas/kingpin/model.go
generated
vendored
@@ -37,7 +37,7 @@ type FlagModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Short rune
|
||||
Default string
|
||||
Default []string
|
||||
Envar string
|
||||
PlaceHolder string
|
||||
Required bool
|
||||
@@ -60,11 +60,15 @@ func (f *FlagModel) FormatPlaceHolder() string {
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder
|
||||
}
|
||||
if f.Default != "" {
|
||||
if _, ok := f.Value.(*stringValue); ok {
|
||||
return strconv.Quote(f.Default)
|
||||
if len(f.Default) > 0 {
|
||||
ellipsis := ""
|
||||
if len(f.Default) > 1 {
|
||||
ellipsis = "..."
|
||||
}
|
||||
return f.Default
|
||||
if _, ok := f.Value.(*stringValue); ok {
|
||||
return strconv.Quote(f.Default[0]) + ellipsis
|
||||
}
|
||||
return f.Default[0] + ellipsis
|
||||
}
|
||||
return strings.ToUpper(f.Name)
|
||||
}
|
||||
@@ -91,7 +95,8 @@ func (a *ArgGroupModel) ArgSummary() string {
|
||||
type ArgModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Default string
|
||||
Default []string
|
||||
Envar string
|
||||
Required bool
|
||||
Value Value
|
||||
}
|
||||
@@ -116,6 +121,7 @@ func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
|
||||
|
||||
type CmdModel struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Help string
|
||||
FullCommand string
|
||||
Depth int
|
||||
@@ -164,7 +170,8 @@ func (a *ArgClause) Model() *ArgModel {
|
||||
return &ArgModel{
|
||||
Name: a.name,
|
||||
Help: a.help,
|
||||
Default: a.defaultValue,
|
||||
Default: a.defaultValues,
|
||||
Envar: a.envar,
|
||||
Required: a.required,
|
||||
Value: a.value,
|
||||
}
|
||||
@@ -183,7 +190,7 @@ func (f *FlagClause) Model() *FlagModel {
|
||||
Name: f.name,
|
||||
Help: f.help,
|
||||
Short: rune(f.shorthand),
|
||||
Default: f.defaultValue,
|
||||
Default: f.defaultValues,
|
||||
Envar: f.envar,
|
||||
PlaceHolder: f.placeholder,
|
||||
Required: f.required,
|
||||
@@ -207,6 +214,7 @@ func (c *CmdClause) Model() *CmdModel {
|
||||
}
|
||||
return &CmdModel{
|
||||
Name: c.name,
|
||||
Aliases: c.aliases,
|
||||
Help: c.help,
|
||||
Depth: depth,
|
||||
Hidden: c.hidden,
|
||||
|
||||
12
vendor/github.com/alecthomas/kingpin/parser.go
generated
vendored
12
vendor/github.com/alecthomas/kingpin/parser.go
generated
vendored
@@ -92,6 +92,7 @@ type ParseContext struct {
|
||||
peek []*Token
|
||||
argi int // Index of current command-line arg we're processing.
|
||||
args []string
|
||||
rawArgs []string
|
||||
flags *flagGroup
|
||||
arguments *argGroup
|
||||
argumenti int // Cursor into arguments
|
||||
@@ -125,6 +126,7 @@ func tokenize(args []string, ignoreDefault bool) *ParseContext {
|
||||
return &ParseContext{
|
||||
ignoreDefault: ignoreDefault,
|
||||
args: args,
|
||||
rawArgs: args,
|
||||
flags: newFlagGroup(),
|
||||
arguments: newArgGroup(),
|
||||
}
|
||||
@@ -295,6 +297,7 @@ loop:
|
||||
if flag, err := context.flags.parse(context); err != nil {
|
||||
if !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
break
|
||||
@@ -312,6 +315,7 @@ loop:
|
||||
if !ok {
|
||||
if !ignoreDefault {
|
||||
if cmd = cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
selectedDefault = true
|
||||
}
|
||||
}
|
||||
@@ -322,6 +326,7 @@ loop:
|
||||
if cmd == HelpCommand {
|
||||
ignoreDefault = true
|
||||
}
|
||||
cmd.completionAlts = nil
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
if !selectedDefault {
|
||||
@@ -350,6 +355,7 @@ loop:
|
||||
// Move to innermost default command.
|
||||
for !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
} else {
|
||||
@@ -363,9 +369,9 @@ loop:
|
||||
|
||||
// Set defaults for all remaining args.
|
||||
for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() {
|
||||
if arg.defaultValue != "" {
|
||||
if err := arg.value.Set(arg.defaultValue); err != nil {
|
||||
return fmt.Errorf("invalid default value '%s' for argument '%s'", arg.defaultValue, arg.name)
|
||||
for _, defaultValue := range arg.defaultValues {
|
||||
if err := arg.value.Set(defaultValue); err != nil {
|
||||
return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
vendor/github.com/alecthomas/kingpin/parsers.go
generated
vendored
11
vendor/github.com/alecthomas/kingpin/parsers.go
generated
vendored
@@ -199,3 +199,14 @@ func (p *parserMixin) Enums(options ...string) (target *[]string) {
|
||||
func (p *parserMixin) EnumsVar(target *[]string, options ...string) {
|
||||
p.SetValue(newEnumsFlag(target, options...))
|
||||
}
|
||||
|
||||
// A Counter increments a number each time it is encountered.
|
||||
func (p *parserMixin) Counter() (target *int) {
|
||||
target = new(int)
|
||||
p.CounterVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) CounterVar(target *int) {
|
||||
p.SetValue(newCounterValue(target))
|
||||
}
|
||||
|
||||
31
vendor/github.com/alecthomas/kingpin/templates.go
generated
vendored
31
vendor/github.com/alecthomas/kingpin/templates.go
generated
vendored
@@ -37,8 +37,8 @@ Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
{{if len .Context.SelectedCommand.Commands}}\
|
||||
Subcommands:
|
||||
{{if .Context.SelectedCommand.Commands}}\
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
@@ -231,3 +231,32 @@ Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
var BashCompletionTemplate = `
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
|
||||
`
|
||||
|
||||
var ZshCompletionTemplate = `
|
||||
#compdef {{.App.Name}}
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
`
|
||||
|
||||
9
vendor/github.com/alecthomas/kingpin/usage.go
generated
vendored
9
vendor/github.com/alecthomas/kingpin/usage.go
generated
vendored
@@ -87,6 +87,9 @@ func formatFlag(haveShort bool, flag *FlagModel) string {
|
||||
if !flag.IsBoolFlag() {
|
||||
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
|
||||
}
|
||||
if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() {
|
||||
flagString += " ..."
|
||||
}
|
||||
return flagString
|
||||
}
|
||||
|
||||
@@ -118,7 +121,7 @@ func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent
|
||||
"Wrap": func(indent int, s string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
indentText := strings.Repeat(" ", indent)
|
||||
doc.ToText(buf, s, indentText, indentText, width)
|
||||
doc.ToText(buf, s, indentText, " "+indentText, width-indent)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatFlag": formatFlag,
|
||||
@@ -180,8 +183,8 @@ func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent
|
||||
"FormatAppUsage": formatAppUsage,
|
||||
"FormatCommandUsage": formatCmdUsage,
|
||||
"IsCumulative": func(value Value) bool {
|
||||
_, ok := value.(remainderArg)
|
||||
return ok
|
||||
r, ok := value.(remainderArg)
|
||||
return ok && r.IsCumulative()
|
||||
},
|
||||
"Char": func(c rune) string {
|
||||
return string(c)
|
||||
|
||||
75
vendor/github.com/alecthomas/kingpin/values.go
generated
vendored
75
vendor/github.com/alecthomas/kingpin/values.go
generated
vendored
@@ -53,6 +53,12 @@ type remainderArg interface {
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
// Optional interface for flags that can be repeated.
|
||||
type repeatableFlag interface {
|
||||
Value
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
type accumulator struct {
|
||||
element func(value interface{}) Value
|
||||
typ reflect.Type
|
||||
@@ -96,6 +102,10 @@ func (a *accumulator) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *accumulator) Get() interface{} {
|
||||
return a.slice.Interface()
|
||||
}
|
||||
|
||||
func (a *accumulator) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
@@ -136,6 +146,11 @@ func (s *stringMapValue) Set(value string) error {
|
||||
(*s)[parts[0]] = parts[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringMapValue) Get() interface{} {
|
||||
return (map[string]string)(*s)
|
||||
}
|
||||
|
||||
func (s *stringMapValue) String() string {
|
||||
return fmt.Sprintf("%s", map[string]string(*s))
|
||||
}
|
||||
@@ -160,6 +175,10 @@ func (i *ipValue) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipValue) Get() interface{} {
|
||||
return (net.IP)(*i)
|
||||
}
|
||||
|
||||
func (i *ipValue) String() string {
|
||||
return (*net.IP)(i).String()
|
||||
}
|
||||
@@ -182,6 +201,10 @@ func (i *tcpAddrValue) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tcpAddrValue) Get() interface{} {
|
||||
return (*net.TCPAddr)(*t.addr)
|
||||
}
|
||||
|
||||
func (i *tcpAddrValue) String() string {
|
||||
return (*i.addr).String()
|
||||
}
|
||||
@@ -212,6 +235,10 @@ func (e *fileStatValue) Set(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileStatValue) Get() interface{} {
|
||||
return (string)(*f.path)
|
||||
}
|
||||
|
||||
func (e *fileStatValue) String() string {
|
||||
return *e.path
|
||||
}
|
||||
@@ -237,6 +264,10 @@ func (f *fileValue) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileValue) Get() interface{} {
|
||||
return (*os.File)(*f.f)
|
||||
}
|
||||
|
||||
func (f *fileValue) String() string {
|
||||
if *f.f == nil {
|
||||
return "<nil>"
|
||||
@@ -262,6 +293,10 @@ func (u *urlValue) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlValue) Get() interface{} {
|
||||
return (*url.URL)(*u.u)
|
||||
}
|
||||
|
||||
func (u *urlValue) String() string {
|
||||
if *u.u == nil {
|
||||
return "<nil>"
|
||||
@@ -285,6 +320,10 @@ func (u *urlListValue) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlListValue) Get() interface{} {
|
||||
return ([]*url.URL)(*u)
|
||||
}
|
||||
|
||||
func (u *urlListValue) String() string {
|
||||
out := []string{}
|
||||
for _, url := range *u {
|
||||
@@ -320,6 +359,10 @@ func (a *enumValue) Set(value string) error {
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumValue) Get() interface{} {
|
||||
return (string)(*e.value)
|
||||
}
|
||||
|
||||
// -- []string Enum Value
|
||||
type enumsValue struct {
|
||||
value *[]string
|
||||
@@ -343,6 +386,10 @@ func (s *enumsValue) Set(value string) error {
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumsValue) Get() interface{} {
|
||||
return ([]string)(*e.value)
|
||||
}
|
||||
|
||||
func (s *enumsValue) String() string {
|
||||
return strings.Join(*s.value, ",")
|
||||
}
|
||||
@@ -389,3 +436,31 @@ func newExistingDirValue(target *string) *fileStatValue {
|
||||
func newExistingFileOrDirValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error { return nil })
|
||||
}
|
||||
|
||||
type counterValue int
|
||||
|
||||
func newCounterValue(n *int) *counterValue {
|
||||
return (*counterValue)(n)
|
||||
}
|
||||
|
||||
func (c *counterValue) Set(s string) error {
|
||||
*c++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counterValue) Get() interface{} { return (int)(*c) }
|
||||
func (c *counterValue) IsBoolFlag() bool { return true }
|
||||
func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) }
|
||||
func (c *counterValue) IsCumulative() bool { return true }
|
||||
|
||||
func resolveHost(value string) (net.IP, error) {
|
||||
if ip := net.ParseIP(value); ip != nil {
|
||||
return ip, nil
|
||||
} else {
|
||||
if addr, err := net.ResolveIPAddr("ip", value); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return addr.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
7
vendor/github.com/alecthomas/kingpin/values.json
generated
vendored
7
vendor/github.com/alecthomas/kingpin/values.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{"type": "bool", "parser": "strconv.ParseBool(s)"},
|
||||
{"type": "string", "parser": "s, error(nil)", "format": "string(*f)", "plural": "Strings"},
|
||||
{"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"},
|
||||
{"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"},
|
||||
{"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"},
|
||||
{"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"},
|
||||
@@ -18,5 +18,8 @@
|
||||
{"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true},
|
||||
{"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true},
|
||||
{"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true},
|
||||
{"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true}
|
||||
{"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true},
|
||||
{"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"},
|
||||
{"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."},
|
||||
{"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."}
|
||||
]
|
||||
|
||||
353
vendor/github.com/alecthomas/kingpin/values_generated.go
generated
vendored
353
vendor/github.com/alecthomas/kingpin/values_generated.go
generated
vendored
@@ -1,8 +1,10 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -10,19 +12,21 @@ import (
|
||||
// This file is autogenerated by "go generate .". Do not modify.
|
||||
|
||||
// -- bool Value
|
||||
type boolValue bool
|
||||
type boolValue struct{ v *bool }
|
||||
|
||||
func newBoolValue(p *bool) *boolValue {
|
||||
return (*boolValue)(p)
|
||||
return &boolValue{p}
|
||||
}
|
||||
|
||||
func (f *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
*f = boolValue(v)
|
||||
if err == nil {
|
||||
*f.v = (bool)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *boolValue) Get() interface{} { return bool(*f) }
|
||||
func (f *boolValue) Get() interface{} { return (bool)(*f.v) }
|
||||
|
||||
func (f *boolValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -45,25 +49,29 @@ func (p *parserMixin) BoolList() (target *[]bool) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) BoolListVar(target *[]bool) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newBoolValue(v.(*bool)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newBoolValue(v.(*bool))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- string Value
|
||||
type stringValue string
|
||||
type stringValue struct{ v *string }
|
||||
|
||||
func newStringValue(p *string) *stringValue {
|
||||
return (*stringValue)(p)
|
||||
return &stringValue{p}
|
||||
}
|
||||
|
||||
func (f *stringValue) Set(s string) error {
|
||||
v, err := s, error(nil)
|
||||
*f = stringValue(v)
|
||||
if err == nil {
|
||||
*f.v = (string)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *stringValue) Get() interface{} { return string(*f) }
|
||||
func (f *stringValue) Get() interface{} { return (string)(*f.v) }
|
||||
|
||||
func (f *stringValue) String() string { return string(*f) }
|
||||
func (f *stringValue) String() string { return string(*f.v) }
|
||||
|
||||
// String parses the next command-line value as string.
|
||||
func (p *parserMixin) String() (target *string) {
|
||||
@@ -84,23 +92,27 @@ func (p *parserMixin) Strings() (target *[]string) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) StringsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newStringValue(v.(*string)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newStringValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint Value
|
||||
type uintValue uint
|
||||
type uintValue struct{ v *uint }
|
||||
|
||||
func newUintValue(p *uint) *uintValue {
|
||||
return (*uintValue)(p)
|
||||
return &uintValue{p}
|
||||
}
|
||||
|
||||
func (f *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*f = uintValue(v)
|
||||
if err == nil {
|
||||
*f.v = (uint)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uintValue) Get() interface{} { return uint(*f) }
|
||||
func (f *uintValue) Get() interface{} { return (uint)(*f.v) }
|
||||
|
||||
func (f *uintValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -123,23 +135,27 @@ func (p *parserMixin) Uints() (target *[]uint) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) UintsVar(target *[]uint) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newUintValue(v.(*uint)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUintValue(v.(*uint))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint8 Value
|
||||
type uint8Value uint8
|
||||
type uint8Value struct{ v *uint8 }
|
||||
|
||||
func newUint8Value(p *uint8) *uint8Value {
|
||||
return (*uint8Value)(p)
|
||||
return &uint8Value{p}
|
||||
}
|
||||
|
||||
func (f *uint8Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 8)
|
||||
*f = uint8Value(v)
|
||||
if err == nil {
|
||||
*f.v = (uint8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint8Value) Get() interface{} { return uint8(*f) }
|
||||
func (f *uint8Value) Get() interface{} { return (uint8)(*f.v) }
|
||||
|
||||
func (f *uint8Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -162,23 +178,27 @@ func (p *parserMixin) Uint8List() (target *[]uint8) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint8ListVar(target *[]uint8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newUint8Value(v.(*uint8)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint8Value(v.(*uint8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint16 Value
|
||||
type uint16Value uint16
|
||||
type uint16Value struct{ v *uint16 }
|
||||
|
||||
func newUint16Value(p *uint16) *uint16Value {
|
||||
return (*uint16Value)(p)
|
||||
return &uint16Value{p}
|
||||
}
|
||||
|
||||
func (f *uint16Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 16)
|
||||
*f = uint16Value(v)
|
||||
if err == nil {
|
||||
*f.v = (uint16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint16Value) Get() interface{} { return uint16(*f) }
|
||||
func (f *uint16Value) Get() interface{} { return (uint16)(*f.v) }
|
||||
|
||||
func (f *uint16Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -201,23 +221,27 @@ func (p *parserMixin) Uint16List() (target *[]uint16) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint16ListVar(target *[]uint16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newUint16Value(v.(*uint16)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint16Value(v.(*uint16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint32 Value
|
||||
type uint32Value uint32
|
||||
type uint32Value struct{ v *uint32 }
|
||||
|
||||
func newUint32Value(p *uint32) *uint32Value {
|
||||
return (*uint32Value)(p)
|
||||
return &uint32Value{p}
|
||||
}
|
||||
|
||||
func (f *uint32Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 32)
|
||||
*f = uint32Value(v)
|
||||
if err == nil {
|
||||
*f.v = (uint32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint32Value) Get() interface{} { return uint32(*f) }
|
||||
func (f *uint32Value) Get() interface{} { return (uint32)(*f.v) }
|
||||
|
||||
func (f *uint32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -240,23 +264,27 @@ func (p *parserMixin) Uint32List() (target *[]uint32) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint32ListVar(target *[]uint32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newUint32Value(v.(*uint32)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint32Value(v.(*uint32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint64 Value
|
||||
type uint64Value uint64
|
||||
type uint64Value struct{ v *uint64 }
|
||||
|
||||
func newUint64Value(p *uint64) *uint64Value {
|
||||
return (*uint64Value)(p)
|
||||
return &uint64Value{p}
|
||||
}
|
||||
|
||||
func (f *uint64Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
*f = uint64Value(v)
|
||||
if err == nil {
|
||||
*f.v = (uint64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint64Value) Get() interface{} { return uint64(*f) }
|
||||
func (f *uint64Value) Get() interface{} { return (uint64)(*f.v) }
|
||||
|
||||
func (f *uint64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -279,23 +307,27 @@ func (p *parserMixin) Uint64List() (target *[]uint64) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint64ListVar(target *[]uint64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newUint64Value(v.(*uint64)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint64Value(v.(*uint64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int Value
|
||||
type intValue int
|
||||
type intValue struct{ v *int }
|
||||
|
||||
func newIntValue(p *int) *intValue {
|
||||
return (*intValue)(p)
|
||||
return &intValue{p}
|
||||
}
|
||||
|
||||
func (f *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
*f = intValue(v)
|
||||
if err == nil {
|
||||
*f.v = (int)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *intValue) Get() interface{} { return int(*f) }
|
||||
func (f *intValue) Get() interface{} { return (int)(*f.v) }
|
||||
|
||||
func (f *intValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -318,23 +350,27 @@ func (p *parserMixin) Ints() (target *[]int) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) IntsVar(target *[]int) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newIntValue(v.(*int)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIntValue(v.(*int))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int8 Value
|
||||
type int8Value int8
|
||||
type int8Value struct{ v *int8 }
|
||||
|
||||
func newInt8Value(p *int8) *int8Value {
|
||||
return (*int8Value)(p)
|
||||
return &int8Value{p}
|
||||
}
|
||||
|
||||
func (f *int8Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 8)
|
||||
*f = int8Value(v)
|
||||
if err == nil {
|
||||
*f.v = (int8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int8Value) Get() interface{} { return int8(*f) }
|
||||
func (f *int8Value) Get() interface{} { return (int8)(*f.v) }
|
||||
|
||||
func (f *int8Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -357,23 +393,27 @@ func (p *parserMixin) Int8List() (target *[]int8) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int8ListVar(target *[]int8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newInt8Value(v.(*int8)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt8Value(v.(*int8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int16 Value
|
||||
type int16Value int16
|
||||
type int16Value struct{ v *int16 }
|
||||
|
||||
func newInt16Value(p *int16) *int16Value {
|
||||
return (*int16Value)(p)
|
||||
return &int16Value{p}
|
||||
}
|
||||
|
||||
func (f *int16Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 16)
|
||||
*f = int16Value(v)
|
||||
if err == nil {
|
||||
*f.v = (int16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int16Value) Get() interface{} { return int16(*f) }
|
||||
func (f *int16Value) Get() interface{} { return (int16)(*f.v) }
|
||||
|
||||
func (f *int16Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -396,23 +436,27 @@ func (p *parserMixin) Int16List() (target *[]int16) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int16ListVar(target *[]int16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newInt16Value(v.(*int16)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt16Value(v.(*int16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int32 Value
|
||||
type int32Value int32
|
||||
type int32Value struct{ v *int32 }
|
||||
|
||||
func newInt32Value(p *int32) *int32Value {
|
||||
return (*int32Value)(p)
|
||||
return &int32Value{p}
|
||||
}
|
||||
|
||||
func (f *int32Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 32)
|
||||
*f = int32Value(v)
|
||||
if err == nil {
|
||||
*f.v = (int32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int32Value) Get() interface{} { return int32(*f) }
|
||||
func (f *int32Value) Get() interface{} { return (int32)(*f.v) }
|
||||
|
||||
func (f *int32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -435,23 +479,27 @@ func (p *parserMixin) Int32List() (target *[]int32) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int32ListVar(target *[]int32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newInt32Value(v.(*int32)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt32Value(v.(*int32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int64 Value
|
||||
type int64Value int64
|
||||
type int64Value struct{ v *int64 }
|
||||
|
||||
func newInt64Value(p *int64) *int64Value {
|
||||
return (*int64Value)(p)
|
||||
return &int64Value{p}
|
||||
}
|
||||
|
||||
func (f *int64Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
*f = int64Value(v)
|
||||
if err == nil {
|
||||
*f.v = (int64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int64Value) Get() interface{} { return int64(*f) }
|
||||
func (f *int64Value) Get() interface{} { return (int64)(*f.v) }
|
||||
|
||||
func (f *int64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -474,23 +522,27 @@ func (p *parserMixin) Int64List() (target *[]int64) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int64ListVar(target *[]int64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newInt64Value(v.(*int64)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt64Value(v.(*int64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float64 Value
|
||||
type float64Value float64
|
||||
type float64Value struct{ v *float64 }
|
||||
|
||||
func newFloat64Value(p *float64) *float64Value {
|
||||
return (*float64Value)(p)
|
||||
return &float64Value{p}
|
||||
}
|
||||
|
||||
func (f *float64Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
*f = float64Value(v)
|
||||
if err == nil {
|
||||
*f.v = (float64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float64Value) Get() interface{} { return float64(*f) }
|
||||
func (f *float64Value) Get() interface{} { return (float64)(*f.v) }
|
||||
|
||||
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -513,23 +565,27 @@ func (p *parserMixin) Float64List() (target *[]float64) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float64ListVar(target *[]float64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newFloat64Value(v.(*float64)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat64Value(v.(*float64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float32 Value
|
||||
type float32Value float32
|
||||
type float32Value struct{ v *float32 }
|
||||
|
||||
func newFloat32Value(p *float32) *float32Value {
|
||||
return (*float32Value)(p)
|
||||
return &float32Value{p}
|
||||
}
|
||||
|
||||
func (f *float32Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
*f = float32Value(v)
|
||||
if err == nil {
|
||||
*f.v = (float32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float32Value) Get() interface{} { return float32(*f) }
|
||||
func (f *float32Value) Get() interface{} { return (float32)(*f.v) }
|
||||
|
||||
func (f *float32Value) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
@@ -552,7 +608,9 @@ func (p *parserMixin) Float32List() (target *[]float32) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float32ListVar(target *[]float32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newFloat32Value(v.(*float32)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat32Value(v.(*float32))
|
||||
}))
|
||||
}
|
||||
|
||||
// DurationList accumulates time.Duration values into a slice.
|
||||
@@ -563,7 +621,9 @@ func (p *parserMixin) DurationList() (target *[]time.Duration) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) DurationListVar(target *[]time.Duration) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newDurationValue(v.(*time.Duration)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newDurationValue(v.(*time.Duration))
|
||||
}))
|
||||
}
|
||||
|
||||
// IPList accumulates net.IP values into a slice.
|
||||
@@ -574,7 +634,9 @@ func (p *parserMixin) IPList() (target *[]net.IP) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) IPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newIPValue(v.(*net.IP)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// TCPList accumulates *net.TCPAddr values into a slice.
|
||||
@@ -585,7 +647,9 @@ func (p *parserMixin) TCPList() (target *[]*net.TCPAddr) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) TCPListVar(target *[]*net.TCPAddr) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newTCPAddrValue(v.(**net.TCPAddr)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newTCPAddrValue(v.(**net.TCPAddr))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFiles accumulates string values into a slice.
|
||||
@@ -596,7 +660,9 @@ func (p *parserMixin) ExistingFiles() (target *[]string) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newExistingFileValue(v.(*string)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingDirs accumulates string values into a slice.
|
||||
@@ -607,7 +673,9 @@ func (p *parserMixin) ExistingDirs() (target *[]string) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newExistingDirValue(v.(*string)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFilesOrDirs accumulates string values into a slice.
|
||||
@@ -618,5 +686,136 @@ func (p *parserMixin) ExistingFilesOrDirs() (target *[]string) {
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value { return newExistingFileOrDirValue(v.(*string)) }))
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileOrDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- *regexp.Regexp Value
|
||||
type regexpValue struct{ v **regexp.Regexp }
|
||||
|
||||
func newRegexpValue(p **regexp.Regexp) *regexpValue {
|
||||
return ®expValue{p}
|
||||
}
|
||||
|
||||
func (f *regexpValue) Set(s string) error {
|
||||
v, err := regexp.Compile(s)
|
||||
if err == nil {
|
||||
*f.v = (*regexp.Regexp)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *regexpValue) Get() interface{} { return (*regexp.Regexp)(*f.v) }
|
||||
|
||||
func (f *regexpValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
// Regexp parses the next command-line value as *regexp.Regexp.
|
||||
func (p *parserMixin) Regexp() (target **regexp.Regexp) {
|
||||
target = new(*regexp.Regexp)
|
||||
p.RegexpVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpVar(target **regexp.Regexp) {
|
||||
p.SetValue(newRegexpValue(target))
|
||||
}
|
||||
|
||||
// RegexpList accumulates *regexp.Regexp values into a slice.
|
||||
func (p *parserMixin) RegexpList() (target *[]*regexp.Regexp) {
|
||||
target = new([]*regexp.Regexp)
|
||||
p.RegexpListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newRegexpValue(v.(**regexp.Regexp))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- net.IP Value
|
||||
type resolvedIPValue struct{ v *net.IP }
|
||||
|
||||
func newResolvedIPValue(p *net.IP) *resolvedIPValue {
|
||||
return &resolvedIPValue{p}
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Set(s string) error {
|
||||
v, err := resolveHost(s)
|
||||
if err == nil {
|
||||
*f.v = (net.IP)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Get() interface{} { return (net.IP)(*f.v) }
|
||||
|
||||
func (f *resolvedIPValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
// Resolve a hostname or IP to an IP.
|
||||
func (p *parserMixin) ResolvedIP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.ResolvedIPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPVar(target *net.IP) {
|
||||
p.SetValue(newResolvedIPValue(target))
|
||||
}
|
||||
|
||||
// ResolvedIPList accumulates net.IP values into a slice.
|
||||
func (p *parserMixin) ResolvedIPList() (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
p.ResolvedIPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newResolvedIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- []byte Value
|
||||
type hexBytesValue struct{ v *[]byte }
|
||||
|
||||
func newHexBytesValue(p *[]byte) *hexBytesValue {
|
||||
return &hexBytesValue{p}
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Set(s string) error {
|
||||
v, err := hex.DecodeString(s)
|
||||
if err == nil {
|
||||
*f.v = ([]byte)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Get() interface{} { return ([]byte)(*f.v) }
|
||||
|
||||
func (f *hexBytesValue) String() string { return fmt.Sprintf("%v", *f) }
|
||||
|
||||
// Bytes as a hex string.
|
||||
func (p *parserMixin) HexBytes() (target *[]byte) {
|
||||
target = new([]byte)
|
||||
p.HexBytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesVar(target *[]byte) {
|
||||
p.SetValue(newHexBytesValue(target))
|
||||
}
|
||||
|
||||
// HexBytesList accumulates []byte values into a slice.
|
||||
func (p *parserMixin) HexBytesList() (target *[][]byte) {
|
||||
target = new([][]byte)
|
||||
p.HexBytesListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesListVar(target *[][]byte) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newHexBytesValue(v.(*[]byte))
|
||||
}))
|
||||
}
|
||||
|
||||
2
vendor/golang.org/x/sys/LICENSE → vendor/github.com/alecthomas/template/LICENSE
generated
vendored
2
vendor/golang.org/x/sys/LICENSE → vendor/github.com/alecthomas/template/LICENSE
generated
vendored
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
1
vendor/github.com/alecthomas/template/exec.go
generated
vendored
1
vendor/github.com/alecthomas/template/exec.go
generated
vendored
@@ -12,6 +12,7 @@ import (
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
|
||||
1
vendor/github.com/alecthomas/template/template.go
generated
vendored
1
vendor/github.com/alecthomas/template/template.go
generated
vendored
@@ -7,6 +7,7 @@ package template
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
|
||||
14
vendor/github.com/codeskyblue/openid-go/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/codeskyblue/openid-go/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
sudo: false
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.3.3
|
||||
- 1.4.2
|
||||
- 1.5.2
|
||||
- 1.6beta2
|
||||
|
||||
# Get deps, build, test, and ensure the code is gofmt'ed.
|
||||
script:
|
||||
- go test -v ./...
|
||||
- diff <(gofmt -d .) <("")
|
||||
13
vendor/github.com/codeskyblue/openid-go/LICENSE
generated
vendored
Normal file
13
vendor/github.com/codeskyblue/openid-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright 2015 Yohann Coppel
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
38
vendor/github.com/codeskyblue/openid-go/README.md
generated
vendored
Normal file
38
vendor/github.com/codeskyblue/openid-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# openid.go
|
||||
|
||||
This is a consumer (Relying party) implementation of OpenId 2.0,
|
||||
written in Go.
|
||||
|
||||
go get -u github.com/yohcop/openid-go
|
||||
|
||||
[](https://travis-ci.org/yohcop/openid-go)
|
||||
|
||||
## Github
|
||||
|
||||
Be awesome! Feel free to clone and use according to the licence.
|
||||
If you make a useful change that can benefit others, send a
|
||||
pull request! This ensures that one version has all the good stuff
|
||||
and doesn't fall behind.
|
||||
|
||||
## Code example
|
||||
|
||||
See `_example/` for a simple webserver using the openID
|
||||
implementation. Also, read the comment about the NonceStore towards
|
||||
the top of that file. The example must be run for the openid-go
|
||||
directory, like so:
|
||||
|
||||
go run _example/server.go
|
||||
|
||||
## App Engine
|
||||
|
||||
In order to use this on Google App Engine, you need to create an instance with a custom `*http.Client` provided by [urlfetch](https://cloud.google.com/appengine/docs/go/urlfetch/).
|
||||
|
||||
```go
|
||||
oid := openid.NewOpenID(urlfetch.Client(appengine.NewContext(r)))
|
||||
oid.RedirectURL(...)
|
||||
oid.Verify(...)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the [Apache v2.0 license](http://www.apache.org/licenses/LICENSE-2.0.html).
|
||||
57
vendor/github.com/codeskyblue/openid-go/discover.go
generated
vendored
Normal file
57
vendor/github.com/codeskyblue/openid-go/discover.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package openid
|
||||
|
||||
// 7.3.1. Discovered Information
|
||||
// Upon successful completion of discovery, the Relying Party will
|
||||
// have one or more sets of the following information (see the
|
||||
// Terminology section for definitions). If more than one set of the
|
||||
// following information has been discovered, the precedence rules
|
||||
// defined in [XRI_Resolution_2.0] are to be applied.
|
||||
// - OP Endpoint URL
|
||||
// - Protocol Version
|
||||
// If the end user did not enter an OP Identifier, the following
|
||||
// information will also be present:
|
||||
// - Claimed Identifier
|
||||
// - OP-Local Identifier
|
||||
// If the end user entered an OP Identifier, there is no Claimed
|
||||
// Identifier. For the purposes of making OpenID Authentication
|
||||
// requests, the value
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" MUST be
|
||||
// used as both the Claimed Identifier and the OP-Local Identifier
|
||||
// when an OP Identifier is entered.
|
||||
func Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
return defaultInstance.Discover(id)
|
||||
}
|
||||
|
||||
func (oid *OpenID) Discover(id string) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
// From OpenID specs, 7.2: Normalization
|
||||
if id, err = Normalize(id); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// From OpenID specs, 7.3: Discovery.
|
||||
|
||||
// If the identifier is an XRI, [XRI_Resolution_2.0] will yield an
|
||||
// XRDS document that contains the necessary information. It
|
||||
// should also be noted that Relying Parties can take advantage of
|
||||
// XRI Proxy Resolvers, such as the one provided by XDI.org at
|
||||
// http://www.xri.net. This will remove the need for the RPs to
|
||||
// perform XRI Resolution locally.
|
||||
|
||||
// XRI not supported.
|
||||
|
||||
// If it is a URL, the Yadis protocol [Yadis] SHALL be first
|
||||
// attempted. If it succeeds, the result is again an XRDS
|
||||
// document.
|
||||
if opEndpoint, opLocalID, err = yadisDiscovery(id, oid.urlGetter); err != nil {
|
||||
// If the Yadis protocol fails and no valid XRDS document is
|
||||
// retrieved, or no Service Elements are found in the XRDS
|
||||
// document, the URL is retrieved and HTML-Based discovery SHALL be
|
||||
// attempted.
|
||||
opEndpoint, opLocalID, claimedID, err = htmlDiscovery(id, oid.urlGetter)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
return
|
||||
}
|
||||
69
vendor/github.com/codeskyblue/openid-go/discovery_cache.go
generated
vendored
Normal file
69
vendor/github.com/codeskyblue/openid-go/discovery_cache.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type DiscoveredInfo interface {
|
||||
OpEndpoint() string
|
||||
OpLocalID() string
|
||||
ClaimedID() string
|
||||
// ProtocolVersion: it's always openId 2.
|
||||
}
|
||||
|
||||
type DiscoveryCache interface {
|
||||
Put(id string, info DiscoveredInfo)
|
||||
// Return a discovered info, or nil.
|
||||
Get(id string) DiscoveredInfo
|
||||
}
|
||||
|
||||
type SimpleDiscoveredInfo struct {
|
||||
opEndpoint string
|
||||
opLocalID string
|
||||
claimedID string
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) OpEndpoint() string {
|
||||
return s.opEndpoint
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) OpLocalID() string {
|
||||
return s.opLocalID
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveredInfo) ClaimedID() string {
|
||||
return s.claimedID
|
||||
}
|
||||
|
||||
type SimpleDiscoveryCache struct {
|
||||
cache map[string]DiscoveredInfo
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSimpleDiscoveryCache() *SimpleDiscoveryCache {
|
||||
return &SimpleDiscoveryCache{cache: map[string]DiscoveredInfo{}, mutex: &sync.Mutex{}}
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveryCache) Put(id string, info DiscoveredInfo) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
s.cache[id] = info
|
||||
}
|
||||
|
||||
func (s *SimpleDiscoveryCache) Get(id string) DiscoveredInfo {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if info, has := s.cache[id]; has {
|
||||
return info
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareDiscoveredInfo(a DiscoveredInfo, opEndpoint, opLocalID, claimedID string) bool {
|
||||
return a != nil &&
|
||||
a.OpEndpoint() == opEndpoint &&
|
||||
a.OpLocalID() == opLocalID &&
|
||||
a.ClaimedID() == claimedID
|
||||
}
|
||||
31
vendor/github.com/codeskyblue/openid-go/getter.go
generated
vendored
Normal file
31
vendor/github.com/codeskyblue/openid-go/getter.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Interface that simplifies testing.
|
||||
type httpGetter interface {
|
||||
Get(uri string, headers map[string]string) (resp *http.Response, err error)
|
||||
Post(uri string, form url.Values) (resp *http.Response, err error)
|
||||
}
|
||||
|
||||
type defaultGetter struct {
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (dg *defaultGetter) Get(uri string, headers map[string]string) (resp *http.Response, err error) {
|
||||
request, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for h, v := range headers {
|
||||
request.Header.Add(h, v)
|
||||
}
|
||||
return dg.client.Do(request)
|
||||
}
|
||||
|
||||
func (dg *defaultGetter) Post(uri string, form url.Values) (resp *http.Response, err error) {
|
||||
return dg.client.PostForm(uri, form)
|
||||
}
|
||||
75
vendor/github.com/codeskyblue/openid-go/html_discovery.go
generated
vendored
Normal file
75
vendor/github.com/codeskyblue/openid-go/html_discovery.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func htmlDiscovery(id string, getter httpGetter) (opEndpoint, opLocalID, claimedID string, err error) {
|
||||
resp, err := getter.Get(id, nil)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
opEndpoint, opLocalID, err = findProviderFromHeadLink(resp.Body)
|
||||
return opEndpoint, opLocalID, resp.Request.URL.String(), err
|
||||
}
|
||||
|
||||
func findProviderFromHeadLink(input io.Reader) (opEndpoint, opLocalID string, err error) {
|
||||
tokenizer := html.NewTokenizer(input)
|
||||
inHead := false
|
||||
for {
|
||||
tt := tokenizer.Next()
|
||||
switch tt {
|
||||
case html.ErrorToken:
|
||||
// Even if the document is malformed after we found a
|
||||
// valid <link> tag, ignore and let's be happy with our
|
||||
// openid2.provider and potentially openid2.local_id as well.
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", tokenizer.Err()
|
||||
case html.StartTagToken, html.EndTagToken, html.SelfClosingTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == "head" {
|
||||
if tt == html.StartTagToken {
|
||||
inHead = true
|
||||
} else {
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", errors.New(
|
||||
"LINK with rel=openid2.provider not found")
|
||||
}
|
||||
} else if inHead && tk.Data == "link" {
|
||||
provider := false
|
||||
localID := false
|
||||
href := ""
|
||||
for _, attr := range tk.Attr {
|
||||
if attr.Key == "rel" {
|
||||
if attr.Val == "openid2.provider" {
|
||||
provider = true
|
||||
} else if attr.Val == "openid2.local_id" {
|
||||
localID = true
|
||||
}
|
||||
} else if attr.Key == "href" {
|
||||
href = attr.Val
|
||||
}
|
||||
}
|
||||
if provider && !localID && len(href) > 0 {
|
||||
opEndpoint = href
|
||||
} else if !provider && localID && len(href) > 0 {
|
||||
opLocalID = href
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// At this point we should probably have returned either from
|
||||
// a closing </head> or a tokenizer error (no </head> found).
|
||||
// But just in case.
|
||||
if len(opEndpoint) > 0 {
|
||||
return
|
||||
}
|
||||
return "", "", errors.New("LINK rel=openid2.provider not found")
|
||||
}
|
||||
87
vendor/github.com/codeskyblue/openid-go/nonce_store.go
generated
vendored
Normal file
87
vendor/github.com/codeskyblue/openid-go/nonce_store.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var maxNonceAge = flag.Duration("openid-max-nonce-age",
|
||||
60*time.Second,
|
||||
"Maximum accepted age for openid nonces. The bigger, the more"+
|
||||
"memory is needed to store used nonces.")
|
||||
|
||||
type NonceStore interface {
|
||||
// Returns nil if accepted, an error otherwise.
|
||||
Accept(endpoint, nonce string) error
|
||||
}
|
||||
|
||||
type Nonce struct {
|
||||
T time.Time
|
||||
S string
|
||||
}
|
||||
|
||||
type SimpleNonceStore struct {
|
||||
store map[string][]*Nonce
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewSimpleNonceStore() *SimpleNonceStore {
|
||||
return &SimpleNonceStore{store: map[string][]*Nonce{}, mutex: &sync.Mutex{}}
|
||||
}
|
||||
|
||||
func (d *SimpleNonceStore) Accept(endpoint, nonce string) error {
|
||||
// Value: A string 255 characters or less in length, that MUST be
|
||||
// unique to this particular successful authentication response.
|
||||
if len(nonce) < 20 || len(nonce) > 256 {
|
||||
return errors.New("Invalid nonce")
|
||||
}
|
||||
|
||||
// The nonce MUST start with the current time on the server, and MAY
|
||||
// contain additional ASCII characters in the range 33-126 inclusive
|
||||
// (printable non-whitespace characters), as necessary to make each
|
||||
// response unique. The date and time MUST be formatted as specified in
|
||||
// section 5.6 of [RFC3339], with the following restrictions:
|
||||
|
||||
// All times must be in the UTC timezone, indicated with a "Z". No
|
||||
// fractional seconds are allowed For example:
|
||||
// 2005-05-15T17:11:51ZUNIQUE
|
||||
ts, err := time.Parse(time.RFC3339, nonce[0:20])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
diff := now.Sub(ts)
|
||||
if diff > *maxNonceAge {
|
||||
return fmt.Errorf("Nonce too old: %ds", diff.Seconds())
|
||||
}
|
||||
|
||||
s := nonce[20:]
|
||||
|
||||
// Meh.. now we have to use a mutex, to protect that map from
|
||||
// concurrent access. Could put a go routine in charge of it
|
||||
// though.
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if nonces, hasOp := d.store[endpoint]; hasOp {
|
||||
// Delete old nonces while we are at it.
|
||||
newNonces := []*Nonce{{ts, s}}
|
||||
for _, n := range nonces {
|
||||
if n.T == ts && n.S == s {
|
||||
// If return early, just ignore the filtered list
|
||||
// we have been building so far...
|
||||
return errors.New("Nonce already used")
|
||||
}
|
||||
if now.Sub(n.T) < *maxNonceAge {
|
||||
newNonces = append(newNonces, n)
|
||||
}
|
||||
}
|
||||
d.store[endpoint] = newNonces
|
||||
} else {
|
||||
d.store[endpoint] = []*Nonce{{ts, s}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
64
vendor/github.com/codeskyblue/openid-go/normalizer.go
generated
vendored
Normal file
64
vendor/github.com/codeskyblue/openid-go/normalizer.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Normalize(id string) (string, error) {
|
||||
id = strings.TrimSpace(id)
|
||||
if len(id) == 0 {
|
||||
return "", errors.New("No id provided")
|
||||
}
|
||||
|
||||
// 7.2 from openID 2.0 spec.
|
||||
|
||||
//If the user's input starts with the "xri://" prefix, it MUST be
|
||||
//stripped off, so that XRIs are used in the canonical form.
|
||||
if strings.HasPrefix(id, "xri://") {
|
||||
id = id[6:]
|
||||
return id, errors.New("XRI identifiers not supported")
|
||||
}
|
||||
|
||||
// If the first character of the resulting string is an XRI
|
||||
// Global Context Symbol ("=", "@", "+", "$", "!") or "(", as
|
||||
// defined in Section 2.2.1 of [XRI_Syntax_2.0], then the input
|
||||
// SHOULD be treated as an XRI.
|
||||
if b := id[0]; b == '=' || b == '@' || b == '+' || b == '$' || b == '!' {
|
||||
return id, errors.New("XRI identifiers not supported")
|
||||
}
|
||||
|
||||
// Otherwise, the input SHOULD be treated as an http URL; if it
|
||||
// does not include a "http" or "https" scheme, the Identifier
|
||||
// MUST be prefixed with the string "http://". If the URL
|
||||
// contains a fragment part, it MUST be stripped off together
|
||||
// with the fragment delimiter character "#". See Section 11.5.2 for
|
||||
// more information.
|
||||
if !strings.HasPrefix(id, "http://") && !strings.HasPrefix(id,
|
||||
"https://") {
|
||||
id = "http://" + id
|
||||
}
|
||||
if fragmentIndex := strings.Index(id, "#"); fragmentIndex != -1 {
|
||||
id = id[0:fragmentIndex]
|
||||
}
|
||||
if u, err := url.ParseRequestURI(id); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
if u.Host == "" {
|
||||
return "", errors.New("Invalid address provided as id")
|
||||
}
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
id = u.String()
|
||||
}
|
||||
|
||||
// URL Identifiers MUST then be further normalized by both
|
||||
// following redirects when retrieving their content and finally
|
||||
// applying the rules in Section 6 of [RFC3986] to the final
|
||||
// destination URL. This final URL MUST be noted by the Relying
|
||||
// Party as the Claimed Identifier and be used when requesting
|
||||
// authentication.
|
||||
return id, nil
|
||||
}
|
||||
15
vendor/github.com/codeskyblue/openid-go/openid.go
generated
vendored
Normal file
15
vendor/github.com/codeskyblue/openid-go/openid.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type OpenID struct {
|
||||
urlGetter httpGetter
|
||||
}
|
||||
|
||||
func NewOpenID(client *http.Client) *OpenID {
|
||||
return &OpenID{urlGetter: &defaultGetter{client: client}}
|
||||
}
|
||||
|
||||
var defaultInstance = NewOpenID(http.DefaultClient)
|
||||
59
vendor/github.com/codeskyblue/openid-go/redirect.go
generated
vendored
Normal file
59
vendor/github.com/codeskyblue/openid-go/redirect.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RedirectURL(id, callbackURL, realm string) (string, error) {
|
||||
return defaultInstance.RedirectURL(id, callbackURL, realm)
|
||||
}
|
||||
|
||||
func (oid *OpenID) RedirectURL(id, callbackURL, realm string) (string, error) {
|
||||
opEndpoint, opLocalID, claimedID, err := oid.Discover(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return BuildRedirectURL(opEndpoint, opLocalID, claimedID, callbackURL, realm)
|
||||
}
|
||||
|
||||
func BuildRedirectURL(opEndpoint, opLocalID, claimedID, returnTo, realm string) (string, error) {
|
||||
values := make(url.Values)
|
||||
values.Add("openid.ns", "http://specs.openid.net/auth/2.0")
|
||||
values.Add("openid.mode", "checkid_setup")
|
||||
values.Add("openid.return_to", returnTo)
|
||||
|
||||
// 9.1. Request Parameters
|
||||
// "openid.claimed_id" and "openid.identity" SHALL be either both present or both absent.
|
||||
if len(claimedID) > 0 {
|
||||
values.Add("openid.claimed_id", claimedID)
|
||||
if len(opLocalID) > 0 {
|
||||
values.Add("openid.identity", opLocalID)
|
||||
} else {
|
||||
// If a different OP-Local Identifier is not specified,
|
||||
// the claimed identifier MUST be used as the value for openid.identity.
|
||||
values.Add("openid.identity", claimedID)
|
||||
}
|
||||
} else {
|
||||
// 7.3.1. Discovered Information
|
||||
// If the end user entered an OP Identifier, there is no Claimed Identifier.
|
||||
// For the purposes of making OpenID Authentication requests, the value
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" MUST be used as both the
|
||||
// Claimed Identifier and the OP-Local Identifier when an OP Identifier is entered.
|
||||
values.Add("openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select")
|
||||
values.Add("openid.identity", "http://specs.openid.net/auth/2.0/identifier_select")
|
||||
}
|
||||
|
||||
if len(realm) > 0 {
|
||||
values.Add("openid.realm", realm)
|
||||
}
|
||||
|
||||
// ssx: quick dirty patch
|
||||
values.Add("openid.ns.sreg", "http://openid.net/extensions/sreg/1.1")
|
||||
values.Add("openid.sreg.required", "nickname,email,fullname")
|
||||
|
||||
if strings.Contains(opEndpoint, "?") {
|
||||
return opEndpoint + "&" + values.Encode(), nil
|
||||
}
|
||||
return opEndpoint + "?" + values.Encode(), nil
|
||||
}
|
||||
250
vendor/github.com/codeskyblue/openid-go/verify.go
generated
vendored
Normal file
250
vendor/github.com/codeskyblue/openid-go/verify.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
|
||||
return defaultInstance.Verify(uri, cache, nonceStore)
|
||||
}
|
||||
|
||||
func (oid *OpenID) Verify(uri string, cache DiscoveryCache, nonceStore NonceStore) (id string, err error) {
|
||||
parsedURL, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
values, err := url.ParseQuery(parsedURL.RawQuery)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 11. Verifying Assertions
|
||||
// When the Relying Party receives a positive assertion, it MUST
|
||||
// verify the following before accepting the assertion:
|
||||
|
||||
// - The value of "openid.signed" contains all the required fields.
|
||||
// (Section 10.1)
|
||||
if err = verifySignedFields(values); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - The signature on the assertion is valid (Section 11.4)
|
||||
if err = verifySignature(uri, values, oid.urlGetter); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - The value of "openid.return_to" matches the URL of the current
|
||||
// request (Section 11.1)
|
||||
if err = verifyReturnTo(parsedURL, values); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - Discovered information matches the information in the assertion
|
||||
// (Section 11.2)
|
||||
if err = oid.verifyDiscovered(parsedURL, values, cache); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// - An assertion has not yet been accepted from this OP with the
|
||||
// same value for "openid.response_nonce" (Section 11.3)
|
||||
if err = verifyNonce(values, nonceStore); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// If all four of these conditions are met, assertion is now
|
||||
// verified. If the assertion contained a Claimed Identifier, the
|
||||
// user is now authenticated with that identifier.
|
||||
return values.Get("openid.claimed_id"), nil
|
||||
}
|
||||
|
||||
// 10.1. Positive Assertions
|
||||
// openid.signed - Comma-separated list of signed fields.
|
||||
// This entry consists of the fields without the "openid." prefix that the signature covers.
|
||||
// This list MUST contain at least "op_endpoint", "return_to" "response_nonce" and "assoc_handle",
|
||||
// and if present in the response, "claimed_id" and "identity".
|
||||
func verifySignedFields(vals url.Values) error {
|
||||
ok := map[string]bool{
|
||||
"op_endpoint": false,
|
||||
"return_to": false,
|
||||
"response_nonce": false,
|
||||
"assoc_handle": false,
|
||||
"claimed_id": vals.Get("openid.claimed_id") == "",
|
||||
"identity": vals.Get("openid.identity") == "",
|
||||
}
|
||||
signed := strings.Split(vals.Get("openid.signed"), ",")
|
||||
for _, sf := range signed {
|
||||
ok[sf] = true
|
||||
}
|
||||
for k, v := range ok {
|
||||
if !v {
|
||||
return fmt.Errorf("%v must be signed but isn't", k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 11.1. Verifying the Return URL
|
||||
// To verify that the "openid.return_to" URL matches the URL that is processing this assertion:
|
||||
// - The URL scheme, authority, and path MUST be the same between the two
|
||||
// URLs.
|
||||
// - Any query parameters that are present in the "openid.return_to" URL
|
||||
// MUST also be present with the same values in the URL of the HTTP
|
||||
// request the RP received.
|
||||
func verifyReturnTo(uri *url.URL, vals url.Values) error {
|
||||
returnTo := vals.Get("openid.return_to")
|
||||
rp, err := url.Parse(returnTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if uri.Scheme != rp.Scheme ||
|
||||
uri.Host != rp.Host ||
|
||||
uri.Path != rp.Path {
|
||||
return errors.New(
|
||||
"Scheme, host or path don't match in return_to URL")
|
||||
}
|
||||
qp, err := url.ParseQuery(rp.RawQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return compareQueryParams(qp, vals)
|
||||
}
|
||||
|
||||
// Any parameter in q1 must also be present in q2, and values must match.
|
||||
func compareQueryParams(q1, q2 url.Values) error {
|
||||
for k := range q1 {
|
||||
v1 := q1.Get(k)
|
||||
v2 := q2.Get(k)
|
||||
if v1 != v2 {
|
||||
return fmt.Errorf(
|
||||
"URLs query params don't match: Param %s different: %s vs %s",
|
||||
k, v1, v2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (oid *OpenID) verifyDiscovered(uri *url.URL, vals url.Values, cache DiscoveryCache) error {
|
||||
version := vals.Get("openid.ns")
|
||||
if version != "http://specs.openid.net/auth/2.0" {
|
||||
return errors.New("Bad protocol version")
|
||||
}
|
||||
|
||||
endpoint := vals.Get("openid.op_endpoint")
|
||||
if len(endpoint) == 0 {
|
||||
return errors.New("missing openid.op_endpoint url param")
|
||||
}
|
||||
localID := vals.Get("openid.identity")
|
||||
if len(localID) == 0 {
|
||||
return errors.New("no localId to verify")
|
||||
}
|
||||
claimedID := vals.Get("openid.claimed_id")
|
||||
if len(claimedID) == 0 {
|
||||
// If no Claimed Identifier is present in the response, the
|
||||
// assertion is not about an identifier and the RP MUST NOT use the
|
||||
// User-supplied Identifier associated with the current OpenID
|
||||
// authentication transaction to identify the user. Extension
|
||||
// information in the assertion MAY still be used.
|
||||
// --- This library does not support this case. So claimed
|
||||
// identifier must be present.
|
||||
return errors.New("no claimed_id to verify")
|
||||
}
|
||||
|
||||
// 11.2. Verifying Discovered Information
|
||||
|
||||
// If the Claimed Identifier in the assertion is a URL and contains a
|
||||
// fragment, the fragment part and the fragment delimiter character "#"
|
||||
// MUST NOT be used for the purposes of verifying the discovered
|
||||
// information.
|
||||
claimedIDVerify := claimedID
|
||||
if fragmentIndex := strings.Index(claimedID, "#"); fragmentIndex != -1 {
|
||||
claimedIDVerify = claimedID[0:fragmentIndex]
|
||||
}
|
||||
|
||||
// If the Claimed Identifier is included in the assertion, it
|
||||
// MUST have been discovered by the Relying Party and the
|
||||
// information in the assertion MUST be present in the
|
||||
// discovered information. The Claimed Identifier MUST NOT be an
|
||||
// OP Identifier.
|
||||
if discovered := cache.Get(claimedIDVerify); discovered != nil &&
|
||||
discovered.OpEndpoint() == endpoint &&
|
||||
discovered.OpLocalID() == localID &&
|
||||
discovered.ClaimedID() == claimedIDVerify {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the Claimed Identifier was not previously discovered by the
|
||||
// Relying Party (the "openid.identity" in the request was
|
||||
// "http://specs.openid.net/auth/2.0/identifier_select" or a different
|
||||
// Identifier, or if the OP is sending an unsolicited positive
|
||||
// assertion), the Relying Party MUST perform discovery on the Claimed
|
||||
// Identifier in the response to make sure that the OP is authorized to
|
||||
// make assertions about the Claimed Identifier.
|
||||
if ep, _, _, err := oid.Discover(claimedID); err == nil {
|
||||
if ep == endpoint {
|
||||
// This claimed ID points to the same endpoint, therefore this
|
||||
// endpoint is authorized to make assertions about that claimed ID.
|
||||
// TODO: There may be multiple endpoints found during discovery.
|
||||
// They should all be checked.
|
||||
cache.Put(claimedIDVerify, &SimpleDiscoveredInfo{opEndpoint: endpoint, opLocalID: localID, claimedID: claimedIDVerify})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("Could not verify the claimed ID")
|
||||
}
|
||||
|
||||
func verifyNonce(vals url.Values, store NonceStore) error {
|
||||
nonce := vals.Get("openid.response_nonce")
|
||||
endpoint := vals.Get("openid.op_endpoint")
|
||||
return store.Accept(endpoint, nonce)
|
||||
}
|
||||
|
||||
func verifySignature(uri string, vals url.Values, getter httpGetter) error {
|
||||
// To have the signature verification performed by the OP, the
|
||||
// Relying Party sends a direct request to the OP. To verify the
|
||||
// signature, the OP uses a private association that was generated
|
||||
// when it issued the positive assertion.
|
||||
|
||||
// 11.4.2.1. Request Parameters
|
||||
params := make(url.Values)
|
||||
// openid.mode: Value: "check_authentication"
|
||||
params.Add("openid.mode", "check_authentication")
|
||||
// Exact copies of all fields from the authentication response,
|
||||
// except for "openid.mode".
|
||||
for k, vs := range vals {
|
||||
if k == "openid.mode" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vs {
|
||||
params.Add(k, v)
|
||||
}
|
||||
}
|
||||
resp, err := getter.Post(vals.Get("openid.op_endpoint"), params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
response := string(content)
|
||||
lines := strings.Split(response, "\n")
|
||||
|
||||
isValid := false
|
||||
nsValid := false
|
||||
for _, l := range lines {
|
||||
if l == "is_valid:true" {
|
||||
isValid = true
|
||||
} else if l == "ns:http://specs.openid.net/auth/2.0" {
|
||||
nsValid = true
|
||||
}
|
||||
}
|
||||
if isValid && nsValid {
|
||||
// Yay !
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("Could not verify assertion with provider")
|
||||
}
|
||||
83
vendor/github.com/codeskyblue/openid-go/xrds.go
generated
vendored
Normal file
83
vendor/github.com/codeskyblue/openid-go/xrds.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: As per 11.2 in openid 2 specs, a service may have multiple
|
||||
// URIs. We don't care for discovery really, but we do care for
|
||||
// verification though.
|
||||
type XrdsIdentifier struct {
|
||||
Type []string `xml:"Type"`
|
||||
URI string `xml:"URI"`
|
||||
LocalID string `xml:"LocalID"`
|
||||
Priority int `xml:"priority,attr"`
|
||||
}
|
||||
|
||||
type Xrd struct {
|
||||
Service []*XrdsIdentifier `xml:"Service"`
|
||||
}
|
||||
|
||||
type XrdsDocument struct {
|
||||
XMLName xml.Name `xml:"XRDS"`
|
||||
Xrd *Xrd `xml:"XRD"`
|
||||
}
|
||||
|
||||
func parseXrds(input []byte) (opEndpoint, opLocalID string, err error) {
|
||||
xrdsDoc := &XrdsDocument{}
|
||||
err = xml.Unmarshal(input, xrdsDoc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if xrdsDoc.Xrd == nil {
|
||||
return "", "", errors.New("XRDS document missing XRD tag")
|
||||
}
|
||||
|
||||
// 7.3.2.2. Extracting Authentication Data
|
||||
// Once the Relying Party has obtained an XRDS document, it
|
||||
// MUST first search the document (following the rules
|
||||
// described in [XRI_Resolution_2.0]) for an OP Identifier
|
||||
// Element. If none is found, the RP will search for a Claimed
|
||||
// Identifier Element.
|
||||
for _, service := range xrdsDoc.Xrd.Service {
|
||||
// 7.3.2.1.1. OP Identifier Element
|
||||
// An OP Identifier Element is an <xrd:Service> element with the
|
||||
// following information:
|
||||
// An <xrd:Type> tag whose text content is
|
||||
// "http://specs.openid.net/auth/2.0/server".
|
||||
// An <xrd:URI> tag whose text content is the OP Endpoint URL
|
||||
if service.hasType("http://specs.openid.net/auth/2.0/server") {
|
||||
opEndpoint = strings.TrimSpace(service.URI)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, service := range xrdsDoc.Xrd.Service {
|
||||
// 7.3.2.1.2. Claimed Identifier Element
|
||||
// A Claimed Identifier Element is an <xrd:Service> element
|
||||
// with the following information:
|
||||
// An <xrd:Type> tag whose text content is
|
||||
// "http://specs.openid.net/auth/2.0/signon".
|
||||
// An <xrd:URI> tag whose text content is the OP Endpoint
|
||||
// URL.
|
||||
// An <xrd:LocalID> tag (optional) whose text content is the
|
||||
// OP-Local Identifier.
|
||||
if service.hasType("http://specs.openid.net/auth/2.0/signon") {
|
||||
opEndpoint = strings.TrimSpace(service.URI)
|
||||
opLocalID = strings.TrimSpace(service.LocalID)
|
||||
return
|
||||
}
|
||||
}
|
||||
return "", "", errors.New("Could not find a compatible service")
|
||||
}
|
||||
|
||||
func (xrdsi *XrdsIdentifier) hasType(tpe string) bool {
|
||||
for _, t := range xrdsi.Type {
|
||||
if t == tpe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
119
vendor/github.com/codeskyblue/openid-go/yadis_discovery.go
generated
vendored
Normal file
119
vendor/github.com/codeskyblue/openid-go/yadis_discovery.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package openid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var yadisHeaders = map[string]string{
|
||||
"Accept": "application/xrds+xml"}
|
||||
|
||||
func yadisDiscovery(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
|
||||
// Section 6.2.4 of Yadis 1.0 specifications.
|
||||
// The Yadis Protocol is initiated by the Relying Party Agent
|
||||
// with an initial HTTP request using the Yadis URL.
|
||||
|
||||
// This request MUST be either a GET or a HEAD request.
|
||||
|
||||
// A GET or HEAD request MAY include an HTTP Accept
|
||||
// request-header (HTTP 14.1) specifying MIME media type,
|
||||
// application/xrds+xml.
|
||||
resp, err := getter.Get(id, yadisHeaders)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Section 6.2.5 from Yadis 1.0 spec: Response
|
||||
|
||||
contentType := resp.Header.Get("Content-Type")
|
||||
|
||||
// The response MUST be one of:
|
||||
// (see 6.2.6 for precedence)
|
||||
if l := resp.Header.Get("X-XRDS-Location"); l != "" {
|
||||
// 2. HTTP response-headers that include an X-XRDS-Location
|
||||
// response-header, together with a document
|
||||
return getYadisResourceDescriptor(l, getter)
|
||||
} else if strings.Contains(contentType, "text/html") {
|
||||
// 1. An HTML document with a <head> element that includes a
|
||||
// <meta> element with http-equiv attribute, X-XRDS-Location,
|
||||
|
||||
metaContent, err := findMetaXrdsLocation(resp.Body)
|
||||
if err == nil {
|
||||
return getYadisResourceDescriptor(metaContent, getter)
|
||||
}
|
||||
return "", "", err
|
||||
} else if strings.Contains(contentType, "application/xrds+xml") {
|
||||
// 4. A document of MIME media type, application/xrds+xml.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
return parseXrds(body)
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
// 3. HTTP response-headers only, which MAY include an
|
||||
// X-XRDS-Location response-header, a content-type
|
||||
// response-header specifying MIME media type,
|
||||
// application/xrds+xml, or both.
|
||||
// (this is handled by one of the 2 previous if statements)
|
||||
return "", "", errors.New("No expected header, or content type")
|
||||
}
|
||||
|
||||
// Similar as above, but we expect an absolute Yadis document URL.
|
||||
func getYadisResourceDescriptor(id string, getter httpGetter) (opEndpoint string, opLocalID string, err error) {
|
||||
resp, err := getter.Get(id, yadisHeaders)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
// 4. A document of MIME media type, application/xrds+xml.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
return parseXrds(body)
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Search for
|
||||
// <head>
|
||||
// <meta http-equiv="X-XRDS-Location" content="....">
|
||||
func findMetaXrdsLocation(input io.Reader) (location string, err error) {
|
||||
tokenizer := html.NewTokenizer(input)
|
||||
inHead := false
|
||||
for {
|
||||
tt := tokenizer.Next()
|
||||
switch tt {
|
||||
case html.ErrorToken:
|
||||
return "", tokenizer.Err()
|
||||
case html.StartTagToken, html.EndTagToken:
|
||||
tk := tokenizer.Token()
|
||||
if tk.Data == "head" {
|
||||
if tt == html.StartTagToken {
|
||||
inHead = true
|
||||
} else {
|
||||
return "", errors.New("Meta X-XRDS-Location not found")
|
||||
}
|
||||
} else if inHead && tk.Data == "meta" {
|
||||
ok := false
|
||||
content := ""
|
||||
for _, attr := range tk.Attr {
|
||||
if attr.Key == "http-equiv" &&
|
||||
strings.ToLower(attr.Val) == "x-xrds-location" {
|
||||
ok = true
|
||||
} else if attr.Key == "content" {
|
||||
content = attr.Val
|
||||
}
|
||||
}
|
||||
if ok && len(content) > 0 {
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", errors.New("Meta X-XRDS-Location not found")
|
||||
}
|
||||
13
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
13
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
@@ -1,13 +0,0 @@
|
||||
Copyright (c) 2012-2013 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
1
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
1
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
@@ -7,6 +7,7 @@ matrix:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
3
vendor/github.com/gorilla/context/README.md
generated
vendored
3
vendor/github.com/gorilla/context/README.md
generated
vendored
@@ -4,4 +4,7 @@ context
|
||||
|
||||
gorilla/context is a general purpose registry for global request variables.
|
||||
|
||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||
|
||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
||||
|
||||
45
vendor/github.com/gorilla/context/context.go
generated
vendored
45
vendor/github.com/gorilla/context/context.go
generated
vendored
@@ -6,31 +6,32 @@ package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
mutex sync.RWMutex
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
data = make(map[*url.URL]map[interface{}]interface{})
|
||||
datat = make(map[*url.URL]int64)
|
||||
)
|
||||
|
||||
// Set stores a value for a given key in a given request.
|
||||
func Set(r *http.Request, key, val interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] == nil {
|
||||
data[r] = make(map[interface{}]interface{})
|
||||
datat[r] = time.Now().Unix()
|
||||
if data[r.URL] == nil {
|
||||
data[r.URL] = make(map[interface{}]interface{})
|
||||
datat[r.URL] = time.Now().Unix()
|
||||
}
|
||||
data[r][key] = val
|
||||
data[r.URL][key] = val
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// Get returns a value stored for a given key in a given request.
|
||||
func Get(r *http.Request, key interface{}) interface{} {
|
||||
mutex.RLock()
|
||||
if ctx := data[r]; ctx != nil {
|
||||
if ctx := data[r.URL]; ctx != nil {
|
||||
value := ctx[key]
|
||||
mutex.RUnlock()
|
||||
return value
|
||||
@@ -42,8 +43,8 @@ func Get(r *http.Request, key interface{}) interface{} {
|
||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||
mutex.RLock()
|
||||
if _, ok := data[r]; ok {
|
||||
value, ok := data[r][key]
|
||||
if _, ok := data[r.URL]; ok {
|
||||
value, ok := data[r.URL][key]
|
||||
mutex.RUnlock()
|
||||
return value, ok
|
||||
}
|
||||
@@ -54,7 +55,7 @@ func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||
mutex.RLock()
|
||||
if context, ok := data[r]; ok {
|
||||
if context, ok := data[r.URL]; ok {
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
@@ -70,7 +71,7 @@ func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||
// the request was registered.
|
||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||
mutex.RLock()
|
||||
context, ok := data[r]
|
||||
context, ok := data[r.URL]
|
||||
result := make(map[interface{}]interface{}, len(context))
|
||||
for k, v := range context {
|
||||
result[k] = v
|
||||
@@ -82,8 +83,8 @@ func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||
// Delete removes a value stored for a given key in a given request.
|
||||
func Delete(r *http.Request, key interface{}) {
|
||||
mutex.Lock()
|
||||
if data[r] != nil {
|
||||
delete(data[r], key)
|
||||
if data[r.URL] != nil {
|
||||
delete(data[r.URL], key)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
@@ -94,14 +95,14 @@ func Delete(r *http.Request, key interface{}) {
|
||||
// variables at the end of a request lifetime. See ClearHandler().
|
||||
func Clear(r *http.Request) {
|
||||
mutex.Lock()
|
||||
clear(r)
|
||||
clear(r.URL)
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
// clear is Clear without the lock.
|
||||
func clear(r *http.Request) {
|
||||
delete(data, r)
|
||||
delete(datat, r)
|
||||
func clear(u *url.URL) {
|
||||
delete(data, u)
|
||||
delete(datat, u)
|
||||
}
|
||||
|
||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||
@@ -118,13 +119,13 @@ func Purge(maxAge int) int {
|
||||
count := 0
|
||||
if maxAge <= 0 {
|
||||
count = len(data)
|
||||
data = make(map[*http.Request]map[interface{}]interface{})
|
||||
datat = make(map[*http.Request]int64)
|
||||
data = make(map[*url.URL]map[interface{}]interface{})
|
||||
datat = make(map[*url.URL]int64)
|
||||
} else {
|
||||
min := time.Now().Unix() - int64(maxAge)
|
||||
for r := range data {
|
||||
if datat[r] < min {
|
||||
clear(r)
|
||||
for u := range data {
|
||||
if datat[u] < min {
|
||||
clear(u)
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
6
vendor/github.com/gorilla/context/doc.go
generated
vendored
6
vendor/github.com/gorilla/context/doc.go
generated
vendored
@@ -5,6 +5,12 @@
|
||||
/*
|
||||
Package context stores values shared during a request lifetime.
|
||||
|
||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
||||
does not play well > with the shallow copying of the request that
|
||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
||||
|
||||
For example, a router can set variables extracted from the URL and later
|
||||
application handlers can access those values, or it can be used to store
|
||||
sessions values to be saved at the end of a request. There are several
|
||||
|
||||
1
vendor/github.com/gorilla/handlers/.travis.yml
generated
vendored
1
vendor/github.com/gorilla/handlers/.travis.yml
generated
vendored
@@ -6,6 +6,7 @@ matrix:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
3
vendor/github.com/gorilla/handlers/compress.go
generated
vendored
3
vendor/github.com/gorilla/handlers/compress.go
generated
vendored
@@ -56,6 +56,9 @@ func (w *compressResponseWriter) Flush() {
|
||||
|
||||
// CompressHandler gzip compresses HTTP responses for clients that support it
|
||||
// via the 'Accept-Encoding' header.
|
||||
//
|
||||
// Compressing TLS traffic may leak the page contents to an attacker if the
|
||||
// page contains user input: http://security.stackexchange.com/a/102015/12208
|
||||
func CompressHandler(h http.Handler) http.Handler {
|
||||
return CompressHandlerLevel(h, gzip.DefaultCompression)
|
||||
}
|
||||
|
||||
15
vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
15
vendor/github.com/gorilla/handlers/recovery.go
generated
vendored
@@ -6,9 +6,14 @@ import (
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
|
||||
type RecoveryHandlerLogger interface {
|
||||
Println(...interface{})
|
||||
}
|
||||
|
||||
type recoveryHandler struct {
|
||||
handler http.Handler
|
||||
logger *log.Logger
|
||||
logger RecoveryHandlerLogger
|
||||
printStack bool
|
||||
}
|
||||
|
||||
@@ -46,7 +51,7 @@ func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
|
||||
|
||||
// RecoveryLogger is a functional option to override
|
||||
// the default logger
|
||||
func RecoveryLogger(logger *log.Logger) RecoveryOption {
|
||||
func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption {
|
||||
return func(h http.Handler) {
|
||||
r := h.(*recoveryHandler)
|
||||
r.logger = logger
|
||||
@@ -73,11 +78,11 @@ func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
h.handler.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
func (h recoveryHandler) log(message interface{}) {
|
||||
func (h recoveryHandler) log(v ...interface{}) {
|
||||
if h.logger != nil {
|
||||
h.logger.Println(message)
|
||||
h.logger.Println(v...)
|
||||
} else {
|
||||
log.Println(message)
|
||||
log.Println(v...)
|
||||
}
|
||||
|
||||
if h.printStack {
|
||||
|
||||
1
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
1
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
@@ -8,6 +8,7 @@ matrix:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
|
||||
install:
|
||||
|
||||
63
vendor/github.com/gorilla/mux/README.md
generated
vendored
63
vendor/github.com/gorilla/mux/README.md
generated
vendored
@@ -1,19 +1,43 @@
|
||||
mux
|
||||
gorilla/mux
|
||||
===
|
||||
[](https://godoc.org/github.com/gorilla/mux)
|
||||
[](https://travis-ci.org/gorilla/mux)
|
||||
|
||||

|
||||
|
||||
http://www.gorillatoolkit.org/pkg/mux
|
||||
|
||||
Package `gorilla/mux` implements a request router and dispatcher.
|
||||
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
||||
their respective handler.
|
||||
|
||||
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
||||
|
||||
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
||||
* URL hosts and paths can have variables with an optional regular expression.
|
||||
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
||||
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
||||
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
||||
|
||||
---
|
||||
|
||||
* [Install](#install)
|
||||
* [Examples](#examples)
|
||||
* [Matching Routes](#matching-routes)
|
||||
* [Static Files](#static-files)
|
||||
* [Registered URLs](#registered-urls)
|
||||
* [Full Example](#full-example)
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
||||
|
||||
```sh
|
||||
go get -u github.com/gorilla/mux
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Let's start registering a couple of URL paths and handlers:
|
||||
|
||||
@@ -47,6 +71,8 @@ category := vars["category"]
|
||||
|
||||
And this is all you need to know about the basic usage. More advanced options are explained below.
|
||||
|
||||
### Matching Routes
|
||||
|
||||
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
||||
|
||||
```go
|
||||
@@ -138,6 +164,37 @@ s.HandleFunc("/{key}/", ProductHandler)
|
||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||
```
|
||||
|
||||
### Static Files
|
||||
|
||||
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
||||
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
||||
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
var dir string
|
||||
|
||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||
flag.Parse()
|
||||
r := mux.NewRouter()
|
||||
|
||||
// This will serve files under http://localhost:8000/static/<filename>
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
Addr: "127.0.0.1:8000",
|
||||
// Good practice: enforce timeouts for servers you create!
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
```
|
||||
|
||||
### Registered URLs
|
||||
|
||||
Now let's see how to build registered URLs.
|
||||
|
||||
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
||||
|
||||
29
vendor/github.com/gorilla/mux/doc.go
generated
vendored
29
vendor/github.com/gorilla/mux/doc.go
generated
vendored
@@ -47,6 +47,10 @@ variable will be anything until the next slash. For example:
|
||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
||||
|
||||
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
||||
|
||||
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
||||
|
||||
The names are used to create a map of route variables which can be retrieved
|
||||
calling mux.Vars():
|
||||
|
||||
@@ -136,6 +140,31 @@ the inner routes use it as base for their paths:
|
||||
// "/products/{key}/details"
|
||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
||||
|
||||
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
||||
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
||||
request that matches "/static/*". This makes it easy to serve static files with mux:
|
||||
|
||||
func main() {
|
||||
var dir string
|
||||
|
||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
||||
flag.Parse()
|
||||
r := mux.NewRouter()
|
||||
|
||||
// This will serve files under http://localhost:8000/static/<filename>
|
||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: r,
|
||||
Addr: "127.0.0.1:8000",
|
||||
// Good practice: enforce timeouts for servers you create!
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
log.Fatal(srv.ListenAndServe())
|
||||
}
|
||||
|
||||
Now let's see how to build registered URLs.
|
||||
|
||||
Routes can be named. All routes that define a name can have their URLs built,
|
||||
|
||||
51
vendor/github.com/gorilla/mux/mux.go
generated
vendored
51
vendor/github.com/gorilla/mux/mux.go
generated
vendored
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewRouter returns a new router instance.
|
||||
@@ -52,6 +53,8 @@ type Router struct {
|
||||
// This has no effect when go1.7+ is used, since the context is stored
|
||||
// on the request itself.
|
||||
KeepContext bool
|
||||
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
||||
useEncodedPath bool
|
||||
}
|
||||
|
||||
// Match matches registered routes against the request.
|
||||
@@ -76,8 +79,12 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
||||
// mux.Vars(request).
|
||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if !r.skipClean {
|
||||
path := req.URL.Path
|
||||
if r.useEncodedPath {
|
||||
path = getPath(req)
|
||||
}
|
||||
// Clean path to canonical form and redirect.
|
||||
if p := cleanPath(req.URL.Path); p != req.URL.Path {
|
||||
if p := cleanPath(path); p != path {
|
||||
|
||||
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
||||
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
||||
@@ -150,6 +157,21 @@ func (r *Router) SkipClean(value bool) *Router {
|
||||
return r
|
||||
}
|
||||
|
||||
// UseEncodedPath tells the router to match the encoded original path
|
||||
// to the routes.
|
||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
||||
// This behavior has the drawback of needing to match routes against
|
||||
// r.RequestURI instead of r.URL.Path. Any modifications (such as http.StripPrefix)
|
||||
// to r.URL.Path will not affect routing when this flag is on and thus may
|
||||
// induce unintended behavior.
|
||||
//
|
||||
// If not called, the router will match the unencoded path to the routes.
|
||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
||||
func (r *Router) UseEncodedPath() *Router {
|
||||
r.useEncodedPath = true
|
||||
return r
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// parentRoute
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -187,7 +209,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
|
||||
|
||||
// NewRoute registers an empty route.
|
||||
func (r *Router) NewRoute() *Route {
|
||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean}
|
||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
||||
r.routes = append(r.routes, route)
|
||||
return route
|
||||
}
|
||||
@@ -285,6 +307,9 @@ func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
||||
if err == SkipRouter {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sr := range t.matchers {
|
||||
if h, ok := sr.(*Router); ok {
|
||||
err := h.walk(walkFn, ancestors)
|
||||
@@ -355,6 +380,28 @@ func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
||||
// Helpers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// getPath returns the escaped path if possible; doing what URL.EscapedPath()
|
||||
// which was added in go1.5 does
|
||||
func getPath(req *http.Request) string {
|
||||
if req.RequestURI != "" {
|
||||
// Extract the path from RequestURI (which is escaped unlike URL.Path)
|
||||
// as detailed here as detailed in https://golang.org/pkg/net/url/#URL
|
||||
// for < 1.5 server side workaround
|
||||
// http://localhost/path/here?v=1 -> /path/here
|
||||
path := req.RequestURI
|
||||
path = strings.TrimPrefix(path, req.URL.Scheme+`://`)
|
||||
path = strings.TrimPrefix(path, req.URL.Host)
|
||||
if i := strings.LastIndex(path, "?"); i > -1 {
|
||||
path = path[:i]
|
||||
}
|
||||
if i := strings.LastIndex(path, "#"); i > -1 {
|
||||
path = path[:i]
|
||||
}
|
||||
return path
|
||||
}
|
||||
return req.URL.Path
|
||||
}
|
||||
|
||||
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
||||
// Borrowed from the net/http package.
|
||||
func cleanPath(p string) string {
|
||||
|
||||
50
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
50
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
@@ -24,7 +24,7 @@ import (
|
||||
// Previously we accepted only Python-like identifiers for variable
|
||||
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
||||
// name and pattern can't be empty, and names can't contain a colon.
|
||||
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) {
|
||||
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
|
||||
// Check if it is well-formed.
|
||||
idxs, errBraces := braceIndices(tpl)
|
||||
if errBraces != nil {
|
||||
@@ -111,14 +111,15 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
|
||||
}
|
||||
// Done!
|
||||
return &routeRegexp{
|
||||
template: template,
|
||||
matchHost: matchHost,
|
||||
matchQuery: matchQuery,
|
||||
strictSlash: strictSlash,
|
||||
regexp: reg,
|
||||
reverse: reverse.String(),
|
||||
varsN: varsN,
|
||||
varsR: varsR,
|
||||
template: template,
|
||||
matchHost: matchHost,
|
||||
matchQuery: matchQuery,
|
||||
strictSlash: strictSlash,
|
||||
useEncodedPath: useEncodedPath,
|
||||
regexp: reg,
|
||||
reverse: reverse.String(),
|
||||
varsN: varsN,
|
||||
varsR: varsR,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -133,6 +134,9 @@ type routeRegexp struct {
|
||||
matchQuery bool
|
||||
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
|
||||
strictSlash bool
|
||||
// Determines whether to use encoded path from getPath function or unencoded
|
||||
// req.URL.Path for path matching
|
||||
useEncodedPath bool
|
||||
// Expanded regexp.
|
||||
regexp *regexp.Regexp
|
||||
// Reverse template.
|
||||
@@ -149,8 +153,11 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
||||
if r.matchQuery {
|
||||
return r.matchQueryString(req)
|
||||
}
|
||||
|
||||
return r.regexp.MatchString(req.URL.Path)
|
||||
path := req.URL.Path
|
||||
if r.useEncodedPath {
|
||||
path = getPath(req)
|
||||
}
|
||||
return r.regexp.MatchString(path)
|
||||
}
|
||||
|
||||
return r.regexp.MatchString(getHost(req))
|
||||
@@ -253,14 +260,18 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
|
||||
extractVars(host, matches, v.host.varsN, m.Vars)
|
||||
}
|
||||
}
|
||||
path := req.URL.Path
|
||||
if r.useEncodedPath {
|
||||
path = getPath(req)
|
||||
}
|
||||
// Store path variables.
|
||||
if v.path != nil {
|
||||
matches := v.path.regexp.FindStringSubmatchIndex(req.URL.Path)
|
||||
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
||||
if len(matches) > 0 {
|
||||
extractVars(req.URL.Path, matches, v.path.varsN, m.Vars)
|
||||
extractVars(path, matches, v.path.varsN, m.Vars)
|
||||
// Check if we should redirect.
|
||||
if v.path.strictSlash {
|
||||
p1 := strings.HasSuffix(req.URL.Path, "/")
|
||||
p1 := strings.HasSuffix(path, "/")
|
||||
p2 := strings.HasSuffix(v.path.template, "/")
|
||||
if p1 != p2 {
|
||||
u, _ := url.Parse(req.URL.String())
|
||||
@@ -299,14 +310,7 @@ func getHost(r *http.Request) string {
|
||||
}
|
||||
|
||||
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
||||
matchesCount := 0
|
||||
prevEnd := -1
|
||||
for i := 2; i < len(matches) && matchesCount < len(names); i += 2 {
|
||||
if prevEnd < matches[i+1] {
|
||||
value := input[matches[i]:matches[i+1]]
|
||||
output[names[matchesCount]] = value
|
||||
prevEnd = matches[i+1]
|
||||
matchesCount++
|
||||
}
|
||||
for i, name := range names {
|
||||
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
||||
}
|
||||
}
|
||||
|
||||
4
vendor/github.com/gorilla/mux/route.go
generated
vendored
4
vendor/github.com/gorilla/mux/route.go
generated
vendored
@@ -29,6 +29,8 @@ type Route struct {
|
||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
||||
// will not redirect
|
||||
skipClean bool
|
||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
||||
useEncodedPath bool
|
||||
// If true, this route never matches: it is only used to build URLs.
|
||||
buildOnly bool
|
||||
// The name used to build URLs.
|
||||
@@ -158,7 +160,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
|
||||
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
||||
}
|
||||
}
|
||||
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash)
|
||||
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
19
vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.3
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
||||
@@ -1,16 +1,16 @@
|
||||
Copyright (c) 2013 Google. All rights reserved.
|
||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
78
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
Normal file
78
vendor/github.com/gorilla/securecookie/README.md
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
securecookie
|
||||
============
|
||||
[](https://godoc.org/github.com/gorilla/securecookie) [](https://travis-ci.org/gorilla/securecookie)
|
||||
|
||||
securecookie encodes and decodes authenticated and optionally encrypted
|
||||
cookie values.
|
||||
|
||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
||||
When encrypted, the content is also inaccessible to malicious eyes. It is still
|
||||
recommended that sensitive data not be stored in cookies, and that HTTPS be used
|
||||
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
|
||||
|
||||
## Examples
|
||||
|
||||
To use it, first create a new SecureCookie instance:
|
||||
|
||||
```go
|
||||
// Hash keys should be at least 32 bytes long
|
||||
var hashKey = []byte("very-secret")
|
||||
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
|
||||
// Shorter keys may weaken the encryption used.
|
||||
var blockKey = []byte("a-lot-secret")
|
||||
var s = securecookie.New(hashKey, blockKey)
|
||||
```
|
||||
|
||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
||||
It is recommended to use a key with 32 or 64 bytes.
|
||||
|
||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
||||
to not use encryption. If set, the length must correspond to the block size
|
||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||
|
||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
||||
|
||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
||||
|
||||
```go
|
||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
value := map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
||||
cookie := &http.Cookie{
|
||||
Name: "cookie-name",
|
||||
Value: encoded,
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
||||
value:
|
||||
|
||||
```go
|
||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
||||
value := make(map[string]string)
|
||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We stored a map[string]string, but secure cookies can hold any value that
|
||||
can be encoded using `encoding/gob`. To store custom types, they must be
|
||||
registered first using gob.Register(). For basic types this is not needed;
|
||||
it works out of the box. An optional JSON encoder that uses `encoding/json` is
|
||||
available for types compatible with JSON.
|
||||
|
||||
## License
|
||||
|
||||
BSD licensed. See the LICENSE file for details.
|
||||
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
Normal file
61
vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package securecookie encodes and decodes authenticated and optionally
|
||||
encrypted cookie values.
|
||||
|
||||
Secure cookies can't be forged, because their values are validated using HMAC.
|
||||
When encrypted, the content is also inaccessible to malicious eyes.
|
||||
|
||||
To use it, first create a new SecureCookie instance:
|
||||
|
||||
var hashKey = []byte("very-secret")
|
||||
var blockKey = []byte("a-lot-secret")
|
||||
var s = securecookie.New(hashKey, blockKey)
|
||||
|
||||
The hashKey is required, used to authenticate the cookie value using HMAC.
|
||||
It is recommended to use a key with 32 or 64 bytes.
|
||||
|
||||
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
||||
to not use encryption. If set, the length must correspond to the block size
|
||||
of the encryption algorithm. For AES, used by default, valid lengths are
|
||||
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||
|
||||
Strong keys can be created using the convenience function GenerateRandomKey().
|
||||
|
||||
Once a SecureCookie instance is set, use it to encode a cookie value:
|
||||
|
||||
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
value := map[string]string{
|
||||
"foo": "bar",
|
||||
}
|
||||
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
||||
cookie := &http.Cookie{
|
||||
Name: "cookie-name",
|
||||
Value: encoded,
|
||||
Path: "/",
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
}
|
||||
|
||||
Later, use the same SecureCookie instance to decode and validate a cookie
|
||||
value:
|
||||
|
||||
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
||||
value := make(map[string]string)
|
||||
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
||||
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
We stored a map[string]string, but secure cookies can hold any value that
|
||||
can be encoded using encoding/gob. To store custom types, they must be
|
||||
registered first using gob.Register(). For basic types this is not needed;
|
||||
it works out of the box.
|
||||
*/
|
||||
package securecookie
|
||||
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
Normal file
25
vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// +build gofuzz
|
||||
|
||||
package securecookie
|
||||
|
||||
var hashKey = []byte("very-secret12345")
|
||||
var blockKey = []byte("a-lot-secret1234")
|
||||
var s = New(hashKey, blockKey)
|
||||
|
||||
type Cookie struct {
|
||||
B bool
|
||||
I int
|
||||
S string
|
||||
}
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
datas := string(data)
|
||||
var c Cookie
|
||||
if err := s.Decode("fuzz", datas, &c); err != nil {
|
||||
return 0
|
||||
}
|
||||
if _, err := s.Encode("fuzz", c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
Normal file
646
vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package securecookie
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Error is the interface of all errors returned by functions in this library.
|
||||
type Error interface {
|
||||
error
|
||||
|
||||
// IsUsage returns true for errors indicating the client code probably
|
||||
// uses this library incorrectly. For example, the client may have
|
||||
// failed to provide a valid hash key, or may have failed to configure
|
||||
// the Serializer adequately for encoding value.
|
||||
IsUsage() bool
|
||||
|
||||
// IsDecode returns true for errors indicating that a cookie could not
|
||||
// be decoded and validated. Since cookies are usually untrusted
|
||||
// user-provided input, errors of this type should be expected.
|
||||
// Usually, the proper action is simply to reject the request.
|
||||
IsDecode() bool
|
||||
|
||||
// IsInternal returns true for unexpected errors occurring in the
|
||||
// securecookie implementation.
|
||||
IsInternal() bool
|
||||
|
||||
// Cause, if it returns a non-nil value, indicates that this error was
|
||||
// propagated from some underlying library. If this method returns nil,
|
||||
// this error was raised directly by this library.
|
||||
//
|
||||
// Cause is provided principally for debugging/logging purposes; it is
|
||||
// rare that application logic should perform meaningfully different
|
||||
// logic based on Cause. See, for example, the caveats described on
|
||||
// (MultiError).Cause().
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// errorType is a bitmask giving the error type(s) of an cookieError value.
|
||||
type errorType int
|
||||
|
||||
const (
|
||||
usageError = errorType(1 << iota)
|
||||
decodeError
|
||||
internalError
|
||||
)
|
||||
|
||||
type cookieError struct {
|
||||
typ errorType
|
||||
msg string
|
||||
cause error
|
||||
}
|
||||
|
||||
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
|
||||
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
|
||||
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
|
||||
|
||||
func (e cookieError) Cause() error { return e.cause }
|
||||
|
||||
func (e cookieError) Error() string {
|
||||
parts := []string{"securecookie: "}
|
||||
if e.msg == "" {
|
||||
parts = append(parts, "error")
|
||||
} else {
|
||||
parts = append(parts, e.msg)
|
||||
}
|
||||
if c := e.Cause(); c != nil {
|
||||
parts = append(parts, " - caused by: ", c.Error())
|
||||
}
|
||||
return strings.Join(parts, "")
|
||||
}
|
||||
|
||||
var (
|
||||
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
|
||||
|
||||
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
|
||||
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
|
||||
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
|
||||
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
|
||||
|
||||
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
|
||||
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
|
||||
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
|
||||
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
|
||||
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
|
||||
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
|
||||
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
|
||||
|
||||
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
|
||||
// could not be extracted and verified. Direct use of this error
|
||||
// variable is deprecated; it is public only for legacy compatibility,
|
||||
// and may be privatized in the future, as it is rarely useful to
|
||||
// distinguish between this error and other Error implementations.
|
||||
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
|
||||
)
|
||||
|
||||
// Codec defines an interface to encode and decode cookie values.
|
||||
type Codec interface {
|
||||
Encode(name string, value interface{}) (string, error)
|
||||
Decode(name, value string, dst interface{}) error
|
||||
}
|
||||
|
||||
// New returns a new SecureCookie.
|
||||
//
|
||||
// hashKey is required, used to authenticate values using HMAC. Create it using
|
||||
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
|
||||
//
|
||||
// blockKey is optional, used to encrypt values. Create it using
|
||||
// GenerateRandomKey(). The key length must correspond to the block size
|
||||
// of the encryption algorithm. For AES, used by default, valid lengths are
|
||||
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||
// The default encoder used for cookie serialization is encoding/gob.
|
||||
//
|
||||
// Note that keys created using GenerateRandomKey() are not automatically
|
||||
// persisted. New keys will be created when the application is restarted, and
|
||||
// previously issued cookies will not be able to be decoded.
|
||||
func New(hashKey, blockKey []byte) *SecureCookie {
|
||||
s := &SecureCookie{
|
||||
hashKey: hashKey,
|
||||
blockKey: blockKey,
|
||||
hashFunc: sha256.New,
|
||||
maxAge: 86400 * 30,
|
||||
maxLength: 4096,
|
||||
sz: GobEncoder{},
|
||||
}
|
||||
if hashKey == nil {
|
||||
s.err = errHashKeyNotSet
|
||||
}
|
||||
if blockKey != nil {
|
||||
s.BlockFunc(aes.NewCipher)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SecureCookie encodes and decodes authenticated and optionally encrypted
|
||||
// cookie values.
|
||||
type SecureCookie struct {
|
||||
hashKey []byte
|
||||
hashFunc func() hash.Hash
|
||||
blockKey []byte
|
||||
block cipher.Block
|
||||
maxLength int
|
||||
maxAge int64
|
||||
minAge int64
|
||||
err error
|
||||
sz Serializer
|
||||
// For testing purposes, the function that returns the current timestamp.
|
||||
// If not set, it will use time.Now().UTC().Unix().
|
||||
timeFunc func() int64
|
||||
}
|
||||
|
||||
// Serializer provides an interface for providing custom serializers for cookie
|
||||
// values.
|
||||
type Serializer interface {
|
||||
Serialize(src interface{}) ([]byte, error)
|
||||
Deserialize(src []byte, dst interface{}) error
|
||||
}
|
||||
|
||||
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
|
||||
// encoder and can handle complex types via gob.Register.
|
||||
type GobEncoder struct{}
|
||||
|
||||
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
|
||||
// encode complex types need to satisfy the json.Marshaller and
|
||||
// json.Unmarshaller interfaces.
|
||||
type JSONEncoder struct{}
|
||||
|
||||
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
|
||||
// (as an interface{}) and returns a []byte. This is particularly useful when
|
||||
// you encoding an object upstream and do not wish to re-encode it.
|
||||
type NopEncoder struct{}
|
||||
|
||||
// MaxLength restricts the maximum length, in bytes, for the cookie value.
|
||||
//
|
||||
// Default is 4096, which is the maximum value accepted by Internet Explorer.
|
||||
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
|
||||
s.maxLength = value
|
||||
return s
|
||||
}
|
||||
|
||||
// MaxAge restricts the maximum age, in seconds, for the cookie value.
|
||||
//
|
||||
// Default is 86400 * 30. Set it to 0 for no restriction.
|
||||
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
|
||||
s.maxAge = int64(value)
|
||||
return s
|
||||
}
|
||||
|
||||
// MinAge restricts the minimum age, in seconds, for the cookie value.
|
||||
//
|
||||
// Default is 0 (no restriction).
|
||||
func (s *SecureCookie) MinAge(value int) *SecureCookie {
|
||||
s.minAge = int64(value)
|
||||
return s
|
||||
}
|
||||
|
||||
// HashFunc sets the hash function used to create HMAC.
|
||||
//
|
||||
// Default is crypto/sha256.New.
|
||||
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
|
||||
s.hashFunc = f
|
||||
return s
|
||||
}
|
||||
|
||||
// BlockFunc sets the encryption function used to create a cipher.Block.
|
||||
//
|
||||
// Default is crypto/aes.New.
|
||||
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
|
||||
if s.blockKey == nil {
|
||||
s.err = errBlockKeyNotSet
|
||||
} else if block, err := f(s.blockKey); err == nil {
|
||||
s.block = block
|
||||
} else {
|
||||
s.err = cookieError{cause: err, typ: usageError}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Encoding sets the encoding/serialization method for cookies.
|
||||
//
|
||||
// Default is encoding/gob. To encode special structures using encoding/gob,
|
||||
// they must be registered first using gob.Register().
|
||||
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
|
||||
s.sz = sz
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Encode encodes a cookie value.
|
||||
//
|
||||
// It serializes, optionally encrypts, signs with a message authentication code,
|
||||
// and finally encodes the value.
|
||||
//
|
||||
// The name argument is the cookie name. It is stored with the encoded value.
|
||||
// The value argument is the value to be encoded. It can be any value that can
|
||||
// be encoded using the currently selected serializer; see SetSerializer().
|
||||
//
|
||||
// It is the client's responsibility to ensure that value, when encoded using
|
||||
// the current serialization/encryption settings on s and then base64-encoded,
|
||||
// is shorter than the maximum permissible length.
|
||||
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
|
||||
if s.err != nil {
|
||||
return "", s.err
|
||||
}
|
||||
if s.hashKey == nil {
|
||||
s.err = errHashKeyNotSet
|
||||
return "", s.err
|
||||
}
|
||||
var err error
|
||||
var b []byte
|
||||
// 1. Serialize.
|
||||
if b, err = s.sz.Serialize(value); err != nil {
|
||||
return "", cookieError{cause: err, typ: usageError}
|
||||
}
|
||||
// 2. Encrypt (optional).
|
||||
if s.block != nil {
|
||||
if b, err = encrypt(s.block, b); err != nil {
|
||||
return "", cookieError{cause: err, typ: usageError}
|
||||
}
|
||||
}
|
||||
b = encode(b)
|
||||
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
||||
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
|
||||
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
|
||||
// Append mac, remove name.
|
||||
b = append(b, mac...)[len(name)+1:]
|
||||
// 4. Encode to base64.
|
||||
b = encode(b)
|
||||
// 5. Check length.
|
||||
if s.maxLength != 0 && len(b) > s.maxLength {
|
||||
return "", errEncodedValueTooLong
|
||||
}
|
||||
// Done.
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
// Decode decodes a cookie value.
|
||||
//
|
||||
// It decodes, verifies a message authentication code, optionally decrypts and
|
||||
// finally deserializes the value.
|
||||
//
|
||||
// The name argument is the cookie name. It must be the same name used when
|
||||
// it was stored. The value argument is the encoded cookie value. The dst
|
||||
// argument is where the cookie will be decoded. It must be a pointer.
|
||||
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
|
||||
if s.err != nil {
|
||||
return s.err
|
||||
}
|
||||
if s.hashKey == nil {
|
||||
s.err = errHashKeyNotSet
|
||||
return s.err
|
||||
}
|
||||
// 1. Check length.
|
||||
if s.maxLength != 0 && len(value) > s.maxLength {
|
||||
return errValueToDecodeTooLong
|
||||
}
|
||||
// 2. Decode from base64.
|
||||
b, err := decode([]byte(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 3. Verify MAC. Value is "date|value|mac".
|
||||
parts := bytes.SplitN(b, []byte("|"), 3)
|
||||
if len(parts) != 3 {
|
||||
return ErrMacInvalid
|
||||
}
|
||||
h := hmac.New(s.hashFunc, s.hashKey)
|
||||
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
|
||||
if err = verifyMac(h, b, parts[2]); err != nil {
|
||||
return err
|
||||
}
|
||||
// 4. Verify date ranges.
|
||||
var t1 int64
|
||||
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
|
||||
return errTimestampInvalid
|
||||
}
|
||||
t2 := s.timestamp()
|
||||
if s.minAge != 0 && t1 > t2-s.minAge {
|
||||
return errTimestampTooNew
|
||||
}
|
||||
if s.maxAge != 0 && t1 < t2-s.maxAge {
|
||||
return errTimestampExpired
|
||||
}
|
||||
// 5. Decrypt (optional).
|
||||
b, err = decode(parts[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.block != nil {
|
||||
if b, err = decrypt(s.block, b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 6. Deserialize.
|
||||
if err = s.sz.Deserialize(b, dst); err != nil {
|
||||
return cookieError{cause: err, typ: decodeError}
|
||||
}
|
||||
// Done.
|
||||
return nil
|
||||
}
|
||||
|
||||
// timestamp returns the current timestamp, in seconds.
|
||||
//
|
||||
// For testing purposes, the function that generates the timestamp can be
|
||||
// overridden. If not set, it will return time.Now().UTC().Unix().
|
||||
func (s *SecureCookie) timestamp() int64 {
|
||||
if s.timeFunc == nil {
|
||||
return time.Now().UTC().Unix()
|
||||
}
|
||||
return s.timeFunc()
|
||||
}
|
||||
|
||||
// Authentication -------------------------------------------------------------
|
||||
|
||||
// createMac creates a message authentication code (MAC).
|
||||
func createMac(h hash.Hash, value []byte) []byte {
|
||||
h.Write(value)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// verifyMac verifies that a message authentication code (MAC) is valid.
|
||||
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
|
||||
mac2 := createMac(h, value)
|
||||
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
|
||||
// does not do this prior to Go 1.4.
|
||||
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
|
||||
return nil
|
||||
}
|
||||
return ErrMacInvalid
|
||||
}
|
||||
|
||||
// Encryption -----------------------------------------------------------------
|
||||
|
||||
// encrypt encrypts a value using the given block in counter mode.
|
||||
//
|
||||
// A random initialization vector (http://goo.gl/zF67k) with the length of the
|
||||
// block size is prepended to the resulting ciphertext.
|
||||
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
|
||||
iv := GenerateRandomKey(block.BlockSize())
|
||||
if iv == nil {
|
||||
return nil, errGeneratingIV
|
||||
}
|
||||
// Encrypt it.
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
stream.XORKeyStream(value, value)
|
||||
// Return iv + ciphertext.
|
||||
return append(iv, value...), nil
|
||||
}
|
||||
|
||||
// decrypt decrypts a value using the given block in counter mode.
|
||||
//
|
||||
// The value to be decrypted must be prepended by a initialization vector
|
||||
// (http://goo.gl/zF67k) with the length of the block size.
|
||||
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
|
||||
size := block.BlockSize()
|
||||
if len(value) > size {
|
||||
// Extract iv.
|
||||
iv := value[:size]
|
||||
// Extract ciphertext.
|
||||
value = value[size:]
|
||||
// Decrypt it.
|
||||
stream := cipher.NewCTR(block, iv)
|
||||
stream.XORKeyStream(value, value)
|
||||
return value, nil
|
||||
}
|
||||
return nil, errDecryptionFailed
|
||||
}
|
||||
|
||||
// Serialization --------------------------------------------------------------
|
||||
|
||||
// Serialize encodes a value using gob.
|
||||
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(src); err != nil {
|
||||
return nil, cookieError{cause: err, typ: usageError}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a value using gob.
|
||||
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||
dec := gob.NewDecoder(bytes.NewBuffer(src))
|
||||
if err := dec.Decode(dst); err != nil {
|
||||
return cookieError{cause: err, typ: decodeError}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize encodes a value using encoding/json.
|
||||
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
if err := enc.Encode(src); err != nil {
|
||||
return nil, cookieError{cause: err, typ: usageError}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a value using encoding/json.
|
||||
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||
dec := json.NewDecoder(bytes.NewReader(src))
|
||||
if err := dec.Decode(dst); err != nil {
|
||||
return cookieError{cause: err, typ: decodeError}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize passes a []byte through as-is.
|
||||
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||
if b, ok := src.([]byte); ok {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
return nil, errValueNotByte
|
||||
}
|
||||
|
||||
// Deserialize passes a []byte through as-is.
|
||||
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||
if dat, ok := dst.(*[]byte); ok {
|
||||
*dat = src
|
||||
return nil
|
||||
}
|
||||
return errValueNotBytePtr
|
||||
}
|
||||
|
||||
// Encoding -------------------------------------------------------------------
|
||||
|
||||
// encode encodes a value using base64.
|
||||
func encode(value []byte) []byte {
|
||||
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
|
||||
base64.URLEncoding.Encode(encoded, value)
|
||||
return encoded
|
||||
}
|
||||
|
||||
// decode decodes a cookie using base64.
|
||||
func decode(value []byte) ([]byte, error) {
|
||||
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
|
||||
b, err := base64.URLEncoding.Decode(decoded, value)
|
||||
if err != nil {
|
||||
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
|
||||
}
|
||||
return decoded[:b], nil
|
||||
}
|
||||
|
||||
// Helpers --------------------------------------------------------------------
|
||||
|
||||
// GenerateRandomKey creates a random key with the given length in bytes.
|
||||
// On failure, returns nil.
|
||||
//
|
||||
// Callers should explicitly check for the possibility of a nil return, treat
|
||||
// it as a failure of the system random number generator, and not continue.
|
||||
func GenerateRandomKey(length int) []byte {
|
||||
k := make([]byte, length)
|
||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||
return nil
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// CodecsFromPairs returns a slice of SecureCookie instances.
|
||||
//
|
||||
// It is a convenience function to create a list of codecs for key rotation. Note
|
||||
// that the generated Codecs will have the default options applied: callers
|
||||
// should iterate over each Codec and type-assert the underlying *SecureCookie to
|
||||
// change these.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// codecs := securecookie.CodecsFromPairs(
|
||||
// []byte("new-hash-key"),
|
||||
// []byte("new-block-key"),
|
||||
// []byte("old-hash-key"),
|
||||
// []byte("old-block-key"),
|
||||
// )
|
||||
//
|
||||
// // Modify each instance.
|
||||
// for _, s := range codecs {
|
||||
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
|
||||
// cookie.MaxAge(86400 * 7)
|
||||
// cookie.SetSerializer(securecookie.JSONEncoder{})
|
||||
// cookie.HashFunc(sha512.New512_256)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
|
||||
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
|
||||
for i := 0; i < len(keyPairs); i += 2 {
|
||||
var blockKey []byte
|
||||
if i+1 < len(keyPairs) {
|
||||
blockKey = keyPairs[i+1]
|
||||
}
|
||||
codecs[i/2] = New(keyPairs[i], blockKey)
|
||||
}
|
||||
return codecs
|
||||
}
|
||||
|
||||
// EncodeMulti encodes a cookie value using a group of codecs.
|
||||
//
|
||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
||||
// key rotation.
|
||||
//
|
||||
// On error, may return a MultiError.
|
||||
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
|
||||
if len(codecs) == 0 {
|
||||
return "", errNoCodecs
|
||||
}
|
||||
|
||||
var errors MultiError
|
||||
for _, codec := range codecs {
|
||||
encoded, err := codec.Encode(name, value)
|
||||
if err == nil {
|
||||
return encoded, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return "", errors
|
||||
}
|
||||
|
||||
// DecodeMulti decodes a cookie value using a group of codecs.
|
||||
//
|
||||
// The codecs are tried in order. Multiple codecs are accepted to allow
|
||||
// key rotation.
|
||||
//
|
||||
// On error, may return a MultiError.
|
||||
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
|
||||
if len(codecs) == 0 {
|
||||
return errNoCodecs
|
||||
}
|
||||
|
||||
var errors MultiError
|
||||
for _, codec := range codecs {
|
||||
err := codec.Decode(name, value, dst)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
// MultiError groups multiple errors.
|
||||
type MultiError []error
|
||||
|
||||
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
|
||||
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
|
||||
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
|
||||
|
||||
// Cause returns nil for MultiError; there is no unique underlying cause in the
|
||||
// general case.
|
||||
//
|
||||
// Note: we could conceivably return a non-nil Cause only when there is exactly
|
||||
// one child error with a Cause. However, it would be brittle for client code
|
||||
// to rely on the arity of causes inside a MultiError, so we have opted not to
|
||||
// provide this functionality. Clients which really wish to access the Causes
|
||||
// of the underlying errors are free to iterate through the errors themselves.
|
||||
func (m MultiError) Cause() error { return nil }
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
s, n := "", 0
|
||||
for _, e := range m {
|
||||
if e != nil {
|
||||
if n == 0 {
|
||||
s = e.Error()
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
switch n {
|
||||
case 0:
|
||||
return "(0 errors)"
|
||||
case 1:
|
||||
return s
|
||||
case 2:
|
||||
return s + " (and 1 other error)"
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||
}
|
||||
|
||||
// any returns true if any element of m is an Error for which pred returns true.
|
||||
func (m MultiError) any(pred func(Error) bool) bool {
|
||||
for _, e := range m {
|
||||
if ourErr, ok := e.(Error); ok && pred(ourErr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
22
vendor/github.com/gorilla/sessions/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/gorilla/sessions/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.3
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
install:
|
||||
- # skip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
||||
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
79
vendor/github.com/gorilla/sessions/README.md
generated
vendored
Normal file
79
vendor/github.com/gorilla/sessions/README.md
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
sessions
|
||||
========
|
||||
[](https://godoc.org/github.com/gorilla/sessions) [](https://travis-ci.org/gorilla/sessions)
|
||||
|
||||
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
|
||||
custom session backends.
|
||||
|
||||
The key features are:
|
||||
|
||||
* Simple API: use it as an easy way to set signed (and optionally
|
||||
encrypted) cookies.
|
||||
* Built-in backends to store sessions in cookies or the filesystem.
|
||||
* Flash messages: session values that last until read.
|
||||
* Convenient way to switch session persistency (aka "remember me") and set
|
||||
other attributes.
|
||||
* Mechanism to rotate authentication and encryption keys.
|
||||
* Multiple sessions per request, even using different backends.
|
||||
* Interfaces and infrastructure for custom session backends: sessions from
|
||||
different stores can be retrieved and batch-saved using a common API.
|
||||
|
||||
Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session. We're ignoring the error resulted from decoding an
|
||||
// existing session: Get() always returns a session, even if empty.
|
||||
session, _ := store.Get(r, "session-name")
|
||||
// Set some session values.
|
||||
session.Values["foo"] = "bar"
|
||||
session.Values[42] = 43
|
||||
// Save it before we write to the response/return from the handler.
|
||||
session.Save(r, w)
|
||||
}
|
||||
```
|
||||
|
||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
`store.Get()` to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
And finally we call `session.Save()` to save the session in the response.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with
|
||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
||||
as or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
mux when calling http.ListenAndServe:
|
||||
|
||||
More examples are available [on the Gorilla
|
||||
website](http://www.gorillatoolkit.org/pkg/sessions).
|
||||
|
||||
## Store Implementations
|
||||
|
||||
Other implementations of the `sessions.Store` interface:
|
||||
|
||||
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
|
||||
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
|
||||
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
|
||||
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
|
||||
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
|
||||
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
|
||||
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
|
||||
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
|
||||
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
|
||||
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
|
||||
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
|
||||
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
|
||||
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
|
||||
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
|
||||
|
||||
## License
|
||||
|
||||
BSD licensed. See the LICENSE file for details.
|
||||
199
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
Normal file
199
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package sessions provides cookie and filesystem sessions and
|
||||
infrastructure for custom session backends.
|
||||
|
||||
The key features are:
|
||||
|
||||
* Simple API: use it as an easy way to set signed (and optionally
|
||||
encrypted) cookies.
|
||||
* Built-in backends to store sessions in cookies or the filesystem.
|
||||
* Flash messages: session values that last until read.
|
||||
* Convenient way to switch session persistency (aka "remember me") and set
|
||||
other attributes.
|
||||
* Mechanism to rotate authentication and encryption keys.
|
||||
* Multiple sessions per request, even using different backends.
|
||||
* Interfaces and infrastructure for custom session backends: sessions from
|
||||
different stores can be retrieved and batch-saved using a common API.
|
||||
|
||||
Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session. We're ignoring the error resulted from decoding an
|
||||
// existing session: Get() always returns a session, even if empty.
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set some session values.
|
||||
session.Values["foo"] = "bar"
|
||||
session.Values[42] = 43
|
||||
// Save it before we write to the response/return from the handler.
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
First we initialize a session store calling NewCookieStore() and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
store.Get() to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a map[interface{}]interface{}.
|
||||
And finally we call session.Save() to save the session in the response.
|
||||
|
||||
Note that in production code, we should check for errors when calling
|
||||
session.Save(r, w), and either display an error message or otherwise handle it.
|
||||
|
||||
Save must be called before writing to the response, otherwise the session
|
||||
cookie will not be sent to the client.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with context.ClearHandler as or else you will leak memory! An easy way to do this
|
||||
is to wrap the top-level mux when calling http.ListenAndServe:
|
||||
|
||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
||||
|
||||
The ClearHandler function is provided by the gorilla/context package.
|
||||
|
||||
That's all you need to know for the basic usage. Let's take a look at other
|
||||
options, starting with flash messages.
|
||||
|
||||
Flash messages are session values that last until read. The term appeared with
|
||||
Ruby On Rails a few years back. When we request a flash message, it is removed
|
||||
from the session. To add a flash, call session.AddFlash(), and to get all
|
||||
flashes, call session.Flashes(). Here is an example:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session.
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the previously flashes, if any.
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
// Use the flash values.
|
||||
} else {
|
||||
// Set a new flash.
|
||||
session.AddFlash("Hello, flash messages world!")
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
Flash messages are useful to set information to be read after a redirection,
|
||||
like after form submissions.
|
||||
|
||||
There may also be cases where you want to store a complex datatype within a
|
||||
session, such as a struct. Sessions are serialised using the encoding/gob package,
|
||||
so it is easy to register new datatypes for storage in sessions:
|
||||
|
||||
import(
|
||||
"encoding/gob"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
Email string
|
||||
Age int
|
||||
}
|
||||
|
||||
type M map[string]interface{}
|
||||
|
||||
func init() {
|
||||
|
||||
gob.Register(&Person{})
|
||||
gob.Register(&M{})
|
||||
}
|
||||
|
||||
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
|
||||
relies on us passing it a value of the desired type. In the example above we've passed
|
||||
it a pointer to a struct and a pointer to a custom type representing a
|
||||
map[string]interface. (We could have passed non-pointer values if we wished.) This will
|
||||
then allow us to serialise/deserialise values of those types to and from our sessions.
|
||||
|
||||
Note that because session values are stored in a map[string]interface{}, there's
|
||||
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve our struct and type-assert it
|
||||
val := session.Values["person"]
|
||||
var person = &Person{}
|
||||
if person, ok := val.(*Person); !ok {
|
||||
// Handle the case that it's not an expected type
|
||||
}
|
||||
|
||||
// Now we can use our person object
|
||||
}
|
||||
|
||||
By default, session cookies last for a month. This is probably too long for
|
||||
some cases, but it is easy to change this and other attributes during
|
||||
runtime. Sessions can be configured individually or the store can be
|
||||
configured and then all sessions saved using it will use that configuration.
|
||||
We access session.Options or store.Options to set a new configuration. The
|
||||
fields are basically a subset of http.Cookie fields. Let's change the
|
||||
maximum age of a session to one week:
|
||||
|
||||
session.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 7,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
Sometimes we may want to change authentication and/or encryption keys without
|
||||
breaking existing sessions. The CookieStore supports key rotation, and to use
|
||||
it you just need to set multiple authentication and encryption keys, in pairs,
|
||||
to be tested in order:
|
||||
|
||||
var store = sessions.NewCookieStore(
|
||||
[]byte("new-authentication-key"),
|
||||
[]byte("new-encryption-key"),
|
||||
[]byte("old-authentication-key"),
|
||||
[]byte("old-encryption-key"),
|
||||
)
|
||||
|
||||
New sessions will be saved using the first pair. Old sessions can still be
|
||||
read because the first pair will fail, and the second will be tested. This
|
||||
makes it easy to "rotate" secret keys and still be able to validate existing
|
||||
sessions. Note: for all pairs the encryption key is optional; set it to nil
|
||||
or omit it and and encryption won't be used.
|
||||
|
||||
Multiple sessions can be used in the same request, even with different
|
||||
session backends. When this happens, calling Save() on each session
|
||||
individually would be cumbersome, so we have a way to save all sessions
|
||||
at once: it's sessions.Save(). Here's an example:
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session and set a value.
|
||||
session1, _ := store.Get(r, "session-one")
|
||||
session1.Values["foo"] = "bar"
|
||||
// Get another session and set another value.
|
||||
session2, _ := store.Get(r, "session-two")
|
||||
session2.Values[42] = 43
|
||||
// Save all sessions.
|
||||
sessions.Save(r, w)
|
||||
}
|
||||
|
||||
This is possible because when we call Get() from a session store, it adds the
|
||||
session to a common registry. Save() uses it to save all registered sessions.
|
||||
*/
|
||||
package sessions
|
||||
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
Normal file
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// This file contains code adapted from the Go standard library
|
||||
// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
|
||||
|
||||
package sessions
|
||||
|
||||
import "strings"
|
||||
|
||||
var isTokenTable = [127]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'W': true,
|
||||
'V': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'|': true,
|
||||
'~': true,
|
||||
}
|
||||
|
||||
func isToken(r rune) bool {
|
||||
i := int(r)
|
||||
return i < len(isTokenTable) && isTokenTable[i]
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !isToken(r)
|
||||
}
|
||||
|
||||
func isCookieNameValid(raw string) bool {
|
||||
if raw == "" {
|
||||
return false
|
||||
}
|
||||
return strings.IndexFunc(raw, isNotToken) < 0
|
||||
}
|
||||
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
Normal file
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
// Default flashes key.
|
||||
const flashesKey = "_flash"
|
||||
|
||||
// Options --------------------------------------------------------------------
|
||||
|
||||
// Options stores configuration for a session or session store.
|
||||
//
|
||||
// Fields are a subset of http.Cookie fields.
|
||||
type Options struct {
|
||||
Path string
|
||||
Domain string
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
}
|
||||
|
||||
// Session --------------------------------------------------------------------
|
||||
|
||||
// NewSession is called by session stores to create a new session instance.
|
||||
func NewSession(store Store, name string) *Session {
|
||||
return &Session{
|
||||
Values: make(map[interface{}]interface{}),
|
||||
store: store,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Session stores the values and optional configuration for a session.
|
||||
type Session struct {
|
||||
// The ID of the session, generated by stores. It should not be used for
|
||||
// user data.
|
||||
ID string
|
||||
// Values contains the user-data for the session.
|
||||
Values map[interface{}]interface{}
|
||||
Options *Options
|
||||
IsNew bool
|
||||
store Store
|
||||
name string
|
||||
}
|
||||
|
||||
// Flashes returns a slice of flash messages from the session.
|
||||
//
|
||||
// A single variadic argument is accepted, and it is optional: it defines
|
||||
// the flash key. If not defined "_flash" is used by default.
|
||||
func (s *Session) Flashes(vars ...string) []interface{} {
|
||||
var flashes []interface{}
|
||||
key := flashesKey
|
||||
if len(vars) > 0 {
|
||||
key = vars[0]
|
||||
}
|
||||
if v, ok := s.Values[key]; ok {
|
||||
// Drop the flashes and return it.
|
||||
delete(s.Values, key)
|
||||
flashes = v.([]interface{})
|
||||
}
|
||||
return flashes
|
||||
}
|
||||
|
||||
// AddFlash adds a flash message to the session.
|
||||
//
|
||||
// A single variadic argument is accepted, and it is optional: it defines
|
||||
// the flash key. If not defined "_flash" is used by default.
|
||||
func (s *Session) AddFlash(value interface{}, vars ...string) {
|
||||
key := flashesKey
|
||||
if len(vars) > 0 {
|
||||
key = vars[0]
|
||||
}
|
||||
var flashes []interface{}
|
||||
if v, ok := s.Values[key]; ok {
|
||||
flashes = v.([]interface{})
|
||||
}
|
||||
s.Values[key] = append(flashes, value)
|
||||
}
|
||||
|
||||
// Save is a convenience method to save this session. It is the same as calling
|
||||
// store.Save(request, response, session). You should call Save before writing to
|
||||
// the response or returning from the handler.
|
||||
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
|
||||
return s.store.Save(r, w, s)
|
||||
}
|
||||
|
||||
// Name returns the name used to register the session.
|
||||
func (s *Session) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Store returns the session store used to register the session.
|
||||
func (s *Session) Store() Store {
|
||||
return s.store
|
||||
}
|
||||
|
||||
// Registry -------------------------------------------------------------------
|
||||
|
||||
// sessionInfo stores a session tracked by the registry.
|
||||
type sessionInfo struct {
|
||||
s *Session
|
||||
e error
|
||||
}
|
||||
|
||||
// contextKey is the type used to store the registry in the context.
|
||||
type contextKey int
|
||||
|
||||
// registryKey is the key used to store the registry in the context.
|
||||
const registryKey contextKey = 0
|
||||
|
||||
// GetRegistry returns a registry instance for the current request.
|
||||
func GetRegistry(r *http.Request) *Registry {
|
||||
registry := context.Get(r, registryKey)
|
||||
if registry != nil {
|
||||
return registry.(*Registry)
|
||||
}
|
||||
newRegistry := &Registry{
|
||||
request: r,
|
||||
sessions: make(map[string]sessionInfo),
|
||||
}
|
||||
context.Set(r, registryKey, newRegistry)
|
||||
return newRegistry
|
||||
}
|
||||
|
||||
// Registry stores sessions used during a request.
|
||||
type Registry struct {
|
||||
request *http.Request
|
||||
sessions map[string]sessionInfo
|
||||
}
|
||||
|
||||
// Get registers and returns a session for the given name and session store.
|
||||
//
|
||||
// It returns a new session if there are no sessions registered for the name.
|
||||
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
|
||||
if !isCookieNameValid(name) {
|
||||
return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
|
||||
}
|
||||
if info, ok := s.sessions[name]; ok {
|
||||
session, err = info.s, info.e
|
||||
} else {
|
||||
session, err = store.New(s.request, name)
|
||||
session.name = name
|
||||
s.sessions[name] = sessionInfo{s: session, e: err}
|
||||
}
|
||||
session.store = store
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves all sessions registered for the current request.
|
||||
func (s *Registry) Save(w http.ResponseWriter) error {
|
||||
var errMulti MultiError
|
||||
for name, info := range s.sessions {
|
||||
session := info.s
|
||||
if session.store == nil {
|
||||
errMulti = append(errMulti, fmt.Errorf(
|
||||
"sessions: missing store for session %q", name))
|
||||
} else if err := session.store.Save(s.request, w, session); err != nil {
|
||||
errMulti = append(errMulti, fmt.Errorf(
|
||||
"sessions: error saving session %q -- %v", name, err))
|
||||
}
|
||||
}
|
||||
if errMulti != nil {
|
||||
return errMulti
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helpers --------------------------------------------------------------------
|
||||
|
||||
func init() {
|
||||
gob.Register([]interface{}{})
|
||||
}
|
||||
|
||||
// Save saves all sessions used during the current request.
|
||||
func Save(r *http.Request, w http.ResponseWriter) error {
|
||||
return GetRegistry(r).Save(w)
|
||||
}
|
||||
|
||||
// NewCookie returns an http.Cookie with the options set. It also sets
|
||||
// the Expires field calculated based on the MaxAge value, for Internet
|
||||
// Explorer compatibility.
|
||||
func NewCookie(name, value string, options *Options) *http.Cookie {
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: options.Path,
|
||||
Domain: options.Domain,
|
||||
MaxAge: options.MaxAge,
|
||||
Secure: options.Secure,
|
||||
HttpOnly: options.HttpOnly,
|
||||
}
|
||||
if options.MaxAge > 0 {
|
||||
d := time.Duration(options.MaxAge) * time.Second
|
||||
cookie.Expires = time.Now().Add(d)
|
||||
} else if options.MaxAge < 0 {
|
||||
// Set it to the past to expire now.
|
||||
cookie.Expires = time.Unix(1, 0)
|
||||
}
|
||||
return cookie
|
||||
}
|
||||
|
||||
// Error ----------------------------------------------------------------------
|
||||
|
||||
// MultiError stores multiple errors.
|
||||
//
|
||||
// Borrowed from the App Engine SDK.
|
||||
type MultiError []error
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
s, n := "", 0
|
||||
for _, e := range m {
|
||||
if e != nil {
|
||||
if n == 0 {
|
||||
s = e.Error()
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
switch n {
|
||||
case 0:
|
||||
return "(0 errors)"
|
||||
case 1:
|
||||
return s
|
||||
case 2:
|
||||
return s + " (and 1 other error)"
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||
}
|
||||
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
Normal file
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
// Store is an interface for custom session stores.
|
||||
//
|
||||
// See CookieStore and FilesystemStore for examples.
|
||||
type Store interface {
|
||||
// Get should return a cached session.
|
||||
Get(r *http.Request, name string) (*Session, error)
|
||||
|
||||
// New should create and return a new session.
|
||||
//
|
||||
// Note that New should never return a nil session, even in the case of
|
||||
// an error if using the Registry infrastructure to cache the session.
|
||||
New(r *http.Request, name string) (*Session, error)
|
||||
|
||||
// Save should persist session to the underlying store implementation.
|
||||
Save(r *http.Request, w http.ResponseWriter, s *Session) error
|
||||
}
|
||||
|
||||
// CookieStore ----------------------------------------------------------------
|
||||
|
||||
// NewCookieStore returns a new CookieStore.
|
||||
//
|
||||
// Keys are defined in pairs to allow key rotation, but the common case is
|
||||
// to set a single authentication key and optionally an encryption key.
|
||||
//
|
||||
// The first key in a pair is used for authentication and the second for
|
||||
// encryption. The encryption key can be set to nil or omitted in the last
|
||||
// pair, but the authentication key is required in all pairs.
|
||||
//
|
||||
// It is recommended to use an authentication key with 32 or 64 bytes.
|
||||
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
|
||||
// AES-128, AES-192, or AES-256 modes.
|
||||
//
|
||||
// Use the convenience function securecookie.GenerateRandomKey() to create
|
||||
// strong keys.
|
||||
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
|
||||
cs := &CookieStore{
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
Options: &Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 30,
|
||||
},
|
||||
}
|
||||
|
||||
cs.MaxAge(cs.Options.MaxAge)
|
||||
return cs
|
||||
}
|
||||
|
||||
// CookieStore stores sessions using secure cookies.
|
||||
type CookieStore struct {
|
||||
Codecs []securecookie.Codec
|
||||
Options *Options // default configuration
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
//
|
||||
// It returns a new session if the sessions doesn't exist. Access IsNew on
|
||||
// the session to check if it is an existing session or a new one.
|
||||
//
|
||||
// It returns a new session and an error if the session exists but could
|
||||
// not be decoded.
|
||||
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
|
||||
return GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
// New returns a session for the given name without adding it to the registry.
|
||||
//
|
||||
// The difference between New() and Get() is that calling New() twice will
|
||||
// decode the session data twice, while Get() registers and reuses the same
|
||||
// decoded session after the first call.
|
||||
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
|
||||
session := NewSession(s, name)
|
||||
opts := *s.Options
|
||||
session.Options = &opts
|
||||
session.IsNew = true
|
||||
var err error
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err = securecookie.DecodeMulti(name, c.Value, &session.Values,
|
||||
s.Codecs...)
|
||||
if err == nil {
|
||||
session.IsNew = false
|
||||
}
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
|
||||
session *Session) error {
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
||||
// = -1 for that session.
|
||||
func (s *CookieStore) MaxAge(age int) {
|
||||
s.Options.MaxAge = age
|
||||
|
||||
// Set the maxAge for each securecookie instance.
|
||||
for _, codec := range s.Codecs {
|
||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
||||
sc.MaxAge(age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FilesystemStore ------------------------------------------------------------
|
||||
|
||||
var fileMutex sync.RWMutex
|
||||
|
||||
// NewFilesystemStore returns a new FilesystemStore.
|
||||
//
|
||||
// The path argument is the directory where sessions will be saved. If empty
|
||||
// it will use os.TempDir().
|
||||
//
|
||||
// See NewCookieStore() for a description of the other parameters.
|
||||
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
|
||||
if path == "" {
|
||||
path = os.TempDir()
|
||||
}
|
||||
fs := &FilesystemStore{
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
Options: &Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 30,
|
||||
},
|
||||
path: path,
|
||||
}
|
||||
|
||||
fs.MaxAge(fs.Options.MaxAge)
|
||||
return fs
|
||||
}
|
||||
|
||||
// FilesystemStore stores sessions in the filesystem.
|
||||
//
|
||||
// It also serves as a reference for custom stores.
|
||||
//
|
||||
// This store is still experimental and not well tested. Feedback is welcome.
|
||||
type FilesystemStore struct {
|
||||
Codecs []securecookie.Codec
|
||||
Options *Options // default configuration
|
||||
path string
|
||||
}
|
||||
|
||||
// MaxLength restricts the maximum length of new sessions to l.
|
||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
||||
// The default for a new FilesystemStore is 4096.
|
||||
func (s *FilesystemStore) MaxLength(l int) {
|
||||
for _, c := range s.Codecs {
|
||||
if codec, ok := c.(*securecookie.SecureCookie); ok {
|
||||
codec.MaxLength(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
//
|
||||
// See CookieStore.Get().
|
||||
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
|
||||
return GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
// New returns a session for the given name without adding it to the registry.
|
||||
//
|
||||
// See CookieStore.New().
|
||||
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
|
||||
session := NewSession(s, name)
|
||||
opts := *s.Options
|
||||
session.Options = &opts
|
||||
session.IsNew = true
|
||||
var err error
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
||||
if err == nil {
|
||||
err = s.load(session)
|
||||
if err == nil {
|
||||
session.IsNew = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
//
|
||||
// If the Options.MaxAge of the session is <= 0 then the session file will be
|
||||
// deleted from the store path. With this process it enforces the properly
|
||||
// session cookie handling so no need to trust in the cookie management in the
|
||||
// web browser.
|
||||
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
|
||||
session *Session) error {
|
||||
// Delete if max-age is <= 0
|
||||
if session.Options.MaxAge <= 0 {
|
||||
if err := s.erase(session); err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
if session.ID == "" {
|
||||
// Because the ID is used in the filename, encode it to
|
||||
// use alphanumeric characters only.
|
||||
session.ID = strings.TrimRight(
|
||||
base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32)), "=")
|
||||
}
|
||||
if err := s.save(session); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
||||
// = -1 for that session.
|
||||
func (s *FilesystemStore) MaxAge(age int) {
|
||||
s.Options.MaxAge = age
|
||||
|
||||
// Set the maxAge for each securecookie instance.
|
||||
for _, codec := range s.Codecs {
|
||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
||||
sc.MaxAge(age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save writes encoded session.Values to a file.
|
||||
func (s *FilesystemStore) save(session *Session) error {
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
fileMutex.Lock()
|
||||
defer fileMutex.Unlock()
|
||||
return ioutil.WriteFile(filename, []byte(encoded), 0600)
|
||||
}
|
||||
|
||||
// load reads a file and decodes its content into session.Values.
|
||||
func (s *FilesystemStore) load(session *Session) error {
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
fileMutex.RLock()
|
||||
defer fileMutex.RUnlock()
|
||||
fdata, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = securecookie.DecodeMulti(session.Name(), string(fdata),
|
||||
&session.Values, s.Codecs...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete session file
|
||||
func (s *FilesystemStore) erase(session *Session) error {
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
|
||||
fileMutex.RLock()
|
||||
defer fileMutex.RUnlock()
|
||||
|
||||
err := os.Remove(filename)
|
||||
return err
|
||||
}
|
||||
21
vendor/github.com/iris-contrib/response/LICENSE
generated
vendored
21
vendor/github.com/iris-contrib/response/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Gerasimos Maropoulos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
23
vendor/github.com/klauspost/compress/LICENSE
generated
vendored
23
vendor/github.com/klauspost/compress/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Klaus Post
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/github.com/pkg/errors) [](https://goreportcard.com/report/github.com/pkg/errors)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## Licence
|
||||
|
||||
BSD-2-Clause
|
||||
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
||||
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
||||
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
||||
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
27
vendor/github.com/pmezard/go-difflib/LICENSE
generated
vendored
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2013, Patrick Mezard
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
The names of its contributors may not be used to endorse or promote
|
||||
products derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
20
vendor/github.com/sergi/go-diff/LICENSE.txt
generated
vendored
20
vendor/github.com/sergi/go-diff/LICENSE.txt
generated
vendored
@@ -1,20 +0,0 @@
|
||||
Copyright (c) 2012 Sergi Mansilla <sergi.mansilla@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
12
vendor/github.com/shogo82148/androidbinary/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/shogo82148/androidbinary/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- '1.2'
|
||||
- '1.3'
|
||||
- '1.4'
|
||||
- '1.5'
|
||||
- '1.6'
|
||||
- '1.7'
|
||||
- '1.8'
|
||||
- 'tip'
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Gerasimos Maropoulos
|
||||
Copyright (c) 2014 Ichinose Shogo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user