Skip to content

Commit b7a4e4a

Browse files
authored
Merge pull request #202 from evanphx/f-html-escape
Add option to control if the output is HTMLEscaped
2 parents 1bcbd0f + 7eef36c commit b7a4e4a

File tree

3 files changed

+79
-35
lines changed

3 files changed

+79
-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

+37-12
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ type partialDoc struct {
6262
self *lazyNode
6363
keys []string
6464
obj map[string]*lazyNode
65+
66+
opts *ApplyOptions
6567
}
6668

6769
type partialArray struct {
@@ -92,6 +94,8 @@ type ApplyOptions struct {
9294
// EnsurePathExistsOnAdd instructs json-patch to recursively create the missing parts of path on "add" operation.
9395
// Default to false.
9496
EnsurePathExistsOnAdd bool
97+
98+
EscapeHTML bool
9599
}
96100

97101
// NewApplyOptions creates a default set of options for calls to ApplyWithOptions.
@@ -101,6 +105,7 @@ func NewApplyOptions() *ApplyOptions {
101105
AccumulatedCopySizeLimit: AccumulatedCopySizeLimit,
102106
AllowMissingPathOnRemove: false,
103107
EnsurePathExistsOnAdd: false,
108+
EscapeHTML: true,
104109
}
105110
}
106111

@@ -143,13 +148,21 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
143148
if err := buf.WriteByte('{'); err != nil {
144149
return err
145150
}
151+
escaped := true
152+
153+
// n.opts should always be set, but in case we missed a case,
154+
// guard.
155+
if n.opts != nil {
156+
escaped = n.opts.EscapeHTML
157+
}
158+
146159
for i, k := range n.keys {
147160
if i > 0 {
148161
if err := buf.WriteByte(','); err != nil {
149162
return err
150163
}
151164
}
152-
key, err := json.Marshal(k)
165+
key, err := json.MarshalEscaped(k, escaped)
153166
if err != nil {
154167
return err
155168
}
@@ -159,7 +172,7 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
159172
if err := buf.WriteByte(':'); err != nil {
160173
return err
161174
}
162-
value, err := json.Marshal(n.obj[k])
175+
value, err := json.MarshalEscaped(n.obj[k], escaped)
163176
if err != nil {
164177
return err
165178
}
@@ -200,11 +213,11 @@ func (n *partialArray) RedirectMarshalJSON() (interface{}, error) {
200213
return n.nodes, nil
201214
}
202215

203-
func deepCopy(src *lazyNode) (*lazyNode, int, error) {
216+
func deepCopy(src *lazyNode, options *ApplyOptions) (*lazyNode, int, error) {
204217
if src == nil {
205218
return nil, 0, nil
206219
}
207-
a, err := json.Marshal(src)
220+
a, err := json.MarshalEscaped(src, options.EscapeHTML)
208221
if err != nil {
209222
return nil, 0, err
210223
}
@@ -222,7 +235,7 @@ func (n *lazyNode) nextByte() byte {
222235
return s[0]
223236
}
224237

225-
func (n *lazyNode) intoDoc() (*partialDoc, error) {
238+
func (n *lazyNode) intoDoc(options *ApplyOptions) (*partialDoc, error) {
226239
if n.which == eDoc {
227240
return n.doc, nil
228241
}
@@ -241,6 +254,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
241254
return nil, ErrInvalid
242255
}
243256

257+
n.doc.opts = options
244258
if err != nil {
245259
return nil, err
246260
}
@@ -551,7 +565,7 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
551565
return nil, ""
552566
}
553567
} else {
554-
doc, err = next.intoDoc()
568+
doc, err = next.intoDoc(options)
555569

556570
if err != nil {
557571
return nil, ""
@@ -769,6 +783,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error {
769783
} else {
770784
pd = &partialDoc{
771785
self: val,
786+
opts: options,
772787
}
773788
}
774789

@@ -874,7 +889,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
874889
newNode := newLazyNode(newRawMessage(rawJSONObject))
875890

876891
doc.add(part, newNode, options)
877-
doc, err = newNode.intoDoc()
892+
doc, err = newNode.intoDoc(options)
878893
if err != nil {
879894
return err
880895
}
@@ -887,7 +902,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
887902
return err
888903
}
889904
} else {
890-
doc, err = target.intoDoc()
905+
doc, err = target.intoDoc(options)
891906

892907
if err != nil {
893908
return err
@@ -973,6 +988,8 @@ func (p Patch) replace(doc *container, op Operation, options *ApplyOptions) erro
973988
if !val.tryAry() {
974989
return errors.Wrapf(err, "replace operation value must be object or array")
975990
}
991+
} else {
992+
val.doc.opts = options
976993
}
977994
}
978995

@@ -1134,7 +1151,7 @@ func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64, op
11341151
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path)
11351152
}
11361153

1137-
valCopy, sz, err := deepCopy(val)
1154+
valCopy, sz, err := deepCopy(val, options)
11381155
if err != nil {
11391156
return errors.Wrapf(err, "error while performing deep copy")
11401157
}
@@ -1221,6 +1238,7 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
12211238
} else {
12221239
pd = &partialDoc{
12231240
self: self,
1241+
opts: options,
12241242
}
12251243
}
12261244

@@ -1257,11 +1275,18 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
12571275
}
12581276
}
12591277

1260-
if indent != "" {
1261-
return json.MarshalIndent(pd, "", indent)
1278+
data, err := json.MarshalEscaped(pd, options.EscapeHTML)
1279+
if err != nil {
1280+
return nil, err
1281+
}
1282+
1283+
if indent == "" {
1284+
return data, nil
12621285
}
12631286

1264-
return json.Marshal(pd)
1287+
var buf bytes.Buffer
1288+
json.Indent(&buf, data, "", indent)
1289+
return buf.Bytes(), nil
12651290
}
12661291

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

0 commit comments

Comments
 (0)