github.com/database64128/shadowsocks-go@v1.10.2-0.20240315062903-143a773533f1/http/server.go (about)

     1  package http
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  	"time"
    12  	_ "unsafe"
    13  
    14  	"github.com/database64128/shadowsocks-go/conn"
    15  	"github.com/database64128/shadowsocks-go/direct"
    16  	"github.com/database64128/shadowsocks-go/pipe"
    17  	"github.com/database64128/shadowsocks-go/zerocopy"
    18  	"go.uber.org/zap"
    19  )
    20  
    21  //go:linkname readRequest net/http.readRequest
    22  func readRequest(b *bufio.Reader) (req *http.Request, err error)
    23  
    24  // NewHttpStreamServerReadWriter handles a HTTP request from rw and wraps rw into a ReadWriter ready for use.
    25  func NewHttpStreamServerReadWriter(rw zerocopy.DirectReadWriteCloser, logger *zap.Logger) (*direct.DirectStreamReadWriter, conn.Addr, error) {
    26  	rwbr := bufio.NewReader(rw)
    27  	req, err := readRequest(rwbr)
    28  	if err != nil {
    29  		return nil, conn.Addr{}, err
    30  	}
    31  
    32  	// Host -> targetAddr
    33  	targetAddr, err := hostHeaderToAddr(req.Host)
    34  	if err != nil {
    35  		send418(rw)
    36  		return nil, conn.Addr{}, err
    37  	}
    38  
    39  	// Fast-track CONNECT.
    40  	if req.Method == http.MethodConnect {
    41  		if _, err = fmt.Fprintf(rw, "HTTP/1.1 200 OK\r\nDate: %s\r\n\r\n", time.Now().UTC().Format(http.TimeFormat)); err != nil {
    42  			return nil, conn.Addr{}, err
    43  		}
    44  		return direct.NewDirectStreamReadWriter(rw), targetAddr, nil
    45  	}
    46  
    47  	// Set up pipes.
    48  	pl, pr := pipe.NewDuplexPipe()
    49  
    50  	// Spin up a goroutine to write processed requests to pl
    51  	// and read responses from pl.
    52  	go func() {
    53  		var rerr, werr error
    54  
    55  		plbr := bufio.NewReader(pl)
    56  		plbw := bufio.NewWriter(pl)
    57  		rwbw := bufio.NewWriter(rw)
    58  
    59  		for {
    60  			// Delete hop-by-hop headers specified in Connection.
    61  			connectionHeader := req.Header["Connection"]
    62  			for i := range connectionHeader {
    63  				req.Header.Del(connectionHeader[i])
    64  			}
    65  			delete(req.Header, "Connection")
    66  
    67  			delete(req.Header, "Proxy-Connection")
    68  
    69  			if ce := logger.Check(zap.DebugLevel, "Writing HTTP request"); ce != nil {
    70  				ce.Write(
    71  					zap.String("proto", req.Proto),
    72  					zap.String("method", req.Method),
    73  					zap.String("url", req.RequestURI),
    74  				)
    75  			}
    76  
    77  			// Write request.
    78  			if werr = req.Write(plbw); werr != nil {
    79  				werr = fmt.Errorf("failed to write HTTP request: %w", werr)
    80  				break
    81  			}
    82  
    83  			// Flush request.
    84  			if werr = plbw.Flush(); werr != nil {
    85  				werr = fmt.Errorf("failed to flush HTTP request: %w", werr)
    86  				break
    87  			}
    88  
    89  			var resp *http.Response
    90  
    91  			// Read response.
    92  			resp, rerr = http.ReadResponse(plbr, req)
    93  			if rerr != nil {
    94  				rerr = fmt.Errorf("failed to read HTTP response: %w", rerr)
    95  				break
    96  			}
    97  
    98  			// Add Connection: close if response is 301, 302, or 307,
    99  			// and Location points to a different host.
   100  			switch resp.StatusCode {
   101  			case http.StatusMovedPermanently, http.StatusFound, http.StatusTemporaryRedirect:
   102  				location := resp.Header["Location"]
   103  
   104  				if ce := logger.Check(zap.DebugLevel, "Checking HTTP 3xx response Location header"); ce != nil {
   105  					ce.Write(
   106  						zap.String("proto", resp.Proto),
   107  						zap.String("status", resp.Status),
   108  						zap.Strings("location", location),
   109  					)
   110  				}
   111  
   112  				if len(location) != 1 {
   113  					break
   114  				}
   115  
   116  				url, err := url.Parse(location[0])
   117  				if err != nil {
   118  					break
   119  				}
   120  
   121  				switch url.Host {
   122  				case req.Host, "":
   123  				default:
   124  					resp.Close = true
   125  				}
   126  			}
   127  
   128  			if ce := logger.Check(zap.DebugLevel, "Writing HTTP response"); ce != nil {
   129  				ce.Write(
   130  					zap.String("proto", resp.Proto),
   131  					zap.String("status", resp.Status),
   132  				)
   133  			}
   134  
   135  			// Write response.
   136  			if rerr = resp.Write(rwbw); rerr != nil {
   137  				rerr = fmt.Errorf("failed to write HTTP response: %w", rerr)
   138  				break
   139  			}
   140  
   141  			// Flush response.
   142  			if rerr = rwbw.Flush(); rerr != nil {
   143  				rerr = fmt.Errorf("failed to flush HTTP response: %w", rerr)
   144  				break
   145  			}
   146  
   147  			// Stop relaying if either client or server indicates that the connection should be closed.
   148  			//
   149  			// RFC 7230 section 6.6 says:
   150  			// The server SHOULD send a "close" connection option in its final response on that connection.
   151  			//
   152  			// It's not a "MUST", so we check both.
   153  			if req.Close || resp.Close {
   154  				break
   155  			}
   156  
   157  			// Read request.
   158  			req, werr = readRequest(rwbr)
   159  			if werr != nil {
   160  				if werr != io.EOF {
   161  					werr = fmt.Errorf("failed to read HTTP request: %w", werr)
   162  				}
   163  				break
   164  			}
   165  		}
   166  
   167  		pl.CloseReadWithError(rerr)
   168  		pl.CloseWriteWithError(werr)
   169  		rw.Close()
   170  	}()
   171  
   172  	// Wrap pr into a direct stream ReadWriter.
   173  	return direct.NewDirectStreamReadWriter(pr), targetAddr, nil
   174  }
   175  
   176  var errEmptyHostHeader = errors.New("empty host header")
   177  
   178  // hostHeaderToAddr parses the Host header into an address.
   179  //
   180  // Host may be in any of the following forms:
   181  //   - example.com
   182  //   - example.com:443
   183  //   - 1.1.1.1
   184  //   - 1.1.1.1:443
   185  //   - [2606:4700:4700::1111]
   186  //   - [2606:4700:4700::1111]:443
   187  func hostHeaderToAddr(host string) (conn.Addr, error) {
   188  	switch {
   189  	case len(host) == 0:
   190  		return conn.Addr{}, errEmptyHostHeader
   191  	case strings.IndexByte(host, ':') == -1:
   192  		return conn.AddrFromHostPort(host, 80)
   193  	case host[0] == '[' && host[len(host)-1] == ']':
   194  		return conn.AddrFromHostPort(host[1:len(host)-1], 80)
   195  	default:
   196  		return conn.ParseAddr(host)
   197  	}
   198  }
   199  
   200  func send418(w io.Writer) error {
   201  	_, err := fmt.Fprint(w, "HTTP/1.1 418 I'm a teapot\r\n\r\n")
   202  	return err
   203  }