this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

all: make HTML escaping in JSON an opt-in

We should avoid calling encoding/json.Marshal where possible.
It does not allow passing in options, and it escapes HTML.
This means that it forces us to always escape HTML by default,
even in cases where we want to not escape by default.

One such case are our MarshalJSON methods,
which always performed HTML escaping without a choice.
This caused the command `cue export` to always escape,
even when its --escape flag wasn't being set.

As can be seen in the updated test, `cue export` no longer escapes.
The --escape flag is now plumbed through as well,
which makes the top-level encoding/json.Encoder.Encode call perform the
necessary escaping when the flag is set.

Another case is cuelang.org/go/pkg/encoding/json,
which has an HTMLEscape API rather than a boolean option,
so Marshal and MarshalStream should not perform any escaping.

This change may break users of `cue export` or `json.Marshal` who did
depend on the HTML escaping to happen by default.
However, we have always had documented flags and APIs to enable the HTML
escaping, and before this fix, there wasn't any way to not escape HTML.
For those reasons, this should be an acceptable change to make.

To get encoding/json.Marshal's behavior without the escaping,
create a new Marshal func in internal/encoding/json
which uses encoding/json.Encoder.Encode with a buffer.

Note that we import internal/encoding/json as "internaljson",
to make its use instead of encoding/json clear and explicit.
For consistency, all calls to encoding/json.Marshal are replaced.

If encoding/json's API is ever improved upon,
we can likely simplify or avoid our workaround.
For now, avoiding upstream's Marshal API is the best we can do.

Fixes #1243.

Signed-off-by: Daniel Martí <mvdan@mvdan.cc>
Change-Id: Ie11c8cfdc3741927c2aae28bce0d67c214411480
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/548783
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>
Unity-Result: CUEcueckoo <cueckoo@cuelang.org>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+42 -24
+1
cmd/cue/cmd/common.go
··· 720 720 PkgName: flagPackage.String(b.cmd), 721 721 Strict: flagStrict.Bool(b.cmd), 722 722 InlineImports: flagInlineImports.Bool(b.cmd), 723 + EscapeHTML: flagEscape.Bool(b.cmd), 723 724 } 724 725 return nil 725 726 }
+1 -1
cmd/cue/cmd/testdata/script/export_escape.txtar
··· 16 16 { 17 17 "simple": "hello", 18 18 "specialJSON": "\\ \"", 19 - "specialHTML": "\u0026 \u003c \u003e" 19 + "specialHTML": "& < >" 20 20 } 21 21 -- stdout-escape.golden -- 22 22 {
+2 -2
cue/testdata/gen.go
··· 16 16 17 17 import ( 18 18 "bytes" 19 - "encoding/json" 20 19 "flag" 21 20 "fmt" 22 21 "go/ast" ··· 36 35 cueformat "cuelang.org/go/cue/format" 37 36 "cuelang.org/go/cue/parser" 38 37 "cuelang.org/go/internal" 38 + internaljson "cuelang.org/go/internal/encoding/json" 39 39 "cuelang.org/go/pkg/encoding/yaml" 40 40 "cuelang.org/go/tools/fix" 41 41 ) ··· 307 307 e.a.Files = append(e.a.Files, 308 308 txtar.File{Name: "out/yaml", Data: []byte(s)}) 309 309 310 - b, err := json.Marshal(v) 310 + b, err := internaljson.Marshal(v) 311 311 if err != nil { 312 312 fmt.Fprintln(e.header, "#bug: true") 313 313 e.warnf("Could not encode as JSON: %v", err)
+9 -8
cue/types.go
··· 38 38 "cuelang.org/go/internal/core/runtime" 39 39 "cuelang.org/go/internal/core/subsume" 40 40 "cuelang.org/go/internal/core/validate" 41 + internaljson "cuelang.org/go/internal/encoding/json" 41 42 "cuelang.org/go/internal/types" 42 43 ) 43 44 ··· 147 148 n := o.Len() 148 149 for i := 0; i < n; i++ { 149 150 k, v := o.At(i) 150 - s, err := json.Marshal(k) 151 + s, err := internaljson.Marshal(k) 151 152 if err != nil { 152 153 return nil, unwrapJSONError(err) 153 154 } 154 155 b = append(b, s...) 155 156 b = append(b, ':') 156 - bb, err := json.Marshal(v) 157 + bb, err := internaljson.Marshal(v) 157 158 if err != nil { 158 159 return nil, unwrapJSONError(err) 159 160 } ··· 292 293 b = append(b, '[') 293 294 if l.Next() { 294 295 for i := 0; ; i++ { 295 - x, err := json.Marshal(l.Value()) 296 + x, err := internaljson.Marshal(l.Value()) 296 297 if err != nil { 297 298 return nil, unwrapJSONError(err) 298 299 } ··· 912 913 func (v Value) marshalJSON() (b []byte, err error) { 913 914 v, _ = v.Default() 914 915 if v.v == nil { 915 - return json.Marshal(nil) 916 + return internaljson.Marshal(nil) 916 917 } 917 918 ctx := newContext(v.idx) 918 919 x := v.eval(ctx) ··· 927 928 // TODO: implement marshalles in value. 928 929 switch k := x.Kind(); k { 929 930 case adt.NullKind: 930 - return json.Marshal(nil) 931 + return internaljson.Marshal(nil) 931 932 case adt.BoolKind: 932 - return json.Marshal(x.(*adt.Bool).B) 933 + return internaljson.Marshal(x.(*adt.Bool).B) 933 934 case adt.IntKind, adt.FloatKind, adt.NumKind: 934 935 b, err := x.(*adt.Num).X.MarshalText() 935 936 b = bytes.TrimLeft(b, "+") 936 937 return b, err 937 938 case adt.StringKind: 938 - return json.Marshal(x.(*adt.String).Str) 939 + return internaljson.Marshal(x.(*adt.String).Str) 939 940 case adt.BytesKind: 940 - return json.Marshal(x.(*adt.Bytes).B) 941 + return internaljson.Marshal(x.(*adt.Bytes).B) 941 942 case adt.ListKind: 942 943 i, _ := v.List() 943 944 return marshalList(&i)
+3 -3
encoding/openapi/openapi.go
··· 15 15 package openapi 16 16 17 17 import ( 18 - "encoding/json" 19 18 "fmt" 20 19 "strings" 21 20 ··· 24 23 "cuelang.org/go/cue/errors" 25 24 "cuelang.org/go/cue/token" 26 25 cuejson "cuelang.org/go/encoding/json" 26 + internaljson "cuelang.org/go/internal/encoding/json" 27 27 ) 28 28 29 29 // A Config defines options for converting CUE to and from OpenAPI. ··· 95 95 if err != nil { 96 96 return nil, err 97 97 } 98 - return json.Marshal(all) 98 + return internaljson.Marshal(all) 99 99 } 100 100 101 101 // Generate generates the set of OpenAPI schema for all top-level types of the ··· 128 128 } 129 129 130 130 func toCUE(name string, x interface{}) (v ast.Expr, err error) { 131 - b, err := json.Marshal(x) 131 + b, err := internaljson.Marshal(x) 132 132 if err == nil { 133 133 v, err = cuejson.Extract(name, b) 134 134 }
+2 -2
encoding/openapi/orderedmap.go
··· 20 20 "cuelang.org/go/cue/ast" 21 21 "cuelang.org/go/cue/literal" 22 22 "cuelang.org/go/cue/token" 23 - "cuelang.org/go/internal/encoding/json" 23 + internaljson "cuelang.org/go/internal/encoding/json" 24 24 ) 25 25 26 26 // An OrderedMap is a set of key-value pairs that preserves the order in which ··· 177 177 func (m *OrderedMap) MarshalJSON() (b []byte, err error) { 178 178 // This is a pointer receiever to enforce that we only store pointers to 179 179 // OrderedMap in the output. 180 - return json.Encode((*ast.StructLit)(m)) 180 + return internaljson.Encode((*ast.StructLit)(m)) 181 181 }
+2 -1
internal/core/convert/go.go
··· 36 36 "cuelang.org/go/internal" 37 37 "cuelang.org/go/internal/core/adt" 38 38 "cuelang.org/go/internal/core/compile" 39 + internaljson "cuelang.org/go/internal/encoding/json" 39 40 "cuelang.org/go/internal/types" 40 41 ) 41 42 ··· 304 305 if err != nil { 305 306 return ctx.AddErr(errors.Promote(err, "encoding.TextMarshaler")) 306 307 } 307 - b, err = json.Marshal(string(b)) 308 + b, err = internaljson.Marshal(string(b)) 308 309 if err != nil { 309 310 return ctx.AddErr(errors.Promote(err, "json")) 310 311 }
+17 -3
internal/encoding/json/encode.go
··· 28 28 "cuelang.org/go/internal/astinternal" 29 29 ) 30 30 31 - // Encode converts a CUE AST to JSON. 31 + // Marshal is a replacement for [json.Marshal] without HTML escaping. 32 + func Marshal(v any) ([]byte, error) { 33 + var buf bytes.Buffer 34 + enc := json.NewEncoder(&buf) 35 + enc.SetEscapeHTML(false) 36 + if err := enc.Encode(v); err != nil { 37 + return nil, err 38 + } 39 + p := buf.Bytes() 40 + // Unlike json.Marshal, json.Encoder.Encode adds a trailing newline. 41 + p = bytes.TrimSuffix(p, []byte("\n")) 42 + return p, nil 43 + } 44 + 45 + // Encode converts a CUE AST to unescaped JSON. 32 46 // 33 47 // The given file must only contain values that can be directly supported by 34 48 // JSON: ··· 187 201 if err != nil { 188 202 return err 189 203 } 190 - b, err := json.Marshal(str) 204 + b, err := Marshal(str) 191 205 if err != nil { 192 206 return err 193 207 } ··· 277 291 if err != nil { 278 292 return errors.Newf(x.Label.Pos(), "json: only literal labels allowed") 279 293 } 280 - b, err := json.Marshal(name) 294 + b, err := Marshal(name) 281 295 if err != nil { 282 296 return err 283 297 }
+3 -2
pkg/encoding/json/manual.go
··· 26 26 "cuelang.org/go/cue/parser" 27 27 "cuelang.org/go/cue/token" 28 28 cuejson "cuelang.org/go/encoding/json" 29 + internaljson "cuelang.org/go/internal/encoding/json" 29 30 ) 30 31 31 32 // Compact generates the JSON-encoded src with insignificant space characters ··· 70 71 71 72 // Marshal returns the JSON encoding of v. 72 73 func Marshal(v cue.Value) (string, error) { 73 - b, err := json.Marshal(v) 74 + b, err := internaljson.Marshal(v) 74 75 return string(b), err 75 76 } 76 77 ··· 83 84 } 84 85 buf := &bytes.Buffer{} 85 86 for iter.Next() { 86 - b, err := json.Marshal(iter.Value()) 87 + b, err := internaljson.Marshal(iter.Value()) 87 88 if err != nil { 88 89 return "", err 89 90 }
+2 -2
pkg/encoding/json/testdata/gen.txtar
··· 65 65 {"b":2} 66 66 67 67 """ 68 - t10: "{\"a\":\"\\\\ \\\" \\u0026 \\u003c \\u003e\"}" 68 + t10: "{\"a\":\"\\\\ \\\" & < >\"}" 69 69 t11: "{\"a\":\"\\\\ \\\" \\u0026 \\u003c \\u003e\"}" 70 70 t12: """ 71 - {"a":"\\\\ \\" \\u0026 \\u003c \\u003e"} 71 + {"a":"\\\\ \\" & < >"} 72 72 {"b":""} 73 73 74 74 """