Reduce idle CPU (cheap PollUI, binary cache, logbuf cap, DOM fingerprint), speed ping/AWG HostPort, CopyBuffer pool; ship arm64 build and update notes. Co-authored-by: Cursor <cursoragent@cursor.com>
235 lines
5.8 KiB
Go
235 lines
5.8 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ProfileInfo is a safe summary for UI lists (no secrets beyond proxy URI the user already owns).
|
|
type ProfileInfo struct {
|
|
Name string `json:"name"`
|
|
Protocol string `json:"protocol"`
|
|
Proxy string `json:"proxy"`
|
|
Host string `json:"host"`
|
|
Active bool `json:"active"`
|
|
}
|
|
|
|
// ListProfiles returns UI-friendly profile summaries.
|
|
func (c *Config) ListProfiles() []ProfileInfo {
|
|
out := make([]ProfileInfo, 0, len(c.Profiles))
|
|
for _, p := range c.Profiles {
|
|
out = append(out, ProfileInfo{
|
|
Name: p.Name,
|
|
Protocol: string(p.Protocol),
|
|
Proxy: p.Proxy,
|
|
Host: proxyHost(p.Proxy),
|
|
Active: p.Name == c.Active,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ListProfilesForPoll is like ListProfiles but omits Proxy URIs (cheaper idle polls).
|
|
func (c *Config) ListProfilesForPoll() []ProfileInfo {
|
|
out := make([]ProfileInfo, 0, len(c.Profiles))
|
|
for _, p := range c.Profiles {
|
|
out = append(out, ProfileInfo{
|
|
Name: p.Name,
|
|
Protocol: string(p.Protocol),
|
|
Host: proxyHost(p.Proxy),
|
|
Active: p.Name == c.Active,
|
|
})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// RedactProfileList clears Proxy URIs from a profile list (host/protocol remain).
|
|
// Use for periodic UI polls so credentials are not echoed for every profile.
|
|
func RedactProfileList(in []ProfileInfo) []ProfileInfo {
|
|
out := make([]ProfileInfo, len(in))
|
|
for i, p := range in {
|
|
out[i] = p
|
|
out[i].Proxy = ""
|
|
}
|
|
return out
|
|
}
|
|
|
|
func RedactProxyURI(proxy string) string {
|
|
proxy = strings.TrimSpace(proxy)
|
|
if proxy == "" {
|
|
return ""
|
|
}
|
|
lower := strings.ToLower(proxy)
|
|
if strings.Contains(lower, "[interface]") || strings.Contains(lower, "privatekey") {
|
|
return "[конфиг скрыт — откройте редактор профиля]"
|
|
}
|
|
if i := strings.Index(proxy, "://"); i >= 0 {
|
|
scheme := proxy[:i+3]
|
|
rest := proxy[i+3:]
|
|
if at := strings.LastIndex(rest, "@"); at >= 0 {
|
|
return scheme + "***@" + rest[at+1:]
|
|
}
|
|
return scheme + rest
|
|
}
|
|
if at := strings.LastIndex(proxy, "@"); at >= 0 {
|
|
return "***@" + proxy[at+1:]
|
|
}
|
|
return proxyHost(proxy)
|
|
}
|
|
|
|
func proxyHost(proxy string) string {
|
|
proxy = strings.TrimSpace(proxy)
|
|
if proxy == "" {
|
|
return ""
|
|
}
|
|
lower := strings.ToLower(proxy)
|
|
if strings.Contains(lower, "[interface]") {
|
|
for _, line := range strings.Split(proxy, "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if len(line) >= 9 && strings.EqualFold(line[:8], "endpoint") {
|
|
_, val, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
_, val, ok = strings.Cut(line, ":")
|
|
}
|
|
if ok {
|
|
return strings.TrimSpace(val)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if i := strings.Index(proxy, "://"); i >= 0 {
|
|
proxy = proxy[i+3:]
|
|
}
|
|
if at := strings.LastIndex(proxy, "@"); at >= 0 {
|
|
proxy = proxy[at+1:]
|
|
}
|
|
if i := strings.IndexAny(proxy, "/?#"); i >= 0 {
|
|
proxy = proxy[:i]
|
|
}
|
|
return proxy
|
|
}
|
|
|
|
// SetActive switches the active profile name.
|
|
func (c *Config) SetActive(name string) error {
|
|
name = strings.TrimSpace(name)
|
|
for _, p := range c.Profiles {
|
|
if p.Name == name {
|
|
c.Active = name
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("profile %q not found", name)
|
|
}
|
|
|
|
// UpsertProfile creates or updates a profile by name.
|
|
func (c *Config) UpsertProfile(name, proxy string) error {
|
|
return c.UpsertProfileWithProtocol(name, proxy, "")
|
|
}
|
|
|
|
// UpsertProfileWithProtocol creates/updates a profile and optionally sets protocol.
|
|
// The upserted profile becomes Active.
|
|
func (c *Config) UpsertProfileWithProtocol(name, proxy string, proto Protocol) error {
|
|
return c.upsertProfile(name, proxy, proto, true)
|
|
}
|
|
|
|
// UpsertProfileKeepActive creates/updates a profile without changing Active.
|
|
func (c *Config) UpsertProfileKeepActive(name, proxy string, proto Protocol) error {
|
|
return c.upsertProfile(name, proxy, proto, false)
|
|
}
|
|
|
|
func (c *Config) upsertProfile(name, proxy string, proto Protocol, activate bool) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return fmt.Errorf("имя профиля пустое")
|
|
}
|
|
proxy = strings.TrimSpace(proxy)
|
|
if proto == "" {
|
|
proto = DetectProtocol(proxy)
|
|
}
|
|
if proto == "" {
|
|
proto = ProtocolNaive
|
|
}
|
|
|
|
for i := range c.Profiles {
|
|
if c.Profiles[i].Name == name {
|
|
c.Profiles[i].Proxy = proxy
|
|
c.Profiles[i].Protocol = proto
|
|
if len(c.Profiles[i].Listen) == 0 {
|
|
c.Profiles[i].Listen = defaultListen()
|
|
}
|
|
if activate {
|
|
c.Active = name
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
c.Profiles = append(c.Profiles, Profile{
|
|
Name: name,
|
|
Protocol: proto,
|
|
Proxy: proxy,
|
|
Listen: defaultListen(),
|
|
})
|
|
if activate {
|
|
c.Active = name
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RenameProfile renames a profile and keeps active pointer in sync.
|
|
func (c *Config) RenameProfile(oldName, newName string) error {
|
|
oldName = strings.TrimSpace(oldName)
|
|
newName = strings.TrimSpace(newName)
|
|
if oldName == "" || newName == "" {
|
|
return fmt.Errorf("имя профиля пустое")
|
|
}
|
|
if oldName == newName {
|
|
return nil
|
|
}
|
|
for _, p := range c.Profiles {
|
|
if p.Name == newName {
|
|
return fmt.Errorf("профиль %q уже существует", newName)
|
|
}
|
|
}
|
|
for i := range c.Profiles {
|
|
if c.Profiles[i].Name == oldName {
|
|
c.Profiles[i].Name = newName
|
|
if c.Active == oldName {
|
|
c.Active = newName
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("profile %q not found", oldName)
|
|
}
|
|
|
|
// DeleteProfile removes a profile. Keeps at least one profile.
|
|
func (c *Config) DeleteProfile(name string) error {
|
|
name = strings.TrimSpace(name)
|
|
if len(c.Profiles) <= 1 {
|
|
return fmt.Errorf("нельзя удалить последний профиль")
|
|
}
|
|
idx := -1
|
|
for i, p := range c.Profiles {
|
|
if p.Name == name {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx < 0 {
|
|
return fmt.Errorf("profile %q not found", name)
|
|
}
|
|
c.Profiles = append(c.Profiles[:idx], c.Profiles[idx+1:]...)
|
|
if c.Active == name {
|
|
c.Active = c.Profiles[0].Name
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func defaultListen() []string {
|
|
return []string{
|
|
"socks://127.0.0.1:1080",
|
|
"http://127.0.0.1:1081",
|
|
}
|
|
}
|