2024-06-18 07:36:36 +02:00
package splithttp
import (
2026-08-27 03:41:43 +07:00
"bytes"
2026-03-07 12:34:41 +00:00
"encoding/base64"
"fmt"
"io"
2026-06-09 23:58:02 +08:00
"math/rand/v2"
2024-06-18 07:36:36 +02:00
"net/http"
2024-07-29 06:35:17 +02:00
"strings"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/common"
2026-03-21 20:48:47 +08:00
"github.com/xtls/xray-core/common/buf"
2025-03-03 14:45:12 +00:00
"github.com/xtls/xray-core/common/crypto"
2026-02-06 01:33:30 +00:00
"github.com/xtls/xray-core/common/utils"
2026-06-09 23:58:02 +08:00
"github.com/xtls/xray-core/common/uuid"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/transport/internet"
)
2024-08-10 23:47:42 +02:00
func ( c * Config ) GetNormalizedPath () string {
2024-07-29 06:35:17 +02:00
pathAndQuery := strings . SplitN ( c . Path , "?" , 2 )
path := pathAndQuery [ 0 ]
if path == "" || path [ 0 ] != '/' {
2024-06-18 07:36:36 +02:00
path = "/" + path
}
2024-08-10 23:47:42 +02:00
2026-07-08 20:59:19 +03:00
if c . GetNormalizedSessionPlacement () == PlacementPath ||
c . GetNormalizedSeqPlacement () == PlacementPath {
if path [ len ( path ) - 1 ] != '/' {
path = path + "/"
}
2024-06-18 07:36:36 +02:00
}
2024-07-29 06:35:17 +02:00
2024-08-10 23:47:42 +02:00
return path
}
func ( c * Config ) GetNormalizedQuery () string {
pathAndQuery := strings . SplitN ( c . Path , "?" , 2 )
query := ""
if len ( pathAndQuery ) > 1 {
query = pathAndQuery [ 1 ]
}
2025-02-18 11:11:36 +00:00
/*
if query != "" {
query += "&"
}
query += "x_version=" + core.Version()
*/
2024-08-10 23:47:42 +02:00
return query
2024-06-18 07:36:36 +02:00
}
2026-01-31 16:34:13 +03:00
func ( c * Config ) GetRequestHeader () http . Header {
2024-06-18 07:36:36 +02:00
header := http . Header {}
2024-12-11 14:05:39 +00:00
for k , v := range c . Headers {
2024-06-18 07:36:36 +02:00
header . Add ( k , v )
}
2026-03-21 12:24:08 +00:00
utils . TryDefaultHeadersWith ( header , "fetch" )
2024-06-18 07:36:36 +02:00
return header
}
2026-03-07 12:34:41 +00:00
func ( c * Config ) GetRequestHeaderWithPayload ( payload [] byte ) http . Header {
header := c . GetRequestHeader ()
key := c . UplinkDataKey
encodedData := base64 . RawURLEncoding . EncodeToString ( payload )
for i := 0 ; len ( encodedData ) > 0 ; i ++ {
chunkSize := min ( int ( c . GetNormalizedUplinkChunkSize (). rand ()), len ( encodedData ))
chunk := encodedData [: chunkSize ]
encodedData = encodedData [ chunkSize :]
headerKey := fmt . Sprintf ( "%s-%d" , key , i )
header . Set ( headerKey , chunk )
}
return header
}
func ( c * Config ) GetRequestCookiesWithPayload ( payload [] byte ) [] * http . Cookie {
cookies := [] * http . Cookie {}
key := c . UplinkDataKey
encodedData := base64 . RawURLEncoding . EncodeToString ( payload )
for i := 0 ; len ( encodedData ) > 0 ; i ++ {
chunkSize := min ( int ( c . GetNormalizedUplinkChunkSize (). rand ()), len ( encodedData ))
chunk := encodedData [: chunkSize ]
encodedData = encodedData [ chunkSize :]
cookieName := fmt . Sprintf ( "%s_%d" , key , i )
cookies = append ( cookies , & http . Cookie { Name : cookieName , Value : chunk })
}
return cookies
}
func ( c * Config ) WriteResponseHeader ( writer http . ResponseWriter , requestMethod string , requestHeader http . Header ) {
2024-09-20 08:55:54 +01:00
// CORS headers for the browser dialer
2026-03-07 12:34:41 +00:00
if origin := requestHeader . Get ( "Origin" ); origin == "" {
writer . Header (). Set ( "Access-Control-Allow-Origin" , "*" )
} else {
// Chrome says: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
writer . Header (). Set ( "Access-Control-Allow-Origin" , origin )
}
if c . GetNormalizedSessionPlacement () == PlacementCookie ||
2026-03-21 20:48:47 +08:00
c . GetNormalizedSeqPlacement () == PlacementCookie ||
c . XPaddingPlacement == PlacementCookie ||
c . GetNormalizedUplinkDataPlacement () == PlacementCookie {
2026-03-07 12:34:41 +00:00
writer . Header (). Set ( "Access-Control-Allow-Credentials" , "true" )
}
if requestMethod == "OPTIONS" {
requestedMethod := requestHeader . Get ( "Access-Control-Request-Method" )
if requestedMethod != "" {
writer . Header (). Set ( "Access-Control-Allow-Methods" , requestedMethod )
} else {
writer . Header (). Set ( "Access-Control-Allow-Methods" , "*" )
}
requestedHeaders := requestHeader . Get ( "Access-Control-Request-Headers" )
if requestedHeaders == "" {
writer . Header (). Set ( "Access-Control-Allow-Headers" , "*" )
} else {
writer . Header (). Set ( "Access-Control-Allow-Headers" , requestedHeaders )
}
}
2024-08-10 23:47:42 +02:00
}
2026-01-31 16:34:13 +03:00
func ( c * Config ) GetNormalizedUplinkHTTPMethod () string {
if c . UplinkHTTPMethod == "" {
return "POST"
2024-06-18 07:36:36 +02:00
}
2026-01-31 16:34:13 +03:00
return c . UplinkHTTPMethod
2024-06-18 07:36:36 +02:00
}
2026-06-09 23:58:02 +08:00
func ( c * Config ) GetNormalizedScMaxEachPostBytes () * RangeConfig {
2024-07-29 12:10:29 +02:00
if c . ScMaxEachPostBytes == nil || c . ScMaxEachPostBytes . To == 0 {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-07-29 12:10:29 +02:00
From : 1000000 ,
To : 1000000 ,
2024-07-29 06:35:17 +02:00
}
2024-06-18 07:36:36 +02:00
}
2026-06-09 23:58:02 +08:00
return c . ScMaxEachPostBytes
2024-06-18 07:36:36 +02:00
}
2026-06-09 23:58:02 +08:00
func ( c * Config ) GetNormalizedScMinPostsIntervalMs () * RangeConfig {
2024-07-29 12:10:29 +02:00
if c . ScMinPostsIntervalMs == nil || c . ScMinPostsIntervalMs . To == 0 {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-07-27 14:52:36 +02:00
From : 30 ,
To : 30 ,
}
}
2026-06-09 23:58:02 +08:00
return c . ScMinPostsIntervalMs
2024-07-27 14:52:36 +02:00
}
2024-12-31 10:00:19 +00:00
func ( c * Config ) GetNormalizedScMaxBufferedPosts () int {
if c . ScMaxBufferedPosts == 0 {
return 30
2024-08-02 03:16:19 +02:00
}
2024-12-31 10:00:19 +00:00
return int ( c . ScMaxBufferedPosts )
2024-08-02 03:16:19 +02:00
}
2026-06-09 23:58:02 +08:00
func ( c * Config ) GetNormalizedScStreamUpServerSecs () * RangeConfig {
2025-01-19 13:32:07 +00:00
if c . ScStreamUpServerSecs == nil || c . ScStreamUpServerSecs . To == 0 {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2025-01-19 13:32:07 +00:00
From : 20 ,
To : 80 ,
}
}
2026-06-09 23:58:02 +08:00
return c . ScStreamUpServerSecs
2025-01-19 13:32:07 +00:00
}
2026-06-09 23:58:02 +08:00
func ( c * Config ) GetNormalizedUplinkChunkSize () * RangeConfig {
2026-03-07 12:34:41 +00:00
if c . UplinkChunkSize == nil || c . UplinkChunkSize . To == 0 {
switch c . UplinkDataPlacement {
case PlacementCookie :
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2026-03-07 12:34:41 +00:00
From : 2 * 1024 , // 2 KiB
To : 3 * 1024 , // 3 KiB
}
case PlacementHeader :
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2026-03-07 12:34:41 +00:00
From : 3 * 1000 , // 3 KB
To : 4 * 1000 , // 4 KB
}
default :
return c . GetNormalizedScMaxEachPostBytes ()
}
} else if c . UplinkChunkSize . From < 64 {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2026-03-07 12:34:41 +00:00
From : 64 ,
To : max ( 64 , c . UplinkChunkSize . To ),
}
}
2026-06-09 23:58:02 +08:00
return c . UplinkChunkSize
2026-03-07 12:34:41 +00:00
}
func ( c * Config ) GetNormalizedServerMaxHeaderBytes () int {
if c . ServerMaxHeaderBytes <= 0 {
return 8192
} else {
return int ( c . ServerMaxHeaderBytes )
}
}
2026-01-31 16:34:13 +03:00
func ( c * Config ) GetNormalizedSessionPlacement () string {
2026-06-09 23:58:02 +08:00
if c . SessionIDPlacement == "" {
2026-01-31 16:34:13 +03:00
return PlacementPath
}
2026-06-09 23:58:02 +08:00
return c . SessionIDPlacement
2026-01-31 16:34:13 +03:00
}
func ( c * Config ) GetNormalizedSeqPlacement () string {
if c . SeqPlacement == "" {
return PlacementPath
}
return c . SeqPlacement
}
func ( c * Config ) GetNormalizedUplinkDataPlacement () string {
if c . UplinkDataPlacement == "" {
return PlacementBody
}
return c . UplinkDataPlacement
}
func ( c * Config ) GetNormalizedSessionKey () string {
2026-06-09 23:58:02 +08:00
if c . SessionIDKey != "" {
return c . SessionIDKey
2026-01-31 16:34:13 +03:00
}
switch c . GetNormalizedSessionPlacement () {
case PlacementHeader :
return "X-Session"
case PlacementCookie , PlacementQuery :
return "x_session"
default :
return ""
}
}
func ( c * Config ) GetNormalizedSeqKey () string {
if c . SeqKey != "" {
return c . SeqKey
}
switch c . GetNormalizedSeqPlacement () {
case PlacementHeader :
return "X-Seq"
case PlacementCookie , PlacementQuery :
return "x_seq"
default :
return ""
}
}
func ( c * Config ) ApplyMetaToRequest ( req * http . Request , sessionId string , seqStr string ) {
sessionPlacement := c . GetNormalizedSessionPlacement ()
seqPlacement := c . GetNormalizedSeqPlacement ()
sessionKey := c . GetNormalizedSessionKey ()
seqKey := c . GetNormalizedSeqKey ()
if sessionId != "" {
switch sessionPlacement {
case PlacementPath :
req . URL . Path = appendToPath ( req . URL . Path , sessionId )
case PlacementQuery :
q := req . URL . Query ()
q . Set ( sessionKey , sessionId )
req . URL . RawQuery = q . Encode ()
case PlacementHeader :
req . Header . Set ( sessionKey , sessionId )
case PlacementCookie :
req . AddCookie ( & http . Cookie { Name : sessionKey , Value : sessionId })
}
}
if seqStr != "" {
switch seqPlacement {
case PlacementPath :
req . URL . Path = appendToPath ( req . URL . Path , seqStr )
case PlacementQuery :
q := req . URL . Query ()
q . Set ( seqKey , seqStr )
req . URL . RawQuery = q . Encode ()
case PlacementHeader :
req . Header . Set ( seqKey , seqStr )
case PlacementCookie :
req . AddCookie ( & http . Cookie { Name : seqKey , Value : seqStr })
}
}
}
2026-03-07 12:34:41 +00:00
func ( c * Config ) FillStreamRequest ( request * http . Request , sessionId string , seqStr string ) {
request . Header = c . GetRequestHeader ()
length := int ( c . GetNormalizedXPaddingBytes (). rand ())
config := XPaddingConfig { Length : length }
if c . XPaddingObfsMode {
config . Placement = XPaddingPlacement {
Placement : c . XPaddingPlacement ,
Key : c . XPaddingKey ,
Header : c . XPaddingHeader ,
RawURL : request . URL . String (),
}
config . Method = PaddingMethod ( c . XPaddingMethod )
} else {
config . Placement = XPaddingPlacement {
Placement : PlacementQueryInHeader ,
Key : "x_padding" ,
Header : "Referer" ,
RawURL : request . URL . String (),
}
}
c . ApplyXPaddingToRequest ( request , config )
c . ApplyMetaToRequest ( request , sessionId , "" )
if request . Body != nil && ! c . NoGRPCHeader { // stream-up/one
request . Header . Set ( "Content-Type" , "application/grpc" )
}
}
2026-03-21 20:48:47 +08:00
func ( c * Config ) FillPacketRequest ( request * http . Request , sessionId string , seqStr string , payload buf . MultiBuffer ) error {
2026-03-07 12:34:41 +00:00
dataPlacement := c . GetNormalizedUplinkDataPlacement ()
2026-08-27 03:41:43 +07:00
data := make ([] byte , payload . Len ())
payload . Copy ( data )
buf . ReleaseMulti ( payload )
2026-03-07 12:34:41 +00:00
if dataPlacement == PlacementBody || dataPlacement == PlacementAuto {
request . Header = c . GetRequestHeader ()
2026-08-27 03:41:43 +07:00
request . Body = io . NopCloser ( bytes . NewReader ( data ))
request . ContentLength = int64 ( len ( data ))
request . GetBody = func () ( io . ReadCloser , error ) {
return io . NopCloser ( bytes . NewReader ( data )), nil
}
2026-03-07 12:34:41 +00:00
} else {
switch dataPlacement {
case PlacementHeader :
request . Header = c . GetRequestHeaderWithPayload ( data )
case PlacementCookie :
request . Header = c . GetRequestHeader ()
for _ , cookie := range c . GetRequestCookiesWithPayload ( data ) {
request . AddCookie ( cookie )
}
}
}
length := int ( c . GetNormalizedXPaddingBytes (). rand ())
config := XPaddingConfig { Length : length }
if c . XPaddingObfsMode {
config . Placement = XPaddingPlacement {
Placement : c . XPaddingPlacement ,
Key : c . XPaddingKey ,
Header : c . XPaddingHeader ,
RawURL : request . URL . String (),
}
config . Method = PaddingMethod ( c . XPaddingMethod )
} else {
config . Placement = XPaddingPlacement {
Placement : PlacementQueryInHeader ,
Key : "x_padding" ,
Header : "Referer" ,
RawURL : request . URL . String (),
}
}
c . ApplyXPaddingToRequest ( request , config )
c . ApplyMetaToRequest ( request , sessionId , seqStr )
return nil
}
2026-01-31 16:34:13 +03:00
func ( c * Config ) ExtractMetaFromRequest ( req * http . Request , path string ) ( sessionId string , seqStr string ) {
sessionPlacement := c . GetNormalizedSessionPlacement ()
seqPlacement := c . GetNormalizedSeqPlacement ()
sessionKey := c . GetNormalizedSessionKey ()
seqKey := c . GetNormalizedSeqKey ()
2026-03-07 12:34:41 +00:00
var subpath [] string
pathPart := 0
if sessionPlacement == PlacementPath || seqPlacement == PlacementPath {
subpath = strings . Split ( req . URL . Path [ len ( path ):], "/" )
2026-01-31 16:34:13 +03:00
}
switch sessionPlacement {
2026-03-07 12:34:41 +00:00
case PlacementPath :
if len ( subpath ) > pathPart {
sessionId = subpath [ pathPart ]
pathPart += 1
}
2026-01-31 16:34:13 +03:00
case PlacementQuery :
sessionId = req . URL . Query (). Get ( sessionKey )
case PlacementHeader :
sessionId = req . Header . Get ( sessionKey )
case PlacementCookie :
if cookie , e := req . Cookie ( sessionKey ); e == nil {
sessionId = cookie . Value
}
}
switch seqPlacement {
2026-03-07 12:34:41 +00:00
case PlacementPath :
if len ( subpath ) > pathPart {
seqStr = subpath [ pathPart ]
pathPart += 1
}
2026-01-31 16:34:13 +03:00
case PlacementQuery :
seqStr = req . URL . Query (). Get ( seqKey )
case PlacementHeader :
seqStr = req . Header . Get ( seqKey )
case PlacementCookie :
if cookie , e := req . Cookie ( seqKey ); e == nil {
seqStr = cookie . Value
}
}
return sessionId , seqStr
}
2026-06-09 23:58:02 +08:00
func ( m * XmuxConfig ) GetNormalizedMaxConcurrency () * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . MaxConcurrency == nil {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-12-15 05:43:10 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-09 23:58:02 +08:00
return m . MaxConcurrency
2024-12-15 05:43:10 +00:00
}
2026-06-09 23:58:02 +08:00
func ( m * XmuxConfig ) GetNormalizedMaxConnections () * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . MaxConnections == nil {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-09 23:58:02 +08:00
return m . MaxConnections
2024-09-16 12:42:01 +00:00
}
2026-06-09 23:58:02 +08:00
func ( m * XmuxConfig ) GetNormalizedCMaxReuseTimes () * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . CMaxReuseTimes == nil {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2024-12-31 10:00:19 +00:00
2026-06-09 23:58:02 +08:00
return m . CMaxReuseTimes
2024-09-16 12:42:01 +00:00
}
2026-06-09 23:58:02 +08:00
func ( m * XmuxConfig ) GetNormalizedHMaxRequestTimes () * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . HMaxRequestTimes == nil {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-09 23:58:02 +08:00
return m . HMaxRequestTimes
2024-09-16 12:42:01 +00:00
}
2026-06-09 23:58:02 +08:00
func ( m * XmuxConfig ) GetNormalizedHMaxReusableSecs () * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . HMaxReusableSecs == nil {
2026-06-09 23:58:02 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-09 23:58:02 +08:00
return m . HMaxReusableSecs
2024-09-16 12:42:01 +00:00
}
2024-06-18 07:36:36 +02:00
func init () {
common . Must ( internet . RegisterProtocolConfigCreator ( protocolName , func () interface {} {
return new ( Config )
}))
}
2024-07-27 14:52:36 +02:00
2026-06-09 23:58:02 +08:00
func ( c * RangeConfig ) rand () int32 {
if c == nil {
return 0
}
2025-03-03 14:45:12 +00:00
return int32 ( crypto . RandBetween ( int64 ( c . From ), int64 ( c . To )))
2024-07-27 14:52:36 +02:00
}
2026-01-31 16:34:13 +03:00
2026-06-09 23:58:02 +08:00
// predefined
var PredefinedTable = map [ string ] string {
"ALPHABET" : "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ,
"Alphabet" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ,
"BASE36" : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ,
"Base62" : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ,
"HEX" : "0123456789ABCDEF" ,
"alphabet" : "abcdefghijklmnopqrstuvwxyz" ,
"base36" : "0123456789abcdefghijklmnopqrstuvwxyz" ,
"hex" : "0123456789abcdef" ,
"number" : "0123456789" ,
}
func ( c * Config ) GenerateSessionID () string {
length := c . SessionIDLength . rand ()
table := c . SessionIDTable
if predefined , ok := PredefinedTable [ table ]; ok {
table = predefined
}
if table != "" && length > 0 {
id := make ([] byte , length )
for i := range id {
id [ i ] = table [ rand . N ( len ( table ))]
}
return string ( id )
} else {
uuid := uuid . New ()
return uuid . String ()
}
}
2026-01-31 16:34:13 +03:00
func appendToPath ( path , value string ) string {
if strings . HasSuffix ( path , "/" ) {
return path + value
}
return path + "/" + value
}