1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package url parses URLs and implements query escaping.
8 // See RFC 3986. This package generally follows RFC 3986, except where
9 // it deviates for compatibility reasons. When sending changes, first
10 // search old issues for history on decisions. Unit tests should also
11 // contain references to issue numbers with details.
21 // Error reports an error and the operation and URL that caused it.
28 func (e
*Error
) Error() string { return e
.Op
+ " " + e
.URL
+ ": " + e
.Err
.Error() }
30 type timeout
interface {
34 func (e
*Error
) Timeout() bool {
35 t
, ok
:= e
.Err
.(timeout
)
36 return ok
&& t
.Timeout()
39 type temporary
interface {
43 func (e
*Error
) Temporary() bool {
44 t
, ok
:= e
.Err
.(temporary
)
45 return ok
&& t
.Temporary()
48 func ishex(c
byte) bool {
50 case '0' <= c
&& c
<= '9':
52 case 'a' <= c
&& c
<= 'f':
54 case 'A' <= c
&& c
<= 'F':
60 func unhex(c
byte) byte {
62 case '0' <= c
&& c
<= '9':
64 case 'a' <= c
&& c
<= 'f':
66 case 'A' <= c
&& c
<= 'F':
75 encodePath encoding
= 1 + iota
84 type EscapeError
string
86 func (e EscapeError
) Error() string {
87 return "invalid URL escape " + strconv
.Quote(string(e
))
90 type InvalidHostError
string
92 func (e InvalidHostError
) Error() string {
93 return "invalid character " + strconv
.Quote(string(e
)) + " in host name"
96 // Return true if the specified character should be escaped when
97 // appearing in a URL string, according to RFC 3986.
99 // Please be informed that for now shouldEscape does not check all
100 // reserved characters correctly. See golang.org/issue/5684.
101 func shouldEscape(c
byte, mode encoding
) bool {
102 // §2.3 Unreserved characters (alphanum)
103 if 'A' <= c
&& c
<= 'Z' ||
'a' <= c
&& c
<= 'z' ||
'0' <= c
&& c
<= '9' {
107 if mode
== encodeHost || mode
== encodeZone
{
108 // §3.2.2 Host allows
109 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
110 // as part of reg-name.
111 // We add : because we include :port as part of host.
112 // We add [ ] because we include [ipv6]:port as part of host.
113 // We add < > because they're the only characters left that
114 // we could possibly allow, and Parse will reject them if we
115 // escape them (because hosts can't use %-encoding for
118 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
124 case '-', '_', '.', '~': // §2.3 Unreserved characters (mark)
127 case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
128 // Different sections of the URL allow a few of
129 // the reserved characters to appear unescaped.
131 case encodePath
: // §3.3
132 // The RFC allows : @ & = + $ but saves / ; , for assigning
133 // meaning to individual path segments. This package
134 // only manipulates the path as a whole, so we allow those
135 // last three as well. That leaves only ? to escape.
138 case encodePathSegment
: // §3.3
139 // The RFC allows : @ & = + $ but saves / ; , for assigning
140 // meaning to individual path segments.
141 return c
== '/' || c
== ';' || c
== ',' || c
== '?'
143 case encodeUserPassword
: // §3.2.1
144 // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in
145 // userinfo, so we must escape only '@', '/', and '?'.
146 // The parsing of userinfo treats ':' as special so we must escape
148 return c
== '@' || c
== '/' || c
== '?' || c
== ':'
150 case encodeQueryComponent
: // §3.4
151 // The RFC reserves (so we must escape) everything.
154 case encodeFragment
: // §4.1
155 // The RFC text is silent but the grammar allows
156 // everything, so escape nothing.
161 if mode
== encodeFragment
{
162 // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are
163 // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not
164 // need to be escaped. To minimize potential breakage, we apply two restrictions:
165 // (1) we always escape sub-delims outside of the fragment, and (2) we always
166 // escape single quote to avoid breaking callers that had previously assumed that
167 // single quotes would be escaped. See issue #19917.
169 case '!', '(', ')', '*':
174 // Everything else must be escaped.
178 // QueryUnescape does the inverse transformation of QueryEscape,
179 // converting each 3-byte encoded substring of the form "%AB" into the
180 // hex-decoded byte 0xAB.
181 // It returns an error if any % is not followed by two hexadecimal
183 func QueryUnescape(s
string) (string, error
) {
184 return unescape(s
, encodeQueryComponent
)
187 // PathUnescape does the inverse transformation of PathEscape,
188 // converting each 3-byte encoded substring of the form "%AB" into the
189 // hex-decoded byte 0xAB. It returns an error if any % is not followed
190 // by two hexadecimal digits.
192 // PathUnescape is identical to QueryUnescape except that it does not
193 // unescape '+' to ' ' (space).
194 func PathUnescape(s
string) (string, error
) {
195 return unescape(s
, encodePathSegment
)
198 // unescape unescapes a string; the mode specifies
199 // which section of the URL string is being unescaped.
200 func unescape(s
string, mode encoding
) (string, error
) {
201 // Count %, check that they're well-formed.
204 for i
:= 0; i
< len(s
); {
208 if i
+2 >= len(s
) ||
!ishex(s
[i
+1]) ||
!ishex(s
[i
+2]) {
213 return "", EscapeError(s
)
215 // Per https://tools.ietf.org/html/rfc3986#page-21
216 // in the host component %-encoding can only be used
217 // for non-ASCII bytes.
218 // But https://tools.ietf.org/html/rfc6874#section-2
219 // introduces %25 being allowed to escape a percent sign
220 // in IPv6 scoped-address literals. Yay.
221 if mode
== encodeHost
&& unhex(s
[i
+1]) < 8 && s
[i
:i
+3] != "%25" {
222 return "", EscapeError(s
[i
: i
+3])
224 if mode
== encodeZone
{
225 // RFC 6874 says basically "anything goes" for zone identifiers
226 // and that even non-ASCII can be redundantly escaped,
227 // but it seems prudent to restrict %-escaped bytes here to those
228 // that are valid host name bytes in their unescaped form.
229 // That is, you can use escaping in the zone identifier but not
230 // to introduce bytes you couldn't just write directly.
231 // But Windows puts spaces here! Yay.
232 v
:= unhex(s
[i
+1])<<4 |
unhex(s
[i
+2])
233 if s
[i
:i
+3] != "%25" && v
!= ' ' && shouldEscape(v
, encodeHost
) {
234 return "", EscapeError(s
[i
: i
+3])
239 hasPlus
= mode
== encodeQueryComponent
242 if (mode
== encodeHost || mode
== encodeZone
) && s
[i
] < 0x80 && shouldEscape(s
[i
], mode
) {
243 return "", InvalidHostError(s
[i
: i
+1])
249 if n
== 0 && !hasPlus
{
253 t
:= make([]byte, len(s
)-2*n
)
255 for i
:= 0; i
< len(s
); {
258 t
[j
] = unhex(s
[i
+1])<<4 |
unhex(s
[i
+2])
262 if mode
== encodeQueryComponent
{
275 return string(t
), nil
278 // QueryEscape escapes the string so it can be safely placed
279 // inside a URL query.
280 func QueryEscape(s
string) string {
281 return escape(s
, encodeQueryComponent
)
284 // PathEscape escapes the string so it can be safely placed
285 // inside a URL path segment.
286 func PathEscape(s
string) string {
287 return escape(s
, encodePathSegment
)
290 func escape(s
string, mode encoding
) string {
291 spaceCount
, hexCount
:= 0, 0
292 for i
:= 0; i
< len(s
); i
++ {
294 if shouldEscape(c
, mode
) {
295 if c
== ' ' && mode
== encodeQueryComponent
{
303 if spaceCount
== 0 && hexCount
== 0 {
307 t
:= make([]byte, len(s
)+2*hexCount
)
309 for i
:= 0; i
< len(s
); i
++ {
311 case c
== ' ' && mode
== encodeQueryComponent
:
314 case shouldEscape(c
, mode
):
316 t
[j
+1] = "0123456789ABCDEF"[c
>>4]
317 t
[j
+2] = "0123456789ABCDEF"[c
&15]
327 // A URL represents a parsed URL (technically, a URI reference).
329 // The general form represented is:
331 // [scheme:][//[userinfo@]host][/]path[?query][#fragment]
333 // URLs that do not start with a slash after the scheme are interpreted as:
335 // scheme:opaque[?query][#fragment]
337 // Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.
338 // A consequence is that it is impossible to tell which slashes in the Path were
339 // slashes in the raw URL and which were %2f. This distinction is rarely important,
340 // but when it is, code must not use Path directly.
341 // The Parse function sets both Path and RawPath in the URL it returns,
342 // and URL's String method uses RawPath if it is a valid encoding of Path,
343 // by calling the EscapedPath method.
346 Opaque
string // encoded opaque data
347 User
*Userinfo
// username and password information
348 Host
string // host or host:port
349 Path
string // path (relative paths may omit leading slash)
350 RawPath
string // encoded path hint (see EscapedPath method)
351 ForceQuery
bool // append a query ('?') even if RawQuery is empty
352 RawQuery
string // encoded query values, without '?'
353 Fragment
string // fragment for references, without '#'
356 // User returns a Userinfo containing the provided username
357 // and no password set.
358 func User(username
string) *Userinfo
{
359 return &Userinfo
{username
, "", false}
362 // UserPassword returns a Userinfo containing the provided username
365 // This functionality should only be used with legacy web sites.
366 // RFC 2396 warns that interpreting Userinfo this way
367 // ``is NOT RECOMMENDED, because the passing of authentication
368 // information in clear text (such as URI) has proven to be a
369 // security risk in almost every case where it has been used.''
370 func UserPassword(username
, password
string) *Userinfo
{
371 return &Userinfo
{username
, password
, true}
374 // The Userinfo type is an immutable encapsulation of username and
375 // password details for a URL. An existing Userinfo value is guaranteed
376 // to have a username set (potentially empty, as allowed by RFC 2396),
377 // and optionally a password.
378 type Userinfo
struct {
384 // Username returns the username.
385 func (u
*Userinfo
) Username() string {
392 // Password returns the password in case it is set, and whether it is set.
393 func (u
*Userinfo
) Password() (string, bool) {
397 return u
.password
, u
.passwordSet
400 // String returns the encoded userinfo information in the standard form
401 // of "username[:password]".
402 func (u
*Userinfo
) String() string {
406 s
:= escape(u
.username
, encodeUserPassword
)
408 s
+= ":" + escape(u
.password
, encodeUserPassword
)
413 // Maybe rawurl is of the form scheme:path.
414 // (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*)
415 // If so, return scheme, path; else return "", rawurl.
416 func getscheme(rawurl
string) (scheme
, path
string, err error
) {
417 for i
:= 0; i
< len(rawurl
); i
++ {
420 case 'a' <= c
&& c
<= 'z' ||
'A' <= c
&& c
<= 'Z':
422 case '0' <= c
&& c
<= '9' || c
== '+' || c
== '-' || c
== '.':
424 return "", rawurl
, nil
428 return "", "", errors
.New("missing protocol scheme")
430 return rawurl
[:i
], rawurl
[i
+1:], nil
432 // we have encountered an invalid character,
433 // so there is no valid scheme
434 return "", rawurl
, nil
437 return "", rawurl
, nil
440 // Maybe s is of the form t c u.
441 // If so, return t, c u (or t, u if cutc == true).
442 // If not, return s, "".
443 func split(s
string, c
string, cutc
bool) (string, string) {
444 i
:= strings
.Index(s
, c
)
449 return s
[:i
], s
[i
+len(c
):]
454 // Parse parses rawurl into a URL structure.
456 // The rawurl may be relative (a path, without a host) or absolute
457 // (starting with a scheme). Trying to parse a hostname and path
458 // without a scheme is invalid but may not necessarily return an
459 // error, due to parsing ambiguities.
460 func Parse(rawurl
string) (*URL
, error
) {
462 u
, frag
:= split(rawurl
, "#", true)
463 url
, err
:= parse(u
, false)
465 return nil, &Error
{"parse", u
, err
}
470 if url
.Fragment
, err
= unescape(frag
, encodeFragment
); err
!= nil {
471 return nil, &Error
{"parse", rawurl
, err
}
476 // ParseRequestURI parses rawurl into a URL structure. It assumes that
477 // rawurl was received in an HTTP request, so the rawurl is interpreted
478 // only as an absolute URI or an absolute path.
479 // The string rawurl is assumed not to have a #fragment suffix.
480 // (Web browsers strip #fragment before sending the URL to a web server.)
481 func ParseRequestURI(rawurl
string) (*URL
, error
) {
482 url
, err
:= parse(rawurl
, true)
484 return nil, &Error
{"parse", rawurl
, err
}
489 // parse parses a URL from a string in one of two contexts. If
490 // viaRequest is true, the URL is assumed to have arrived via an HTTP request,
491 // in which case only absolute URLs or path-absolute relative URLs are allowed.
492 // If viaRequest is false, all forms of relative URLs are allowed.
493 func parse(rawurl
string, viaRequest
bool) (*URL
, error
) {
497 if rawurl
== "" && viaRequest
{
498 return nil, errors
.New("empty url")
507 // Split off possible leading "http:", "mailto:", etc.
508 // Cannot contain escaped characters.
509 if url
.Scheme
, rest
, err
= getscheme(rawurl
); err
!= nil {
512 url
.Scheme
= strings
.ToLower(url
.Scheme
)
514 if strings
.HasSuffix(rest
, "?") && strings
.Count(rest
, "?") == 1 {
515 url
.ForceQuery
= true
516 rest
= rest
[:len(rest
)-1]
518 rest
, url
.RawQuery
= split(rest
, "?", true)
521 if !strings
.HasPrefix(rest
, "/") {
522 if url
.Scheme
!= "" {
523 // We consider rootless paths per RFC 3986 as opaque.
528 return nil, errors
.New("invalid URI for request")
531 // Avoid confusion with malformed schemes, like cache_object:foo/bar.
532 // See golang.org/issue/16822.
535 // In addition, a URI reference (Section 4.1) may be a relative-path reference,
536 // in which case the first path segment cannot contain a colon (":") character.
537 colon
:= strings
.Index(rest
, ":")
538 slash
:= strings
.Index(rest
, "/")
539 if colon
>= 0 && (slash
< 0 || colon
< slash
) {
540 // First path segment has colon. Not allowed in relative URL.
541 return nil, errors
.New("first path segment in URL cannot contain colon")
545 if (url
.Scheme
!= "" ||
!viaRequest
&& !strings
.HasPrefix(rest
, "///")) && strings
.HasPrefix(rest
, "//") {
547 authority
, rest
= split(rest
[2:], "/", false)
548 url
.User
, url
.Host
, err
= parseAuthority(authority
)
553 // Set Path and, optionally, RawPath.
554 // RawPath is a hint of the encoding of Path. We don't want to set it if
555 // the default escaping of Path is equivalent, to help make sure that people
556 // don't rely on it in general.
557 if err
:= url
.setPath(rest
); err
!= nil {
563 func parseAuthority(authority
string) (user
*Userinfo
, host
string, err error
) {
564 i
:= strings
.LastIndex(authority
, "@")
566 host
, err
= parseHost(authority
)
568 host
, err
= parseHost(authority
[i
+1:])
574 return nil, host
, nil
576 userinfo
:= authority
[:i
]
577 if !validUserinfo(userinfo
) {
578 return nil, "", errors
.New("net/url: invalid userinfo")
580 if !strings
.Contains(userinfo
, ":") {
581 if userinfo
, err
= unescape(userinfo
, encodeUserPassword
); err
!= nil {
584 user
= User(userinfo
)
586 username
, password
:= split(userinfo
, ":", true)
587 if username
, err
= unescape(username
, encodeUserPassword
); err
!= nil {
590 if password
, err
= unescape(password
, encodeUserPassword
); err
!= nil {
593 user
= UserPassword(username
, password
)
595 return user
, host
, nil
598 // parseHost parses host as an authority without user
599 // information. That is, as host[:port].
600 func parseHost(host
string) (string, error
) {
601 if strings
.HasPrefix(host
, "[") {
602 // Parse an IP-Literal in RFC 3986 and RFC 6874.
603 // E.g., "[fe80::1]", "[fe80::1%25en0]", "[fe80::1]:80".
604 i
:= strings
.LastIndex(host
, "]")
606 return "", errors
.New("missing ']' in host")
608 colonPort
:= host
[i
+1:]
609 if !validOptionalPort(colonPort
) {
610 return "", fmt
.Errorf("invalid port %q after host", colonPort
)
613 // RFC 6874 defines that %25 (%-encoded percent) introduces
614 // the zone identifier, and the zone identifier can use basically
615 // any %-encoding it likes. That's different from the host, which
616 // can only %-encode non-ASCII bytes.
617 // We do impose some restrictions on the zone, to avoid stupidity
619 zone
:= strings
.Index(host
[:i
], "%25")
621 host1
, err
:= unescape(host
[:zone
], encodeHost
)
625 host2
, err
:= unescape(host
[zone
:i
], encodeZone
)
629 host3
, err
:= unescape(host
[i
:], encodeHost
)
633 return host1
+ host2
+ host3
, nil
638 if host
, err
= unescape(host
, encodeHost
); err
!= nil {
644 // setPath sets the Path and RawPath fields of the URL based on the provided
645 // escaped path p. It maintains the invariant that RawPath is only specified
646 // when it differs from the default encoding of the path.
648 // - setPath("/foo/bar") will set Path="/foo/bar" and RawPath=""
649 // - setPath("/foo%2fbar") will set Path="/foo/bar" and RawPath="/foo%2fbar"
650 // setPath will return an error only if the provided path contains an invalid
652 func (u
*URL
) setPath(p
string) error
{
653 path
, err
:= unescape(p
, encodePath
)
658 if escp
:= escape(path
, encodePath
); p
== escp
{
659 // Default encoding is fine.
667 // EscapedPath returns the escaped form of u.Path.
668 // In general there are multiple possible escaped forms of any path.
669 // EscapedPath returns u.RawPath when it is a valid escaping of u.Path.
670 // Otherwise EscapedPath ignores u.RawPath and computes an escaped
672 // The String and RequestURI methods use EscapedPath to construct
674 // In general, code should call EscapedPath instead of
675 // reading u.RawPath directly.
676 func (u
*URL
) EscapedPath() string {
677 if u
.RawPath
!= "" && validEncodedPath(u
.RawPath
) {
678 p
, err
:= unescape(u
.RawPath
, encodePath
)
679 if err
== nil && p
== u
.Path
{
684 return "*" // don't escape (Issue 11202)
686 return escape(u
.Path
, encodePath
)
689 // validEncodedPath reports whether s is a valid encoded path.
690 // It must not contain any bytes that require escaping during path encoding.
691 func validEncodedPath(s
string) bool {
692 for i
:= 0; i
< len(s
); i
++ {
693 // RFC 3986, Appendix A.
694 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@".
695 // shouldEscape is not quite compliant with the RFC,
696 // so we check the sub-delims ourselves and let
697 // shouldEscape handle the others.
699 case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '@':
702 // ok - not specified in RFC 3986 but left alone by modern browsers
704 // ok - percent encoded, will decode
706 if shouldEscape(s
[i
], encodePath
) {
714 // validOptionalPort reports whether port is either an empty string
715 // or matches /^:\d*$/
716 func validOptionalPort(port
string) bool {
723 for _
, b
:= range port
[1:] {
724 if b
< '0' || b
> '9' {
731 // String reassembles the URL into a valid URL string.
732 // The general form of the result is one of:
734 // scheme:opaque?query#fragment
735 // scheme://userinfo@host/path?query#fragment
737 // If u.Opaque is non-empty, String uses the first form;
738 // otherwise it uses the second form.
739 // To obtain the path, String uses u.EscapedPath().
741 // In the second form, the following rules apply:
742 // - if u.Scheme is empty, scheme: is omitted.
743 // - if u.User is nil, userinfo@ is omitted.
744 // - if u.Host is empty, host/ is omitted.
745 // - if u.Scheme and u.Host are empty and u.User is nil,
746 // the entire scheme://userinfo@host/ is omitted.
747 // - if u.Host is non-empty and u.Path begins with a /,
748 // the form host/path does not add its own /.
749 // - if u.RawQuery is empty, ?query is omitted.
750 // - if u.Fragment is empty, #fragment is omitted.
751 func (u
*URL
) String() string {
752 var buf strings
.Builder
754 buf
.WriteString(u
.Scheme
)
758 buf
.WriteString(u
.Opaque
)
760 if u
.Scheme
!= "" || u
.Host
!= "" || u
.User
!= nil {
761 if u
.Host
!= "" || u
.Path
!= "" || u
.User
!= nil {
762 buf
.WriteString("//")
764 if ui
:= u
.User
; ui
!= nil {
765 buf
.WriteString(ui
.String())
768 if h
:= u
.Host
; h
!= "" {
769 buf
.WriteString(escape(h
, encodeHost
))
772 path
:= u
.EscapedPath()
773 if path
!= "" && path
[0] != '/' && u
.Host
!= "" {
778 // A path segment that contains a colon character (e.g., "this:that")
779 // cannot be used as the first segment of a relative-path reference, as
780 // it would be mistaken for a scheme name. Such a segment must be
781 // preceded by a dot-segment (e.g., "./this:that") to make a relative-
783 if i
:= strings
.IndexByte(path
, ':'); i
> -1 && strings
.IndexByte(path
[:i
], '/') == -1 {
784 buf
.WriteString("./")
787 buf
.WriteString(path
)
789 if u
.ForceQuery || u
.RawQuery
!= "" {
791 buf
.WriteString(u
.RawQuery
)
793 if u
.Fragment
!= "" {
795 buf
.WriteString(escape(u
.Fragment
, encodeFragment
))
800 // Values maps a string key to a list of values.
801 // It is typically used for query parameters and form values.
802 // Unlike in the http.Header map, the keys in a Values map
803 // are case-sensitive.
804 type Values
map[string][]string
806 // Get gets the first value associated with the given key.
807 // If there are no values associated with the key, Get returns
808 // the empty string. To access multiple values, use the map
810 func (v Values
) Get(key
string) string {
821 // Set sets the key to value. It replaces any existing
823 func (v Values
) Set(key
, value
string) {
824 v
[key
] = []string{value
}
827 // Add adds the value to key. It appends to any existing
828 // values associated with key.
829 func (v Values
) Add(key
, value
string) {
830 v
[key
] = append(v
[key
], value
)
833 // Del deletes the values associated with key.
834 func (v Values
) Del(key
string) {
838 // ParseQuery parses the URL-encoded query string and returns
839 // a map listing the values specified for each key.
840 // ParseQuery always returns a non-nil map containing all the
841 // valid query parameters found; err describes the first decoding error
842 // encountered, if any.
844 // Query is expected to be a list of key=value settings separated by
845 // ampersands or semicolons. A setting without an equals sign is
846 // interpreted as a key set to an empty value.
847 func ParseQuery(query
string) (Values
, error
) {
849 err
:= parseQuery(m
, query
)
853 func parseQuery(m Values
, query
string) (err error
) {
856 if i
:= strings
.IndexAny(key
, "&;"); i
>= 0 {
857 key
, query
= key
[:i
], key
[i
+1:]
865 if i
:= strings
.Index(key
, "="); i
>= 0 {
866 key
, value
= key
[:i
], key
[i
+1:]
868 key
, err1
:= QueryUnescape(key
)
875 value
, err1
= QueryUnescape(value
)
882 m
[key
] = append(m
[key
], value
)
887 // Encode encodes the values into ``URL encoded'' form
888 // ("bar=baz&foo=quux") sorted by key.
889 func (v Values
) Encode() string {
893 var buf strings
.Builder
894 keys
:= make([]string, 0, len(v
))
896 keys
= append(keys
, k
)
899 for _
, k
:= range keys
{
901 keyEscaped
:= QueryEscape(k
)
902 for _
, v
:= range vs
{
906 buf
.WriteString(keyEscaped
)
908 buf
.WriteString(QueryEscape(v
))
914 // resolvePath applies special path segments from refs and applies
915 // them to base, per RFC 3986.
916 func resolvePath(base
, ref
string) string {
920 } else if ref
[0] != '/' {
921 i
:= strings
.LastIndex(base
, "/")
922 full
= base
[:i
+1] + ref
930 src
:= strings
.Split(full
, "/")
931 for _
, elem
:= range src
{
937 dst
= dst
[:len(dst
)-1]
940 dst
= append(dst
, elem
)
943 if last
:= src
[len(src
)-1]; last
== "." || last
== ".." {
944 // Add final slash to the joined path.
945 dst
= append(dst
, "")
947 return "/" + strings
.TrimPrefix(strings
.Join(dst
, "/"), "/")
950 // IsAbs reports whether the URL is absolute.
951 // Absolute means that it has a non-empty scheme.
952 func (u
*URL
) IsAbs() bool {
953 return u
.Scheme
!= ""
956 // Parse parses a URL in the context of the receiver. The provided URL
957 // may be relative or absolute. Parse returns nil, err on parse
958 // failure, otherwise its return value is the same as ResolveReference.
959 func (u
*URL
) Parse(ref
string) (*URL
, error
) {
960 refurl
, err
:= Parse(ref
)
964 return u
.ResolveReference(refurl
), nil
967 // ResolveReference resolves a URI reference to an absolute URI from
968 // an absolute base URI u, per RFC 3986 Section 5.2. The URI reference
969 // may be relative or absolute. ResolveReference always returns a new
970 // URL instance, even if the returned URL is identical to either the
971 // base or reference. If ref is an absolute URL, then ResolveReference
972 // ignores base and returns a copy of ref.
973 func (u
*URL
) ResolveReference(ref
*URL
) *URL
{
975 if ref
.Scheme
== "" {
976 url
.Scheme
= u
.Scheme
978 if ref
.Scheme
!= "" || ref
.Host
!= "" || ref
.User
!= nil {
979 // The "absoluteURI" or "net_path" cases.
980 // We can ignore the error from setPath since we know we provided a
981 // validly-escaped path.
982 url
.setPath(resolvePath(ref
.EscapedPath(), ""))
985 if ref
.Opaque
!= "" {
991 if ref
.Path
== "" && ref
.RawQuery
== "" {
992 url
.RawQuery
= u
.RawQuery
993 if ref
.Fragment
== "" {
994 url
.Fragment
= u
.Fragment
997 // The "abs_path" or "rel_path" cases.
1000 url
.setPath(resolvePath(u
.EscapedPath(), ref
.EscapedPath()))
1004 // Query parses RawQuery and returns the corresponding values.
1005 // It silently discards malformed value pairs.
1006 // To check errors use ParseQuery.
1007 func (u
*URL
) Query() Values
{
1008 v
, _
:= ParseQuery(u
.RawQuery
)
1012 // RequestURI returns the encoded path?query or opaque?query
1013 // string that would be used in an HTTP request for u.
1014 func (u
*URL
) RequestURI() string {
1017 result
= u
.EscapedPath()
1022 if strings
.HasPrefix(result
, "//") {
1023 result
= u
.Scheme
+ ":" + result
1026 if u
.ForceQuery || u
.RawQuery
!= "" {
1027 result
+= "?" + u
.RawQuery
1032 // Hostname returns u.Host, without any port number.
1034 // If Host is an IPv6 literal with a port number, Hostname returns the
1035 // IPv6 literal without the square brackets. IPv6 literals may include
1036 // a zone identifier.
1037 func (u
*URL
) Hostname() string {
1038 return stripPort(u
.Host
)
1041 // Port returns the port part of u.Host, without the leading colon.
1042 // If u.Host doesn't contain a port, Port returns an empty string.
1043 func (u
*URL
) Port() string {
1044 return portOnly(u
.Host
)
1047 func stripPort(hostport
string) string {
1048 colon
:= strings
.IndexByte(hostport
, ':')
1052 if i
:= strings
.IndexByte(hostport
, ']'); i
!= -1 {
1053 return strings
.TrimPrefix(hostport
[:i
], "[")
1055 return hostport
[:colon
]
1058 func portOnly(hostport
string) string {
1059 colon
:= strings
.IndexByte(hostport
, ':')
1063 if i
:= strings
.Index(hostport
, "]:"); i
!= -1 {
1064 return hostport
[i
+len("]:"):]
1066 if strings
.Contains(hostport
, "]") {
1069 return hostport
[colon
+len(":"):]
1072 // Marshaling interface implementations.
1073 // Would like to implement MarshalText/UnmarshalText but that will change the JSON representation of URLs.
1075 func (u
*URL
) MarshalBinary() (text
[]byte, err error
) {
1076 return []byte(u
.String()), nil
1079 func (u
*URL
) UnmarshalBinary(text
[]byte) error
{
1080 u1
, err
:= Parse(string(text
))
1088 // validUserinfo reports whether s is a valid userinfo string per RFC 3986
1090 // userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
1091 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
1092 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
1093 // / "*" / "+" / "," / ";" / "="
1095 // It doesn't validate pct-encoded. The caller does that via func unescape.
1096 func validUserinfo(s
string) bool {
1097 for _
, r
:= range s
{
1098 if 'A' <= r
&& r
<= 'Z' {
1101 if 'a' <= r
&& r
<= 'z' {
1104 if '0' <= r
&& r
<= '9' {
1108 case '-', '.', '_', ':', '~', '!', '$', '&', '\'',
1109 '(', ')', '*', '+', ',', ';', '=', '%', '@':