1
2
3
4
5
6
7
8 package cgi
9
10 import (
11 "bufio"
12 "crypto/tls"
13 "errors"
14 "fmt"
15 "io"
16 "net"
17 "net/http"
18 "net/url"
19 "os"
20 "strconv"
21 "strings"
22 )
23
24
25
26
27
28 func Request() (*http.Request, error) {
29 r, err := RequestFromMap(envMap(os.Environ()))
30 if err != nil {
31 return nil, err
32 }
33 if r.ContentLength > 0 {
34 r.Body = io.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
35 }
36 return r, nil
37 }
38
39 func envMap(env []string) map[string]string {
40 m := make(map[string]string)
41 for _, kv := range env {
42 if k, v, ok := strings.Cut(kv, "="); ok {
43 m[k] = v
44 }
45 }
46 return m
47 }
48
49
50
51 func RequestFromMap(params map[string]string) (*http.Request, error) {
52 r := new(http.Request)
53 r.Method = params["REQUEST_METHOD"]
54 if r.Method == "" {
55 return nil, errors.New("cgi: no REQUEST_METHOD in environment")
56 }
57
58 r.Proto = params["SERVER_PROTOCOL"]
59 var ok bool
60 if r.Proto == "INCLUDED" {
61
62
63 r.ProtoMajor, r.ProtoMinor = 1, 0
64 } else if r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto); !ok {
65 return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
66 }
67
68 r.Close = true
69 r.Trailer = http.Header{}
70 r.Header = http.Header{}
71
72 r.Host = params["HTTP_HOST"]
73
74 if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
75 clen, err := strconv.ParseInt(lenstr, 10, 64)
76 if err != nil {
77 return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
78 }
79 r.ContentLength = clen
80 }
81
82 if ct := params["CONTENT_TYPE"]; ct != "" {
83 r.Header.Set("Content-Type", ct)
84 }
85
86
87 for k, v := range params {
88 if k == "HTTP_HOST" {
89 continue
90 }
91 if after, found := strings.CutPrefix(k, "HTTP_"); found {
92 r.Header.Add(strings.ReplaceAll(after, "_", "-"), v)
93 }
94 }
95
96 uriStr := params["REQUEST_URI"]
97 if uriStr == "" {
98
99 uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
100 s := params["QUERY_STRING"]
101 if s != "" {
102 uriStr += "?" + s
103 }
104 }
105
106
107
108 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
109 r.TLS = &tls.ConnectionState{HandshakeComplete: true}
110 }
111
112 if r.Host != "" {
113
114 rawurl := r.Host + uriStr
115 if r.TLS == nil {
116 rawurl = "http://" + rawurl
117 } else {
118 rawurl = "https://" + rawurl
119 }
120 url, err := url.Parse(rawurl)
121 if err != nil {
122 return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
123 }
124 r.URL = url
125 }
126
127
128 if r.URL == nil {
129 url, err := url.Parse(uriStr)
130 if err != nil {
131 return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
132 }
133 r.URL = url
134 }
135
136
137
138 remotePort, _ := strconv.Atoi(params["REMOTE_PORT"])
139 r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort))
140
141 return r, nil
142 }
143
144
145
146
147
148 func Serve(handler http.Handler) error {
149 req, err := Request()
150 if err != nil {
151 return err
152 }
153 if req.Body == nil {
154 req.Body = http.NoBody
155 }
156 if handler == nil {
157 handler = http.DefaultServeMux
158 }
159 rw := &response{
160 req: req,
161 header: make(http.Header),
162 bufw: bufio.NewWriter(os.Stdout),
163 }
164 handler.ServeHTTP(rw, req)
165 rw.Write(nil)
166 if err = rw.bufw.Flush(); err != nil {
167 return err
168 }
169 return nil
170 }
171
172 type response struct {
173 req *http.Request
174 header http.Header
175 code int
176 wroteHeader bool
177 wroteCGIHeader bool
178 bufw *bufio.Writer
179 }
180
181 func (r *response) Flush() {
182 r.bufw.Flush()
183 }
184
185 func (r *response) Header() http.Header {
186 return r.header
187 }
188
189 func (r *response) Write(p []byte) (n int, err error) {
190 if !r.wroteHeader {
191 r.WriteHeader(http.StatusOK)
192 }
193 if !r.wroteCGIHeader {
194 r.writeCGIHeader(p)
195 }
196 return r.bufw.Write(p)
197 }
198
199 func (r *response) WriteHeader(code int) {
200 if r.wroteHeader {
201
202 fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
203 return
204 }
205 r.wroteHeader = true
206 r.code = code
207 }
208
209
210
211
212
213 func (r *response) writeCGIHeader(p []byte) {
214 if r.wroteCGIHeader {
215 return
216 }
217 r.wroteCGIHeader = true
218 fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
219 if _, hasType := r.header["Content-Type"]; !hasType {
220 r.header.Set("Content-Type", http.DetectContentType(p))
221 }
222 r.header.Write(r.bufw)
223 r.bufw.WriteString("\r\n")
224 r.bufw.Flush()
225 }
226
View as plain text