Skip to content

Commit

Permalink
Merge pull request #202 from evanphx/f-html-escape
Browse files Browse the repository at this point in the history
Add option to control if the output is HTMLEscaped
  • Loading branch information
evanphx authored Jan 28, 2024
2 parents 1bcbd0f + 7eef36c commit b7a4e4a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 35 deletions.
13 changes: 13 additions & 0 deletions v5/internal/json/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,19 @@ func Marshal(v any) ([]byte, error) {
return buf, nil
}

func MarshalEscaped(v any, escape bool) ([]byte, error) {
e := newEncodeState()
defer encodeStatePool.Put(e)

err := e.marshal(v, encOpts{escapeHTML: escape})
if err != nil {
return nil, err
}
buf := append([]byte(nil), e.Bytes()...)

return buf, nil
}

// MarshalIndent is like Marshal but applies Indent to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
Expand Down
52 changes: 29 additions & 23 deletions v5/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,26 @@ import (
"github.com/evanphx/json-patch/v5/internal/json"
)

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

if err != nil {
pruneNulls(patch)
pruneNulls(patch, options)
return patch
}

patchDoc, err := patch.intoDoc()
patchDoc, err := patch.intoDoc(options)

if err != nil {
return patch
}

mergeDocs(curDoc, patchDoc, mergeMerge)
mergeDocs(curDoc, patchDoc, mergeMerge, options)

return cur
}

func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
func mergeDocs(doc, patch *partialDoc, mergeMerge bool, options *ApplyOptions) {
for k, v := range patch.obj {
if v == nil {
if mergeMerge {
Expand All @@ -45,55 +45,55 @@ func mergeDocs(doc, patch *partialDoc, mergeMerge bool) {
}
doc.obj[k] = nil
} else {
_ = doc.remove(k, &ApplyOptions{})
_ = doc.remove(k, options)
}
} else {
cur, ok := doc.obj[k]

if !ok || cur == nil {
if !mergeMerge {
pruneNulls(v)
pruneNulls(v, options)
}
_ = doc.set(k, v, &ApplyOptions{})
_ = doc.set(k, v, options)
} else {
_ = doc.set(k, merge(cur, v, mergeMerge), &ApplyOptions{})
_ = doc.set(k, merge(cur, v, mergeMerge, options), options)
}
}
}
}

func pruneNulls(n *lazyNode) {
sub, err := n.intoDoc()
func pruneNulls(n *lazyNode, options *ApplyOptions) {
sub, err := n.intoDoc(options)

if err == nil {
pruneDocNulls(sub)
pruneDocNulls(sub, options)
} else {
ary, err := n.intoAry()

if err == nil {
pruneAryNulls(ary)
pruneAryNulls(ary, options)
}
}
}

func pruneDocNulls(doc *partialDoc) *partialDoc {
func pruneDocNulls(doc *partialDoc, options *ApplyOptions) *partialDoc {
for k, v := range doc.obj {
if v == nil {
_ = doc.remove(k, &ApplyOptions{})
} else {
pruneNulls(v)
pruneNulls(v, options)
}
}

return doc
}

func pruneAryNulls(ary *partialArray) *partialArray {
func pruneAryNulls(ary *partialArray, options *ApplyOptions) *partialArray {
newAry := []*lazyNode{}

for _, v := range ary.nodes {
if v != nil {
pruneNulls(v)
pruneNulls(v, options)
}
newAry = append(newAry, v)
}
Expand Down Expand Up @@ -128,11 +128,17 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
return nil, errBadJSONPatch
}

doc := &partialDoc{}
options := NewApplyOptions()

doc := &partialDoc{
opts: options,
}

docErr := doc.UnmarshalJSON(docData)

patch := &partialDoc{}
patch := &partialDoc{
opts: options,
}

patchErr := patch.UnmarshalJSON(patchData)

Expand All @@ -158,7 +164,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
if mergeMerge {
doc = patch
} else {
doc = pruneDocNulls(patch)
doc = pruneDocNulls(patch, options)
}
} else {
patchAry := &partialArray{}
Expand All @@ -172,7 +178,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
return nil, errBadJSONPatch
}

pruneAryNulls(patchAry)
pruneAryNulls(patchAry, options)

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

Expand All @@ -183,7 +189,7 @@ func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) {
return out, nil
}
} else {
mergeDocs(doc, patch, mergeMerge)
mergeDocs(doc, patch, mergeMerge, options)
}

return json.Marshal(doc)
Expand Down
49 changes: 37 additions & 12 deletions v5/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type partialDoc struct {
self *lazyNode
keys []string
obj map[string]*lazyNode

opts *ApplyOptions
}

type partialArray struct {
Expand Down Expand Up @@ -92,6 +94,8 @@ type ApplyOptions struct {
// EnsurePathExistsOnAdd instructs json-patch to recursively create the missing parts of path on "add" operation.
// Default to false.
EnsurePathExistsOnAdd bool

EscapeHTML bool
}

// NewApplyOptions creates a default set of options for calls to ApplyWithOptions.
Expand All @@ -101,6 +105,7 @@ func NewApplyOptions() *ApplyOptions {
AccumulatedCopySizeLimit: AccumulatedCopySizeLimit,
AllowMissingPathOnRemove: false,
EnsurePathExistsOnAdd: false,
EscapeHTML: true,
}
}

Expand Down Expand Up @@ -143,13 +148,21 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
if err := buf.WriteByte('{'); err != nil {
return err
}
escaped := true

// n.opts should always be set, but in case we missed a case,
// guard.
if n.opts != nil {
escaped = n.opts.EscapeHTML
}

for i, k := range n.keys {
if i > 0 {
if err := buf.WriteByte(','); err != nil {
return err
}
}
key, err := json.Marshal(k)
key, err := json.MarshalEscaped(k, escaped)
if err != nil {
return err
}
Expand All @@ -159,7 +172,7 @@ func (n *partialDoc) TrustMarshalJSON(buf *bytes.Buffer) error {
if err := buf.WriteByte(':'); err != nil {
return err
}
value, err := json.Marshal(n.obj[k])
value, err := json.MarshalEscaped(n.obj[k], escaped)
if err != nil {
return err
}
Expand Down Expand Up @@ -200,11 +213,11 @@ func (n *partialArray) RedirectMarshalJSON() (interface{}, error) {
return n.nodes, nil
}

func deepCopy(src *lazyNode) (*lazyNode, int, error) {
func deepCopy(src *lazyNode, options *ApplyOptions) (*lazyNode, int, error) {
if src == nil {
return nil, 0, nil
}
a, err := json.Marshal(src)
a, err := json.MarshalEscaped(src, options.EscapeHTML)
if err != nil {
return nil, 0, err
}
Expand All @@ -222,7 +235,7 @@ func (n *lazyNode) nextByte() byte {
return s[0]
}

func (n *lazyNode) intoDoc() (*partialDoc, error) {
func (n *lazyNode) intoDoc(options *ApplyOptions) (*partialDoc, error) {
if n.which == eDoc {
return n.doc, nil
}
Expand All @@ -241,6 +254,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
return nil, ErrInvalid
}

n.doc.opts = options
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -551,7 +565,7 @@ func findObject(pd *container, path string, options *ApplyOptions) (container, s
return nil, ""
}
} else {
doc, err = next.intoDoc()
doc, err = next.intoDoc(options)

if err != nil {
return nil, ""
Expand Down Expand Up @@ -769,6 +783,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error {
} else {
pd = &partialDoc{
self: val,
opts: options,
}
}

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

doc.add(part, newNode, options)
doc, err = newNode.intoDoc()
doc, err = newNode.intoDoc(options)
if err != nil {
return err
}
Expand All @@ -887,7 +902,7 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
return err
}
} else {
doc, err = target.intoDoc()
doc, err = target.intoDoc(options)

if err != nil {
return err
Expand Down Expand Up @@ -973,6 +988,8 @@ func (p Patch) replace(doc *container, op Operation, options *ApplyOptions) erro
if !val.tryAry() {
return errors.Wrapf(err, "replace operation value must be object or array")
}
} else {
val.doc.opts = options
}
}

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

valCopy, sz, err := deepCopy(val)
valCopy, sz, err := deepCopy(val, options)
if err != nil {
return errors.Wrapf(err, "error while performing deep copy")
}
Expand Down Expand Up @@ -1221,6 +1238,7 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
} else {
pd = &partialDoc{
self: self,
opts: options,
}
}

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

if indent != "" {
return json.MarshalIndent(pd, "", indent)
data, err := json.MarshalEscaped(pd, options.EscapeHTML)
if err != nil {
return nil, err
}

if indent == "" {
return data, nil
}

return json.Marshal(pd)
var buf bytes.Buffer
json.Indent(&buf, data, "", indent)
return buf.Bytes(), nil
}

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

0 comments on commit b7a4e4a

Please sign in to comment.