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