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
7// This file implements FastCGI from the perspective of a child process.
8
9import (
10	"context"
11	"errors"
12	"fmt"
13	"io"
14	"io/ioutil"
15	"net"
16	"net/http"
17	"net/http/cgi"
18	"os"
19	"strings"
20	"sync"
21	"time"
22)
23
24// request holds the state for an in-progress request. As soon as it's complete,
25// it's converted to an http.Request.
26type request struct {
27	pw        *io.PipeWriter
28	reqId     uint16
29	params    map[string]string
30	buf       [1024]byte
31	rawParams []byte
32	keepConn  bool
33}
34
35// envVarsContextKey uniquely identifies a mapping of CGI
36// environment variables to their values in a request context
37type envVarsContextKey struct{}
38
39func newRequest(reqId uint16, flags uint8) *request {
40	r := &request{
41		reqId:    reqId,
42		params:   map[string]string{},
43		keepConn: flags&flagKeepConn != 0,
44	}
45	r.rawParams = r.buf[:0]
46	return r
47}
48
49// parseParams reads an encoded []byte into Params.
50func (r *request) parseParams() {
51	text := r.rawParams
52	r.rawParams = nil
53	for len(text) > 0 {
54		keyLen, n := readSize(text)
55		if n == 0 {
56			return
57		}
58		text = text[n:]
59		valLen, n := readSize(text)
60		if n == 0 {
61			return
62		}
63		text = text[n:]
64		if int(keyLen)+int(valLen) > len(text) {
65			return
66		}
67		key := readString(text, keyLen)
68		text = text[keyLen:]
69		val := readString(text, valLen)
70		text = text[valLen:]
71		r.params[key] = val
72	}
73}
74
75// response implements http.ResponseWriter.
76type response struct {
77	req         *request
78	header      http.Header
79	w           *bufWriter
80	wroteHeader bool
81}
82
83func newResponse(c *child, req *request) *response {
84	return &response{
85		req:    req,
86		header: http.Header{},
87		w:      newWriter(c.conn, typeStdout, req.reqId),
88	}
89}
90
91func (r *response) Header() http.Header {
92	return r.header
93}
94
95func (r *response) Write(data []byte) (int, error) {
96	if !r.wroteHeader {
97		r.WriteHeader(http.StatusOK)
98	}
99	return r.w.Write(data)
100}
101
102func (r *response) WriteHeader(code int) {
103	if r.wroteHeader {
104		return
105	}
106	r.wroteHeader = true
107	if code == http.StatusNotModified {
108		// Must not have body.
109		r.header.Del("Content-Type")
110		r.header.Del("Content-Length")
111		r.header.Del("Transfer-Encoding")
112	} else if r.header.Get("Content-Type") == "" {
113		r.header.Set("Content-Type", "text/html; charset=utf-8")
114	}
115
116	if r.header.Get("Date") == "" {
117		r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
118	}
119
120	fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code))
121	r.header.Write(r.w)
122	r.w.WriteString("\r\n")
123}
124
125func (r *response) Flush() {
126	if !r.wroteHeader {
127		r.WriteHeader(http.StatusOK)
128	}
129	r.w.Flush()
130}
131
132func (r *response) Close() error {
133	r.Flush()
134	return r.w.Close()
135}
136
137type child struct {
138	conn    *conn
139	handler http.Handler
140
141	mu       sync.Mutex          // protects requests:
142	requests map[uint16]*request // keyed by request ID
143}
144
145func newChild(rwc io.ReadWriteCloser, handler http.Handler) *child {
146	return &child{
147		conn:     newConn(rwc),
148		handler:  handler,
149		requests: make(map[uint16]*request),
150	}
151}
152
153func (c *child) serve() {
154	defer c.conn.Close()
155	defer c.cleanUp()
156	var rec record
157	for {
158		if err := rec.read(c.conn.rwc); err != nil {
159			return
160		}
161		if err := c.handleRecord(&rec); err != nil {
162			return
163		}
164	}
165}
166
167var errCloseConn = errors.New("fcgi: connection should be closed")
168
169var emptyBody = ioutil.NopCloser(strings.NewReader(""))
170
171// ErrRequestAborted is returned by Read when a handler attempts to read the
172// body of a request that has been aborted by the web server.
173var ErrRequestAborted = errors.New("fcgi: request aborted by web server")
174
175// ErrConnClosed is returned by Read when a handler attempts to read the body of
176// a request after the connection to the web server has been closed.
177var ErrConnClosed = errors.New("fcgi: connection to web server closed")
178
179func (c *child) handleRecord(rec *record) error {
180	c.mu.Lock()
181	req, ok := c.requests[rec.h.Id]
182	c.mu.Unlock()
183	if !ok && rec.h.Type != typeBeginRequest && rec.h.Type != typeGetValues {
184		// The spec says to ignore unknown request IDs.
185		return nil
186	}
187
188	switch rec.h.Type {
189	case typeBeginRequest:
190		if req != nil {
191			// The server is trying to begin a request with the same ID
192			// as an in-progress request. This is an error.
193			return errors.New("fcgi: received ID that is already in-flight")
194		}
195
196		var br beginRequest
197		if err := br.read(rec.content()); err != nil {
198			return err
199		}
200		if br.role != roleResponder {
201			c.conn.writeEndRequest(rec.h.Id, 0, statusUnknownRole)
202			return nil
203		}
204		req = newRequest(rec.h.Id, br.flags)
205		c.mu.Lock()
206		c.requests[rec.h.Id] = req
207		c.mu.Unlock()
208		return nil
209	case typeParams:
210		// NOTE(eds): Technically a key-value pair can straddle the boundary
211		// between two packets. We buffer until we've received all parameters.
212		if len(rec.content()) > 0 {
213			req.rawParams = append(req.rawParams, rec.content()...)
214			return nil
215		}
216		req.parseParams()
217		return nil
218	case typeStdin:
219		content := rec.content()
220		if req.pw == nil {
221			var body io.ReadCloser
222			if len(content) > 0 {
223				// body could be an io.LimitReader, but it shouldn't matter
224				// as long as both sides are behaving.
225				body, req.pw = io.Pipe()
226			} else {
227				body = emptyBody
228			}
229			go c.serveRequest(req, body)
230		}
231		if len(content) > 0 {
232			// TODO(eds): This blocks until the handler reads from the pipe.
233			// If the handler takes a long time, it might be a problem.
234			req.pw.Write(content)
235		} else if req.pw != nil {
236			req.pw.Close()
237		}
238		return nil
239	case typeGetValues:
240		values := map[string]string{"FCGI_MPXS_CONNS": "1"}
241		c.conn.writePairs(typeGetValuesResult, 0, values)
242		return nil
243	case typeData:
244		// If the filter role is implemented, read the data stream here.
245		return nil
246	case typeAbortRequest:
247		c.mu.Lock()
248		delete(c.requests, rec.h.Id)
249		c.mu.Unlock()
250		c.conn.writeEndRequest(rec.h.Id, 0, statusRequestComplete)
251		if req.pw != nil {
252			req.pw.CloseWithError(ErrRequestAborted)
253		}
254		if !req.keepConn {
255			// connection will close upon return
256			return errCloseConn
257		}
258		return nil
259	default:
260		b := make([]byte, 8)
261		b[0] = byte(rec.h.Type)
262		c.conn.writeRecord(typeUnknownType, 0, b)
263		return nil
264	}
265}
266
267// filterOutUsedEnvVars returns a new map of env vars without the
268// variables in the given envVars map that are read for creating each http.Request
269func filterOutUsedEnvVars(envVars map[string]string) map[string]string {
270	withoutUsedEnvVars := make(map[string]string)
271	for k, v := range envVars {
272		if addFastCGIEnvToContext(k) {
273			withoutUsedEnvVars[k] = v
274		}
275	}
276	return withoutUsedEnvVars
277}
278
279func (c *child) serveRequest(req *request, body io.ReadCloser) {
280	r := newResponse(c, req)
281	httpReq, err := cgi.RequestFromMap(req.params)
282	if err != nil {
283		// there was an error reading the request
284		r.WriteHeader(http.StatusInternalServerError)
285		c.conn.writeRecord(typeStderr, req.reqId, []byte(err.Error()))
286	} else {
287		httpReq.Body = body
288		withoutUsedEnvVars := filterOutUsedEnvVars(req.params)
289		envVarCtx := context.WithValue(httpReq.Context(), envVarsContextKey{}, withoutUsedEnvVars)
290		httpReq = httpReq.WithContext(envVarCtx)
291		c.handler.ServeHTTP(r, httpReq)
292	}
293	r.Close()
294	c.mu.Lock()
295	delete(c.requests, req.reqId)
296	c.mu.Unlock()
297	c.conn.writeEndRequest(req.reqId, 0, statusRequestComplete)
298
299	// Consume the entire body, so the host isn't still writing to
300	// us when we close the socket below in the !keepConn case,
301	// otherwise we'd send a RST. (golang.org/issue/4183)
302	// TODO(bradfitz): also bound this copy in time. Or send
303	// some sort of abort request to the host, so the host
304	// can properly cut off the client sending all the data.
305	// For now just bound it a little and
306	io.CopyN(ioutil.Discard, body, 100<<20)
307	body.Close()
308
309	if !req.keepConn {
310		c.conn.Close()
311	}
312}
313
314func (c *child) cleanUp() {
315	c.mu.Lock()
316	defer c.mu.Unlock()
317	for _, req := range c.requests {
318		if req.pw != nil {
319			// race with call to Close in c.serveRequest doesn't matter because
320			// Pipe(Reader|Writer).Close are idempotent
321			req.pw.CloseWithError(ErrConnClosed)
322		}
323	}
324}
325
326// Serve accepts incoming FastCGI connections on the listener l, creating a new
327// goroutine for each. The goroutine reads requests and then calls handler
328// to reply to them.
329// If l is nil, Serve accepts connections from os.Stdin.
330// If handler is nil, http.DefaultServeMux is used.
331func Serve(l net.Listener, handler http.Handler) error {
332	if l == nil {
333		var err error
334		l, err = net.FileListener(os.Stdin)
335		if err != nil {
336			return err
337		}
338		defer l.Close()
339	}
340	if handler == nil {
341		handler = http.DefaultServeMux
342	}
343	for {
344		rw, err := l.Accept()
345		if err != nil {
346			return err
347		}
348		c := newChild(rw, handler)
349		go c.serve()
350	}
351}
352
353// ProcessEnv returns FastCGI environment variables associated with the request r
354// for which no effort was made to be included in the request itself - the data
355// is hidden in the request's context. As an example, if REMOTE_USER is set for a
356// request, it will not be found anywhere in r, but it will be included in
357// ProcessEnv's response (via r's context).
358func ProcessEnv(r *http.Request) map[string]string {
359	env, _ := r.Context().Value(envVarsContextKey{}).(map[string]string)
360	return env
361}
362
363// addFastCGIEnvToContext reports whether to include the FastCGI environment variable s
364// in the http.Request.Context, accessible via ProcessEnv.
365func addFastCGIEnvToContext(s string) bool {
366	// Exclude things supported by net/http natively:
367	switch s {
368	case "CONTENT_LENGTH", "CONTENT_TYPE", "HTTPS",
369		"PATH_INFO", "QUERY_STRING", "REMOTE_ADDR",
370		"REMOTE_HOST", "REMOTE_PORT", "REQUEST_METHOD",
371		"REQUEST_URI", "SCRIPT_NAME", "SERVER_PROTOCOL":
372		return false
373	}
374	if strings.HasPrefix(s, "HTTP_") {
375		return false
376	}
377	// Explicitly include FastCGI-specific things.
378	// This list is redundant with the default "return true" below.
379	// Consider this documentation of the sorts of things we expect
380	// to maybe see.
381	switch s {
382	case "REMOTE_USER":
383		return true
384	}
385	// Unknown, so include it to be safe.
386	return true
387}
388