package fastcgi import ( "bytes" "encoding/binary" "errors" "io" "net/http" "net/url" "strconv" ) // -- TYPES -- // These are the types needed to house the data for an FCGI request type Header struct { Version uint8 Type FCGIRequestType Id uint16 ContentLength uint16 PaddingLength uint8 Reserved uint8 } func (h *Header) init(reqType FCGIRequestType, reqId uint16, l int) { h.Version = 1 h.Type = reqType h.Id = reqId h.ContentLength = uint16(l) h.PaddingLength = uint8(-l & 7) } // A record is essentially a "packet" in FastCGI. // The header lets the server know what type // of data is being sent, and it expects // a certain structure depending on // the type. For example, the // FCGI_BEGIN_REQUEST record should // have a body of 8 bytes with: // - The first byte being the role // - The second byte being also the role // - The third byte being the flags // - The last five bytes are reserved for future use type Record struct { Header Header Content []byte Buffer bytes.Buffer // Buffer to use when writing to the network ReadBuffer []byte // Buffer to use when reading a response } // Turn a record into a byte array so it can be // sent over the network socket func (r *Record) toBytes() ([]byte, error) { // client.h.init(recType, client.reqId, len(content)) if err := binary.Write(&r.Buffer, binary.BigEndian, r.Header); err != nil { return nil, err } if _, err := r.Buffer.Write(r.Content); err != nil { return nil, err } if _, err := r.Buffer.Write(pad[:r.Header.PaddingLength]); err != nil { return nil, err } return r.Buffer.Bytes(), nil } func (rec *Record) read(r io.Reader) (buf []byte, err error) { if err = binary.Read(r, binary.BigEndian, &rec.Header); err != nil { return } if rec.Header.Version != 1 { err = errors.New("fcgi: invalid header version") return } if rec.Header.Type == FCGI_END_REQUEST { err = io.EOF return } n := int(rec.Header.ContentLength) + int(rec.Header.PaddingLength) if len(rec.ReadBuffer) < n { rec.ReadBuffer = make([]byte, n) } if n, err = io.ReadFull(r, rec.ReadBuffer[:n]); err != nil { return } buf = rec.ReadBuffer[:int(rec.Header.ContentLength)] return } // 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 type FCGIRequest struct { Id uint16 Type FCGIRequestType Context map[string]string Records []Record } func RequestFromHttp(r *http.Request) *FCGIRequest { c := FCGIRequest{} c.Context = make(map[string]string) c.Context["SERVER_SOFTWARE"] = "oasis / fastcgi" c.Context["QUERY_STRING"] = r.URL.RawQuery c.Context["REMOTE_ADDR"] = "127.0.0.1" return &c } func NewBeginRequestRecord() *Record { role := uint16(FCGI_RESPONDER) flags := byte(0) // Create an 8-byte array as per the FastCGI specification. var b [8]byte // Split the 16-bit role into two bytes and assign them. b[0] = byte(role >> 8) // High byte b[1] = byte(role) // Low byte // Set the flags. b[2] = flags // The reserved bytes (b[3] to b[7]) will remain zero by default. // Return a begin request record h := Header{} h.init(FCGI_BEGIN_REQUEST, 1, len(b)) return &Record{ Header: h, Content: b[:], } } func (req *FCGIRequest) Script(path string) { req.Context["SCRIPT_FILENAME"] = path } func (req *FCGIRequest) Method(m string) { req.Context["REQUEST_METHOD"] = m } // Get issues a GET request to the fcgi responder. func (r *FCGIRequest) TypeGet() { r.Context["REQUEST_METHOD"] = "GET" r.Context["CONTENT_LENGTH"] = "0" } // Get issues a Post request to the fcgi responder. with request body // in the format that bodyType specified func (r *FCGIRequest) TypePost(bodyType string, body io.Reader, l int) { if len(r.Context["REQUEST_METHOD"]) == 0 || r.Context["REQUEST_METHOD"] == "GET" { r.Context["REQUEST_METHOD"] = "POST" } r.Context["CONTENT_LENGTH"] = strconv.Itoa(l) if len(bodyType) > 0 { r.Context["CONTENT_TYPE"] = bodyType } else { r.Context["CONTENT_TYPE"] = "application/x-www-form-urlencoded" } } // PostForm issues a POST to the fcgi responder, with form // as a string key to a list values (url.Values) func (r *FCGIRequest) TypePostForm(data url.Values) { body := bytes.NewReader([]byte(data.Encode())) r.TypePost("application/x-www-form-urlencoded", body, body.Len()) } // PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, // with form as a string key to a list values (url.Values), // and/or with file as a string key to a list file path. // func (r *FCGIRequest) PostFile(p map[string]string, data url.Values, file map[string]string) { // buf := &bytes.Buffer{} // writer := multipart.NewWriter(buf) // bodyType := writer.FormDataContentType() // // for key, val := range data { // for _, v0 := range val { // err = writer.WriteField(key, v0) // if err != nil { // return // } // } // } // // for key, val := range file { // fd, e := os.Open(val) // if e != nil { // return nil, e // } // defer fd.Close() // // part, e := writer.CreateFormFile(key, filepath.Base(val)) // if e != nil { // return nil, e // } // _, err = io.Copy(part, fd) // } // // err = writer.Close() // if err != nil { // return // } // // // return client.Post(p, bodyType, buf, buf.Len()) // } // Format the context for sending // func (r *FCGIRequest) writePairs() error { // w := newWriter(client, recType) // b := make([]byte, 8) // nn := 0 // for k, v := range r.Context { // m := 8 + len(k) + len(v) // if m > maxWrite { // // param data size exceed 65535 bytes" // vl := maxWrite - 8 - len(k) // v = v[:vl] // } // n := encodeSize(b, uint32(len(k))) // n += encodeSize(b[n:], uint32(len(v))) // m = n + len(k) + len(v) // if (nn + m) > maxWrite { // w.Flush() // nn = 0 // } // nn += m // 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 // }