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
5package fcgi
6
7import (
8	"bytes"
9	"errors"
10	"io"
11	"net/http"
12	"strings"
13	"testing"
14	"time"
15)
16
17var 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
28func 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
45var 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	// this data will have to be split into two records
56	{"two records", typeStdin, 300, make([]byte, 66000),
57		bytes.Join([][]byte{
58			// header for the first record
59			{1, byte(typeStdin), 0x01, 0x2C, 0xFF, 0xFF, 1, 0},
60			make([]byte, 65536),
61			// header for the second
62			{1, byte(typeStdin), 0x01, 0x2C, 0x01, 0xD1, 7, 0},
63			make([]byte, 472),
64			// header for the empty record
65			{1, byte(typeStdin), 0x01, 0x2C, 0, 0, 0, 0},
66		},
67			nil),
68	},
69}
70
71type nilCloser struct {
72	io.ReadWriter
73}
74
75func (c *nilCloser) Close() error { return nil }
76
77func TestStreams(t *testing.T) {
78	var rec record
79outer:
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
119type writeOnlyConn struct {
120	buf []byte
121}
122
123func (c *writeOnlyConn) Write(p []byte) (int, error) {
124	c.buf = append(c.buf, p...)
125	return len(p), nil
126}
127
128func (c *writeOnlyConn) Read(p []byte) (int, error) {
129	return 0, errors.New("conn is write-only")
130}
131
132func (c *writeOnlyConn) Close() error {
133	return nil
134}
135
136func 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
155func 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
166func 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// a series of FastCGI records that start a request and begin sending the
186// request body
187var streamBeginTypeStdin = bytes.Join([][]byte{
188	// set up request 1
189	makeRecord(typeBeginRequest, 1,
190		[]byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}),
191	// add required parameters to request 1
192	makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")),
193	makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")),
194	makeRecord(typeParams, 1, nil),
195	// begin sending body of request 1
196	makeRecord(typeStdin, 1, []byte("0123456789abcdef")),
197},
198	nil)
199
200var cleanUpTests = []struct {
201	input []byte
202	err   error
203}{
204	// confirm that child.handleRecord closes req.pw after aborting req
205	{
206		bytes.Join([][]byte{
207			streamBeginTypeStdin,
208			makeRecord(typeAbortRequest, 1, nil),
209		},
210			nil),
211		ErrRequestAborted,
212	},
213	// confirm that child.serve closes all pipes after error reading record
214	{
215		bytes.Join([][]byte{
216			streamBeginTypeStdin,
217			nil,
218		},
219			nil),
220		ErrConnClosed,
221	},
222}
223
224type nopWriteCloser struct {
225	io.Reader
226}
227
228func (nopWriteCloser) Write(buf []byte) (int, error) {
229	return len(buf), nil
230}
231
232func (nopWriteCloser) Close() error {
233	return nil
234}
235
236// Test that child.serve closes the bodies of aborted requests and closes the
237// bodies of all requests before returning. Causes deadlock if either condition
238// isn't met. See issue 6934.
239func 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 struct{})
245		c := newChild(rc, http.HandlerFunc(func(
246			w http.ResponseWriter,
247			r *http.Request,
248		) {
249			// block on reading body of request
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			// not reached if body of request isn't closed
255			close(done)
256		}))
257		c.serve()
258		// wait for body of request to be closed or all goroutines to block
259		<-done
260	}
261}
262
263type rwNopCloser struct {
264	io.Reader
265	io.Writer
266}
267
268func (rwNopCloser) Close() error {
269	return nil
270}
271
272// Verifies it doesn't crash. 	Issue 11824.
273func TestMalformedParams(t *testing.T) {
274	input := []byte{
275		// beginRequest, requestId=1, contentLength=8, role=1, keepConn=1
276		1, 1, 0, 1, 0, 8, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
277		// params, requestId=1, contentLength=10, k1Len=50, v1Len=50 (malformed, wrong length)
278		1, 4, 0, 1, 0, 10, 0, 0, 50, 50, 3, 4, 5, 6, 7, 8, 9, 10,
279		// end of params
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// a series of FastCGI records that start and end a request
288var streamFullRequestStdin = bytes.Join([][]byte{
289	// set up request
290	makeRecord(typeBeginRequest, 1,
291		[]byte{0, byte(roleResponder), 0, 0, 0, 0, 0, 0}),
292	// add required parameters
293	makeRecord(typeParams, 1, nameValuePair11("REQUEST_METHOD", "GET")),
294	makeRecord(typeParams, 1, nameValuePair11("SERVER_PROTOCOL", "HTTP/1.1")),
295	// set optional parameters
296	makeRecord(typeParams, 1, nameValuePair11("REMOTE_USER", "jane.doe")),
297	makeRecord(typeParams, 1, nameValuePair11("QUERY_STRING", "/foo/bar")),
298	makeRecord(typeParams, 1, nil),
299	// begin sending body of request
300	makeRecord(typeStdin, 1, []byte("0123456789abcdef")),
301	// end request
302	makeRecord(typeEndRequest, 1, nil),
303},
304	nil)
305
306var 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// Test that environment variables set for a request can be
327// read by a handler. Ensures that variables not set will not
328// be exposed to a handler.
329func 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 struct{})
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			close(done)
347		}))
348		c.serve()
349		<-done
350	}
351}
352
353func 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 struct{})
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				close(done)
393			}))
394			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}
402
403type signalingNopWriteCloser struct {
404	io.ReadCloser
405	closed chan bool
406}
407
408func (*signalingNopWriteCloser) Write(buf []byte) (int, error) {
409	return len(buf), nil
410}
411
412func (rc *signalingNopWriteCloser) Close() error {
413	close(rc.closed)
414	return rc.ReadCloser.Close()
415}
416
417// Test whether server properly closes connection when processing slow
418// requests
419func TestSlowRequest(t *testing.T) {
420	pr, pw := io.Pipe()
421
422	writerDone := make(chan struct{})
423	go func() {
424		for _, buf := range [][]byte{
425			streamBeginTypeStdin,
426			makeRecord(typeStdin, 1, nil),
427		} {
428			pw.Write(buf)
429			time.Sleep(100 * time.Millisecond)
430		}
431		close(writerDone)
432	}()
433	defer func() {
434		<-writerDone
435		pw.Close()
436	}()
437
438	rc := &signalingNopWriteCloser{pr, make(chan bool)}
439	handlerDone := make(chan bool)
440
441	c := newChild(rc, http.HandlerFunc(func(
442		w http.ResponseWriter,
443		r *http.Request,
444	) {
445		w.WriteHeader(200)
446		close(handlerDone)
447	}))
448	c.serve()
449
450	<-handlerDone
451	<-rc.closed
452	t.Log("FastCGI child closed connection")
453}
454