* fix(channel): improve proxy client compatibility and cache lifecycle * test(controller): use non-fatal assertions for channel tests
78 lines
2.3 KiB
Go
78 lines
2.3 KiB
Go
package common
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ParseProxyURLStrict validates and normalizes a proxy URL for persistence.
|
|
func ParseProxyURLStrict(rawProxyURL string) (*url.URL, error) {
|
|
parsedURL, _, err := parseProxyURL(rawProxyURL, false)
|
|
return parsedURL, err
|
|
}
|
|
|
|
// ParseProxyURLRuntime validates and normalizes a proxy URL for runtime use.
|
|
// The boolean result reports whether a legacy path, query, or fragment was removed.
|
|
func ParseProxyURLRuntime(rawProxyURL string) (*url.URL, bool, error) {
|
|
return parseProxyURL(rawProxyURL, true)
|
|
}
|
|
|
|
func parseProxyURL(rawProxyURL string, allowLegacySuffix bool) (*url.URL, bool, error) {
|
|
trimmedProxyURL := strings.TrimSpace(rawProxyURL)
|
|
if trimmedProxyURL == "" {
|
|
return nil, false, nil
|
|
}
|
|
|
|
parsedURL, err := url.Parse(trimmedProxyURL)
|
|
if err != nil {
|
|
return nil, false, fmt.Errorf("invalid proxy URL")
|
|
}
|
|
parsedURL.Scheme = strings.ToLower(parsedURL.Scheme)
|
|
switch parsedURL.Scheme {
|
|
case "http", "https", "socks5", "socks5h":
|
|
default:
|
|
return nil, false, fmt.Errorf("proxy URL must use http, https, socks5, or socks5h")
|
|
}
|
|
if parsedURL.Hostname() == "" {
|
|
return nil, false, fmt.Errorf("proxy URL must include a host")
|
|
}
|
|
if portText := parsedURL.Port(); portText != "" {
|
|
port, err := strconv.Atoi(portText)
|
|
if err != nil || port < 1 || port > 65535 {
|
|
return nil, false, fmt.Errorf("proxy URL must include a valid port")
|
|
}
|
|
}
|
|
|
|
hasQuery := parsedURL.RawQuery != "" || parsedURL.ForceQuery
|
|
hasFragment := strings.Contains(trimmedProxyURL, "#")
|
|
escapedPath := parsedURL.EscapedPath()
|
|
hasNonRootPath := escapedPath != "" && escapedPath != "/"
|
|
legacySuffixStripped := hasQuery || hasFragment || hasNonRootPath
|
|
if !allowLegacySuffix {
|
|
switch {
|
|
case hasQuery:
|
|
return nil, false, fmt.Errorf("proxy URL must not include a query")
|
|
case hasFragment:
|
|
return nil, false, fmt.Errorf("proxy URL must not include a fragment")
|
|
case hasNonRootPath:
|
|
return nil, false, fmt.Errorf("proxy URL must not include a path")
|
|
}
|
|
}
|
|
|
|
parsedURL.Path = ""
|
|
parsedURL.RawPath = ""
|
|
parsedURL.RawQuery = ""
|
|
parsedURL.ForceQuery = false
|
|
parsedURL.Fragment = ""
|
|
parsedURL.RawFragment = ""
|
|
|
|
if (parsedURL.Scheme == "socks5" || parsedURL.Scheme == "socks5h") && parsedURL.Port() == "" {
|
|
parsedURL.Host = net.JoinHostPort(parsedURL.Hostname(), "1080")
|
|
}
|
|
|
|
return parsedURL, legacySuffixStripped, nil
|
|
}
|