// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package fcgi implements the FastCGI protocol. // // The protocol is not an official standard and the original // documentation is no longer online. See the Internet Archive's // mirror at: https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22 // // Currently only the responder role is supported. package fcgi // This file defines the raw protocol and some utilities used by the child and // the host. import ( "bufio" "bytes" "encoding/binary" "errors" "io" "sync" ) // recType is a record type, as defined by // http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S8 type recType uint8 const ( typeBeginRequest recType = 1 typeAbortRequest recType = 2 typeEndRequest recType = 3 typeParams recType = 4 typeStdin recType = 5 typeStdout recType = 6 typeStderr recType = 7 typeData recType = 8 typeGetValues recType = 9 typeGetValuesResult recType = 10 typeUnknownType recType = 11 ) // keep the connection between web-server and responder open after request const flagKeepConn = 1 const ( maxWrite = 65535 // maximum record body maxPad = 255 ) const ( roleResponder = iota + 1 // only Responders are implemented. roleAuthorizer roleFilter ) const ( statusRequestComplete = iota statusCantMultiplex statusOverloaded statusUnknownRole ) type header struct { Version uint8 Type recType Id uint16 ContentLength uint16 PaddingLength uint8 Reserved uint8 } type beginRequest struct { role uint16 flags uint8 reserved [5]uint8 } func (br *beginRequest) read(content []byte) error { if len(content) != 8 { return errors.New("fcgi: invalid begin request record") } br.role = binary.BigEndian.Uint16(content) br.flags = content[2] return nil } // for padding so we don't have to allocate all the time // not synchronized because we don't care what the contents are var pad [maxPad]byte func (h *header) init(recType recType, reqId uint16, contentLength int) { h.Version = 1 h.Type = recType h.Id = reqId h.ContentLength = uint16(contentLength) h.PaddingLength = uint8(-contentLength & 7) } // conn sends records over rwc type conn struct { mutex sync.Mutex rwc io.ReadWriteCloser // to avoid allocations buf bytes.Buffer h header } func newConn(rwc io.ReadWriteCloser) *conn { return &conn{rwc: rwc} } func (c *conn) Close() error { c.mutex.Lock() defer c.mutex.Unlock() return c.rwc.Close() } type record struct { h header buf [maxWrite + maxPad]byte } func (rec *record) read(r io.Reader) (err error) { if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { return err } if rec.h.Version != 1 { return errors.New("fcgi: invalid header version") } n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) if _, err = io.ReadFull(r, rec.buf[:n]); err != nil { return err } return nil } func (r *record) content() []byte { return r.buf[:r.h.ContentLength] } // writeRecord writes and sends a single record. func (c *conn) writeRecord(recType recType, reqId uint16, b []byte) error { c.mutex.Lock() defer c.mutex.Unlock() c.buf.Reset() c.h.init(recType, reqId, len(b)) if err := binary.Write(&c.buf, binary.BigEndian, c.h); err != nil { return err } if _, err := c.buf.Write(b); err != nil { return err } if _, err := c.buf.Write(pad[:c.h.PaddingLength]); err != nil { return err } _, err := c.rwc.Write(c.buf.Bytes()) return err } func (c *conn) writeEndRequest(reqId uint16, appStatus int, protocolStatus uint8) error { b := make([]byte, 8) binary.BigEndian.PutUint32(b, uint32(appStatus)) b[4] = protocolStatus return c.writeRecord(typeEndRequest, reqId, b) } func (c *conn) writePairs(recType recType, reqId uint16, pairs map[string]string) error { w := newWriter(c, recType, reqId) b := make([]byte, 8) for k, v := range pairs { n := encodeSize(b, uint32(len(k))) n += encodeSize(b[n:], uint32(len(v))) if _, err := w.Write(b[:n]); err != nil { return err } if _, err := w.WriteString(k); err != nil { return err } if _, err := w.WriteString(v); err != nil { return err } } w.Close() return nil } func readSize(s []byte) (uint32, int) { if len(s) == 0 { return 0, 0 } size, n := uint32(s[0]), 1 if size&(1<<7) != 0 { if len(s) < 4 { return 0, 0 } n = 4 size = binary.BigEndian.Uint32(s) size &^= 1 << 31 } return size, n } func readString(s []byte, size uint32) string { if size > uint32(len(s)) { return "" } return string(s[:size]) } func encodeSize(b []byte, size uint32) int { if size > 127 { size |= 1 << 31 binary.BigEndian.PutUint32(b, size) return 4 } b[0] = byte(size) return 1 } // bufWriter encapsulates bufio.Writer but also closes the underlying stream when // Closed. type bufWriter struct { closer io.Closer *bufio.Writer } func (w *bufWriter) Close() error { if err := w.Writer.Flush(); err != nil { w.closer.Close() return err } return w.closer.Close() } func newWriter(c *conn, recType recType, reqId uint16) *bufWriter { s := &streamWriter{c: c, recType: recType, reqId: reqId} w := bufio.NewWriterSize(s, maxWrite) return &bufWriter{s, w} } // streamWriter abstracts out the separation of a stream into discrete records. // It only writes maxWrite bytes at a time. type streamWriter struct { c *conn recType recType reqId uint16 } func (w *streamWriter) Write(p []byte) (int, error) { nn := 0 for len(p) > 0 { n := len(p) if n > maxWrite { n = maxWrite } if err := w.c.writeRecord(w.recType, w.reqId, p[:n]); err != nil { return nn, err } nn += n p = p[n:] } return nn, nil } func (w *streamWriter) Close() error { // send empty record to close the stream return w.c.writeRecord(w.recType, w.reqId, nil) }