1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cgi
6
7import (
8	"fmt"
9	"io"
10	"net/http"
11	"os"
12	"path"
13	"slices"
14	"strings"
15	"time"
16)
17
18func cgiMain() {
19	switch path.Join(os.Getenv("SCRIPT_NAME"), os.Getenv("PATH_INFO")) {
20	case "/bar", "/test.cgi", "/myscript/bar", "/test.cgi/extrapath":
21		testCGI()
22		return
23	}
24	childCGIProcess()
25}
26
27// testCGI is a CGI program translated from a Perl program to complete host_test.
28// test cases in host_test should be provided by testCGI.
29func testCGI() {
30	req, err := Request()
31	if err != nil {
32		panic(err)
33	}
34
35	err = req.ParseForm()
36	if err != nil {
37		panic(err)
38	}
39
40	params := req.Form
41	if params.Get("loc") != "" {
42		fmt.Printf("Location: %s\r\n\r\n", params.Get("loc"))
43		return
44	}
45
46	fmt.Printf("Content-Type: text/html\r\n")
47	fmt.Printf("X-CGI-Pid: %d\r\n", os.Getpid())
48	fmt.Printf("X-Test-Header: X-Test-Value\r\n")
49	fmt.Printf("\r\n")
50
51	if params.Get("writestderr") != "" {
52		fmt.Fprintf(os.Stderr, "Hello, stderr!\n")
53	}
54
55	if params.Get("bigresponse") != "" {
56		// 17 MB, for OS X: golang.org/issue/4958
57		line := strings.Repeat("A", 1024)
58		for i := 0; i < 17*1024; i++ {
59			fmt.Printf("%s\r\n", line)
60		}
61		return
62	}
63
64	fmt.Printf("test=Hello CGI\r\n")
65
66	keys := make([]string, 0, len(params))
67	for k := range params {
68		keys = append(keys, k)
69	}
70	slices.Sort(keys)
71	for _, key := range keys {
72		fmt.Printf("param-%s=%s\r\n", key, params.Get(key))
73	}
74
75	envs := envMap(os.Environ())
76	keys = make([]string, 0, len(envs))
77	for k := range envs {
78		keys = append(keys, k)
79	}
80	slices.Sort(keys)
81	for _, key := range keys {
82		fmt.Printf("env-%s=%s\r\n", key, envs[key])
83	}
84
85	cwd, _ := os.Getwd()
86	fmt.Printf("cwd=%s\r\n", cwd)
87}
88
89type neverEnding byte
90
91func (b neverEnding) Read(p []byte) (n int, err error) {
92	for i := range p {
93		p[i] = byte(b)
94	}
95	return len(p), nil
96}
97
98// childCGIProcess is used by integration_test to complete unit tests.
99func childCGIProcess() {
100	if os.Getenv("REQUEST_METHOD") == "" {
101		// Not in a CGI environment; skipping test.
102		return
103	}
104	switch os.Getenv("REQUEST_URI") {
105	case "/immediate-disconnect":
106		os.Exit(0)
107	case "/no-content-type":
108		fmt.Printf("Content-Length: 6\n\nHello\n")
109		os.Exit(0)
110	case "/empty-headers":
111		fmt.Printf("\nHello")
112		os.Exit(0)
113	}
114	Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
115		if req.FormValue("nil-request-body") == "1" {
116			fmt.Fprintf(rw, "nil-request-body=%v\n", req.Body == nil)
117			return
118		}
119		rw.Header().Set("X-Test-Header", "X-Test-Value")
120		req.ParseForm()
121		if req.FormValue("no-body") == "1" {
122			return
123		}
124		if eb, ok := req.Form["exact-body"]; ok {
125			io.WriteString(rw, eb[0])
126			return
127		}
128		if req.FormValue("write-forever") == "1" {
129			io.Copy(rw, neverEnding('a'))
130			for {
131				time.Sleep(5 * time.Second) // hang forever, until killed
132			}
133		}
134		fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
135		for k, vv := range req.Form {
136			for _, v := range vv {
137				fmt.Fprintf(rw, "param-%s=%s\n", k, v)
138			}
139		}
140		for _, kv := range os.Environ() {
141			fmt.Fprintf(rw, "env-%s\n", kv)
142		}
143	}))
144	os.Exit(0)
145}
146