// 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. // // See https://fast-cgi.github.io/ for an unofficial mirror of the // original documentation. // // 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 ( ) // recType is a record type, as defined by // https://web.archive.org/web/20150420080736/http://www.fastcgi.com/drupal/node/6?q=node/22#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 ( *beginRequest) ( []byte) error { if len() != 8 { return errors.New("fcgi: invalid begin request record") } .role = binary.BigEndian.Uint16() .flags = [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 ( *header) ( recType, uint16, int) { .Version = 1 .Type = .Id = .ContentLength = uint16() .PaddingLength = uint8(- & 7) } // conn sends records over rwc type conn struct { mutex sync.Mutex rwc io.ReadWriteCloser closeErr error closed bool // to avoid allocations buf bytes.Buffer h header } func newConn( io.ReadWriteCloser) *conn { return &conn{rwc: } } // Close closes the conn if it is not already closed. func ( *conn) () error { .mutex.Lock() defer .mutex.Unlock() if !.closed { .closeErr = .rwc.Close() .closed = true } return .closeErr } type record struct { h header buf [maxWrite + maxPad]byte } func ( *record) ( io.Reader) ( error) { if = binary.Read(, binary.BigEndian, &.h); != nil { return } if .h.Version != 1 { return errors.New("fcgi: invalid header version") } := int(.h.ContentLength) + int(.h.PaddingLength) if _, = io.ReadFull(, .buf[:]); != nil { return } return nil } func ( *record) () []byte { return .buf[:.h.ContentLength] } // writeRecord writes and sends a single record. func ( *conn) ( recType, uint16, []byte) error { .mutex.Lock() defer .mutex.Unlock() .buf.Reset() .h.init(, , len()) if := binary.Write(&.buf, binary.BigEndian, .h); != nil { return } if , := .buf.Write(); != nil { return } if , := .buf.Write(pad[:.h.PaddingLength]); != nil { return } , := .rwc.Write(.buf.Bytes()) return } func ( *conn) ( uint16, int, uint8) error { := make([]byte, 8) binary.BigEndian.PutUint32(, uint32()) [4] = return .writeRecord(typeEndRequest, , ) } func ( *conn) ( recType, uint16, map[string]string) error { := newWriter(, , ) := make([]byte, 8) for , := range { := encodeSize(, uint32(len())) += encodeSize([:], uint32(len())) if , := .Write([:]); != nil { return } if , := .WriteString(); != nil { return } if , := .WriteString(); != nil { return } } .Close() return nil } func readSize( []byte) (uint32, int) { if len() == 0 { return 0, 0 } , := uint32([0]), 1 if &(1<<7) != 0 { if len() < 4 { return 0, 0 } = 4 = binary.BigEndian.Uint32() &^= 1 << 31 } return , } func readString( []byte, uint32) string { if > uint32(len()) { return "" } return string([:]) } func encodeSize( []byte, uint32) int { if > 127 { |= 1 << 31 binary.BigEndian.PutUint32(, ) return 4 } [0] = byte() return 1 } // bufWriter encapsulates bufio.Writer but also closes the underlying stream when // Closed. type bufWriter struct { closer io.Closer *bufio.Writer } func ( *bufWriter) () error { if := .Writer.Flush(); != nil { .closer.Close() return } return .closer.Close() } func newWriter( *conn, recType, uint16) *bufWriter { := &streamWriter{c: , recType: , reqId: } := bufio.NewWriterSize(, maxWrite) return &bufWriter{, } } // 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 ( *streamWriter) ( []byte) (int, error) { := 0 for len() > 0 { := len() if > maxWrite { = maxWrite } if := .c.writeRecord(.recType, .reqId, [:]); != nil { return , } += = [:] } return , nil } func ( *streamWriter) () error { // send empty record to close the stream return .c.writeRecord(.recType, .reqId, nil) }