Skip to content

Commit 053469c

Browse files
committed
[release-branch.go1.14] all: merge release-branch.go1.14-security into release-branch.go1.14
Change-Id: I52c14764e354cb9b11be6019cf8fb44930786ab8
2 parents 9017642 + c187a3d commit 053469c

File tree

6 files changed

+228
-23
lines changed

6 files changed

+228
-23
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
go1.14.7
1+
go1.14.8

src/net/http/cgi/child.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,12 @@ func Serve(handler http.Handler) error {
165165
}
166166

167167
type response struct {
168-
req *http.Request
169-
header http.Header
170-
bufw *bufio.Writer
171-
headerSent bool
168+
req *http.Request
169+
header http.Header
170+
code int
171+
wroteHeader bool
172+
wroteCGIHeader bool
173+
bufw *bufio.Writer
172174
}
173175

174176
func (r *response) Flush() {
@@ -180,26 +182,38 @@ func (r *response) Header() http.Header {
180182
}
181183

182184
func (r *response) Write(p []byte) (n int, err error) {
183-
if !r.headerSent {
185+
if !r.wroteHeader {
184186
r.WriteHeader(http.StatusOK)
185187
}
188+
if !r.wroteCGIHeader {
189+
r.writeCGIHeader(p)
190+
}
186191
return r.bufw.Write(p)
187192
}
188193

189194
func (r *response) WriteHeader(code int) {
190-
if r.headerSent {
195+
if r.wroteHeader {
191196
// Note: explicitly using Stderr, as Stdout is our HTTP output.
192197
fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
193198
return
194199
}
195-
r.headerSent = true
196-
fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
200+
r.wroteHeader = true
201+
r.code = code
202+
}
197203

198-
// Set a default Content-Type
204+
// writeCGIHeader finalizes the header sent to the client and writes it to the output.
205+
// p is not written by writeHeader, but is the first chunk of the body
206+
// that will be written. It is sniffed for a Content-Type if none is
207+
// set explicitly.
208+
func (r *response) writeCGIHeader(p []byte) {
209+
if r.wroteCGIHeader {
210+
return
211+
}
212+
r.wroteCGIHeader = true
213+
fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
199214
if _, hasType := r.header["Content-Type"]; !hasType {
200-
r.header.Add("Content-Type", "text/html; charset=utf-8")
215+
r.header.Set("Content-Type", http.DetectContentType(p))
201216
}
202-
203217
r.header.Write(r.bufw)
204218
r.bufw.WriteString("\r\n")
205219
r.bufw.Flush()

src/net/http/cgi/child_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
package cgi
88

99
import (
10+
"bufio"
11+
"bytes"
12+
"net/http"
13+
"net/http/httptest"
14+
"strings"
1015
"testing"
1116
)
1217

@@ -148,3 +153,67 @@ func TestRequestWithoutRemotePort(t *testing.T) {
148153
t.Errorf("RemoteAddr: got %q; want %q", g, e)
149154
}
150155
}
156+
157+
type countingWriter int
158+
159+
func (c *countingWriter) Write(p []byte) (int, error) {
160+
*c += countingWriter(len(p))
161+
return len(p), nil
162+
}
163+
func (c *countingWriter) WriteString(p string) (int, error) {
164+
*c += countingWriter(len(p))
165+
return len(p), nil
166+
}
167+
168+
func TestResponse(t *testing.T) {
169+
var tests = []struct {
170+
name string
171+
body string
172+
wantCT string
173+
}{
174+
{
175+
name: "no body",
176+
wantCT: "text/plain; charset=utf-8",
177+
},
178+
{
179+
name: "html",
180+
body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
181+
wantCT: "text/html; charset=utf-8",
182+
},
183+
{
184+
name: "text",
185+
body: strings.Repeat("gopher", 86),
186+
wantCT: "text/plain; charset=utf-8",
187+
},
188+
{
189+
name: "jpg",
190+
body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
191+
wantCT: "image/jpeg",
192+
},
193+
}
194+
for _, tt := range tests {
195+
t.Run(tt.name, func(t *testing.T) {
196+
var buf bytes.Buffer
197+
resp := response{
198+
req: httptest.NewRequest("GET", "/", nil),
199+
header: http.Header{},
200+
bufw: bufio.NewWriter(&buf),
201+
}
202+
n, err := resp.Write([]byte(tt.body))
203+
if err != nil {
204+
t.Errorf("Write: unexpected %v", err)
205+
}
206+
if want := len(tt.body); n != want {
207+
t.Errorf("reported short Write: got %v want %v", n, want)
208+
}
209+
resp.writeCGIHeader(nil)
210+
resp.Flush()
211+
if got := resp.Header().Get("Content-Type"); got != tt.wantCT {
212+
t.Errorf("wrong content-type: got %q, want %q", got, tt.wantCT)
213+
}
214+
if !bytes.HasSuffix(buf.Bytes(), []byte(tt.body)) {
215+
t.Errorf("body was not correctly written")
216+
}
217+
})
218+
}
219+
}

src/net/http/cgi/integration_test.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"io"
1717
"net/http"
1818
"net/http/httptest"
19+
"net/url"
1920
"os"
21+
"strings"
2022
"testing"
2123
"time"
2224
)
@@ -52,7 +54,7 @@ func TestHostingOurselves(t *testing.T) {
5254
}
5355
replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
5456

55-
if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
57+
if expected, got := "text/plain; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
5658
t.Errorf("got a Content-Type of %q; expected %q", got, expected)
5759
}
5860
if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
@@ -152,6 +154,51 @@ func TestChildOnlyHeaders(t *testing.T) {
152154
}
153155
}
154156

157+
func TestChildContentType(t *testing.T) {
158+
testenv.MustHaveExec(t)
159+
160+
h := &Handler{
161+
Path: os.Args[0],
162+
Root: "/test.go",
163+
Args: []string{"-test.run=TestBeChildCGIProcess"},
164+
}
165+
var tests = []struct {
166+
name string
167+
body string
168+
wantCT string
169+
}{
170+
{
171+
name: "no body",
172+
wantCT: "text/plain; charset=utf-8",
173+
},
174+
{
175+
name: "html",
176+
body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
177+
wantCT: "text/html; charset=utf-8",
178+
},
179+
{
180+
name: "text",
181+
body: strings.Repeat("gopher", 86),
182+
wantCT: "text/plain; charset=utf-8",
183+
},
184+
{
185+
name: "jpg",
186+
body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
187+
wantCT: "image/jpeg",
188+
},
189+
}
190+
for _, tt := range tests {
191+
t.Run(tt.name, func(t *testing.T) {
192+
expectedMap := map[string]string{"_body": tt.body}
193+
req := fmt.Sprintf("GET /test.go?exact-body=%s HTTP/1.0\nHost: example.com\n\n", url.QueryEscape(tt.body))
194+
replay := runCgiTest(t, h, req, expectedMap)
195+
if got := replay.Header().Get("Content-Type"); got != tt.wantCT {
196+
t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT)
197+
}
198+
})
199+
}
200+
}
201+
155202
// golang.org/issue/7198
156203
func Test500WithNoHeaders(t *testing.T) { want500Test(t, "/immediate-disconnect") }
157204
func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") }
@@ -203,6 +250,10 @@ func TestBeChildCGIProcess(t *testing.T) {
203250
if req.FormValue("no-body") == "1" {
204251
return
205252
}
253+
if eb, ok := req.Form["exact-body"]; ok {
254+
io.WriteString(rw, eb[0])
255+
return
256+
}
206257
if req.FormValue("write-forever") == "1" {
207258
io.Copy(rw, neverEnding('a'))
208259
for {

src/net/http/fcgi/child.go

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,12 @@ func (r *request) parseParams() {
7474

7575
// response implements http.ResponseWriter.
7676
type response struct {
77-
req *request
78-
header http.Header
79-
w *bufWriter
80-
wroteHeader bool
77+
req *request
78+
header http.Header
79+
code int
80+
wroteHeader bool
81+
wroteCGIHeader bool
82+
w *bufWriter
8183
}
8284

8385
func newResponse(c *child, req *request) *response {
@@ -92,34 +94,49 @@ func (r *response) Header() http.Header {
9294
return r.header
9395
}
9496

95-
func (r *response) Write(data []byte) (int, error) {
97+
func (r *response) Write(p []byte) (n int, err error) {
9698
if !r.wroteHeader {
9799
r.WriteHeader(http.StatusOK)
98100
}
99-
return r.w.Write(data)
101+
if !r.wroteCGIHeader {
102+
r.writeCGIHeader(p)
103+
}
104+
return r.w.Write(p)
100105
}
101106

102107
func (r *response) WriteHeader(code int) {
103108
if r.wroteHeader {
104109
return
105110
}
106111
r.wroteHeader = true
112+
r.code = code
107113
if code == http.StatusNotModified {
108114
// Must not have body.
109115
r.header.Del("Content-Type")
110116
r.header.Del("Content-Length")
111117
r.header.Del("Transfer-Encoding")
112-
} else if r.header.Get("Content-Type") == "" {
113-
r.header.Set("Content-Type", "text/html; charset=utf-8")
114118
}
115-
116119
if r.header.Get("Date") == "" {
117120
r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
118121
}
122+
}
119123

120-
fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code))
124+
// writeCGIHeader finalizes the header sent to the client and writes it to the output.
125+
// p is not written by writeHeader, but is the first chunk of the body
126+
// that will be written. It is sniffed for a Content-Type if none is
127+
// set explicitly.
128+
func (r *response) writeCGIHeader(p []byte) {
129+
if r.wroteCGIHeader {
130+
return
131+
}
132+
r.wroteCGIHeader = true
133+
fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
134+
if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType {
135+
r.header.Set("Content-Type", http.DetectContentType(p))
136+
}
121137
r.header.Write(r.w)
122138
r.w.WriteString("\r\n")
139+
r.w.Flush()
123140
}
124141

125142
func (r *response) Flush() {
@@ -290,6 +307,8 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {
290307
httpReq = httpReq.WithContext(envVarCtx)
291308
c.handler.ServeHTTP(r, httpReq)
292309
}
310+
// Make sure we serve something even if nothing was written to r
311+
r.Write(nil)
293312
r.Close()
294313
c.mu.Lock()
295314
delete(c.requests, req.reqId)

src/net/http/fcgi/fcgi_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"io/ioutil"
1212
"net/http"
13+
"strings"
1314
"testing"
1415
)
1516

@@ -344,3 +345,54 @@ func TestChildServeReadsEnvVars(t *testing.T) {
344345
<-done
345346
}
346347
}
348+
349+
func TestResponseWriterSniffsContentType(t *testing.T) {
350+
var tests = []struct {
351+
name string
352+
body string
353+
wantCT string
354+
}{
355+
{
356+
name: "no body",
357+
wantCT: "text/plain; charset=utf-8",
358+
},
359+
{
360+
name: "html",
361+
body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
362+
wantCT: "text/html; charset=utf-8",
363+
},
364+
{
365+
name: "text",
366+
body: strings.Repeat("gopher", 86),
367+
wantCT: "text/plain; charset=utf-8",
368+
},
369+
{
370+
name: "jpg",
371+
body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
372+
wantCT: "image/jpeg",
373+
},
374+
}
375+
for _, tt := range tests {
376+
t.Run(tt.name, func(t *testing.T) {
377+
input := make([]byte, len(streamFullRequestStdin))
378+
copy(input, streamFullRequestStdin)
379+
rc := nopWriteCloser{bytes.NewBuffer(input)}
380+
done := make(chan bool)
381+
var resp *response
382+
c := newChild(rc, http.HandlerFunc(func(
383+
w http.ResponseWriter,
384+
r *http.Request,
385+
) {
386+
io.WriteString(w, tt.body)
387+
resp = w.(*response)
388+
done <- true
389+
}))
390+
defer c.cleanUp()
391+
go c.serve()
392+
<-done
393+
if got := resp.Header().Get("Content-Type"); got != tt.wantCT {
394+
t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT)
395+
}
396+
})
397+
}
398+
}

0 commit comments

Comments
 (0)