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
| 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 |