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