Skip to content

fix(xhttp): don't append trailing slash to file-like paths#2915

Closed
voltara13 wants to merge 1 commit into
MetaCubeX:Alphafrom
voltara13:release/v1.19.28-pathfix
Closed

fix(xhttp): don't append trailing slash to file-like paths#2915
voltara13 wants to merge 1 commit into
MetaCubeX:Alphafrom
voltara13:release/v1.19.28-pathfix

Conversation

@voltara13

Copy link
Copy Markdown

Backport of the NormalizedPath fix onto v1.19.27. A trailing slash was forced onto every xhttp path, breaking paths that point at a file (e.g. /foo.txt). Skip the trailing slash when the last path segment contains a dot.

NormalizedPath kept forcing a trailing slash on every path, which breaks
xhttp paths that point at a file (e.g. /foo.txt). Skip the trailing slash
when the last path segment contains a dot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@voltara13
voltara13 force-pushed the release/v1.19.28-pathfix branch from 91a7076 to 147b5b8 Compare June 26, 2026 05:37
@wwqgtxx

wwqgtxx commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

The current behavior matches that of the official client, and we have no interest in making any changes before XTLS/Xray-core#6307 is merged upstream.

@wwqgtxx wwqgtxx closed this Jun 26, 2026
@tuefalek

Copy link
Copy Markdown

The argument "matches the official client" doesn't hold here: the official client (Xray-core) has the same bug — XTLS/Xray-core#6307 is open precisely because GetNormalizedPath unconditionally appends a trailing slash, which is wrong when sessionID and seq are placed in query/cookie/header rather than in the path.
The concrete breakage: when using xhttp behind a CDN (e.g. Yandex CDN, Cloudflare), the CDN whitelist contains specific paths like /stream/data.bin. With the forced trailing slash, mihomo sends GET /stream/data.bin/ — a 404 or a cache miss, because CDN path matching is exact. The connection never establishes. This is not a theoretical concern; it affects any deployment that uses file-like paths for camouflage.
The fix in this PR is conservative: it skips the trailing slash only when the last path segment contains a dot. It does not change behavior for the default case (path-based sessionID/seq placement).
Blocking a correct fix in mihomo while waiting for upstream to merge a separate PR leaves users with a client that simply doesn't work in a common CDN scenario. Consider merging this as a stopgap, or at minimum reopening the PR when #6307 lands upstream.

@ashdkv

ashdkv commented Jun 29, 2026

Copy link
Copy Markdown

The heuristic used in this PR (skip trailing slash when the last segment contains a dot) is a workaround, not the correct fix.

The trailing slash exists for one reason only: to separate the configured base path from the appended sessionID/seq segments, so the server can slice them back off via req.URL.Path[len(path):]. It is meaningful only when at least one of sessionID or seq uses path placement.

The correct condition is:

if sessionIDPlacement == "path" || seqPlacement == "path" {
    // append trailing slash
}

This is exactly what XTLS/Xray-core#6307 implements. If neither field goes into the path, the configured path must be sent verbatim — no slash appended, no matter what the path looks like. A dot-based heuristic fails for paths like /stream/v2 (no dot, but both fields in query) and is unnecessarily fragile.

@wwqgtxx

wwqgtxx commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

It is pointless to spend time discussing implementation details with me; if you want to resolve your issue, please urge the upstream project to make the necessary changes. We will synchronize the updates as soon as they are merged upstream.

@Medium1992

Copy link
Copy Markdown

It is pointless to spend time discussing implementation details with me; if you want to resolve your issue, please urge the upstream project to make the necessary changes. We will synchronize the updates as soon as they are merged upstream.

Xray-core has merged the upstream fix:
XTLS/Xray-core@1aabe7e

Could this PR be reopened and adjusted to match Xray’s behavior?

The current PR uses a file-like path heuristic, while upstream now appends the trailing slash only when sessionID or seq are placed in the path.

Maybe mihomo could use the same condition as Xray here:

if c.GetNormalizedSessionPlacement() == PlacementPath ||
	c.GetNormalizedSeqPlacement() == PlacementPath {
	if !strings.HasSuffix(path, "/") {
		path += "/"
	}
}

@wwqgtxx

wwqgtxx commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

6a52d23

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants