diff --git a/go.mod b/go.mod index 1666281609b8e3dd587a55197f129c2d5e84cb32..64ad900fe392caa9f8f491e44addc9fe5eb9a4f1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/subcommands v1.2.0 github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff - github.com/lpar/gzipped v1.1.1-0.20190413023519-5d9a18ea7f47 + github.com/kevinpollet/nego v0.0.0-20201213172553-d6ce2e30cfd6 // indirect + github.com/lpar/gzipped/v2 v2.0.2 github.com/miekg/dns v1.1.42 github.com/prometheus/client_golang v1.10.0 github.com/prometheus/common v0.25.0 diff --git a/go.sum b/go.sum index 76f5c9298874ae9dd9886d2b29d0a6ac22da9c92..fe30cc5b5fb0611d5deb9c4e991570c6b212255f 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,6 @@ github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0 h1:CfaPdCDbZu8jSwjq0flJv2u+WreQM0KqytUQahZ6Xf4= -github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -261,6 +259,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kevinpollet/nego v0.0.0-20200324111829-b3061ca9dd9d h1:BaIpmhcqpBnz4+NZjUjVGxKNA+/E7ovKsjmwqjXcGYc= +github.com/kevinpollet/nego v0.0.0-20200324111829-b3061ca9dd9d/go.mod h1:3FSWkzk9h42opyV0o357Fq6gsLF/A6MI/qOca9kKobY= +github.com/kevinpollet/nego v0.0.0-20201213172553-d6ce2e30cfd6 h1:krpl9dNZMNk/Hn1PSEUqwvzCPexzBdPDTPXZSw6etNI= +github.com/kevinpollet/nego v0.0.0-20201213172553-d6ce2e30cfd6/go.mod h1:bST7PtmFt4otZfrYPAUmYA1v3hZBhX4ttQzBSxeRE4E= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -280,8 +282,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lpar/gzipped v1.1.1-0.20190413023519-5d9a18ea7f47 h1:2FzWkGC6+N0dbam4RJDlOowwCPoReUHbOv8df47AvKo= -github.com/lpar/gzipped v1.1.1-0.20190413023519-5d9a18ea7f47/go.mod h1:WMk8eaBoNwo+GboXt/zvvwIGRNgs2HNYhQAiF8BoTbY= +github.com/lpar/gzipped/v2 v2.0.2 h1:y7FjyTH07f8dX0YQ5o0sg2DTbRnmS3oT1pUvxViQ//o= +github.com/lpar/gzipped/v2 v2.0.2/go.mod h1:qb7pLOGFgqz5w9xGGiiRFPxuGZ7GqWEuXUKXSbgonkQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -291,8 +293,6 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY= github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -375,8 +375,6 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0 h1:WCVKW7aL6LEe1uryfI9dnEc2ZqNB1Fn0ok930v0iL1Y= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.23.0 h1:GXWvPYuTUenIa+BhOq/x+L/QZzCqASkVRny5KTlPDGM= -github.com/prometheus/common v0.23.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= github.com/prometheus/common v0.25.0 h1:IjJYZJCI8HZYtqA3xYwGyDzSCy1r4CA2GRh+4vdOmtE= github.com/prometheus/common v0.25.0/go.mod h1:H6QK/N6XVT42whUeIdI3dp36w49c+/iMDk7UAI2qm7Q= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -435,6 +433,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -505,8 +505,6 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -733,8 +731,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -773,6 +769,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/node/http.go b/node/http.go index 31d6a03f3871f9928503d6a0189e621fea7c6685..428c854f7619461b9962bc3a50a53764442d1498 100644 --- a/node/http.go +++ b/node/http.go @@ -25,7 +25,7 @@ import ( pb "git.autistici.org/ale/autoradio/proto" "github.com/NYTimes/gziphandler" assetfs "github.com/elazarl/go-bindata-assetfs" - "github.com/lpar/gzipped" + "github.com/lpar/gzipped/v2" ) var ( @@ -33,18 +33,35 @@ var ( restrictDebugHandlers = flag.Bool("http-restrict-debug", false, "restrict access to /debug from localhost only") ) +// A simple AssetFS wrapper that provides the Exists() method needed +// by the gzipped.FileSystem interface. +type assetFSWrapper struct { + *assetfs.AssetFS +} + +func (f *assetFSWrapper) Exists(name string) bool { + _, err := f.AssetFS.Asset(name) + return err == nil +} + +func newAssetFSWrapper() *assetFSWrapper { + return &assetFSWrapper{ + AssetFS: &assetfs.AssetFS{ + Asset: Asset, + AssetDir: AssetDir, + AssetInfo: AssetInfo, + Prefix: "static", + }, + } +} + // Build the HTTP handler for the public HTTP endpoint. func newHTTPHandler(n *Node, icecastPort int, domain string) http.Handler { mux := http.NewServeMux() // Serve /static/ from builtin assets. Also serve directly // /favicon.ico using the same mechanism. - fs := withCachingHeaders(gzipped.FileServer(&assetfs.AssetFS{ - Asset: Asset, - AssetDir: AssetDir, - AssetInfo: AssetInfo, - Prefix: "static", - })) + fs := withCachingHeaders(gzipped.FileServer(newAssetFSWrapper())) mux.Handle("/static/", http.StripPrefix("/static/", fs)) mux.Handle("/favicon.ico", fs) diff --git a/vendor/github.com/golang/gddo/LICENSE b/vendor/github.com/golang/gddo/LICENSE deleted file mode 100644 index 65d761bc9f28c7de26b4f39c495d5ebd365b114d..0000000000000000000000000000000000000000 --- a/vendor/github.com/golang/gddo/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/golang/gddo/httputil/header/header.go b/vendor/github.com/golang/gddo/httputil/header/header.go deleted file mode 100644 index 0f1572e3f95701cd5c35af3f01c2c4a3a872b468..0000000000000000000000000000000000000000 --- a/vendor/github.com/golang/gddo/httputil/header/header.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd. - -// Package header provides functions for parsing HTTP headers. -package header - -import ( - "net/http" - "strings" - "time" -) - -// Octet types from RFC 2616. -var octetTypes [256]octetType - -type octetType byte - -const ( - isToken octetType = 1 << iota - isSpace -) - -func init() { - // OCTET = <any 8-bit sequence of data> - // CHAR = <any US-ASCII character (octets 0 - 127)> - // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> - // CR = <US-ASCII CR, carriage return (13)> - // LF = <US-ASCII LF, linefeed (10)> - // SP = <US-ASCII SP, space (32)> - // HT = <US-ASCII HT, horizontal-tab (9)> - // <"> = <US-ASCII double-quote mark (34)> - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = <any OCTET except CTLs, but including LWS> - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1*<any CHAR except CTLs or separators> - // qdtext = <any TEXT except <">> - - for c := 0; c < 256; c++ { - var t octetType - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { - t |= isSpace - } - if isChar && !isCtl && !isSeparator { - t |= isToken - } - octetTypes[c] = t - } -} - -// Copy returns a shallow copy of the header. -func Copy(header http.Header) http.Header { - h := make(http.Header) - for k, vs := range header { - h[k] = vs - } - return h -} - -var timeLayouts = []string{"Mon, 02 Jan 2006 15:04:05 GMT", time.RFC850, time.ANSIC} - -// ParseTime parses the header as time. The zero value is returned if the -// header is not present or there is an error parsing the -// header. -func ParseTime(header http.Header, key string) time.Time { - if s := header.Get(key); s != "" { - for _, layout := range timeLayouts { - if t, err := time.Parse(layout, s); err == nil { - return t.UTC() - } - } - } - return time.Time{} -} - -// ParseList parses a comma separated list of values. Commas are ignored in -// quoted strings. Quoted values are not unescaped or unquoted. Whitespace is -// trimmed. -func ParseList(header http.Header, key string) []string { - var result []string - for _, s := range header[http.CanonicalHeaderKey(key)] { - begin := 0 - end := 0 - escape := false - quote := false - for i := 0; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - end = i + 1 - case quote: - switch b { - case '\\': - escape = true - case '"': - quote = false - } - end = i + 1 - case b == '"': - quote = true - end = i + 1 - case octetTypes[b]&isSpace != 0: - if begin == end { - begin = i + 1 - end = begin - } - case b == ',': - if begin < end { - result = append(result, s[begin:end]) - } - begin = i + 1 - end = begin - default: - end = i + 1 - } - } - if begin < end { - result = append(result, s[begin:end]) - } - } - return result -} - -// ParseValueAndParams parses a comma separated list of values with optional -// semicolon separated name-value pairs. Content-Type and Content-Disposition -// headers are in this format. -func ParseValueAndParams(header http.Header, key string) (value string, params map[string]string) { - params = make(map[string]string) - s := header.Get(key) - value, s = expectTokenSlash(s) - if value == "" { - return - } - value = strings.ToLower(value) - s = skipSpace(s) - for strings.HasPrefix(s, ";") { - var pkey string - pkey, s = expectToken(skipSpace(s[1:])) - if pkey == "" { - return - } - if !strings.HasPrefix(s, "=") { - return - } - var pvalue string - pvalue, s = expectTokenOrQuoted(s[1:]) - if pvalue == "" { - return - } - pkey = strings.ToLower(pkey) - params[pkey] = pvalue - s = skipSpace(s) - } - return -} - -// AcceptSpec describes an Accept* header. -type AcceptSpec struct { - Value string - Q float64 -} - -// ParseAccept parses Accept* headers. -func ParseAccept(header http.Header, key string) (specs []AcceptSpec) { -loop: - for _, s := range header[key] { - for { - var spec AcceptSpec - spec.Value, s = expectTokenSlash(s) - if spec.Value == "" { - continue loop - } - spec.Q = 1.0 - s = skipSpace(s) - if strings.HasPrefix(s, ";") { - s = skipSpace(s[1:]) - if !strings.HasPrefix(s, "q=") { - continue loop - } - spec.Q, s = expectQuality(s[2:]) - if spec.Q < 0.0 { - continue loop - } - } - specs = append(specs, spec) - s = skipSpace(s) - if !strings.HasPrefix(s, ",") { - continue loop - } - s = skipSpace(s[1:]) - } - } - return -} - -func skipSpace(s string) (rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpace == 0 { - break - } - } - return s[i:] -} - -func expectToken(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - if octetTypes[s[i]]&isToken == 0 { - break - } - } - return s[:i], s[i:] -} - -func expectTokenSlash(s string) (token, rest string) { - i := 0 - for ; i < len(s); i++ { - b := s[i] - if (octetTypes[b]&isToken == 0) && b != '/' { - break - } - } - return s[:i], s[i:] -} - -func expectQuality(s string) (q float64, rest string) { - switch { - case len(s) == 0: - return -1, "" - case s[0] == '0': - q = 0 - case s[0] == '1': - q = 1 - default: - return -1, "" - } - s = s[1:] - if !strings.HasPrefix(s, ".") { - return q, s - } - s = s[1:] - i := 0 - n := 0 - d := 1 - for ; i < len(s); i++ { - b := s[i] - if b < '0' || b > '9' { - break - } - n = n*10 + int(b) - '0' - d *= 10 - } - return q + float64(n)/float64(d), s[i:] -} - -func expectTokenOrQuoted(s string) (value string, rest string) { - if !strings.HasPrefix(s, "\"") { - return expectToken(s) - } - s = s[1:] - for i := 0; i < len(s); i++ { - switch s[i] { - case '"': - return s[:i], s[i+1:] - case '\\': - p := make([]byte, len(s)-1) - j := copy(p, s[:i]) - escape := true - for i = i + 1; i < len(s); i++ { - b := s[i] - switch { - case escape: - escape = false - p[j] = b - j++ - case b == '\\': - escape = true - case b == '"': - return string(p[:j]), s[i+1:] - default: - p[j] = b - j++ - } - } - return "", "" - } - } - return "", "" -} diff --git a/vendor/github.com/kevinpollet/nego/.editorconfig b/vendor/github.com/kevinpollet/nego/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..2046d0ef7770f8ae0808a9a2400f92e889d7bb10 --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_size = 2 +indent_style = space +trim_trailing_whitespace = true + +[*.go] +indent_size = 8 +indent_style = tab + +[*.md] +trim_trailing_whitespace = false diff --git a/vendor/github.com/kevinpollet/nego/.gitignore b/vendor/github.com/kevinpollet/nego/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e43b0f988953ae3a84b00331d0ccf5f7d51cb3cf --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/vendor/github.com/kevinpollet/nego/.golangci.yml b/vendor/github.com/kevinpollet/nego/.golangci.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ba42364e7b6bd582387747e2c3e5ca07f373ad1 --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/.golangci.yml @@ -0,0 +1,2 @@ +linters: + enable-all: true diff --git a/vendor/github.com/kevinpollet/nego/CODE_OF_CONDUCT.md b/vendor/github.com/kevinpollet/nego/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000000000000000000000000000000..22a38214321a92518529bcb94829ad0f7937f2e7 --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at pollet.kevin@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/github.com/kevinpollet/nego/LICENSE.md b/vendor/github.com/kevinpollet/nego/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..41579d1b448d4f270e0a4a99ef7a9706bcde025d --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/LICENSE.md @@ -0,0 +1,24 @@ +# The MIT License (MIT) + +Copyright © `2020` `kevinpollet <pollet.kevin@gmail.com>` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kevinpollet/nego/README.md b/vendor/github.com/kevinpollet/nego/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a8a2be4b51a20dd7cef4172b81ec6c5be49ba9ae --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/README.md @@ -0,0 +1,68 @@ +# nego <!-- omit in toc --> + +[](https://github.com/kevinpollet/nego/actions) +[](https://goreportcard.com/report/github.com/kevinpollet/nego) +[](https://pkg.go.dev/github.com/kevinpollet/nego) +[](https://conventionalcommits.org) +[](./LICENSE.md) + +Package `nego` provides an implementation of [HTTP Content Negotiation](https://en.wikipedia.org/wiki/Content_negotiation) compliant with [RFC 7231](https://tools.ietf.org/html/rfc7231#section-5.3). + +As defined in [RFC 7231](https://tools.ietf.org/html/rfc7231#section-5.3) the following request headers are sent by a user agent to engage in a proactive negotiation of the response content: `Accept`, `Accept-Charset`, `Accept-Language` and `Accept-Encoding`. This package provides convenient functions to negotiate the best and acceptable response content `type`, `charset`, `language` and `encoding`. + +## Table of Contents <!-- omit in toc --> + +- [Install](#install) +- [Usage](#usage) +- [Examples](#examples) +- [Contributing](#contributing) +- [License](#license) + +## Install + +```shell +go get github.com/kevinpollet/nego +``` + +## Usage + +```go +package main + +import ( + "log" + "net/http" + "github.com/kevinpollet/nego" +) + +func main() { + handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + contentCharset := nego.NegotiateContentCharset(req, "utf-8") + contentEncoding := nego.NegotiateContentEncoding(req, "gzip", "deflate") + contentLanguage := nego.NegotiateContentLanguage(req, "fr", "en") + contentType := nego.NegotiateContentType(req, "text/html", "text/plain") + ... + }) +} +``` + +## Examples + +The [examples](./examples) directory contains the following examples: + +- [echo](./examples/echo) — This example returns the negotiated response content `type`, `charset`, `encoding` and `language`. +- [compress](./examples/compress) — This example negotiates the response content `encoding` and compresses the response body if the client supports the `gzip` encoding. + +## Contributing + +Contributions are welcome! + +Want to file a bug, request a feature or contribute some code? + +1. Check out the [Code of Conduct](./CODE_OF_CONDUCT.md). +2. Check for an existing [issue](https://github.com/kevinpollet/nego/issues) corresponding to your bug or feature request. +3. Open an issue to describe your bug or feature request. + +## License + +[MIT](./LICENSE.md) diff --git a/vendor/github.com/kevinpollet/nego/go.mod b/vendor/github.com/kevinpollet/nego/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..56cc38a0f3df472d867c1d4b5efd68fef6427f2f --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/go.mod @@ -0,0 +1,5 @@ +module github.com/kevinpollet/nego + +go 1.14 + +require github.com/stretchr/testify v1.6.1 diff --git a/vendor/github.com/kevinpollet/nego/go.sum b/vendor/github.com/kevinpollet/nego/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..6b06ef118d9d337e499a10be53d2f9ca00706849 --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/go.sum @@ -0,0 +1,18 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= +github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/kevinpollet/nego/negotiate.go b/vendor/github.com/kevinpollet/nego/negotiate.go new file mode 100644 index 0000000000000000000000000000000000000000..ea2a894bce97f0355150610d1ce3c8fb1102202e --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/negotiate.go @@ -0,0 +1,128 @@ +// Package nego implements HTTP Content Negotiation functions compliant with RFC 7231. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3 for more details. +// +// Example +// +// This example shows how to use the negotiation functions. +// +// import ( +// "net/http" +// "github.com/kevinpollet/nego" +// ) +// +// handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { +// nego.NegotiateContentCharset(req, "utf-8") +// nego.NegotiateContentEncoding(req, "gzip", "deflate") +// nego.NegotiateContentLanguage(req, "fr", "en") +// nego.NegotiateContentType(req, "text/plain") +// }) +package nego + +import "net/http" + +// The identity encoding constant used as a synonym for "no encoding" in order to communicate when +// no encoding is preferred. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3.4 for more details. +const EncodingIdentity = "identity" + +// NegotiateContentCharset returns the best acceptable charset offer to use in the response according +// to the Accept-Charset request's header. If the given offer list is empty or no offer is acceptable +// then, an empty string is returned. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3.3 for more details. +func NegotiateContentCharset(req *http.Request, offerCharsets ...string) string { + bestQvalue := 0.0 + bestCharset := "" + + acceptCharsets, exists := parseAccept(req.Header, "Accept-Charset") + if !exists && len(offerCharsets) > 0 { + return offerCharsets[0] + } + + for _, offer := range offerCharsets { + if qvalue, exists := acceptCharsets.qvalue(offer); exists && qvalue > bestQvalue { + bestCharset = offer + bestQvalue = qvalue + } + } + + return bestCharset +} + +// NegotiateContentEncoding returns the best acceptable encoding offer to use in the response according +// to the Accept-Encoding request's header. If the given offer list is empty or no offer is acceptable +// then, an empty string is returned. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3.4 for more details. +func NegotiateContentEncoding(req *http.Request, offerEncodings ...string) string { + bestQvalue := 0.0 + bestEncoding := "" + + acceptEncodings, exists := parseAccept(req.Header, "Accept-Encoding") + if !exists && len(offerEncodings) > 0 { + return offerEncodings[0] + } + + for _, offer := range offerEncodings { + if qvalue, exists := acceptEncodings.qvalue(offer); exists && qvalue > bestQvalue { + bestEncoding = offer + bestQvalue = qvalue + } + } + + if qvalue, exists := acceptEncodings.qvalue(EncodingIdentity); bestEncoding == "" && (!exists || qvalue > 0.0) { + return EncodingIdentity + } + + return bestEncoding +} + +// NegotiateContentLanguage returns the best acceptable language offer to use in the response according +// to the Accept-Language request's header. If the given offer list is empty or no offer is acceptable +// then, an empty string is returned. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3.5 for more details. +func NegotiateContentLanguage(req *http.Request, offerLanguages ...string) string { + bestQvalue := 0.0 + bestLanguage := "" + + acceptLanguages, exists := parseAccept(req.Header, "Accept-Language") + if !exists && len(offerLanguages) > 0 { + return offerLanguages[0] + } + + for _, offer := range offerLanguages { + if qvalue, exists := acceptLanguages.qvalue(offer); exists && qvalue > bestQvalue { + bestLanguage = offer + bestQvalue = qvalue + } + } + + return bestLanguage +} + +// NegotiateContentType returns the best acceptable media type offer to use in the response according +// to the Accept-Language request's header. If the given offer list is empty or no offer is acceptable +// then, an empty string is returned. +// +// See https://tools.ietf.org/html/rfc7231#section-5.3.2 for more details. +func NegotiateContentType(req *http.Request, offerMediaTypes ...string) string { + bestMediaType := "" + bestQvalue := 0.0 + + acceptTypes, exists := parseAccept(req.Header, "Accept") + if !exists && len(offerMediaTypes) > 0 { + return offerMediaTypes[0] + } + + for _, offer := range offerMediaTypes { + if qvalue, exists := acceptTypes.qvalue(offer); exists && qvalue > bestQvalue { + bestMediaType = offer + bestQvalue = qvalue + } + } + + return bestMediaType +} diff --git a/vendor/github.com/kevinpollet/nego/parser.go b/vendor/github.com/kevinpollet/nego/parser.go new file mode 100644 index 0000000000000000000000000000000000000000..b533e995795965bf7be81d22285ba93e9a9070b9 --- /dev/null +++ b/vendor/github.com/kevinpollet/nego/parser.go @@ -0,0 +1,78 @@ +package nego + +import ( + "net/http" + "strconv" + "strings" +) + +type accept map[string]float64 + +func (a accept) qvalue(offer string) (float64, bool) { + if qvalue, exists := a[offer]; exists { + return qvalue, exists + } + + if !strings.Contains(offer, "/") { + qvalue, exists := a["*"] + return qvalue, exists + } + + slashIndex := strings.Index(offer, "/") + + if qvalue, exists := a[offer[:slashIndex]+"/*"]; exists { + return qvalue, exists + } + + if qvalue, exists := a["*/*"]; exists { + return qvalue, exists + } + + return 0.0, false // nolint +} + +// parseAccept parses the values of a content negotiation header. The following request headers are sent +// by a user agent to engage in proactive negotiation: Accept, Accept-Charset, Accept-Encoding, Accept-Language. +func parseAccept(header http.Header, key string) (accept, bool) { + values, exists := header[key] + accepts := make(map[string]float64) + + for _, value := range values { + if value == "" { + continue + } + + for _, spec := range strings.Split(value, ",") { + name, qvalue := parseSpec(spec) + accepts[name] = qvalue + } + } + + return accepts, exists +} + +func parseSpec(spec string) (string, float64) { + qvalue := 1.0 + sToken := strings.ReplaceAll(spec, " ", "") + parts := strings.Split(sToken, ";") + + for _, param := range parts[1:] { + lowerParam := strings.ToLower(param) + qvalueStr := strings.TrimPrefix(lowerParam, "q=") + + if qvalueStr != lowerParam { + qvalue = parseQuality(qvalueStr) + } + } + + return parts[0], qvalue +} + +func parseQuality(value string) float64 { + float, err := strconv.ParseFloat(value, 64) + if err != nil { + return -1 + } + + return float +} diff --git a/vendor/github.com/lpar/gzipped/fileserver.go b/vendor/github.com/lpar/gzipped/fileserver.go deleted file mode 100644 index eb8d3ba9da765061048505b267d9780ad1f3ccf9..0000000000000000000000000000000000000000 --- a/vendor/github.com/lpar/gzipped/fileserver.go +++ /dev/null @@ -1,185 +0,0 @@ -package gzipped - -import ( - "fmt" - "net/http" - "os" - "path" - "sort" - "strings" - - "github.com/golang/gddo/httputil/header" -) - -// Encoding represents an Accept-Encoding. All of these fields are pre-populated -// in the supportedEncodings variable, except the clientPreference which is updated -// (by copying a value from supportedEncodings) when examining client headers. -type encoding struct { - name string // the encoding name - extension string // the file extension (including a leading dot) - clientPreference float64 // the client's preference - serverPreference int // the server's preference -} - -// Helper type to sort encodings, using clientPreference first, and then -// serverPreference as a tie breaker. This sorts in *DESCENDING* order, rather -// than the usual ascending order. -type encodingByPreference []encoding - -// Implement the sort.Interface interface -func (e encodingByPreference) Len() int { return len(e) } -func (e encodingByPreference) Less(i, j int) bool { - if e[i].clientPreference == e[j].clientPreference { - return e[i].serverPreference > e[j].serverPreference - } - return e[i].clientPreference > e[j].clientPreference -} -func (e encodingByPreference) Swap(i, j int) { e[i], e[j] = e[j], e[i] } - -// Supported encodings. Higher server preference means the encoding will be when -// the client doesn't have an explicit preference. -var supportedEncodings = [...]encoding{ - { - name: "gzip", - extension: ".gz", - serverPreference: 1, - }, - { - name: "br", - extension: ".br", - serverPreference: 2, - }, -} - -type fileHandler struct { - root http.FileSystem -} - -// FileServer is a drop-in replacement for Go's standard http.FileServer -// which adds support for static resources precompressed with gzip, at -// the cost of removing the support for directory browsing. -// -// If file filename.ext has a compressed version filename.ext.gz alongside -// it, if the client indicates that it accepts gzip-compressed data, and -// if the .gz file can be opened, then the compressed version of the file -// will be sent to the client. Otherwise the request is passed on to -// http.ServeContent, and the raw (uncompressed) version is used. -// -// It is up to you to ensure that the compressed and uncompressed versions -// of files match and have sensible timestamps. -// -// Compressed or not, requests are fulfilled using http.ServeContent, and -// details like accept ranges and content-type sniffing are handled by that -// method. -func FileServer(root http.FileSystem) http.Handler { - return &fileHandler{root} -} - -func (f *fileHandler) openAndStat(path string) (http.File, os.FileInfo, error) { - file, err := f.root.Open(path) - var info os.FileInfo - // This slightly weird variable reuse is so we can get 100% test coverage - // without having to come up with a test file that can be opened, yet - // fails to stat. - if err == nil { - info, err = file.Stat() - } - if err != nil { - return file, nil, err - } - if info.IsDir() { - return file, nil, fmt.Errorf("%s is directory", path) - } - return file, info, nil -} - -// Build a []encoding based on the Accept-Encoding header supplied by the -// client. The returned list will be sorted from most-preferred to -// least-preferred. -func acceptable(r *http.Request) []encoding { - // list of acceptable encodings, as provided by the client - acceptEncodings := make([]encoding, 0, len(supportedEncodings)) - - // the quality of the * encoding; this will be -1 if not sent by client - starQuality := -1. - - // encodings we've already seen (used to handle duplicates and *) - seenEncodings := make(map[string]interface{}) - - // match the client accept encodings against the ones we support - for _, aspec := range header.ParseAccept(r.Header, "Accept-Encoding") { - if _, alreadySeen := seenEncodings[aspec.Value]; alreadySeen { - continue - } - seenEncodings[aspec.Value] = nil - if aspec.Value == "*" { - starQuality = aspec.Q - continue - } - for _, known := range supportedEncodings { - if aspec.Value == known.name && aspec.Q != 0 { - enc := known - enc.clientPreference = aspec.Q - acceptEncodings = append(acceptEncodings, enc) - break - } - } - } - - // If the client sent Accept: *, add all our extra known encodings. Use - // the quality of * as the client quality for the encoding. - if starQuality != -1. { - for _, known := range supportedEncodings { - if _, seen := seenEncodings[known.name]; !seen { - enc := known - enc.clientPreference = starQuality - acceptEncodings = append(acceptEncodings, enc) - } - } - } - - // sort the encoding based on client/server preference - sort.Sort(encodingByPreference(acceptEncodings)) - return acceptEncodings -} - -// Find the best file to serve based on the client's Accept-Encoding, and which -// files actually exist on the filesystem. If no file was found that can satisfy -// the request, the error field will be non-nil. -func (f *fileHandler) findBestFile(w http.ResponseWriter, r *http.Request, fpath string) (http.File, os.FileInfo, error) { - // find the best matching file - for _, enc := range acceptable(r) { - if file, info, err := f.openAndStat(fpath + enc.extension); err == nil { - w.Header().Set("Content-Encoding", enc.name) - return file, info, nil - } - } - - // if nothing found, try the base file with no content-encoding - return f.openAndStat(fpath) -} - -func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - upath := r.URL.Path - if !strings.HasPrefix(upath, "/") { - upath = "/" + upath - r.URL.Path = upath - } - fpath := path.Clean(upath) - if strings.HasSuffix(fpath, "/") { - // If you wanted to put back directory browsing support, this is - // where you'd do it. - http.NotFound(w, r) - return - } - - // Find the best acceptable file, including trying uncompressed - if file, info, err := f.findBestFile(w, r, fpath); err == nil { - http.ServeContent(w, r, fpath, info.ModTime(), file) - file.Close() - return - } - - // Doesn't exist, compressed or uncompressed - http.NotFound(w, r) -} diff --git a/vendor/github.com/lpar/gzipped/go.mod b/vendor/github.com/lpar/gzipped/go.mod deleted file mode 100644 index d8f55b48d0267c0b729605914b543dc6c06749cd..0000000000000000000000000000000000000000 --- a/vendor/github.com/lpar/gzipped/go.mod +++ /dev/null @@ -1,6 +0,0 @@ -module github.com/lpar/gzipped - -require ( - github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0 - github.com/google/go-cmp v0.2.0 // indirect -) diff --git a/vendor/github.com/lpar/gzipped/go.sum b/vendor/github.com/lpar/gzipped/go.sum deleted file mode 100644 index bd21c98cdf474a46d9082da0fb3f270dc5976770..0000000000000000000000000000000000000000 --- a/vendor/github.com/lpar/gzipped/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5 h1:yrv1uUvgXH/tEat+wdvJMRJ4g51GlIydtDpU9pFjaaI= -github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= -github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0 h1:CfaPdCDbZu8jSwjq0flJv2u+WreQM0KqytUQahZ6Xf4= -github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= diff --git a/vendor/github.com/lpar/gzipped/.gitignore b/vendor/github.com/lpar/gzipped/v2/.gitignore similarity index 97% rename from vendor/github.com/lpar/gzipped/.gitignore rename to vendor/github.com/lpar/gzipped/v2/.gitignore index daf913b1b347aae6de6f48d599bc89ef8c8693d6..25e241ae48140cc667f1d518453c994e85be09a7 100644 --- a/vendor/github.com/lpar/gzipped/.gitignore +++ b/vendor/github.com/lpar/gzipped/v2/.gitignore @@ -22,3 +22,4 @@ _testmain.go *.exe *.test *.prof +.idea diff --git a/vendor/github.com/lpar/gzipped/LICENSE b/vendor/github.com/lpar/gzipped/v2/LICENSE similarity index 95% rename from vendor/github.com/lpar/gzipped/LICENSE rename to vendor/github.com/lpar/gzipped/v2/LICENSE index 41b1afb7803f572206eb3b173790a55ab843634e..fb91431115f068900d2b3b6efc417372d5de2698 100644 --- a/vendor/github.com/lpar/gzipped/LICENSE +++ b/vendor/github.com/lpar/gzipped/v2/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, IBM Corporation. All rights reserved. +Copyright (c) 2016-2020, IBM Corporation. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/vendor/github.com/lpar/gzipped/README.md b/vendor/github.com/lpar/gzipped/v2/README.md similarity index 56% rename from vendor/github.com/lpar/gzipped/README.md rename to vendor/github.com/lpar/gzipped/v2/README.md index f8d8ef6e2353a5bf89283201d236d9e767718f67..6c55f14f45f6bc547ac343bb0bb721426918c4fd 100644 --- a/vendor/github.com/lpar/gzipped/README.md +++ b/vendor/github.com/lpar/gzipped/v2/README.md @@ -19,12 +19,12 @@ Suppose `/var/www/assets/css` contains your style sheets, and you want to make t "log" "net/http" - "github.com/lpar/gzipped" + "github.com/lpar/gzipped/v2" ) func main() { log.Fatal(http.ListenAndServe(":8080", http.StripPrefix("/css", - gzipped.FileServer(http.Dir("/var/www/assets/css"))))) + gzipped.FileServer(gzipped.Dir("/var/www/assets/css"))))) } // curl localhost:8080/css/styles.css @@ -33,9 +33,19 @@ Using [httprouter](https://github.com/julienschmidt/httprouter)? router := httprouter.New() router.Handler("GET", "/css/*filepath", - gzipped.FileServer(http.Dir("/var/www/assets/css")))) + gzipped.FileServer(gzipped.Dir("/var/www/assets/css")))) log.Fatal(http.ListenAndServe(":8080", router) +## Change history + +In version 2.0, we require use of `gzipped.Dir`, a drop-in replacement for `http.Dir`. Our `gzipped.Dir` has the +additional feature of letting us check for the existence of files without opening them. This means we can scan +to see what encodings are available, then negotiate that list against the client's preferences, and then only (attempt +to) open and serve the correct file. + +This change means we can let `github.com/kevinpollet/nego` handle the content negotiation, and remove the dependency +on gddo (godoc), which was pulling in 48 dependencies (see [#6](https://github.com/lpar/gzipped/issues/6)). + ## Detail For any given request at `/path/filename.ext`, if: @@ -61,9 +71,40 @@ tricky details are handled by that method. It is up to you to ensure that your compressed and uncompressed resources are kept in sync. -Directory browsing isn't supported. (You probably don't want it on your -application anyway, and if you do then you probably don't want Go's default -implementation.) +Directory browsing isn't supported. That includes remapping URLs ending in `/` to `index.html`, +`index.htm`, `Welcome.html` or whatever -- if you want URLs remapped that way, +I suggest having your router do it, or using middleware, so that you have control +over the behavior. For example, to add support for `index.html` files in directories: + +```go +func withIndexHTML(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/") { + newpath := path.Join(r.URL.Path, "index.html") + r.URL.Path = newpath + } + h.ServeHTTP(w, r) + }) +} +// ... + +fs := withIndexHTML(gzipped.FileServer(http.Dir("/var/www"))) +``` + +Or to add support for directory browsing: + +```go +func withBrowsing(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/") { + http.ServeFile(w, r, ".") + } + }) +} +// ... + +fs := withBrowsing(gzipped.FileServer(http.Dir("/var/www"))) +``` ## Related diff --git a/vendor/github.com/lpar/gzipped/v2/fileserver.go b/vendor/github.com/lpar/gzipped/v2/fileserver.go new file mode 100644 index 0000000000000000000000000000000000000000..fb33224f50ef12035d7698b0f00ecc598a094603 --- /dev/null +++ b/vendor/github.com/lpar/gzipped/v2/fileserver.go @@ -0,0 +1,153 @@ +package gzipped + +import ( + "fmt" + "net/http" + "os" + "path" + "strconv" + "strings" + + "github.com/kevinpollet/nego" +) + +// List of encodings we would prefer to use, in order of preference, best first. +var preferredEncodings = []string{"br", "gzip", "identity"} + +// File extension to use for different encodings. +func extensionForEncoding(encname string) string { + switch encname { + case "gzip": + return ".gz" + case "br": + return ".br" + case "identity": + return "" + } + return "" +} + +// Function to negotiate the best content encoding +// Pulled out here so we have the option of overriding nego's behavior and so we can test +func negotiate(r *http.Request, available []string) string { + return nego.NegotiateContentEncoding(r, available...) +} + +type fileHandler struct { + root FileSystem +} + +// FileServer is a drop-in replacement for Go's standard http.FileServer +// which adds support for static resources precompressed with gzip, at +// the cost of removing the support for directory browsing. +// +// If file filename.ext has a compressed version filename.ext.gz alongside +// it, if the client indicates that it accepts gzip-compressed data, and +// if the .gz file can be opened, then the compressed version of the file +// will be sent to the client. Otherwise the request is passed on to +// http.ServeContent, and the raw (uncompressed) version is used. +// +// It is up to you to ensure that the compressed and uncompressed versions +// of files match and have sensible timestamps. +// +// Compressed or not, requests are fulfilled using http.ServeContent, and +// details like accept ranges and content-type sniffing are handled by that +// method. +func FileServer(root FileSystem) http.Handler { + return &fileHandler{root} +} + +func (f *fileHandler) openAndStat(path string) (http.File, os.FileInfo, error) { + file, err := f.root.Open(path) + var info os.FileInfo + // This slightly weird variable reuse is so we can get 100% test coverage + // without having to come up with a test file that can be opened, yet + // fails to stat. + if err == nil { + info, err = file.Stat() + } + if err != nil { + return file, nil, err + } + if info.IsDir() { + return file, nil, fmt.Errorf("%s is directory", path) + } + return file, info, nil +} + +const ( + acceptEncodingHeader = "Accept-Encoding" + contentEncodingHeader = "Content-Encoding" + contentLengthHeader = "Content-Length" + rangeHeader = "Range" + varyHeader = "Vary" +) + +// Find the best file to serve based on the client's Accept-Encoding, and which +// files actually exist on the filesystem. If no file was found that can satisfy +// the request, the error field will be non-nil. +func (f *fileHandler) findBestFile(w http.ResponseWriter, r *http.Request, fpath string) (http.File, os.FileInfo, error) { + ae := r.Header.Get(acceptEncodingHeader) + if ae == "" { + return f.openAndStat(fpath) + } + // Got an accept header? See what possible encodings we can send by looking for files + var available []string + for _, posenc := range preferredEncodings { + ext := extensionForEncoding(posenc) + fname := fpath + ext + if f.root.Exists(fname) { + available = append(available, posenc) + } + } + if len(available) == 0 { + return f.openAndStat(fpath) + } + // Carry out standard HTTP negotiation + negenc := negotiate(r, available) + if negenc == "" || negenc == "identity" { + // If we fail to negotiate anything or if we negotiated the identity encoding, again try the base file + return f.openAndStat(fpath) + } + ext := extensionForEncoding(negenc) + if file, info, err := f.openAndStat(fpath + ext); err == nil { + wHeader := w.Header() + wHeader[contentEncodingHeader] = []string{negenc} + wHeader.Add(varyHeader, acceptEncodingHeader) + + if len(r.Header[rangeHeader]) == 0 { + // If not a range request then we can easily set the content length which the + // Go standard library does not do if "Content-Encoding" is set. + wHeader[contentLengthHeader] = []string{strconv.FormatInt(info.Size(), 10)} + } + return file, info, nil + } + + // If all else failed, fall back to base file once again + return f.openAndStat(fpath) +} + +func (f *fileHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + upath := r.URL.Path + if !strings.HasPrefix(upath, "/") { + upath = "/" + upath + r.URL.Path = upath + } + fpath := path.Clean(upath) + if strings.HasSuffix(fpath, "/") { + // If you wanted to put back directory browsing support, this is + // where you'd do it. + http.NotFound(w, r) + return + } + + // Find the best acceptable file, including trying uncompressed + if file, info, err := f.findBestFile(w, r, fpath); err == nil { + http.ServeContent(w, r, fpath, info.ModTime(), file) + file.Close() + return + } + + // Doesn't exist, compressed or uncompressed + http.NotFound(w, r) +} diff --git a/vendor/github.com/lpar/gzipped/v2/filesystem.go b/vendor/github.com/lpar/gzipped/v2/filesystem.go new file mode 100644 index 0000000000000000000000000000000000000000..2faa408e710a00adffd6cf04d052ed8fa5ec16cc --- /dev/null +++ b/vendor/github.com/lpar/gzipped/v2/filesystem.go @@ -0,0 +1,41 @@ +package gzipped + +import ( + "net/http" + "os" + "path" + "path/filepath" + "strings" +) + +// FileSystem is a wrapper around the http.FileSystem interface, adding a method to let us check for the existence +// of files without (attempting to) open them. +type FileSystem interface { + http.FileSystem + Exists(string) bool +} + +// Dir is a replacement for the http.Dir type, and implements FileSystem. +type Dir string + +// Exists tests whether a file with the specified name exists, resolved relative to the base directory. +func (d Dir) Exists(name string) bool { + if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) { + return false + } + dir := string(d) + if dir == "" { + dir = "." + } + fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) + _, err := os.Stat(fullName) + if err != nil { + return false + } + return true +} + +// Open defers to http.Dir's Open so that gzipped.Dir implements http.FileSystem. +func (d Dir) Open(name string) (http.File, error) { + return http.Dir(d).Open(name) +} diff --git a/vendor/github.com/lpar/gzipped/v2/go.mod b/vendor/github.com/lpar/gzipped/v2/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..2723db0284743b408a0643d47eb121b65cc25f92 --- /dev/null +++ b/vendor/github.com/lpar/gzipped/v2/go.mod @@ -0,0 +1,7 @@ +module github.com/lpar/gzipped/v2 + +require ( + github.com/kevinpollet/nego v0.0.0-20200324111829-b3061ca9dd9d +) + +go 1.13 diff --git a/vendor/github.com/lpar/gzipped/v2/go.sum b/vendor/github.com/lpar/gzipped/v2/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..bf153be61f065c2e5ee6710e11333b166057f4ac --- /dev/null +++ b/vendor/github.com/lpar/gzipped/v2/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kevinpollet/nego v0.0.0-20200324111829-b3061ca9dd9d h1:BaIpmhcqpBnz4+NZjUjVGxKNA+/E7ovKsjmwqjXcGYc= +github.com/kevinpollet/nego v0.0.0-20200324111829-b3061ca9dd9d/go.mod h1:3FSWkzk9h42opyV0o357Fq6gsLF/A6MI/qOca9kKobY= +github.com/lpar/accept v0.1.0 h1:q4+k1TJuCfoe8cIBRLTfiMMoiaHKpn1rlD1yYE/wj7o= +github.com/lpar/accept v0.1.0/go.mod h1:/ZcJqAhzugu4J4EZ7hwixKslL0y07dMxQzIZZLpbZbk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/modules.txt b/vendor/modules.txt index f9bd2111a5c33a9970e8ab23b5fbac41f0f2902f..d49abb20e4a4b1b43a809ae8a70ae23faf80f36c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -22,8 +22,6 @@ github.com/form3tech-oss/jwt-go github.com/gogo/protobuf/gogoproto github.com/gogo/protobuf/proto github.com/gogo/protobuf/protoc-gen-gogo/descriptor -# github.com/golang/gddo v0.0.0-20190312205958-5a2505f3dbf0 -github.com/golang/gddo/httputil/header # github.com/golang/protobuf v1.5.2 github.com/golang/protobuf/descriptor github.com/golang/protobuf/jsonpb @@ -56,9 +54,12 @@ github.com/jmcvetta/randutil github.com/jonboulle/clockwork # github.com/json-iterator/go v1.1.10 github.com/json-iterator/go -# github.com/lpar/gzipped v1.1.1-0.20190413023519-5d9a18ea7f47 +# github.com/kevinpollet/nego v0.0.0-20201213172553-d6ce2e30cfd6 ## explicit -github.com/lpar/gzipped +github.com/kevinpollet/nego +# github.com/lpar/gzipped/v2 v2.0.2 +## explicit +github.com/lpar/gzipped/v2 # github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/miekg/dns v1.1.42