github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/common/protocol/http/sniff.go (about)

     1  package http
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"strings"
     7  
     8  	"github.com/xtls/xray-core/common"
     9  	"github.com/xtls/xray-core/common/net"
    10  )
    11  
    12  type version byte
    13  
    14  const (
    15  	HTTP1 version = iota
    16  	HTTP2
    17  )
    18  
    19  type SniffHeader struct {
    20  	version version
    21  	host    string
    22  }
    23  
    24  func (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  
    35  func (h *SniffHeader) Domain() string {
    36  	return h.host
    37  }
    38  
    39  var (
    40  	methods = [...]string{"get", "post", "head", "put", "delete", "options", "connect"}
    41  
    42  	errNotHTTPMethod = errors.New("not an HTTP method")
    43  )
    44  
    45  func 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  
    59  func 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  }