Add remote routing URL support (#6168)

* Add remote routing URL support

* Harden remote routing refresh

* fix(sub): harden remote routing fetch and accept Mihomo src rule flag

Remote routing bytes reach the YAML/JSON parsers from goroutines that run
outside Gin's recovery, so a parser panic on crafted input would take down
the whole panel. Contain it in fetch() (a panic now degrades to a failed
refresh that keeps the last-good value and releases the in-flight slot)
and start the refresh, cache-load and startup-warm goroutines through
common.GoRecover like the other background workers.

The route-graph validator only skipped a trailing no-resolve flag, so a
valid Mihomo rule like IP-CIDR,x,DIRECT,no-resolve,src was rejected as an
unknown target; skip both option flags.

Also deduplicate the HTTPS-source classification into
common.ParseRemoteRoutingURL so the save-time validator and the resolver
can never drift (internal/sub imports internal/web/service, so the copy
existed only to avoid the import cycle), move the test-only
mergeRemoteClashRulesYAML helper into the test file, and trim oversized
comment blocks.

---------

Co-authored-by: Duxxie <yelloduxx@users.noreply.github.com>
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
Duxxie
2026-08-18 17:02:11 +03:00
committed by GitHub
parent 3a2f9b48da
commit 380aff4d82
25 changed files with 1835 additions and 54 deletions
+27 -1
View File
@@ -1,6 +1,10 @@
package common
import "strings"
import (
"errors"
"net/url"
"strings"
)
// EnsureURLScheme prepends https:// to a URL that carries no scheme, so
// subscription apps and browsers don't resolve it relative to the panel's own
@@ -19,3 +23,25 @@ func EnsureURLScheme(raw string) string {
}
return "https://" + trimmed
}
// ParseRemoteRoutingURL classifies a routing settings value: one single-line
// absolute HTTPS URL is a remote source (canonicalized); anything else is inline.
func ParseRemoteRoutingURL(raw string) (string, bool, error) {
trimmed := strings.TrimSpace(raw)
if trimmed == "" || strings.ContainsAny(trimmed, "\r\n") {
return "", false, nil
}
if !strings.HasPrefix(strings.ToLower(trimmed), "https://") {
return "", false, nil
}
u, err := url.Parse(trimmed)
if err != nil || u.Host == "" || u.Hostname() == "" {
return "", true, errors.New("must be an absolute HTTPS URL")
}
if u.User != nil {
return "", true, errors.New("must not contain URL credentials")
}
u.Scheme = "https"
u.Fragment = ""
return u.String(), true, nil
}