mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-02 11:15:03 +03:00
fix(sub): bound what a remote routing profile renders to
Remote routing sources are fetched documents the panel re-renders on every subscription request. A Clash YAML document stays small through aliases, which decode to one shared value but are written out in full, so it now has to fit a 4 MiB written-size budget and 16 levels of nesting when it is accepted (and when a persisted copy is loaded). A JSON routing profile could repeat route groups, each copy repeating every site list; a group now counts once, since a repeat can never match.
This commit is contained in:
@@ -39,6 +39,11 @@ const (
|
||||
remoteRoutingHappMaxBody = 16 << 10 // 16 KiB; Happ emits the result in a response header
|
||||
remoteRoutingHappMaxValue = 8 << 10 // normalized Routing header value
|
||||
remoteRoutingClashMaxBody = 2 << 20 // 2 MiB
|
||||
|
||||
// What a document may marshal to, aliases copied out; the encoder re-indents a node's text
|
||||
// once per enclosing level, hence the depth bound.
|
||||
remoteRoutingClashMaxWritten = 4 << 20
|
||||
remoteRoutingClashMaxDepth = 16
|
||||
)
|
||||
|
||||
// isHappPayloadKind reports whether the kind carries a happ-payload source:
|
||||
@@ -448,6 +453,9 @@ func normalizeClashRouting(body []byte) (string, map[string]any, error) {
|
||||
if len(document) == 0 {
|
||||
return "", nil, errors.New("Clash routing response must be a YAML map")
|
||||
}
|
||||
if budget := remoteRoutingClashMaxWritten; !clashFitsWritten(document, 0, &budget) {
|
||||
return "", nil, errors.New("Clash routing response is too large or too deeply nested once written out")
|
||||
}
|
||||
hasSupportedKey := false
|
||||
for key := range document {
|
||||
if remoteClashKeyClasses[key] == remoteClashRouteGraph {
|
||||
@@ -471,6 +479,43 @@ func normalizeClashRouting(body []byte) (string, map[string]any, error) {
|
||||
return text, document, nil
|
||||
}
|
||||
|
||||
// clashFitsWritten charges what the encoder writes for v: every string per line at its
|
||||
// indentation, every node its indentation. It stops as soon as the budget is spent.
|
||||
func clashFitsWritten(v any, depth int, budget *int) bool {
|
||||
if depth > remoteRoutingClashMaxDepth {
|
||||
return false
|
||||
}
|
||||
indent := 2*depth + 2
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
*budget -= len(v) + (strings.Count(v, "\n")+1)*indent
|
||||
case []byte:
|
||||
// The encoder writes !!binary values as a list with one item per byte.
|
||||
*budget -= len(v) * (indent + 6)
|
||||
case map[string]any:
|
||||
for key, child := range v {
|
||||
if *budget -= len(key) + indent; *budget < 0 || !clashFitsWritten(child, depth+1, budget) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case map[any]any:
|
||||
for key, child := range v {
|
||||
if *budget -= indent; *budget < 0 || !clashFitsWritten(key, depth+1, budget) || !clashFitsWritten(child, depth+1, budget) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, child := range v {
|
||||
if *budget -= indent; *budget < 0 || !clashFitsWritten(child, depth+1, budget) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
*budget -= indent + 24
|
||||
}
|
||||
return *budget >= 0
|
||||
}
|
||||
|
||||
func resolveIncyRoutingSource(raw string) (string, bool, error) {
|
||||
source, remote, err := common.ParseRemoteRoutingURL(raw)
|
||||
if err != nil || !remote {
|
||||
|
||||
Reference in New Issue
Block a user