// 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.

// This file implements CGI from the perspective of a child
// process.

package cgi

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"strings"
)

// Request returns the HTTP request as represented in the current
// environment. This assumes the current program is being run
// by a web server in a CGI environment.
// The returned Request's Body is populated, if applicable.
func () (*http.Request, error) {
	,  := RequestFromMap(envMap(os.Environ()))
	if  != nil {
		return nil, 
	}
	if .ContentLength > 0 {
		.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, .ContentLength))
	}
	return , nil
}

func envMap( []string) map[string]string {
	 := make(map[string]string)
	for ,  := range  {
		if  := strings.Index(, "=");  != -1 {
			[[:]] = [+1:]
		}
	}
	return 
}

// RequestFromMap creates an http.Request from CGI variables.
// The returned Request's Body field is not populated.
func ( map[string]string) (*http.Request, error) {
	 := new(http.Request)
	.Method = ["REQUEST_METHOD"]
	if .Method == "" {
		return nil, errors.New("cgi: no REQUEST_METHOD in environment")
	}

	.Proto = ["SERVER_PROTOCOL"]
	var  bool
	.ProtoMajor, .ProtoMinor,  = http.ParseHTTPVersion(.Proto)
	if ! {
		return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
	}

	.Close = true
	.Trailer = http.Header{}
	.Header = http.Header{}

	.Host = ["HTTP_HOST"]

	if  := ["CONTENT_LENGTH"];  != "" {
		,  := strconv.ParseInt(, 10, 64)
		if  != nil {
			return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + )
		}
		.ContentLength = 
	}

	if  := ["CONTENT_TYPE"];  != "" {
		.Header.Set("Content-Type", )
	}

	// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
	for ,  := range  {
		if !strings.HasPrefix(, "HTTP_") ||  == "HTTP_HOST" {
			continue
		}
		.Header.Add(strings.ReplaceAll([5:], "_", "-"), )
	}

	 := ["REQUEST_URI"]
	if  == "" {
		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
		 = ["SCRIPT_NAME"] + ["PATH_INFO"]
		 := ["QUERY_STRING"]
		if  != "" {
			 += "?" + 
		}
	}

	// There's apparently a de-facto standard for this.
	// https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
	if  := ["HTTPS"];  == "on" ||  == "ON" ||  == "1" {
		.TLS = &tls.ConnectionState{HandshakeComplete: true}
	}

	if .Host != "" {
		// Hostname is provided, so we can reasonably construct a URL.
		 := .Host + 
		if .TLS == nil {
			 = "http://" + 
		} else {
			 = "https://" + 
		}
		,  := url.Parse()
		if  != nil {
			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + )
		}
		.URL = 
	}
	// Fallback logic if we don't have a Host header or the URL
	// failed to parse
	if .URL == nil {
		,  := url.Parse()
		if  != nil {
			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + )
		}
		.URL = 
	}

	// Request.RemoteAddr has its port set by Go's standard http
	// server, so we do here too.
	,  := strconv.Atoi(["REMOTE_PORT"]) // zero if unset or invalid
	.RemoteAddr = net.JoinHostPort(["REMOTE_ADDR"], strconv.Itoa())

	return , nil
}

// Serve executes the provided Handler on the currently active CGI
// request, if any. If there's no current CGI environment
// an error is returned. The provided handler may be nil to use
// http.DefaultServeMux.
func ( http.Handler) error {
	,  := Request()
	if  != nil {
		return 
	}
	if  == nil {
		 = http.DefaultServeMux
	}
	 := &response{
		req:    ,
		header: make(http.Header),
		bufw:   bufio.NewWriter(os.Stdout),
	}
	.ServeHTTP(, )
	.Write(nil) // make sure a response is sent
	if  = .bufw.Flush();  != nil {
		return 
	}
	return nil
}

type response struct {
	req            *http.Request
	header         http.Header
	code           int
	wroteHeader    bool
	wroteCGIHeader bool
	bufw           *bufio.Writer
}

func ( *response) () {
	.bufw.Flush()
}

func ( *response) () http.Header {
	return .header
}

func ( *response) ( []byte) ( int,  error) {
	if !.wroteHeader {
		.WriteHeader(http.StatusOK)
	}
	if !.wroteCGIHeader {
		.writeCGIHeader()
	}
	return .bufw.Write()
}

func ( *response) ( int) {
	if .wroteHeader {
		// Note: explicitly using Stderr, as Stdout is our HTTP output.
		fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", .req.URL)
		return
	}
	.wroteHeader = true
	.code = 
}

// writeCGIHeader finalizes the header sent to the client and writes it to the output.
// p is not written by writeHeader, but is the first chunk of the body
// that will be written. It is sniffed for a Content-Type if none is
// set explicitly.
func ( *response) ( []byte) {
	if .wroteCGIHeader {
		return
	}
	.wroteCGIHeader = true
	fmt.Fprintf(.bufw, "Status: %d %s\r\n", .code, http.StatusText(.code))
	if ,  := .header["Content-Type"]; ! {
		.header.Set("Content-Type", http.DetectContentType())
	}
	.header.Write(.bufw)
	.bufw.WriteString("\r\n")
	.bufw.Flush()
}