mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-30 02:26:46 +03:00
175 lines
3.7 KiB
Go
175 lines
3.7 KiB
Go
package httpmask
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
tunnelEarlyDataQueryKey = "ed"
|
|
tunnelEarlyDataHeader = "X-Sudoku-Early"
|
|
)
|
|
|
|
type ClientEarlyHandshake struct {
|
|
RequestPayload []byte
|
|
HandleResponse func(payload []byte) error
|
|
Ready func() bool
|
|
WrapConn func(raw net.Conn) (net.Conn, error)
|
|
}
|
|
|
|
type TunnelServerEarlyHandshake struct {
|
|
Prepare func(payload []byte) (*PreparedServerEarlyHandshake, error)
|
|
}
|
|
|
|
type PreparedServerEarlyHandshake struct {
|
|
ResponsePayload []byte
|
|
WrapConn func(raw net.Conn) (net.Conn, error)
|
|
UserHash string
|
|
}
|
|
|
|
type earlyHandshakeMeta interface {
|
|
HTTPMaskEarlyHandshakeUserHash() string
|
|
}
|
|
|
|
type earlyHandshakeConn struct {
|
|
net.Conn
|
|
userHash string
|
|
}
|
|
|
|
func (c *earlyHandshakeConn) HTTPMaskEarlyHandshakeUserHash() string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
return c.userHash
|
|
}
|
|
|
|
func wrapEarlyHandshakeConn(conn net.Conn, userHash string) net.Conn {
|
|
if conn == nil {
|
|
return nil
|
|
}
|
|
return &earlyHandshakeConn{Conn: conn, userHash: userHash}
|
|
}
|
|
|
|
func EarlyHandshakeUserHash(conn net.Conn) (string, bool) {
|
|
if conn == nil {
|
|
return "", false
|
|
}
|
|
v, ok := conn.(earlyHandshakeMeta)
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
return v.HTTPMaskEarlyHandshakeUserHash(), true
|
|
}
|
|
|
|
type authorizeResponse struct {
|
|
token string
|
|
earlyPayload []byte
|
|
}
|
|
|
|
func isTunnelTokenByte(c byte) bool {
|
|
return (c >= 'a' && c <= 'z') ||
|
|
(c >= 'A' && c <= 'Z') ||
|
|
(c >= '0' && c <= '9') ||
|
|
c == '-' ||
|
|
c == '_'
|
|
}
|
|
|
|
func parseAuthorizeResponse(body []byte) (*authorizeResponse, error) {
|
|
s := strings.TrimSpace(string(body))
|
|
idx := strings.Index(s, "token=")
|
|
if idx < 0 {
|
|
return nil, errors.New("missing token")
|
|
}
|
|
s = s[idx+len("token="):]
|
|
if s == "" {
|
|
return nil, errors.New("empty token")
|
|
}
|
|
|
|
var b strings.Builder
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if isTunnelTokenByte(c) {
|
|
b.WriteByte(c)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
token := b.String()
|
|
if token == "" {
|
|
return nil, errors.New("empty token")
|
|
}
|
|
|
|
out := &authorizeResponse{token: token}
|
|
if earlyLine := findAuthorizeField(body, "ed="); earlyLine != "" {
|
|
decoded, err := base64.RawURLEncoding.DecodeString(earlyLine)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode early authorize payload failed: %w", err)
|
|
}
|
|
out.earlyPayload = decoded
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func findAuthorizeField(body []byte, prefix string) string {
|
|
for _, line := range strings.Split(strings.TrimSpace(string(body)), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if strings.HasPrefix(line, prefix) {
|
|
return strings.TrimSpace(strings.TrimPrefix(line, prefix))
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func setEarlyDataQuery(rawURL string, payload []byte) (string, error) {
|
|
if len(payload) == 0 {
|
|
return rawURL, nil
|
|
}
|
|
u, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
q := u.Query()
|
|
q.Set(tunnelEarlyDataQueryKey, base64.RawURLEncoding.EncodeToString(payload))
|
|
u.RawQuery = q.Encode()
|
|
return u.String(), nil
|
|
}
|
|
|
|
func parseEarlyDataQuery(u *url.URL) ([]byte, error) {
|
|
if u == nil {
|
|
return nil, nil
|
|
}
|
|
val := strings.TrimSpace(u.Query().Get(tunnelEarlyDataQueryKey))
|
|
if val == "" {
|
|
return nil, nil
|
|
}
|
|
return base64.RawURLEncoding.DecodeString(val)
|
|
}
|
|
|
|
func applyEarlyHandshakeOrUpgrade(raw net.Conn, opts TunnelDialOptions) (net.Conn, error) {
|
|
out := raw
|
|
if opts.EarlyHandshake != nil && opts.EarlyHandshake.WrapConn != nil && (opts.EarlyHandshake.Ready == nil || opts.EarlyHandshake.Ready()) {
|
|
wrapped, err := opts.EarlyHandshake.WrapConn(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if wrapped != nil {
|
|
out = wrapped
|
|
}
|
|
return out, nil
|
|
}
|
|
if opts.Upgrade != nil {
|
|
wrapped, err := opts.Upgrade(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if wrapped != nil {
|
|
out = wrapped
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|