source: code/trunk/vendor/github.com/valyala/fasthttp/uri.go@ 145

Last change on this file since 145 was 145, checked in by Izuru Yakumo, 22 months ago

Updated the Makefile and vendored depedencies

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 22.8 KB
RevLine 
[145]1package fasthttp
2
3import (
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.
17func 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.
25func ReleaseURI(u *URI) {
26 u.Reset()
27 uriPool.Put(u)
28}
29
30var 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.
42type 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.
72func (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.
94func (u *URI) Hash() []byte {
95 return u.hash
96}
97
98// SetHash sets URI hash.
99func (u *URI) SetHash(hash string) {
100 u.hash = append(u.hash[:0], hash...)
101}
102
103// SetHashBytes sets URI hash.
104func (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.
111func (u *URI) Username() []byte {
112 return u.username
113}
114
115// SetUsername sets URI username.
116func (u *URI) SetUsername(username string) {
117 u.username = append(u.username[:0], username...)
118}
119
120// SetUsernameBytes sets URI username.
121func (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.
128func (u *URI) Password() []byte {
129 return u.password
130}
131
132// SetPassword sets URI password.
133func (u *URI) SetPassword(password string) {
134 u.password = append(u.password[:0], password...)
135}
136
137// SetPasswordBytes sets URI password.
138func (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.
146func (u *URI) QueryString() []byte {
147 return u.queryString
148}
149
150// SetQueryString sets URI query string.
151func (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.
157func (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.
168func (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.
177func (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.
183func (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.
191func (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.
200func (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.
209func (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.
215func (u *URI) SetSchemeBytes(scheme []byte) {
216 u.scheme = append(u.scheme[:0], scheme...)
217 lowercaseBytes(u.scheme)
218}
219
220func (u *URI) isHttps() bool {
221 return bytes.Equal(u.scheme, strHTTPS)
222}
223
224func (u *URI) isHttp() bool {
225 return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)
226}
227
228// Reset clears uri.
229func (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.
255func (u *URI) Host() []byte {
256 return u.host
257}
258
259// SetHost sets host for the uri.
260func (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.
266func (u *URI) SetHostBytes(host []byte) {
267 u.host = append(u.host[:0], host...)
268 lowercaseBytes(u.host)
269}
270
271var (
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.
281func (u *URI) Parse(host, uri []byte) error {
282 return u.parse(host, uri, false)
283}
284
285func (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.
367func 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
416type encoding int
417
418const (
419 encodeHost encoding = 1 + iota
420 encodeZone
421)
422
423type EscapeError string
424
425func (e EscapeError) Error() string {
426 return "invalid URL escape " + strconv.Quote(string(e))
427}
428
429type InvalidHostError string
430
431func (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.
441func 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
510func 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
540func 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
552func 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*$/
566func 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
581func 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.
696func (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.
723func (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.
744func (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.
760func (u *URI) UpdateBytes(newURI []byte) {
761 u.requestURI = u.updateBytes(newURI, u.requestURI)
762}
763
764func (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.
826func (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.
832func (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
842func (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.
851func (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.
857func (u *URI) String() string {
858 return string(u.FullURI())
859}
860
861func 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.
894func (u *URI) QueryArgs() *Args {
895 u.parseQueryArgs()
896 return &u.queryArgs
897}
898
899func (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.
908func 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}
Note: See TracBrowser for help on using the repository browser.