[145] | 1 | package fasthttp
|
---|
| 2 |
|
---|
| 3 | import (
|
---|
| 4 | "bytes"
|
---|
| 5 | "errors"
|
---|
| 6 | "fmt"
|
---|
| 7 | "io"
|
---|
| 8 | "path/filepath"
|
---|
| 9 | "strconv"
|
---|
| 10 | "sync"
|
---|
| 11 | )
|
---|
| 12 |
|
---|
| 13 | // AcquireURI returns an empty URI instance from the pool.
|
---|
| 14 | //
|
---|
| 15 | // Release the URI with ReleaseURI after the URI is no longer needed.
|
---|
| 16 | // This allows reducing GC load.
|
---|
| 17 | func AcquireURI() *URI {
|
---|
| 18 | return uriPool.Get().(*URI)
|
---|
| 19 | }
|
---|
| 20 |
|
---|
| 21 | // ReleaseURI releases the URI acquired via AcquireURI.
|
---|
| 22 | //
|
---|
| 23 | // The released URI mustn't be used after releasing it, otherwise data races
|
---|
| 24 | // may occur.
|
---|
| 25 | func ReleaseURI(u *URI) {
|
---|
| 26 | u.Reset()
|
---|
| 27 | uriPool.Put(u)
|
---|
| 28 | }
|
---|
| 29 |
|
---|
| 30 | var uriPool = &sync.Pool{
|
---|
| 31 | New: func() interface{} {
|
---|
| 32 | return &URI{}
|
---|
| 33 | },
|
---|
| 34 | }
|
---|
| 35 |
|
---|
| 36 | // URI represents URI :) .
|
---|
| 37 | //
|
---|
| 38 | // It is forbidden copying URI instances. Create new instance and use CopyTo
|
---|
| 39 | // instead.
|
---|
| 40 | //
|
---|
| 41 | // URI instance MUST NOT be used from concurrently running goroutines.
|
---|
| 42 | type URI struct {
|
---|
| 43 | noCopy noCopy //nolint:unused,structcheck
|
---|
| 44 |
|
---|
| 45 | pathOriginal []byte
|
---|
| 46 | scheme []byte
|
---|
| 47 | path []byte
|
---|
| 48 | queryString []byte
|
---|
| 49 | hash []byte
|
---|
| 50 | host []byte
|
---|
| 51 |
|
---|
| 52 | queryArgs Args
|
---|
| 53 | parsedQueryArgs bool
|
---|
| 54 |
|
---|
| 55 | // Path values are sent as-is without normalization
|
---|
| 56 | //
|
---|
| 57 | // Disabled path normalization may be useful for proxying incoming requests
|
---|
| 58 | // to servers that are expecting paths to be forwarded as-is.
|
---|
| 59 | //
|
---|
| 60 | // By default path values are normalized, i.e.
|
---|
| 61 | // extra slashes are removed, special characters are encoded.
|
---|
| 62 | DisablePathNormalizing bool
|
---|
| 63 |
|
---|
| 64 | fullURI []byte
|
---|
| 65 | requestURI []byte
|
---|
| 66 |
|
---|
| 67 | username []byte
|
---|
| 68 | password []byte
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | // CopyTo copies uri contents to dst.
|
---|
| 72 | func (u *URI) CopyTo(dst *URI) {
|
---|
| 73 | dst.Reset()
|
---|
| 74 | dst.pathOriginal = append(dst.pathOriginal, u.pathOriginal...)
|
---|
| 75 | dst.scheme = append(dst.scheme, u.scheme...)
|
---|
| 76 | dst.path = append(dst.path, u.path...)
|
---|
| 77 | dst.queryString = append(dst.queryString, u.queryString...)
|
---|
| 78 | dst.hash = append(dst.hash, u.hash...)
|
---|
| 79 | dst.host = append(dst.host, u.host...)
|
---|
| 80 | dst.username = append(dst.username, u.username...)
|
---|
| 81 | dst.password = append(dst.password, u.password...)
|
---|
| 82 |
|
---|
| 83 | u.queryArgs.CopyTo(&dst.queryArgs)
|
---|
| 84 | dst.parsedQueryArgs = u.parsedQueryArgs
|
---|
| 85 | dst.DisablePathNormalizing = u.DisablePathNormalizing
|
---|
| 86 |
|
---|
| 87 | // fullURI and requestURI shouldn't be copied, since they are created
|
---|
| 88 | // from scratch on each FullURI() and RequestURI() call.
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | // Hash returns URI hash, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .
|
---|
| 92 | //
|
---|
| 93 | // The returned bytes are valid until the next URI method call.
|
---|
| 94 | func (u *URI) Hash() []byte {
|
---|
| 95 | return u.hash
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | // SetHash sets URI hash.
|
---|
| 99 | func (u *URI) SetHash(hash string) {
|
---|
| 100 | u.hash = append(u.hash[:0], hash...)
|
---|
| 101 | }
|
---|
| 102 |
|
---|
| 103 | // SetHashBytes sets URI hash.
|
---|
| 104 | func (u *URI) SetHashBytes(hash []byte) {
|
---|
| 105 | u.hash = append(u.hash[:0], hash...)
|
---|
| 106 | }
|
---|
| 107 |
|
---|
| 108 | // Username returns URI username
|
---|
| 109 | //
|
---|
| 110 | // The returned bytes are valid until the next URI method call.
|
---|
| 111 | func (u *URI) Username() []byte {
|
---|
| 112 | return u.username
|
---|
| 113 | }
|
---|
| 114 |
|
---|
| 115 | // SetUsername sets URI username.
|
---|
| 116 | func (u *URI) SetUsername(username string) {
|
---|
| 117 | u.username = append(u.username[:0], username...)
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | // SetUsernameBytes sets URI username.
|
---|
| 121 | func (u *URI) SetUsernameBytes(username []byte) {
|
---|
| 122 | u.username = append(u.username[:0], username...)
|
---|
| 123 | }
|
---|
| 124 |
|
---|
| 125 | // Password returns URI password
|
---|
| 126 | //
|
---|
| 127 | // The returned bytes are valid until the next URI method call.
|
---|
| 128 | func (u *URI) Password() []byte {
|
---|
| 129 | return u.password
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | // SetPassword sets URI password.
|
---|
| 133 | func (u *URI) SetPassword(password string) {
|
---|
| 134 | u.password = append(u.password[:0], password...)
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | // SetPasswordBytes sets URI password.
|
---|
| 138 | func (u *URI) SetPasswordBytes(password []byte) {
|
---|
| 139 | u.password = append(u.password[:0], password...)
|
---|
| 140 | }
|
---|
| 141 |
|
---|
| 142 | // QueryString returns URI query string,
|
---|
| 143 | // i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .
|
---|
| 144 | //
|
---|
| 145 | // The returned bytes are valid until the next URI method call.
|
---|
| 146 | func (u *URI) QueryString() []byte {
|
---|
| 147 | return u.queryString
|
---|
| 148 | }
|
---|
| 149 |
|
---|
| 150 | // SetQueryString sets URI query string.
|
---|
| 151 | func (u *URI) SetQueryString(queryString string) {
|
---|
| 152 | u.queryString = append(u.queryString[:0], queryString...)
|
---|
| 153 | u.parsedQueryArgs = false
|
---|
| 154 | }
|
---|
| 155 |
|
---|
| 156 | // SetQueryStringBytes sets URI query string.
|
---|
| 157 | func (u *URI) SetQueryStringBytes(queryString []byte) {
|
---|
| 158 | u.queryString = append(u.queryString[:0], queryString...)
|
---|
| 159 | u.parsedQueryArgs = false
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | // Path returns URI path, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .
|
---|
| 163 | //
|
---|
| 164 | // The returned path is always urldecoded and normalized,
|
---|
| 165 | // i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.
|
---|
| 166 | //
|
---|
| 167 | // The returned bytes are valid until the next URI method call.
|
---|
| 168 | func (u *URI) Path() []byte {
|
---|
| 169 | path := u.path
|
---|
| 170 | if len(path) == 0 {
|
---|
| 171 | path = strSlash
|
---|
| 172 | }
|
---|
| 173 | return path
|
---|
| 174 | }
|
---|
| 175 |
|
---|
| 176 | // SetPath sets URI path.
|
---|
| 177 | func (u *URI) SetPath(path string) {
|
---|
| 178 | u.pathOriginal = append(u.pathOriginal[:0], path...)
|
---|
| 179 | u.path = normalizePath(u.path, u.pathOriginal)
|
---|
| 180 | }
|
---|
| 181 |
|
---|
| 182 | // SetPathBytes sets URI path.
|
---|
| 183 | func (u *URI) SetPathBytes(path []byte) {
|
---|
| 184 | u.pathOriginal = append(u.pathOriginal[:0], path...)
|
---|
| 185 | u.path = normalizePath(u.path, u.pathOriginal)
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | // PathOriginal returns the original path from requestURI passed to URI.Parse().
|
---|
| 189 | //
|
---|
| 190 | // The returned bytes are valid until the next URI method call.
|
---|
| 191 | func (u *URI) PathOriginal() []byte {
|
---|
| 192 | return u.pathOriginal
|
---|
| 193 | }
|
---|
| 194 |
|
---|
| 195 | // Scheme returns URI scheme, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .
|
---|
| 196 | //
|
---|
| 197 | // Returned scheme is always lowercased.
|
---|
| 198 | //
|
---|
| 199 | // The returned bytes are valid until the next URI method call.
|
---|
| 200 | func (u *URI) Scheme() []byte {
|
---|
| 201 | scheme := u.scheme
|
---|
| 202 | if len(scheme) == 0 {
|
---|
| 203 | scheme = strHTTP
|
---|
| 204 | }
|
---|
| 205 | return scheme
|
---|
| 206 | }
|
---|
| 207 |
|
---|
| 208 | // SetScheme sets URI scheme, i.e. http, https, ftp, etc.
|
---|
| 209 | func (u *URI) SetScheme(scheme string) {
|
---|
| 210 | u.scheme = append(u.scheme[:0], scheme...)
|
---|
| 211 | lowercaseBytes(u.scheme)
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | // SetSchemeBytes sets URI scheme, i.e. http, https, ftp, etc.
|
---|
| 215 | func (u *URI) SetSchemeBytes(scheme []byte) {
|
---|
| 216 | u.scheme = append(u.scheme[:0], scheme...)
|
---|
| 217 | lowercaseBytes(u.scheme)
|
---|
| 218 | }
|
---|
| 219 |
|
---|
| 220 | func (u *URI) isHttps() bool {
|
---|
| 221 | return bytes.Equal(u.scheme, strHTTPS)
|
---|
| 222 | }
|
---|
| 223 |
|
---|
| 224 | func (u *URI) isHttp() bool {
|
---|
| 225 | return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)
|
---|
| 226 | }
|
---|
| 227 |
|
---|
| 228 | // Reset clears uri.
|
---|
| 229 | func (u *URI) Reset() {
|
---|
| 230 | u.pathOriginal = u.pathOriginal[:0]
|
---|
| 231 | u.scheme = u.scheme[:0]
|
---|
| 232 | u.path = u.path[:0]
|
---|
| 233 | u.queryString = u.queryString[:0]
|
---|
| 234 | u.hash = u.hash[:0]
|
---|
| 235 | u.username = u.username[:0]
|
---|
| 236 | u.password = u.password[:0]
|
---|
| 237 |
|
---|
| 238 | u.host = u.host[:0]
|
---|
| 239 | u.queryArgs.Reset()
|
---|
| 240 | u.parsedQueryArgs = false
|
---|
| 241 | u.DisablePathNormalizing = false
|
---|
| 242 |
|
---|
| 243 | // There is no need in u.fullURI = u.fullURI[:0], since full uri
|
---|
| 244 | // is calculated on each call to FullURI().
|
---|
| 245 |
|
---|
| 246 | // There is no need in u.requestURI = u.requestURI[:0], since requestURI
|
---|
| 247 | // is calculated on each call to RequestURI().
|
---|
| 248 | }
|
---|
| 249 |
|
---|
| 250 | // Host returns host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .
|
---|
| 251 | //
|
---|
| 252 | // Host is always lowercased.
|
---|
| 253 | //
|
---|
| 254 | // The returned bytes are valid until the next URI method call.
|
---|
| 255 | func (u *URI) Host() []byte {
|
---|
| 256 | return u.host
|
---|
| 257 | }
|
---|
| 258 |
|
---|
| 259 | // SetHost sets host for the uri.
|
---|
| 260 | func (u *URI) SetHost(host string) {
|
---|
| 261 | u.host = append(u.host[:0], host...)
|
---|
| 262 | lowercaseBytes(u.host)
|
---|
| 263 | }
|
---|
| 264 |
|
---|
| 265 | // SetHostBytes sets host for the uri.
|
---|
| 266 | func (u *URI) SetHostBytes(host []byte) {
|
---|
| 267 | u.host = append(u.host[:0], host...)
|
---|
| 268 | lowercaseBytes(u.host)
|
---|
| 269 | }
|
---|
| 270 |
|
---|
| 271 | var (
|
---|
| 272 | ErrorInvalidURI = errors.New("invalid uri")
|
---|
| 273 | )
|
---|
| 274 |
|
---|
| 275 | // Parse initializes URI from the given host and uri.
|
---|
| 276 | //
|
---|
| 277 | // host may be nil. In this case uri must contain fully qualified uri,
|
---|
| 278 | // i.e. with scheme and host. http is assumed if scheme is omitted.
|
---|
| 279 | //
|
---|
| 280 | // uri may contain e.g. RequestURI without scheme and host if host is non-empty.
|
---|
| 281 | func (u *URI) Parse(host, uri []byte) error {
|
---|
| 282 | return u.parse(host, uri, false)
|
---|
| 283 | }
|
---|
| 284 |
|
---|
| 285 | func (u *URI) parse(host, uri []byte, isTLS bool) error {
|
---|
| 286 | u.Reset()
|
---|
| 287 |
|
---|
| 288 | if stringContainsCTLByte(uri) {
|
---|
| 289 | return ErrorInvalidURI
|
---|
| 290 | }
|
---|
| 291 |
|
---|
| 292 | if len(host) == 0 || bytes.Contains(uri, strColonSlashSlash) {
|
---|
| 293 | scheme, newHost, newURI := splitHostURI(host, uri)
|
---|
| 294 | u.SetSchemeBytes(scheme)
|
---|
| 295 | host = newHost
|
---|
| 296 | uri = newURI
|
---|
| 297 | }
|
---|
| 298 |
|
---|
| 299 | if isTLS {
|
---|
| 300 | u.SetSchemeBytes(strHTTPS)
|
---|
| 301 | }
|
---|
| 302 |
|
---|
| 303 | if n := bytes.IndexByte(host, '@'); n >= 0 {
|
---|
| 304 | auth := host[:n]
|
---|
| 305 | host = host[n+1:]
|
---|
| 306 |
|
---|
| 307 | if n := bytes.IndexByte(auth, ':'); n >= 0 {
|
---|
| 308 | u.username = append(u.username[:0], auth[:n]...)
|
---|
| 309 | u.password = append(u.password[:0], auth[n+1:]...)
|
---|
| 310 | } else {
|
---|
| 311 | u.username = append(u.username[:0], auth...)
|
---|
| 312 | u.password = u.password[:0]
|
---|
| 313 | }
|
---|
| 314 | }
|
---|
| 315 |
|
---|
| 316 | u.host = append(u.host, host...)
|
---|
| 317 | if parsedHost, err := parseHost(u.host); err != nil {
|
---|
| 318 | return err
|
---|
| 319 | } else {
|
---|
| 320 | u.host = parsedHost
|
---|
| 321 | }
|
---|
| 322 | lowercaseBytes(u.host)
|
---|
| 323 |
|
---|
| 324 | b := uri
|
---|
| 325 | queryIndex := bytes.IndexByte(b, '?')
|
---|
| 326 | fragmentIndex := bytes.IndexByte(b, '#')
|
---|
| 327 | // Ignore query in fragment part
|
---|
| 328 | if fragmentIndex >= 0 && queryIndex > fragmentIndex {
|
---|
| 329 | queryIndex = -1
|
---|
| 330 | }
|
---|
| 331 |
|
---|
| 332 | if queryIndex < 0 && fragmentIndex < 0 {
|
---|
| 333 | u.pathOriginal = append(u.pathOriginal, b...)
|
---|
| 334 | u.path = normalizePath(u.path, u.pathOriginal)
|
---|
| 335 | return nil
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | if queryIndex >= 0 {
|
---|
| 339 | // Path is everything up to the start of the query
|
---|
| 340 | u.pathOriginal = append(u.pathOriginal, b[:queryIndex]...)
|
---|
| 341 | u.path = normalizePath(u.path, u.pathOriginal)
|
---|
| 342 |
|
---|
| 343 | if fragmentIndex < 0 {
|
---|
| 344 | u.queryString = append(u.queryString, b[queryIndex+1:]...)
|
---|
| 345 | } else {
|
---|
| 346 | u.queryString = append(u.queryString, b[queryIndex+1:fragmentIndex]...)
|
---|
| 347 | u.hash = append(u.hash, b[fragmentIndex+1:]...)
|
---|
| 348 | }
|
---|
| 349 | return nil
|
---|
| 350 | }
|
---|
| 351 |
|
---|
| 352 | // fragmentIndex >= 0 && queryIndex < 0
|
---|
| 353 | // Path is up to the start of fragment
|
---|
| 354 | u.pathOriginal = append(u.pathOriginal, b[:fragmentIndex]...)
|
---|
| 355 | u.path = normalizePath(u.path, u.pathOriginal)
|
---|
| 356 | u.hash = append(u.hash, b[fragmentIndex+1:]...)
|
---|
| 357 |
|
---|
| 358 | return nil
|
---|
| 359 | }
|
---|
| 360 |
|
---|
| 361 | // parseHost parses host as an authority without user
|
---|
| 362 | // information. That is, as host[:port].
|
---|
| 363 | //
|
---|
| 364 | // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L619
|
---|
| 365 | //
|
---|
| 366 | // The host is parsed and unescaped in place overwriting the contents of the host parameter.
|
---|
| 367 | func parseHost(host []byte) ([]byte, error) {
|
---|
| 368 | if len(host) > 0 && host[0] == '[' {
|
---|
| 369 | // Parse an IP-Literal in RFC 3986 and RFC 6874.
|
---|
| 370 | // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
|
---|
| 371 | i := bytes.LastIndexByte(host, ']')
|
---|
| 372 | if i < 0 {
|
---|
| 373 | return nil, errors.New("missing ']' in host")
|
---|
| 374 | }
|
---|
| 375 | colonPort := host[i+1:]
|
---|
| 376 | if !validOptionalPort(colonPort) {
|
---|
| 377 | return nil, fmt.Errorf("invalid port %q after host", colonPort)
|
---|
| 378 | }
|
---|
| 379 |
|
---|
| 380 | // RFC 6874 defines that %25 (%-encoded percent) introduces
|
---|
| 381 | // the zone identifier, and the zone identifier can use basically
|
---|
| 382 | // any %-encoding it likes. That's different from the host, which
|
---|
| 383 | // can only %-encode non-ASCII bytes.
|
---|
| 384 | // We do impose some restrictions on the zone, to avoid stupidity
|
---|
| 385 | // like newlines.
|
---|
| 386 | zone := bytes.Index(host[:i], []byte("%25"))
|
---|
| 387 | if zone >= 0 {
|
---|
| 388 | host1, err := unescape(host[:zone], encodeHost)
|
---|
| 389 | if err != nil {
|
---|
| 390 | return nil, err
|
---|
| 391 | }
|
---|
| 392 | host2, err := unescape(host[zone:i], encodeZone)
|
---|
| 393 | if err != nil {
|
---|
| 394 | return nil, err
|
---|
| 395 | }
|
---|
| 396 | host3, err := unescape(host[i:], encodeHost)
|
---|
| 397 | if err != nil {
|
---|
| 398 | return nil, err
|
---|
| 399 | }
|
---|
| 400 | return append(host1, append(host2, host3...)...), nil
|
---|
| 401 | }
|
---|
| 402 | } else if i := bytes.LastIndexByte(host, ':'); i != -1 {
|
---|
| 403 | colonPort := host[i:]
|
---|
| 404 | if !validOptionalPort(colonPort) {
|
---|
| 405 | return nil, fmt.Errorf("invalid port %q after host", colonPort)
|
---|
| 406 | }
|
---|
| 407 | }
|
---|
| 408 |
|
---|
| 409 | var err error
|
---|
| 410 | if host, err = unescape(host, encodeHost); err != nil {
|
---|
| 411 | return nil, err
|
---|
| 412 | }
|
---|
| 413 | return host, nil
|
---|
| 414 | }
|
---|
| 415 |
|
---|
| 416 | type encoding int
|
---|
| 417 |
|
---|
| 418 | const (
|
---|
| 419 | encodeHost encoding = 1 + iota
|
---|
| 420 | encodeZone
|
---|
| 421 | )
|
---|
| 422 |
|
---|
| 423 | type EscapeError string
|
---|
| 424 |
|
---|
| 425 | func (e EscapeError) Error() string {
|
---|
| 426 | return "invalid URL escape " + strconv.Quote(string(e))
|
---|
| 427 | }
|
---|
| 428 |
|
---|
| 429 | type InvalidHostError string
|
---|
| 430 |
|
---|
| 431 | func (e InvalidHostError) Error() string {
|
---|
| 432 | return "invalid character " + strconv.Quote(string(e)) + " in host name"
|
---|
| 433 | }
|
---|
| 434 |
|
---|
| 435 | // unescape unescapes a string; the mode specifies
|
---|
| 436 | // which section of the URL string is being unescaped.
|
---|
| 437 | //
|
---|
| 438 | // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L199
|
---|
| 439 | //
|
---|
| 440 | // Unescapes in place overwriting the contents of s and returning it.
|
---|
| 441 | func unescape(s []byte, mode encoding) ([]byte, error) {
|
---|
| 442 | // Count %, check that they're well-formed.
|
---|
| 443 | n := 0
|
---|
| 444 | for i := 0; i < len(s); {
|
---|
| 445 | switch s[i] {
|
---|
| 446 | case '%':
|
---|
| 447 | n++
|
---|
| 448 | if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
---|
| 449 | s = s[i:]
|
---|
| 450 | if len(s) > 3 {
|
---|
| 451 | s = s[:3]
|
---|
| 452 | }
|
---|
| 453 | return nil, EscapeError(s)
|
---|
| 454 | }
|
---|
| 455 | // Per https://tools.ietf.org/html/rfc3986#page-21
|
---|
| 456 | // in the host component %-encoding can only be used
|
---|
| 457 | // for non-ASCII bytes.
|
---|
| 458 | // But https://tools.ietf.org/html/rfc6874#section-2
|
---|
| 459 | // introduces %25 being allowed to escape a percent sign
|
---|
| 460 | // in IPv6 scoped-address literals. Yay.
|
---|
| 461 | if mode == encodeHost && unhex(s[i+1]) < 8 && !bytes.Equal(s[i:i+3], []byte("%25")) {
|
---|
| 462 | return nil, EscapeError(s[i : i+3])
|
---|
| 463 | }
|
---|
| 464 | if mode == encodeZone {
|
---|
| 465 | // RFC 6874 says basically "anything goes" for zone identifiers
|
---|
| 466 | // and that even non-ASCII can be redundantly escaped,
|
---|
| 467 | // but it seems prudent to restrict %-escaped bytes here to those
|
---|
| 468 | // that are valid host name bytes in their unescaped form.
|
---|
| 469 | // That is, you can use escaping in the zone identifier but not
|
---|
| 470 | // to introduce bytes you couldn't just write directly.
|
---|
| 471 | // But Windows puts spaces here! Yay.
|
---|
| 472 | v := unhex(s[i+1])<<4 | unhex(s[i+2])
|
---|
| 473 | if !bytes.Equal(s[i:i+3], []byte("%25")) && v != ' ' && shouldEscape(v, encodeHost) {
|
---|
| 474 | return nil, EscapeError(s[i : i+3])
|
---|
| 475 | }
|
---|
| 476 | }
|
---|
| 477 | i += 3
|
---|
| 478 | default:
|
---|
| 479 | if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) {
|
---|
| 480 | return nil, InvalidHostError(s[i : i+1])
|
---|
| 481 | }
|
---|
| 482 | i++
|
---|
| 483 | }
|
---|
| 484 | }
|
---|
| 485 |
|
---|
| 486 | if n == 0 {
|
---|
| 487 | return s, nil
|
---|
| 488 | }
|
---|
| 489 |
|
---|
| 490 | t := s[:0]
|
---|
| 491 | for i := 0; i < len(s); i++ {
|
---|
| 492 | switch s[i] {
|
---|
| 493 | case '%':
|
---|
| 494 | t = append(t, unhex(s[i+1])<<4|unhex(s[i+2]))
|
---|
| 495 | i += 2
|
---|
| 496 | default:
|
---|
| 497 | t = append(t, s[i])
|
---|
| 498 | }
|
---|
| 499 | }
|
---|
| 500 | return t, nil
|
---|
| 501 | }
|
---|
| 502 |
|
---|
| 503 | // Return true if the specified character should be escaped when
|
---|
| 504 | // appearing in a URL string, according to RFC 3986.
|
---|
| 505 | //
|
---|
| 506 | // Please be informed that for now shouldEscape does not check all
|
---|
| 507 | // reserved characters correctly. See golang.org/issue/5684.
|
---|
| 508 | //
|
---|
| 509 | // Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100
|
---|
| 510 | func shouldEscape(c byte, mode encoding) bool {
|
---|
| 511 | // §2.3 Unreserved characters (alphanum)
|
---|
| 512 | if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
|
---|
| 513 | return false
|
---|
| 514 | }
|
---|
| 515 |
|
---|
| 516 | if mode == encodeHost || mode == encodeZone {
|
---|
| 517 | // §3.2.2 Host allows
|
---|
| 518 | // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
---|
| 519 | // as part of reg-name.
|
---|
| 520 | // We add : because we include :port as part of host.
|
---|
| 521 | // We add [ ] because we include [ipv6]:port as part of host.
|
---|
| 522 | // We add < > because they're the only characters left that
|
---|
| 523 | // we could possibly allow, and Parse will reject them if we
|
---|
| 524 | // escape them (because hosts can't use %-encoding for
|
---|
| 525 | // ASCII bytes).
|
---|
| 526 | switch c {
|
---|
| 527 | case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
|
---|
| 528 | return false
|
---|
| 529 | }
|
---|
| 530 | }
|
---|
| 531 |
|
---|
| 532 | if c == '-' || c == '_' || c == '.' || c == '~' { // §2.3 Unreserved characters (mark)
|
---|
| 533 | return false
|
---|
| 534 | }
|
---|
| 535 |
|
---|
| 536 | // Everything else must be escaped.
|
---|
| 537 | return true
|
---|
| 538 | }
|
---|
| 539 |
|
---|
| 540 | func ishex(c byte) bool {
|
---|
| 541 | switch {
|
---|
| 542 | case '0' <= c && c <= '9':
|
---|
| 543 | return true
|
---|
| 544 | case 'a' <= c && c <= 'f':
|
---|
| 545 | return true
|
---|
| 546 | case 'A' <= c && c <= 'F':
|
---|
| 547 | return true
|
---|
| 548 | }
|
---|
| 549 | return false
|
---|
| 550 | }
|
---|
| 551 |
|
---|
| 552 | func unhex(c byte) byte {
|
---|
| 553 | switch {
|
---|
| 554 | case '0' <= c && c <= '9':
|
---|
| 555 | return c - '0'
|
---|
| 556 | case 'a' <= c && c <= 'f':
|
---|
| 557 | return c - 'a' + 10
|
---|
| 558 | case 'A' <= c && c <= 'F':
|
---|
| 559 | return c - 'A' + 10
|
---|
| 560 | }
|
---|
| 561 | return 0
|
---|
| 562 | }
|
---|
| 563 |
|
---|
| 564 | // validOptionalPort reports whether port is either an empty string
|
---|
| 565 | // or matches /^:\d*$/
|
---|
| 566 | func validOptionalPort(port []byte) bool {
|
---|
| 567 | if len(port) == 0 {
|
---|
| 568 | return true
|
---|
| 569 | }
|
---|
| 570 | if port[0] != ':' {
|
---|
| 571 | return false
|
---|
| 572 | }
|
---|
| 573 | for _, b := range port[1:] {
|
---|
| 574 | if b < '0' || b > '9' {
|
---|
| 575 | return false
|
---|
| 576 | }
|
---|
| 577 | }
|
---|
| 578 | return true
|
---|
| 579 | }
|
---|
| 580 |
|
---|
| 581 | func normalizePath(dst, src []byte) []byte {
|
---|
| 582 | dst = dst[:0]
|
---|
| 583 | dst = addLeadingSlash(dst, src)
|
---|
| 584 | dst = decodeArgAppendNoPlus(dst, src)
|
---|
| 585 |
|
---|
| 586 | // remove duplicate slashes
|
---|
| 587 | b := dst
|
---|
| 588 | bSize := len(b)
|
---|
| 589 | for {
|
---|
| 590 | n := bytes.Index(b, strSlashSlash)
|
---|
| 591 | if n < 0 {
|
---|
| 592 | break
|
---|
| 593 | }
|
---|
| 594 | b = b[n:]
|
---|
| 595 | copy(b, b[1:])
|
---|
| 596 | b = b[:len(b)-1]
|
---|
| 597 | bSize--
|
---|
| 598 | }
|
---|
| 599 | dst = dst[:bSize]
|
---|
| 600 |
|
---|
| 601 | // remove /./ parts
|
---|
| 602 | b = dst
|
---|
| 603 | for {
|
---|
| 604 | n := bytes.Index(b, strSlashDotSlash)
|
---|
| 605 | if n < 0 {
|
---|
| 606 | break
|
---|
| 607 | }
|
---|
| 608 | nn := n + len(strSlashDotSlash) - 1
|
---|
| 609 | copy(b[n:], b[nn:])
|
---|
| 610 | b = b[:len(b)-nn+n]
|
---|
| 611 | }
|
---|
| 612 |
|
---|
| 613 | // remove /foo/../ parts
|
---|
| 614 | for {
|
---|
| 615 | n := bytes.Index(b, strSlashDotDotSlash)
|
---|
| 616 | if n < 0 {
|
---|
| 617 | break
|
---|
| 618 | }
|
---|
| 619 | nn := bytes.LastIndexByte(b[:n], '/')
|
---|
| 620 | if nn < 0 {
|
---|
| 621 | nn = 0
|
---|
| 622 | }
|
---|
| 623 | n += len(strSlashDotDotSlash) - 1
|
---|
| 624 | copy(b[nn:], b[n:])
|
---|
| 625 | b = b[:len(b)-n+nn]
|
---|
| 626 | }
|
---|
| 627 |
|
---|
| 628 | // remove trailing /foo/..
|
---|
| 629 | n := bytes.LastIndex(b, strSlashDotDot)
|
---|
| 630 | if n >= 0 && n+len(strSlashDotDot) == len(b) {
|
---|
| 631 | nn := bytes.LastIndexByte(b[:n], '/')
|
---|
| 632 | if nn < 0 {
|
---|
| 633 | return append(dst[:0], strSlash...)
|
---|
| 634 | }
|
---|
| 635 | b = b[:nn+1]
|
---|
| 636 | }
|
---|
| 637 |
|
---|
| 638 | if filepath.Separator == '\\' {
|
---|
| 639 | // remove \.\ parts
|
---|
| 640 | b = dst
|
---|
| 641 | for {
|
---|
| 642 | n := bytes.Index(b, strBackSlashDotBackSlash)
|
---|
| 643 | if n < 0 {
|
---|
| 644 | break
|
---|
| 645 | }
|
---|
| 646 | nn := n + len(strSlashDotSlash) - 1
|
---|
| 647 | copy(b[n:], b[nn:])
|
---|
| 648 | b = b[:len(b)-nn+n]
|
---|
| 649 | }
|
---|
| 650 |
|
---|
| 651 | // remove /foo/..\ parts
|
---|
| 652 | for {
|
---|
| 653 | n := bytes.Index(b, strSlashDotDotBackSlash)
|
---|
| 654 | if n < 0 {
|
---|
| 655 | break
|
---|
| 656 | }
|
---|
| 657 | nn := bytes.LastIndexByte(b[:n], '/')
|
---|
| 658 | if nn < 0 {
|
---|
| 659 | nn = 0
|
---|
| 660 | }
|
---|
| 661 | n += len(strSlashDotDotBackSlash) - 1
|
---|
| 662 | copy(b[nn:], b[n:])
|
---|
| 663 | b = b[:len(b)-n+nn]
|
---|
| 664 | }
|
---|
| 665 |
|
---|
| 666 | // remove /foo\..\ parts
|
---|
| 667 | for {
|
---|
| 668 | n := bytes.Index(b, strBackSlashDotDotBackSlash)
|
---|
| 669 | if n < 0 {
|
---|
| 670 | break
|
---|
| 671 | }
|
---|
| 672 | nn := bytes.LastIndexByte(b[:n], '/')
|
---|
| 673 | if nn < 0 {
|
---|
| 674 | nn = 0
|
---|
| 675 | }
|
---|
| 676 | n += len(strBackSlashDotDotBackSlash) - 1
|
---|
| 677 | copy(b[nn:], b[n:])
|
---|
| 678 | b = b[:len(b)-n+nn]
|
---|
| 679 | }
|
---|
| 680 |
|
---|
| 681 | // remove trailing \foo\..
|
---|
| 682 | n := bytes.LastIndex(b, strBackSlashDotDot)
|
---|
| 683 | if n >= 0 && n+len(strSlashDotDot) == len(b) {
|
---|
| 684 | nn := bytes.LastIndexByte(b[:n], '/')
|
---|
| 685 | if nn < 0 {
|
---|
| 686 | return append(dst[:0], strSlash...)
|
---|
| 687 | }
|
---|
| 688 | b = b[:nn+1]
|
---|
| 689 | }
|
---|
| 690 | }
|
---|
| 691 |
|
---|
| 692 | return b
|
---|
| 693 | }
|
---|
| 694 |
|
---|
| 695 | // RequestURI returns RequestURI - i.e. URI without Scheme and Host.
|
---|
| 696 | func (u *URI) RequestURI() []byte {
|
---|
| 697 | var dst []byte
|
---|
| 698 | if u.DisablePathNormalizing {
|
---|
| 699 | dst = append(u.requestURI[:0], u.PathOriginal()...)
|
---|
| 700 | } else {
|
---|
| 701 | dst = appendQuotedPath(u.requestURI[:0], u.Path())
|
---|
| 702 | }
|
---|
| 703 | if u.parsedQueryArgs && u.queryArgs.Len() > 0 {
|
---|
| 704 | dst = append(dst, '?')
|
---|
| 705 | dst = u.queryArgs.AppendBytes(dst)
|
---|
| 706 | } else if len(u.queryString) > 0 {
|
---|
| 707 | dst = append(dst, '?')
|
---|
| 708 | dst = append(dst, u.queryString...)
|
---|
| 709 | }
|
---|
| 710 | u.requestURI = dst
|
---|
| 711 | return u.requestURI
|
---|
| 712 | }
|
---|
| 713 |
|
---|
| 714 | // LastPathSegment returns the last part of uri path after '/'.
|
---|
| 715 | //
|
---|
| 716 | // Examples:
|
---|
| 717 | //
|
---|
| 718 | // * For /foo/bar/baz.html path returns baz.html.
|
---|
| 719 | // * For /foo/bar/ returns empty byte slice.
|
---|
| 720 | // * For /foobar.js returns foobar.js.
|
---|
| 721 | //
|
---|
| 722 | // The returned bytes are valid until the next URI method call.
|
---|
| 723 | func (u *URI) LastPathSegment() []byte {
|
---|
| 724 | path := u.Path()
|
---|
| 725 | n := bytes.LastIndexByte(path, '/')
|
---|
| 726 | if n < 0 {
|
---|
| 727 | return path
|
---|
| 728 | }
|
---|
| 729 | return path[n+1:]
|
---|
| 730 | }
|
---|
| 731 |
|
---|
| 732 | // Update updates uri.
|
---|
| 733 | //
|
---|
| 734 | // The following newURI types are accepted:
|
---|
| 735 | //
|
---|
| 736 | // * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
|
---|
| 737 | // uri is replaced by newURI.
|
---|
| 738 | // * Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case
|
---|
| 739 | // the original scheme is preserved.
|
---|
| 740 | // * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
|
---|
| 741 | // of the original uri is replaced.
|
---|
| 742 | // * Relative path, i.e. xx?yy=abc . In this case the original RequestURI
|
---|
| 743 | // is updated according to the new relative path.
|
---|
| 744 | func (u *URI) Update(newURI string) {
|
---|
| 745 | u.UpdateBytes(s2b(newURI))
|
---|
| 746 | }
|
---|
| 747 |
|
---|
| 748 | // UpdateBytes updates uri.
|
---|
| 749 | //
|
---|
| 750 | // The following newURI types are accepted:
|
---|
| 751 | //
|
---|
| 752 | // * Absolute, i.e. http://foobar.com/aaa/bb?cc . In this case the original
|
---|
| 753 | // uri is replaced by newURI.
|
---|
| 754 | // * Absolute without scheme, i.e. //foobar.com/aaa/bb?cc. In this case
|
---|
| 755 | // the original scheme is preserved.
|
---|
| 756 | // * Missing host, i.e. /aaa/bb?cc . In this case only RequestURI part
|
---|
| 757 | // of the original uri is replaced.
|
---|
| 758 | // * Relative path, i.e. xx?yy=abc . In this case the original RequestURI
|
---|
| 759 | // is updated according to the new relative path.
|
---|
| 760 | func (u *URI) UpdateBytes(newURI []byte) {
|
---|
| 761 | u.requestURI = u.updateBytes(newURI, u.requestURI)
|
---|
| 762 | }
|
---|
| 763 |
|
---|
| 764 | func (u *URI) updateBytes(newURI, buf []byte) []byte {
|
---|
| 765 | if len(newURI) == 0 {
|
---|
| 766 | return buf
|
---|
| 767 | }
|
---|
| 768 |
|
---|
| 769 | n := bytes.Index(newURI, strSlashSlash)
|
---|
| 770 | if n >= 0 {
|
---|
| 771 | // absolute uri
|
---|
| 772 | var b [32]byte
|
---|
| 773 | schemeOriginal := b[:0]
|
---|
| 774 | if len(u.scheme) > 0 {
|
---|
| 775 | schemeOriginal = append([]byte(nil), u.scheme...)
|
---|
| 776 | }
|
---|
| 777 | if err := u.Parse(nil, newURI); err != nil {
|
---|
| 778 | return nil
|
---|
| 779 | }
|
---|
| 780 | if len(schemeOriginal) > 0 && len(u.scheme) == 0 {
|
---|
| 781 | u.scheme = append(u.scheme[:0], schemeOriginal...)
|
---|
| 782 | }
|
---|
| 783 | return buf
|
---|
| 784 | }
|
---|
| 785 |
|
---|
| 786 | if newURI[0] == '/' {
|
---|
| 787 | // uri without host
|
---|
| 788 | buf = u.appendSchemeHost(buf[:0])
|
---|
| 789 | buf = append(buf, newURI...)
|
---|
| 790 | if err := u.Parse(nil, buf); err != nil {
|
---|
| 791 | return nil
|
---|
| 792 | }
|
---|
| 793 | return buf
|
---|
| 794 | }
|
---|
| 795 |
|
---|
| 796 | // relative path
|
---|
| 797 | switch newURI[0] {
|
---|
| 798 | case '?':
|
---|
| 799 | // query string only update
|
---|
| 800 | u.SetQueryStringBytes(newURI[1:])
|
---|
| 801 | return append(buf[:0], u.FullURI()...)
|
---|
| 802 | case '#':
|
---|
| 803 | // update only hash
|
---|
| 804 | u.SetHashBytes(newURI[1:])
|
---|
| 805 | return append(buf[:0], u.FullURI()...)
|
---|
| 806 | default:
|
---|
| 807 | // update the last path part after the slash
|
---|
| 808 | path := u.Path()
|
---|
| 809 | n = bytes.LastIndexByte(path, '/')
|
---|
| 810 | if n < 0 {
|
---|
| 811 | panic(fmt.Sprintf("BUG: path must contain at least one slash: %s %s", u.Path(), newURI))
|
---|
| 812 | }
|
---|
| 813 | buf = u.appendSchemeHost(buf[:0])
|
---|
| 814 | buf = appendQuotedPath(buf, path[:n+1])
|
---|
| 815 | buf = append(buf, newURI...)
|
---|
| 816 | if err := u.Parse(nil, buf); err != nil {
|
---|
| 817 | return nil
|
---|
| 818 | }
|
---|
| 819 | return buf
|
---|
| 820 | }
|
---|
| 821 | }
|
---|
| 822 |
|
---|
| 823 | // FullURI returns full uri in the form {Scheme}://{Host}{RequestURI}#{Hash}.
|
---|
| 824 | //
|
---|
| 825 | // The returned bytes are valid until the next URI method call.
|
---|
| 826 | func (u *URI) FullURI() []byte {
|
---|
| 827 | u.fullURI = u.AppendBytes(u.fullURI[:0])
|
---|
| 828 | return u.fullURI
|
---|
| 829 | }
|
---|
| 830 |
|
---|
| 831 | // AppendBytes appends full uri to dst and returns the extended dst.
|
---|
| 832 | func (u *URI) AppendBytes(dst []byte) []byte {
|
---|
| 833 | dst = u.appendSchemeHost(dst)
|
---|
| 834 | dst = append(dst, u.RequestURI()...)
|
---|
| 835 | if len(u.hash) > 0 {
|
---|
| 836 | dst = append(dst, '#')
|
---|
| 837 | dst = append(dst, u.hash...)
|
---|
| 838 | }
|
---|
| 839 | return dst
|
---|
| 840 | }
|
---|
| 841 |
|
---|
| 842 | func (u *URI) appendSchemeHost(dst []byte) []byte {
|
---|
| 843 | dst = append(dst, u.Scheme()...)
|
---|
| 844 | dst = append(dst, strColonSlashSlash...)
|
---|
| 845 | return append(dst, u.Host()...)
|
---|
| 846 | }
|
---|
| 847 |
|
---|
| 848 | // WriteTo writes full uri to w.
|
---|
| 849 | //
|
---|
| 850 | // WriteTo implements io.WriterTo interface.
|
---|
| 851 | func (u *URI) WriteTo(w io.Writer) (int64, error) {
|
---|
| 852 | n, err := w.Write(u.FullURI())
|
---|
| 853 | return int64(n), err
|
---|
| 854 | }
|
---|
| 855 |
|
---|
| 856 | // String returns full uri.
|
---|
| 857 | func (u *URI) String() string {
|
---|
| 858 | return string(u.FullURI())
|
---|
| 859 | }
|
---|
| 860 |
|
---|
| 861 | func splitHostURI(host, uri []byte) ([]byte, []byte, []byte) {
|
---|
| 862 | n := bytes.Index(uri, strSlashSlash)
|
---|
| 863 | if n < 0 {
|
---|
| 864 | return strHTTP, host, uri
|
---|
| 865 | }
|
---|
| 866 | scheme := uri[:n]
|
---|
| 867 | if bytes.IndexByte(scheme, '/') >= 0 {
|
---|
| 868 | return strHTTP, host, uri
|
---|
| 869 | }
|
---|
| 870 | if len(scheme) > 0 && scheme[len(scheme)-1] == ':' {
|
---|
| 871 | scheme = scheme[:len(scheme)-1]
|
---|
| 872 | }
|
---|
| 873 | n += len(strSlashSlash)
|
---|
| 874 | uri = uri[n:]
|
---|
| 875 | n = bytes.IndexByte(uri, '/')
|
---|
| 876 | nq := bytes.IndexByte(uri, '?')
|
---|
| 877 | if nq >= 0 && nq < n {
|
---|
| 878 | // A hack for urls like foobar.com?a=b/xyz
|
---|
| 879 | n = nq
|
---|
| 880 | } else if n < 0 {
|
---|
| 881 | // A hack for bogus urls like foobar.com?a=b without
|
---|
| 882 | // slash after host.
|
---|
| 883 | if nq >= 0 {
|
---|
| 884 | return scheme, uri[:nq], uri[nq:]
|
---|
| 885 | }
|
---|
| 886 | return scheme, uri, strSlash
|
---|
| 887 | }
|
---|
| 888 | return scheme, uri[:n], uri[n:]
|
---|
| 889 | }
|
---|
| 890 |
|
---|
| 891 | // QueryArgs returns query args.
|
---|
| 892 | //
|
---|
| 893 | // The returned args are valid until the next URI method call.
|
---|
| 894 | func (u *URI) QueryArgs() *Args {
|
---|
| 895 | u.parseQueryArgs()
|
---|
| 896 | return &u.queryArgs
|
---|
| 897 | }
|
---|
| 898 |
|
---|
| 899 | func (u *URI) parseQueryArgs() {
|
---|
| 900 | if u.parsedQueryArgs {
|
---|
| 901 | return
|
---|
| 902 | }
|
---|
| 903 | u.queryArgs.ParseBytes(u.queryString)
|
---|
| 904 | u.parsedQueryArgs = true
|
---|
| 905 | }
|
---|
| 906 |
|
---|
| 907 | // stringContainsCTLByte reports whether s contains any ASCII control character.
|
---|
| 908 | func stringContainsCTLByte(s []byte) bool {
|
---|
| 909 | for i := 0; i < len(s); i++ {
|
---|
| 910 | b := s[i]
|
---|
| 911 | if b < ' ' || b == 0x7f {
|
---|
| 912 | return true
|
---|
| 913 | }
|
---|
| 914 | }
|
---|
| 915 | return false
|
---|
| 916 | }
|
---|