github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/net/http/fcgi/fcgi_test.go (about) 1 // Copyright 2011 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 fcgi 6 7 import ( 8 "bytes" 9 "errors" 10 "io" 11 "net/http" 12 "strings" 13 "testing" 14 ) 15 16 var sizeTests = []struct { 17 size uint32 18 bytes []byte 19 }{ 20 {0, []byte{0x00}}, 21 {127, []byte{0x7F}}, 22 {128, []byte{0x80, 0x00, 0x00, 0x80}}, 23 {1000, []byte{0x80, 0x00, 0x03, 0xE8}}, 24 {33554431, []byte{0x81, 0xFF, 0xFF, 0xFF}}, 25 } 26 27 func TestSize(t *testing.T) { 28 b := make([]byte, 4) 29 for i, test := range sizeTests { 30 n := encodeSize(b, test.size) 31 if !bytes.Equal(b[:n], test.bytes) { 32 t.Errorf("%d expected %x, encoded %x", i, test.bytes, b) 33 } 34 size, n := readSize(test.bytes) 35 if size != test.size { 36 t.Errorf("%d expected %d, read %d", i, test.size, size) 37 } 38 if len(test.bytes) != n { 39 t.Errorf("%d did not consume all the bytes", i) 40 } 41 } 42 } 43 44 var streamTests = []struct { 45 desc string 46 recType recType 47 reqId uint16 48 content []byte 49 raw []byte 50 }{ 51 {"single record", typeStdout, 1, nil, 52 []byte{1, byte(typeStdout), 0, 1, 0, 0, 0, 0}, 53 }, 54 // this data will have to be split into two records 55 {"two records", typeStdin, 300, make([]byte, 66000), 56 bytes.Join([][]byte{ 57 // header for the first record 58 {1, byte(typeStdin), 0x01, 0x2C, 0xFF, 0xFF, 1, 0}, 59 make([]byte, 65536), 60 // header for the second 61 {1, byte(typeStdin), 0x01, 0x2C, 0x01, 0xD1, 7, 0}, 62 make([]byte, 472), 63 // header for the empty record 64 {1, byte(typeStdin), 0x01, 0x2C, 0, 0, 0, 0}, 65 }, 66 nil), 67 }, 68 } 69 70 type nilCloser struct { 71 io.ReadWriter 72 } 73 74 func (c *nilCloser) Close() error { return nil } 75 76 func TestStreams(t *testing.T) { 77 var rec record 78 outer: 79 for _, test := range streamTests { 80 buf := bytes.NewBuffer(test.raw) 81 var content []byte 82 for buf.Len() > 0 { 83 if err := rec.read(buf); err != nil { 84 t.Errorf("%s: error reading record: %v", test.desc, err) 85 continue outer 86 } 87 content = append(content, rec.content()...) 88 } 89 if rec.h.Type != test.recType { 90 t.Errorf("%s: got type %d expected %d", test.desc, rec.h.Type, test.recType) 91 continue 92 } 93 if rec.h.Id != test.reqId { 94 t.Errorf("%s: got request ID %d expected %d", test.desc, rec.h.Id, test.reqId) 95 continue 96 } 97 if !bytes.Equal(content, test.content) { 98 t.Errorf("%s: read wrong content", test.desc) 99 continue 100 } 101 buf.Reset() 102 c := newConn(&nilCloser{buf}) 103 w := newWriter(c, test.recType, test.reqId) 104 if _, err := w.Write(test.content); err != nil { 105 t.Errorf("%s: error writing record: %v", test.desc, err) 106 continue 107 } 108 if err := w.Close(); err != nil { 109 t.Errorf("%s: error closing stream: %v", test.desc, err) 110 continue 111 } 112 if !bytes.Equal(buf.Bytes(), test.raw) { 113 t.Errorf("%s: wrote wrong content", test.desc) 114 } 115 } 116 } 117 118 type writeOnlyConn struct { 119 buf []byte 120 } 121 122 func (c *writeOnlyConn) Write(p []byte) (int, error) { 123 c.buf = append(c.buf, p...) 124 return len(p), nil 125 } 126 127 func (c *writeOnlyConn) Read(p []byte) (int, error) { 128 return 0, errors.New("conn is write-only") 129 } 130 131 func (c *writeOnlyConn) Close() error { 132 return nil 133 } 134 135 func TestGetValues(t *testing.T) { 136 var rec record 137 rec.h.Type = typeGetValues 138 139 wc := new(writeOnlyConn) 140 c := newChild(wc, nil) 141 err := c.handleRecord(&rec) 142 if err != nil { 143 t.Fatalf("handleRecord: %v", err) 144 } 145 146 const want = "\x01\n\x00\x00\x00\x12\x06\x00" + 147 "\x0f\x01FCGI_MPXS_CONNS1" + 148 "\x00\x00\x00\x00\x00\x00\x01\n\x00\x00\x00\x00\x00\x00" 149 if got := string(wc.buf); got != want { 150 t.Errorf(" got: %q\nwant: %q\n", got, want) 151 } 152 } 153 154 func nameValuePair11(nameData, valueData string) []byte { 155 return bytes.Join( 156 [][]byte{ 157 {byte(len(nameData)), byte(len(valueData))}, 158 []byte(nameData), 159 []byte(valueData), 160 }, 161 nil, 162 ) 163 } 164 165 func makeRecord( 166 recordType recType, 167 requestId uint16, 168 contentData []byte, 169 ) []byte { 170 requestIdB1 := byte(requestId >> 8) 171 requestIdB0 := byte(requestId) 172 173 contentLength := len(contentData) 174 contentLengthB1 := byte(contentLength >> 8) 175 contentLengthB0 := byte(contentLength) 176 return bytes.Join([][]byte{ 177 {1, byte(recordType), requestIdB1, requestIdB0, contentLengthB1, 178 contentLengthB0, 0, 0}, 179 contentData, 180 }, 181 nil) 182 } 183 184 // a series of FastCGI records that start a request and begin sending the 185 // request body 186 var streamBeginTypeStdin = bytes.Join([][]byte{ 187 // set up request 1 188 makeRecord(typeBeginRequest, 1, 189 []byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}), 190 // add required parameters to request 1 191 makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")), 192 makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")), 193 makeRecord(typeParams, 1, nil), 194 // begin sending body of request 1 195 makeRecord(typeStdin, 1, []byte("0123456789abcdef")), 196 }, 197 nil) 198 199 var cleanUpTests = []struct { 200 input []byte 201 err error 202 }{ 203 // confirm that child.handleRecord closes req.pw after aborting req 204 { 205 bytes.Join([][]byte{ 206 streamBeginTypeStdin, 207 makeRecord(typeAbortRequest, 1, nil), 208 }, 209 nil), 210 ErrRequestAborted, 211 }, 212 // confirm that child.serve closes all pipes after error reading record 213 { 214 bytes.Join([][]byte{ 215 streamBeginTypeStdin, 216 nil, 217 }, 218 nil), 219 ErrConnClosed, 220 }, 221 } 222 223 type nopWriteCloser struct { 224 io.Reader 225 } 226 227 func (nopWriteCloser) Write(buf []byte) (int, error) { 228 return len(buf), nil 229 } 230 231 func (nopWriteCloser) Close() error { 232 return nil 233 } 234 235 // Test that child.serve closes the bodies of aborted requests and closes the 236 // bodies of all requests before returning. Causes deadlock if either condition 237 // isn't met. See issue 6934. 238 func TestChildServeCleansUp(t *testing.T) { 239 for _, tt := range cleanUpTests { 240 input := make([]byte, len(tt.input)) 241 copy(input, tt.input) 242 rc := nopWriteCloser{bytes.NewReader(input)} 243 done := make(chan bool) 244 c := newChild(rc, http.HandlerFunc(func( 245 w http.ResponseWriter, 246 r *http.Request, 247 ) { 248 // block on reading body of request 249 _, err := io.Copy(io.Discard, r.Body) 250 if err != tt.err { 251 t.Errorf("Expected %#v, got %#v", tt.err, err) 252 } 253 // not reached if body of request isn't closed 254 done <- true 255 })) 256 go c.serve() 257 // wait for body of request to be closed or all goroutines to block 258 <-done 259 } 260 } 261 262 type rwNopCloser struct { 263 io.Reader 264 io.Writer 265 } 266 267 func (rwNopCloser) Close() error { 268 return nil 269 } 270 271 // Verifies it doesn't crash. Issue 11824. 272 func TestMalformedParams(t *testing.T) { 273 input := []byte{ 274 // beginRequest, requestId=1, contentLength=8, role=1, keepConn=1 275 1, 1, 0, 1, 0, 8, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 276 // params, requestId=1, contentLength=10, k1Len=50, v1Len=50 (malformed, wrong length) 277 1, 4, 0, 1, 0, 10, 0, 0, 50, 50, 3, 4, 5, 6, 7, 8, 9, 10, 278 // end of params 279 1, 4, 0, 1, 0, 0, 0, 0, 280 } 281 rw := rwNopCloser{bytes.NewReader(input), io.Discard} 282 c := newChild(rw, http.DefaultServeMux) 283 c.serve() 284 } 285 286 // a series of FastCGI records that start and end a request 287 var streamFullRequestStdin = bytes.Join([][]byte{ 288 // set up request 289 makeRecord(typeBeginRequest, 1, 290 []byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}), 291 // add required parameters 292 makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")), 293 makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")), 294 // set optional parameters 295 makeRecord(typeParams, 1, nameValuePair11("REMOTE_USER", "jane.doe")), 296 makeRecord(typeParams, 1, nameValuePair11("QUERY_STRING", "/foo/bar")), 297 makeRecord(typeParams, 1, nil), 298 // begin sending body of request 299 makeRecord(typeStdin, 1, []byte("0123456789abcdef")), 300 // end request 301 makeRecord(typeEndRequest, 1, nil), 302 }, 303 nil) 304 305 var envVarTests = []struct { 306 input []byte 307 envVar string 308 expectedVal string 309 expectedFilteredOut bool 310 }{ 311 { 312 streamFullRequestStdin, 313 "REMOTE_USER", 314 "jane.doe", 315 false, 316 }, 317 { 318 streamFullRequestStdin, 319 "QUERY_STRING", 320 "", 321 true, 322 }, 323 } 324 325 // Test that environment variables set for a request can be 326 // read by a handler. Ensures that variables not set will not 327 // be exposed to a handler. 328 func TestChildServeReadsEnvVars(t *testing.T) { 329 for _, tt := range envVarTests { 330 input := make([]byte, len(tt.input)) 331 copy(input, tt.input) 332 rc := nopWriteCloser{bytes.NewReader(input)} 333 done := make(chan bool) 334 c := newChild(rc, http.HandlerFunc(func( 335 w http.ResponseWriter, 336 r *http.Request, 337 ) { 338 env := ProcessEnv(r) 339 if _, ok := env[tt.envVar]; ok && tt.expectedFilteredOut { 340 t.Errorf("Expected environment variable %s to not be set, but set to %s", 341 tt.envVar, env[tt.envVar]) 342 } else if env[tt.envVar] != tt.expectedVal { 343 t.Errorf("Expected %s, got %s", tt.expectedVal, env[tt.envVar]) 344 } 345 done <- true 346 })) 347 go c.serve() 348 <-done 349 } 350 } 351 352 func TestResponseWriterSniffsContentType(t *testing.T) { 353 var tests = []struct { 354 name string 355 body string 356 wantCT string 357 }{ 358 { 359 name: "no body", 360 wantCT: "text/plain; charset=utf-8", 361 }, 362 { 363 name: "html", 364 body: "<html><head><title>test page</title></head><body>This is a body</body></html>", 365 wantCT: "text/html; charset=utf-8", 366 }, 367 { 368 name: "text", 369 body: strings.Repeat("gopher", 86), 370 wantCT: "text/plain; charset=utf-8", 371 }, 372 { 373 name: "jpg", 374 body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024), 375 wantCT: "image/jpeg", 376 }, 377 } 378 for _, tt := range tests { 379 t.Run(tt.name, func(t *testing.T) { 380 input := make([]byte, len(streamFullRequestStdin)) 381 copy(input, streamFullRequestStdin) 382 rc := nopWriteCloser{bytes.NewReader(input)} 383 done := make(chan bool) 384 var resp *response 385 c := newChild(rc, http.HandlerFunc(func( 386 w http.ResponseWriter, 387 r *http.Request, 388 ) { 389 io.WriteString(w, tt.body) 390 resp = w.(*response) 391 done <- true 392 })) 393 defer c.cleanUp() 394 go c.serve() 395 <-done 396 if got := resp.Header().Get("Content-Type"); got != tt.wantCT { 397 t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT) 398 } 399 }) 400 } 401 }