167 lines
3.0 KiB
Go
167 lines
3.0 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type encoding int
|
|
|
|
const (
|
|
encodePath encoding = 1 + iota
|
|
encodePathSegment
|
|
encodeHost
|
|
encodeZone
|
|
encodeUserPassword
|
|
encodeQueryComponent
|
|
encodeFragment
|
|
)
|
|
|
|
const upperhex = "0123456789ABCDEF"
|
|
|
|
func ParseURLToString(u *url.URL) 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.Host != "" || u.Path != "" || u.User != nil {
|
|
buf.WriteString("//")
|
|
}
|
|
if ui := u.User; ui != nil {
|
|
username := ui.Username()
|
|
password, _ := ui.Password()
|
|
buf.WriteString(fmt.Sprintf("%s:%s", username, password))
|
|
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 {
|
|
if i := strings.IndexByte(path, ':'); i > -1 && strings.IndexByte(path[:i], '/') == -1 {
|
|
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()
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func shouldEscape(c byte, mode encoding) bool {
|
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
|
|
return false
|
|
}
|
|
|
|
if mode == encodeHost || mode == encodeZone {
|
|
switch c {
|
|
case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"':
|
|
return false
|
|
}
|
|
}
|
|
|
|
switch c {
|
|
case '-', '_', '.', '~':
|
|
return false
|
|
|
|
case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@':
|
|
switch mode {
|
|
case encodePath:
|
|
return c == '?'
|
|
|
|
case encodePathSegment:
|
|
return c == '/' || c == ';' || c == ',' || c == '?'
|
|
|
|
case encodeUserPassword:
|
|
return c == '@' || c == '/' || c == '?' || c == ':'
|
|
|
|
case encodeQueryComponent:
|
|
return true
|
|
|
|
case encodeFragment:
|
|
return false
|
|
}
|
|
}
|
|
|
|
if mode == encodeFragment {
|
|
switch c {
|
|
case '!', '(', ')', '*':
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|