gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

h2c.go (7201B)


      1 // Copyright 2018 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package h2c implements the unencrypted "h2c" form of HTTP/2.
      6 //
      7 // The h2c protocol is the non-TLS version of HTTP/2 which is not available from
      8 // net/http or golang.org/x/net/http2.
      9 package h2c
     10 
     11 import (
     12 	"bufio"
     13 	"bytes"
     14 	"encoding/base64"
     15 	"errors"
     16 	"fmt"
     17 	"io"
     18 	"log"
     19 	"net"
     20 	"net/http"
     21 	"net/textproto"
     22 	"os"
     23 	"strings"
     24 
     25 	"golang.org/x/net/http/httpguts"
     26 	"golang.org/x/net/http2"
     27 )
     28 
     29 var (
     30 	http2VerboseLogs bool
     31 )
     32 
     33 func init() {
     34 	e := os.Getenv("GODEBUG")
     35 	if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
     36 		http2VerboseLogs = true
     37 	}
     38 }
     39 
     40 // h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic
     41 // that should be h2c traffic. There are two ways to begin a h2c connection
     42 // (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this
     43 // works by starting an h2c connection with a string of bytes that is valid
     44 // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
     45 // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
     46 // h2c. When either of those situations occur we hijack the HTTP/1 connection,
     47 // convert it to an HTTP/2 connection and pass the net.Conn to http2.ServeConn.
     48 type h2cHandler struct {
     49 	Handler http.Handler
     50 	s       *http2.Server
     51 }
     52 
     53 // NewHandler returns an http.Handler that wraps h, intercepting any h2c
     54 // traffic. If a request is an h2c connection, it's hijacked and redirected to
     55 // s.ServeConn. Otherwise the returned Handler just forwards requests to h. This
     56 // works because h2c is designed to be parseable as valid HTTP/1, but ignored by
     57 // any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1
     58 // compatible parts of the Go http library to parse and recognize h2c requests.
     59 // Once a request is recognized as h2c, we hijack the connection and convert it
     60 // to an HTTP/2 connection which is understandable to s.ServeConn. (s.ServeConn
     61 // understands HTTP/2 except for the h2c part of it.)
     62 //
     63 // The first request on an h2c connection is read entirely into memory before
     64 // the Handler is called. To limit the memory consumed by this request, wrap
     65 // the result of NewHandler in an http.MaxBytesHandler.
     66 func NewHandler(h http.Handler, s *http2.Server) http.Handler {
     67 	return &h2cHandler{
     68 		Handler: h,
     69 		s:       s,
     70 	}
     71 }
     72 
     73 // extractServer extracts existing http.Server instance from http.Request or create an empty http.Server
     74 func extractServer(r *http.Request) *http.Server {
     75 	server, ok := r.Context().Value(http.ServerContextKey).(*http.Server)
     76 	if ok {
     77 		return server
     78 	}
     79 	return new(http.Server)
     80 }
     81 
     82 // ServeHTTP implement the h2c support that is enabled by h2c.GetH2CHandler.
     83 func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
     84 	// Handle h2c with prior knowledge (RFC 7540 Section 3.4)
     85 	if r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" {
     86 		if http2VerboseLogs {
     87 			log.Print("h2c: attempting h2c with prior knowledge.")
     88 		}
     89 		conn, err := initH2CWithPriorKnowledge(w)
     90 		if err != nil {
     91 			if http2VerboseLogs {
     92 				log.Printf("h2c: error h2c with prior knowledge: %v", err)
     93 			}
     94 			return
     95 		}
     96 		defer conn.Close()
     97 		s.s.ServeConn(conn, &http2.ServeConnOpts{
     98 			Context:          r.Context(),
     99 			BaseConfig:       extractServer(r),
    100 			Handler:          s.Handler,
    101 			SawClientPreface: true,
    102 		})
    103 		return
    104 	}
    105 	// Handle Upgrade to h2c (RFC 7540 Section 3.2)
    106 	if isH2CUpgrade(r.Header) {
    107 		conn, settings, err := h2cUpgrade(w, r)
    108 		if err != nil {
    109 			if http2VerboseLogs {
    110 				log.Printf("h2c: error h2c upgrade: %v", err)
    111 			}
    112 			w.WriteHeader(http.StatusInternalServerError)
    113 			return
    114 		}
    115 		defer conn.Close()
    116 		s.s.ServeConn(conn, &http2.ServeConnOpts{
    117 			Context:        r.Context(),
    118 			BaseConfig:     extractServer(r),
    119 			Handler:        s.Handler,
    120 			UpgradeRequest: r,
    121 			Settings:       settings,
    122 		})
    123 		return
    124 	}
    125 	s.Handler.ServeHTTP(w, r)
    126 	return
    127 }
    128 
    129 // initH2CWithPriorKnowledge implements creating a h2c connection with prior
    130 // knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn.
    131 // All we have to do is look for the client preface that is suppose to be part
    132 // of the body, and reforward the client preface on the net.Conn this function
    133 // creates.
    134 func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
    135 	hijacker, ok := w.(http.Hijacker)
    136 	if !ok {
    137 		return nil, errors.New("h2c: connection does not support Hijack")
    138 	}
    139 	conn, rw, err := hijacker.Hijack()
    140 	if err != nil {
    141 		return nil, err
    142 	}
    143 
    144 	const expectedBody = "SM\r\n\r\n"
    145 
    146 	buf := make([]byte, len(expectedBody))
    147 	n, err := io.ReadFull(rw, buf)
    148 	if err != nil {
    149 		return nil, fmt.Errorf("h2c: error reading client preface: %s", err)
    150 	}
    151 
    152 	if string(buf[:n]) == expectedBody {
    153 		return newBufConn(conn, rw), nil
    154 	}
    155 
    156 	conn.Close()
    157 	return nil, errors.New("h2c: invalid client preface")
    158 }
    159 
    160 // h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
    161 func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []byte, err error) {
    162 	settings, err = getH2Settings(r.Header)
    163 	if err != nil {
    164 		return nil, nil, err
    165 	}
    166 	hijacker, ok := w.(http.Hijacker)
    167 	if !ok {
    168 		return nil, nil, errors.New("h2c: connection does not support Hijack")
    169 	}
    170 
    171 	body, err := io.ReadAll(r.Body)
    172 	if err != nil {
    173 		return nil, nil, err
    174 	}
    175 	r.Body = io.NopCloser(bytes.NewBuffer(body))
    176 
    177 	conn, rw, err := hijacker.Hijack()
    178 	if err != nil {
    179 		return nil, nil, err
    180 	}
    181 
    182 	rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" +
    183 		"Connection: Upgrade\r\n" +
    184 		"Upgrade: h2c\r\n\r\n"))
    185 	return newBufConn(conn, rw), settings, nil
    186 }
    187 
    188 // isH2CUpgrade returns true if the header properly request an upgrade to h2c
    189 // as specified by Section 3.2.
    190 func isH2CUpgrade(h http.Header) bool {
    191 	return httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") &&
    192 		httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings")
    193 }
    194 
    195 // getH2Settings returns the settings in the HTTP2-Settings header.
    196 func getH2Settings(h http.Header) ([]byte, error) {
    197 	vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")]
    198 	if !ok {
    199 		return nil, errors.New("missing HTTP2-Settings header")
    200 	}
    201 	if len(vals) != 1 {
    202 		return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals)
    203 	}
    204 	settings, err := base64.RawURLEncoding.DecodeString(vals[0])
    205 	if err != nil {
    206 		return nil, err
    207 	}
    208 	return settings, nil
    209 }
    210 
    211 func newBufConn(conn net.Conn, rw *bufio.ReadWriter) net.Conn {
    212 	rw.Flush()
    213 	if rw.Reader.Buffered() == 0 {
    214 		// If there's no buffered data to be read,
    215 		// we can just discard the bufio.ReadWriter.
    216 		return conn
    217 	}
    218 	return &bufConn{conn, rw.Reader}
    219 }
    220 
    221 // bufConn wraps a net.Conn, but reads drain the bufio.Reader first.
    222 type bufConn struct {
    223 	net.Conn
    224 	*bufio.Reader
    225 }
    226 
    227 func (c *bufConn) Read(p []byte) (int, error) {
    228 	if c.Reader == nil {
    229 		return c.Conn.Read(p)
    230 	}
    231 	n := c.Reader.Buffered()
    232 	if n == 0 {
    233 		c.Reader = nil
    234 		return c.Conn.Read(p)
    235 	}
    236 	if n < len(p) {
    237 		p = p[:n]
    238 	}
    239 	return c.Reader.Read(p)
    240 }