123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909 |
- package fasthttp
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "path/filepath"
- "strconv"
- "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() any {
- 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
- // Path values are sent as-is without normalization.
- //
- // Disabled path normalization may be useful for proxying incoming requests
- // to servers that are expecting paths to be forwarded as-is.
- //
- // By default path values are normalized, i.e.
- // extra slashes are removed, special characters are encoded.
- DisablePathNormalizing bool
- fullURI []byte
- requestURI []byte
- username []byte
- password []byte
- }
- // CopyTo copies uri contents to dst.
- func (u *URI) CopyTo(dst *URI) {
- dst.Reset()
- dst.pathOriginal = append(dst.pathOriginal, u.pathOriginal...)
- dst.scheme = append(dst.scheme, u.scheme...)
- dst.path = append(dst.path, u.path...)
- dst.queryString = append(dst.queryString, u.queryString...)
- dst.hash = append(dst.hash, u.hash...)
- dst.host = append(dst.host, u.host...)
- dst.username = append(dst.username, u.username...)
- dst.password = append(dst.password, u.password...)
- u.queryArgs.CopyTo(&dst.queryArgs)
- dst.parsedQueryArgs = u.parsedQueryArgs
- dst.DisablePathNormalizing = u.DisablePathNormalizing
- // fullURI and requestURI shouldn't be copied, since they are created
- // from scratch on each FullURI() and RequestURI() call.
- }
- // Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .
- //
- // The returned bytes are 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...)
- }
- // Username returns URI username
- //
- // The returned bytes are valid until the next URI method call.
- func (u *URI) Username() []byte {
- return u.username
- }
- // SetUsername sets URI username.
- func (u *URI) SetUsername(username string) {
- u.username = append(u.username[:0], username...)
- }
- // SetUsernameBytes sets URI username.
- func (u *URI) SetUsernameBytes(username []byte) {
- u.username = append(u.username[:0], username...)
- }
- // Password returns URI password.
- //
- // The returned bytes are valid until the next URI method call.
- func (u *URI) Password() []byte {
- return u.password
- }
- // SetPassword sets URI password.
- func (u *URI) SetPassword(password string) {
- u.password = append(u.password[:0], password...)
- }
- // SetPasswordBytes sets URI password.
- func (u *URI) SetPasswordBytes(password []byte) {
- u.password = append(u.password[:0], password...)
- }
- // QueryString returns URI query string,
- // i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .
- //
- // The returned bytes are 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 bytes are 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 bytes are 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 bytes are 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)
- }
- func (u *URI) isHTTPS() bool {
- return bytes.Equal(u.scheme, strHTTPS)
- }
- func (u *URI) isHTTP() bool {
- return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)
- }
- // 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.username = u.username[:0]
- u.password = u.password[:0]
- u.host = u.host[:0]
- u.queryArgs.Reset()
- u.parsedQueryArgs = false
- u.DisablePathNormalizing = 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().
- }
- // Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .
- //
- // Host is always lowercased.
- //
- // The returned bytes are valid until the next URI method call.
- func (u *URI) Host() []byte {
- 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)
- }
- var ErrorInvalidURI = errors.New("invalid uri")
- // 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) error {
- return u.parse(host, uri, false)
- }
- func (u *URI) parse(host, uri []byte, isTLS bool) error {
- u.Reset()
- if stringContainsCTLByte(uri) {
- return ErrorInvalidURI
- }
- if len(host) == 0 || bytes.Contains(uri, strColonSlashSlash) {
- scheme, newHost, newURI := splitHostURI(host, uri)
- u.SetSchemeBytes(scheme)
- host = newHost
- uri = newURI
- }
- if isTLS {
- u.SetSchemeBytes(strHTTPS)
- }
- if n := bytes.IndexByte(host, '@'); n >= 0 {
- auth := host[:n]
- host = host[n+1:]
- if n := bytes.IndexByte(auth, ':'); n >= 0 {
- u.username = append(u.username[:0], auth[:n]...)
- u.password = append(u.password[:0], auth[n+1:]...)
- } else {
- u.username = append(u.username[:0], auth...)
- u.password = u.password[:0]
- }
- }
- u.host = append(u.host, host...)
- if parsedHost, err := parseHost(u.host); err != nil {
- return err
- } else {
- u.host = parsedHost
- }
- 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 nil
- }
- 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 nil
- }
- // 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:]...)
- return nil
- }
- // parseHost parses host as an authority without user
- // information. That is, as host[:port].
- //
- // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L619
- //
- // The host is parsed and unescaped in place overwriting the contents of the host parameter.
- func parseHost(host []byte) ([]byte, error) {
- if len(host) > 0 && host[0] == '[' {
- // Parse an IP-Literal in RFC 3986 and RFC 6874.
- // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
- i := bytes.LastIndexByte(host, ']')
- if i < 0 {
- return nil, errors.New("missing ']' in host")
- }
- colonPort := host[i+1:]
- if !validOptionalPort(colonPort) {
- return nil, fmt.Errorf("invalid port %q after host", colonPort)
- }
- // RFC 6874 defines that %25 (%-encoded percent) introduces
- // the zone identifier, and the zone identifier can use basically
- // any %-encoding it likes. That's different from the host, which
- // can only %-encode non-ASCII bytes.
- // We do impose some restrictions on the zone, to avoid stupidity
- // like newlines.
- zone := bytes.Index(host[:i], []byte("%25"))
- if zone >= 0 {
- host1, err := unescape(host[:zone], encodeHost)
- if err != nil {
- return nil, err
- }
- host2, err := unescape(host[zone:i], encodeZone)
- if err != nil {
- return nil, err
- }
- host3, err := unescape(host[i:], encodeHost)
- if err != nil {
- return nil, err
- }
- return append(host1, append(host2, host3...)...), nil
- }
- } else if i := bytes.LastIndexByte(host, ':'); i != -1 {
- colonPort := host[i:]
- if !validOptionalPort(colonPort) {
- return nil, fmt.Errorf("invalid port %q after host", colonPort)
- }
- }
- var err error
- if host, err = unescape(host, encodeHost); err != nil {
- return nil, err
- }
- return host, nil
- }
- type encoding int
- const (
- encodeHost encoding = 1 + iota
- encodeZone
- )
- type EscapeError string
- func (e EscapeError) Error() string {
- return "invalid URL escape " + strconv.Quote(string(e))
- }
- type InvalidHostError string
- func (e InvalidHostError) Error() string {
- return "invalid character " + strconv.Quote(string(e)) + " in host name"
- }
- // unescape unescapes a string; the mode specifies
- // which section of the URL string is being unescaped.
- //
- // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L199
- //
- // Unescapes in place overwriting the contents of s and returning it.
- func unescape(s []byte, mode encoding) ([]byte, error) {
- // Count %, check that they're well-formed.
- n := 0
- for i := 0; i < len(s); {
- switch s[i] {
- case '%':
- n++
- if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
- s = s[i:]
- if len(s) > 3 {
- s = s[:3]
- }
- return nil, EscapeError(s)
- }
- // Per https://tools.ietf.org/html/rfc3986#page-21
- // in the host component %-encoding can only be used
- // for non-ASCII bytes.
- // But https://tools.ietf.org/html/rfc6874#section-2
- // introduces %25 being allowed to escape a percent sign
- // in IPv6 scoped-address literals. Yay.
- if mode == encodeHost && unhex(s[i+1]) < 8 && !bytes.Equal(s[i:i+3], []byte("%25")) {
- return nil, EscapeError(s[i : i+3])
- }
- if mode == encodeZone {
- // RFC 6874 says basically "anything goes" for zone identifiers
- // and that even non-ASCII can be redundantly escaped,
- // but it seems prudent to restrict %-escaped bytes here to those
- // that are valid host name bytes in their unescaped form.
- // That is, you can use escaping in the zone identifier but not
- // to introduce bytes you couldn't just write directly.
- // But Windows puts spaces here! Yay.
- v := unhex(s[i+1])<<4 | unhex(s[i+2])
- if !bytes.Equal(s[i:i+3], []byte("%25")) && v != ' ' && shouldEscape(v, encodeHost) {
- return nil, EscapeError(s[i : i+3])
- }
- }
- i += 3
- default:
- if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
- return nil, InvalidHostError(s[i : i+1])
- }
- i++
- }
- }
- if n == 0 {
- return s, nil
- }
- t := s[:0]
- for i := 0; i < len(s); i++ {
- switch s[i] {
- case '%':
- t = append(t, unhex(s[i+1])<<4|unhex(s[i+2]))
- i += 2
- default:
- t = append(t, s[i])
- }
- }
- return t, nil
- }
- // Return true if the specified character should be escaped when
- // appearing in a URL string, according to RFC 3986.
- //
- // Please be informed that for now shouldEscape does not check all
- // reserved characters correctly. See https://github.com/golang/go/issues/5684.
- //
- // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100
- func shouldEscape(c byte, mode encoding) bool {
- // §2.3 Unreserved characters (alphanum)
- if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
- return false
- }
- if mode == encodeHost || mode == encodeZone {
- // §3.2.2 Host allows
- // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
- // as part of reg-name.
- // We add : because we include :port as part of host.
- // We add [ ] because we include [ipv6]:port as part of host.
- // We add < > because they're the only characters left that
- // we could possibly allow, and Parse will reject them if we
- // escape them (because hosts can't use %-encoding for
- // ASCII bytes).
- switch c {
- case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
- return false
- }
- }
- if c == '-' || c == '_' || c == '.' || c == '~' { // §2.3 Unreserved characters (mark)
- return false
- }
- // Everything else must be escaped.
- return true
- }
- func ishex(c byte) bool {
- return ('0' <= c && c <= '9') ||
- ('a' <= c && c <= 'f') ||
- ('A' <= c && c <= 'F')
- }
- func unhex(c byte) byte {
- switch {
- case '0' <= c && c <= '9':
- return c - '0'
- case 'a' <= c && c <= 'f':
- return c - 'a' + 10
- case 'A' <= c && c <= 'F':
- return c - 'A' + 10
- }
- return 0
- }
- // validOptionalPort reports whether port is either an empty string
- // or matches /^:\d*$/.
- func validOptionalPort(port []byte) bool {
- if len(port) == 0 {
- return true
- }
- if port[0] != ':' {
- return false
- }
- for _, b := range port[1:] {
- if b < '0' || b > '9' {
- return false
- }
- }
- return true
- }
- 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 append(dst[:0], strSlash...)
- }
- b = b[:nn+1]
- }
- if filepath.Separator == '\\' {
- // remove \.\ parts
- for {
- n := bytes.Index(b, strBackSlashDotBackSlash)
- 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, strSlashDotDotBackSlash)
- if n < 0 {
- break
- }
- nn := bytes.LastIndexByte(b[:n], '/')
- if nn < 0 {
- nn = 0
- }
- nn++
- n += len(strSlashDotDotBackSlash)
- copy(b[nn:], b[n:])
- b = b[:len(b)-n+nn]
- }
- // remove /foo\..\ parts
- for {
- n := bytes.Index(b, strBackSlashDotDotBackSlash)
- if n < 0 {
- break
- }
- nn := bytes.LastIndexByte(b[:n], '/')
- if nn < 0 {
- nn = 0
- }
- n += len(strBackSlashDotDotBackSlash) - 1
- copy(b[nn:], b[n:])
- b = b[:len(b)-n+nn]
- }
- // remove trailing \foo\..
- n := bytes.LastIndex(b, strBackSlashDotDot)
- if n >= 0 && n+len(strSlashDotDot) == len(b) {
- nn := bytes.LastIndexByte(b[:n], '/')
- if nn < 0 {
- return append(dst[:0], strSlash...)
- }
- b = b[:nn+1]
- }
- }
- return b
- }
- // RequestURI returns RequestURI - i.e. URI without Scheme and Host.
- func (u *URI) RequestURI() []byte {
- var dst []byte
- if u.DisablePathNormalizing {
- dst = u.requestURI[:0]
- dst = append(dst, u.PathOriginal()...)
- } else {
- dst = appendQuotedPath(u.requestURI[:0], u.Path())
- }
- if u.parsedQueryArgs && 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...)
- }
- 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.
- //
- // The returned bytes are valid until the next URI method call.
- 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...)
- }
- if err := u.Parse(nil, newURI); err != nil {
- return nil
- }
- 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...)
- if err := u.Parse(nil, buf); err != nil {
- return nil
- }
- 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(fmt.Sprintf("BUG: path must contain at least one slash: %q %q", u.Path(), newURI))
- }
- buf = u.appendSchemeHost(buf[:0])
- buf = appendQuotedPath(buf, path[:n+1])
- buf = append(buf, newURI...)
- if err := u.Parse(nil, buf); err != nil {
- return nil
- }
- return buf
- }
- }
- // FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.
- //
- // The returned bytes are valid until the next URI method call.
- 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)
- dst = append(dst, u.RequestURI()...)
- if len(u.hash) > 0 {
- dst = append(dst, '#')
- dst = append(dst, u.hash...)
- }
- return dst
- }
- 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, '/')
- nq := bytes.IndexByte(uri, '?')
- if nq >= 0 && nq < n {
- // A hack for urls like foobar.com?a=b/xyz
- n = nq
- } else if n < 0 {
- // A hack for bogus urls like foobar.com?a=b without
- // slash after host.
- if nq >= 0 {
- return scheme, uri[:nq], uri[nq:]
- }
- return scheme, uri, strSlash
- }
- return scheme, uri[:n], uri[n:]
- }
- // QueryArgs returns query args.
- //
- // The returned args are valid until the next URI method call.
- 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
- }
- // stringContainsCTLByte reports whether s contains any ASCII control character.
- func stringContainsCTLByte(s []byte) bool {
- for i := 0; i < len(s); i++ {
- b := s[i]
- if b < ' ' || b == 0x7f {
- return true
- }
- }
- return false
- }
|