Source file
src/net/http/sniff_test.go
1
2
3
4
5 package http_test
6
7 import (
8 "bytes"
9 "fmt"
10 "io"
11 "log"
12 . "net/http"
13 "slices"
14 "strconv"
15 "strings"
16 "testing"
17 )
18
19 var sniffTests = []struct {
20 desc string
21 data []byte
22 contentType string
23 }{
24
25 {"Empty", []byte{}, "text/plain; charset=utf-8"},
26 {"Binary", []byte{1, 2, 3}, "application/octet-stream"},
27
28 {"HTML document #1", []byte(`<HtMl><bOdY>blah blah blah</body></html>`), "text/html; charset=utf-8"},
29 {"HTML document #2", []byte(`<HTML></HTML>`), "text/html; charset=utf-8"},
30 {"HTML document #3 (leading whitespace)", []byte(` <!DOCTYPE HTML>...`), "text/html; charset=utf-8"},
31 {"HTML document #4 (leading CRLF)", []byte("\r\n<html>..."), "text/html; charset=utf-8"},
32
33 {"Plain text", []byte(`This is not HTML. It has ☃ though.`), "text/plain; charset=utf-8"},
34
35 {"XML", []byte("\n<?xml!"), "text/xml; charset=utf-8"},
36
37
38 {"Windows icon", []byte("\x00\x00\x01\x00"), "image/x-icon"},
39 {"Windows cursor", []byte("\x00\x00\x02\x00"), "image/x-icon"},
40 {"BMP image", []byte("BM..."), "image/bmp"},
41 {"GIF 87a", []byte(`GIF87a`), "image/gif"},
42 {"GIF 89a", []byte(`GIF89a...`), "image/gif"},
43 {"WEBP image", []byte("RIFF\x00\x00\x00\x00WEBPVP"), "image/webp"},
44 {"PNG image", []byte("\x89PNG\x0D\x0A\x1A\x0A"), "image/png"},
45 {"JPEG image", []byte("\xFF\xD8\xFF"), "image/jpeg"},
46
47
48 {"MIDI audio", []byte("MThd\x00\x00\x00\x06\x00\x01"), "audio/midi"},
49 {"MP3 audio/MPEG audio", []byte("ID3\x03\x00\x00\x00\x00\x0f"), "audio/mpeg"},
50 {"WAV audio #1", []byte("RIFFb\xb8\x00\x00WAVEfmt \x12\x00\x00\x00\x06"), "audio/wave"},
51 {"WAV audio #2", []byte("RIFF,\x00\x00\x00WAVEfmt \x12\x00\x00\x00\x06"), "audio/wave"},
52 {"AIFF audio #1", []byte("FORM\x00\x00\x00\x00AIFFCOMM\x00\x00\x00\x12\x00\x01\x00\x00\x57\x55\x00\x10\x40\x0d\xf3\x34"), "audio/aiff"},
53
54 {"OGG audio", []byte("OggS\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x7e\x46\x00\x00\x00\x00\x00\x00\x1f\xf6\xb4\xfc\x01\x1e\x01\x76\x6f\x72"), "application/ogg"},
55 {"Must not match OGG", []byte("owow\x00"), "application/octet-stream"},
56 {"Must not match OGG", []byte("oooS\x00"), "application/octet-stream"},
57 {"Must not match OGG", []byte("oggS\x00"), "application/octet-stream"},
58
59
60 {"MP4 video", []byte("\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom<\x06t\xbfmdat"), "video/mp4"},
61 {"AVI video #1", []byte("RIFF,O\n\x00AVI LISTÀ"), "video/avi"},
62 {"AVI video #2", []byte("RIFF,\n\x00\x00AVI LISTÀ"), "video/avi"},
63
64
65
66 {"TTF sample I", []byte("\x00\x01\x00\x00\x00\x17\x01\x00\x00\x04\x01\x60\x4f"), "font/ttf"},
67 {"TTF sample II", []byte("\x00\x01\x00\x00\x00\x0e\x00\x80\x00\x03\x00\x60\x46"), "font/ttf"},
68
69 {"OTTO sample I", []byte("\x4f\x54\x54\x4f\x00\x0e\x00\x80\x00\x03\x00\x60\x42\x41\x53\x45"), "font/otf"},
70
71 {"woff sample I", []byte("\x77\x4f\x46\x46\x00\x01\x00\x00\x00\x00\x30\x54\x00\x0d\x00\x00"), "font/woff"},
72 {"woff2 sample", []byte("\x77\x4f\x46\x32\x00\x01\x00\x00\x00"), "font/woff2"},
73 {"wasm sample", []byte("\x00\x61\x73\x6d\x01\x00"), "application/wasm"},
74
75
76 {"RAR v1.5-v4.0", []byte("Rar!\x1A\x07\x00"), "application/x-rar-compressed"},
77 {"RAR v5+", []byte("Rar!\x1A\x07\x01\x00"), "application/x-rar-compressed"},
78 {"Incorrect RAR v1.5-v4.0", []byte("Rar \x1A\x07\x00"), "application/octet-stream"},
79 {"Incorrect RAR v5+", []byte("Rar \x1A\x07\x01\x00"), "application/octet-stream"},
80 }
81
82 func TestDetectContentType(t *testing.T) {
83 for _, tt := range sniffTests {
84 ct := DetectContentType(tt.data)
85 if ct != tt.contentType {
86 t.Errorf("%v: DetectContentType = %q, want %q", tt.desc, ct, tt.contentType)
87 }
88 }
89 }
90
91 func TestServerContentTypeSniff(t *testing.T) { run(t, testServerContentTypeSniff) }
92 func testServerContentTypeSniff(t *testing.T, mode testMode) {
93 cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
94 i, _ := strconv.Atoi(r.FormValue("i"))
95 tt := sniffTests[i]
96 n, err := w.Write(tt.data)
97 if n != len(tt.data) || err != nil {
98 log.Fatalf("%v: Write(%q) = %v, %v want %d, nil", tt.desc, tt.data, n, err, len(tt.data))
99 }
100 }))
101 defer cst.close()
102
103 for i, tt := range sniffTests {
104 resp, err := cst.c.Get(cst.ts.URL + "/?i=" + strconv.Itoa(i))
105 if err != nil {
106 t.Errorf("%v: %v", tt.desc, err)
107 continue
108 }
109
110
111
112
113
114 wantContentType := tt.contentType
115 if len(tt.data) == 0 {
116 wantContentType = ""
117 }
118 if ct := resp.Header.Get("Content-Type"); ct != wantContentType {
119 t.Errorf("%v: Content-Type = %q, want %q", tt.desc, ct, wantContentType)
120 }
121 data, err := io.ReadAll(resp.Body)
122 if err != nil {
123 t.Errorf("%v: reading body: %v", tt.desc, err)
124 } else if !bytes.Equal(data, tt.data) {
125 t.Errorf("%v: data is %q, want %q", tt.desc, data, tt.data)
126 }
127 resp.Body.Close()
128 }
129 }
130
131
132
133 func TestServerIssue5953(t *testing.T) { run(t, testServerIssue5953) }
134 func testServerIssue5953(t *testing.T, mode testMode) {
135 cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
136 w.Header()["Content-Type"] = []string{""}
137 fmt.Fprintf(w, "<html><head></head><body>hi</body></html>")
138 }))
139
140 resp, err := cst.c.Get(cst.ts.URL)
141 if err != nil {
142 t.Fatal(err)
143 }
144
145 got := resp.Header["Content-Type"]
146 want := []string{""}
147 if !slices.Equal(got, want) {
148 t.Errorf("Content-Type = %q; want %q", got, want)
149 }
150 resp.Body.Close()
151 }
152
153 type byteAtATimeReader struct {
154 buf []byte
155 }
156
157 func (b *byteAtATimeReader) Read(p []byte) (n int, err error) {
158 if len(p) < 1 {
159 return 0, nil
160 }
161 if len(b.buf) == 0 {
162 return 0, io.EOF
163 }
164 p[0] = b.buf[0]
165 b.buf = b.buf[1:]
166 return 1, nil
167 }
168
169 func TestContentTypeWithVariousSources(t *testing.T) { run(t, testContentTypeWithVariousSources) }
170 func testContentTypeWithVariousSources(t *testing.T, mode testMode) {
171 const (
172 input = "\n<html>\n\t<head>\n"
173 expected = "text/html; charset=utf-8"
174 )
175
176 for _, test := range []struct {
177 name string
178 handler func(ResponseWriter, *Request)
179 }{{
180 name: "write",
181 handler: func(w ResponseWriter, r *Request) {
182
183 n, err := w.Write([]byte(input))
184 if int(n) != len(input) || err != nil {
185 t.Errorf("w.Write(%q) = %v, %v want %d, nil", input, n, err, len(input))
186 }
187 },
188 }, {
189 name: "write one byte at a time",
190 handler: func(w ResponseWriter, r *Request) {
191
192 buf := []byte(input)
193 for i := range buf {
194 n, err := w.Write(buf[i : i+1])
195 if n != 1 || err != nil {
196 t.Errorf("w.Write(%q) = %v, %v want 1, nil", input, n, err)
197 }
198 }
199 },
200 }, {
201 name: "copy from Reader",
202 handler: func(w ResponseWriter, r *Request) {
203
204 type readerOnly struct{ io.Reader }
205 buf := bytes.NewBuffer([]byte(input))
206 n, err := io.Copy(w, readerOnly{buf})
207 if int(n) != len(input) || err != nil {
208 t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input))
209 }
210 },
211 }, {
212 name: "copy from bytes.Buffer",
213 handler: func(w ResponseWriter, r *Request) {
214
215 buf := bytes.NewBuffer([]byte(input))
216 n, err := io.Copy(w, buf)
217 if int(n) != len(input) || err != nil {
218 t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input))
219 }
220 },
221 }, {
222 name: "copy one byte at a time",
223 handler: func(w ResponseWriter, r *Request) {
224
225 n, err := io.Copy(w, &byteAtATimeReader{[]byte(input)})
226 if int(n) != len(input) || err != nil {
227 t.Errorf("io.Copy(w, %q) = %v, %v want %d, nil", input, n, err, len(input))
228 }
229 },
230 }} {
231 t.Run(test.name, func(t *testing.T) {
232 cst := newClientServerTest(t, mode, HandlerFunc(test.handler))
233
234 resp, err := cst.c.Get(cst.ts.URL)
235 if err != nil {
236 t.Fatalf("Get: %v", err)
237 }
238 if ct := resp.Header.Get("Content-Type"); ct != expected {
239 t.Errorf("Content-Type = %q, want %q", ct, expected)
240 }
241 if want, got := resp.Header.Get("Content-Length"), fmt.Sprint(len(input)); want != got {
242 t.Errorf("Content-Length = %q, want %q", want, got)
243 }
244 data, err := io.ReadAll(resp.Body)
245 if err != nil {
246 t.Errorf("reading body: %v", err)
247 } else if !bytes.Equal(data, []byte(input)) {
248 t.Errorf("data is %q, want %q", data, input)
249 }
250 resp.Body.Close()
251
252 })
253
254 }
255 }
256
257 func TestSniffWriteSize(t *testing.T) { run(t, testSniffWriteSize) }
258 func testSniffWriteSize(t *testing.T, mode testMode) {
259 cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
260 size, _ := strconv.Atoi(r.FormValue("size"))
261 written, err := io.WriteString(w, strings.Repeat("a", size))
262 if err != nil {
263 t.Errorf("write of %d bytes: %v", size, err)
264 return
265 }
266 if written != size {
267 t.Errorf("write of %d bytes wrote %d bytes", size, written)
268 }
269 }))
270 for _, size := range []int{0, 1, 200, 600, 999, 1000, 1023, 1024, 512 << 10, 1 << 20} {
271 res, err := cst.c.Get(fmt.Sprintf("%s/?size=%d", cst.ts.URL, size))
272 if err != nil {
273 t.Fatalf("size %d: %v", size, err)
274 }
275 if _, err := io.Copy(io.Discard, res.Body); err != nil {
276 t.Fatalf("size %d: io.Copy of body = %v", size, err)
277 }
278 if err := res.Body.Close(); err != nil {
279 t.Fatalf("size %d: body Close = %v", size, err)
280 }
281 }
282 }
283
View as plain text