github.com/igoogolx/clash@v1.19.8/transport/simple-obfs/http.go (about)

     1  package obfs
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"encoding/base64"
     7  	"fmt"
     8  	"io"
     9  	mathRand "math/rand"
    10  	"net"
    11  	"net/http"
    12  
    13  	"github.com/igoogolx/clash/common/pool"
    14  )
    15  
    16  // HTTPObfs is shadowsocks http simple-obfs implementation
    17  type HTTPObfs struct {
    18  	net.Conn
    19  	host          string
    20  	port          string
    21  	buf           []byte
    22  	offset        int
    23  	firstRequest  bool
    24  	firstResponse bool
    25  }
    26  
    27  func (ho *HTTPObfs) Read(b []byte) (int, error) {
    28  	if ho.buf != nil {
    29  		n := copy(b, ho.buf[ho.offset:])
    30  		ho.offset += n
    31  		if ho.offset == len(ho.buf) {
    32  			pool.Put(ho.buf)
    33  			ho.buf = nil
    34  		}
    35  		return n, nil
    36  	}
    37  
    38  	if ho.firstResponse {
    39  		buf := pool.Get(pool.RelayBufferSize)
    40  		n, err := ho.Conn.Read(buf)
    41  		if err != nil {
    42  			pool.Put(buf)
    43  			return 0, err
    44  		}
    45  		idx := bytes.Index(buf[:n], []byte("\r\n\r\n"))
    46  		if idx == -1 {
    47  			pool.Put(buf)
    48  			return 0, io.EOF
    49  		}
    50  		ho.firstResponse = false
    51  		length := n - (idx + 4)
    52  		n = copy(b, buf[idx+4:n])
    53  		if length > n {
    54  			ho.buf = buf[:idx+4+length]
    55  			ho.offset = idx + 4 + n
    56  		} else {
    57  			pool.Put(buf)
    58  		}
    59  		return n, nil
    60  	}
    61  	return ho.Conn.Read(b)
    62  }
    63  
    64  func (ho *HTTPObfs) Write(b []byte) (int, error) {
    65  	if ho.firstRequest {
    66  		randBytes := make([]byte, 16)
    67  		rand.Read(randBytes)
    68  		req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
    69  		req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", mathRand.Int()%54, mathRand.Int()%2))
    70  		req.Header.Set("Upgrade", "websocket")
    71  		req.Header.Set("Connection", "Upgrade")
    72  		req.Host = ho.host
    73  		if ho.port != "80" {
    74  			req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port)
    75  		}
    76  		req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes))
    77  		req.ContentLength = int64(len(b))
    78  		err := req.Write(ho.Conn)
    79  		ho.firstRequest = false
    80  		return len(b), err
    81  	}
    82  
    83  	return ho.Conn.Write(b)
    84  }
    85  
    86  // NewHTTPObfs return a HTTPObfs
    87  func NewHTTPObfs(conn net.Conn, host string, port string) net.Conn {
    88  	return &HTTPObfs{
    89  		Conn:          conn,
    90  		firstRequest:  true,
    91  		firstResponse: true,
    92  		host:          host,
    93  		port:          port,
    94  	}
    95  }