package proxyutil import ( "fmt" "net" "net/url" "strings" ) // Info is a parsed proxy configuration. // // It supports the following input formats: // - http://host:port // - http://user:pass@host:port // - host:port (defaults to http) // - host:port:user:pass (defaults to http; password may contain ':') // - user:pass@host:port (defaults to http) type Info struct { // URL is the normalized proxy URL (may include credentials). URL *url.URL // Server is the proxy server URL without credentials (scheme + host:port). Server *url.URL Username string Password string } func Parse(raw string) (*Info, error) { raw = strings.TrimSpace(raw) if raw == "" { return &Info{}, nil } // URL-style proxy (has scheme) if strings.Contains(raw, "://") { u, err := url.Parse(raw) if err != nil { return nil, fmt.Errorf("parse proxy url: %w", err) } if u.Scheme == "" || u.Host == "" { return nil, fmt.Errorf("invalid proxy url: %q", raw) } u = &url.URL{ Scheme: strings.ToLower(u.Scheme), Host: u.Host, User: u.User, } user := "" pass := "" if u.User != nil { user = u.User.Username() pass, _ = u.User.Password() } server := &url.URL{Scheme: u.Scheme, Host: u.Host} return &Info{ URL: u, Server: server, Username: user, Password: pass, }, nil } // user:pass@host:port (no scheme) if strings.Contains(raw, "@") { return Parse("http://" + raw) } // host:port[:user:pass] parts := strings.Split(raw, ":") switch { case len(parts) == 2: host, port := parts[0], parts[1] hp := net.JoinHostPort(host, port) u := &url.URL{Scheme: "http", Host: hp} return &Info{URL: u, Server: &url.URL{Scheme: u.Scheme, Host: u.Host}}, nil case len(parts) >= 4: host, port := parts[0], parts[1] user := parts[2] pass := strings.Join(parts[3:], ":") hp := net.JoinHostPort(host, port) u := &url.URL{Scheme: "http", Host: hp, User: url.UserPassword(user, pass)} return &Info{ URL: u, Server: &url.URL{Scheme: u.Scheme, Host: u.Host}, Username: user, Password: pass, }, nil default: return nil, fmt.Errorf("unsupported proxy format: %q", raw) } } // Normalize returns a normalized proxy URL string. // Empty input returns empty output. func Normalize(raw string) (string, error) { info, err := Parse(raw) if err != nil { return "", err } if info.URL == nil { return "", nil } return info.URL.String(), nil }