Skip to content

Commit eed7579

Browse files
authored
Merge pull request #180 from radwaretaltr/master
Pre-flight DecodePatch validation: Issue #177
2 parents 6775340 + 16760a7 commit eed7579

File tree

2 files changed

+97
-8
lines changed

2 files changed

+97
-8
lines changed

v5/patch.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,11 @@ func (o Operation) value() *lazyNode {
454454

455455
// ValueInterface decodes the operation value into an interface.
456456
func (o Operation) ValueInterface() (interface{}, error) {
457-
if obj, ok := o["value"]; ok && obj != nil {
457+
if obj, ok := o["value"]; ok {
458+
if obj == nil {
459+
return nil, nil
460+
}
461+
458462
var v interface{}
459463

460464
err := json.Unmarshal(*obj, &v)
@@ -816,6 +820,43 @@ func ensurePathExists(pd *container, path string, options *ApplyOptions) error {
816820
return nil
817821
}
818822

823+
func validateOperation(op Operation) error {
824+
switch op.Kind() {
825+
case "add", "replace":
826+
if _, err := op.ValueInterface(); err != nil {
827+
return errors.Wrapf(err, "failed to decode 'value'")
828+
}
829+
case "move", "copy":
830+
if _, err := op.From(); err != nil {
831+
return errors.Wrapf(err, "failed to decode 'from'")
832+
}
833+
case "remove", "test":
834+
default:
835+
return fmt.Errorf("unsupported operation")
836+
}
837+
838+
if _, err := op.Path(); err != nil {
839+
return errors.Wrapf(err, "failed to decode 'path'")
840+
}
841+
842+
return nil
843+
}
844+
845+
func validatePatch(p Patch) error {
846+
for _, op := range p {
847+
if err := validateOperation(op); err != nil {
848+
opData, infoErr := json.Marshal(op)
849+
if infoErr != nil {
850+
return errors.Wrapf(err, "invalid operation")
851+
}
852+
853+
return errors.Wrapf(err, "invalid operation %s", opData)
854+
}
855+
}
856+
857+
return nil
858+
}
859+
819860
func (p Patch) remove(doc *container, op Operation, options *ApplyOptions) error {
820861
path, err := op.Path()
821862
if err != nil {
@@ -1044,6 +1085,10 @@ func DecodePatch(buf []byte) (Patch, error) {
10441085
return nil, err
10451086
}
10461087

1088+
if err := validatePatch(p); err != nil {
1089+
return nil, err
1090+
}
1091+
10471092
return p, nil
10481093
}
10491094

v5/patch_test.go

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func applyPatch(doc, patch string) (string, error) {
3131
obj, err := DecodePatch([]byte(patch))
3232

3333
if err != nil {
34-
panic(err)
34+
return "", err
3535
}
3636

3737
out, err := obj.Apply([]byte(doc))
@@ -47,7 +47,7 @@ func applyPatchIndented(doc, patch string) (string, error) {
4747
obj, err := DecodePatch([]byte(patch))
4848

4949
if err != nil {
50-
panic(err)
50+
return "", err
5151
}
5252

5353
out, err := obj.ApplyIndent([]byte(doc), " ")
@@ -63,7 +63,7 @@ func applyPatchWithOptions(doc, patch string, options *ApplyOptions) (string, er
6363
obj, err := DecodePatch([]byte(patch))
6464

6565
if err != nil {
66-
panic(err)
66+
return "", err
6767
}
6868

6969
out, err := obj.ApplyWithOptions([]byte(doc), options)
@@ -574,97 +574,125 @@ var Cases = []Case{
574574
}
575575

576576
type BadCase struct {
577-
doc, patch string
577+
doc, patch string
578+
failOnDecode bool
578579
}
579580

580581
var MutationTestCases = []BadCase{
581582
{
582583
`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
583584
`[ { "op": "remove", "path": "/qux/bar" } ]`,
585+
false,
584586
},
585587
{
586588
`{ "foo": "bar", "qux": { "baz": 1, "bar": null } }`,
587589
`[ { "op": "replace", "path": "/qux/baz", "value": null } ]`,
590+
true,
591+
},
592+
// malformed value
593+
{
594+
`{ "foo": "bar" }`,
595+
`[ { "op": "add", "path": "/", "value": "{qux" } ]`,
596+
true,
588597
},
589598
}
590599

591600
var BadCases = []BadCase{
592601
{
593602
`{ "foo": "bar" }`,
594603
`[ { "op": "add", "path": "/baz/bat", "value": "qux" } ]`,
604+
false,
595605
},
596606
{
597607
`{ "a": { "b": { "d": 1 } } }`,
598608
`[ { "op": "remove", "path": "/a/b/c" } ]`,
609+
false,
599610
},
600611
{
601612
`{ "a": { "b": { "d": 1 } } }`,
602613
`[ { "op": "move", "from": "/a/b/c", "path": "/a/b/e" } ]`,
614+
false,
603615
},
604616
{
605617
`{ "a": { "b": [1] } }`,
606618
`[ { "op": "remove", "path": "/a/b/1" } ]`,
619+
false,
607620
},
608621
{
609622
`{ "a": { "b": [1] } }`,
610623
`[ { "op": "move", "from": "/a/b/1", "path": "/a/b/2" } ]`,
624+
false,
611625
},
612626
{
613627
`{ "foo": "bar" }`,
614628
`[ { "op": "add", "pathz": "/baz", "value": "qux" } ]`,
629+
true,
615630
},
616631
{
617632
`{ "foo": "bar" }`,
618633
`[ { "op": "add", "path": "", "value": "qux" } ]`,
634+
false,
619635
},
620636
{
621637
`{ "foo": ["bar","baz"]}`,
622638
`[ { "op": "replace", "path": "/foo/2", "value": "bum"}]`,
639+
false,
623640
},
624641
{
625642
`{ "foo": ["bar","baz"]}`,
626643
`[ { "op": "add", "path": "/foo/-4", "value": "bum"}]`,
644+
false,
627645
},
628646
{
629647
`{ "name":{ "foo": "bat", "qux": "bum"}}`,
630648
`[ { "op": "replace", "path": "/foo/bar", "value":"baz"}]`,
649+
false,
631650
},
632651
{
633652
`{ "foo": ["bar"]}`,
634653
`[ {"op": "add", "path": "/foo/2", "value": "bum"}]`,
654+
false,
635655
},
636656
{
637657
`{ "foo": []}`,
638658
`[ {"op": "remove", "path": "/foo/-"}]`,
659+
false,
639660
},
640661
{
641662
`{ "foo": []}`,
642663
`[ {"op": "remove", "path": "/foo/-1"}]`,
664+
false,
643665
},
644666
{
645667
`{ "foo": ["bar"]}`,
646668
`[ {"op": "remove", "path": "/foo/-2"}]`,
669+
false,
647670
},
648671
{
649672
`{}`,
650673
`[ {"op":null,"path":""} ]`,
674+
true,
651675
},
652676
{
653677
`{}`,
654678
`[ {"op":"add","path":null} ]`,
679+
true,
655680
},
656681
{
657682
`{}`,
658683
`[ { "op": "copy", "from": null }]`,
684+
true,
659685
},
660686
{
661687
`{ "foo": ["bar"]}`,
662688
`[{"op": "copy", "path": "/foo/6666666666", "from": "/"}]`,
689+
false,
663690
},
664691
// Can't copy into an index greater than the size of the array
665692
{
666693
`{ "foo": ["bar"]}`,
667694
`[{"op": "copy", "path": "/foo/2", "from": "/foo/0"}]`,
695+
false,
668696
},
669697
// Accumulated copy size cannot exceed AccumulatedCopySizeLimit.
670698
{
@@ -673,20 +701,24 @@ var BadCases = []BadCase{
673701
// size, so each copy operation increases the size by 51 bytes.
674702
`[ { "op": "copy", "path": "/foo/-", "from": "/foo/1" },
675703
{ "op": "copy", "path": "/foo/-", "from": "/foo/1" }]`,
704+
false,
676705
},
677706
// Can't move into an index greater than or equal to the size of the array
678707
{
679708
`{ "foo": [ "all", "grass", "cows", "eat" ] }`,
680709
`[ { "op": "move", "from": "/foo/1", "path": "/foo/4" } ]`,
710+
false,
681711
},
682712
{
683713
`{ "baz": "qux" }`,
684714
`[ { "op": "replace", "path": "/foo", "value": "bar" } ]`,
715+
false,
685716
},
686717
// Can't copy from non-existent "from" key.
687718
{
688719
`{ "foo": "bar"}`,
689720
`[{"op": "copy", "path": "/qux", "from": "/baz"}]`,
721+
false,
690722
},
691723
}
692724

@@ -753,10 +785,22 @@ func TestAllCases(t *testing.T) {
753785
}
754786

755787
for _, c := range BadCases {
756-
_, err := applyPatch(c.doc, c.patch)
788+
p, err := DecodePatch([]byte(c.patch))
789+
if err == nil && c.failOnDecode {
790+
t.Errorf("Patch %q should have failed decode but did not", c.patch)
791+
}
792+
793+
if err != nil && !c.failOnDecode {
794+
t.Errorf("Patch %q should have passed decode but failed with %v", c.patch, err)
795+
}
796+
797+
if err == nil && !c.failOnDecode {
798+
_, err = p.Apply([]byte(c.doc))
799+
800+
if err == nil {
801+
t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
802+
}
757803

758-
if err == nil {
759-
t.Errorf("Patch %q should have failed to apply but it did not", c.patch)
760804
}
761805
}
762806
}

0 commit comments

Comments
 (0)