diff --git a/goyaml.v2/encode.go b/goyaml.v2/encode.go index 0ee738e..1738581 100644 --- a/goyaml.v2/encode.go +++ b/goyaml.v2/encode.go @@ -4,6 +4,7 @@ import ( "encoding" "fmt" "io" + "math/big" "reflect" "regexp" "sort" @@ -122,6 +123,9 @@ func (e *encoder) marshal(tag string, in reflect.Value) { // we don't want to treat it as a string for YAML // purposes because YAML has special support for // timestamps. + case *big.Int: + e.bigintv(tag, reflect.ValueOf(m.String())) + return case Marshaler: v, err := m.MarshalYAML() if err != nil { @@ -354,6 +358,12 @@ func (e *encoder) uintv(tag string, in reflect.Value) { e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) } +func (e *encoder) bigintv(tag string, in reflect.Value) { + value := &big.Int{} + s, _ := value.SetString(in.String(), 0) + e.emitScalar(s.String(), "", tag, yaml_PLAIN_SCALAR_STYLE) +} + func (e *encoder) timev(tag string, in reflect.Value) { t := in.Interface().(time.Time) s := t.Format(time.RFC3339Nano) diff --git a/goyaml.v2/encode_test.go b/goyaml.v2/encode_test.go index aeef023..d53a161 100644 --- a/goyaml.v2/encode_test.go +++ b/goyaml.v2/encode_test.go @@ -12,7 +12,7 @@ import ( "os" . "gopkg.in/check.v1" - "sigs.k8s.io/yaml/goyaml.v2" + yaml "sigs.k8s.io/yaml/goyaml.v2" ) type jsonNumberT string @@ -641,6 +641,40 @@ func (s *S) TestSortedOutput(c *C) { } } +func (s *S) TestQuotingMaintained(c *C) { + var buf bytes.Buffer + var yamlValue map[string]interface{} + const originalYaml = `data: + A1: "0x0000000000000000000000010000000000000000" + A2: "0x000000000000000000000000FFFFFFFFFFFFFFFF" + A3: "1234" + A4: 0x0000000000000000000000010000000000000000 + A5: 0x000000000000000000000000FFFFFFFFFFFFFFFF + A6: 1234 +` + const outputYaml = `data: + A1: "0x0000000000000000000000010000000000000000" + A2: "0x000000000000000000000000FFFFFFFFFFFFFFFF" + A3: "1234" + A4: 18446744073709551616 + A5: 18446744073709551615 + A6: 1234 +` + + dec := yaml.NewDecoder(strings.NewReader(originalYaml)) + errDec := dec.Decode(&yamlValue) + c.Assert(errDec, IsNil) + + enc := yaml.NewEncoder(&buf) + errEnc := enc.Encode(yamlValue) + c.Assert(errEnc, IsNil) + + errClose := enc.Close() + c.Assert(errClose, IsNil) + + c.Assert(buf.String(), Equals, outputYaml) +} + func newTime(t time.Time) *time.Time { return &t } diff --git a/goyaml.v2/resolve.go b/goyaml.v2/resolve.go index 4120e0c..00c45a4 100644 --- a/goyaml.v2/resolve.go +++ b/goyaml.v2/resolve.go @@ -2,7 +2,9 @@ package yaml import ( "encoding/base64" + "errors" "math" + "math/big" "regexp" "strconv" "strings" @@ -148,16 +150,18 @@ func resolve(tag string, in string) (rtag string, out interface{}) { } plain := strings.Replace(in, "_", "", -1) - intv, err := strconv.ParseInt(plain, 0, 64) - if err == nil { + + var intConvErr error + intv, intConvErr := strconv.ParseInt(plain, 0, 64) + if intConvErr == nil { if intv == int64(int(intv)) { return yaml_INT_TAG, int(intv) } else { return yaml_INT_TAG, intv } } - uintv, err := strconv.ParseUint(plain, 0, 64) - if err == nil { + uintv, intConvErr := strconv.ParseUint(plain, 0, 64) + if intConvErr == nil { return yaml_INT_TAG, uintv } if yamlStyleFloat.MatchString(plain) { @@ -189,6 +193,17 @@ func resolve(tag string, in string) (rtag string, out interface{}) { } } } + + // If number out of range and doesn't fit any of the other cases, + // check if it's valid for bigger than 64 bytes + if errors.Is(intConvErr, strconv.ErrRange) { + value := &big.Int{} + + bigintv, ok := value.SetString(plain, 0) + if ok { + return yaml_INT_TAG, bigintv + } + } default: panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") } diff --git a/goyaml.v3/encode.go b/goyaml.v3/encode.go index de9e72a..d7197c1 100644 --- a/goyaml.v3/encode.go +++ b/goyaml.v3/encode.go @@ -19,6 +19,7 @@ import ( "encoding" "fmt" "io" + "math/big" "reflect" "regexp" "sort" @@ -136,6 +137,9 @@ func (e *encoder) marshal(tag string, in reflect.Value) { case time.Duration: e.stringv(tag, reflect.ValueOf(value.String())) return + case *big.Int: + e.bigintv(tag, reflect.ValueOf(value.String())) + return case Marshaler: v, err := value.MarshalYAML() if err != nil { @@ -382,6 +386,12 @@ func (e *encoder) uintv(tag string, in reflect.Value) { e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) } +func (e *encoder) bigintv(tag string, in reflect.Value) { + value := &big.Int{} + s, _ := value.SetString(in.String(), 0) + e.emitScalar(s.String(), "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil) +} + func (e *encoder) timev(tag string, in reflect.Value) { t := in.Interface().(time.Time) s := t.Format(time.RFC3339Nano) diff --git a/goyaml.v3/encode_test.go b/goyaml.v3/encode_test.go index aa08acd..d42c9d6 100644 --- a/goyaml.v3/encode_test.go +++ b/goyaml.v3/encode_test.go @@ -27,7 +27,7 @@ import ( "os" . "gopkg.in/check.v1" - "sigs.k8s.io/yaml/goyaml.v3" + yaml "sigs.k8s.io/yaml/goyaml.v3" ) var marshalIntTest = 123 @@ -731,6 +731,40 @@ func (s *S) TestSortedOutput(c *C) { } } +func (s *S) TestQuotingMaintained(c *C) { + var buf bytes.Buffer + var yamlValue map[string]interface{} + const originalYaml = `data: + A1: "0x0000000000000000000000010000000000000000" + A2: "0x000000000000000000000000FFFFFFFFFFFFFFFF" + A3: "1234" + A4: 0x0000000000000000000000010000000000000000 + A5: 0x000000000000000000000000FFFFFFFFFFFFFFFF + A6: 1234 +` + const outputYaml = `data: + A1: "0x0000000000000000000000010000000000000000" + A2: "0x000000000000000000000000FFFFFFFFFFFFFFFF" + A3: "1234" + A4: 18446744073709551616 + A5: 18446744073709551615 + A6: 1234 +` + + dec := yaml.NewDecoder(strings.NewReader(originalYaml)) + errDec := dec.Decode(&yamlValue) + c.Assert(errDec, IsNil) + + enc := yaml.NewEncoder(&buf) + errEnc := enc.Encode(yamlValue) + c.Assert(errEnc, IsNil) + + errClose := enc.Close() + c.Assert(errClose, IsNil) + + c.Assert(buf.String(), Equals, outputYaml) +} + func newTime(t time.Time) *time.Time { return &t } diff --git a/goyaml.v3/resolve.go b/goyaml.v3/resolve.go index 64ae888..0b3c711 100644 --- a/goyaml.v3/resolve.go +++ b/goyaml.v3/resolve.go @@ -17,7 +17,9 @@ package yaml import ( "encoding/base64" + "errors" "math" + "math/big" "regexp" "strconv" "strings" @@ -189,16 +191,18 @@ func resolve(tag string, in string) (rtag string, out interface{}) { } plain := strings.Replace(in, "_", "", -1) - intv, err := strconv.ParseInt(plain, 0, 64) - if err == nil { + + var intConvErr error + intv, intConvErr := strconv.ParseInt(plain, 0, 64) + if intConvErr == nil { if intv == int64(int(intv)) { return intTag, int(intv) } else { return intTag, intv } } - uintv, err := strconv.ParseUint(plain, 0, 64) - if err == nil { + uintv, intConvErr := strconv.ParseUint(plain, 0, 64) + if intConvErr == nil { return intTag, uintv } if yamlStyleFloat.MatchString(plain) { @@ -257,6 +261,17 @@ func resolve(tag string, in string) (rtag string, out interface{}) { } } } + + // If number out of range and doesn't fit any of the other cases, + // check if it's valid for bigger than 64 bytes + if errors.Is(intConvErr, strconv.ErrRange) { + value := &big.Int{} + + bigintv, ok := value.SetString(plain, 0) + if ok { + return intTag, bigintv + } + } default: panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")") } diff --git a/yaml_test.go b/yaml_test.go index bcabc79..e7cad84 100644 --- a/yaml_test.go +++ b/yaml_test.go @@ -726,6 +726,31 @@ func TestYAMLToJSON(t *testing.T) { json: `{"a":"\ufffd\ufffd\ufffd"}`, yamlReverseOverwrite: strPtr("a: \ufffd\ufffd\ufffd\n"), }, + "small hex value": { + yaml: "key: 0x0001", + json: `{"key":1}`, + yamlReverseOverwrite: strPtr("key: 1\n"), + }, + "hex value at the 64 byte mark": { + yaml: "key: 0x000000000000000000000000FFFFFFFFFFFFFFFF", + json: `{"key":18446744073709551615}`, + yamlReverseOverwrite: strPtr("key: 18446744073709551615\n"), + }, + "hex value at the 64 byte mark in string format": { + yaml: "key: \"0x000000000000000000000000FFFFFFFFFFFFFFFF\"", + json: `{"key":"0x000000000000000000000000FFFFFFFFFFFFFFFF"}`, + yamlReverseOverwrite: strPtr("key: \"0x000000000000000000000000FFFFFFFFFFFFFFFF\"\n"), + }, + "huge hex value bigger than 64 bytes": { + yaml: "key: 0x0000000000000000000000010000000000000000", + json: `{"key":18446744073709551616}`, + yamlReverseOverwrite: strPtr("key: 1.8446744073709552e+19\n"), + }, + "huge hex value bigger than 64 bytes in string format": { + yaml: "key: \"0x0000000000000000000000010000000000000000\"", + json: `{"key":"0x0000000000000000000000010000000000000000"}`, + yamlReverseOverwrite: strPtr("key: \"0x0000000000000000000000010000000000000000\"\n"), + }, // Cases that should produce errors. "~ key": { @@ -809,6 +834,7 @@ func TestJSONObjectToYAMLObject(t *testing.T) { "slice": []interface{}{"foo", "bar"}, "string": string("foo"), "uint64 big": bigUint64, + "big hex int": "0x0000000000000000000000010000000000000000", }, expected: yamlv2.MapSlice{ {Key: "nil slice"}, @@ -826,6 +852,7 @@ func TestJSONObjectToYAMLObject(t *testing.T) { {Key: "slice", Value: []interface{}{"foo", "bar"}}, {Key: "string", Value: string("foo")}, {Key: "uint64 big", Value: bigUint64}, + {Key: "big hex int", Value: "0x0000000000000000000000010000000000000000"}, }, }, }