cgi

package module
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2020 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

A lot of this code is copied straight from the Go standard library: https://golang.org/src/net/http/cgi/host.go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	Path string
	Root string

	Name string // value to use for SERVER_SOFTWARE env var
	Port string

	Dir string

	InheritEnv []string
	Logger     *log.Logger
	Args       []string
	Stderr     io.Writer

	// Header contains header values that should be used by default.
	// If the client CGI process writes a header to its stdout thats already in Header, it will be replaced.
	Header http.Header
	// OutputHandler takes care of responding the HTTP client based on the CGI client processes output.
	OutputHandler OutputHandler
}

Handler runs an executable in a subprocess with an almost CGI environment. Currently ignored headers: "Location"

func (*Handler) ServeHTTP

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type OutputHandler added in v0.6.0

type OutputHandler func(http.ResponseWriter, *http.Request, *Handler, io.Reader)

OutputHandler should handle reading in the client CGI process' output from stdoutRead and write out the response to the HTTP client. By the time OutputHandler is called, the client CGI process will have already been started and will be killed right after OutputHandler returns.

The client CGI process does not need to provide any headers, Handler will provide default Header values. If the executable does provide header values, they will overwrite the default values in Header. Currently ignored headers: "Location"

var DefaultOutputHandler OutputHandler = func(w http.ResponseWriter, r *http.Request,
	h *Handler, stdoutRead io.Reader) {
	linebody := bufio.NewReaderSize(stdoutRead, 1024)
	headers := make(http.Header)
	statusCode := 0
	headerLines := 0
	sawBlankLine := false
	for {
		line, isPrefix, err := linebody.ReadLine()
		if isPrefix {
			w.WriteHeader(http.StatusInternalServerError)
			h.logErr("cgi: long header line from subprocess.")
			return
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			h.logErr("cgi: error reading headers: %v", err)
			return
		}
		if len(line) == 0 {
			sawBlankLine = true
			break
		}
		headerLines++
		parts := strings.SplitN(string(line), ":", 2)
		if len(parts) < 2 {
			h.logErr("cgi: bogus header line: %s", string(line))
			continue
		}
		header, val := parts[0], parts[1]
		header = strings.TrimSpace(header)
		val = strings.TrimSpace(val)
		switch {
		case header == "Status":
			if len(val) < 3 {
				h.logErr("cgi: bogus status (short): %q", val)
				return
			}
			code, err := strconv.Atoi(val[0:3])
			if err != nil {
				h.logErr("cgi: bogus status: %q", val)
				h.logErr("cgi: line was %q", line)
				return
			}
			statusCode = code
		default:
			headers.Add(header, val)
		}
	}
	if headerLines == 0 || !sawBlankLine {
		w.WriteHeader(http.StatusInternalServerError)
		h.logErr("cgi: no headers")
		return
	}

	if loc := headers.Get("Location"); loc != "" {
		if statusCode == 0 {
			statusCode = http.StatusFound
		}
	}

	if statusCode == 0 && headers.Get("Content-Type") == "" {
		w.WriteHeader(http.StatusInternalServerError)
		h.logErr("cgi: missing required Content-Type in headers")
		return
	}

	if statusCode == 0 {
		statusCode = http.StatusOK
	}

	for k, vv := range headers {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}

	w.WriteHeader(statusCode)

	_, err := io.Copy(w, linebody)
	if err != nil {
		h.logErr("cgi: copy error: %v", err)
	}
}

DefaultOutputHandler *mostly* mimics the behavior of the net/http/cgi package in the Go standard library. The only difference is DefaultOutputHandler does not call on the PathLocationHandler function found in the standard library. Currently ignored headers: "Location"

var EZOutputHandler OutputHandler = func(w http.ResponseWriter, r *http.Request, h *Handler, stdoutRead io.Reader) {
	internalError := func(err error) {
		w.WriteHeader(http.StatusInternalServerError)
		h.logErr("CGI error: %v", err)
	}

	// readBytes holds the bytes read during header scan but that aren't part of the header.
	// This data will be added to the front of the responses body
	var readBytes []byte
	linebody := bufio.NewReaderSize(stdoutRead, 1024)
	statusCode := 0

	for {
		line, tooBig, err := linebody.ReadLine()
		if tooBig || err == io.EOF {
			break
		}
		if err != nil {
			internalError(err)
			return
		}
		if len(line) == 0 {
			break
		}

		parts := strings.SplitN(string(line), ":", 2)
		if len(parts) < 2 {

			readBytes = []byte(line)
			break
		}

		k := strings.TrimSpace(parts[0])
		v := strings.TrimSpace(parts[1])

		switch {
		case k == "Status":
			if len(v) < 3 {
				h.logErr("cgi: bogus status (short): %q", v)
				return
			}
			code, err := strconv.Atoi(v[0:3])
			if err != nil {
				h.logErr("cgi: bogus status: %q", v)
				h.logErr("cgi: line was %q", line)
				return
			}
			statusCode = code
		default:
			h.Header.Set(k, v)
		}
	}

	if statusCode == 0 {
		statusCode = http.StatusOK
	}

	for k, vv := range h.Header {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}
	w.WriteHeader(statusCode)

	if readBytes != nil {
		_, err := w.Write(readBytes)
		if err != nil {
			h.logErr("cgi: copy error: %v", err)
			return
		}
	}

	_, err := io.Copy(w, linebody)
	if err != nil {
		h.logErr("cgi: copy error: %v", err)
		return
	}
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL