use ffprobe
This commit is contained in:
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sentriz/gonic/db"
|
||||
"github.com/sentriz/gonic/tags"
|
||||
|
||||
"github.com/dhowden/tag"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
"github.com/karrick/godirwalk"
|
||||
@@ -65,16 +65,11 @@ func isCover(filename string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func readTags(fullPath string) (tag.Metadata, error) {
|
||||
trackData, err := os.Open(fullPath)
|
||||
func readTags(fullPath string) (tags.Metadata, error) {
|
||||
tags, err := tags.Read(fullPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("when tags from disk: %v", err)
|
||||
}
|
||||
defer trackData.Close()
|
||||
tags, err := tag.ReadFrom(trackData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
@@ -130,19 +125,15 @@ func handleFile(fullPath string, info *godirwalk.Dirent) error {
|
||||
return nil
|
||||
}
|
||||
tags, err := readTags(fullPath)
|
||||
fmt.Println(tags.Raw())
|
||||
os.Exit(0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("when reading tags: %v", err)
|
||||
}
|
||||
trackNumber, totalTracks := tags.Track()
|
||||
discNumber, TotalDiscs := tags.Disc()
|
||||
track.Path = fullPath
|
||||
track.Title = tags.Title()
|
||||
track.DiscNumber = uint(discNumber)
|
||||
track.TotalDiscs = uint(TotalDiscs)
|
||||
track.TotalTracks = uint(totalTracks)
|
||||
track.TrackNumber = uint(trackNumber)
|
||||
track.DiscNumber = uint(tags.Disc())
|
||||
track.TotalDiscs = uint(tags.TotalDiscs())
|
||||
track.TrackNumber = uint(tags.Track())
|
||||
track.TotalTracks = uint(tags.TotalTracks())
|
||||
track.Year = uint(tags.Year())
|
||||
// set artist {
|
||||
artist := db.Artist{
|
||||
|
||||
7
go.mod
7
go.mod
@@ -3,7 +3,7 @@ module github.com/sentriz/gonic
|
||||
require (
|
||||
cloud.google.com/go v0.37.1 // indirect
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect
|
||||
github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca
|
||||
github.com/dhowden/tag v0.0.0-20181104225729-a9f04c2798ca // indirect
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
@@ -11,10 +11,15 @@ require (
|
||||
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
|
||||
github.com/jinzhu/now v1.0.0 // indirect
|
||||
github.com/karrick/godirwalk v1.8.0
|
||||
github.com/labstack/gommon v0.2.8 // indirect
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.7 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||
github.com/myesui/uuid v1.0.0 // indirect
|
||||
github.com/twinj/uuid v1.0.0
|
||||
github.com/valyala/fasttemplate v1.0.1 // indirect
|
||||
github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 // indirect
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
)
|
||||
|
||||
15
go.sum
15
go.sum
@@ -66,8 +66,15 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
|
||||
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -95,6 +102,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
||||
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628 h1:hYOyf8es7yvMucqlPar3CdobJeY0+0OmR5iT4hHYW54=
|
||||
github.com/wtolson/go-taglib v0.0.0-20180718000046-586eb63c2628/go.mod h1:p+WHGfN/a+Ol37Pm7EIOO/6Cylieb2qn1jmKfxtSsUg=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
@@ -137,6 +150,8 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -34,9 +34,9 @@ func respondRaw(w http.ResponseWriter, r *http.Request, code int, sub *subsonic.
|
||||
log.Printf("could not marshall to json: %v\n", err)
|
||||
}
|
||||
callback := r.URL.Query().Get("callback")
|
||||
w.Write([]byte(fmt.Sprintf("%s(", callback)))
|
||||
w.Write([]byte(fmt.Sprintf(`%s({"subsonic-response":`, callback)))
|
||||
w.Write(data)
|
||||
w.Write([]byte(");"))
|
||||
w.Write([]byte("});"))
|
||||
default:
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
data, err := xml.Marshal(sub)
|
||||
|
||||
@@ -44,7 +44,9 @@ func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc {
|
||||
if param != "" {
|
||||
continue
|
||||
}
|
||||
respondError(w, r, 10, fmt.Sprintf("please provide a `%s` parameter", req))
|
||||
respondError(w, r,
|
||||
10, fmt.Sprintf("please provide a `%s` parameter", req),
|
||||
)
|
||||
return
|
||||
}
|
||||
username := r.URL.Query().Get("u")
|
||||
@@ -54,7 +56,9 @@ func (c *Controller) CheckParameters(next http.HandlerFunc) http.HandlerFunc {
|
||||
passwordAuth := token == "" && salt == ""
|
||||
tokenAuth := password == ""
|
||||
if tokenAuth == passwordAuth {
|
||||
respondError(w, r, 10, "please provide parameters `t` and `s`, or just `p`")
|
||||
respondError(w, r,
|
||||
10, "please provide parameters `t` and `s`, or just `p`",
|
||||
)
|
||||
return
|
||||
}
|
||||
user := db.User{
|
||||
|
||||
19
tags/format.go
Normal file
19
tags/format.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package tags
|
||||
|
||||
type probeData struct {
|
||||
Format *probeFormat `json:"format"`
|
||||
}
|
||||
|
||||
type probeFormat struct {
|
||||
Filename string `json:"filename"`
|
||||
NBStreams int `json:"nb_streams"`
|
||||
NBPrograms int `json:"nb_programs"`
|
||||
FormatName string `json:"format_name"`
|
||||
FormatLongName string `json:"format_long_name"`
|
||||
StartTime float64 `json:"start_time,string"`
|
||||
Duration float64 `json:"duration,string"`
|
||||
Size string `json:"size"`
|
||||
Bitrate int `json:"bit_rate,string"`
|
||||
ProbeScore int `json:"probe_score"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
}
|
||||
BIN
tags/static/test_flac
Normal file
BIN
tags/static/test_flac
Normal file
Binary file not shown.
BIN
tags/static/test_mp3
Normal file
BIN
tags/static/test_mp3
Normal file
Binary file not shown.
203
tags/tags.go
Normal file
203
tags/tags.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package tags
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
titleFields = []string{
|
||||
"TITLE",
|
||||
"title",
|
||||
}
|
||||
albumFields = []string{
|
||||
"ALBUM",
|
||||
"album",
|
||||
}
|
||||
artistFields = []string{
|
||||
"ARTIST",
|
||||
"artist",
|
||||
}
|
||||
albumArtistFields = []string{
|
||||
"ALBUM ARTIST",
|
||||
"ALBUMARTIST",
|
||||
"ALBUM_ARTIST",
|
||||
"album artist",
|
||||
"album_artist",
|
||||
"albumartist",
|
||||
}
|
||||
composerFields = []string{
|
||||
"COMPOSER",
|
||||
"composer",
|
||||
}
|
||||
genreFields = []string{
|
||||
"GENRE",
|
||||
"genre",
|
||||
}
|
||||
yearFields = []string{
|
||||
"YEAR",
|
||||
"year",
|
||||
}
|
||||
trackFields = []string{
|
||||
"TRACK",
|
||||
"track",
|
||||
}
|
||||
totaltrackFields = []string{
|
||||
"TOTALTRACKS",
|
||||
"TRACKC",
|
||||
"TRACKTOTAL",
|
||||
"totaltracks",
|
||||
"trackc",
|
||||
"tracktotal",
|
||||
}
|
||||
discFields = []string{
|
||||
"DISC",
|
||||
"disc",
|
||||
}
|
||||
totaldiscFields = []string{
|
||||
"DISCC",
|
||||
"DISCTOTAL",
|
||||
"TOTALDISCS",
|
||||
"discc",
|
||||
"disctotal",
|
||||
"totaldiscs",
|
||||
}
|
||||
lyricsFields = []string{
|
||||
"LYRICS",
|
||||
"lyrics",
|
||||
}
|
||||
commentFields = []string{
|
||||
"COMMENT",
|
||||
"COMMENTS",
|
||||
"comment",
|
||||
"comments",
|
||||
}
|
||||
)
|
||||
|
||||
func firstExisting(keys *[]string, map_ *map[string]string) string {
|
||||
for _, field := range *keys {
|
||||
v, ok := (*map_)[field]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type Metadata interface {
|
||||
Title() string
|
||||
Album() string
|
||||
Artist() string
|
||||
AlbumArtist() string
|
||||
Composer() string
|
||||
Genre() string
|
||||
Year() int
|
||||
Track() int
|
||||
TotalTracks() int
|
||||
Disc() int
|
||||
TotalDiscs() int
|
||||
Lyrics() string
|
||||
Comment() string
|
||||
Length() float64
|
||||
Format() string
|
||||
Bitrate() int
|
||||
}
|
||||
|
||||
type Track struct {
|
||||
format *probeFormat
|
||||
}
|
||||
|
||||
func (t *Track) Title() string {
|
||||
return firstExisting(&titleFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Album() string {
|
||||
return firstExisting(&albumFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Artist() string {
|
||||
return firstExisting(&artistFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) AlbumArtist() string {
|
||||
return firstExisting(&albumArtistFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Composer() string {
|
||||
return firstExisting(&composerFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Genre() string {
|
||||
return firstExisting(&genreFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Year() int {
|
||||
i, _ := strconv.Atoi(firstExisting(&yearFields, &t.format.Tags))
|
||||
return i
|
||||
}
|
||||
|
||||
func (t *Track) Track() int {
|
||||
i, _ := strconv.Atoi(firstExisting(&trackFields, &t.format.Tags))
|
||||
return i
|
||||
}
|
||||
|
||||
func (t *Track) TotalTracks() int {
|
||||
i, _ := strconv.Atoi(firstExisting(&totaltrackFields, &t.format.Tags))
|
||||
return i
|
||||
}
|
||||
|
||||
func (t *Track) Disc() int {
|
||||
i, _ := strconv.Atoi(firstExisting(&discFields, &t.format.Tags))
|
||||
return i
|
||||
}
|
||||
|
||||
func (t *Track) TotalDiscs() int {
|
||||
i, _ := strconv.Atoi(firstExisting(&totaldiscFields, &t.format.Tags))
|
||||
return i
|
||||
}
|
||||
|
||||
func (t *Track) Lyrics() string {
|
||||
return firstExisting(&lyricsFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Comment() string {
|
||||
return firstExisting(&commentFields, &t.format.Tags)
|
||||
}
|
||||
|
||||
func (t *Track) Format() string {
|
||||
return t.format.FormatName
|
||||
}
|
||||
|
||||
func (t *Track) Length() float64 {
|
||||
return t.format.Duration
|
||||
}
|
||||
|
||||
func (t *Track) Bitrate() int {
|
||||
return t.format.Bitrate
|
||||
}
|
||||
|
||||
func Read(filename string) (Metadata, error) {
|
||||
command := exec.Command(
|
||||
"ffprobe",
|
||||
"-print_format", "json",
|
||||
"-show_format",
|
||||
filename,
|
||||
)
|
||||
probe, err := command.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("when running ffprobe with `%s`: %v\n",
|
||||
filename, err)
|
||||
}
|
||||
var data probeData
|
||||
err = json.Unmarshal(probe, &data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("when unmarshalling: %v\n", err)
|
||||
}
|
||||
track := Track{
|
||||
format: data.Format,
|
||||
}
|
||||
return &track, nil
|
||||
}
|
||||
37
tags/tags_test.go
Normal file
37
tags/tags_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package tags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
start := time.Now()
|
||||
cwd, _ := os.Getwd()
|
||||
data, err := Read(
|
||||
fmt.Sprintf("%s/static/test_flac", cwd),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("when reading tags: %v\n", err)
|
||||
return
|
||||
}
|
||||
length := data.Length()
|
||||
if length != 160.4 {
|
||||
t.Errorf("expected length to be `160.4`, got %f", length)
|
||||
}
|
||||
bitrate := data.Bitrate()
|
||||
if bitrate != 815694 {
|
||||
t.Errorf("expected bitrate to be `815694`, got %d", bitrate)
|
||||
}
|
||||
format := data.Format()
|
||||
if format != "flac" {
|
||||
t.Errorf("expected format to be `flac`, got %s", format)
|
||||
}
|
||||
fmt.Println(data.Title())
|
||||
fmt.Println(data.Album())
|
||||
fmt.Println(data.AlbumArtist())
|
||||
fmt.Println(data.Year())
|
||||
fmt.Printf("it's been %s\n", time.Since(start))
|
||||
}
|
||||
4
vendor/github.com/dgrijalva/jwt-go/.gitignore
generated
vendored
4
vendor/github.com/dgrijalva/jwt-go/.gitignore
generated
vendored
@@ -1,4 +0,0 @@
|
||||
.DS_Store
|
||||
bin
|
||||
|
||||
|
||||
13
vendor/github.com/dgrijalva/jwt-go/.travis.yml
generated
vendored
13
vendor/github.com/dgrijalva/jwt-go/.travis.yml
generated
vendored
@@ -1,13 +0,0 @@
|
||||
language: go
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -v ./...
|
||||
|
||||
go:
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- tip
|
||||
8
vendor/github.com/dgrijalva/jwt-go/LICENSE
generated
vendored
8
vendor/github.com/dgrijalva/jwt-go/LICENSE
generated
vendored
@@ -1,8 +0,0 @@
|
||||
Copyright (c) 2012 Dave Grijalva
|
||||
|
||||
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.
|
||||
|
||||
97
vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md
generated
vendored
97
vendor/github.com/dgrijalva/jwt-go/MIGRATION_GUIDE.md
generated
vendored
@@ -1,97 +0,0 @@
|
||||
## Migration Guide from v2 -> v3
|
||||
|
||||
Version 3 adds several new, frequently requested features. To do so, it introduces a few breaking changes. We've worked to keep these as minimal as possible. This guide explains the breaking changes and how you can quickly update your code.
|
||||
|
||||
### `Token.Claims` is now an interface type
|
||||
|
||||
The most requested feature from the 2.0 verison of this library was the ability to provide a custom type to the JSON parser for claims. This was implemented by introducing a new interface, `Claims`, to replace `map[string]interface{}`. We also included two concrete implementations of `Claims`: `MapClaims` and `StandardClaims`.
|
||||
|
||||
`MapClaims` is an alias for `map[string]interface{}` with built in validation behavior. It is the default claims type when using `Parse`. The usage is unchanged except you must type cast the claims property.
|
||||
|
||||
The old example for parsing a token looked like this..
|
||||
|
||||
```go
|
||||
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
|
||||
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
|
||||
}
|
||||
```
|
||||
|
||||
is now directly mapped to...
|
||||
|
||||
```go
|
||||
if token, err := jwt.Parse(tokenString, keyLookupFunc); err == nil {
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
|
||||
}
|
||||
```
|
||||
|
||||
`StandardClaims` is designed to be embedded in your custom type. You can supply a custom claims type with the new `ParseWithClaims` function. Here's an example of using a custom claims type.
|
||||
|
||||
```go
|
||||
type MyCustomClaims struct {
|
||||
User string
|
||||
*StandardClaims
|
||||
}
|
||||
|
||||
if token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, keyLookupFunc); err == nil {
|
||||
claims := token.Claims.(*MyCustomClaims)
|
||||
fmt.Printf("Token for user %v expires %v", claims.User, claims.StandardClaims.ExpiresAt)
|
||||
}
|
||||
```
|
||||
|
||||
### `ParseFromRequest` has been moved
|
||||
|
||||
To keep this library focused on the tokens without becoming overburdened with complex request processing logic, `ParseFromRequest` and its new companion `ParseFromRequestWithClaims` have been moved to a subpackage, `request`. The method signatues have also been augmented to receive a new argument: `Extractor`.
|
||||
|
||||
`Extractors` do the work of picking the token string out of a request. The interface is simple and composable.
|
||||
|
||||
This simple parsing example:
|
||||
|
||||
```go
|
||||
if token, err := jwt.ParseFromRequest(tokenString, req, keyLookupFunc); err == nil {
|
||||
fmt.Printf("Token for user %v expires %v", token.Claims["user"], token.Claims["exp"])
|
||||
}
|
||||
```
|
||||
|
||||
is directly mapped to:
|
||||
|
||||
```go
|
||||
if token, err := request.ParseFromRequest(req, request.OAuth2Extractor, keyLookupFunc); err == nil {
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
fmt.Printf("Token for user %v expires %v", claims["user"], claims["exp"])
|
||||
}
|
||||
```
|
||||
|
||||
There are several concrete `Extractor` types provided for your convenience:
|
||||
|
||||
* `HeaderExtractor` will search a list of headers until one contains content.
|
||||
* `ArgumentExtractor` will search a list of keys in request query and form arguments until one contains content.
|
||||
* `MultiExtractor` will try a list of `Extractors` in order until one returns content.
|
||||
* `AuthorizationHeaderExtractor` will look in the `Authorization` header for a `Bearer` token.
|
||||
* `OAuth2Extractor` searches the places an OAuth2 token would be specified (per the spec): `Authorization` header and `access_token` argument
|
||||
* `PostExtractionFilter` wraps an `Extractor`, allowing you to process the content before it's parsed. A simple example is stripping the `Bearer ` text from a header
|
||||
|
||||
|
||||
### RSA signing methods no longer accept `[]byte` keys
|
||||
|
||||
Due to a [critical vulnerability](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/), we've decided the convenience of accepting `[]byte` instead of `rsa.PublicKey` or `rsa.PrivateKey` isn't worth the risk of misuse.
|
||||
|
||||
To replace this behavior, we've added two helper methods: `ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)` and `ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)`. These are just simple helpers for unpacking PEM encoded PKCS1 and PKCS8 keys. If your keys are encoded any other way, all you need to do is convert them to the `crypto/rsa` package's types.
|
||||
|
||||
```go
|
||||
func keyLookupFunc(*Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
// Look up key
|
||||
key, err := lookupPublicKey(token.Header["kid"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unpack key from PEM encoded PKCS8
|
||||
return jwt.ParseRSAPublicKeyFromPEM(key)
|
||||
}
|
||||
```
|
||||
100
vendor/github.com/dgrijalva/jwt-go/README.md
generated
vendored
100
vendor/github.com/dgrijalva/jwt-go/README.md
generated
vendored
@@ -1,100 +0,0 @@
|
||||
# jwt-go
|
||||
|
||||
[](https://travis-ci.org/dgrijalva/jwt-go)
|
||||
[](https://godoc.org/github.com/dgrijalva/jwt-go)
|
||||
|
||||
A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html)
|
||||
|
||||
**NEW VERSION COMING:** There have been a lot of improvements suggested since the version 3.0.0 released in 2016. I'm working now on cutting two different releases: 3.2.0 will contain any non-breaking changes or enhancements. 4.0.0 will follow shortly which will include breaking changes. See the 4.0.0 milestone to get an idea of what's coming. If you have other ideas, or would like to participate in 4.0.0, now's the time. If you depend on this library and don't want to be interrupted, I recommend you use your dependency mangement tool to pin to version 3.
|
||||
|
||||
**SECURITY NOTICE:** Some older versions of Go have a security issue in the cryotp/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue #216 for more detail.
|
||||
|
||||
**SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided.
|
||||
|
||||
## What the heck is a JWT?
|
||||
|
||||
JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens.
|
||||
|
||||
In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way.
|
||||
|
||||
The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used.
|
||||
|
||||
The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own.
|
||||
|
||||
## What's in the box?
|
||||
|
||||
This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own.
|
||||
|
||||
## Examples
|
||||
|
||||
See [the project documentation](https://godoc.org/github.com/dgrijalva/jwt-go) for examples of usage:
|
||||
|
||||
* [Simple example of parsing and validating a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac)
|
||||
* [Simple example of building and signing a token](https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac)
|
||||
* [Directory of Examples](https://godoc.org/github.com/dgrijalva/jwt-go#pkg-examples)
|
||||
|
||||
## Extensions
|
||||
|
||||
This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.
|
||||
|
||||
Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go
|
||||
|
||||
## Compliance
|
||||
|
||||
This library was last reviewed to comply with [RTF 7519](http://www.rfc-editor.org/info/rfc7519) dated May 2015 with a few notable differences:
|
||||
|
||||
* In order to protect against accidental use of [Unsecured JWTs](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#UnsecuredJWT), tokens using `alg=none` will only be accepted if the constant `jwt.UnsafeAllowNoneSignatureType` is provided as the key.
|
||||
|
||||
## Project Status & Versioning
|
||||
|
||||
This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
|
||||
|
||||
This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases).
|
||||
|
||||
While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v3`. It will do the right thing WRT semantic versioning.
|
||||
|
||||
**BREAKING CHANGES:***
|
||||
* Version 3.0.0 includes _a lot_ of changes from the 2.x line, including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes is available in `VERSION_HISTORY.md`. See `MIGRATION_GUIDE.md` for more information on updating your code.
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### Signing vs Encryption
|
||||
|
||||
A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data:
|
||||
|
||||
* The author of the token was in the possession of the signing secret
|
||||
* The data has not been modified since it was signed
|
||||
|
||||
It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library.
|
||||
|
||||
### Choosing a Signing Method
|
||||
|
||||
There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric.
|
||||
|
||||
Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation.
|
||||
|
||||
Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification.
|
||||
|
||||
### Signing Methods and Key Types
|
||||
|
||||
Each signing method expects a different object type for its signing keys. See the package documentation for details. Here are the most common ones:
|
||||
|
||||
* The [HMAC signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodHMAC) (`HS256`,`HS384`,`HS512`) expect `[]byte` values for signing and validation
|
||||
* The [RSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodRSA) (`RS256`,`RS384`,`RS512`) expect `*rsa.PrivateKey` for signing and `*rsa.PublicKey` for validation
|
||||
* The [ECDSA signing method](https://godoc.org/github.com/dgrijalva/jwt-go#SigningMethodECDSA) (`ES256`,`ES384`,`ES512`) expect `*ecdsa.PrivateKey` for signing and `*ecdsa.PublicKey` for validation
|
||||
|
||||
### JWT and OAuth
|
||||
|
||||
It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication.
|
||||
|
||||
Without going too far down the rabbit hole, here's a description of the interaction of these technologies:
|
||||
|
||||
* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth.
|
||||
* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token.
|
||||
* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL.
|
||||
|
||||
## More
|
||||
|
||||
Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go).
|
||||
|
||||
The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. You'll also find several implementation examples in the documentation.
|
||||
118
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
generated
vendored
118
vendor/github.com/dgrijalva/jwt-go/VERSION_HISTORY.md
generated
vendored
@@ -1,118 +0,0 @@
|
||||
## `jwt-go` Version History
|
||||
|
||||
#### 3.2.0
|
||||
|
||||
* Added method `ParseUnverified` to allow users to split up the tasks of parsing and validation
|
||||
* HMAC signing method returns `ErrInvalidKeyType` instead of `ErrInvalidKey` where appropriate
|
||||
* Added options to `request.ParseFromRequest`, which allows for an arbitrary list of modifiers to parsing behavior. Initial set include `WithClaims` and `WithParser`. Existing usage of this function will continue to work as before.
|
||||
* Deprecated `ParseFromRequestWithClaims` to simplify API in the future.
|
||||
|
||||
#### 3.1.0
|
||||
|
||||
* Improvements to `jwt` command line tool
|
||||
* Added `SkipClaimsValidation` option to `Parser`
|
||||
* Documentation updates
|
||||
|
||||
#### 3.0.0
|
||||
|
||||
* **Compatibility Breaking Changes**: See MIGRATION_GUIDE.md for tips on updating your code
|
||||
* Dropped support for `[]byte` keys when using RSA signing methods. This convenience feature could contribute to security vulnerabilities involving mismatched key types with signing methods.
|
||||
* `ParseFromRequest` has been moved to `request` subpackage and usage has changed
|
||||
* The `Claims` property on `Token` is now type `Claims` instead of `map[string]interface{}`. The default value is type `MapClaims`, which is an alias to `map[string]interface{}`. This makes it possible to use a custom type when decoding claims.
|
||||
* Other Additions and Changes
|
||||
* Added `Claims` interface type to allow users to decode the claims into a custom type
|
||||
* Added `ParseWithClaims`, which takes a third argument of type `Claims`. Use this function instead of `Parse` if you have a custom type you'd like to decode into.
|
||||
* Dramatically improved the functionality and flexibility of `ParseFromRequest`, which is now in the `request` subpackage
|
||||
* Added `ParseFromRequestWithClaims` which is the `FromRequest` equivalent of `ParseWithClaims`
|
||||
* Added new interface type `Extractor`, which is used for extracting JWT strings from http requests. Used with `ParseFromRequest` and `ParseFromRequestWithClaims`.
|
||||
* Added several new, more specific, validation errors to error type bitmask
|
||||
* Moved examples from README to executable example files
|
||||
* Signing method registry is now thread safe
|
||||
* Added new property to `ValidationError`, which contains the raw error returned by calls made by parse/verify (such as those returned by keyfunc or json parser)
|
||||
|
||||
#### 2.7.0
|
||||
|
||||
This will likely be the last backwards compatible release before 3.0.0, excluding essential bug fixes.
|
||||
|
||||
* Added new option `-show` to the `jwt` command that will just output the decoded token without verifying
|
||||
* Error text for expired tokens includes how long it's been expired
|
||||
* Fixed incorrect error returned from `ParseRSAPublicKeyFromPEM`
|
||||
* Documentation updates
|
||||
|
||||
#### 2.6.0
|
||||
|
||||
* Exposed inner error within ValidationError
|
||||
* Fixed validation errors when using UseJSONNumber flag
|
||||
* Added several unit tests
|
||||
|
||||
#### 2.5.0
|
||||
|
||||
* Added support for signing method none. You shouldn't use this. The API tries to make this clear.
|
||||
* Updated/fixed some documentation
|
||||
* Added more helpful error message when trying to parse tokens that begin with `BEARER `
|
||||
|
||||
#### 2.4.0
|
||||
|
||||
* Added new type, Parser, to allow for configuration of various parsing parameters
|
||||
* You can now specify a list of valid signing methods. Anything outside this set will be rejected.
|
||||
* You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON
|
||||
* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go)
|
||||
* Fixed some bugs with ECDSA parsing
|
||||
|
||||
#### 2.3.0
|
||||
|
||||
* Added support for ECDSA signing methods
|
||||
* Added support for RSA PSS signing methods (requires go v1.4)
|
||||
|
||||
#### 2.2.0
|
||||
|
||||
* Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic.
|
||||
|
||||
#### 2.1.0
|
||||
|
||||
Backwards compatible API change that was missed in 2.0.0.
|
||||
|
||||
* The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte`
|
||||
|
||||
#### 2.0.0
|
||||
|
||||
There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change.
|
||||
|
||||
The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`.
|
||||
|
||||
It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`.
|
||||
|
||||
* **Compatibility Breaking Changes**
|
||||
* `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct`
|
||||
* `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct`
|
||||
* `KeyFunc` now returns `interface{}` instead of `[]byte`
|
||||
* `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key
|
||||
* `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key
|
||||
* Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type.
|
||||
* Added public package global `SigningMethodHS256`
|
||||
* Added public package global `SigningMethodHS384`
|
||||
* Added public package global `SigningMethodHS512`
|
||||
* Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type.
|
||||
* Added public package global `SigningMethodRS256`
|
||||
* Added public package global `SigningMethodRS384`
|
||||
* Added public package global `SigningMethodRS512`
|
||||
* Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged.
|
||||
* Refactored the RSA implementation to be easier to read
|
||||
* Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM`
|
||||
|
||||
#### 1.0.2
|
||||
|
||||
* Fixed bug in parsing public keys from certificates
|
||||
* Added more tests around the parsing of keys for RS256
|
||||
* Code refactoring in RS256 implementation. No functional changes
|
||||
|
||||
#### 1.0.1
|
||||
|
||||
* Fixed panic if RS256 signing method was passed an invalid key
|
||||
|
||||
#### 1.0.0
|
||||
|
||||
* First versioned release
|
||||
* API stabilized
|
||||
* Supports creating, signing, parsing, and validating JWT tokens
|
||||
* Supports RS256 and HS256 signing methods
|
||||
134
vendor/github.com/dgrijalva/jwt-go/claims.go
generated
vendored
134
vendor/github.com/dgrijalva/jwt-go/claims.go
generated
vendored
@@ -1,134 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// For a type to be a Claims object, it must just have a Valid method that determines
|
||||
// if the token is invalid for any supported reason
|
||||
type Claims interface {
|
||||
Valid() error
|
||||
}
|
||||
|
||||
// Structured version of Claims Section, as referenced at
|
||||
// https://tools.ietf.org/html/rfc7519#section-4.1
|
||||
// See examples for how to use this with your own claim types
|
||||
type StandardClaims struct {
|
||||
Audience string `json:"aud,omitempty"`
|
||||
ExpiresAt int64 `json:"exp,omitempty"`
|
||||
Id string `json:"jti,omitempty"`
|
||||
IssuedAt int64 `json:"iat,omitempty"`
|
||||
Issuer string `json:"iss,omitempty"`
|
||||
NotBefore int64 `json:"nbf,omitempty"`
|
||||
Subject string `json:"sub,omitempty"`
|
||||
}
|
||||
|
||||
// Validates time based claims "exp, iat, nbf".
|
||||
// There is no accounting for clock skew.
|
||||
// As well, if any of the above claims are not in the token, it will still
|
||||
// be considered a valid claim.
|
||||
func (c StandardClaims) Valid() error {
|
||||
vErr := new(ValidationError)
|
||||
now := TimeFunc().Unix()
|
||||
|
||||
// The claims below are optional, by default, so if they are set to the
|
||||
// default value in Go, let's not fail the verification for them.
|
||||
if c.VerifyExpiresAt(now, false) == false {
|
||||
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
|
||||
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if c.VerifyIssuedAt(now, false) == false {
|
||||
vErr.Inner = fmt.Errorf("Token used before issued")
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if c.VerifyNotBefore(now, false) == false {
|
||||
vErr.Inner = fmt.Errorf("token is not valid yet")
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
if vErr.valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vErr
|
||||
}
|
||||
|
||||
// Compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
|
||||
return verifyAud(c.Audience, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the exp claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
|
||||
return verifyExp(c.ExpiresAt, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the iat claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
|
||||
return verifyIat(c.IssuedAt, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||
return verifyIss(c.Issuer, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the nbf claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
||||
return verifyNbf(c.NotBefore, cmp, req)
|
||||
}
|
||||
|
||||
// ----- helpers
|
||||
|
||||
func verifyAud(aud string, cmp string, required bool) bool {
|
||||
if aud == "" {
|
||||
return !required
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(aud), []byte(cmp)) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func verifyExp(exp int64, now int64, required bool) bool {
|
||||
if exp == 0 {
|
||||
return !required
|
||||
}
|
||||
return now <= exp
|
||||
}
|
||||
|
||||
func verifyIat(iat int64, now int64, required bool) bool {
|
||||
if iat == 0 {
|
||||
return !required
|
||||
}
|
||||
return now >= iat
|
||||
}
|
||||
|
||||
func verifyIss(iss string, cmp string, required bool) bool {
|
||||
if iss == "" {
|
||||
return !required
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func verifyNbf(nbf int64, now int64, required bool) bool {
|
||||
if nbf == 0 {
|
||||
return !required
|
||||
}
|
||||
return now >= nbf
|
||||
}
|
||||
4
vendor/github.com/dgrijalva/jwt-go/doc.go
generated
vendored
4
vendor/github.com/dgrijalva/jwt-go/doc.go
generated
vendored
@@ -1,4 +0,0 @@
|
||||
// Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html
|
||||
//
|
||||
// See README.md for more info.
|
||||
package jwt
|
||||
148
vendor/github.com/dgrijalva/jwt-go/ecdsa.go
generated
vendored
148
vendor/github.com/dgrijalva/jwt-go/ecdsa.go
generated
vendored
@@ -1,148 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
// Sadly this is missing from crypto/ecdsa compared to crypto/rsa
|
||||
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
|
||||
)
|
||||
|
||||
// Implements the ECDSA family of signing methods signing methods
|
||||
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
|
||||
type SigningMethodECDSA struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
KeySize int
|
||||
CurveBits int
|
||||
}
|
||||
|
||||
// Specific instances for EC256 and company
|
||||
var (
|
||||
SigningMethodES256 *SigningMethodECDSA
|
||||
SigningMethodES384 *SigningMethodECDSA
|
||||
SigningMethodES512 *SigningMethodECDSA
|
||||
)
|
||||
|
||||
func init() {
|
||||
// ES256
|
||||
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
|
||||
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
|
||||
return SigningMethodES256
|
||||
})
|
||||
|
||||
// ES384
|
||||
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
|
||||
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
|
||||
return SigningMethodES384
|
||||
})
|
||||
|
||||
// ES512
|
||||
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
|
||||
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
|
||||
return SigningMethodES512
|
||||
})
|
||||
}
|
||||
|
||||
func (m *SigningMethodECDSA) Alg() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// Implements the Verify method from SigningMethod
|
||||
// For this verify method, key must be an ecdsa.PublicKey struct
|
||||
func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error {
|
||||
var err error
|
||||
|
||||
// Decode the signature
|
||||
var sig []byte
|
||||
if sig, err = DecodeSegment(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the key
|
||||
var ecdsaKey *ecdsa.PublicKey
|
||||
switch k := key.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
ecdsaKey = k
|
||||
default:
|
||||
return ErrInvalidKeyType
|
||||
}
|
||||
|
||||
if len(sig) != 2*m.KeySize {
|
||||
return ErrECDSAVerification
|
||||
}
|
||||
|
||||
r := big.NewInt(0).SetBytes(sig[:m.KeySize])
|
||||
s := big.NewInt(0).SetBytes(sig[m.KeySize:])
|
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() {
|
||||
return ErrHashUnavailable
|
||||
}
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
// Verify the signature
|
||||
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
|
||||
return nil
|
||||
} else {
|
||||
return ErrECDSAVerification
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the Sign method from SigningMethod
|
||||
// For this signing method, key must be an ecdsa.PrivateKey struct
|
||||
func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) {
|
||||
// Get the key
|
||||
var ecdsaKey *ecdsa.PrivateKey
|
||||
switch k := key.(type) {
|
||||
case *ecdsa.PrivateKey:
|
||||
ecdsaKey = k
|
||||
default:
|
||||
return "", ErrInvalidKeyType
|
||||
}
|
||||
|
||||
// Create the hasher
|
||||
if !m.Hash.Available() {
|
||||
return "", ErrHashUnavailable
|
||||
}
|
||||
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
// Sign the string and return r, s
|
||||
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
|
||||
curveBits := ecdsaKey.Curve.Params().BitSize
|
||||
|
||||
if m.CurveBits != curveBits {
|
||||
return "", ErrInvalidKey
|
||||
}
|
||||
|
||||
keyBytes := curveBits / 8
|
||||
if curveBits%8 > 0 {
|
||||
keyBytes += 1
|
||||
}
|
||||
|
||||
// We serialize the outpus (r and s) into big-endian byte arrays and pad
|
||||
// them with zeros on the left to make sure the sizes work out. Both arrays
|
||||
// must be keyBytes long, and the output must be 2*keyBytes long.
|
||||
rBytes := r.Bytes()
|
||||
rBytesPadded := make([]byte, keyBytes)
|
||||
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
|
||||
|
||||
sBytes := s.Bytes()
|
||||
sBytesPadded := make([]byte, keyBytes)
|
||||
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
|
||||
|
||||
out := append(rBytesPadded, sBytesPadded...)
|
||||
|
||||
return EncodeSegment(out), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
67
vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go
generated
vendored
67
vendor/github.com/dgrijalva/jwt-go/ecdsa_utils.go
generated
vendored
@@ -1,67 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key")
|
||||
ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key")
|
||||
)
|
||||
|
||||
// Parse PEM encoded Elliptic Curve Private Key Structure
|
||||
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, ErrKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pkey *ecdsa.PrivateKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
|
||||
return nil, ErrNotECPrivateKey
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
|
||||
// Parse PEM encoded PKCS1 or PKCS8 public key
|
||||
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, ErrKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
||||
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
|
||||
parsedKey = cert.PublicKey
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var pkey *ecdsa.PublicKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
|
||||
return nil, ErrNotECPublicKey
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
59
vendor/github.com/dgrijalva/jwt-go/errors.go
generated
vendored
59
vendor/github.com/dgrijalva/jwt-go/errors.go
generated
vendored
@@ -1,59 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Error constants
|
||||
var (
|
||||
ErrInvalidKey = errors.New("key is invalid")
|
||||
ErrInvalidKeyType = errors.New("key is of invalid type")
|
||||
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
|
||||
)
|
||||
|
||||
// The errors that might occur when parsing and validating a token
|
||||
const (
|
||||
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
|
||||
ValidationErrorUnverifiable // Token could not be verified because of signing problems
|
||||
ValidationErrorSignatureInvalid // Signature validation failed
|
||||
|
||||
// Standard Claim validation errors
|
||||
ValidationErrorAudience // AUD validation failed
|
||||
ValidationErrorExpired // EXP validation failed
|
||||
ValidationErrorIssuedAt // IAT validation failed
|
||||
ValidationErrorIssuer // ISS validation failed
|
||||
ValidationErrorNotValidYet // NBF validation failed
|
||||
ValidationErrorId // JTI validation failed
|
||||
ValidationErrorClaimsInvalid // Generic claims validation error
|
||||
)
|
||||
|
||||
// Helper for constructing a ValidationError with a string error message
|
||||
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
|
||||
return &ValidationError{
|
||||
text: errorText,
|
||||
Errors: errorFlags,
|
||||
}
|
||||
}
|
||||
|
||||
// The error from Parse if token is not valid
|
||||
type ValidationError struct {
|
||||
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
|
||||
Errors uint32 // bitfield. see ValidationError... constants
|
||||
text string // errors that do not have a valid error just have text
|
||||
}
|
||||
|
||||
// Validation error is an error type
|
||||
func (e ValidationError) Error() string {
|
||||
if e.Inner != nil {
|
||||
return e.Inner.Error()
|
||||
} else if e.text != "" {
|
||||
return e.text
|
||||
} else {
|
||||
return "token is invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// No errors
|
||||
func (e *ValidationError) valid() bool {
|
||||
return e.Errors == 0
|
||||
}
|
||||
95
vendor/github.com/dgrijalva/jwt-go/hmac.go
generated
vendored
95
vendor/github.com/dgrijalva/jwt-go/hmac.go
generated
vendored
@@ -1,95 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Implements the HMAC-SHA family of signing methods signing methods
|
||||
// Expects key type of []byte for both signing and validation
|
||||
type SigningMethodHMAC struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
// Specific instances for HS256 and company
|
||||
var (
|
||||
SigningMethodHS256 *SigningMethodHMAC
|
||||
SigningMethodHS384 *SigningMethodHMAC
|
||||
SigningMethodHS512 *SigningMethodHMAC
|
||||
ErrSignatureInvalid = errors.New("signature is invalid")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// HS256
|
||||
SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256}
|
||||
RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod {
|
||||
return SigningMethodHS256
|
||||
})
|
||||
|
||||
// HS384
|
||||
SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384}
|
||||
RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod {
|
||||
return SigningMethodHS384
|
||||
})
|
||||
|
||||
// HS512
|
||||
SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512}
|
||||
RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod {
|
||||
return SigningMethodHS512
|
||||
})
|
||||
}
|
||||
|
||||
func (m *SigningMethodHMAC) Alg() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
|
||||
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
|
||||
// Verify the key is the right type
|
||||
keyBytes, ok := key.([]byte)
|
||||
if !ok {
|
||||
return ErrInvalidKeyType
|
||||
}
|
||||
|
||||
// Decode signature, for comparison
|
||||
sig, err := DecodeSegment(signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Can we use the specified hashing method?
|
||||
if !m.Hash.Available() {
|
||||
return ErrHashUnavailable
|
||||
}
|
||||
|
||||
// This signing method is symmetric, so we validate the signature
|
||||
// by reproducing the signature from the signing string and key, then
|
||||
// comparing that against the provided signature.
|
||||
hasher := hmac.New(m.Hash.New, keyBytes)
|
||||
hasher.Write([]byte(signingString))
|
||||
if !hmac.Equal(sig, hasher.Sum(nil)) {
|
||||
return ErrSignatureInvalid
|
||||
}
|
||||
|
||||
// No validation errors. Signature is good.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements the Sign method from SigningMethod for this signing method.
|
||||
// Key must be []byte
|
||||
func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) {
|
||||
if keyBytes, ok := key.([]byte); ok {
|
||||
if !m.Hash.Available() {
|
||||
return "", ErrHashUnavailable
|
||||
}
|
||||
|
||||
hasher := hmac.New(m.Hash.New, keyBytes)
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
return EncodeSegment(hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
return "", ErrInvalidKeyType
|
||||
}
|
||||
94
vendor/github.com/dgrijalva/jwt-go/map_claims.go
generated
vendored
94
vendor/github.com/dgrijalva/jwt-go/map_claims.go
generated
vendored
@@ -1,94 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
// "fmt"
|
||||
)
|
||||
|
||||
// Claims type that uses the map[string]interface{} for JSON decoding
|
||||
// This is the default claims type if you don't supply one
|
||||
type MapClaims map[string]interface{}
|
||||
|
||||
// Compares the aud claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyAudience(cmp string, req bool) bool {
|
||||
aud, _ := m["aud"].(string)
|
||||
return verifyAud(aud, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the exp claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool {
|
||||
switch exp := m["exp"].(type) {
|
||||
case float64:
|
||||
return verifyExp(int64(exp), cmp, req)
|
||||
case json.Number:
|
||||
v, _ := exp.Int64()
|
||||
return verifyExp(v, cmp, req)
|
||||
}
|
||||
return req == false
|
||||
}
|
||||
|
||||
// Compares the iat claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool {
|
||||
switch iat := m["iat"].(type) {
|
||||
case float64:
|
||||
return verifyIat(int64(iat), cmp, req)
|
||||
case json.Number:
|
||||
v, _ := iat.Int64()
|
||||
return verifyIat(v, cmp, req)
|
||||
}
|
||||
return req == false
|
||||
}
|
||||
|
||||
// Compares the iss claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyIssuer(cmp string, req bool) bool {
|
||||
iss, _ := m["iss"].(string)
|
||||
return verifyIss(iss, cmp, req)
|
||||
}
|
||||
|
||||
// Compares the nbf claim against cmp.
|
||||
// If required is false, this method will return true if the value matches or is unset
|
||||
func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool {
|
||||
switch nbf := m["nbf"].(type) {
|
||||
case float64:
|
||||
return verifyNbf(int64(nbf), cmp, req)
|
||||
case json.Number:
|
||||
v, _ := nbf.Int64()
|
||||
return verifyNbf(v, cmp, req)
|
||||
}
|
||||
return req == false
|
||||
}
|
||||
|
||||
// Validates time based claims "exp, iat, nbf".
|
||||
// There is no accounting for clock skew.
|
||||
// As well, if any of the above claims are not in the token, it will still
|
||||
// be considered a valid claim.
|
||||
func (m MapClaims) Valid() error {
|
||||
vErr := new(ValidationError)
|
||||
now := TimeFunc().Unix()
|
||||
|
||||
if m.VerifyExpiresAt(now, false) == false {
|
||||
vErr.Inner = errors.New("Token is expired")
|
||||
vErr.Errors |= ValidationErrorExpired
|
||||
}
|
||||
|
||||
if m.VerifyIssuedAt(now, false) == false {
|
||||
vErr.Inner = errors.New("Token used before issued")
|
||||
vErr.Errors |= ValidationErrorIssuedAt
|
||||
}
|
||||
|
||||
if m.VerifyNotBefore(now, false) == false {
|
||||
vErr.Inner = errors.New("Token is not valid yet")
|
||||
vErr.Errors |= ValidationErrorNotValidYet
|
||||
}
|
||||
|
||||
if vErr.valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vErr
|
||||
}
|
||||
52
vendor/github.com/dgrijalva/jwt-go/none.go
generated
vendored
52
vendor/github.com/dgrijalva/jwt-go/none.go
generated
vendored
@@ -1,52 +0,0 @@
|
||||
package jwt
|
||||
|
||||
// Implements the none signing method. This is required by the spec
|
||||
// but you probably should never use it.
|
||||
var SigningMethodNone *signingMethodNone
|
||||
|
||||
const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
|
||||
|
||||
var NoneSignatureTypeDisallowedError error
|
||||
|
||||
type signingMethodNone struct{}
|
||||
type unsafeNoneMagicConstant string
|
||||
|
||||
func init() {
|
||||
SigningMethodNone = &signingMethodNone{}
|
||||
NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
|
||||
|
||||
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
|
||||
return SigningMethodNone
|
||||
})
|
||||
}
|
||||
|
||||
func (m *signingMethodNone) Alg() string {
|
||||
return "none"
|
||||
}
|
||||
|
||||
// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key
|
||||
func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) {
|
||||
// Key must be UnsafeAllowNoneSignatureType to prevent accidentally
|
||||
// accepting 'none' signing method
|
||||
if _, ok := key.(unsafeNoneMagicConstant); !ok {
|
||||
return NoneSignatureTypeDisallowedError
|
||||
}
|
||||
// If signing method is none, signature must be an empty string
|
||||
if signature != "" {
|
||||
return NewValidationError(
|
||||
"'none' signing method with non-empty signature",
|
||||
ValidationErrorSignatureInvalid,
|
||||
)
|
||||
}
|
||||
|
||||
// Accept 'none' signing method.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key
|
||||
func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) {
|
||||
if _, ok := key.(unsafeNoneMagicConstant); ok {
|
||||
return "", nil
|
||||
}
|
||||
return "", NoneSignatureTypeDisallowedError
|
||||
}
|
||||
148
vendor/github.com/dgrijalva/jwt-go/parser.go
generated
vendored
148
vendor/github.com/dgrijalva/jwt-go/parser.go
generated
vendored
@@ -1,148 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
ValidMethods []string // If populated, only these methods will be considered valid
|
||||
UseJSONNumber bool // Use JSON Number format in JSON decoder
|
||||
SkipClaimsValidation bool // Skip claims validation during token parsing
|
||||
}
|
||||
|
||||
// Parse, validate, and return a token.
|
||||
// keyFunc will receive the parsed token and should return the key for validating.
|
||||
// If everything is kosher, err will be nil
|
||||
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
||||
return p.ParseWithClaims(tokenString, MapClaims{}, keyFunc)
|
||||
}
|
||||
|
||||
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||
token, parts, err := p.ParseUnverified(tokenString, claims)
|
||||
if err != nil {
|
||||
return token, err
|
||||
}
|
||||
|
||||
// Verify signing method is in the required set
|
||||
if p.ValidMethods != nil {
|
||||
var signingMethodValid = false
|
||||
var alg = token.Method.Alg()
|
||||
for _, m := range p.ValidMethods {
|
||||
if m == alg {
|
||||
signingMethodValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !signingMethodValid {
|
||||
// signing method is not in the listed set
|
||||
return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup key
|
||||
var key interface{}
|
||||
if keyFunc == nil {
|
||||
// keyFunc was not provided. short circuiting validation
|
||||
return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable)
|
||||
}
|
||||
if key, err = keyFunc(token); err != nil {
|
||||
// keyFunc returned an error
|
||||
if ve, ok := err.(*ValidationError); ok {
|
||||
return token, ve
|
||||
}
|
||||
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
|
||||
}
|
||||
|
||||
vErr := &ValidationError{}
|
||||
|
||||
// Validate Claims
|
||||
if !p.SkipClaimsValidation {
|
||||
if err := token.Claims.Valid(); err != nil {
|
||||
|
||||
// If the Claims Valid returned an error, check if it is a validation error,
|
||||
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
|
||||
if e, ok := err.(*ValidationError); !ok {
|
||||
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
|
||||
} else {
|
||||
vErr = e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform validation
|
||||
token.Signature = parts[2]
|
||||
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
|
||||
vErr.Inner = err
|
||||
vErr.Errors |= ValidationErrorSignatureInvalid
|
||||
}
|
||||
|
||||
if vErr.valid() {
|
||||
token.Valid = true
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return token, vErr
|
||||
}
|
||||
|
||||
// WARNING: Don't use this method unless you know what you're doing
|
||||
//
|
||||
// This method parses the token but doesn't validate the signature. It's only
|
||||
// ever useful in cases where you know the signature is valid (because it has
|
||||
// been checked previously in the stack) and you want to extract values from
|
||||
// it.
|
||||
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error) {
|
||||
parts = strings.Split(tokenString, ".")
|
||||
if len(parts) != 3 {
|
||||
return nil, parts, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed)
|
||||
}
|
||||
|
||||
token = &Token{Raw: tokenString}
|
||||
|
||||
// parse Header
|
||||
var headerBytes []byte
|
||||
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
|
||||
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") {
|
||||
return token, parts, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed)
|
||||
}
|
||||
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||
}
|
||||
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
|
||||
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
// parse Claims
|
||||
var claimBytes []byte
|
||||
token.Claims = claims
|
||||
|
||||
if claimBytes, err = DecodeSegment(parts[1]); err != nil {
|
||||
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||
}
|
||||
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
|
||||
if p.UseJSONNumber {
|
||||
dec.UseNumber()
|
||||
}
|
||||
// JSON Decode. Special case for map type to avoid weird pointer behavior
|
||||
if c, ok := token.Claims.(MapClaims); ok {
|
||||
err = dec.Decode(&c)
|
||||
} else {
|
||||
err = dec.Decode(&claims)
|
||||
}
|
||||
// Handle decode error
|
||||
if err != nil {
|
||||
return token, parts, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
|
||||
}
|
||||
|
||||
// Lookup signature method
|
||||
if method, ok := token.Header["alg"].(string); ok {
|
||||
if token.Method = GetSigningMethod(method); token.Method == nil {
|
||||
return token, parts, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable)
|
||||
}
|
||||
} else {
|
||||
return token, parts, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable)
|
||||
}
|
||||
|
||||
return token, parts, nil
|
||||
}
|
||||
101
vendor/github.com/dgrijalva/jwt-go/rsa.go
generated
vendored
101
vendor/github.com/dgrijalva/jwt-go/rsa.go
generated
vendored
@@ -1,101 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// Implements the RSA family of signing methods signing methods
|
||||
// Expects *rsa.PrivateKey for signing and *rsa.PublicKey for validation
|
||||
type SigningMethodRSA struct {
|
||||
Name string
|
||||
Hash crypto.Hash
|
||||
}
|
||||
|
||||
// Specific instances for RS256 and company
|
||||
var (
|
||||
SigningMethodRS256 *SigningMethodRSA
|
||||
SigningMethodRS384 *SigningMethodRSA
|
||||
SigningMethodRS512 *SigningMethodRSA
|
||||
)
|
||||
|
||||
func init() {
|
||||
// RS256
|
||||
SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256}
|
||||
RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod {
|
||||
return SigningMethodRS256
|
||||
})
|
||||
|
||||
// RS384
|
||||
SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384}
|
||||
RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod {
|
||||
return SigningMethodRS384
|
||||
})
|
||||
|
||||
// RS512
|
||||
SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512}
|
||||
RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod {
|
||||
return SigningMethodRS512
|
||||
})
|
||||
}
|
||||
|
||||
func (m *SigningMethodRSA) Alg() string {
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// Implements the Verify method from SigningMethod
|
||||
// For this signing method, must be an *rsa.PublicKey structure.
|
||||
func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error {
|
||||
var err error
|
||||
|
||||
// Decode the signature
|
||||
var sig []byte
|
||||
if sig, err = DecodeSegment(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rsaKey *rsa.PublicKey
|
||||
var ok bool
|
||||
|
||||
if rsaKey, ok = key.(*rsa.PublicKey); !ok {
|
||||
return ErrInvalidKeyType
|
||||
}
|
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() {
|
||||
return ErrHashUnavailable
|
||||
}
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
// Verify the signature
|
||||
return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig)
|
||||
}
|
||||
|
||||
// Implements the Sign method from SigningMethod
|
||||
// For this signing method, must be an *rsa.PrivateKey structure.
|
||||
func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) {
|
||||
var rsaKey *rsa.PrivateKey
|
||||
var ok bool
|
||||
|
||||
// Validate type of key
|
||||
if rsaKey, ok = key.(*rsa.PrivateKey); !ok {
|
||||
return "", ErrInvalidKey
|
||||
}
|
||||
|
||||
// Create the hasher
|
||||
if !m.Hash.Available() {
|
||||
return "", ErrHashUnavailable
|
||||
}
|
||||
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
// Sign the string and return the encoded bytes
|
||||
if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil {
|
||||
return EncodeSegment(sigBytes), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
126
vendor/github.com/dgrijalva/jwt-go/rsa_pss.go
generated
vendored
126
vendor/github.com/dgrijalva/jwt-go/rsa_pss.go
generated
vendored
@@ -1,126 +0,0 @@
|
||||
// +build go1.4
|
||||
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
)
|
||||
|
||||
// Implements the RSAPSS family of signing methods signing methods
|
||||
type SigningMethodRSAPSS struct {
|
||||
*SigningMethodRSA
|
||||
Options *rsa.PSSOptions
|
||||
}
|
||||
|
||||
// Specific instances for RS/PS and company
|
||||
var (
|
||||
SigningMethodPS256 *SigningMethodRSAPSS
|
||||
SigningMethodPS384 *SigningMethodRSAPSS
|
||||
SigningMethodPS512 *SigningMethodRSAPSS
|
||||
)
|
||||
|
||||
func init() {
|
||||
// PS256
|
||||
SigningMethodPS256 = &SigningMethodRSAPSS{
|
||||
&SigningMethodRSA{
|
||||
Name: "PS256",
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
&rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA256,
|
||||
},
|
||||
}
|
||||
RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod {
|
||||
return SigningMethodPS256
|
||||
})
|
||||
|
||||
// PS384
|
||||
SigningMethodPS384 = &SigningMethodRSAPSS{
|
||||
&SigningMethodRSA{
|
||||
Name: "PS384",
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
&rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA384,
|
||||
},
|
||||
}
|
||||
RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod {
|
||||
return SigningMethodPS384
|
||||
})
|
||||
|
||||
// PS512
|
||||
SigningMethodPS512 = &SigningMethodRSAPSS{
|
||||
&SigningMethodRSA{
|
||||
Name: "PS512",
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
&rsa.PSSOptions{
|
||||
SaltLength: rsa.PSSSaltLengthAuto,
|
||||
Hash: crypto.SHA512,
|
||||
},
|
||||
}
|
||||
RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod {
|
||||
return SigningMethodPS512
|
||||
})
|
||||
}
|
||||
|
||||
// Implements the Verify method from SigningMethod
|
||||
// For this verify method, key must be an rsa.PublicKey struct
|
||||
func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error {
|
||||
var err error
|
||||
|
||||
// Decode the signature
|
||||
var sig []byte
|
||||
if sig, err = DecodeSegment(signature); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var rsaKey *rsa.PublicKey
|
||||
switch k := key.(type) {
|
||||
case *rsa.PublicKey:
|
||||
rsaKey = k
|
||||
default:
|
||||
return ErrInvalidKey
|
||||
}
|
||||
|
||||
// Create hasher
|
||||
if !m.Hash.Available() {
|
||||
return ErrHashUnavailable
|
||||
}
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options)
|
||||
}
|
||||
|
||||
// Implements the Sign method from SigningMethod
|
||||
// For this signing method, key must be an rsa.PrivateKey struct
|
||||
func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) {
|
||||
var rsaKey *rsa.PrivateKey
|
||||
|
||||
switch k := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
rsaKey = k
|
||||
default:
|
||||
return "", ErrInvalidKeyType
|
||||
}
|
||||
|
||||
// Create the hasher
|
||||
if !m.Hash.Available() {
|
||||
return "", ErrHashUnavailable
|
||||
}
|
||||
|
||||
hasher := m.Hash.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
|
||||
// Sign the string and return the encoded bytes
|
||||
if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil {
|
||||
return EncodeSegment(sigBytes), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
101
vendor/github.com/dgrijalva/jwt-go/rsa_utils.go
generated
vendored
101
vendor/github.com/dgrijalva/jwt-go/rsa_utils.go
generated
vendored
@@ -1,101 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key")
|
||||
ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key")
|
||||
ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key")
|
||||
)
|
||||
|
||||
// Parse PEM encoded PKCS1 or PKCS8 private key
|
||||
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, ErrKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var pkey *rsa.PrivateKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
|
||||
return nil, ErrNotRSAPrivateKey
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
|
||||
// Parse PEM encoded PKCS1 or PKCS8 private key protected with password
|
||||
func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, ErrKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
var parsedKey interface{}
|
||||
|
||||
var blockDecrypted []byte
|
||||
if blockDecrypted, err = x509.DecryptPEMBlock(block, []byte(password)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if parsedKey, err = x509.ParsePKCS1PrivateKey(blockDecrypted); err != nil {
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(blockDecrypted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var pkey *rsa.PrivateKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok {
|
||||
return nil, ErrNotRSAPrivateKey
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
|
||||
// Parse PEM encoded PKCS1 or PKCS8 public key
|
||||
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, ErrKeyMustBePEMEncoded
|
||||
}
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
|
||||
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
|
||||
parsedKey = cert.PublicKey
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var pkey *rsa.PublicKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
|
||||
return nil, ErrNotRSAPublicKey
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
35
vendor/github.com/dgrijalva/jwt-go/signing_method.go
generated
vendored
35
vendor/github.com/dgrijalva/jwt-go/signing_method.go
generated
vendored
@@ -1,35 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var signingMethods = map[string]func() SigningMethod{}
|
||||
var signingMethodLock = new(sync.RWMutex)
|
||||
|
||||
// Implement SigningMethod to add new methods for signing or verifying tokens.
|
||||
type SigningMethod interface {
|
||||
Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid
|
||||
Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error
|
||||
Alg() string // returns the alg identifier for this method (example: 'HS256')
|
||||
}
|
||||
|
||||
// Register the "alg" name and a factory function for signing method.
|
||||
// This is typically done during init() in the method's implementation
|
||||
func RegisterSigningMethod(alg string, f func() SigningMethod) {
|
||||
signingMethodLock.Lock()
|
||||
defer signingMethodLock.Unlock()
|
||||
|
||||
signingMethods[alg] = f
|
||||
}
|
||||
|
||||
// Get a signing method from an "alg" string
|
||||
func GetSigningMethod(alg string) (method SigningMethod) {
|
||||
signingMethodLock.RLock()
|
||||
defer signingMethodLock.RUnlock()
|
||||
|
||||
if methodF, ok := signingMethods[alg]; ok {
|
||||
method = methodF()
|
||||
}
|
||||
return
|
||||
}
|
||||
108
vendor/github.com/dgrijalva/jwt-go/token.go
generated
vendored
108
vendor/github.com/dgrijalva/jwt-go/token.go
generated
vendored
@@ -1,108 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time).
|
||||
// You can override it to use another time value. This is useful for testing or if your
|
||||
// server uses a different time zone than your tokens.
|
||||
var TimeFunc = time.Now
|
||||
|
||||
// Parse methods use this callback function to supply
|
||||
// the key for verification. The function receives the parsed,
|
||||
// but unverified Token. This allows you to use properties in the
|
||||
// Header of the token (such as `kid`) to identify which key to use.
|
||||
type Keyfunc func(*Token) (interface{}, error)
|
||||
|
||||
// A JWT Token. Different fields will be used depending on whether you're
|
||||
// creating or parsing/verifying a token.
|
||||
type Token struct {
|
||||
Raw string // The raw token. Populated when you Parse a token
|
||||
Method SigningMethod // The signing method used or to be used
|
||||
Header map[string]interface{} // The first segment of the token
|
||||
Claims Claims // The second segment of the token
|
||||
Signature string // The third segment of the token. Populated when you Parse a token
|
||||
Valid bool // Is the token valid? Populated when you Parse/Verify a token
|
||||
}
|
||||
|
||||
// Create a new Token. Takes a signing method
|
||||
func New(method SigningMethod) *Token {
|
||||
return NewWithClaims(method, MapClaims{})
|
||||
}
|
||||
|
||||
func NewWithClaims(method SigningMethod, claims Claims) *Token {
|
||||
return &Token{
|
||||
Header: map[string]interface{}{
|
||||
"typ": "JWT",
|
||||
"alg": method.Alg(),
|
||||
},
|
||||
Claims: claims,
|
||||
Method: method,
|
||||
}
|
||||
}
|
||||
|
||||
// Get the complete, signed token
|
||||
func (t *Token) SignedString(key interface{}) (string, error) {
|
||||
var sig, sstr string
|
||||
var err error
|
||||
if sstr, err = t.SigningString(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if sig, err = t.Method.Sign(sstr, key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Join([]string{sstr, sig}, "."), nil
|
||||
}
|
||||
|
||||
// Generate the signing string. This is the
|
||||
// most expensive part of the whole deal. Unless you
|
||||
// need this for something special, just go straight for
|
||||
// the SignedString.
|
||||
func (t *Token) SigningString() (string, error) {
|
||||
var err error
|
||||
parts := make([]string, 2)
|
||||
for i, _ := range parts {
|
||||
var jsonValue []byte
|
||||
if i == 0 {
|
||||
if jsonValue, err = json.Marshal(t.Header); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
if jsonValue, err = json.Marshal(t.Claims); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
parts[i] = EncodeSegment(jsonValue)
|
||||
}
|
||||
return strings.Join(parts, "."), nil
|
||||
}
|
||||
|
||||
// Parse, validate, and return a token.
|
||||
// keyFunc will receive the parsed token and should return the key for validating.
|
||||
// If everything is kosher, err will be nil
|
||||
func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
|
||||
return new(Parser).Parse(tokenString, keyFunc)
|
||||
}
|
||||
|
||||
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
|
||||
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
|
||||
}
|
||||
|
||||
// Encode JWT specific base64url encoding with padding stripped
|
||||
func EncodeSegment(seg []byte) string {
|
||||
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
|
||||
}
|
||||
|
||||
// Decode JWT specific base64url encoding with padding stripped
|
||||
func DecodeSegment(seg string) ([]byte, error) {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
}
|
||||
19
vendor/github.com/dhowden/tag/.editorconfig
generated
vendored
19
vendor/github.com/dhowden/tag/.editorconfig
generated
vendored
@@ -1,19 +0,0 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 3
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*]
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
5
vendor/github.com/dhowden/tag/.travis.yml
generated
vendored
5
vendor/github.com/dhowden/tag/.travis.yml
generated
vendored
@@ -1,5 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- tip
|
||||
23
vendor/github.com/dhowden/tag/LICENSE
generated
vendored
23
vendor/github.com/dhowden/tag/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
||||
Copyright 2015, David Howden
|
||||
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.
|
||||
72
vendor/github.com/dhowden/tag/README.md
generated
vendored
72
vendor/github.com/dhowden/tag/README.md
generated
vendored
@@ -1,72 +0,0 @@
|
||||
# MP3/MP4/OGG/FLAC metadata parsing library
|
||||
[](https://travis-ci.org/dhowden/tag)
|
||||
[](https://godoc.org/github.com/dhowden/tag)
|
||||
|
||||
This package provides MP3 (ID3v1,2.{2,3,4}) and MP4 (ACC, M4A, ALAC), OGG and FLAC metadata detection, parsing and artwork extraction.
|
||||
|
||||
Detect and parse tag metadata from an `io.ReadSeeker` (i.e. an `*os.File`):
|
||||
|
||||
```go
|
||||
m, err := tag.ReadFrom(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Print(m.Format()) // The detected format.
|
||||
log.Print(m.Title()) // The title of the track (see Metadata interface for more details).
|
||||
```
|
||||
|
||||
Parsed metadata is exported via a single interface (giving a consistent API for all supported metadata formats).
|
||||
|
||||
```go
|
||||
// Metadata is an interface which is used to describe metadata retrieved by this package.
|
||||
type Metadata interface {
|
||||
Format() Format
|
||||
FileType() FileType
|
||||
|
||||
Title() string
|
||||
Album() string
|
||||
Artist() string
|
||||
AlbumArtist() string
|
||||
Composer() string
|
||||
Genre() string
|
||||
Year() int
|
||||
|
||||
Track() (int, int) // Number, Total
|
||||
Disc() (int, int) // Number, Total
|
||||
|
||||
Picture() *Picture // Artwork
|
||||
Lyrics() string
|
||||
Comment() string
|
||||
|
||||
Raw() map[string]interface{} // NB: raw tag names are not consistent across formats.
|
||||
}
|
||||
```
|
||||
|
||||
## Audio Data Checksum (SHA1)
|
||||
|
||||
This package also provides a metadata-invariant checksum for audio files: only the audio data is used to
|
||||
construct the checksum.
|
||||
|
||||
[http://godoc.org/github.com/dhowden/tag#Sum](http://godoc.org/github.com/dhowden/tag#Sum)
|
||||
|
||||
## Tools
|
||||
|
||||
There are simple command-line tools which demonstrate basic tag extraction and summing:
|
||||
|
||||
```console
|
||||
$ go get github.com/dhowden/tag/...
|
||||
$ cd $GOPATH/bin
|
||||
$ ./tag 11\ High\ Hopes.m4a
|
||||
Metadata Format: MP4
|
||||
Title: High Hopes
|
||||
Album: The Division Bell
|
||||
Artist: Pink Floyd
|
||||
Composer: Abbey Road Recording Studios/David Gilmour/Polly Samson
|
||||
Year: 1994
|
||||
Track: 11 of 11
|
||||
Disc: 1 of 1
|
||||
Picture: Picture{Ext: jpeg, MIMEType: image/jpeg, Type: , Description: , Data.Size: 606109}
|
||||
|
||||
$ ./sum 11\ High\ Hopes.m4a
|
||||
2ae208c5f00a1f21f5fac9b7f6e0b8e52c06da29
|
||||
```
|
||||
110
vendor/github.com/dhowden/tag/dsf.go
generated
vendored
110
vendor/github.com/dhowden/tag/dsf.go
generated
vendored
@@ -1,110 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadDSFTags reads DSF metadata from the io.ReadSeeker, returning the resulting
|
||||
// metadata in a Metadata implementation, or non-nil error if there was a problem.
|
||||
// samples: http://www.2l.no/hires/index.html
|
||||
func ReadDSFTags(r io.ReadSeeker) (Metadata, error) {
|
||||
dsd, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dsd != "DSD " {
|
||||
return nil, errors.New("expected 'DSD '")
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(16), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n4, err := readBytes(r, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id3Pointer := getIntLittleEndian(n4)
|
||||
|
||||
_, err = r.Seek(int64(id3Pointer), io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id3, err := ReadID3v2Tags(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metadataDSF{id3}, nil
|
||||
}
|
||||
|
||||
type metadataDSF struct {
|
||||
id3 Metadata
|
||||
}
|
||||
|
||||
func (m metadataDSF) Format() Format {
|
||||
return m.id3.Format()
|
||||
}
|
||||
|
||||
func (m metadataDSF) FileType() FileType {
|
||||
return DSF
|
||||
}
|
||||
|
||||
func (m metadataDSF) Title() string {
|
||||
return m.id3.Title()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Album() string {
|
||||
return m.id3.Album()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Artist() string {
|
||||
return m.id3.Artist()
|
||||
}
|
||||
|
||||
func (m metadataDSF) AlbumArtist() string {
|
||||
return m.id3.AlbumArtist()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Composer() string {
|
||||
return m.id3.Composer()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Year() int {
|
||||
return m.id3.Year()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Genre() string {
|
||||
return m.id3.Genre()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Track() (int, int) {
|
||||
return m.id3.Track()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Disc() (int, int) {
|
||||
return m.id3.Disc()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Picture() *Picture {
|
||||
return m.id3.Picture()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Lyrics() string {
|
||||
return m.id3.Lyrics()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Comment() string {
|
||||
return m.id3.Comment()
|
||||
}
|
||||
|
||||
func (m metadataDSF) Raw() map[string]interface{} {
|
||||
return m.id3.Raw()
|
||||
}
|
||||
89
vendor/github.com/dhowden/tag/flac.go
generated
vendored
89
vendor/github.com/dhowden/tag/flac.go
generated
vendored
@@ -1,89 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// blockType is a type which represents an enumeration of valid FLAC blocks
|
||||
type blockType byte
|
||||
|
||||
// FLAC block types.
|
||||
const (
|
||||
// Stream Info Block 0
|
||||
// Padding Block 1
|
||||
// Application Block 2
|
||||
// Seektable Block 3
|
||||
// Cue Sheet Block 5
|
||||
vorbisCommentBlock blockType = 4
|
||||
pictureBlock blockType = 6
|
||||
)
|
||||
|
||||
// ReadFLACTags reads FLAC metadata from the io.ReadSeeker, returning the resulting
|
||||
// metadata in a Metadata implementation, or non-nil error if there was a problem.
|
||||
func ReadFLACTags(r io.ReadSeeker) (Metadata, error) {
|
||||
flac, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if flac != "fLaC" {
|
||||
return nil, errors.New("expected 'fLaC'")
|
||||
}
|
||||
|
||||
m := &metadataFLAC{
|
||||
newMetadataVorbis(),
|
||||
}
|
||||
|
||||
for {
|
||||
last, err := m.readFLACMetadataBlock(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if last {
|
||||
break
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
type metadataFLAC struct {
|
||||
*metadataVorbis
|
||||
}
|
||||
|
||||
func (m *metadataFLAC) readFLACMetadataBlock(r io.ReadSeeker) (last bool, err error) {
|
||||
blockHeader, err := readBytes(r, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if getBit(blockHeader[0], 7) {
|
||||
blockHeader[0] ^= (1 << 7)
|
||||
last = true
|
||||
}
|
||||
|
||||
blockLen, err := readInt(r, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch blockType(blockHeader[0]) {
|
||||
case vorbisCommentBlock:
|
||||
err = m.readVorbisComment(r)
|
||||
|
||||
case pictureBlock:
|
||||
err = m.readPictureBlock(r)
|
||||
|
||||
default:
|
||||
_, err = r.Seek(int64(blockLen), io.SeekCurrent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *metadataFLAC) FileType() FileType {
|
||||
return FLAC
|
||||
}
|
||||
81
vendor/github.com/dhowden/tag/id.go
generated
vendored
81
vendor/github.com/dhowden/tag/id.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Identify identifies the format and file type of the data in the ReadSeeker.
|
||||
func Identify(r io.ReadSeeker) (format Format, fileType FileType, err error) {
|
||||
b, err := readBytes(r, 11)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.Seek(-11, io.SeekCurrent)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not seek back to original position: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case string(b[0:4]) == "fLaC":
|
||||
return VORBIS, FLAC, nil
|
||||
|
||||
case string(b[0:4]) == "OggS":
|
||||
return VORBIS, OGG, nil
|
||||
|
||||
case string(b[4:8]) == "ftyp":
|
||||
b = b[8:11]
|
||||
fileType = UnknownFileType
|
||||
switch string(b) {
|
||||
case "M4A":
|
||||
fileType = M4A
|
||||
|
||||
case "M4B":
|
||||
fileType = M4B
|
||||
|
||||
case "M4P":
|
||||
fileType = M4P
|
||||
}
|
||||
return MP4, fileType, nil
|
||||
|
||||
case string(b[0:3]) == "ID3":
|
||||
b := b[3:]
|
||||
switch uint(b[0]) {
|
||||
case 2:
|
||||
format = ID3v2_2
|
||||
case 3:
|
||||
format = ID3v2_3
|
||||
case 4:
|
||||
format = ID3v2_4
|
||||
case 0, 1:
|
||||
fallthrough
|
||||
default:
|
||||
err = fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
|
||||
return
|
||||
}
|
||||
return format, MP3, nil
|
||||
}
|
||||
|
||||
n, err := r.Seek(-128, io.SeekEnd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tag, err := readString(r, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.Seek(-n, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tag != "TAG" {
|
||||
err = ErrNoTagsFound
|
||||
return
|
||||
}
|
||||
return ID3v1, MP3, nil
|
||||
}
|
||||
144
vendor/github.com/dhowden/tag/id3v1.go
generated
vendored
144
vendor/github.com/dhowden/tag/id3v1.go
generated
vendored
@@ -1,144 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// id3v1Genres is a list of genres as given in the ID3v1 specification.
|
||||
var id3v1Genres = [...]string{
|
||||
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
|
||||
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
|
||||
"Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
|
||||
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
|
||||
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
|
||||
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
|
||||
"Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
|
||||
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
|
||||
"Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
|
||||
"Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
|
||||
"Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
|
||||
"Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer",
|
||||
"Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro",
|
||||
"Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock",
|
||||
"National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival",
|
||||
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",
|
||||
"Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
|
||||
"Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
|
||||
"Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
|
||||
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
|
||||
"Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
|
||||
"Duet", "Punk Rock", "Drum Solo", "Acapella", "Euro-House", "Dance Hall",
|
||||
}
|
||||
|
||||
// ErrNotID3v1 is an error which is returned when no ID3v1 header is found.
|
||||
var ErrNotID3v1 = errors.New("invalid ID3v1 header")
|
||||
|
||||
// ReadID3v1Tags reads ID3v1 tags from the io.ReadSeeker. Returns ErrNotID3v1
|
||||
// if there are no ID3v1 tags, otherwise non-nil error if there was a problem.
|
||||
func ReadID3v1Tags(r io.ReadSeeker) (Metadata, error) {
|
||||
_, err := r.Seek(-128, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if tag, err := readString(r, 3); err != nil {
|
||||
return nil, err
|
||||
} else if tag != "TAG" {
|
||||
return nil, ErrNotID3v1
|
||||
}
|
||||
|
||||
title, err := readString(r, 30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
artist, err := readString(r, 30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
album, err := readString(r, 30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
year, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commentBytes, err := readBytes(r, 30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var comment string
|
||||
var track int
|
||||
if commentBytes[28] == 0 {
|
||||
comment = trimString(string(commentBytes[:28]))
|
||||
track = int(commentBytes[29])
|
||||
} else {
|
||||
comment = trimString(string(commentBytes))
|
||||
}
|
||||
|
||||
var genre string
|
||||
genreID, err := readBytes(r, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int(genreID[0]) < len(id3v1Genres) {
|
||||
genre = id3v1Genres[int(genreID[0])]
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
m["title"] = trimString(title)
|
||||
m["artist"] = trimString(artist)
|
||||
m["album"] = trimString(album)
|
||||
m["year"] = trimString(year)
|
||||
m["comment"] = trimString(comment)
|
||||
m["track"] = track
|
||||
m["genre"] = genre
|
||||
|
||||
return metadataID3v1(m), nil
|
||||
}
|
||||
|
||||
func trimString(x string) string {
|
||||
return strings.TrimSpace(strings.Trim(x, "\x00"))
|
||||
}
|
||||
|
||||
// metadataID3v1 is the implementation of Metadata used for ID3v1 tags.
|
||||
type metadataID3v1 map[string]interface{}
|
||||
|
||||
func (metadataID3v1) Format() Format { return ID3v1 }
|
||||
func (metadataID3v1) FileType() FileType { return MP3 }
|
||||
func (m metadataID3v1) Raw() map[string]interface{} { return m }
|
||||
|
||||
func (m metadataID3v1) Title() string { return m["title"].(string) }
|
||||
func (m metadataID3v1) Album() string { return m["album"].(string) }
|
||||
func (m metadataID3v1) Artist() string { return m["artist"].(string) }
|
||||
func (m metadataID3v1) Genre() string { return m["genre"].(string) }
|
||||
|
||||
func (m metadataID3v1) Year() int {
|
||||
y := m["year"].(string)
|
||||
n, err := strconv.Atoi(y)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m metadataID3v1) Track() (int, int) { return m["track"].(int), 0 }
|
||||
|
||||
func (m metadataID3v1) AlbumArtist() string { return "" }
|
||||
func (m metadataID3v1) Composer() string { return "" }
|
||||
func (metadataID3v1) Disc() (int, int) { return 0, 0 }
|
||||
func (m metadataID3v1) Picture() *Picture { return nil }
|
||||
func (m metadataID3v1) Lyrics() string { return "" }
|
||||
func (m metadataID3v1) Comment() string { return m["comment"].(string) }
|
||||
434
vendor/github.com/dhowden/tag/id3v2.go
generated
vendored
434
vendor/github.com/dhowden/tag/id3v2.go
generated
vendored
@@ -1,434 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var id3v2Genres = [...]string{
|
||||
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
|
||||
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
|
||||
"Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
|
||||
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
|
||||
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
|
||||
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel",
|
||||
"Noise", "AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
|
||||
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
|
||||
"Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk",
|
||||
"Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta",
|
||||
"Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
|
||||
"Cabaret", "New Wave", "Psychedelic", "Rave", "Showtunes", "Trailer",
|
||||
"Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro",
|
||||
"Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk-Rock",
|
||||
"National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival",
|
||||
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock",
|
||||
"Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band",
|
||||
"Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson",
|
||||
"Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus",
|
||||
"Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
|
||||
"Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
|
||||
"Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
|
||||
"Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
|
||||
"Britpop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
|
||||
"Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
|
||||
"Christian Rock ", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop",
|
||||
"Synthpop",
|
||||
}
|
||||
|
||||
// id3v2Header is a type which represents an ID3v2 tag header.
|
||||
type id3v2Header struct {
|
||||
Version Format
|
||||
Unsynchronisation bool
|
||||
ExtendedHeader bool
|
||||
Experimental bool
|
||||
Size int
|
||||
}
|
||||
|
||||
// readID3v2Header reads the ID3v2 header from the given io.Reader.
|
||||
// offset it number of bytes of header that was read
|
||||
func readID3v2Header(r io.Reader) (h *id3v2Header, offset int, err error) {
|
||||
offset = 10
|
||||
b, err := readBytes(r, offset)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("expected to read 10 bytes (ID3v2Header): %v", err)
|
||||
}
|
||||
|
||||
if string(b[0:3]) != "ID3" {
|
||||
return nil, 0, fmt.Errorf("expected to read \"ID3\"")
|
||||
}
|
||||
|
||||
b = b[3:]
|
||||
var vers Format
|
||||
switch uint(b[0]) {
|
||||
case 2:
|
||||
vers = ID3v2_2
|
||||
case 3:
|
||||
vers = ID3v2_3
|
||||
case 4:
|
||||
vers = ID3v2_4
|
||||
case 0, 1:
|
||||
fallthrough
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("ID3 version: %v, expected: 2, 3 or 4", uint(b[0]))
|
||||
}
|
||||
|
||||
// NB: We ignore b[1] (the revision) as we don't currently rely on it.
|
||||
h = &id3v2Header{
|
||||
Version: vers,
|
||||
Unsynchronisation: getBit(b[2], 7),
|
||||
ExtendedHeader: getBit(b[2], 6),
|
||||
Experimental: getBit(b[2], 5),
|
||||
Size: get7BitChunkedInt(b[3:7]),
|
||||
}
|
||||
|
||||
if h.ExtendedHeader {
|
||||
switch vers {
|
||||
case ID3v2_3:
|
||||
b, err := readBytes(r, 4)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("expected to read 4 bytes (ID3v23 extended header len): %v", err)
|
||||
}
|
||||
// skip header, size is excluding len bytes
|
||||
extendedHeaderSize := getInt(b)
|
||||
_, err = readBytes(r, extendedHeaderSize)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("expected to read %d bytes (ID3v23 skip extended header): %v", extendedHeaderSize, err)
|
||||
}
|
||||
offset += extendedHeaderSize
|
||||
case ID3v2_4:
|
||||
b, err := readBytes(r, 4)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("expected to read 4 bytes (ID3v24 extended header len): %v", err)
|
||||
}
|
||||
// skip header, size is synchsafe int including len bytes
|
||||
extendedHeaderSize := get7BitChunkedInt(b) - 4
|
||||
_, err = readBytes(r, extendedHeaderSize)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("expected to read %d bytes (ID3v24 skip extended header): %v", extendedHeaderSize, err)
|
||||
}
|
||||
offset += extendedHeaderSize
|
||||
default:
|
||||
// nop, only 2.3 and 2.4 should have extended header
|
||||
}
|
||||
}
|
||||
|
||||
return h, offset, nil
|
||||
}
|
||||
|
||||
// id3v2FrameFlags is a type which represents the flags which can be set on an ID3v2 frame.
|
||||
type id3v2FrameFlags struct {
|
||||
// Message (ID3 2.3.0 and 2.4.0)
|
||||
TagAlterPreservation bool
|
||||
FileAlterPreservation bool
|
||||
ReadOnly bool
|
||||
|
||||
// Format (ID3 2.3.0 and 2.4.0)
|
||||
Compression bool
|
||||
Encryption bool
|
||||
GroupIdentity bool
|
||||
// ID3 2.4.0 only (see http://id3.org/id3v2.4.0-structure sec 4.1)
|
||||
Unsynchronisation bool
|
||||
DataLengthIndicator bool
|
||||
}
|
||||
|
||||
func readID3v23FrameFlags(r io.Reader) (*id3v2FrameFlags, error) {
|
||||
b, err := readBytes(r, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := b[0]
|
||||
fmt := b[1]
|
||||
|
||||
return &id3v2FrameFlags{
|
||||
TagAlterPreservation: getBit(msg, 7),
|
||||
FileAlterPreservation: getBit(msg, 6),
|
||||
ReadOnly: getBit(msg, 5),
|
||||
Compression: getBit(fmt, 7),
|
||||
Encryption: getBit(fmt, 6),
|
||||
GroupIdentity: getBit(fmt, 5),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readID3v24FrameFlags(r io.Reader) (*id3v2FrameFlags, error) {
|
||||
b, err := readBytes(r, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := b[0]
|
||||
fmt := b[1]
|
||||
|
||||
return &id3v2FrameFlags{
|
||||
TagAlterPreservation: getBit(msg, 6),
|
||||
FileAlterPreservation: getBit(msg, 5),
|
||||
ReadOnly: getBit(msg, 4),
|
||||
GroupIdentity: getBit(fmt, 6),
|
||||
Compression: getBit(fmt, 3),
|
||||
Encryption: getBit(fmt, 2),
|
||||
Unsynchronisation: getBit(fmt, 1),
|
||||
DataLengthIndicator: getBit(fmt, 0),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func readID3v2_2FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
|
||||
name, err = readString(r, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = readInt(r, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headerSize = 6
|
||||
return
|
||||
}
|
||||
|
||||
func readID3v2_3FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
|
||||
name, err = readString(r, 4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = readInt(r, 4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headerSize = 8
|
||||
return
|
||||
}
|
||||
|
||||
func readID3v2_4FrameHeader(r io.Reader) (name string, size int, headerSize int, err error) {
|
||||
name, err = readString(r, 4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
size, err = read7BitChunkedInt(r, 4)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
headerSize = 8
|
||||
return
|
||||
}
|
||||
|
||||
// readID3v2Frames reads ID3v2 frames from the given reader using the ID3v2Header.
|
||||
func readID3v2Frames(r io.Reader, offset int, h *id3v2Header) (map[string]interface{}, error) {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
for offset < h.Size {
|
||||
var err error
|
||||
var name string
|
||||
var size, headerSize int
|
||||
var flags *id3v2FrameFlags
|
||||
|
||||
switch h.Version {
|
||||
case ID3v2_2:
|
||||
name, size, headerSize, err = readID3v2_2FrameHeader(r)
|
||||
|
||||
case ID3v2_3:
|
||||
name, size, headerSize, err = readID3v2_3FrameHeader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags, err = readID3v23FrameFlags(r)
|
||||
headerSize += 2
|
||||
|
||||
case ID3v2_4:
|
||||
name, size, headerSize, err = readID3v2_4FrameHeader(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags, err = readID3v24FrameFlags(r)
|
||||
headerSize += 2
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// FIXME: Do we still need this?
|
||||
// if size=0, we certainly are in a padding zone. ignore the rest of
|
||||
// the tags
|
||||
if size == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
offset += headerSize + size
|
||||
|
||||
// Avoid corrupted padding (see http://id3.org/Compliance%20Issues).
|
||||
if !validID3Frame(h.Version, name) && offset > h.Size {
|
||||
break
|
||||
}
|
||||
|
||||
if flags != nil {
|
||||
if flags.Compression {
|
||||
_, err = read7BitChunkedInt(r, 4) // read 4
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size -= 4
|
||||
}
|
||||
|
||||
if flags.Encryption {
|
||||
_, err = readBytes(r, 1) // read 1 byte of encryption method
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size -= 1
|
||||
}
|
||||
}
|
||||
|
||||
b, err := readBytes(r, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// There can be multiple tag with the same name. Append a number to the
|
||||
// name if there is more than one.
|
||||
rawName := name
|
||||
if _, ok := result[rawName]; ok {
|
||||
for i := 0; ok; i++ {
|
||||
rawName = name + "_" + strconv.Itoa(i)
|
||||
_, ok = result[rawName]
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case name == "TXXX" || name == "TXX":
|
||||
t, err := readTextWithDescrFrame(b, false, true) // no lang, but enc
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = t
|
||||
|
||||
case name[0] == 'T':
|
||||
txt, err := readTFrame(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = txt
|
||||
|
||||
case name == "UFID" || name == "UFI":
|
||||
t, err := readUFID(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = t
|
||||
|
||||
case name == "WXXX" || name == "WXX":
|
||||
t, err := readTextWithDescrFrame(b, false, false) // no lang, no enc
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = t
|
||||
|
||||
case name[0] == 'W':
|
||||
txt, err := readWFrame(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = txt
|
||||
|
||||
case name == "COMM" || name == "COM" || name == "USLT" || name == "ULT":
|
||||
t, err := readTextWithDescrFrame(b, true, true) // both lang and enc
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = t
|
||||
|
||||
case name == "APIC":
|
||||
p, err := readAPICFrame(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = p
|
||||
|
||||
case name == "PIC":
|
||||
p, err := readPICFrame(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[rawName] = p
|
||||
|
||||
default:
|
||||
result[rawName] = b
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type unsynchroniser struct {
|
||||
io.Reader
|
||||
ff bool
|
||||
}
|
||||
|
||||
// filter io.Reader which skip the Unsynchronisation bytes
|
||||
func (r *unsynchroniser) Read(p []byte) (int, error) {
|
||||
b := make([]byte, 1)
|
||||
i := 0
|
||||
for i < len(p) {
|
||||
if n, err := r.Reader.Read(b); err != nil || n == 0 {
|
||||
return i, err
|
||||
}
|
||||
if r.ff && b[0] == 0x00 {
|
||||
r.ff = false
|
||||
continue
|
||||
}
|
||||
p[i] = b[0]
|
||||
i++
|
||||
r.ff = (b[0] == 0xFF)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// ReadID3v2Tags parses ID3v2.{2,3,4} tags from the io.ReadSeeker into a Metadata, returning
|
||||
// non-nil error on failure.
|
||||
func ReadID3v2Tags(r io.ReadSeeker) (Metadata, error) {
|
||||
h, offset, err := readID3v2Header(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ur io.Reader = r
|
||||
if h.Unsynchronisation {
|
||||
ur = &unsynchroniser{Reader: r}
|
||||
}
|
||||
|
||||
f, err := readID3v2Frames(ur, offset, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return metadataID3v2{header: h, frames: f}, nil
|
||||
}
|
||||
|
||||
var id3v2genreRe = regexp.MustCompile(`(.*[^(]|.* |^)\(([0-9]+)\) *(.*)$`)
|
||||
|
||||
// id3v2genre parse a id3v2 genre tag and expand the numeric genres
|
||||
func id3v2genre(genre string) string {
|
||||
c := true
|
||||
for c {
|
||||
orig := genre
|
||||
if match := id3v2genreRe.FindStringSubmatch(genre); len(match) > 0 {
|
||||
if genreID, err := strconv.Atoi(match[2]); err == nil {
|
||||
if genreID < len(id3v2Genres) {
|
||||
genre = id3v2Genres[genreID]
|
||||
if match[1] != "" {
|
||||
genre = strings.TrimSpace(match[1]) + " " + genre
|
||||
}
|
||||
if match[3] != "" {
|
||||
genre = genre + " " + match[3]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c = (orig != genre)
|
||||
}
|
||||
return strings.Replace(genre, "((", "(", -1)
|
||||
}
|
||||
638
vendor/github.com/dhowden/tag/id3v2frames.go
generated
vendored
638
vendor/github.com/dhowden/tag/id3v2frames.go
generated
vendored
@@ -1,638 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// DefaultUTF16WithBOMByteOrder is the byte order used when the "UTF16 with BOM" encoding
|
||||
// is specified without a corresponding BOM in the data.
|
||||
var DefaultUTF16WithBOMByteOrder binary.ByteOrder = binary.LittleEndian
|
||||
|
||||
// ID3v2.2.0 frames (see http://id3.org/id3v2-00, sec 4).
|
||||
var id3v22Frames = map[string]string{
|
||||
"BUF": "Recommended buffer size",
|
||||
|
||||
"CNT": "Play counter",
|
||||
"COM": "Comments",
|
||||
"CRA": "Audio encryption",
|
||||
"CRM": "Encrypted meta frame",
|
||||
|
||||
"ETC": "Event timing codes",
|
||||
"EQU": "Equalization",
|
||||
|
||||
"GEO": "General encapsulated object",
|
||||
|
||||
"IPL": "Involved people list",
|
||||
|
||||
"LNK": "Linked information",
|
||||
|
||||
"MCI": "Music CD Identifier",
|
||||
"MLL": "MPEG location lookup table",
|
||||
|
||||
"PIC": "Attached picture",
|
||||
"POP": "Popularimeter",
|
||||
|
||||
"REV": "Reverb",
|
||||
"RVA": "Relative volume adjustment",
|
||||
|
||||
"SLT": "Synchronized lyric/text",
|
||||
"STC": "Synced tempo codes",
|
||||
|
||||
"TAL": "Album/Movie/Show title",
|
||||
"TBP": "BPM (Beats Per Minute)",
|
||||
"TCM": "Composer",
|
||||
"TCO": "Content type",
|
||||
"TCR": "Copyright message",
|
||||
"TDA": "Date",
|
||||
"TDY": "Playlist delay",
|
||||
"TEN": "Encoded by",
|
||||
"TFT": "File type",
|
||||
"TIM": "Time",
|
||||
"TKE": "Initial key",
|
||||
"TLA": "Language(s)",
|
||||
"TLE": "Length",
|
||||
"TMT": "Media type",
|
||||
"TOA": "Original artist(s)/performer(s)",
|
||||
"TOF": "Original filename",
|
||||
"TOL": "Original Lyricist(s)/text writer(s)",
|
||||
"TOR": "Original release year",
|
||||
"TOT": "Original album/Movie/Show title",
|
||||
"TP1": "Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",
|
||||
"TP2": "Band/Orchestra/Accompaniment",
|
||||
"TP3": "Conductor/Performer refinement",
|
||||
"TP4": "Interpreted, remixed, or otherwise modified by",
|
||||
"TPA": "Part of a set",
|
||||
"TPB": "Publisher",
|
||||
"TRC": "ISRC (International Standard Recording Code)",
|
||||
"TRD": "Recording dates",
|
||||
"TRK": "Track number/Position in set",
|
||||
"TSI": "Size",
|
||||
"TSS": "Software/hardware and settings used for encoding",
|
||||
"TT1": "Content group description",
|
||||
"TT2": "Title/Songname/Content description",
|
||||
"TT3": "Subtitle/Description refinement",
|
||||
"TXT": "Lyricist/text writer",
|
||||
"TXX": "User defined text information frame",
|
||||
"TYE": "Year",
|
||||
|
||||
"UFI": "Unique file identifier",
|
||||
"ULT": "Unsychronized lyric/text transcription",
|
||||
|
||||
"WAF": "Official audio file webpage",
|
||||
"WAR": "Official artist/performer webpage",
|
||||
"WAS": "Official audio source webpage",
|
||||
"WCM": "Commercial information",
|
||||
"WCP": "Copyright/Legal information",
|
||||
"WPB": "Publishers official webpage",
|
||||
"WXX": "User defined URL link frame",
|
||||
}
|
||||
|
||||
// ID3v2.3.0 frames (see http://id3.org/id3v2.3.0#Declared_ID3v2_frames).
|
||||
var id3v23Frames = map[string]string{
|
||||
"AENC": "Audio encryption]",
|
||||
"APIC": "Attached picture",
|
||||
"COMM": "Comments",
|
||||
"COMR": "Commercial frame",
|
||||
"ENCR": "Encryption method registration",
|
||||
"EQUA": "Equalization",
|
||||
"ETCO": "Event timing codes",
|
||||
"GEOB": "General encapsulated object",
|
||||
"GRID": "Group identification registration",
|
||||
"IPLS": "Involved people list",
|
||||
"LINK": "Linked information",
|
||||
"MCDI": "Music CD identifier",
|
||||
"MLLT": "MPEG location lookup table",
|
||||
"OWNE": "Ownership frame",
|
||||
"PRIV": "Private frame",
|
||||
"PCNT": "Play counter",
|
||||
"POPM": "Popularimeter",
|
||||
"POSS": "Position synchronisation frame",
|
||||
"RBUF": "Recommended buffer size",
|
||||
"RVAD": "Relative volume adjustment",
|
||||
"RVRB": "Reverb",
|
||||
"SYLT": "Synchronized lyric/text",
|
||||
"SYTC": "Synchronized tempo codes",
|
||||
"TALB": "Album/Movie/Show title",
|
||||
"TBPM": "BPM (beats per minute)",
|
||||
"TCMP": "iTunes Compilation Flag",
|
||||
"TCOM": "Composer",
|
||||
"TCON": "Content type",
|
||||
"TCOP": "Copyright message",
|
||||
"TDAT": "Date",
|
||||
"TDLY": "Playlist delay",
|
||||
"TENC": "Encoded by",
|
||||
"TEXT": "Lyricist/Text writer",
|
||||
"TFLT": "File type",
|
||||
"TIME": "Time",
|
||||
"TIT1": "Content group description",
|
||||
"TIT2": "Title/songname/content description",
|
||||
"TIT3": "Subtitle/Description refinement",
|
||||
"TKEY": "Initial key",
|
||||
"TLAN": "Language(s)",
|
||||
"TLEN": "Length",
|
||||
"TMED": "Media type",
|
||||
"TOAL": "Original album/movie/show title",
|
||||
"TOFN": "Original filename",
|
||||
"TOLY": "Original lyricist(s)/text writer(s)",
|
||||
"TOPE": "Original artist(s)/performer(s)",
|
||||
"TORY": "Original release year",
|
||||
"TOWN": "File owner/licensee",
|
||||
"TPE1": "Lead performer(s)/Soloist(s)",
|
||||
"TPE2": "Band/orchestra/accompaniment",
|
||||
"TPE3": "Conductor/performer refinement",
|
||||
"TPE4": "Interpreted, remixed, or otherwise modified by",
|
||||
"TPOS": "Part of a set",
|
||||
"TPUB": "Publisher",
|
||||
"TRCK": "Track number/Position in set",
|
||||
"TRDA": "Recording dates",
|
||||
"TRSN": "Internet radio station name",
|
||||
"TRSO": "Internet radio station owner",
|
||||
"TSIZ": "Size",
|
||||
"TSO2": "iTunes uses this for Album Artist sort order",
|
||||
"TSOC": "iTunes uses this for Composer sort order",
|
||||
"TSRC": "ISRC (international standard recording code)",
|
||||
"TSSE": "Software/Hardware and settings used for encoding",
|
||||
"TYER": "Year",
|
||||
"TXXX": "User defined text information frame",
|
||||
"UFID": "Unique file identifier",
|
||||
"USER": "Terms of use",
|
||||
"USLT": "Unsychronized lyric/text transcription",
|
||||
"WCOM": "Commercial information",
|
||||
"WCOP": "Copyright/Legal information",
|
||||
"WOAF": "Official audio file webpage",
|
||||
"WOAR": "Official artist/performer webpage",
|
||||
"WOAS": "Official audio source webpage",
|
||||
"WORS": "Official internet radio station homepage",
|
||||
"WPAY": "Payment",
|
||||
"WPUB": "Publishers official webpage",
|
||||
"WXXX": "User defined URL link frame",
|
||||
}
|
||||
|
||||
// ID3v2.4.0 frames (see http://id3.org/id3v2.4.0-frames, sec 4).
|
||||
var id3v24Frames = map[string]string{
|
||||
"AENC": "Audio encryption",
|
||||
"APIC": "Attached picture",
|
||||
"ASPI": "Audio seek point index",
|
||||
|
||||
"COMM": "Comments",
|
||||
"COMR": "Commercial frame",
|
||||
|
||||
"ENCR": "Encryption method registration",
|
||||
"EQU2": "Equalisation (2)",
|
||||
"ETCO": "Event timing codes",
|
||||
|
||||
"GEOB": "General encapsulated object",
|
||||
"GRID": "Group identification registration",
|
||||
|
||||
"LINK": "Linked information",
|
||||
|
||||
"MCDI": "Music CD identifier",
|
||||
"MLLT": "MPEG location lookup table",
|
||||
|
||||
"OWNE": "Ownership frame",
|
||||
|
||||
"PRIV": "Private frame",
|
||||
"PCNT": "Play counter",
|
||||
"POPM": "Popularimeter",
|
||||
"POSS": "Position synchronisation frame",
|
||||
|
||||
"RBUF": "Recommended buffer size",
|
||||
"RVA2": "Relative volume adjustment (2)",
|
||||
"RVRB": "Reverb",
|
||||
|
||||
"SEEK": "Seek frame",
|
||||
"SIGN": "Signature frame",
|
||||
"SYLT": "Synchronised lyric/text",
|
||||
"SYTC": "Synchronised tempo codes",
|
||||
|
||||
"TALB": "Album/Movie/Show title",
|
||||
"TBPM": "BPM (beats per minute)",
|
||||
"TCMP": "iTunes Compilation Flag",
|
||||
"TCOM": "Composer",
|
||||
"TCON": "Content type",
|
||||
"TCOP": "Copyright message",
|
||||
"TDEN": "Encoding time",
|
||||
"TDLY": "Playlist delay",
|
||||
"TDOR": "Original release time",
|
||||
"TDRC": "Recording time",
|
||||
"TDRL": "Release time",
|
||||
"TDTG": "Tagging time",
|
||||
"TENC": "Encoded by",
|
||||
"TEXT": "Lyricist/Text writer",
|
||||
"TFLT": "File type",
|
||||
"TIPL": "Involved people list",
|
||||
"TIT1": "Content group description",
|
||||
"TIT2": "Title/songname/content description",
|
||||
"TIT3": "Subtitle/Description refinement",
|
||||
"TKEY": "Initial key",
|
||||
"TLAN": "Language(s)",
|
||||
"TLEN": "Length",
|
||||
"TMCL": "Musician credits list",
|
||||
"TMED": "Media type",
|
||||
"TMOO": "Mood",
|
||||
"TOAL": "Original album/movie/show title",
|
||||
"TOFN": "Original filename",
|
||||
"TOLY": "Original lyricist(s)/text writer(s)",
|
||||
"TOPE": "Original artist(s)/performer(s)",
|
||||
"TOWN": "File owner/licensee",
|
||||
"TPE1": "Lead performer(s)/Soloist(s)",
|
||||
"TPE2": "Band/orchestra/accompaniment",
|
||||
"TPE3": "Conductor/performer refinement",
|
||||
"TPE4": "Interpreted, remixed, or otherwise modified by",
|
||||
"TPOS": "Part of a set",
|
||||
"TPRO": "Produced notice",
|
||||
"TPUB": "Publisher",
|
||||
"TRCK": "Track number/Position in set",
|
||||
"TRSN": "Internet radio station name",
|
||||
"TRSO": "Internet radio station owner",
|
||||
"TSO2": "iTunes uses this for Album Artist sort order",
|
||||
"TSOA": "Album sort order",
|
||||
"TSOC": "iTunes uses this for Composer sort order",
|
||||
"TSOP": "Performer sort order",
|
||||
"TSOT": "Title sort order",
|
||||
"TSRC": "ISRC (international standard recording code)",
|
||||
"TSSE": "Software/Hardware and settings used for encoding",
|
||||
"TSST": "Set subtitle",
|
||||
"TXXX": "User defined text information frame",
|
||||
|
||||
"UFID": "Unique file identifier",
|
||||
"USER": "Terms of use",
|
||||
"USLT": "Unsynchronised lyric/text transcription",
|
||||
|
||||
"WCOM": "Commercial information",
|
||||
"WCOP": "Copyright/Legal information",
|
||||
"WOAF": "Official audio file webpage",
|
||||
"WOAR": "Official artist/performer webpage",
|
||||
"WOAS": "Official audio source webpage",
|
||||
"WORS": "Official Internet radio station homepage",
|
||||
"WPAY": "Payment",
|
||||
"WPUB": "Publishers official webpage",
|
||||
"WXXX": "User defined URL link frame",
|
||||
}
|
||||
|
||||
// ID3 frames that are defined in the specs.
|
||||
var id3Frames = map[Format]map[string]string{
|
||||
ID3v2_2: id3v22Frames,
|
||||
ID3v2_3: id3v23Frames,
|
||||
ID3v2_4: id3v24Frames,
|
||||
}
|
||||
|
||||
func validID3Frame(version Format, name string) bool {
|
||||
names, ok := id3Frames[version]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = names[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func readWFrame(b []byte) (string, error) {
|
||||
// Frame text is always encoded in ISO-8859-1
|
||||
b = append([]byte{0}, b...)
|
||||
return readTFrame(b)
|
||||
}
|
||||
|
||||
func readTFrame(b []byte) (string, error) {
|
||||
if len(b) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
txt, err := decodeText(b[0], b[1:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Join(strings.Split(txt, string(singleZero)), ""), nil
|
||||
}
|
||||
|
||||
const (
|
||||
encodingISO8859 byte = 0
|
||||
encodingUTF16WithBOM byte = 1
|
||||
encodingUTF16 byte = 2
|
||||
encodingUTF8 byte = 3
|
||||
)
|
||||
|
||||
func decodeText(enc byte, b []byte) (string, error) {
|
||||
if len(b) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
switch enc {
|
||||
case encodingISO8859: // ISO-8859-1
|
||||
return decodeISO8859(b), nil
|
||||
|
||||
case encodingUTF16WithBOM: // UTF-16 with byte order marker
|
||||
if len(b) == 1 {
|
||||
return "", nil
|
||||
}
|
||||
return decodeUTF16WithBOM(b)
|
||||
|
||||
case encodingUTF16: // UTF-16 without byte order (assuming BigEndian)
|
||||
if len(b) == 1 {
|
||||
return "", nil
|
||||
}
|
||||
return decodeUTF16(b, binary.BigEndian)
|
||||
|
||||
case encodingUTF8: // UTF-8
|
||||
return string(b), nil
|
||||
|
||||
default: // Fallback to ISO-8859-1
|
||||
return decodeISO8859(b), nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
singleZero = []byte{0}
|
||||
doubleZero = []byte{0, 0}
|
||||
)
|
||||
|
||||
func dataSplit(b []byte, enc byte) [][]byte {
|
||||
delim := singleZero
|
||||
if enc == encodingUTF16 || enc == encodingUTF16WithBOM {
|
||||
delim = doubleZero
|
||||
}
|
||||
|
||||
result := bytes.SplitN(b, delim, 2)
|
||||
if len(result) != 2 {
|
||||
return result
|
||||
}
|
||||
|
||||
if len(result[1]) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
if result[1][0] == 0 {
|
||||
// there was a double (or triple) 0 and we cut too early
|
||||
result[0] = append(result[0], result[1][0])
|
||||
result[1] = result[1][1:]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func decodeISO8859(b []byte) string {
|
||||
r := make([]rune, len(b))
|
||||
for i, x := range b {
|
||||
r[i] = rune(x)
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func decodeUTF16WithBOM(b []byte) (string, error) {
|
||||
if len(b) < 2 {
|
||||
return "", errors.New("invalid encoding: expected at least 2 bytes for UTF-16 byte order mark")
|
||||
}
|
||||
|
||||
var bo binary.ByteOrder
|
||||
switch {
|
||||
case b[0] == 0xFE && b[1] == 0xFF:
|
||||
bo = binary.BigEndian
|
||||
b = b[2:]
|
||||
|
||||
case b[0] == 0xFF && b[1] == 0xFE:
|
||||
bo = binary.LittleEndian
|
||||
b = b[2:]
|
||||
|
||||
default:
|
||||
bo = DefaultUTF16WithBOMByteOrder
|
||||
}
|
||||
return decodeUTF16(b, bo)
|
||||
}
|
||||
|
||||
func decodeUTF16(b []byte, bo binary.ByteOrder) (string, error) {
|
||||
if len(b)%2 != 0 {
|
||||
return "", errors.New("invalid encoding: expected even number of bytes for UTF-16 encoded text")
|
||||
}
|
||||
s := make([]uint16, 0, len(b)/2)
|
||||
for i := 0; i < len(b); i += 2 {
|
||||
s = append(s, bo.Uint16(b[i:i+2]))
|
||||
}
|
||||
return string(utf16.Decode(s)), nil
|
||||
}
|
||||
|
||||
// Comm is a type used in COMM, UFID, TXXX, WXXX and USLT tag.
|
||||
// It's a text with a description and a specified language
|
||||
// For WXXX, TXXX and UFID, we don't set a Language
|
||||
type Comm struct {
|
||||
Language string
|
||||
Description string
|
||||
Text string
|
||||
}
|
||||
|
||||
// String returns a string representation of the underlying Comm instance.
|
||||
func (t Comm) String() string {
|
||||
if t.Language != "" {
|
||||
return fmt.Sprintf("Text{Lang: '%v', Description: '%v', %v lines}",
|
||||
t.Language, t.Description, strings.Count(t.Text, "\n"))
|
||||
}
|
||||
return fmt.Sprintf("Text{Description: '%v', %v}", t.Description, t.Text)
|
||||
}
|
||||
|
||||
// IDv2.{3,4}
|
||||
// -- Header
|
||||
// <Header for 'Unsynchronised lyrics/text transcription', ID: "USLT">
|
||||
// <Header for 'Comment', ID: "COMM">
|
||||
// -- readTextWithDescrFrame(data, true, true)
|
||||
// Text encoding $xx
|
||||
// Language $xx xx xx
|
||||
// Content descriptor <text string according to encoding> $00 (00)
|
||||
// Lyrics/text <full text string according to encoding>
|
||||
// -- Header
|
||||
// <Header for 'User defined text information frame', ID: "TXXX">
|
||||
// <Header for 'User defined URL link frame', ID: "WXXX">
|
||||
// -- readTextWithDescrFrame(data, false, <isDataEncoded>)
|
||||
// Text encoding $xx
|
||||
// Description <text string according to encoding> $00 (00)
|
||||
// Value <text string according to encoding>
|
||||
func readTextWithDescrFrame(b []byte, hasLang bool, encoded bool) (*Comm, error) {
|
||||
enc := b[0]
|
||||
b = b[1:]
|
||||
|
||||
c := &Comm{}
|
||||
if hasLang {
|
||||
c.Language = string(b[:3])
|
||||
b = b[3:]
|
||||
}
|
||||
|
||||
descTextSplit := dataSplit(b, enc)
|
||||
if len(descTextSplit) < 1 {
|
||||
return nil, fmt.Errorf("error decoding tag description text: invalid encoding")
|
||||
}
|
||||
|
||||
desc, err := decodeText(enc, descTextSplit[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding tag description text: %v", err)
|
||||
}
|
||||
c.Description = desc
|
||||
|
||||
if len(descTextSplit) == 1 {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
if !encoded {
|
||||
enc = byte(0)
|
||||
}
|
||||
text, err := decodeText(enc, descTextSplit[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding tag text: %v", err)
|
||||
}
|
||||
c.Text = text
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// UFID is composed of a provider (frequently a URL and a binary identifier)
|
||||
// The identifier can be a text (Musicbrainz use texts, but not necessary)
|
||||
type UFID struct {
|
||||
Provider string
|
||||
Identifier []byte
|
||||
}
|
||||
|
||||
func (u UFID) String() string {
|
||||
return fmt.Sprintf("%v (%v)", u.Provider, string(u.Identifier))
|
||||
}
|
||||
|
||||
func readUFID(b []byte) (*UFID, error) {
|
||||
result := bytes.SplitN(b, singleZero, 2)
|
||||
if len(result) != 2 {
|
||||
return nil, errors.New("expected to split UFID data into 2 pieces")
|
||||
}
|
||||
|
||||
return &UFID{
|
||||
Provider: string(result[0]),
|
||||
Identifier: result[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
var pictureTypes = map[byte]string{
|
||||
0x00: "Other",
|
||||
0x01: "32x32 pixels 'file icon' (PNG only)",
|
||||
0x02: "Other file icon",
|
||||
0x03: "Cover (front)",
|
||||
0x04: "Cover (back)",
|
||||
0x05: "Leaflet page",
|
||||
0x06: "Media (e.g. lable side of CD)",
|
||||
0x07: "Lead artist/lead performer/soloist",
|
||||
0x08: "Artist/performer",
|
||||
0x09: "Conductor",
|
||||
0x0A: "Band/Orchestra",
|
||||
0x0B: "Composer",
|
||||
0x0C: "Lyricist/text writer",
|
||||
0x0D: "Recording Location",
|
||||
0x0E: "During recording",
|
||||
0x0F: "During performance",
|
||||
0x10: "Movie/video screen capture",
|
||||
0x11: "A bright coloured fish",
|
||||
0x12: "Illustration",
|
||||
0x13: "Band/artist logotype",
|
||||
0x14: "Publisher/Studio logotype",
|
||||
}
|
||||
|
||||
// Picture is a type which represents an attached picture extracted from metadata.
|
||||
type Picture struct {
|
||||
Ext string // Extension of the picture file.
|
||||
MIMEType string // MIMEType of the picture.
|
||||
Type string // Type of the picture (see pictureTypes).
|
||||
Description string // Description.
|
||||
Data []byte // Raw picture data.
|
||||
}
|
||||
|
||||
// String returns a string representation of the underlying Picture instance.
|
||||
func (p Picture) String() string {
|
||||
return fmt.Sprintf("Picture{Ext: %v, MIMEType: %v, Type: %v, Description: %v, Data.Size: %v}",
|
||||
p.Ext, p.MIMEType, p.Type, p.Description, len(p.Data))
|
||||
}
|
||||
|
||||
// IDv2.2
|
||||
// -- Header
|
||||
// Attached picture "PIC"
|
||||
// Frame size $xx xx xx
|
||||
// -- readPICFrame
|
||||
// Text encoding $xx
|
||||
// Image format $xx xx xx
|
||||
// Picture type $xx
|
||||
// Description <textstring> $00 (00)
|
||||
// Picture data <binary data>
|
||||
func readPICFrame(b []byte) (*Picture, error) {
|
||||
enc := b[0]
|
||||
ext := string(b[1:4])
|
||||
picType := b[4]
|
||||
|
||||
descDataSplit := dataSplit(b[5:], enc)
|
||||
if len(descDataSplit) != 2 {
|
||||
return nil, errors.New("error decoding PIC description text: invalid encoding")
|
||||
}
|
||||
desc, err := decodeText(enc, descDataSplit[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding PIC description text: %v", err)
|
||||
}
|
||||
|
||||
var mimeType string
|
||||
switch ext {
|
||||
case "jpeg", "jpg":
|
||||
mimeType = "image/jpeg"
|
||||
case "png":
|
||||
mimeType = "image/png"
|
||||
}
|
||||
|
||||
return &Picture{
|
||||
Ext: ext,
|
||||
MIMEType: mimeType,
|
||||
Type: pictureTypes[picType],
|
||||
Description: desc,
|
||||
Data: descDataSplit[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IDv2.{3,4}
|
||||
// -- Header
|
||||
// <Header for 'Attached picture', ID: "APIC">
|
||||
// -- readAPICFrame
|
||||
// Text encoding $xx
|
||||
// MIME type <text string> $00
|
||||
// Picture type $xx
|
||||
// Description <text string according to encoding> $00 (00)
|
||||
// Picture data <binary data>
|
||||
func readAPICFrame(b []byte) (*Picture, error) {
|
||||
enc := b[0]
|
||||
mimeDataSplit := bytes.SplitN(b[1:], singleZero, 2)
|
||||
mimeType := string(mimeDataSplit[0])
|
||||
|
||||
b = mimeDataSplit[1]
|
||||
if len(b) < 1 {
|
||||
return nil, fmt.Errorf("error decoding APIC mimetype")
|
||||
}
|
||||
picType := b[0]
|
||||
|
||||
descDataSplit := dataSplit(b[1:], enc)
|
||||
if len(descDataSplit) != 2 {
|
||||
return nil, errors.New("error decoding APIC description text: invalid encoding")
|
||||
}
|
||||
desc, err := decodeText(enc, descDataSplit[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding APIC description text: %v", err)
|
||||
}
|
||||
|
||||
var ext string
|
||||
switch mimeType {
|
||||
case "image/jpeg":
|
||||
ext = "jpg"
|
||||
case "image/png":
|
||||
ext = "png"
|
||||
}
|
||||
|
||||
return &Picture{
|
||||
Ext: ext,
|
||||
MIMEType: mimeType,
|
||||
Type: pictureTypes[picType],
|
||||
Description: desc,
|
||||
Data: descDataSplit[1],
|
||||
}, nil
|
||||
}
|
||||
141
vendor/github.com/dhowden/tag/id3v2metadata.go
generated
vendored
141
vendor/github.com/dhowden/tag/id3v2metadata.go
generated
vendored
@@ -1,141 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type frameNames map[string][2]string
|
||||
|
||||
func (f frameNames) Name(s string, fm Format) string {
|
||||
l, ok := f[s]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch fm {
|
||||
case ID3v2_2:
|
||||
return l[0]
|
||||
case ID3v2_3:
|
||||
return l[1]
|
||||
case ID3v2_4:
|
||||
if s == "year" {
|
||||
return "TDRC"
|
||||
}
|
||||
return l[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var frames = frameNames(map[string][2]string{
|
||||
"title": [2]string{"TT2", "TIT2"},
|
||||
"artist": [2]string{"TP1", "TPE1"},
|
||||
"album": [2]string{"TAL", "TALB"},
|
||||
"album_artist": [2]string{"TP2", "TPE2"},
|
||||
"composer": [2]string{"TCM", "TCOM"},
|
||||
"year": [2]string{"TYE", "TYER"},
|
||||
"track": [2]string{"TRK", "TRCK"},
|
||||
"disc": [2]string{"TPA", "TPOS"},
|
||||
"genre": [2]string{"TCO", "TCON"},
|
||||
"picture": [2]string{"PIC", "APIC"},
|
||||
"lyrics": [2]string{"", "USLT"},
|
||||
"comment": [2]string{"COM", "COMM"},
|
||||
})
|
||||
|
||||
// metadataID3v2 is the implementation of Metadata used for ID3v2 tags.
|
||||
type metadataID3v2 struct {
|
||||
header *id3v2Header
|
||||
frames map[string]interface{}
|
||||
}
|
||||
|
||||
func (m metadataID3v2) getString(k string) string {
|
||||
v, ok := m.frames[k]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Format() Format { return m.header.Version }
|
||||
func (m metadataID3v2) FileType() FileType { return MP3 }
|
||||
func (m metadataID3v2) Raw() map[string]interface{} { return m.frames }
|
||||
|
||||
func (m metadataID3v2) Title() string {
|
||||
return m.getString(frames.Name("title", m.Format()))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Artist() string {
|
||||
return m.getString(frames.Name("artist", m.Format()))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Album() string {
|
||||
return m.getString(frames.Name("album", m.Format()))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) AlbumArtist() string {
|
||||
return m.getString(frames.Name("album_artist", m.Format()))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Composer() string {
|
||||
return m.getString(frames.Name("composer", m.Format()))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Genre() string {
|
||||
return id3v2genre(m.getString(frames.Name("genre", m.Format())))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Year() int {
|
||||
year, _ := strconv.Atoi(m.getString(frames.Name("year", m.Format())))
|
||||
return year
|
||||
}
|
||||
|
||||
func parseXofN(s string) (x, n int) {
|
||||
xn := strings.Split(s, "/")
|
||||
if len(xn) != 2 {
|
||||
x, _ = strconv.Atoi(s)
|
||||
return x, 0
|
||||
}
|
||||
x, _ = strconv.Atoi(strings.TrimSpace(xn[0]))
|
||||
n, _ = strconv.Atoi(strings.TrimSpace(xn[1]))
|
||||
return x, n
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Track() (int, int) {
|
||||
return parseXofN(m.getString(frames.Name("track", m.Format())))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Disc() (int, int) {
|
||||
return parseXofN(m.getString(frames.Name("disc", m.Format())))
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Lyrics() string {
|
||||
t, ok := m.frames[frames.Name("lyrics", m.Format())]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return t.(*Comm).Text
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Comment() string {
|
||||
t, ok := m.frames[frames.Name("comment", m.Format())]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
// id3v23 has Text, id3v24 has Description
|
||||
if t.(*Comm).Description == "" {
|
||||
return trimString(t.(*Comm).Text)
|
||||
}
|
||||
return trimString(t.(*Comm).Description)
|
||||
}
|
||||
|
||||
func (m metadataID3v2) Picture() *Picture {
|
||||
v, ok := m.frames[frames.Name("picture", m.Format())]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return v.(*Picture)
|
||||
}
|
||||
362
vendor/github.com/dhowden/tag/mp4.go
generated
vendored
362
vendor/github.com/dhowden/tag/mp4.go
generated
vendored
@@ -1,362 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var atomTypes = map[int]string{
|
||||
0: "implicit", // automatic based on atom name
|
||||
1: "text",
|
||||
13: "jpeg",
|
||||
14: "png",
|
||||
21: "uint8",
|
||||
}
|
||||
|
||||
// NB: atoms does not include "----", this is handled separately
|
||||
var atoms = atomNames(map[string]string{
|
||||
"\xa9alb": "album",
|
||||
"\xa9art": "artist",
|
||||
"\xa9ART": "artist",
|
||||
"aART": "album_artist",
|
||||
"\xa9day": "year",
|
||||
"\xa9nam": "title",
|
||||
"\xa9gen": "genre",
|
||||
"trkn": "track",
|
||||
"\xa9wrt": "composer",
|
||||
"\xa9too": "encoder",
|
||||
"cprt": "copyright",
|
||||
"covr": "picture",
|
||||
"\xa9grp": "grouping",
|
||||
"keyw": "keyword",
|
||||
"\xa9lyr": "lyrics",
|
||||
"\xa9cmt": "comment",
|
||||
"tmpo": "tempo",
|
||||
"cpil": "compilation",
|
||||
"disk": "disc",
|
||||
})
|
||||
|
||||
// Detect PNG image if "implicit" class is used
|
||||
var pngHeader = []byte{137, 80, 78, 71, 13, 10, 26, 10}
|
||||
|
||||
type atomNames map[string]string
|
||||
|
||||
func (f atomNames) Name(n string) []string {
|
||||
res := make([]string, 1)
|
||||
for k, v := range f {
|
||||
if v == n {
|
||||
res = append(res, k)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// metadataMP4 is the implementation of Metadata for MP4 tag (atom) data.
|
||||
type metadataMP4 struct {
|
||||
fileType FileType
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
// ReadAtoms reads MP4 metadata atoms from the io.ReadSeeker into a Metadata, returning
|
||||
// non-nil error if there was a problem.
|
||||
func ReadAtoms(r io.ReadSeeker) (Metadata, error) {
|
||||
m := metadataMP4{
|
||||
data: make(map[string]interface{}),
|
||||
fileType: UnknownFileType,
|
||||
}
|
||||
err := m.readAtoms(r)
|
||||
return m, err
|
||||
}
|
||||
|
||||
func (m metadataMP4) readAtoms(r io.ReadSeeker) error {
|
||||
for {
|
||||
name, size, err := readAtomHeader(r)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "meta":
|
||||
// next_item_id (int32)
|
||||
_, err := readBytes(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case "moov", "udta", "ilst":
|
||||
return m.readAtoms(r)
|
||||
}
|
||||
|
||||
_, ok := atoms[name]
|
||||
if name == "----" {
|
||||
name, size, err = readCustomAtom(r, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if name != "----" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
_, err := r.Seek(int64(size-8), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = m.readAtomData(r, name, size-8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m metadataMP4) readAtomData(r io.ReadSeeker, name string, size uint32) error {
|
||||
b, err := readBytes(r, int(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(b) < 8 {
|
||||
return fmt.Errorf("invalid encoding: expected at least %d bytes, got %d", 8, len(b))
|
||||
}
|
||||
|
||||
// "data" + size (4 bytes each)
|
||||
b = b[8:]
|
||||
|
||||
if len(b) < 3 {
|
||||
return fmt.Errorf("invalid encoding: expected at least %d bytes, for class, got %d", 3, len(b))
|
||||
}
|
||||
class := getInt(b[1:4])
|
||||
contentType, ok := atomTypes[class]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid content type: %v (%x) (%x)", class, b[1:4], b)
|
||||
}
|
||||
|
||||
// 4: atom version (1 byte) + atom flags (3 bytes)
|
||||
// 4: NULL (usually locale indicator)
|
||||
if len(b) < 8 {
|
||||
return fmt.Errorf("invalid encoding: expected at least %d bytes, for atom version and flags, got %d", 8, len(b))
|
||||
}
|
||||
b = b[8:]
|
||||
|
||||
if name == "trkn" || name == "disk" {
|
||||
if len(b) < 6 {
|
||||
return fmt.Errorf("invalid encoding: expected at least %d bytes, for track and disk numbers, got %d", 6, len(b))
|
||||
}
|
||||
|
||||
m.data[name] = int(b[3])
|
||||
m.data[name+"_count"] = int(b[5])
|
||||
return nil
|
||||
}
|
||||
|
||||
if contentType == "implicit" {
|
||||
if name == "covr" {
|
||||
if bytes.HasPrefix(b, pngHeader) {
|
||||
contentType = "png"
|
||||
}
|
||||
// TODO(dhowden): Detect JPEG formats too (harder).
|
||||
}
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
switch contentType {
|
||||
case "implicit":
|
||||
if _, ok := atoms[name]; ok {
|
||||
return fmt.Errorf("unhandled implicit content type for required atom: %q", name)
|
||||
}
|
||||
return nil
|
||||
|
||||
case "text":
|
||||
data = string(b)
|
||||
|
||||
case "uint8":
|
||||
if len(b) < 1 {
|
||||
return fmt.Errorf("invalid encoding: expected at least %d bytes, for integer tag data, got %d", 1, len(b))
|
||||
}
|
||||
data = getInt(b[:1])
|
||||
|
||||
case "jpeg", "png":
|
||||
data = &Picture{
|
||||
Ext: contentType,
|
||||
MIMEType: "image/" + contentType,
|
||||
Data: b,
|
||||
}
|
||||
}
|
||||
m.data[name] = data
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAtomHeader(r io.ReadSeeker) (name string, size uint32, err error) {
|
||||
err = binary.Read(r, binary.BigEndian, &size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name, err = readString(r, 4)
|
||||
return
|
||||
}
|
||||
|
||||
// Generic atom.
|
||||
// Should have 3 sub atoms : mean, name and data.
|
||||
// We check that mean is "com.apple.iTunes" and we use the subname as
|
||||
// the name, and move to the data atom.
|
||||
// If anything goes wrong, we jump at the end of the "----" atom.
|
||||
func readCustomAtom(r io.ReadSeeker, size uint32) (string, uint32, error) {
|
||||
subNames := make(map[string]string)
|
||||
var dataSize uint32
|
||||
|
||||
for size > 8 {
|
||||
subName, subSize, err := readAtomHeader(r)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
// Remove the size of the atom from the size counter
|
||||
size -= subSize
|
||||
|
||||
switch subName {
|
||||
case "mean", "name":
|
||||
b, err := readBytes(r, int(subSize-8))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
if len(b) < 4 {
|
||||
return "", 0, fmt.Errorf("invalid encoding: expected at least %d bytes, got %d", 4, len(b))
|
||||
}
|
||||
subNames[subName] = string(b[4:])
|
||||
|
||||
case "data":
|
||||
// Found the "data" atom, rewind
|
||||
dataSize = subSize + 8 // will need to re-read "data" + size (4 + 4)
|
||||
_, err := r.Seek(-8, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there should remain only the header size
|
||||
if size != 8 {
|
||||
err := errors.New("---- atom out of bounds")
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
if subNames["mean"] != "com.apple.iTunes" || subNames["name"] == "" || dataSize == 0 {
|
||||
return "----", 0, nil
|
||||
}
|
||||
return subNames["name"], dataSize, nil
|
||||
}
|
||||
|
||||
func (metadataMP4) Format() Format { return MP4 }
|
||||
func (m metadataMP4) FileType() FileType { return m.fileType }
|
||||
|
||||
func (m metadataMP4) Raw() map[string]interface{} { return m.data }
|
||||
|
||||
func (m metadataMP4) getString(n []string) string {
|
||||
for _, k := range n {
|
||||
if x, ok := m.data[k]; ok {
|
||||
return x.(string)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m metadataMP4) getInt(n []string) int {
|
||||
for _, k := range n {
|
||||
if x, ok := m.data[k]; ok {
|
||||
return x.(int)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Title() string {
|
||||
return m.getString(atoms.Name("title"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Artist() string {
|
||||
return m.getString(atoms.Name("artist"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Album() string {
|
||||
return m.getString(atoms.Name("album"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) AlbumArtist() string {
|
||||
return m.getString(atoms.Name("album_artist"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Composer() string {
|
||||
return m.getString(atoms.Name("composer"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Genre() string {
|
||||
return m.getString(atoms.Name("genre"))
|
||||
}
|
||||
|
||||
func (m metadataMP4) Year() int {
|
||||
date := m.getString(atoms.Name("year"))
|
||||
if len(date) >= 4 {
|
||||
year, _ := strconv.Atoi(date[:4])
|
||||
return year
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Track() (int, int) {
|
||||
x := m.getInt([]string{"trkn"})
|
||||
if n, ok := m.data["trkn_count"]; ok {
|
||||
return x, n.(int)
|
||||
}
|
||||
return x, 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Disc() (int, int) {
|
||||
x := m.getInt([]string{"disk"})
|
||||
if n, ok := m.data["disk_count"]; ok {
|
||||
return x, n.(int)
|
||||
}
|
||||
return x, 0
|
||||
}
|
||||
|
||||
func (m metadataMP4) Lyrics() string {
|
||||
t, ok := m.data["\xa9lyr"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return t.(string)
|
||||
}
|
||||
|
||||
func (m metadataMP4) Comment() string {
|
||||
t, ok := m.data["\xa9cmt"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return t.(string)
|
||||
}
|
||||
|
||||
func (m metadataMP4) Picture() *Picture {
|
||||
v, ok := m.data["covr"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
p, _ := v.(*Picture)
|
||||
return p
|
||||
}
|
||||
119
vendor/github.com/dhowden/tag/ogg.go
generated
vendored
119
vendor/github.com/dhowden/tag/ogg.go
generated
vendored
@@ -1,119 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
idType int = 1
|
||||
commentType int = 3
|
||||
)
|
||||
|
||||
// ReadOGGTags reads OGG metadata from the io.ReadSeeker, returning the resulting
|
||||
// metadata in a Metadata implementation, or non-nil error if there was a problem.
|
||||
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html
|
||||
// and http://www.xiph.org/ogg/doc/framing.html for details.
|
||||
func ReadOGGTags(r io.ReadSeeker) (Metadata, error) {
|
||||
oggs, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if oggs != "OggS" {
|
||||
return nil, errors.New("expected 'OggS'")
|
||||
}
|
||||
|
||||
// Skip 22 bytes of Page header to read page_segments length byte at position 26
|
||||
// See http://www.xiph.org/ogg/doc/framing.html
|
||||
_, err = r.Seek(22, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nS, err := readInt(r, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Seek and discard the segments
|
||||
_, err = r.Seek(int64(nS), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First packet type is identification, type 1
|
||||
t, err := readInt(r, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != idType {
|
||||
return nil, errors.New("expected 'vorbis' identification type 1")
|
||||
}
|
||||
|
||||
// Seek and discard 29 bytes from common and identification header
|
||||
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2
|
||||
_, err = r.Seek(29, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Beginning of a new page. Comment packet is on a separate page
|
||||
// See http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-132000A.2
|
||||
oggs, err = readString(r, 4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if oggs != "OggS" {
|
||||
return nil, errors.New("expected 'OggS'")
|
||||
}
|
||||
|
||||
// Skip page 2 header, same as line 30
|
||||
_, err = r.Seek(22, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nS, err = readInt(r, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(nS), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Packet type is comment, type 3
|
||||
t, err = readInt(r, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t != commentType {
|
||||
return nil, errors.New("expected 'vorbis' comment type 3")
|
||||
}
|
||||
|
||||
// Seek and discard 6 bytes from common header
|
||||
_, err = r.Seek(6, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &metadataOGG{
|
||||
newMetadataVorbis(),
|
||||
}
|
||||
|
||||
err = m.readVorbisComment(r)
|
||||
return m, err
|
||||
}
|
||||
|
||||
type metadataOGG struct {
|
||||
*metadataVorbis
|
||||
}
|
||||
|
||||
func (m *metadataOGG) FileType() FileType {
|
||||
return OGG
|
||||
}
|
||||
219
vendor/github.com/dhowden/tag/sum.go
generated
vendored
219
vendor/github.com/dhowden/tag/sum.go
generated
vendored
@@ -1,219 +0,0 @@
|
||||
package tag
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Sum creates a checksum of the audio file data provided by the io.ReadSeeker which is metadata
|
||||
// (ID3, MP4) invariant.
|
||||
func Sum(r io.ReadSeeker) (string, error) {
|
||||
b, err := readBytes(r, 11)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = r.Seek(-11, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not seek back to original position: %v", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case string(b[0:4]) == "fLaC":
|
||||
return SumFLAC(r)
|
||||
|
||||
case string(b[4:11]) == "ftypM4A":
|
||||
return SumAtoms(r)
|
||||
|
||||
case string(b[0:3]) == "ID3":
|
||||
return SumID3v2(r)
|
||||
}
|
||||
|
||||
h, err := SumID3v1(r)
|
||||
if err != nil {
|
||||
if err == ErrNotID3v1 {
|
||||
return SumAll(r)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SumAll returns a checksum of the content from the reader (until EOF).
|
||||
func SumAll(r io.ReadSeeker) (string, error) {
|
||||
h := sha1.New()
|
||||
_, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return hashSum(h), nil
|
||||
}
|
||||
|
||||
// SumAtoms constructs a checksum of MP4 audio file data provided by the io.ReadSeeker which is
|
||||
// metadata invariant.
|
||||
func SumAtoms(r io.ReadSeeker) (string, error) {
|
||||
for {
|
||||
var size uint32
|
||||
err := binary.Read(r, binary.BigEndian, &size)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return "", fmt.Errorf("reached EOF before audio data")
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
name, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "meta":
|
||||
// next_item_id (int32)
|
||||
_, err := r.Seek(4, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case "moov", "udta", "ilst":
|
||||
continue
|
||||
|
||||
case "mdat": // stop when we get to the data
|
||||
h := sha1.New()
|
||||
_, err := io.CopyN(h, r, int64(size-8))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading audio data: %v", err)
|
||||
}
|
||||
return hashSum(h), nil
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(size-8), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading '%v' tag: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sizeToEndOffset(r io.ReadSeeker, offset int64) (int64, error) {
|
||||
n, err := r.Seek(-128, io.SeekEnd)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error seeking end offset (%d bytes): %v", offset, err)
|
||||
}
|
||||
|
||||
_, err = r.Seek(-n, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error seeking back to original position: %v", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// SumID3v1 constructs a checksum of MP3 audio file data (assumed to have ID3v1 tags) provided
|
||||
// by the io.ReadSeeker which is metadata invariant.
|
||||
func SumID3v1(r io.ReadSeeker) (string, error) {
|
||||
n, err := sizeToEndOffset(r, 128)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
|
||||
}
|
||||
|
||||
// TODO: improve this check???
|
||||
if n <= 0 {
|
||||
return "", fmt.Errorf("file size must be greater than 128 bytes (ID3v1 header size) for MP3")
|
||||
}
|
||||
|
||||
h := sha1.New()
|
||||
_, err = io.CopyN(h, r, n)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading %v bytes: %v", n, err)
|
||||
}
|
||||
return hashSum(h), nil
|
||||
}
|
||||
|
||||
// SumID3v2 constructs a checksum of MP3 audio file data (assumed to have ID3v2 tags) provided by the
|
||||
// io.ReadSeeker which is metadata invariant.
|
||||
func SumID3v2(r io.ReadSeeker) (string, error) {
|
||||
header, _, err := readID3v2Header(r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading ID3v2 header: %v", err)
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(header.Size), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error seeking to end of ID3V2 header: %v", err)
|
||||
}
|
||||
|
||||
n, err := sizeToEndOffset(r, 128)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error determining read size to ID3v1 header: %v", err)
|
||||
}
|
||||
|
||||
// TODO: remove this check?????
|
||||
if n < 0 {
|
||||
return "", fmt.Errorf("file size must be greater than 128 bytes for MP3: %v bytes", n)
|
||||
}
|
||||
|
||||
h := sha1.New()
|
||||
_, err = io.CopyN(h, r, n)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading %v bytes: %v", n, err)
|
||||
}
|
||||
return hashSum(h), nil
|
||||
}
|
||||
|
||||
// SumFLAC costructs a checksum of the FLAC audio file data provided by the io.ReadSeeker (ignores
|
||||
// metadata fields).
|
||||
func SumFLAC(r io.ReadSeeker) (string, error) {
|
||||
flac, err := readString(r, 4)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if flac != "fLaC" {
|
||||
return "", errors.New("expected 'fLaC'")
|
||||
}
|
||||
|
||||
for {
|
||||
last, err := skipFLACMetadataBlock(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if last {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
h := sha1.New()
|
||||
_, err = io.Copy(h, r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading data bytes from FLAC: %v", err)
|
||||
}
|
||||
return hashSum(h), nil
|
||||
}
|
||||
|
||||
func skipFLACMetadataBlock(r io.ReadSeeker) (last bool, err error) {
|
||||
blockHeader, err := readBytes(r, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if getBit(blockHeader[0], 7) {
|
||||
blockHeader[0] ^= (1 << 7)
|
||||
last = true
|
||||
}
|
||||
|
||||
blockLen, err := readInt(r, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(blockLen), io.SeekCurrent)
|
||||
return
|
||||
}
|
||||
|
||||
func hashSum(h hash.Hash) string {
|
||||
return fmt.Sprintf("%x", h.Sum([]byte{}))
|
||||
}
|
||||
147
vendor/github.com/dhowden/tag/tag.go
generated
vendored
147
vendor/github.com/dhowden/tag/tag.go
generated
vendored
@@ -1,147 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tag provides MP3 (ID3: v1, 2.2, 2.3 and 2.4), MP4, FLAC and OGG metadata detection,
|
||||
// parsing and artwork extraction.
|
||||
//
|
||||
// Detect and parse tag metadata from an io.ReadSeeker (i.e. an *os.File):
|
||||
// m, err := tag.ReadFrom(f)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// log.Print(m.Format()) // The detected format.
|
||||
// log.Print(m.Title()) // The title of the track (see Metadata interface for more details).
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ErrNoTagsFound is the error returned by ReadFrom when the metadata format
|
||||
// cannot be identified.
|
||||
var ErrNoTagsFound = errors.New("no tags found")
|
||||
|
||||
// ReadFrom detects and parses audio file metadata tags (currently supports ID3v1,2.{2,3,4}, MP4, FLAC/OGG).
|
||||
// Returns non-nil error if the format of the given data could not be determined, or if there was a problem
|
||||
// parsing the data.
|
||||
func ReadFrom(r io.ReadSeeker) (Metadata, error) {
|
||||
b, err := readBytes(r, 11)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.Seek(-11, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not seek back to original position: %v", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case string(b[0:4]) == "fLaC":
|
||||
return ReadFLACTags(r)
|
||||
|
||||
case string(b[0:4]) == "OggS":
|
||||
return ReadOGGTags(r)
|
||||
|
||||
case string(b[4:8]) == "ftyp":
|
||||
return ReadAtoms(r)
|
||||
|
||||
case string(b[0:3]) == "ID3":
|
||||
return ReadID3v2Tags(r)
|
||||
|
||||
case string(b[0:4]) == "DSD ":
|
||||
return ReadDSFTags(r)
|
||||
}
|
||||
|
||||
m, err := ReadID3v1Tags(r)
|
||||
if err != nil {
|
||||
if err == ErrNotID3v1 {
|
||||
err = ErrNoTagsFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Format is an enumeration of metadata types supported by this package.
|
||||
type Format string
|
||||
|
||||
// Supported tag formats.
|
||||
const (
|
||||
UnknownFormat Format = "" // Unknown Format.
|
||||
ID3v1 Format = "ID3v1" // ID3v1 tag format.
|
||||
ID3v2_2 Format = "ID3v2.2" // ID3v2.2 tag format.
|
||||
ID3v2_3 Format = "ID3v2.3" // ID3v2.3 tag format (most common).
|
||||
ID3v2_4 Format = "ID3v2.4" // ID3v2.4 tag format.
|
||||
MP4 Format = "MP4" // MP4 tag (atom) format (see http://www.ftyps.com/ for a full file type list)
|
||||
VORBIS Format = "VORBIS" // Vorbis Comment tag format.
|
||||
)
|
||||
|
||||
// FileType is an enumeration of the audio file types supported by this package, in particular
|
||||
// there are audio file types which share metadata formats, and this type is used to distinguish
|
||||
// between them.
|
||||
type FileType string
|
||||
|
||||
// Supported file types.
|
||||
const (
|
||||
UnknownFileType FileType = "" // Unknown FileType.
|
||||
MP3 FileType = "MP3" // MP3 file
|
||||
M4A FileType = "M4A" // M4A file Apple iTunes (ACC) Audio
|
||||
M4B FileType = "M4B" // M4A file Apple iTunes (ACC) Audio Book
|
||||
M4P FileType = "M4P" // M4A file Apple iTunes (ACC) AES Protected Audio
|
||||
ALAC FileType = "ALAC" // Apple Lossless file FIXME: actually detect this
|
||||
FLAC FileType = "FLAC" // FLAC file
|
||||
OGG FileType = "OGG" // OGG file
|
||||
DSF FileType = "DSF" // DSF file DSD Sony format see https://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
|
||||
)
|
||||
|
||||
// Metadata is an interface which is used to describe metadata retrieved by this package.
|
||||
type Metadata interface {
|
||||
// Format returns the metadata Format used to encode the data.
|
||||
Format() Format
|
||||
|
||||
// FileType returns the file type of the audio file.
|
||||
FileType() FileType
|
||||
|
||||
// Title returns the title of the track.
|
||||
Title() string
|
||||
|
||||
// Album returns the album name of the track.
|
||||
Album() string
|
||||
|
||||
// Artist returns the artist name of the track.
|
||||
Artist() string
|
||||
|
||||
// AlbumArtist returns the album artist name of the track.
|
||||
AlbumArtist() string
|
||||
|
||||
// Composer returns the composer of the track.
|
||||
Composer() string
|
||||
|
||||
// Year returns the year of the track.
|
||||
Year() int
|
||||
|
||||
// Genre returns the genre of the track.
|
||||
Genre() string
|
||||
|
||||
// Track returns the track number and total tracks, or zero values if unavailable.
|
||||
Track() (int, int)
|
||||
|
||||
// Disc returns the disc number and total discs, or zero values if unavailable.
|
||||
Disc() (int, int)
|
||||
|
||||
// Picture returns a picture, or nil if not available.
|
||||
Picture() *Picture
|
||||
|
||||
// Lyrics returns the lyrics, or an empty string if unavailable.
|
||||
Lyrics() string
|
||||
|
||||
// Comment returns the comment, or an empty string if unavailable.
|
||||
Comment() string
|
||||
|
||||
// Raw returns the raw mapping of retrieved tag names and associated values.
|
||||
// NB: tag/atom names are not standardised between formats.
|
||||
Raw() map[string]interface{}
|
||||
}
|
||||
81
vendor/github.com/dhowden/tag/util.go
generated
vendored
81
vendor/github.com/dhowden/tag/util.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
func getBit(b byte, n uint) bool {
|
||||
x := byte(1 << n)
|
||||
return (b & x) == x
|
||||
}
|
||||
|
||||
func get7BitChunkedInt(b []byte) int {
|
||||
var n int
|
||||
for _, x := range b {
|
||||
n = n << 7
|
||||
n |= int(x)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func getInt(b []byte) int {
|
||||
var n int
|
||||
for _, x := range b {
|
||||
n = n << 8
|
||||
n |= int(x)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func getIntLittleEndian(b []byte) int {
|
||||
var n int
|
||||
for i := len(b) - 1; i >= 0; i-- {
|
||||
n = n << 8
|
||||
n |= int(b[i])
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func readBytes(r io.Reader, n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := io.ReadFull(r, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func readString(r io.Reader, n int) (string, error) {
|
||||
b, err := readBytes(r, n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func readInt(r io.Reader, n int) (int, error) {
|
||||
b, err := readBytes(r, n)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return getInt(b), nil
|
||||
}
|
||||
|
||||
func read7BitChunkedInt(r io.Reader, n int) (int, error) {
|
||||
b, err := readBytes(r, n)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return get7BitChunkedInt(b), nil
|
||||
}
|
||||
|
||||
func readInt32LittleEndian(r io.Reader) (int, error) {
|
||||
var n int32
|
||||
err := binary.Read(r, binary.LittleEndian, &n)
|
||||
return int(n), err
|
||||
}
|
||||
255
vendor/github.com/dhowden/tag/vorbis.go
generated
vendored
255
vendor/github.com/dhowden/tag/vorbis.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
// Copyright 2015, David Howden
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func newMetadataVorbis() *metadataVorbis {
|
||||
return &metadataVorbis{
|
||||
c: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
type metadataVorbis struct {
|
||||
c map[string]string // the vorbis comments
|
||||
p *Picture
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) readVorbisComment(r io.Reader) error {
|
||||
vendorLen, err := readInt32LittleEndian(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vendorLen < 0 {
|
||||
return fmt.Errorf("invalid encoding: expected positive length, got %d", vendorLen)
|
||||
}
|
||||
|
||||
vendor, err := readString(r, vendorLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.c["vendor"] = vendor
|
||||
|
||||
commentsLen, err := readInt32LittleEndian(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < commentsLen; i++ {
|
||||
l, err := readInt32LittleEndian(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s, err := readString(r, l)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k, v, err := parseComment(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.c[strings.ToLower(k)] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) readPictureBlock(r io.Reader) error {
|
||||
b, err := readInt(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pictureType, ok := pictureTypes[byte(b)]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid picture type: %v", b)
|
||||
}
|
||||
mimeLen, err := readInt(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mime, err := readString(r, mimeLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := ""
|
||||
switch mime {
|
||||
case "image/jpeg":
|
||||
ext = "jpg"
|
||||
case "image/png":
|
||||
ext = "png"
|
||||
case "image/gif":
|
||||
ext = "gif"
|
||||
}
|
||||
|
||||
descLen, err := readInt(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
desc, err := readString(r, descLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We skip width <32>, height <32>, colorDepth <32>, coloresUsed <32>
|
||||
_, err = readInt(r, 4) // width
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = readInt(r, 4) // height
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = readInt(r, 4) // color depth
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = readInt(r, 4) // colors used
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dataLen, err := readInt(r, 4)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data := make([]byte, dataLen)
|
||||
_, err = io.ReadFull(r, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.p = &Picture{
|
||||
Ext: ext,
|
||||
MIMEType: mime,
|
||||
Type: pictureType,
|
||||
Description: desc,
|
||||
Data: data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseComment(c string) (k, v string, err error) {
|
||||
kv := strings.SplitN(c, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
err = errors.New("vorbis comment must contain '='")
|
||||
return
|
||||
}
|
||||
k = kv[0]
|
||||
v = kv[1]
|
||||
return
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Format() Format {
|
||||
return VORBIS
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Raw() map[string]interface{} {
|
||||
raw := make(map[string]interface{}, len(m.c))
|
||||
for k, v := range m.c {
|
||||
raw[k] = v
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Title() string {
|
||||
return m.c["title"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Artist() string {
|
||||
// PERFORMER
|
||||
// The artist(s) who performed the work. In classical music this would be the
|
||||
// conductor, orchestra, soloists. In an audio book it would be the actor who
|
||||
// did the reading. In popular music this is typically the same as the ARTIST
|
||||
// and is omitted.
|
||||
if m.c["performer"] != "" {
|
||||
return m.c["performer"]
|
||||
}
|
||||
return m.c["artist"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Album() string {
|
||||
return m.c["album"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) AlbumArtist() string {
|
||||
// This field isn't actually included in the standard, though
|
||||
// it is commonly assigned to albumartist.
|
||||
return m.c["albumartist"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Composer() string {
|
||||
// ARTIST
|
||||
// The artist generally considered responsible for the work. In popular music
|
||||
// this is usually the performing band or singer. For classical music it would
|
||||
// be the composer. For an audio book it would be the author of the original text.
|
||||
if m.c["composer"] != "" {
|
||||
return m.c["composer"]
|
||||
}
|
||||
if m.c["performer"] == "" {
|
||||
return ""
|
||||
}
|
||||
return m.c["artist"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Genre() string {
|
||||
return m.c["genre"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Year() int {
|
||||
var dateFormat string
|
||||
|
||||
// The date need to follow the international standard https://en.wikipedia.org/wiki/ISO_8601
|
||||
// and obviously the VorbisComment standard https://wiki.xiph.org/VorbisComment#Date_and_time
|
||||
switch len(m.c["date"]) {
|
||||
case 0:
|
||||
return 0
|
||||
case 4:
|
||||
dateFormat = "2006"
|
||||
case 7:
|
||||
dateFormat = "2006-01"
|
||||
case 10:
|
||||
dateFormat = "2006-01-02"
|
||||
}
|
||||
|
||||
t, _ := time.Parse(dateFormat, m.c["date"])
|
||||
return t.Year()
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Track() (int, int) {
|
||||
x, _ := strconv.Atoi(m.c["tracknumber"])
|
||||
// https://wiki.xiph.org/Field_names
|
||||
n, _ := strconv.Atoi(m.c["tracktotal"])
|
||||
return x, n
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Disc() (int, int) {
|
||||
// https://wiki.xiph.org/Field_names
|
||||
x, _ := strconv.Atoi(m.c["discnumber"])
|
||||
n, _ := strconv.Atoi(m.c["disctotal"])
|
||||
return x, n
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Lyrics() string {
|
||||
return m.c["lyrics"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Comment() string {
|
||||
if m.c["comment"] != "" {
|
||||
return m.c["comment"]
|
||||
}
|
||||
return m.c["description"]
|
||||
}
|
||||
|
||||
func (m *metadataVorbis) Picture() *Picture {
|
||||
return m.p
|
||||
}
|
||||
25
vendor/github.com/labstack/echo/.editorconfig
generated
vendored
25
vendor/github.com/labstack/echo/.editorconfig
generated
vendored
@@ -1,25 +0,0 @@
|
||||
# EditorConfig coding styles definitions. For more information about the
|
||||
# properties used in this file, please see the EditorConfig documentation:
|
||||
# http://editorconfig.org/
|
||||
|
||||
# indicate this is the root of the project
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
|
||||
end_of_line = LF
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
20
vendor/github.com/labstack/echo/.gitattributes
generated
vendored
20
vendor/github.com/labstack/echo/.gitattributes
generated
vendored
@@ -1,20 +0,0 @@
|
||||
# Automatically normalize line endings for all text-based files
|
||||
# http://git-scm.com/docs/gitattributes#_end_of_line_conversion
|
||||
* text=auto
|
||||
|
||||
# For the following file types, normalize line endings to LF on checking and
|
||||
# prevent conversion to CRLF when they are checked out (this is required in
|
||||
# order to prevent newline related issues)
|
||||
.* text eol=lf
|
||||
*.go text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.html text eol=lf
|
||||
*.css text eol=lf
|
||||
*.js text eol=lf
|
||||
*.json text eol=lf
|
||||
LICENSE text eol=lf
|
||||
|
||||
# Exclude `website` and `cookbook` from GitHub's language statistics
|
||||
# https://github.com/github/linguist#using-gitattributes
|
||||
cookbook/* linguist-documentation
|
||||
website/* linguist-documentation
|
||||
7
vendor/github.com/labstack/echo/.gitignore
generated
vendored
7
vendor/github.com/labstack/echo/.gitignore
generated
vendored
@@ -1,7 +0,0 @@
|
||||
.DS_Store
|
||||
coverage.txt
|
||||
_test
|
||||
vendor
|
||||
.idea
|
||||
*.iml
|
||||
*.out
|
||||
16
vendor/github.com/labstack/echo/.travis.yml
generated
vendored
16
vendor/github.com/labstack/echo/.travis.yml
generated
vendored
@@ -1,16 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.11.x
|
||||
- tip
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
install:
|
||||
- go get -v golang.org/x/lint/golint
|
||||
script:
|
||||
- golint -set_exit_status ./...
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
21
vendor/github.com/labstack/echo/LICENSE
generated
vendored
21
vendor/github.com/labstack/echo/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 LabStack
|
||||
|
||||
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.
|
||||
3
vendor/github.com/labstack/echo/Makefile
generated
vendored
3
vendor/github.com/labstack/echo/Makefile
generated
vendored
@@ -1,3 +0,0 @@
|
||||
tag:
|
||||
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
|
||||
@git tag|grep -v ^v
|
||||
99
vendor/github.com/labstack/echo/README.md
generated
vendored
99
vendor/github.com/labstack/echo/README.md
generated
vendored
@@ -1,99 +0,0 @@
|
||||
<a href="https://echo.labstack.com"><img height="80" src="https://cdn.labstack.com/images/echo-logo.svg"></a>
|
||||
|
||||
[](https://sourcegraph.com/github.com/labstack/echo?badge)
|
||||
[](http://godoc.org/github.com/labstack/echo)
|
||||
[](https://goreportcard.com/report/github.com/labstack/echo)
|
||||
[](https://travis-ci.org/labstack/echo)
|
||||
[](https://codecov.io/gh/labstack/echo)
|
||||
[](https://gitter.im/labstack/echo)
|
||||
[](https://forum.labstack.com)
|
||||
[](https://twitter.com/labstack)
|
||||
[](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
|
||||
|
||||
## Feature Overview
|
||||
|
||||
- Optimized HTTP router which smartly prioritize routes
|
||||
- Build robust and scalable RESTful APIs
|
||||
- Group APIs
|
||||
- Extensible middleware framework
|
||||
- Define middleware at root, group or route level
|
||||
- Data binding for JSON, XML and form payload
|
||||
- Handy functions to send variety of HTTP responses
|
||||
- Centralized HTTP error handling
|
||||
- Template rendering with any template engine
|
||||
- Define your format for the logger
|
||||
- Highly customizable
|
||||
- Automatic TLS via Let’s Encrypt
|
||||
- HTTP/2 support
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Date: 2018/03/15<br>
|
||||
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||
Lower is better!
|
||||
|
||||
<img src="https://i.imgur.com/I32VdMJ.png">
|
||||
|
||||
## [Guide](https://echo.labstack.com/guide)
|
||||
|
||||
### Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Echo instance
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Routes
|
||||
e.GET("/", hello)
|
||||
|
||||
// Start server
|
||||
e.Logger.Fatal(e.Start(":1323"))
|
||||
}
|
||||
|
||||
// Handler
|
||||
func hello(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
}
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
- [Forum](https://forum.labstack.com)
|
||||
- [Chat](https://gitter.im/labstack/echo)
|
||||
|
||||
## Contribute
|
||||
|
||||
**Use issues for everything**
|
||||
|
||||
- For a small change, just send a PR.
|
||||
- For bigger changes open an issue for discussion before sending a PR.
|
||||
- PR should have:
|
||||
- Test case
|
||||
- Documentation
|
||||
- Example (If it makes sense)
|
||||
- You can also contribute by:
|
||||
- Reporting issues
|
||||
- Suggesting new features or enhancements
|
||||
- Improve/fix documentation
|
||||
|
||||
## Credits
|
||||
- [Vishal Rana](https://github.com/vishr) - Author
|
||||
- [Nitin Rana](https://github.com/nr17) - Consultant
|
||||
- [Contributors](https://github.com/labstack/echo/graphs/contributors)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/labstack/echo/blob/master/LICENSE)
|
||||
277
vendor/github.com/labstack/echo/bind.go
generated
vendored
277
vendor/github.com/labstack/echo/bind.go
generated
vendored
@@ -1,277 +0,0 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Binder is the interface that wraps the Bind method.
|
||||
Binder interface {
|
||||
Bind(i interface{}, c Context) error
|
||||
}
|
||||
|
||||
// DefaultBinder is the default implementation of the Binder interface.
|
||||
DefaultBinder struct{}
|
||||
|
||||
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
|
||||
BindUnmarshaler interface {
|
||||
// UnmarshalParam decodes and assigns a value from an form or query param.
|
||||
UnmarshalParam(param string) error
|
||||
}
|
||||
)
|
||||
|
||||
// Bind implements the `Binder#Bind` function.
|
||||
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||
req := c.Request()
|
||||
if req.ContentLength == 0 {
|
||||
if req.Method == http.MethodGet || req.Method == http.MethodDelete {
|
||||
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
|
||||
}
|
||||
ctype := req.Header.Get(HeaderContentType)
|
||||
switch {
|
||||
case strings.HasPrefix(ctype, MIMEApplicationJSON):
|
||||
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
|
||||
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
|
||||
} else if se, ok := err.(*json.SyntaxError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
|
||||
} else {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
|
||||
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
||||
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
|
||||
} else if se, ok := err.(*xml.SyntaxError); ok {
|
||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
|
||||
} else {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||
params, err := c.FormParams()
|
||||
if err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
if err = b.bindData(i, params, "form"); err != nil {
|
||||
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||
}
|
||||
default:
|
||||
return ErrUnsupportedMediaType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error {
|
||||
typ := reflect.TypeOf(ptr).Elem()
|
||||
val := reflect.ValueOf(ptr).Elem()
|
||||
|
||||
if typ.Kind() != reflect.Struct {
|
||||
return errors.New("binding element must be a struct")
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
typeField := typ.Field(i)
|
||||
structField := val.Field(i)
|
||||
if !structField.CanSet() {
|
||||
continue
|
||||
}
|
||||
structFieldKind := structField.Kind()
|
||||
inputFieldName := typeField.Tag.Get(tag)
|
||||
|
||||
if inputFieldName == "" {
|
||||
inputFieldName = typeField.Name
|
||||
// If tag is nil, we inspect if the field is a struct.
|
||||
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
|
||||
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
inputValue, exists := data[inputFieldName]
|
||||
if !exists {
|
||||
// Go json.Unmarshal supports case insensitive binding. However the
|
||||
// url params are bound case sensitive which is inconsistent. To
|
||||
// fix this we must check all of the map values in a
|
||||
// case-insensitive search.
|
||||
inputFieldName = strings.ToLower(inputFieldName)
|
||||
for k, v := range data {
|
||||
if strings.ToLower(k) == inputFieldName {
|
||||
inputValue = v
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// Call this first, in case we're dealing with an alias to an array type
|
||||
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
numElems := len(inputValue)
|
||||
if structFieldKind == reflect.Slice && numElems > 0 {
|
||||
sliceOf := structField.Type().Elem().Kind()
|
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||
for j := 0; j < numElems; j++ {
|
||||
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
val.Field(i).Set(slice)
|
||||
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
|
||||
// But also call it here, in case we're dealing with an array of BindUnmarshalers
|
||||
if ok, err := unmarshalField(valueKind, val, structField); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
switch valueKind {
|
||||
case reflect.Ptr:
|
||||
return setWithProperType(structField.Elem().Kind(), val, structField.Elem())
|
||||
case reflect.Int:
|
||||
return setIntField(val, 0, structField)
|
||||
case reflect.Int8:
|
||||
return setIntField(val, 8, structField)
|
||||
case reflect.Int16:
|
||||
return setIntField(val, 16, structField)
|
||||
case reflect.Int32:
|
||||
return setIntField(val, 32, structField)
|
||||
case reflect.Int64:
|
||||
return setIntField(val, 64, structField)
|
||||
case reflect.Uint:
|
||||
return setUintField(val, 0, structField)
|
||||
case reflect.Uint8:
|
||||
return setUintField(val, 8, structField)
|
||||
case reflect.Uint16:
|
||||
return setUintField(val, 16, structField)
|
||||
case reflect.Uint32:
|
||||
return setUintField(val, 32, structField)
|
||||
case reflect.Uint64:
|
||||
return setUintField(val, 64, structField)
|
||||
case reflect.Bool:
|
||||
return setBoolField(val, structField)
|
||||
case reflect.Float32:
|
||||
return setFloatField(val, 32, structField)
|
||||
case reflect.Float64:
|
||||
return setFloatField(val, 64, structField)
|
||||
case reflect.String:
|
||||
structField.SetString(val)
|
||||
default:
|
||||
return errors.New("unknown type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
|
||||
switch valueKind {
|
||||
case reflect.Ptr:
|
||||
return unmarshalFieldPtr(val, field)
|
||||
default:
|
||||
return unmarshalFieldNonPtr(val, field)
|
||||
}
|
||||
}
|
||||
|
||||
// bindUnmarshaler attempts to unmarshal a reflect.Value into a BindUnmarshaler
|
||||
func bindUnmarshaler(field reflect.Value) (BindUnmarshaler, bool) {
|
||||
ptr := reflect.New(field.Type())
|
||||
if ptr.CanInterface() {
|
||||
iface := ptr.Interface()
|
||||
if unmarshaler, ok := iface.(BindUnmarshaler); ok {
|
||||
return unmarshaler, ok
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
|
||||
if unmarshaler, ok := bindUnmarshaler(field); ok {
|
||||
err := unmarshaler.UnmarshalParam(value)
|
||||
field.Set(reflect.ValueOf(unmarshaler).Elem())
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
|
||||
if field.IsNil() {
|
||||
// Initialize the pointer to a nil value
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
return unmarshalFieldNonPtr(value, field.Elem())
|
||||
}
|
||||
|
||||
func setIntField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0"
|
||||
}
|
||||
intVal, err := strconv.ParseInt(value, 10, bitSize)
|
||||
if err == nil {
|
||||
field.SetInt(intVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setUintField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0"
|
||||
}
|
||||
uintVal, err := strconv.ParseUint(value, 10, bitSize)
|
||||
if err == nil {
|
||||
field.SetUint(uintVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setBoolField(value string, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "false"
|
||||
}
|
||||
boolVal, err := strconv.ParseBool(value)
|
||||
if err == nil {
|
||||
field.SetBool(boolVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func setFloatField(value string, bitSize int, field reflect.Value) error {
|
||||
if value == "" {
|
||||
value = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(value, bitSize)
|
||||
if err == nil {
|
||||
field.SetFloat(floatVal)
|
||||
}
|
||||
return err
|
||||
}
|
||||
600
vendor/github.com/labstack/echo/context.go
generated
vendored
600
vendor/github.com/labstack/echo/context.go
generated
vendored
@@ -1,600 +0,0 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
// Context represents the context of the current HTTP request. It holds request and
|
||||
// response objects, path, path parameters, data and registered handler.
|
||||
Context interface {
|
||||
// Request returns `*http.Request`.
|
||||
Request() *http.Request
|
||||
|
||||
// SetRequest sets `*http.Request`.
|
||||
SetRequest(r *http.Request)
|
||||
|
||||
// Response returns `*Response`.
|
||||
Response() *Response
|
||||
|
||||
// IsTLS returns true if HTTP connection is TLS otherwise false.
|
||||
IsTLS() bool
|
||||
|
||||
// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
|
||||
IsWebSocket() bool
|
||||
|
||||
// Scheme returns the HTTP protocol scheme, `http` or `https`.
|
||||
Scheme() string
|
||||
|
||||
// RealIP returns the client's network address based on `X-Forwarded-For`
|
||||
// or `X-Real-IP` request header.
|
||||
RealIP() string
|
||||
|
||||
// Path returns the registered path for the handler.
|
||||
Path() string
|
||||
|
||||
// SetPath sets the registered path for the handler.
|
||||
SetPath(p string)
|
||||
|
||||
// Param returns path parameter by name.
|
||||
Param(name string) string
|
||||
|
||||
// ParamNames returns path parameter names.
|
||||
ParamNames() []string
|
||||
|
||||
// SetParamNames sets path parameter names.
|
||||
SetParamNames(names ...string)
|
||||
|
||||
// ParamValues returns path parameter values.
|
||||
ParamValues() []string
|
||||
|
||||
// SetParamValues sets path parameter values.
|
||||
SetParamValues(values ...string)
|
||||
|
||||
// QueryParam returns the query param for the provided name.
|
||||
QueryParam(name string) string
|
||||
|
||||
// QueryParams returns the query parameters as `url.Values`.
|
||||
QueryParams() url.Values
|
||||
|
||||
// QueryString returns the URL query string.
|
||||
QueryString() string
|
||||
|
||||
// FormValue returns the form field value for the provided name.
|
||||
FormValue(name string) string
|
||||
|
||||
// FormParams returns the form parameters as `url.Values`.
|
||||
FormParams() (url.Values, error)
|
||||
|
||||
// FormFile returns the multipart form file for the provided name.
|
||||
FormFile(name string) (*multipart.FileHeader, error)
|
||||
|
||||
// MultipartForm returns the multipart form.
|
||||
MultipartForm() (*multipart.Form, error)
|
||||
|
||||
// Cookie returns the named cookie provided in the request.
|
||||
Cookie(name string) (*http.Cookie, error)
|
||||
|
||||
// SetCookie adds a `Set-Cookie` header in HTTP response.
|
||||
SetCookie(cookie *http.Cookie)
|
||||
|
||||
// Cookies returns the HTTP cookies sent with the request.
|
||||
Cookies() []*http.Cookie
|
||||
|
||||
// Get retrieves data from the context.
|
||||
Get(key string) interface{}
|
||||
|
||||
// Set saves data in the context.
|
||||
Set(key string, val interface{})
|
||||
|
||||
// Bind binds the request body into provided type `i`. The default binder
|
||||
// does it based on Content-Type header.
|
||||
Bind(i interface{}) error
|
||||
|
||||
// Validate validates provided `i`. It is usually called after `Context#Bind()`.
|
||||
// Validator must be registered using `Echo#Validator`.
|
||||
Validate(i interface{}) error
|
||||
|
||||
// Render renders a template with data and sends a text/html response with status
|
||||
// code. Renderer must be registered using `Echo.Renderer`.
|
||||
Render(code int, name string, data interface{}) error
|
||||
|
||||
// HTML sends an HTTP response with status code.
|
||||
HTML(code int, html string) error
|
||||
|
||||
// HTMLBlob sends an HTTP blob response with status code.
|
||||
HTMLBlob(code int, b []byte) error
|
||||
|
||||
// String sends a string response with status code.
|
||||
String(code int, s string) error
|
||||
|
||||
// JSON sends a JSON response with status code.
|
||||
JSON(code int, i interface{}) error
|
||||
|
||||
// JSONPretty sends a pretty-print JSON with status code.
|
||||
JSONPretty(code int, i interface{}, indent string) error
|
||||
|
||||
// JSONBlob sends a JSON blob response with status code.
|
||||
JSONBlob(code int, b []byte) error
|
||||
|
||||
// JSONP sends a JSONP response with status code. It uses `callback` to construct
|
||||
// the JSONP payload.
|
||||
JSONP(code int, callback string, i interface{}) error
|
||||
|
||||
// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
|
||||
// to construct the JSONP payload.
|
||||
JSONPBlob(code int, callback string, b []byte) error
|
||||
|
||||
// XML sends an XML response with status code.
|
||||
XML(code int, i interface{}) error
|
||||
|
||||
// XMLPretty sends a pretty-print XML with status code.
|
||||
XMLPretty(code int, i interface{}, indent string) error
|
||||
|
||||
// XMLBlob sends an XML blob response with status code.
|
||||
XMLBlob(code int, b []byte) error
|
||||
|
||||
// Blob sends a blob response with status code and content type.
|
||||
Blob(code int, contentType string, b []byte) error
|
||||
|
||||
// Stream sends a streaming response with status code and content type.
|
||||
Stream(code int, contentType string, r io.Reader) error
|
||||
|
||||
// File sends a response with the content of the file.
|
||||
File(file string) error
|
||||
|
||||
// Attachment sends a response as attachment, prompting client to save the
|
||||
// file.
|
||||
Attachment(file string, name string) error
|
||||
|
||||
// Inline sends a response as inline, opening the file in the browser.
|
||||
Inline(file string, name string) error
|
||||
|
||||
// NoContent sends a response with no body and a status code.
|
||||
NoContent(code int) error
|
||||
|
||||
// Redirect redirects the request to a provided URL with status code.
|
||||
Redirect(code int, url string) error
|
||||
|
||||
// Error invokes the registered HTTP error handler. Generally used by middleware.
|
||||
Error(err error)
|
||||
|
||||
// Handler returns the matched handler by router.
|
||||
Handler() HandlerFunc
|
||||
|
||||
// SetHandler sets the matched handler by router.
|
||||
SetHandler(h HandlerFunc)
|
||||
|
||||
// Logger returns the `Logger` instance.
|
||||
Logger() Logger
|
||||
|
||||
// Echo returns the `Echo` instance.
|
||||
Echo() *Echo
|
||||
|
||||
// Reset resets the context after request completes. It must be called along
|
||||
// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
|
||||
// See `Echo#ServeHTTP()`
|
||||
Reset(r *http.Request, w http.ResponseWriter)
|
||||
}
|
||||
|
||||
context struct {
|
||||
request *http.Request
|
||||
response *Response
|
||||
path string
|
||||
pnames []string
|
||||
pvalues []string
|
||||
query url.Values
|
||||
handler HandlerFunc
|
||||
store Map
|
||||
echo *Echo
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMemory = 32 << 20 // 32 MB
|
||||
indexPage = "index.html"
|
||||
defaultIndent = " "
|
||||
)
|
||||
|
||||
func (c *context) writeContentType(value string) {
|
||||
header := c.Response().Header()
|
||||
if header.Get(HeaderContentType) == "" {
|
||||
header.Set(HeaderContentType, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Request() *http.Request {
|
||||
return c.request
|
||||
}
|
||||
|
||||
func (c *context) SetRequest(r *http.Request) {
|
||||
c.request = r
|
||||
}
|
||||
|
||||
func (c *context) Response() *Response {
|
||||
return c.response
|
||||
}
|
||||
|
||||
func (c *context) IsTLS() bool {
|
||||
return c.request.TLS != nil
|
||||
}
|
||||
|
||||
func (c *context) IsWebSocket() bool {
|
||||
upgrade := c.request.Header.Get(HeaderUpgrade)
|
||||
return upgrade == "websocket" || upgrade == "Websocket"
|
||||
}
|
||||
|
||||
func (c *context) Scheme() string {
|
||||
// Can't use `r.Request.URL.Scheme`
|
||||
// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
|
||||
if c.IsTLS() {
|
||||
return "https"
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
|
||||
return "https"
|
||||
}
|
||||
if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
|
||||
return scheme
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func (c *context) RealIP() string {
|
||||
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||
return strings.Split(ip, ", ")[0]
|
||||
}
|
||||
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||
return ip
|
||||
}
|
||||
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
|
||||
return ra
|
||||
}
|
||||
|
||||
func (c *context) Path() string {
|
||||
return c.path
|
||||
}
|
||||
|
||||
func (c *context) SetPath(p string) {
|
||||
c.path = p
|
||||
}
|
||||
|
||||
func (c *context) Param(name string) string {
|
||||
for i, n := range c.pnames {
|
||||
if i < len(c.pvalues) {
|
||||
if n == name {
|
||||
return c.pvalues[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *context) ParamNames() []string {
|
||||
return c.pnames
|
||||
}
|
||||
|
||||
func (c *context) SetParamNames(names ...string) {
|
||||
c.pnames = names
|
||||
}
|
||||
|
||||
func (c *context) ParamValues() []string {
|
||||
return c.pvalues[:len(c.pnames)]
|
||||
}
|
||||
|
||||
func (c *context) SetParamValues(values ...string) {
|
||||
c.pvalues = values
|
||||
}
|
||||
|
||||
func (c *context) QueryParam(name string) string {
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query.Get(name)
|
||||
}
|
||||
|
||||
func (c *context) QueryParams() url.Values {
|
||||
if c.query == nil {
|
||||
c.query = c.request.URL.Query()
|
||||
}
|
||||
return c.query
|
||||
}
|
||||
|
||||
func (c *context) QueryString() string {
|
||||
return c.request.URL.RawQuery
|
||||
}
|
||||
|
||||
func (c *context) FormValue(name string) string {
|
||||
return c.request.FormValue(name)
|
||||
}
|
||||
|
||||
func (c *context) FormParams() (url.Values, error) {
|
||||
if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
|
||||
if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err := c.request.ParseForm(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return c.request.Form, nil
|
||||
}
|
||||
|
||||
func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
|
||||
_, fh, err := c.request.FormFile(name)
|
||||
return fh, err
|
||||
}
|
||||
|
||||
func (c *context) MultipartForm() (*multipart.Form, error) {
|
||||
err := c.request.ParseMultipartForm(defaultMemory)
|
||||
return c.request.MultipartForm, err
|
||||
}
|
||||
|
||||
func (c *context) Cookie(name string) (*http.Cookie, error) {
|
||||
return c.request.Cookie(name)
|
||||
}
|
||||
|
||||
func (c *context) SetCookie(cookie *http.Cookie) {
|
||||
http.SetCookie(c.Response(), cookie)
|
||||
}
|
||||
|
||||
func (c *context) Cookies() []*http.Cookie {
|
||||
return c.request.Cookies()
|
||||
}
|
||||
|
||||
func (c *context) Get(key string) interface{} {
|
||||
return c.store[key]
|
||||
}
|
||||
|
||||
func (c *context) Set(key string, val interface{}) {
|
||||
if c.store == nil {
|
||||
c.store = make(Map)
|
||||
}
|
||||
c.store[key] = val
|
||||
}
|
||||
|
||||
func (c *context) Bind(i interface{}) error {
|
||||
return c.echo.Binder.Bind(i, c)
|
||||
}
|
||||
|
||||
func (c *context) Validate(i interface{}) error {
|
||||
if c.echo.Validator == nil {
|
||||
return ErrValidatorNotRegistered
|
||||
}
|
||||
return c.echo.Validator.Validate(i)
|
||||
}
|
||||
|
||||
func (c *context) Render(code int, name string, data interface{}) (err error) {
|
||||
if c.echo.Renderer == nil {
|
||||
return ErrRendererNotRegistered
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
|
||||
return
|
||||
}
|
||||
return c.HTMLBlob(code, buf.Bytes())
|
||||
}
|
||||
|
||||
func (c *context) HTML(code int, html string) (err error) {
|
||||
return c.HTMLBlob(code, []byte(html))
|
||||
}
|
||||
|
||||
func (c *context) HTMLBlob(code int, b []byte) (err error) {
|
||||
return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
|
||||
}
|
||||
|
||||
func (c *context) String(code int, s string) (err error) {
|
||||
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
|
||||
}
|
||||
|
||||
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
|
||||
enc := json.NewEncoder(c.response)
|
||||
_, pretty := c.QueryParams()["pretty"]
|
||||
if c.echo.Debug || pretty {
|
||||
enc.SetIndent("", " ")
|
||||
}
|
||||
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||
return
|
||||
}
|
||||
if err = enc.Encode(i); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.response.Write([]byte(");")); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) json(code int, i interface{}, indent string) error {
|
||||
enc := json.NewEncoder(c.response)
|
||||
if indent != "" {
|
||||
enc.SetIndent("", indent)
|
||||
}
|
||||
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
return enc.Encode(i)
|
||||
}
|
||||
|
||||
func (c *context) JSON(code int, i interface{}) (err error) {
|
||||
indent := ""
|
||||
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||
indent = defaultIndent
|
||||
}
|
||||
return c.json(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
|
||||
return c.json(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) JSONBlob(code int, b []byte) (err error) {
|
||||
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
|
||||
}
|
||||
|
||||
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
||||
return c.jsonPBlob(code, callback, i)
|
||||
}
|
||||
|
||||
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = c.response.Write(b); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.response.Write([]byte(");"))
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) xml(code int, i interface{}, indent string) (err error) {
|
||||
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
enc := xml.NewEncoder(c.response)
|
||||
if indent != "" {
|
||||
enc.Indent("", indent)
|
||||
}
|
||||
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||
return
|
||||
}
|
||||
return enc.Encode(i)
|
||||
}
|
||||
|
||||
func (c *context) XML(code int, i interface{}) (err error) {
|
||||
indent := ""
|
||||
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||
indent = defaultIndent
|
||||
}
|
||||
return c.xml(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
|
||||
return c.xml(code, i, indent)
|
||||
}
|
||||
|
||||
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||
c.response.WriteHeader(code)
|
||||
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.response.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
|
||||
c.writeContentType(contentType)
|
||||
c.response.WriteHeader(code)
|
||||
_, err = c.response.Write(b)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
|
||||
c.writeContentType(contentType)
|
||||
c.response.WriteHeader(code)
|
||||
_, err = io.Copy(c.response, r)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) File(file string) (err error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return NotFoundHandler(c)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fi, _ := f.Stat()
|
||||
if fi.IsDir() {
|
||||
file = filepath.Join(file, indexPage)
|
||||
f, err = os.Open(file)
|
||||
if err != nil {
|
||||
return NotFoundHandler(c)
|
||||
}
|
||||
defer f.Close()
|
||||
if fi, err = f.Stat(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *context) Attachment(file, name string) error {
|
||||
return c.contentDisposition(file, name, "attachment")
|
||||
}
|
||||
|
||||
func (c *context) Inline(file, name string) error {
|
||||
return c.contentDisposition(file, name, "inline")
|
||||
}
|
||||
|
||||
func (c *context) contentDisposition(file, name, dispositionType string) error {
|
||||
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
|
||||
return c.File(file)
|
||||
}
|
||||
|
||||
func (c *context) NoContent(code int) error {
|
||||
c.response.WriteHeader(code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Redirect(code int, url string) error {
|
||||
if code < 300 || code > 308 {
|
||||
return ErrInvalidRedirectCode
|
||||
}
|
||||
c.response.Header().Set(HeaderLocation, url)
|
||||
c.response.WriteHeader(code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) Error(err error) {
|
||||
c.echo.HTTPErrorHandler(err, c)
|
||||
}
|
||||
|
||||
func (c *context) Echo() *Echo {
|
||||
return c.echo
|
||||
}
|
||||
|
||||
func (c *context) Handler() HandlerFunc {
|
||||
return c.handler
|
||||
}
|
||||
|
||||
func (c *context) SetHandler(h HandlerFunc) {
|
||||
c.handler = h
|
||||
}
|
||||
|
||||
func (c *context) Logger() Logger {
|
||||
return c.echo.Logger
|
||||
}
|
||||
|
||||
func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
|
||||
c.request = r
|
||||
c.response.reset(w)
|
||||
c.query = nil
|
||||
c.handler = NotFoundHandler
|
||||
c.store = nil
|
||||
c.path = ""
|
||||
c.pnames = nil
|
||||
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
|
||||
// c.pvalues = nil
|
||||
}
|
||||
|
||||
782
vendor/github.com/labstack/echo/echo.go
generated
vendored
782
vendor/github.com/labstack/echo/echo.go
generated
vendored
@@ -1,782 +0,0 @@
|
||||
/*
|
||||
Package echo implements high performance, minimalist Go web framework.
|
||||
|
||||
Example:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
// Handler
|
||||
func hello(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "Hello, World!")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Echo instance
|
||||
e := echo.New()
|
||||
|
||||
// Middleware
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
|
||||
// Routes
|
||||
e.GET("/", hello)
|
||||
|
||||
// Start server
|
||||
e.Logger.Fatal(e.Start(":1323"))
|
||||
}
|
||||
|
||||
Learn more at https://echo.labstack.com
|
||||
*/
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
stdContext "context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
stdLog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/gommon/color"
|
||||
"github.com/labstack/gommon/log"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
type (
|
||||
// Echo is the top-level framework instance.
|
||||
Echo struct {
|
||||
StdLogger *stdLog.Logger
|
||||
colorer *color.Color
|
||||
premiddleware []MiddlewareFunc
|
||||
middleware []MiddlewareFunc
|
||||
maxParam *int
|
||||
router *Router
|
||||
notFoundHandler HandlerFunc
|
||||
pool sync.Pool
|
||||
Server *http.Server
|
||||
TLSServer *http.Server
|
||||
Listener net.Listener
|
||||
TLSListener net.Listener
|
||||
AutoTLSManager autocert.Manager
|
||||
DisableHTTP2 bool
|
||||
Debug bool
|
||||
HideBanner bool
|
||||
HidePort bool
|
||||
HTTPErrorHandler HTTPErrorHandler
|
||||
Binder Binder
|
||||
Validator Validator
|
||||
Renderer Renderer
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// Route contains a handler and information for matching against requests.
|
||||
Route struct {
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// HTTPError represents an error that occurred while handling a request.
|
||||
HTTPError struct {
|
||||
Code int
|
||||
Message interface{}
|
||||
Internal error // Stores the error returned by an external dependency
|
||||
}
|
||||
|
||||
// MiddlewareFunc defines a function to process middleware.
|
||||
MiddlewareFunc func(HandlerFunc) HandlerFunc
|
||||
|
||||
// HandlerFunc defines a function to serve HTTP requests.
|
||||
HandlerFunc func(Context) error
|
||||
|
||||
// HTTPErrorHandler is a centralized HTTP error handler.
|
||||
HTTPErrorHandler func(error, Context)
|
||||
|
||||
// Validator is the interface that wraps the Validate function.
|
||||
Validator interface {
|
||||
Validate(i interface{}) error
|
||||
}
|
||||
|
||||
// Renderer is the interface that wraps the Render function.
|
||||
Renderer interface {
|
||||
Render(io.Writer, string, interface{}, Context) error
|
||||
}
|
||||
|
||||
// Map defines a generic map of type `map[string]interface{}`.
|
||||
Map map[string]interface{}
|
||||
|
||||
// i is the interface for Echo and Group.
|
||||
i interface {
|
||||
GET(string, HandlerFunc, ...MiddlewareFunc) *Route
|
||||
}
|
||||
)
|
||||
|
||||
// HTTP methods
|
||||
// NOTE: Deprecated, please use the stdlib constants directly instead.
|
||||
const (
|
||||
CONNECT = http.MethodConnect
|
||||
DELETE = http.MethodDelete
|
||||
GET = http.MethodGet
|
||||
HEAD = http.MethodHead
|
||||
OPTIONS = http.MethodOptions
|
||||
PATCH = http.MethodPatch
|
||||
POST = http.MethodPost
|
||||
// PROPFIND = "PROPFIND"
|
||||
PUT = http.MethodPut
|
||||
TRACE = http.MethodTrace
|
||||
)
|
||||
|
||||
// MIME types
|
||||
const (
|
||||
MIMEApplicationJSON = "application/json"
|
||||
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
|
||||
MIMEApplicationJavaScript = "application/javascript"
|
||||
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
|
||||
MIMEApplicationXML = "application/xml"
|
||||
MIMEApplicationXMLCharsetUTF8 = MIMEApplicationXML + "; " + charsetUTF8
|
||||
MIMETextXML = "text/xml"
|
||||
MIMETextXMLCharsetUTF8 = MIMETextXML + "; " + charsetUTF8
|
||||
MIMEApplicationForm = "application/x-www-form-urlencoded"
|
||||
MIMEApplicationProtobuf = "application/protobuf"
|
||||
MIMEApplicationMsgpack = "application/msgpack"
|
||||
MIMETextHTML = "text/html"
|
||||
MIMETextHTMLCharsetUTF8 = MIMETextHTML + "; " + charsetUTF8
|
||||
MIMETextPlain = "text/plain"
|
||||
MIMETextPlainCharsetUTF8 = MIMETextPlain + "; " + charsetUTF8
|
||||
MIMEMultipartForm = "multipart/form-data"
|
||||
MIMEOctetStream = "application/octet-stream"
|
||||
)
|
||||
|
||||
const (
|
||||
charsetUTF8 = "charset=UTF-8"
|
||||
// PROPFIND Method can be used on collection and property resources.
|
||||
PROPFIND = "PROPFIND"
|
||||
)
|
||||
|
||||
// Headers
|
||||
const (
|
||||
HeaderAccept = "Accept"
|
||||
HeaderAcceptEncoding = "Accept-Encoding"
|
||||
HeaderAllow = "Allow"
|
||||
HeaderAuthorization = "Authorization"
|
||||
HeaderContentDisposition = "Content-Disposition"
|
||||
HeaderContentEncoding = "Content-Encoding"
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderContentType = "Content-Type"
|
||||
HeaderCookie = "Cookie"
|
||||
HeaderSetCookie = "Set-Cookie"
|
||||
HeaderIfModifiedSince = "If-Modified-Since"
|
||||
HeaderLastModified = "Last-Modified"
|
||||
HeaderLocation = "Location"
|
||||
HeaderUpgrade = "Upgrade"
|
||||
HeaderVary = "Vary"
|
||||
HeaderWWWAuthenticate = "WWW-Authenticate"
|
||||
HeaderXForwardedFor = "X-Forwarded-For"
|
||||
HeaderXForwardedProto = "X-Forwarded-Proto"
|
||||
HeaderXForwardedProtocol = "X-Forwarded-Protocol"
|
||||
HeaderXForwardedSsl = "X-Forwarded-Ssl"
|
||||
HeaderXUrlScheme = "X-Url-Scheme"
|
||||
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
||||
HeaderXRealIP = "X-Real-IP"
|
||||
HeaderXRequestID = "X-Request-ID"
|
||||
HeaderXRequestedWith = "X-Requested-With"
|
||||
HeaderServer = "Server"
|
||||
HeaderOrigin = "Origin"
|
||||
|
||||
// Access control
|
||||
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
|
||||
HeaderAccessControlRequestHeaders = "Access-Control-Request-Headers"
|
||||
HeaderAccessControlAllowOrigin = "Access-Control-Allow-Origin"
|
||||
HeaderAccessControlAllowMethods = "Access-Control-Allow-Methods"
|
||||
HeaderAccessControlAllowHeaders = "Access-Control-Allow-Headers"
|
||||
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
|
||||
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
|
||||
HeaderAccessControlMaxAge = "Access-Control-Max-Age"
|
||||
|
||||
// Security
|
||||
HeaderStrictTransportSecurity = "Strict-Transport-Security"
|
||||
HeaderXContentTypeOptions = "X-Content-Type-Options"
|
||||
HeaderXXSSProtection = "X-XSS-Protection"
|
||||
HeaderXFrameOptions = "X-Frame-Options"
|
||||
HeaderContentSecurityPolicy = "Content-Security-Policy"
|
||||
HeaderXCSRFToken = "X-CSRF-Token"
|
||||
)
|
||||
|
||||
const (
|
||||
// Version of Echo
|
||||
Version = "3.3.10-dev"
|
||||
website = "https://echo.labstack.com"
|
||||
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
||||
banner = `
|
||||
____ __
|
||||
/ __/___/ / ___
|
||||
/ _// __/ _ \/ _ \
|
||||
/___/\__/_//_/\___/ %s
|
||||
High performance, minimalist Go web framework
|
||||
%s
|
||||
____________________________________O/_______
|
||||
O\
|
||||
`
|
||||
)
|
||||
|
||||
var (
|
||||
methods = [...]string{
|
||||
http.MethodConnect,
|
||||
http.MethodDelete,
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
http.MethodOptions,
|
||||
http.MethodPatch,
|
||||
http.MethodPost,
|
||||
PROPFIND,
|
||||
http.MethodPut,
|
||||
http.MethodTrace,
|
||||
}
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrUnsupportedMediaType = NewHTTPError(http.StatusUnsupportedMediaType)
|
||||
ErrNotFound = NewHTTPError(http.StatusNotFound)
|
||||
ErrUnauthorized = NewHTTPError(http.StatusUnauthorized)
|
||||
ErrForbidden = NewHTTPError(http.StatusForbidden)
|
||||
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
|
||||
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
||||
ErrTooManyRequests = NewHTTPError(http.StatusTooManyRequests)
|
||||
ErrBadRequest = NewHTTPError(http.StatusBadRequest)
|
||||
ErrBadGateway = NewHTTPError(http.StatusBadGateway)
|
||||
ErrInternalServerError = NewHTTPError(http.StatusInternalServerError)
|
||||
ErrRequestTimeout = NewHTTPError(http.StatusRequestTimeout)
|
||||
ErrServiceUnavailable = NewHTTPError(http.StatusServiceUnavailable)
|
||||
ErrValidatorNotRegistered = errors.New("validator not registered")
|
||||
ErrRendererNotRegistered = errors.New("renderer not registered")
|
||||
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
|
||||
ErrCookieNotFound = errors.New("cookie not found")
|
||||
)
|
||||
|
||||
// Error handlers
|
||||
var (
|
||||
NotFoundHandler = func(c Context) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
MethodNotAllowedHandler = func(c Context) error {
|
||||
return ErrMethodNotAllowed
|
||||
}
|
||||
)
|
||||
|
||||
// New creates an instance of Echo.
|
||||
func New() (e *Echo) {
|
||||
e = &Echo{
|
||||
Server: new(http.Server),
|
||||
TLSServer: new(http.Server),
|
||||
AutoTLSManager: autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
},
|
||||
Logger: log.New("echo"),
|
||||
colorer: color.New(),
|
||||
maxParam: new(int),
|
||||
}
|
||||
e.Server.Handler = e
|
||||
e.TLSServer.Handler = e
|
||||
e.HTTPErrorHandler = e.DefaultHTTPErrorHandler
|
||||
e.Binder = &DefaultBinder{}
|
||||
e.Logger.SetLevel(log.ERROR)
|
||||
e.StdLogger = stdLog.New(e.Logger.Output(), e.Logger.Prefix()+": ", 0)
|
||||
e.pool.New = func() interface{} {
|
||||
return e.NewContext(nil, nil)
|
||||
}
|
||||
e.router = NewRouter(e)
|
||||
return
|
||||
}
|
||||
|
||||
// NewContext returns a Context instance.
|
||||
func (e *Echo) NewContext(r *http.Request, w http.ResponseWriter) Context {
|
||||
return &context{
|
||||
request: r,
|
||||
response: NewResponse(w, e),
|
||||
store: make(Map),
|
||||
echo: e,
|
||||
pvalues: make([]string, *e.maxParam),
|
||||
handler: NotFoundHandler,
|
||||
}
|
||||
}
|
||||
|
||||
// Router returns router.
|
||||
func (e *Echo) Router() *Router {
|
||||
return e.router
|
||||
}
|
||||
|
||||
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
|
||||
// with status code.
|
||||
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||
var (
|
||||
code = http.StatusInternalServerError
|
||||
msg interface{}
|
||||
)
|
||||
|
||||
if he, ok := err.(*HTTPError); ok {
|
||||
code = he.Code
|
||||
msg = he.Message
|
||||
if he.Internal != nil {
|
||||
err = fmt.Errorf("%v, %v", err, he.Internal)
|
||||
}
|
||||
} else if e.Debug {
|
||||
msg = err.Error()
|
||||
} else {
|
||||
msg = http.StatusText(code)
|
||||
}
|
||||
if _, ok := msg.(string); ok {
|
||||
msg = Map{"message": msg}
|
||||
}
|
||||
|
||||
// Send response
|
||||
if !c.Response().Committed {
|
||||
if c.Request().Method == http.MethodHead { // Issue #608
|
||||
err = c.NoContent(code)
|
||||
} else {
|
||||
err = c.JSON(code, msg)
|
||||
}
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre adds middleware to the chain which is run before router.
|
||||
func (e *Echo) Pre(middleware ...MiddlewareFunc) {
|
||||
e.premiddleware = append(e.premiddleware, middleware...)
|
||||
}
|
||||
|
||||
// Use adds middleware to the chain which is run after router.
|
||||
func (e *Echo) Use(middleware ...MiddlewareFunc) {
|
||||
e.middleware = append(e.middleware, middleware...)
|
||||
}
|
||||
|
||||
// CONNECT registers a new CONNECT route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodConnect, path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE registers a new DELETE route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodDelete, path, h, m...)
|
||||
}
|
||||
|
||||
// GET registers a new GET route for a path with matching handler in the router
|
||||
// with optional route-level middleware.
|
||||
func (e *Echo) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodGet, path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD registers a new HEAD route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodHead, path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS registers a new OPTIONS route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodOptions, path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH registers a new PATCH route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodPatch, path, h, m...)
|
||||
}
|
||||
|
||||
// POST registers a new POST route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodPost, path, h, m...)
|
||||
}
|
||||
|
||||
// PUT registers a new PUT route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodPut, path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE registers a new TRACE route for a path with matching handler in the
|
||||
// router with optional route-level middleware.
|
||||
func (e *Echo) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return e.Add(http.MethodTrace, path, h, m...)
|
||||
}
|
||||
|
||||
// Any registers a new route for all HTTP methods and path with matching handler
|
||||
// in the router with optional route-level middleware.
|
||||
func (e *Echo) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = e.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Match registers a new route for multiple HTTP methods and path with matching
|
||||
// handler in the router with optional route-level middleware.
|
||||
func (e *Echo) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = e.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Static registers a new route with path prefix to serve static files from the
|
||||
// provided root directory.
|
||||
func (e *Echo) Static(prefix, root string) *Route {
|
||||
if root == "" {
|
||||
root = "." // For security we want to restrict to CWD.
|
||||
}
|
||||
return static(e, prefix, root)
|
||||
}
|
||||
|
||||
func static(i i, prefix, root string) *Route {
|
||||
h := func(c Context) error {
|
||||
p, err := url.PathUnescape(c.Param("*"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := filepath.Join(root, path.Clean("/"+p)) // "/"+ for security
|
||||
return c.File(name)
|
||||
}
|
||||
i.GET(prefix, h)
|
||||
if prefix == "/" {
|
||||
return i.GET(prefix+"*", h)
|
||||
}
|
||||
|
||||
return i.GET(prefix+"/*", h)
|
||||
}
|
||||
|
||||
// File registers a new route with path to serve a static file with optional route-level middleware.
|
||||
func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
|
||||
return e.GET(path, func(c Context) error {
|
||||
return c.File(file)
|
||||
}, m...)
|
||||
}
|
||||
|
||||
// Add registers a new route for an HTTP method and path with matching handler
|
||||
// in the router with optional route-level middleware.
|
||||
func (e *Echo) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||
name := handlerName(handler)
|
||||
e.router.Add(method, path, func(c Context) error {
|
||||
h := handler
|
||||
// Chain middleware
|
||||
for i := len(middleware) - 1; i >= 0; i-- {
|
||||
h = middleware[i](h)
|
||||
}
|
||||
return h(c)
|
||||
})
|
||||
r := &Route{
|
||||
Method: method,
|
||||
Path: path,
|
||||
Name: name,
|
||||
}
|
||||
e.router.routes[method+path] = r
|
||||
return r
|
||||
}
|
||||
|
||||
// Group creates a new router group with prefix and optional group-level middleware.
|
||||
func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
|
||||
g = &Group{prefix: prefix, echo: e}
|
||||
g.Use(m...)
|
||||
return
|
||||
}
|
||||
|
||||
// URI generates a URI from handler.
|
||||
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
|
||||
name := handlerName(handler)
|
||||
return e.Reverse(name, params...)
|
||||
}
|
||||
|
||||
// URL is an alias for `URI` function.
|
||||
func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
|
||||
return e.URI(h, params...)
|
||||
}
|
||||
|
||||
// Reverse generates an URL from route name and provided parameters.
|
||||
func (e *Echo) Reverse(name string, params ...interface{}) string {
|
||||
uri := new(bytes.Buffer)
|
||||
ln := len(params)
|
||||
n := 0
|
||||
for _, r := range e.router.routes {
|
||||
if r.Name == name {
|
||||
for i, l := 0, len(r.Path); i < l; i++ {
|
||||
if r.Path[i] == ':' && n < ln {
|
||||
for ; i < l && r.Path[i] != '/'; i++ {
|
||||
}
|
||||
uri.WriteString(fmt.Sprintf("%v", params[n]))
|
||||
n++
|
||||
}
|
||||
if i < l {
|
||||
uri.WriteByte(r.Path[i])
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return uri.String()
|
||||
}
|
||||
|
||||
// Routes returns the registered routes.
|
||||
func (e *Echo) Routes() []*Route {
|
||||
routes := make([]*Route, 0, len(e.router.routes))
|
||||
for _, v := range e.router.routes {
|
||||
routes = append(routes, v)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// AcquireContext returns an empty `Context` instance from the pool.
|
||||
// You must return the context by calling `ReleaseContext()`.
|
||||
func (e *Echo) AcquireContext() Context {
|
||||
return e.pool.Get().(Context)
|
||||
}
|
||||
|
||||
// ReleaseContext returns the `Context` instance back to the pool.
|
||||
// You must call it after `AcquireContext()`.
|
||||
func (e *Echo) ReleaseContext(c Context) {
|
||||
e.pool.Put(c)
|
||||
}
|
||||
|
||||
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
||||
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Acquire context
|
||||
c := e.pool.Get().(*context)
|
||||
c.Reset(r, w)
|
||||
|
||||
h := NotFoundHandler
|
||||
|
||||
if e.premiddleware == nil {
|
||||
e.router.Find(r.Method, getPath(r), c)
|
||||
h = c.Handler()
|
||||
for i := len(e.middleware) - 1; i >= 0; i-- {
|
||||
h = e.middleware[i](h)
|
||||
}
|
||||
} else {
|
||||
h = func(c Context) error {
|
||||
e.router.Find(r.Method, getPath(r), c)
|
||||
h := c.Handler()
|
||||
for i := len(e.middleware) - 1; i >= 0; i-- {
|
||||
h = e.middleware[i](h)
|
||||
}
|
||||
return h(c)
|
||||
}
|
||||
for i := len(e.premiddleware) - 1; i >= 0; i-- {
|
||||
h = e.premiddleware[i](h)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute chain
|
||||
if err := h(c); err != nil {
|
||||
e.HTTPErrorHandler(err, c)
|
||||
}
|
||||
|
||||
// Release context
|
||||
e.pool.Put(c)
|
||||
}
|
||||
|
||||
// Start starts an HTTP server.
|
||||
func (e *Echo) Start(address string) error {
|
||||
e.Server.Addr = address
|
||||
return e.StartServer(e.Server)
|
||||
}
|
||||
|
||||
// StartTLS starts an HTTPS server.
|
||||
func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
|
||||
if certFile == "" || keyFile == "" {
|
||||
return errors.New("invalid tls configuration")
|
||||
}
|
||||
s := e.TLSServer
|
||||
s.TLSConfig = new(tls.Config)
|
||||
s.TLSConfig.Certificates = make([]tls.Certificate, 1)
|
||||
s.TLSConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return e.startTLS(address)
|
||||
}
|
||||
|
||||
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
|
||||
func (e *Echo) StartAutoTLS(address string) error {
|
||||
s := e.TLSServer
|
||||
s.TLSConfig = new(tls.Config)
|
||||
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
|
||||
return e.startTLS(address)
|
||||
}
|
||||
|
||||
func (e *Echo) startTLS(address string) error {
|
||||
s := e.TLSServer
|
||||
s.Addr = address
|
||||
if !e.DisableHTTP2 {
|
||||
s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2")
|
||||
}
|
||||
return e.StartServer(e.TLSServer)
|
||||
}
|
||||
|
||||
// StartServer starts a custom http server.
|
||||
func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||
// Setup
|
||||
e.colorer.SetOutput(e.Logger.Output())
|
||||
s.ErrorLog = e.StdLogger
|
||||
s.Handler = e
|
||||
if e.Debug {
|
||||
e.Logger.SetLevel(log.DEBUG)
|
||||
}
|
||||
|
||||
if !e.HideBanner {
|
||||
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
|
||||
}
|
||||
|
||||
if s.TLSConfig == nil {
|
||||
if e.Listener == nil {
|
||||
e.Listener, err = newListener(s.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !e.HidePort {
|
||||
e.colorer.Printf("⇨ http server started on %s\n", e.colorer.Green(e.Listener.Addr()))
|
||||
}
|
||||
return s.Serve(e.Listener)
|
||||
}
|
||||
if e.TLSListener == nil {
|
||||
l, err := newListener(s.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.TLSListener = tls.NewListener(l, s.TLSConfig)
|
||||
}
|
||||
if !e.HidePort {
|
||||
e.colorer.Printf("⇨ https server started on %s\n", e.colorer.Green(e.TLSListener.Addr()))
|
||||
}
|
||||
return s.Serve(e.TLSListener)
|
||||
}
|
||||
|
||||
// Close immediately stops the server.
|
||||
// It internally calls `http.Server#Close()`.
|
||||
func (e *Echo) Close() error {
|
||||
if err := e.TLSServer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Server.Close()
|
||||
}
|
||||
|
||||
// Shutdown stops the server gracefully.
|
||||
// It internally calls `http.Server#Shutdown()`.
|
||||
func (e *Echo) Shutdown(ctx stdContext.Context) error {
|
||||
if err := e.TLSServer.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// NewHTTPError creates a new HTTPError instance.
|
||||
func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
||||
he := &HTTPError{Code: code, Message: http.StatusText(code)}
|
||||
if len(message) > 0 {
|
||||
he.Message = message[0]
|
||||
}
|
||||
return he
|
||||
}
|
||||
|
||||
// Error makes it compatible with `error` interface.
|
||||
func (he *HTTPError) Error() string {
|
||||
return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message)
|
||||
}
|
||||
|
||||
// SetInternal sets error to HTTPError.Internal
|
||||
func (he *HTTPError) SetInternal(err error) *HTTPError {
|
||||
he.Internal = err
|
||||
return he
|
||||
}
|
||||
|
||||
// WrapHandler wraps `http.Handler` into `echo.HandlerFunc`.
|
||||
func WrapHandler(h http.Handler) HandlerFunc {
|
||||
return func(c Context) error {
|
||||
h.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WrapMiddleware wraps `func(http.Handler) http.Handler` into `echo.MiddlewareFunc`
|
||||
func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
|
||||
return func(next HandlerFunc) HandlerFunc {
|
||||
return func(c Context) (err error) {
|
||||
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
c.SetRequest(r)
|
||||
err = next(c)
|
||||
})).ServeHTTP(c.Response(), c.Request())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPath(r *http.Request) string {
|
||||
path := r.URL.RawPath
|
||||
if path == "" {
|
||||
path = r.URL.Path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func handlerName(h HandlerFunc) string {
|
||||
t := reflect.ValueOf(h).Type()
|
||||
if t.Kind() == reflect.Func {
|
||||
return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
|
||||
}
|
||||
return t.String()
|
||||
}
|
||||
|
||||
// // PathUnescape is wraps `url.PathUnescape`
|
||||
// func PathUnescape(s string) (string, error) {
|
||||
// return url.PathUnescape(s)
|
||||
// }
|
||||
|
||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||
// go away.
|
||||
type tcpKeepAliveListener struct {
|
||||
*net.TCPListener
|
||||
}
|
||||
|
||||
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
|
||||
tc, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tc.SetKeepAlive(true)
|
||||
tc.SetKeepAlivePeriod(3 * time.Minute)
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func newListener(address string) (*tcpKeepAliveListener, error) {
|
||||
l, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tcpKeepAliveListener{l.(*net.TCPListener)}, nil
|
||||
}
|
||||
121
vendor/github.com/labstack/echo/group.go
generated
vendored
121
vendor/github.com/labstack/echo/group.go
generated
vendored
@@ -1,121 +0,0 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
type (
|
||||
// Group is a set of sub-routes for a specified route. It can be used for inner
|
||||
// routes that share a common middleware or functionality that should be separate
|
||||
// from the parent echo instance while still inheriting from it.
|
||||
Group struct {
|
||||
prefix string
|
||||
middleware []MiddlewareFunc
|
||||
echo *Echo
|
||||
}
|
||||
)
|
||||
|
||||
// Use implements `Echo#Use()` for sub-routes within the Group.
|
||||
func (g *Group) Use(middleware ...MiddlewareFunc) {
|
||||
g.middleware = append(g.middleware, middleware...)
|
||||
// Allow all requests to reach the group as they might get dropped if router
|
||||
// doesn't find a match, making none of the group middleware process.
|
||||
for _, p := range []string{"", "/*"} {
|
||||
g.echo.Any(path.Clean(g.prefix+p), func(c Context) error {
|
||||
return NotFoundHandler(c)
|
||||
}, g.middleware...)
|
||||
}
|
||||
}
|
||||
|
||||
// CONNECT implements `Echo#CONNECT()` for sub-routes within the Group.
|
||||
func (g *Group) CONNECT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodConnect, path, h, m...)
|
||||
}
|
||||
|
||||
// DELETE implements `Echo#DELETE()` for sub-routes within the Group.
|
||||
func (g *Group) DELETE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodDelete, path, h, m...)
|
||||
}
|
||||
|
||||
// GET implements `Echo#GET()` for sub-routes within the Group.
|
||||
func (g *Group) GET(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodGet, path, h, m...)
|
||||
}
|
||||
|
||||
// HEAD implements `Echo#HEAD()` for sub-routes within the Group.
|
||||
func (g *Group) HEAD(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodHead, path, h, m...)
|
||||
}
|
||||
|
||||
// OPTIONS implements `Echo#OPTIONS()` for sub-routes within the Group.
|
||||
func (g *Group) OPTIONS(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodOptions, path, h, m...)
|
||||
}
|
||||
|
||||
// PATCH implements `Echo#PATCH()` for sub-routes within the Group.
|
||||
func (g *Group) PATCH(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPatch, path, h, m...)
|
||||
}
|
||||
|
||||
// POST implements `Echo#POST()` for sub-routes within the Group.
|
||||
func (g *Group) POST(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPost, path, h, m...)
|
||||
}
|
||||
|
||||
// PUT implements `Echo#PUT()` for sub-routes within the Group.
|
||||
func (g *Group) PUT(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodPut, path, h, m...)
|
||||
}
|
||||
|
||||
// TRACE implements `Echo#TRACE()` for sub-routes within the Group.
|
||||
func (g *Group) TRACE(path string, h HandlerFunc, m ...MiddlewareFunc) *Route {
|
||||
return g.Add(http.MethodTrace, path, h, m...)
|
||||
}
|
||||
|
||||
// Any implements `Echo#Any()` for sub-routes within the Group.
|
||||
func (g *Group) Any(path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Match implements `Echo#Match()` for sub-routes within the Group.
|
||||
func (g *Group) Match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) []*Route {
|
||||
routes := make([]*Route, len(methods))
|
||||
for i, m := range methods {
|
||||
routes[i] = g.Add(m, path, handler, middleware...)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
|
||||
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
|
||||
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||
m = append(m, g.middleware...)
|
||||
m = append(m, middleware...)
|
||||
return g.echo.Group(g.prefix+prefix, m...)
|
||||
}
|
||||
|
||||
// Static implements `Echo#Static()` for sub-routes within the Group.
|
||||
func (g *Group) Static(prefix, root string) {
|
||||
static(g, prefix, root)
|
||||
}
|
||||
|
||||
// File implements `Echo#File()` for sub-routes within the Group.
|
||||
func (g *Group) File(path, file string) {
|
||||
g.echo.File(g.prefix+path, file)
|
||||
}
|
||||
|
||||
// Add implements `Echo#Add()` for sub-routes within the Group.
|
||||
func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route {
|
||||
// Combine into a new slice to avoid accidentally passing the same slice for
|
||||
// multiple routes, which would lead to later add() calls overwriting the
|
||||
// middleware from earlier calls.
|
||||
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||
m = append(m, g.middleware...)
|
||||
m = append(m, middleware...)
|
||||
return g.echo.Add(method, g.prefix+path, handler, m...)
|
||||
}
|
||||
41
vendor/github.com/labstack/echo/log.go
generated
vendored
41
vendor/github.com/labstack/echo/log.go
generated
vendored
@@ -1,41 +0,0 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/labstack/gommon/log"
|
||||
)
|
||||
|
||||
type (
|
||||
// Logger defines the logging interface.
|
||||
Logger interface {
|
||||
Output() io.Writer
|
||||
SetOutput(w io.Writer)
|
||||
Prefix() string
|
||||
SetPrefix(p string)
|
||||
Level() log.Lvl
|
||||
SetLevel(v log.Lvl)
|
||||
SetHeader(h string)
|
||||
Print(i ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Printj(j log.JSON)
|
||||
Debug(i ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Debugj(j log.JSON)
|
||||
Info(i ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Infoj(j log.JSON)
|
||||
Warn(i ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warnj(j log.JSON)
|
||||
Error(i ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Errorj(j log.JSON)
|
||||
Fatal(i ...interface{})
|
||||
Fatalj(j log.JSON)
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panic(i ...interface{})
|
||||
Panicj(j log.JSON)
|
||||
Panicf(format string, args ...interface{})
|
||||
}
|
||||
)
|
||||
106
vendor/github.com/labstack/echo/middleware/basic_auth.go
generated
vendored
106
vendor/github.com/labstack/echo/middleware/basic_auth.go
generated
vendored
@@ -1,106 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// BasicAuthConfig defines the config for BasicAuth middleware.
|
||||
BasicAuthConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Validator is a function to validate BasicAuth credentials.
|
||||
// Required.
|
||||
Validator BasicAuthValidator
|
||||
|
||||
// Realm is a string to define realm attribute of BasicAuth.
|
||||
// Default value "Restricted".
|
||||
Realm string
|
||||
}
|
||||
|
||||
// BasicAuthValidator defines a function to validate BasicAuth credentials.
|
||||
BasicAuthValidator func(string, string, echo.Context) (bool, error)
|
||||
)
|
||||
|
||||
const (
|
||||
basic = "basic"
|
||||
defaultRealm = "Restricted"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBasicAuthConfig is the default BasicAuth middleware config.
|
||||
DefaultBasicAuthConfig = BasicAuthConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Realm: defaultRealm,
|
||||
}
|
||||
)
|
||||
|
||||
// BasicAuth returns an BasicAuth middleware.
|
||||
//
|
||||
// For valid credentials it calls the next handler.
|
||||
// For missing or invalid credentials, it sends "401 - Unauthorized" response.
|
||||
func BasicAuth(fn BasicAuthValidator) echo.MiddlewareFunc {
|
||||
c := DefaultBasicAuthConfig
|
||||
c.Validator = fn
|
||||
return BasicAuthWithConfig(c)
|
||||
}
|
||||
|
||||
// BasicAuthWithConfig returns an BasicAuth middleware with config.
|
||||
// See `BasicAuth()`.
|
||||
func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Validator == nil {
|
||||
panic("echo: basic-auth middleware requires a validator function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBasicAuthConfig.Skipper
|
||||
}
|
||||
if config.Realm == "" {
|
||||
config.Realm = defaultRealm
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
auth := c.Request().Header.Get(echo.HeaderAuthorization)
|
||||
l := len(basic)
|
||||
|
||||
if len(auth) > l+1 && strings.ToLower(auth[:l]) == basic {
|
||||
b, err := base64.StdEncoding.DecodeString(auth[l+1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cred := string(b)
|
||||
for i := 0; i < len(cred); i++ {
|
||||
if cred[i] == ':' {
|
||||
// Verify credentials
|
||||
valid, err := config.Validator(cred[:i], cred[i+1:], c)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if valid {
|
||||
return next(c)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
realm := defaultRealm
|
||||
if config.Realm != defaultRealm {
|
||||
realm = strconv.Quote(config.Realm)
|
||||
}
|
||||
|
||||
// Need to return `401` for browsers to pop-up login box.
|
||||
c.Response().Header().Set(echo.HeaderWWWAuthenticate, basic+" realm="+realm)
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
}
|
||||
}
|
||||
111
vendor/github.com/labstack/echo/middleware/body_dump.go
generated
vendored
111
vendor/github.com/labstack/echo/middleware/body_dump.go
generated
vendored
@@ -1,111 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// BodyDumpConfig defines the config for BodyDump middleware.
|
||||
BodyDumpConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Handler receives request and response payload.
|
||||
// Required.
|
||||
Handler BodyDumpHandler
|
||||
}
|
||||
|
||||
// BodyDumpHandler receives the request and response payload.
|
||||
BodyDumpHandler func(echo.Context, []byte, []byte)
|
||||
|
||||
bodyDumpResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBodyDumpConfig is the default BodyDump middleware config.
|
||||
DefaultBodyDumpConfig = BodyDumpConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
// BodyDump returns a BodyDump middleware.
|
||||
//
|
||||
// BodyLimit middleware captures the request and response payload and calls the
|
||||
// registered handler.
|
||||
func BodyDump(handler BodyDumpHandler) echo.MiddlewareFunc {
|
||||
c := DefaultBodyDumpConfig
|
||||
c.Handler = handler
|
||||
return BodyDumpWithConfig(c)
|
||||
}
|
||||
|
||||
// BodyDumpWithConfig returns a BodyDump middleware with config.
|
||||
// See: `BodyDump()`.
|
||||
func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Handler == nil {
|
||||
panic("echo: body-dump middleware requires a handler function")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Request
|
||||
reqBody := []byte{}
|
||||
if c.Request().Body != nil { // Read
|
||||
reqBody, _ = ioutil.ReadAll(c.Request().Body)
|
||||
}
|
||||
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset
|
||||
|
||||
// Response
|
||||
resBody := new(bytes.Buffer)
|
||||
mw := io.MultiWriter(c.Response().Writer, resBody)
|
||||
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
|
||||
c.Response().Writer = writer
|
||||
|
||||
if err = next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
|
||||
// Callback
|
||||
config.Handler(c, reqBody, resBody.Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) WriteHeader(code int) {
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Flush() {
|
||||
w.ResponseWriter.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (w *bodyDumpResponseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
117
vendor/github.com/labstack/echo/middleware/body_limit.go
generated
vendored
117
vendor/github.com/labstack/echo/middleware/body_limit.go
generated
vendored
@@ -1,117 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/bytes"
|
||||
)
|
||||
|
||||
type (
|
||||
// BodyLimitConfig defines the config for BodyLimit middleware.
|
||||
BodyLimitConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Maximum allowed size for a request body, it can be specified
|
||||
// as `4x` or `4xB`, where x is one of the multiple from K, M, G, T or P.
|
||||
Limit string `yaml:"limit"`
|
||||
limit int64
|
||||
}
|
||||
|
||||
limitedReader struct {
|
||||
BodyLimitConfig
|
||||
reader io.ReadCloser
|
||||
read int64
|
||||
context echo.Context
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultBodyLimitConfig is the default BodyLimit middleware config.
|
||||
DefaultBodyLimitConfig = BodyLimitConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
// BodyLimit returns a BodyLimit middleware.
|
||||
//
|
||||
// BodyLimit middleware sets the maximum allowed size for a request body, if the
|
||||
// size exceeds the configured limit, it sends "413 - Request Entity Too Large"
|
||||
// response. The BodyLimit is determined based on both `Content-Length` request
|
||||
// header and actual content read, which makes it super secure.
|
||||
// Limit can be specified as `4x` or `4xB`, where x is one of the multiple from K, M,
|
||||
// G, T or P.
|
||||
func BodyLimit(limit string) echo.MiddlewareFunc {
|
||||
c := DefaultBodyLimitConfig
|
||||
c.Limit = limit
|
||||
return BodyLimitWithConfig(c)
|
||||
}
|
||||
|
||||
// BodyLimitWithConfig returns a BodyLimit middleware with config.
|
||||
// See: `BodyLimit()`.
|
||||
func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyLimitConfig.Skipper
|
||||
}
|
||||
|
||||
limit, err := bytes.Parse(config.Limit)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("echo: invalid body-limit=%s", config.Limit))
|
||||
}
|
||||
config.limit = limit
|
||||
pool := limitedReaderPool(config)
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
|
||||
// Based on content length
|
||||
if req.ContentLength > config.limit {
|
||||
return echo.ErrStatusRequestEntityTooLarge
|
||||
}
|
||||
|
||||
// Based on content read
|
||||
r := pool.Get().(*limitedReader)
|
||||
r.Reset(req.Body, c)
|
||||
defer pool.Put(r)
|
||||
req.Body = r
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *limitedReader) Read(b []byte) (n int, err error) {
|
||||
n, err = r.reader.Read(b)
|
||||
r.read += int64(n)
|
||||
if r.read > r.limit {
|
||||
return n, echo.ErrStatusRequestEntityTooLarge
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *limitedReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
||||
|
||||
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
|
||||
r.reader = reader
|
||||
r.context = context
|
||||
r.read = 0
|
||||
}
|
||||
|
||||
func limitedReaderPool(c BodyLimitConfig) sync.Pool {
|
||||
return sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &limitedReader{BodyLimitConfig: c}
|
||||
},
|
||||
}
|
||||
}
|
||||
122
vendor/github.com/labstack/echo/middleware/compress.go
generated
vendored
122
vendor/github.com/labstack/echo/middleware/compress.go
generated
vendored
@@ -1,122 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// GzipConfig defines the config for Gzip middleware.
|
||||
GzipConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Gzip compression level.
|
||||
// Optional. Default value -1.
|
||||
Level int `yaml:"level"`
|
||||
}
|
||||
|
||||
gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
gzipScheme = "gzip"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultGzipConfig is the default Gzip middleware config.
|
||||
DefaultGzipConfig = GzipConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Level: -1,
|
||||
}
|
||||
)
|
||||
|
||||
// Gzip returns a middleware which compresses HTTP response using gzip compression
|
||||
// scheme.
|
||||
func Gzip() echo.MiddlewareFunc {
|
||||
return GzipWithConfig(DefaultGzipConfig)
|
||||
}
|
||||
|
||||
// GzipWithConfig return Gzip middleware with config.
|
||||
// See: `Gzip()`.
|
||||
func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultGzipConfig.Skipper
|
||||
}
|
||||
if config.Level == 0 {
|
||||
config.Level = DefaultGzipConfig.Level
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
res := c.Response()
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding)
|
||||
if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {
|
||||
res.Header().Set(echo.HeaderContentEncoding, gzipScheme) // Issue #806
|
||||
rw := res.Writer
|
||||
w, err := gzip.NewWriterLevel(rw, config.Level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if res.Size == 0 {
|
||||
if res.Header().Get(echo.HeaderContentEncoding) == gzipScheme {
|
||||
res.Header().Del(echo.HeaderContentEncoding)
|
||||
}
|
||||
// We have to reset response to it's pristine state when
|
||||
// nothing is written to body or error is returned.
|
||||
// See issue #424, #407.
|
||||
res.Writer = rw
|
||||
w.Reset(ioutil.Discard)
|
||||
}
|
||||
w.Close()
|
||||
}()
|
||||
grw := &gzipResponseWriter{Writer: w, ResponseWriter: rw}
|
||||
res.Writer = grw
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) WriteHeader(code int) {
|
||||
if code == http.StatusNoContent { // Issue #489
|
||||
w.ResponseWriter.Header().Del(echo.HeaderContentEncoding)
|
||||
}
|
||||
w.Header().Del(echo.HeaderContentLength) // Issue #444
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
if w.Header().Get(echo.HeaderContentType) == "" {
|
||||
w.Header().Set(echo.HeaderContentType, http.DetectContentType(b))
|
||||
}
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Flush() {
|
||||
w.Writer.(*gzip.Writer).Flush()
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
func (w *gzipResponseWriter) CloseNotify() <-chan bool {
|
||||
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
143
vendor/github.com/labstack/echo/middleware/cors.go
generated
vendored
143
vendor/github.com/labstack/echo/middleware/cors.go
generated
vendored
@@ -1,143 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// CORSConfig defines the config for CORS middleware.
|
||||
CORSConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// AllowOrigin defines a list of origins that may access the resource.
|
||||
// Optional. Default value []string{"*"}.
|
||||
AllowOrigins []string `yaml:"allow_origins"`
|
||||
|
||||
// AllowMethods defines a list methods allowed when accessing the resource.
|
||||
// This is used in response to a preflight request.
|
||||
// Optional. Default value DefaultCORSConfig.AllowMethods.
|
||||
AllowMethods []string `yaml:"allow_methods"`
|
||||
|
||||
// AllowHeaders defines a list of request headers that can be used when
|
||||
// making the actual request. This is in response to a preflight request.
|
||||
// Optional. Default value []string{}.
|
||||
AllowHeaders []string `yaml:"allow_headers"`
|
||||
|
||||
// AllowCredentials indicates whether or not the response to the request
|
||||
// can be exposed when the credentials flag is true. When used as part of
|
||||
// a response to a preflight request, this indicates whether or not the
|
||||
// actual request can be made using credentials.
|
||||
// Optional. Default value false.
|
||||
AllowCredentials bool `yaml:"allow_credentials"`
|
||||
|
||||
// ExposeHeaders defines a whitelist headers that clients are allowed to
|
||||
// access.
|
||||
// Optional. Default value []string{}.
|
||||
ExposeHeaders []string `yaml:"expose_headers"`
|
||||
|
||||
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||
// can be cached.
|
||||
// Optional. Default value 0.
|
||||
MaxAge int `yaml:"max_age"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultCORSConfig is the default CORS middleware config.
|
||||
DefaultCORSConfig = CORSConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
|
||||
}
|
||||
)
|
||||
|
||||
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
|
||||
// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
|
||||
func CORS() echo.MiddlewareFunc {
|
||||
return CORSWithConfig(DefaultCORSConfig)
|
||||
}
|
||||
|
||||
// CORSWithConfig returns a CORS middleware with config.
|
||||
// See: `CORS()`.
|
||||
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultCORSConfig.Skipper
|
||||
}
|
||||
if len(config.AllowOrigins) == 0 {
|
||||
config.AllowOrigins = DefaultCORSConfig.AllowOrigins
|
||||
}
|
||||
if len(config.AllowMethods) == 0 {
|
||||
config.AllowMethods = DefaultCORSConfig.AllowMethods
|
||||
}
|
||||
|
||||
allowMethods := strings.Join(config.AllowMethods, ",")
|
||||
allowHeaders := strings.Join(config.AllowHeaders, ",")
|
||||
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
|
||||
maxAge := strconv.Itoa(config.MaxAge)
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
origin := req.Header.Get(echo.HeaderOrigin)
|
||||
allowOrigin := ""
|
||||
|
||||
// Check allowed origins
|
||||
for _, o := range config.AllowOrigins {
|
||||
if o == "*" && config.AllowCredentials {
|
||||
allowOrigin = origin
|
||||
break
|
||||
}
|
||||
if o == "*" || o == origin {
|
||||
allowOrigin = o
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Simple request
|
||||
if req.Method != http.MethodOptions {
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
|
||||
if config.AllowCredentials {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
|
||||
}
|
||||
if exposeHeaders != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlExposeHeaders, exposeHeaders)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Preflight request
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderOrigin)
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestMethod)
|
||||
res.Header().Add(echo.HeaderVary, echo.HeaderAccessControlRequestHeaders)
|
||||
res.Header().Set(echo.HeaderAccessControlAllowOrigin, allowOrigin)
|
||||
res.Header().Set(echo.HeaderAccessControlAllowMethods, allowMethods)
|
||||
if config.AllowCredentials {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowCredentials, "true")
|
||||
}
|
||||
if allowHeaders != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, allowHeaders)
|
||||
} else {
|
||||
h := req.Header.Get(echo.HeaderAccessControlRequestHeaders)
|
||||
if h != "" {
|
||||
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
|
||||
}
|
||||
}
|
||||
if config.MaxAge > 0 {
|
||||
res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
|
||||
}
|
||||
return c.NoContent(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
210
vendor/github.com/labstack/echo/middleware/csrf.go
generated
vendored
210
vendor/github.com/labstack/echo/middleware/csrf.go
generated
vendored
@@ -1,210 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/random"
|
||||
)
|
||||
|
||||
type (
|
||||
// CSRFConfig defines the config for CSRF middleware.
|
||||
CSRFConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// TokenLength is the length of the generated token.
|
||||
TokenLength uint8 `yaml:"token_length"`
|
||||
// Optional. Default value 32.
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<key>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:X-CSRF-Token".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "form:<name>"
|
||||
// - "query:<name>"
|
||||
TokenLookup string `yaml:"token_lookup"`
|
||||
|
||||
// Context key to store generated CSRF token into context.
|
||||
// Optional. Default value "csrf".
|
||||
ContextKey string `yaml:"context_key"`
|
||||
|
||||
// Name of the CSRF cookie. This cookie will store CSRF token.
|
||||
// Optional. Default value "csrf".
|
||||
CookieName string `yaml:"cookie_name"`
|
||||
|
||||
// Domain of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookieDomain string `yaml:"cookie_domain"`
|
||||
|
||||
// Path of the CSRF cookie.
|
||||
// Optional. Default value none.
|
||||
CookiePath string `yaml:"cookie_path"`
|
||||
|
||||
// Max age (in seconds) of the CSRF cookie.
|
||||
// Optional. Default value 86400 (24hr).
|
||||
CookieMaxAge int `yaml:"cookie_max_age"`
|
||||
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool `yaml:"cookie_secure"`
|
||||
|
||||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool `yaml:"cookie_http_only"`
|
||||
}
|
||||
|
||||
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
|
||||
// either a token or an error.
|
||||
csrfTokenExtractor func(echo.Context) (string, error)
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultCSRFConfig is the default CSRF middleware config.
|
||||
DefaultCSRFConfig = CSRFConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
TokenLength: 32,
|
||||
TokenLookup: "header:" + echo.HeaderXCSRFToken,
|
||||
ContextKey: "csrf",
|
||||
CookieName: "_csrf",
|
||||
CookieMaxAge: 86400,
|
||||
}
|
||||
)
|
||||
|
||||
// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
|
||||
// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
|
||||
func CSRF() echo.MiddlewareFunc {
|
||||
c := DefaultCSRFConfig
|
||||
return CSRFWithConfig(c)
|
||||
}
|
||||
|
||||
// CSRFWithConfig returns a CSRF middleware with config.
|
||||
// See `CSRF()`.
|
||||
func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultCSRFConfig.Skipper
|
||||
}
|
||||
if config.TokenLength == 0 {
|
||||
config.TokenLength = DefaultCSRFConfig.TokenLength
|
||||
}
|
||||
if config.TokenLookup == "" {
|
||||
config.TokenLookup = DefaultCSRFConfig.TokenLookup
|
||||
}
|
||||
if config.ContextKey == "" {
|
||||
config.ContextKey = DefaultCSRFConfig.ContextKey
|
||||
}
|
||||
if config.CookieName == "" {
|
||||
config.CookieName = DefaultCSRFConfig.CookieName
|
||||
}
|
||||
if config.CookieMaxAge == 0 {
|
||||
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
|
||||
}
|
||||
|
||||
// Initialize
|
||||
parts := strings.Split(config.TokenLookup, ":")
|
||||
extractor := csrfTokenFromHeader(parts[1])
|
||||
switch parts[0] {
|
||||
case "form":
|
||||
extractor = csrfTokenFromForm(parts[1])
|
||||
case "query":
|
||||
extractor = csrfTokenFromQuery(parts[1])
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
k, err := c.Cookie(config.CookieName)
|
||||
token := ""
|
||||
|
||||
// Generate token
|
||||
if err != nil {
|
||||
token = random.String(config.TokenLength)
|
||||
} else {
|
||||
// Reuse token
|
||||
token = k.Value
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace:
|
||||
default:
|
||||
// Validate token only for requests which are not defined as 'safe' by RFC7231
|
||||
clientToken, err := extractor(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if !validateCSRFToken(token, clientToken) {
|
||||
return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
|
||||
}
|
||||
}
|
||||
|
||||
// Set CSRF cookie
|
||||
cookie := new(http.Cookie)
|
||||
cookie.Name = config.CookieName
|
||||
cookie.Value = token
|
||||
if config.CookiePath != "" {
|
||||
cookie.Path = config.CookiePath
|
||||
}
|
||||
if config.CookieDomain != "" {
|
||||
cookie.Domain = config.CookieDomain
|
||||
}
|
||||
cookie.Expires = time.Now().Add(time.Duration(config.CookieMaxAge) * time.Second)
|
||||
cookie.Secure = config.CookieSecure
|
||||
cookie.HttpOnly = config.CookieHTTPOnly
|
||||
c.SetCookie(cookie)
|
||||
|
||||
// Store token in the context
|
||||
c.Set(config.ContextKey, token)
|
||||
|
||||
// Protect clients from caching the response
|
||||
c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
|
||||
// provided request header.
|
||||
func csrfTokenFromHeader(header string) csrfTokenExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
return c.Request().Header.Get(header), nil
|
||||
}
|
||||
}
|
||||
|
||||
// csrfTokenFromForm returns a `csrfTokenExtractor` that extracts token from the
|
||||
// provided form parameter.
|
||||
func csrfTokenFromForm(param string) csrfTokenExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
token := c.FormValue(param)
|
||||
if token == "" {
|
||||
return "", errors.New("missing csrf token in the form parameter")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
// csrfTokenFromQuery returns a `csrfTokenExtractor` that extracts token from the
|
||||
// provided query parameter.
|
||||
func csrfTokenFromQuery(param string) csrfTokenExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
token := c.QueryParam(param)
|
||||
if token == "" {
|
||||
return "", errors.New("missing csrf token in the query string")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateCSRFToken(token, clientToken string) bool {
|
||||
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
|
||||
}
|
||||
227
vendor/github.com/labstack/echo/middleware/jwt.go
generated
vendored
227
vendor/github.com/labstack/echo/middleware/jwt.go
generated
vendored
@@ -1,227 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// JWTConfig defines the config for JWT middleware.
|
||||
JWTConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// BeforeFunc defines a function which is executed just before the middleware.
|
||||
BeforeFunc BeforeFunc
|
||||
|
||||
// SuccessHandler defines a function which is executed for a valid token.
|
||||
SuccessHandler JWTSuccessHandler
|
||||
|
||||
// ErrorHandler defines a function which is executed for an invalid token.
|
||||
// It may be used to define a custom JWT error.
|
||||
ErrorHandler JWTErrorHandler
|
||||
|
||||
// Signing key to validate token.
|
||||
// Required.
|
||||
SigningKey interface{}
|
||||
|
||||
// Signing method, used to check token signing method.
|
||||
// Optional. Default value HS256.
|
||||
SigningMethod string
|
||||
|
||||
// Context key to store user information from the token into context.
|
||||
// Optional. Default value "user".
|
||||
ContextKey string
|
||||
|
||||
// Claims are extendable claims data defining token content.
|
||||
// Optional. Default value jwt.MapClaims
|
||||
Claims jwt.Claims
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "cookie:<name>"
|
||||
TokenLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
|
||||
keyFunc jwt.Keyfunc
|
||||
}
|
||||
|
||||
// JWTSuccessHandler defines a function which is executed for a valid token.
|
||||
JWTSuccessHandler func(echo.Context)
|
||||
|
||||
// JWTErrorHandler defines a function which is executed for an invalid token.
|
||||
JWTErrorHandler func(error) error
|
||||
|
||||
jwtExtractor func(echo.Context) (string, error)
|
||||
)
|
||||
|
||||
// Algorithms
|
||||
const (
|
||||
AlgorithmHS256 = "HS256"
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultJWTConfig is the default JWT auth middleware config.
|
||||
DefaultJWTConfig = JWTConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
SigningMethod: AlgorithmHS256,
|
||||
ContextKey: "user",
|
||||
TokenLookup: "header:" + echo.HeaderAuthorization,
|
||||
AuthScheme: "Bearer",
|
||||
Claims: jwt.MapClaims{},
|
||||
}
|
||||
)
|
||||
|
||||
// JWT returns a JSON Web Token (JWT) auth middleware.
|
||||
//
|
||||
// For valid token, it sets the user in context and calls next handler.
|
||||
// For invalid token, it returns "401 - Unauthorized" error.
|
||||
// For missing token, it returns "400 - Bad Request" error.
|
||||
//
|
||||
// See: https://jwt.io/introduction
|
||||
// See `JWTConfig.TokenLookup`
|
||||
func JWT(key interface{}) echo.MiddlewareFunc {
|
||||
c := DefaultJWTConfig
|
||||
c.SigningKey = key
|
||||
return JWTWithConfig(c)
|
||||
}
|
||||
|
||||
// JWTWithConfig returns a JWT auth middleware with config.
|
||||
// See: `JWT()`.
|
||||
func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultJWTConfig.Skipper
|
||||
}
|
||||
if config.SigningKey == nil {
|
||||
panic("echo: jwt middleware requires signing key")
|
||||
}
|
||||
if config.SigningMethod == "" {
|
||||
config.SigningMethod = DefaultJWTConfig.SigningMethod
|
||||
}
|
||||
if config.ContextKey == "" {
|
||||
config.ContextKey = DefaultJWTConfig.ContextKey
|
||||
}
|
||||
if config.Claims == nil {
|
||||
config.Claims = DefaultJWTConfig.Claims
|
||||
}
|
||||
if config.TokenLookup == "" {
|
||||
config.TokenLookup = DefaultJWTConfig.TokenLookup
|
||||
}
|
||||
if config.AuthScheme == "" {
|
||||
config.AuthScheme = DefaultJWTConfig.AuthScheme
|
||||
}
|
||||
config.keyFunc = func(t *jwt.Token) (interface{}, error) {
|
||||
// Check the signing method
|
||||
if t.Method.Alg() != config.SigningMethod {
|
||||
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
|
||||
}
|
||||
return config.SigningKey, nil
|
||||
}
|
||||
|
||||
// Initialize
|
||||
parts := strings.Split(config.TokenLookup, ":")
|
||||
extractor := jwtFromHeader(parts[1], config.AuthScheme)
|
||||
switch parts[0] {
|
||||
case "query":
|
||||
extractor = jwtFromQuery(parts[1])
|
||||
case "cookie":
|
||||
extractor = jwtFromCookie(parts[1])
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if config.BeforeFunc != nil {
|
||||
config.BeforeFunc(c)
|
||||
}
|
||||
|
||||
auth, err := extractor(c)
|
||||
if err != nil {
|
||||
if config.ErrorHandler != nil {
|
||||
return config.ErrorHandler(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
token := new(jwt.Token)
|
||||
// Issue #647, #656
|
||||
if _, ok := config.Claims.(jwt.MapClaims); ok {
|
||||
token, err = jwt.Parse(auth, config.keyFunc)
|
||||
} else {
|
||||
t := reflect.ValueOf(config.Claims).Type().Elem()
|
||||
claims := reflect.New(t).Interface().(jwt.Claims)
|
||||
token, err = jwt.ParseWithClaims(auth, claims, config.keyFunc)
|
||||
}
|
||||
if err == nil && token.Valid {
|
||||
// Store user information from token into context.
|
||||
c.Set(config.ContextKey, token)
|
||||
if config.SuccessHandler != nil {
|
||||
config.SuccessHandler(c)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
if config.ErrorHandler != nil {
|
||||
return config.ErrorHandler(err)
|
||||
}
|
||||
return &echo.HTTPError{
|
||||
Code: http.StatusUnauthorized,
|
||||
Message: "invalid or expired jwt",
|
||||
Internal: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// jwtFromHeader returns a `jwtExtractor` that extracts token from the request header.
|
||||
func jwtFromHeader(header string, authScheme string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
auth := c.Request().Header.Get(header)
|
||||
l := len(authScheme)
|
||||
if len(auth) > l+1 && auth[:l] == authScheme {
|
||||
return auth[l+1:], nil
|
||||
}
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
}
|
||||
|
||||
// jwtFromQuery returns a `jwtExtractor` that extracts token from the query string.
|
||||
func jwtFromQuery(param string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
token := c.QueryParam(param)
|
||||
if token == "" {
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
}
|
||||
|
||||
// jwtFromCookie returns a `jwtExtractor` that extracts token from the named cookie.
|
||||
func jwtFromCookie(name string) jwtExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
cookie, err := c.Cookie(name)
|
||||
if err != nil {
|
||||
return "", ErrJWTMissing
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
150
vendor/github.com/labstack/echo/middleware/key_auth.go
generated
vendored
150
vendor/github.com/labstack/echo/middleware/key_auth.go
generated
vendored
@@ -1,150 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// KeyAuthConfig defines the config for KeyAuth middleware.
|
||||
KeyAuthConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// KeyLookup is a string in the form of "<source>:<name>" that is used
|
||||
// to extract key from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "form:<name>"
|
||||
KeyLookup string `yaml:"key_lookup"`
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
|
||||
// Validator is a function to validate key.
|
||||
// Required.
|
||||
Validator KeyAuthValidator
|
||||
}
|
||||
|
||||
// KeyAuthValidator defines a function to validate KeyAuth credentials.
|
||||
KeyAuthValidator func(string, echo.Context) (bool, error)
|
||||
|
||||
keyExtractor func(echo.Context) (string, error)
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
|
||||
DefaultKeyAuthConfig = KeyAuthConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
KeyLookup: "header:" + echo.HeaderAuthorization,
|
||||
AuthScheme: "Bearer",
|
||||
}
|
||||
)
|
||||
|
||||
// KeyAuth returns an KeyAuth middleware.
|
||||
//
|
||||
// For valid key it calls the next handler.
|
||||
// For invalid key, it sends "401 - Unauthorized" response.
|
||||
// For missing key, it sends "400 - Bad Request" response.
|
||||
func KeyAuth(fn KeyAuthValidator) echo.MiddlewareFunc {
|
||||
c := DefaultKeyAuthConfig
|
||||
c.Validator = fn
|
||||
return KeyAuthWithConfig(c)
|
||||
}
|
||||
|
||||
// KeyAuthWithConfig returns an KeyAuth middleware with config.
|
||||
// See `KeyAuth()`.
|
||||
func KeyAuthWithConfig(config KeyAuthConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultKeyAuthConfig.Skipper
|
||||
}
|
||||
// Defaults
|
||||
if config.AuthScheme == "" {
|
||||
config.AuthScheme = DefaultKeyAuthConfig.AuthScheme
|
||||
}
|
||||
if config.KeyLookup == "" {
|
||||
config.KeyLookup = DefaultKeyAuthConfig.KeyLookup
|
||||
}
|
||||
if config.Validator == nil {
|
||||
panic("echo: key-auth middleware requires a validator function")
|
||||
}
|
||||
|
||||
// Initialize
|
||||
parts := strings.Split(config.KeyLookup, ":")
|
||||
extractor := keyFromHeader(parts[1], config.AuthScheme)
|
||||
switch parts[0] {
|
||||
case "query":
|
||||
extractor = keyFromQuery(parts[1])
|
||||
case "form":
|
||||
extractor = keyFromForm(parts[1])
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// Extract and verify key
|
||||
key, err := extractor(c)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
valid, err := config.Validator(key, c)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if valid {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
return echo.ErrUnauthorized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keyFromHeader returns a `keyExtractor` that extracts key from the request header.
|
||||
func keyFromHeader(header string, authScheme string) keyExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
auth := c.Request().Header.Get(header)
|
||||
if auth == "" {
|
||||
return "", errors.New("missing key in request header")
|
||||
}
|
||||
if header == echo.HeaderAuthorization {
|
||||
l := len(authScheme)
|
||||
if len(auth) > l+1 && auth[:l] == authScheme {
|
||||
return auth[l+1:], nil
|
||||
}
|
||||
return "", errors.New("invalid key in the request header")
|
||||
}
|
||||
return auth, nil
|
||||
}
|
||||
}
|
||||
|
||||
// keyFromQuery returns a `keyExtractor` that extracts key from the query string.
|
||||
func keyFromQuery(param string) keyExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
key := c.QueryParam(param)
|
||||
if key == "" {
|
||||
return "", errors.New("missing key in the query string")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
|
||||
// keyFromForm returns a `keyExtractor` that extracts key from the form.
|
||||
func keyFromForm(param string) keyExtractor {
|
||||
return func(c echo.Context) (string, error) {
|
||||
key := c.FormValue(param)
|
||||
if key == "" {
|
||||
return "", errors.New("missing key in the form")
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
217
vendor/github.com/labstack/echo/middleware/logger.go
generated
vendored
217
vendor/github.com/labstack/echo/middleware/logger.go
generated
vendored
@@ -1,217 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/color"
|
||||
"github.com/valyala/fasttemplate"
|
||||
)
|
||||
|
||||
type (
|
||||
// LoggerConfig defines the config for Logger middleware.
|
||||
LoggerConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Tags to constructed the logger format.
|
||||
//
|
||||
// - time_unix
|
||||
// - time_unix_nano
|
||||
// - time_rfc3339
|
||||
// - time_rfc3339_nano
|
||||
// - time_custom
|
||||
// - id (Request ID)
|
||||
// - remote_ip
|
||||
// - uri
|
||||
// - host
|
||||
// - method
|
||||
// - path
|
||||
// - protocol
|
||||
// - referer
|
||||
// - user_agent
|
||||
// - status
|
||||
// - error
|
||||
// - latency (In nanoseconds)
|
||||
// - latency_human (Human readable)
|
||||
// - bytes_in (Bytes received)
|
||||
// - bytes_out (Bytes sent)
|
||||
// - header:<NAME>
|
||||
// - query:<NAME>
|
||||
// - form:<NAME>
|
||||
//
|
||||
// Example "${remote_ip} ${status}"
|
||||
//
|
||||
// Optional. Default value DefaultLoggerConfig.Format.
|
||||
Format string `yaml:"format"`
|
||||
|
||||
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
|
||||
CustomTimeFormat string `yaml:"custom_time_format"`
|
||||
|
||||
// Output is a writer where logs in JSON format are written.
|
||||
// Optional. Default value os.Stdout.
|
||||
Output io.Writer
|
||||
|
||||
template *fasttemplate.Template
|
||||
colorer *color.Color
|
||||
pool *sync.Pool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultLoggerConfig is the default Logger middleware config.
|
||||
DefaultLoggerConfig = LoggerConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
|
||||
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
|
||||
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
|
||||
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
|
||||
CustomTimeFormat: "2006-01-02 15:04:05.00000",
|
||||
Output: os.Stdout,
|
||||
colorer: color.New(),
|
||||
}
|
||||
)
|
||||
|
||||
// Logger returns a middleware that logs HTTP requests.
|
||||
func Logger() echo.MiddlewareFunc {
|
||||
return LoggerWithConfig(DefaultLoggerConfig)
|
||||
}
|
||||
|
||||
// LoggerWithConfig returns a Logger middleware with config.
|
||||
// See: `Logger()`.
|
||||
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultLoggerConfig.Skipper
|
||||
}
|
||||
if config.Format == "" {
|
||||
config.Format = DefaultLoggerConfig.Format
|
||||
}
|
||||
if config.Output == nil {
|
||||
config.Output = DefaultLoggerConfig.Output
|
||||
}
|
||||
|
||||
config.template = fasttemplate.New(config.Format, "${", "}")
|
||||
config.colorer = color.New()
|
||||
config.colorer.SetOutput(config.Output)
|
||||
config.pool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bytes.NewBuffer(make([]byte, 256))
|
||||
},
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
start := time.Now()
|
||||
if err = next(c); err != nil {
|
||||
c.Error(err)
|
||||
}
|
||||
stop := time.Now()
|
||||
buf := config.pool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer config.pool.Put(buf)
|
||||
|
||||
if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
|
||||
switch tag {
|
||||
case "time_unix":
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
case "time_unix_nano":
|
||||
return buf.WriteString(strconv.FormatInt(time.Now().UnixNano(), 10))
|
||||
case "time_rfc3339":
|
||||
return buf.WriteString(time.Now().Format(time.RFC3339))
|
||||
case "time_rfc3339_nano":
|
||||
return buf.WriteString(time.Now().Format(time.RFC3339Nano))
|
||||
case "time_custom":
|
||||
return buf.WriteString(time.Now().Format(config.CustomTimeFormat))
|
||||
case "id":
|
||||
id := req.Header.Get(echo.HeaderXRequestID)
|
||||
if id == "" {
|
||||
id = res.Header().Get(echo.HeaderXRequestID)
|
||||
}
|
||||
return buf.WriteString(id)
|
||||
case "remote_ip":
|
||||
return buf.WriteString(c.RealIP())
|
||||
case "host":
|
||||
return buf.WriteString(req.Host)
|
||||
case "uri":
|
||||
return buf.WriteString(req.RequestURI)
|
||||
case "method":
|
||||
return buf.WriteString(req.Method)
|
||||
case "path":
|
||||
p := req.URL.Path
|
||||
if p == "" {
|
||||
p = "/"
|
||||
}
|
||||
return buf.WriteString(p)
|
||||
case "protocol":
|
||||
return buf.WriteString(req.Proto)
|
||||
case "referer":
|
||||
return buf.WriteString(req.Referer())
|
||||
case "user_agent":
|
||||
return buf.WriteString(req.UserAgent())
|
||||
case "status":
|
||||
n := res.Status
|
||||
s := config.colorer.Green(n)
|
||||
switch {
|
||||
case n >= 500:
|
||||
s = config.colorer.Red(n)
|
||||
case n >= 400:
|
||||
s = config.colorer.Yellow(n)
|
||||
case n >= 300:
|
||||
s = config.colorer.Cyan(n)
|
||||
}
|
||||
return buf.WriteString(s)
|
||||
case "error":
|
||||
if err != nil {
|
||||
return buf.WriteString(err.Error())
|
||||
}
|
||||
case "latency":
|
||||
l := stop.Sub(start)
|
||||
return buf.WriteString(strconv.FormatInt(int64(l), 10))
|
||||
case "latency_human":
|
||||
return buf.WriteString(stop.Sub(start).String())
|
||||
case "bytes_in":
|
||||
cl := req.Header.Get(echo.HeaderContentLength)
|
||||
if cl == "" {
|
||||
cl = "0"
|
||||
}
|
||||
return buf.WriteString(cl)
|
||||
case "bytes_out":
|
||||
return buf.WriteString(strconv.FormatInt(res.Size, 10))
|
||||
default:
|
||||
switch {
|
||||
case strings.HasPrefix(tag, "header:"):
|
||||
return buf.Write([]byte(c.Request().Header.Get(tag[7:])))
|
||||
case strings.HasPrefix(tag, "query:"):
|
||||
return buf.Write([]byte(c.QueryParam(tag[6:])))
|
||||
case strings.HasPrefix(tag, "form:"):
|
||||
return buf.Write([]byte(c.FormValue(tag[5:])))
|
||||
case strings.HasPrefix(tag, "cookie:"):
|
||||
cookie, err := c.Cookie(tag[7:])
|
||||
if err == nil {
|
||||
return buf.Write([]byte(cookie.Value))
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = config.Output.Write(buf.Bytes())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
92
vendor/github.com/labstack/echo/middleware/method_override.go
generated
vendored
92
vendor/github.com/labstack/echo/middleware/method_override.go
generated
vendored
@@ -1,92 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// MethodOverrideConfig defines the config for MethodOverride middleware.
|
||||
MethodOverrideConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Getter is a function that gets overridden method from the request.
|
||||
// Optional. Default values MethodFromHeader(echo.HeaderXHTTPMethodOverride).
|
||||
Getter MethodOverrideGetter
|
||||
}
|
||||
|
||||
// MethodOverrideGetter is a function that gets overridden method from the request
|
||||
MethodOverrideGetter func(echo.Context) string
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
|
||||
DefaultMethodOverrideConfig = MethodOverrideConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
|
||||
}
|
||||
)
|
||||
|
||||
// MethodOverride returns a MethodOverride middleware.
|
||||
// MethodOverride middleware checks for the overridden method from the request and
|
||||
// uses it instead of the original method.
|
||||
//
|
||||
// For security reasons, only `POST` method can be overridden.
|
||||
func MethodOverride() echo.MiddlewareFunc {
|
||||
return MethodOverrideWithConfig(DefaultMethodOverrideConfig)
|
||||
}
|
||||
|
||||
// MethodOverrideWithConfig returns a MethodOverride middleware with config.
|
||||
// See: `MethodOverride()`.
|
||||
func MethodOverrideWithConfig(config MethodOverrideConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultMethodOverrideConfig.Skipper
|
||||
}
|
||||
if config.Getter == nil {
|
||||
config.Getter = DefaultMethodOverrideConfig.Getter
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
if req.Method == http.MethodPost {
|
||||
m := config.Getter(c)
|
||||
if m != "" {
|
||||
req.Method = m
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromHeader is a `MethodOverrideGetter` that gets overridden method from
|
||||
// the request header.
|
||||
func MethodFromHeader(header string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.Request().Header.Get(header)
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromForm is a `MethodOverrideGetter` that gets overridden method from the
|
||||
// form parameter.
|
||||
func MethodFromForm(param string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.FormValue(param)
|
||||
}
|
||||
}
|
||||
|
||||
// MethodFromQuery is a `MethodOverrideGetter` that gets overridden method from
|
||||
// the query parameter.
|
||||
func MethodFromQuery(param string) MethodOverrideGetter {
|
||||
return func(c echo.Context) string {
|
||||
return c.QueryParam(param)
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/labstack/echo/middleware/middleware.go
generated
vendored
38
vendor/github.com/labstack/echo/middleware/middleware.go
generated
vendored
@@ -1,38 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// Skipper defines a function to skip middleware. Returning true skips processing
|
||||
// the middleware.
|
||||
Skipper func(echo.Context) bool
|
||||
|
||||
// BeforeFunc defines a function which is executed just before the middleware.
|
||||
BeforeFunc func(echo.Context)
|
||||
)
|
||||
|
||||
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
|
||||
groups := pattern.FindAllStringSubmatch(input, -1)
|
||||
if groups == nil {
|
||||
return nil
|
||||
}
|
||||
values := groups[0][1:]
|
||||
replace := make([]string, 2*len(values))
|
||||
for i, v := range values {
|
||||
j := 2 * i
|
||||
replace[j] = "$" + strconv.Itoa(i+1)
|
||||
replace[j+1] = v
|
||||
}
|
||||
return strings.NewReplacer(replace...)
|
||||
}
|
||||
|
||||
// DefaultSkipper returns false which processes the middleware.
|
||||
func DefaultSkipper(echo.Context) bool {
|
||||
return false
|
||||
}
|
||||
258
vendor/github.com/labstack/echo/middleware/proxy.go
generated
vendored
258
vendor/github.com/labstack/echo/middleware/proxy.go
generated
vendored
@@ -1,258 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// TODO: Handle TLS proxy
|
||||
|
||||
type (
|
||||
// ProxyConfig defines the config for Proxy middleware.
|
||||
ProxyConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Balancer defines a load balancing technique.
|
||||
// Required.
|
||||
Balancer ProxyBalancer
|
||||
|
||||
// Rewrite defines URL path rewrite rules. The values captured in asterisk can be
|
||||
// retrieved by index e.g. $1, $2 and so on.
|
||||
// Examples:
|
||||
// "/old": "/new",
|
||||
// "/api/*": "/$1",
|
||||
// "/js/*": "/public/javascripts/$1",
|
||||
// "/users/*/orders/*": "/user/$1/order/$2",
|
||||
Rewrite map[string]string
|
||||
|
||||
// Context key to store selected ProxyTarget into context.
|
||||
// Optional. Default value "target".
|
||||
ContextKey string
|
||||
|
||||
// To customize the transport to remote.
|
||||
// Examples: If custom TLS certificates are required.
|
||||
Transport http.RoundTripper
|
||||
|
||||
rewriteRegex map[*regexp.Regexp]string
|
||||
}
|
||||
|
||||
// ProxyTarget defines the upstream target.
|
||||
ProxyTarget struct {
|
||||
Name string
|
||||
URL *url.URL
|
||||
Meta echo.Map
|
||||
}
|
||||
|
||||
// ProxyBalancer defines an interface to implement a load balancing technique.
|
||||
ProxyBalancer interface {
|
||||
AddTarget(*ProxyTarget) bool
|
||||
RemoveTarget(string) bool
|
||||
Next(echo.Context) *ProxyTarget
|
||||
}
|
||||
|
||||
commonBalancer struct {
|
||||
targets []*ProxyTarget
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// RandomBalancer implements a random load balancing technique.
|
||||
randomBalancer struct {
|
||||
*commonBalancer
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
// RoundRobinBalancer implements a round-robin load balancing technique.
|
||||
roundRobinBalancer struct {
|
||||
*commonBalancer
|
||||
i uint32
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultProxyConfig is the default Proxy middleware config.
|
||||
DefaultProxyConfig = ProxyConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
ContextKey: "target",
|
||||
}
|
||||
)
|
||||
|
||||
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
in, _, err := c.Response().Hijack()
|
||||
if err != nil {
|
||||
c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err))
|
||||
return
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := net.Dial("tcp", t.URL.Host)
|
||||
if err != nil {
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err))
|
||||
c.Error(he)
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Write header
|
||||
err = r.Write(out)
|
||||
if err != nil {
|
||||
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err))
|
||||
c.Error(he)
|
||||
return
|
||||
}
|
||||
|
||||
errCh := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err = io.Copy(dst, src)
|
||||
errCh <- err
|
||||
}
|
||||
|
||||
go cp(out, in)
|
||||
go cp(in, out)
|
||||
err = <-errCh
|
||||
if err != nil && err != io.EOF {
|
||||
c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NewRandomBalancer returns a random proxy balancer.
|
||||
func NewRandomBalancer(targets []*ProxyTarget) ProxyBalancer {
|
||||
b := &randomBalancer{commonBalancer: new(commonBalancer)}
|
||||
b.targets = targets
|
||||
return b
|
||||
}
|
||||
|
||||
// NewRoundRobinBalancer returns a round-robin proxy balancer.
|
||||
func NewRoundRobinBalancer(targets []*ProxyTarget) ProxyBalancer {
|
||||
b := &roundRobinBalancer{commonBalancer: new(commonBalancer)}
|
||||
b.targets = targets
|
||||
return b
|
||||
}
|
||||
|
||||
// AddTarget adds an upstream target to the list.
|
||||
func (b *commonBalancer) AddTarget(target *ProxyTarget) bool {
|
||||
for _, t := range b.targets {
|
||||
if t.Name == target.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
b.targets = append(b.targets, target)
|
||||
return true
|
||||
}
|
||||
|
||||
// RemoveTarget removes an upstream target from the list.
|
||||
func (b *commonBalancer) RemoveTarget(name string) bool {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
for i, t := range b.targets {
|
||||
if t.Name == name {
|
||||
b.targets = append(b.targets[:i], b.targets[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Next randomly returns an upstream target.
|
||||
func (b *randomBalancer) Next(c echo.Context) *ProxyTarget {
|
||||
if b.random == nil {
|
||||
b.random = rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
|
||||
}
|
||||
b.mutex.RLock()
|
||||
defer b.mutex.RUnlock()
|
||||
return b.targets[b.random.Intn(len(b.targets))]
|
||||
}
|
||||
|
||||
// Next returns an upstream target using round-robin technique.
|
||||
func (b *roundRobinBalancer) Next(c echo.Context) *ProxyTarget {
|
||||
b.i = b.i % uint32(len(b.targets))
|
||||
t := b.targets[b.i]
|
||||
atomic.AddUint32(&b.i, 1)
|
||||
return t
|
||||
}
|
||||
|
||||
// Proxy returns a Proxy middleware.
|
||||
//
|
||||
// Proxy middleware forwards the request to upstream server using a configured load balancing technique.
|
||||
func Proxy(balancer ProxyBalancer) echo.MiddlewareFunc {
|
||||
c := DefaultProxyConfig
|
||||
c.Balancer = balancer
|
||||
return ProxyWithConfig(c)
|
||||
}
|
||||
|
||||
// ProxyWithConfig returns a Proxy middleware with config.
|
||||
// See: `Proxy()`
|
||||
func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultLoggerConfig.Skipper
|
||||
}
|
||||
if config.Balancer == nil {
|
||||
panic("echo: proxy middleware requires balancer")
|
||||
}
|
||||
config.rewriteRegex = map[*regexp.Regexp]string{}
|
||||
|
||||
// Initialize
|
||||
for k, v := range config.Rewrite {
|
||||
k = strings.Replace(k, "*", "(\\S*)", -1)
|
||||
config.rewriteRegex[regexp.MustCompile(k)] = v
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
tgt := config.Balancer.Next(c)
|
||||
c.Set(config.ContextKey, tgt)
|
||||
|
||||
// Rewrite
|
||||
for k, v := range config.rewriteRegex {
|
||||
replacer := captureTokens(k, req.URL.Path)
|
||||
if replacer != nil {
|
||||
req.URL.Path = replacer.Replace(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Fix header
|
||||
if req.Header.Get(echo.HeaderXRealIP) == "" {
|
||||
req.Header.Set(echo.HeaderXRealIP, c.RealIP())
|
||||
}
|
||||
if req.Header.Get(echo.HeaderXForwardedProto) == "" {
|
||||
req.Header.Set(echo.HeaderXForwardedProto, c.Scheme())
|
||||
}
|
||||
if c.IsWebSocket() && req.Header.Get(echo.HeaderXForwardedFor) == "" { // For HTTP, it is automatically set by Go HTTP reverse proxy.
|
||||
req.Header.Set(echo.HeaderXForwardedFor, c.RealIP())
|
||||
}
|
||||
|
||||
// Proxy
|
||||
switch {
|
||||
case c.IsWebSocket():
|
||||
proxyRaw(tgt, c).ServeHTTP(res, req)
|
||||
case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
|
||||
default:
|
||||
proxyHTTP(tgt, c, config).ServeHTTP(res, req)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/labstack/echo/middleware/proxy_1_11.go
generated
vendored
25
vendor/github.com/labstack/echo/middleware/proxy_1_11.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
// +build go1.11
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
|
||||
proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
|
||||
proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
|
||||
desc := tgt.URL.String()
|
||||
if tgt.Name != "" {
|
||||
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
|
||||
}
|
||||
c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err)
|
||||
c.Error(echo.NewHTTPError(http.StatusServiceUnavailable))
|
||||
}
|
||||
proxy.Transport = config.Transport
|
||||
return proxy
|
||||
}
|
||||
13
vendor/github.com/labstack/echo/middleware/proxy_1_11_n.go
generated
vendored
13
vendor/github.com/labstack/echo/middleware/proxy_1_11_n.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
// +build !go1.11
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
func proxyHTTP(t *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
|
||||
return httputil.NewSingleHostReverseProxy(t.URL)
|
||||
}
|
||||
81
vendor/github.com/labstack/echo/middleware/recover.go
generated
vendored
81
vendor/github.com/labstack/echo/middleware/recover.go
generated
vendored
@@ -1,81 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// RecoverConfig defines the config for Recover middleware.
|
||||
RecoverConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Size of the stack to be printed.
|
||||
// Optional. Default value 4KB.
|
||||
StackSize int `yaml:"stack_size"`
|
||||
|
||||
// DisableStackAll disables formatting stack traces of all other goroutines
|
||||
// into buffer after the trace for the current goroutine.
|
||||
// Optional. Default value false.
|
||||
DisableStackAll bool `yaml:"disable_stack_all"`
|
||||
|
||||
// DisablePrintStack disables printing stack trace.
|
||||
// Optional. Default value as false.
|
||||
DisablePrintStack bool `yaml:"disable_print_stack"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRecoverConfig is the default Recover middleware config.
|
||||
DefaultRecoverConfig = RecoverConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
StackSize: 4 << 10, // 4 KB
|
||||
DisableStackAll: false,
|
||||
DisablePrintStack: false,
|
||||
}
|
||||
)
|
||||
|
||||
// Recover returns a middleware which recovers from panics anywhere in the chain
|
||||
// and handles the control to the centralized HTTPErrorHandler.
|
||||
func Recover() echo.MiddlewareFunc {
|
||||
return RecoverWithConfig(DefaultRecoverConfig)
|
||||
}
|
||||
|
||||
// RecoverWithConfig returns a Recover middleware with config.
|
||||
// See: `Recover()`.
|
||||
func RecoverWithConfig(config RecoverConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultRecoverConfig.Skipper
|
||||
}
|
||||
if config.StackSize == 0 {
|
||||
config.StackSize = DefaultRecoverConfig.StackSize
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
stack := make([]byte, config.StackSize)
|
||||
length := runtime.Stack(stack, !config.DisableStackAll)
|
||||
if !config.DisablePrintStack {
|
||||
c.Logger().Printf("[PANIC RECOVER] %v %s\n", err, stack[:length])
|
||||
}
|
||||
c.Error(err)
|
||||
}
|
||||
}()
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
153
vendor/github.com/labstack/echo/middleware/redirect.go
generated
vendored
153
vendor/github.com/labstack/echo/middleware/redirect.go
generated
vendored
@@ -1,153 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
// RedirectConfig defines the config for Redirect middleware.
|
||||
type RedirectConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional. Default value http.StatusMovedPermanently.
|
||||
Code int `yaml:"code"`
|
||||
}
|
||||
|
||||
// redirectLogic represents a function that given a scheme, host and uri
|
||||
// can both: 1) determine if redirect is needed (will set ok accordingly) and
|
||||
// 2) return the appropriate redirect url.
|
||||
type redirectLogic func(scheme, host, uri string) (ok bool, url string)
|
||||
|
||||
const www = "www"
|
||||
|
||||
// DefaultRedirectConfig is the default Redirect middleware config.
|
||||
var DefaultRedirectConfig = RedirectConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Code: http.StatusMovedPermanently,
|
||||
}
|
||||
|
||||
// HTTPSRedirect redirects http requests to https.
|
||||
// For example, http://labstack.com will be redirect to https://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSRedirect())`
|
||||
func HTTPSRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSRedirect()`.
|
||||
func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if ok = scheme != "https"; ok {
|
||||
url = "https://" + host + uri
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// HTTPSWWWRedirect redirects http requests to https www.
|
||||
// For example, http://labstack.com will be redirect to https://www.labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSWWWRedirect())`
|
||||
func HTTPSWWWRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSWWWRedirect()`.
|
||||
func HTTPSWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if ok = scheme != "https" && host[:3] != www; ok {
|
||||
url = "https://www." + host + uri
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// HTTPSNonWWWRedirect redirects http requests to https non www.
|
||||
// For example, http://www.labstack.com will be redirect to https://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(HTTPSNonWWWRedirect())`
|
||||
func HTTPSNonWWWRedirect() echo.MiddlewareFunc {
|
||||
return HTTPSNonWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// HTTPSNonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `HTTPSNonWWWRedirect()`.
|
||||
func HTTPSNonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if ok = scheme != "https"; ok {
|
||||
if host[:3] == www {
|
||||
host = host[4:]
|
||||
}
|
||||
url = "https://" + host + uri
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// WWWRedirect redirects non www requests to www.
|
||||
// For example, http://labstack.com will be redirect to http://www.labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(WWWRedirect())`
|
||||
func WWWRedirect() echo.MiddlewareFunc {
|
||||
return WWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// WWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `WWWRedirect()`.
|
||||
func WWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if ok = host[:3] != www; ok {
|
||||
url = scheme + "://www." + host + uri
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// NonWWWRedirect redirects www requests to non www.
|
||||
// For example, http://www.labstack.com will be redirect to http://labstack.com.
|
||||
//
|
||||
// Usage `Echo#Pre(NonWWWRedirect())`
|
||||
func NonWWWRedirect() echo.MiddlewareFunc {
|
||||
return NonWWWRedirectWithConfig(DefaultRedirectConfig)
|
||||
}
|
||||
|
||||
// NonWWWRedirectWithConfig returns an HTTPSRedirect middleware with config.
|
||||
// See `NonWWWRedirect()`.
|
||||
func NonWWWRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc {
|
||||
return redirect(config, func(scheme, host, uri string) (ok bool, url string) {
|
||||
if ok = host[:3] == www; ok {
|
||||
url = scheme + "://" + host[4:] + uri
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func redirect(config RedirectConfig, cb redirectLogic) echo.MiddlewareFunc {
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTrailingSlashConfig.Skipper
|
||||
}
|
||||
if config.Code == 0 {
|
||||
config.Code = DefaultRedirectConfig.Code
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req, scheme := c.Request(), c.Scheme()
|
||||
host := req.Host
|
||||
if ok, url := cb(scheme, host, req.RequestURI); ok {
|
||||
return c.Redirect(config.Code, url)
|
||||
}
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
64
vendor/github.com/labstack/echo/middleware/request_id.go
generated
vendored
64
vendor/github.com/labstack/echo/middleware/request_id.go
generated
vendored
@@ -1,64 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/random"
|
||||
)
|
||||
|
||||
type (
|
||||
// RequestIDConfig defines the config for RequestID middleware.
|
||||
RequestIDConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Generator defines a function to generate an ID.
|
||||
// Optional. Default value random.String(32).
|
||||
Generator func() string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRequestIDConfig is the default RequestID middleware config.
|
||||
DefaultRequestIDConfig = RequestIDConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Generator: generator,
|
||||
}
|
||||
)
|
||||
|
||||
// RequestID returns a X-Request-ID middleware.
|
||||
func RequestID() echo.MiddlewareFunc {
|
||||
return RequestIDWithConfig(DefaultRequestIDConfig)
|
||||
}
|
||||
|
||||
// RequestIDWithConfig returns a X-Request-ID middleware with config.
|
||||
func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultRequestIDConfig.Skipper
|
||||
}
|
||||
if config.Generator == nil {
|
||||
config.Generator = generator
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
rid := req.Header.Get(echo.HeaderXRequestID)
|
||||
if rid == "" {
|
||||
rid = config.Generator()
|
||||
}
|
||||
res.Header().Set(echo.HeaderXRequestID, rid)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generator() string {
|
||||
return random.String(32)
|
||||
}
|
||||
84
vendor/github.com/labstack/echo/middleware/rewrite.go
generated
vendored
84
vendor/github.com/labstack/echo/middleware/rewrite.go
generated
vendored
@@ -1,84 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// RewriteConfig defines the config for Rewrite middleware.
|
||||
RewriteConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Rules defines the URL path rewrite rules. The values captured in asterisk can be
|
||||
// retrieved by index e.g. $1, $2 and so on.
|
||||
// Example:
|
||||
// "/old": "/new",
|
||||
// "/api/*": "/$1",
|
||||
// "/js/*": "/public/javascripts/$1",
|
||||
// "/users/*/orders/*": "/user/$1/order/$2",
|
||||
// Required.
|
||||
Rules map[string]string `yaml:"rules"`
|
||||
|
||||
rulesRegex map[*regexp.Regexp]string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultRewriteConfig is the default Rewrite middleware config.
|
||||
DefaultRewriteConfig = RewriteConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
// Rewrite returns a Rewrite middleware.
|
||||
//
|
||||
// Rewrite middleware rewrites the URL path based on the provided rules.
|
||||
func Rewrite(rules map[string]string) echo.MiddlewareFunc {
|
||||
c := DefaultRewriteConfig
|
||||
c.Rules = rules
|
||||
return RewriteWithConfig(c)
|
||||
}
|
||||
|
||||
// RewriteWithConfig returns a Rewrite middleware with config.
|
||||
// See: `Rewrite()`.
|
||||
func RewriteWithConfig(config RewriteConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Rules == nil {
|
||||
panic("echo: rewrite middleware requires url path rewrite rules")
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultBodyDumpConfig.Skipper
|
||||
}
|
||||
config.rulesRegex = map[*regexp.Regexp]string{}
|
||||
|
||||
// Initialize
|
||||
for k, v := range config.Rules {
|
||||
k = strings.Replace(k, "*", "(.*)", -1)
|
||||
k = k + "$"
|
||||
config.rulesRegex[regexp.MustCompile(k)] = v
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
|
||||
// Rewrite
|
||||
for k, v := range config.rulesRegex {
|
||||
replacer := captureTokens(k, req.URL.Path)
|
||||
if replacer != nil {
|
||||
req.URL.Path = replacer.Replace(v)
|
||||
break
|
||||
}
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
116
vendor/github.com/labstack/echo/middleware/secure.go
generated
vendored
116
vendor/github.com/labstack/echo/middleware/secure.go
generated
vendored
@@ -1,116 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// SecureConfig defines the config for Secure middleware.
|
||||
SecureConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// XSSProtection provides protection against cross-site scripting attack (XSS)
|
||||
// by setting the `X-XSS-Protection` header.
|
||||
// Optional. Default value "1; mode=block".
|
||||
XSSProtection string `yaml:"xss_protection"`
|
||||
|
||||
// ContentTypeNosniff provides protection against overriding Content-Type
|
||||
// header by setting the `X-Content-Type-Options` header.
|
||||
// Optional. Default value "nosniff".
|
||||
ContentTypeNosniff string `yaml:"content_type_nosniff"`
|
||||
|
||||
// XFrameOptions can be used to indicate whether or not a browser should
|
||||
// be allowed to render a page in a <frame>, <iframe> or <object> .
|
||||
// Sites can use this to avoid clickjacking attacks, by ensuring that their
|
||||
// content is not embedded into other sites.provides protection against
|
||||
// clickjacking.
|
||||
// Optional. Default value "SAMEORIGIN".
|
||||
// Possible values:
|
||||
// - "SAMEORIGIN" - The page can only be displayed in a frame on the same origin as the page itself.
|
||||
// - "DENY" - The page cannot be displayed in a frame, regardless of the site attempting to do so.
|
||||
// - "ALLOW-FROM uri" - The page can only be displayed in a frame on the specified origin.
|
||||
XFrameOptions string `yaml:"x_frame_options"`
|
||||
|
||||
// HSTSMaxAge sets the `Strict-Transport-Security` header to indicate how
|
||||
// long (in seconds) browsers should remember that this site is only to
|
||||
// be accessed using HTTPS. This reduces your exposure to some SSL-stripping
|
||||
// man-in-the-middle (MITM) attacks.
|
||||
// Optional. Default value 0.
|
||||
HSTSMaxAge int `yaml:"hsts_max_age"`
|
||||
|
||||
// HSTSExcludeSubdomains won't include subdomains tag in the `Strict Transport Security`
|
||||
// header, excluding all subdomains from security policy. It has no effect
|
||||
// unless HSTSMaxAge is set to a non-zero value.
|
||||
// Optional. Default value false.
|
||||
HSTSExcludeSubdomains bool `yaml:"hsts_exclude_subdomains"`
|
||||
|
||||
// ContentSecurityPolicy sets the `Content-Security-Policy` header providing
|
||||
// security against cross-site scripting (XSS), clickjacking and other code
|
||||
// injection attacks resulting from execution of malicious content in the
|
||||
// trusted web page context.
|
||||
// Optional. Default value "".
|
||||
ContentSecurityPolicy string `yaml:"content_security_policy"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultSecureConfig is the default Secure middleware config.
|
||||
DefaultSecureConfig = SecureConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
XSSProtection: "1; mode=block",
|
||||
ContentTypeNosniff: "nosniff",
|
||||
XFrameOptions: "SAMEORIGIN",
|
||||
}
|
||||
)
|
||||
|
||||
// Secure returns a Secure middleware.
|
||||
// Secure middleware provides protection against cross-site scripting (XSS) attack,
|
||||
// content type sniffing, clickjacking, insecure connection and other code injection
|
||||
// attacks.
|
||||
func Secure() echo.MiddlewareFunc {
|
||||
return SecureWithConfig(DefaultSecureConfig)
|
||||
}
|
||||
|
||||
// SecureWithConfig returns a Secure middleware with config.
|
||||
// See: `Secure()`.
|
||||
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultSecureConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
res := c.Response()
|
||||
|
||||
if config.XSSProtection != "" {
|
||||
res.Header().Set(echo.HeaderXXSSProtection, config.XSSProtection)
|
||||
}
|
||||
if config.ContentTypeNosniff != "" {
|
||||
res.Header().Set(echo.HeaderXContentTypeOptions, config.ContentTypeNosniff)
|
||||
}
|
||||
if config.XFrameOptions != "" {
|
||||
res.Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
|
||||
}
|
||||
if (c.IsTLS() || (req.Header.Get(echo.HeaderXForwardedProto) == "https")) && config.HSTSMaxAge != 0 {
|
||||
subdomains := ""
|
||||
if !config.HSTSExcludeSubdomains {
|
||||
subdomains = "; includeSubdomains"
|
||||
}
|
||||
res.Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
|
||||
}
|
||||
if config.ContentSecurityPolicy != "" {
|
||||
res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
119
vendor/github.com/labstack/echo/middleware/slash.go
generated
vendored
119
vendor/github.com/labstack/echo/middleware/slash.go
generated
vendored
@@ -1,119 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/labstack/echo"
|
||||
)
|
||||
|
||||
type (
|
||||
// TrailingSlashConfig defines the config for TrailingSlash middleware.
|
||||
TrailingSlashConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Status code to be used when redirecting the request.
|
||||
// Optional, but when provided the request is redirected using this code.
|
||||
RedirectCode int `yaml:"redirect_code"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
|
||||
DefaultTrailingSlashConfig = TrailingSlashConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
}
|
||||
)
|
||||
|
||||
// AddTrailingSlash returns a root level (before router) middleware which adds a
|
||||
// trailing slash to the request `URL#Path`.
|
||||
//
|
||||
// Usage `Echo#Pre(AddTrailingSlash())`
|
||||
func AddTrailingSlash() echo.MiddlewareFunc {
|
||||
return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig)
|
||||
}
|
||||
|
||||
// AddTrailingSlashWithConfig returns a AddTrailingSlash middleware with config.
|
||||
// See `AddTrailingSlash()`.
|
||||
func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTrailingSlashConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
if path != "/" && path[len(path)-1] != '/' {
|
||||
path += "/"
|
||||
uri := path
|
||||
if qs != "" {
|
||||
uri += "?" + qs
|
||||
}
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, uri)
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveTrailingSlash returns a root level (before router) middleware which removes
|
||||
// a trailing slash from the request URI.
|
||||
//
|
||||
// Usage `Echo#Pre(RemoveTrailingSlash())`
|
||||
func RemoveTrailingSlash() echo.MiddlewareFunc {
|
||||
return RemoveTrailingSlashWithConfig(TrailingSlashConfig{})
|
||||
}
|
||||
|
||||
// RemoveTrailingSlashWithConfig returns a RemoveTrailingSlash middleware with config.
|
||||
// See `RemoveTrailingSlash()`.
|
||||
func RemoveTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultTrailingSlashConfig.Skipper
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
req := c.Request()
|
||||
url := req.URL
|
||||
path := url.Path
|
||||
qs := c.QueryString()
|
||||
l := len(path) - 1
|
||||
if l >= 0 && path != "/" && path[l] == '/' {
|
||||
path = path[:l]
|
||||
uri := path
|
||||
if qs != "" {
|
||||
uri += "?" + qs
|
||||
}
|
||||
|
||||
// Redirect
|
||||
if config.RedirectCode != 0 {
|
||||
return c.Redirect(config.RedirectCode, uri)
|
||||
}
|
||||
|
||||
// Forward
|
||||
req.RequestURI = uri
|
||||
url.Path = path
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
229
vendor/github.com/labstack/echo/middleware/static.go
generated
vendored
229
vendor/github.com/labstack/echo/middleware/static.go
generated
vendored
@@ -1,229 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/gommon/bytes"
|
||||
)
|
||||
|
||||
type (
|
||||
// StaticConfig defines the config for Static middleware.
|
||||
StaticConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// Root directory from where the static content is served.
|
||||
// Required.
|
||||
Root string `yaml:"root"`
|
||||
|
||||
// Index file for serving a directory.
|
||||
// Optional. Default value "index.html".
|
||||
Index string `yaml:"index"`
|
||||
|
||||
// Enable HTML5 mode by forwarding all not-found requests to root so that
|
||||
// SPA (single-page application) can handle the routing.
|
||||
// Optional. Default value false.
|
||||
HTML5 bool `yaml:"html5"`
|
||||
|
||||
// Enable directory browsing.
|
||||
// Optional. Default value false.
|
||||
Browse bool `yaml:"browse"`
|
||||
}
|
||||
)
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>{{ .Name }}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Menlo, Consolas, monospace;
|
||||
padding: 48px;
|
||||
}
|
||||
header {
|
||||
padding: 4px 16px;
|
||||
font-size: 24px;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 20px 0 0 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
li {
|
||||
width: 300px;
|
||||
padding: 16px;
|
||||
}
|
||||
li a {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
li span {
|
||||
color: #707070;
|
||||
font-size: 12px;
|
||||
}
|
||||
li a:hover {
|
||||
opacity: 0.50;
|
||||
}
|
||||
.dir {
|
||||
color: #E91E63;
|
||||
}
|
||||
.file {
|
||||
color: #673AB7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
{{ .Name }}
|
||||
</header>
|
||||
<ul>
|
||||
{{ range .Files }}
|
||||
<li>
|
||||
{{ if .Dir }}
|
||||
{{ $name := print .Name "/" }}
|
||||
<a class="dir" href="{{ $name }}">{{ $name }}</a>
|
||||
{{ else }}
|
||||
<a class="file" href="{{ .Name }}">{{ .Name }}</a>
|
||||
<span>{{ .Size }}</span>
|
||||
{{ end }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
var (
|
||||
// DefaultStaticConfig is the default Static middleware config.
|
||||
DefaultStaticConfig = StaticConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Index: "index.html",
|
||||
}
|
||||
)
|
||||
|
||||
// Static returns a Static middleware to serves static content from the provided
|
||||
// root directory.
|
||||
func Static(root string) echo.MiddlewareFunc {
|
||||
c := DefaultStaticConfig
|
||||
c.Root = root
|
||||
return StaticWithConfig(c)
|
||||
}
|
||||
|
||||
// StaticWithConfig returns a Static middleware with config.
|
||||
// See `Static()`.
|
||||
func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||
// Defaults
|
||||
if config.Root == "" {
|
||||
config.Root = "." // For security we want to restrict to CWD.
|
||||
}
|
||||
if config.Skipper == nil {
|
||||
config.Skipper = DefaultStaticConfig.Skipper
|
||||
}
|
||||
if config.Index == "" {
|
||||
config.Index = DefaultStaticConfig.Index
|
||||
}
|
||||
|
||||
// Index template
|
||||
t, err := template.New("index").Parse(html)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("echo: %v", err))
|
||||
}
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) (err error) {
|
||||
if config.Skipper(c) {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
p := c.Request().URL.Path
|
||||
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
|
||||
p = c.Param("*")
|
||||
}
|
||||
p, err = url.PathUnescape(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := filepath.Join(config.Root, path.Clean("/"+p)) // "/"+ for security
|
||||
|
||||
fi, err := os.Stat(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = next(c); err != nil {
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
if config.HTML5 && he.Code == http.StatusNotFound {
|
||||
return c.File(filepath.Join(config.Root, config.Index))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
index := filepath.Join(name, config.Index)
|
||||
fi, err = os.Stat(index)
|
||||
|
||||
if err != nil {
|
||||
if config.Browse {
|
||||
return listDir(t, name, c.Response())
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return next(c)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return c.File(index)
|
||||
}
|
||||
|
||||
return c.File(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
|
||||
file, err := os.Open(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
files, err := file.Readdir(-1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create directory index
|
||||
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||
data := struct {
|
||||
Name string
|
||||
Files []interface{}
|
||||
}{
|
||||
Name: name,
|
||||
}
|
||||
for _, f := range files {
|
||||
data.Files = append(data.Files, struct {
|
||||
Name string
|
||||
Dir bool
|
||||
Size string
|
||||
}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
|
||||
}
|
||||
return t.Execute(res, data)
|
||||
}
|
||||
110
vendor/github.com/labstack/echo/response.go
generated
vendored
110
vendor/github.com/labstack/echo/response.go
generated
vendored
@@ -1,110 +0,0 @@
|
||||
package echo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Response wraps an http.ResponseWriter and implements its interface to be used
|
||||
// by an HTTP handler to construct an HTTP response.
|
||||
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||
Response struct {
|
||||
echo *Echo
|
||||
beforeFuncs []func()
|
||||
afterFuncs []func()
|
||||
Writer http.ResponseWriter
|
||||
Status int
|
||||
Size int64
|
||||
Committed bool
|
||||
}
|
||||
)
|
||||
|
||||
// NewResponse creates a new instance of Response.
|
||||
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
|
||||
return &Response{Writer: w, echo: e}
|
||||
}
|
||||
|
||||
// Header returns the header map for the writer that will be sent by
|
||||
// WriteHeader. Changing the header after a call to WriteHeader (or Write) has
|
||||
// no effect unless the modified headers were declared as trailers by setting
|
||||
// the "Trailer" header before the call to WriteHeader (see example)
|
||||
// To suppress implicit response headers, set their value to nil.
|
||||
// Example: https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
||||
func (r *Response) Header() http.Header {
|
||||
return r.Writer.Header()
|
||||
}
|
||||
|
||||
// Before registers a function which is called just before the response is written.
|
||||
func (r *Response) Before(fn func()) {
|
||||
r.beforeFuncs = append(r.beforeFuncs, fn)
|
||||
}
|
||||
|
||||
// After registers a function which is called just after the response is written.
|
||||
// If the `Content-Length` is unknown, none of the after function is executed.
|
||||
func (r *Response) After(fn func()) {
|
||||
r.afterFuncs = append(r.afterFuncs, fn)
|
||||
}
|
||||
|
||||
// WriteHeader sends an HTTP response header with status code. If WriteHeader is
|
||||
// not called explicitly, the first call to Write will trigger an implicit
|
||||
// WriteHeader(http.StatusOK). Thus explicit calls to WriteHeader are mainly
|
||||
// used to send error codes.
|
||||
func (r *Response) WriteHeader(code int) {
|
||||
if r.Committed {
|
||||
r.echo.Logger.Warn("response already committed")
|
||||
return
|
||||
}
|
||||
for _, fn := range r.beforeFuncs {
|
||||
fn()
|
||||
}
|
||||
r.Status = code
|
||||
r.Writer.WriteHeader(code)
|
||||
r.Committed = true
|
||||
}
|
||||
|
||||
// Write writes the data to the connection as part of an HTTP reply.
|
||||
func (r *Response) Write(b []byte) (n int, err error) {
|
||||
if !r.Committed {
|
||||
r.WriteHeader(http.StatusOK)
|
||||
}
|
||||
n, err = r.Writer.Write(b)
|
||||
r.Size += int64(n)
|
||||
for _, fn := range r.afterFuncs {
|
||||
fn()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Flush implements the http.Flusher interface to allow an HTTP handler to flush
|
||||
// buffered data to the client.
|
||||
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
|
||||
func (r *Response) Flush() {
|
||||
r.Writer.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
|
||||
// take over the connection.
|
||||
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
|
||||
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
return r.Writer.(http.Hijacker).Hijack()
|
||||
}
|
||||
|
||||
// CloseNotify implements the http.CloseNotifier interface to allow detecting
|
||||
// when the underlying connection has gone away.
|
||||
// This mechanism can be used to cancel long operations on the server if the
|
||||
// client has disconnected before the response is ready.
|
||||
// See [http.CloseNotifier](https://golang.org/pkg/net/http/#CloseNotifier)
|
||||
func (r *Response) CloseNotify() <-chan bool {
|
||||
return r.Writer.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (r *Response) reset(w http.ResponseWriter) {
|
||||
r.beforeFuncs = nil
|
||||
r.afterFuncs = nil
|
||||
r.Writer = w
|
||||
r.Size = 0
|
||||
r.Status = http.StatusOK
|
||||
r.Committed = false
|
||||
}
|
||||
435
vendor/github.com/labstack/echo/router.go
generated
vendored
435
vendor/github.com/labstack/echo/router.go
generated
vendored
@@ -1,435 +0,0 @@
|
||||
package echo
|
||||
|
||||
import "net/http"
|
||||
|
||||
type (
|
||||
// Router is the registry of all registered routes for an `Echo` instance for
|
||||
// request matching and URL path parameter parsing.
|
||||
Router struct {
|
||||
tree *node
|
||||
routes map[string]*Route
|
||||
echo *Echo
|
||||
}
|
||||
node struct {
|
||||
kind kind
|
||||
label byte
|
||||
prefix string
|
||||
parent *node
|
||||
children children
|
||||
ppath string
|
||||
pnames []string
|
||||
methodHandler *methodHandler
|
||||
}
|
||||
kind uint8
|
||||
children []*node
|
||||
methodHandler struct {
|
||||
connect HandlerFunc
|
||||
delete HandlerFunc
|
||||
get HandlerFunc
|
||||
head HandlerFunc
|
||||
options HandlerFunc
|
||||
patch HandlerFunc
|
||||
post HandlerFunc
|
||||
propfind HandlerFunc
|
||||
put HandlerFunc
|
||||
trace HandlerFunc
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
skind kind = iota
|
||||
pkind
|
||||
akind
|
||||
)
|
||||
|
||||
// NewRouter returns a new Router instance.
|
||||
func NewRouter(e *Echo) *Router {
|
||||
return &Router{
|
||||
tree: &node{
|
||||
methodHandler: new(methodHandler),
|
||||
},
|
||||
routes: map[string]*Route{},
|
||||
echo: e,
|
||||
}
|
||||
}
|
||||
|
||||
// Add registers a new route for method and path with matching handler.
|
||||
func (r *Router) Add(method, path string, h HandlerFunc) {
|
||||
// Validate path
|
||||
if path == "" {
|
||||
panic("echo: path cannot be empty")
|
||||
}
|
||||
if path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
pnames := []string{} // Param names
|
||||
ppath := path // Pristine path
|
||||
|
||||
for i, l := 0, len(path); i < l; i++ {
|
||||
if path[i] == ':' {
|
||||
j := i + 1
|
||||
|
||||
r.insert(method, path[:i], nil, skind, "", nil)
|
||||
for ; i < l && path[i] != '/'; i++ {
|
||||
}
|
||||
|
||||
pnames = append(pnames, path[j:i])
|
||||
path = path[:j] + path[i:]
|
||||
i, l = j, len(path)
|
||||
|
||||
if i == l {
|
||||
r.insert(method, path[:i], h, pkind, ppath, pnames)
|
||||
return
|
||||
}
|
||||
r.insert(method, path[:i], nil, pkind, "", nil)
|
||||
} else if path[i] == '*' {
|
||||
r.insert(method, path[:i], nil, skind, "", nil)
|
||||
pnames = append(pnames, "*")
|
||||
r.insert(method, path[:i+1], h, akind, ppath, pnames)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.insert(method, path, h, skind, ppath, pnames)
|
||||
}
|
||||
|
||||
func (r *Router) insert(method, path string, h HandlerFunc, t kind, ppath string, pnames []string) {
|
||||
// Adjust max param
|
||||
l := len(pnames)
|
||||
if *r.echo.maxParam < l {
|
||||
*r.echo.maxParam = l
|
||||
}
|
||||
|
||||
cn := r.tree // Current node as root
|
||||
if cn == nil {
|
||||
panic("echo: invalid method")
|
||||
}
|
||||
search := path
|
||||
|
||||
for {
|
||||
sl := len(search)
|
||||
pl := len(cn.prefix)
|
||||
l := 0
|
||||
|
||||
// LCP
|
||||
max := pl
|
||||
if sl < max {
|
||||
max = sl
|
||||
}
|
||||
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||
}
|
||||
|
||||
if l == 0 {
|
||||
// At root node
|
||||
cn.label = search[0]
|
||||
cn.prefix = search
|
||||
if h != nil {
|
||||
cn.kind = t
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
cn.pnames = pnames
|
||||
}
|
||||
} else if l < pl {
|
||||
// Split node
|
||||
n := newNode(cn.kind, cn.prefix[l:], cn, cn.children, cn.methodHandler, cn.ppath, cn.pnames)
|
||||
|
||||
// Reset parent node
|
||||
cn.kind = skind
|
||||
cn.label = cn.prefix[0]
|
||||
cn.prefix = cn.prefix[:l]
|
||||
cn.children = nil
|
||||
cn.methodHandler = new(methodHandler)
|
||||
cn.ppath = ""
|
||||
cn.pnames = nil
|
||||
|
||||
cn.addChild(n)
|
||||
|
||||
if l == sl {
|
||||
// At parent node
|
||||
cn.kind = t
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
cn.pnames = pnames
|
||||
} else {
|
||||
// Create child node
|
||||
n = newNode(t, search[l:], cn, nil, new(methodHandler), ppath, pnames)
|
||||
n.addHandler(method, h)
|
||||
cn.addChild(n)
|
||||
}
|
||||
} else if l < sl {
|
||||
search = search[l:]
|
||||
c := cn.findChildWithLabel(search[0])
|
||||
if c != nil {
|
||||
// Go deeper
|
||||
cn = c
|
||||
continue
|
||||
}
|
||||
// Create child node
|
||||
n := newNode(t, search, cn, nil, new(methodHandler), ppath, pnames)
|
||||
n.addHandler(method, h)
|
||||
cn.addChild(n)
|
||||
} else {
|
||||
// Node already exists
|
||||
if h != nil {
|
||||
cn.addHandler(method, h)
|
||||
cn.ppath = ppath
|
||||
if len(cn.pnames) == 0 { // Issue #729
|
||||
cn.pnames = pnames
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func newNode(t kind, pre string, p *node, c children, mh *methodHandler, ppath string, pnames []string) *node {
|
||||
return &node{
|
||||
kind: t,
|
||||
label: pre[0],
|
||||
prefix: pre,
|
||||
parent: p,
|
||||
children: c,
|
||||
ppath: ppath,
|
||||
pnames: pnames,
|
||||
methodHandler: mh,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) addChild(c *node) {
|
||||
n.children = append(n.children, c)
|
||||
}
|
||||
|
||||
func (n *node) findChild(l byte, t kind) *node {
|
||||
for _, c := range n.children {
|
||||
if c.label == l && c.kind == t {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildWithLabel(l byte) *node {
|
||||
for _, c := range n.children {
|
||||
if c.label == l {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findChildByKind(t kind) *node {
|
||||
for _, c := range n.children {
|
||||
if c.kind == t {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) addHandler(method string, h HandlerFunc) {
|
||||
switch method {
|
||||
case http.MethodConnect:
|
||||
n.methodHandler.connect = h
|
||||
case http.MethodDelete:
|
||||
n.methodHandler.delete = h
|
||||
case http.MethodGet:
|
||||
n.methodHandler.get = h
|
||||
case http.MethodHead:
|
||||
n.methodHandler.head = h
|
||||
case http.MethodOptions:
|
||||
n.methodHandler.options = h
|
||||
case http.MethodPatch:
|
||||
n.methodHandler.patch = h
|
||||
case http.MethodPost:
|
||||
n.methodHandler.post = h
|
||||
case PROPFIND:
|
||||
n.methodHandler.propfind = h
|
||||
case http.MethodPut:
|
||||
n.methodHandler.put = h
|
||||
case http.MethodTrace:
|
||||
n.methodHandler.trace = h
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) findHandler(method string) HandlerFunc {
|
||||
switch method {
|
||||
case http.MethodConnect:
|
||||
return n.methodHandler.connect
|
||||
case http.MethodDelete:
|
||||
return n.methodHandler.delete
|
||||
case http.MethodGet:
|
||||
return n.methodHandler.get
|
||||
case http.MethodHead:
|
||||
return n.methodHandler.head
|
||||
case http.MethodOptions:
|
||||
return n.methodHandler.options
|
||||
case http.MethodPatch:
|
||||
return n.methodHandler.patch
|
||||
case http.MethodPost:
|
||||
return n.methodHandler.post
|
||||
case PROPFIND:
|
||||
return n.methodHandler.propfind
|
||||
case http.MethodPut:
|
||||
return n.methodHandler.put
|
||||
case http.MethodTrace:
|
||||
return n.methodHandler.trace
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) checkMethodNotAllowed() HandlerFunc {
|
||||
for _, m := range methods {
|
||||
if h := n.findHandler(m); h != nil {
|
||||
return MethodNotAllowedHandler
|
||||
}
|
||||
}
|
||||
return NotFoundHandler
|
||||
}
|
||||
|
||||
// Find lookup a handler registered for method and path. It also parses URL for path
|
||||
// parameters and load them into context.
|
||||
//
|
||||
// For performance:
|
||||
//
|
||||
// - Get context from `Echo#AcquireContext()`
|
||||
// - Reset it `Context#Reset()`
|
||||
// - Return it `Echo#ReleaseContext()`.
|
||||
func (r *Router) Find(method, path string, c Context) {
|
||||
ctx := c.(*context)
|
||||
ctx.path = path
|
||||
cn := r.tree // Current node as root
|
||||
|
||||
var (
|
||||
search = path
|
||||
child *node // Child node
|
||||
n int // Param counter
|
||||
nk kind // Next kind
|
||||
nn *node // Next node
|
||||
ns string // Next search
|
||||
pvalues = ctx.pvalues // Use the internal slice so the interface can keep the illusion of a dynamic slice
|
||||
)
|
||||
|
||||
// Search order static > param > any
|
||||
for {
|
||||
if search == "" {
|
||||
break
|
||||
}
|
||||
|
||||
pl := 0 // Prefix length
|
||||
l := 0 // LCP length
|
||||
|
||||
if cn.label != ':' {
|
||||
sl := len(search)
|
||||
pl = len(cn.prefix)
|
||||
|
||||
// LCP
|
||||
max := pl
|
||||
if sl < max {
|
||||
max = sl
|
||||
}
|
||||
for ; l < max && search[l] == cn.prefix[l]; l++ {
|
||||
}
|
||||
}
|
||||
|
||||
if l == pl {
|
||||
// Continue search
|
||||
search = search[l:]
|
||||
} else {
|
||||
cn = nn
|
||||
search = ns
|
||||
if nk == pkind {
|
||||
goto Param
|
||||
} else if nk == akind {
|
||||
goto Any
|
||||
}
|
||||
// Not found
|
||||
return
|
||||
}
|
||||
|
||||
if search == "" {
|
||||
break
|
||||
}
|
||||
|
||||
// Static node
|
||||
if child = cn.findChild(search[0], skind); child != nil {
|
||||
// Save next
|
||||
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||
nk = pkind
|
||||
nn = cn
|
||||
ns = search
|
||||
}
|
||||
cn = child
|
||||
continue
|
||||
}
|
||||
|
||||
// Param node
|
||||
Param:
|
||||
if child = cn.findChildByKind(pkind); child != nil {
|
||||
// Issue #378
|
||||
if len(pvalues) == n {
|
||||
continue
|
||||
}
|
||||
|
||||
// Save next
|
||||
if cn.prefix[len(cn.prefix)-1] == '/' { // Issue #623
|
||||
nk = akind
|
||||
nn = cn
|
||||
ns = search
|
||||
}
|
||||
|
||||
cn = child
|
||||
i, l := 0, len(search)
|
||||
for ; i < l && search[i] != '/'; i++ {
|
||||
}
|
||||
pvalues[n] = search[:i]
|
||||
n++
|
||||
search = search[i:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Any node
|
||||
Any:
|
||||
if cn = cn.findChildByKind(akind); cn == nil {
|
||||
if nn != nil {
|
||||
cn = nn
|
||||
nn = cn.parent // Next (Issue #954)
|
||||
search = ns
|
||||
if nk == pkind {
|
||||
goto Param
|
||||
} else if nk == akind {
|
||||
goto Any
|
||||
}
|
||||
}
|
||||
// Not found
|
||||
return
|
||||
}
|
||||
pvalues[len(cn.pnames)-1] = search
|
||||
break
|
||||
}
|
||||
|
||||
ctx.handler = cn.findHandler(method)
|
||||
ctx.path = cn.ppath
|
||||
ctx.pnames = cn.pnames
|
||||
|
||||
// NOTE: Slow zone...
|
||||
if ctx.handler == nil {
|
||||
ctx.handler = cn.checkMethodNotAllowed()
|
||||
|
||||
// Dig further for any, might have an empty value for *, e.g.
|
||||
// serving a directory. Issue #207.
|
||||
if cn = cn.findChildByKind(akind); cn == nil {
|
||||
return
|
||||
}
|
||||
if h := cn.findHandler(method); h != nil {
|
||||
ctx.handler = h
|
||||
} else {
|
||||
ctx.handler = cn.checkMethodNotAllowed()
|
||||
}
|
||||
ctx.path = cn.ppath
|
||||
ctx.pnames = cn.pnames
|
||||
pvalues[len(cn.pnames)-1] = ""
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
22
vendor/github.com/labstack/gommon/LICENSE
generated
vendored
22
vendor/github.com/labstack/gommon/LICENSE
generated
vendored
@@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 labstack
|
||||
|
||||
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.
|
||||
|
||||
29
vendor/github.com/labstack/gommon/bytes/README.md
generated
vendored
29
vendor/github.com/labstack/gommon/bytes/README.md
generated
vendored
@@ -1,29 +0,0 @@
|
||||
# Bytes
|
||||
|
||||
- Format bytes integer to human readable bytes string.
|
||||
- Parse human readable bytes string to bytes integer.
|
||||
|
||||
## Installation
|
||||
|
||||
```go
|
||||
go get github.com/labstack/gommon/bytes
|
||||
```
|
||||
|
||||
## [Usage](https://github.com/labstack/gommon/blob/master/bytes/bytes_test.go)
|
||||
|
||||
### Format
|
||||
|
||||
```go
|
||||
println(bytes.Format(13231323))
|
||||
```
|
||||
|
||||
`12.62MB`
|
||||
|
||||
### Parse
|
||||
|
||||
```go
|
||||
b, _ = Parse("2M")
|
||||
println(b)
|
||||
```
|
||||
|
||||
`2097152`
|
||||
108
vendor/github.com/labstack/gommon/bytes/bytes.go
generated
vendored
108
vendor/github.com/labstack/gommon/bytes/bytes.go
generated
vendored
@@ -1,108 +0,0 @@
|
||||
package bytes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type (
|
||||
// Bytes struct
|
||||
Bytes struct{}
|
||||
)
|
||||
|
||||
const (
|
||||
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
|
||||
KB
|
||||
MB
|
||||
GB
|
||||
TB
|
||||
PB
|
||||
EB
|
||||
)
|
||||
|
||||
var (
|
||||
pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)([KMGTPE]B?|B?)$`)
|
||||
global = New()
|
||||
)
|
||||
|
||||
// New creates a Bytes instance.
|
||||
func New() *Bytes {
|
||||
return &Bytes{}
|
||||
}
|
||||
|
||||
// Format formats bytes integer to human readable string.
|
||||
// For example, 31323 bytes will return 30.59KB.
|
||||
func (*Bytes) Format(b int64) string {
|
||||
multiple := ""
|
||||
value := float64(b)
|
||||
|
||||
switch {
|
||||
case b >= EB:
|
||||
value /= EB
|
||||
multiple = "EB"
|
||||
case b >= PB:
|
||||
value /= PB
|
||||
multiple = "PB"
|
||||
case b >= TB:
|
||||
value /= TB
|
||||
multiple = "TB"
|
||||
case b >= GB:
|
||||
value /= GB
|
||||
multiple = "GB"
|
||||
case b >= MB:
|
||||
value /= MB
|
||||
multiple = "MB"
|
||||
case b >= KB:
|
||||
value /= KB
|
||||
multiple = "KB"
|
||||
case b == 0:
|
||||
return "0"
|
||||
default:
|
||||
return strconv.FormatInt(b, 10) + "B"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.2f%s", value, multiple)
|
||||
}
|
||||
|
||||
// Parse parses human readable bytes string to bytes integer.
|
||||
// For example, 6GB (6G is also valid) will return 6442450944.
|
||||
func (*Bytes) Parse(value string) (i int64, err error) {
|
||||
parts := pattern.FindStringSubmatch(value)
|
||||
if len(parts) < 3 {
|
||||
return 0, fmt.Errorf("error parsing value=%s", value)
|
||||
}
|
||||
bytesString := parts[1]
|
||||
multiple := parts[2]
|
||||
bytes, err := strconv.ParseFloat(bytesString, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch multiple {
|
||||
default:
|
||||
return int64(bytes), nil
|
||||
case "K", "KB":
|
||||
return int64(bytes * KB), nil
|
||||
case "M", "MB":
|
||||
return int64(bytes * MB), nil
|
||||
case "G", "GB":
|
||||
return int64(bytes * GB), nil
|
||||
case "T", "TB":
|
||||
return int64(bytes * TB), nil
|
||||
case "P", "PB":
|
||||
return int64(bytes * PB), nil
|
||||
case "E", "EB":
|
||||
return int64(bytes * EB), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Format wraps global Bytes's Format function.
|
||||
func Format(b int64) string {
|
||||
return global.Format(b)
|
||||
}
|
||||
|
||||
// Parse wraps global Bytes's Parse function.
|
||||
func Parse(val string) (int64, error) {
|
||||
return global.Parse(val)
|
||||
}
|
||||
86
vendor/github.com/labstack/gommon/color/README.md
generated
vendored
86
vendor/github.com/labstack/gommon/color/README.md
generated
vendored
@@ -1,86 +0,0 @@
|
||||
# Color
|
||||
|
||||
Style terminal text.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
go get github.com/labstack/gommon/color
|
||||
```
|
||||
|
||||
## Windows?
|
||||
|
||||
Try [cmder](http://bliker.github.io/cmder) or https://github.com/mattn/go-colorable
|
||||
|
||||
## [Usage](https://github.com/labstack/gommon/blob/master/color/color_test.go)
|
||||
|
||||
```sh
|
||||
import github.com/labstack/gommon/color
|
||||
```
|
||||
|
||||
### Colored text
|
||||
|
||||
```go
|
||||
color.Println(color.Black("black"))
|
||||
color.Println(color.Red("red"))
|
||||
color.Println(color.Green("green"))
|
||||
color.Println(color.Yellow("yellow"))
|
||||
color.Println(color.Blue("blue"))
|
||||
color.Println(color.Magenta("magenta"))
|
||||
color.Println(color.Cyan("cyan"))
|
||||
color.Println(color.White("white"))
|
||||
color.Println(color.Grey("grey"))
|
||||
```
|
||||

|
||||
|
||||
### Colored background
|
||||
|
||||
```go
|
||||
color.Println(color.BlackBg("black background", color.Wht))
|
||||
color.Println(color.RedBg("red background"))
|
||||
color.Println(color.GreenBg("green background"))
|
||||
color.Println(color.YellowBg("yellow background"))
|
||||
color.Println(color.BlueBg("blue background"))
|
||||
color.Println(color.MagentaBg("magenta background"))
|
||||
color.Println(color.CyanBg("cyan background"))
|
||||
color.Println(color.WhiteBg("white background"))
|
||||
```
|
||||

|
||||
|
||||
### Emphasis
|
||||
|
||||
```go
|
||||
color.Println(color.Bold("bold"))
|
||||
color.Println(color.Dim("dim"))
|
||||
color.Println(color.Italic("italic"))
|
||||
color.Println(color.Underline("underline"))
|
||||
color.Println(color.Inverse("inverse"))
|
||||
color.Println(color.Hidden("hidden"))
|
||||
color.Println(color.Strikeout("strikeout"))
|
||||
```
|
||||

|
||||
|
||||
### Mix and match
|
||||
|
||||
```go
|
||||
color.Println(color.Green("bold green with white background", color.B, color.WhtBg))
|
||||
color.Println(color.Red("underline red", color.U))
|
||||
color.Println(color.Yellow("dim yellow", color.D))
|
||||
color.Println(color.Cyan("inverse cyan", color.In))
|
||||
color.Println(color.Blue("bold underline dim blue", color.B, color.U, color.D))
|
||||
```
|
||||

|
||||
|
||||
### Enable/Disable the package
|
||||
|
||||
```go
|
||||
color.Disable()
|
||||
color.Enable()
|
||||
```
|
||||
|
||||
### New instance
|
||||
|
||||
```go
|
||||
c := New()
|
||||
c.Green("green")
|
||||
```
|
||||
407
vendor/github.com/labstack/gommon/color/color.go
generated
vendored
407
vendor/github.com/labstack/gommon/color/color.go
generated
vendored
@@ -1,407 +0,0 @@
|
||||
package color
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
type (
|
||||
inner func(interface{}, []string, *Color) string
|
||||
)
|
||||
|
||||
// Color styles
|
||||
const (
|
||||
// Blk Black text style
|
||||
Blk = "30"
|
||||
// Rd red text style
|
||||
Rd = "31"
|
||||
// Grn green text style
|
||||
Grn = "32"
|
||||
// Yel yellow text style
|
||||
Yel = "33"
|
||||
// Blu blue text style
|
||||
Blu = "34"
|
||||
// Mgn magenta text style
|
||||
Mgn = "35"
|
||||
// Cyn cyan text style
|
||||
Cyn = "36"
|
||||
// Wht white text style
|
||||
Wht = "37"
|
||||
// Gry grey text style
|
||||
Gry = "90"
|
||||
|
||||
// BlkBg black background style
|
||||
BlkBg = "40"
|
||||
// RdBg red background style
|
||||
RdBg = "41"
|
||||
// GrnBg green background style
|
||||
GrnBg = "42"
|
||||
// YelBg yellow background style
|
||||
YelBg = "43"
|
||||
// BluBg blue background style
|
||||
BluBg = "44"
|
||||
// MgnBg magenta background style
|
||||
MgnBg = "45"
|
||||
// CynBg cyan background style
|
||||
CynBg = "46"
|
||||
// WhtBg white background style
|
||||
WhtBg = "47"
|
||||
|
||||
// R reset emphasis style
|
||||
R = "0"
|
||||
// B bold emphasis style
|
||||
B = "1"
|
||||
// D dim emphasis style
|
||||
D = "2"
|
||||
// I italic emphasis style
|
||||
I = "3"
|
||||
// U underline emphasis style
|
||||
U = "4"
|
||||
// In inverse emphasis style
|
||||
In = "7"
|
||||
// H hidden emphasis style
|
||||
H = "8"
|
||||
// S strikeout emphasis style
|
||||
S = "9"
|
||||
)
|
||||
|
||||
var (
|
||||
black = outer(Blk)
|
||||
red = outer(Rd)
|
||||
green = outer(Grn)
|
||||
yellow = outer(Yel)
|
||||
blue = outer(Blu)
|
||||
magenta = outer(Mgn)
|
||||
cyan = outer(Cyn)
|
||||
white = outer(Wht)
|
||||
grey = outer(Gry)
|
||||
|
||||
blackBg = outer(BlkBg)
|
||||
redBg = outer(RdBg)
|
||||
greenBg = outer(GrnBg)
|
||||
yellowBg = outer(YelBg)
|
||||
blueBg = outer(BluBg)
|
||||
magentaBg = outer(MgnBg)
|
||||
cyanBg = outer(CynBg)
|
||||
whiteBg = outer(WhtBg)
|
||||
|
||||
reset = outer(R)
|
||||
bold = outer(B)
|
||||
dim = outer(D)
|
||||
italic = outer(I)
|
||||
underline = outer(U)
|
||||
inverse = outer(In)
|
||||
hidden = outer(H)
|
||||
strikeout = outer(S)
|
||||
|
||||
global = New()
|
||||
)
|
||||
|
||||
func outer(n string) inner {
|
||||
return func(msg interface{}, styles []string, c *Color) string {
|
||||
// TODO: Drop fmt to boost performance?
|
||||
if c.disabled {
|
||||
return fmt.Sprintf("%v", msg)
|
||||
}
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
b.WriteString("\x1b[")
|
||||
b.WriteString(n)
|
||||
for _, s := range styles {
|
||||
b.WriteString(";")
|
||||
b.WriteString(s)
|
||||
}
|
||||
b.WriteString("m")
|
||||
return fmt.Sprintf("%s%v\x1b[0m", b.String(), msg)
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
Color struct {
|
||||
output io.Writer
|
||||
disabled bool
|
||||
}
|
||||
)
|
||||
|
||||
// New creates a Color instance.
|
||||
func New() (c *Color) {
|
||||
c = new(Color)
|
||||
c.SetOutput(colorable.NewColorableStdout())
|
||||
return
|
||||
}
|
||||
|
||||
// Output returns the output.
|
||||
func (c *Color) Output() io.Writer {
|
||||
return c.output
|
||||
}
|
||||
|
||||
// SetOutput sets the output.
|
||||
func (c *Color) SetOutput(w io.Writer) {
|
||||
c.output = w
|
||||
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
||||
c.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// Disable disables the colors and styles.
|
||||
func (c *Color) Disable() {
|
||||
c.disabled = true
|
||||
}
|
||||
|
||||
// Enable enables the colors and styles.
|
||||
func (c *Color) Enable() {
|
||||
c.disabled = false
|
||||
}
|
||||
|
||||
// Print is analogous to `fmt.Print` with termial detection.
|
||||
func (c *Color) Print(args ...interface{}) {
|
||||
fmt.Fprint(c.output, args...)
|
||||
}
|
||||
|
||||
// Println is analogous to `fmt.Println` with termial detection.
|
||||
func (c *Color) Println(args ...interface{}) {
|
||||
fmt.Fprintln(c.output, args...)
|
||||
}
|
||||
|
||||
// Printf is analogous to `fmt.Printf` with termial detection.
|
||||
func (c *Color) Printf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(c.output, format, args...)
|
||||
}
|
||||
|
||||
func (c *Color) Black(msg interface{}, styles ...string) string {
|
||||
return black(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Red(msg interface{}, styles ...string) string {
|
||||
return red(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Green(msg interface{}, styles ...string) string {
|
||||
return green(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Yellow(msg interface{}, styles ...string) string {
|
||||
return yellow(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Blue(msg interface{}, styles ...string) string {
|
||||
return blue(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Magenta(msg interface{}, styles ...string) string {
|
||||
return magenta(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Cyan(msg interface{}, styles ...string) string {
|
||||
return cyan(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) White(msg interface{}, styles ...string) string {
|
||||
return white(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Grey(msg interface{}, styles ...string) string {
|
||||
return grey(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) BlackBg(msg interface{}, styles ...string) string {
|
||||
return blackBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) RedBg(msg interface{}, styles ...string) string {
|
||||
return redBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) GreenBg(msg interface{}, styles ...string) string {
|
||||
return greenBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) YellowBg(msg interface{}, styles ...string) string {
|
||||
return yellowBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) BlueBg(msg interface{}, styles ...string) string {
|
||||
return blueBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) MagentaBg(msg interface{}, styles ...string) string {
|
||||
return magentaBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) CyanBg(msg interface{}, styles ...string) string {
|
||||
return cyanBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) WhiteBg(msg interface{}, styles ...string) string {
|
||||
return whiteBg(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Reset(msg interface{}, styles ...string) string {
|
||||
return reset(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Bold(msg interface{}, styles ...string) string {
|
||||
return bold(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Dim(msg interface{}, styles ...string) string {
|
||||
return dim(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Italic(msg interface{}, styles ...string) string {
|
||||
return italic(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Underline(msg interface{}, styles ...string) string {
|
||||
return underline(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Inverse(msg interface{}, styles ...string) string {
|
||||
return inverse(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Hidden(msg interface{}, styles ...string) string {
|
||||
return hidden(msg, styles, c)
|
||||
}
|
||||
|
||||
func (c *Color) Strikeout(msg interface{}, styles ...string) string {
|
||||
return strikeout(msg, styles, c)
|
||||
}
|
||||
|
||||
// Output returns the output.
|
||||
func Output() io.Writer {
|
||||
return global.output
|
||||
}
|
||||
|
||||
// SetOutput sets the output.
|
||||
func SetOutput(w io.Writer) {
|
||||
global.SetOutput(w)
|
||||
}
|
||||
|
||||
func Disable() {
|
||||
global.Disable()
|
||||
}
|
||||
|
||||
func Enable() {
|
||||
global.Enable()
|
||||
}
|
||||
|
||||
// Print is analogous to `fmt.Print` with termial detection.
|
||||
func Print(args ...interface{}) {
|
||||
global.Print(args...)
|
||||
}
|
||||
|
||||
// Println is analogous to `fmt.Println` with termial detection.
|
||||
func Println(args ...interface{}) {
|
||||
global.Println(args...)
|
||||
}
|
||||
|
||||
// Printf is analogous to `fmt.Printf` with termial detection.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
global.Printf(format, args...)
|
||||
}
|
||||
|
||||
func Black(msg interface{}, styles ...string) string {
|
||||
return global.Black(msg, styles...)
|
||||
}
|
||||
|
||||
func Red(msg interface{}, styles ...string) string {
|
||||
return global.Red(msg, styles...)
|
||||
}
|
||||
|
||||
func Green(msg interface{}, styles ...string) string {
|
||||
return global.Green(msg, styles...)
|
||||
}
|
||||
|
||||
func Yellow(msg interface{}, styles ...string) string {
|
||||
return global.Yellow(msg, styles...)
|
||||
}
|
||||
|
||||
func Blue(msg interface{}, styles ...string) string {
|
||||
return global.Blue(msg, styles...)
|
||||
}
|
||||
|
||||
func Magenta(msg interface{}, styles ...string) string {
|
||||
return global.Magenta(msg, styles...)
|
||||
}
|
||||
|
||||
func Cyan(msg interface{}, styles ...string) string {
|
||||
return global.Cyan(msg, styles...)
|
||||
}
|
||||
|
||||
func White(msg interface{}, styles ...string) string {
|
||||
return global.White(msg, styles...)
|
||||
}
|
||||
|
||||
func Grey(msg interface{}, styles ...string) string {
|
||||
return global.Grey(msg, styles...)
|
||||
}
|
||||
|
||||
func BlackBg(msg interface{}, styles ...string) string {
|
||||
return global.BlackBg(msg, styles...)
|
||||
}
|
||||
|
||||
func RedBg(msg interface{}, styles ...string) string {
|
||||
return global.RedBg(msg, styles...)
|
||||
}
|
||||
|
||||
func GreenBg(msg interface{}, styles ...string) string {
|
||||
return global.GreenBg(msg, styles...)
|
||||
}
|
||||
|
||||
func YellowBg(msg interface{}, styles ...string) string {
|
||||
return global.YellowBg(msg, styles...)
|
||||
}
|
||||
|
||||
func BlueBg(msg interface{}, styles ...string) string {
|
||||
return global.BlueBg(msg, styles...)
|
||||
}
|
||||
|
||||
func MagentaBg(msg interface{}, styles ...string) string {
|
||||
return global.MagentaBg(msg, styles...)
|
||||
}
|
||||
|
||||
func CyanBg(msg interface{}, styles ...string) string {
|
||||
return global.CyanBg(msg, styles...)
|
||||
}
|
||||
|
||||
func WhiteBg(msg interface{}, styles ...string) string {
|
||||
return global.WhiteBg(msg, styles...)
|
||||
}
|
||||
|
||||
func Reset(msg interface{}, styles ...string) string {
|
||||
return global.Reset(msg, styles...)
|
||||
}
|
||||
|
||||
func Bold(msg interface{}, styles ...string) string {
|
||||
return global.Bold(msg, styles...)
|
||||
}
|
||||
|
||||
func Dim(msg interface{}, styles ...string) string {
|
||||
return global.Dim(msg, styles...)
|
||||
}
|
||||
|
||||
func Italic(msg interface{}, styles ...string) string {
|
||||
return global.Italic(msg, styles...)
|
||||
}
|
||||
|
||||
func Underline(msg interface{}, styles ...string) string {
|
||||
return global.Underline(msg, styles...)
|
||||
}
|
||||
|
||||
func Inverse(msg interface{}, styles ...string) string {
|
||||
return global.Inverse(msg, styles...)
|
||||
}
|
||||
|
||||
func Hidden(msg interface{}, styles ...string) string {
|
||||
return global.Hidden(msg, styles...)
|
||||
}
|
||||
|
||||
func Strikeout(msg interface{}, styles ...string) string {
|
||||
return global.Strikeout(msg, styles...)
|
||||
}
|
||||
5
vendor/github.com/labstack/gommon/log/README.md
generated
vendored
5
vendor/github.com/labstack/gommon/log/README.md
generated
vendored
@@ -1,5 +0,0 @@
|
||||
## WORK IN PROGRESS
|
||||
|
||||
### Usage
|
||||
|
||||
[log_test.go](log_test.go)
|
||||
13
vendor/github.com/labstack/gommon/log/color.go
generated
vendored
13
vendor/github.com/labstack/gommon/log/color.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
// +build !appengine
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
func output() io.Writer {
|
||||
return colorable.NewColorableStdout()
|
||||
}
|
||||
415
vendor/github.com/labstack/gommon/log/log.go
generated
vendored
415
vendor/github.com/labstack/gommon/log/log.go
generated
vendored
@@ -1,415 +0,0 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/valyala/fasttemplate"
|
||||
|
||||
"github.com/labstack/gommon/color"
|
||||
)
|
||||
|
||||
type (
|
||||
Logger struct {
|
||||
prefix string
|
||||
level Lvl
|
||||
skip int
|
||||
output io.Writer
|
||||
template *fasttemplate.Template
|
||||
levels []string
|
||||
color *color.Color
|
||||
bufferPool sync.Pool
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
Lvl uint8
|
||||
|
||||
JSON map[string]interface{}
|
||||
)
|
||||
|
||||
const (
|
||||
DEBUG Lvl = iota + 1
|
||||
INFO
|
||||
WARN
|
||||
ERROR
|
||||
OFF
|
||||
panicLevel
|
||||
fatalLevel
|
||||
)
|
||||
|
||||
var (
|
||||
global = New("-")
|
||||
defaultHeader = `{"time":"${time_rfc3339_nano}","level":"${level}","prefix":"${prefix}",` +
|
||||
`"file":"${short_file}","line":"${line}"}`
|
||||
)
|
||||
|
||||
func init() {
|
||||
global.skip = 3
|
||||
}
|
||||
|
||||
func New(prefix string) (l *Logger) {
|
||||
l = &Logger{
|
||||
level: INFO,
|
||||
skip: 2,
|
||||
prefix: prefix,
|
||||
template: l.newTemplate(defaultHeader),
|
||||
color: color.New(),
|
||||
bufferPool: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bytes.NewBuffer(make([]byte, 256))
|
||||
},
|
||||
},
|
||||
}
|
||||
l.initLevels()
|
||||
l.SetOutput(output())
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Logger) initLevels() {
|
||||
l.levels = []string{
|
||||
"-",
|
||||
l.color.Blue("DEBUG"),
|
||||
l.color.Green("INFO"),
|
||||
l.color.Yellow("WARN"),
|
||||
l.color.Red("ERROR"),
|
||||
"",
|
||||
l.color.Yellow("PANIC", color.U),
|
||||
l.color.Red("FATAL", color.U),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) newTemplate(format string) *fasttemplate.Template {
|
||||
return fasttemplate.New(format, "${", "}")
|
||||
}
|
||||
|
||||
func (l *Logger) DisableColor() {
|
||||
l.color.Disable()
|
||||
l.initLevels()
|
||||
}
|
||||
|
||||
func (l *Logger) EnableColor() {
|
||||
l.color.Enable()
|
||||
l.initLevels()
|
||||
}
|
||||
|
||||
func (l *Logger) Prefix() string {
|
||||
return l.prefix
|
||||
}
|
||||
|
||||
func (l *Logger) SetPrefix(p string) {
|
||||
l.prefix = p
|
||||
}
|
||||
|
||||
func (l *Logger) Level() Lvl {
|
||||
return l.level
|
||||
}
|
||||
|
||||
func (l *Logger) SetLevel(v Lvl) {
|
||||
l.level = v
|
||||
}
|
||||
|
||||
func (l *Logger) Output() io.Writer {
|
||||
return l.output
|
||||
}
|
||||
|
||||
func (l *Logger) SetOutput(w io.Writer) {
|
||||
l.output = w
|
||||
if w, ok := w.(*os.File); !ok || !isatty.IsTerminal(w.Fd()) {
|
||||
l.DisableColor()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Color() *color.Color {
|
||||
return l.color
|
||||
}
|
||||
|
||||
func (l *Logger) SetHeader(h string) {
|
||||
l.template = l.newTemplate(h)
|
||||
}
|
||||
|
||||
func (l *Logger) Print(i ...interface{}) {
|
||||
l.log(0, "", i...)
|
||||
// fmt.Fprintln(l.output, i...)
|
||||
}
|
||||
|
||||
func (l *Logger) Printf(format string, args ...interface{}) {
|
||||
l.log(0, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Printj(j JSON) {
|
||||
l.log(0, "json", j)
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(i ...interface{}) {
|
||||
l.log(DEBUG, "", i...)
|
||||
}
|
||||
|
||||
func (l *Logger) Debugf(format string, args ...interface{}) {
|
||||
l.log(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Debugj(j JSON) {
|
||||
l.log(DEBUG, "json", j)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(i ...interface{}) {
|
||||
l.log(INFO, "", i...)
|
||||
}
|
||||
|
||||
func (l *Logger) Infof(format string, args ...interface{}) {
|
||||
l.log(INFO, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Infoj(j JSON) {
|
||||
l.log(INFO, "json", j)
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(i ...interface{}) {
|
||||
l.log(WARN, "", i...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warnf(format string, args ...interface{}) {
|
||||
l.log(WARN, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Warnj(j JSON) {
|
||||
l.log(WARN, "json", j)
|
||||
}
|
||||
|
||||
func (l *Logger) Error(i ...interface{}) {
|
||||
l.log(ERROR, "", i...)
|
||||
}
|
||||
|
||||
func (l *Logger) Errorf(format string, args ...interface{}) {
|
||||
l.log(ERROR, format, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) Errorj(j JSON) {
|
||||
l.log(ERROR, "json", j)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(i ...interface{}) {
|
||||
l.log(fatalLevel, "", i...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalf(format string, args ...interface{}) {
|
||||
l.log(fatalLevel, format, args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalj(j JSON) {
|
||||
l.log(fatalLevel, "json", j)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Panic(i ...interface{}) {
|
||||
l.log(panicLevel, "", i...)
|
||||
panic(fmt.Sprint(i...))
|
||||
}
|
||||
|
||||
func (l *Logger) Panicf(format string, args ...interface{}) {
|
||||
l.log(panicLevel, format, args...)
|
||||
panic(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (l *Logger) Panicj(j JSON) {
|
||||
l.log(panicLevel, "json", j)
|
||||
panic(j)
|
||||
}
|
||||
|
||||
func DisableColor() {
|
||||
global.DisableColor()
|
||||
}
|
||||
|
||||
func EnableColor() {
|
||||
global.EnableColor()
|
||||
}
|
||||
|
||||
func Prefix() string {
|
||||
return global.Prefix()
|
||||
}
|
||||
|
||||
func SetPrefix(p string) {
|
||||
global.SetPrefix(p)
|
||||
}
|
||||
|
||||
func Level() Lvl {
|
||||
return global.Level()
|
||||
}
|
||||
|
||||
func SetLevel(v Lvl) {
|
||||
global.SetLevel(v)
|
||||
}
|
||||
|
||||
func Output() io.Writer {
|
||||
return global.Output()
|
||||
}
|
||||
|
||||
func SetOutput(w io.Writer) {
|
||||
global.SetOutput(w)
|
||||
}
|
||||
|
||||
func SetHeader(h string) {
|
||||
global.SetHeader(h)
|
||||
}
|
||||
|
||||
func Print(i ...interface{}) {
|
||||
global.Print(i...)
|
||||
}
|
||||
|
||||
func Printf(format string, args ...interface{}) {
|
||||
global.Printf(format, args...)
|
||||
}
|
||||
|
||||
func Printj(j JSON) {
|
||||
global.Printj(j)
|
||||
}
|
||||
|
||||
func Debug(i ...interface{}) {
|
||||
global.Debug(i...)
|
||||
}
|
||||
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
global.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func Debugj(j JSON) {
|
||||
global.Debugj(j)
|
||||
}
|
||||
|
||||
func Info(i ...interface{}) {
|
||||
global.Info(i...)
|
||||
}
|
||||
|
||||
func Infof(format string, args ...interface{}) {
|
||||
global.Infof(format, args...)
|
||||
}
|
||||
|
||||
func Infoj(j JSON) {
|
||||
global.Infoj(j)
|
||||
}
|
||||
|
||||
func Warn(i ...interface{}) {
|
||||
global.Warn(i...)
|
||||
}
|
||||
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
global.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func Warnj(j JSON) {
|
||||
global.Warnj(j)
|
||||
}
|
||||
|
||||
func Error(i ...interface{}) {
|
||||
global.Error(i...)
|
||||
}
|
||||
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
global.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func Errorj(j JSON) {
|
||||
global.Errorj(j)
|
||||
}
|
||||
|
||||
func Fatal(i ...interface{}) {
|
||||
global.Fatal(i...)
|
||||
}
|
||||
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
global.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func Fatalj(j JSON) {
|
||||
global.Fatalj(j)
|
||||
}
|
||||
|
||||
func Panic(i ...interface{}) {
|
||||
global.Panic(i...)
|
||||
}
|
||||
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
global.Panicf(format, args...)
|
||||
}
|
||||
|
||||
func Panicj(j JSON) {
|
||||
global.Panicj(j)
|
||||
}
|
||||
|
||||
func (l *Logger) log(v Lvl, format string, args ...interface{}) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
buf := l.bufferPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer l.bufferPool.Put(buf)
|
||||
_, file, line, _ := runtime.Caller(l.skip)
|
||||
|
||||
if v >= l.level || v == 0 {
|
||||
message := ""
|
||||
if format == "" {
|
||||
message = fmt.Sprint(args...)
|
||||
} else if format == "json" {
|
||||
b, err := json.Marshal(args[0])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
message = string(b)
|
||||
} else {
|
||||
message = fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
_, err := l.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
|
||||
switch tag {
|
||||
case "time_rfc3339":
|
||||
return w.Write([]byte(time.Now().Format(time.RFC3339)))
|
||||
case "time_rfc3339_nano":
|
||||
return w.Write([]byte(time.Now().Format(time.RFC3339Nano)))
|
||||
case "level":
|
||||
return w.Write([]byte(l.levels[v]))
|
||||
case "prefix":
|
||||
return w.Write([]byte(l.prefix))
|
||||
case "long_file":
|
||||
return w.Write([]byte(file))
|
||||
case "short_file":
|
||||
return w.Write([]byte(path.Base(file)))
|
||||
case "line":
|
||||
return w.Write([]byte(strconv.Itoa(line)))
|
||||
}
|
||||
return 0, nil
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
s := buf.String()
|
||||
i := buf.Len() - 1
|
||||
if s[i] == '}' {
|
||||
// JSON header
|
||||
buf.Truncate(i)
|
||||
buf.WriteByte(',')
|
||||
if format == "json" {
|
||||
buf.WriteString(message[1:])
|
||||
} else {
|
||||
buf.WriteString(`"message":`)
|
||||
buf.WriteString(strconv.Quote(message))
|
||||
buf.WriteString(`}`)
|
||||
}
|
||||
} else {
|
||||
// Text header
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(message)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
l.output.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/labstack/gommon/log/white.go
generated
vendored
12
vendor/github.com/labstack/gommon/log/white.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
// +build appengine
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func output() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
48
vendor/github.com/labstack/gommon/random/random.go
generated
vendored
48
vendor/github.com/labstack/gommon/random/random.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
package random
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
Random struct {
|
||||
}
|
||||
)
|
||||
|
||||
// Charsets
|
||||
const (
|
||||
Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
Lowercase = "abcdefghijklmnopqrstuvwxyz"
|
||||
Alphabetic = Uppercase + Lowercase
|
||||
Numeric = "0123456789"
|
||||
Alphanumeric = Alphabetic + Numeric
|
||||
Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?`
|
||||
Hex = Numeric + "abcdef"
|
||||
)
|
||||
|
||||
var (
|
||||
global = New()
|
||||
)
|
||||
|
||||
func New() *Random {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return new(Random)
|
||||
}
|
||||
|
||||
func (r *Random) String(length uint8, charsets ...string) string {
|
||||
charset := strings.Join(charsets, "")
|
||||
if charset == "" {
|
||||
charset = Alphanumeric
|
||||
}
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Int63()%int64(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func String(length uint8, charsets ...string) string {
|
||||
return global.String(length, charsets...)
|
||||
}
|
||||
9
vendor/github.com/mattn/go-colorable/.travis.yml
generated
vendored
9
vendor/github.com/mattn/go-colorable/.travis.yml
generated
vendored
@@ -1,9 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw
|
||||
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
21
vendor/github.com/mattn/go-colorable/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
||||
48
vendor/github.com/mattn/go-colorable/README.md
generated
vendored
48
vendor/github.com/mattn/go-colorable/README.md
generated
vendored
@@ -1,48 +0,0 @@
|
||||
# go-colorable
|
||||
|
||||
[](http://godoc.org/github.com/mattn/go-colorable)
|
||||
[](https://travis-ci.org/mattn/go-colorable)
|
||||
[](https://coveralls.io/github/mattn/go-colorable?branch=master)
|
||||
[](https://goreportcard.com/report/mattn/go-colorable)
|
||||
|
||||
Colorable writer for windows.
|
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||
This package is possible to handle escape sequence for ansi color on windows.
|
||||
|
||||
## Too Bad!
|
||||
|
||||

|
||||
|
||||
|
||||
## So Good!
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
||||
logrus.SetOutput(colorable.NewColorableStdout())
|
||||
|
||||
logrus.Info("succeeded")
|
||||
logrus.Warn("not correct")
|
||||
logrus.Error("something error")
|
||||
logrus.Fatal("panic")
|
||||
```
|
||||
|
||||
You can compile above code on non-windows OSs.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-colorable
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
29
vendor/github.com/mattn/go-colorable/colorable_appengine.go
generated
vendored
@@ -1,29 +0,0 @@
|
||||
// +build appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
30
vendor/github.com/mattn/go-colorable/colorable_others.go
generated
vendored
@@ -1,30 +0,0 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
980
vendor/github.com/mattn/go-colorable/colorable_windows.go
generated
vendored
980
vendor/github.com/mattn/go-colorable/colorable_windows.go
generated
vendored
@@ -1,980 +0,0 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = 0x1
|
||||
foregroundGreen = 0x2
|
||||
foregroundRed = 0x4
|
||||
foregroundIntensity = 0x8
|
||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||
backgroundBlue = 0x10
|
||||
backgroundGreen = 0x20
|
||||
backgroundRed = 0x40
|
||||
backgroundIntensity = 0x80
|
||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||
)
|
||||
|
||||
const (
|
||||
genericRead = 0x80000000
|
||||
genericWrite = 0x40000000
|
||||
)
|
||||
|
||||
const (
|
||||
consoleTextmodeBuffer = 0x1
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type short int16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
|
||||
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||
)
|
||||
|
||||
// Writer provide colorable Writer to the console
|
||||
type Writer struct {
|
||||
out io.Writer
|
||||
handle syscall.Handle
|
||||
althandle syscall.Handle
|
||||
oldattr word
|
||||
oldpos coord
|
||||
rest bytes.Buffer
|
||||
}
|
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence from File.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
if isatty.IsTerminal(file.Fd()) {
|
||||
var csbi consoleScreenBufferInfo
|
||||
handle := syscall.Handle(file.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return NewColorable(os.Stdout)
|
||||
}
|
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return NewColorable(os.Stderr)
|
||||
}
|
||||
|
||||
var color256 = map[int]int{
|
||||
0: 0x000000,
|
||||
1: 0x800000,
|
||||
2: 0x008000,
|
||||
3: 0x808000,
|
||||
4: 0x000080,
|
||||
5: 0x800080,
|
||||
6: 0x008080,
|
||||
7: 0xc0c0c0,
|
||||
8: 0x808080,
|
||||
9: 0xff0000,
|
||||
10: 0x00ff00,
|
||||
11: 0xffff00,
|
||||
12: 0x0000ff,
|
||||
13: 0xff00ff,
|
||||
14: 0x00ffff,
|
||||
15: 0xffffff,
|
||||
16: 0x000000,
|
||||
17: 0x00005f,
|
||||
18: 0x000087,
|
||||
19: 0x0000af,
|
||||
20: 0x0000d7,
|
||||
21: 0x0000ff,
|
||||
22: 0x005f00,
|
||||
23: 0x005f5f,
|
||||
24: 0x005f87,
|
||||
25: 0x005faf,
|
||||
26: 0x005fd7,
|
||||
27: 0x005fff,
|
||||
28: 0x008700,
|
||||
29: 0x00875f,
|
||||
30: 0x008787,
|
||||
31: 0x0087af,
|
||||
32: 0x0087d7,
|
||||
33: 0x0087ff,
|
||||
34: 0x00af00,
|
||||
35: 0x00af5f,
|
||||
36: 0x00af87,
|
||||
37: 0x00afaf,
|
||||
38: 0x00afd7,
|
||||
39: 0x00afff,
|
||||
40: 0x00d700,
|
||||
41: 0x00d75f,
|
||||
42: 0x00d787,
|
||||
43: 0x00d7af,
|
||||
44: 0x00d7d7,
|
||||
45: 0x00d7ff,
|
||||
46: 0x00ff00,
|
||||
47: 0x00ff5f,
|
||||
48: 0x00ff87,
|
||||
49: 0x00ffaf,
|
||||
50: 0x00ffd7,
|
||||
51: 0x00ffff,
|
||||
52: 0x5f0000,
|
||||
53: 0x5f005f,
|
||||
54: 0x5f0087,
|
||||
55: 0x5f00af,
|
||||
56: 0x5f00d7,
|
||||
57: 0x5f00ff,
|
||||
58: 0x5f5f00,
|
||||
59: 0x5f5f5f,
|
||||
60: 0x5f5f87,
|
||||
61: 0x5f5faf,
|
||||
62: 0x5f5fd7,
|
||||
63: 0x5f5fff,
|
||||
64: 0x5f8700,
|
||||
65: 0x5f875f,
|
||||
66: 0x5f8787,
|
||||
67: 0x5f87af,
|
||||
68: 0x5f87d7,
|
||||
69: 0x5f87ff,
|
||||
70: 0x5faf00,
|
||||
71: 0x5faf5f,
|
||||
72: 0x5faf87,
|
||||
73: 0x5fafaf,
|
||||
74: 0x5fafd7,
|
||||
75: 0x5fafff,
|
||||
76: 0x5fd700,
|
||||
77: 0x5fd75f,
|
||||
78: 0x5fd787,
|
||||
79: 0x5fd7af,
|
||||
80: 0x5fd7d7,
|
||||
81: 0x5fd7ff,
|
||||
82: 0x5fff00,
|
||||
83: 0x5fff5f,
|
||||
84: 0x5fff87,
|
||||
85: 0x5fffaf,
|
||||
86: 0x5fffd7,
|
||||
87: 0x5fffff,
|
||||
88: 0x870000,
|
||||
89: 0x87005f,
|
||||
90: 0x870087,
|
||||
91: 0x8700af,
|
||||
92: 0x8700d7,
|
||||
93: 0x8700ff,
|
||||
94: 0x875f00,
|
||||
95: 0x875f5f,
|
||||
96: 0x875f87,
|
||||
97: 0x875faf,
|
||||
98: 0x875fd7,
|
||||
99: 0x875fff,
|
||||
100: 0x878700,
|
||||
101: 0x87875f,
|
||||
102: 0x878787,
|
||||
103: 0x8787af,
|
||||
104: 0x8787d7,
|
||||
105: 0x8787ff,
|
||||
106: 0x87af00,
|
||||
107: 0x87af5f,
|
||||
108: 0x87af87,
|
||||
109: 0x87afaf,
|
||||
110: 0x87afd7,
|
||||
111: 0x87afff,
|
||||
112: 0x87d700,
|
||||
113: 0x87d75f,
|
||||
114: 0x87d787,
|
||||
115: 0x87d7af,
|
||||
116: 0x87d7d7,
|
||||
117: 0x87d7ff,
|
||||
118: 0x87ff00,
|
||||
119: 0x87ff5f,
|
||||
120: 0x87ff87,
|
||||
121: 0x87ffaf,
|
||||
122: 0x87ffd7,
|
||||
123: 0x87ffff,
|
||||
124: 0xaf0000,
|
||||
125: 0xaf005f,
|
||||
126: 0xaf0087,
|
||||
127: 0xaf00af,
|
||||
128: 0xaf00d7,
|
||||
129: 0xaf00ff,
|
||||
130: 0xaf5f00,
|
||||
131: 0xaf5f5f,
|
||||
132: 0xaf5f87,
|
||||
133: 0xaf5faf,
|
||||
134: 0xaf5fd7,
|
||||
135: 0xaf5fff,
|
||||
136: 0xaf8700,
|
||||
137: 0xaf875f,
|
||||
138: 0xaf8787,
|
||||
139: 0xaf87af,
|
||||
140: 0xaf87d7,
|
||||
141: 0xaf87ff,
|
||||
142: 0xafaf00,
|
||||
143: 0xafaf5f,
|
||||
144: 0xafaf87,
|
||||
145: 0xafafaf,
|
||||
146: 0xafafd7,
|
||||
147: 0xafafff,
|
||||
148: 0xafd700,
|
||||
149: 0xafd75f,
|
||||
150: 0xafd787,
|
||||
151: 0xafd7af,
|
||||
152: 0xafd7d7,
|
||||
153: 0xafd7ff,
|
||||
154: 0xafff00,
|
||||
155: 0xafff5f,
|
||||
156: 0xafff87,
|
||||
157: 0xafffaf,
|
||||
158: 0xafffd7,
|
||||
159: 0xafffff,
|
||||
160: 0xd70000,
|
||||
161: 0xd7005f,
|
||||
162: 0xd70087,
|
||||
163: 0xd700af,
|
||||
164: 0xd700d7,
|
||||
165: 0xd700ff,
|
||||
166: 0xd75f00,
|
||||
167: 0xd75f5f,
|
||||
168: 0xd75f87,
|
||||
169: 0xd75faf,
|
||||
170: 0xd75fd7,
|
||||
171: 0xd75fff,
|
||||
172: 0xd78700,
|
||||
173: 0xd7875f,
|
||||
174: 0xd78787,
|
||||
175: 0xd787af,
|
||||
176: 0xd787d7,
|
||||
177: 0xd787ff,
|
||||
178: 0xd7af00,
|
||||
179: 0xd7af5f,
|
||||
180: 0xd7af87,
|
||||
181: 0xd7afaf,
|
||||
182: 0xd7afd7,
|
||||
183: 0xd7afff,
|
||||
184: 0xd7d700,
|
||||
185: 0xd7d75f,
|
||||
186: 0xd7d787,
|
||||
187: 0xd7d7af,
|
||||
188: 0xd7d7d7,
|
||||
189: 0xd7d7ff,
|
||||
190: 0xd7ff00,
|
||||
191: 0xd7ff5f,
|
||||
192: 0xd7ff87,
|
||||
193: 0xd7ffaf,
|
||||
194: 0xd7ffd7,
|
||||
195: 0xd7ffff,
|
||||
196: 0xff0000,
|
||||
197: 0xff005f,
|
||||
198: 0xff0087,
|
||||
199: 0xff00af,
|
||||
200: 0xff00d7,
|
||||
201: 0xff00ff,
|
||||
202: 0xff5f00,
|
||||
203: 0xff5f5f,
|
||||
204: 0xff5f87,
|
||||
205: 0xff5faf,
|
||||
206: 0xff5fd7,
|
||||
207: 0xff5fff,
|
||||
208: 0xff8700,
|
||||
209: 0xff875f,
|
||||
210: 0xff8787,
|
||||
211: 0xff87af,
|
||||
212: 0xff87d7,
|
||||
213: 0xff87ff,
|
||||
214: 0xffaf00,
|
||||
215: 0xffaf5f,
|
||||
216: 0xffaf87,
|
||||
217: 0xffafaf,
|
||||
218: 0xffafd7,
|
||||
219: 0xffafff,
|
||||
220: 0xffd700,
|
||||
221: 0xffd75f,
|
||||
222: 0xffd787,
|
||||
223: 0xffd7af,
|
||||
224: 0xffd7d7,
|
||||
225: 0xffd7ff,
|
||||
226: 0xffff00,
|
||||
227: 0xffff5f,
|
||||
228: 0xffff87,
|
||||
229: 0xffffaf,
|
||||
230: 0xffffd7,
|
||||
231: 0xffffff,
|
||||
232: 0x080808,
|
||||
233: 0x121212,
|
||||
234: 0x1c1c1c,
|
||||
235: 0x262626,
|
||||
236: 0x303030,
|
||||
237: 0x3a3a3a,
|
||||
238: 0x444444,
|
||||
239: 0x4e4e4e,
|
||||
240: 0x585858,
|
||||
241: 0x626262,
|
||||
242: 0x6c6c6c,
|
||||
243: 0x767676,
|
||||
244: 0x808080,
|
||||
245: 0x8a8a8a,
|
||||
246: 0x949494,
|
||||
247: 0x9e9e9e,
|
||||
248: 0xa8a8a8,
|
||||
249: 0xb2b2b2,
|
||||
250: 0xbcbcbc,
|
||||
251: 0xc6c6c6,
|
||||
252: 0xd0d0d0,
|
||||
253: 0xdadada,
|
||||
254: 0xe4e4e4,
|
||||
255: 0xeeeeee,
|
||||
}
|
||||
|
||||
// `\033]0;TITLESTR\007`
|
||||
func doTitleSequence(er *bytes.Reader) error {
|
||||
var c byte
|
||||
var err error
|
||||
|
||||
c, err = er.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != '0' && c != '2' {
|
||||
return nil
|
||||
}
|
||||
c, err = er.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c != ';' {
|
||||
return nil
|
||||
}
|
||||
title := make([]byte, 0, 80)
|
||||
for {
|
||||
c, err = er.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c == 0x07 || c == '\n' {
|
||||
break
|
||||
}
|
||||
title = append(title, c)
|
||||
}
|
||||
if len(title) > 0 {
|
||||
title8, err := syscall.UTF16PtrFromString(string(title))
|
||||
if err == nil {
|
||||
procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write write data on console
|
||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
handle := w.handle
|
||||
|
||||
var er *bytes.Reader
|
||||
if w.rest.Len() > 0 {
|
||||
var rest bytes.Buffer
|
||||
w.rest.WriteTo(&rest)
|
||||
w.rest.Reset()
|
||||
rest.Write(data)
|
||||
er = bytes.NewReader(rest.Bytes())
|
||||
} else {
|
||||
er = bytes.NewReader(data)
|
||||
}
|
||||
var bw [1]byte
|
||||
loop:
|
||||
for {
|
||||
c1, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c1 != 0x1b {
|
||||
bw[0] = c1
|
||||
w.out.Write(bw[:])
|
||||
continue
|
||||
}
|
||||
c2, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
|
||||
switch c2 {
|
||||
case '>':
|
||||
continue
|
||||
case ']':
|
||||
w.rest.WriteByte(c1)
|
||||
w.rest.WriteByte(c2)
|
||||
er.WriteTo(&w.rest)
|
||||
if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
|
||||
break loop
|
||||
}
|
||||
er = bytes.NewReader(w.rest.Bytes()[2:])
|
||||
err := doTitleSequence(er)
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
w.rest.Reset()
|
||||
continue
|
||||
// https://github.com/mattn/go-colorable/issues/27
|
||||
case '7':
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
w.oldpos = csbi.cursorPosition
|
||||
continue
|
||||
case '8':
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
|
||||
continue
|
||||
case 0x5b:
|
||||
// execute part after switch
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
w.rest.WriteByte(c1)
|
||||
w.rest.WriteByte(c2)
|
||||
er.WriteTo(&w.rest)
|
||||
|
||||
var buf bytes.Buffer
|
||||
var m byte
|
||||
for i, c := range w.rest.Bytes()[2:] {
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||
m = c
|
||||
er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
|
||||
w.rest.Reset()
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(string(c)))
|
||||
}
|
||||
if m == 0 {
|
||||
break loop
|
||||
}
|
||||
|
||||
switch m {
|
||||
case 'A':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.y -= short(n)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'B':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.y += short(n)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'C':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.x += short(n)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'D':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.x -= short(n)
|
||||
if csbi.cursorPosition.x < 0 {
|
||||
csbi.cursorPosition.x = 0
|
||||
}
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'E':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.x = 0
|
||||
csbi.cursorPosition.y += short(n)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'F':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.x = 0
|
||||
csbi.cursorPosition.y -= short(n)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'G':
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
csbi.cursorPosition.x = short(n - 1)
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'H', 'f':
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
if buf.Len() > 0 {
|
||||
token := strings.Split(buf.String(), ";")
|
||||
switch len(token) {
|
||||
case 1:
|
||||
n1, err := strconv.Atoi(token[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
csbi.cursorPosition.y = short(n1 - 1)
|
||||
case 2:
|
||||
n1, err := strconv.Atoi(token[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
n2, err := strconv.Atoi(token[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
csbi.cursorPosition.x = short(n2 - 1)
|
||||
csbi.cursorPosition.y = short(n1 - 1)
|
||||
}
|
||||
} else {
|
||||
csbi.cursorPosition.y = 0
|
||||
}
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
|
||||
case 'J':
|
||||
n := 0
|
||||
if buf.Len() > 0 {
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
var count, written dword
|
||||
var cursor coord
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
switch n {
|
||||
case 0:
|
||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
|
||||
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||
case 1:
|
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top}
|
||||
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||
case 2:
|
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top}
|
||||
count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
|
||||
}
|
||||
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||
case 'K':
|
||||
n := 0
|
||||
if buf.Len() > 0 {
|
||||
n, err = strconv.Atoi(buf.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
var cursor coord
|
||||
var count, written dword
|
||||
switch n {
|
||||
case 0:
|
||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
|
||||
count = dword(csbi.size.x - csbi.cursorPosition.x)
|
||||
case 1:
|
||||
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
|
||||
count = dword(csbi.size.x - csbi.cursorPosition.x)
|
||||
case 2:
|
||||
cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
|
||||
count = dword(csbi.size.x)
|
||||
}
|
||||
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||
procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
|
||||
case 'm':
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
attr := csbi.attributes
|
||||
cs := buf.String()
|
||||
if cs == "" {
|
||||
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
|
||||
continue
|
||||
}
|
||||
token := strings.Split(cs, ";")
|
||||
for i := 0; i < len(token); i++ {
|
||||
ns := token[i]
|
||||
if n, err = strconv.Atoi(ns); err == nil {
|
||||
switch {
|
||||
case n == 0 || n == 100:
|
||||
attr = w.oldattr
|
||||
case 1 <= n && n <= 5:
|
||||
attr |= foregroundIntensity
|
||||
case n == 7:
|
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||
case n == 22 || n == 25:
|
||||
attr |= foregroundIntensity
|
||||
case n == 27:
|
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
|
||||
case 30 <= n && n <= 37:
|
||||
attr &= backgroundMask
|
||||
if (n-30)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-30)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-30)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case n == 38: // set foreground color.
|
||||
if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
|
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||
if n256foreAttr == nil {
|
||||
n256setup()
|
||||
}
|
||||
attr &= backgroundMask
|
||||
attr |= n256foreAttr[n256]
|
||||
i += 2
|
||||
}
|
||||
} else if len(token) == 5 && token[i+1] == "2" {
|
||||
var r, g, b int
|
||||
r, _ = strconv.Atoi(token[i+2])
|
||||
g, _ = strconv.Atoi(token[i+3])
|
||||
b, _ = strconv.Atoi(token[i+4])
|
||||
i += 4
|
||||
if r > 127 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if g > 127 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if b > 127 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
} else {
|
||||
attr = attr & (w.oldattr & backgroundMask)
|
||||
}
|
||||
case n == 39: // reset foreground color.
|
||||
attr &= backgroundMask
|
||||
attr |= w.oldattr & foregroundMask
|
||||
case 40 <= n && n <= 47:
|
||||
attr &= foregroundMask
|
||||
if (n-40)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-40)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-40)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
case n == 48: // set background color.
|
||||
if i < len(token)-2 && token[i+1] == "5" {
|
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil {
|
||||
if n256backAttr == nil {
|
||||
n256setup()
|
||||
}
|
||||
attr &= foregroundMask
|
||||
attr |= n256backAttr[n256]
|
||||
i += 2
|
||||
}
|
||||
} else if len(token) == 5 && token[i+1] == "2" {
|
||||
var r, g, b int
|
||||
r, _ = strconv.Atoi(token[i+2])
|
||||
g, _ = strconv.Atoi(token[i+3])
|
||||
b, _ = strconv.Atoi(token[i+4])
|
||||
i += 4
|
||||
if r > 127 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if g > 127 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if b > 127 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
} else {
|
||||
attr = attr & (w.oldattr & foregroundMask)
|
||||
}
|
||||
case n == 49: // reset foreground color.
|
||||
attr &= foregroundMask
|
||||
attr |= w.oldattr & backgroundMask
|
||||
case 90 <= n && n <= 97:
|
||||
attr = (attr & backgroundMask)
|
||||
attr |= foregroundIntensity
|
||||
if (n-90)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-90)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-90)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case 100 <= n && n <= 107:
|
||||
attr = (attr & foregroundMask)
|
||||
attr |= backgroundIntensity
|
||||
if (n-100)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-100)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-100)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
}
|
||||
procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
|
||||
}
|
||||
}
|
||||
case 'h':
|
||||
var ci consoleCursorInfo
|
||||
cs := buf.String()
|
||||
if cs == "5>" {
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
ci.visible = 0
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
} else if cs == "?25" {
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
ci.visible = 1
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
} else if cs == "?1049" {
|
||||
if w.althandle == 0 {
|
||||
h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
|
||||
w.althandle = syscall.Handle(h)
|
||||
if w.althandle != 0 {
|
||||
handle = w.althandle
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'l':
|
||||
var ci consoleCursorInfo
|
||||
cs := buf.String()
|
||||
if cs == "5>" {
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
ci.visible = 1
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
} else if cs == "?25" {
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
ci.visible = 0
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
|
||||
} else if cs == "?1049" {
|
||||
if w.althandle != 0 {
|
||||
syscall.CloseHandle(w.althandle)
|
||||
w.althandle = 0
|
||||
handle = w.handle
|
||||
}
|
||||
}
|
||||
case 's':
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
w.oldpos = csbi.cursorPosition
|
||||
case 'u':
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
|
||||
}
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
type consoleColor struct {
|
||||
rgb int
|
||||
red bool
|
||||
green bool
|
||||
blue bool
|
||||
intensity bool
|
||||
}
|
||||
|
||||
func (c consoleColor) foregroundAttr() (attr word) {
|
||||
if c.red {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if c.green {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if c.blue {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
if c.intensity {
|
||||
attr |= foregroundIntensity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c consoleColor) backgroundAttr() (attr word) {
|
||||
if c.red {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if c.green {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if c.blue {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
if c.intensity {
|
||||
attr |= backgroundIntensity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var color16 = []consoleColor{
|
||||
{0x000000, false, false, false, false},
|
||||
{0x000080, false, false, true, false},
|
||||
{0x008000, false, true, false, false},
|
||||
{0x008080, false, true, true, false},
|
||||
{0x800000, true, false, false, false},
|
||||
{0x800080, true, false, true, false},
|
||||
{0x808000, true, true, false, false},
|
||||
{0xc0c0c0, true, true, true, false},
|
||||
{0x808080, false, false, false, true},
|
||||
{0x0000ff, false, false, true, true},
|
||||
{0x00ff00, false, true, false, true},
|
||||
{0x00ffff, false, true, true, true},
|
||||
{0xff0000, true, false, false, true},
|
||||
{0xff00ff, true, false, true, true},
|
||||
{0xffff00, true, true, false, true},
|
||||
{0xffffff, true, true, true, true},
|
||||
}
|
||||
|
||||
type hsv struct {
|
||||
h, s, v float32
|
||||
}
|
||||
|
||||
func (a hsv) dist(b hsv) float32 {
|
||||
dh := a.h - b.h
|
||||
switch {
|
||||
case dh > 0.5:
|
||||
dh = 1 - dh
|
||||
case dh < -0.5:
|
||||
dh = -1 - dh
|
||||
}
|
||||
ds := a.s - b.s
|
||||
dv := a.v - b.v
|
||||
return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
|
||||
}
|
||||
|
||||
func toHSV(rgb int) hsv {
|
||||
r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
|
||||
float32((rgb&0x00FF00)>>8)/256.0,
|
||||
float32(rgb&0x0000FF)/256.0
|
||||
min, max := minmax3f(r, g, b)
|
||||
h := max - min
|
||||
if h > 0 {
|
||||
if max == r {
|
||||
h = (g - b) / h
|
||||
if h < 0 {
|
||||
h += 6
|
||||
}
|
||||
} else if max == g {
|
||||
h = 2 + (b-r)/h
|
||||
} else {
|
||||
h = 4 + (r-g)/h
|
||||
}
|
||||
}
|
||||
h /= 6.0
|
||||
s := max - min
|
||||
if max != 0 {
|
||||
s /= max
|
||||
}
|
||||
v := max
|
||||
return hsv{h: h, s: s, v: v}
|
||||
}
|
||||
|
||||
type hsvTable []hsv
|
||||
|
||||
func toHSVTable(rgbTable []consoleColor) hsvTable {
|
||||
t := make(hsvTable, len(rgbTable))
|
||||
for i, c := range rgbTable {
|
||||
t[i] = toHSV(c.rgb)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t hsvTable) find(rgb int) consoleColor {
|
||||
hsv := toHSV(rgb)
|
||||
n := 7
|
||||
l := float32(5.0)
|
||||
for i, p := range t {
|
||||
d := hsv.dist(p)
|
||||
if d < l {
|
||||
l, n = d, i
|
||||
}
|
||||
}
|
||||
return color16[n]
|
||||
}
|
||||
|
||||
func minmax3f(a, b, c float32) (min, max float32) {
|
||||
if a < b {
|
||||
if b < c {
|
||||
return a, c
|
||||
} else if a < c {
|
||||
return a, b
|
||||
} else {
|
||||
return c, b
|
||||
}
|
||||
} else {
|
||||
if a < c {
|
||||
return b, c
|
||||
} else if b < c {
|
||||
return b, a
|
||||
} else {
|
||||
return c, a
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var n256foreAttr []word
|
||||
var n256backAttr []word
|
||||
|
||||
func n256setup() {
|
||||
n256foreAttr = make([]word, 256)
|
||||
n256backAttr = make([]word, 256)
|
||||
t := toHSVTable(color16)
|
||||
for i, rgb := range color256 {
|
||||
c := t.find(rgb)
|
||||
n256foreAttr[i] = c.foregroundAttr()
|
||||
n256backAttr[i] = c.backgroundAttr()
|
||||
}
|
||||
}
|
||||
3
vendor/github.com/mattn/go-colorable/go.mod
generated
vendored
3
vendor/github.com/mattn/go-colorable/go.mod
generated
vendored
@@ -1,3 +0,0 @@
|
||||
module github.com/mattn/go-colorable
|
||||
|
||||
require github.com/mattn/go-isatty v0.0.5
|
||||
4
vendor/github.com/mattn/go-colorable/go.sum
generated
vendored
4
vendor/github.com/mattn/go-colorable/go.sum
generated
vendored
@@ -1,4 +0,0 @@
|
||||
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user