@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 10, 22:30 UTC

master
go 546 lines 14.7 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 caddy
16
17 import (
18 "bytes"
19 "container/list"
20 "fmt"
21 "io"
22 "net/http"
23 "os"
24 "path/filepath"
25 "runtime"
26 "strconv"
27 "strings"
28 "sync"
29 "time"
30
31 "go.uber.org/zap"
32
33 "github.com/caddyserver/caddy/v2/internal/systemd"
34 )
35
36 // NewReplacer returns a new Replacer.
37 func NewReplacer() *Replacer {
38 rep := &Replacer{
39 static: make(map[string]any),
40 mapMutex: &sync.RWMutex{},
41 }
42 rep.providers = []replacementProvider{
43 globalDefaultReplacementProvider{},
44 fileReplacementProvider{},
45 ReplacerFunc(rep.fromStatic),
46 }
47 return rep
48 }
49
50 // NewEmptyReplacer returns a new Replacer,
51 // without the global default replacements.
52 func NewEmptyReplacer() *Replacer {
53 rep := &Replacer{
54 static: make(map[string]any),
55 mapMutex: &sync.RWMutex{},
56 }
57 rep.providers = []replacementProvider{
58 ReplacerFunc(rep.fromStatic),
59 }
60 return rep
61 }
62
63 // Replacer can replace values in strings.
64 // A default/empty Replacer is not valid;
65 // use NewReplacer to make one.
66 type Replacer struct {
67 providers []replacementProvider
68 static map[string]any
69 mapMutex *sync.RWMutex
70 }
71
72 // WithoutFile returns a copy of the current Replacer
73 // without support for the {file.*} placeholder, which
74 // may be unsafe in some contexts.
75 //
76 // EXPERIMENTAL: Subject to change or removal.
77 func (r *Replacer) WithoutFile() *Replacer {
78 rep := &Replacer{static: r.static}
79 for _, v := range r.providers {
80 if _, ok := v.(fileReplacementProvider); ok {
81 continue
82 }
83 rep.providers = append(rep.providers, v)
84 }
85 return rep
86 }
87
88 // Map adds mapFunc to the list of value providers.
89 // mapFunc will be executed only at replace-time.
90 func (r *Replacer) Map(mapFunc ReplacerFunc) {
91 r.providers = append(r.providers, mapFunc)
92 }
93
94 // Set sets a custom variable to a static value.
95 func (r *Replacer) Set(variable string, value any) {
96 r.mapMutex.Lock()
97 r.static[variable] = value
98 r.mapMutex.Unlock()
99 }
100
101 // Get gets a value from the replacer. It returns
102 // the value and whether the variable was known.
103 func (r *Replacer) Get(variable string) (any, bool) {
104 for _, mapFunc := range r.providers {
105 if val, ok := mapFunc.replace(variable); ok {
106 return val, true
107 }
108 }
109 return nil, false
110 }
111
112 // GetString is the same as Get, but coerces the value to a
113 // string representation as efficiently as possible.
114 func (r *Replacer) GetString(variable string) (string, bool) {
115 s, found := r.Get(variable)
116 return ToString(s), found
117 }
118
119 // Delete removes a variable with a static value
120 // that was created using Set.
121 func (r *Replacer) Delete(variable string) {
122 r.mapMutex.Lock()
123 delete(r.static, variable)
124 r.mapMutex.Unlock()
125 }
126
127 // DeleteByPrefix removes all static variables with
128 // keys starting with the given prefix
129 func (r *Replacer) DeleteByPrefix(prefix string) {
130 r.mapMutex.Lock()
131 for key := range r.static {
132 if strings.HasPrefix(key, prefix) {
133 delete(r.static, key)
134 }
135 }
136 r.mapMutex.Unlock()
137 }
138
139 // fromStatic provides values from r.static.
140 func (r *Replacer) fromStatic(key string) (any, bool) {
141 r.mapMutex.RLock()
142 defer r.mapMutex.RUnlock()
143 val, ok := r.static[key]
144 return val, ok
145 }
146
147 // ReplaceOrErr is like ReplaceAll, but any placeholders
148 // that are empty or not recognized will cause an error to
149 // be returned.
150 func (r *Replacer) ReplaceOrErr(input string, errOnEmpty, errOnUnknown bool) (string, error) {
151 return r.replace(input, "", false, errOnEmpty, errOnUnknown, nil)
152 }
153
154 // ReplaceKnown is like ReplaceAll but only replaces
155 // placeholders that are known (recognized). Unrecognized
156 // placeholders will remain in the output.
157 func (r *Replacer) ReplaceKnown(input, empty string) string {
158 out, _ := r.replace(input, empty, false, false, false, nil)
159 return out
160 }
161
162 // ReplaceAll efficiently replaces placeholders in input with
163 // their values. All placeholders are replaced in the output
164 // whether they are recognized or not. Values that are empty
165 // string will be substituted with empty.
166 func (r *Replacer) ReplaceAll(input, empty string) string {
167 out, _ := r.replace(input, empty, true, false, false, nil)
168 return out
169 }
170
171 // ReplaceFunc is the same as ReplaceAll, but calls f for every
172 // replacement to be made, in case f wants to change or inspect
173 // the replacement.
174 func (r *Replacer) ReplaceFunc(input string, f ReplacementFunc) (string, error) {
175 return r.replace(input, "", true, false, false, f)
176 }
177
178 func (r *Replacer) replace(input, empty string,
179 treatUnknownAsEmpty, errOnEmpty, errOnUnknown bool,
180 f ReplacementFunc,
181 ) (string, error) {
182 if !strings.Contains(input, string(phOpen)) && !strings.Contains(input, string(phClose)) {
183 return input, nil
184 }
185
186 var sb strings.Builder
187
188 // it is reasonable to assume that the output
189 // will be approximately as long as the input
190 sb.Grow(len(input))
191
192 // iterate the input to find each placeholder
193 var lastWriteCursor int
194
195 // fail fast if too many placeholders are unclosed
196 var unclosedCount int
197
198 // {file.*} lookups hit the disk, so repeats within one call are memoized
199 var files *fileCache
200
201 scan:
202 for i := 0; i < len(input); i++ {
203 // check for escaped braces
204 if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
205 sb.WriteString(input[lastWriteCursor : i-1])
206 lastWriteCursor = i
207 continue
208 }
209
210 if input[i] != phOpen {
211 continue
212 }
213
214 // our iterator is now on an unescaped open brace (start of placeholder)
215
216 // too many unclosed placeholders in absolutely ridiculous input can be extremely slow (issue #4170)
217 if unclosedCount > 100 {
218 return "", fmt.Errorf("too many unclosed placeholders")
219 }
220
221 // find the end of the placeholder
222 end := strings.Index(input[i:], string(phClose)) + i
223 if end < i {
224 unclosedCount++
225 continue
226 }
227
228 // if necessary look for the first closing brace that is not escaped
229 for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
230 nextEnd := strings.Index(input[end+1:], string(phClose))
231 if nextEnd < 0 {
232 unclosedCount++
233 continue scan
234 }
235 end += nextEnd + 1
236 }
237
238 // write the substring from the last cursor to this point
239 sb.WriteString(input[lastWriteCursor:i])
240
241 // trim opening bracket
242 key := input[i+1 : end]
243
244 // try to get a value for this key, handle empty values accordingly
245 var val any
246 var found bool
247 isFileKey := strings.HasPrefix(key, filePrefix)
248 if files == nil && isFileKey && r.hasFileProvider() {
249 files = new(fileCache)
250 }
251 if files != nil && isFileKey {
252 val, found = files.get(key, r.Get)
253 } else {
254 val, found = r.Get(key)
255 }
256 if !found {
257 // placeholder is unknown (unrecognized); handle accordingly
258 if errOnUnknown {
259 return "", fmt.Errorf("unrecognized placeholder %s%s%s",
260 string(phOpen), key, string(phClose))
261 } else if !treatUnknownAsEmpty {
262 // if treatUnknownAsEmpty is true, we'll handle an empty
263 // val later; so only continue otherwise
264 lastWriteCursor = i
265 continue
266 }
267 }
268
269 // apply any transformations
270 if f != nil {
271 var err error
272 val, err = f(key, val)
273 if err != nil {
274 return "", err
275 }
276 }
277
278 // convert val to a string as efficiently as possible
279 valStr := ToString(val)
280
281 // write the value; if it's empty, either return
282 // an error or write a default value
283 if valStr == "" {
284 if errOnEmpty {
285 return "", fmt.Errorf("evaluated placeholder %s%s%s is empty",
286 string(phOpen), key, string(phClose))
287 } else if empty != "" {
288 sb.WriteString(empty)
289 }
290 } else {
291 sb.WriteString(valStr)
292 }
293
294 // advance cursor to end of placeholder
295 i = end
296 lastWriteCursor = i + 1
297 }
298
299 // flush any unwritten remainder
300 sb.WriteString(input[lastWriteCursor:])
301
302 return sb.String(), nil
303 }
304
305 // ToString returns val as a string, as efficiently as possible.
306 // EXPERIMENTAL: may be changed or removed later.
307 func ToString(val any) string {
308 switch v := val.(type) {
309 case nil:
310 return ""
311 case string:
312 return v
313 case fmt.Stringer:
314 return v.String()
315 case error:
316 return v.Error()
317 case byte:
318 return string(v)
319 case []byte:
320 return string(v)
321 case []rune:
322 return string(v)
323 case int:
324 return strconv.Itoa(v)
325 case int32:
326 return strconv.Itoa(int(v))
327 case int64:
328 return strconv.Itoa(int(v))
329 case uint:
330 return strconv.FormatUint(uint64(v), 10)
331 case uint32:
332 return strconv.FormatUint(uint64(v), 10)
333 case uint64:
334 return strconv.FormatUint(v, 10)
335 case float32:
336 return strconv.FormatFloat(float64(v), 'f', -1, 32)
337 case float64:
338 return strconv.FormatFloat(v, 'f', -1, 64)
339 case bool:
340 if v {
341 return "true"
342 }
343 return "false"
344 default:
345 return fmt.Sprintf("%+v", v)
346 }
347 }
348
349 // ReplacerFunc is a function that returns a replacement for the
350 // given key along with true if the function is able to service
351 // that key (even if the value is blank). If the function does
352 // not recognize the key, false should be returned.
353 type ReplacerFunc func(key string) (any, bool)
354
355 func (f ReplacerFunc) replace(key string) (any, bool) {
356 return f(key)
357 }
358
359 // replacementProvider is a type that can provide replacements
360 // for placeholders. Allows for type assertion to determine
361 // which type of provider it is.
362 type replacementProvider interface {
363 replace(key string) (any, bool)
364 }
365
366 // fileReplacementProvider handles {file.*} replacements,
367 // reading a file from disk and replacing with its contents.
368 type fileReplacementProvider struct{}
369
370 func (f fileReplacementProvider) replace(key string) (any, bool) {
371 if !strings.HasPrefix(key, filePrefix) {
372 return nil, false
373 }
374
375 filename := key[len(filePrefix):]
376 maxSize := 1024 * 1024
377 body, err := readFileIntoBuffer(filename, maxSize)
378 if err != nil {
379 wd, _ := os.Getwd()
380 Log().Error("placeholder: failed to read file",
381 zap.String("file", filename),
382 zap.String("working_dir", wd),
383 zap.Error(err))
384 return nil, true
385 }
386 body = bytes.TrimSuffix(body, []byte("\n"))
387 body = bytes.TrimSuffix(body, []byte("\r"))
388 return string(body), true
389 }
390
391 func (r *Replacer) hasFileProvider() bool {
392 for _, p := range r.providers {
393 if _, ok := p.(fileReplacementProvider); ok {
394 return true
395 }
396 }
397 return false
398 }
399
400 // fileCache memoizes {file.*} lookups for one replace call. Contents live in a
401 // byte-bounded LRU; misses and empty results cost no more than their key.
402 type fileCache struct {
403 lru *list.List
404 entries map[string]*list.Element
405 size int
406 emptyResults map[string]any
407 }
408
409 type fileCacheEntry struct {
410 key string
411 val any
412 size int
413 }
414
415 func (c *fileCache) get(key string, load func(string) (any, bool)) (any, bool) {
416 if el, ok := c.entries[key]; ok {
417 c.lru.MoveToFront(el)
418 return el.Value.(*fileCacheEntry).val, true
419 }
420 if val, ok := c.emptyResults[key]; ok {
421 return val, true
422 }
423
424 val, found := load(key)
425 if !found {
426 return val, found
427 }
428 switch s, isStr := val.(string); {
429 case val == nil || (isStr && s == ""):
430 if c.emptyResults == nil {
431 c.emptyResults = make(map[string]any)
432 }
433 c.emptyResults[key] = val
434 case isStr && len(s) <= maxFileCacheBytes:
435 c.add(key, val, len(s))
436 }
437 return val, found
438 }
439
440 func (c *fileCache) add(key string, val any, size int) {
441 if c.lru == nil {
442 c.lru = list.New()
443 c.entries = make(map[string]*list.Element)
444 }
445 for c.size+size > maxFileCacheBytes {
446 evicted := c.lru.Remove(c.lru.Back()).(*fileCacheEntry)
447 delete(c.entries, evicted.key)
448 c.size -= evicted.size
449 }
450 c.entries[key] = c.lru.PushFront(&fileCacheEntry{key: key, val: val, size: size})
451 c.size += size
452 }
453
454 // globalDefaultReplacementProvider handles replacements
455 // that can be used in any context, such as system variables,
456 // time, or environment variables.
457 type globalDefaultReplacementProvider struct{}
458
459 func (f globalDefaultReplacementProvider) replace(key string) (any, bool) {
460 // check environment variable
461 const envPrefix = "env."
462 if strings.HasPrefix(key, envPrefix) {
463 return os.Getenv(key[len(envPrefix):]), true
464 }
465 if name, ok := strings.CutPrefix(key, systemdListenPrefix); ok {
466 descriptor, err := systemd.ListenFD(name)
467 if err != nil {
468 return nil, false
469 }
470 return descriptor, true
471 }
472
473 switch key {
474 case "system.hostname":
475 // OK if there is an error; just return empty string
476 name, _ := os.Hostname()
477 return name, true
478 case "system.slash":
479 return string(filepath.Separator), true
480 case "system.os":
481 return runtime.GOOS, true
482 case "system.wd":
483 // OK if there is an error; just return empty string
484 wd, _ := os.Getwd()
485 return wd, true
486 case "system.arch":
487 return runtime.GOARCH, true
488 case "time.now":
489 return nowFunc(), true
490 case "time.now.http":
491 // According to the comment for http.TimeFormat, the timezone must be in UTC
492 // to generate the correct format.
493 // https://github.com/caddyserver/caddy/issues/5773
494 return nowFunc().UTC().Format(http.TimeFormat), true
495 case "time.now.rfc3339":
496 return nowFunc().Format(time.RFC3339), true
497 case "time.now.common_log":
498 return nowFunc().Format("02/Jan/2006:15:04:05 -0700"), true
499 case "time.now.year":
500 return strconv.Itoa(nowFunc().Year()), true
501 case "time.now.unix":
502 return strconv.FormatInt(nowFunc().Unix(), 10), true
503 case "time.now.unix_ms":
504 return strconv.FormatInt(nowFunc().UnixNano()/int64(time.Millisecond), 10), true
505 }
506
507 return nil, false
508 }
509
510 // readFileIntoBuffer reads the file at filePath into a size limited buffer.
511 func readFileIntoBuffer(filename string, size int) ([]byte, error) {
512 file, err := os.Open(filename)
513 if err != nil {
514 return nil, err
515 }
516 defer file.Close()
517
518 // io.LimitReader ensures we never read more than 'size' bytes.
519 // io.ReadAll starts with a small buffer and grows it as needed,
520 // preventing a massive 1MB allocation for small files.
521 return io.ReadAll(io.LimitReader(file, int64(size)))
522 }
523
524 // ReplacementFunc is a function that is called when a
525 // replacement is being performed. It receives the
526 // variable (i.e. placeholder name) and the value that
527 // will be the replacement, and returns the value that
528 // will actually be the replacement, or an error. Note
529 // that errors are sometimes ignored by replacers.
530 type ReplacementFunc func(variable string, val any) (any, error)
531
532 // nowFunc is a variable so tests can change it
533 // in order to obtain a deterministic time.
534 var nowFunc = time.Now
535
536 // ReplacerCtxKey is the context key for a replacer.
537 const ReplacerCtxKey CtxKey = "replacer"
538
539 const phOpen, phClose, phEscape = '{', '}', '\\'
540
541 const filePrefix = "file."
542
543 const systemdListenPrefix = "systemd.listen."
544
545 // maxFileCacheBytes bounds the memoized file contents to 8 MiB (8,388,608 bytes) per replace call.
546 const maxFileCacheBytes = 8 << 20