139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
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
|
|
}
|