Files
Xray-core/common/protocol/http/sniff.go
T

109 lines
2.2 KiB
Go
Raw Normal View History

2020-11-25 19:01:53 +08:00
package http
import (
2026-08-31 14:55:10 +08:00
"bufio"
2020-11-25 19:01:53 +08:00
"bytes"
"context"
2020-11-25 19:01:53 +08:00
"errors"
2026-08-31 14:55:10 +08:00
"io"
"net/http"
2020-11-25 19:01:53 +08:00
"strings"
2026-08-31 14:55:10 +08:00
"unsafe"
2020-11-25 19:01:53 +08:00
2020-12-04 09:36:16 +08:00
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/session"
2020-11-25 19:01:53 +08:00
)
type version byte
const (
HTTP1 version = iota
HTTP2
)
type SniffHeader struct {
version version
host string
}
func (h *SniffHeader) Protocol() string {
switch h.version {
case HTTP1:
return "http1"
case HTTP2:
return "http2"
default:
return "unknown"
}
}
func (h *SniffHeader) Domain() string {
return h.host
}
var (
2026-08-31 14:55:10 +08:00
validMethods = map[string]bool{}
errNotHTTP = errors.New("not an HTTP request")
2020-11-25 19:01:53 +08:00
)
2026-08-31 14:55:10 +08:00
func init() {
// https://www.iana.org/assignments/http-methods
methods := []string{
"ACL", "BASELINE-CONTROL", "BIND", "CHECKIN", "CHECKOUT",
"CONNECT", "COPY", "DELETE", "GET", "HEAD",
"LABEL", "LINK", "LOCK", "MERGE", "MKACTIVITY",
"MKCALENDAR", "MKCOL", "MKREDIRECTREF", "MKWORKSPACE", "MOVE",
"OPTIONS", "ORDERPATCH", "PATCH", "POST", "PRI",
"PROPFIND", "PROPPATCH", "PUT", "QUERY", "REBIND",
"REPORT", "SEARCH", "TRACE", "UNBIND", "UNCHECKOUT",
"UNLINK", "UNLOCK", "UPDATE", "UPDATEREDIRECTREF", "VERSION-CONTROL",
}
for _, m := range methods {
validMethods[m] = true
2020-11-25 19:01:53 +08:00
}
2026-08-31 14:55:10 +08:00
}
2020-11-25 19:01:53 +08:00
2026-08-31 14:55:10 +08:00
func isValidHTTPMethod(b []byte) bool {
if len(b) == 0 {
return false
}
idx := bytes.IndexByte(b, ' ')
if idx == -1 {
return false
}
method := unsafe.String(unsafe.SliceData(b), idx)
return validMethods[method]
2020-11-25 19:01:53 +08:00
}
func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
2026-08-31 14:55:10 +08:00
if !isValidHTTPMethod(b) {
return nil, errNotHTTP
}
content := session.ContentFromContext(c)
2026-08-31 14:55:10 +08:00
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b)))
if err != nil {
if err == io.ErrUnexpectedEOF {
return nil, common.ErrNoClue
}
return nil, errNotHTTP
}
2026-08-31 14:55:10 +08:00
if r.Host == "" {
return nil, common.ErrNoClue
2020-11-25 19:01:53 +08:00
}
sh := &SniffHeader{
version: HTTP1,
2026-08-31 14:55:10 +08:00
host: r.Host,
2020-11-25 19:01:53 +08:00
}
2026-08-31 14:55:10 +08:00
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
// It will set attributes, so skip it.
if content != nil && len(content.Attributes) == 0 {
for key, h := range r.Header {
content.Attributes[key] = strings.Join(h, ",")
}
2026-08-31 14:55:10 +08:00
content.Attributes[":method"] = r.Method
content.Attributes[":path"] = r.URL.Path
}
2020-11-25 19:01:53 +08:00
2026-08-31 14:55:10 +08:00
return sh, nil
2020-11-25 19:01:53 +08:00
}