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