package lspool import ( "fmt" "net/url" "os" "os/exec" "strings" "github.com/Wei-Shaw/sub2api/internal/pkg/proxyurl" ) type lsLaunchPlan struct { cmd *exec.Cmd effectiveProxyURL string proxyMode string cleanup func() } func prepareLSLaunchPlan(binPath string, args []string, rawProxyURL string) (*lsLaunchPlan, error) { normalized, parsed, err := proxyurl.Parse(rawProxyURL) if err != nil { return nil, err } plan := &lsLaunchPlan{ cmd: exec.Command(binPath, args...), proxyMode: "direct", } if parsed == nil { return plan, nil } switch strings.ToLower(parsed.Scheme) { case "http", "https": plan.effectiveProxyURL = normalized plan.proxyMode = "env-http-proxy" return plan, nil case "socks5", "socks5h": if proxychainsPath, err := exec.LookPath("proxychains4"); err == nil { cfgPath, err := writeProxychainsConfig(parsed) if err != nil { return nil, err } plan.cmd = exec.Command(proxychainsPath, append([]string{"-f", cfgPath, binPath}, args...)...) plan.proxyMode = "proxychains4" plan.cleanup = func() { _ = os.Remove(cfgPath) } return plan, nil } effectiveProxyURL, err := prepareLSProxyURL(normalized) if err != nil { return nil, err } plan.effectiveProxyURL = effectiveProxyURL plan.proxyMode = "http-connect-bridge" return plan, nil default: return nil, fmt.Errorf("unsupported LS proxy scheme: %s", parsed.Scheme) } } func writeProxychainsConfig(proxyURL *url.URL) (string, error) { content, err := buildProxychainsConfig(proxyURL) if err != nil { return "", err } file, err := os.CreateTemp("", "sub2api-proxychains-*.conf") if err != nil { return "", fmt.Errorf("create proxychains config: %w", err) } if _, err := file.WriteString(content); err != nil { _ = file.Close() _ = os.Remove(file.Name()) return "", fmt.Errorf("write proxychains config: %w", err) } if err := file.Close(); err != nil { _ = os.Remove(file.Name()) return "", fmt.Errorf("close proxychains config: %w", err) } return file.Name(), nil } func buildProxychainsConfig(proxyURL *url.URL) (string, error) { if proxyURL == nil { return "", fmt.Errorf("proxy url is nil") } if scheme := strings.ToLower(proxyURL.Scheme); scheme != "socks5" && scheme != "socks5h" { return "", fmt.Errorf("proxychains only supports socks5/socks5h, got %s", proxyURL.Scheme) } host := strings.TrimSpace(proxyURL.Hostname()) port := strings.TrimSpace(proxyURL.Port()) if host == "" { return "", fmt.Errorf("proxy host is empty") } if port == "" { port = "1080" } username := proxyURL.User.Username() password, _ := proxyURL.User.Password() if strings.ContainsAny(username, " \t\r\n") || strings.ContainsAny(password, " \t\r\n") { return "", fmt.Errorf("proxychains credentials cannot contain whitespace") } var builder strings.Builder builder.WriteString("strict_chain\n") builder.WriteString("proxy_dns\n") builder.WriteString("remote_dns_subnet 224\n") builder.WriteString("tcp_connect_time_out 8000\n") builder.WriteString("tcp_read_time_out 15000\n") builder.WriteString("localnet 127.0.0.0/255.0.0.0\n") builder.WriteString("localnet ::1/128\n") builder.WriteString("[ProxyList]\n") builder.WriteString("socks5 ") builder.WriteString(host) builder.WriteString(" ") builder.WriteString(port) if username != "" { builder.WriteString(" ") builder.WriteString(username) if password != "" { builder.WriteString(" ") builder.WriteString(password) } } builder.WriteString("\n") return builder.String(), nil }