cgi

package
v2.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2023 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddRequest

func AddRequest(env []string, r *http.Request) []string

func NewEnv

func NewEnv(base, inherit []string) []string

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

func NewHandler

func NewHandler(conf HandlerConfig) (*Handler, error)

func (*Handler) ServeHTTP

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

type HandlerConfig

type HandlerConfig struct {
	Cmd         []string
	WorkingDir  string
	InheritEnvs []string
	BaseEnv     []string
	Header      http.Header

	OutputHandler OutputHandler
	Stderr        io.Writer
}

type OutputHandler

type OutputHandler func(w http.ResponseWriter, r *http.Request, h *Handler, stdoutReader io.Reader)
var DefaultOutputHandler OutputHandler = func(w http.ResponseWriter, r *http.Request,
	h *Handler, stdoutRead io.Reader) {
	var (
		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)
			fmt.Fprint(h.stderr, "cgi: long header line from subprocess.\n")
			return
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			fmt.Fprintf(h.stderr, "cgi: error reading headers: %v\n", err)
			return
		}
		if len(line) == 0 {
			sawBlankLine = true
			break
		}
		headerLines++
		parts := strings.SplitN(string(line), ":", 2)
		if len(parts) < 2 {
			fmt.Fprintf(h.stderr, "cgi: bogus header line: %s\n", 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 {
				fmt.Fprintf(h.stderr, "cgi: bogus status (short): %q\n", val)
				return
			}
			code, err := strconv.Atoi(val[0:3])
			if err != nil {
				fmt.Fprintf(h.stderr, "cgi: bogus status: %q\n", val)
				fmt.Fprintf(h.stderr, "cgi: line was %q\n", line)
				return
			}
			statusCode = code
		default:
			headers.Add(header, val)
		}
	}
	if headerLines == 0 || !sawBlankLine {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(h.stderr, "cgi: no headers\n")
		return
	}

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

	if statusCode == 0 && headers.Get("Content-Type") == "" {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprint(h.stderr, "cgi: missing required Content-Type in headers\n")
		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 {
		fmt.Fprintf(h.stderr, "cgi: copy error: %v\n", err)
	}
}
var EZOutputHandler OutputHandler = func(w http.ResponseWriter, r *http.Request, h *Handler, stdoutRead io.Reader) {
	for k, vv := range h.header {
		for _, v := range vv {
			w.Header().Add(k, v)
		}
	}
	w.WriteHeader(http.StatusOK)

	linebody := bufio.NewReaderSize(stdoutRead, 1024)
	_, err := io.Copy(w, linebody)
	if err != nil {
		fmt.Fprintf(h.stderr, "cgi: copy error: %v", err)
		return
	}
}

EZOutputHandler sends the entire output of the client process without scanning for headers. Always responds with a 200 status code.

var OutputHandlerReplacer OutputHandler = func(w http.ResponseWriter, r *http.Request, h *Handler, stdoutRead io.Reader) {
	internalError := func(err error) {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintf(h.stderr, "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 = append(line, '\n')
			break
		}

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

		switch {
		case k == "Status":
			if len(v) < 3 {
				fmt.Fprintf(h.stderr, "cgi: bogus status (short): %q\n", v)
				return
			}
			code, err := strconv.Atoi(v[0:3])
			if err != nil {
				fmt.Fprintf(h.stderr, "cgi: bogus status: %q\n", v)
				fmt.Fprintf(h.stderr, "cgi: line was %q\n", 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 {
			fmt.Fprintf(h.stderr, "cgi: copy error: %v\n", err)
			return
		}
	}

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

OutputHandlerReplacer scans the output of the client process for headers which replaces the default header values. Stops scanning for headers after encountering the first non-header line. The rest of the output is then sent as the response body.

Jump to

Keyboard shortcuts

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