@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 227 lines 6.5 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package requestbody
16
17 import (
18 "context"
19 "errors"
20 "io"
21 "net/http"
22 "net/http/httptest"
23 "strings"
24 "testing"
25
26 "github.com/caddyserver/caddy/v2"
27 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
28 )
29
30 func newRequestBodySetRequest(t *testing.T) (*http.Request, *caddy.Replacer) {
31 t.Helper()
32
33 req := httptest.NewRequest(http.MethodPost, "https://example.com/upload", nil)
34 repl := caddy.NewReplacer()
35 ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
36
37 return req.WithContext(ctx), repl
38 }
39
40 // TestRequestBodySetKeepsUnknownPlaceholders is a regression test for the JSON
41 // case: a set body is user-supplied and commonly JSON, so braces the replacer
42 // does not recognize must survive the expansion instead of being blanked, the
43 // same known-placeholder policy the respond body already follows.
44 func TestRequestBodySetKeepsUnknownPlaceholders(t *testing.T) {
45 const jsonBody = `{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}`
46
47 rb := RequestBody{Set: jsonBody}
48
49 req, _ := newRequestBodySetRequest(t)
50
51 var gotBody string
52 next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
53 body, err := io.ReadAll(r.Body)
54 if err != nil {
55 return err
56 }
57 gotBody = string(body)
58 return nil
59 })
60
61 err := rb.ServeHTTP(httptest.NewRecorder(), req, next)
62 if err != nil {
63 t.Fatalf("did not expect an error, but got: %v", err)
64 }
65
66 if gotBody != jsonBody {
67 t.Errorf("set body was mangled by placeholder expansion: got %q, want %q", gotBody, jsonBody)
68 }
69 }
70
71 // TestRequestBodySetStillReplacesKnownPlaceholders guards the other direction:
72 // keeping unknown braces intact must not stop real placeholders from being
73 // replaced.
74 func TestRequestBodySetStillReplacesKnownPlaceholders(t *testing.T) {
75 rb := RequestBody{Set: `{"token":"{caddy_request_body_test_token}"}`}
76 want := `{"token":"s3cret"}`
77
78 req, repl := newRequestBodySetRequest(t)
79 repl.Set("caddy_request_body_test_token", "s3cret")
80
81 var gotBody string
82 next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
83 body, err := io.ReadAll(r.Body)
84 if err != nil {
85 return err
86 }
87 gotBody = string(body)
88 return nil
89 })
90
91 err := rb.ServeHTTP(httptest.NewRecorder(), req, next)
92 if err != nil {
93 t.Fatalf("did not expect an error, but got: %v", err)
94 }
95
96 if gotBody != want {
97 t.Errorf("known placeholder not expanded: got %q, want %q", gotBody, want)
98 }
99 }
100
101 // TestRequestBodySetHeaderPlaceholders verifies that HTTP request header placeholders
102 // resolve to an empty string when absent under ReplaceKnown, and expand to the header
103 // value when present, so the backend never receives literal placeholder braces.
104 func TestRequestBodySetHeaderPlaceholders(t *testing.T) {
105 for _, tc := range []struct {
106 name string
107 setBody string
108 setupReq func(req *http.Request)
109 wantBody string
110 }{
111 {
112 name: "absent header resolves to empty string",
113 setBody: `{"api_key":"{http.request.header.X-Api-Key}"}`,
114 wantBody: `{"api_key":""}`,
115 },
116 {
117 name: "present header resolves to value",
118 setBody: `{"api_key":"{http.request.header.X-Api-Key}"}`,
119 setupReq: func(req *http.Request) {
120 req.Header.Set("X-Api-Key", "secret-token")
121 },
122 wantBody: `{"api_key":"secret-token"}`,
123 },
124 } {
125 t.Run(tc.name, func(t *testing.T) {
126 rb := RequestBody{Set: tc.setBody}
127
128 req := httptest.NewRequest(http.MethodPost, "https://example.com/upload", nil)
129 if tc.setupReq != nil {
130 tc.setupReq(req)
131 }
132 _ = caddyhttp.NewTestReplacer(req)
133
134 var gotBody string
135 next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
136 body, err := io.ReadAll(r.Body)
137 if err != nil {
138 return err
139 }
140 gotBody = string(body)
141 return nil
142 })
143
144 err := rb.ServeHTTP(httptest.NewRecorder(), req, next)
145 if err != nil {
146 t.Fatalf("did not expect an error, but got: %v", err)
147 }
148
149 if gotBody != tc.wantBody {
150 t.Errorf("got %q, want %q", gotBody, tc.wantBody)
151 }
152 })
153 }
154 }
155
156 // TestRequestBodyMaxSize verifies that a declared Content-Length over the limit
157 // is rejected before the next handler runs, while bodies of unknown length still
158 // fail on read, both with the same 413 error.
159 func TestRequestBodyMaxSize(t *testing.T) {
160 const maxSize = 8
161
162 for _, tc := range []struct {
163 name string
164 body string
165 contentLength int64
166 wantNext bool
167 wantErr bool
168 }{
169 {
170 name: "declared length over the limit",
171 body: "0123456789",
172 contentLength: 10,
173 wantErr: true,
174 },
175 {
176 name: "declared length within the limit",
177 body: "01234567",
178 contentLength: 8,
179 wantNext: true,
180 },
181 {
182 name: "unknown length over the limit",
183 body: "0123456789",
184 contentLength: -1,
185 wantNext: true,
186 wantErr: true,
187 },
188 } {
189 t.Run(tc.name, func(t *testing.T) {
190 rb := RequestBody{MaxSize: maxSize}
191
192 req := httptest.NewRequest(http.MethodPost, "https://example.com/upload", strings.NewReader(tc.body))
193 req.ContentLength = tc.contentLength
194 if tc.contentLength < 0 {
195 req.TransferEncoding = []string{"chunked"}
196 }
197
198 var nextCalled bool
199 next := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
200 nextCalled = true
201 _, err := io.ReadAll(r.Body)
202 return err
203 })
204
205 err := rb.ServeHTTP(httptest.NewRecorder(), req, next)
206
207 if nextCalled != tc.wantNext {
208 t.Errorf("next handler called = %t, want %t", nextCalled, tc.wantNext)
209 }
210
211 if !tc.wantErr {
212 if err != nil {
213 t.Fatalf("did not expect an error, but got: %v", err)
214 }
215 return
216 }
217
218 he, ok := errors.AsType[caddyhttp.HandlerError](err)
219 if !ok || he.StatusCode != http.StatusRequestEntityTooLarge {
220 t.Fatalf("want a %d handler error, got: %v", http.StatusRequestEntityTooLarge, err)
221 }
222 if mbe, ok := errors.AsType[*http.MaxBytesError](err); !ok || mbe.Limit != maxSize {
223 t.Errorf("want a *http.MaxBytesError with limit %d, got: %v", maxSize, err)
224 }
225 })
226 }
227 }