Description

While the implement of binding.FormPost will trying to usejson.Unmarshal to deal with custom or unknown type. https://github.com/gin-gonic/gin/blob/3100b7cb05a8072b76d31686d8a7b4f9b12df4be/binding/form_mapping.go#L212 But it will failed if the data is represent as string in json, since application/x-www-form-urlencoded won't quote string data. These data will not pass the json's validation.
And the json.Unmarshal for custom data feature is totally broken here. Since we have no way to known if it a string type or other type in application/x-www-form-urlencoded format. I suggest we can add a logic here if it parse custom type failed with json,then add a quote then try again.

How to reproduce

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/gin/binding"
    "net/http"
    "time"
)

func main() {
    g := gin.Default()
    g.POST("/hello", func(c *gin.Context) {
        data := TestForm{}
        err := c.MustBindWith(&data,binding.FormPost)
        fmt.Printf("%+v\n",c.Request.PostForm)
        if err != nil {
            fmt.Printf("%+v\n",err)
            return
        }

        c.String(http.StatusOK,data.Time.String())


    })
    g.Run(":9000")
}

type TestForm struct{
    Time CustomDateTime
}

type CustomDateTime time.Time

const CustomDateTimeFormat = "2006/01/02 15:04:05"

func (t CustomDateTime) String() string {
    return time.Time(t).Format(CustomDateTimeFormat)
}

func (t *CustomDateTime) UnmarshalJSON(data []byte) error {
    var value string
    err := json.Unmarshal(data, &value)
    if err != nil {
        return err
    }
    parseTime, err := time.Parse(CustomDateTimeFormat, value)
    if err != nil {
        return err
    }
    *t = CustomDateTime(parseTime)
    return nil
}

Expectations

$ curl -H "Content-Type: application/x-www-form-urlencoded" -X POST -d "Time=2020%2F09%2F23+13%3A20%3A49" http://localhost:9000/hello
2020/09/23 13:20:49

Actual result

$ curl -H "Content-Type: application/x-www-form-urlencoded" -X POST -d "Time=2020%2F09%2F23+13%3A20%3A49" http://localhost:9000/hello
<400 Bad Request>

console output

GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /hello                    --> main.main.func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :9000
map[Time:[2020/09/23 13:20:49]]
invalid character '/' after top-level value
[GIN] 2020/09/23 - 14:59:47 | 400 |            0s |             ::1 | POST     "/hello"

Environment

  • go version: 1.14
  • gin version (or commit ref): v1.6.3
  • operating system: windows 10

Comment From: kuoruan

If you only want to custom the datetime format, here is a tag already exist in Gin: time_format

https://github.com/gin-gonic/gin/blob/65ed60ed1334140d8583a3d0533cea76c66b69fe/binding/form_mapping.go#L280-L329

Comment From: eloyekunle

This issue is fixed by either https://github.com/gin-gonic/gin/pull/3045 or https://github.com/gin-gonic/gin/pull/2511, but they're still not merged yet.

Related issues: https://github.com/gin-gonic/gin/issues/3060, https://github.com/gin-gonic/gin/issues/2673 and https://github.com/gin-gonic/gin/issues/2631.

Comment From: appleboy

I will take it.

Comment From: doriotp

Hi @LaysDragon is this issue still persist?