Files
xray-core/transport/internet/splithttp/config.go

496 lines
12 KiB
Go

package splithttp
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/transport/internet"
)
func (c *Config) GetNormalizedPath() string {
pathAndQuery := strings.SplitN(c.Path, "?", 2)
path := pathAndQuery[0]
if path == "" || path[0] != '/' {
path = "/" + path
}
if path[len(path)-1] != '/' {
path = path + "/"
}
return path
}
func (c *Config) GetNormalizedQuery() string {
pathAndQuery := strings.SplitN(c.Path, "?", 2)
query := ""
if len(pathAndQuery) > 1 {
query = pathAndQuery[1]
}
/*
if query != "" {
query += "&"
}
query += "x_version=" + core.Version()
*/
return query
}
func (c *Config) GetRequestHeader() http.Header {
header := http.Header{}
for k, v := range c.Headers {
header.Add(k, v)
}
utils.TryDefaultHeadersWith(header, "fetch")
return header
}
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) {
// CORS headers for the browser dialer
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 ||
c.GetNormalizedSeqPlacement() == PlacementCookie ||
c.XPaddingPlacement == PlacementCookie ||
c.GetNormalizedUplinkDataPlacement() == PlacementCookie {
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)
}
}
}
func (c *Config) GetNormalizedUplinkHTTPMethod() string {
if c.UplinkHTTPMethod == "" {
return "POST"
}
return c.UplinkHTTPMethod
}
func (c *Config) GetNormalizedScMaxEachPostBytes() RangeConfig {
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
return RangeConfig{
From: 1000000,
To: 1000000,
}
}
return *c.ScMaxEachPostBytes
}
func (c *Config) GetNormalizedScMinPostsIntervalMs() RangeConfig {
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
return RangeConfig{
From: 30,
To: 30,
}
}
return *c.ScMinPostsIntervalMs
}
func (c *Config) GetNormalizedScMaxBufferedPosts() int {
if c.ScMaxBufferedPosts == 0 {
return 30
}
return int(c.ScMaxBufferedPosts)
}
func (c *Config) GetNormalizedScStreamUpServerSecs() RangeConfig {
if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {
return RangeConfig{
From: 20,
To: 80,
}
}
return *c.ScStreamUpServerSecs
}
func (c *Config) GetNormalizedUplinkChunkSize() RangeConfig {
if c.UplinkChunkSize == nil || c.UplinkChunkSize.To == 0 {
switch c.UplinkDataPlacement {
case PlacementCookie:
return RangeConfig{
From: 2 * 1024, // 2 KiB
To: 3 * 1024, // 3 KiB
}
case PlacementHeader:
return RangeConfig{
From: 3 * 1000, // 3 KB
To: 4 * 1000, // 4 KB
}
default:
return c.GetNormalizedScMaxEachPostBytes()
}
} else if c.UplinkChunkSize.From < 64 {
return RangeConfig{
From: 64,
To: max(64, c.UplinkChunkSize.To),
}
}
return *c.UplinkChunkSize
}
func (c *Config) GetNormalizedServerMaxHeaderBytes() int {
if c.ServerMaxHeaderBytes <= 0 {
return 8192
} else {
return int(c.ServerMaxHeaderBytes)
}
}
func (c *Config) GetNormalizedSessionPlacement() string {
if c.SessionPlacement == "" {
return PlacementPath
}
return c.SessionPlacement
}
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 {
if c.SessionKey != "" {
return c.SessionKey
}
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})
}
}
}
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")
}
}
func (c *Config) FillPacketRequest(request *http.Request, sessionId string, seqStr string) error {
dataPlacement := c.GetNormalizedUplinkDataPlacement()
if dataPlacement == PlacementBody || dataPlacement == PlacementAuto {
request.Header = c.GetRequestHeader()
} else {
var data []byte
var err error
if request.Body != nil {
data, err = io.ReadAll(request.Body)
if err != nil {
return err
}
}
request.Body = nil
request.ContentLength = 0
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
}
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()
var subpath []string
pathPart := 0
if sessionPlacement == PlacementPath || seqPlacement == PlacementPath {
subpath = strings.Split(req.URL.Path[len(path):], "/")
}
switch sessionPlacement {
case PlacementPath:
if len(subpath) > pathPart {
sessionId = subpath[pathPart]
pathPart += 1
}
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 {
case PlacementPath:
if len(subpath) > pathPart {
seqStr = subpath[pathPart]
pathPart += 1
}
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
}
func (m *XmuxConfig) GetNormalizedMaxConcurrency() RangeConfig {
if m.MaxConcurrency == nil {
return RangeConfig{
From: 0,
To: 0,
}
}
return *m.MaxConcurrency
}
func (m *XmuxConfig) GetNormalizedMaxConnections() RangeConfig {
if m.MaxConnections == nil {
return RangeConfig{
From: 0,
To: 0,
}
}
return *m.MaxConnections
}
func (m *XmuxConfig) GetNormalizedCMaxReuseTimes() RangeConfig {
if m.CMaxReuseTimes == nil {
return RangeConfig{
From: 0,
To: 0,
}
}
return *m.CMaxReuseTimes
}
func (m *XmuxConfig) GetNormalizedHMaxRequestTimes() RangeConfig {
if m.HMaxRequestTimes == nil {
return RangeConfig{
From: 0,
To: 0,
}
}
return *m.HMaxRequestTimes
}
func (m *XmuxConfig) GetNormalizedHMaxReusableSecs() RangeConfig {
if m.HMaxReusableSecs == nil {
return RangeConfig{
From: 0,
To: 0,
}
}
return *m.HMaxReusableSecs
}
func init() {
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
return new(Config)
}))
}
func (c RangeConfig) rand() int32 {
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
}
func appendToPath(path, value string) string {
if strings.HasSuffix(path, "/") {
return path + value
}
return path + "/" + value
}