mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-29 02:01:52 +03:00
110 lines
3.2 KiB
Go
110 lines
3.2 KiB
Go
package httpmask
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gobwas/ws"
|
|
)
|
|
|
|
func looksLikeWebSocketUpgrade(headers map[string]string) bool {
|
|
if headers == nil {
|
|
return false
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(headers["upgrade"]), "websocket") {
|
|
return false
|
|
}
|
|
conn := headers["connection"]
|
|
for _, part := range strings.Split(conn, ",") {
|
|
if strings.EqualFold(strings.TrimSpace(part), "upgrade") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *TunnelServer) handleWS(rawConn net.Conn, req *httpRequestHeader, headerBytes []byte, buffered []byte) (HandleResult, net.Conn, error) {
|
|
rejectOrReply := func(code int, body string) (HandleResult, net.Conn, error) {
|
|
if s.passThroughOnReject {
|
|
prefix := make([]byte, 0, len(headerBytes)+len(buffered))
|
|
prefix = append(prefix, headerBytes...)
|
|
prefix = append(prefix, buffered...)
|
|
return HandlePassThrough, newRejectedPreBufferedConn(rawConn, prefix), nil
|
|
}
|
|
_ = writeSimpleHTTPResponse(rawConn, code, body)
|
|
_ = rawConn.Close()
|
|
return HandleDone, nil, nil
|
|
}
|
|
|
|
u, err := url.ParseRequestURI(req.target)
|
|
if err != nil {
|
|
return rejectOrReply(http.StatusBadRequest, "bad request")
|
|
}
|
|
|
|
path, ok := stripPathRoot(s.pathRoot, u.Path)
|
|
if !ok || path != "/ws" {
|
|
return rejectOrReply(http.StatusNotFound, "not found")
|
|
}
|
|
if strings.ToUpper(strings.TrimSpace(req.method)) != http.MethodGet {
|
|
return rejectOrReply(http.StatusBadRequest, "bad request")
|
|
}
|
|
if !looksLikeWebSocketUpgrade(req.headers) {
|
|
return rejectOrReply(http.StatusBadRequest, "bad request")
|
|
}
|
|
|
|
authVal := req.headers["authorization"]
|
|
if authVal == "" {
|
|
authVal = u.Query().Get(tunnelAuthQueryKey)
|
|
}
|
|
if !s.auth.verifyValue(authVal, TunnelModeWS, req.method, path, time.Now()) {
|
|
return rejectOrReply(http.StatusNotFound, "not found")
|
|
}
|
|
|
|
earlyPayload, err := parseEarlyDataQuery(u)
|
|
if err != nil {
|
|
return rejectOrReply(http.StatusBadRequest, "bad request")
|
|
}
|
|
var prepared *PreparedServerEarlyHandshake
|
|
if len(earlyPayload) > 0 && s.earlyHandshake != nil && s.earlyHandshake.Prepare != nil {
|
|
prepared, err = s.earlyHandshake.Prepare(earlyPayload)
|
|
if err != nil {
|
|
return rejectOrReply(http.StatusNotFound, "not found")
|
|
}
|
|
}
|
|
|
|
prefix := make([]byte, 0, len(headerBytes)+len(buffered))
|
|
prefix = append(prefix, headerBytes...)
|
|
prefix = append(prefix, buffered...)
|
|
wsConnRaw := newPreBufferedConn(rawConn, prefix)
|
|
|
|
upgrader := ws.Upgrader{}
|
|
if prepared != nil && len(prepared.ResponsePayload) > 0 {
|
|
upgrader.OnBeforeUpgrade = func() (ws.HandshakeHeader, error) {
|
|
h := http.Header{}
|
|
h.Set(tunnelEarlyDataHeader, base64.RawURLEncoding.EncodeToString(prepared.ResponsePayload))
|
|
return ws.HandshakeHeaderHTTP(h), nil
|
|
}
|
|
}
|
|
if _, err := upgrader.Upgrade(wsConnRaw); err != nil {
|
|
_ = rawConn.Close()
|
|
return HandleDone, nil, nil
|
|
}
|
|
|
|
outConn := net.Conn(newWSStreamConn(wsConnRaw, ws.StateServerSide))
|
|
if prepared != nil && prepared.WrapConn != nil {
|
|
wrapped, err := prepared.WrapConn(outConn)
|
|
if err != nil {
|
|
_ = outConn.Close()
|
|
return HandleDone, nil, nil
|
|
}
|
|
if wrapped != nil {
|
|
outConn = wrapEarlyHandshakeConn(wrapped, prepared.UserHash)
|
|
}
|
|
}
|
|
return HandleStartTunnel, outConn, nil
|
|
}
|