diff --git a/httpbin/helpers.go b/httpbin/helpers.go index ef48f60ff7391ffdf02fdde0ee51976f5b506873..98324df6072eda1bdb944ca918f4f0d2d0093f3e 100644 --- a/httpbin/helpers.go +++ b/httpbin/helpers.go @@ -339,3 +339,61 @@ func (b *base64Helper) Decode() ([]byte, error) { _, err := base64.StdEncoding.Decode(buff, []byte(b.data)) return buff, err } + +// parseAcceptEncodings parses an Accept-Encoding header value into a map of +// coding name and weight. Invalid or malformed codings are ignored, and +// parsing does not attempt to accommodate variations in whitespace in the +// input format. +// +// For more info: +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding +// https://httpwg.org/specs/rfc9110.html#field.accept-encoding +func parseAcceptEncodings(acceptEncodingHeaderVal string) codings { + var ( + codingVals = strings.Split(acceptEncodingHeaderVal, ",") + result = make(codings, 0, len(codingVals)) + ) + for _, codingVal := range codingVals { + if idx := strings.Index(codingVal, ";"); idx > -1 { + // should have a ;q=x.y weight + if !strings.HasPrefix(codingVal[idx:], ";q=") { + continue + } + // 3 == len(";q=") + if q, err := strconv.ParseFloat(codingVal[idx+3:], 64); err == nil { + name := strings.TrimSpace(codingVal[:idx]) + if len(name) > 0 { + result = append(result, coding{name, q}) + } + } + } else { + // no weight given, default to 1.0 + name := strings.TrimSpace(codingVal) + if len(name) > 0 { + result = append(result, coding{name, 1.0}) + } + } + } + return result +} + +type coding struct { + name string + weight float64 +} + +type codings []coding + +func (cs codings) Preferred() string { + var ( + result string + best float64 + ) + for _, c := range cs { + if c.weight > best { + result = c.name + best = c.weight + } + } + return result +} diff --git a/httpbin/helpers_test.go b/httpbin/helpers_test.go index 18d9208ac6c4e5a7c7644aec7e8ac30b90188355..35003ce4d776156381ea9ad57c30f31123a497fd 100644 --- a/httpbin/helpers_test.go +++ b/httpbin/helpers_test.go @@ -215,3 +215,94 @@ func Test_getClientIP(t *testing.T) { }) } } + +func TestParseAcceptEncodings(t *testing.T) { + t.Parallel() + + testCases := []struct { + given string + expected codings + expectPreferred string + }{ + { + given: "gzip", + expected: codings{{"gzip", 1.0}}, + expectPreferred: "gzip", + }, + { + given: "gzip, compress, br", + expected: codings{{"gzip", 1.0}, {"compress", 1.0}, {"br", 1.0}}, + expectPreferred: "gzip", // first entry wins tie + }, + { + given: "br;q=0.8, gzip;q=1.0, *;q=0.1", + expected: codings{{"br", 0.8}, {"gzip", 1.0}, {"*", 0.1}}, + expectPreferred: "gzip", + }, + { + given: "deflate, gzip;q=1.0, *;q=0.5", + expected: codings{{"deflate", 1.0}, {"gzip", 1.0}, {"*", 0.5}}, + expectPreferred: "deflate", + }, + { + given: "", + expected: codings{}, + }, + { + given: " ", + expected: codings{}, + }, + { + // malformed entry ignored + given: "deflate, ;", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + { + // malformed entry ignored + given: "deflate, gzip;", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + { + // malformed entry ignored + given: "deflate, gzip;q=", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + { + // malformed entry without encoding name is ignored + given: "deflate, ;q=1.0", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + { + // strict parsing of qvalues does not allow any spaces + given: "deflate, gzip; q=1.0", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + { + // strict parsing of qvalues does not allow any spaces + given: "deflate, gzip;q = 1.0", + expected: codings{{"deflate", 1.0}}, + expectPreferred: "deflate", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.given, func(t *testing.T) { + t.Parallel() + + got := parseAcceptEncodings(tc.given) + if !reflect.DeepEqual(tc.expected, got) { + t.Fatalf("failed to parse %q:\ngot: %#v\nwant: %#v", tc.given, got, tc.expected) + } + gotPreferred := got.Preferred() + if !reflect.DeepEqual(tc.expectPreferred, gotPreferred) { + t.Fatalf("expected preferred coding %#v, got %#v", tc.expectPreferred, gotPreferred) + } + }) + } +} diff --git a/httpbin/middleware.go b/httpbin/middleware.go index 13e937a3edac8ea983e50c6d0caef1cab9f4cad6..d3c26e51e67048cd8127b9e3e797cccdcd654ac7 100644 --- a/httpbin/middleware.go +++ b/httpbin/middleware.go @@ -58,6 +58,20 @@ func limitRequestSize(maxSize int64, h http.Handler) http.Handler { }) } +func gzipHandler(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // buf := &bytes.Buffer{} + // gzw := gzip.NewWriter(buf) + // gzw.Write(body) + // gzw.Close() + + // gzBody := buf.Bytes() + + // w.Header().Set("Content-Encoding", "gzip") + // writeJSON(w, gzBody, http.StatusOK) + }) +} + // headResponseWriter implements http.ResponseWriter in order to discard the // body of the response type headResponseWriter struct {