mirror of https://github.com/XTLS/Xray-core
118 lines
2.4 KiB
Go
118 lines
2.4 KiB
Go
package http
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/xtls/xray-core/common"
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/session"
|
|
)
|
|
|
|
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 (
|
|
methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"}
|
|
|
|
errNotHTTPMethod = errors.New("not an HTTP method")
|
|
)
|
|
|
|
func beginWithHTTPMethod(b []byte) error {
|
|
for _, m := range &methods {
|
|
if len(b) >= len(m) && strings.EqualFold(string(b[:len(m)]), m) {
|
|
return nil
|
|
}
|
|
|
|
if len(b) < len(m) {
|
|
return common.ErrNoClue
|
|
}
|
|
}
|
|
|
|
return errNotHTTPMethod
|
|
}
|
|
|
|
func SniffHTTP(b []byte, c context.Context) (*SniffHeader, error) {
|
|
content := session.ContentFromContext(c)
|
|
ShouldSniffAttr := true
|
|
// If content.Attributes have information, that means it comes from HTTP inbound PlainHTTP mode.
|
|
// It will set attributes, so skip it.
|
|
if content == nil || content.AttributeLen() != 0 {
|
|
ShouldSniffAttr = false
|
|
}
|
|
if err := beginWithHTTPMethod(b); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sh := &SniffHeader{
|
|
version: HTTP1,
|
|
}
|
|
|
|
headers := bytes.Split(b, []byte{'\n'})
|
|
for i := 1; i < len(headers); i++ {
|
|
header := headers[i]
|
|
if len(header) == 0 {
|
|
break
|
|
}
|
|
parts := bytes.SplitN(header, []byte{':'}, 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
key := strings.ToLower(string(parts[0]))
|
|
value := string(bytes.TrimSpace(parts[1]))
|
|
if ShouldSniffAttr {
|
|
content.SetAttribute(key, value) // Put header in attribute
|
|
}
|
|
if key == "host" {
|
|
rawHost := strings.ToLower(value)
|
|
dest, err := ParseHost(rawHost, net.Port(80))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sh.host = dest.Address.String()
|
|
}
|
|
}
|
|
// Parse request line
|
|
// Request line is like this
|
|
// "GET /homo/114514 HTTP/1.1"
|
|
if len(headers) > 0 && ShouldSniffAttr {
|
|
RequestLineParts := bytes.Split(headers[0], []byte{' '})
|
|
if len(RequestLineParts) == 3 {
|
|
content.SetAttribute(":method", string(RequestLineParts[0]))
|
|
content.SetAttribute(":path", string(RequestLineParts[1]))
|
|
}
|
|
}
|
|
|
|
if len(sh.host) > 0 {
|
|
return sh, nil
|
|
}
|
|
|
|
return nil, common.ErrNoClue
|
|
}
|