Golang url error

Package url parses URLs and implements query escaping.

Package url parses URLs and implements query escaping.

  • func JoinPath(base string, elem …string) (result string, err error)
  • func PathEscape(s string) string
  • func PathUnescape(s string) (string, error)
  • func QueryEscape(s string) string
  • func QueryUnescape(s string) (string, error)
  • type Error
    • func (e *Error) Error() string
    • func (e *Error) Temporary() bool
    • func (e *Error) Timeout() bool
    • func (e *Error) Unwrap() error
  • type EscapeError
    • func (e EscapeError) Error() string
  • type InvalidHostError
    • func (e InvalidHostError) Error() string
  • type URL
    • func Parse(rawURL string) (*URL, error)
    • func ParseRequestURI(rawURL string) (*URL, error)
    • func (u *URL) EscapedFragment() string
    • func (u *URL) EscapedPath() string
    • func (u *URL) Hostname() string
    • func (u *URL) IsAbs() bool
    • func (u *URL) JoinPath(elem …string) *URL
    • func (u *URL) MarshalBinary() (text []byte, err error)
    • func (u *URL) Parse(ref string) (*URL, error)
    • func (u *URL) Port() string
    • func (u *URL) Query() Values
    • func (u *URL) Redacted() string
    • func (u *URL) RequestURI() string
    • func (u *URL) ResolveReference(ref *URL) *URL
    • func (u *URL) String() string
    • func (u *URL) UnmarshalBinary(text []byte) error
  • type Userinfo
    • func User(username string) *Userinfo
    • func UserPassword(username, password string) *Userinfo
    • func (u *Userinfo) Password() (string, bool)
    • func (u *Userinfo) String() string
    • func (u *Userinfo) Username() string
  • type Values
    • func ParseQuery(query string) (Values, error)
    • func (v Values) Add(key, value string)
    • func (v Values) Del(key string)
    • func (v Values) Encode() string
    • func (v Values) Get(key string) string
    • func (v Values) Has(key string) bool
    • func (v Values) Set(key, value string)
  • ParseQuery
  • PathEscape
  • PathUnescape
  • QueryEscape
  • QueryUnescape
  • URL
  • URL (Roundtrip)
  • URL.EscapedFragment
  • URL.EscapedPath
  • URL.Hostname
  • URL.IsAbs
  • URL.MarshalBinary
  • URL.Parse
  • URL.Port
  • URL.Query
  • URL.Redacted
  • URL.RequestURI
  • URL.ResolveReference
  • URL.String
  • URL.UnmarshalBinary
  • Values
  • Values.Add
  • Values.Del
  • Values.Encode
  • Values.Get
  • Values.Has
  • Values.Set

This section is empty.

This section is empty.

JoinPath returns a URL string with the provided path elements joined to
the existing path of base and the resulting path cleaned of any ./ or ../ elements.

PathEscape escapes the string so it can be safely placed inside a URL path segment,
replacing special characters (including /) with %XX sequences as needed.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	path := url.PathEscape("my/cool+blog&about,stuff")
	fmt.Println(path)

}
Output:

my%2Fcool+blog&about%2Cstuff

PathUnescape does the inverse transformation of PathEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB. It returns an error if any % is not followed
by two hexadecimal digits.

PathUnescape is identical to QueryUnescape except that it does not
unescape ‘+’ to ‘ ‘ (space).

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	escapedPath := "my%2Fcool+blog&about%2Cstuff"
	path, err := url.PathUnescape(escapedPath)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(path)

}
Output:

my/cool+blog&about,stuff

QueryEscape escapes the string so it can be safely placed
inside a URL query.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	query := url.QueryEscape("my/cool+blog&about,stuff")
	fmt.Println(query)

}
Output:

my%2Fcool%2Bblog%26about%2Cstuff

QueryUnescape does the inverse transformation of QueryEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB.
It returns an error if any % is not followed by two hexadecimal
digits.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	escapedQuery := "my%2Fcool%2Bblog%26about%2Cstuff"
	query, err := url.QueryUnescape(escapedQuery)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(query)

}
Output:

my/cool+blog&about,stuff

Error reports an error and the operation and URL that caused it.

func (e *Error) Temporary() bool

func (e *Error) Timeout() bool

A URL represents a parsed URL (technically, a URI reference).

The general form represented is:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URLs that do not start with a slash after the scheme are interpreted as:

scheme:opaque[?query][#fragment]

Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.
A consequence is that it is impossible to tell which slashes in the Path were
slashes in the raw URL and which were %2f. This distinction is rarely important,
but when it is, the code should use the EscapedPath method, which preserves
the original encoding of Path.

The RawPath field is an optional field which is only set when the default
encoding of Path is different from the escaped path. See the EscapedPath method
for more details.

URL’s String method uses the EscapedPath method to obtain the path.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("http://bing.com/search?q=dotnet")
	if err != nil {
		log.Fatal(err)
	}
	u.Scheme = "https"
	u.Host = "google.com"
	q := u.Query()
	q.Set("q", "golang")
	u.RawQuery = q.Encode()
	fmt.Println(u)
}
Output:

https://google.com/search?q=golang
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	// Parse + String preserve the original encoding.
	u, err := url.Parse("https://example.com/foo%2fbar")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Path)
	fmt.Println(u.RawPath)
	fmt.Println(u.String())
}
Output:

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

Parse parses a raw url into a URL structure.

The url may be relative (a path, without a host) or absolute
(starting with a scheme). Trying to parse a hostname and path
without a scheme is invalid but may not necessarily return an
error, due to parsing ambiguities.

ParseRequestURI parses a raw url into a URL structure. It assumes that
url was received in an HTTP request, so the url is interpreted
only as an absolute URI or an absolute path.
The string url is assumed not to have a #fragment suffix.
(Web browsers strip #fragment before sending the URL to a web server.)

func (u *URL) EscapedFragment() string

EscapedFragment returns the escaped form of u.Fragment.
In general there are multiple possible escaped forms of any fragment.
EscapedFragment returns u.RawFragment when it is a valid escaping of u.Fragment.
Otherwise EscapedFragment ignores u.RawFragment and computes an escaped
form on its own.
The String method uses EscapedFragment to construct its result.
In general, code should call EscapedFragment instead of
reading u.RawFragment directly.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("http://example.com/#x/y%2Fz")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Fragment:", u.Fragment)
	fmt.Println("RawFragment:", u.RawFragment)
	fmt.Println("EscapedFragment:", u.EscapedFragment())
}
Output:

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

EscapedPath returns the escaped form of u.Path.
In general there are multiple possible escaped forms of any path.
EscapedPath returns u.RawPath when it is a valid escaping of u.Path.
Otherwise EscapedPath ignores u.RawPath and computes an escaped
form on its own.
The String and RequestURI methods use EscapedPath to construct
their results.
In general, code should call EscapedPath instead of
reading u.RawPath directly.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("http://example.com/x/y%2Fz")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Path:", u.Path)
	fmt.Println("RawPath:", u.RawPath)
	fmt.Println("EscapedPath:", u.EscapedPath())
}
Output:

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

Hostname returns u.Host, stripping any valid port number if present.

If the result is enclosed in square brackets, as literal IPv6 addresses are,
the square brackets are removed from the result.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org:8000/path")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Hostname())
	u, err = url.Parse("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Hostname())
}
Output:

example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334
func (u *URL) IsAbs() bool

IsAbs reports whether the URL is absolute.
Absolute means that it has a non-empty scheme.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	u := url.URL{Host: "example.com", Path: "foo"}
	fmt.Println(u.IsAbs())
	u.Scheme = "http"
	fmt.Println(u.IsAbs())
}
Output:

false
true
func (u *URL) JoinPath(elem ...string) *URL

JoinPath returns a new URL with the provided path elements joined to
any existing path and the resulting path cleaned of any ./ or ../ elements.
Any sequences of multiple / characters will be reduced to a single /.

func (u *URL) MarshalBinary() (text []byte, err error)

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, _ := url.Parse("https://example.org")
	b, err := u.MarshalBinary()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%sn", b)
}
Output:

https://example.org

Parse parses a URL in the context of the receiver. The provided URL
may be relative or absolute. Parse returns nil, err on parse
failure, otherwise its return value is the same as ResolveReference.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org")
	if err != nil {
		log.Fatal(err)
	}
	rel, err := u.Parse("/foo")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(rel)
	_, err = u.Parse(":foo")
	if _, ok := err.(*url.Error); !ok {
		log.Fatal(err)
	}
}
Output:

https://example.org/foo

Port returns the port part of u.Host, without the leading colon.

If u.Host doesn’t contain a valid numeric port, Port returns an empty string.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Port())
	u, err = url.Parse("https://example.org:8080")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.Port())
}
Output:


8080
func (u *URL) Query() Values

Query parses RawQuery and returns the corresponding values.
It silently discards malformed value pairs.
To check errors use ParseQuery.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org/?a=1&a=2&b=&=3&&&&")
	if err != nil {
		log.Fatal(err)
	}
	q := u.Query()
	fmt.Println(q["a"])
	fmt.Println(q.Get("b"))
	fmt.Println(q.Get(""))
}
Output:

[1 2]

3

Redacted is like String but replaces any password with «xxxxx».
Only the password in u.URL is redacted.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	u := &url.URL{
		Scheme: "https",
		User:   url.UserPassword("user", "password"),
		Host:   "example.com",
		Path:   "foo/bar",
	}
	fmt.Println(u.Redacted())
	u.User = url.UserPassword("me", "newerPassword")
	fmt.Println(u.Redacted())
}
Output:

https://user:xxxxx@example.com/foo/bar
https://me:xxxxx@example.com/foo/bar

RequestURI returns the encoded path?query or opaque?query
string that would be used in an HTTP request for u.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("https://example.org/path?foo=bar")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(u.RequestURI())
}
Output:

/path?foo=bar
func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference resolves a URI reference to an absolute URI from
an absolute base URI u, per RFC 3986 Section 5.2. The URI reference
may be relative or absolute. ResolveReference always returns a new
URL instance, even if the returned URL is identical to either the
base or reference. If ref is an absolute URL, then ResolveReference
ignores base and returns a copy of ref.

package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u, err := url.Parse("../../..//search?q=dotnet")
	if err != nil {
		log.Fatal(err)
	}
	base, err := url.Parse("http://example.com/directory/")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(base.ResolveReference(u))
}
Output:

http://example.com/search?q=dotnet

String reassembles the URL into a valid URL string.
The general form of the result is one of:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

If u.Opaque is non-empty, String uses the first form;
otherwise it uses the second form.
Any non-ASCII characters in host are escaped.
To obtain the path, String uses u.EscapedPath().

In the second form, the following rules apply:

  • if u.Scheme is empty, scheme: is omitted.
  • if u.User is nil, userinfo@ is omitted.
  • if u.Host is empty, host/ is omitted.
  • if u.Scheme and u.Host are empty and u.User is nil,
    the entire scheme://userinfo@host/ is omitted.
  • if u.Host is non-empty and u.Path begins with a /,
    the form host/path does not add its own /.
  • if u.RawQuery is empty, ?query is omitted.
  • if u.Fragment is empty, #fragment is omitted.
package main

import (
	"fmt"
	"net/url"
)

func main() {
	u := &url.URL{
		Scheme:   "https",
		User:     url.UserPassword("me", "pass"),
		Host:     "example.com",
		Path:     "foo/bar",
		RawQuery: "x=1&y=2",
		Fragment: "anchor",
	}
	fmt.Println(u.String())
	u.Opaque = "opaque"
	fmt.Println(u.String())
}
Output:

https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor
package main

import (
	"fmt"
	"log"
	"net/url"
)

func main() {
	u := &url.URL{}
	err := u.UnmarshalBinary([]byte("https://example.org/foo"))
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%sn", u)
}
Output:

https://example.org/foo

The Userinfo type is an immutable encapsulation of username and
password details for a URL. An existing Userinfo value is guaranteed
to have a username set (potentially empty, as allowed by RFC 2396),
and optionally a password.

User returns a Userinfo containing the provided username
and no password set.

func UserPassword(username, password string) *Userinfo

UserPassword returns a Userinfo containing the provided username
and password.

This functionality should only be used with legacy web sites.
RFC 2396 warns that interpreting Userinfo this way
“is NOT RECOMMENDED, because the passing of authentication
information in clear text (such as URI) has proven to be a
security risk in almost every case where it has been used.”

Password returns the password in case it is set, and whether it is set.

String returns the encoded userinfo information in the standard form
of «username[:password]».

Username returns the username.

Values maps a string key to a list of values.
It is typically used for query parameters and form values.
Unlike in the http.Header map, the keys in a Values map
are case-sensitive.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Set("name", "Ava")
	v.Add("friend", "Jess")
	v.Add("friend", "Sarah")
	v.Add("friend", "Zoe")
	// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
	fmt.Println(v.Get("name"))
	fmt.Println(v.Get("friend"))
	fmt.Println(v["friend"])
}
Output:

Ava
Jess
[Jess Sarah Zoe]

ParseQuery parses the URL-encoded query string and returns
a map listing the values specified for each key.
ParseQuery always returns a non-nil map containing all the
valid query parameters found; err describes the first decoding error
encountered, if any.

Query is expected to be a list of key=value settings separated by ampersands.
A setting without an equals sign is interpreted as a key set to an empty
value.
Settings containing a non-URL-encoded semicolon are considered invalid.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/url"
	"strings"
)

func main() {
	m, err := url.ParseQuery(`x=1&y=2&y=3`)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(toJSON(m))
}

func toJSON(m any) string {
	js, err := json.Marshal(m)
	if err != nil {
		log.Fatal(err)
	}
	return strings.ReplaceAll(string(js), ",", ", ")
}
Output:

{"x":["1"], "y":["2", "3"]}

Add adds the value to key. It appends to any existing
values associated with key.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew")
	v.Add("cat sounds", "mau")
	fmt.Println(v["cat sounds"])

}
Output:

[meow mew mau]

Del deletes the values associated with key.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew")
	v.Add("cat sounds", "mau")
	fmt.Println(v["cat sounds"])

	v.Del("cat sounds")
	fmt.Println(v["cat sounds"])

}
Output:

[meow mew mau]
[]

Encode encodes the values into “URL encoded” form
(«bar=baz&foo=quux») sorted by key.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew/")
	v.Add("cat sounds", "mau$")
	fmt.Println(v.Encode())

}
Output:

cat+sounds=meow&cat+sounds=mew%2F&cat+sounds=mau%24

Get gets the first value associated with the given key.
If there are no values associated with the key, Get returns
the empty string. To access multiple values, use the map
directly.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew")
	v.Add("cat sounds", "mau")
	fmt.Printf("%qn", v.Get("cat sounds"))
	fmt.Printf("%qn", v.Get("dog sounds"))

}
Output:

"meow"
""

Has checks whether a given key is set.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew")
	v.Add("cat sounds", "mau")
	fmt.Println(v.Has("cat sounds"))
	fmt.Println(v.Has("dog sounds"))

}
Output:

true
false

Set sets the key to value. It replaces any existing
values.

package main

import (
	"fmt"
	"net/url"
)

func main() {
	v := url.Values{}
	v.Add("cat sounds", "meow")
	v.Add("cat sounds", "mew")
	v.Add("cat sounds", "mau")
	fmt.Println(v["cat sounds"])

	v.Set("cat sounds", "meow")
	fmt.Println(v["cat sounds"])

}
Output:

[meow mew mau]
[meow]

import "net/url"
Overview
Index
Examples

Overview ▸

Overview ▾

Package url parses URLs and implements query escaping.

Index ▸

Index ▾

func JoinPath(base string, elem …string) (result string, err error)
func PathEscape(s string) string
func PathUnescape(s string) (string, error)
func QueryEscape(s string) string
func QueryUnescape(s string) (string, error)
type Error
    func (e *Error) Error() string
    func (e *Error) Temporary() bool
    func (e *Error) Timeout() bool
    func (e *Error) Unwrap() error
type EscapeError
    func (e EscapeError) Error() string
type InvalidHostError
    func (e InvalidHostError) Error() string
type URL
    func Parse(rawURL string) (*URL, error)
    func ParseRequestURI(rawURL string) (*URL, error)
    func (u *URL) EscapedFragment() string
    func (u *URL) EscapedPath() string
    func (u *URL) Hostname() string
    func (u *URL) IsAbs() bool
    func (u *URL) JoinPath(elem …string) *URL
    func (u *URL) MarshalBinary() (text []byte, err error)
    func (u *URL) Parse(ref string) (*URL, error)
    func (u *URL) Port() string
    func (u *URL) Query() Values
    func (u *URL) Redacted() string
    func (u *URL) RequestURI() string
    func (u *URL) ResolveReference(ref *URL) *URL
    func (u *URL) String() string
    func (u *URL) UnmarshalBinary(text []byte) error
type Userinfo
    func User(username string) *Userinfo
    func UserPassword(username, password string) *Userinfo
    func (u *Userinfo) Password() (string, bool)
    func (u *Userinfo) String() string
    func (u *Userinfo) Username() string
type Values
    func ParseQuery(query string) (Values, error)
    func (v Values) Add(key, value string)
    func (v Values) Del(key string)
    func (v Values) Encode() string
    func (v Values) Get(key string) string
    func (v Values) Has(key string) bool
    func (v Values) Set(key, value string)

Package files

url.go

func JoinPath

1.19

func JoinPath(base string, elem ...string) (result string, err error)

JoinPath returns a URL string with the provided path elements joined to
the existing path of base and the resulting path cleaned of any ./ or ../ elements.

func PathEscape

1.8

func PathEscape(s string) string

PathEscape escapes the string so it can be safely placed inside a URL path segment,
replacing special characters (including /) with %XX sequences as needed.

Example

Example

my%2Fcool+blog&about%2Cstuff

func PathUnescape

1.8

func PathUnescape(s string) (string, error)

PathUnescape does the inverse transformation of PathEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB. It returns an error if any % is not followed
by two hexadecimal digits.

PathUnescape is identical to QueryUnescape except that it does not
unescape ‘+’ to ‘ ‘ (space).

func QueryEscape

func QueryEscape(s string) string

QueryEscape escapes the string so it can be safely placed
inside a URL query.

Example

Example

my%2Fcool%2Bblog%26about%2Cstuff

func QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape does the inverse transformation of QueryEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB.
It returns an error if any % is not followed by two hexadecimal
digits.

type Error

Error reports an error and the operation and URL that caused it.

type Error struct {
    Op  string
    URL string
    Err error
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Temporary

1.6

func (e *Error) Temporary() bool

func (*Error) Timeout

1.6

func (e *Error) Timeout() bool

func (*Error) Unwrap

1.13

func (e *Error) Unwrap() error

type EscapeError

type EscapeError string

func (EscapeError) Error

func (e EscapeError) Error() string

type InvalidHostError

1.6

type InvalidHostError string

func (InvalidHostError) Error

1.6

func (e InvalidHostError) Error() string

type URL

A URL represents a parsed URL (technically, a URI reference).

The general form represented is:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URLs that do not start with a slash after the scheme are interpreted as:

scheme:opaque[?query][#fragment]

Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.
A consequence is that it is impossible to tell which slashes in the Path were
slashes in the raw URL and which were %2f. This distinction is rarely important,
but when it is, the code should use the EscapedPath method, which preserves
the original encoding of Path.

The RawPath field is an optional field which is only set when the default
encoding of Path is different from the escaped path. See the EscapedPath method
for more details.

URL’s String method uses the EscapedPath method to obtain the path.

type URL struct {
    Scheme      string
    Opaque      string    
    User        *Userinfo 
    Host        string    
    Path        string    
    RawPath     string    
    OmitHost    bool      
    ForceQuery  bool      
    RawQuery    string    
    Fragment    string    
    RawFragment string    
}

Example

Example

https://google.com/search?q=golang

Example (Roundtrip)

Example (Roundtrip)

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

func Parse

func Parse(rawURL string) (*URL, error)

Parse parses a raw url into a URL structure.

The url may be relative (a path, without a host) or absolute
(starting with a scheme). Trying to parse a hostname and path
without a scheme is invalid but may not necessarily return an
error, due to parsing ambiguities.

func ParseRequestURI

func ParseRequestURI(rawURL string) (*URL, error)

ParseRequestURI parses a raw url into a URL structure. It assumes that
url was received in an HTTP request, so the url is interpreted
only as an absolute URI or an absolute path.
The string url is assumed not to have a #fragment suffix.
(Web browsers strip #fragment before sending the URL to a web server.)

func (*URL) EscapedFragment

1.15

func (u *URL) EscapedFragment() string

EscapedFragment returns the escaped form of u.Fragment.
In general there are multiple possible escaped forms of any fragment.
EscapedFragment returns u.RawFragment when it is a valid escaping of u.Fragment.
Otherwise EscapedFragment ignores u.RawFragment and computes an escaped
form on its own.
The String method uses EscapedFragment to construct its result.
In general, code should call EscapedFragment instead of
reading u.RawFragment directly.

Example

Example

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

func (*URL) EscapedPath

1.5

func (u *URL) EscapedPath() string

EscapedPath returns the escaped form of u.Path.
In general there are multiple possible escaped forms of any path.
EscapedPath returns u.RawPath when it is a valid escaping of u.Path.
Otherwise EscapedPath ignores u.RawPath and computes an escaped
form on its own.
The String and RequestURI methods use EscapedPath to construct
their results.
In general, code should call EscapedPath instead of
reading u.RawPath directly.

Example

Example

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

func (*URL) Hostname

1.8

func (u *URL) Hostname() string

Hostname returns u.Host, stripping any valid port number if present.

If the result is enclosed in square brackets, as literal IPv6 addresses are,
the square brackets are removed from the result.

Example

Example

example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334

func (*URL) IsAbs

func (u *URL) IsAbs() bool

IsAbs reports whether the URL is absolute.
Absolute means that it has a non-empty scheme.

func (*URL) JoinPath

1.19

func (u *URL) JoinPath(elem ...string) *URL

JoinPath returns a new URL with the provided path elements joined to
any existing path and the resulting path cleaned of any ./ or ../ elements.
Any sequences of multiple / characters will be reduced to a single /.

func (*URL) MarshalBinary

1.8

func (u *URL) MarshalBinary() (text []byte, err error)

func (*URL) Parse

func (u *URL) Parse(ref string) (*URL, error)

Parse parses a URL in the context of the receiver. The provided URL
may be relative or absolute. Parse returns nil, err on parse
failure, otherwise its return value is the same as ResolveReference.

func (*URL) Port

1.8

func (u *URL) Port() string

Port returns the port part of u.Host, without the leading colon.

If u.Host doesn’t contain a valid numeric port, Port returns an empty string.

func (*URL) Query

func (u *URL) Query() Values

Query parses RawQuery and returns the corresponding values.
It silently discards malformed value pairs.
To check errors use ParseQuery.

func (*URL) Redacted

1.15

func (u *URL) Redacted() string

Redacted is like String but replaces any password with «xxxxx».
Only the password in u.URL is redacted.

Example

Example

https://user:xxxxx@example.com/foo/bar
https://me:xxxxx@example.com/foo/bar

func (*URL) RequestURI

func (u *URL) RequestURI() string

RequestURI returns the encoded path?query or opaque?query
string that would be used in an HTTP request for u.

func (*URL) ResolveReference

func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference resolves a URI reference to an absolute URI from
an absolute base URI u, per RFC 3986 Section 5.2. The URI reference
may be relative or absolute. ResolveReference always returns a new
URL instance, even if the returned URL is identical to either the
base or reference. If ref is an absolute URL, then ResolveReference
ignores base and returns a copy of ref.

Example

Example

http://example.com/search?q=dotnet

func (*URL) String

func (u *URL) String() string

String reassembles the URL into a valid URL string.
The general form of the result is one of:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

If u.Opaque is non-empty, String uses the first form;
otherwise it uses the second form.
Any non-ASCII characters in host are escaped.
To obtain the path, String uses u.EscapedPath().

In the second form, the following rules apply:

  • if u.Scheme is empty, scheme: is omitted.
  • if u.User is nil, userinfo@ is omitted.
  • if u.Host is empty, host/ is omitted.
  • if u.Scheme and u.Host are empty and u.User is nil,
    the entire scheme://userinfo@host/ is omitted.
  • if u.Host is non-empty and u.Path begins with a /,
    the form host/path does not add its own /.
  • if u.RawQuery is empty, ?query is omitted.
  • if u.Fragment is empty, #fragment is omitted.

Example

Example

https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor

func (*URL) UnmarshalBinary

1.8

func (u *URL) UnmarshalBinary(text []byte) error

type Userinfo

The Userinfo type is an immutable encapsulation of username and
password details for a URL. An existing Userinfo value is guaranteed
to have a username set (potentially empty, as allowed by RFC 2396),
and optionally a password.

type Userinfo struct {
    
}

func User

func User(username string) *Userinfo

User returns a Userinfo containing the provided username
and no password set.

func UserPassword

func UserPassword(username, password string) *Userinfo

UserPassword returns a Userinfo containing the provided username
and password.

This functionality should only be used with legacy web sites.
RFC 2396 warns that interpreting Userinfo this way
“is NOT RECOMMENDED, because the passing of authentication
information in clear text (such as URI) has proven to be a
security risk in almost every case where it has been used.”

func (*Userinfo) Password

func (u *Userinfo) Password() (string, bool)

Password returns the password in case it is set, and whether it is set.

func (*Userinfo) String

func (u *Userinfo) String() string

String returns the encoded userinfo information in the standard form
of «username[:password]».

func (*Userinfo) Username

func (u *Userinfo) Username() string

Username returns the username.

type Values

Values maps a string key to a list of values.
It is typically used for query parameters and form values.
Unlike in the http.Header map, the keys in a Values map
are case-sensitive.

type Values map[string][]string

Example

Example

Ava
Jess
[Jess Sarah Zoe]

func ParseQuery

func ParseQuery(query string) (Values, error)

ParseQuery parses the URL-encoded query string and returns
a map listing the values specified for each key.
ParseQuery always returns a non-nil map containing all the
valid query parameters found; err describes the first decoding error
encountered, if any.

Query is expected to be a list of key=value settings separated by ampersands.
A setting without an equals sign is interpreted as a key set to an empty
value.
Settings containing a non-URL-encoded semicolon are considered invalid.

Example

Example

{"x":["1"], "y":["2", "3"]}

func (Values) Add

func (v Values) Add(key, value string)

Add adds the value to key. It appends to any existing
values associated with key.

func (Values) Del

func (v Values) Del(key string)

Del deletes the values associated with key.

func (Values) Encode

func (v Values) Encode() string

Encode encodes the values into “URL encoded” form
(«bar=baz&foo=quux») sorted by key.

Example

Example

cat+sounds=meow&cat+sounds=mew%2F&cat+sounds=mau%24

func (Values) Get

func (v Values) Get(key string) string

Get gets the first value associated with the given key.
If there are no values associated with the key, Get returns
the empty string. To access multiple values, use the map
directly.

func (Values) Has

1.17

func (v Values) Has(key string) bool

Has checks whether a given key is set.

func (Values) Set

func (v Values) Set(key, value string)

Set sets the key to value. It replaces any existing
values.

Package url

import "net/url"
Overview
Index
Examples

Overview ▹

Overview ▾

Package url parses URLs and implements query escaping.

Index ▹

Index ▾

func PathEscape(s string) string
func PathUnescape(s string) (string, error)
func QueryEscape(s string) string
func QueryUnescape(s string) (string, error)
type Error
    func (e *Error) Error() string
    func (e *Error) Temporary() bool
    func (e *Error) Timeout() bool
    func (e *Error) Unwrap() error
type EscapeError
    func (e EscapeError) Error() string
type InvalidHostError
    func (e InvalidHostError) Error() string
type URL
    func Parse(rawURL string) (*URL, error)
    func ParseRequestURI(rawURL string) (*URL, error)
    func (u *URL) EscapedFragment() string
    func (u *URL) EscapedPath() string
    func (u *URL) Hostname() string
    func (u *URL) IsAbs() bool
    func (u *URL) MarshalBinary() (text []byte, err error)
    func (u *URL) Parse(ref string) (*URL, error)
    func (u *URL) Port() string
    func (u *URL) Query() Values
    func (u *URL) Redacted() string
    func (u *URL) RequestURI() string
    func (u *URL) ResolveReference(ref *URL) *URL
    func (u *URL) String() string
    func (u *URL) UnmarshalBinary(text []byte) error
type Userinfo
    func User(username string) *Userinfo
    func UserPassword(username, password string) *Userinfo
    func (u *Userinfo) Password() (string, bool)
    func (u *Userinfo) String() string
    func (u *Userinfo) Username() string
type Values
    func ParseQuery(query string) (Values, error)
    func (v Values) Add(key, value string)
    func (v Values) Del(key string)
    func (v Values) Encode() string
    func (v Values) Get(key string) string
    func (v Values) Has(key string) bool
    func (v Values) Set(key, value string)

Package files

url.go

func PathEscape

1.8

func PathEscape(s string) string

PathEscape escapes the string so it can be safely placed inside a URL path segment,
replacing special characters (including /) with %XX sequences as needed.

func PathUnescape

1.8

func PathUnescape(s string) (string, error)

PathUnescape does the inverse transformation of PathEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB. It returns an error if any % is not followed
by two hexadecimal digits.

PathUnescape is identical to QueryUnescape except that it does not
unescape ‘+’ to ‘ ‘ (space).

func QueryEscape

func QueryEscape(s string) string

QueryEscape escapes the string so it can be safely placed
inside a URL query.

func QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape does the inverse transformation of QueryEscape,
converting each 3-byte encoded substring of the form «%AB» into the
hex-decoded byte 0xAB.
It returns an error if any % is not followed by two hexadecimal
digits.

type Error

Error reports an error and the operation and URL that caused it.

type Error struct {
    Op  string
    URL string
    Err error
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Temporary

1.6

func (e *Error) Temporary() bool

func (*Error) Timeout

1.6

func (e *Error) Timeout() bool

func (*Error) Unwrap

1.13

func (e *Error) Unwrap() error

type EscapeError

type EscapeError string

func (EscapeError) Error

func (e EscapeError) Error() string

type InvalidHostError

1.6

type InvalidHostError string

func (InvalidHostError) Error

1.6

func (e InvalidHostError) Error() string

type URL

A URL represents a parsed URL (technically, a URI reference).

The general form represented is:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URLs that do not start with a slash after the scheme are interpreted as:

scheme:opaque[?query][#fragment]

Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.
A consequence is that it is impossible to tell which slashes in the Path were
slashes in the raw URL and which were %2f. This distinction is rarely important,
but when it is, the code should use RawPath, an optional field which only gets
set if the default encoding is different from Path.

URL’s String method uses the EscapedPath method to obtain the path. See the
EscapedPath method for more details.

type URL struct {
    Scheme      string
    Opaque      string    
    User        *Userinfo 
    Host        string    
    Path        string    
    RawPath     string    
    ForceQuery  bool      
    RawQuery    string    
    Fragment    string    
    RawFragment string    
}

Example

Example

https://google.com/search?q=golang

Example (Roundtrip)

Example (Roundtrip)

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

func Parse

func Parse(rawURL string) (*URL, error)

Parse parses a raw url into a URL structure.

The url may be relative (a path, without a host) or absolute
(starting with a scheme). Trying to parse a hostname and path
without a scheme is invalid but may not necessarily return an
error, due to parsing ambiguities.

func ParseRequestURI

func ParseRequestURI(rawURL string) (*URL, error)

ParseRequestURI parses a raw url into a URL structure. It assumes that
url was received in an HTTP request, so the url is interpreted
only as an absolute URI or an absolute path.
The string url is assumed not to have a #fragment suffix.
(Web browsers strip #fragment before sending the URL to a web server.)

func (*URL) EscapedFragment

1.15

func (u *URL) EscapedFragment() string

EscapedFragment returns the escaped form of u.Fragment.
In general there are multiple possible escaped forms of any fragment.
EscapedFragment returns u.RawFragment when it is a valid escaping of u.Fragment.
Otherwise EscapedFragment ignores u.RawFragment and computes an escaped
form on its own.
The String method uses EscapedFragment to construct its result.
In general, code should call EscapedFragment instead of
reading u.RawFragment directly.

Example

Example

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

func (*URL) EscapedPath

1.5

func (u *URL) EscapedPath() string

EscapedPath returns the escaped form of u.Path.
In general there are multiple possible escaped forms of any path.
EscapedPath returns u.RawPath when it is a valid escaping of u.Path.
Otherwise EscapedPath ignores u.RawPath and computes an escaped
form on its own.
The String and RequestURI methods use EscapedPath to construct
their results.
In general, code should call EscapedPath instead of
reading u.RawPath directly.

Example

Example

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

func (*URL) Hostname

1.8

func (u *URL) Hostname() string

Hostname returns u.Host, stripping any valid port number if present.

If the result is enclosed in square brackets, as literal IPv6 addresses are,
the square brackets are removed from the result.

Example

Example

example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334

func (*URL) IsAbs

func (u *URL) IsAbs() bool

IsAbs reports whether the URL is absolute.
Absolute means that it has a non-empty scheme.

func (*URL) MarshalBinary

1.8

func (u *URL) MarshalBinary() (text []byte, err error)

func (*URL) Parse

func (u *URL) Parse(ref string) (*URL, error)

Parse parses a URL in the context of the receiver. The provided URL
may be relative or absolute. Parse returns nil, err on parse
failure, otherwise its return value is the same as ResolveReference.

func (*URL) Port

1.8

func (u *URL) Port() string

Port returns the port part of u.Host, without the leading colon.

If u.Host doesn’t contain a valid numeric port, Port returns an empty string.

func (*URL) Query

func (u *URL) Query() Values

Query parses RawQuery and returns the corresponding values.
It silently discards malformed value pairs.
To check errors use ParseQuery.

func (*URL) Redacted

1.15

func (u *URL) Redacted() string

Redacted is like String but replaces any password with «xxxxx».
Only the password in u.URL is redacted.

Example

Example

https://user:xxxxx@example.com/foo/bar
https://me:xxxxx@example.com/foo/bar

func (*URL) RequestURI

func (u *URL) RequestURI() string

RequestURI returns the encoded path?query or opaque?query
string that would be used in an HTTP request for u.

func (*URL) ResolveReference

func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference resolves a URI reference to an absolute URI from
an absolute base URI u, per RFC 3986 Section 5.2. The URI reference
may be relative or absolute. ResolveReference always returns a new
URL instance, even if the returned URL is identical to either the
base or reference. If ref is an absolute URL, then ResolveReference
ignores base and returns a copy of ref.

Example

Example

http://example.com/search?q=dotnet

func (*URL) String

func (u *URL) String() string

String reassembles the URL into a valid URL string.
The general form of the result is one of:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

If u.Opaque is non-empty, String uses the first form;
otherwise it uses the second form.
Any non-ASCII characters in host are escaped.
To obtain the path, String uses u.EscapedPath().

In the second form, the following rules apply:

- if u.Scheme is empty, scheme: is omitted.
- if u.User is nil, userinfo@ is omitted.
- if u.Host is empty, host/ is omitted.
- if u.Scheme and u.Host are empty and u.User is nil,
   the entire scheme://userinfo@host/ is omitted.
- if u.Host is non-empty and u.Path begins with a /,
   the form host/path does not add its own /.
- if u.RawQuery is empty, ?query is omitted.
- if u.Fragment is empty, #fragment is omitted.

Example

Example

https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor

func (*URL) UnmarshalBinary

1.8

func (u *URL) UnmarshalBinary(text []byte) error

type Userinfo

The Userinfo type is an immutable encapsulation of username and
password details for a URL. An existing Userinfo value is guaranteed
to have a username set (potentially empty, as allowed by RFC 2396),
and optionally a password.

type Userinfo struct {
    
}

func User

func User(username string) *Userinfo

User returns a Userinfo containing the provided username
and no password set.

func UserPassword

func UserPassword(username, password string) *Userinfo

UserPassword returns a Userinfo containing the provided username
and password.

This functionality should only be used with legacy web sites.
RFC 2396 warns that interpreting Userinfo this way
“is NOT RECOMMENDED, because the passing of authentication
information in clear text (such as URI) has proven to be a
security risk in almost every case where it has been used.”

func (*Userinfo) Password

func (u *Userinfo) Password() (string, bool)

Password returns the password in case it is set, and whether it is set.

func (*Userinfo) String

func (u *Userinfo) String() string

String returns the encoded userinfo information in the standard form
of «username[:password]».

func (*Userinfo) Username

func (u *Userinfo) Username() string

Username returns the username.

type Values

Values maps a string key to a list of values.
It is typically used for query parameters and form values.
Unlike in the http.Header map, the keys in a Values map
are case-sensitive.

type Values map[string][]string

Example

Example

Ava
Jess
[Jess Sarah Zoe]

func ParseQuery

func ParseQuery(query string) (Values, error)

ParseQuery parses the URL-encoded query string and returns
a map listing the values specified for each key.
ParseQuery always returns a non-nil map containing all the
valid query parameters found; err describes the first decoding error
encountered, if any.

Query is expected to be a list of key=value settings separated by ampersands.
A setting without an equals sign is interpreted as a key set to an empty
value.
Settings containing a non-URL-encoded semicolon are considered invalid.

Example

Example

{"x":["1"], "y":["2", "3"]}

func (Values) Add

func (v Values) Add(key, value string)

Add adds the value to key. It appends to any existing
values associated with key.

func (Values) Del

func (v Values) Del(key string)

Del deletes the values associated with key.

func (Values) Encode

func (v Values) Encode() string

Encode encodes the values into “URL encoded” form
(«bar=baz&foo=quux») sorted by key.

func (Values) Get

func (v Values) Get(key string) string

Get gets the first value associated with the given key.
If there are no values associated with the key, Get returns
the empty string. To access multiple values, use the map
directly.

func (Values) Has

1.17

func (v Values) Has(key string) bool

Has checks whether a given key is set.

func (Values) Set

func (v Values) Set(key, value string)

Set sets the key to value. It replaces any existing
values.

// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package url parses URLs and implements query escaping. package url // See RFC 3986. This package generally follows RFC 3986, except where // it deviates for compatibility reasons. When sending changes, first // search old issues for history on decisions. Unit tests should also // contain references to issue numbers with details. import ( «errors» «fmt» «path» «sort» «strconv» «strings» ) // Error reports an error and the operation and URL that caused it. type Error struct { Op string URL string Err error } func (e *Error) Unwrap() error { return e.Err } func (e *Error) Error() string { return fmt.Sprintf(«%s %q: %s», e.Op, e.URL, e.Err) } func (e *Error) Timeout() bool { t, ok := e.Err.(interface { Timeout() bool }) return ok && t.Timeout() } func (e *Error) Temporary() bool { t, ok := e.Err.(interface { Temporary() bool }) return ok && t.Temporary() } const upperhex = «0123456789ABCDEF» func ishex(c byte) bool { switch { case ‘0’ <= c && c <= ‘9’: return true case ‘a’ <= c && c <= ‘f’: return true case ‘A’ <= c && c <= ‘F’: return true } return false } 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 } type encoding int const ( encodePath encoding = 1 + iota encodePathSegment encodeHost encodeZone encodeUserPassword encodeQueryComponent encodeFragment ) 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» } // 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 golang.org/issue/5684. 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 } } switch c { case ‘-‘, ‘_’, ‘.’, ‘~’: // §2.3 Unreserved characters (mark) return false case ‘$’, ‘&’, ‘+’, ‘,’, ‘/’, ‘:’, ‘;’, ‘=’, ‘?’, ‘@’: // §2.2 Reserved characters (reserved) // Different sections of the URL allow a few of // the reserved characters to appear unescaped. switch mode { case encodePath: // §3.3 // The RFC allows : @ & = + $ but saves / ; , for assigning // meaning to individual path segments. This package // only manipulates the path as a whole, so we allow those // last three as well. That leaves only ? to escape. return c == ‘?’ case encodePathSegment: // §3.3 // The RFC allows : @ & = + $ but saves / ; , for assigning // meaning to individual path segments. return c == ‘/’ || c == ‘;’ || c == ‘,’ || c == ‘?’ case encodeUserPassword: // §3.2.1 // The RFC allows ‘;’, ‘:’, ‘&’, ‘=’, ‘+’, ‘$’, and ‘,’ in // userinfo, so we must escape only ‘@’, ‘/’, and ‘?’. // The parsing of userinfo treats ‘:’ as special so we must escape // that too. return c == ‘@’ || c == ‘/’ || c == ‘?’ || c == ‘:’ case encodeQueryComponent: // §3.4 // The RFC reserves (so we must escape) everything. return true case encodeFragment: // §4.1 // The RFC text is silent but the grammar allows // everything, so escape nothing. return false } } if mode == encodeFragment { // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not // need to be escaped. To minimize potential breakage, we apply two restrictions: // (1) we always escape sub-delims outside of the fragment, and (2) we always // escape single quote to avoid breaking callers that had previously assumed that // single quotes would be escaped. See issue #19917. switch c { case ‘!’, ‘(‘, ‘)’, ‘*’: return false } } // Everything else must be escaped. return true } // QueryUnescape does the inverse transformation of QueryEscape, // converting each 3-byte encoded substring of the form «%AB» into the // hex-decoded byte 0xAB. // It returns an error if any % is not followed by two hexadecimal // digits. func QueryUnescape(s string) (string, error) { return unescape(s, encodeQueryComponent) } // PathUnescape does the inverse transformation of PathEscape, // converting each 3-byte encoded substring of the form «%AB» into the // hex-decoded byte 0xAB. It returns an error if any % is not followed // by two hexadecimal digits. // // PathUnescape is identical to QueryUnescape except that it does not // unescape ‘+’ to ‘ ‘ (space). func PathUnescape(s string) (string, error) { return unescape(s, encodePathSegment) } // unescape unescapes a string; the mode specifies // which section of the URL string is being unescaped. func unescape(s string, mode encoding) (string, error) { // Count %, check that they’re well-formed. n := 0 hasPlus := false 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 «», 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 && s[i:i+3] != «%25» { return «», 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 s[i:i+3] != «%25» && v != ‘ ‘ && shouldEscape(v, encodeHost) { return «», EscapeError(s[i : i+3]) } } i += 3 case ‘+’: hasPlus = mode == encodeQueryComponent i++ default: if (mode == encodeHost || mode == encodeZone) && s[i] < 0x80 && shouldEscape(s[i], mode) { return «», InvalidHostError(s[i : i+1]) } i++ } } if n == 0 && !hasPlus { return s, nil } var t strings.Builder t.Grow(len(s) 2*n) for i := 0; i < len(s); i++ { switch s[i] { case ‘%’: t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2])) i += 2 case ‘+’: if mode == encodeQueryComponent { t.WriteByte(‘ ‘) } else { t.WriteByte(‘+’) } default: t.WriteByte(s[i]) } } return t.String(), nil } // QueryEscape escapes the string so it can be safely placed // inside a URL query. func QueryEscape(s string) string { return escape(s, encodeQueryComponent) } // PathEscape escapes the string so it can be safely placed inside a URL path segment, // replacing special characters (including /) with %XX sequences as needed. func PathEscape(s string) string { return escape(s, encodePathSegment) } func escape(s string, mode encoding) string { spaceCount, hexCount := 0, 0 for i := 0; i < len(s); i++ { c := s[i] if shouldEscape(c, mode) { if c == ‘ ‘ && mode == encodeQueryComponent { spaceCount++ } else { hexCount++ } } } if spaceCount == 0 && hexCount == 0 { return s } var buf [64]byte var t []byte required := len(s) + 2*hexCount if required <= len(buf) { t = buf[:required] } else { t = make([]byte, required) } if hexCount == 0 { copy(t, s) for i := 0; i < len(s); i++ { if s[i] == ‘ ‘ { t[i] = ‘+’ } } return string(t) } j := 0 for i := 0; i < len(s); i++ { switch c := s[i]; { case c == ‘ ‘ && mode == encodeQueryComponent: t[j] = ‘+’ j++ case shouldEscape(c, mode): t[j] = ‘%’ t[j+1] = upperhex[c>>4] t[j+2] = upperhex[c&15] j += 3 default: t[j] = s[i] j++ } } return string(t) } // A URL represents a parsed URL (technically, a URI reference). // // The general form represented is: // // [scheme:][//[userinfo@]host][/]path[?query][#fragment] // // URLs that do not start with a slash after the scheme are interpreted as: // // scheme:opaque[?query][#fragment] // // Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/. // A consequence is that it is impossible to tell which slashes in the Path were // slashes in the raw URL and which were %2f. This distinction is rarely important, // but when it is, the code should use the EscapedPath method, which preserves // the original encoding of Path. // // The RawPath field is an optional field which is only set when the default // encoding of Path is different from the escaped path. See the EscapedPath method // for more details. // // URL’s String method uses the EscapedPath method to obtain the path. type URL struct { Scheme string Opaque string // encoded opaque data User *Userinfo // username and password information Host string // host or host:port Path string // path (relative paths may omit leading slash) RawPath string // encoded path hint (see EscapedPath method) OmitHost bool // do not emit empty host (authority) ForceQuery bool // append a query (‘?’) even if RawQuery is empty RawQuery string // encoded query values, without ‘?’ Fragment string // fragment for references, without ‘#’ RawFragment string // encoded fragment hint (see EscapedFragment method) } // User returns a Userinfo containing the provided username // and no password set. func User(username string) *Userinfo { return &Userinfo{username, «», false} } // UserPassword returns a Userinfo containing the provided username // and password. // // This functionality should only be used with legacy web sites. // RFC 2396 warns that interpreting Userinfo this way // “is NOT RECOMMENDED, because the passing of authentication // information in clear text (such as URI) has proven to be a // security risk in almost every case where it has been used.” func UserPassword(username, password string) *Userinfo { return &Userinfo{username, password, true} } // The Userinfo type is an immutable encapsulation of username and // password details for a URL. An existing Userinfo value is guaranteed // to have a username set (potentially empty, as allowed by RFC 2396), // and optionally a password. type Userinfo struct { username string password string passwordSet bool } // Username returns the username. func (u *Userinfo) Username() string { if u == nil { return «» } return u.username } // Password returns the password in case it is set, and whether it is set. func (u *Userinfo) Password() (string, bool) { if u == nil { return «», false } return u.password, u.passwordSet } // String returns the encoded userinfo information in the standard form // of «username[:password]». func (u *Userinfo) String() string { if u == nil { return «» } s := escape(u.username, encodeUserPassword) if u.passwordSet { s += «:» + escape(u.password, encodeUserPassword) } return s } // Maybe rawURL is of the form scheme:path. // (Scheme must be [a-zA-Z][a-zA-Z0-9+.-]*) // If so, return scheme, path; else return «», rawURL. func getScheme(rawURL string) (scheme, path string, err error) { for i := 0; i < len(rawURL); i++ { c := rawURL[i] switch { case ‘a’ <= c && c <= ‘z’ || ‘A’ <= c && c <= ‘Z’: // do nothing case ‘0’ <= c && c <= ‘9’ || c == ‘+’ || c == ‘-‘ || c == ‘.’: if i == 0 { return «», rawURL, nil } case c == ‘:’: if i == 0 { return «», «», errors.New(«missing protocol scheme») } return rawURL[:i], rawURL[i+1:], nil default: // we have encountered an invalid character, // so there is no valid scheme return «», rawURL, nil } } return «», rawURL, nil } // Parse parses a raw url into a URL structure. // // The url may be relative (a path, without a host) or absolute // (starting with a scheme). Trying to parse a hostname and path // without a scheme is invalid but may not necessarily return an // error, due to parsing ambiguities. func Parse(rawURL string) (*URL, error) { // Cut off #frag u, frag, _ := strings.Cut(rawURL, «#») url, err := parse(u, false) if err != nil { return nil, &Error{«parse», u, err} } if frag == «» { return url, nil } if err = url.setFragment(frag); err != nil { return nil, &Error{«parse», rawURL, err} } return url, nil } // ParseRequestURI parses a raw url into a URL structure. It assumes that // url was received in an HTTP request, so the url is interpreted // only as an absolute URI or an absolute path. // The string url is assumed not to have a #fragment suffix. // (Web browsers strip #fragment before sending the URL to a web server.) func ParseRequestURI(rawURL string) (*URL, error) { url, err := parse(rawURL, true) if err != nil { return nil, &Error{«parse», rawURL, err} } return url, nil } // parse parses a URL from a string in one of two contexts. If // viaRequest is true, the URL is assumed to have arrived via an HTTP request, // in which case only absolute URLs or path-absolute relative URLs are allowed. // If viaRequest is false, all forms of relative URLs are allowed. func parse(rawURL string, viaRequest bool) (*URL, error) { var rest string var err error if stringContainsCTLByte(rawURL) { return nil, errors.New(«net/url: invalid control character in URL») } if rawURL == «» && viaRequest { return nil, errors.New(«empty url») } url := new(URL) if rawURL == «*» { url.Path = «*» return url, nil } // Split off possible leading «http:», «mailto:», etc. // Cannot contain escaped characters. if url.Scheme, rest, err = getScheme(rawURL); err != nil { return nil, err } url.Scheme = strings.ToLower(url.Scheme) if strings.HasSuffix(rest, «?») && strings.Count(rest, «?») == 1 { url.ForceQuery = true rest = rest[:len(rest)1] } else { rest, url.RawQuery, _ = strings.Cut(rest, «?») } if !strings.HasPrefix(rest, «/») { if url.Scheme != «» { // We consider rootless paths per RFC 3986 as opaque. url.Opaque = rest return url, nil } if viaRequest { return nil, errors.New(«invalid URI for request») } // Avoid confusion with malformed schemes, like cache_object:foo/bar. // See golang.org/issue/16822. // // RFC 3986, §3.3: // In addition, a URI reference (Section 4.1) may be a relative-path reference, // in which case the first path segment cannot contain a colon («:») character. if segment, _, _ := strings.Cut(rest, «/»); strings.Contains(segment, «:») { // First path segment has colon. Not allowed in relative URL. return nil, errors.New(«first path segment in URL cannot contain colon») } } if (url.Scheme != «» || !viaRequest && !strings.HasPrefix(rest, «///»)) && strings.HasPrefix(rest, «//») { var authority string authority, rest = rest[2:], «» if i := strings.Index(authority, «/»); i >= 0 { authority, rest = authority[:i], authority[i:] } url.User, url.Host, err = parseAuthority(authority) if err != nil { return nil, err } } else if url.Scheme != «» && strings.HasPrefix(rest, «/») { // OmitHost is set to true when rawURL has an empty host (authority). // See golang.org/issue/46059. url.OmitHost = true } // Set Path and, optionally, RawPath. // RawPath is a hint of the encoding of Path. We don’t want to set it if // the default escaping of Path is equivalent, to help make sure that people // don’t rely on it in general. if err := url.setPath(rest); err != nil { return nil, err } return url, nil } func parseAuthority(authority string) (user *Userinfo, host string, err error) { i := strings.LastIndex(authority, «@») if i < 0 { host, err = parseHost(authority) } else { host, err = parseHost(authority[i+1:]) } if err != nil { return nil, «», err } if i < 0 { return nil, host, nil } userinfo := authority[:i] if !validUserinfo(userinfo) { return nil, «», errors.New(«net/url: invalid userinfo») } if !strings.Contains(userinfo, «:») { if userinfo, err = unescape(userinfo, encodeUserPassword); err != nil { return nil, «», err } user = User(userinfo) } else { username, password, _ := strings.Cut(userinfo, «:») if username, err = unescape(username, encodeUserPassword); err != nil { return nil, «», err } if password, err = unescape(password, encodeUserPassword); err != nil { return nil, «», err } user = UserPassword(username, password) } return user, host, nil } // parseHost parses host as an authority without user // information. That is, as host[:port]. func parseHost(host string) (string, error) { if strings.HasPrefix(host, «[«) { // Parse an IP-Literal in RFC 3986 and RFC 6874. // E.g., «[fe80::1]», «[fe80::1%25en0]», «[fe80::1]:80». i := strings.LastIndex(host, «]») if i < 0 { return «», errors.New(«missing ‘]’ in host») } colonPort := host[i+1:] if !validOptionalPort(colonPort) { return «», 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 := strings.Index(host[:i], «%25») if zone >= 0 { host1, err := unescape(host[:zone], encodeHost) if err != nil { return «», err } host2, err := unescape(host[zone:i], encodeZone) if err != nil { return «», err } host3, err := unescape(host[i:], encodeHost) if err != nil { return «», err } return host1 + host2 + host3, nil } } else if i := strings.LastIndex(host, «:»); i != 1 { colonPort := host[i:] if !validOptionalPort(colonPort) { return «», fmt.Errorf(«invalid port %q after host», colonPort) } } var err error if host, err = unescape(host, encodeHost); err != nil { return «», err } return host, nil } // setPath sets the Path and RawPath fields of the URL based on the provided // escaped path p. It maintains the invariant that RawPath is only specified // when it differs from the default encoding of the path. // For example: // — setPath(«/foo/bar») will set Path=»/foo/bar» and RawPath=»» // — setPath(«/foo%2fbar») will set Path=»/foo/bar» and RawPath=»/foo%2fbar» // setPath will return an error only if the provided path contains an invalid // escaping. func (u *URL) setPath(p string) error { path, err := unescape(p, encodePath) if err != nil { return err } u.Path = path if escp := escape(path, encodePath); p == escp { // Default encoding is fine. u.RawPath = «» } else { u.RawPath = p } return nil } // EscapedPath returns the escaped form of u.Path. // In general there are multiple possible escaped forms of any path. // EscapedPath returns u.RawPath when it is a valid escaping of u.Path. // Otherwise EscapedPath ignores u.RawPath and computes an escaped // form on its own. // The String and RequestURI methods use EscapedPath to construct // their results. // In general, code should call EscapedPath instead of // reading u.RawPath directly. func (u *URL) EscapedPath() string { if u.RawPath != «» && validEncoded(u.RawPath, encodePath) { p, err := unescape(u.RawPath, encodePath) if err == nil && p == u.Path { return u.RawPath } } if u.Path == «*» { return «*» // don’t escape (Issue 11202) } return escape(u.Path, encodePath) } // validEncoded reports whether s is a valid encoded path or fragment, // according to mode. // It must not contain any bytes that require escaping during encoding. func validEncoded(s string, mode encoding) bool { for i := 0; i < len(s); i++ { // RFC 3986, Appendix A. // pchar = unreserved / pct-encoded / sub-delims / «:» / «@». // shouldEscape is not quite compliant with the RFC, // so we check the sub-delims ourselves and let // shouldEscape handle the others. switch s[i] { case ‘!’, ‘$’, ‘&’, »’, ‘(‘, ‘)’, ‘*’, ‘+’, ‘,’, ‘;’, ‘=’, ‘:’, ‘@’: // ok case ‘[‘, ‘]’: // ok — not specified in RFC 3986 but left alone by modern browsers case ‘%’: // ok — percent encoded, will decode default: if shouldEscape(s[i], mode) { return false } } } return true } // setFragment is like setPath but for Fragment/RawFragment. func (u *URL) setFragment(f string) error { frag, err := unescape(f, encodeFragment) if err != nil { return err } u.Fragment = frag if escf := escape(frag, encodeFragment); f == escf { // Default encoding is fine. u.RawFragment = «» } else { u.RawFragment = f } return nil } // EscapedFragment returns the escaped form of u.Fragment. // In general there are multiple possible escaped forms of any fragment. // EscapedFragment returns u.RawFragment when it is a valid escaping of u.Fragment. // Otherwise EscapedFragment ignores u.RawFragment and computes an escaped // form on its own. // The String method uses EscapedFragment to construct its result. // In general, code should call EscapedFragment instead of // reading u.RawFragment directly. func (u *URL) EscapedFragment() string { if u.RawFragment != «» && validEncoded(u.RawFragment, encodeFragment) { f, err := unescape(u.RawFragment, encodeFragment) if err == nil && f == u.Fragment { return u.RawFragment } } return escape(u.Fragment, encodeFragment) } // validOptionalPort reports whether port is either an empty string // or matches /^:d*$/ func validOptionalPort(port string) bool { if port == «» { return true } if port[0] != ‘:’ { return false } for _, b := range port[1:] { if b < ‘0’ || b > ‘9’ { return false } } return true } // String reassembles the URL into a valid URL string. // The general form of the result is one of: // // scheme:opaque?query#fragment // scheme://userinfo@host/path?query#fragment // // If u.Opaque is non-empty, String uses the first form; // otherwise it uses the second form. // Any non-ASCII characters in host are escaped. // To obtain the path, String uses u.EscapedPath(). // // In the second form, the following rules apply: // — if u.Scheme is empty, scheme: is omitted. // — if u.User is nil, userinfo@ is omitted. // — if u.Host is empty, host/ is omitted. // — if u.Scheme and u.Host are empty and u.User is nil, // the entire scheme://userinfo@host/ is omitted. // — if u.Host is non-empty and u.Path begins with a /, // the form host/path does not add its own /. // — if u.RawQuery is empty, ?query is omitted. // — if u.Fragment is empty, #fragment is omitted. func (u *URL) String() string { var buf strings.Builder if u.Scheme != «» { buf.WriteString(u.Scheme) buf.WriteByte(‘:’) } if u.Opaque != «» { buf.WriteString(u.Opaque) } else { if u.Scheme != «» || u.Host != «» || u.User != nil { if u.OmitHost && u.Host == «» && u.User == nil { // omit empty host } else { if u.Host != «» || u.Path != «» || u.User != nil { buf.WriteString(«//») } if ui := u.User; ui != nil { buf.WriteString(ui.String()) buf.WriteByte(‘@’) } if h := u.Host; h != «» { buf.WriteString(escape(h, encodeHost)) } } } path := u.EscapedPath() if path != «» && path[0] != ‘/’ && u.Host != «» { buf.WriteByte(‘/’) } if buf.Len() == 0 { // RFC 3986 §4.2 // A path segment that contains a colon character (e.g., «this:that») // cannot be used as the first segment of a relative-path reference, as // it would be mistaken for a scheme name. Such a segment must be // preceded by a dot-segment (e.g., «./this:that») to make a relative- // path reference. if segment, _, _ := strings.Cut(path, «/»); strings.Contains(segment, «:») { buf.WriteString(«./») } } buf.WriteString(path) } if u.ForceQuery || u.RawQuery != «» { buf.WriteByte(‘?’) buf.WriteString(u.RawQuery) } if u.Fragment != «» { buf.WriteByte(‘#’) buf.WriteString(u.EscapedFragment()) } return buf.String() } // Redacted is like String but replaces any password with «xxxxx». // Only the password in u.URL is redacted. func (u *URL) Redacted() string { if u == nil { return «» } ru := *u if _, has := ru.User.Password(); has { ru.User = UserPassword(ru.User.Username(), «xxxxx») } return ru.String() } // Values maps a string key to a list of values. // It is typically used for query parameters and form values. // Unlike in the http.Header map, the keys in a Values map // are case-sensitive. type Values map[string][]string // Get gets the first value associated with the given key. // If there are no values associated with the key, Get returns // the empty string. To access multiple values, use the map // directly. func (v Values) Get(key string) string { if v == nil { return «» } vs := v[key] if len(vs) == 0 { return «» } return vs[0] } // Set sets the key to value. It replaces any existing // values. func (v Values) Set(key, value string) { v[key] = []string{value} } // Add adds the value to key. It appends to any existing // values associated with key. func (v Values) Add(key, value string) { v[key] = append(v[key], value) } // Del deletes the values associated with key. func (v Values) Del(key string) { delete(v, key) } // Has checks whether a given key is set. func (v Values) Has(key string) bool { _, ok := v[key] return ok } // ParseQuery parses the URL-encoded query string and returns // a map listing the values specified for each key. // ParseQuery always returns a non-nil map containing all the // valid query parameters found; err describes the first decoding error // encountered, if any. // // Query is expected to be a list of key=value settings separated by ampersands. // A setting without an equals sign is interpreted as a key set to an empty // value. // Settings containing a non-URL-encoded semicolon are considered invalid. func ParseQuery(query string) (Values, error) { m := make(Values) err := parseQuery(m, query) return m, err } func parseQuery(m Values, query string) (err error) { for query != «» { var key string key, query, _ = strings.Cut(query, «&») if strings.Contains(key, «;») { err = fmt.Errorf(«invalid semicolon separator in query») continue } if key == «» { continue } key, value, _ := strings.Cut(key, «=») key, err1 := QueryUnescape(key) if err1 != nil { if err == nil { err = err1 } continue } value, err1 = QueryUnescape(value) if err1 != nil { if err == nil { err = err1 } continue } m[key] = append(m[key], value) } return err } // Encode encodes the values into “URL encoded” form // («bar=baz&foo=quux») sorted by key. func (v Values) Encode() string { if v == nil { return «» } var buf strings.Builder keys := make([]string, 0, len(v)) for k := range v { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { vs := v[k] keyEscaped := QueryEscape(k) for _, v := range vs { if buf.Len() > 0 { buf.WriteByte(‘&’) } buf.WriteString(keyEscaped) buf.WriteByte(‘=’) buf.WriteString(QueryEscape(v)) } } return buf.String() } // resolvePath applies special path segments from refs and applies // them to base, per RFC 3986. func resolvePath(base, ref string) string { var full string if ref == «» { full = base } else if ref[0] != ‘/’ { i := strings.LastIndex(base, «/») full = base[:i+1] + ref } else { full = ref } if full == «» { return «» } var ( elem string dst strings.Builder ) first := true remaining := full // We want to return a leading ‘/’, so write it now. dst.WriteByte(‘/’) found := true for found { elem, remaining, found = strings.Cut(remaining, «/») if elem == «.» { first = false // drop continue } if elem == «..» { // Ignore the leading ‘/’ we already wrote. str := dst.String()[1:] index := strings.LastIndexByte(str, ‘/’) dst.Reset() dst.WriteByte(‘/’) if index == 1 { first = true } else { dst.WriteString(str[:index]) } } else { if !first { dst.WriteByte(‘/’) } dst.WriteString(elem) first = false } } if elem == «.» || elem == «..» { dst.WriteByte(‘/’) } // We wrote an initial ‘/’, but we don’t want two. r := dst.String() if len(r) > 1 && r[1] == ‘/’ { r = r[1:] } return r } // IsAbs reports whether the URL is absolute. // Absolute means that it has a non-empty scheme. func (u *URL) IsAbs() bool { return u.Scheme != «» } // Parse parses a URL in the context of the receiver. The provided URL // may be relative or absolute. Parse returns nil, err on parse // failure, otherwise its return value is the same as ResolveReference. func (u *URL) Parse(ref string) (*URL, error) { refURL, err := Parse(ref) if err != nil { return nil, err } return u.ResolveReference(refURL), nil } // ResolveReference resolves a URI reference to an absolute URI from // an absolute base URI u, per RFC 3986 Section 5.2. The URI reference // may be relative or absolute. ResolveReference always returns a new // URL instance, even if the returned URL is identical to either the // base or reference. If ref is an absolute URL, then ResolveReference // ignores base and returns a copy of ref. func (u *URL) ResolveReference(ref *URL) *URL { url := *ref if ref.Scheme == «» { url.Scheme = u.Scheme } if ref.Scheme != «» || ref.Host != «» || ref.User != nil { // The «absoluteURI» or «net_path» cases. // We can ignore the error from setPath since we know we provided a // validly-escaped path. url.setPath(resolvePath(ref.EscapedPath(), «»)) return &url } if ref.Opaque != «» { url.User = nil url.Host = «» url.Path = «» return &url } if ref.Path == «» && !ref.ForceQuery && ref.RawQuery == «» { url.RawQuery = u.RawQuery if ref.Fragment == «» { url.Fragment = u.Fragment url.RawFragment = u.RawFragment } } // The «abs_path» or «rel_path» cases. url.Host = u.Host url.User = u.User url.setPath(resolvePath(u.EscapedPath(), ref.EscapedPath())) return &url } // Query parses RawQuery and returns the corresponding values. // It silently discards malformed value pairs. // To check errors use ParseQuery. func (u *URL) Query() Values { v, _ := ParseQuery(u.RawQuery) return v } // RequestURI returns the encoded path?query or opaque?query // string that would be used in an HTTP request for u. func (u *URL) RequestURI() string { result := u.Opaque if result == «» { result = u.EscapedPath() if result == «» { result = «/» } } else { if strings.HasPrefix(result, «//») { result = u.Scheme + «:» + result } } if u.ForceQuery || u.RawQuery != «» { result += «?» + u.RawQuery } return result } // Hostname returns u.Host, stripping any valid port number if present. // // If the result is enclosed in square brackets, as literal IPv6 addresses are, // the square brackets are removed from the result. func (u *URL) Hostname() string { host, _ := splitHostPort(u.Host) return host } // Port returns the port part of u.Host, without the leading colon. // // If u.Host doesn’t contain a valid numeric port, Port returns an empty string. func (u *URL) Port() string { _, port := splitHostPort(u.Host) return port } // splitHostPort separates host and port. If the port is not valid, it returns // the entire input as host, and it doesn’t check the validity of the host. // Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric. func splitHostPort(hostPort string) (host, port string) { host = hostPort colon := strings.LastIndexByte(host, ‘:’) if colon != 1 && validOptionalPort(host[colon:]) { host, port = host[:colon], host[colon+1:] } if strings.HasPrefix(host, «[«) && strings.HasSuffix(host, «]») { host = host[1 : len(host)1] } return } // Marshaling interface implementations. // Would like to implement MarshalText/UnmarshalText but that will change the JSON representation of URLs. func (u *URL) MarshalBinary() (text []byte, err error) { return []byte(u.String()), nil } func (u *URL) UnmarshalBinary(text []byte) error { u1, err := Parse(string(text)) if err != nil { return err } *u = *u1 return nil } // JoinPath returns a new URL with the provided path elements joined to // any existing path and the resulting path cleaned of any ./ or ../ elements. // Any sequences of multiple / characters will be reduced to a single /. func (u *URL) JoinPath(elem string) *URL { elem = append([]string{u.EscapedPath()}, elem) var p string if !strings.HasPrefix(elem[0], «/») { // Return a relative path if u is relative, // but ensure that it contains no ../ elements. elem[0] = «/» + elem[0] p = path.Join(elem)[1:] } else { p = path.Join(elem) } // path.Join will remove any trailing slashes. // Preserve at least one. if strings.HasSuffix(elem[len(elem)1], «/») && !strings.HasSuffix(p, «/») { p += «/» } url := *u url.setPath(p) return &url } // validUserinfo reports whether s is a valid userinfo string per RFC 3986 // Section 3.2.1: // // userinfo = *( unreserved / pct-encoded / sub-delims / «:» ) // unreserved = ALPHA / DIGIT / «-» / «.» / «_» / «~» // sub-delims = «!» / «$» / «&» / «‘» / «(» / «)» // / «*» / «+» / «,» / «;» / «=» // // It doesn’t validate pct-encoded. The caller does that via func unescape. func validUserinfo(s string) bool { for _, r := range s { if ‘A’ <= r && r <= ‘Z’ { continue } if ‘a’ <= r && r <= ‘z’ { continue } if ‘0’ <= r && r <= ‘9’ { continue } switch r { case ‘-‘, ‘.’, ‘_’, ‘:’, ‘~’, ‘!’, ‘$’, ‘&’, »’, ‘(‘, ‘)’, ‘*’, ‘+’, ‘,’, ‘;’, ‘=’, ‘%’, ‘@’: continue default: return false } } return true } // stringContainsCTLByte reports whether s contains any ASCII control character. func stringContainsCTLByte(s string) bool { for i := 0; i < len(s); i++ { b := s[i] if b < ‘ ‘ || b == 0x7f { return true } } return false } // JoinPath returns a URL string with the provided path elements joined to // the existing path of base and the resulting path cleaned of any ./ or ../ elements. func JoinPath(base string, elem string) (result string, err error) { url, err := Parse(base) if err != nil { return } result = url.JoinPath(elem).String() return }

Validation is part of every programmer life, especially when working on backend services, validation must be done right. In this blog post we talk about how to properly validate a URL in Go (Golang).
URL stands for Unique Resource Locator, and is a sub-type of URI (even though many people interchangeably use the two terms). A URL is a reference to a web resource, typically seen as a web address (eg https://golang.org/project/)

Below you can see the structure of a URL, which conforms with he structure of a URI

URI = scheme:[//authority]path[?query][#fragment]
authority = [[email protected]]host[:port]

How do you validate a URL in Go (Golang)?

I’ve always been impressed by the number of native packages in the Go standard library. Also this time we have a package that fulfils our needs. The net/url package https://golang.org/pkg/net/url/

We can use url.ParseRequestURI in order to validate our URL

func ParseRequestURI(rawurl string) (*URL, error)

The behaviour is described below

ParseRequestURI parses rawurl into a URL structure. It assumes that rawurl was received in an HTTP request, so the rawurl is interpreted only as an absolute URI or an absolute path. The string rawurl is assumed not to have a #fragment suffix. (Web browsers strip #fragment before sending the URL to a web server.)

Caveat: ParseRequestURI vs Parse

There is also another method that is supposed to be used for Parsing URL strings, but there are some caveats. It allows relative URLs making the validation a bit more loose. It’s url.Parse

func Parse(rawurl string) (*URL, error)

As described on the docs

Parse parses rawurl into a URL structure.
The rawurl may be relative (a path, without a host) or absolute (starting with a scheme). Trying to parse a hostname and path without a scheme is invalid but may not necessarily return an error, due to parsing ambiguities.

Parse URL Example

Let’s see now an example on how to parse and validate a URL in Go using ParseRequestURI

package main

import (
  "log"
  "net/url"
)

func main() {
  u, err := url.ParseRequestURI("hi/there?")	
  log.Printf("hi/there?: err=%+v url=%+vn", err, u)
  
  u, err = url.ParseRequestURI("http://golang.cafe/")	
  log.Printf("hi/there?: err=%+v url=%+vn", err, u)
  
  u, err = url.ParseRequestURI("http://golang.org/index.html?#page1")	
  log.Printf("hi/there?: err=%+v url=%+vn", err, u)
  
  u, err = url.ParseRequestURI("golang.org")	
  log.Printf("hi/there?: err=%+v url=%+vn", err, u)
}

Which outputs the following

2009/11/10 23:00:00 hi/there?: err=parse hi/there?: invalid URI for request url=<nil>
2009/11/10 23:00:00 hi/there?: err=<nil> url=http://golang.cafe/
2009/11/10 23:00:00 hi/there?: err=<nil> url=http://golang.org/index.html?#page1
2009/11/10 23:00:00 hi/there?: err=parse golang.org: invalid URI for request url=<nil>

Overview ?

Overview ?

Package url parses URLs and implements query escaping.
See RFC 3986.

func QueryEscape(s string) string
func QueryUnescape(s string) (string, error)
type Error
    func (e *Error) Error() string
type EscapeError
    func (e EscapeError) Error() string
type URL
    func Parse(rawurl string) (url *URL, err error)
    func ParseRequestURI(rawurl string) (url *URL, err error)
    func (u *URL) IsAbs() bool
    func (u *URL) Parse(ref string) (*URL, error)
    func (u *URL) Query() Values
    func (u *URL) RequestURI() string
    func (u *URL) ResolveReference(ref *URL) *URL
    func (u *URL) String() string
type Userinfo
    func User(username string) *Userinfo
    func UserPassword(username, password string) *Userinfo
    func (u *Userinfo) Password() (string, bool)
    func (u *Userinfo) String() string
    func (u *Userinfo) Username() string
type Values
    func ParseQuery(query string) (m Values, err error)
    func (v Values) Add(key, value string)
    func (v Values) Del(key string)
    func (v Values) Encode() string
    func (v Values) Get(key string) string
    func (v Values) Set(key, value string)

Examples

URL
Values

Package files


url.go

func QueryEscape

func QueryEscape(s string) string

QueryEscape escapes the string so it can be safely placed
inside a URL query.

func QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape does the inverse transformation of QueryEscape, converting
%AB into the byte 0xAB and ‘+’ into ‘ ‘ (space). It returns an error if
any % is not followed by two hexadecimal digits.

type Error

type Error struct {
    Op  string
    URL string
    Err error
}

Error reports an error and the operation and URL that caused it.

func (*Error) Error

func (e *Error) Error() string

type EscapeError

type EscapeError string

func (EscapeError) Error

func (e EscapeError) Error() string

type URL

type URL struct {
    Scheme   string
    Opaque   string    // encoded opaque data
    User     *Userinfo // username and password information
    Host     string
    Path     string
    RawQuery string // encoded query values, without '?'
    Fragment string // fragment for references, without '#'
}

A URL represents a parsed URL (technically, a URI reference).
The general form represented is:

scheme://[[email protected]]host/path[?query][#fragment]

URLs that do not start with a slash after the scheme are interpreted as:

scheme:opaque[?query][#fragment]

? Example

? Example

Code:

u, err := url.Parse("http://bing.com/search?q=dotnet")
if err != nil {
    log.Fatal(err)
}
u.Scheme = "https"
u.Host = "google.com"
q := u.Query()
q.Set("q", "golang")
u.RawQuery = q.Encode()
fmt.Println(u)

Output:

https://google.com/search?q=golang

func Parse

func Parse(rawurl string) (url *URL, err error)

Parse parses rawurl into a URL structure.
The rawurl may be relative or absolute.

func ParseRequestURI

func ParseRequestURI(rawurl string) (url *URL, err error)

ParseRequestURI parses rawurl into a URL structure. It assumes that
rawurl was received in an HTTP request, so the rawurl is interpreted
only as an absolute URI or an absolute path.
The string rawurl is assumed not to have a #fragment suffix.
(Web browsers strip #fragment before sending the URL to a web server.)

func (*URL) IsAbs

func (u *URL) IsAbs() bool

IsAbs returns true if the URL is absolute.

func (*URL) Parse

func (u *URL) Parse(ref string) (*URL, error)

Parse parses a URL in the context of the receiver. The provided URL
may be relative or absolute. Parse returns nil, err on parse
failure, otherwise its return value is the same as ResolveReference.

func (*URL) Query

func (u *URL) Query() Values

Query parses RawQuery and returns the corresponding values.

func (*URL) RequestURI

func (u *URL) RequestURI() string

RequestURI returns the encoded path?query or opaque?query
string that would be used in an HTTP request for u.

func (*URL) ResolveReference

func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference resolves a URI reference to an absolute URI from
an absolute base URI, per RFC 2396 Section 5.2. The URI reference
may be relative or absolute. ResolveReference always returns a new
URL instance, even if the returned URL is identical to either the
base or reference. If ref is an absolute URL, then ResolveReference
ignores base and returns a copy of ref.

func (*URL) String

func (u *URL) String() string

String reassembles the URL into a valid URL string.

type Userinfo

type Userinfo struct {
    // contains filtered or unexported fields
}

The Userinfo type is an immutable encapsulation of username and
password details for a URL. An existing Userinfo value is guaranteed
to have a username set (potentially empty, as allowed by RFC 2396),
and optionally a password.

func User

func User(username string) *Userinfo

User returns a Userinfo containing the provided username
and no password set.

func UserPassword

func UserPassword(username, password string) *Userinfo

UserPassword returns a Userinfo containing the provided username
and password.
This functionality should only be used with legacy web sites.
RFC 2396 warns that interpreting Userinfo this way
“is NOT RECOMMENDED, because the passing of authentication
information in clear text (such as URI) has proven to be a
security risk in almost every case where it has been used.”

func (*Userinfo) Password

func (u *Userinfo) Password() (string, bool)

Password returns the password in case it is set, and whether it is set.

func (*Userinfo) String

func (u *Userinfo) String() string

String returns the encoded userinfo information in the standard form
of «username[:password]».

func (*Userinfo) Username

func (u *Userinfo) Username() string

Username returns the username.

type Values

type Values map[string][]string

Values maps a string key to a list of values.
It is typically used for query parameters and form values.
Unlike in the http.Header map, the keys in a Values map
are case-sensitive.

? Example

? Example

Code:

v := url.Values{}
v.Set("name", "Ava")
v.Add("friend", "Jess")
v.Add("friend", "Sarah")
v.Add("friend", "Zoe")
// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
fmt.Println(v.Get("name"))
fmt.Println(v.Get("friend"))
fmt.Println(v["friend"])

Output:

Ava
Jess
[Jess Sarah Zoe]

func ParseQuery

func ParseQuery(query string) (m Values, err error)

ParseQuery parses the URL-encoded query string and returns
a map listing the values specified for each key.
ParseQuery always returns a non-nil map containing all the
valid query parameters found; err describes the first decoding error
encountered, if any.

func (Values) Add

func (v Values) Add(key, value string)

Add adds the key to value. It appends to any existing
values associated with key.

func (Values) Del

func (v Values) Del(key string)

Del deletes the values associated with key.

func (Values) Encode

func (v Values) Encode() string

Encode encodes the values into “URL encoded” form.
e.g. «foo=bar&bar=baz»

func (Values) Get

func (v Values) Get(key string) string

Get gets the first value associated with the given key.
If there are no values associated with the key, Get returns
the empty string. To access multiple values, use the map
directly.

func (Values) Set

func (v Values) Set(key, value string)

Set sets the key to value. It replaces any existing
values.

Давайте я сразу зайду с козырей. Сколько ошибок в коде этой функции вы можете найти за 60 секунд?

func NewConnectionString(host, path, database, user, password string, debug bool) string {
	return fmt.Sprintf(
		"proto://%s/%s?userName=%s&password=%s&database=%s&debug=%t",
		host, path, database, user, password, debug,
	)
}

Все ошибки в этом довольно небольшом коде найти и обезвредить довольно сложно. Я попробую их сейчас сформулировать и скомпоновать в две основные:

  • очевидная — перепутаны параметры;
  • не очевидная — параметры не экранируются.

Ладно, признавайтесь — наверняка каждый за 60 секунд смог заметить первую ошибку, но не все из вас смогут заметить вторую. Если ваш список ошибок оказался короче, чем мой — тогда прошу под кат.

История проблемы

Не подумайте, что тема высосана из пальца. Хоть и не часто, но она порой всплывает то тут, то там. То при клиент-серверном взаимодействии, когда разработчики не использовали openapi кодогенерацию и писали код вручную. То при генерации ConnectionString для драйвера базы данных. Да мало ли где можно ошибиться с этим дурацким URL?

Какие последствия? Да очень разнообразные, выбирайте на свой вкус:

логические ошибки

В некотором кол-ве случаев, когда параметр содержит недопустимый для query символ, весь URL либо становится полностью невалидным, либо мы получаем искаженный, но рабочий запрос. Если в первом случае мы можем заметить записи в журнале ошибок, то второй случай гораздо хуже — мы не сможем вовремя заметить ошибку. Возможно, даже никогда.

ошибки при внесении изменений

Не поверите, но есть такая история. В какой-то момент, когда разработчик изменял такую функцию, он перепутал местоположение плейсхолдера и параметра. И в такой форме функция существовала довольно долгое время. Почему никто не заметил проблемы? Да потому, что имя пользователя, пароль и наименование базы данных совпадало. А в тот момент, когда в этой схеме обновили уровень безопасности, и пароли стали генерироваться случайным порядком, все сломалось. Понятия не имею, сколько разработчик потратил времени на обнаружение ошибки, но когда я увидел эту функцию, я не заметил ошибки вообще.

нет гарантии корректности на этапе компиляции

Это очень важный пункт. Как мы все знаем, ошибки бывают двух основных видов: run-time и compile-time. Т.е. ошибки времени выполнения и времени компиляции соответственно. Какой вид хуже? Конечно ошибки времени выполнения. Почему? Да потому, что программа уже выполняется, а это значит, что данные уже могли пострадать. Кроме того, такие ошибки нужно сначала обнаружить и локализовать. Если мы вдруг пропустим знак амперсанда между параметрами — никто нам этого не скажет.

сложности экранирования

Поверьте мне на слово — экранировать правильно все части URL строки не так-то просто. Там есть, по крайней мере, три части, правила экранирования символов в которых отличаются. И я знаю не один пример реальных процессов коммерческой разработки, когда в связи с этим было принято решение ограничить допустимые символы для логина и пароля.

query injection

Да. Это почти как SQL injection, только query. Ну да, использование этой уязвимости имеет довольно узкий круг возможностей. Фактически, почти никаких возможностей, если мы не даем пользователю возможность влиять на параметры. Но все же, если отмести всякие контексты, и взглянуть правде в лицо, то это самая настоящая уязвимость.

Вона уже, как много смог описать. Надеюсь, я убедил вас в существовании реальной проблемы. Тогда давайте с ней бороться.

А как правильно?

Пакет net/url является стандартным пакетом языка GO и предоставляет довольно простой и удобный инструмент для формирования URL со всеми прилагающимися плюшками (согласно RFC 3986): гарантия синтаксической правильности, экранирование по всем канонам и проверка литералов на этапе компиляции.

Давайте еще раз посмотрим на “неправильный” код, я немного усложнил его расположением логина и пароля перед именем сервера, как положено по старому стандарту:

func NewSprintfConnectionString(proto, host, path, database, user, password string, debug bool) string {
	return fmt.Sprintf("%s://%s:%s@%s%s?database=%s&debug=%t", proto, user, password, host, path, database, debug)
}

А теперь код, который структурирован:

func NewURLConnectionString(proto, host, path, database, user, password string, debug bool) string {
	const (
		cDataBaseURLParameter = "database"
		cDebugURLParameter    = "debug"
	)
	var v = make(url.Values)
	v.Set(cDataBaseURLParameter, database)
	if debug {
		v.Set(cDebugURLParameter, "true")
	} else {
		v.Set(cDebugURLParameter, "false")
	}
	var u = url.URL{
		Scheme:   proto,
		Host:     host,
		Path:     path,
		User:     url.UserPassword(user, password),
		RawQuery: v.Encode(),
	}
	return u.String()
}

Как вы могли заметить, код заметно увеличился. Но стал ли он сложнее? Давайте разберемся.
В верхней части я объявил константы, которые отвечают за наименование параметров. Это не обязательно и кроме того, если вы реализуете какой-то межпроцессный обмен между двумя сервисами вашей системы, возможно, эти константы уже есть. Я их выделил для того, чтобы не было magic-words. Это позволяет видеть мне список всех возможных параметров в одном месте и не дает мне ошибиться при многократном использовании одного и того же имени параметра в коде программы в нескольких местах. Я имею в виду использование cDebugURLParameter дважды — в случае, если мы используем magic-words у нас каждый раз есть риск допустить опечатку.

Следом я создаю и затем наполняю url.Values — структура, которая поможет мне собрать разные параметры query и затем собрать из них экранированную query строку. Обращаю внимание на то, что в такой форме код выходит довольно наглядным — мы видим имя параметра и прямо напротив него значение. Сравните расположение имен и значений в варианте с использованием fmt.Sprintf.

В нижней части литерал url.URL в котором я заполняю необходимые элементы URL строки: протокол, хост, путь и прочее. Обратите внимание на то, как используются параметры user и password. Любые символы в имени пользователя и пароле будут корректно экранированы, мы можем не ограничиваться в каких-то диапазонах, которые потом еще придется контролировать.

Обозримость кода существенно не изменилась — кол-во строк меньше 30, зато по ширине код стал компактнее и приобрел наглядность. Использование литерала url.URL и структуры url.Values позволяет заручиться гарантиями компилятора, что я не нарушу правил расположения амперсанд и других символов. Специальные символы, которые были внесены аргументами функции будут гарантировано экранированы по всем канонам (как я уже и говорил).

Ну и как вишенка на торте — сложность внесения изменений в эту функцию не зависит от количества параметров. Т.е. остается константой. Представьте, что у нас есть 7 параметров, а нам нужно убрать один из них, а затем добавить еще 7. В случае с форматной строкой (fmt.Sprintf), нам нужно быть аккуратными при внесении изменений, а если мы используем net/url, нам нужно добавить константы, которые будут нести имена новых параметров, а затем заполнить параметры с помощью v.Set(cDebugURLParameter, «false»). Что тут может пойти не так?

А что там с производительностью?

Ну да, придется немного добавить горчинки в мой салат. За все нужно платить, и нам придется заплатить за гарантии безопасности нашей URL строки производительностью. Сколько стоит? Давайте проверим. Для начала соберем вот такой бенчмарк:

Посмотреть код бенчмарка

func benchConnectionStringFunction(b *testing.B, fn func(proto, host, path, database, user, password string, debug bool) string) {
	for i := 0; i < b.N; i++ {
		_ = fn(
			"http",
			"my-host-name.com",
			"/users/preferences/email",
			"storage",
			"vladimir@yandex.ru",
			"foo-bar$100",
			false,
		)
	}
}

func BenchmarkNewSprintfConnectionString(b *testing.B) {
	benchConnectionStringFunction(b, NewSprintfConnectionString)
}

func BenchmarkNewURLConnectionString(b *testing.B) {
	benchConnectionStringFunction(b, NewURLConnectionString)
}

func BenchmarkNewConcatenateConnectionString(b *testing.B) {
	benchConnectionStringFunction(b, NewConcatenateConnectionString)
}

Тут у нас

  • NewSprintfConnectionString — это вариант с fmt.Sprintf;
  • NewURLConnectionString — это вариант с net/url;
  • NewConcatenateConnectionString — это секретный вариант, который я раскрою позже, правда по названию уже можно догадаться в чем секрет.

Принимаем за номинальную производительность первый вариант. Ну так как я его критикую, как довольно простое и довольно неверное решение, считаю, что производительность нужно оценивать относительно этого варианта.

Итак, что там у нас получается?

Время: 435 ns/op
Память: 208 B/op
Аллокаций: 7 allocs/op

Довольно производительный вариант. Немного расстраивает количеством аллокаций, но тут ожидаемо, ведь все параметры для fmt.Sprintf приходят, как interface{} — а это всем известная драма.

Окей. Следующий на разделочной доске вариант с net/url (мой любимый). Сразу скажу, все map — это в любом случае аллокация в памяти, кроме того, если слайс используется вне функции (а в нашем случае у нас url.Values это как раз карта слайсов: map[string][]string), то он тоже приводит к аллокации. Ну и сложные манипуляции со строками внутри реализации url.URL.String() тоже не без аллокаций. Так что (барабанная дробь):

Время: 1237 ns/op
Память: 608 B/op
Аллокаций: 14 allocs/op

Ну на самом деле совсем неплохо, учитывая то, что все переменные (включая аргументы функции) утекли в heap. Мы получили всего троекратное проседание по производительности. Полторы микросекунды на операцию я считаю не такой уж большой платой за качество кода и стабильность процесса разработки. И если бы потребовалось немного затюнить производительность, то я бы обратил внимание на эту функцию, при условии многократного ее выполнения. Но пришел бы к очевидному выводу — проблема производительности при общении с сетью кроется совсем не во времени формирования URL, а на переходах в уровни ядра, времени передачи данных по сети и все связанные с этим накладные расходы. Так что к чему вообще все эти бенчмарки?

Окей, а если мы возьмем совершенно абстрактный случай: нам нужно сформировать множество URL строк из заданных параметров и отправить их в обработку дальше одним большим массивом. Т.е. возьмем случай, когда производительность этой процедуры имеет значение. Тогда давайте глянем на третий бенч:

Время: 416 ns/op
Память: 160 B/op
Аллокаций: 3 allocs/op

Довольно неплохо. Тут радует кол-во аллокаций. Да и по памяти есть существенный выигрыш по сравнению с первой функцией. Ну давайте глянем, что там за секрет.

Обратно в каменный век

Все знают, что один из самых производительных способов собрать строку в GO — это простая операция конкатенации. И вот функция построенная на этом простом принципе:

func NewConcatenateConnectionString(proto, host, path, database, user, password string, debug bool) string {
	var appendix = "&debug=false"
	if debug {
		appendix = "&debug=true"
	}
	return proto + "://" + url.QueryEscape(user) + ":" + url.QueryEscape(password) + "@" + host + path + "?database=" + url.QueryEscape(database) + appendix
}

Ну да, я зачем-то отступил от своего принципа и отправился обратно в каменный век: потерял возможность экранировать path, допустил некоторые деградации в экранировании user и password (там есть разница экранирования с query). В целом выглядит не очень.

Но давайте взглянем правде в лицо. Допустить ошибку при изменении такой функции все-таки сложнее, чем в функции с использованием fmt.Sprintf не смотря на то, что код этой функции выглядит довольно печально.

Давайте попробуем избавиться от уродства этой функции, добавим в нее немного красоты.

func NewConcatenateConnectionString(proto, host, path, database, user, password string, debug bool) string {
	var URL strings.Builder
	URL.Grow(128)
	URL.WriteString(proto + "://")
	if user != "" {
		URL.WriteString(url.QueryEscape(user) + ":" + url.QueryEscape(password) + "@")
	}
	URL.WriteString(host + path)
	URL.WriteString("?database=" + url.QueryEscape(database))
	if debug {
		URL.WriteString("&debug=true")
	} else {
		URL.WriteString("&debug=false")
	}
	return URL.String()
}

Удалось вернуть немного наглядности и структурированности: мы снова видим параметр напротив значения: URL.WriteString(«?database=» + url.QueryEscape(database)) за сравнительно небольшую плату. Давайте глянем, какую.

Время: 478 ns/op
Память: 272 B/op
Аллокации: 5 allocs/op

Мне кажется, что этот вариант будет слегка поприличнее, чем вариант с fmt.Sprintf. Но давайте разберем, какие у него есть преимущества и недостатки (в сравнении с эталонным).

1 ый минус: Нет возможности обозреть шаблон итогового URL — я имею в виду, что по сравнению с форматной строкой, которая представляет нам хоть и не очень наглядно, но зато целиком весь URL, который мы можем получить, этот случай имеет просто набор некоторых этапов заполнения строки, из которой в итоге возникнет URL. Довольно слабый минус, вряд ли кому-то захочется видеть пример формируемого URL, а если захочется, его можно вложить в комментарии.

2 ой минус: Все параметры необходимо привести к типу данных строка — обратили внимание на то, как пришлось поступить с параметром debug? А представьте, что придется сделать с целочисленными параметрами! Но в любом случае, тут никто не запрещает использовать fmt.Sprintf внутри вызова URL.WriteString. Так что этот минус еще слабее предыдущего.

1 ый плюс: Более гибкая логика. В варианте с простым fmt.Sprintf мы можем транслировать параметры в URL строку как они есть, а это значит, что готовить их нужно до передачи в функцию. В нашем же случае мы можем дополнять функцию какой-то логикой и даже опускать некоторые параметры, как это сделано с user+password.

2 ой плюс: Стоимость доработок. С увеличением кол-ва параметров сложность не изменяется — мы не будем увеличивать длину форматной строки и все большее время тратить на внимание к правильности очередности имен параметров и их значений. А вот это, на мой взгляд, довольно существенный плюс.

Победитель

Ну и раз это номинация, давайте же выберем победителя через нечестное и предвзятое судейство. Оценивать буду по следующим критериям (они соотносятся с проблемами, которые описаны выше и добавим еще производительность):

  • логика — возможность сломать URL и сложность обнаружения ошибки;
  • сложность изменения — вероятность внесения ошибки при изменении функции;
  • гарантии компилятора — возможность обнаружить ошибки на этапе компиляции;
  • производительность.

Думаю, что победитель в номинации “худший способ сформировать URL строку в golang” нами обнаружен. Прошу вас, по возможности не используйте этот способ.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

А что вы думаете по этому поводу?


38.16%
всегда использую функции net/url
29


25%
можно аккуратно использовать fmt.Sprintf
19


10.53%
собираю URL конкатенацией используя QueryEscape
8


26.32%
нет разницы, главное, чтобы работало без ошибок
20

Проголосовали 76 пользователей.

Воздержались 28 пользователей.

Пакет url анализирует URL-адреса и реализует экранирование запросов.

Функция PathEscape

func PathEscape(s string) string

PathEscape экранирует строку, чтобы ее можно было безопасно разместить внутри сегмента пути URL, заменяя при необходимости специальные символы (включая /) последовательностями %XX.

Пример использования функции PathEscape

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space := url.PathEscape("one two")
    fmt.Println(space)
    
    percent := url.PathEscape("10%")
    fmt.Println(percent)
    
    symbols := url.PathEscape(" ?&=#+%!<>#"{}|\^[]`☺t:/@$'()*,;")
    fmt.Println(symbols)
}

Вывод:

one%20two
10%25
%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B

Пример в песочнице https://play.golang.org/p/LNmUJmU8uaq

Функция PathUnescape

func PathUnescape(s string) (string, error)

PathUnescape выполняет обратное преобразование PathEscape, преобразуя каждую 3-байтовую закодированную подстроку формы «%AB» в шестнадцатеричный байт 0xAB. Он возвращает ошибку, если за любым % не следуют две шестнадцатеричные цифры.

PathUnescape идентичен QueryUnescape, за исключением того, что он не отменяет экранирование ‘+’ на ‘ ‘ (пробел).

Пример использования функции PathUnescape

package main

import (
	"fmt"
	"net/url"
)

func main() {
	space, err := url.PathUnescape("one%20two")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(space)
	
	percent, err := url.PathUnescape("10%25")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(percent)
	
	symbols, err := url.PathUnescape("%20%3F&=%23+%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09:%2F@$%27%28%29%2A%2C%3B")
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(symbols)
}

Вывод:

one two
10%
 ?&=#+%!<>#"{}|^[]`☺	:/@$'()*,;

Пример в песочнице https://play.golang.org/p/RevOg4UxSG7

Функция QueryEscape

func QueryEscape(s string) string

QueryEscape экранирует строку, чтобы ее можно было безопасно поместить в URL-запрос.

Пример использования функции QueryEscape

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space := url.QueryEscape("one two")
    fmt.Println(space)
    
    percent := url.QueryEscape("10%")
    fmt.Println(percent)
    
    symbols := url.QueryEscape(" ?&=#+%!<>#"{}|\^[]`☺t:/@$'()*,;")
    fmt.Println(symbols)
}

Вывод:

one+two
10%25
+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B

Пример в песочнице https://play.golang.org/p/hfKAuFsV00i

Функция QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape выполняет обратное преобразование QueryEscape, преобразуя каждую 3-байтовую закодированную подстроку формы «%AB» в шестнадцатеричный байт 0xAB. Он возвращает ошибку, если за любым % не следуют две шестнадцатеричные цифры.

Пример использования функции QueryUnescape

package main

import (
    "fmt"
    "net/url"
)

func main() {
    space, err := url.QueryUnescape("one+two")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(space)
    
    percent, err := url.QueryUnescape("10%25")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(percent)
    
    symbols, err := url.QueryUnescape("+%3F%26%3D%23%2B%25%21%3C%3E%23%22%7B%7D%7C%5C%5E%5B%5D%60%E2%98%BA%09%3A%2F%40%24%27%28%29%2A%2C%3B")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(symbols)
}

Вывод:

one two
10%
 ?&=#+%!<>#"{}|^[]`☺	:/@$'()*,;

Пример в песочнице https://play.golang.org/p/cIDrkclH5Qa

Тип URL

type URL struct {
    Scheme      string
    Opaque      string    // закодированные непрозрачные данные
    User        *Userinfo // username и password информация
    Host        string    // host или host:port
    Path        string    // путь (в относительных путях может отсутствовать начальная косая черта)
    RawPath     string    // закодированный путь (EscapedPath)
    ForceQuery  bool      // добавить запрос ('?') даже если RawQuery пуст
    RawQuery    string    // закодированные значения запроса, без '?'
    Fragment    string    // фрагмент для ссылок, без '#'
    RawFragment string    // закодированный фрагмент (EscapedFragment)
}

URL представляет собой проанализированный URL-адрес (технически ссылка на URI).

Общая форма представленна как:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URL-адреса, которые не начинаются с косой черты после схемы, интерпретируются как:

scheme:opaque[?query][#fragment]

Обратите внимание, что поле Path хранится в декодированной форме: /%47%6f%2f становится /Go/. Как следствие, невозможно определить, какие косые черты в пути были косыми чертами в необработанном URL, а какие были %2f. Это различие редко бывает важным, но когда это так, код должен использовать RawPath, необязательное поле, которое устанавливается только в том случае, если кодировка по умолчанию отличается от Path.

Метод URL String использует метод EscapedPath для получения пути.

Пример использования типа URL

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("http://bing.com/search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    u.Scheme = "https"
    u.Host = "google.com"
    q := u.Query()
    q.Set("q", "golang")
    u.RawQuery = q.Encode()
    fmt.Println(u)
}

Вывод

https://google.com/search?q=golang

Пример использования типа URL

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    // Parse + String сохраняют первоначальное кодирование.
    u, err := url.Parse("https://example.com/foo%2fbar")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Path)
    fmt.Println(u.RawPath)
    fmt.Println(u.String())
}

Вывод

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

Функция Parse

func Parse(rawURL string) (*URL, error)

Parse анализирует необработанный URL-адрес в структуру URL.

URL-адрес может быть относительным (путь без хоста) или абсолютным (начиная со схемы). Попытка проанализировать имя хоста и путь без схемы недопустима, но может не обязательно возвращать ошибку из-за неоднозначности синтаксического анализа.

Пример использования функции Parse

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("http://bing.com/search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Scheme: ", u.Scheme)
    fmt.Println("Host: ", u.Host)
    fmt.Println("Path: ", u.Path)
    fmt.Println("RawQuery: ", u.RawQuery)
}

Вывод

Scheme:  http
Host:  bing.com
Path:  /search
RawQuery:  q=dotnet

Пример в песочнице https://play.golang.org/p/o5gLj5_fteS

Функция ParseRequestURI

func ParseRequestURI(rawURL string) (*URL, error)

ParseRequestURI анализирует необработанный URL-адрес в структуру URL. Предполагается, что URL-адрес был получен в HTTP-запросе, поэтому URL-адрес интерпретируется только как абсолютный URI или абсолютный путь. Предполагается, что строковый url не имеет суффикса #fragment. (Веб-браузеры удаляют #fragment перед отправкой URL-адреса на веб-сервер.)

Метод EscapedFragment

func (u *URL) EscapedFragment() string

EscapedFragment возвращает экранированную форму u.Fragment. В общем, существует несколько возможных экранированных форм любого фрагмента. EscapedFragment возвращает u.RawFragment, если это допустимое экранирование u.Fragment. В противном случае EscapedFragment игнорирует u.RawFragment и самостоятельно вычисляет экранированную форму. Метод String использует EscapedFragment для построения своего результата. В общем, код должен вызывать EscapedFragment вместо непосредственного чтения u.RawFragment.

Пример использования метода EscapedFragment

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("http://example.com/#x/y%2Fz")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Fragment:", u.Fragment)
    fmt.Println("RawFragment:", u.RawFragment)
    fmt.Println("EscapedFragment:", u.EscapedFragment())
}

Вывод

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

Пример в песочнице https://play.golang.org/p/Q-I-8bfoXFO

Метод EscapedPath

func (u *URL) EscapedPath() string

EscapedPath возвращает экранированную форму u.Path. Как правило, у любого пути есть несколько возможных экранированных форм. EscapedPath возвращает u.RawPath, если это действительное экранирование u.Path. В противном случае EscapedPath игнорирует u.RawPath и вычисляет экранированную форму самостоятельно. Методы String и RequestURI используют EscapedPath для построения своих результатов. В общем, код должен вызывать EscapedPath вместо непосредственного чтения u.RawPath.

Пример использования метода EscapedPath

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("http://example.com/x/y%2Fz")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("Path:", u.Path)
    fmt.Println("RawPath:", u.RawPath)
    fmt.Println("EscapedPath:", u.EscapedPath())
}

Вывод

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

Пример в песочнице https://play.golang.org/p/xvc45jZkqq6

Метод Hostname

func (u *URL) Hostname() string

Hostname возвращает u.Host, удаляя любой допустимый номер порта, если он присутствует.

Если результат заключен в квадратные скобки, как буквальные адреса IPv6, квадратные скобки удаляются из результата.

Пример использования метода Hostname

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org:8000/path")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Hostname())
    u, err = url.Parse("https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Hostname())
}

Вывод

example.org
2001:0db8:85a3:0000:0000:8a2e:0370:7334

Пример в песочнице https://play.golang.org/p/0MV8vkqssmO

Метод IsAbs

func (u *URL) IsAbs() bool

IsAbs сообщает, является ли URL абсолютным. Абсолютное означает, что у него непустая схема.

Пример использования метода IsAbs

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := url.URL{Host: "example.com", Path: "foo"}
    fmt.Println(u.IsAbs())
    u.Scheme = "http"
    fmt.Println(u.IsAbs())
}

Вывод

false
true

Пример в песочнице https://play.golang.org/p/UfOBOdHopyF

Метод MarshalBinary

func (u *URL) MarshalBinary() (text []byte, err error)

Преобразует URL в срез байтов, выдает ошибку если она возникает при преобразовании.

Пример использования метода MarshalBinary

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, _ := url.Parse("https://example.org")
    b, err := u.MarshalBinary()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%sn", b)
}

Вывод

https://example.org

Пример в песочнице https://play.golang.org/p/L2uSp9WIN1Y

Метод Parse

func (u *URL) Parse(ref string) (*URL, error)

Parse анализирует URL-адрес в контексте получателя. Предоставленный URL-адрес может быть относительным или абсолютным. Parse возвращает nil, ошибку при сбое синтаксического анализа, в противном случае его возвращаемое значение такое же, как ResolveReference.

Пример использования метода Parse

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org")
    if err != nil {
        log.Fatal(err)
    }
    rel, err := u.Parse("/foo")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(rel)
    _, err = u.Parse(":foo")
    if _, ok := err.(*url.Error); !ok {
        log.Fatal(err)
    }
}

Вывод

https://example.org/foo

Пример в песочнице https://play.golang.org/p/lyPqaigN1pw

Метод Port

func (u *URL) Port() string

Port возвращает часть порта u.Host без начального двоеточия.

Если u.Host не содержит допустимого числового порта, Port возвращает пустую строку.

Пример использования метода Port

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Port())
    u, err = url.Parse("https://example.org:8080")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.Port())
}

Вывод

8080

Пример в песочнице https://play.golang.org/p/xcP1_ozPxfG

Метод Query

func (u *URL) Query() Values

Query анализирует RawQuery и возвращает соответствующие значения. Он молча отбрасывает искаженные пары значений. Для проверки ошибок используйте ParseQuery.

Пример использования метода Query

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org/?a=1&a=2&b=&=3&&&&")
    if err != nil {
        log.Fatal(err)
    }
    q := u.Query()
    fmt.Println(q["a"])
    fmt.Println(q.Get("b"))
    fmt.Println(q.Get(""))
}

Вывод

[1 2]

3

Пример в песочнице https://play.golang.org/p/MJrTerqSaqd

Метод Redacted

func (u *URL) Redacted() string

Redacted аналогично String, но заменяет любой пароль на «xxxxx». Удаляется только пароль в u.URL.

Пример использования метода Redacted

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme: "https",
        User:   url.UserPassword("user", "password"),
        Host:   "example.com",
        Path:   "foo/bar",
    }
    fmt.Println(u.Redacted())
    u.User = url.UserPassword("me", "newerPassword")
    fmt.Println(u.Redacted())
}

Вывод

https://user:xxxxx@example.com/foo/bar
https://me:xxxxx@example.com/foo/bar

Пример в песочнице https://play.golang.org/p/FPsxk_4bbCV

Метод RequestURI

func (u *URL) RequestURI() string

RequestURI возвращает закодированный path?query или opaque?query строку, которая будет использоваться в HTTP-запросе для u.

Пример использования метода RequestURI

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.org/path?foo=bar")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(u.RequestURI())
}

Вывод

/path?foo=bar

Пример в песочнице https://play.golang.org/p/QK-lScEgjou

Метод ResolveReference

func (u *URL) ResolveReference(ref *URL) *URL

ResolveReference разрешает ссылку URI на абсолютный URI из абсолютного базового URI u, согласно RFC 3986. Ссылка URI может быть относительной или абсолютной. ResolveReference всегда возвращает новый экземпляр URL, даже если возвращенный URL идентичен базовому или ref. Если ref является абсолютным URL, ResolveReference игнорирует base и возвращает копию ref.

Пример использования метода ResolveReference

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u, err := url.Parse("../../..//search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    base, err := url.Parse("http://example.com/directory/")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(base.ResolveReference(u))
}

Вывод

http://example.com/search?q=dotnet

Пример в песочнице https://play.golang.org/p/WGNUEkswSmY

Метод String

func (u *URL) String() string

String повторно собирает URL в допустимую строку URL-адреса. Общий вид результата — один из:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

Если u.Opaque не пусто, String использует первую форму; в противном случае используется вторая форма. Любые символы, отличные от ASCII, в host экранируются. Чтобы получить путь (path), String использует u.EscapedPath().

Во второй форме действуют следующие правила:

- если u.Scheme пуст, scheme: не отображается.
- если u.User равен nil, userinfo@ опускается.
- если u.Host пуст, host/ не указывается.
- если u.Scheme и u.Host пусты, а u.User - nil,
    вся часть scheme://userinfo@host/ опускается.
- если u.Host не пуст и u.Path начинается с /,
    форма host/path не добавляет свой /.
- если u.RawQuery пуст, ?query опускается.
- если u.Fragment пуст, #fragment опускается.

Пример использования метода String

package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme:   "https",
        User:     url.UserPassword("me", "pass"),
        Host:     "example.com",
        Path:     "foo/bar",
        RawQuery: "x=1&y=2",
        Fragment: "anchor",
    }
    fmt.Println(u.String())
    u.Opaque = "opaque"
    fmt.Println(u.String())
}

Вывод

https://me:pass@example.com/foo/bar?x=1&y=2#anchor
https:opaque?x=1&y=2#anchor

Пример в песочнице https://play.golang.org/p/trgWI4TlgAP

Метод UnmarshalBinary

func (u *URL) UnmarshalBinary(text []byte) error

Преобразует срез байтов и наполняет поля структуры URL, на которой был вызван. Возвращает ошибку в случае ее возникновения при анализе среза байтов.

Пример использования метода UnmarshalBinary

package main

import (
    "fmt"
    "log"
    "net/url"
)

func main() {
    u := &url.URL{}
    err := u.UnmarshalBinary([]byte("https://example.org/foo"))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%sn", u)
}

Вывод

https://example.org/foo

Пример в песочнице https://play.golang.org/p/SyeYQ-6_Vy4

Тип Values

type Values map[string][]string

Values сопоставляет строковый ключ со списком значений. Обычно он используется для параметров запроса и значений формы. В отличие от карты http.Header, ключи в карте Values чувствительны к регистру.

Пример использования типа Values

package main

import (
    "fmt"
    "net/url"
)

func main() {
    v := url.Values{}
    v.Set("name", "Ava")
    v.Add("friend", "Jess")
    v.Add("friend", "Sarah")
    v.Add("friend", "Zoe")
    fmt.Println(v.Encode())
    fmt.Println(v.Get("name"))
    fmt.Println(v.Get("friend"))
    fmt.Println(v["friend"])
    fmt.Println(v.Has("name"))
    v.Del("name")
    fmt.Println(v.Encode())
}

Вывод

friend=Jess&friend=Sarah&friend=Zoe&name=Ava
Ava
Jess
[Jess Sarah Zoe]
true
friend=Jess&friend=Sarah&friend=Zoe

Пример в песочнице https://play.golang.org/p/9Gvyq0Rnlx7

Функция ParseQuery

func ParseQuery(query string) (Values, error)

ParseQuery анализирует строку запроса в кодировке URL и возвращает карту, в которой перечислены значения, указанные для каждого ключа. ParseQuery всегда возвращает ненулевую карту, содержащую все найденные допустимые параметры запроса; err описывает первую обнаруженную ошибку декодирования, если таковая имеется.

Ожидается, что запрос будет списком настроек ключ = значение, разделенных амперсандами. Параметр без знака равенства интерпретируется как ключ, установленный на пустое значение. Параметры, содержащие точку с запятой без кодировки URL, считаются недействительными.

Пример использования функции ParseQuery

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/url"
    "strings"
)

func main() {
    m, err := url.ParseQuery(`x=1&y=2&y=3`)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(toJSON(m))
}

func toJSON(m interface{}) string {
    js, err := json.Marshal(m)
    if err != nil {
        log.Fatal(err)
    }
    return strings.ReplaceAll(string(js), ",", ", ")
}

Вывод

{"x":["1"], "y":["2", "3"]}

Пример в песочнице https://play.golang.org/p/3ytJDMl5P5y

Методы типа Values

func (v Values) Add(key, value string)

Add добавляет значение к ключу. Он добавляется к любым существующим значениям, связанным с ключом.

func (v Values) Del(key string)

Del удаляет значения, связанные с ключом.

func (v Values) Encode() string

Encode кодирует значения в форму «закодированный URL» («bar=baz&foo=quux»), отсортированные по ключу.

func (v Values) Get(key string) string

Get получает первое значение, связанное с данным ключом. Если с ключом не связаны никакие значения, Get возвращает пустую строку. Чтобы получить доступ к нескольким значениям, используйте карту напрямую.

func (v Values) Has(key string) bool

Has проверяет, установлен ли данный ключ.

func (v Values) Set(key, value string)

Set устанавливает значение ключа. Он заменяет любые существующие значения.


Читайте также:

  • Веб-приложение на Go: введение в пакет net/http, пример веб-сервера на Go
  • Пакет net/http, краткий обзор
  • Разработка RESTful API с помощью Go и Gin
  • Функции пакета net/http, примеры

Канал в Телеграм

  • import "net/url"
  • Overview
  • Index
  • Examples

Overview

Пакет url разбирает URL и реализует экранирование запросов.

Index

  • func JoinPath (базовая строка, элемент … строка) (строка результата, ошибка ошибки)
  • включить строку PathEscape(s string)
  • func PathUnescape(s string)(строка,ошибка)
  • строка функционала QueryEscape(s string)
  • funceryUnescape(s string)(строка,ошибка)
  • type Error
  • функция (и *Error)Error()строка
  • функция (e *Error)Temporary()bool
  • func (e *Error) Timeout() bool
  • ошибка Unwrap()func (e *Error)Unwrap()error
  • type EscapeError
  • строка Error()func (и EscapeError)Error()
  • type InvalidHostError
  • строка Error()func (и InvalidHostError)
  • type URL
  • func Parse(rawURL string) (*URL, error)
  • func ParseRequestURI(rawURL string) (*URL, error)
  • func (u *URL) EscapedFragment() string
  • функция (u *URL)EscapedPath()строка
  • func (u *URL) Hostname() string
  • функционал (u *URL)IsAbs()bool
  • func (u *URL) JoinPath(elem …string) *URL
  • func (u *URL)MarshalBinary()(text []byte,error)
  • func (u *URL)Parse(ref string)(*URL,ошибка)
  • func (u *URL) Port() string
  • func (u *URL) Query() Values
  • func (u *URL) Redacted() string
  • func (u *URL) RequestURI() string
  • func (u *URL) ResolveReference(ref *URL) *URL
  • func (u *URL) String() string
  • func (u *URL)UnmarshalBinary(text []byte)ошибка
  • type Userinfo
  • включить опцию User(username string)*Userinfo
  • включить UserPassword(имя пользователя,строка пароля)*Userinfo
  • func (u *Userinfo) Password() (string, bool)
  • func (u *Userinfo)String()строка
  • func (u *Userinfo) Username() string
  • type Values
  • включить ParseQuery (строка запроса)(Значения,ошибка)
  • func (v Values) Add(key, value string)
  • func (v Values)Del(строка ключа)
  • func (v Values) Encode() string
  • func (v Values) Get(key string) string
  • func (v Values) Has(key string) bool
  • func (v Values) Set(key, value string)

Package files

url.go

func JoinPath1.19

func JoinPath(base string, elem ...string) (result string, err error)

JoinPath возвращает строку URL-адреса с предоставленными элементами пути, присоединенными к существующему пути базы, и полученный путь очищен от любых элементов ./ или ../.

func PathEscape1.8

func PathEscape(s string) string

PathEscape экранирует строку,чтобы ее можно было безопасно разместить внутри сегмента пути URL,заменяя специальные символы (включая /)на последовательности %XX по мере необходимости.

Example

Code:

path := url.PathEscape("my/cool+blog&about,stuff")
fmt.Println(path)

Output:

my%2Fcool+blog&about%2Cstuff

func PathUnescape1.8

func PathUnescape(s string) (string, error)

PathUnescape выполняет обратное преобразование PathEscape,преобразуя каждую 3-байтовую закодированную подстроку формы «%AB» в шестнадцатеричный дешифрованный байт 0xAB.Возвращает ошибку,если за любым % не следуют две шестнадцатеричные цифры.

PathUnescape идентичен QueryUnescape за исключением того,что он не отбрасывает символ ‘+’ к » (пробел).

Example

Code:

escapedPath := "my%2Fcool+blog&about%2Cstuff"
path, err := url.PathUnescape(escapedPath)
if err != nil {
    log.Fatal(err)
}
fmt.Println(path)

Output:

my/cool+blog&about,stuff

func QueryEscape

func QueryEscape(s string) string

QueryEscape экранирует строку,чтобы ее можно было безопасно поместить внутри URL запроса.

Example

Code:

query := url.QueryEscape("my/cool+blog&about,stuff")
fmt.Println(query)

Output:

my%2Fcool%2Bblog%26about%2Cstuff

func QueryUnescape

func QueryUnescape(s string) (string, error)

QueryUnescape выполняет обратное преобразование QueryEscape,преобразуя каждую 3-байтовую закодированную подстроку формы «%AB» в шестнадцатеричный дешифрованный байт 0xAB.Возвращает ошибку,если за любым % не следуют две шестнадцатеричные цифры.

Example

Code:

escapedQuery := "my%2Fcool%2Bblog%26about%2Cstuff"
query, err := url.QueryUnescape(escapedQuery)
if err != nil {
    log.Fatal(err)
}
fmt.Println(query)

Output:

my/cool+blog&about,stuff

type Error

Ошибка сообщает об ошибке,а также о вызвавшей ее операции и URL-адресе.

type Error struct {
    Op  string
    URL string
    Err error
}

func (*Error) Error

func (e *Error) Error() string

func (*Error) Temporary1.6

func (e *Error) Temporary() bool

func (*Ошибка)Таймаут1.6

func (e *Error) Timeout() bool

func (*Error) Unwrap1.13

func (e *Error) Unwrap() error

type EscapeError

type EscapeError string

func (EscapeError)Ошибка

func (e EscapeError) Error() string

type InvalidHostError1.6

type InvalidHostError string

func (InvalidHostError) Error1.6

func (e InvalidHostError) Error() string

type URL

URL представляет собой разобранный URL (технически-ссылка на URI).

Представлена общая форма:

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

URL,которые не начинаются со слеша после схемы,интерпретируются как:

scheme:opaque[?query][

Обратите внимание,что поле Path хранится в декодированном виде:/%47%6f%2f становится /Go/.Следствием этого является то,что невозможно определить,какие слэши в Path были слэшами в исходном URL,а какие-%2f.Это различие редко важно,но когда оно есть,код должен использовать RawPath,необязательное поле,которое устанавливается только в том случае,если кодировка по умолчанию отличается от Path.

Метод URL String использует метод EscapedPath для получения пути.См.подробнее метод EscapedPath.

type URL struct {
    Scheme      string
    Opaque      string    
    User        *Userinfo 
    Host        string    
    Path        string    
    RawPath     string    
    OmitHost    bool      
    ForceQuery  bool      
    RawQuery    string    
    Fragment    string    
    RawFragment string    
}

Example

Code:

u, err := url.Parse("http://bing.com/search?q=dotnet")
if err != nil {
    log.Fatal(err)
}
u.Scheme = "https"
u.Host = "google.com"
q := u.Query()
q.Set("q", "golang")
u.RawQuery = q.Encode()
fmt.Println(u)

Output:

https://google.com/search?q=golang

Example (Roundtrip)

Code:

u, err := url.Parse("https://example.com/foo%2fbar")
if err != nil {
    log.Fatal(err)
}
fmt.Println(u.Path)
fmt.Println(u.RawPath)
fmt.Println(u.String())

Output:

/foo/bar
/foo%2fbar
https://example.com/foo%2fbar

func Parse

func Parse(rawURL string) (*URL, error)

Parse разбирает необработанный url в структуру URL.

url может быть относительным (путь без имени хоста)или абсолютным (начинающимся со схемы).Попытка разобрать имя хоста и путь без схемы является недействительной,но не обязательно вернет ошибку из-за неоднозначности разбора.

func ParseRequestURI

func ParseRequestURI(rawURL string) (*URL, error)

ParseRequestURI анализирует необработанный url в структуру URL.Предполагается,что url был получен в HTTP-запросе,поэтому url интерпретируется только как абсолютный URI или абсолютный путь.Предполагается,что строка url не содержит суффикса #fragment.(Веб-браузеры удаляют #fragment перед отправкой URL на веб-сервер).

func (*URL) EscapedFragment1.15

func (u *URL) EscapedFragment() string

EscapedFragment возвращает экранированную форму u.Fragment.В общем случае существует несколько возможных экранированных форм любого фрагмента.EscapedFragment возвращает u.RawFragment,если он является допустимой эскейп-формой u.Fragment.В противном случае EscapedFragment игнорирует u.RawFragment и вычисляет экранированную форму самостоятельно.Метод String использует EscapedFragment для построения своего результата.В общем случае код должен вызывать EscapedFragment вместо того,чтобы читать u.RawFragment напрямую.

Example

Code:

u, err := url.Parse("http://example.com/#x/y%2Fz")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Fragment:", u.Fragment)
fmt.Println("RawFragment:", u.RawFragment)
fmt.Println("EscapedFragment:", u.EscapedFragment())

Output:

Fragment: x/y/z
RawFragment: x/y%2Fz
EscapedFragment: x/y%2Fz

func (*URL) EscapedPath1.5

func (u *URL) EscapedPath() string

EscapedPath возвращает сбежавшую форму u.Path.В общем,существует несколько возможных экзекутивных форм любого пути.EscapedPath возвращает u.RawPath,когда он является действительным экранированием u.Path.В противном случае EscapedPath игнорирует u.RawPath и самостоятельно вычисляет экранированную форму.Методы String и RequestURI используют EscapedPath для построения своих результатов.В общем случае,код должен вызывать EscapedPath вместо того,чтобы читать u.RawPath напрямую.

Example

Code:

u, err := url.Parse("http://example.com/x/y%2Fz")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Path:", u.Path)
fmt.Println("RawPath:", u.RawPath)
fmt.Println("EscapedPath:", u.EscapedPath())

Output:

Path: /x/y/z
RawPath: /x/y%2Fz
EscapedPath: /x/y%2Fz

© Google, Inc.
Licensed under the Creative Commons Attribution License 3.0.
http://golang.org/pkg/net/url/


Go

1.19

  • Package smtp

  • Package textproto

  • func (*URL) Hostname 1.8

  • Package exec

Понравилась статья? Поделить с друзьями:
  • Golang type error to string
  • Glvw error 65543
  • Golang syntax error unexpected at end of statement
  • Golang syntax error non declaration statement outside function body
  • Glu32 dll ошибка как исправить