Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FormPost will binding failed of custom type in some case with json.Unmarshal #2510

Open
LaysDragon opened this issue Sep 23, 2020 · 4 comments
Labels

Comments

@LaysDragon
Copy link

LaysDragon commented Sep 23, 2020

Description

While the implement of binding.FormPost will trying to usejson.Unmarshal to deal with custom or unknown type.

return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())

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
@kuoruan
Copy link

kuoruan commented Dec 14, 2020

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

gin/binding/form_mapping.go

Lines 280 to 329 in 65ed60e

func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" {
timeFormat = time.RFC3339
}
switch tf := strings.ToLower(timeFormat); tf {
case "unix", "unixnano":
tv, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return err
}
d := time.Duration(1)
if tf == "unixnano" {
d = time.Second
}
t := time.Unix(tv/int64(d), tv%int64(d))
value.Set(reflect.ValueOf(t))
return nil
}
if val == "" {
value.Set(reflect.ValueOf(time.Time{}))
return nil
}
l := time.Local
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
l = time.UTC
}
if locTag := structField.Tag.Get("time_location"); locTag != "" {
loc, err := time.LoadLocation(locTag)
if err != nil {
return err
}
l = loc
}
t, err := time.ParseInLocation(timeFormat, val, l)
if err != nil {
return err
}
value.Set(reflect.ValueOf(t))
return nil
}

@eloyekunle
Copy link

eloyekunle commented Apr 6, 2023

This issue is fixed by either #3045 or #2511, but they're still not merged yet.

Related issues: #3060, #2673 and #2631.

@appleboy
Copy link
Member

appleboy commented Apr 6, 2023

I will take it.

@appleboy appleboy added the bug label Apr 6, 2023
@doriotp
Copy link

doriotp commented Jan 13, 2024

Hi @LaysDragon is this issue still persist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants