mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-15 12:23:41 +00:00
rtl8720dn: add support for http.Get(), http.Post() and cookie
This commit is contained in:
@@ -0,0 +1,504 @@
|
||||
// Copyright 2012 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 cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
|
||||
package cookiejar
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
// PublicSuffixList provides the public suffix of a domain. For example:
|
||||
// - the public suffix of "example.com" is "com",
|
||||
// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and
|
||||
// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".
|
||||
//
|
||||
// Implementations of PublicSuffixList must be safe for concurrent use by
|
||||
// multiple goroutines.
|
||||
//
|
||||
// An implementation that always returns "" is valid and may be useful for
|
||||
// testing but it is not secure: it means that the HTTP server for foo.com can
|
||||
// set a cookie for bar.com.
|
||||
//
|
||||
// A public suffix list implementation is in the package
|
||||
// golang.org/x/net/publicsuffix.
|
||||
type PublicSuffixList interface {
|
||||
// PublicSuffix returns the public suffix of domain.
|
||||
//
|
||||
// TODO: specify which of the caller and callee is responsible for IP
|
||||
// addresses, for leading and trailing dots, for case sensitivity, and
|
||||
// for IDN/Punycode.
|
||||
PublicSuffix(domain string) string
|
||||
|
||||
// String returns a description of the source of this public suffix
|
||||
// list. The description will typically contain something like a time
|
||||
// stamp or version number.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Options are the options for creating a new Jar.
|
||||
type Options struct {
|
||||
// PublicSuffixList is the public suffix list that determines whether
|
||||
// an HTTP server can set a cookie for a domain.
|
||||
//
|
||||
// A nil value is valid and may be useful for testing but it is not
|
||||
// secure: it means that the HTTP server for foo.co.uk can set a cookie
|
||||
// for bar.co.uk.
|
||||
PublicSuffixList PublicSuffixList
|
||||
}
|
||||
|
||||
// Jar implements the http.CookieJar interface from the net/http package.
|
||||
type Jar struct {
|
||||
psList PublicSuffixList
|
||||
|
||||
// mu locks the remaining fields.
|
||||
mu sync.Mutex
|
||||
|
||||
// entries is a set of entries, keyed by their eTLD+1 and subkeyed by
|
||||
// their name/domain/path.
|
||||
entries map[string]map[string]entry
|
||||
|
||||
// nextSeqNum is the next sequence number assigned to a new cookie
|
||||
// created SetCookies.
|
||||
nextSeqNum uint64
|
||||
}
|
||||
|
||||
// New returns a new cookie jar. A nil *Options is equivalent to a zero
|
||||
// Options.
|
||||
func New(o *Options) (*Jar, error) {
|
||||
jar := &Jar{
|
||||
entries: make(map[string]map[string]entry),
|
||||
}
|
||||
if o != nil {
|
||||
jar.psList = o.PublicSuffixList
|
||||
}
|
||||
return jar, nil
|
||||
}
|
||||
|
||||
// entry is the internal representation of a cookie.
|
||||
//
|
||||
// This struct type is not used outside of this package per se, but the exported
|
||||
// fields are those of RFC 6265.
|
||||
type entry struct {
|
||||
Name string
|
||||
Value string
|
||||
Domain string
|
||||
Path string
|
||||
SameSite string
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
Persistent bool
|
||||
HostOnly bool
|
||||
Expires time.Time
|
||||
Creation time.Time
|
||||
LastAccess time.Time
|
||||
|
||||
// seqNum is a sequence number so that Cookies returns cookies in a
|
||||
// deterministic order, even for cookies that have equal Path length and
|
||||
// equal Creation time. This simplifies testing.
|
||||
seqNum uint64
|
||||
}
|
||||
|
||||
// id returns the domain;path;name triple of e as an id.
|
||||
func (e *entry) id() string {
|
||||
return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
|
||||
}
|
||||
|
||||
// shouldSend determines whether e's cookie qualifies to be included in a
|
||||
// request to host/path. It is the caller's responsibility to check if the
|
||||
// cookie is expired.
|
||||
func (e *entry) shouldSend(https bool, host, path string) bool {
|
||||
return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
|
||||
}
|
||||
|
||||
// domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
|
||||
func (e *entry) domainMatch(host string) bool {
|
||||
if e.Domain == host {
|
||||
return true
|
||||
}
|
||||
return !e.HostOnly && hasDotSuffix(host, e.Domain)
|
||||
}
|
||||
|
||||
// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
|
||||
func (e *entry) pathMatch(requestPath string) bool {
|
||||
if requestPath == e.Path {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(requestPath, e.Path) {
|
||||
if e.Path[len(e.Path)-1] == '/' {
|
||||
return true // The "/any/" matches "/any/path" case.
|
||||
} else if requestPath[len(e.Path)] == '/' {
|
||||
return true // The "/any" matches "/any/path" case.
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasDotSuffix reports whether s ends in "."+suffix.
|
||||
func hasDotSuffix(s, suffix string) bool {
|
||||
return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
|
||||
}
|
||||
|
||||
// Cookies implements the Cookies method of the http.CookieJar interface.
|
||||
//
|
||||
// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
|
||||
func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
|
||||
return j.cookies(u, time.Now())
|
||||
}
|
||||
|
||||
// cookies is like Cookies but takes the current time as a parameter.
|
||||
func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return cookies
|
||||
}
|
||||
host, err := canonicalHost(u.Host)
|
||||
if err != nil {
|
||||
return cookies
|
||||
}
|
||||
key := jarKey(host, j.psList)
|
||||
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
submap := j.entries[key]
|
||||
if submap == nil {
|
||||
return cookies
|
||||
}
|
||||
|
||||
https := u.Scheme == "https"
|
||||
path := u.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
modified := false
|
||||
var selected []entry
|
||||
for id, e := range submap {
|
||||
if e.Persistent && !e.Expires.After(now) {
|
||||
delete(submap, id)
|
||||
modified = true
|
||||
continue
|
||||
}
|
||||
if !e.shouldSend(https, host, path) {
|
||||
continue
|
||||
}
|
||||
e.LastAccess = now
|
||||
submap[id] = e
|
||||
selected = append(selected, e)
|
||||
modified = true
|
||||
}
|
||||
if modified {
|
||||
if len(submap) == 0 {
|
||||
delete(j.entries, key)
|
||||
} else {
|
||||
j.entries[key] = submap
|
||||
}
|
||||
}
|
||||
|
||||
// sort according to RFC 6265 section 5.4 point 2: by longest
|
||||
// path and then by earliest creation time.
|
||||
sort.Slice(selected, func(i, j int) bool {
|
||||
s := selected
|
||||
if len(s[i].Path) != len(s[j].Path) {
|
||||
return len(s[i].Path) > len(s[j].Path)
|
||||
}
|
||||
if !s[i].Creation.Equal(s[j].Creation) {
|
||||
return s[i].Creation.Before(s[j].Creation)
|
||||
}
|
||||
return s[i].seqNum < s[j].seqNum
|
||||
})
|
||||
for _, e := range selected {
|
||||
cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
|
||||
}
|
||||
|
||||
return cookies
|
||||
}
|
||||
|
||||
// SetCookies implements the SetCookies method of the http.CookieJar interface.
|
||||
//
|
||||
// It does nothing if the URL's scheme is not HTTP or HTTPS.
|
||||
func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
||||
j.setCookies(u, cookies, time.Now())
|
||||
}
|
||||
|
||||
// setCookies is like SetCookies but takes the current time as parameter.
|
||||
func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
|
||||
if len(cookies) == 0 {
|
||||
return
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
return
|
||||
}
|
||||
host, err := canonicalHost(u.Host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key := jarKey(host, j.psList)
|
||||
defPath := defaultPath(u.Path)
|
||||
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
submap := j.entries[key]
|
||||
|
||||
modified := false
|
||||
for _, cookie := range cookies {
|
||||
e, remove, err := j.newEntry(cookie, now, defPath, host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
id := e.id()
|
||||
if remove {
|
||||
if submap != nil {
|
||||
if _, ok := submap[id]; ok {
|
||||
delete(submap, id)
|
||||
modified = true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if submap == nil {
|
||||
submap = make(map[string]entry)
|
||||
}
|
||||
|
||||
if old, ok := submap[id]; ok {
|
||||
e.Creation = old.Creation
|
||||
e.seqNum = old.seqNum
|
||||
} else {
|
||||
e.Creation = now
|
||||
e.seqNum = j.nextSeqNum
|
||||
j.nextSeqNum++
|
||||
}
|
||||
e.LastAccess = now
|
||||
submap[id] = e
|
||||
modified = true
|
||||
}
|
||||
|
||||
if modified {
|
||||
if len(submap) == 0 {
|
||||
delete(j.entries, key)
|
||||
} else {
|
||||
j.entries[key] = submap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalHost strips port from host if present and returns the canonicalized
|
||||
// host name.
|
||||
func canonicalHost(host string) (string, error) {
|
||||
var err error
|
||||
host = strings.ToLower(host)
|
||||
if hasPort(host) {
|
||||
host, _, err = net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(host, ".") {
|
||||
// Strip trailing dot from fully qualified domain names.
|
||||
host = host[:len(host)-1]
|
||||
}
|
||||
return toASCII(host)
|
||||
}
|
||||
|
||||
// hasPort reports whether host contains a port number. host may be a host
|
||||
// name, an IPv4 or an IPv6 address.
|
||||
func hasPort(host string) bool {
|
||||
colons := strings.Count(host, ":")
|
||||
if colons == 0 {
|
||||
return false
|
||||
}
|
||||
if colons == 1 {
|
||||
return true
|
||||
}
|
||||
return host[0] == '[' && strings.Contains(host, "]:")
|
||||
}
|
||||
|
||||
// jarKey returns the key to use for a jar.
|
||||
func jarKey(host string, psl PublicSuffixList) string {
|
||||
if isIP(host) {
|
||||
return host
|
||||
}
|
||||
|
||||
var i int
|
||||
if psl == nil {
|
||||
i = strings.LastIndex(host, ".")
|
||||
if i <= 0 {
|
||||
return host
|
||||
}
|
||||
} else {
|
||||
suffix := psl.PublicSuffix(host)
|
||||
if suffix == host {
|
||||
return host
|
||||
}
|
||||
i = len(host) - len(suffix)
|
||||
if i <= 0 || host[i-1] != '.' {
|
||||
// The provided public suffix list psl is broken.
|
||||
// Storing cookies under host is a safe stopgap.
|
||||
return host
|
||||
}
|
||||
// Only len(suffix) is used to determine the jar key from
|
||||
// here on, so it is okay if psl.PublicSuffix("www.buggy.psl")
|
||||
// returns "com" as the jar key is generated from host.
|
||||
}
|
||||
prevDot := strings.LastIndex(host[:i-1], ".")
|
||||
return host[prevDot+1:]
|
||||
}
|
||||
|
||||
// isIP reports whether host is an IP address.
|
||||
func isIP(host string) bool {
|
||||
return net.ParseIP(host) != nil
|
||||
}
|
||||
|
||||
// defaultPath returns the directory part of an URL's path according to
|
||||
// RFC 6265 section 5.1.4.
|
||||
func defaultPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
return "/" // Path is empty or malformed.
|
||||
}
|
||||
|
||||
i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
|
||||
if i == 0 {
|
||||
return "/" // Path has the form "/abc".
|
||||
}
|
||||
return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
|
||||
}
|
||||
|
||||
// newEntry creates an entry from a http.Cookie c. now is the current time and
|
||||
// is compared to c.Expires to determine deletion of c. defPath and host are the
|
||||
// default-path and the canonical host name of the URL c was received from.
|
||||
//
|
||||
// remove records whether the jar should delete this cookie, as it has already
|
||||
// expired with respect to now. In this case, e may be incomplete, but it will
|
||||
// be valid to call e.id (which depends on e's Name, Domain and Path).
|
||||
//
|
||||
// A malformed c.Domain will result in an error.
|
||||
func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
|
||||
e.Name = c.Name
|
||||
|
||||
if c.Path == "" || c.Path[0] != '/' {
|
||||
e.Path = defPath
|
||||
} else {
|
||||
e.Path = c.Path
|
||||
}
|
||||
|
||||
e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
|
||||
if err != nil {
|
||||
return e, false, err
|
||||
}
|
||||
|
||||
// MaxAge takes precedence over Expires.
|
||||
if c.MaxAge < 0 {
|
||||
return e, true, nil
|
||||
} else if c.MaxAge > 0 {
|
||||
e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
|
||||
e.Persistent = true
|
||||
} else {
|
||||
if c.Expires.IsZero() {
|
||||
e.Expires = endOfTime
|
||||
e.Persistent = false
|
||||
} else {
|
||||
if !c.Expires.After(now) {
|
||||
return e, true, nil
|
||||
}
|
||||
e.Expires = c.Expires
|
||||
e.Persistent = true
|
||||
}
|
||||
}
|
||||
|
||||
e.Value = c.Value
|
||||
e.Secure = c.Secure
|
||||
e.HttpOnly = c.HttpOnly
|
||||
|
||||
switch c.SameSite {
|
||||
case http.SameSiteDefaultMode:
|
||||
e.SameSite = "SameSite"
|
||||
case http.SameSiteStrictMode:
|
||||
e.SameSite = "SameSite=Strict"
|
||||
case http.SameSiteLaxMode:
|
||||
e.SameSite = "SameSite=Lax"
|
||||
}
|
||||
|
||||
return e, false, nil
|
||||
}
|
||||
|
||||
var (
|
||||
errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute")
|
||||
errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
|
||||
errNoHostname = errors.New("cookiejar: no host name available (IP only)")
|
||||
)
|
||||
|
||||
// endOfTime is the time when session (non-persistent) cookies expire.
|
||||
// This instant is representable in most date/time formats (not just
|
||||
// Go's time.Time) and should be far enough in the future.
|
||||
var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
// domainAndType determines the cookie's domain and hostOnly attribute.
|
||||
func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
|
||||
if domain == "" {
|
||||
// No domain attribute in the SetCookie header indicates a
|
||||
// host cookie.
|
||||
return host, true, nil
|
||||
}
|
||||
|
||||
if isIP(host) {
|
||||
// According to RFC 6265 domain-matching includes not being
|
||||
// an IP address.
|
||||
// TODO: This might be relaxed as in common browsers.
|
||||
return "", false, errNoHostname
|
||||
}
|
||||
|
||||
// From here on: If the cookie is valid, it is a domain cookie (with
|
||||
// the one exception of a public suffix below).
|
||||
// See RFC 6265 section 5.2.3.
|
||||
if domain[0] == '.' {
|
||||
domain = domain[1:]
|
||||
}
|
||||
|
||||
if len(domain) == 0 || domain[0] == '.' {
|
||||
// Received either "Domain=." or "Domain=..some.thing",
|
||||
// both are illegal.
|
||||
return "", false, errMalformedDomain
|
||||
}
|
||||
domain = strings.ToLower(domain)
|
||||
|
||||
if domain[len(domain)-1] == '.' {
|
||||
// We received stuff like "Domain=www.example.com.".
|
||||
// Browsers do handle such stuff (actually differently) but
|
||||
// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
|
||||
// requiring a reject. 4.1.2.3 is not normative, but
|
||||
// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
|
||||
// (5.1.2) are.
|
||||
return "", false, errMalformedDomain
|
||||
}
|
||||
|
||||
// See RFC 6265 section 5.3 #5.
|
||||
if j.psList != nil {
|
||||
if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
|
||||
if host == domain {
|
||||
// This is the one exception in which a cookie
|
||||
// with a domain attribute is a host cookie.
|
||||
return host, true, nil
|
||||
}
|
||||
return "", false, errIllegalDomain
|
||||
}
|
||||
}
|
||||
|
||||
// The domain must domain-match host: www.mycompany.com cannot
|
||||
// set cookies for .ourcompetitors.com.
|
||||
if host != domain && !hasDotSuffix(host, domain) {
|
||||
return "", false, errIllegalDomain
|
||||
}
|
||||
|
||||
return domain, false, nil
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2012 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 cookiejar
|
||||
|
||||
// This file implements the Punycode algorithm from RFC 3492.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// These parameter values are specified in section 5.
|
||||
//
|
||||
// All computation is done with int32s, so that overflow behavior is identical
|
||||
// regardless of whether int is 32-bit or 64-bit.
|
||||
const (
|
||||
base int32 = 36
|
||||
damp int32 = 700
|
||||
initialBias int32 = 72
|
||||
initialN int32 = 128
|
||||
skew int32 = 38
|
||||
tmax int32 = 26
|
||||
tmin int32 = 1
|
||||
)
|
||||
|
||||
// encode encodes a string as specified in section 6.3 and prepends prefix to
|
||||
// the result.
|
||||
//
|
||||
// The "while h < length(input)" line in the specification becomes "for
|
||||
// remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes.
|
||||
func encode(prefix, s string) (string, error) {
|
||||
output := make([]byte, len(prefix), len(prefix)+1+2*len(s))
|
||||
copy(output, prefix)
|
||||
delta, n, bias := int32(0), initialN, initialBias
|
||||
b, remaining := int32(0), int32(0)
|
||||
for _, r := range s {
|
||||
if r < utf8.RuneSelf {
|
||||
b++
|
||||
output = append(output, byte(r))
|
||||
} else {
|
||||
remaining++
|
||||
}
|
||||
}
|
||||
h := b
|
||||
if b > 0 {
|
||||
output = append(output, '-')
|
||||
}
|
||||
for remaining != 0 {
|
||||
m := int32(0x7fffffff)
|
||||
for _, r := range s {
|
||||
if m > r && r >= n {
|
||||
m = r
|
||||
}
|
||||
}
|
||||
delta += (m - n) * (h + 1)
|
||||
if delta < 0 {
|
||||
return "", fmt.Errorf("cookiejar: invalid label %q", s)
|
||||
}
|
||||
n = m
|
||||
for _, r := range s {
|
||||
if r < n {
|
||||
delta++
|
||||
if delta < 0 {
|
||||
return "", fmt.Errorf("cookiejar: invalid label %q", s)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r > n {
|
||||
continue
|
||||
}
|
||||
q := delta
|
||||
for k := base; ; k += base {
|
||||
t := k - bias
|
||||
if t < tmin {
|
||||
t = tmin
|
||||
} else if t > tmax {
|
||||
t = tmax
|
||||
}
|
||||
if q < t {
|
||||
break
|
||||
}
|
||||
output = append(output, encodeDigit(t+(q-t)%(base-t)))
|
||||
q = (q - t) / (base - t)
|
||||
}
|
||||
output = append(output, encodeDigit(q))
|
||||
bias = adapt(delta, h+1, h == b)
|
||||
delta = 0
|
||||
h++
|
||||
remaining--
|
||||
}
|
||||
delta++
|
||||
n++
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func encodeDigit(digit int32) byte {
|
||||
switch {
|
||||
case 0 <= digit && digit < 26:
|
||||
return byte(digit + 'a')
|
||||
case 26 <= digit && digit < 36:
|
||||
return byte(digit + ('0' - 26))
|
||||
}
|
||||
panic("cookiejar: internal error in punycode encoding")
|
||||
}
|
||||
|
||||
// adapt is the bias adaptation function specified in section 6.1.
|
||||
func adapt(delta, numPoints int32, firstTime bool) int32 {
|
||||
if firstTime {
|
||||
delta /= damp
|
||||
} else {
|
||||
delta /= 2
|
||||
}
|
||||
delta += delta / numPoints
|
||||
k := int32(0)
|
||||
for delta > ((base-tmin)*tmax)/2 {
|
||||
delta /= base - tmin
|
||||
k += base
|
||||
}
|
||||
return k + (base-tmin+1)*delta/(delta+skew)
|
||||
}
|
||||
|
||||
// Strictly speaking, the remaining code below deals with IDNA (RFC 5890 and
|
||||
// friends) and not Punycode (RFC 3492) per se.
|
||||
|
||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||
const acePrefix = "xn--"
|
||||
|
||||
// toASCII converts a domain or domain label to its ASCII form. For example,
|
||||
// toASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||
// toASCII("golang") is "golang".
|
||||
func toASCII(s string) (string, error) {
|
||||
if ascii(s) {
|
||||
return s, nil
|
||||
}
|
||||
labels := strings.Split(s, ".")
|
||||
for i, label := range labels {
|
||||
if !ascii(label) {
|
||||
a, err := encode(acePrefix, label)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
labels[i] = a
|
||||
}
|
||||
}
|
||||
return strings.Join(labels, "."), nil
|
||||
}
|
||||
|
||||
func ascii(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] >= utf8.RuneSelf {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user