package cnp import ( "bytes" "strconv" "strings" "testing" "time" ) var ( responseTests = []responseTest{ // invalid intent {"", nil, ErrorInvalid{}, ErrorInvalid{}}, {"foo", nil, ErrorInvalid{}, ErrorInvalid{}}, {"example.com/path", nil, ErrorInvalid{}, ErrorInvalid{}}, {"errors", nil, ErrorInvalid{}, ErrorInvalid{}}, {" ok", nil, ErrorInvalid{}, ErrorInvalid{}}, {"ok ", nil, ErrorInvalid{}, ErrorInvalid{}}, {"оk", nil, ErrorInvalid{}, ErrorInvalid{}}, // invalid response params {"ok", Parameters{"length": "w"}, nil, ErrorInvalid{}}, {"ok", Parameters{"length": "-1"}, nil, ErrorInvalid{}}, {"ok", Parameters{"length": "03"}, nil, ErrorInvalid{}}, {"ok", Parameters{"name": "/.."}, nil, ErrorInvalid{}}, {"ok", Parameters{"name": "/"}, nil, ErrorInvalid{}}, {"ok", Parameters{"name": "foo/bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"name": "foo/bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"name": "foo\x00bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "\x00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "text/plain\x00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo/bar "}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": " foo/bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo /bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo/ bar"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo/bar\n"}, nil, ErrorInvalid{}}, {"ok", Parameters{"type": "foo/b(r"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "now"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "today"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "Thu Jan 1 00:00:00 UTC 1970"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01 00:00:00+00:00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01 00:00:00 UTC"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01 00:00:00+0000"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01 00:00:00+00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01T00:00:00+00:00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01T00:00:00 UTC"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01T00:00:00+0000"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1970-01-01T00:00:00+00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-00-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-01-00T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-01-01T24:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-01-01T00:60:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-01-01T00:00:60Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0000-11-31T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0001-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0002-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0003-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0005-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "0100-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "1000-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "123-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "12345-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "-5-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"time": "-2005-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "now"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "today"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "Thu Jan 1 00:00:00 UTC 1970"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01 00:00:00+00:00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01 00:00:00 UTC"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01 00:00:00+0000"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01 00:00:00+00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01T00:00:00+00:00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01T00:00:00 UTC"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01T00:00:00+0000"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1970-01-01T00:00:00+00"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-00-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-01-00T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-01-01T24:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-01-01T00:60:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-01-01T00:00:60Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0000-11-31T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0001-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0002-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0003-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0005-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "0100-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "1000-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "123-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "12345-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "-5-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"ok", Parameters{"modified": "-2005-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"redirect", Parameters{"location": "foo"}, nil, ErrorInvalid{}}, {"redirect", Parameters{"location": "foo bar/baz quux"}, nil, ErrorInvalid{}}, {"redirect", Parameters{"location": "/foo\x00bar"}, nil, ErrorInvalid{}}, {"error", Parameters{"reason": "ok"}, nil, ErrorInvalid{}}, {"error", Parameters{"reason": "not supported"}, nil, ErrorInvalid{}}, {"error", Parameters{"reason": "syntax\n"}, nil, ErrorInvalid{}}, {"error", Parameters{"reason": " server_error"}, nil, ErrorInvalid{}}, {"error", Parameters{"reason": "invalid "}, nil, ErrorInvalid{}}, {"ok", Parameters{"select": "w"}, nil, ErrorInvalid{}}, {"ok", Parameters{"select": " "}, nil, ErrorInvalid{}}, {"ok", Parameters{"select": ":"}, nil, ErrorInvalid{}}, {"ok", Parameters{"select": ":foobar"}, nil, ErrorInvalid{}}, // invalid: redirect *requires* the location parameter {"redirect", nil, nil, ErrorInvalid{}}, {"redirect", Parameters{"location": ""}, nil, ErrorInvalid{}}, // valid simple responses {"ok", nil, nil, nil}, {"not_modified", nil, nil, nil}, {"error", nil, nil, nil}, // valid responses with parameters {"ok", Parameters{"length": "", "name": "", "type": "", "time": "", "reason": "", "location": "", "modified": "", "": "", "q\x00we": "=a s\nd"}, nil, nil}, {"ok", Parameters{"length": "0"}, nil, nil}, {"ok", Parameters{"length": "1"}, nil, nil}, {"ok", Parameters{"length": "12345670089000000"}, nil, nil}, {"ok", Parameters{"length": "12345678900"}, nil, nil}, {"ok", Parameters{"name": "foobar"}, nil, nil}, {"ok", Parameters{"name": "foo bar"}, nil, nil}, {"ok", Parameters{"name": "foo=bar\nbaz\rquux"}, nil, nil}, {"ok", Parameters{"name": "..-~!foo bar\nbaz\rquux=qwe\\asd"}, nil, nil}, {"ok", Parameters{"name": strings.Repeat("w", 1024*8)}, nil, nil}, {"ok", Parameters{"name": " "}, nil, nil}, {"ok", Parameters{"name": ".."}, nil, nil}, {"ok", Parameters{"name": "."}, nil, nil}, {"ok", Parameters{"type": "foo/bar"}, nil, nil}, {"ok", Parameters{"type": "application/octet-stream"}, nil, nil}, {"ok", Parameters{"type": "x-test/x-testing"}, nil, nil}, {"ok", Parameters{"type": "application/vnd.testing.test-test.5+xml"}, nil, nil}, {"ok", Parameters{"modified": "1970-01-01T00:00:00Z"}, nil, nil}, {"ok", Parameters{"modified": "0000-01-01T00:00:00Z"}, nil, nil}, {"ok", Parameters{"modified": "9999-12-31T23:59:59Z"}, nil, nil}, {"ok", Parameters{"modified": "0123-05-06T07:08:09Z"}, nil, nil}, {"ok", Parameters{"modified": "0000-02-29T00:00:00Z"}, nil, nil}, {"ok", Parameters{"modified": "2000-02-29T00:00:00Z"}, nil, nil}, {"ok", Parameters{"time": "1970-01-01T00:00:00Z"}, nil, nil}, {"ok", Parameters{"time": "0000-01-01T00:00:00Z"}, nil, nil}, {"ok", Parameters{"time": "9999-12-31T23:59:59Z"}, nil, nil}, {"ok", Parameters{"time": "0123-05-06T07:08:09Z"}, nil, nil}, {"ok", Parameters{"time": "0000-02-29T00:00:00Z"}, nil, nil}, {"ok", Parameters{"time": "2000-02-29T00:00:00Z"}, nil, nil}, {"error", Parameters{"reason": "syntax"}, nil, nil}, {"error", Parameters{"reason": "version"}, nil, nil}, {"error", Parameters{"reason": "invalid"}, nil, nil}, {"error", Parameters{"reason": "not_supported"}, nil, nil}, {"error", Parameters{"reason": "too_large"}, nil, nil}, {"error", Parameters{"reason": "not_found"}, nil, nil}, {"error", Parameters{"reason": "denied"}, nil, nil}, {"error", Parameters{"reason": "rejected"}, nil, nil}, {"error", Parameters{"reason": "server_error"}, nil, nil}, {"redirect", Parameters{"location": "/"}, nil, nil}, {"redirect", Parameters{"location": "foo/bar"}, nil, nil}, {"redirect", Parameters{"location": "foo/"}, nil, nil}, {"redirect", Parameters{"location": "/bar"}, nil, nil}, {"redirect", Parameters{"location": "[::1]:12345/ foo\n\x01\xff/"}, nil, nil}, {"redirect", Parameters{"location": "/../../////././.."}, nil, nil}, {"ok", Parameters{"select": "\x00:\x00"}, nil, nil}, {"ok", Parameters{"select": "foo:bar:baz"}, nil, nil}, {"ok", Parameters{"select": "byte:5-"}, nil, nil}, {"ok", Parameters{"select": "cnm:#/foo/bar"}, nil, nil}, } ) type responseTest struct { i string p Parameters e, v error } func TestNewResponse(t *testing.T) { for _, tst := range responseTests { resp, err := NewResponse(tst.i, []byte{}) if !errorEqual(tst.e, err) { t.Errorf("NewResponse(%q): expected error %+v, got %+v (%v)", tst.i, tst.e, err, err) continue } if tst.e == nil { if tst.i != resp.ResponseIntent() { t.Errorf("NewResponse(%q): got unexpected intent %q", tst.i, resp.ResponseIntent()) } } } } func TestWriteParseResponse(t *testing.T) { for _, tst := range responseTests { if tst.e != nil { continue // skip invalid intents } resp := &Response{Message{Header{0, 3, "ok", Parameters{}}, nil, nil}} if err := resp.SetResponseIntent(tst.i); err != nil { t.Errorf("SetResponseIntent(%q) error: %v", tst.i, err) continue } for k, v := range tst.p { resp.SetParam(k, v) } var buf bytes.Buffer if err := resp.Write(&buf); err != nil { t.Errorf("%+v.Write: error %v", resp, err) continue } s := buf.String() resp2, err := ParseResponse(strings.NewReader(s)) if err != nil { t.Errorf("ParseResponse(%q) error: %v", s, err) continue } if !msgEqual(&resp.Message, &resp2.Message) { t.Errorf("Write/Parse: expected %+v, got %+v", resp, resp2) continue } } } func TestResponseValidate(t *testing.T) { for _, tst := range responseTests { resp := &Response{Message{Header{Intent: tst.i, Parameters: tst.p}, nil, nil}} if err := resp.Validate(); !errorEqual(tst.v, err) { t.Errorf("%+v.Validate(): expected error %+v, got %+v (%v)", resp, tst.v, err, err) } } } func TestResponseGetSet(t *testing.T) { e := func(k, v string, expect, err error) { if !errorEqual(err, expect) { t.Errorf("setting param %q to %q: expected %+v, got %+v (%v)", k, v, expect, err, err) } } c := func(k, v string, expect error, p string) { if expect == nil && v != p { t.Errorf("getting param %q: expected %q, got %q", k, v, p) } } for _, tst := range responseTests { resp, _ := NewResponse("ok", nil) if err := resp.SetResponseIntent(tst.i); !errorEqual(err, tst.e) { t.Errorf("setting response intent to %q: expected %+v, got %+v (%v)", tst.i, tst.e, err, err) continue } if tst.e == nil && resp.ResponseIntent() != tst.i { t.Errorf("getting response intent: expected %q, got %q", tst.i, resp.ResponseIntent()) continue } for k, v := range tst.p { switch k { case "length": n, _ := strconv.ParseUint(v, 10, 63) resp.SetLength(int64(n)) s := strconv.FormatInt(resp.Length(), 10) if s == "0" && v == "" { s = "" } c(k, v, tst.v, s) case "name": e(k, v, tst.v, resp.SetName(v)) c(k, v, tst.v, resp.Name()) case "type": e(k, v, tst.v, resp.SetType(v)) t := resp.Type() if t == "application/octet-stream" && v == "" { t = "" } c(k, v, tst.v, t) case "time": var tm time.Time if v != "" { tm, _ = time.Parse(time.RFC3339, v) } resp.SetTime(tm) tm2 := "" if !resp.Time().IsZero() { tm2 = resp.Time().Format(time.RFC3339) } c(k, v, tst.v, tm2) case "modified": var tm time.Time if v != "" { tm, _ = time.Parse(time.RFC3339, v) } resp.SetModified(tm) tm2 := "" if !resp.Modified().IsZero() { tm2 = resp.Modified().Format(time.RFC3339) } c(k, v, tst.v, tm2) case "location": ss := strings.SplitN(v, "/", 2) if len(ss) != 2 { continue // invalid location } e(k, v, tst.v, resp.SetLocation(ss[0], "/"+ss[1])) host, path, err := resp.Location() if err != nil { t.Errorf("getting parameter location: error %v", err) } else { c(k, v, tst.v, host+path) } case "reason": e(k, v, tst.v, resp.SetReason(v)) c(k, v, tst.v, resp.Reason()) case "select": if tst.v != nil { continue } sel := "" ss := strings.SplitN(v, ":", 2) if len(ss) == 2 { resp.SetSelect(ss[0], ss[1]) a, b := resp.Select() if a != "" { sel = a + ":" + b } } c(k, v, tst.v, sel) default: resp.SetParam(k, v) c(k, v, tst.v, resp.Param(k)) } } } }