package cnp import ( "bytes" "strconv" "strings" "testing" "time" ) type requestTest struct { h, s string p Parameters e, v error } var requestTests = []requestTest{ // invalid intent {"", "", nil, ErrorInvalid{}, nil}, {"", "foo/bar", nil, ErrorInvalid{}, nil}, {"foo/", "bar", nil, ErrorInvalid{}, nil}, {"foo/", "/bar", nil, ErrorInvalid{}, nil}, // invalid request params {"", "/", Parameters{"length": "w"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"length": "-1"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"length": "03"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"name": "foo/bar"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"name": "foo\x00bar"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "\x00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "text/plain\x00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo/bar "}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": " foo/bar"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo /bar"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo/ bar"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo/bar\n"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"type": "foo/b(r"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "now"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "today"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "Thu Jan 1 00:00:00 UTC 1970"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01 00:00:00+00:00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01 00:00:00 UTC"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01 00:00:00+0000"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01 00:00:00+00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01T00:00:00+00:00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01T00:00:00 UTC"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01T00:00:00+0000"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1970-01-01T00:00:00+00"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-00-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-01-00T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-01-01T24:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-01-01T00:60:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-01-01T00:00:60Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0000-11-31T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0001-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0002-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0003-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0005-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "0100-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "1000-02-29T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "123-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "12345-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "-5-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"if_modified": "-2005-01-01T00:00:00Z"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"select": "w"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"select": " "}, nil, ErrorInvalid{}}, {"", "/", Parameters{"select": ":"}, nil, ErrorInvalid{}}, {"", "/", Parameters{"select": ":foobar"}, nil, ErrorInvalid{}}, // valid simple requests {"", "/", nil, nil, nil}, {"", "/foo/bar", nil, nil, nil}, {"cnp.example.com", "/", nil, nil, nil}, {"foo", "/bar", nil, nil, nil}, {"example.com", "/ f=#\\oo///.././.../~/\x01/\xff/ba\nr", nil, nil, nil}, // valid request params {"", "/", Parameters{"length": "", "name": "", "type": "", "if_modified": "", "select": "", "": "", "q\x00we": "=a s\nd"}, nil, nil}, {"", "/", Parameters{"length": "0"}, nil, nil}, {"", "/", Parameters{"length": "1"}, nil, nil}, {"", "/", Parameters{"length": "12345670089000000"}, nil, nil}, {"", "/", Parameters{"name": "foobar"}, nil, nil}, {"", "/", Parameters{"name": "x"}, nil, nil}, {"", "/", Parameters{"name": "..-~!foo bar\nbaz\rquux=qwe\\asd"}, nil, nil}, {"", "/", Parameters{"name": strings.Repeat("w", 1024*8)}, nil, nil}, {"", "/", Parameters{"type": "qwe/asd"}, nil, nil}, {"", "/", Parameters{"type": "qwe+asd/foo"}, nil, nil}, {"", "/", Parameters{"type": "qwe-asd/foo"}, nil, nil}, {"", "/", Parameters{"if_modified": "1970-01-01T00:00:00Z"}, nil, nil}, {"", "/", Parameters{"if_modified": "0000-01-01T00:00:00Z"}, nil, nil}, {"", "/", Parameters{"if_modified": "9999-12-31T23:59:59Z"}, nil, nil}, {"", "/", Parameters{"if_modified": "0123-05-06T07:08:09Z"}, nil, nil}, {"", "/", Parameters{"if_modified": "0000-02-29T00:00:00Z"}, nil, nil}, {"", "/", Parameters{"if_modified": "2000-02-29T00:00:00Z"}, nil, nil}, {"", "/", Parameters{"select": "\x00:\x00"}, nil, nil}, {"", "/", Parameters{"select": "foo:bar:baz"}, nil, nil}, {"", "/", Parameters{"select": "byte:5-"}, nil, nil}, {"", "/", Parameters{"select": "cnm:#/foo/bar"}, nil, nil}, } func TestNewRequest(t *testing.T) { for _, tst := range requestTests { req, err := NewRequest(tst.h, tst.s, []byte{}) if !errorEqual(tst.e, err) { t.Errorf("NewRequest(%q, %q): expected error %+v, got %+v (%v)", tst.h, tst.s, tst.e, err, err) continue } if tst.e == nil { if tst.h != req.Host() { t.Errorf("NewRequest(%q: %q): got unexpected host %q", tst.h, tst.s, req.Host()) } else if Clean(tst.s) != req.Path() { t.Errorf("NewRequest(%q: %q): got unexpected path %q", tst.h, tst.s, req.Path()) } } } } type requestURLTest struct { u string h, p string e error } var requestURLTests = []requestURLTest{ // invalid {"cnp:example.com/path/to/file", "", "", ErrorURL{}}, {"http://example.com/path/to/file", "", "", ErrorURL{}}, {"cnp://foo@bar:example.com/path/to/file", "", "", ErrorURL{}}, {"cnp://foo:example.com/path/to/file", "", "", ErrorURL{}}, {"", "", "/", ErrorURL{}}, {"cnp://example.com/%5", "", "", ErrorURL{}}, //{"cnp://example.com/?%5", "", "", ErrorURL{}}, // valid {"cnp://example.com/path/to/file", "example.com", "/path/to/file", nil}, {"cnp://2130706433/", "2130706433", "/", nil}, //{"cnp://02130706433/", "2130706433", "/", nil}, {"cnp://127.0.0.1/", "127.0.0.1", "/", nil}, //{"cnp://0127.0.00.01/", "127.0.0.1", "/", nil}, {"cnp://localhost", "localhost", "/", nil}, {"cnp://[::1]/foo%20bar", "[::1]", "/foo bar", nil}, {"cnp://[2001:db8::7334]/foo%0abar", "[2001:db8::7334]", "/foo\nbar", nil}, //{"cnp://[2001:0db8:0000::7334]/foo%0abar", "[2001:db8::7334]", "/foo\nbar", nil}, //{"cnp://example.com/qwe%20asd?foo=bar&baz=qu%20ux", "example.com", "/qwe asd?foo=bar&baz=qu ux", nil}, {"cnp://example.com/qwe%20asd?foo=bar&baz=qu%20ux", "example.com", "/qwe asd", nil}, {"cnp://localhost:25454", "localhost", "/", nil}, {"cnp://localhost:12345/foo/bar", "localhost:12345", "/foo/bar", nil}, {"cnp://localhost:25454/foo/bar", "localhost", "/foo/bar", nil}, {"cnp://2130706433:12345/foo/bar", "2130706433:12345", "/foo/bar", nil}, {"cnp://2130706433:25454/foo/bar", "2130706433", "/foo/bar", nil}, {"cnp://127.0.0.1:12345/foo/bar", "127.0.0.1:12345", "/foo/bar", nil}, {"cnp://127.0.0.1:25454/foo/bar", "127.0.0.1", "/foo/bar", nil}, {"cnp://[::1]:12345/foo/bar", "[::1]:12345", "/foo/bar", nil}, {"cnp://[::1]:25454/foo/bar", "[::1]", "/foo/bar", nil}, {"//example.com/path/", "example.com", "/path/", nil}, {"/example.com/path/", "", "/example.com/path/", nil}, {"/foo/bar", "", "/foo/bar", nil}, {"/", "", "/", nil}, {"cnp://example.com/ ™☺)\n", "example.com", "/ ™☺)\n", nil}, {"cnp://œ¤å₥¶ḹə.©°ɱ/baz/../foo/.//bar////.//../bar//", "œ¤å₥¶ḹə.©°ɱ", "/foo/bar/", nil}, {"cnp://foo/bar/", "foo", "/bar/", nil}, {"cnp:///foo/bar/", "", "/foo/bar/", nil}, {"cnp:////////foo/bar/", "", "/foo/bar/", nil}, } func TestNewRequestURL(t *testing.T) { for _, tst := range requestURLTests { req, err := NewRequestURL(tst.u, nil) if !errorEqual(tst.e, err) { t.Errorf("NewRequestURL(%q, nil): expected error %+v, got %+v (%v)", tst.u, tst.e, err, err) continue } if tst.e == nil { if req.Host() != tst.h { t.Errorf("NewRequestURL(%q, nil): expected host %q, got %q", tst.u, tst.h, req.Host()) } else if req.Path() != tst.p { t.Errorf("NewRequestURL(%q, nil): expected path %q, got %q", tst.u, tst.p, req.Path()) } } } } func TestWriteParseRequest(t *testing.T) { for _, tst := range requestTests { if tst.e != nil { continue // skip syntax errors but not invalid params } req := &Request{Message{Header{0, 3, "/", Parameters{}}, nil, nil}} if err := req.SetHost(tst.h); err != nil { t.Errorf("SetHost(%q) error: %v", tst.h, err) continue } if err := req.SetPath(tst.s); err != nil { t.Errorf("SetPath(%q) error: %v", tst.s, err) continue } for k, v := range tst.p { req.SetParam(k, v) } var buf bytes.Buffer if err := req.Write(&buf); err != nil { t.Errorf("%+v.Write: error %v", req, err) continue } s := buf.String() req2, err := ParseRequest(strings.NewReader(s)) if err != nil { t.Errorf("ParseRequest(%q) error: %v", s, err) continue } if !msgEqual(&req.Message, &req2.Message) { t.Errorf("Write/Parse: expected %+v, got %+v", req, req2) continue } } } func TestRequestValidate(t *testing.T) { for _, tst := range requestTests { if tst.e != nil { continue } req := &Request{Message{Header{Intent: tst.h + tst.s, Parameters: tst.p}, nil, nil}} if err := req.Validate(); !errorEqual(tst.v, err) { t.Errorf("%+v.Validate(): expected error %+v, got %+v (%v)", req, tst.v, err, err) } } } func TestRequestGetSet(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 requestTests { if tst.e != nil { continue } req, _ := NewRequest(tst.h, tst.s, nil) for k, v := range tst.p { switch k { case "length": n, _ := strconv.ParseUint(v, 10, 63) req.SetLength(int64(n)) s := strconv.FormatInt(req.Length(), 10) if s == "0" && v == "" { s = "" } c(k, v, tst.v, s) case "name": e(k, v, tst.v, req.SetName(v)) c(k, v, tst.v, req.Name()) case "type": e(k, v, tst.v, req.SetType(v)) t := req.Type() if t == "application/octet-stream" && v == "" { t = "" } c(k, v, tst.v, t) case "if_modified": if tst.v != nil { continue // invalid time format, can't even parse } var tm time.Time if v != "" { tm, _ = time.Parse(time.RFC3339, v) } req.SetIfModified(tm) tm2 := "" if !req.IfModified().IsZero() { tm2 = req.IfModified().Format(time.RFC3339) } c(k, v, tst.v, tm2) case "select": if tst.v != nil { continue } sel := "" ss := strings.SplitN(v, ":", 2) if len(ss) == 2 { req.SetSelect(ss[0], ss[1]) a, b := req.Select() if a != "" { sel = a + ":" + b } } c(k, v, tst.v, sel) default: req.SetParam(k, v) c(k, v, tst.v, req.Param(k)) } } } } var urlTests = map[string]string{ "œ¤å₥¶ḹə.©°ɱ/foo/bar": "cnp://%C5%93%C2%A4%C3%A5%E2%82%A5%C2%B6%E1%B8%B9%C9%99.%C2%A9%C2%B0%C9%B1/foo/bar", "2130706433/": "cnp://2130706433/", "example.com/path/to/file/": "cnp://example.com/path/to/file/", "[::1]/foo bar": "cnp://[::1]/foo%20bar", "[2001:db8::7334]/foo\nbar": "cnp://[2001:db8::7334]/foo%0Abar", "example.com/qwe asd": "cnp://example.com/qwe%20asd", "example.com/ ™☺)\n": "cnp://example.com/%20%E2%84%A2%E2%98%BA%29%0A", } func TestRequestURL(t *testing.T) { for k, v := range urlTests { t.Run(v, func(t *testing.T) { req, err := NewRequestURL(v, nil) if err != nil { t.Fatalf("NewRequestURL(%q): error: %v", v, err) } if req.Intent() != k { t.Fatalf("NewRequestURL(%q).Intent(): expected %q, got %q", v, k, req.Intent()) } if req.URL().String() != v { t.Fatalf("NewRequestURL(%q).URL(): expected %q, got %q", v, v, req.URL()) } }) } } var cleanTests = map[string]string{ "/../.././//foo/bar/..": "/foo", "//foo/bar/../": "/foo/", "/": "/", "//": "/", "/..": "/", "/../..": "/", "/../": "/", "/.": "/", "/./": "/", "/./.": "/", "/././": "/", "/./../": "/", "/.././": "/", "/foo/bar/../baz": "/foo/baz", "/foo/bar/../baz/": "/foo/baz/", "/foo/../foo/bar/../bar/baz/quux/../": "/foo/bar/baz/", "/foo/../foo/bar/../bar/baz/quux/..": "/foo/bar/baz", } func TestClean(t *testing.T) { for k, v := range cleanTests { t.Run(k, func(t *testing.T) { c := Clean(k) if c != v { t.Fatalf("Clean(%q): expected %q, got %q", k, v, c) } }) } }