Browse Source

SplitHTTP: More range options, change defaults, enforce maxUploadSize, fix querystring behavior (#3603)

* maxUploadSize and maxConcurrentUploads can now be ranges on the client
* maxUploadSize is now enforced on the server
* the default of maxUploadSize is 2MB on the server, and 1MB on the client
* the default of maxConcurrentUploads is 200 on the server, and 100 on the client
* ranges on the server are treated as a single number. if server is configured as `"1-2"`, server will enforce `2`
* querystrings in `path` are now handled correctly
mmmray 1 year ago
parent
commit
59f6685774

+ 13 - 7
infra/conf/transport_internet.go

@@ -229,8 +229,8 @@ type SplitHTTPConfig struct {
 	Host                 string            `json:"host"`
 	Path                 string            `json:"path"`
 	Headers              map[string]string `json:"headers"`
-	MaxConcurrentUploads int32             `json:"maxConcurrentUploads"`
-	MaxUploadSize        int32             `json:"maxUploadSize"`
+	MaxConcurrentUploads Int32Range        `json:"maxConcurrentUploads"`
+	MaxUploadSize        Int32Range        `json:"maxUploadSize"`
 	MinUploadIntervalMs  Int32Range        `json:"minUploadIntervalMs"`
 }
 
@@ -245,11 +245,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
 		c.Host = c.Headers["Host"]
 	}
 	config := &splithttp.Config{
-		Path:                 c.Path,
-		Host:                 c.Host,
-		Header:               c.Headers,
-		MaxConcurrentUploads: c.MaxConcurrentUploads,
-		MaxUploadSize:        c.MaxUploadSize,
+		Path:   c.Path,
+		Host:   c.Host,
+		Header: c.Headers,
+		MaxConcurrentUploads: &splithttp.RandRangeConfig{
+			From: c.MaxConcurrentUploads.From,
+			To:   c.MaxConcurrentUploads.To,
+		},
+		MaxUploadSize: &splithttp.RandRangeConfig{
+			From: c.MaxUploadSize.From,
+			To:   c.MaxUploadSize.To,
+		},
 		MinUploadIntervalMs: &splithttp.RandRangeConfig{
 			From: c.MinUploadIntervalMs.From,
 			To:   c.MinUploadIntervalMs.To,

+ 42 - 19
transport/internet/splithttp/config.go

@@ -4,23 +4,28 @@ import (
 	"crypto/rand"
 	"math/big"
 	"net/http"
+	"strings"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/transport/internet"
 )
 
-func (c *Config) GetNormalizedPath() string {
-	path := c.Path
-	if path == "" {
-		path = "/"
+func (c *Config) GetNormalizedPath(addPath string, addQuery bool) string {
+	pathAndQuery := strings.SplitN(c.Path, "?", 2)
+	path := pathAndQuery[0]
+	query := ""
+	if len(pathAndQuery) > 1 && addQuery {
+		query = "?" + pathAndQuery[1]
 	}
-	if path[0] != '/' {
+
+	if path == "" || path[0] != '/' {
 		path = "/" + path
 	}
 	if path[len(path)-1] != '/' {
 		path = path + "/"
 	}
-	return path
+
+	return path + addPath + query
 }
 
 func (c *Config) GetRequestHeader() http.Header {
@@ -31,33 +36,51 @@ func (c *Config) GetRequestHeader() http.Header {
 	return header
 }
 
-func (c *Config) GetNormalizedMaxConcurrentUploads() int32 {
-	if c.MaxConcurrentUploads == 0 {
-		return 10
+func (c *Config) GetNormalizedMaxConcurrentUploads(isServer bool) RandRangeConfig {
+	if c.MaxConcurrentUploads == nil {
+		if isServer {
+			return RandRangeConfig{
+				From: 200,
+				To:   200,
+			}
+		} else {
+			return RandRangeConfig{
+				From: 100,
+				To:   100,
+			}
+		}
 	}
 
-	return c.MaxConcurrentUploads
+	return *c.MaxConcurrentUploads
 }
 
-func (c *Config) GetNormalizedMaxUploadSize() int32 {
-	if c.MaxUploadSize == 0 {
-		return 1000000
+func (c *Config) GetNormalizedMaxUploadSize(isServer bool) RandRangeConfig {
+	if c.MaxUploadSize == nil {
+		if isServer {
+			return RandRangeConfig{
+				From: 2000000,
+				To:   2000000,
+			}
+		} else {
+			return RandRangeConfig{
+				From: 1000000,
+				To:   1000000,
+			}
+		}
 	}
 
-	return c.MaxUploadSize
+	return *c.MaxUploadSize
 }
 
 func (c *Config) GetNormalizedMinUploadInterval() RandRangeConfig {
-	r := c.MinUploadIntervalMs
-
-	if r == nil {
-		r = &RandRangeConfig{
+	if c.MinUploadIntervalMs == nil {
+		return RandRangeConfig{
 			From: 30,
 			To:   30,
 		}
 	}
 
-	return *r
+	return *c.MinUploadIntervalMs
 }
 
 func init() {

+ 51 - 43
transport/internet/splithttp/config.pb.go

@@ -28,8 +28,8 @@ type Config struct {
 	Host                 string            `protobuf:"bytes,1,opt,name=host,proto3" json:"host,omitempty"`
 	Path                 string            `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
 	Header               map[string]string `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	MaxConcurrentUploads int32             `protobuf:"varint,4,opt,name=maxConcurrentUploads,proto3" json:"maxConcurrentUploads,omitempty"`
-	MaxUploadSize        int32             `protobuf:"varint,5,opt,name=maxUploadSize,proto3" json:"maxUploadSize,omitempty"`
+	MaxConcurrentUploads *RandRangeConfig  `protobuf:"bytes,4,opt,name=maxConcurrentUploads,proto3" json:"maxConcurrentUploads,omitempty"`
+	MaxUploadSize        *RandRangeConfig  `protobuf:"bytes,5,opt,name=maxUploadSize,proto3" json:"maxUploadSize,omitempty"`
 	MinUploadIntervalMs  *RandRangeConfig  `protobuf:"bytes,6,opt,name=minUploadIntervalMs,proto3" json:"minUploadIntervalMs,omitempty"`
 }
 
@@ -86,18 +86,18 @@ func (x *Config) GetHeader() map[string]string {
 	return nil
 }
 
-func (x *Config) GetMaxConcurrentUploads() int32 {
+func (x *Config) GetMaxConcurrentUploads() *RandRangeConfig {
 	if x != nil {
 		return x.MaxConcurrentUploads
 	}
-	return 0
+	return nil
 }
 
-func (x *Config) GetMaxUploadSize() int32 {
+func (x *Config) GetMaxUploadSize() *RandRangeConfig {
 	if x != nil {
 		return x.MaxUploadSize
 	}
-	return 0
+	return nil
 }
 
 func (x *Config) GetMinUploadIntervalMs() *RandRangeConfig {
@@ -169,8 +169,8 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{
 	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x63,
 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x78, 0x72, 0x61,
 	0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0xfa,
-	0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73,
+	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x22, 0xe2,
+	0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73,
 	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a,
 	0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
 	0x68, 0x12, 0x4d, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28,
@@ -178,35 +178,41 @@ var file_transport_internet_splithttp_config_proto_rawDesc = []byte{
 	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69,
 	0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x65, 0x61,
 	0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72,
-	0x12, 0x32, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
-	0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14,
-	0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x55, 0x70, 0x6c,
-	0x6f, 0x61, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61,
-	0x64, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78,
-	0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x6d, 0x69,
-	0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d,
-	0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
-	0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64,
-	0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x13, 0x6d, 0x69, 0x6e,
-	0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73,
-	0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
-	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
-	0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x35, 0x0a, 0x0f, 0x52,
-	0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12,
-	0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72,
-	0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02,
-	0x74, 0x6f, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
-	0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36,
-	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f,
-	0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
-	0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c,
-	0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72,
-	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
-	0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	0x12, 0x66, 0x0a, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
+	0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32,
+	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74,
+	0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
+	0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x12, 0x58, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x55,
+	0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68,
+	0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x69,
+	0x7a, 0x65, 0x12, 0x64, 0x0a, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49,
+	0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x32, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68,
+	0x74, 0x74, 0x70, 0x2e, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e,
+	0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64,
+	0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
+	0x02, 0x38, 0x01, 0x22, 0x35, 0x0a, 0x0f, 0x52, 0x61, 0x6e, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x74, 0x6f, 0x42, 0x85, 0x01, 0x0a, 0x25, 0x63,
+	0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x73, 0x70, 0x6c, 0x69, 0x74,
+	0x68, 0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+	0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
+	0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02,
+	0x21, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x48, 0x74,
+	0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -229,12 +235,14 @@ var file_transport_internet_splithttp_config_proto_goTypes = []interface{}{
 }
 var file_transport_internet_splithttp_config_proto_depIdxs = []int32{
 	2, // 0: xray.transport.internet.splithttp.Config.header:type_name -> xray.transport.internet.splithttp.Config.HeaderEntry
-	1, // 1: xray.transport.internet.splithttp.Config.minUploadIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig
-	2, // [2:2] is the sub-list for method output_type
-	2, // [2:2] is the sub-list for method input_type
-	2, // [2:2] is the sub-list for extension type_name
-	2, // [2:2] is the sub-list for extension extendee
-	0, // [0:2] is the sub-list for field type_name
+	1, // 1: xray.transport.internet.splithttp.Config.maxConcurrentUploads:type_name -> xray.transport.internet.splithttp.RandRangeConfig
+	1, // 2: xray.transport.internet.splithttp.Config.maxUploadSize:type_name -> xray.transport.internet.splithttp.RandRangeConfig
+	1, // 3: xray.transport.internet.splithttp.Config.minUploadIntervalMs:type_name -> xray.transport.internet.splithttp.RandRangeConfig
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
 }
 
 func init() { file_transport_internet_splithttp_config_proto_init() }

+ 2 - 2
transport/internet/splithttp/config.proto

@@ -10,8 +10,8 @@ message Config {
   string host = 1;
   string path = 2;
   map<string, string> header = 3;
-  int32 maxConcurrentUploads = 4;
-  int32 maxUploadSize = 5;
+  RandRangeConfig maxConcurrentUploads = 4;
+  RandRangeConfig maxUploadSize = 5;
   RandRangeConfig minUploadIntervalMs = 6;
 }
 

+ 51 - 0
transport/internet/splithttp/config_test.go

@@ -0,0 +1,51 @@
+package splithttp_test
+
+import (
+	"testing"
+
+	. "github.com/xtls/xray-core/transport/internet/splithttp"
+)
+
+func Test_GetNormalizedPath(t *testing.T) {
+	c := Config{
+		Path: "/?world",
+	}
+
+	path := c.GetNormalizedPath("hello", true)
+	if path != "/hello?world" {
+		t.Error("Unexpected: ", path)
+	}
+}
+
+func Test_GetNormalizedPath2(t *testing.T) {
+	c := Config{
+		Path: "?world",
+	}
+
+	path := c.GetNormalizedPath("hello", true)
+	if path != "/hello?world" {
+		t.Error("Unexpected: ", path)
+	}
+}
+
+func Test_GetNormalizedPath3(t *testing.T) {
+	c := Config{
+		Path: "hello?world",
+	}
+
+	path := c.GetNormalizedPath("", true)
+	if path != "/hello/?world" {
+		t.Error("Unexpected: ", path)
+	}
+}
+
+func Test_GetNormalizedPath4(t *testing.T) {
+	c := Config{
+		Path: "hello?world",
+	}
+
+	path := c.GetNormalizedPath("", false)
+	if path != "/hello/" {
+		t.Error("Unexpected: ", path)
+	}
+}

+ 8 - 9
transport/internet/splithttp/dialer.go

@@ -181,8 +181,8 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 	transportConfiguration := streamSettings.ProtocolSettings.(*Config)
 	tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
 
-	maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads()
-	maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize()
+	maxConcurrentUploads := transportConfiguration.GetNormalizedMaxConcurrentUploads(false)
+	maxUploadSize := transportConfiguration.GetNormalizedMaxUploadSize(false)
 	minUploadInterval := transportConfiguration.GetNormalizedMinUploadInterval()
 
 	if tlsConfig != nil {
@@ -194,18 +194,17 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 	if requestURL.Host == "" {
 		requestURL.Host = dest.NetAddr()
 	}
-	requestURL.Path = transportConfiguration.GetNormalizedPath()
-
-	httpClient := getHTTPClient(ctx, dest, streamSettings)
 
 	sessionIdUuid := uuid.New()
-	sessionId := sessionIdUuid.String()
-	baseURL := requestURL.String() + sessionId
+	requestURL.Path = transportConfiguration.GetNormalizedPath(sessionIdUuid.String(), true)
+	baseURL := requestURL.String()
+
+	httpClient := getHTTPClient(ctx, dest, streamSettings)
 
-	uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize))
+	uploadPipeReader, uploadPipeWriter := pipe.New(pipe.WithSizeLimit(maxUploadSize.roll()))
 
 	go func() {
-		requestsLimiter := semaphore.New(int(maxConcurrentUploads))
+		requestsLimiter := semaphore.New(int(maxConcurrentUploads.roll()))
 		var requestCounter int64
 
 		lastWrite := time.Now()

+ 10 - 2
transport/internet/splithttp/hub.go

@@ -75,7 +75,7 @@ func (h *requestHandler) upsertSession(sessionId string) *httpSession {
 	}
 
 	s := &httpSession{
-		uploadQueue:      NewUploadQueue(int(2 * h.ln.config.GetNormalizedMaxConcurrentUploads())),
+		uploadQueue:      NewUploadQueue(int(h.ln.config.GetNormalizedMaxConcurrentUploads(true).To)),
 		isFullyConnected: done.New(),
 	}
 
@@ -122,6 +122,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
 	}
 
 	currentSession := h.upsertSession(sessionId)
+	maxUploadSize := int(h.ln.config.GetNormalizedMaxUploadSize(true).To)
 
 	if request.Method == "POST" {
 		seq := ""
@@ -136,6 +137,13 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
 		}
 
 		payload, err := io.ReadAll(request.Body)
+
+		if len(payload) > maxUploadSize {
+			errors.LogInfo(context.Background(), "Too large upload. maxUploadSize is set to", maxUploadSize, "but request had size", len(payload), ". Adjust maxUploadSize on the server to be at least as large as client.")
+			writer.WriteHeader(http.StatusRequestEntityTooLarge)
+			return
+		}
+
 		if err != nil {
 			errors.LogInfoInner(context.Background(), err, "failed to upload")
 			writer.WriteHeader(http.StatusInternalServerError)
@@ -260,7 +268,7 @@ func ListenSH(ctx context.Context, address net.Address, port net.Port, streamSet
 	var localAddr = gonet.TCPAddr{}
 	handler := &requestHandler{
 		host:      shSettings.Host,
-		path:      shSettings.GetNormalizedPath(),
+		path:      shSettings.GetNormalizedPath("", false),
 		ln:        l,
 		sessionMu: &sync.Mutex{},
 		sessions:  sync.Map{},

+ 46 - 0
transport/internet/splithttp/splithttp_test.go

@@ -360,3 +360,49 @@ func Test_listenSHAndDial_Unix(t *testing.T) {
 
 	common.Must(listen.Close())
 }
+
+func Test_queryString(t *testing.T) {
+	listenPort := tcp.PickPort()
+	listen, err := ListenSH(context.Background(), net.LocalHostIP, listenPort, &internet.MemoryStreamConfig{
+		ProtocolName: "splithttp",
+		ProtocolSettings: &Config{
+			// this querystring does not have any effect, but sometimes people blindly copy it from websocket config. make sure the outbound doesn't break
+			Path: "/sh?ed=2048",
+		},
+	}, func(conn stat.Connection) {
+		go func(c stat.Connection) {
+			defer c.Close()
+
+			var b [1024]byte
+			c.SetReadDeadline(time.Now().Add(2 * time.Second))
+			_, err := c.Read(b[:])
+			if err != nil {
+				return
+			}
+
+			common.Must2(c.Write([]byte("Response")))
+		}(conn)
+	})
+	common.Must(err)
+	ctx := context.Background()
+	streamSettings := &internet.MemoryStreamConfig{
+		ProtocolName:     "splithttp",
+		ProtocolSettings: &Config{Path: "sh"},
+	}
+	conn, err := Dial(ctx, net.TCPDestination(net.DomainAddress("localhost"), listenPort), streamSettings)
+
+	common.Must(err)
+	_, err = conn.Write([]byte("Test connection 1"))
+	common.Must(err)
+
+	var b [1024]byte
+	fmt.Println("test2")
+	n, _ := conn.Read(b[:])
+	fmt.Println("string is", n)
+	if string(b[:n]) != "Response" {
+		t.Error("response: ", string(b[:n]))
+	}
+
+	common.Must(conn.Close())
+	common.Must(listen.Close())
+}