Fix XHTTP

This commit is contained in:
2026-07-04 23:59:03 -03:00
parent f87023ebff
commit 7200dbd236
+33 -48
View File
@@ -144,13 +144,13 @@ func (ib *nativeInbound) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
if !ib.xhttpHostAllowed(r.Host) { if !ib.xhttpHostAllowed(r.Host) {
xrayLogf("native xray: xhttp reject inbound=%q reason=host method=%s path=%q host=%q want=%q remote=%s", ib.tag, r.Method, r.URL.RequestURI(), r.Host, ib.xhttpHost, r.RemoteAddr) xrayLogf("native xray: xhttp reject inbound=%q reason=host method=%s path=%q host=%q want=%q remote=%s", ib.tag, r.Method, r.URL.RequestURI(), r.Host, ib.xhttpHost, r.RemoteAddr)
xhttpBadRequest(w) w.WriteHeader(http.StatusNotFound)
return return
} }
base, ok := ib.matchXHTTPPath(r.URL.Path) base, ok := ib.matchXHTTPPath(r.URL.Path)
if !ok { if !ok {
xrayLogf("native xray: xhttp reject inbound=%q reason=path method=%s path=%q want=%q host=%q remote=%s", ib.tag, r.Method, r.URL.RequestURI(), ib.path, r.Host, r.RemoteAddr) xrayLogf("native xray: xhttp reject inbound=%q reason=path method=%s path=%q want=%q host=%q remote=%s", ib.tag, r.Method, r.URL.RequestURI(), ib.path, r.Host, r.RemoteAddr)
xhttpBadRequest(w) w.WriteHeader(http.StatusNotFound)
return return
} }
@@ -164,45 +164,39 @@ func (ib *nativeInbound) ServeHTTP(w http.ResponseWriter, r *http.Request) {
mode := ib.normalizedXHTTPMode() mode := ib.normalizedXHTTPMode()
xrayTracef("native xray: xhttp request inbound=%q method=%s proto=%s path=%q host=%q session=%q seq=%q len=%d mode=%s remote=%s", ib.tag, r.Method, r.Proto, r.URL.RequestURI(), r.Host, sessionID, seqStr, r.ContentLength, mode, r.RemoteAddr) xrayTracef("native xray: xhttp request inbound=%q method=%s proto=%s path=%q host=%q session=%q seq=%q len=%d mode=%s remote=%s", ib.tag, r.Method, r.Proto, r.URL.RequestURI(), r.Host, sessionID, seqStr, r.ContentLength, mode, r.RemoteAddr)
// Xray's SplitHTTP treats GET with a sequence id as an uplink packet, not as // Routing mirrors xray-core splithttp hub.go ServeHTTP exactly.
// stream-down. Some clients use this when the upload payload is carried in if sessionID == "" && mode != "" && mode != "auto" && mode != "stream-one" && mode != "stream-up" {
// headers/cookies instead of the body. The previous native handler always http.Error(w, "stream-one mode is not allowed", http.StatusBadRequest)
// treated GET as download and dropped those packets, so normal sites such as return
// fast.com could authenticate but then stall with no upstream data. }
if r.Method == http.MethodGet && sessionID != "" && seqStr != "" {
// GET carries uplink data only when it has a sequence id; every other method
// (POST/PUT/PATCH) is always an uplink request.
isUplinkRequest := true
if r.Method == http.MethodGet {
isUplinkRequest = seqStr != ""
}
if isUplinkRequest && sessionID != "" { // stream-up, packet-up
sess := ib.upsertXHTTPSession(w, sessionID) sess := ib.upsertXHTTPSession(w, sessionID)
if sess == nil { if sess == nil {
return return
} }
if seqStr == "" {
ib.handleXHTTPStreamUpload(w, r, sess)
return
}
ib.handleXHTTPPacketUpload(w, r, sess, seqStr) ib.handleXHTTPPacketUpload(w, r, sess, seqStr)
return return
} }
if r.Method == http.MethodGet || r.Method == http.MethodHead { if r.Method == http.MethodGet || sessionID == "" { // stream-down, stream-one
if sessionID == "" { if sessionID != "" {
// Do not look like a fake web site. A plain browser request is not an sess := ib.upsertXHTTPSession(w, sessionID)
// XHTTP stream. External Xray normally answers this kind of access as a if sess == nil {
// bad request because the required XHTTP metadata/padding is missing. return
xhttpBadRequest(w) }
return ib.handleXHTTPDownload(w, r, sess, sessionID)
}
sess := ib.upsertXHTTPSession(w, sessionID)
if sess == nil {
return
}
ib.handleXHTTPDownload(w, r, sess, sessionID)
return
}
if !isXHTTPUploadMethod(r.Method) {
w.Header().Set("Allow", "GET, POST, PUT, PATCH, OPTIONS")
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if sessionID == "" {
if mode != "auto" && mode != "stream-one" && mode != "stream-up" {
http.Error(w, "xhttp stream-one mode is not allowed", http.StatusBadRequest)
return return
} }
if r.Body == nil || (r.ContentLength == 0 && len(r.TransferEncoding) == 0) { if r.Body == nil || (r.ContentLength == 0 && len(r.TransferEncoding) == 0) {
@@ -213,15 +207,8 @@ func (ib *nativeInbound) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
sess := ib.upsertXHTTPSession(w, sessionID) w.Header().Set("Allow", "GET, POST, PUT, PATCH, OPTIONS")
if sess == nil { w.WriteHeader(http.StatusMethodNotAllowed)
return
}
if seqStr == "" {
ib.handleXHTTPStreamUpload(w, r, sess)
return
}
ib.handleXHTTPPacketUpload(w, r, sess, seqStr)
} }
func xhttpBadRequest(w http.ResponseWriter) { func xhttpBadRequest(w http.ResponseWriter) {
@@ -332,16 +319,14 @@ func (ib *nativeInbound) extractXHTTPMeta(r *http.Request, base string) (session
sessionKey := firstNonEmpty(ib.xhttpSessionKey, defaultXHTTPMetaKey(sessionPlacement, true)) sessionKey := firstNonEmpty(ib.xhttpSessionKey, defaultXHTTPMetaKey(sessionPlacement, true))
seqKey := firstNonEmpty(ib.xhttpSeqKey, defaultXHTTPMetaKey(seqPlacement, false)) seqKey := firstNonEmpty(ib.xhttpSeqKey, defaultXHTTPMetaKey(seqPlacement, false))
// Matches xray-core ExtractMetaFromRequest: split the path suffix after the
// base directly, without trimming empty segments, so segment indices line up
// exactly with what the client produced via appendToPath.
var parts []string var parts []string
pathPart := 0 pathPart := 0
if sessionPlacement == xhttpPlacementPath || seqPlacement == xhttpPlacementPath { if sessionPlacement == xhttpPlacementPath || seqPlacement == xhttpPlacementPath {
rest := ""
if strings.HasPrefix(r.URL.Path, base) { if strings.HasPrefix(r.URL.Path, base) {
rest = r.URL.Path[len(base):] parts = strings.Split(r.URL.Path[len(base):], "/")
}
rest = strings.Trim(rest, "/")
if rest != "" {
parts = strings.Split(rest, "/")
} }
} }