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 implements the FastCGI protocol.
6//
7// See https://fast-cgi.github.io/ for an unofficial mirror of the
8// original documentation.
9//
10// Currently only the responder role is supported.
11package fcgi
12
13// This file defines the raw protocol and some utilities used by the child and
14// the host.
15
16import (
17	"bufio"
18	"bytes"
19	"encoding/binary"
20	"errors"
21	"io"
22	"sync"
23)
24
25// recType is a record type, as defined by
26// https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22#S8
27type recType uint8
28
29const (
30	typeBeginRequest    recType = 1
31	typeAbortRequest    recType = 2
32	typeEndRequest      recType = 3
33	typeParams          recType = 4
34	typeStdin           recType = 5
35	typeStdout          recType = 6
36	typeStderr          recType = 7
37	typeData            recType = 8
38	typeGetValues       recType = 9
39	typeGetValuesResult recType = 10
40	typeUnknownType     recType = 11
41)
42
43// keep the connection between web-server and responder open after request
44const flagKeepConn = 1
45
46const (
47	maxWrite = 65535 // maximum record body
48	maxPad   = 255
49)
50
51const (
52	roleResponder = iota + 1 // only Responders are implemented.
53	roleAuthorizer
54	roleFilter
55)
56
57const (
58	statusRequestComplete = iota
59	statusCantMultiplex
60	statusOverloaded
61	statusUnknownRole
62)
63
64type header struct {
65	Version       uint8
66	Type          recType
67	Id            uint16
68	ContentLength uint16
69	PaddingLength uint8
70	Reserved      uint8
71}
72
73type beginRequest struct {
74	role     uint16
75	flags    uint8
76	reserved [5]uint8
77}
78
79func (br *beginRequest) read(content []byte) error {
80	if len(content) != 8 {
81		return errors.New("fcgi: invalid begin request record")
82	}
83	br.role = binary.BigEndian.Uint16(content)
84	br.flags = content[2]
85	return nil
86}
87
88// for padding so we don't have to allocate all the time
89// not synchronized because we don't care what the contents are
90var pad [maxPad]byte
91
92func (h *header) init(recType recType, reqId uint16, contentLength int) {
93	h.Version = 1
94	h.Type = recType
95	h.Id = reqId
96	h.ContentLength = uint16(contentLength)
97	h.PaddingLength = uint8(-contentLength & 7)
98}
99
100// conn sends records over rwc
101type conn struct {
102	mutex sync.Mutex
103	rwc   io.ReadWriteCloser
104
105	// to avoid allocations
106	buf bytes.Buffer
107	h   header
108}
109
110func newConn(rwc io.ReadWriteCloser) *conn {
111	return &conn{rwc: rwc}
112}
113
114func (c *conn) Close() error {
115	c.mutex.Lock()
116	defer c.mutex.Unlock()
117	return c.rwc.Close()
118}
119
120type record struct {
121	h   header
122	buf [maxWrite + maxPad]byte
123}
124
125func (rec *record) read(r io.Reader) (err error) {
126	if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil {
127		return err
128	}
129	if rec.h.Version != 1 {
130		return errors.New("fcgi: invalid header version")
131	}
132	n := int(rec.h.ContentLength) + int(rec.h.PaddingLength)
133	if _, err = io.ReadFull(r, rec.buf[:n]); err != nil {
134		return err
135	}
136	return nil
137}
138
139func (r *record) content() []byte {
140	return r.buf[:r.h.ContentLength]
141}
142
143// writeRecord writes and sends a single record.
144func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error {
145	c.mutex.Lock()
146	defer c.mutex.Unlock()
147	c.buf.Reset()
148	c.h.init(recType, reqId, len(b))
149	if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil {
150		return err
151	}
152	if _, err := c.buf.Write(b); err != nil {
153		return err
154	}
155	if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil {
156		return err
157	}
158	_, err := c.rwc.Write(c.buf.Bytes())
159	return err
160}
161
162func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error {
163	b := make([]byte, 8)
164	binary.BigEndian.PutUint32(b, uint32(appStatus))
165	b[4] = protocolStatus
166	return c.writeRecord(typeEndRequest, reqId, b)
167}
168
169func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error {
170	w := newWriter(c, recType, reqId)
171	b := make([]byte, 8)
172	for k, v := range pairs {
173		n := encodeSize(b, uint32(len(k)))
174		n += encodeSize(b[n:], uint32(len(v)))
175		if _, err := w.Write(b[:n]); err != nil {
176			return err
177		}
178		if _, err := w.WriteString(k); err != nil {
179			return err
180		}
181		if _, err := w.WriteString(v); err != nil {
182			return err
183		}
184	}
185	w.Close()
186	return nil
187}
188
189func readSize(s []byte) (uint32, int) {
190	if len(s) == 0 {
191		return 0, 0
192	}
193	size, n := uint32(s[0]), 1
194	if size&(1<<7) != 0 {
195		if len(s) < 4 {
196			return 0, 0
197		}
198		n = 4
199		size = binary.BigEndian.Uint32(s)
200		size &^= 1 << 31
201	}
202	return size, n
203}
204
205func readString(s []byte, size uint32) string {
206	if size > uint32(len(s)) {
207		return ""
208	}
209	return string(s[:size])
210}
211
212func encodeSize(b []byte, size uint32) int {
213	if size > 127 {
214		size |= 1 << 31
215		binary.BigEndian.PutUint32(b, size)
216		return 4
217	}
218	b[0] = byte(size)
219	return 1
220}
221
222// bufWriter encapsulates bufio.Writer but also closes the underlying stream when
223// Closed.
224type bufWriter struct {
225	closer io.Closer
226	*bufio.Writer
227}
228
229func (w *bufWriter) Close() error {
230	if err := w.Writer.Flush(); err != nil {
231		w.closer.Close()
232		return err
233	}
234	return w.closer.Close()
235}
236
237func newWriter(c *conn, recType recType, reqId uint16) *bufWriter {
238	s := &streamWriter{c: c, recType: recType, reqId: reqId}
239	w := bufio.NewWriterSize(s, maxWrite)
240	return &bufWriter{s, w}
241}
242
243// streamWriter abstracts out the separation of a stream into discrete records.
244// It only writes maxWrite bytes at a time.
245type streamWriter struct {
246	c       *conn
247	recType recType
248	reqId   uint16
249}
250
251func (w *streamWriter) Write(p []byte) (int, error) {
252	nn := 0
253	for len(p) > 0 {
254		n := len(p)
255		if n > maxWrite {
256			n = maxWrite
257		}
258		if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil {
259			return nn, err
260		}
261		nn += n
262		p = p[n:]
263	}
264	return nn, nil
265}
266
267func (w *streamWriter) Close() error {
268	// send empty record to close the stream
269	return w.c.writeRecord(w.recType, w.reqId, nil)
270}
271