Description

When header tags are added to a struct member for gin to use in BindHeader, they will be case-sensitive in the code. The Bind function for headers in turn relies directly on the http.Request.Header map instead of making use of the Header.Get function, which handles MIME decoding and ignores case when retrieving values. As a result, let's say you've got load balancers or some other infrastructure that rewrites your headers along the way, or the calling client uses all lower-case when your tags have specified the correct MIME standard case (eg. Content-Type as opposed to content-type), the headers will not marshal.

Tracing the gin code, the bind header function access the Header map[string][]string directly.

The following test illustrates the issue:

How to reproduce

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"

    "github.com/gin-gonic/gin"
)

type MyRequest struct {
    MyHeader   string `header:"Content-Type"`
    CustHeader string `header:"X-Custom-Header"`
}

func main() {
    var headers MyRequest
    w := httptest.NewRecorder()
    c, _ := gin.CreateTestContext(w)
    c.Request = &http.Request{
        Header: make(http.Header),
    }
    c.Request.Header["content-TYPE"] = []string{"this/will/not/bind"}
    c.Request.Header["x-custom-header"] = []string{"this/will/not/work"}

    err := c.BindHeader(&headers)
    if err != nil {
        fmt.Printf("BindHeaders failed: %v\n", err)
    } else {
        fmt.Printf("Test 1: Header cases don't match:\n")
        fmt.Printf("MyRequest struct after binding: %+v\n\n", headers)   // Will print nothing because the struct wasn't populated
    }

    headers = MyRequest{}
    c.Request.Header["Content-Type"] = []string{"this/will/bind"}
    c.Request.Header.Add("X-cuSTOM-HEADer", "this/works")

    err = c.BindHeader(&headers)
    if err != nil {
        fmt.Printf("BindHeaders failed: %v\n", err)
    } else {
        fmt.Printf("Test 2: Header cases match or normalized with Header.Add:\n")
        fmt.Printf("MyRequest struct after binding: %+v\n", headers)  // Will be populated
    }
}

Expectations

The above code illustrates expectations. If fields are stated as Content-Type but a curl command is issued against a server, such as curl -H "content-type: text/plain" http://localhost:8888/hello/world and the code depends on that struct element, the code will incorrectly fail.

Environment

  • go1.21
  • gin-gonic v1.9.1