Skip to content

Commit 9d7ba23

Browse files
committed
Add option to control if the output is HTMLEscaped
1 parent b82b685 commit 9d7ba23

File tree

3 files changed

+71
-35
lines changed

3 files changed

+71
-35
lines changed

v5/internal/json/encode.go

+13
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,19 @@ func Marshal(v any) ([]byte, error) {
167167
return buf, nil
168168
}
169169

170+
func MarshalEscaped(v any, escape bool) ([]byte, error) {
171+
e := newEncodeState()
172+
defer encodeStatePool.Put(e)
173+
174+
err := e.marshal(v, encOpts{escapeHTML: escape})
175+
if err != nil {
176+
return nil, err
177+
}
178+
buf := append([]byte(nil), e.Bytes()...)
179+
180+
return buf, nil
181+
}
182+
170183
// MarshalIndent is like Marshal but applies Indent to format the output.
171184
// Each JSON element in the output will begin on a new line beginning with prefix
172185
// followed by one or more copies of indent according to the indentation nesting.

v5/merge.go

+29-23
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,26 @@ import (
1010
"github.com/evanphx/json-patch/v5/internal/json"
1111
)
1212

13-
func merge(cur, patch *lazyNode, mergeMerge bool) *lazyNode {
14-
curDoc, err := cur.intoDoc()
13+
func merge(cur, patch *lazyNode, mergeMerge bool, options *ApplyOptions) *lazyNode {
14+
curDoc, err := cur.intoDoc(options)
1515

1616
if err != nil {
17-
pruneNulls(patch)
17+
pruneNulls(patch, options)
1818
return patch
1919
}
2020

21-
patchDoc, err := patch.intoDoc()
21+
patchDoc, err := patch.intoDoc(options)
2222

2323
if err != nil {
2424
return patch
2525
}
2626

27-
mergeDocs(curDoc, patchDoc, mergeMerge)
27+
mergeDocs(curDoc, patchDoc, mergeMerge, options)
2828

2929
return cur
3030
}
3131

32-
func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
32+
func mergeDocs(doc, patch *partialDoc, mergeMerge bool, options *ApplyOptions) {
3333
for k, v := range patch.obj {
3434
if v == nil {
3535
if mergeMerge {
@@ -45,55 +45,55 @@ func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
4545
}
4646
doc.obj[k] = nil
4747
} else {
48-
_ = doc.remove(k, &ApplyOptions{})
48+
_ = doc.remove(k, options)
4949
}
5050
} else {
5151
cur, ok := doc.obj[k]
5252

5353
if !ok || cur == nil {
5454
if !mergeMerge {
55-
pruneNulls(v)
55+
pruneNulls(v, options)
5656
}
57-
_ = doc.set(k, v, &ApplyOptions{})
57+
_ = doc.set(k, v, options)
5858
} else {
59-
_ = doc.set(k, merge(cur, v, mergeMerge), &ApplyOptions{})
59+
_ = doc.set(k, merge(cur, v, mergeMerge, options), options)
6060
}
6161
}
6262
}
6363
}
6464

65-
func pruneNulls(n *lazyNode) {
66-
sub, err := n.intoDoc()
65+
func pruneNulls(n *lazyNode, options *ApplyOptions) {
66+
sub, err := n.intoDoc(options)
6767

6868
if err == nil {
69-
pruneDocNulls(sub)
69+
pruneDocNulls(sub, options)
7070
} else {
7171
ary, err := n.intoAry()
7272

7373
if err == nil {
74-
pruneAryNulls(ary)
74+
pruneAryNulls(ary, options)
7575
}
7676
}
7777
}
7878

79-
func pruneDocNulls(doc *partialDoc) *partialDoc {
79+
func pruneDocNulls(doc *partialDoc, options *ApplyOptions) *partialDoc {
8080
for k, v := range doc.obj {
8181
if v == nil {
8282
_ = doc.remove(k, &ApplyOptions{})
8383
} else {
84-
pruneNulls(v)
84+
pruneNulls(v, options)
8585
}
8686
}
8787

8888
return doc
8989
}
9090

91-
func pruneAryNulls(ary *partialArray) *partialArray {
91+
func pruneAryNulls(ary *partialArray, options *ApplyOptions) *partialArray {
9292
newAry := []*lazyNode{}
9393

9494
for _, v := range ary.nodes {
9595
if v != nil {
96-
pruneNulls(v)
96+
pruneNulls(v, options)
9797
}
9898
newAry = append(newAry, v)
9999
}
@@ -128,11 +128,17 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
128128
return nil, errBadJSONPatch
129129
}
130130

131-
doc := &partialDoc{}
131+
options := NewApplyOptions()
132+
133+
doc := &partialDoc{
134+
opts: options,
135+
}
132136

133137
docErr := doc.UnmarshalJSON(docData)
134138

135-
patch := &partialDoc{}
139+
patch := &partialDoc{
140+
opts: options,
141+
}
136142

137143
patchErr := patch.UnmarshalJSON(patchData)
138144

@@ -158,7 +164,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
158164
if mergeMerge {
159165
doc = patch
160166
} else {
161-
doc = pruneDocNulls(patch)
167+
doc = pruneDocNulls(patch, options)
162168
}
163169
} else {
164170
patchAry := &partialArray{}
@@ -172,7 +178,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
172178
return nil, errBadJSONPatch
173179
}
174180

175-
pruneAryNulls(patchAry)
181+
pruneAryNulls(patchAry, options)
176182

177183
out, patchErr := json.Marshal(patchAry.nodes)
178184

@@ -183,7 +189,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
183189
return out, nil
184190
}
185191
} else {
186-
mergeDocs(doc, patch, mergeMerge)
192+
mergeDocs(doc, patch, mergeMerge, options)
187193
}
188194

189195
return json.Marshal(doc)

v5/patch.go

+29-12
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type partialDoc struct {
6060
self *lazyNode
6161
keys []string
6262
obj map[string]*lazyNode
63+
64+
opts *ApplyOptions
6365
}
6466

6567
type partialArray struct {
@@ -90,6 +92,8 @@ type ApplyOptions struct {
9092
// EnsurePathExistsOnAdd instructs json-patch to recursively create the missing parts of path on "add" operation.
9193
// Default to false.
9294
EnsurePathExistsOnAdd bool
95+
96+
EscapeHTML bool
9397
}
9498

9599
// NewApplyOptions creates a default set of options for calls to ApplyWithOptions.
@@ -99,6 +103,7 @@ func NewApplyOptions() *ApplyOptions {
99103
AccumulatedCopySizeLimit: AccumulatedCopySizeLimit,
100104
AllowMissingPathOnRemove: false,
101105
EnsurePathExistsOnAdd: false,
106+
EscapeHTML: true,
102107
}
103108
}
104109

@@ -143,7 +148,7 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
143148
return err
144149
}
145150
}
146-
key, err := json.Marshal(k)
151+
key, err := json.MarshalEscaped(k, n.opts.EscapeHTML)
147152
if err != nil {
148153
return err
149154
}
@@ -153,7 +158,7 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
153158
if err := buf.WriteByte(':'); err != nil {
154159
return err
155160
}
156-
value, err := json.Marshal(n.obj[k])
161+
value, err := json.MarshalEscaped(n.obj[k], n.opts.EscapeHTML)
157162
if err != nil {
158163
return err
159164
}
@@ -194,11 +199,11 @@ func (n *partialArray) RedirectMarshalJSON() (interface{}, error) {
194199
return n.nodes, nil
195200
}
196201

197-
func deepCopy(src *lazyNode) (*lazyNode, int, error) {
202+
func deepCopy(src *lazyNode, options *ApplyOptions) (*lazyNode, int, error) {
198203
if src == nil {
199204
return nil, 0, nil
200205
}
201-
a, err := json.Marshal(src)
206+
a, err := json.MarshalEscaped(src, options.EscapeHTML)
202207
if err != nil {
203208
return nil, 0, err
204209
}
@@ -216,7 +221,7 @@ func (n *lazyNode) nextByte() byte {
216221
return s[0]
217222
}
218223

219-
func (n *lazyNode) intoDoc() (*partialDoc, error) {
224+
func (n *lazyNode) intoDoc(options *ApplyOptions) (*partialDoc, error) {
220225
if n.which == eDoc {
221226
return n.doc, nil
222227
}
@@ -235,6 +240,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
235240
return nil, ErrInvalid
236241
}
237242

243+
n.doc.opts = options
238244
if err != nil {
239245
return nil, err
240246
}
@@ -545,7 +551,7 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
545551
return nil, ""
546552
}
547553
} else {
548-
doc, err = next.intoDoc()
554+
doc, err = next.intoDoc(options)
549555

550556
if err != nil {
551557
return nil, ""
@@ -750,6 +756,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error {
750756
} else {
751757
pd = &partialDoc{
752758
self: val,
759+
opts: options,
753760
}
754761
}
755762

@@ -855,7 +862,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
855862
newNode := newLazyNode(newRawMessage(rawJSONObject))
856863

857864
doc.add(part, newNode, options)
858-
doc, err = newNode.intoDoc()
865+
doc, err = newNode.intoDoc(options)
859866
if err != nil {
860867
return err
861868
}
@@ -868,7 +875,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
868875
return err
869876
}
870877
} else {
871-
doc, err = target.intoDoc()
878+
doc, err = target.intoDoc(options)
872879

873880
if err != nil {
874881
return err
@@ -954,6 +961,8 @@ func (p Patch) replace(doc *container, op Operation, options *ApplyOptions) erro
954961
if !val.tryAry() {
955962
return errors.Wrapf(err, "replace operation value must be object or array")
956963
}
964+
} else {
965+
val.doc.opts = options
957966
}
958967
}
959968

@@ -1115,7 +1124,7 @@ func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64, op
11151124
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path)
11161125
}
11171126

1118-
valCopy, sz, err := deepCopy(val)
1127+
valCopy, sz, err := deepCopy(val, options)
11191128
if err != nil {
11201129
return errors.Wrapf(err, "error while performing deep copy")
11211130
}
@@ -1202,6 +1211,7 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
12021211
} else {
12031212
pd = &partialDoc{
12041213
self: self,
1214+
opts: options,
12051215
}
12061216
}
12071217

@@ -1238,11 +1248,18 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
12381248
}
12391249
}
12401250

1241-
if indent != "" {
1242-
return json.MarshalIndent(pd, "", indent)
1251+
data, err := json.MarshalEscaped(pd, options.EscapeHTML)
1252+
if err != nil {
1253+
return nil, err
1254+
}
1255+
1256+
if indent == "" {
1257+
return data, nil
12431258
}
12441259

1245-
return json.Marshal(pd)
1260+
var buf bytes.Buffer
1261+
json.Indent(&buf, data, "", indent)
1262+
return buf.Bytes(), nil
12461263
}
12471264

12481265
// From http://tools.ietf.org/html/rfc6901#section-4 :

0 commit comments

Comments
 (0)