| // 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 ( | |
| "bufio" | |
| "bytes" | |
| "encoding/binary" | |
| "errors" | |
| "io" | |
| "sync" | |
| ) | |
| // 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 (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 | |
| closeErr error | |
| closed bool | |
| // to avoid allocations | |
| buf bytes.Buffer | |
| h header | |
| } | |
| func newConn(rwc io.ReadWriteCloser) *conn { | |
| return &conn{rwc: rwc} | |
| } | |
| // Close closes the conn if it is not already closed. | |
| func (c *conn) Close() error { | |
| c.mutex.Lock() | |
| defer c.mutex.Unlock() | |
| if !c.closed { | |
| c.closeErr = c.rwc.Close() | |
| c.closed = true | |
| } | |
| return c.closeErr | |
| } | |
| 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 | |
| } | |
| } | |
| return w.Close() | |
| } | |
| 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) | |
| } |