1
2
3
4
5
6
7
8
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
41
42
43
44
45
46
47
48 type h2cHandler struct {
49 Handler http.Handler
50 s *http2.Server
51 }
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 func NewHandler(h http.Handler, s *http2.Server) http.Handler {
67 return &h2cHandler{
68 Handler: h,
69 s: s,
70 }
71 }
72
73
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
83 func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
84
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
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
130
131
132
133
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
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
189
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
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
215
216 return conn
217 }
218 return &bufConn{conn, rw.Reader}
219 }
220
221
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 }
241
View as plain text