Files
3x-ui-web-src/internal/sub/clash_yaml.go
T
Sanaei f727d04f65
CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

90 lines
2.1 KiB
Go

package sub
import (
"reflect"
"regexp"
"strings"
"github.com/goccy/go-yaml"
)
type yamlQuotedString string
func (s yamlQuotedString) MarshalYAML() ([]byte, error) {
return []byte("'" + strings.ReplaceAll(string(s), "'", "''") + "'"), nil
}
var yamlNonStringWords = map[string]bool{
"~": true, "null": true, "Null": true, "NULL": true,
"true": true, "True": true, "TRUE": true,
"false": true, "False": true, "FALSE": true,
"yes": true, "Yes": true, "YES": true,
"no": true, "No": true, "NO": true,
"on": true, "On": true, "ON": true,
"off": true, "Off": true, "OFF": true,
"y": true, "Y": true, "n": true, "N": true,
}
var yamlNonStringNumber = regexp.MustCompile(`^(?:` +
`[-+]?[0-9]+|` +
`0[oO]?[0-7]+|0[xX][0-9a-fA-F]+|` +
`[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+|` +
`[-+]?(?:[0-9]*\.[0-9]+|[0-9]+\.?[0-9]*)(?:[eE][-+]?[0-9]+)?|` +
`[-+]?\.(?:inf|Inf|INF)|\.(?:nan|NaN|NAN)|` +
`[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}(?:[Tt ].*)?` +
`)$`)
func yamlScalarIsAmbiguous(s string) bool {
if s == "" {
return false
}
return yamlNonStringWords[s] || yamlNonStringNumber.MatchString(s)
}
func quoteAmbiguousYAMLScalars(v any) any {
if v == nil {
return nil
}
if s, ok := v.(string); ok {
if yamlScalarIsAmbiguous(s) {
return yamlQuotedString(s)
}
return s
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Map:
out := make(map[string]any, rv.Len())
iter := rv.MapRange()
for iter.Next() {
key, ok := iter.Key().Interface().(string)
if !ok {
return v
}
out[key] = quoteAmbiguousYAMLScalars(iter.Value().Interface())
}
return out
case reflect.Slice, reflect.Array:
if rv.Kind() == reflect.Slice && rv.Type().Elem().Kind() == reflect.Uint8 {
return v
}
out := make([]any, rv.Len())
for i := range rv.Len() {
out[i] = quoteAmbiguousYAMLScalars(rv.Index(i).Interface())
}
return out
case reflect.Pointer, reflect.Interface:
if rv.IsNil() {
return v
}
return quoteAmbiguousYAMLScalars(rv.Elem().Interface())
default:
return v
}
}
func marshalClashYAML(config any) ([]byte, error) {
return yaml.Marshal(quoteAmbiguousYAMLScalars(config))
}