Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to control if the output is HTMLEscaped #202

Merged
merged 2 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -60,6 +60,8 @@ type partialDoc struct {
self *lazyNode
keys []string
obj map[string]*lazyNode

opts *ApplyOptions
}

type partialArray struct {
Expand Down Expand Up @@ -90,6 +92,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 @@ -99,6 +103,7 @@ func NewApplyOptions() *ApplyOptions {
AccumulatedCopySizeLimit: AccumulatedCopySizeLimit,
AllowMissingPathOnRemove: false,
EnsurePathExistsOnAdd: false,
EscapeHTML: true,
}
}

Expand Down Expand Up @@ -137,13 +142,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 @@ -153,7 +166,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 @@ -194,11 +207,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 @@ -216,7 +229,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 @@ -235,6 +248,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
return nil, ErrInvalid
}

n.doc.opts = options
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -545,7 +559,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 @@ -750,6 +764,7 @@ func (p Patch) add(doc *container, op Operation, options *ApplyOptions) error {
} else {
pd = &partialDoc{
self: val,
opts: options,
}
}

Expand Down Expand Up @@ -855,7 +870,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 @@ -868,7 +883,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 @@ -954,6 +969,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 @@ -1115,7 +1132,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 @@ -1202,6 +1219,7 @@ func (p Patch) ApplyIndentWithOptions(doc []byte, indent string, options *ApplyO
} else {
pd = &partialDoc{
self: self,
opts: options,
}
}

Expand Down Expand Up @@ -1238,11 +1256,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
Loading