gin
gin copied to clipboard
base64 Unmarshalling from c.ShouldBindQuery not working
Description
I have a base64 yJUaW1lIjoiMjAyMi0wOC0wNlQxODoxMzo1Ni45OTA3MjkrMDU6MzAifQo=
which decodes to {"Time":"2022-08-06T18:13:56.990729+05:30"}
Now I wanted to pass this encoded base64 in queryparam
and unmarshal it to struct.
But I found it was not happening.
every time i tried, I got.
{
"detail": {
"Offset": 1
},
"error": "invalid character 'e' looking for beginning of value"
}
Is there not any way to unmarshal it in struct while passing base64 encoded data in queryparams?
How to reproduce
*Updated Aug 6,2022 6:25 PM
package main
import (
"encoding/base64"
"encoding/json"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.Any("/health", health)
router.Run(":8080")
}
type Temp struct {
Time time.Time
}
type Request struct {
Token Temp `json:"token" form:"token"`
}
func (t *Temp) UnmarshalJSON(src []byte) error {
b64len := len(src) - 2
src = src[1 : b64len+1]
dst := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
n, err := base64.StdEncoding.Decode(dst, src)
if err != nil {
return err
}
type __ Temp
var x __
if err := json.Unmarshal(dst[:n], &x); err != nil {
return err
}
*t = Temp(x)
return nil
}
func health(c *gin.Context) {
var req Request
var err error
if c.Request.Method == "GET" {
err = c.ShouldBindQuery(&req)
} else if c.Request.Method == "POST" {
err = c.ShouldBindJSON(&req)
} else {
return
}
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
"detail": err,
})
return
}
c.JSON(http.StatusOK, req)
}
Expectations
$ curl http://localhost:8080/health?token=eyJ0b2tlbiI6eyJUaW1lIjoiMjAyMi0wOC0wNlQxODoxMToxNi4xMjA3NCswNTozMCJ9fQo=
{
"token": {
"Time": "2022-08-06T18:13:56.990729+05:30"
}
}
Actual result
$ curl http://localhost:8080/health?token=eyJ0b2tlbiI6IjIwMjItMDgtMDZUMDM6MjI6MjcuNzM1ODE0KzA1OjMwIn0K
{
"detail": {
"Offset": 1
},
"error": "invalid character 'e' looking for beginning of value"
}
Note
With Post I am getting expected response.
curl --location --request POST 'http://localhost:8080/health' \
--header 'Content-Transfer-Encoding: base64' \
--header 'Content-Type: application/json' \
--data-raw '{
"token": "eyJUaW1lIjoiMjAyMi0wOC0wNlQxODoxMzo1Ni45OTA3MjkrMDU6MzAifQo="
}'
{
"token": {
"Time": "2022-08-06T18:13:56.990729+05:30"
}
}
Environment
go version go1.19 darwin/amd64
go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/vajahat/Library/Caches/go-build"
GOENV="/Users/vajahat/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/vajahat/go/pkg/mod"
GONOPROXY="gitlab.com/dotcomino"
GONOSUMDB="gitlab.com/dotcomino"
GOOS="darwin"
GOPATH="/Users/vajahat/go"
GOPRIVATE="gitlab.com/dotcomino"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.19"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/lf/j1n10m694bv_xvzgxw9_nwh40000gn/T/go-build14186801=/tmp/go-build -gno-record-gcc-switches -fno-common"
There are 2 approaches I have discovered so far.
- Either I write a middleware
func tokenprocessor(c *gin.Context) {
if x, ok := c.GetQuery("token"); ok {
if x[len(x)-1] != '"' {
x = x + "\""
}
if x[0] != '"' {
x = "\"" + x
}
fmt.Println(x)
val := c.Request.URL.Query()
val.Set("token", x)
c.Request.URL.RawQuery = val.Encode()
}
}
- Or I write a custom binder.
const defaultMemory = 32 << 20
type SpecialTokenBinder struct{}
var SpltokenBinder = SpecialTokenBinder{}
func (SpecialTokenBinder) Name() string {
return "form"
}
func (SpecialTokenBinder) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if x := req.URL.Query().Get("token"); x != "" {
if x[len(x)-1] != '"' {
x = x + "\""
}
if x[0] != '"' {
x = "\"" + x
}
val := req.URL.Query()
val.Set("token", x)
req.URL.RawQuery = val.Encode()
}
if err := binding.MapFormWithTag(obj, req.URL.Query(), "form"); err != nil {
return err
}
if binding.Validator != nil {
return binding.Validator.ValidateStruct(obj)
}
return nil
}