@marcel91200 / caddy Mirror · read-only / commits / 105acfa0
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 14, 05:15 UTC

Keep type information with placeholders until replacements happen

Matthew Holt committed Mar 30, 2020 at 11:49 UTC 105acfa08664c97460a6fe3fb49635618be5bcb2
7 files changed +184 -109
modules/caddyhttp/celmatcher.go
+30 -2
@@ -86,7 +86,7 @@ func (m *MatchExpression) Provision(_ caddy.Context) error {
86 decls.NewFunction(placeholderFuncName,
87 decls.NewOverload(placeholderFuncName+"_httpRequest_string",
88 []*exprpb.Type{httpRequestObjectType, decls.String},
89 - decls.String)),
89 + decls.Any)),
90 ),
91 cel.CustomTypeAdapter(celHTTPRequestTypeAdapter{}),
92 ext.Strings(),
@@ -210,7 +210,35 @@ func caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
210 repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
211 val, _ := repl.Get(string(phStr))
212
213 - return types.String(val)
213 + // TODO: this is... kinda awful and underwhelming, how can we expand CEL's type system more easily?
214 + switch v := val.(type) {
215 + case string:
216 + return types.String(v)
217 + case fmt.Stringer:
218 + return types.String(v.String())
219 + case error:
220 + return types.NewErr(v.Error())
221 + case int:
222 + return types.Int(v)
223 + case int32:
224 + return types.Int(v)
225 + case int64:
226 + return types.Int(v)
227 + case uint:
228 + return types.Int(v)
229 + case uint32:
230 + return types.Int(v)
231 + case uint64:
232 + return types.Int(v)
233 + case float32:
234 + return types.Double(v)
235 + case float64:
236 + return types.Double(v)
237 + case bool:
238 + return types.Bool(v)
239 + default:
240 + return types.String(fmt.Sprintf("%+v", v))
241 + }
242 }
243
244 // Interface guards
modules/caddyhttp/replacer.go
+18 -36
@@ -31,7 +31,7 @@ import (
31 )
32
33 func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) {
34 - httpVars := func(key string) (string, bool) {
34 + httpVars := func(key string) (interface{}, bool) {
35 if req != nil {
36 // query string parameters
37 if strings.HasPrefix(key, reqURIQueryReplPrefix) {
@@ -62,7 +62,7 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
62 }
63 }
64
65 - // http.request.tls.
65 + // http.request.tls.*
66 if strings.HasPrefix(key, reqTLSReplPrefix) {
67 return getReqTLSReplacement(req, key)
68 }
@@ -182,21 +182,10 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
182 if strings.HasPrefix(key, varsReplPrefix) {
183 varName := key[len(varsReplPrefix):]
184 tbl := req.Context().Value(VarsCtxKey).(map[string]interface{})
185 - raw, ok := tbl[varName]
186 - if !ok {
187 - // variables can be dynamic, so always return true
188 - // even when it may not be set; treat as empty
189 - return "", true
190 - }
191 - // do our best to convert it to a string efficiently
192 - switch val := raw.(type) {
193 - case string:
194 - return val, true
195 - case fmt.Stringer:
196 - return val.String(), true
197 - default:
198 - return fmt.Sprintf("%s", val), true
199 - }
185 + raw, _ := tbl[varName]
186 + // variables can be dynamic, so always return true
187 + // even when it may not be set; treat as empty then
188 + return raw, true
189 }
190 }
191
@@ -211,19 +200,19 @@ func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.Respo
200 }
201 }
202
214 - return "", false
203 + return nil, false
204 }
205
206 repl.Map(httpVars)
207 }
208
220 -func getReqTLSReplacement(req *http.Request, key string) (string, bool) {
209 +func getReqTLSReplacement(req *http.Request, key string) (interface{}, bool) {
210 if req == nil || req.TLS == nil {
222 - return "", false
211 + return nil, false
212 }
213
214 if len(key) < len(reqTLSReplPrefix) {
226 - return "", false
215 + return nil, false
216 }
217
218 field := strings.ToLower(key[len(reqTLSReplPrefix):])
@@ -231,20 +220,20 @@ func getReqTLSReplacement(req *http.Request, key string) (string, bool) {
220 if strings.HasPrefix(field, "client.") {
221 cert := getTLSPeerCert(req.TLS)
222 if cert == nil {
234 - return "", false
223 + return nil, false
224 }
225
226 switch field {
227 case "client.fingerprint":
228 return fmt.Sprintf("%x", sha256.Sum256(cert.Raw)), true
229 case "client.issuer":
241 - return cert.Issuer.String(), true
230 + return cert.Issuer, true
231 case "client.serial":
243 - return fmt.Sprintf("%x", cert.SerialNumber), true
232 + return cert.SerialNumber, true
233 case "client.subject":
245 - return cert.Subject.String(), true
234 + return cert.Subject, true
235 default:
247 - return "", false
236 + return nil, false
237 }
238 }
239
@@ -254,22 +243,15 @@ func getReqTLSReplacement(req *http.Request, key string) (string, bool) {
243 case "cipher_suite":
244 return tls.CipherSuiteName(req.TLS.CipherSuite), true
245 case "resumed":
257 - if req.TLS.DidResume {
258 - return "true", true
259 - }
260 - return "false", true
246 + return req.TLS.DidResume, true
247 case "proto":
248 return req.TLS.NegotiatedProtocol, true
249 case "proto_mutual":
264 - if req.TLS.NegotiatedProtocolIsMutual {
265 - return "true", true
266 - }
267 - return "false", true
250 + return req.TLS.NegotiatedProtocolIsMutual, true
251 case "server_name":
252 return req.TLS.ServerName, true
270 - default:
271 - return "", false
253 }
254 + return nil, false
255 }
256
257 // getTLSPeerCert retrieves the first peer certificate from a TLS session.
modules/caddyhttp/reverseproxy/reverseproxy.go
+3 -4
@@ -24,7 +24,6 @@ import (
24 "net"
25 "net/http"
26 "regexp"
27 - "strconv"
27 "strings"
28 "sync"
29 "time"
@@ -328,9 +327,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
327 repl.Set("http.reverse_proxy.upstream.hostport", dialInfo.Address)
328 repl.Set("http.reverse_proxy.upstream.host", dialInfo.Host)
329 repl.Set("http.reverse_proxy.upstream.port", dialInfo.Port)
331 - repl.Set("http.reverse_proxy.upstream.requests", strconv.Itoa(upstream.Host.NumRequests()))
332 - repl.Set("http.reverse_proxy.upstream.max_requests", strconv.Itoa(upstream.MaxRequests))
333 - repl.Set("http.reverse_proxy.upstream.fails", strconv.Itoa(upstream.Host.Fails()))
330 + repl.Set("http.reverse_proxy.upstream.requests", upstream.Host.NumRequests())
331 + repl.Set("http.reverse_proxy.upstream.max_requests", upstream.MaxRequests)
332 + repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails())
333
334 // mutate request headers according to this upstream;
335 // because we're in a retry loop, we have to copy
modules/caddyhttp/rewrite/rewrite.go
+15 -2
@@ -15,8 +15,10 @@
15 package rewrite
16
17 import (
18 + "fmt"
19 "net/http"
20 "net/url"
21 + "strconv"
22 "strings"
23
24 "github.com/caddyserver/caddy/v2"
@@ -208,11 +210,22 @@ func buildQueryString(qs string, repl *caddy.Replacer) string {
210
211 // consume the component and write the result
212 comp := qs[:end]
211 - comp, _ = repl.ReplaceFunc(comp, func(name, val string) (string, error) {
213 + comp, _ = repl.ReplaceFunc(comp, func(name string, val interface{}) (interface{}, error) {
214 if name == "http.request.uri.query" && wroteVal {
215 return val, nil // already escaped
216 }
215 - return url.QueryEscape(val), nil
217 + var valStr string
218 + switch v := val.(type) {
219 + case string:
220 + valStr = v
221 + case fmt.Stringer:
222 + valStr = v.String()
223 + case int:
224 + valStr = strconv.Itoa(v)
225 + default:
226 + valStr = fmt.Sprintf("%+v", v)
227 + }
228 + return url.QueryEscape(valStr), nil
229 })
230 if end < len(qs) {
231 end++ // consume delimiter
modules/caddyhttp/server.go
+5 -6
@@ -21,7 +21,6 @@ import (
21 "net"
22 "net/http"
23 "net/url"
24 - "strconv"
24 "strings"
25 "time"
26
@@ -166,9 +165,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
165 defer func() {
166 latency := time.Since(start)
167
169 - repl.Set("http.response.status", strconv.Itoa(wrec.Status()))
170 - repl.Set("http.response.size", strconv.Itoa(wrec.Size()))
171 - repl.Set("http.response.latency", latency.String())
168 + repl.Set("http.response.status", wrec.Status())
169 + repl.Set("http.response.size", wrec.Size())
170 + repl.Set("http.response.latency", latency)
171
172 logger := accLog
173 if s.Logs != nil && s.Logs.LoggerNames != nil {
@@ -360,9 +359,9 @@ func (*HTTPErrorConfig) WithError(r *http.Request, err error) *http.Request {
359
360 // add error values to the replacer
361 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
363 - repl.Set("http.error", err.Error())
362 + repl.Set("http.error", err)
363 if handlerErr, ok := err.(HandlerError); ok {
365 - repl.Set("http.error.status_code", strconv.Itoa(handlerErr.StatusCode))
364 + repl.Set("http.error.status_code", handlerErr.StatusCode)
365 repl.Set("http.error.status_text", http.StatusText(handlerErr.StatusCode))
366 repl.Set("http.error.trace", handlerErr.Trace)
367 repl.Set("http.error.id", handlerErr.ID)
replacer.go
+63 -22
@@ -27,7 +27,7 @@ import (
27 // NewReplacer returns a new Replacer.
28 func NewReplacer() *Replacer {
29 rep := &Replacer{
30 - static: make(map[string]string),
30 + static: make(map[string]interface{}),
31 }
32 rep.providers = []ReplacerFunc{
33 globalDefaultReplacements,
@@ -41,7 +41,7 @@ func NewReplacer() *Replacer {
41 // use NewReplacer to make one.
42 type Replacer struct {
43 providers []ReplacerFunc
44 - static map[string]string
44 + static map[string]interface{}
45 }
46
47 // Map adds mapFunc to the list of value providers.
@@ -51,19 +51,19 @@ func (r *Replacer) Map(mapFunc ReplacerFunc) {
51 }
52
53 // Set sets a custom variable to a static value.
54 -func (r *Replacer) Set(variable, value string) {
54 +func (r *Replacer) Set(variable string, value interface{}) {
55 r.static[variable] = value
56 }
57
58 // Get gets a value from the replacer. It returns
59 // the value and whether the variable was known.
60 -func (r *Replacer) Get(variable string) (string, bool) {
60 +func (r *Replacer) Get(variable string) (interface{}, bool) {
61 for _, mapFunc := range r.providers {
62 if val, ok := mapFunc(variable); ok {
63 return val, true
64 }
65 }
66 - return "", false
66 + return nil, false
67 }
68
69 // Delete removes a variable with a static value
@@ -73,9 +73,9 @@ func (r *Replacer) Delete(variable string) {
73 }
74
75 // fromStatic provides values from r.static.
76 -func (r *Replacer) fromStatic(key string) (val string, ok bool) {
77 - val, ok = r.static[key]
78 - return
76 +func (r *Replacer) fromStatic(key string) (interface{}, bool) {
77 + val, ok := r.static[key]
78 + return val, ok
79 }
80
81 // ReplaceOrErr is like ReplaceAll, but any placeholders
@@ -102,10 +102,9 @@ func (r *Replacer) ReplaceAll(input, empty string) string {
102 return out
103 }
104
105 -// ReplaceFunc calls ReplaceAll efficiently replaces placeholders in input with
106 -// their values. All placeholders are replaced in the output
107 -// whether they are recognized or not. Values that are empty
108 -// string will be substituted with empty.
105 +// ReplaceFunc is the same as ReplaceAll, but calls f for every
106 +// replacement to be made, in case f wants to change or inspect
107 +// the replacement.
108 func (r *Replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) {
109 return r.replace(input, "", true, false, false, f)
110 }
@@ -125,7 +124,7 @@ func (r *Replacer) replace(input, empty string,
124
125 // iterate the input to find each placeholder
126 var lastWriteCursor int
128 -
127 +
128 scan:
129 for i := 0; i < len(input); i++ {
130
@@ -169,9 +168,8 @@ scan:
168 return "", fmt.Errorf("unrecognized placeholder %s%s%s",
169 string(phOpen), key, string(phClose))
170 } else if !treatUnknownAsEmpty {
172 - // if treatUnknownAsEmpty is true, we'll
173 - // handle an empty val later; so only
174 - // continue otherwise
171 + // if treatUnknownAsEmpty is true, we'll handle an empty
172 + // val later; so only continue otherwise
173 lastWriteCursor = i
174 continue
175 }
@@ -186,9 +184,12 @@ scan:
184 }
185 }
186
187 + // convert val to a string as efficiently as possible
188 + valStr := toString(val)
189 +
190 // write the value; if it's empty, either return
191 // an error or write a default value
191 - if val == "" {
192 + if valStr == "" {
193 if errOnEmpty {
194 return "", fmt.Errorf("evaluated placeholder %s%s%s is empty",
195 string(phOpen), key, string(phClose))
@@ -196,7 +197,7 @@ scan:
197 sb.WriteString(empty)
198 }
199 } else {
199 - sb.WriteString(val)
200 + sb.WriteString(valStr)
201 }
202
203 // advance cursor to end of placeholder
@@ -210,14 +211,54 @@ scan:
211 return sb.String(), nil
212 }
213
214 +func toString(val interface{}) string {
215 + switch v := val.(type) {
216 + case nil:
217 + return ""
218 + case string:
219 + return v
220 + case fmt.Stringer:
221 + return v.String()
222 + case byte:
223 + return string(v)
224 + case []byte:
225 + return string(v)
226 + case []rune:
227 + return string(v)
228 + case int:
229 + return strconv.Itoa(v)
230 + case int32:
231 + return strconv.Itoa(int(v))
232 + case int64:
233 + return strconv.Itoa(int(v))
234 + case uint:
235 + return strconv.Itoa(int(v))
236 + case uint32:
237 + return strconv.Itoa(int(v))
238 + case uint64:
239 + return strconv.Itoa(int(v))
240 + case float32:
241 + return strconv.FormatFloat(float64(v), 'f', -1, 32)
242 + case float64:
243 + return strconv.FormatFloat(v, 'f', -1, 64)
244 + case bool:
245 + if v {
246 + return "true"
247 + }
248 + return "false"
249 + default:
250 + return fmt.Sprintf("%+v", v)
251 + }
252 +}
253 +
254 // ReplacerFunc is a function that returns a replacement
255 // for the given key along with true if the function is able
256 // to service that key (even if the value is blank). If the
257 // function does not recognize the key, false should be
258 // returned.
218 -type ReplacerFunc func(key string) (val string, ok bool)
259 +type ReplacerFunc func(key string) (interface{}, bool)
260
220 -func globalDefaultReplacements(key string) (string, bool) {
261 +func globalDefaultReplacements(key string) (interface{}, bool) {
262 // check environment variable
263 const envPrefix = "env."
264 if strings.HasPrefix(key, envPrefix) {
@@ -241,7 +282,7 @@ func globalDefaultReplacements(key string) (string, bool) {
282 return strconv.Itoa(nowFunc().Year()), true
283 }
284
244 - return "", false
285 + return nil, false
286 }
287
288 // ReplacementFunc is a function that is called when a
@@ -250,7 +291,7 @@ func globalDefaultReplacements(key string) (string, bool) {
291 // will be the replacement, and returns the value that
292 // will actually be the replacement, or an error. Note
293 // that errors are sometimes ignored by replacers.
253 -type ReplacementFunc func(variable, val string) (string, error)
294 +type ReplacementFunc func(variable string, val interface{}) (interface{}, error)
295
296 // nowFunc is a variable so tests can change it
297 // in order to obtain a deterministic time.
replacer_test.go
+50 -37
@@ -173,41 +173,12 @@ func TestReplacer(t *testing.T) {
173 }
174 }
175
176 -func BenchmarkReplacer(b *testing.B) {
177 - type testCase struct {
178 - name, input, empty string
179 - }
180 -
181 - rep := testReplacer()
182 -
183 - for _, bm := range []testCase{
184 - {
185 - name: "no placeholder",
186 - input: `simple string`,
187 - },
188 - {
189 - name: "placeholder",
190 - input: `{"json": "object"}`,
191 - },
192 - {
193 - name: "escaped placeholder",
194 - input: `\{"json": \{"nested": "{bar}"\}\}`,
195 - },
196 - } {
197 - b.Run(bm.name, func(b *testing.B) {
198 - for i := 0; i < b.N; i++ {
199 - rep.ReplaceAll(bm.input, bm.empty)
200 - }
201 - })
202 - }
203 -}
204 -
176 func TestReplacerSet(t *testing.T) {
177 rep := testReplacer()
178
179 for _, tc := range []struct {
180 variable string
210 - value string
181 + value interface{}
182 }{
183 {
184 variable: "test1",
@@ -217,6 +188,10 @@ func TestReplacerSet(t *testing.T) {
188 variable: "asdf",
189 value: "123",
190 },
191 + {
192 + variable: "numbers",
193 + value: 123.456,
194 + },
195 {
196 variable: "äöü",
197 value: "öö_äü",
@@ -252,7 +227,7 @@ func TestReplacerSet(t *testing.T) {
227
228 // test if all keys are still there (by length)
229 length := len(rep.static)
255 - if len(rep.static) != 7 {
230 + if len(rep.static) != 8 {
231 t.Errorf("Expected length '%v' got '%v'", 7, length)
232 }
233 }
@@ -261,7 +236,7 @@ func TestReplacerReplaceKnown(t *testing.T) {
236 rep := Replacer{
237 providers: []ReplacerFunc{
238 // split our possible vars to two functions (to test if both functions are called)
264 - func(key string) (val string, ok bool) {
239 + func(key string) (val interface{}, ok bool) {
240 switch key {
241 case "test1":
242 return "val1", true
@@ -275,7 +250,7 @@ func TestReplacerReplaceKnown(t *testing.T) {
250 return "NOOO", false
251 }
252 },
278 - func(key string) (val string, ok bool) {
253 + func(key string) (val interface{}, ok bool) {
254 switch key {
255 case "1":
256 return "test-123", true
@@ -331,7 +306,7 @@ func TestReplacerReplaceKnown(t *testing.T) {
306
307 func TestReplacerDelete(t *testing.T) {
308 rep := Replacer{
334 - static: map[string]string{
309 + static: map[string]interface{}{
310 "key1": "val1",
311 "key2": "val2",
312 "key3": "val3",
@@ -366,10 +341,10 @@ func TestReplacerMap(t *testing.T) {
341 rep := testReplacer()
342
343 for i, tc := range []ReplacerFunc{
369 - func(key string) (val string, ok bool) {
344 + func(key string) (val interface{}, ok bool) {
345 return "", false
346 },
372 - func(key string) (val string, ok bool) {
347 + func(key string) (val interface{}, ok bool) {
348 return "", false
349 },
350 } {
@@ -434,12 +409,50 @@ func TestReplacerNew(t *testing.T) {
409 }
410 }
411 }
412 +}
413
414 +func BenchmarkReplacer(b *testing.B) {
415 + type testCase struct {
416 + name, input, empty string
417 + }
418 +
419 + rep := testReplacer()
420 + rep.Set("str", "a string")
421 + rep.Set("int", 123.456)
422 +
423 + for _, bm := range []testCase{
424 + {
425 + name: "no placeholder",
426 + input: `simple string`,
427 + },
428 + {
429 + name: "string replacement",
430 + input: `str={str}`,
431 + },
432 + {
433 + name: "int replacement",
434 + input: `int={int}`,
435 + },
436 + {
437 + name: "placeholder",
438 + input: `{"json": "object"}`,
439 + },
440 + {
441 + name: "escaped placeholder",
442 + input: `\{"json": \{"nested": "{bar}"\}\}`,
443 + },
444 + } {
445 + b.Run(bm.name, func(b *testing.B) {
446 + for i := 0; i < b.N; i++ {
447 + rep.ReplaceAll(bm.input, bm.empty)
448 + }
449 + })
450 + }
451 }
452
453 func testReplacer() Replacer {
454 return Replacer{
455 providers: make([]ReplacerFunc, 0),
443 - static: make(map[string]string),
456 + static: make(map[string]interface{}),
457 }
458 }