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 closeErr error 105 closed bool 106 107 // to avoid allocations 108 buf bytes.Buffer 109 h header 110} 111 112func newConn(rwc io.ReadWriteCloser) *conn { 113 return &conn{rwc: rwc} 114} 115 116// Close closes the conn if it is not already closed. 117func (c *conn) Close() error { 118 c.mutex.Lock() 119 defer c.mutex.Unlock() 120 if !c.closed { 121 c.closeErr = c.rwc.Close() 122 c.closed = true 123 } 124 return c.closeErr 125} 126 127type record struct { 128 h header 129 buf [maxWrite + maxPad]byte 130} 131 132func (rec *record) read(r io.Reader) (err error) { 133 if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { 134 return err 135 } 136 if rec.h.Version != 1 { 137 return errors.New("fcgi: invalid header version") 138 } 139 n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) 140 if _, err = io.ReadFull(r, rec.buf[:n]); err != nil { 141 return err 142 } 143 return nil 144} 145 146func (r *record) content() []byte { 147 return r.buf[:r.h.ContentLength] 148} 149 150// writeRecord writes and sends a single record. 151func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error { 152 c.mutex.Lock() 153 defer c.mutex.Unlock() 154 c.buf.Reset() 155 c.h.init(recType, reqId, len(b)) 156 if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil { 157 return err 158 } 159 if _, err := c.buf.Write(b); err != nil { 160 return err 161 } 162 if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil { 163 return err 164 } 165 _, err := c.rwc.Write(c.buf.Bytes()) 166 return err 167} 168 169func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error { 170 b := make([]byte, 8) 171 binary.BigEndian.PutUint32(b, uint32(appStatus)) 172 b[4] = protocolStatus 173 return c.writeRecord(typeEndRequest, reqId, b) 174} 175 176func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error { 177 w := newWriter(c, recType, reqId) 178 b := make([]byte, 8) 179 for k, v := range pairs { 180 n := encodeSize(b, uint32(len(k))) 181 n += encodeSize(b[n:], uint32(len(v))) 182 if _, err := w.Write(b[:n]); err != nil { 183 return err 184 } 185 if _, err := w.WriteString(k); err != nil { 186 return err 187 } 188 if _, err := w.WriteString(v); err != nil { 189 return err 190 } 191 } 192 w.Close() 193 return nil 194} 195 196func readSize(s []byte) (uint32, int) { 197 if len(s) == 0 { 198 return 0, 0 199 } 200 size, n := uint32(s[0]), 1 201 if size&(1<<7) != 0 { 202 if len(s) < 4 { 203 return 0, 0 204 } 205 n = 4 206 size = binary.BigEndian.Uint32(s) 207 size &^= 1 << 31 208 } 209 return size, n 210} 211 212func readString(s []byte, size uint32) string { 213 if size > uint32(len(s)) { 214 return "" 215 } 216 return string(s[:size]) 217} 218 219func encodeSize(b []byte, size uint32) int { 220 if size > 127 { 221 size |= 1 << 31 222 binary.BigEndian.PutUint32(b, size) 223 return 4 224 } 225 b[0] = byte(size) 226 return 1 227} 228 229// bufWriter encapsulates bufio.Writer but also closes the underlying stream when 230// Closed. 231type bufWriter struct { 232 closer io.Closer 233 *bufio.Writer 234} 235 236func (w *bufWriter) Close() error { 237 if err := w.Writer.Flush(); err != nil { 238 w.closer.Close() 239 return err 240 } 241 return w.closer.Close() 242} 243 244func newWriter(c *conn, recType recType, reqId uint16) *bufWriter { 245 s := &streamWriter{c: c, recType: recType, reqId: reqId} 246 w := bufio.NewWriterSize(s, maxWrite) 247 return &bufWriter{s, w} 248} 249 250// streamWriter abstracts out the separation of a stream into discrete records. 251// It only writes maxWrite bytes at a time. 252type streamWriter struct { 253 c *conn 254 recType recType 255 reqId uint16 256} 257 258func (w *streamWriter) Write(p []byte) (int, error) { 259 nn := 0 260 for len(p) > 0 { 261 n := len(p) 262 if n > maxWrite { 263 n = maxWrite 264 } 265 if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil { 266 return nn, err 267 } 268 nn += n 269 p = p[n:] 270 } 271 return nn, nil 272} 273 274func (w *streamWriter) Close() error { 275 // send empty record to close the stream 276 return w.c.writeRecord(w.recType, w.reqId, nil) 277} 278