diff --git a/bytesconv.go b/bytesconv.go index 5f39dcc..13ce724 100644 --- a/bytesconv.go +++ b/bytesconv.go @@ -8,6 +8,7 @@ import ( "io" "math" "net" + "reflect" "sync" "time" "unsafe" @@ -360,6 +361,20 @@ func b2s(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } +// s2b converts string to a byte slice without memory allocation. +// +// Note it may break if string and/or slice header will change +// in the future go versions. +func s2b(s string) []byte { + sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) + bh := reflect.SliceHeader{ + Data: sh.Data, + Len: sh.Len, + Cap: sh.Len, + } + return *(*[]byte)(unsafe.Pointer(&bh)) +} + // AppendQuotedArg appends url-encoded src to dst and returns appended dst. func AppendQuotedArg(dst, src []byte) []byte { for _, c := range src { diff --git a/uri.go b/uri.go index 6249f44..684b8f9 100644 --- a/uri.go +++ b/uri.go @@ -286,8 +286,19 @@ func normalizePath(dst, src []byte) []byte { } dst = dst[:bSize] - // remove /foo/../ parts + // remove /./ parts b = dst + for { + n := bytes.Index(b, strSlashDotSlash) + if n < 0 { + break + } + nn := n + len(strSlashDotSlash) - 1 + copy(b[n:], b[nn:]) + b = b[:len(b)-nn+n] + } + + // remove /foo/../ parts for { n := bytes.Index(b, strSlashDotDotSlash) if n < 0 { @@ -302,17 +313,6 @@ func normalizePath(dst, src []byte) []byte { b = b[:len(b)-n+nn] } - // remove /./ parts - for { - n := bytes.Index(b, strSlashDotSlash) - if n < 0 { - break - } - nn := n + len(strSlashDotSlash) - 1 - copy(b[n:], b[nn:]) - b = b[:len(b)-nn+n] - } - // remove trailing /foo/.. n := bytes.LastIndex(b, strSlashDotDot) if n >= 0 && n+len(strSlashDotDot) == len(b) { @@ -371,8 +371,7 @@ func (u *URI) LastPathSegment() []byte { // * Relative path, i.e. xx?yy=abc . In this case the original RequestURI // is updated according to the new relative path. func (u *URI) Update(newURI string) { - u.fullURI = append(u.fullURI[:0], newURI...) - u.UpdateBytes(u.fullURI) + u.UpdateBytes(s2b(newURI)) } // UpdateBytes updates uri. @@ -409,22 +408,28 @@ func (u *URI) updateBytes(newURI, buf []byte) []byte { } // relative path - if newURI[0] == '?' { + switch newURI[0] { + case '?': // query string only update u.SetQueryStringBytes(newURI[1:]) + return append(buf[:0], u.FullURI()...) + case '#': + // update only hash + u.SetHashBytes(newURI[1:]) + return append(buf[:0], u.FullURI()...) + default: + // update the last path part after the slash + path := u.Path() + n = bytes.LastIndexByte(path, '/') + if n < 0 { + panic("BUG: path must contain at least one slash") + } + buf = u.appendSchemeHost(buf[:0]) + buf = appendQuotedPath(buf, path[:n+1]) + buf = append(buf, newURI...) + u.Parse(nil, buf) return buf } - - path := u.Path() - n = bytes.LastIndexByte(path, '/') - if n < 0 { - panic("BUG: path must contain at least one slash") - } - buf = u.appendSchemeHost(buf[:0]) - buf = appendQuotedPath(buf, path[:n+1]) - buf = append(buf, newURI...) - u.Parse(nil, buf) - return buf } // FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}. diff --git a/uri_test.go b/uri_test.go index 3e519dc..508a9ba 100644 --- a/uri_test.go +++ b/uri_test.go @@ -109,6 +109,9 @@ func TestURIUpdate(t *testing.T) { testURIUpdate(t, "http://foo.bar/baz", "~a/%20b=c,тест?йцу=ке", "http://foo.bar/~a/%20b=c,%D1%82%D0%B5%D1%81%D1%82?йцу=ке") testURIUpdate(t, "http://foo.bar/baz", "/qwe#fragment", "http://foo.bar/qwe#fragment") testURIUpdate(t, "http://foobar/baz/xxx", "aaa.html#bb?cc=dd&ee=dfd", "http://foobar/baz/aaa.html#bb?cc=dd&ee=dfd") + + // hash + testURIUpdate(t, "http://foo.bar/baz#aaa", "#fragment", "http://foo.bar/baz#fragment") } func testURIUpdate(t *testing.T, base, update, result string) { @@ -166,6 +169,7 @@ func TestURIPathNormalize(t *testing.T) { testURIPathNormalize(t, &u, "/a/./b/././c/./d.html", "/a/b/c/d.html") testURIPathNormalize(t, &u, "./foo/", "/foo/") testURIPathNormalize(t, &u, "./../.././../../aaa/bbb/../../../././../", "/") + testURIPathNormalize(t, &u, "./a/./.././../b/./foo.html", "/b/foo.html") } func testURIPathNormalize(t *testing.T, u *URI, requestURI, expectedPath string) {