Skip to content

Commit

Permalink
test parse encodings
Browse files Browse the repository at this point in the history
  • Loading branch information
1cedsoda committed Feb 23, 2024
1 parent 3e2139d commit 9f78f68
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 23 deletions.
91 changes: 76 additions & 15 deletions encoding.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package traefik_umami_plugin

import (
"fmt"
"regexp"
"strconv"
"strings"
)
Expand All @@ -10,28 +12,86 @@ type Encoding struct {
q float64
}

func ParseEncoding(encoding string) *Encoding {
return &Encoding{
name: encoding,
q: 1.0,
const encodingRegex = `(?:([a-z*]+)(?:\;?q=(\d(?:\.\d)?))?)`

var encodingRegexp = regexp.MustCompile(encodingRegex)

var IdentityEncoding = &Encoding{name: Identity, q: 1.0}

var IdentityEncodings = &Encodings{encodings: []Encoding{*IdentityEncoding}}

var SupportedEncodingNames = []string{Identity, Gzip, Deflate}

func GetSupportedEncodings(q float64) *Encodings {
result := make([]Encoding, 0, len(SupportedEncodingNames))
for _, name := range SupportedEncodingNames {
result = append(result, Encoding{name: name, q: q})
}

return &Encodings{encodings: result}
}

func ParseEncoding(encoding string) (*Encoding, error) {
matches := encodingRegexp.FindStringSubmatch(encoding)

if len(matches) < 2 {
return nil, fmt.Errorf("no matches")
}

// get q
q := 1.0
if matches[2] != "" {
// if is float
if strings.Contains(matches[2], ".") {
q, _ = strconv.ParseFloat(matches[2], 64)
} else {
// if is int
qInt, _ := strconv.ParseInt(matches[2], 10, 64)
q = float64(qInt) / 100
}
}

return &Encoding{
name: matches[1],
q: q,
}, nil
}

type Encodings struct {
encodings []Encoding
}

func ParseEncodings(acceptEncoding string) *Encodings {
encodingList := strings.Split(acceptEncoding, ",")
result := make([]Encoding, 0, len(encodingList))

for _, encoding := range encodingList {
split := strings.Split(strings.TrimSpace(encoding), ";q=")
q := 1.0
if len(split) > 1 {
q, _ = strconv.ParseFloat(split[1], 64)
// remove any spaces
acceptEncoding = strings.ReplaceAll(acceptEncoding, " ", "")

// split by separator
encodingStringList := strings.Split(acceptEncoding, ",")

// parse
result := make([]Encoding, 0, len(encodingStringList))
for _, encodingString := range encodingStringList {
if encodingString == "" {
continue
}
result = append(result, Encoding{name: split[0], q: q})

encoding, err := ParseEncoding(encodingString)
if err != nil {
continue
}

// if * save index
if encoding.name == "*" {
// append all supported with asterisk q
for _, name := range SupportedEncodingNames {
result = append(result, Encoding{name: name, q: encoding.q})
}
}
result = append(result, *encoding)
}

if len(result) == 0 {
return IdentityEncodings
}

return &Encodings{encodings: result}
Expand All @@ -44,15 +104,16 @@ func (ae *Encodings) String() string {
result = append(result, encoding.name)
}

return strings.Join(result, ",")
return strings.Join(result, ", ")
}

func (ae *Encodings) FilterSupported() *Encodings {
result := make([]Encoding, 0, len(ae.encodings))

for _, encoding := range ae.encodings {
switch encoding.name {
case Gzip, Deflate, Identity:
// * added all encodings in ParseEncodings, we still keep it here to prevent unexpected behavior
case Gzip, Deflate, Identity, "*":
result = append(result, encoding)
}
}
Expand Down
46 changes: 46 additions & 0 deletions encodings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package traefik_umami_plugin

import (
"testing"
)

func TestParseEncodings(t *testing.T) {
acceptEncodingStrings := []string{
"gzip, deflate,br",
"",
"identity;q=0.5, *;q=0",
"gzip, deflate, br;q=0.5",
}
expectedList := []Encodings{
{encodings: []Encoding{{name: "gzip", q: 1.0}, {name: "deflate", q: 1.0}, {name: "br", q: 1.0}}},
{encodings: []Encoding{{name: "identity", q: 1.0}}},
{encodings: []Encoding{{name: "identity", q: 0.5}, {name: "identity", q: 0.0}, {name: "gzip", q: 0.0}, {name: "deflate", q: 0.0}, {name: "*", q: 0.0}}},
{encodings: []Encoding{{name: "gzip", q: 1.0}, {name: "deflate", q: 1.0}, {name: "br", q: 0.5}}},
}
for i, acceptEncodingString := range acceptEncodingStrings {
result := ParseEncodings(acceptEncodingString)
expected := &expectedList[i]
AssertEncodingsEquals(result, expected, t)
}
}

func AssertEncodingEquals(a, b *Encoding, t *testing.T) {
if a.name != b.name {
t.Errorf("name does not match: %+v != %+v", a, b)
return
}
if a.q != b.q {
t.Errorf("q does not match: %+v != %+v", a, b)
return
}
}

func AssertEncodingsEquals(a, b *Encodings, t *testing.T) {
if len(a.encodings) != len(b.encodings) {
t.Errorf("len does not match: %+v != %+v", a, b)
return
}
for i := range a.encodings {
AssertEncodingEquals(&a.encodings[i], &b.encodings[i], t)
}
}
3 changes: 2 additions & 1 deletion plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func (h *PluginHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if h.config.ScriptInjection && myReq.CouldBeInjectable() {
// intercept body
myRw := NewResponseWriter(rw)
encoding := myReq.GetSupportedEncodings().GetPreferred()
myReq.SetSupportedEncoding()
h.next.ServeHTTP(myRw, &myReq.Request)

Expand All @@ -154,7 +155,7 @@ func (h *PluginHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
body, err := myRw.ReadDecoded()
if err != nil {
newBody := InsertAtBodyEnd(body, h.scriptHtml)
myRw.WriteEncoded(newBody, myReq.GetPreferredSupportedEncoding())
myRw.WriteEncoded(newBody, encoding)
injected = true
}
}
Expand Down
9 changes: 4 additions & 5 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ type Request struct {
}

func (req *Request) SetSupportedEncoding() {
acceptEncoding := ParseEncodings(req.Header.Get("Accept-Encoding"))
supported := acceptEncoding.FilterSupported().String()
req.Header.Set("Accept-Encoding", supported)
supported := req.GetSupportedEncodings()
req.Header.Set("Accept-Encoding", supported.String())
}

func (req *Request) GetPreferredSupportedEncoding() *Encoding {
func (req *Request) GetSupportedEncodings() *Encodings {
acceptEncoding := req.Header.Get("Accept-Encoding")
return ParseEncodings(acceptEncoding).FilterSupported().GetPreferred()
return ParseEncodings(acceptEncoding).FilterSupported()
}

func (req *Request) CouldBeInjectable() bool {
Expand Down
7 changes: 5 additions & 2 deletions response_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ func (w *ResponseWriter) Read() []byte {
// Always uncompressed
// Error if encoding is not supported.
func (w *ResponseWriter) ReadDecoded() ([]byte, error) {
encoding := w.GetContentEncoding()
encoding, err := w.GetContentEncoding()
if err != nil {
return nil, err
}
return Decode(w.buffer, encoding)
}

Expand All @@ -54,7 +57,7 @@ func (w *ResponseWriter) WriteEncoded(plain []byte, encoding *Encoding) (int, er
}

// Content-Encoding header.
func (w *ResponseWriter) GetContentEncoding() *Encoding {
func (w *ResponseWriter) GetContentEncoding() (*Encoding, error) {
str := w.Header().Get("Content-Encoding")
return ParseEncoding(str)
}
Expand Down

0 comments on commit 9f78f68

Please sign in to comment.