From 791610f1c893fc76d6fcf350a7383a2479aa723a Mon Sep 17 00:00:00 2001 From: Hank Donnay Date: Wed, 15 Jan 2020 14:42:26 -0500 Subject: [PATCH] clair: remove goautoneg This commit uses a hand-rolled Accept-Encoding parser instead of goautoneg. The new code recognizes commonly used values correct, whereas the goautoneg package is tailored for the Accept header. --- cmd/clair/httpcompress.go | 85 +++++++++++++++++++++++++++++++++++++-- go.mod | 1 - go.sum | 2 - 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/cmd/clair/httpcompress.go b/cmd/clair/httpcompress.go index b552d9a27c..4471ecf9e0 100644 --- a/cmd/clair/httpcompress.go +++ b/cmd/clair/httpcompress.go @@ -3,13 +3,16 @@ package main import ( "fmt" "io" + "mime" "net/http" + "sort" + "strconv" + "strings" "sync" "github.com/klauspost/compress/flate" "github.com/klauspost/compress/gzip" "github.com/klauspost/compress/snappy" - "github.com/markusthoemmes/goautoneg" ) // Compress wraps the provided http.Handler and provides transparent body @@ -48,8 +51,59 @@ type header interface { WriteHeader(int) } +// ParseAccept parses an "Accept-Encoding" header. +// +// Reports a sorted list of encodings and a map of disallowed encodings. +// Reports nil if no selections were present. +func parseAccept(h string) ([]accept, map[string]struct{}) { + if h == "" { + return nil, nil + } + + segs := strings.Split(h, ",") + ret := make([]accept, 0, len(segs)) + nok := make(map[string]struct{}) + for _, s := range segs { + a := accept{} + t, param, err := mime.ParseMediaType(s) + if err != nil { + continue + } + a.Type = t + if q, ok := param["q"]; ok { + if q == "0" { + nok[t] = struct{}{} + continue + } + qv, err := strconv.ParseFloat(param["q"], 64) + if err != nil { + nok[t] = struct{}{} + continue + } + a.Q = qv + } + ret = append(ret, a) + } + + sort.SliceStable(ret, func(i, j int) bool { + return ret[i].Q > ret[j].Q + }) + return ret, nok +} + +type accept struct { + Type string + Q float64 +} + // ServeHTTP implements http.Handler. func (c *compressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ae, nok := parseAccept(r.Header.Get("accept-encoding")) + if ae == nil { + // If there was no header, play it cool. + c.next.ServeHTTP(w, r) + return + } var ( flusher http.Flusher pusher http.Pusher @@ -59,7 +113,9 @@ func (c *compressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { pusher, _ = w.(http.Pusher) // Find the first accept-encoding we support. - for _, a := range goautoneg.ParseAccept(r.Header.Get("accept-encoding")) { + // See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 for + // all the sematics. + for _, a := range ae { switch a.Type { case "gzip": w.Header().Set("content-encoding", "gzip") @@ -82,6 +138,27 @@ func (c *compressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "identity": w.Header().Set("content-encoding", "identity") case "*": + // If we hit a star, it's technically OK to return any encoding not + // already specified. So, attempt to use gzip and then identity and + // give up. + // Clients that do extremely weird things like + // *;q=1.0, gzip;q=0.1, identity;q=0.1" + // deserve extremely weird replies. + _, gznok := nok["gzip"] + _, idnok := nok["identity"] + switch { + case !gznok: + w.Header().Set("content-encoding", "gzip") + gz := c.gzip.Get().(*gzip.Writer) + gz.Reset(w) + defer c.gzip.Put(gz) + cw = gz + case !idnok: + w.Header().Set("content-encoding", "identity") + default: + w.WriteHeader(http.StatusNotAcceptable) + return + } default: continue } @@ -103,7 +180,7 @@ func (c *compressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // This is a giant truth table to make anonymous types that satisfy as many // optional interfaces as possible. // - // We care about 3 interfaces, so there are 2^3 == 8 combinations + // We care about 3 interfaces, so there are 2^3 == 8 combinations. switch { case flusher == nil && pusher == nil && cw == nil: nw = w @@ -148,7 +225,7 @@ func (c *compressHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Pusher }{w, cw, flusher, pusher} default: - panic(fmt.Sprintf("unexpect type combination: %T/%T/%T", flusher, pusher, cw)) + panic(fmt.Sprintf("unexpected type combination: %T/%T/%T", flusher, pusher, cw)) } c.next.ServeHTTP(nw, r) } diff --git a/go.mod b/go.mod index 14506f798b..5c3241aae3 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.13 require ( github.com/klauspost/compress v1.9.4 - github.com/markusthoemmes/goautoneg v0.0.0-20190713162725-c6008fefa5b1 github.com/mattn/go-sqlite3 v1.11.0 // indirect github.com/quay/claircore v0.0.13 github.com/rs/zerolog v1.16.0 diff --git a/go.sum b/go.sum index c78262c832..922ac1a396 100644 --- a/go.sum +++ b/go.sum @@ -169,8 +169,6 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/markusthoemmes/goautoneg v0.0.0-20190713162725-c6008fefa5b1 h1:Qhv4Ni88zV+8TY65yr2ak8xU4sblgs6aRT9RuGM5SNU= -github.com/markusthoemmes/goautoneg v0.0.0-20190713162725-c6008fefa5b1/go.mod h1:qFhy2RoC9EWZC7fgczcBbUpzGNFfIm5//VO/gde0AbI= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=