mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2025-01-23 11:21:26 +01:00
e3cc329d85
The v0.1.0 points to the last verified changes made by me.
I'm afraid that releases after v0.1.0 may contain completely broken changes like
996610f021
526 lines
13 KiB
Go
526 lines
13 KiB
Go
package fasthttp
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
// AcquireURI returns an empty URI instance from the pool.
|
|
//
|
|
// Release the URI with ReleaseURI after the URI is no longer needed.
|
|
// This allows reducing GC load.
|
|
func AcquireURI() *URI {
|
|
return uriPool.Get().(*URI)
|
|
}
|
|
|
|
// ReleaseURI releases the URI acquired via AcquireURI.
|
|
//
|
|
// The released URI mustn't be used after releasing it, otherwise data races
|
|
// may occur.
|
|
func ReleaseURI(u *URI) {
|
|
u.Reset()
|
|
uriPool.Put(u)
|
|
}
|
|
|
|
var uriPool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return &URI{}
|
|
},
|
|
}
|
|
|
|
// URI represents URI :) .
|
|
//
|
|
// It is forbidden copying URI instances. Create new instance and use CopyTo
|
|
// instead.
|
|
//
|
|
// URI instance MUST NOT be used from concurrently running goroutines.
|
|
type URI struct {
|
|
noCopy noCopy
|
|
|
|
pathOriginal []byte
|
|
scheme []byte
|
|
path []byte
|
|
queryString []byte
|
|
hash []byte
|
|
host []byte
|
|
|
|
queryArgs Args
|
|
parsedQueryArgs bool
|
|
|
|
fullURI []byte
|
|
requestURI []byte
|
|
|
|
h *RequestHeader
|
|
}
|
|
|
|
// CopyTo copies uri contents to dst.
|
|
func (u *URI) CopyTo(dst *URI) {
|
|
dst.Reset()
|
|
dst.pathOriginal = append(dst.pathOriginal[:0], u.pathOriginal...)
|
|
dst.scheme = append(dst.scheme[:0], u.scheme...)
|
|
dst.path = append(dst.path[:0], u.path...)
|
|
dst.queryString = append(dst.queryString[:0], u.queryString...)
|
|
dst.hash = append(dst.hash[:0], u.hash...)
|
|
dst.host = append(dst.host[:0], u.host...)
|
|
|
|
u.queryArgs.CopyTo(&dst.queryArgs)
|
|
dst.parsedQueryArgs = u.parsedQueryArgs
|
|
|
|
// fullURI and requestURI shouldn't be copied, since they are created
|
|
// from scratch on each FullURI() and RequestURI() call.
|
|
dst.h = u.h
|
|
}
|
|
|
|
// Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .
|
|
//
|
|
// The returned value is valid until the next URI method call.
|
|
func (u *URI) Hash() []byte {
|
|
return u.hash
|
|
}
|
|
|
|
// SetHash sets URI hash.
|
|
func (u *URI) SetHash(hash string) {
|
|
u.hash = append(u.hash[:0], hash...)
|
|
}
|
|
|
|
// SetHashBytes sets URI hash.
|
|
func (u *URI) SetHashBytes(hash []byte) {
|
|
u.hash = append(u.hash[:0], hash...)
|
|
}
|
|
|
|
// QueryString returns URI query string,
|
|
// i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .
|
|
//
|
|
// The returned value is valid until the next URI method call.
|
|
func (u *URI) QueryString() []byte {
|
|
return u.queryString
|
|
}
|
|
|
|
// SetQueryString sets URI query string.
|
|
func (u *URI) SetQueryString(queryString string) {
|
|
u.queryString = append(u.queryString[:0], queryString...)
|
|
u.parsedQueryArgs = false
|
|
}
|
|
|
|
// SetQueryStringBytes sets URI query string.
|
|
func (u *URI) SetQueryStringBytes(queryString []byte) {
|
|
u.queryString = append(u.queryString[:0], queryString...)
|
|
u.parsedQueryArgs = false
|
|
}
|
|
|
|
// Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .
|
|
//
|
|
// The returned path is always urldecoded and normalized,
|
|
// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.
|
|
//
|
|
// The returned value is valid until the next URI method call.
|
|
func (u *URI) Path() []byte {
|
|
path := u.path
|
|
if len(path) == 0 {
|
|
path = strSlash
|
|
}
|
|
return path
|
|
}
|
|
|
|
// SetPath sets URI path.
|
|
func (u *URI) SetPath(path string) {
|
|
u.pathOriginal = append(u.pathOriginal[:0], path...)
|
|
u.path = normalizePath(u.path, u.pathOriginal)
|
|
}
|
|
|
|
// SetPathBytes sets URI path.
|
|
func (u *URI) SetPathBytes(path []byte) {
|
|
u.pathOriginal = append(u.pathOriginal[:0], path...)
|
|
u.path = normalizePath(u.path, u.pathOriginal)
|
|
}
|
|
|
|
// PathOriginal returns the original path from requestURI passed to URI.Parse().
|
|
//
|
|
// The returned value is valid until the next URI method call.
|
|
func (u *URI) PathOriginal() []byte {
|
|
return u.pathOriginal
|
|
}
|
|
|
|
// Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .
|
|
//
|
|
// Returned scheme is always lowercased.
|
|
//
|
|
// The returned value is valid until the next URI method call.
|
|
func (u *URI) Scheme() []byte {
|
|
scheme := u.scheme
|
|
if len(scheme) == 0 {
|
|
scheme = strHTTP
|
|
}
|
|
return scheme
|
|
}
|
|
|
|
// SetScheme sets URI scheme, i.e. http, https, ftp, etc.
|
|
func (u *URI) SetScheme(scheme string) {
|
|
u.scheme = append(u.scheme[:0], scheme...)
|
|
lowercaseBytes(u.scheme)
|
|
}
|
|
|
|
// SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.
|
|
func (u *URI) SetSchemeBytes(scheme []byte) {
|
|
u.scheme = append(u.scheme[:0], scheme...)
|
|
lowercaseBytes(u.scheme)
|
|
}
|
|
|
|
// Reset clears uri.
|
|
func (u *URI) Reset() {
|
|
u.pathOriginal = u.pathOriginal[:0]
|
|
u.scheme = u.scheme[:0]
|
|
u.path = u.path[:0]
|
|
u.queryString = u.queryString[:0]
|
|
u.hash = u.hash[:0]
|
|
|
|
u.host = u.host[:0]
|
|
u.queryArgs.Reset()
|
|
u.parsedQueryArgs = false
|
|
|
|
// There is no need in u.fullURI = u.fullURI[:0], since full uri
|
|
// is calculated on each call to FullURI().
|
|
|
|
// There is no need in u.requestURI = u.requestURI[:0], since requestURI
|
|
// is calculated on each call to RequestURI().
|
|
|
|
u.h = nil
|
|
}
|
|
|
|
// Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .
|
|
//
|
|
// Host is always lowercased.
|
|
func (u *URI) Host() []byte {
|
|
if len(u.host) == 0 && u.h != nil {
|
|
u.host = append(u.host[:0], u.h.Host()...)
|
|
lowercaseBytes(u.host)
|
|
u.h = nil
|
|
}
|
|
return u.host
|
|
}
|
|
|
|
// SetHost sets host for the uri.
|
|
func (u *URI) SetHost(host string) {
|
|
u.host = append(u.host[:0], host...)
|
|
lowercaseBytes(u.host)
|
|
}
|
|
|
|
// SetHostBytes sets host for the uri.
|
|
func (u *URI) SetHostBytes(host []byte) {
|
|
u.host = append(u.host[:0], host...)
|
|
lowercaseBytes(u.host)
|
|
}
|
|
|
|
// Parse initializes URI from the given host and uri.
|
|
//
|
|
// host may be nil. In this case uri must contain fully qualified uri,
|
|
// i.e. with scheme and host. http is assumed if scheme is omitted.
|
|
//
|
|
// uri may contain e.g. RequestURI without scheme and host if host is non-empty.
|
|
func (u *URI) Parse(host, uri []byte) {
|
|
u.parse(host, uri, nil)
|
|
}
|
|
|
|
func (u *URI) parseQuick(uri []byte, h *RequestHeader, isTLS bool) {
|
|
u.parse(nil, uri, h)
|
|
if isTLS {
|
|
u.scheme = append(u.scheme[:0], strHTTPS...)
|
|
}
|
|
}
|
|
|
|
func (u *URI) parse(host, uri []byte, h *RequestHeader) {
|
|
u.Reset()
|
|
u.h = h
|
|
|
|
scheme, host, uri := splitHostURI(host, uri)
|
|
u.scheme = append(u.scheme, scheme...)
|
|
lowercaseBytes(u.scheme)
|
|
u.host = append(u.host, host...)
|
|
lowercaseBytes(u.host)
|
|
|
|
b := uri
|
|
queryIndex := bytes.IndexByte(b, '?')
|
|
fragmentIndex := bytes.IndexByte(b, '#')
|
|
// Ignore query in fragment part
|
|
if fragmentIndex >= 0 && queryIndex > fragmentIndex {
|
|
queryIndex = -1
|
|
}
|
|
|
|
if queryIndex < 0 && fragmentIndex < 0 {
|
|
u.pathOriginal = append(u.pathOriginal, b...)
|
|
u.path = normalizePath(u.path, u.pathOriginal)
|
|
return
|
|
}
|
|
|
|
if queryIndex >= 0 {
|
|
// Path is everything up to the start of the query
|
|
u.pathOriginal = append(u.pathOriginal, b[:queryIndex]...)
|
|
u.path = normalizePath(u.path, u.pathOriginal)
|
|
|
|
if fragmentIndex < 0 {
|
|
u.queryString = append(u.queryString, b[queryIndex+1:]...)
|
|
} else {
|
|
u.queryString = append(u.queryString, b[queryIndex+1:fragmentIndex]...)
|
|
u.hash = append(u.hash, b[fragmentIndex+1:]...)
|
|
}
|
|
return
|
|
}
|
|
|
|
// fragmentIndex >= 0 && queryIndex < 0
|
|
// Path is up to the start of fragment
|
|
u.pathOriginal = append(u.pathOriginal, b[:fragmentIndex]...)
|
|
u.path = normalizePath(u.path, u.pathOriginal)
|
|
u.hash = append(u.hash, b[fragmentIndex+1:]...)
|
|
}
|
|
|
|
func normalizePath(dst, src []byte) []byte {
|
|
dst = dst[:0]
|
|
dst = addLeadingSlash(dst, src)
|
|
dst = decodeArgAppendNoPlus(dst, src)
|
|
|
|
// remove duplicate slashes
|
|
b := dst
|
|
bSize := len(b)
|
|
for {
|
|
n := bytes.Index(b, strSlashSlash)
|
|
if n < 0 {
|
|
break
|
|
}
|
|
b = b[n:]
|
|
copy(b, b[1:])
|
|
b = b[:len(b)-1]
|
|
bSize--
|
|
}
|
|
dst = dst[:bSize]
|
|
|
|
// 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 {
|
|
break
|
|
}
|
|
nn := bytes.LastIndexByte(b[:n], '/')
|
|
if nn < 0 {
|
|
nn = 0
|
|
}
|
|
n += len(strSlashDotDotSlash) - 1
|
|
copy(b[nn:], b[n:])
|
|
b = b[:len(b)-n+nn]
|
|
}
|
|
|
|
// remove trailing /foo/..
|
|
n := bytes.LastIndex(b, strSlashDotDot)
|
|
if n >= 0 && n+len(strSlashDotDot) == len(b) {
|
|
nn := bytes.LastIndexByte(b[:n], '/')
|
|
if nn < 0 {
|
|
return strSlash
|
|
}
|
|
b = b[:nn+1]
|
|
}
|
|
|
|
return b
|
|
}
|
|
|
|
// RequestURI returns RequestURI - i.e. URI without Scheme and Host.
|
|
func (u *URI) RequestURI() []byte {
|
|
dst := appendQuotedPath(u.requestURI[:0], u.Path())
|
|
if u.queryArgs.Len() > 0 {
|
|
dst = append(dst, '?')
|
|
dst = u.queryArgs.AppendBytes(dst)
|
|
} else if len(u.queryString) > 0 {
|
|
dst = append(dst, '?')
|
|
dst = append(dst, u.queryString...)
|
|
}
|
|
if len(u.hash) > 0 {
|
|
dst = append(dst, '#')
|
|
dst = append(dst, u.hash...)
|
|
}
|
|
u.requestURI = dst
|
|
return u.requestURI
|
|
}
|
|
|
|
// LastPathSegment returns the last part of uri path after '/'.
|
|
//
|
|
// Examples:
|
|
//
|
|
// * For /foo/bar/baz.html path returns baz.html.
|
|
// * For /foo/bar/ returns empty byte slice.
|
|
// * For /foobar.js returns foobar.js.
|
|
func (u *URI) LastPathSegment() []byte {
|
|
path := u.Path()
|
|
n := bytes.LastIndexByte(path, '/')
|
|
if n < 0 {
|
|
return path
|
|
}
|
|
return path[n+1:]
|
|
}
|
|
|
|
// Update updates uri.
|
|
//
|
|
// The following newURI types are accepted:
|
|
//
|
|
// * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
|
|
// uri is replaced by newURI.
|
|
// * Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case
|
|
// the original scheme is preserved.
|
|
// * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
|
|
// of the original uri is replaced.
|
|
// * 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.UpdateBytes(s2b(newURI))
|
|
}
|
|
|
|
// UpdateBytes updates uri.
|
|
//
|
|
// The following newURI types are accepted:
|
|
//
|
|
// * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
|
|
// uri is replaced by newURI.
|
|
// * Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case
|
|
// the original scheme is preserved.
|
|
// * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
|
|
// of the original uri is replaced.
|
|
// * Relative path, i.e. xx?yy=abc . In this case the original RequestURI
|
|
// is updated according to the new relative path.
|
|
func (u *URI) UpdateBytes(newURI []byte) {
|
|
u.requestURI = u.updateBytes(newURI, u.requestURI)
|
|
}
|
|
|
|
func (u *URI) updateBytes(newURI, buf []byte) []byte {
|
|
if len(newURI) == 0 {
|
|
return buf
|
|
}
|
|
|
|
n := bytes.Index(newURI, strSlashSlash)
|
|
if n >= 0 {
|
|
// absolute uri
|
|
var b [32]byte
|
|
schemeOriginal := b[:0]
|
|
if len(u.scheme) > 0 {
|
|
schemeOriginal = append([]byte(nil), u.scheme...)
|
|
}
|
|
u.Parse(nil, newURI)
|
|
if len(schemeOriginal) > 0 && len(u.scheme) == 0 {
|
|
u.scheme = append(u.scheme[:0], schemeOriginal...)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
if newURI[0] == '/' {
|
|
// uri without host
|
|
buf = u.appendSchemeHost(buf[:0])
|
|
buf = append(buf, newURI...)
|
|
u.Parse(nil, buf)
|
|
return buf
|
|
}
|
|
|
|
// relative path
|
|
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
|
|
}
|
|
}
|
|
|
|
// FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.
|
|
func (u *URI) FullURI() []byte {
|
|
u.fullURI = u.AppendBytes(u.fullURI[:0])
|
|
return u.fullURI
|
|
}
|
|
|
|
// AppendBytes appends full uri to dst and returns the extended dst.
|
|
func (u *URI) AppendBytes(dst []byte) []byte {
|
|
dst = u.appendSchemeHost(dst)
|
|
return append(dst, u.RequestURI()...)
|
|
}
|
|
|
|
func (u *URI) appendSchemeHost(dst []byte) []byte {
|
|
dst = append(dst, u.Scheme()...)
|
|
dst = append(dst, strColonSlashSlash...)
|
|
return append(dst, u.Host()...)
|
|
}
|
|
|
|
// WriteTo writes full uri to w.
|
|
//
|
|
// WriteTo implements io.WriterTo interface.
|
|
func (u *URI) WriteTo(w io.Writer) (int64, error) {
|
|
n, err := w.Write(u.FullURI())
|
|
return int64(n), err
|
|
}
|
|
|
|
// String returns full uri.
|
|
func (u *URI) String() string {
|
|
return string(u.FullURI())
|
|
}
|
|
|
|
func splitHostURI(host, uri []byte) ([]byte, []byte, []byte) {
|
|
n := bytes.Index(uri, strSlashSlash)
|
|
if n < 0 {
|
|
return strHTTP, host, uri
|
|
}
|
|
scheme := uri[:n]
|
|
if bytes.IndexByte(scheme, '/') >= 0 {
|
|
return strHTTP, host, uri
|
|
}
|
|
if len(scheme) > 0 && scheme[len(scheme)-1] == ':' {
|
|
scheme = scheme[:len(scheme)-1]
|
|
}
|
|
n += len(strSlashSlash)
|
|
uri = uri[n:]
|
|
n = bytes.IndexByte(uri, '/')
|
|
if n < 0 {
|
|
// A hack for bogus urls like foobar.com?a=b without
|
|
// slash after host.
|
|
if n = bytes.IndexByte(uri, '?'); n >= 0 {
|
|
return scheme, uri[:n], uri[n:]
|
|
}
|
|
return scheme, uri, strSlash
|
|
}
|
|
return scheme, uri[:n], uri[n:]
|
|
}
|
|
|
|
// QueryArgs returns query args.
|
|
func (u *URI) QueryArgs() *Args {
|
|
u.parseQueryArgs()
|
|
return &u.queryArgs
|
|
}
|
|
|
|
func (u *URI) parseQueryArgs() {
|
|
if u.parsedQueryArgs {
|
|
return
|
|
}
|
|
u.queryArgs.ParseBytes(u.queryString)
|
|
u.parsedQueryArgs = true
|
|
}
|