Files
elmprodvpn/selective-vpn-api/app/traffic_appkey.go
2026-02-16 01:31:56 +03:00

135 lines
3.4 KiB
Go

package app
import (
"path/filepath"
"strings"
)
// ---------------------------------------------------------------------
// traffic app key normalization
// ---------------------------------------------------------------------
//
// EN: app_key is used as a stable per-app identity for:
// EN: - deduplicating runtime marks (avoid unbounded growth)
// EN: - matching profiles <-> runtime marks in UI
// EN:
// EN: Raw command token[0] is not stable across launch methods:
// EN: - "/usr/bin/google-chrome-stable" vs "google-chrome-stable"
// EN: - "flatpak run org.mozilla.firefox" (token[0]="flatpak")
// EN:
// EN: We normalize app_key into a canonical form.
// RU: app_key используется как стабильный идентификатор приложения для:
// RU: - дедупликации runtime marks (не плодить бесконечно)
// RU: - сопоставления profiles <-> runtime marks в UI
// RU:
// RU: token[0] команды нестабилен для разных способов запуска:
// RU: - "/usr/bin/google-chrome-stable" vs "google-chrome-stable"
// RU: - "flatpak run org.mozilla.firefox" (token[0]="flatpak")
// RU:
// RU: Нормализуем app_key в канонический вид.
func canonicalizeAppKey(appKey string, command string) string {
key := strings.TrimSpace(appKey)
cmd := strings.TrimSpace(command)
fields := strings.Fields(cmd)
if len(fields) == 0 && key != "" {
fields = []string{key}
}
primary := key
if len(fields) > 0 {
primary = fields[0]
}
primary = stripOuterQuotes(strings.TrimSpace(primary))
if primary == "" {
return ""
}
// Normalize common wrappers into stable identifiers.
base := strings.ToLower(filepath.Base(primary))
// Build a cleaned field list for wrapper parsing.
clean := make([]string, 0, len(fields))
for _, f := range fields {
f = stripOuterQuotes(strings.TrimSpace(f))
if f == "" {
continue
}
clean = append(clean, f)
}
switch base {
case "flatpak":
if id := extractRunTarget(clean); id != "" {
return "flatpak:" + id
}
return "flatpak"
case "snap":
if name := extractRunTarget(clean); name != "" {
return "snap:" + name
}
return "snap"
case "gtk-launch":
// gtk-launch <desktop-id>
if len(clean) >= 2 {
id := strings.TrimSpace(clean[1])
if id != "" && !strings.HasPrefix(id, "-") {
return "desktop:" + id
}
}
}
// If it looks like a path, canonicalize to basename.
if strings.Contains(primary, "/") {
b := filepath.Base(primary)
if b != "" && b != "." && b != "/" {
return b
}
}
return primary
}
func stripOuterQuotes(s string) string {
in := strings.TrimSpace(s)
if len(in) >= 2 {
if (in[0] == '"' && in[len(in)-1] == '"') || (in[0] == '\'' && in[len(in)-1] == '\'') {
return strings.TrimSpace(in[1 : len(in)-1])
}
}
return in
}
// extractRunTarget finds the first non-flag token after "run".
// Example: flatpak run --branch=stable org.mozilla.firefox => org.mozilla.firefox
// Example: snap run chromium => chromium
func extractRunTarget(fields []string) string {
if len(fields) == 0 {
return ""
}
idx := -1
for i := 0; i < len(fields); i++ {
if strings.TrimSpace(fields[i]) == "run" {
idx = i
break
}
}
if idx < 0 {
return ""
}
for j := idx + 1; j < len(fields); j++ {
tok := strings.TrimSpace(fields[j])
if tok == "" {
continue
}
if tok == "--" {
continue
}
if strings.HasPrefix(tok, "-") {
continue
}
return tok
}
return ""
}