2024-06-18 07:36:36 +02:00
package splithttp
import (
2026-03-07 12:34:41 +00:00
"encoding/base64"
"fmt"
"io"
2026-06-03 22:06:15 +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-03 22:06:15 +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
2024-06-18 07:36:36 +02:00
if path [ len ( path ) - 1 ] != '/' {
path = path + "/"
}
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-03 22:06:15 +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-03 22:06:15 +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-03 22:06:15 +08:00
return c . ScMaxEachPostBytes
2024-06-18 07:36:36 +02:00
}
2026-06-03 22:06:15 +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-03 22:06:15 +08:00
return & RangeConfig {
2024-07-27 14:52:36 +02:00
From : 30 ,
To : 30 ,
}
}
2026-06-03 22:06:15 +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-03 22:06:15 +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-03 22:06:15 +08:00
return & RangeConfig {
2025-01-19 13:32:07 +00:00
From : 20 ,
To : 80 ,
}
}
2026-06-03 22:06:15 +08:00
return c . ScStreamUpServerSecs
2025-01-19 13:32:07 +00:00
}
2026-06-03 22:06:15 +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-03 22:06:15 +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-03 22:06:15 +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-03 22:06:15 +08:00
return & RangeConfig {
2026-03-07 12:34:41 +00:00
From : 64 ,
To : max ( 64 , c . UplinkChunkSize . To ) ,
}
}
2026-06-03 22:06:15 +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 20:12:54 +08:00
if c . SessionIDPlacement == "" {
2026-01-31 16:34:13 +03:00
return PlacementPath
}
2026-06-09 20:12:54 +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 20:12:54 +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 ( )
if dataPlacement == PlacementBody || dataPlacement == PlacementAuto {
request . Header = c . GetRequestHeader ( )
2026-03-21 20:48:47 +08:00
request . Body = io . NopCloser ( & buf . MultiBufferContainer { MultiBuffer : payload } )
request . ContentLength = int64 ( payload . Len ( ) )
2026-03-07 12:34:41 +00:00
} else {
2026-03-21 20:48:47 +08:00
data := make ( [ ] byte , payload . Len ( ) )
payload . Copy ( data )
buf . ReleaseMulti ( payload )
2026-03-07 12:34:41 +00:00
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-03 22:06:15 +08:00
func ( m * XmuxConfig ) GetNormalizedMaxConcurrency ( ) * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . MaxConcurrency == nil {
2026-06-03 22:06:15 +08:00
return & RangeConfig {
2024-12-15 05:43:10 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-03 22:06:15 +08:00
return m . MaxConcurrency
2024-12-15 05:43:10 +00:00
}
2026-06-03 22:06:15 +08:00
func ( m * XmuxConfig ) GetNormalizedMaxConnections ( ) * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . MaxConnections == nil {
2026-06-03 22:06:15 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-03 22:06:15 +08:00
return m . MaxConnections
2024-09-16 12:42:01 +00:00
}
2026-06-03 22:06:15 +08:00
func ( m * XmuxConfig ) GetNormalizedCMaxReuseTimes ( ) * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . CMaxReuseTimes == nil {
2026-06-03 22:06:15 +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-03 22:06:15 +08:00
return m . CMaxReuseTimes
2024-09-16 12:42:01 +00:00
}
2026-06-03 22:06:15 +08:00
func ( m * XmuxConfig ) GetNormalizedHMaxRequestTimes ( ) * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . HMaxRequestTimes == nil {
2026-06-03 22:06:15 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-03 22:06:15 +08:00
return m . HMaxRequestTimes
2024-09-16 12:42:01 +00:00
}
2026-06-03 22:06:15 +08:00
func ( m * XmuxConfig ) GetNormalizedHMaxReusableSecs ( ) * RangeConfig {
2024-12-31 10:00:19 +00:00
if m . HMaxReusableSecs == nil {
2026-06-03 22:06:15 +08:00
return & RangeConfig {
2024-09-16 12:42:01 +00:00
From : 0 ,
To : 0 ,
}
}
2026-06-03 22:06:15 +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-03 22:06:15 +08:00
func ( c * RangeConfig ) rand ( ) int32 {
2026-06-03 22:42:37 +08:00
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-03 22:06:15 +08:00
// predefined
2026-06-06 06:11:54 +08:00
var PredefinedTable = map [ string ] string {
2026-06-09 04:29:57 +08:00
"ALPHABET" : "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ,
"Alphabet" : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ,
2026-06-06 06:11:54 +08:00
"BASE36" : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ,
2026-06-09 04:29:57 +08:00
"Base62" : "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ,
2026-06-06 06:11:54 +08:00
"HEX" : "0123456789ABCDEF" ,
"alphabet" : "abcdefghijklmnopqrstuvwxyz" ,
2026-06-09 04:29:57 +08:00
"base36" : "0123456789abcdefghijklmnopqrstuvwxyz" ,
"hex" : "0123456789abcdef" ,
"number" : "0123456789" ,
2026-06-06 06:11:54 +08:00
}
2026-06-03 22:06:15 +08:00
func ( c * Config ) GenerateSessionID ( ) string {
length := c . SessionIDLength . rand ( )
table := c . SessionIDTable
2026-06-06 06:11:54 +08:00
if predefined , ok := PredefinedTable [ table ] ; ok {
table = predefined
2026-06-03 22:06:15 +08:00
}
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
}