Skip to content

Commit

Permalink
Disallow unknown struct fields in request parameters.
Browse files Browse the repository at this point in the history
If the argument of UnmarshalParams is a struct, report an error if the struct
does not support one or more of the fields of the object being unmarshaled.
  • Loading branch information
creachadair committed Jul 28, 2019
1 parent 3f71dc2 commit 78545e2
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 8 deletions.
11 changes: 9 additions & 2 deletions base.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jrpc2

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -67,12 +68,18 @@ func (r *Request) Method() string { return r.method }
func (r *Request) HasParams() bool { return len(r.params) != 0 }

// UnmarshalParams decodes the parameters into v. If r has empty parameters, it
// returns nil without modifying v.
// returns nil without modifying v. If r is invalid it returns an InvalidParams
// error.
func (r *Request) UnmarshalParams(v interface{}) error {
if len(r.params) == 0 {
return nil
}
return json.Unmarshal(r.params, v)
dec := json.NewDecoder(bytes.NewReader(r.params))
dec.DisallowUnknownFields()
if err := dec.Decode(v); err != nil {
return Errorf(code.InvalidParams, "invalid parameters: %v", err.Error())
}
return nil
}

// ErrInvalidVersion is returned by ParseRequests if one or more of the
Expand Down
31 changes: 25 additions & 6 deletions internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,18 +81,32 @@ func errEQ(x, y error) bool {
}

func TestUnmarshalParams(t *testing.T) {
type xy struct {
X int `json:"x"`
Y bool `json:"y"`
}

tests := []struct {
input string
want interface{}
code code.Code
}{
// If parameters are set, the target should be updated.
{`{"jsonrpc":"2.0", "id":1, "method":"X", "params":[1,2]}`, []int{1, 2}},
{`{"jsonrpc":"2.0", "id":1, "method":"X", "params":[1,2]}`, []int{1, 2}, code.NoError},

// If parameters are null, the target should not be modified.
{`{"jsonrpc":"2.0", "id":2, "method":"Y", "params":null}`, ""},
{`{"jsonrpc":"2.0", "id":2, "method":"Y", "params":null}`, "", code.NoError},

// If parameters are not set, the target should not be modified.
{`{"jsonrpc":"2.0", "id":2, "method":"Y"}`, 0},
{`{"jsonrpc":"2.0", "id":2, "method":"Y"}`, 0, code.NoError},

// Unmarshaling should work into a struct as long as the fields match.
{`{"jsonrpc":"2.0", "id":3, "method":"Z", "params":{}}`, xy{}, code.NoError},
{`{"jsonrpc":"2.0", "id":4, "method":"Z", "params":{"x":17}}`, xy{X: 17}, code.NoError},
{`{"jsonrpc":"2.0", "id":5, "method":"Z", "params":{"x":23, "y":true}}`,
xy{X: 23, Y: true}, code.NoError},
{`{"jsonrpc":"2.0", "id":6, "method":"Z", "params":{"x":23, "z":"wat"}}`,
xy{}, code.InvalidParams},
}
for _, test := range tests {
req, err := ParseRequests([]byte(test.input))
Expand All @@ -104,9 +118,14 @@ func TestUnmarshalParams(t *testing.T) {

// Allocate a zero of the expected type to unmarshal into.
target := reflect.New(reflect.TypeOf(test.want)).Interface()
if err := req[0].UnmarshalParams(target); err != nil {
t.Errorf("Unmarshaling parameters failed: %v", err)
continue
{
err := req[0].UnmarshalParams(target)
if got := code.FromError(err); got != test.code {
t.Errorf("UnmarshalParams error: got code %d, want %d [%v]", got, test.code, err)
}
if err != nil {
continue
}
}

// Dereference the target to get the value to compare.
Expand Down

0 comments on commit 78545e2

Please sign in to comment.