1package http
2
3import (
4	"bytes"
5	"errors"
6	"strings"
7
8	"github.com/xtls/xray-core/common"
9	"github.com/xtls/xray-core/common/net"
10)
11
12type version byte
13
14const (
15	HTTP1 version = iota
16	HTTP2
17)
18
19type SniffHeader struct {
20	version version
21	host    string
22}
23
24func (h *SniffHeader) Protocol() string {
25	switch h.version {
26	case HTTP1:
27		return "http1"
28	case HTTP2:
29		return "http2"
30	default:
31		return "unknown"
32	}
33}
34
35func (h *SniffHeader) Domain() string {
36	return h.host
37}
38
39var (
40	methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"}
41
42	errNotHTTPMethod = errors.New("not an HTTP method")
43)
44
45func beginWithHTTPMethod(b []byte) error {
46	for _, m := range &methods {
47		if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) {
48			return nil
49		}
50
51		if len(b) < len(m) {
52			return common.ErrNoClue
53		}
54	}
55
56	return errNotHTTPMethod
57}
58
59func SniffHTTP(b []byte) (*SniffHeader, error) {
60	if err := beginWithHTTPMethod(b); err != nil {
61		return nil, err
62	}
63
64	sh := &SniffHeader{
65		version: HTTP1,
66	}
67
68	headers := bytes.Split(b, []byte{'\n'})
69	for i := 1; i < len(headers); i++ {
70		header := headers[i]
71		if len(header) == 0 {
72			break
73		}
74		parts := bytes.SplitN(header, []byte{':'}, 2)
75		if len(parts) != 2 {
76			continue
77		}
78		key := strings.ToLower(string(parts[0]))
79		if key == "host" {
80			rawHost := strings.ToLower(string(bytes.TrimSpace(parts[1])))
81			dest, err := ParseHost(rawHost, net.Port(80))
82			if err != nil {
83				return nil, err
84			}
85			sh.host = dest.Address.String()
86		}
87	}
88
89	if len(sh.host) > 0 {
90		return sh, nil
91	}
92
93	return nil, common.ErrNoClue
94}
95