@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 505 lines 14.1 KB
Raw
1 package caddyhttp
2
3 import (
4 "bufio"
5 "bytes"
6 "context"
7 "io"
8 "net"
9 "net/http"
10 "net/http/httptest"
11 "strings"
12 "testing"
13 "time"
14 )
15
16 type responseWriterSpy interface {
17 http.ResponseWriter
18 Written() string
19 CalledReadFrom() bool
20 }
21
22 var (
23 _ responseWriterSpy = (*baseRespWriter)(nil)
24 _ responseWriterSpy = (*readFromRespWriter)(nil)
25 )
26
27 // a barebones http.ResponseWriter mock
28 type baseRespWriter []byte
29
30 func (brw *baseRespWriter) Write(d []byte) (int, error) {
31 *brw = append(*brw, d...)
32 return len(d), nil
33 }
34 func (brw *baseRespWriter) Header() http.Header { return nil }
35 func (brw *baseRespWriter) WriteHeader(statusCode int) {}
36 func (brw *baseRespWriter) Written() string { return string(*brw) }
37 func (brw *baseRespWriter) CalledReadFrom() bool { return false }
38
39 // an http.ResponseWriter mock that supports ReadFrom
40 type readFromRespWriter struct {
41 baseRespWriter
42 called bool
43 }
44
45 func (rf *readFromRespWriter) ReadFrom(r io.Reader) (int64, error) {
46 rf.called = true
47 return io.Copy(&rf.baseRespWriter, r)
48 }
49
50 func (rf *readFromRespWriter) CalledReadFrom() bool { return rf.called }
51
52 type hijackRespWriter struct {
53 baseRespWriter
54 header http.Header
55 status int
56 conn net.Conn
57 }
58
59 func newHijackRespWriter() *hijackRespWriter {
60 return &hijackRespWriter{
61 header: make(http.Header),
62 conn: stubConn{},
63 }
64 }
65
66 func (hrw *hijackRespWriter) Header() http.Header {
67 return hrw.header
68 }
69
70 func (hrw *hijackRespWriter) WriteHeader(statusCode int) {
71 hrw.status = statusCode
72 }
73
74 func (hrw *hijackRespWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
75 br := bufio.NewReader(hrw.conn)
76 bw := bufio.NewWriter(hrw.conn)
77 return hrw.conn, bufio.NewReadWriter(br, bw), nil
78 }
79
80 type stubConn struct{}
81
82 func (stubConn) Read(_ []byte) (int, error) { return 0, io.EOF }
83 func (stubConn) Write(p []byte) (int, error) { return len(p), nil }
84 func (stubConn) Close() error { return nil }
85 func (stubConn) LocalAddr() net.Addr { return stubAddr("local") }
86 func (stubConn) RemoteAddr() net.Addr { return stubAddr("remote") }
87 func (stubConn) SetDeadline(time.Time) error { return nil }
88 func (stubConn) SetReadDeadline(time.Time) error { return nil }
89 func (stubConn) SetWriteDeadline(time.Time) error { return nil }
90
91 type stubAddr string
92
93 func (a stubAddr) Network() string { return "tcp" }
94 func (a stubAddr) String() string { return string(a) }
95
96 func TestResponseWriterWrapperReadFrom(t *testing.T) {
97 tests := map[string]struct {
98 responseWriter responseWriterSpy
99 wantReadFrom bool
100 }{
101 "no ReadFrom": {
102 responseWriter: &baseRespWriter{},
103 wantReadFrom: false,
104 },
105 "has ReadFrom": {
106 responseWriter: &readFromRespWriter{},
107 wantReadFrom: true,
108 },
109 }
110 for name, tt := range tests {
111 t.Run(name, func(t *testing.T) {
112 // what we expect middlewares to do:
113 type myWrapper struct {
114 *ResponseWriterWrapper
115 }
116
117 wrapped := myWrapper{
118 ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: tt.responseWriter},
119 }
120
121 const srcData = "boo!"
122 // hides everything but Read, since strings.Reader implements WriteTo it would
123 // take precedence over our ReadFrom.
124 src := struct{ io.Reader }{strings.NewReader(srcData)}
125
126 if _, err := io.Copy(wrapped, src); err != nil {
127 t.Errorf("%s: Copy() err = %v", name, err)
128 }
129
130 if got := tt.responseWriter.Written(); got != srcData {
131 t.Errorf("%s: data = %q, want %q", name, got, srcData)
132 }
133
134 if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom {
135 if tt.wantReadFrom {
136 t.Errorf("%s: ReadFrom() should have been called", name)
137 } else {
138 t.Errorf("%s: ReadFrom() should not have been called", name)
139 }
140 }
141 })
142 }
143 }
144
145 func TestResponseWriterWrapperUnwrap(t *testing.T) {
146 w := &ResponseWriterWrapper{&baseRespWriter{}}
147
148 if _, ok := w.Unwrap().(*baseRespWriter); !ok {
149 t.Errorf("Unwrap() doesn't return the underlying ResponseWriter")
150 }
151 }
152
153 func TestResponseRecorderReadFrom(t *testing.T) {
154 tests := map[string]struct {
155 responseWriter responseWriterSpy
156 shouldBuffer bool
157 wantReadFrom bool
158 }{
159 "buffered plain": {
160 responseWriter: &baseRespWriter{},
161 shouldBuffer: true,
162 wantReadFrom: false,
163 },
164 "streamed plain": {
165 responseWriter: &baseRespWriter{},
166 shouldBuffer: false,
167 wantReadFrom: false,
168 },
169 "buffered ReadFrom": {
170 responseWriter: &readFromRespWriter{},
171 shouldBuffer: true,
172 wantReadFrom: false,
173 },
174 "streamed ReadFrom": {
175 responseWriter: &readFromRespWriter{},
176 shouldBuffer: false,
177 wantReadFrom: true,
178 },
179 }
180 for name, tt := range tests {
181 t.Run(name, func(t *testing.T) {
182 var buf bytes.Buffer
183
184 rr := NewResponseRecorder(tt.responseWriter, &buf, func(status int, header http.Header) bool {
185 return tt.shouldBuffer
186 })
187
188 const srcData = "boo!"
189 // hides everything but Read, since strings.Reader implements WriteTo it would
190 // take precedence over our ReadFrom.
191 src := struct{ io.Reader }{strings.NewReader(srcData)}
192
193 if _, err := io.Copy(rr, src); err != nil {
194 t.Errorf("Copy() err = %v", err)
195 }
196
197 wantStreamed := srcData
198 wantBuffered := ""
199 if tt.shouldBuffer {
200 wantStreamed = ""
201 wantBuffered = srcData
202 }
203
204 if got := tt.responseWriter.Written(); got != wantStreamed {
205 t.Errorf("streamed data = %q, want %q", got, wantStreamed)
206 }
207 if got := buf.String(); got != wantBuffered {
208 t.Errorf("buffered data = %q, want %q", got, wantBuffered)
209 }
210
211 if tt.responseWriter.CalledReadFrom() != tt.wantReadFrom {
212 if tt.wantReadFrom {
213 t.Errorf("ReadFrom() should have been called")
214 } else {
215 t.Errorf("ReadFrom() should not have been called")
216 }
217 }
218 })
219 }
220 }
221
222 func TestResponseRecorderSwitchingProtocolsIsHijackAware(t *testing.T) {
223 w := newHijackRespWriter()
224 var buf bytes.Buffer
225
226 rr := NewResponseRecorder(w, &buf, func(status int, header http.Header) bool {
227 return true
228 })
229 rr.WriteHeader(http.StatusSwitchingProtocols)
230
231 if rr.Status() != http.StatusSwitchingProtocols {
232 t.Fatalf("status = %d, want %d", rr.Status(), http.StatusSwitchingProtocols)
233 }
234 if w.status != http.StatusSwitchingProtocols {
235 t.Fatalf("underlying status = %d, want %d", w.status, http.StatusSwitchingProtocols)
236 }
237
238 hj, ok := rr.(http.Hijacker)
239 if !ok {
240 t.Fatal("response recorder does not implement http.Hijacker")
241 }
242 conn, _, err := hj.Hijack()
243 if err != nil {
244 t.Fatalf("Hijack() error = %v", err)
245 }
246 defer conn.Close()
247
248 if rr.Buffered() {
249 t.Fatal("hijacked response should not remain buffered")
250 }
251 if rr.DetachAfterHijack(true) {
252 t.Fatal("response recorder should report hijacked state by returning false")
253 }
254 req := requestWithResponseWriterBoundary(w)
255 if DetachResponseWriterAfterHijack(rr, req, true) {
256 t.Fatal("DetachResponseWriterAfterHijack() should report false after hijack")
257 }
258 if err := rr.WriteResponse(); err != nil {
259 t.Fatalf("WriteResponse() after hijack returned error: %v", err)
260 }
261 if rr.Size() != 0 {
262 t.Fatalf("size = %d, want 0 after hijack handshake", rr.Size())
263 }
264 if got := w.Written(); got != "" {
265 t.Fatalf("unexpected buffered body write after hijack: %q", got)
266 }
267 }
268
269 func TestResponseRecorderNestedDetach(t *testing.T) {
270 for _, detached := range []bool{false, true} {
271 name := "attached"
272 if detached {
273 name = "detached"
274 }
275 t.Run(name, func(t *testing.T) {
276 inner := NewResponseRecorder(newHijackRespWriter(), nil, nil)
277 // Include an intervening middleware wrapper in the recorder chain.
278 outer := NewResponseRecorder(&ResponseWriterWrapper{ResponseWriter: inner}, nil, nil)
279 outer.WriteHeader(http.StatusSwitchingProtocols)
280 req := requestWithResponseWriterBoundary(inner)
281 if !DetachResponseWriterAfterHijack(outer, req, detached) {
282 t.Fatal("detach configuration failed")
283 }
284 conn, brw, err := http.NewResponseController(outer).Hijack()
285 if err != nil {
286 t.Fatal(err)
287 }
288 defer conn.Close()
289 const payload = "stream payload"
290 if _, err := conn.Write([]byte(payload)); err != nil {
291 t.Fatal(err)
292 }
293 if _, err := brw.WriteString(payload); err != nil {
294 t.Fatal(err)
295 }
296 if err := brw.Flush(); err != nil {
297 t.Fatal(err)
298 }
299 want := 2 * len(payload)
300 if detached {
301 want = 0
302 }
303 for name, rr := range map[string]ResponseRecorder{"inner": inner, "outer": outer} {
304 if rr.Size() != want {
305 t.Errorf("%s recorder size = %d, want %d", name, rr.Size(), want)
306 }
307 }
308 if DetachResponseWriterAfterHijack(outer, req, true) {
309 t.Error("detach should fail after hijack")
310 }
311 })
312 }
313 }
314
315 func TestDetachResponseWriterAfterHijackRejectsOpaqueInnerWriter(t *testing.T) {
316 raw := newHijackRespWriter()
317 opaque := struct{ http.ResponseWriter }{raw}
318 recorder := NewResponseRecorder(opaque, nil, nil)
319 req := requestWithResponseWriterBoundary(raw)
320
321 if DetachResponseWriterAfterHijack(recorder, req, true) {
322 t.Fatal("detach succeeded through a writer without Unwrap")
323 }
324 }
325
326 func TestDetachResponseWriterAfterHijackAtBoundary(t *testing.T) {
327 w := newHijackRespWriter()
328 req := requestWithResponseWriterBoundary(w)
329 if !DetachResponseWriterAfterHijack(w, req, true) {
330 t.Fatal("detach failed at the server-owned writer")
331 }
332 if DetachResponseWriterAfterHijack(w, nil, true) {
333 t.Fatal("detach succeeded without a request")
334 }
335 }
336
337 func TestDetachResponseWriterAfterHijackRejectsCycles(t *testing.T) {
338 raw := newHijackRespWriter()
339 req := requestWithResponseWriterBoundary(raw)
340 a := &cycleWriter{}
341 b := &cycleWriter{next: a}
342 a.next = b
343
344 for name, w := range map[string]http.ResponseWriter{
345 "uncomparable self-reference": uncomparableWriter{},
346 "indirect cycle": a,
347 } {
348 t.Run(name, func(t *testing.T) {
349 if DetachResponseWriterAfterHijack(w, req, true) {
350 t.Fatal("detach succeeded without reaching the server-owned writer")
351 }
352 })
353 }
354 }
355
356 func requestWithResponseWriterBoundary(w http.ResponseWriter) *http.Request {
357 req := httptest.NewRequest(http.MethodGet, "/", nil)
358 ctx := context.WithValue(req.Context(), responseWriterBoundaryCtxKey, w)
359 return req.WithContext(ctx)
360 }
361
362 // targetIface is an interface that only the innermost writer in the tests
363 // below implements; it's used to assert UnwrapResponseWriterAs walks past
364 // outer wrappers to find it.
365 type targetIface interface {
366 http.ResponseWriter
367 magic() string
368 }
369
370 type targetWriter struct {
371 baseRespWriter
372 }
373
374 func (*targetWriter) magic() string { return "ok" }
375
376 // plainWrapper wraps an http.ResponseWriter and forwards only the mandatory
377 // methods. It implements Unwrap() so the helper can traverse it.
378 type plainWrapper struct{ inner http.ResponseWriter }
379
380 func (p *plainWrapper) Header() http.Header { return p.inner.Header() }
381 func (p *plainWrapper) Write(b []byte) (int, error) { return p.inner.Write(b) }
382 func (p *plainWrapper) WriteHeader(statusCode int) { p.inner.WriteHeader(statusCode) }
383 func (p *plainWrapper) Unwrap() http.ResponseWriter { return p.inner }
384
385 func TestUnwrapResponseWriterAs(t *testing.T) {
386 inner := &targetWriter{}
387 for _, tc := range []struct {
388 name string
389 w http.ResponseWriter
390 ok bool
391 }{
392 {"direct", inner, true},
393 {"single wrapper", &ResponseWriterWrapper{ResponseWriter: inner}, true},
394 {"multiple wrappers", &plainWrapper{inner: &ResponseWriterWrapper{ResponseWriter: &plainWrapper{inner: inner}}}, true},
395 {"not found", &ResponseWriterWrapper{ResponseWriter: &baseRespWriter{}}, false},
396 } {
397 t.Run(tc.name, func(t *testing.T) {
398 got, ok := UnwrapResponseWriterAs[targetIface](tc.w)
399 if ok != tc.ok {
400 t.Fatalf("ok = %v, want %v", ok, tc.ok)
401 }
402 if ok && got.magic() != "ok" {
403 t.Errorf("unexpected writer returned: %v", got)
404 }
405 })
406 }
407 }
408
409 type selfUnwrapWriter struct{ baseRespWriter }
410
411 func (s *selfUnwrapWriter) Unwrap() http.ResponseWriter { return s }
412
413 func TestUnwrapResponseWriterAs_StopsOnSelfReference(t *testing.T) {
414 // A wrapper whose Unwrap returns itself must not loop forever.
415 loop := &selfUnwrapWriter{}
416 done := make(chan struct{})
417 go func() {
418 defer close(done)
419 _, _ = UnwrapResponseWriterAs[targetIface](loop)
420 }()
421 select {
422 case <-done:
423 case <-time.After(time.Second):
424 t.Fatal("UnwrapResponseWriterAs hung on self-referential Unwrap")
425 }
426 }
427
428 // uncomparableWriter contains a slice so the concrete type is not comparable.
429 // The previous `next == w` guard panics on this shape. Methods use value
430 // receivers so the wrapper is stored as a non-pointer in the interface.
431 type uncomparableWriter struct {
432 hdr http.Header
433 _ []byte
434 }
435
436 func (u uncomparableWriter) Header() http.Header { return u.hdr }
437 func (u uncomparableWriter) Write(b []byte) (int, error) { return len(b), nil }
438 func (u uncomparableWriter) WriteHeader(int) {}
439 func (u uncomparableWriter) Unwrap() http.ResponseWriter { return u }
440
441 func TestUnwrapResponseWriterAs_UncomparableWriter(t *testing.T) {
442 w := uncomparableWriter{}
443 done := make(chan struct{})
444 var panicked any
445 go func() {
446 defer close(done)
447 defer func() { panicked = recover() }()
448 _, _ = UnwrapResponseWriterAs[targetIface](w)
449 }()
450 select {
451 case <-done:
452 if panicked != nil {
453 t.Fatalf("panicked on uncomparable writer: %v", panicked)
454 }
455 case <-time.After(time.Second):
456 t.Fatal("UnwrapResponseWriterAs hung on uncomparable self-unwrap")
457 }
458 }
459
460 type cycleWriter struct {
461 baseRespWriter
462 next http.ResponseWriter
463 }
464
465 func (c *cycleWriter) Unwrap() http.ResponseWriter { return c.next }
466
467 func TestUnwrapResponseWriterAs_StopsOnIndirectCycle(t *testing.T) {
468 a := &cycleWriter{}
469 b := &cycleWriter{next: a}
470 a.next = b
471 done := make(chan struct{})
472 go func() {
473 defer close(done)
474 _, _ = UnwrapResponseWriterAs[targetIface](a)
475 }()
476 select {
477 case <-done:
478 case <-time.After(time.Second):
479 t.Fatal("UnwrapResponseWriterAs hung on A→B→A unwrap cycle")
480 }
481 }
482
483 type headerSpy struct {
484 baseRespWriter
485 status int
486 }
487
488 func (h *headerSpy) WriteHeader(code int) { h.status = code }
489
490 func TestRecordHijackedStatus(t *testing.T) {
491 inner := &headerSpy{}
492 rec := NewResponseRecorder(inner, new(bytes.Buffer), nil)
493 wrapped := &ResponseWriterWrapper{ResponseWriter: rec}
494
495 if rec.Status() != 0 {
496 t.Fatalf("status = %d, want 0 before hijack", rec.Status())
497 }
498 RecordHijackedStatus(wrapped, http.StatusOK)
499 if rec.Status() != http.StatusOK {
500 t.Fatalf("status = %d, want 200", rec.Status())
501 }
502 if inner.status != 0 {
503 t.Errorf("WriteHeader(%d) called on inner writer; hijack must not write again", inner.status)
504 }
505 }