summaryrefslogtreecommitdiffstats
path: root/meta/recipes-devtools/go/go-1.12
diff options
context:
space:
mode:
Diffstat (limited to 'meta/recipes-devtools/go/go-1.12')
-rw-r--r--meta/recipes-devtools/go/go-1.12/0001-net-http-cgi-rename-a-test-file-to-be-less-cute.patch28
-rw-r--r--meta/recipes-devtools/go/go-1.12/CVE-2020-15586.patch131
-rw-r--r--meta/recipes-devtools/go/go-1.12/CVE-2020-16845.patch110
-rw-r--r--meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch429
4 files changed, 698 insertions, 0 deletions
diff --git a/meta/recipes-devtools/go/go-1.12/0001-net-http-cgi-rename-a-test-file-to-be-less-cute.patch b/meta/recipes-devtools/go/go-1.12/0001-net-http-cgi-rename-a-test-file-to-be-less-cute.patch
new file mode 100644
index 0000000000..7c07961c03
--- /dev/null
+++ b/meta/recipes-devtools/go/go-1.12/0001-net-http-cgi-rename-a-test-file-to-be-less-cute.patch
@@ -0,0 +1,28 @@
1From 8390c478600b852392cb116741b3cb239c94d123 Mon Sep 17 00:00:00 2001
2From: Brad Fitzpatrick <bradfitz@golang.org>
3Date: Wed, 15 Jan 2020 18:08:10 +0000
4Subject: [PATCH] net/http/cgi: rename a test file to be less cute
5
6My fault (from CL 4245070), sorry.
7
8Change-Id: Ib95d3170dc326e74aa74c22421c4e44a8b00f577
9Reviewed-on: https://go-review.googlesource.com/c/go/+/214920
10Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
11TryBot-Result: Gobot Gobot <gobot@golang.org>
12Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
13
14Upstream-Status: Backport
15[lz: Add this patch for merging the patch for CVE-2020-24553]
16Signed-off-by: Li Zhou <li.zhou@windriver.com>
17---
18 src/net/http/cgi/{matryoshka_test.go => integration_test.go} | 0
19 1 file changed, 0 insertions(+), 0 deletions(-)
20 rename src/net/http/cgi/{matryoshka_test.go => integration_test.go} (100%)
21
22diff --git a/src/net/http/cgi/matryoshka_test.go b/src/net/http/cgi/integration_test.go
23similarity index 100%
24rename from src/net/http/cgi/matryoshka_test.go
25rename to src/net/http/cgi/integration_test.go
26--
272.17.1
28
diff --git a/meta/recipes-devtools/go/go-1.12/CVE-2020-15586.patch b/meta/recipes-devtools/go/go-1.12/CVE-2020-15586.patch
new file mode 100644
index 0000000000..ebdc5aec6d
--- /dev/null
+++ b/meta/recipes-devtools/go/go-1.12/CVE-2020-15586.patch
@@ -0,0 +1,131 @@
1From fa98f46741f818913a8c11b877520a548715131f Mon Sep 17 00:00:00 2001
2From: Russ Cox <rsc@golang.org>
3Date: Mon, 13 Jul 2020 13:27:22 -0400
4Subject: [PATCH] net/http: synchronize "100 Continue" write and Handler writes
5
6The expectContinueReader writes to the connection on the first
7Request.Body read. Since a Handler might be doing a read in parallel or
8before a write, expectContinueReader needs to synchronize with the
9ResponseWriter, and abort if a response already went out.
10
11The tests will land in a separate CL.
12
13Fixes #34902
14Fixes CVE-2020-15586
15
16Change-Id: Icdd8dd539f45e8863762bd378194bb4741e875fc
17Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/793350
18Reviewed-by: Filippo Valsorda <valsorda@google.com>
19Reviewed-on: https://go-review.googlesource.com/c/go/+/242598
20Run-TryBot: Katie Hockman <katie@golang.org>
21Reviewed-by: Filippo Valsorda <filippo@golang.org>
22TryBot-Result: Gobot Gobot <gobot@golang.org>
23
24Upstream-Status: Backport
25CVE: CVE-2020-15586
26Signed-off-by: Li Zhou <li.zhou@windriver.com>
27---
28 src/net/http/server.go | 43 +++++++++++++++++++++++++++++++++++-------
29 1 file changed, 36 insertions(+), 7 deletions(-)
30
31diff --git a/src/net/http/server.go b/src/net/http/server.go
32index a995a50658..d41b5f6f48 100644
33--- a/src/net/http/server.go
34+++ b/src/net/http/server.go
35@@ -425,6 +425,16 @@ type response struct {
36 wants10KeepAlive bool // HTTP/1.0 w/ Connection "keep-alive"
37 wantsClose bool // HTTP request has Connection "close"
38
39+ // canWriteContinue is a boolean value accessed as an atomic int32
40+ // that says whether or not a 100 Continue header can be written
41+ // to the connection.
42+ // writeContinueMu must be held while writing the header.
43+ // These two fields together synchronize the body reader
44+ // (the expectContinueReader, which wants to write 100 Continue)
45+ // against the main writer.
46+ canWriteContinue atomicBool
47+ writeContinueMu sync.Mutex
48+
49 w *bufio.Writer // buffers output in chunks to chunkWriter
50 cw chunkWriter
51
52@@ -515,6 +525,7 @@ type atomicBool int32
53
54 func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
55 func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
56+func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
57
58 // declareTrailer is called for each Trailer header when the
59 // response header is written. It notes that a header will need to be
60@@ -878,21 +889,27 @@ type expectContinueReader struct {
61 resp *response
62 readCloser io.ReadCloser
63 closed bool
64- sawEOF bool
65+ sawEOF atomicBool
66 }
67
68 func (ecr *expectContinueReader) Read(p []byte) (n int, err error) {
69 if ecr.closed {
70 return 0, ErrBodyReadAfterClose
71 }
72- if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() {
73- ecr.resp.wroteContinue = true
74- ecr.resp.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
75- ecr.resp.conn.bufw.Flush()
76+ w := ecr.resp
77+ if !w.wroteContinue && w.canWriteContinue.isSet() && !w.conn.hijacked() {
78+ w.wroteContinue = true
79+ w.writeContinueMu.Lock()
80+ if w.canWriteContinue.isSet() {
81+ w.conn.bufw.WriteString("HTTP/1.1 100 Continue\r\n\r\n")
82+ w.conn.bufw.Flush()
83+ w.canWriteContinue.setFalse()
84+ }
85+ w.writeContinueMu.Unlock()
86 }
87 n, err = ecr.readCloser.Read(p)
88 if err == io.EOF {
89- ecr.sawEOF = true
90+ ecr.sawEOF.setTrue()
91 }
92 return
93 }
94@@ -1311,7 +1328,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
95 // because we don't know if the next bytes on the wire will be
96 // the body-following-the-timer or the subsequent request.
97 // See Issue 11549.
98- if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF {
99+ if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF.isSet() {
100 w.closeAfterReply = true
101 }
102
103@@ -1561,6 +1578,17 @@ func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err er
104 }
105 return 0, ErrHijacked
106 }
107+
108+ if w.canWriteContinue.isSet() {
109+ // Body reader wants to write 100 Continue but hasn't yet.
110+ // Tell it not to. The store must be done while holding the lock
111+ // because the lock makes sure that there is not an active write
112+ // this very moment.
113+ w.writeContinueMu.Lock()
114+ w.canWriteContinue.setFalse()
115+ w.writeContinueMu.Unlock()
116+ }
117+
118 if !w.wroteHeader {
119 w.WriteHeader(StatusOK)
120 }
121@@ -1872,6 +1900,7 @@ func (c *conn) serve(ctx context.Context) {
122 if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
123 // Wrap the Body reader with one that replies on the connection
124 req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
125+ w.canWriteContinue.setTrue()
126 }
127 } else if req.Header.get("Expect") != "" {
128 w.sendExpectationFailed()
129--
1302.17.1
131
diff --git a/meta/recipes-devtools/go/go-1.12/CVE-2020-16845.patch b/meta/recipes-devtools/go/go-1.12/CVE-2020-16845.patch
new file mode 100644
index 0000000000..80f467522f
--- /dev/null
+++ b/meta/recipes-devtools/go/go-1.12/CVE-2020-16845.patch
@@ -0,0 +1,110 @@
1From 027d7241ce050d197e7fabea3d541ffbe3487258 Mon Sep 17 00:00:00 2001
2From: Katie Hockman <katie@golang.org>
3Date: Tue, 4 Aug 2020 11:45:32 -0400
4Subject: [PATCH] encoding/binary: read at most MaxVarintLen64 bytes in
5 ReadUvarint
6MIME-Version: 1.0
7Content-Type: text/plain; charset=UTF-8
8Content-Transfer-Encoding: 8bit
9
10This CL ensures that ReadUvarint consumes only a limited
11amount of input (instead of an unbounded amount).
12
13On some inputs, ReadUvarint could read an arbitrary number
14of bytes before deciding to return an overflow error.
15After this CL, ReadUvarint returns that same overflow
16error sooner, after reading at most MaxVarintLen64 bytes.
17
18Fix authored by Robert Griesemer and Filippo Valsorda.
19
20Thanks to Diederik Loerakker, Jonny Rhea, Raúl Kripalani,
21and Preston Van Loon for reporting this.
22
23Fixes #40618
24Fixes CVE-2020-16845
25
26Change-Id: Ie0cb15972f14c38b7cf7af84c45c4ce54909bb8f
27Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/812099
28Reviewed-by: Filippo Valsorda <valsorda@google.com>
29Reviewed-on: https://go-review.googlesource.com/c/go/+/247120
30Run-TryBot: Katie Hockman <katie@golang.org>
31TryBot-Result: Gobot Gobot <gobot@golang.org>
32Reviewed-by: Alexander Rakoczy <alex@golang.org>
33
34Upstream-Status: Backport [https://github.com/golang/go.git]
35CVE: CVE-2020-16845
36Signed-off-by: Zhixiong Chi <zhixiong.chi@windriver.com>
37---
38 src/encoding/binary/varint.go | 5 +++--
39 src/encoding/binary/varint_test.go | 18 ++++++++++++------
40 2 files changed, 15 insertions(+), 8 deletions(-)
41
42diff --git a/src/encoding/binary/varint.go b/src/encoding/binary/varint.go
43index bcb8ac9a45..38af61075c 100644
44--- a/src/encoding/binary/varint.go
45+++ b/src/encoding/binary/varint.go
46@@ -106,13 +106,13 @@ var overflow = errors.New("binary: varint overflows a 64-bit integer")
47 func ReadUvarint(r io.ByteReader) (uint64, error) {
48 var x uint64
49 var s uint
50- for i := 0; ; i++ {
51+ for i := 0; i < MaxVarintLen64; i++ {
52 b, err := r.ReadByte()
53 if err != nil {
54 return x, err
55 }
56 if b < 0x80 {
57- if i > 9 || i == 9 && b > 1 {
58+ if i == 9 && b > 1 {
59 return x, overflow
60 }
61 return x | uint64(b)<<s, nil
62@@ -120,6 +120,7 @@ func ReadUvarint(r io.ByteReader) (uint64, error) {
63 x |= uint64(b&0x7f) << s
64 s += 7
65 }
66+ return x, overflow
67 }
68
69 // ReadVarint reads an encoded signed integer from r and returns it as an int64.
70diff --git a/src/encoding/binary/varint_test.go b/src/encoding/binary/varint_test.go
71index ca411ecbd6..6ef4c99505 100644
72--- a/src/encoding/binary/varint_test.go
73+++ b/src/encoding/binary/varint_test.go
74@@ -121,21 +121,27 @@ func TestBufferTooSmall(t *testing.T) {
75 }
76 }
77
78-func testOverflow(t *testing.T, buf []byte, n0 int, err0 error) {
79+func testOverflow(t *testing.T, buf []byte, x0 uint64, n0 int, err0 error) {
80 x, n := Uvarint(buf)
81 if x != 0 || n != n0 {
82 t.Errorf("Uvarint(%v): got x = %d, n = %d; want 0, %d", buf, x, n, n0)
83 }
84
85- x, err := ReadUvarint(bytes.NewReader(buf))
86- if x != 0 || err != err0 {
87- t.Errorf("ReadUvarint(%v): got x = %d, err = %s; want 0, %s", buf, x, err, err0)
88+ r := bytes.NewReader(buf)
89+ len := r.Len()
90+ x, err := ReadUvarint(r)
91+ if x != x0 || err != err0 {
92+ t.Errorf("ReadUvarint(%v): got x = %d, err = %s; want %d, %s", buf, x, err, x0, err0)
93+ }
94+ if read := len - r.Len(); read > MaxVarintLen64 {
95+ t.Errorf("ReadUvarint(%v): read more than MaxVarintLen64 bytes, got %d", buf, read)
96 }
97 }
98
99 func TestOverflow(t *testing.T) {
100- testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2}, -10, overflow)
101- testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0}, -13, overflow)
102+ testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x2}, 0, -10, overflow)
103+ testOverflow(t, []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x1, 0, 0}, 0, -13, overflow)
104+ testOverflow(t, []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 1<<64-1, 0, overflow) // 11 bytes, should overflow
105 }
106
107 func TestNonCanonicalZero(t *testing.T) {
108--
1092.17.0
110
diff --git a/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch b/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch
new file mode 100644
index 0000000000..18a218bc9a
--- /dev/null
+++ b/meta/recipes-devtools/go/go-1.12/CVE-2020-24553.patch
@@ -0,0 +1,429 @@
1From eb07103a083237414145a45f029c873d57037e06 Mon Sep 17 00:00:00 2001
2From: Roberto Clapis <roberto@golang.org>
3Date: Wed, 26 Aug 2020 08:53:03 +0200
4Subject: [PATCH] [release-branch.go1.15-security] net/http/cgi,net/http/fcgi:
5 add Content-Type detection
6
7This CL ensures that responses served via CGI and FastCGI
8have a Content-Type header based on the content of the
9response if not explicitly set by handlers.
10
11If the implementers of the handler did not explicitly
12specify a Content-Type both CGI implementations would default
13to "text/html", potentially causing cross-site scripting.
14
15Thanks to RedTeam Pentesting GmbH for reporting this.
16
17Fixes CVE-2020-24553
18
19Change-Id: I82cfc396309b5ab2e8d6e9a87eda8ea7e3799473
20Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/823217
21Reviewed-by: Russ Cox <rsc@google.com>
22(cherry picked from commit 23d675d07fdc56aafd67c0a0b63d5b7e14708ff0)
23Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/835311
24Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
25
26Upstream-Status: Backport
27CVE: CVE-2020-24553
28Signed-off-by: Li Zhou <li.zhou@windriver.com>
29---
30 src/net/http/cgi/child.go | 36 ++++++++++-----
31 src/net/http/cgi/child_test.go | 69 ++++++++++++++++++++++++++++
32 src/net/http/cgi/integration_test.go | 53 ++++++++++++++++++++-
33 src/net/http/fcgi/child.go | 39 ++++++++++++----
34 src/net/http/fcgi/fcgi_test.go | 52 +++++++++++++++++++++
35 5 files changed, 227 insertions(+), 22 deletions(-)
36
37diff --git a/src/net/http/cgi/child.go b/src/net/http/cgi/child.go
38index 9474175f17..61de6165f6 100644
39--- a/src/net/http/cgi/child.go
40+++ b/src/net/http/cgi/child.go
41@@ -163,10 +163,12 @@ func Serve(handler http.Handler) error {
42 }
43
44 type response struct {
45- req *http.Request
46- header http.Header
47- bufw *bufio.Writer
48- headerSent bool
49+ req *http.Request
50+ header http.Header
51+ code int
52+ wroteHeader bool
53+ wroteCGIHeader bool
54+ bufw *bufio.Writer
55 }
56
57 func (r *response) Flush() {
58@@ -178,26 +180,38 @@ func (r *response) Header() http.Header {
59 }
60
61 func (r *response) Write(p []byte) (n int, err error) {
62- if !r.headerSent {
63+ if !r.wroteHeader {
64 r.WriteHeader(http.StatusOK)
65 }
66+ if !r.wroteCGIHeader {
67+ r.writeCGIHeader(p)
68+ }
69 return r.bufw.Write(p)
70 }
71
72 func (r *response) WriteHeader(code int) {
73- if r.headerSent {
74+ if r.wroteHeader {
75 // Note: explicitly using Stderr, as Stdout is our HTTP output.
76 fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
77 return
78 }
79- r.headerSent = true
80- fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
81+ r.wroteHeader = true
82+ r.code = code
83+}
84
85- // Set a default Content-Type
86+// writeCGIHeader finalizes the header sent to the client and writes it to the output.
87+// p is not written by writeHeader, but is the first chunk of the body
88+// that will be written. It is sniffed for a Content-Type if none is
89+// set explicitly.
90+func (r *response) writeCGIHeader(p []byte) {
91+ if r.wroteCGIHeader {
92+ return
93+ }
94+ r.wroteCGIHeader = true
95+ fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
96 if _, hasType := r.header["Content-Type"]; !hasType {
97- r.header.Add("Content-Type", "text/html; charset=utf-8")
98+ r.header.Set("Content-Type", http.DetectContentType(p))
99 }
100-
101 r.header.Write(r.bufw)
102 r.bufw.WriteString("\r\n")
103 r.bufw.Flush()
104diff --git a/src/net/http/cgi/child_test.go b/src/net/http/cgi/child_test.go
105index 14e0af475f..f6ecb6eb80 100644
106--- a/src/net/http/cgi/child_test.go
107+++ b/src/net/http/cgi/child_test.go
108@@ -7,6 +7,11 @@
109 package cgi
110
111 import (
112+ "bufio"
113+ "bytes"
114+ "net/http"
115+ "net/http/httptest"
116+ "strings"
117 "testing"
118 )
119
120@@ -148,3 +153,67 @@ func TestRequestWithoutRemotePort(t *testing.T) {
121 t.Errorf("RemoteAddr: got %q; want %q", g, e)
122 }
123 }
124+
125+type countingWriter int
126+
127+func (c *countingWriter) Write(p []byte) (int, error) {
128+ *c += countingWriter(len(p))
129+ return len(p), nil
130+}
131+func (c *countingWriter) WriteString(p string) (int, error) {
132+ *c += countingWriter(len(p))
133+ return len(p), nil
134+}
135+
136+func TestResponse(t *testing.T) {
137+ var tests = []struct {
138+ name string
139+ body string
140+ wantCT string
141+ }{
142+ {
143+ name: "no body",
144+ wantCT: "text/plain; charset=utf-8",
145+ },
146+ {
147+ name: "html",
148+ body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
149+ wantCT: "text/html; charset=utf-8",
150+ },
151+ {
152+ name: "text",
153+ body: strings.Repeat("gopher", 86),
154+ wantCT: "text/plain; charset=utf-8",
155+ },
156+ {
157+ name: "jpg",
158+ body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
159+ wantCT: "image/jpeg",
160+ },
161+ }
162+ for _, tt := range tests {
163+ t.Run(tt.name, func(t *testing.T) {
164+ var buf bytes.Buffer
165+ resp := response{
166+ req: httptest.NewRequest("GET", "/", nil),
167+ header: http.Header{},
168+ bufw: bufio.NewWriter(&buf),
169+ }
170+ n, err := resp.Write([]byte(tt.body))
171+ if err != nil {
172+ t.Errorf("Write: unexpected %v", err)
173+ }
174+ if want := len(tt.body); n != want {
175+ t.Errorf("reported short Write: got %v want %v", n, want)
176+ }
177+ resp.writeCGIHeader(nil)
178+ resp.Flush()
179+ if got := resp.Header().Get("Content-Type"); got != tt.wantCT {
180+ t.Errorf("wrong content-type: got %q, want %q", got, tt.wantCT)
181+ }
182+ if !bytes.HasSuffix(buf.Bytes(), []byte(tt.body)) {
183+ t.Errorf("body was not correctly written")
184+ }
185+ })
186+ }
187+}
188diff --git a/src/net/http/cgi/integration_test.go b/src/net/http/cgi/integration_test.go
189index 32d59c09a3..295c3b82d4 100644
190--- a/src/net/http/cgi/integration_test.go
191+++ b/src/net/http/cgi/integration_test.go
192@@ -16,7 +16,9 @@ import (
193 "io"
194 "net/http"
195 "net/http/httptest"
196+ "net/url"
197 "os"
198+ "strings"
199 "testing"
200 "time"
201 )
202@@ -52,7 +54,7 @@ func TestHostingOurselves(t *testing.T) {
203 }
204 replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
205
206- if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
207+ if expected, got := "text/plain; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
208 t.Errorf("got a Content-Type of %q; expected %q", got, expected)
209 }
210 if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
211@@ -152,6 +154,51 @@ func TestChildOnlyHeaders(t *testing.T) {
212 }
213 }
214
215+func TestChildContentType(t *testing.T) {
216+ testenv.MustHaveExec(t)
217+
218+ h := &Handler{
219+ Path: os.Args[0],
220+ Root: "/test.go",
221+ Args: []string{"-test.run=TestBeChildCGIProcess"},
222+ }
223+ var tests = []struct {
224+ name string
225+ body string
226+ wantCT string
227+ }{
228+ {
229+ name: "no body",
230+ wantCT: "text/plain; charset=utf-8",
231+ },
232+ {
233+ name: "html",
234+ body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
235+ wantCT: "text/html; charset=utf-8",
236+ },
237+ {
238+ name: "text",
239+ body: strings.Repeat("gopher", 86),
240+ wantCT: "text/plain; charset=utf-8",
241+ },
242+ {
243+ name: "jpg",
244+ body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
245+ wantCT: "image/jpeg",
246+ },
247+ }
248+ for _, tt := range tests {
249+ t.Run(tt.name, func(t *testing.T) {
250+ expectedMap := map[string]string{"_body": tt.body}
251+ req := fmt.Sprintf("GET /test.go?exact-body=%s HTTP/1.0\nHost: example.com\n\n", url.QueryEscape(tt.body))
252+ replay := runCgiTest(t, h, req, expectedMap)
253+ if got := replay.Header().Get("Content-Type"); got != tt.wantCT {
254+ t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT)
255+ }
256+ })
257+ }
258+}
259+
260 // golang.org/issue/7198
261 func Test500WithNoHeaders(t *testing.T) { want500Test(t, "/immediate-disconnect") }
262 func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") }
263@@ -203,6 +250,10 @@ func TestBeChildCGIProcess(t *testing.T) {
264 if req.FormValue("no-body") == "1" {
265 return
266 }
267+ if eb, ok := req.Form["exact-body"]; ok {
268+ io.WriteString(rw, eb[0])
269+ return
270+ }
271 if req.FormValue("write-forever") == "1" {
272 io.Copy(rw, neverEnding('a'))
273 for {
274diff --git a/src/net/http/fcgi/child.go b/src/net/http/fcgi/child.go
275index 30a6b2ce2d..a31273b3ec 100644
276--- a/src/net/http/fcgi/child.go
277+++ b/src/net/http/fcgi/child.go
278@@ -74,10 +74,12 @@ func (r *request) parseParams() {
279
280 // response implements http.ResponseWriter.
281 type response struct {
282- req *request
283- header http.Header
284- w *bufWriter
285- wroteHeader bool
286+ req *request
287+ header http.Header
288+ code int
289+ wroteHeader bool
290+ wroteCGIHeader bool
291+ w *bufWriter
292 }
293
294 func newResponse(c *child, req *request) *response {
295@@ -92,11 +94,14 @@ func (r *response) Header() http.Header {
296 return r.header
297 }
298
299-func (r *response) Write(data []byte) (int, error) {
300+func (r *response) Write(p []byte) (n int, err error) {
301 if !r.wroteHeader {
302 r.WriteHeader(http.StatusOK)
303 }
304- return r.w.Write(data)
305+ if !r.wroteCGIHeader {
306+ r.writeCGIHeader(p)
307+ }
308+ return r.w.Write(p)
309 }
310
311 func (r *response) WriteHeader(code int) {
312@@ -104,22 +109,34 @@ func (r *response) WriteHeader(code int) {
313 return
314 }
315 r.wroteHeader = true
316+ r.code = code
317 if code == http.StatusNotModified {
318 // Must not have body.
319 r.header.Del("Content-Type")
320 r.header.Del("Content-Length")
321 r.header.Del("Transfer-Encoding")
322- } else if r.header.Get("Content-Type") == "" {
323- r.header.Set("Content-Type", "text/html; charset=utf-8")
324 }
325-
326 if r.header.Get("Date") == "" {
327 r.header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
328 }
329+}
330
331- fmt.Fprintf(r.w, "Status: %d %s\r\n", code, http.StatusText(code))
332+// writeCGIHeader finalizes the header sent to the client and writes it to the output.
333+// p is not written by writeHeader, but is the first chunk of the body
334+// that will be written. It is sniffed for a Content-Type if none is
335+// set explicitly.
336+func (r *response) writeCGIHeader(p []byte) {
337+ if r.wroteCGIHeader {
338+ return
339+ }
340+ r.wroteCGIHeader = true
341+ fmt.Fprintf(r.w, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
342+ if _, hasType := r.header["Content-Type"]; r.code != http.StatusNotModified && !hasType {
343+ r.header.Set("Content-Type", http.DetectContentType(p))
344+ }
345 r.header.Write(r.w)
346 r.w.WriteString("\r\n")
347+ r.w.Flush()
348 }
349
350 func (r *response) Flush() {
351@@ -290,6 +307,8 @@ func (c *child) serveRequest(req *request, body io.ReadCloser) {
352 httpReq = httpReq.WithContext(envVarCtx)
353 c.handler.ServeHTTP(r, httpReq)
354 }
355+ // Make sure we serve something even if nothing was written to r
356+ r.Write(nil)
357 r.Close()
358 c.mu.Lock()
359 delete(c.requests, req.reqId)
360diff --git a/src/net/http/fcgi/fcgi_test.go b/src/net/http/fcgi/fcgi_test.go
361index e9d2b34023..4a27a12c35 100644
362--- a/src/net/http/fcgi/fcgi_test.go
363+++ b/src/net/http/fcgi/fcgi_test.go
364@@ -10,6 +10,7 @@ import (
365 "io"
366 "io/ioutil"
367 "net/http"
368+ "strings"
369 "testing"
370 )
371
372@@ -344,3 +345,54 @@ func TestChildServeReadsEnvVars(t *testing.T) {
373 <-done
374 }
375 }
376+
377+func TestResponseWriterSniffsContentType(t *testing.T) {
378+ var tests = []struct {
379+ name string
380+ body string
381+ wantCT string
382+ }{
383+ {
384+ name: "no body",
385+ wantCT: "text/plain; charset=utf-8",
386+ },
387+ {
388+ name: "html",
389+ body: "<html><head><title>test page</title></head><body>This is a body</body></html>",
390+ wantCT: "text/html; charset=utf-8",
391+ },
392+ {
393+ name: "text",
394+ body: strings.Repeat("gopher", 86),
395+ wantCT: "text/plain; charset=utf-8",
396+ },
397+ {
398+ name: "jpg",
399+ body: "\xFF\xD8\xFF" + strings.Repeat("B", 1024),
400+ wantCT: "image/jpeg",
401+ },
402+ }
403+ for _, tt := range tests {
404+ t.Run(tt.name, func(t *testing.T) {
405+ input := make([]byte, len(streamFullRequestStdin))
406+ copy(input, streamFullRequestStdin)
407+ rc := nopWriteCloser{bytes.NewBuffer(input)}
408+ done := make(chan bool)
409+ var resp *response
410+ c := newChild(rc, http.HandlerFunc(func(
411+ w http.ResponseWriter,
412+ r *http.Request,
413+ ) {
414+ io.WriteString(w, tt.body)
415+ resp = w.(*response)
416+ done <- true
417+ }))
418+ defer c.cleanUp()
419+ go c.serve()
420+ <-done
421+ if got := resp.Header().Get("Content-Type"); got != tt.wantCT {
422+ t.Errorf("got a Content-Type of %q; expected it to start with %q", got, tt.wantCT)
423+ }
424+ })
425+ }
426+}
427--
4282.17.1
429