Source file src/net/http/fcgi/fcgi_test.go

     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  	"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  	// 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  
    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  // a series of FastCGI records that start a request and begin sending the
   186  // request body
   187  var 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  
   200  var 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  
   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  // 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.
   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  			// 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  			done <- true
   256  		}))
   257  		go c.serve()
   258  		// wait for body of request to be closed or all goroutines to block
   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  // Verifies it doesn't crash. 	Issue 11824.
   273  func 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
   288  var 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  
   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  // 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.
   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  // Test whether server properly closes connection when processing slow
   419  // requests
   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