From 359c9ba4975b75c09985c1a63d7de15d2f8b20fb Mon Sep 17 00:00:00 2001 From: Will McCutchen Date: Fri, 30 Jun 2023 16:32:46 -0400 Subject: [PATCH] fix: minor refactor of base64 handling Noticed how inelegant the baes64 handling was, mostly due to needing to hard code and handle the valid operations (encode and decode) in two different functions. --- httpbin/handlers.go | 16 +------- httpbin/handlers_test.go | 6 +-- httpbin/helpers.go | 79 +++++++++++++++++++++------------------- 3 files changed, 46 insertions(+), 55 deletions(-) diff --git a/httpbin/handlers.go b/httpbin/handlers.go index a189a07f..a040ad97 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -1051,21 +1051,9 @@ func (h *HTTPBin) UUID(w http.ResponseWriter, _ *http.Request) { // Base64 - encodes/decodes input data func (h *HTTPBin) Base64(w http.ResponseWriter, r *http.Request) { - b, err := newBase64Helper(r.URL.Path) + result, err := newBase64Helper(r.URL.Path, h.MaxBodySize).transform() if err != nil { - writeError(w, http.StatusBadRequest, fmt.Errorf("invalid base64 data: %w", err)) - return - } - - var result []byte - if b.operation == "decode" { - result, err = b.Decode() - } else { - result, err = b.Encode() - } - - if err != nil { - writeError(w, http.StatusBadRequest, fmt.Errorf("%s failed: %w", b.operation, err)) + writeError(w, http.StatusBadRequest, err) return } writeResponse(w, http.StatusOK, textContentType, result) diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index 22f85afb..8dafb85d 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -2752,8 +2752,8 @@ func TestBase64(t *testing.T) { "decode failed", }, { - "/base64/decode/" + strings.Repeat("X", Base64MaxLen+1), - "Cannot handle input", + "/base64/decode/" + strings.Repeat("X", int(maxBodySize)+1), + "input data exceeds max length", }, { "/base64/", @@ -2765,7 +2765,7 @@ func TestBase64(t *testing.T) { }, { "/base64/decode/dmFsaWRfYmFzZTY0X2VuY29kZWRfc3RyaW5n/extra", - "invalid URL", + "decode failed", }, { "/base64/unknown/dmFsaWRfYmFzZTY0X2VuY29kZWRfc3RyaW5n", diff --git a/httpbin/helpers.go b/httpbin/helpers.go index b9c36b39..d708d528 100644 --- a/httpbin/helpers.go +++ b/httpbin/helpers.go @@ -20,9 +20,6 @@ import ( "time" ) -// Base64MaxLen - Maximum input length for Base64 functions -const Base64MaxLen = 2000 - // requestHeaders takes in incoming request and returns an http.Header map // suitable for inclusion in our response data structures. // @@ -385,60 +382,66 @@ func uuidv4() string { return fmt.Sprintf("%x-%x-%x-%x-%x", buff[0:4], buff[4:6], buff[6:8], buff[8:10], buff[10:]) } -// base64Helper - describes the base64 operation (encode|decode) and input data +// base64Helper encapsulates a base64 operation (encode or decode) and its input +// data. type base64Helper struct { + maxLen int64 operation string data string } -// newbase64Helper - create a new base64Helper struct -// Supports the following URL paths -// - /base64/input_str -// - /base64/encode/input_str -// - /base64/decode/input_str -func newBase64Helper(path string) (*base64Helper, error) { - parts := strings.Split(path, "/") - - if len(parts) != 3 && len(parts) != 4 { - return nil, errors.New("invalid URL") - } - - var b base64Helper - - // Validation for - /base64/input_str - if len(parts) == 3 { +// newBase64Helper creates a new base64Helper from a URL path, which should be +// in one of two forms: +// - /base64/ +// - /base64// +func newBase64Helper(path string, maxLen int64) *base64Helper { + parts := strings.SplitN(path, "/", 4) + b := &base64Helper{maxLen: maxLen} + switch len(parts) { + // Any other cases will be rejected when transform() is called + case 3: + // handle /base64/ b.operation = "decode" b.data = parts[2] - } else { - // Validation for - // - /base64/encode/input_str - // - /base64/encode/input_str + case 4: + // handle /base64// b.operation = parts[2] - if b.operation != "encode" && b.operation != "decode" { - return nil, fmt.Errorf("invalid operation: %s", b.operation) - } b.data = parts[3] } - if len(b.data) == 0 { + return b +} + +// transform performs the base64 operation on the input data. +func (b *base64Helper) transform() ([]byte, error) { + if dataLen := int64(len(b.data)); dataLen == 0 { return nil, errors.New("no input data") - } - if len(b.data) >= Base64MaxLen { - return nil, fmt.Errorf("input length - %d, Cannot handle input >= %d", len(b.data), Base64MaxLen) + } else if dataLen > b.maxLen { + return nil, fmt.Errorf("input data exceeds max length of %d", b.maxLen) } - return &b, nil + switch b.operation { + case "encode": + return b.encode(), nil + case "decode": + result, err := b.decode() + if err != nil { + return nil, fmt.Errorf("base64 decode failed: %w", err) + } + return result, nil + default: + return nil, fmt.Errorf("invalid operation: %s", b.operation) + } } -// Encode - encode data as URL-safe base64 -func (b *base64Helper) Encode() ([]byte, error) { +func (b *base64Helper) encode() []byte { + // always encode using the URL-safe character set buff := make([]byte, base64.URLEncoding.EncodedLen(len(b.data))) base64.URLEncoding.Encode(buff, []byte(b.data)) - return buff, nil + return buff } -// Decode - decode data from base64, attempting both URL-safe and standard -// encodings. -func (b *base64Helper) Decode() ([]byte, error) { +func (b *base64Helper) decode() ([]byte, error) { + // first, try URL-safe encoding, then std encoding if result, err := base64.URLEncoding.DecodeString(b.data); err == nil { return result, nil }