├── caddyfile
├── testdata
│ ├── import_test1.txt
│ ├── import_test2.txt
│ ├── import_glob2.txt
│ ├── import_glob1.txt
│ └── import_glob0.txt
├── lexer.go
├── lexer_test.go
├── json_test.go
├── json.go
├── dispenser.go
├── dispenser_test.go
├── parse.go
└── parse_test.go
├── go.mod
├── .gitignore
├── go.sum
├── onevent
├── hook
│ ├── config.go
│ ├── hook.go
│ └── hook_test.go
├── on.go
└── on_test.go
├── sigtrap_nonposix.go
├── rlimit_nonposix.go
├── assets_test.go
├── rlimit_posix.go
├── .gitattributes
├── assets.go
├── sigtrap_posix.go
├── sigtrap.go
├── commands.go
├── controller.go
├── caddy_test.go
├── upgrade.go
├── commands_test.go
├── README.md
├── LICENSE.txt
├── plugins.go
└── caddy.go
/caddyfile/testdata/import_test1.txt:
--------------------------------------------------------------------------------
1 | dir2 arg1 arg2
2 | dir3
--------------------------------------------------------------------------------
/caddyfile/testdata/import_test2.txt:
--------------------------------------------------------------------------------
1 | host1 {
2 | dir1
3 | dir2 arg1
4 | }
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob2.txt:
--------------------------------------------------------------------------------
1 | glob2.host0 {
2 | dir2 arg1
3 | }
4 |
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob1.txt:
--------------------------------------------------------------------------------
1 | glob1.host0 {
2 | dir1
3 | dir2 arg1
4 | }
5 |
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob0.txt:
--------------------------------------------------------------------------------
1 | glob0.host0 {
2 | dir2 arg1
3 | }
4 |
5 | glob0.host1 {
6 | }
7 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/coredns/caddy
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568
7 | github.com/google/uuid v1.1.1
8 | )
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Thumbs.db
3 | _gitignore/
4 | Vagrantfile
5 | .vagrant/
6 | /.idea
7 |
8 | dist/builds/
9 | dist/release/
10 |
11 | error.log
12 | access.log
13 |
14 | /*.conf
15 | Caddyfile
16 | !caddyfile/
17 |
18 | og_static/
19 |
20 | .vscode/
21 |
22 | *.bat
23 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
2 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
3 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
4 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5 |
--------------------------------------------------------------------------------
/onevent/hook/config.go:
--------------------------------------------------------------------------------
1 | package hook
2 |
3 | import (
4 | "github.com/coredns/caddy"
5 | )
6 |
7 | // Config describes how Hook should be configured and used.
8 | type Config struct {
9 | ID string
10 | Event caddy.EventName
11 | Command string
12 | Args []string
13 | }
14 |
15 | // SupportedEvents is a map of supported events.
16 | var SupportedEvents = map[string]caddy.EventName{
17 | "startup": caddy.InstanceStartupEvent,
18 | "shutdown": caddy.ShutdownEvent,
19 | "certrenew": caddy.CertRenewEvent,
20 | }
21 |
--------------------------------------------------------------------------------
/sigtrap_nonposix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | // +build windows plan9 nacl js
16 |
17 | package caddy
18 |
19 | func trapSignalsPosix() {}
20 |
--------------------------------------------------------------------------------
/rlimit_nonposix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | // +build windows plan9 nacl js
16 |
17 | package caddy
18 |
19 | // checkFdlimit issues a warning if the OS limit for
20 | // max file descriptors is below a recommended minimum.
21 | func checkFdlimit() {
22 | }
23 |
--------------------------------------------------------------------------------
/onevent/hook/hook.go:
--------------------------------------------------------------------------------
1 | package hook
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/exec"
7 | "strings"
8 |
9 | "github.com/coredns/caddy"
10 | )
11 |
12 | // Hook executes a command.
13 | func (cfg *Config) Hook(event caddy.EventName, info interface{}) error {
14 | if event != cfg.Event {
15 | return nil
16 | }
17 |
18 | nonblock := false
19 | if len(cfg.Args) >= 1 && cfg.Args[len(cfg.Args)-1] == "&" {
20 | // Run command in background; non-blocking
21 | nonblock = true
22 | cfg.Args = cfg.Args[:len(cfg.Args)-1]
23 | }
24 |
25 | // Execute command.
26 | cmd := exec.Command(cfg.Command, cfg.Args...)
27 | cmd.Stdin = os.Stdin
28 | cmd.Stdout = os.Stdout
29 | cmd.Stderr = os.Stderr
30 | if nonblock {
31 | log.Printf("[INFO] Nonblocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID)
32 | return cmd.Start()
33 | }
34 | log.Printf("[INFO] Blocking Command \"%s %s\" with ID %s", cfg.Command, strings.Join(cfg.Args, " "), cfg.ID)
35 | err := cmd.Run()
36 | if err != nil {
37 | return err
38 | }
39 |
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/assets_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "os"
19 | "strings"
20 | "testing"
21 | )
22 |
23 | func TestAssetsPath(t *testing.T) {
24 | if actual := AssetsPath(); !strings.HasSuffix(actual, ".caddy") {
25 | t.Errorf("Expected path to be a .caddy folder, got: %v", actual)
26 | }
27 |
28 | err := os.Setenv("CADDYPATH", "testpath")
29 | if err != nil {
30 | t.Error("Could not set CADDYPATH")
31 | }
32 | if actual, expected := AssetsPath(), "testpath"; actual != expected {
33 | t.Errorf("Expected path to be %v, got: %v", expected, actual)
34 | }
35 | err = os.Setenv("CADDYPATH", "")
36 | if err != nil {
37 | t.Error("Could not set CADDYPATH")
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/rlimit_posix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | // +build !windows,!plan9,!nacl,!js
16 |
17 | package caddy
18 |
19 | import (
20 | "fmt"
21 | "syscall"
22 | )
23 |
24 | // checkFdlimit issues a warning if the OS limit for
25 | // max file descriptors is below a recommended minimum.
26 | func checkFdlimit() {
27 | const min = 8192
28 |
29 | // Warn if ulimit is too low for production sites
30 | rlimit := &syscall.Rlimit{}
31 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimit)
32 | if err == nil && rlimit.Cur < min {
33 | fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+
34 | "At least %d is recommended. Fix with `ulimit -n %d`.\n", rlimit.Cur, min, min)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # shell scripts should not use tabs to indent!
2 | *.bash text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
3 | *.sh text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
4 |
5 | # files for systemd (shell-similar)
6 | *.path text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
7 | *.service text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
8 | *.timer text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
9 |
10 | # go fmt will enforce this, but in case a user has not called "go fmt" allow GIT to catch this:
11 | *.go text eol=lf core.whitespace whitespace=indent-with-non-tab,trailing-space,tabwidth=4
12 | go.mod text eol=lf
13 | go.sum text eol=lf
14 |
15 | *.txt text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
16 | *.tpl text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
17 | *.htm text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
18 | *.html text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
19 | *.md text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
20 | *.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
21 | .git* text eol=auto core.whitespace whitespace=trailing-space
22 |
--------------------------------------------------------------------------------
/assets.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "os"
19 | "path/filepath"
20 | "runtime"
21 | )
22 |
23 | // AssetsPath returns the path to the folder
24 | // where the application may store data. If
25 | // CADDYPATH env variable is set, that value
26 | // is used. Otherwise, the path is the result
27 | // of evaluating "$HOME/.caddy".
28 | func AssetsPath() string {
29 | if caddyPath := os.Getenv("CADDYPATH"); caddyPath != "" {
30 | return caddyPath
31 | }
32 | return filepath.Join(userHomeDir(), ".caddy")
33 | }
34 |
35 | // userHomeDir returns the user's home directory according to
36 | // environment variables.
37 | //
38 | // Credit: http://stackoverflow.com/a/7922977/1048862
39 | func userHomeDir() string {
40 | if runtime.GOOS == "windows" {
41 | home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
42 | if home == "" {
43 | home = os.Getenv("USERPROFILE")
44 | }
45 | return home
46 | }
47 | return os.Getenv("HOME")
48 | }
49 |
--------------------------------------------------------------------------------
/onevent/on.go:
--------------------------------------------------------------------------------
1 | package onevent
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/coredns/caddy"
7 | "github.com/coredns/caddy/onevent/hook"
8 | "github.com/google/uuid"
9 | )
10 |
11 | func init() {
12 | // Register Directive.
13 | caddy.RegisterPlugin("on", caddy.Plugin{Action: setup})
14 | }
15 |
16 | func setup(c *caddy.Controller) error {
17 | config, err := onParse(c)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | // Register Event Hooks.
23 | err = c.OncePerServerBlock(func() error {
24 | for _, cfg := range config {
25 | caddy.RegisterEventHook("on-"+cfg.ID, cfg.Hook)
26 | }
27 | return nil
28 | })
29 | if err != nil {
30 | return err
31 | }
32 |
33 | return nil
34 | }
35 |
36 | func onParse(c *caddy.Controller) ([]*hook.Config, error) {
37 | var config []*hook.Config
38 |
39 | for c.Next() {
40 | cfg := new(hook.Config)
41 |
42 | if !c.NextArg() {
43 | return config, c.ArgErr()
44 | }
45 |
46 | // Configure Event.
47 | event, ok := hook.SupportedEvents[strings.ToLower(c.Val())]
48 | if !ok {
49 | return config, c.Errf("Wrong event name or event not supported: '%s'", c.Val())
50 | }
51 | cfg.Event = event
52 |
53 | // Assign an unique ID.
54 | cfg.ID = uuid.New().String()
55 |
56 | args := c.RemainingArgs()
57 |
58 | // Extract command and arguments.
59 | command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
60 | if err != nil {
61 | return config, c.Err(err.Error())
62 | }
63 |
64 | cfg.Command = command
65 | cfg.Args = args
66 |
67 | config = append(config, cfg)
68 | }
69 |
70 | return config, nil
71 | }
72 |
--------------------------------------------------------------------------------
/onevent/hook/hook_test.go:
--------------------------------------------------------------------------------
1 | package hook
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strconv"
7 | "testing"
8 | "time"
9 |
10 | "github.com/coredns/caddy"
11 | "github.com/google/uuid"
12 | )
13 |
14 | func TestHook(t *testing.T) {
15 | tempDirPath := os.TempDir()
16 |
17 | testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_command")
18 | defer func() {
19 | // clean up after non-blocking startup function quits
20 | time.Sleep(500 * time.Millisecond)
21 | os.RemoveAll(testDir)
22 | }()
23 | osSensitiveTestDir := filepath.FromSlash(testDir)
24 | os.RemoveAll(osSensitiveTestDir) // start with a clean slate
25 |
26 | tests := []struct {
27 | name string
28 | event caddy.EventName
29 | command string
30 | args []string
31 | shouldErr bool
32 | shouldRemoveErr bool
33 | }{
34 | {name: "blocking", event: caddy.InstanceStartupEvent, command: "mkdir", args: []string{osSensitiveTestDir}, shouldErr: false, shouldRemoveErr: false},
35 | {name: "nonBlocking", event: caddy.ShutdownEvent, command: "mkdir", args: []string{osSensitiveTestDir, "&"}, shouldErr: false, shouldRemoveErr: true},
36 | {name: "nonBlocking2", event: caddy.ShutdownEvent, command: "echo", args: []string{"&"}, shouldErr: false, shouldRemoveErr: true},
37 | {name: "nonExistent", event: caddy.CertRenewEvent, command: strconv.Itoa(int(time.Now().UnixNano())), shouldErr: true, shouldRemoveErr: true},
38 | }
39 |
40 | for _, test := range tests {
41 | t.Run(test.name, func(t *testing.T) {
42 | cfg := new(Config)
43 | cfg.ID = uuid.New().String()
44 | cfg.Event = test.event
45 | cfg.Command = test.command
46 | cfg.Args = test.args
47 |
48 | err := cfg.Hook(test.event, nil)
49 | if err == nil && test.shouldErr {
50 | t.Error("Test didn't error, but it should have")
51 | } else if err != nil && !test.shouldErr {
52 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err)
53 | }
54 |
55 | err = os.Remove(osSensitiveTestDir)
56 | if err != nil && !test.shouldRemoveErr {
57 | t.Errorf("Test received an error of:\n%v", err)
58 | }
59 | })
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/onevent/on_test.go:
--------------------------------------------------------------------------------
1 | package onevent
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/coredns/caddy"
7 | "github.com/coredns/caddy/onevent/hook"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | tests := []struct {
12 | name string
13 | input string
14 | shouldErr bool
15 | }{
16 | {name: "noInput", input: "on", shouldErr: true},
17 | {name: "nonExistent", input: "on xyz cmd arg", shouldErr: true},
18 | {name: "startup", input: "on startup cmd arg", shouldErr: false},
19 | {name: "shutdown", input: "on shutdown cmd arg &", shouldErr: false},
20 | {name: "certrenew", input: "on certrenew cmd arg", shouldErr: false},
21 | }
22 |
23 | for _, test := range tests {
24 | t.Run(test.name, func(t *testing.T) {
25 | c := caddy.NewTestController("http", test.input)
26 | c.Key = test.name
27 |
28 | err := setup(c)
29 |
30 | if err == nil && test.shouldErr {
31 | t.Error("Test didn't error, but it should have")
32 | } else if err != nil && !test.shouldErr {
33 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err)
34 | }
35 | })
36 | }
37 | }
38 |
39 | func TestCommandParse(t *testing.T) {
40 | tests := []struct {
41 | name string
42 | input string
43 | shouldErr bool
44 | config hook.Config
45 | }{
46 | {name: "noInput", input: `on`, shouldErr: true},
47 | {name: "nonExistent", input: "on xyz cmd arg", shouldErr: true},
48 | {name: "startup", input: `on startup cmd arg1 arg2`, shouldErr: false, config: hook.Config{Event: caddy.InstanceStartupEvent, Command: "cmd", Args: []string{"arg1", "arg2"}}},
49 | {name: "shutdown", input: `on shutdown cmd arg1 arg2 &`, shouldErr: false, config: hook.Config{Event: caddy.ShutdownEvent, Command: "cmd", Args: []string{"arg1", "arg2", "&"}}},
50 | {name: "certrenew", input: `on certrenew cmd arg1 arg2`, shouldErr: false, config: hook.Config{Event: caddy.CertRenewEvent, Command: "cmd", Args: []string{"arg1", "arg2"}}},
51 | }
52 |
53 | for _, test := range tests {
54 | t.Run(test.name, func(t *testing.T) {
55 | config, err := onParse(caddy.NewTestController("http", test.input))
56 |
57 | if err == nil && test.shouldErr {
58 | t.Error("Test didn't error, but it should have")
59 | } else if err != nil && !test.shouldErr {
60 | t.Errorf("Test errored, but it shouldn't have; got '%v'", err)
61 | }
62 |
63 | for _, cfg := range config {
64 | if cfg.Event != test.config.Event {
65 | t.Errorf("Expected event %s; got %s", test.config.Event, cfg.Event)
66 | }
67 |
68 | if cfg.Command != test.config.Command {
69 | t.Errorf("Expected command %s; got %s", test.config.Command, cfg.Command)
70 | }
71 |
72 | for i, arg := range cfg.Args {
73 | if arg != test.config.Args[i] {
74 | t.Errorf("Expected arg in position %d to be %s, got %s", i, test.config.Args[i], arg)
75 | }
76 | }
77 |
78 | }
79 | })
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/sigtrap_posix.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | // +build !windows,!plan9,!nacl,!js
16 |
17 | package caddy
18 |
19 | import (
20 | "log"
21 | "os"
22 | "os/signal"
23 | "syscall"
24 | )
25 |
26 | // trapSignalsPosix captures POSIX-only signals.
27 | func trapSignalsPosix() {
28 | go func() {
29 | sigchan := make(chan os.Signal, 1)
30 | signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
31 |
32 | for sig := range sigchan {
33 | switch sig {
34 | case syscall.SIGQUIT:
35 | log.Println("[INFO] SIGQUIT: Quitting process immediately")
36 | for _, f := range OnProcessExit {
37 | f() // only perform important cleanup actions
38 | }
39 | os.Exit(0)
40 |
41 | case syscall.SIGTERM:
42 | log.Println("[INFO] SIGTERM: Shutting down servers then terminating")
43 | exitCode := executeShutdownCallbacks("SIGTERM")
44 | for _, f := range OnProcessExit {
45 | f() // only perform important cleanup actions
46 | }
47 | err := Stop()
48 | if err != nil {
49 | log.Printf("[ERROR] SIGTERM stop: %v", err)
50 | exitCode = 3
51 | }
52 |
53 | os.Exit(exitCode)
54 |
55 | case syscall.SIGUSR1:
56 | log.Println("[INFO] SIGUSR1: Reloading")
57 |
58 | // Start with the existing Caddyfile
59 | caddyfileToUse, inst, err := getCurrentCaddyfile()
60 | if err != nil {
61 | log.Printf("[ERROR] SIGUSR1: %v", err)
62 | continue
63 | }
64 | if loaderUsed.loader == nil {
65 | // This also should never happen
66 | log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile")
67 | continue
68 | }
69 |
70 | // Load the updated Caddyfile
71 | newCaddyfile, err := loaderUsed.loader.Load(inst.serverType)
72 | if err != nil {
73 | log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err)
74 | continue
75 | }
76 | if newCaddyfile != nil {
77 | caddyfileToUse = newCaddyfile
78 | }
79 |
80 | // Backup old event hooks
81 | oldEventHooks := cloneEventHooks()
82 |
83 | // Purge the old event hooks
84 | purgeEventHooks()
85 |
86 | // Kick off the restart; our work is done
87 | EmitEvent(InstanceRestartEvent, nil)
88 | _, err = inst.Restart(caddyfileToUse)
89 | if err != nil {
90 | restoreEventHooks(oldEventHooks)
91 |
92 | log.Printf("[ERROR] SIGUSR1: %v", err)
93 | }
94 |
95 | case syscall.SIGUSR2:
96 | log.Println("[INFO] SIGUSR2: Upgrading")
97 | if err := Upgrade(); err != nil {
98 | log.Printf("[ERROR] SIGUSR2: upgrading: %v", err)
99 | }
100 |
101 | case syscall.SIGHUP:
102 | // ignore; this signal is sometimes sent outside of the user's control
103 | }
104 | }
105 | }()
106 | }
107 |
--------------------------------------------------------------------------------
/sigtrap.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "log"
19 | "os"
20 | "os/signal"
21 | "sync"
22 | )
23 |
24 | // TrapSignals create signal handlers for all applicable signals for this
25 | // system. If your Go program uses signals, this is a rather invasive
26 | // function; best to implement them yourself in that case. Signals are not
27 | // required for the caddy package to function properly, but this is a
28 | // convenient way to allow the user to control this part of your program.
29 | func TrapSignals() {
30 | trapSignalsCrossPlatform()
31 | trapSignalsPosix()
32 | }
33 |
34 | // trapSignalsCrossPlatform captures SIGINT, which triggers forceful
35 | // shutdown that executes shutdown callbacks first. A second interrupt
36 | // signal will exit the process immediately.
37 | func trapSignalsCrossPlatform() {
38 | go func() {
39 | shutdown := make(chan os.Signal, 1)
40 | signal.Notify(shutdown, os.Interrupt)
41 |
42 | for i := 0; true; i++ {
43 | <-shutdown
44 |
45 | if i > 0 {
46 | log.Println("[INFO] SIGINT: Force quit")
47 | for _, f := range OnProcessExit {
48 | f() // important cleanup actions only
49 | }
50 | os.Exit(2)
51 | }
52 |
53 | log.Println("[INFO] SIGINT: Shutting down")
54 |
55 | // important cleanup actions before shutdown callbacks
56 | for _, f := range OnProcessExit {
57 | f()
58 | }
59 |
60 | go func() {
61 | os.Exit(executeShutdownCallbacks("SIGINT"))
62 | }()
63 | }
64 | }()
65 | }
66 |
67 | // executeShutdownCallbacks executes the shutdown callbacks as initiated
68 | // by signame. It logs any errors and returns the recommended exit status.
69 | // This function is idempotent; subsequent invocations always return 0.
70 | func executeShutdownCallbacks(signame string) (exitCode int) {
71 | shutdownCallbacksOnce.Do(func() {
72 | // execute third-party shutdown hooks
73 | EmitEvent(ShutdownEvent, signame)
74 |
75 | errs := allShutdownCallbacks()
76 | if len(errs) > 0 {
77 | for _, err := range errs {
78 | log.Printf("[ERROR] %s shutdown: %v", signame, err)
79 | }
80 | exitCode = 4
81 | }
82 | })
83 | return
84 | }
85 |
86 | // allShutdownCallbacks executes all the shutdown callbacks
87 | // for all the instances, and returns all the errors generated
88 | // during their execution. An error executing one shutdown
89 | // callback does not stop execution of others. Only one shutdown
90 | // callback is executed at a time.
91 | func allShutdownCallbacks() []error {
92 | var errs []error
93 | instancesMu.Lock()
94 | for _, inst := range instances {
95 | errs = append(errs, inst.ShutdownCallbacks()...)
96 | }
97 | instancesMu.Unlock()
98 | return errs
99 | }
100 |
101 | // shutdownCallbacksOnce ensures that shutdown callbacks
102 | // for all instances are only executed once.
103 | var shutdownCallbacksOnce sync.Once
104 |
--------------------------------------------------------------------------------
/caddyfile/lexer.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "bufio"
19 | "io"
20 | "unicode"
21 | )
22 |
23 | type (
24 | // lexer is a utility which can get values, token by
25 | // token, from a Reader. A token is a word, and tokens
26 | // are separated by whitespace. A word can be enclosed
27 | // in quotes if it contains whitespace.
28 | lexer struct {
29 | reader *bufio.Reader
30 | token Token
31 | line int
32 | }
33 |
34 | // Token represents a single parsable unit.
35 | Token struct {
36 | File string
37 | Line int
38 | Text string
39 | }
40 | )
41 |
42 | // load prepares the lexer to scan an input for tokens.
43 | // It discards any leading byte order mark.
44 | func (l *lexer) load(input io.Reader) error {
45 | l.reader = bufio.NewReader(input)
46 | l.line = 1
47 |
48 | // discard byte order mark, if present
49 | firstCh, _, err := l.reader.ReadRune()
50 | if err != nil {
51 | if err == io.EOF {
52 | return nil
53 | }
54 | return err
55 | }
56 | if firstCh != 0xFEFF {
57 | err := l.reader.UnreadRune()
58 | if err != nil {
59 | return err
60 | }
61 | }
62 |
63 | return nil
64 | }
65 |
66 | // next loads the next token into the lexer.
67 | // A token is delimited by whitespace, unless
68 | // the token starts with a quotes character (")
69 | // in which case the token goes until the closing
70 | // quotes (the enclosing quotes are not included).
71 | // Inside quoted strings, quotes may be escaped
72 | // with a preceding \ character. No other chars
73 | // may be escaped. The rest of the line is skipped
74 | // if a "#" character is read in. Returns true if
75 | // a token was loaded; false otherwise.
76 | func (l *lexer) next() bool {
77 | var val []rune
78 | var comment, quoted, escaped bool
79 |
80 | makeToken := func() bool {
81 | l.token.Text = string(val)
82 | return true
83 | }
84 |
85 | for {
86 | ch, _, err := l.reader.ReadRune()
87 | if err != nil {
88 | if len(val) > 0 {
89 | return makeToken()
90 | }
91 | if err == io.EOF {
92 | return false
93 | }
94 | panic(err)
95 | }
96 |
97 | if quoted {
98 | if !escaped {
99 | if ch == '\\' {
100 | escaped = true
101 | continue
102 | } else if ch == '"' {
103 | quoted = false
104 | return makeToken()
105 | }
106 | }
107 | if ch == '\n' {
108 | l.line++
109 | }
110 | if escaped {
111 | // only escape quotes
112 | if ch != '"' {
113 | val = append(val, '\\')
114 | }
115 | }
116 | val = append(val, ch)
117 | escaped = false
118 | continue
119 | }
120 |
121 | if unicode.IsSpace(ch) {
122 | if ch == '\r' {
123 | continue
124 | }
125 | if ch == '\n' {
126 | l.line++
127 | comment = false
128 | }
129 | if len(val) > 0 {
130 | return makeToken()
131 | }
132 | continue
133 | }
134 |
135 | if ch == '#' {
136 | comment = true
137 | }
138 |
139 | if comment {
140 | continue
141 | }
142 |
143 | if len(val) == 0 {
144 | l.token = Token{Line: l.line}
145 | if ch == '"' {
146 | quoted = true
147 | continue
148 | }
149 | }
150 |
151 | val = append(val, ch)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/commands.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "errors"
19 | "runtime"
20 | "unicode"
21 |
22 | "github.com/flynn/go-shlex"
23 | )
24 |
25 | var runtimeGoos = runtime.GOOS
26 |
27 | // SplitCommandAndArgs takes a command string and parses it shell-style into the
28 | // command and its separate arguments.
29 | func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
30 | var parts []string
31 |
32 | if runtimeGoos == "windows" {
33 | parts = parseWindowsCommand(command) // parse it Windows-style
34 | } else {
35 | parts, err = parseUnixCommand(command) // parse it Unix-style
36 | if err != nil {
37 | err = errors.New("error parsing command: " + err.Error())
38 | return
39 | }
40 | }
41 |
42 | if len(parts) == 0 {
43 | err = errors.New("no command contained in '" + command + "'")
44 | return
45 | }
46 |
47 | cmd = parts[0]
48 | if len(parts) > 1 {
49 | args = parts[1:]
50 | }
51 |
52 | return
53 | }
54 |
55 | // parseUnixCommand parses a unix style command line and returns the
56 | // command and its arguments or an error
57 | func parseUnixCommand(cmd string) ([]string, error) {
58 | return shlex.Split(cmd)
59 | }
60 |
61 | // parseWindowsCommand parses windows command lines and
62 | // returns the command and the arguments as an array. It
63 | // should be able to parse commonly used command lines.
64 | // Only basic syntax is supported:
65 | // - spaces in double quotes are not token delimiters
66 | // - double quotes are escaped by either backspace or another double quote
67 | // - except for the above case backspaces are path separators (not special)
68 | //
69 | // Many sources point out that escaping quotes using backslash can be unsafe.
70 | // Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
71 | //
72 | // This function has to be used on Windows instead
73 | // of the shlex package because this function treats backslash
74 | // characters properly.
75 | func parseWindowsCommand(cmd string) []string {
76 | const backslash = '\\'
77 | const quote = '"'
78 |
79 | var parts []string
80 | var part string
81 | var inQuotes bool
82 | var lastRune rune
83 |
84 | for i, ch := range cmd {
85 |
86 | if i != 0 {
87 | lastRune = rune(cmd[i-1])
88 | }
89 |
90 | if ch == backslash {
91 | // put it in the part - for now we don't know if it's an
92 | // escaping char or path separator
93 | part += string(ch)
94 | continue
95 | }
96 |
97 | if ch == quote {
98 | if lastRune == backslash {
99 | // remove the backslash from the part and add the escaped quote instead
100 | part = part[:len(part)-1]
101 | part += string(ch)
102 | continue
103 | }
104 |
105 | if lastRune == quote {
106 | // revert the last change of the inQuotes state
107 | // it was an escaping quote
108 | inQuotes = !inQuotes
109 | part += string(ch)
110 | continue
111 | }
112 |
113 | // normal escaping quotes
114 | inQuotes = !inQuotes
115 | continue
116 |
117 | }
118 |
119 | if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
120 | parts = append(parts, part)
121 | part = ""
122 | continue
123 | }
124 |
125 | part += string(ch)
126 | }
127 |
128 | if len(part) > 0 {
129 | parts = append(parts, part)
130 | }
131 |
132 | return parts
133 | }
134 |
--------------------------------------------------------------------------------
/caddyfile/lexer_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "log"
19 | "strings"
20 | "testing"
21 | )
22 |
23 | type lexerTestCase struct {
24 | input string
25 | expected []Token
26 | }
27 |
28 | func TestLexer(t *testing.T) {
29 | testCases := []lexerTestCase{
30 | {
31 | input: `host:123`,
32 | expected: []Token{
33 | {Line: 1, Text: "host:123"},
34 | },
35 | },
36 | {
37 | input: `host:123
38 |
39 | directive`,
40 | expected: []Token{
41 | {Line: 1, Text: "host:123"},
42 | {Line: 3, Text: "directive"},
43 | },
44 | },
45 | {
46 | input: `host:123 {
47 | directive
48 | }`,
49 | expected: []Token{
50 | {Line: 1, Text: "host:123"},
51 | {Line: 1, Text: "{"},
52 | {Line: 2, Text: "directive"},
53 | {Line: 3, Text: "}"},
54 | },
55 | },
56 | {
57 | input: `host:123 { directive }`,
58 | expected: []Token{
59 | {Line: 1, Text: "host:123"},
60 | {Line: 1, Text: "{"},
61 | {Line: 1, Text: "directive"},
62 | {Line: 1, Text: "}"},
63 | },
64 | },
65 | {
66 | input: `host:123 {
67 | #comment
68 | directive
69 | # comment
70 | foobar # another comment
71 | }`,
72 | expected: []Token{
73 | {Line: 1, Text: "host:123"},
74 | {Line: 1, Text: "{"},
75 | {Line: 3, Text: "directive"},
76 | {Line: 5, Text: "foobar"},
77 | {Line: 6, Text: "}"},
78 | },
79 | },
80 | {
81 | input: `a "quoted value" b
82 | foobar`,
83 | expected: []Token{
84 | {Line: 1, Text: "a"},
85 | {Line: 1, Text: "quoted value"},
86 | {Line: 1, Text: "b"},
87 | {Line: 2, Text: "foobar"},
88 | },
89 | },
90 | {
91 | input: `A "quoted \"value\" inside" B`,
92 | expected: []Token{
93 | {Line: 1, Text: "A"},
94 | {Line: 1, Text: `quoted "value" inside`},
95 | {Line: 1, Text: "B"},
96 | },
97 | },
98 | {
99 | input: `"don't\escape"`,
100 | expected: []Token{
101 | {Line: 1, Text: `don't\escape`},
102 | },
103 | },
104 | {
105 | input: `"don't\\escape"`,
106 | expected: []Token{
107 | {Line: 1, Text: `don't\\escape`},
108 | },
109 | },
110 | {
111 | input: `A "quoted value with line
112 | break inside" {
113 | foobar
114 | }`,
115 | expected: []Token{
116 | {Line: 1, Text: "A"},
117 | {Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},
118 | {Line: 2, Text: "{"},
119 | {Line: 3, Text: "foobar"},
120 | {Line: 4, Text: "}"},
121 | },
122 | },
123 | {
124 | input: `"C:\php\php-cgi.exe"`,
125 | expected: []Token{
126 | {Line: 1, Text: `C:\php\php-cgi.exe`},
127 | },
128 | },
129 | {
130 | input: `empty "" string`,
131 | expected: []Token{
132 | {Line: 1, Text: `empty`},
133 | {Line: 1, Text: ``},
134 | {Line: 1, Text: `string`},
135 | },
136 | },
137 | {
138 | input: "skip those\r\nCR characters",
139 | expected: []Token{
140 | {Line: 1, Text: "skip"},
141 | {Line: 1, Text: "those"},
142 | {Line: 2, Text: "CR"},
143 | {Line: 2, Text: "characters"},
144 | },
145 | },
146 | {
147 | input: "\xEF\xBB\xBF:8080", // test with leading byte order mark
148 | expected: []Token{
149 | {Line: 1, Text: ":8080"},
150 | },
151 | },
152 | }
153 |
154 | for i, testCase := range testCases {
155 | actual := tokenize(testCase.input)
156 | lexerCompare(t, i, testCase.expected, actual)
157 | }
158 | }
159 |
160 | func tokenize(input string) (tokens []Token) {
161 | l := lexer{}
162 | if err := l.load(strings.NewReader(input)); err != nil {
163 | log.Printf("[ERROR] load failed: %v", err)
164 | }
165 | for l.next() {
166 | tokens = append(tokens, l.token)
167 | }
168 | return
169 | }
170 |
171 | func lexerCompare(t *testing.T, n int, expected, actual []Token) {
172 | if len(expected) != len(actual) {
173 | t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
174 | }
175 |
176 | for i := 0; i < len(actual) && i < len(expected); i++ {
177 | if actual[i].Line != expected[i].Line {
178 | t.Errorf("Test case %d token %d ('%s'): expected line %d but was line %d",
179 | n, i, expected[i].Text, expected[i].Line, actual[i].Line)
180 | break
181 | }
182 | if actual[i].Text != expected[i].Text {
183 | t.Errorf("Test case %d token %d: expected text '%s' but was '%s'",
184 | n, i, expected[i].Text, actual[i].Text)
185 | break
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/caddyfile/json_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import "testing"
18 |
19 | var tests = []struct {
20 | caddyfile, json string
21 | }{
22 | { // 0
23 | caddyfile: `foo {
24 | root /bar
25 | }`,
26 | json: `[{"keys":["foo"],"body":[["root","/bar"]]}]`,
27 | },
28 | { // 1
29 | caddyfile: `host1, host2 {
30 | dir {
31 | def
32 | }
33 | }`,
34 | json: `[{"keys":["host1","host2"],"body":[["dir",[["def"]]]]}]`,
35 | },
36 | { // 2
37 | caddyfile: `host1, host2 {
38 | dir abc {
39 | def ghi
40 | jkl
41 | }
42 | }`,
43 | json: `[{"keys":["host1","host2"],"body":[["dir","abc",[["def","ghi"],["jkl"]]]]}]`,
44 | },
45 | { // 3
46 | caddyfile: `host1:1234, host2:5678 {
47 | dir abc {
48 | }
49 | }`,
50 | json: `[{"keys":["host1:1234","host2:5678"],"body":[["dir","abc",[]]]}]`,
51 | },
52 | { // 4
53 | caddyfile: `host {
54 | foo "bar baz"
55 | }`,
56 | json: `[{"keys":["host"],"body":[["foo","bar baz"]]}]`,
57 | },
58 | { // 5
59 | caddyfile: `host, host:80 {
60 | foo "bar \"baz\""
61 | }`,
62 | json: `[{"keys":["host","host:80"],"body":[["foo","bar \"baz\""]]}]`,
63 | },
64 | { // 6
65 | caddyfile: `host {
66 | foo "bar
67 | baz"
68 | }`,
69 | json: `[{"keys":["host"],"body":[["foo","bar\nbaz"]]}]`,
70 | },
71 | { // 7
72 | caddyfile: `host {
73 | dir 123 4.56 true
74 | }`,
75 | json: `[{"keys":["host"],"body":[["dir","123","4.56","true"]]}]`, // NOTE: I guess we assume numbers and booleans should be encoded as strings...?
76 | },
77 | { // 8
78 | caddyfile: `http://host, https://host {
79 | }`,
80 | json: `[{"keys":["http://host","https://host"],"body":[]}]`, // hosts in JSON are always host:port format (if port is specified), for consistency
81 | },
82 | { // 9
83 | caddyfile: `host {
84 | dir1 a b
85 | dir2 c d
86 | }`,
87 | json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2","c","d"]]}]`,
88 | },
89 | { // 10
90 | caddyfile: `host {
91 | dir a b
92 | dir c d
93 | }`,
94 | json: `[{"keys":["host"],"body":[["dir","a","b"],["dir","c","d"]]}]`,
95 | },
96 | { // 11
97 | caddyfile: `host {
98 | dir1 a b
99 | dir2 {
100 | c
101 | d
102 | }
103 | }`,
104 | json: `[{"keys":["host"],"body":[["dir1","a","b"],["dir2",[["c"],["d"]]]]}]`,
105 | },
106 | { // 12
107 | caddyfile: `host1 {
108 | dir1
109 | }
110 |
111 | host2 {
112 | dir2
113 | }`,
114 | json: `[{"keys":["host1"],"body":[["dir1"]]},{"keys":["host2"],"body":[["dir2"]]}]`,
115 | },
116 | }
117 |
118 | func TestToJSON(t *testing.T) {
119 | for i, test := range tests {
120 | output, err := ToJSON([]byte(test.caddyfile))
121 | if err != nil {
122 | t.Errorf("Test %d: %v", i, err)
123 | }
124 | if string(output) != test.json {
125 | t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.json, string(output))
126 | }
127 | }
128 | }
129 |
130 | func TestFromJSON(t *testing.T) {
131 | for i, test := range tests {
132 | output, err := FromJSON([]byte(test.json))
133 | if err != nil {
134 | t.Errorf("Test %d: %v", i, err)
135 | }
136 | if string(output) != test.caddyfile {
137 | t.Errorf("Test %d\nExpected:\n'%s'\nActual:\n'%s'", i, test.caddyfile, string(output))
138 | }
139 | }
140 | }
141 |
142 | // TODO: Will these tests come in handy somewhere else?
143 | /*
144 | func TestStandardizeAddress(t *testing.T) {
145 | // host:https should be converted to https://host
146 | output, err := ToJSON([]byte(`host:https`))
147 | if err != nil {
148 | t.Fatal(err)
149 | }
150 | if expected, actual := `[{"keys":["https://host"],"body":[]}]`, string(output); expected != actual {
151 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
152 | }
153 |
154 | output, err = FromJSON([]byte(`[{"keys":["https://host"],"body":[]}]`))
155 | if err != nil {
156 | t.Fatal(err)
157 | }
158 | if expected, actual := "https://host {\n}", string(output); expected != actual {
159 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
160 | }
161 |
162 | // host: should be converted to just host
163 | output, err = ToJSON([]byte(`host:`))
164 | if err != nil {
165 | t.Fatal(err)
166 | }
167 | if expected, actual := `[{"keys":["host"],"body":[]}]`, string(output); expected != actual {
168 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
169 | }
170 | output, err = FromJSON([]byte(`[{"keys":["host:"],"body":[]}]`))
171 | if err != nil {
172 | t.Fatal(err)
173 | }
174 | if expected, actual := "host {\n}", string(output); expected != actual {
175 | t.Errorf("Expected:\n'%s'\nActual:\n'%s'", expected, actual)
176 | }
177 | }
178 | */
179 |
--------------------------------------------------------------------------------
/caddyfile/json.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "bytes"
19 | "encoding/json"
20 | "fmt"
21 | "sort"
22 | "strconv"
23 | "strings"
24 | )
25 |
26 | const filename = "Caddyfile"
27 |
28 | // ToJSON converts caddyfile to its JSON representation.
29 | func ToJSON(caddyfile []byte) ([]byte, error) {
30 | var j EncodedCaddyfile
31 |
32 | serverBlocks, err := Parse(filename, bytes.NewReader(caddyfile), nil)
33 | if err != nil {
34 | return nil, err
35 | }
36 |
37 | for _, sb := range serverBlocks {
38 | block := EncodedServerBlock{
39 | Keys: sb.Keys,
40 | Body: [][]interface{}{},
41 | }
42 |
43 | // Extract directives deterministically by sorting them
44 | var directives = make([]string, len(sb.Tokens))
45 | for dir := range sb.Tokens {
46 | directives = append(directives, dir)
47 | }
48 | sort.Strings(directives)
49 |
50 | // Convert each directive's tokens into our JSON structure
51 | for _, dir := range directives {
52 | disp := NewDispenserTokens(filename, sb.Tokens[dir])
53 | for disp.Next() {
54 | block.Body = append(block.Body, constructLine(&disp))
55 | }
56 | }
57 |
58 | // tack this block onto the end of the list
59 | j = append(j, block)
60 | }
61 |
62 | result, err := json.Marshal(j)
63 | if err != nil {
64 | return nil, err
65 | }
66 |
67 | return result, nil
68 | }
69 |
70 | // constructLine transforms tokens into a JSON-encodable structure;
71 | // but only one line at a time, to be used at the top-level of
72 | // a server block only (where the first token on each line is a
73 | // directive) - not to be used at any other nesting level.
74 | func constructLine(d *Dispenser) []interface{} {
75 | var args []interface{}
76 |
77 | args = append(args, d.Val())
78 |
79 | for d.NextArg() {
80 | if d.Val() == "{" {
81 | args = append(args, constructBlock(d))
82 | continue
83 | }
84 | args = append(args, d.Val())
85 | }
86 |
87 | return args
88 | }
89 |
90 | // constructBlock recursively processes tokens into a
91 | // JSON-encodable structure. To be used in a directive's
92 | // block. Goes to end of block.
93 | func constructBlock(d *Dispenser) [][]interface{} {
94 | block := [][]interface{}{}
95 |
96 | for d.Next() {
97 | if d.Val() == "}" {
98 | break
99 | }
100 | block = append(block, constructLine(d))
101 | }
102 |
103 | return block
104 | }
105 |
106 | // FromJSON converts JSON-encoded jsonBytes to Caddyfile text
107 | func FromJSON(jsonBytes []byte) ([]byte, error) {
108 | var j EncodedCaddyfile
109 | var result string
110 |
111 | err := json.Unmarshal(jsonBytes, &j)
112 | if err != nil {
113 | return nil, err
114 | }
115 |
116 | for sbPos, sb := range j {
117 | if sbPos > 0 {
118 | result += "\n\n"
119 | }
120 | for i, key := range sb.Keys {
121 | if i > 0 {
122 | result += ", "
123 | }
124 | //result += standardizeScheme(key)
125 | result += key
126 | }
127 | result += jsonToText(sb.Body, 1)
128 | }
129 |
130 | return []byte(result), nil
131 | }
132 |
133 | // jsonToText recursively transforms a scope of JSON into plain
134 | // Caddyfile text.
135 | func jsonToText(scope interface{}, depth int) string {
136 | var result string
137 |
138 | switch val := scope.(type) {
139 | case string:
140 | if strings.ContainsAny(val, "\" \n\t\r") {
141 | result += `"` + strings.Replace(val, "\"", "\\\"", -1) + `"`
142 | } else {
143 | result += val
144 | }
145 | case int:
146 | result += strconv.Itoa(val)
147 | case float64:
148 | result += fmt.Sprintf("%v", val)
149 | case bool:
150 | result += fmt.Sprintf("%t", val)
151 | case [][]interface{}:
152 | result += " {\n"
153 | for _, arg := range val {
154 | result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
155 | }
156 | result += strings.Repeat("\t", depth-1) + "}"
157 | case []interface{}:
158 | for i, v := range val {
159 | if block, ok := v.([]interface{}); ok {
160 | result += "{\n"
161 | for _, arg := range block {
162 | result += strings.Repeat("\t", depth) + jsonToText(arg, depth+1) + "\n"
163 | }
164 | result += strings.Repeat("\t", depth-1) + "}"
165 | continue
166 | }
167 | result += jsonToText(v, depth)
168 | if i < len(val)-1 {
169 | result += " "
170 | }
171 | }
172 | }
173 |
174 | return result
175 | }
176 |
177 | // TODO: Will this function come in handy somewhere else?
178 | /*
179 | // standardizeScheme turns an address like host:https into https://host,
180 | // or "host:" into "host".
181 | func standardizeScheme(addr string) string {
182 | if hostname, port, err := net.SplitHostPort(addr); err == nil {
183 | if port == "http" || port == "https" {
184 | addr = port + "://" + hostname
185 | }
186 | }
187 | return strings.TrimSuffix(addr, ":")
188 | }
189 | */
190 |
191 | // EncodedCaddyfile encapsulates a slice of EncodedServerBlocks.
192 | type EncodedCaddyfile []EncodedServerBlock
193 |
194 | // EncodedServerBlock represents a server block ripe for encoding.
195 | type EncodedServerBlock struct {
196 | Keys []string `json:"keys"`
197 | Body [][]interface{} `json:"body"`
198 | }
199 |
--------------------------------------------------------------------------------
/controller.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "strings"
19 |
20 | "github.com/coredns/caddy/caddyfile"
21 | )
22 |
23 | // Controller is given to the setup function of directives which
24 | // gives them access to be able to read tokens with which to
25 | // configure themselves. It also stores state for the setup
26 | // functions, can get the current context, and can be used to
27 | // identify a particular server block using the Key field.
28 | type Controller struct {
29 | caddyfile.Dispenser
30 |
31 | // The instance in which the setup is occurring
32 | instance *Instance
33 |
34 | // Key is the key from the top of the server block, usually
35 | // an address, hostname, or identifier of some sort.
36 | Key string
37 |
38 | // OncePerServerBlock is a function that executes f
39 | // exactly once per server block, no matter how many
40 | // hosts are associated with it. If it is the first
41 | // time, the function f is executed immediately
42 | // (not deferred) and may return an error which is
43 | // returned by OncePerServerBlock.
44 | OncePerServerBlock func(f func() error) error
45 |
46 | // ServerBlockIndex is the 0-based index of the
47 | // server block as it appeared in the input.
48 | ServerBlockIndex int
49 |
50 | // ServerBlockKeyIndex is the 0-based index of this
51 | // key as it appeared in the input at the head of the
52 | // server block.
53 | ServerBlockKeyIndex int
54 |
55 | // ServerBlockKeys is a list of keys that are
56 | // associated with this server block. All these
57 | // keys, consequently, share the same tokens.
58 | ServerBlockKeys []string
59 |
60 | // ServerBlockStorage is used by a directive's
61 | // setup function to persist state between all
62 | // the keys on a server block.
63 | ServerBlockStorage interface{}
64 | }
65 |
66 | // ServerType gets the name of the server type that is being set up.
67 | func (c *Controller) ServerType() string {
68 | return c.instance.serverType
69 | }
70 |
71 | // OnFirstStartup adds fn to the list of callback functions to execute
72 | // when the server is about to be started NOT as part of a restart.
73 | func (c *Controller) OnFirstStartup(fn func() error) {
74 | c.instance.OnFirstStartup = append(c.instance.OnFirstStartup, fn)
75 | }
76 |
77 | // OnStartup adds fn to the list of callback functions to execute
78 | // when the server is about to be started (including restarts).
79 | func (c *Controller) OnStartup(fn func() error) {
80 | c.instance.OnStartup = append(c.instance.OnStartup, fn)
81 | }
82 |
83 | // OnRestart adds fn to the list of callback functions to execute
84 | // when the server is about to be restarted.
85 | func (c *Controller) OnRestart(fn func() error) {
86 | c.instance.OnRestart = append(c.instance.OnRestart, fn)
87 | }
88 |
89 | // OnRestartFailed adds fn to the list of callback functions to execute
90 | // if the server failed to restart.
91 | func (c *Controller) OnRestartFailed(fn func() error) {
92 | c.instance.OnRestartFailed = append(c.instance.OnRestartFailed, fn)
93 | }
94 |
95 | // OnShutdown adds fn to the list of callback functions to execute
96 | // when the server is about to be shut down (including restarts).
97 | func (c *Controller) OnShutdown(fn func() error) {
98 | c.instance.OnShutdown = append(c.instance.OnShutdown, fn)
99 | }
100 |
101 | // OnFinalShutdown adds fn to the list of callback functions to execute
102 | // when the server is about to be shut down NOT as part of a restart.
103 | func (c *Controller) OnFinalShutdown(fn func() error) {
104 | c.instance.OnFinalShutdown = append(c.instance.OnFinalShutdown, fn)
105 | }
106 |
107 | // Context gets the context associated with the instance associated with c.
108 | func (c *Controller) Context() Context {
109 | return c.instance.context
110 | }
111 |
112 | // Get safely gets a value from the Instance's storage.
113 | func (c *Controller) Get(key interface{}) interface{} {
114 | c.instance.StorageMu.RLock()
115 | defer c.instance.StorageMu.RUnlock()
116 | return c.instance.Storage[key]
117 | }
118 |
119 | // Set safely sets a value on the Instance's storage.
120 | func (c *Controller) Set(key, val interface{}) {
121 | c.instance.StorageMu.Lock()
122 | c.instance.Storage[key] = val
123 | c.instance.StorageMu.Unlock()
124 | }
125 |
126 | // NewTestController creates a new Controller for
127 | // the server type and input specified. The filename
128 | // is "Testfile". If the server type is not empty and
129 | // is plugged in, a context will be created so that
130 | // the results of setup functions can be checked for
131 | // correctness.
132 | //
133 | // Used only for testing, but exported so plugins can
134 | // use this for convenience.
135 | func NewTestController(serverType, input string) *Controller {
136 | testInst := &Instance{serverType: serverType, Storage: make(map[interface{}]interface{})}
137 | if stype, err := getServerType(serverType); err == nil {
138 | testInst.context = stype.NewContext(testInst)
139 | }
140 | return &Controller{
141 | instance: testInst,
142 | Dispenser: caddyfile.NewDispenser("Testfile", strings.NewReader(input)),
143 | OncePerServerBlock: func(f func() error) error { return f() },
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/caddy_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "fmt"
19 | "log"
20 | "reflect"
21 | "sync"
22 | "testing"
23 |
24 | "github.com/coredns/caddy/caddyfile"
25 | )
26 |
27 | /*
28 | // TODO
29 | func TestCaddyStartStop(t *testing.T) {
30 | caddyfile := "localhost:1984"
31 |
32 | for i := 0; i < 2; i++ {
33 | _, err := Start(CaddyfileInput{Contents: []byte(caddyfile)})
34 | if err != nil {
35 | t.Fatalf("Error starting, iteration %d: %v", i, err)
36 | }
37 |
38 | client := http.Client{
39 | Timeout: time.Duration(2 * time.Second),
40 | }
41 | resp, err := client.Get("http://localhost:1984")
42 | if err != nil {
43 | t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err)
44 | }
45 | resp.Body.Close()
46 |
47 | err = Stop()
48 | if err != nil {
49 | t.Fatalf("Error stopping, iteration %d: %v", i, err)
50 | }
51 | }
52 | }
53 | */
54 |
55 | // CallbackTestContext implements Context interface
56 | type CallbackTestContext struct {
57 | // If MakeServersFail is set to true then MakeServers returns an error
58 | MakeServersFail bool
59 | }
60 |
61 | func (h *CallbackTestContext) InspectServerBlocks(name string, sblock []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
62 | return sblock, nil
63 | }
64 | func (h *CallbackTestContext) MakeServers() ([]Server, error) {
65 | if h.MakeServersFail {
66 | return make([]Server, 0), fmt.Errorf("MakeServers failed")
67 | }
68 | return make([]Server, 0), nil
69 | }
70 |
71 | func TestCaddyRestartCallbacks(t *testing.T) {
72 | for i, test := range []struct {
73 | restartFail bool
74 | expectedCalls []string
75 | }{
76 | {false, []string{"OnRestart", "OnShutdown"}},
77 | {true, []string{"OnRestart", "OnRestartFailed"}},
78 | } {
79 | serverName := fmt.Sprintf("%v", i)
80 | // RegisterServerType to make successful restart possible
81 | RegisterServerType(serverName, ServerType{
82 | Directives: func() []string { return []string{} },
83 | // If MakeServersFail is true then the restart will fail due to context failure
84 | NewContext: func(inst *Instance) Context { return &CallbackTestContext{MakeServersFail: test.restartFail} },
85 | })
86 | c := NewTestController(serverName, "")
87 | c.instance = &Instance{
88 | serverType: serverName,
89 | wg: new(sync.WaitGroup),
90 | }
91 |
92 | // Register callbacks which save the calls order
93 | calls := make([]string, 0)
94 | c.OnRestart(func() error {
95 | calls = append(calls, "OnRestart")
96 | return nil
97 | })
98 | c.OnRestartFailed(func() error {
99 | calls = append(calls, "OnRestartFailed")
100 | return nil
101 | })
102 | c.OnShutdown(func() error {
103 | calls = append(calls, "OnShutdown")
104 | return nil
105 | })
106 |
107 | _, err := c.instance.Restart(CaddyfileInput{Contents: []byte(""), ServerTypeName: serverName})
108 | if err != nil {
109 | log.Printf("[ERROR] Restart failed: %v", err)
110 | }
111 |
112 | if !reflect.DeepEqual(calls, test.expectedCalls) {
113 | t.Errorf("Test %d: Callbacks expected: %v, got: %v", i, test.expectedCalls, calls)
114 | }
115 |
116 | err = c.instance.Stop()
117 | if err != nil {
118 | log.Printf("[ERROR] Stop failed: %v", err)
119 | }
120 |
121 | c.instance.Wait()
122 | }
123 |
124 | }
125 |
126 | func TestIsLoopback(t *testing.T) {
127 | for i, test := range []struct {
128 | input string
129 | expect bool
130 | }{
131 | {"example.com", false},
132 | {"localhost", true},
133 | {"localhost:1234", true},
134 | {"localhost:", true},
135 | {"127.0.0.1", true},
136 | {"127.0.0.1:443", true},
137 | {"127.0.1.5", true},
138 | {"10.0.0.5", false},
139 | {"12.7.0.1", false},
140 | {"[::1]", true},
141 | {"[::1]:1234", true},
142 | {"::1", true},
143 | {"::", false},
144 | {"[::]", false},
145 | {"local", false},
146 | } {
147 | if got, want := IsLoopback(test.input), test.expect; got != want {
148 | t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got)
149 | }
150 | }
151 | }
152 |
153 | func TestIsInternal(t *testing.T) {
154 | for i, test := range []struct {
155 | input string
156 | expect bool
157 | }{
158 | {"9.255.255.255", false},
159 | {"10.0.0.0", true},
160 | {"10.0.0.1", true},
161 | {"10.255.255.254", true},
162 | {"10.255.255.255", true},
163 | {"11.0.0.0", false},
164 | {"10.0.0.5:1234", true},
165 | {"11.0.0.5:1234", false},
166 |
167 | {"172.15.255.255", false},
168 | {"172.16.0.0", true},
169 | {"172.16.0.1", true},
170 | {"172.31.255.254", true},
171 | {"172.31.255.255", true},
172 | {"172.32.0.0", false},
173 | {"172.16.0.1:1234", true},
174 |
175 | {"192.167.255.255", false},
176 | {"192.168.0.0", true},
177 | {"192.168.0.1", true},
178 | {"192.168.255.254", true},
179 | {"192.168.255.255", true},
180 | {"192.169.0.0", false},
181 | {"192.168.0.1:1234", true},
182 |
183 | {"fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false},
184 | {"fc00::", true},
185 | {"fc00::1", true},
186 | {"[fc00::1]", true},
187 | {"[fc00::1]:8888", true},
188 | {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", true},
189 | {"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
190 | {"fe00::", false},
191 | {"fd12:3456:789a:1::1:1234", true},
192 |
193 | {"example.com", false},
194 | {"localhost", false},
195 | {"localhost:1234", false},
196 | {"localhost:", false},
197 | {"127.0.0.1", false},
198 | {"127.0.0.1:443", false},
199 | {"127.0.1.5", false},
200 | {"12.7.0.1", false},
201 | {"[::1]", false},
202 | {"[::1]:1234", false},
203 | {"::1", false},
204 | {"::", false},
205 | {"[::]", false},
206 | {"local", false},
207 | } {
208 | if got, want := IsInternal(test.input), test.expect; got != want {
209 | t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got)
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/upgrade.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "encoding/gob"
19 | "fmt"
20 | "io/ioutil"
21 | "log"
22 | "os"
23 | "os/exec"
24 | "sync"
25 | )
26 |
27 | func init() {
28 | // register CaddyfileInput with gob so it knows into
29 | // which concrete type to decode an Input interface
30 | gob.Register(CaddyfileInput{})
31 | }
32 |
33 | // IsUpgrade returns true if this process is part of an upgrade
34 | // where a parent caddy process spawned this one to upgrade
35 | // the binary.
36 | func IsUpgrade() bool {
37 | mu.Lock()
38 | defer mu.Unlock()
39 | return isUpgrade
40 | }
41 |
42 | // Upgrade re-launches the process, preserving the listeners
43 | // for a graceful upgrade. It does NOT load new configuration;
44 | // it only starts the process anew with the current config.
45 | // This makes it possible to perform zero-downtime binary upgrades.
46 | //
47 | // TODO: For more information when debugging, see:
48 | // https://forum.golangbridge.org/t/bind-address-already-in-use-even-after-listener-closed/1510?u=matt
49 | // https://github.com/mholt/shared-conn
50 | func Upgrade() error {
51 | log.Println("[INFO] Upgrading")
52 |
53 | // use existing Caddyfile; do not change configuration during upgrade
54 | currentCaddyfile, _, err := getCurrentCaddyfile()
55 | if err != nil {
56 | return err
57 | }
58 |
59 | if len(os.Args) == 0 { // this should never happen, but...
60 | os.Args = []string{""}
61 | }
62 |
63 | // tell the child that it's a restart
64 | env := os.Environ()
65 | if !IsUpgrade() {
66 | env = append(env, "CADDY__UPGRADE=1")
67 | }
68 |
69 | // prepare our payload to the child process
70 | cdyfileGob := transferGob{
71 | ListenerFds: make(map[string]uintptr),
72 | Caddyfile: currentCaddyfile,
73 | }
74 |
75 | // prepare a pipe to the fork's stdin so it can get the Caddyfile
76 | rpipe, wpipe, err := os.Pipe()
77 | if err != nil {
78 | return err
79 | }
80 |
81 | // prepare a pipe that the child process will use to communicate
82 | // its success with us by sending > 0 bytes
83 | sigrpipe, sigwpipe, err := os.Pipe()
84 | if err != nil {
85 | return err
86 | }
87 |
88 | // pass along relevant file descriptors to child process; ordering
89 | // is very important since we rely on these being in certain positions.
90 | extraFiles := []*os.File{sigwpipe} // fd 3
91 |
92 | // add file descriptors of all the sockets
93 | for i, j := 0, 0; ; i++ {
94 | instancesMu.Lock()
95 | if i >= len(instances) {
96 | instancesMu.Unlock()
97 | break
98 | }
99 | inst := instances[i]
100 | instancesMu.Unlock()
101 |
102 | for _, s := range inst.servers {
103 | gs, gracefulOk := s.server.(GracefulServer)
104 | ln, lnOk := s.listener.(Listener)
105 | pc, pcOk := s.packet.(PacketConn)
106 | if gracefulOk {
107 | if lnOk {
108 | lnFile, _ := ln.File()
109 | extraFiles = append(extraFiles, lnFile)
110 | cdyfileGob.ListenerFds["tcp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners
111 | j++
112 | }
113 | if pcOk {
114 | pcFile, _ := pc.File()
115 | extraFiles = append(extraFiles, pcFile)
116 | cdyfileGob.ListenerFds["udp"+gs.Address()] = uintptr(4 + j) // 4 fds come before any of the listeners
117 | j++
118 | }
119 | }
120 | }
121 | }
122 |
123 | // set up the command
124 | cmd := exec.Command(os.Args[0], os.Args[1:]...)
125 | cmd.Stdin = rpipe // fd 0
126 | cmd.Stdout = os.Stdout // fd 1
127 | cmd.Stderr = os.Stderr // fd 2
128 | cmd.ExtraFiles = extraFiles
129 | cmd.Env = env
130 |
131 | // spawn the child process
132 | err = cmd.Start()
133 | if err != nil {
134 | return err
135 | }
136 |
137 | // immediately close our dup'ed fds and the write end of our signal pipe
138 | for _, f := range extraFiles {
139 | err = f.Close()
140 | if err != nil {
141 | return err
142 | }
143 | }
144 |
145 | // feed Caddyfile to the child
146 | err = gob.NewEncoder(wpipe).Encode(cdyfileGob)
147 | if err != nil {
148 | return err
149 | }
150 | err = wpipe.Close()
151 | if err != nil {
152 | return err
153 | }
154 |
155 | // determine whether child startup succeeded
156 | answer, readErr := ioutil.ReadAll(sigrpipe)
157 | if len(answer) == 0 {
158 | cmdErr := cmd.Wait() // get exit status
159 | errStr := fmt.Sprintf("child failed to initialize: %v", cmdErr)
160 | if readErr != nil {
161 | errStr += fmt.Sprintf(" - additionally, error communicating with child process: %v", readErr)
162 | }
163 | return fmt.Errorf(errStr)
164 | }
165 |
166 | // looks like child is successful; we can exit gracefully.
167 | log.Println("[INFO] Upgrade finished")
168 | return Stop()
169 | }
170 |
171 | // getCurrentCaddyfile gets the Caddyfile used by the
172 | // current (first) Instance and returns both of them.
173 | func getCurrentCaddyfile() (Input, *Instance, error) {
174 | instancesMu.Lock()
175 | if len(instances) == 0 {
176 | instancesMu.Unlock()
177 | return nil, nil, fmt.Errorf("no server instances are fully running")
178 | }
179 | inst := instances[0]
180 | instancesMu.Unlock()
181 |
182 | currentCaddyfile := inst.caddyfileInput
183 | if currentCaddyfile == nil {
184 | // hmm, did spawning process forget to close stdin? Anyhow, this is unusual.
185 | return nil, inst, fmt.Errorf("no Caddyfile to reload (was stdin left open?)")
186 | }
187 | return currentCaddyfile, inst, nil
188 | }
189 |
190 | // signalSuccessToParent tells the parent our status using pipe at index 3.
191 | // If this process is not a restart, this function does nothing.
192 | // Calling this function once this process has successfully initialized
193 | // is vital so that the parent process can unblock and kill itself.
194 | // This function is idempotent; it executes at most once per process.
195 | func signalSuccessToParent() {
196 | signalParentOnce.Do(func() {
197 | if IsUpgrade() {
198 | ppipe := os.NewFile(3, "") // parent is reading from pipe at index 3
199 | _, err := ppipe.Write([]byte("success")) // we must send some bytes to the parent
200 | if err != nil {
201 | log.Printf("[ERROR] Communicating successful init to parent: %v", err)
202 | }
203 | ppipe.Close()
204 | }
205 | })
206 | }
207 |
208 | // signalParentOnce is used to make sure that the parent is only
209 | // signaled once; doing so more than once breaks whatever socket is
210 | // at fd 4 (TODO: the reason for this is still unclear - to reproduce,
211 | // call Stop() and Start() in succession at least once after a
212 | // restart, then try loading first host of Caddyfile in the browser
213 | // - this was pre-v0.9; this code and godoc is borrowed from the
214 | // implementation then, but I'm not sure if it's been fixed yet, as
215 | // of v0.10.7). Do not use this directly; call signalSuccessToParent
216 | // instead.
217 | var signalParentOnce sync.Once
218 |
219 | // transferGob is used if this is a child process as part of
220 | // a graceful upgrade; it is used to map listeners to their
221 | // index in the list of inherited file descriptors. This
222 | // variable is not safe for concurrent access.
223 | var loadedGob transferGob
224 |
225 | // transferGob maps bind address to index of the file descriptor
226 | // in the Files array passed to the child process. It also contains
227 | // the Caddyfile contents and any other state needed by the new process.
228 | // Used only during graceful upgrades.
229 | type transferGob struct {
230 | ListenerFds map[string]uintptr
231 | Caddyfile Input
232 | }
233 |
--------------------------------------------------------------------------------
/caddyfile/dispenser.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | "io"
21 | "strings"
22 | )
23 |
24 | // Dispenser is a type that dispenses tokens, similarly to a lexer,
25 | // except that it can do so with some notion of structure and has
26 | // some really convenient methods.
27 | type Dispenser struct {
28 | filename string
29 | tokens []Token
30 | cursor int
31 | nesting int
32 | }
33 |
34 | // NewDispenser returns a Dispenser, ready to use for parsing the given input.
35 | func NewDispenser(filename string, input io.Reader) Dispenser {
36 | tokens, _ := allTokens(input) // ignoring error because nothing to do with it
37 | return Dispenser{
38 | filename: filename,
39 | tokens: tokens,
40 | cursor: -1,
41 | }
42 | }
43 |
44 | // NewDispenserTokens returns a Dispenser filled with the given tokens.
45 | func NewDispenserTokens(filename string, tokens []Token) Dispenser {
46 | return Dispenser{
47 | filename: filename,
48 | tokens: tokens,
49 | cursor: -1,
50 | }
51 | }
52 |
53 | // Next loads the next token. Returns true if a token
54 | // was loaded; false otherwise. If false, all tokens
55 | // have been consumed.
56 | func (d *Dispenser) Next() bool {
57 | if d.cursor < len(d.tokens)-1 {
58 | d.cursor++
59 | return true
60 | }
61 | return false
62 | }
63 |
64 | // NextArg loads the next token if it is on the same
65 | // line. Returns true if a token was loaded; false
66 | // otherwise. If false, all tokens on the line have
67 | // been consumed. It handles imported tokens correctly.
68 | func (d *Dispenser) NextArg() bool {
69 | if d.cursor < 0 {
70 | d.cursor++
71 | return true
72 | }
73 | if d.cursor >= len(d.tokens) {
74 | return false
75 | }
76 | if d.cursor < len(d.tokens)-1 &&
77 | d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
78 | d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
79 | d.cursor++
80 | return true
81 | }
82 | return false
83 | }
84 |
85 | // NextLine loads the next token only if it is not on the same
86 | // line as the current token, and returns true if a token was
87 | // loaded; false otherwise. If false, there is not another token
88 | // or it is on the same line. It handles imported tokens correctly.
89 | func (d *Dispenser) NextLine() bool {
90 | if d.cursor < 0 {
91 | d.cursor++
92 | return true
93 | }
94 | if d.cursor >= len(d.tokens) {
95 | return false
96 | }
97 | if d.cursor < len(d.tokens)-1 &&
98 | (d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
99 | d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
100 | d.cursor++
101 | return true
102 | }
103 | return false
104 | }
105 |
106 | // NextBlock can be used as the condition of a for loop
107 | // to load the next token as long as it opens a block or
108 | // is already in a block. It returns true if a token was
109 | // loaded, or false when the block's closing curly brace
110 | // was loaded and thus the block ended. Nested blocks are
111 | // not supported.
112 | func (d *Dispenser) NextBlock() bool {
113 | if d.nesting > 0 {
114 | if !d.Next() {
115 | return false
116 | }
117 | if d.Val() == "}" {
118 | d.nesting--
119 | return false
120 | }
121 | return true
122 | }
123 | if !d.NextArg() { // block must open on same line
124 | return false
125 | }
126 | if d.Val() != "{" {
127 | d.cursor-- // roll back if not opening brace
128 | return false
129 | }
130 | if !d.Next() {
131 | return false
132 | }
133 | if d.Val() == "}" {
134 | // Open and then closed right away
135 | return false
136 | }
137 | d.nesting++
138 | return true
139 | }
140 |
141 | // Val gets the text of the current token. If there is no token
142 | // loaded, it returns empty string.
143 | func (d *Dispenser) Val() string {
144 | if d.cursor < 0 || d.cursor >= len(d.tokens) {
145 | return ""
146 | }
147 | return d.tokens[d.cursor].Text
148 | }
149 |
150 | // Line gets the line number of the current token. If there is no token
151 | // loaded, it returns 0.
152 | func (d *Dispenser) Line() int {
153 | if d.cursor < 0 || d.cursor >= len(d.tokens) {
154 | return 0
155 | }
156 | return d.tokens[d.cursor].Line
157 | }
158 |
159 | // File gets the filename of the current token. If there is no token loaded,
160 | // it returns the filename originally given when parsing started.
161 | func (d *Dispenser) File() string {
162 | if d.cursor < 0 || d.cursor >= len(d.tokens) {
163 | return d.filename
164 | }
165 | if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" {
166 | return tokenFilename
167 | }
168 | return d.filename
169 | }
170 |
171 | // Args is a convenience function that loads the next arguments
172 | // (tokens on the same line) into an arbitrary number of strings
173 | // pointed to in targets. If there are fewer tokens available
174 | // than string pointers, the remaining strings will not be changed
175 | // and false will be returned. If there were enough tokens available
176 | // to fill the arguments, then true will be returned.
177 | func (d *Dispenser) Args(targets ...*string) bool {
178 | enough := true
179 | for i := 0; i < len(targets); i++ {
180 | if !d.NextArg() {
181 | enough = false
182 | break
183 | }
184 | *targets[i] = d.Val()
185 | }
186 | return enough
187 | }
188 |
189 | // RemainingArgs loads any more arguments (tokens on the same line)
190 | // into a slice and returns them. Open curly brace tokens also indicate
191 | // the end of arguments, and the curly brace is not included in
192 | // the return value nor is it loaded.
193 | func (d *Dispenser) RemainingArgs() []string {
194 | var args []string
195 |
196 | for d.NextArg() {
197 | if d.Val() == "{" {
198 | d.cursor--
199 | break
200 | }
201 | args = append(args, d.Val())
202 | }
203 |
204 | return args
205 | }
206 |
207 | // ArgErr returns an argument error, meaning that another
208 | // argument was expected but not found. In other words,
209 | // a line break or open curly brace was encountered instead of
210 | // an argument.
211 | func (d *Dispenser) ArgErr() error {
212 | if d.Val() == "{" {
213 | return d.Err("Unexpected token '{', expecting argument")
214 | }
215 | return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
216 | }
217 |
218 | // SyntaxErr creates a generic syntax error which explains what was
219 | // found and what was expected.
220 | func (d *Dispenser) SyntaxErr(expected string) error {
221 | msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
222 | return errors.New(msg)
223 | }
224 |
225 | // EOFErr returns an error indicating that the dispenser reached
226 | // the end of the input when searching for the next token.
227 | func (d *Dispenser) EOFErr() error {
228 | return d.Errf("Unexpected EOF")
229 | }
230 |
231 | // Err generates a custom parse-time error with a message of msg.
232 | func (d *Dispenser) Err(msg string) error {
233 | msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg)
234 | return errors.New(msg)
235 | }
236 |
237 | // Errf is like Err, but for formatted error messages
238 | func (d *Dispenser) Errf(format string, args ...interface{}) error {
239 | return d.Err(fmt.Sprintf(format, args...))
240 | }
241 |
242 | // numLineBreaks counts how many line breaks are in the token
243 | // value given by the token index tknIdx. It returns 0 if the
244 | // token does not exist or there are no line breaks.
245 | func (d *Dispenser) numLineBreaks(tknIdx int) int {
246 | if tknIdx < 0 || tknIdx >= len(d.tokens) {
247 | return 0
248 | }
249 | return strings.Count(d.tokens[tknIdx].Text, "\n")
250 | }
251 |
252 | // isNewLine determines whether the current token is on a different
253 | // line (higher line number) than the previous token. It handles imported
254 | // tokens correctly. If there isn't a previous token, it returns true.
255 | func (d *Dispenser) isNewLine() bool {
256 | if d.cursor < 1 {
257 | return true
258 | }
259 | if d.cursor > len(d.tokens)-1 {
260 | return false
261 | }
262 | return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
263 | d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
264 | }
265 |
--------------------------------------------------------------------------------
/commands_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "fmt"
19 | "runtime"
20 | "strings"
21 | "testing"
22 | )
23 |
24 | func TestParseUnixCommand(t *testing.T) {
25 | tests := []struct {
26 | input string
27 | expected []string
28 | }{
29 | // 0 - empty command
30 | {
31 | input: ``,
32 | expected: []string{},
33 | },
34 | // 1 - command without arguments
35 | {
36 | input: `command`,
37 | expected: []string{`command`},
38 | },
39 | // 2 - command with single argument
40 | {
41 | input: `command arg1`,
42 | expected: []string{`command`, `arg1`},
43 | },
44 | // 3 - command with multiple arguments
45 | {
46 | input: `command arg1 arg2`,
47 | expected: []string{`command`, `arg1`, `arg2`},
48 | },
49 | // 4 - command with single argument with space character - in quotes
50 | {
51 | input: `command "arg1 arg1"`,
52 | expected: []string{`command`, `arg1 arg1`},
53 | },
54 | // 5 - command with multiple spaces and tab character
55 | {
56 | input: "command arg1 arg2\targ3",
57 | expected: []string{`command`, `arg1`, `arg2`, `arg3`},
58 | },
59 | // 6 - command with single argument with space character - escaped with backspace
60 | {
61 | input: `command arg1\ arg2`,
62 | expected: []string{`command`, `arg1 arg2`},
63 | },
64 | // 7 - single quotes should escape special chars
65 | {
66 | input: `command 'arg1\ arg2'`,
67 | expected: []string{`command`, `arg1\ arg2`},
68 | },
69 | }
70 |
71 | for i, test := range tests {
72 | errorPrefix := fmt.Sprintf("Test [%d]: ", i)
73 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
74 | actual, _ := parseUnixCommand(test.input)
75 | if len(actual) != len(test.expected) {
76 | t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
77 | continue
78 | }
79 | for j := 0; j < len(actual); j++ {
80 | if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
81 | t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
82 | }
83 | }
84 | }
85 | }
86 |
87 | func TestParseWindowsCommand(t *testing.T) {
88 | tests := []struct {
89 | input string
90 | expected []string
91 | }{
92 | { // 0 - empty command - do not fail
93 | input: ``,
94 | expected: []string{},
95 | },
96 | { // 1 - cmd without args
97 | input: `cmd`,
98 | expected: []string{`cmd`},
99 | },
100 | { // 2 - multiple args
101 | input: `cmd arg1 arg2`,
102 | expected: []string{`cmd`, `arg1`, `arg2`},
103 | },
104 | { // 3 - multiple args with space
105 | input: `cmd "combined arg" arg2`,
106 | expected: []string{`cmd`, `combined arg`, `arg2`},
107 | },
108 | { // 4 - path without spaces
109 | input: `mkdir C:\Windows\foo\bar`,
110 | expected: []string{`mkdir`, `C:\Windows\foo\bar`},
111 | },
112 | { // 5 - command with space in quotes
113 | input: `"command here"`,
114 | expected: []string{`command here`},
115 | },
116 | { // 6 - argument with escaped quotes (two quotes)
117 | input: `cmd ""arg""`,
118 | expected: []string{`cmd`, `"arg"`},
119 | },
120 | { // 7 - argument with escaped quotes (backslash)
121 | input: `cmd \"arg\"`,
122 | expected: []string{`cmd`, `"arg"`},
123 | },
124 | { // 8 - two quotes (escaped) inside an inQuote element
125 | input: `cmd "a ""quoted value"`,
126 | expected: []string{`cmd`, `a "quoted value`},
127 | },
128 | // TODO - see how many quotes are displayed if we use "", """, """""""
129 | { // 9 - two quotes outside an inQuote element
130 | input: `cmd a ""quoted value`,
131 | expected: []string{`cmd`, `a`, `"quoted`, `value`},
132 | },
133 | { // 10 - path with space in quotes
134 | input: `mkdir "C:\directory name\foobar"`,
135 | expected: []string{`mkdir`, `C:\directory name\foobar`},
136 | },
137 | { // 11 - space without quotes
138 | input: `mkdir C:\ space`,
139 | expected: []string{`mkdir`, `C:\`, `space`},
140 | },
141 | { // 12 - space in quotes
142 | input: `mkdir "C:\ space"`,
143 | expected: []string{`mkdir`, `C:\ space`},
144 | },
145 | { // 13 - UNC
146 | input: `mkdir \\?\C:\Users`,
147 | expected: []string{`mkdir`, `\\?\C:\Users`},
148 | },
149 | { // 14 - UNC with space
150 | input: `mkdir "\\?\C:\Program Files"`,
151 | expected: []string{`mkdir`, `\\?\C:\Program Files`},
152 | },
153 |
154 | { // 15 - unclosed quotes - treat as if the path ends with quote
155 | input: `mkdir "c:\Program files`,
156 | expected: []string{`mkdir`, `c:\Program files`},
157 | },
158 | { // 16 - quotes used inside the argument
159 | input: `mkdir "c:\P"rogra"m f"iles`,
160 | expected: []string{`mkdir`, `c:\Program files`},
161 | },
162 | }
163 |
164 | for i, test := range tests {
165 | errorPrefix := fmt.Sprintf("Test [%d]: ", i)
166 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
167 |
168 | actual := parseWindowsCommand(test.input)
169 | if len(actual) != len(test.expected) {
170 | t.Errorf(errorPrefix+"Expected %d parts, got %d: %#v."+errorSuffix, len(test.expected), len(actual), actual)
171 | continue
172 | }
173 | for j := 0; j < len(actual); j++ {
174 | if expectedPart, actualPart := test.expected[j], actual[j]; expectedPart != actualPart {
175 | t.Errorf(errorPrefix+"Expected: %v Actual: %v (index %d)."+errorSuffix, expectedPart, actualPart, j)
176 | }
177 | }
178 | }
179 | }
180 |
181 | func TestSplitCommandAndArgs(t *testing.T) {
182 |
183 | // force linux parsing. It's more robust and covers error cases
184 | runtimeGoos = "linux"
185 | defer func() {
186 | runtimeGoos = runtime.GOOS
187 | }()
188 |
189 | var parseErrorContent = "error parsing command:"
190 | var noCommandErrContent = "no command contained in"
191 |
192 | tests := []struct {
193 | input string
194 | expectedCommand string
195 | expectedArgs []string
196 | expectedErrContent string
197 | }{
198 | // 0 - empty command
199 | {
200 | input: ``,
201 | expectedCommand: ``,
202 | expectedArgs: nil,
203 | expectedErrContent: noCommandErrContent,
204 | },
205 | // 1 - command without arguments
206 | {
207 | input: `command`,
208 | expectedCommand: `command`,
209 | expectedArgs: nil,
210 | expectedErrContent: ``,
211 | },
212 | // 2 - command with single argument
213 | {
214 | input: `command arg1`,
215 | expectedCommand: `command`,
216 | expectedArgs: []string{`arg1`},
217 | expectedErrContent: ``,
218 | },
219 | // 3 - command with multiple arguments
220 | {
221 | input: `command arg1 arg2`,
222 | expectedCommand: `command`,
223 | expectedArgs: []string{`arg1`, `arg2`},
224 | expectedErrContent: ``,
225 | },
226 | // 4 - command with unclosed quotes
227 | {
228 | input: `command "arg1 arg2`,
229 | expectedCommand: "",
230 | expectedArgs: nil,
231 | expectedErrContent: parseErrorContent,
232 | },
233 | // 5 - command with unclosed quotes
234 | {
235 | input: `command 'arg1 arg2"`,
236 | expectedCommand: "",
237 | expectedArgs: nil,
238 | expectedErrContent: parseErrorContent,
239 | },
240 | }
241 |
242 | for i, test := range tests {
243 | errorPrefix := fmt.Sprintf("Test [%d]: ", i)
244 | errorSuffix := fmt.Sprintf(" Command to parse: [%s]", test.input)
245 | actualCommand, actualArgs, actualErr := SplitCommandAndArgs(test.input)
246 |
247 | // test if error matches expectation
248 | if test.expectedErrContent != "" {
249 | if actualErr == nil {
250 | t.Errorf(errorPrefix+"Expected error with content [%s], found no error."+errorSuffix, test.expectedErrContent)
251 | } else if !strings.Contains(actualErr.Error(), test.expectedErrContent) {
252 | t.Errorf(errorPrefix+"Expected error with content [%s], found [%v]."+errorSuffix, test.expectedErrContent, actualErr)
253 | }
254 | } else if actualErr != nil {
255 | t.Errorf(errorPrefix+"Expected no error, found [%v]."+errorSuffix, actualErr)
256 | }
257 |
258 | // test if command matches
259 | if test.expectedCommand != actualCommand {
260 | t.Errorf(errorPrefix+"Expected command: [%s], actual: [%s]."+errorSuffix, test.expectedCommand, actualCommand)
261 | }
262 |
263 | // test if arguments match
264 | if len(test.expectedArgs) != len(actualArgs) {
265 | t.Errorf(errorPrefix+"Wrong number of arguments! Expected [%v], actual [%v]."+errorSuffix, test.expectedArgs, actualArgs)
266 | } else {
267 | // test args only if the count matches.
268 | for j, actualArg := range actualArgs {
269 | expectedArg := test.expectedArgs[j]
270 | if actualArg != expectedArg {
271 | t.Errorf(errorPrefix+"Argument at position [%d] differ! Expected [%s], actual [%s]"+errorSuffix, j, expectedArg, actualArg)
272 | }
273 | }
274 | }
275 | }
276 | }
277 |
278 | func ExampleSplitCommandAndArgs() {
279 | var commandLine string
280 | var command string
281 | var args []string
282 |
283 | // just for the test - change GOOS and reset it at the end of the test
284 | runtimeGoos = "windows"
285 | defer func() {
286 | runtimeGoos = runtime.GOOS
287 | }()
288 |
289 | commandLine = `mkdir /P "C:\Program Files"`
290 | command, args, _ = SplitCommandAndArgs(commandLine)
291 |
292 | fmt.Printf("Windows: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
293 |
294 | // set GOOS to linux
295 | runtimeGoos = "linux"
296 |
297 | commandLine = `mkdir -p /path/with\ space`
298 | command, args, _ = SplitCommandAndArgs(commandLine)
299 |
300 | fmt.Printf("Linux: %s: %s [%s]\n", commandLine, command, strings.Join(args, ","))
301 |
302 | // Output:
303 | // Windows: mkdir /P "C:\Program Files": mkdir [/P,C:\Program Files]
304 | // Linux: mkdir -p /path/with\ space: mkdir [-p,/path/with space]
305 | }
306 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | THIS IS A FORK OF CADDY v1 - EVERYTHING IS STRIPPED EXCEPT THE PIECES NEEDED IN COREDNS.
2 |
3 | Issues are not enabled in this repository. Please raise any issues in coredns/coredns.
4 |
5 | ---
6 |
7 |
8 | Caddy is a **production-ready** open-source web server that is fast, easy to use, and makes you more productive.
9 |
10 | Available for Windows, Mac, Linux, BSD, Solaris, and [Android](https://github.com/caddyserver/caddy/wiki/Running-Caddy-on-Android).
11 |
12 |
13 | Thanks to our special sponsor:
14 |
15 |
16 |
17 |
18 | ## Menu
19 |
20 | - [Features](#features)
21 | - [Install](#install)
22 | - [Quick Start](#quick-start)
23 | - [Running in Production](#running-in-production)
24 | - [Contributing](#contributing)
25 | - [Donors](#donors)
26 | - [About the Project](#about-the-project)
27 |
28 | ## Features
29 |
30 | - **Easy configuration** with the Caddyfile
31 | - **Automatic HTTPS** on by default (via [Let's Encrypt](https://letsencrypt.org))
32 | - **HTTP/2** by default
33 | - **Virtual hosting** so multiple sites just work
34 | - Experimental **QUIC support** for cutting-edge transmissions
35 | - TLS session ticket **key rotation** for more secure connections
36 | - **Extensible with plugins** because a convenient web server is a helpful one
37 | - **Runs anywhere** with **no external dependencies** (not even libc)
38 |
39 | [See a more complete list of features built into Caddy.](https://caddyserver.com/#features) On top of all those, Caddy does even more with plugins: choose which plugins you want at [download](https://caddyserver.com/download).
40 |
41 | Altogether, Caddy can do things other web servers simply cannot do. Its features and plugins save you time and mistakes, and will cheer you up. Your Caddy instance takes care of the details for you!
42 |
43 |
44 |
45 | Powered by
46 |
47 |
48 |
49 |
50 |
51 | ## Install
52 |
53 | Caddy binaries have no dependencies and are available for every platform. Get Caddy any of these ways:
54 |
55 | - **[Download page](https://caddyserver.com/download)** (RECOMMENDED) allows you to customize your build in the browser
56 | - **[Latest release](https://github.com/caddyserver/caddy/releases/latest)** for pre-built, vanilla binaries
57 | - **[AWS Marketplace](https://aws.amazon.com/marketplace/pp/B07J1WNK75?qid=1539015041932&sr=0-1&ref_=srh_res_product_title&cl_spe=C)** makes it easy to deploy directly to your cloud environment.
58 |
59 |
60 |
61 | ## Build
62 |
63 | To build from source you need **[Git](https://git-scm.com/downloads)** and **[Go](https://golang.org/doc/install)** (1.13 or newer).
64 |
65 | **To build Caddy without plugins:**
66 |
67 | - Run `go get github.com/caddyserver/caddy/caddy`
68 |
69 | Caddy will be installed to your `$GOPATH/bin` folder.
70 |
71 | With these instructions, the binary will not have embedded version information (see [golang/go#29228](https://github.com/golang/go/issues/29228)), but it is fine for a quick start.
72 |
73 | **To build Caddy with plugins (and with version information):**
74 |
75 | There is no need to modify the Caddy code to build it with plugins. We will create a simple Go module with our own `main()` that you can use to make custom Caddy builds.
76 | - Create a new folder anywhere and within create a Go file (with an extension of `.go`, such as `main.go`) with the contents below, adjusting to import the plugins you want to include:
77 | ```go
78 | package main
79 |
80 | import (
81 | "github.com/caddyserver/caddy/caddy/caddymain"
82 |
83 | // plug in plugins here, for example:
84 | // _ "import/path/here"
85 | )
86 |
87 | func main() {
88 | // optional: disable telemetry
89 | // caddymain.EnableTelemetry = false
90 | caddymain.Run()
91 | }
92 | ```
93 | 3. `go mod init caddy`
94 | 4. Run `go get github.com/caddyserver/caddy`
95 | 5. `go install` will then create your binary at `$GOPATH/bin`, or `go build` will put it in the current directory.
96 |
97 | **To install Caddy's source code for development:**
98 |
99 | - Run `git clone https://github.com/caddyserver/caddy.git` in any folder (doesn't have to be in GOPATH).
100 |
101 | You can make changes to the source code from that clone and checkout any commit or tag you wish to develop on.
102 |
103 | When building from source, telemetry is enabled by default. You can disable it by changing `caddymain.EnableTelemetry = false` in run.go, or use the `-disabled-metrics` flag at runtime to disable only certain metrics.
104 |
105 |
106 | ## Quick Start
107 |
108 | To serve static files from the current working directory, run:
109 |
110 | ```
111 | caddy
112 | ```
113 |
114 | Caddy's default port is 2015, so open your browser to [http://localhost:2015](http://localhost:2015).
115 |
116 | ### Go from 0 to HTTPS in 5 seconds
117 |
118 | If the `caddy` binary has permission to bind to low ports and your domain name's DNS records point to the machine you're on:
119 |
120 | ```
121 | caddy -host example.com
122 | ```
123 |
124 | This command serves static files from the current directory over HTTPS. Certificates are automatically obtained and renewed for you! Caddy is also automatically configuring ports 80 and 443 for you, and redirecting HTTP to HTTPS. Cool, huh?
125 |
126 | ### Customizing your site
127 |
128 | To customize how your site is served, create a file named Caddyfile by your site and paste this into it:
129 |
130 | ```plain
131 | localhost
132 |
133 | push
134 | browse
135 | websocket /echo cat
136 | ext .html
137 | log /var/log/access.log
138 | proxy /api 127.0.0.1:7005
139 | header /api Access-Control-Allow-Origin *
140 | ```
141 |
142 | When you run `caddy` in that directory, it will automatically find and use that Caddyfile.
143 |
144 | This simple file enables server push (via Link headers), allows directory browsing (for folders without an index file), hosts a WebSocket echo server at /echo, serves clean URLs, logs requests to an access log, proxies all API requests to a backend on port 7005, and adds the coveted `Access-Control-Allow-Origin: *` header for all responses from the API.
145 |
146 | Wow! Caddy can do a lot with just a few lines.
147 |
148 | ### Doing more with Caddy
149 |
150 | To host multiple sites and do more with the Caddyfile, please see the [Caddyfile tutorial](https://caddyserver.com/tutorial/caddyfile).
151 |
152 | Sites with qualifying hostnames are served over [HTTPS by default](https://caddyserver.com/docs/automatic-https).
153 |
154 | Caddy has a nice little command line interface. Run `caddy -h` to view basic help or see the [CLI documentation](https://caddyserver.com/docs/cli) for details.
155 |
156 |
157 | ## Running in Production
158 |
159 | Caddy is production-ready if you find it to be a good fit for your site and workflow.
160 |
161 | **Running as root:** We advise against this. You can still listen on ports < 1024 on Linux using setcap like so: `sudo setcap cap_net_bind_service=+ep ./caddy`
162 |
163 | The Caddy project does not officially maintain any system-specific integrations nor suggest how to administer your own system. But your download file includes [unofficial resources](https://github.com/caddyserver/caddy/tree/master/dist/init) contributed by the community that you may find helpful for running Caddy in production.
164 |
165 | How you choose to run Caddy is up to you. Many users are satisfied with `nohup caddy &`. Others use `screen`. Users who need Caddy to come back up after reboots either do so in the script that caused the reboot, add a command to an init script, or configure a service with their OS.
166 |
167 | If you have questions or concerns about Caddy' underlying crypto implementations, consult Go's [crypto packages](https://golang.org/pkg/crypto), starting with their documentation, then issues, then the code itself; as Caddy uses mainly those libraries.
168 |
169 |
170 | ## Contributing
171 |
172 | **[Join our forum](https://caddy.community) where you can chat with other Caddy users and developers!** To get familiar with the code base, try [Caddy code search on Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy/)!
173 |
174 | Please see our [contributing guidelines](https://github.com/caddyserver/caddy/blob/master/.github/CONTRIBUTING.md) for instructions. If you want to write a plugin, check out the [developer wiki](https://github.com/caddyserver/caddy/wiki).
175 |
176 | We use GitHub issues and pull requests only for discussing bug reports and the development of specific changes. We welcome all other topics on the [forum](https://caddy.community)!
177 |
178 | If you want to contribute to the documentation, please [submit an issue](https://github.com/caddyserver/caddy/issues/new) describing the change that should be made.
179 |
180 | ### Good First Issue
181 |
182 | If you are looking for somewhere to start and would like to help out by working on an existing issue, take a look at our [`Good First Issue`](https://github.com/caddyserver/caddy/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) tag
183 |
184 | Thanks for making Caddy -- and the Web -- better!
185 |
186 |
187 | ## Donors
188 |
189 | - [DigitalOcean](https://m.do.co/c/6d7bdafccf96) is hosting the Caddy project.
190 | - [DNSimple](https://dnsimple.link/resolving-caddy) provides DNS services for Caddy's sites.
191 | - [DNS Spy](https://dnsspy.io) keeps an eye on Caddy's DNS properties.
192 |
193 | We thank them for their services. **If you want to help keep Caddy free, please [become a sponsor](https://github.com/sponsors/mholt)!**
194 |
195 |
196 | ## About the Project
197 |
198 | Caddy was born out of the need for a "batteries-included" web server that runs anywhere and doesn't have to take its configuration with it. Caddy took inspiration from [spark](https://github.com/rif/spark), [nginx](https://github.com/nginx/nginx), lighttpd,
199 | [Websocketd](https://github.com/joewalnes/websocketd) and [Vagrant](https://www.vagrantup.com/), which provides a pleasant mixture of features from each of them.
200 |
201 | **The name "Caddy" is trademarked:** The name of the software is "Caddy", not "Caddy Server" or "CaddyServer". Please call it "Caddy" or, if you wish to clarify, "the Caddy web server". See [brand guidelines](https://caddyserver.com/brand). Caddy is a registered trademark of Light Code Labs, LLC.
202 |
203 | *Author on Twitter: [@mholt6](https://twitter.com/mholt6)*
204 |
--------------------------------------------------------------------------------
/caddyfile/dispenser_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "reflect"
19 | "strings"
20 | "testing"
21 | )
22 |
23 | func TestDispenser_Val_Next(t *testing.T) {
24 | input := `host:port
25 | dir1 arg1
26 | dir2 arg2 arg3
27 | dir3`
28 | d := NewDispenser("Testfile", strings.NewReader(input))
29 |
30 | if val := d.Val(); val != "" {
31 | t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
32 | }
33 |
34 | assertNext := func(shouldLoad bool, expectedCursor int, expectedVal string) {
35 | if loaded := d.Next(); loaded != shouldLoad {
36 | t.Errorf("Next(): Expected %v but got %v instead (val '%s')", shouldLoad, loaded, d.Val())
37 | }
38 | if d.cursor != expectedCursor {
39 | t.Errorf("Expected cursor to be %d, but was %d", expectedCursor, d.cursor)
40 | }
41 | if d.nesting != 0 {
42 | t.Errorf("Nesting should be 0, was %d instead", d.nesting)
43 | }
44 | if val := d.Val(); val != expectedVal {
45 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
46 | }
47 | }
48 |
49 | assertNext(true, 0, "host:port")
50 | assertNext(true, 1, "dir1")
51 | assertNext(true, 2, "arg1")
52 | assertNext(true, 3, "dir2")
53 | assertNext(true, 4, "arg2")
54 | assertNext(true, 5, "arg3")
55 | assertNext(true, 6, "dir3")
56 | // Note: This next test simply asserts existing behavior.
57 | // If desired, we may wish to empty the token value after
58 | // reading past the EOF. Open an issue if you want this change.
59 | assertNext(false, 6, "dir3")
60 | }
61 |
62 | func TestDispenser_NextArg(t *testing.T) {
63 | input := `dir1 arg1
64 | dir2 arg2 arg3
65 | dir3`
66 | d := NewDispenser("Testfile", strings.NewReader(input))
67 |
68 | assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
69 | if d.Next() != shouldLoad {
70 | t.Errorf("Next(): Should load token but got false instead (val: '%s')", d.Val())
71 | }
72 | if d.cursor != expectedCursor {
73 | t.Errorf("Next(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
74 | }
75 | if val := d.Val(); val != expectedVal {
76 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
77 | }
78 | }
79 |
80 | assertNextArg := func(expectedVal string, loadAnother bool, expectedCursor int) {
81 | if !d.NextArg() {
82 | t.Error("NextArg(): Should load next argument but got false instead")
83 | }
84 | if d.cursor != expectedCursor {
85 | t.Errorf("NextArg(): Expected cursor to be at %d, but it was %d", expectedCursor, d.cursor)
86 | }
87 | if val := d.Val(); val != expectedVal {
88 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
89 | }
90 | if !loadAnother {
91 | if d.NextArg() {
92 | t.Fatalf("NextArg(): Should NOT load another argument, but got true instead (val: '%s')", d.Val())
93 | }
94 | if d.cursor != expectedCursor {
95 | t.Errorf("NextArg(): Expected cursor to remain at %d, but it was %d", expectedCursor, d.cursor)
96 | }
97 | }
98 | }
99 |
100 | assertNext(true, "dir1", 0)
101 | assertNextArg("arg1", false, 1)
102 | assertNext(true, "dir2", 2)
103 | assertNextArg("arg2", true, 3)
104 | assertNextArg("arg3", false, 4)
105 | assertNext(true, "dir3", 5)
106 | assertNext(false, "dir3", 5)
107 | }
108 |
109 | func TestDispenser_NextLine(t *testing.T) {
110 | input := `host:port
111 | dir1 arg1
112 | dir2 arg2 arg3`
113 | d := NewDispenser("Testfile", strings.NewReader(input))
114 |
115 | assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
116 | if d.NextLine() != shouldLoad {
117 | t.Errorf("NextLine(): Should load token but got false instead (val: '%s')", d.Val())
118 | }
119 | if d.cursor != expectedCursor {
120 | t.Errorf("NextLine(): Expected cursor to be %d, instead was %d", expectedCursor, d.cursor)
121 | }
122 | if val := d.Val(); val != expectedVal {
123 | t.Errorf("Val(): Expected '%s' but got '%s'", expectedVal, val)
124 | }
125 | }
126 |
127 | assertNextLine(true, "host:port", 0)
128 | assertNextLine(true, "dir1", 1)
129 | assertNextLine(false, "dir1", 1)
130 | d.Next() // arg1
131 | assertNextLine(true, "dir2", 3)
132 | assertNextLine(false, "dir2", 3)
133 | d.Next() // arg2
134 | assertNextLine(false, "arg2", 4)
135 | d.Next() // arg3
136 | assertNextLine(false, "arg3", 5)
137 | }
138 |
139 | func TestDispenser_NextBlock(t *testing.T) {
140 | input := `foobar1 {
141 | sub1 arg1
142 | sub2
143 | }
144 | foobar2 {
145 | }`
146 | d := NewDispenser("Testfile", strings.NewReader(input))
147 |
148 | assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
149 | if loaded := d.NextBlock(); loaded != shouldLoad {
150 | t.Errorf("NextBlock(): Should return %v but got %v", shouldLoad, loaded)
151 | }
152 | if d.cursor != expectedCursor {
153 | t.Errorf("NextBlock(): Expected cursor to be %d, was %d", expectedCursor, d.cursor)
154 | }
155 | if d.nesting != expectedNesting {
156 | t.Errorf("NextBlock(): Nesting should be %d, not %d", expectedNesting, d.nesting)
157 | }
158 | }
159 |
160 | assertNextBlock(false, -1, 0)
161 | d.Next() // foobar1
162 | assertNextBlock(true, 2, 1)
163 | assertNextBlock(true, 3, 1)
164 | assertNextBlock(true, 4, 1)
165 | assertNextBlock(false, 5, 0)
166 | d.Next() // foobar2
167 | assertNextBlock(false, 8, 0) // empty block is as if it didn't exist
168 | }
169 |
170 | func TestDispenser_NextBlock_MakesProgressOrStopsOnEOF(t *testing.T) {
171 | // Unclosed block should not cause NextBlock to loop without advancing
172 | input := `foobar {
173 | sub1 arg1`
174 | d := NewDispenser("Testfile", strings.NewReader(input))
175 |
176 | if !d.Next() { // foobar
177 | t.Fatal("expected first token")
178 | }
179 |
180 | if !d.NextBlock() {
181 | t.Fatal("expected to enter block with NextBlock")
182 | }
183 |
184 | // Iterate, ensuring each true result advanced the cursor,
185 | // and that we terminate within a reasonable number of steps
186 | const maxIters = 10
187 | prevCursor := d.cursor
188 | for i := 0; i < maxIters; i++ {
189 | ok := d.NextBlock()
190 | if !ok {
191 | return
192 | }
193 | if d.cursor == prevCursor {
194 | t.Fatalf("NextBlock(): did not advance cursor (stuck at %d) — potential infinite loop", d.cursor)
195 | }
196 | prevCursor = d.cursor
197 | }
198 | t.Fatalf("NextBlock(): exceeded %d iterations without terminating", maxIters)
199 | }
200 |
201 | func TestDispenser_Args(t *testing.T) {
202 | var s1, s2, s3 string
203 | input := `dir1 arg1 arg2 arg3
204 | dir2 arg4 arg5
205 | dir3 arg6 arg7
206 | dir4`
207 | d := NewDispenser("Testfile", strings.NewReader(input))
208 |
209 | d.Next() // dir1
210 |
211 | // As many strings as arguments
212 | if all := d.Args(&s1, &s2, &s3); !all {
213 | t.Error("Args(): Expected true, got false")
214 | }
215 | if s1 != "arg1" {
216 | t.Errorf("Args(): Expected s1 to be 'arg1', got '%s'", s1)
217 | }
218 | if s2 != "arg2" {
219 | t.Errorf("Args(): Expected s2 to be 'arg2', got '%s'", s2)
220 | }
221 | if s3 != "arg3" {
222 | t.Errorf("Args(): Expected s3 to be 'arg3', got '%s'", s3)
223 | }
224 |
225 | d.Next() // dir2
226 |
227 | // More strings than arguments
228 | if all := d.Args(&s1, &s2, &s3); all {
229 | t.Error("Args(): Expected false, got true")
230 | }
231 | if s1 != "arg4" {
232 | t.Errorf("Args(): Expected s1 to be 'arg4', got '%s'", s1)
233 | }
234 | if s2 != "arg5" {
235 | t.Errorf("Args(): Expected s2 to be 'arg5', got '%s'", s2)
236 | }
237 | if s3 != "arg3" {
238 | t.Errorf("Args(): Expected s3 to be unchanged ('arg3'), instead got '%s'", s3)
239 | }
240 |
241 | // (quick cursor check just for kicks and giggles)
242 | if d.cursor != 6 {
243 | t.Errorf("Cursor should be 6, but is %d", d.cursor)
244 | }
245 |
246 | d.Next() // dir3
247 |
248 | // More arguments than strings
249 | if all := d.Args(&s1); !all {
250 | t.Error("Args(): Expected true, got false")
251 | }
252 | if s1 != "arg6" {
253 | t.Errorf("Args(): Expected s1 to be 'arg6', got '%s'", s1)
254 | }
255 |
256 | d.Next() // dir4
257 |
258 | // No arguments or strings
259 | if all := d.Args(); !all {
260 | t.Error("Args(): Expected true, got false")
261 | }
262 |
263 | // No arguments but at least one string
264 | if all := d.Args(&s1); all {
265 | t.Error("Args(): Expected false, got true")
266 | }
267 | }
268 |
269 | func TestDispenser_RemainingArgs(t *testing.T) {
270 | input := `dir1 arg1 arg2 arg3
271 | dir2 arg4 arg5
272 | dir3 arg6 { arg7
273 | dir4`
274 | d := NewDispenser("Testfile", strings.NewReader(input))
275 |
276 | d.Next() // dir1
277 |
278 | args := d.RemainingArgs()
279 | if expected := []string{"arg1", "arg2", "arg3"}; !reflect.DeepEqual(args, expected) {
280 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
281 | }
282 |
283 | d.Next() // dir2
284 |
285 | args = d.RemainingArgs()
286 | if expected := []string{"arg4", "arg5"}; !reflect.DeepEqual(args, expected) {
287 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
288 | }
289 |
290 | d.Next() // dir3
291 |
292 | args = d.RemainingArgs()
293 | if expected := []string{"arg6"}; !reflect.DeepEqual(args, expected) {
294 | t.Errorf("RemainingArgs(): Expected %v, got %v", expected, args)
295 | }
296 |
297 | d.Next() // {
298 | d.Next() // arg7
299 | d.Next() // dir4
300 |
301 | args = d.RemainingArgs()
302 | if len(args) != 0 {
303 | t.Errorf("RemainingArgs(): Expected %v, got %v", []string{}, args)
304 | }
305 | }
306 |
307 | func TestDispenser_ArgErr_Err(t *testing.T) {
308 | input := `dir1 {
309 | }
310 | dir2 arg1 arg2`
311 | d := NewDispenser("Testfile", strings.NewReader(input))
312 |
313 | d.cursor = 1 // {
314 |
315 | if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "{") {
316 | t.Errorf("ArgErr(): Expected an error message with { in it, but got '%v'", err)
317 | }
318 |
319 | d.cursor = 5 // arg2
320 |
321 | if err := d.ArgErr(); err == nil || !strings.Contains(err.Error(), "arg2") {
322 | t.Errorf("ArgErr(): Expected an error message with 'arg2' in it; got '%v'", err)
323 | }
324 |
325 | err := d.Err("foobar")
326 | if err == nil {
327 | t.Fatalf("Err(): Expected an error, got nil")
328 | }
329 |
330 | if !strings.Contains(err.Error(), "Testfile:3") {
331 | t.Errorf("Expected error message with filename:line in it; got '%v'", err)
332 | }
333 |
334 | if !strings.Contains(err.Error(), "foobar") {
335 | t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/plugins.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 | "fmt"
19 | "log"
20 | "net"
21 | "sort"
22 | "sync"
23 |
24 | "github.com/coredns/caddy/caddyfile"
25 | )
26 |
27 | // These are all the registered plugins.
28 | var (
29 | // serverTypes is a map of registered server types.
30 | serverTypes = make(map[string]ServerType)
31 |
32 | // plugins is a map of server type to map of plugin name to
33 | // Plugin. These are the "general" plugins that may or may
34 | // not be associated with a specific server type. If it's
35 | // applicable to multiple server types or the server type is
36 | // irrelevant, the key is empty string (""). But all plugins
37 | // must have a name.
38 | plugins = make(map[string]map[string]Plugin)
39 |
40 | // eventHooks is a map of hook name to Hook. All hooks plugins
41 | // must have a name.
42 | eventHooks = &sync.Map{}
43 |
44 | // parsingCallbacks maps server type to map of directive
45 | // to list of callback functions. These aren't really
46 | // plugins on their own, but are often registered from
47 | // plugins.
48 | parsingCallbacks = make(map[string]map[string][]ParsingCallback)
49 |
50 | // caddyfileLoaders is the list of all Caddyfile loaders
51 | // in registration order.
52 | caddyfileLoaders []caddyfileLoader
53 | )
54 |
55 | // DescribePlugins returns a string describing the registered plugins.
56 | func DescribePlugins() string {
57 | pl := ListPlugins()
58 |
59 | str := ""
60 | for _, name := range pl["others"] {
61 | if len(name) > 3 {
62 | str += name[4:] + "\n" // drop dns. prefix caddy adds
63 | } else {
64 | str += name + "\n"
65 | }
66 | }
67 |
68 | return str
69 | }
70 |
71 | // ListPlugins makes a list of the registered plugins,
72 | // keyed by plugin type.
73 | func ListPlugins() map[string][]string {
74 | p := make(map[string][]string)
75 |
76 | // server type plugins
77 | for name := range serverTypes {
78 | p["server_types"] = append(p["server_types"], name)
79 | }
80 |
81 | // caddyfile loaders in registration order
82 | for _, loader := range caddyfileLoaders {
83 | p["caddyfile_loaders"] = append(p["caddyfile_loaders"], loader.name)
84 | }
85 | if defaultCaddyfileLoader.name != "" {
86 | p["caddyfile_loaders"] = append(p["caddyfile_loaders"], defaultCaddyfileLoader.name)
87 | }
88 |
89 | // List the event hook plugins
90 | eventHooks.Range(func(k, _ interface{}) bool {
91 | p["event_hooks"] = append(p["event_hooks"], k.(string))
92 | return true
93 | })
94 |
95 | // alphabetize the rest of the plugins
96 | var others []string
97 | for stype, stypePlugins := range plugins {
98 | for name := range stypePlugins {
99 | var s string
100 | if stype != "" {
101 | s = stype + "."
102 | }
103 | s += name
104 | others = append(others, s)
105 | }
106 | }
107 |
108 | sort.Strings(others)
109 | for _, name := range others {
110 | p["others"] = append(p["others"], name)
111 | }
112 |
113 | return p
114 | }
115 |
116 | // ValidDirectives returns the list of all directives that are
117 | // recognized for the server type serverType. However, not all
118 | // directives may be installed. This makes it possible to give
119 | // more helpful error messages, like "did you mean ..." or
120 | // "maybe you need to plug in ...".
121 | func ValidDirectives(serverType string) []string {
122 | stype, err := getServerType(serverType)
123 | if err != nil {
124 | return nil
125 | }
126 | return stype.Directives()
127 | }
128 |
129 | // ServerListener pairs a server to its listener and/or packetconn.
130 | type ServerListener struct {
131 | server Server
132 | listener net.Listener
133 | packet net.PacketConn
134 | }
135 |
136 | // LocalAddr returns the local network address of the packetconn. It returns
137 | // nil when it is not set.
138 | func (s ServerListener) LocalAddr() net.Addr {
139 | if s.packet == nil {
140 | return nil
141 | }
142 | return s.packet.LocalAddr()
143 | }
144 |
145 | // Addr returns the listener's network address. It returns nil when it is
146 | // not set.
147 | func (s ServerListener) Addr() net.Addr {
148 | if s.listener == nil {
149 | return nil
150 | }
151 | return s.listener.Addr()
152 | }
153 |
154 | // Context is a type which carries a server type through
155 | // the load and setup phase; it maintains the state
156 | // between loading the Caddyfile, then executing its
157 | // directives, then making the servers for Caddy to
158 | // manage. Typically, such state involves configuration
159 | // structs, etc.
160 | type Context interface {
161 | // Called after the Caddyfile is parsed into server
162 | // blocks but before the directives are executed,
163 | // this method gives you an opportunity to inspect
164 | // the server blocks and prepare for the execution
165 | // of directives. Return the server blocks (which
166 | // you may modify, if desired) and an error, if any.
167 | // The first argument is the name or path to the
168 | // configuration file (Caddyfile).
169 | //
170 | // This function can be a no-op and simply return its
171 | // input if there is nothing to do here.
172 | InspectServerBlocks(string, []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error)
173 |
174 | // This is what Caddy calls to make server instances.
175 | // By this time, all directives have been executed and,
176 | // presumably, the context has enough state to produce
177 | // server instances for Caddy to start.
178 | MakeServers() ([]Server, error)
179 | }
180 |
181 | // RegisterServerType registers a server type srv by its
182 | // name, typeName.
183 | func RegisterServerType(typeName string, srv ServerType) {
184 | if _, ok := serverTypes[typeName]; ok {
185 | panic("server type already registered")
186 | }
187 | serverTypes[typeName] = srv
188 | }
189 |
190 | // ServerType contains information about a server type.
191 | type ServerType struct {
192 | // Function that returns the list of directives, in
193 | // execution order, that are valid for this server
194 | // type. Directives should be one word if possible
195 | // and lower-cased.
196 | Directives func() []string
197 |
198 | // DefaultInput returns a default config input if none
199 | // is otherwise loaded. This is optional, but highly
200 | // recommended, otherwise a blank Caddyfile will be
201 | // used.
202 | DefaultInput func() Input
203 |
204 | // The function that produces a new server type context.
205 | // This will be called when a new Caddyfile is being
206 | // loaded, parsed, and executed independently of any
207 | // startup phases before this one. It's a way to keep
208 | // each set of server instances separate and to reduce
209 | // the amount of global state you need.
210 | NewContext func(inst *Instance) Context
211 | }
212 |
213 | // Plugin is a type which holds information about a plugin.
214 | type Plugin struct {
215 | // ServerType is the type of server this plugin is for.
216 | // Can be empty if not applicable, or if the plugin
217 | // can associate with any server type.
218 | ServerType string
219 |
220 | // Action is the plugin's setup function, if associated
221 | // with a directive in the Caddyfile.
222 | Action SetupFunc
223 | }
224 |
225 | // RegisterPlugin plugs in plugin. All plugins should register
226 | // themselves, even if they do not perform an action associated
227 | // with a directive. It is important for the process to know
228 | // which plugins are available.
229 | //
230 | // The plugin MUST have a name: lower case and one word.
231 | // If this plugin has an action, it must be the name of
232 | // the directive that invokes it. A name is always required
233 | // and must be unique for the server type.
234 | func RegisterPlugin(name string, plugin Plugin) {
235 | if name == "" {
236 | panic("plugin must have a name")
237 | }
238 | if _, ok := plugins[plugin.ServerType]; !ok {
239 | plugins[plugin.ServerType] = make(map[string]Plugin)
240 | }
241 | if _, dup := plugins[plugin.ServerType][name]; dup {
242 | panic("plugin named " + name + " already registered for server type " + plugin.ServerType)
243 | }
244 | plugins[plugin.ServerType][name] = plugin
245 | }
246 |
247 | // EventName represents the name of an event used with event hooks.
248 | type EventName string
249 |
250 | // Define names for the various events
251 | const (
252 | StartupEvent EventName = "startup"
253 | ShutdownEvent = "shutdown"
254 | CertRenewEvent = "certrenew"
255 | InstanceStartupEvent = "instancestartup"
256 | InstanceRestartEvent = "instancerestart"
257 | )
258 |
259 | // EventHook is a type which holds information about a startup hook plugin.
260 | type EventHook func(eventType EventName, eventInfo interface{}) error
261 |
262 | // RegisterEventHook plugs in hook. All the hooks should register themselves
263 | // and they must have a name.
264 | func RegisterEventHook(name string, hook EventHook) {
265 | if name == "" {
266 | panic("event hook must have a name")
267 | }
268 | _, dup := eventHooks.LoadOrStore(name, hook)
269 | if dup {
270 | panic("hook named " + name + " already registered")
271 | }
272 | }
273 |
274 | // EmitEvent executes the different hooks passing the EventType as an
275 | // argument. This is a blocking function. Hook developers should
276 | // use 'go' keyword if they don't want to block Caddy.
277 | func EmitEvent(event EventName, info interface{}) {
278 | eventHooks.Range(func(k, v interface{}) bool {
279 | err := v.(EventHook)(event, info)
280 | if err != nil {
281 | log.Printf("error on '%s' hook: %v", k.(string), err)
282 | }
283 | return true
284 | })
285 | }
286 |
287 | // cloneEventHooks return a clone of the event hooks *sync.Map
288 | func cloneEventHooks() *sync.Map {
289 | c := &sync.Map{}
290 | eventHooks.Range(func(k, v interface{}) bool {
291 | c.Store(k, v)
292 | return true
293 | })
294 | return c
295 | }
296 |
297 | // purgeEventHooks purges all event hooks from the map
298 | func purgeEventHooks() {
299 | eventHooks.Range(func(k, _ interface{}) bool {
300 | eventHooks.Delete(k)
301 | return true
302 | })
303 | }
304 |
305 | // restoreEventHooks restores eventHooks with a provided *sync.Map
306 | func restoreEventHooks(m *sync.Map) {
307 | // Purge old event hooks
308 | purgeEventHooks()
309 |
310 | // Restore event hooks
311 | m.Range(func(k, v interface{}) bool {
312 | eventHooks.Store(k, v)
313 | return true
314 | })
315 | }
316 |
317 | // ParsingCallback is a function that is called after
318 | // a directive's setup functions have been executed
319 | // for all the server blocks.
320 | type ParsingCallback func(Context) error
321 |
322 | // RegisterParsingCallback registers callback to be called after
323 | // executing the directive afterDir for server type serverType.
324 | func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) {
325 | if _, ok := parsingCallbacks[serverType]; !ok {
326 | parsingCallbacks[serverType] = make(map[string][]ParsingCallback)
327 | }
328 | parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback)
329 | }
330 |
331 | // SetupFunc is used to set up a plugin, or in other words,
332 | // execute a directive. It will be called once per key for
333 | // each server block it appears in.
334 | type SetupFunc func(c *Controller) error
335 |
336 | // DirectiveAction gets the action for directive dir of
337 | // server type serverType.
338 | func DirectiveAction(serverType, dir string) (SetupFunc, error) {
339 | if stypePlugins, ok := plugins[serverType]; ok {
340 | if plugin, ok := stypePlugins[dir]; ok {
341 | return plugin.Action, nil
342 | }
343 | }
344 | if genericPlugins, ok := plugins[""]; ok {
345 | if plugin, ok := genericPlugins[dir]; ok {
346 | return plugin.Action, nil
347 | }
348 | }
349 | return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)",
350 | dir, serverType)
351 | }
352 |
353 | // Loader is a type that can load a Caddyfile.
354 | // It is passed the name of the server type.
355 | // It returns an error only if something went
356 | // wrong, not simply if there is no Caddyfile
357 | // for this loader to load.
358 | //
359 | // A Loader should only load the Caddyfile if
360 | // a certain condition or requirement is met,
361 | // as returning a non-nil Input value along with
362 | // another Loader will result in an error.
363 | // In other words, loading the Caddyfile must
364 | // be deliberate & deterministic, not haphazard.
365 | //
366 | // The exception is the default Caddyfile loader,
367 | // which will be called only if no other Caddyfile
368 | // loaders return a non-nil Input. The default
369 | // loader may always return an Input value.
370 | type Loader interface {
371 | Load(serverType string) (Input, error)
372 | }
373 |
374 | // LoaderFunc is a convenience type similar to http.HandlerFunc
375 | // that allows you to use a plain function as a Load() method.
376 | type LoaderFunc func(serverType string) (Input, error)
377 |
378 | // Load loads a Caddyfile.
379 | func (lf LoaderFunc) Load(serverType string) (Input, error) {
380 | return lf(serverType)
381 | }
382 |
383 | // RegisterCaddyfileLoader registers loader named name.
384 | func RegisterCaddyfileLoader(name string, loader Loader) {
385 | caddyfileLoaders = append(caddyfileLoaders, caddyfileLoader{name: name, loader: loader})
386 | }
387 |
388 | // SetDefaultCaddyfileLoader registers loader by name
389 | // as the default Caddyfile loader if no others produce
390 | // a Caddyfile. If another Caddyfile loader has already
391 | // been set as the default, this replaces it.
392 | //
393 | // Do not call RegisterCaddyfileLoader on the same
394 | // loader; that would be redundant.
395 | func SetDefaultCaddyfileLoader(name string, loader Loader) {
396 | defaultCaddyfileLoader = caddyfileLoader{name: name, loader: loader}
397 | }
398 |
399 | // loadCaddyfileInput iterates the registered Caddyfile loaders
400 | // and, if needed, calls the default loader, to load a Caddyfile.
401 | // It is an error if any of the loaders return an error or if
402 | // more than one loader returns a Caddyfile.
403 | func loadCaddyfileInput(serverType string) (Input, error) {
404 | var loadedBy string
405 | var caddyfileToUse Input
406 | for _, l := range caddyfileLoaders {
407 | cdyfile, err := l.loader.Load(serverType)
408 | if err != nil {
409 | return nil, fmt.Errorf("loading Caddyfile via %s: %v", l.name, err)
410 | }
411 | if cdyfile != nil {
412 | if caddyfileToUse != nil {
413 | return nil, fmt.Errorf("Caddyfile loaded multiple times; first by %s, then by %s", loadedBy, l.name)
414 | }
415 | loaderUsed = l
416 | caddyfileToUse = cdyfile
417 | loadedBy = l.name
418 | }
419 | }
420 | if caddyfileToUse == nil && defaultCaddyfileLoader.loader != nil {
421 | cdyfile, err := defaultCaddyfileLoader.loader.Load(serverType)
422 | if err != nil {
423 | return nil, err
424 | }
425 | if cdyfile != nil {
426 | loaderUsed = defaultCaddyfileLoader
427 | caddyfileToUse = cdyfile
428 | }
429 | }
430 | return caddyfileToUse, nil
431 | }
432 |
433 | // OnProcessExit is a list of functions to run when the process
434 | // exits -- they are ONLY for cleanup and should not block,
435 | // return errors, or do anything fancy. They will be run with
436 | // every signal, even if "shutdown callbacks" are not executed.
437 | // This variable must only be modified in the main goroutine
438 | // from init() functions.
439 | var OnProcessExit []func()
440 |
441 | // caddyfileLoader pairs the name of a loader to the loader.
442 | type caddyfileLoader struct {
443 | name string
444 | loader Loader
445 | }
446 |
447 | var (
448 | defaultCaddyfileLoader caddyfileLoader // the default loader if all else fail
449 | loaderUsed caddyfileLoader // the loader that was used (relevant for reloads)
450 | )
451 |
--------------------------------------------------------------------------------
/caddyfile/parse.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "io"
19 | "log"
20 | "os"
21 | "path/filepath"
22 | "strings"
23 | )
24 |
25 | // Parse parses the input just enough to group tokens, in
26 | // order, by server block. No further parsing is performed.
27 | // Server blocks are returned in the order in which they appear.
28 | // Directives that do not appear in validDirectives will cause
29 | // an error. If you do not want to check for valid directives,
30 | // pass in nil instead.
31 | func Parse(filename string, input io.Reader, validDirectives []string) ([]ServerBlock, error) {
32 | p := parser{Dispenser: NewDispenser(filename, input), validDirectives: validDirectives}
33 | return p.parseAll()
34 | }
35 |
36 | // allTokens lexes the entire input, but does not parse it.
37 | // It returns all the tokens from the input, unstructured
38 | // and in order.
39 | func allTokens(input io.Reader) ([]Token, error) {
40 | l := new(lexer)
41 | err := l.load(input)
42 | if err != nil {
43 | return nil, err
44 | }
45 | var tokens []Token
46 | for l.next() {
47 | tokens = append(tokens, l.token)
48 | }
49 | return tokens, nil
50 | }
51 |
52 | type parser struct {
53 | Dispenser
54 | block ServerBlock // current server block being parsed
55 | validDirectives []string // a directive must be valid or it's an error
56 | eof bool // if we encounter a valid EOF in a hard place
57 | definedSnippets map[string][]Token
58 | snippetExpansions int // counts snippet imports expanded during this parse
59 | fileExpansions int // counts file/glob imports expanded during this parse
60 | }
61 |
62 | // maxSnippetExpansions is a hard cap to prevent excessively deep or cyclic snippet imports.
63 | // set as a variable to allow modifications for testing
64 | var maxSnippetExpansions = 10000
65 |
66 | // maxFileExpansions is a hard cap to prevent excessively deep or cyclic file imports.
67 | // set as a variable to allow modifications for testing
68 | var maxFileExpansions = 100000
69 |
70 | func (p *parser) parseAll() ([]ServerBlock, error) {
71 | var blocks []ServerBlock
72 |
73 | for p.Next() {
74 | err := p.parseOne()
75 | if err != nil {
76 | return blocks, err
77 | }
78 | if len(p.block.Keys) > 0 {
79 | blocks = append(blocks, p.block)
80 | }
81 | }
82 |
83 | return blocks, nil
84 | }
85 |
86 | func (p *parser) parseOne() error {
87 | p.block = ServerBlock{Tokens: make(map[string][]Token)}
88 |
89 | return p.begin()
90 | }
91 |
92 | func (p *parser) begin() error {
93 | if len(p.tokens) == 0 {
94 | return nil
95 | }
96 |
97 | err := p.addresses()
98 |
99 | if err != nil {
100 | return err
101 | }
102 |
103 | if p.eof {
104 | // this happens if the Caddyfile consists of only
105 | // a line of addresses and nothing else
106 | return nil
107 | }
108 |
109 | if ok, name := p.isSnippet(); ok {
110 | if p.definedSnippets == nil {
111 | p.definedSnippets = map[string][]Token{}
112 | }
113 | if _, found := p.definedSnippets[name]; found {
114 | return p.Errf("redeclaration of previously declared snippet %s", name)
115 | }
116 | // consume all tokens til matched close brace
117 | tokens, err := p.snippetTokens()
118 | if err != nil {
119 | return err
120 | }
121 | // minimal guard: detect trivial self-import in snippet body
122 | for i := 0; i+1 < len(tokens); i++ {
123 | if tokens[i].Text == "import" {
124 | // Only consider it an import directive if at start of a line
125 | atLineStart := i == 0 || tokens[i-1].File != tokens[i].File || tokens[i-1].Line != tokens[i].Line
126 | if atLineStart && replaceEnvVars(tokens[i+1].Text) == name {
127 | return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions)
128 | }
129 | }
130 | }
131 | p.definedSnippets[name] = tokens
132 | // empty block keys so we don't save this block as a real server.
133 | p.block.Keys = nil
134 | return nil
135 | }
136 |
137 | return p.blockContents()
138 | }
139 |
140 | func (p *parser) addresses() error {
141 | var expectingAnother bool
142 |
143 | for {
144 | tkn := replaceEnvVars(p.Val())
145 |
146 | // special case: import directive replaces tokens during parse-time
147 | if tkn == "import" && p.isNewLine() {
148 | err := p.doImport()
149 | if err != nil {
150 | return err
151 | }
152 | continue
153 | }
154 |
155 | // Open brace definitely indicates end of addresses
156 | if tkn == "{" {
157 | if expectingAnother {
158 | return p.Errf("Expected another address but had '%s' - check for extra comma", tkn)
159 | }
160 | break
161 | }
162 |
163 | if tkn != "" { // empty token possible if user typed ""
164 | // Trailing comma indicates another address will follow, which
165 | // may possibly be on the next line
166 | if tkn[len(tkn)-1] == ',' {
167 | tkn = tkn[:len(tkn)-1]
168 | expectingAnother = true
169 | } else {
170 | expectingAnother = false // but we may still see another one on this line
171 | }
172 |
173 | p.block.Keys = append(p.block.Keys, tkn)
174 | }
175 |
176 | // Advance token and possibly break out of loop or return error
177 | hasNext := p.Next()
178 | if expectingAnother && !hasNext {
179 | return p.EOFErr()
180 | }
181 | if !hasNext {
182 | p.eof = true
183 | break // EOF
184 | }
185 | if !expectingAnother && p.isNewLine() {
186 | break
187 | }
188 | }
189 |
190 | return nil
191 | }
192 |
193 | func (p *parser) blockContents() error {
194 | errOpenCurlyBrace := p.openCurlyBrace()
195 | if errOpenCurlyBrace != nil {
196 | // single-server configs don't need curly braces
197 | p.cursor--
198 | }
199 |
200 | err := p.directives()
201 | if err != nil {
202 | return err
203 | }
204 |
205 | // Only look for close curly brace if there was an opening
206 | if errOpenCurlyBrace == nil {
207 | err = p.closeCurlyBrace()
208 | if err != nil {
209 | return err
210 | }
211 | }
212 |
213 | return nil
214 | }
215 |
216 | // directives parses through all the lines for directives
217 | // and it expects the next token to be the first
218 | // directive. It goes until EOF or closing curly brace
219 | // which ends the server block.
220 | func (p *parser) directives() error {
221 | for p.Next() {
222 | // end of server block
223 | if p.Val() == "}" {
224 | break
225 | }
226 |
227 | // special case: import directive replaces tokens during parse-time
228 | if p.Val() == "import" {
229 | err := p.doImport()
230 | if err != nil {
231 | return err
232 | }
233 | p.cursor-- // cursor is advanced when we continue, so roll back one more
234 | continue
235 | }
236 |
237 | // normal case: parse a directive on this line
238 | if err := p.directive(); err != nil {
239 | return err
240 | }
241 | }
242 | return nil
243 | }
244 |
245 | // doImport swaps out the import directive and its argument
246 | // (a total of 2 tokens) with the tokens in the specified file
247 | // or globbing pattern. When the function returns, the cursor
248 | // is on the token before where the import directive was. In
249 | // other words, call Next() to access the first token that was
250 | // imported.
251 | func (p *parser) doImport() error {
252 | // syntax checks
253 | if !p.NextArg() {
254 | return p.ArgErr()
255 | }
256 | importPattern := replaceEnvVars(p.Val())
257 | if importPattern == "" {
258 | return p.Err("Import requires a non-empty filepath")
259 | }
260 | if p.NextArg() {
261 | return p.Err("Import takes only one argument (glob pattern or file)")
262 | }
263 | // splice out the import directive and its argument (2 tokens total)
264 | tokensBefore := p.tokens[:p.cursor-1]
265 | tokensAfter := p.tokens[p.cursor+1:]
266 | var importedTokens []Token
267 |
268 | // first check snippets. Count expansion and enforce cap.
269 | if p.definedSnippets != nil && p.definedSnippets[importPattern] != nil {
270 | if p.snippetExpansions >= maxSnippetExpansions {
271 | return p.Errf("maximum snippet import depth (%d) exceeded", maxSnippetExpansions)
272 | }
273 | p.snippetExpansions++
274 | importedTokens = p.definedSnippets[importPattern]
275 | } else {
276 | if p.fileExpansions >= maxFileExpansions {
277 | return p.Errf("maximum file import depth (%d) exceeded", maxSnippetExpansions)
278 | }
279 | p.fileExpansions++
280 | // make path relative to the file of the _token_ being processed rather
281 | // than current working directory (issue #867) and then use glob to get
282 | // list of matching filenames
283 | absFile, err := filepath.Abs(p.Dispenser.File())
284 | if err != nil {
285 | return p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
286 | }
287 |
288 | var matches []string
289 | var globPattern string
290 | if !filepath.IsAbs(importPattern) {
291 | globPattern = filepath.Join(filepath.Dir(absFile), importPattern)
292 | } else {
293 | globPattern = importPattern
294 | }
295 | if strings.Count(globPattern, "*") > 1 || strings.Count(globPattern, "?") > 1 ||
296 | (strings.Contains(globPattern, "[") && strings.Contains(globPattern, "]")) {
297 | // See issue #2096 - a pattern with many glob expansions can hang for too long
298 | return p.Errf("Glob pattern may only contain one wildcard (*), but has others: %s", globPattern)
299 | }
300 | matches, err = filepath.Glob(globPattern)
301 |
302 | if err != nil {
303 | return p.Errf("Failed to use import pattern %s: %v", importPattern, err)
304 | }
305 | if len(matches) == 0 {
306 | if strings.ContainsAny(globPattern, "*?[]") {
307 | log.Printf("[WARNING] No files matching import glob pattern: %s", importPattern)
308 | } else {
309 | return p.Errf("File to import not found: %s", importPattern)
310 | }
311 | }
312 |
313 | // collect all the imported tokens
314 |
315 | for _, importFile := range matches {
316 | newTokens, err := p.doSingleImport(importFile)
317 | if err != nil {
318 | return err
319 | }
320 | importedTokens = append(importedTokens, newTokens...)
321 | }
322 | }
323 |
324 | // splice the imported tokens in the place of the import statement
325 | // and rewind cursor so Next() will land on first imported token
326 | p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
327 | p.cursor--
328 |
329 | return nil
330 | }
331 |
332 | // doSingleImport lexes the individual file at importFile and returns
333 | // its tokens or an error, if any.
334 | func (p *parser) doSingleImport(importFile string) ([]Token, error) {
335 | file, err := os.Open(importFile)
336 | if err != nil {
337 | return nil, p.Errf("Could not import %s: %v", importFile, err)
338 | }
339 | defer file.Close()
340 |
341 | if info, err := file.Stat(); err != nil {
342 | return nil, p.Errf("Could not import %s: %v", importFile, err)
343 | } else if info.IsDir() {
344 | return nil, p.Errf("Could not import %s: is a directory", importFile)
345 | }
346 |
347 | importedTokens, err := allTokens(file)
348 | if err != nil {
349 | return nil, p.Errf("Could not read tokens while importing %s: %v", importFile, err)
350 | }
351 |
352 | // Tack the file path onto these tokens so errors show the imported file's name
353 | // (we use full, absolute path to avoid bugs: issue #1892)
354 | filename, err := filepath.Abs(importFile)
355 | if err != nil {
356 | return nil, p.Errf("Failed to get absolute path of file: %s: %v", p.Dispenser.filename, err)
357 | }
358 | for i := 0; i < len(importedTokens); i++ {
359 | importedTokens[i].File = filename
360 | }
361 |
362 | return importedTokens, nil
363 | }
364 |
365 | // directive collects tokens until the directive's scope
366 | // closes (either end of line or end of curly brace block).
367 | // It expects the currently-loaded token to be a directive
368 | // (or } that ends a server block). The collected tokens
369 | // are loaded into the current server block for later use
370 | // by directive setup functions.
371 | func (p *parser) directive() error {
372 | dir := replaceEnvVars(p.Val())
373 | nesting := 0
374 |
375 | // TODO: More helpful error message ("did you mean..." or "maybe you need to install its server type")
376 | if !p.validDirective(dir) {
377 | return p.Errf("Unknown directive '%s'", dir)
378 | }
379 |
380 | // The directive itself is appended as a relevant token
381 | p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
382 |
383 | for p.Next() {
384 | if p.Val() == "{" {
385 | nesting++
386 | } else if p.isNewLine() && nesting == 0 {
387 | p.cursor-- // read too far
388 | break
389 | } else if p.Val() == "}" && nesting > 0 {
390 | nesting--
391 | } else if p.Val() == "}" && nesting == 0 {
392 | return p.Err("Unexpected '}' because no matching opening brace")
393 | } else if p.Val() == "import" && p.isNewLine() {
394 | if err := p.doImport(); err != nil {
395 | return err
396 | }
397 | p.cursor-- // cursor is advanced when we continue, so roll back one more
398 | continue
399 | }
400 | p.tokens[p.cursor].Text = replaceEnvVars(p.tokens[p.cursor].Text)
401 | p.block.Tokens[dir] = append(p.block.Tokens[dir], p.tokens[p.cursor])
402 | }
403 |
404 | if nesting > 0 {
405 | return p.EOFErr()
406 | }
407 | return nil
408 | }
409 |
410 | // openCurlyBrace expects the current token to be an
411 | // opening curly brace. This acts like an assertion
412 | // because it returns an error if the token is not
413 | // a opening curly brace. It does NOT advance the token.
414 | func (p *parser) openCurlyBrace() error {
415 | if p.Val() != "{" {
416 | return p.SyntaxErr("{")
417 | }
418 | return nil
419 | }
420 |
421 | // closeCurlyBrace expects the current token to be
422 | // a closing curly brace. This acts like an assertion
423 | // because it returns an error if the token is not
424 | // a closing curly brace. It does NOT advance the token.
425 | func (p *parser) closeCurlyBrace() error {
426 | if p.Val() != "}" {
427 | return p.SyntaxErr("}")
428 | }
429 | return nil
430 | }
431 |
432 | // validDirective returns true if dir is in p.validDirectives.
433 | func (p *parser) validDirective(dir string) bool {
434 | if p.validDirectives == nil {
435 | return true
436 | }
437 | for _, d := range p.validDirectives {
438 | if d == dir {
439 | return true
440 | }
441 | }
442 | return false
443 | }
444 |
445 | // replaceEnvVars replaces environment variables that appear in the token
446 | // and understands both the $UNIX and %WINDOWS% syntaxes.
447 | func replaceEnvVars(s string) string {
448 | s = replaceEnvReferences(s, "{%", "%}")
449 | s = replaceEnvReferences(s, "{$", "}")
450 | return s
451 | }
452 |
453 | // replaceEnvReferences performs the actual replacement of env variables
454 | // in s, given the placeholder start and placeholder end strings.
455 | func replaceEnvReferences(s, refStart, refEnd string) string {
456 | index := strings.Index(s, refStart)
457 | for index != -1 {
458 | endIndex := strings.Index(s[index:], refEnd)
459 | if endIndex == -1 {
460 | break
461 | }
462 |
463 | endIndex += index
464 | if endIndex > index+len(refStart) {
465 | ref := s[index : endIndex+len(refEnd)]
466 | s = strings.Replace(s, ref, os.Getenv(ref[len(refStart):len(ref)-len(refEnd)]), -1)
467 | } else {
468 | return s
469 | }
470 | index = strings.Index(s, refStart)
471 | }
472 | return s
473 | }
474 |
475 | // ServerBlock associates any number of keys (usually addresses
476 | // of some sort) with tokens (grouped by directive name).
477 | type ServerBlock struct {
478 | Keys []string
479 | Tokens map[string][]Token
480 | }
481 |
482 | func (p *parser) isSnippet() (bool, string) {
483 | keys := p.block.Keys
484 | // A snippet block is a single key with parens. Nothing else qualifies.
485 | if len(keys) == 1 && strings.HasPrefix(keys[0], "(") && strings.HasSuffix(keys[0], ")") {
486 | return true, strings.TrimSuffix(keys[0][1:], ")")
487 | }
488 | return false, ""
489 | }
490 |
491 | // read and store everything in a block for later replay.
492 | func (p *parser) snippetTokens() ([]Token, error) {
493 | // TODO: disallow imports in snippets for simplicity at import time
494 | // snippet must have curlies.
495 | err := p.openCurlyBrace()
496 | if err != nil {
497 | return nil, err
498 | }
499 | count := 1
500 | tokens := []Token{}
501 | for p.Next() {
502 | if p.Val() == "}" {
503 | count--
504 | if count == 0 {
505 | break
506 | }
507 | }
508 | if p.Val() == "{" {
509 | count++
510 | }
511 | tokens = append(tokens, p.tokens[p.cursor])
512 | }
513 | // make sure we're matched up
514 | if count != 0 {
515 | return nil, p.SyntaxErr("}")
516 | }
517 | return tokens, nil
518 | }
519 |
--------------------------------------------------------------------------------
/caddyfile/parse_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
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 caddyfile
16 |
17 | import (
18 | "fmt"
19 | "io/ioutil"
20 | "os"
21 | "path/filepath"
22 | "strings"
23 | "testing"
24 | )
25 |
26 | func init() {
27 | // set a lower limit for testing only
28 | maxSnippetExpansions = 10
29 | maxFileExpansions = 10
30 | }
31 |
32 | func TestAllTokens(t *testing.T) {
33 | tests := []struct {
34 | name string
35 | input string
36 | expected []string
37 | }{
38 | {
39 | name: "not-empty",
40 | input: "a b c\nd e",
41 | expected: []string{"a", "b", "c", "d", "e"},
42 | }, {
43 | name: "empty",
44 | input: "",
45 | }, {
46 | name: "newline",
47 | input: "\n",
48 | }, {
49 | name: "space",
50 | input: " ",
51 | }, {
52 | name: "tab and newline",
53 | input: "\t\n",
54 | },
55 | }
56 |
57 | for _, tt := range tests {
58 | t.Run(tt.name, func(t *testing.T) {
59 | tokens, err := allTokens(strings.NewReader(tt.input))
60 |
61 | if err != nil {
62 | t.Fatalf("Expected no error, got %v", err)
63 | }
64 | if len(tokens) != len(tt.expected) {
65 | t.Fatalf("Expected %d tokens, got %d", len(tt.expected), len(tokens))
66 | }
67 |
68 | for i, val := range tt.expected {
69 | if tokens[i].Text != val {
70 | t.Errorf("Token %d should be '%s' but was '%s'", i, val, tokens[i].Text)
71 | }
72 | }
73 | })
74 | }
75 | }
76 |
77 | func TestParseOneAndImport(t *testing.T) {
78 | testParseOne := func(input string) (ServerBlock, error) {
79 | p := testParser(input)
80 | p.Next() // parseOne doesn't call Next() to start, so we must
81 | err := p.parseOne()
82 | return p.block, err
83 | }
84 |
85 | for i, test := range []struct {
86 | input string
87 | shouldErr bool
88 | keys []string
89 | tokens map[string]int // map of directive name to number of tokens expected
90 | }{
91 | {`localhost`, false, []string{
92 | "localhost",
93 | }, map[string]int{}},
94 |
95 | {`localhost
96 | dir1`, false, []string{
97 | "localhost",
98 | }, map[string]int{
99 | "dir1": 1,
100 | }},
101 |
102 | {`localhost:1234
103 | dir1 foo bar`, false, []string{
104 | "localhost:1234",
105 | }, map[string]int{
106 | "dir1": 3,
107 | }},
108 |
109 | {`localhost {
110 | dir1
111 | }`, false, []string{
112 | "localhost",
113 | }, map[string]int{
114 | "dir1": 1,
115 | }},
116 |
117 | {`localhost:1234 {
118 | dir1 foo bar
119 | dir2
120 | }`, false, []string{
121 | "localhost:1234",
122 | }, map[string]int{
123 | "dir1": 3,
124 | "dir2": 1,
125 | }},
126 |
127 | {`http://localhost https://localhost
128 | dir1 foo bar`, false, []string{
129 | "http://localhost",
130 | "https://localhost",
131 | }, map[string]int{
132 | "dir1": 3,
133 | }},
134 |
135 | {`http://localhost https://localhost {
136 | dir1 foo bar
137 | }`, false, []string{
138 | "http://localhost",
139 | "https://localhost",
140 | }, map[string]int{
141 | "dir1": 3,
142 | }},
143 |
144 | {`http://localhost, https://localhost {
145 | dir1 foo bar
146 | }`, false, []string{
147 | "http://localhost",
148 | "https://localhost",
149 | }, map[string]int{
150 | "dir1": 3,
151 | }},
152 |
153 | {`http://localhost, {
154 | }`, true, []string{
155 | "http://localhost",
156 | }, map[string]int{}},
157 |
158 | {`host1:80, http://host2.com
159 | dir1 foo bar
160 | dir2 baz`, false, []string{
161 | "host1:80",
162 | "http://host2.com",
163 | }, map[string]int{
164 | "dir1": 3,
165 | "dir2": 2,
166 | }},
167 |
168 | {`http://host1.com,
169 | http://host2.com,
170 | https://host3.com`, false, []string{
171 | "http://host1.com",
172 | "http://host2.com",
173 | "https://host3.com",
174 | }, map[string]int{}},
175 |
176 | {`http://host1.com:1234, https://host2.com
177 | dir1 foo {
178 | bar baz
179 | }
180 | dir2`, false, []string{
181 | "http://host1.com:1234",
182 | "https://host2.com",
183 | }, map[string]int{
184 | "dir1": 6,
185 | "dir2": 1,
186 | }},
187 |
188 | {`127.0.0.1
189 | dir1 {
190 | bar baz
191 | }
192 | dir2 {
193 | foo bar
194 | }`, false, []string{
195 | "127.0.0.1",
196 | }, map[string]int{
197 | "dir1": 5,
198 | "dir2": 5,
199 | }},
200 |
201 | {`localhost
202 | dir1 {
203 | foo`, true, []string{
204 | "localhost",
205 | }, map[string]int{
206 | "dir1": 3,
207 | }},
208 |
209 | {`localhost
210 | dir1 {
211 | }`, false, []string{
212 | "localhost",
213 | }, map[string]int{
214 | "dir1": 3,
215 | }},
216 |
217 | {`localhost
218 | dir1 {
219 | } }`, true, []string{
220 | "localhost",
221 | }, map[string]int{
222 | "dir1": 3,
223 | }},
224 |
225 | {`localhost
226 | dir1 {
227 | nested {
228 | foo
229 | }
230 | }
231 | dir2 foo bar`, false, []string{
232 | "localhost",
233 | }, map[string]int{
234 | "dir1": 7,
235 | "dir2": 3,
236 | }},
237 |
238 | {``, false, []string{}, map[string]int{}},
239 |
240 | {`localhost
241 | dir1 arg1
242 | import testdata/import_test1.txt`, false, []string{
243 | "localhost",
244 | }, map[string]int{
245 | "dir1": 2,
246 | "dir2": 3,
247 | "dir3": 1,
248 | }},
249 |
250 | {`import testdata/import_test2.txt`, false, []string{
251 | "host1",
252 | }, map[string]int{
253 | "dir1": 1,
254 | "dir2": 2,
255 | }},
256 |
257 | {`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, map[string]int{}},
258 |
259 | {`import testdata/not_found.txt`, true, []string{}, map[string]int{}},
260 |
261 | {`""`, false, []string{}, map[string]int{}},
262 |
263 | {``, false, []string{}, map[string]int{}},
264 |
265 | // test cases found by fuzzing!
266 | {`import }{$"`, true, []string{}, map[string]int{}},
267 | {`import /*/*.txt`, true, []string{}, map[string]int{}},
268 | {`import /???/?*?o`, true, []string{}, map[string]int{}},
269 | {`import /??`, true, []string{}, map[string]int{}},
270 | {`import /[a-z]`, true, []string{}, map[string]int{}},
271 | {`import {$}`, true, []string{}, map[string]int{}},
272 | {`import {%}`, true, []string{}, map[string]int{}},
273 | {`import {$$}`, true, []string{}, map[string]int{}},
274 | {`import {%%}`, true, []string{}, map[string]int{}},
275 | } {
276 | result, err := testParseOne(test.input)
277 |
278 | if test.shouldErr && err == nil {
279 | t.Errorf("Test %d: Expected an error, but didn't get one", i)
280 | }
281 | if !test.shouldErr && err != nil {
282 | t.Errorf("Test %d: Expected no error, but got: %v", i, err)
283 | }
284 |
285 | if len(result.Keys) != len(test.keys) {
286 | t.Errorf("Test %d: Expected %d keys, got %d",
287 | i, len(test.keys), len(result.Keys))
288 | continue
289 | }
290 | for j, addr := range result.Keys {
291 | if addr != test.keys[j] {
292 | t.Errorf("Test %d, key %d: Expected '%s', but was '%s'",
293 | i, j, test.keys[j], addr)
294 | }
295 | }
296 |
297 | if len(result.Tokens) != len(test.tokens) {
298 | t.Errorf("Test %d: Expected %d directives, had %d",
299 | i, len(test.tokens), len(result.Tokens))
300 | continue
301 | }
302 | for directive, tokens := range result.Tokens {
303 | if len(tokens) != test.tokens[directive] {
304 | t.Errorf("Test %d, directive '%s': Expected %d tokens, counted %d",
305 | i, directive, test.tokens[directive], len(tokens))
306 | continue
307 | }
308 | }
309 | }
310 | }
311 |
312 | func TestRecursiveImport(t *testing.T) {
313 | testParseOne := func(input string) (ServerBlock, error) {
314 | p := testParser(input)
315 | p.Next() // parseOne doesn't call Next() to start, so we must
316 | err := p.parseOne()
317 | return p.block, err
318 | }
319 |
320 | isExpected := func(got ServerBlock) bool {
321 | if len(got.Keys) != 1 || got.Keys[0] != "localhost" {
322 | t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
323 | return false
324 | }
325 | if len(got.Tokens) != 2 {
326 | t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
327 | return false
328 | }
329 | if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["dir2"]) != 2 {
330 | t.Errorf("got unexpect tokens: %v", got.Tokens)
331 | return false
332 | }
333 | return true
334 | }
335 |
336 | recursiveFile1, err := filepath.Abs("testdata/recursive_import_test1")
337 | if err != nil {
338 | t.Fatal(err)
339 | }
340 | recursiveFile2, err := filepath.Abs("testdata/recursive_import_test2")
341 | if err != nil {
342 | t.Fatal(err)
343 | }
344 |
345 | // test relative recursive import
346 | err = ioutil.WriteFile(recursiveFile1, []byte(
347 | `localhost
348 | dir1
349 | import recursive_import_test2`), 0644)
350 | if err != nil {
351 | t.Fatal(err)
352 | }
353 | defer os.Remove(recursiveFile1)
354 |
355 | err = ioutil.WriteFile(recursiveFile2, []byte("dir2 1"), 0644)
356 | if err != nil {
357 | t.Fatal(err)
358 | }
359 | defer os.Remove(recursiveFile2)
360 |
361 | // import absolute path
362 | result, err := testParseOne("import " + recursiveFile1)
363 | if err != nil {
364 | t.Fatal(err)
365 | }
366 | if !isExpected(result) {
367 | t.Error("absolute+relative import failed")
368 | }
369 |
370 | // import relative path
371 | result, err = testParseOne("import testdata/recursive_import_test1")
372 | if err != nil {
373 | t.Fatal(err)
374 | }
375 | if !isExpected(result) {
376 | t.Error("relative+relative import failed")
377 | }
378 |
379 | // test absolute recursive import
380 | err = ioutil.WriteFile(recursiveFile1, []byte(
381 | `localhost
382 | dir1
383 | import `+recursiveFile2), 0644)
384 | if err != nil {
385 | t.Fatal(err)
386 | }
387 |
388 | // import absolute path
389 | result, err = testParseOne("import " + recursiveFile1)
390 | if err != nil {
391 | t.Fatal(err)
392 | }
393 | if !isExpected(result) {
394 | t.Error("absolute+absolute import failed")
395 | }
396 |
397 | // import relative path
398 | result, err = testParseOne("import testdata/recursive_import_test1")
399 | if err != nil {
400 | t.Fatal(err)
401 | }
402 | if !isExpected(result) {
403 | t.Error("relative+absolute import failed")
404 | }
405 | }
406 |
407 | func TestDirectiveImport(t *testing.T) {
408 | testParseOne := func(input string) (ServerBlock, error) {
409 | p := testParser(input)
410 | p.Next() // parseOne doesn't call Next() to start, so we must
411 | err := p.parseOne()
412 | return p.block, err
413 | }
414 |
415 | isExpected := func(got ServerBlock) bool {
416 | if len(got.Keys) != 1 || got.Keys[0] != "localhost" {
417 | t.Errorf("got keys unexpected: expect localhost, got %v", got.Keys)
418 | return false
419 | }
420 | if len(got.Tokens) != 2 {
421 | t.Errorf("got wrong number of tokens: expect 2, got %d", len(got.Tokens))
422 | return false
423 | }
424 | if len(got.Tokens["dir1"]) != 1 || len(got.Tokens["proxy"]) != 8 {
425 | t.Errorf("got unexpect tokens: %v", got.Tokens)
426 | return false
427 | }
428 | return true
429 | }
430 |
431 | directiveFile, err := filepath.Abs("testdata/directive_import_test")
432 | if err != nil {
433 | t.Fatal(err)
434 | }
435 |
436 | err = ioutil.WriteFile(directiveFile, []byte(`prop1 1
437 | prop2 2`), 0644)
438 | if err != nil {
439 | t.Fatal(err)
440 | }
441 | defer os.Remove(directiveFile)
442 |
443 | // import from existing file
444 | result, err := testParseOne(`localhost
445 | dir1
446 | proxy {
447 | import testdata/directive_import_test
448 | transparent
449 | }`)
450 | if err != nil {
451 | t.Fatal(err)
452 | }
453 | if !isExpected(result) {
454 | t.Error("directive import failed")
455 | }
456 |
457 | // import from nonexistent file
458 | _, err = testParseOne(`localhost
459 | dir1
460 | proxy {
461 | import testdata/nonexistent_file
462 | transparent
463 | }`)
464 | if err == nil {
465 | t.Fatal("expected error when importing a nonexistent file")
466 | }
467 | }
468 |
469 | func TestParseAll(t *testing.T) {
470 | for i, test := range []struct {
471 | input string
472 | shouldErr bool
473 | keys [][]string // keys per server block, in order
474 | }{
475 | {`localhost`, false, [][]string{
476 | {"localhost"},
477 | }},
478 |
479 | {`localhost:1234`, false, [][]string{
480 | {"localhost:1234"},
481 | }},
482 |
483 | {`localhost:1234 {
484 | }
485 | localhost:2015 {
486 | }`, false, [][]string{
487 | {"localhost:1234"},
488 | {"localhost:2015"},
489 | }},
490 |
491 | {`localhost:1234, http://host2`, false, [][]string{
492 | {"localhost:1234", "http://host2"},
493 | }},
494 |
495 | {`localhost:1234, http://host2,`, true, [][]string{}},
496 |
497 | {`http://host1.com, http://host2.com {
498 | }
499 | https://host3.com, https://host4.com {
500 | }`, false, [][]string{
501 | {"http://host1.com", "http://host2.com"},
502 | {"https://host3.com", "https://host4.com"},
503 | }},
504 |
505 | {`import testdata/import_glob*.txt`, false, [][]string{
506 | {"glob0.host0"},
507 | {"glob0.host1"},
508 | {"glob1.host0"},
509 | {"glob2.host0"},
510 | }},
511 |
512 | {`import notfound/*`, false, [][]string{}}, // glob needn't error with no matches
513 | {`import notfound/file.conf`, true, [][]string{}}, // but a specific file should
514 | } {
515 | p := testParser(test.input)
516 | blocks, err := p.parseAll()
517 |
518 | if test.shouldErr && err == nil {
519 | t.Errorf("Test %d: Expected an error, but didn't get one", i)
520 | }
521 | if !test.shouldErr && err != nil {
522 | t.Errorf("Test %d: Expected no error, but got: %v", i, err)
523 | }
524 |
525 | if len(blocks) != len(test.keys) {
526 | t.Errorf("Test %d: Expected %d server blocks, got %d",
527 | i, len(test.keys), len(blocks))
528 | continue
529 | }
530 | for j, block := range blocks {
531 | if len(block.Keys) != len(test.keys[j]) {
532 | t.Errorf("Test %d: Expected %d keys in block %d, got %d",
533 | i, len(test.keys[j]), j, len(block.Keys))
534 | continue
535 | }
536 | for k, addr := range block.Keys {
537 | if addr != test.keys[j][k] {
538 | t.Errorf("Test %d, block %d, key %d: Expected '%s', but got '%s'",
539 | i, j, k, test.keys[j][k], addr)
540 | }
541 | }
542 | }
543 | }
544 | }
545 |
546 | func TestEnvironmentReplacement(t *testing.T) {
547 | os.Setenv("PORT", "8080")
548 | os.Setenv("ADDRESS", "servername.com")
549 | os.Setenv("FOOBAR", "foobar")
550 | os.Setenv("PARTIAL_DIR", "r1")
551 |
552 | // basic test; unix-style env vars
553 | p := testParser(`{$ADDRESS}`)
554 | blocks, _ := p.parseAll()
555 | if actual, expected := blocks[0].Keys[0], "servername.com"; expected != actual {
556 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
557 | }
558 |
559 | // basic test; unix-style env vars
560 | p = testParser(`di{$PARTIAL_DIR}`)
561 | blocks, _ = p.parseAll()
562 | if actual, expected := blocks[0].Keys[0], "dir1"; expected != actual {
563 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
564 | }
565 |
566 | // multiple vars per token
567 | p = testParser(`{$ADDRESS}:{$PORT}`)
568 | blocks, _ = p.parseAll()
569 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
570 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
571 | }
572 |
573 | // windows-style var and unix style in same token
574 | p = testParser(`{%ADDRESS%}:{$PORT}`)
575 | blocks, _ = p.parseAll()
576 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
577 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
578 | }
579 |
580 | // reverse order
581 | p = testParser(`{$ADDRESS}:{%PORT%}`)
582 | blocks, _ = p.parseAll()
583 | if actual, expected := blocks[0].Keys[0], "servername.com:8080"; expected != actual {
584 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
585 | }
586 |
587 | // env var in server block body as argument
588 | p = testParser(":{%PORT%}\ndir1 {$FOOBAR}")
589 | blocks, _ = p.parseAll()
590 | if actual, expected := blocks[0].Keys[0], ":8080"; expected != actual {
591 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
592 | }
593 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "foobar"; expected != actual {
594 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
595 | }
596 |
597 | // combined windows env vars in argument
598 | p = testParser(":{%PORT%}\ndir1 {%ADDRESS%}/{%FOOBAR%}")
599 | blocks, _ = p.parseAll()
600 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "servername.com/foobar"; expected != actual {
601 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
602 | }
603 |
604 | // malformed env var (windows)
605 | p = testParser(":1234\ndir1 {%ADDRESS}")
606 | blocks, _ = p.parseAll()
607 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "{%ADDRESS}"; expected != actual {
608 | t.Errorf("Expected host to be '%s' but was '%s'", expected, actual)
609 | }
610 |
611 | // malformed (non-existent) env var (unix)
612 | p = testParser(`:{$PORT$}`)
613 | blocks, _ = p.parseAll()
614 | if actual, expected := blocks[0].Keys[0], ":"; expected != actual {
615 | t.Errorf("Expected key to be '%s' but was '%s'", expected, actual)
616 | }
617 |
618 | // in quoted field
619 | p = testParser(":1234\ndir1 \"Test {$FOOBAR} test\"")
620 | blocks, _ = p.parseAll()
621 | if actual, expected := blocks[0].Tokens["dir1"][1].Text, "Test foobar test"; expected != actual {
622 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
623 | }
624 |
625 | // after end token
626 | p = testParser(":1234\nanswer \"{{ .Name }} {$FOOBAR}\"")
627 | blocks, _ = p.parseAll()
628 | if actual, expected := blocks[0].Tokens["answer"][1].Text, "{{ .Name }} foobar"; expected != actual {
629 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
630 | }
631 | }
632 |
633 | func testParser(input string) parser {
634 | buf := strings.NewReader(input)
635 | p := parser{Dispenser: NewDispenser("Caddyfile", buf)}
636 | return p
637 | }
638 |
639 | func TestSnippets(t *testing.T) {
640 | p := testParser(`
641 | (common) {
642 | gzip foo
643 | errors stderr
644 | }
645 | http://example.com {
646 | import common
647 | }
648 | `)
649 | blocks, err := p.parseAll()
650 | if err != nil {
651 | t.Fatal(err)
652 | }
653 | for _, b := range blocks {
654 | t.Log(b.Keys)
655 | t.Log(b.Tokens)
656 | }
657 | if len(blocks) != 1 {
658 | t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
659 | }
660 | if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
661 | t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
662 | }
663 | if len(blocks[0].Tokens) != 2 {
664 | t.Fatalf("Server block should have tokens from import")
665 | }
666 | if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
667 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
668 | }
669 | if actual, expected := blocks[0].Tokens["errors"][1].Text, "stderr"; expected != actual {
670 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
671 | }
672 |
673 | }
674 |
675 | func writeStringToTempFileOrDie(t *testing.T, str string) (pathToFile string) {
676 | file, err := ioutil.TempFile("", t.Name())
677 | if err != nil {
678 | panic(err) // get a stack trace so we know where this was called from.
679 | }
680 | if _, err := file.WriteString(str); err != nil {
681 | panic(err)
682 | }
683 | if err := file.Close(); err != nil {
684 | panic(err)
685 | }
686 | return file.Name()
687 | }
688 |
689 | func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
690 | fileName := writeStringToTempFileOrDie(t, `
691 | http://example.com {
692 | # This isn't an import directive, it's just an arg with value 'import'
693 | basicauth / import password
694 | }
695 | `)
696 | // Parse the root file that imports the other one.
697 | p := testParser(`import ` + fileName)
698 | blocks, err := p.parseAll()
699 | if err != nil {
700 | t.Fatal(err)
701 | }
702 | for _, b := range blocks {
703 | t.Log(b.Keys)
704 | t.Log(b.Tokens)
705 | }
706 | auth := blocks[0].Tokens["basicauth"]
707 | line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
708 | if line != "basicauth / import password" {
709 | // Previously, it would be changed to:
710 | // basicauth / import /path/to/test/dir/password
711 | // referencing a file that (probably) doesn't exist and changing the
712 | // password!
713 | t.Errorf("Expected basicauth tokens to be 'basicauth / import password' but got %#q", line)
714 | }
715 | }
716 |
717 | func TestSnippetAcrossMultipleFiles(t *testing.T) {
718 | // Make the derived Caddyfile that expects (common) to be defined.
719 | fileName := writeStringToTempFileOrDie(t, `
720 | http://example.com {
721 | import common
722 | }
723 | `)
724 |
725 | // Parse the root file that defines (common) and then imports the other one.
726 | p := testParser(`
727 | (common) {
728 | gzip foo
729 | }
730 | import ` + fileName + `
731 | `)
732 |
733 | blocks, err := p.parseAll()
734 | if err != nil {
735 | t.Fatal(err)
736 | }
737 | for _, b := range blocks {
738 | t.Log(b.Keys)
739 | t.Log(b.Tokens)
740 | }
741 | if len(blocks) != 1 {
742 | t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
743 | }
744 | if actual, expected := blocks[0].Keys[0], "http://example.com"; expected != actual {
745 | t.Errorf("Expected server name to be '%s' but was '%s'", expected, actual)
746 | }
747 | if len(blocks[0].Tokens) != 1 {
748 | t.Fatalf("Server block should have tokens from import")
749 | }
750 | if actual, expected := blocks[0].Tokens["gzip"][0].Text, "gzip"; expected != actual {
751 | t.Errorf("Expected argument to be '%s' but was '%s'", expected, actual)
752 | }
753 | }
754 |
755 | func TestSnippetImportCycle(t *testing.T) {
756 | tests := []struct {
757 | name string
758 | input string
759 | }{
760 | {
761 | name: "direct self-import",
762 | input: `
763 | (loop) {
764 | import loop
765 | }
766 | import loop
767 | `,
768 | },
769 | {
770 | name: "self-import via recursion",
771 | input: `
772 | (loop) {
773 | { }
774 | import loop
775 | }
776 | import loop
777 | `,
778 | },
779 | }
780 |
781 | for _, tt := range tests {
782 | t.Run(tt.name, func(t *testing.T) {
783 | p := testParser(tt.input)
784 | _, err := p.parseAll()
785 | if err == nil {
786 | t.Fatalf("expected error for snippet self-import cycle, got nil")
787 | }
788 | })
789 | }
790 | }
791 |
792 | func TestFileImportCycleError(t *testing.T) {
793 | fileA := writeStringToTempFileOrDie(t, "")
794 | defer os.Remove(fileA)
795 | fileB := writeStringToTempFileOrDie(t, "")
796 | defer os.Remove(fileB)
797 |
798 | if err := ioutil.WriteFile(fileA, []byte("import "+fileB), 0644); err != nil {
799 | t.Fatal(err)
800 | }
801 | if err := ioutil.WriteFile(fileB, []byte("import "+fileA), 0644); err != nil {
802 | t.Fatal(err)
803 | }
804 |
805 | p := testParser("import " + fileA)
806 | _, err := p.parseAll()
807 | if err == nil {
808 | t.Fatalf("expected error for file import cycle, got nil")
809 | }
810 | }
811 |
812 | func TestFileImportDir(t *testing.T) {
813 | dir, err := ioutil.TempDir("", t.Name())
814 | if err != nil {
815 | t.Fatal(err)
816 | }
817 | defer os.RemoveAll(dir)
818 |
819 | // create 10x the maxFileExpansions files
820 | // a single import with a glob should not error
821 | for i := 0; i < maxFileExpansions*10; i++ {
822 | fp := filepath.Join(dir, filepath.Base(dir)+"_"+fmt.Sprintf("%d", i))
823 | if err := ioutil.WriteFile(fp, []byte(""), 0644); err != nil {
824 | t.Fatal(err)
825 | }
826 | }
827 |
828 | input := "import " + filepath.Join(dir, "*")
829 | p := testParser(input)
830 | _, err = p.parseAll()
831 | if err != nil {
832 | t.Fatalf("unexpected error importing temp dir via glob: %v", err)
833 | }
834 | }
835 |
--------------------------------------------------------------------------------
/caddy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Light Code Labs, LLC
2 | //
3 | // Copyright 2024 MWS
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | // Package caddy implements the Caddy server manager.
18 | //
19 | // To use this package:
20 | //
21 | // 1. Set the AppName and AppVersion variables.
22 | // 2. Call LoadCaddyfile() to get the Caddyfile.
23 | // Pass in the name of the server type (like "http").
24 | // Make sure the server type's package is imported
25 | // (import _ "github.com/coredns/caddy/caddyhttp").
26 | // 3. Call caddy.Start() to start Caddy. You get back
27 | // an Instance, on which you can call Restart() to
28 | // restart it or Stop() to stop it.
29 | //
30 | // You should call Wait() on your instance to wait for
31 | // all servers to quit before your process exits.
32 | package caddy
33 |
34 | import (
35 | "bytes"
36 | "encoding/gob"
37 | "fmt"
38 | "io"
39 | "io/ioutil"
40 | "log"
41 | "net"
42 | "os"
43 | "strconv"
44 | "strings"
45 | "sync"
46 | "time"
47 |
48 | "github.com/coredns/caddy/caddyfile"
49 | )
50 |
51 | // Configurable application parameters
52 | var (
53 | // AppName is the name of the application.
54 | AppName string
55 |
56 | // AppVersion is the version of the application.
57 | AppVersion string
58 |
59 | // Quiet mode will not show any informative output on initialization.
60 | Quiet bool
61 |
62 | // PidFile is the path to the pidfile to create.
63 | PidFile string
64 |
65 | // GracefulTimeout is the maximum duration of a graceful shutdown.
66 | GracefulTimeout time.Duration
67 |
68 | // isUpgrade will be set to true if this process
69 | // was started as part of an upgrade, where a parent
70 | // Caddy process started this one.
71 | isUpgrade = os.Getenv("CADDY__UPGRADE") == "1"
72 |
73 | // started will be set to true when the first
74 | // instance is started; it never gets set to
75 | // false after that.
76 | started bool
77 |
78 | // mu protects the variables 'isUpgrade' and 'started'.
79 | mu sync.Mutex
80 | )
81 |
82 | func init() {
83 | OnProcessExit = append(OnProcessExit, func() {
84 | if PidFile != "" {
85 | os.Remove(PidFile)
86 | }
87 | })
88 | }
89 |
90 | // Instance contains the state of servers created as a result of
91 | // calling Start and can be used to access or control those servers.
92 | // It is literally an instance of a server type. Instance values
93 | // should NOT be copied. Use *Instance for safety.
94 | type Instance struct {
95 | // serverType is the name of the instance's server type
96 | serverType string
97 |
98 | // caddyfileInput is the input configuration text used for this process
99 | caddyfileInput Input
100 |
101 | // wg is used to wait for all servers to shut down
102 | wg *sync.WaitGroup
103 |
104 | // context is the context created for this instance,
105 | // used to coordinate the setting up of the server type
106 | context Context
107 |
108 | // servers is the list of servers with their listeners
109 | servers []ServerListener
110 |
111 | // these callbacks execute when certain events occur
112 | OnFirstStartup []func() error // starting, not as part of a restart
113 | OnStartup []func() error // starting, even as part of a restart
114 | OnRestart []func() error // before restart commences
115 | OnRestartFailed []func() error // if restart failed
116 | OnShutdown []func() error // stopping, even as part of a restart
117 | OnFinalShutdown []func() error // stopping, not as part of a restart
118 |
119 | // storing values on an instance is preferable to
120 | // global state because these will get garbage-
121 | // collected after in-process reloads when the
122 | // old instances are destroyed; use StorageMu
123 | // to access this value safely
124 | Storage map[interface{}]interface{}
125 | StorageMu sync.RWMutex
126 | }
127 |
128 | // Instances returns the list of instances.
129 | func Instances() []*Instance {
130 | return instances
131 | }
132 |
133 | // Servers returns the ServerListeners in i.
134 | func (i *Instance) Servers() []ServerListener { return i.servers }
135 |
136 | // Stop stops all servers contained in i. It does NOT
137 | // execute shutdown callbacks.
138 | func (i *Instance) Stop() error {
139 | // stop the servers
140 | for _, s := range i.servers {
141 | if gs, ok := s.server.(GracefulServer); ok {
142 | if err := gs.Stop(); err != nil {
143 | log.Printf("[ERROR] Stopping %s: %v", gs.Address(), err)
144 | }
145 | }
146 | }
147 |
148 | // splice i out of instance list, causing it to be garbage-collected
149 | instancesMu.Lock()
150 | for j, other := range instances {
151 | if other == i {
152 | instances = append(instances[:j], instances[j+1:]...)
153 | break
154 | }
155 | }
156 | instancesMu.Unlock()
157 |
158 | return nil
159 | }
160 |
161 | // ShutdownCallbacks executes all the shutdown callbacks of i,
162 | // including ones that are scheduled only for the final shutdown
163 | // of i. An error returned from one does not stop execution of
164 | // the rest. All the non-nil errors will be returned.
165 | func (i *Instance) ShutdownCallbacks() []error {
166 | var errs []error
167 | for _, shutdownFunc := range i.OnShutdown {
168 | err := shutdownFunc()
169 | if err != nil {
170 | errs = append(errs, err)
171 | }
172 | }
173 | for _, finalShutdownFunc := range i.OnFinalShutdown {
174 | err := finalShutdownFunc()
175 | if err != nil {
176 | errs = append(errs, err)
177 | }
178 | }
179 | return errs
180 | }
181 |
182 | // Restart replaces the servers in i with new servers created from
183 | // executing the newCaddyfile. Upon success, it returns the new
184 | // instance to replace i. Upon failure, i will not be replaced.
185 | func (i *Instance) Restart(newCaddyfile Input) (*Instance, error) {
186 | log.Println("[INFO] Reloading")
187 |
188 | i.wg.Add(1)
189 | defer i.wg.Done()
190 |
191 | var err error
192 | // if something went wrong on restart then run onRestartFailed callbacks
193 | defer func() {
194 | r := recover()
195 | if err != nil || r != nil {
196 | for _, fn := range i.OnRestartFailed {
197 | if err := fn(); err != nil {
198 | log.Printf("[ERROR] Restart failed callback returned error: %v", err)
199 | }
200 | }
201 | if err != nil {
202 | log.Printf("[ERROR] Restart failed: %v", err)
203 | }
204 | if r != nil {
205 | log.Printf("[PANIC] Restart: %v", r)
206 | }
207 | }
208 | }()
209 |
210 | // run restart callbacks
211 | for _, fn := range i.OnRestart {
212 | err = fn()
213 | if err != nil {
214 | return i, err
215 | }
216 | }
217 |
218 | if newCaddyfile == nil {
219 | newCaddyfile = i.caddyfileInput
220 | }
221 |
222 | // Add file descriptors of all the sockets that are capable of it
223 | restartFds := make(map[string][]restartTriple)
224 | for _, s := range i.servers {
225 | gs, srvOk := s.server.(GracefulServer)
226 | ln, lnOk := s.listener.(Listener)
227 | pc, pcOk := s.packet.(PacketConn)
228 | if srvOk {
229 | if lnOk && pcOk {
230 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln, packet: pc})
231 | continue
232 | }
233 | if lnOk {
234 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, listener: ln})
235 | continue
236 | }
237 | if pcOk {
238 | restartFds[gs.Address()] = append(restartFds[gs.Address()], restartTriple{server: gs, packet: pc})
239 | continue
240 | }
241 | }
242 | }
243 |
244 | // create new instance; if the restart fails, it is simply discarded
245 | newInst := &Instance{serverType: newCaddyfile.ServerType(), wg: i.wg, Storage: make(map[interface{}]interface{})}
246 |
247 | // attempt to start new instance
248 | err = startWithListenerFds(newCaddyfile, newInst, restartFds)
249 | if err != nil {
250 | return i, fmt.Errorf("starting with listener file descriptors: %v", err)
251 | }
252 |
253 | // success! stop the old instance
254 | err = i.Stop()
255 | if err != nil {
256 | return i, err
257 | }
258 | for _, shutdownFunc := range i.OnShutdown {
259 | err = shutdownFunc()
260 | if err != nil {
261 | return i, err
262 | }
263 | }
264 |
265 | // Execute instantiation events
266 | EmitEvent(InstanceStartupEvent, newInst)
267 |
268 | log.Println("[INFO] Reloading complete")
269 |
270 | return newInst, nil
271 | }
272 |
273 | // SaveServer adds s and its associated listener ln to the
274 | // internally-kept list of servers that is running. For
275 | // saved servers, graceful restarts will be provided.
276 | func (i *Instance) SaveServer(s Server, ln net.Listener) {
277 | i.servers = append(i.servers, ServerListener{server: s, listener: ln})
278 | }
279 |
280 | // TCPServer is a type that can listen and serve connections.
281 | // A TCPServer must associate with exactly zero or one net.Listeners.
282 | type TCPServer interface {
283 | // Listen starts listening by creating a new listener
284 | // and returning it. It does not start accepting
285 | // connections. For UDP-only servers, this method
286 | // can be a no-op that returns (nil, nil).
287 | Listen() (net.Listener, error)
288 |
289 | // Serve starts serving using the provided listener.
290 | // Serve must start the server loop nearly immediately,
291 | // or at least not return any errors before the server
292 | // loop begins. Serve blocks indefinitely, or in other
293 | // words, until the server is stopped. For UDP-only
294 | // servers, this method can be a no-op that returns nil.
295 | Serve(net.Listener) error
296 | }
297 |
298 | // UDPServer is a type that can listen and serve packets.
299 | // A UDPServer must associate with exactly zero or one net.PacketConns.
300 | type UDPServer interface {
301 | // ListenPacket starts listening by creating a new packetconn
302 | // and returning it. It does not start accepting connections.
303 | // TCP-only servers may leave this method blank and return
304 | // (nil, nil).
305 | ListenPacket() (net.PacketConn, error)
306 |
307 | // ServePacket starts serving using the provided packetconn.
308 | // ServePacket must start the server loop nearly immediately,
309 | // or at least not return any errors before the server
310 | // loop begins. ServePacket blocks indefinitely, or in other
311 | // words, until the server is stopped. For TCP-only servers,
312 | // this method can be a no-op that returns nil.
313 | ServePacket(net.PacketConn) error
314 | }
315 |
316 | // Server is a type that can listen and serve. It supports both
317 | // TCP and UDP, although the UDPServer interface can be used
318 | // for more than just UDP.
319 | //
320 | // If the server uses TCP, it should implement TCPServer completely.
321 | // If it uses UDP or some other protocol, it should implement
322 | // UDPServer completely. If it uses both, both interfaces should be
323 | // fully implemented. Any unimplemented methods should be made as
324 | // no-ops that simply return nil values.
325 | type Server interface {
326 | TCPServer
327 | UDPServer
328 | }
329 |
330 | // Stopper is a type that can stop serving. The stop
331 | // does not necessarily have to be graceful.
332 | type Stopper interface {
333 | // Stop stops the server. It blocks until the
334 | // server is completely stopped.
335 | Stop() error
336 | }
337 |
338 | // GracefulServer is a Server and Stopper, the stopping
339 | // of which is graceful (whatever that means for the kind
340 | // of server being implemented). It must be able to return
341 | // the address it is configured to listen on so that its
342 | // listener can be paired with it upon graceful restarts.
343 | // The net.Listener that a GracefulServer creates must
344 | // implement the Listener interface for restarts to be
345 | // graceful (assuming the listener is for TCP).
346 | type GracefulServer interface {
347 | Server
348 | Stopper
349 |
350 | // Address returns the address the server should
351 | // listen on; it is used to pair the server to
352 | // its listener during a graceful/zero-downtime
353 | // restart. Thus when implementing this method,
354 | // you must not access a listener to get the
355 | // address; you must store the address the
356 | // server is to serve on some other way.
357 | Address() string
358 |
359 | // WrapListener wraps a listener with the
360 | // listener middlewares configured for this
361 | // server, if any.
362 | WrapListener(net.Listener) net.Listener
363 | }
364 |
365 | // Listener is a net.Listener with an underlying file descriptor.
366 | // A server's listener should implement this interface if it is
367 | // to support zero-downtime reloads.
368 | type Listener interface {
369 | net.Listener
370 | File() (*os.File, error)
371 | }
372 |
373 | // PacketConn is a net.PacketConn with an underlying file descriptor.
374 | // A server's packetconn should implement this interface if it is
375 | // to support zero-downtime reloads (in sofar this holds true for datagram
376 | // connections).
377 | type PacketConn interface {
378 | net.PacketConn
379 | File() (*os.File, error)
380 | }
381 |
382 | // AfterStartup is an interface that can be implemented
383 | // by a server type that wants to run some code after all
384 | // servers for the same Instance have started.
385 | type AfterStartup interface {
386 | OnStartupComplete()
387 | }
388 |
389 | // LoadCaddyfile loads a Caddyfile by calling the plugged in
390 | // Caddyfile loader methods. An error is returned if more than
391 | // one loader returns a non-nil Caddyfile input. If no loaders
392 | // load a Caddyfile, the default loader is used. If no default
393 | // loader is registered or it returns nil, the server type's
394 | // default Caddyfile is loaded. If the server type does not
395 | // specify any default Caddyfile value, then an empty Caddyfile
396 | // is returned. Consequently, this function never returns a nil
397 | // value as long as there are no errors.
398 | func LoadCaddyfile(serverType string) (Input, error) {
399 | // If we are finishing an upgrade, we must obtain the Caddyfile
400 | // from our parent process, regardless of configured loaders.
401 | if IsUpgrade() {
402 | err := gob.NewDecoder(os.Stdin).Decode(&loadedGob)
403 | if err != nil {
404 | return nil, err
405 | }
406 | return loadedGob.Caddyfile, nil
407 | }
408 |
409 | // Ask plugged-in loaders for a Caddyfile
410 | cdyfile, err := loadCaddyfileInput(serverType)
411 | if err != nil {
412 | return nil, err
413 | }
414 |
415 | // Otherwise revert to default
416 | if cdyfile == nil {
417 | cdyfile = DefaultInput(serverType)
418 | }
419 |
420 | // Still nil? Geez.
421 | if cdyfile == nil {
422 | cdyfile = CaddyfileInput{ServerTypeName: serverType}
423 | }
424 |
425 | return cdyfile, nil
426 | }
427 |
428 | // Wait blocks until all of i's servers have stopped.
429 | func (i *Instance) Wait() {
430 | i.wg.Wait()
431 | }
432 |
433 | // CaddyfileFromPipe loads the Caddyfile input from f if f is
434 | // not interactive input. f is assumed to be a pipe or stream,
435 | // such as os.Stdin. If f is not a pipe, no error is returned
436 | // but the Input value will be nil. An error is only returned
437 | // if there was an error reading the pipe, even if the length
438 | // of what was read is 0.
439 | func CaddyfileFromPipe(f *os.File, serverType string) (Input, error) {
440 | fi, err := f.Stat()
441 | if err == nil && fi.Mode()&os.ModeCharDevice == 0 {
442 | // Note that a non-nil error is not a problem. Windows
443 | // will not create a stdin if there is no pipe, which
444 | // produces an error when calling Stat(). But Unix will
445 | // make one either way, which is why we also check that
446 | // bitmask.
447 | // NOTE: Reading from stdin after this fails (e.g. for the let's encrypt email address) (OS X)
448 | confBody, err := ioutil.ReadAll(f)
449 | if err != nil {
450 | return nil, err
451 | }
452 | return CaddyfileInput{
453 | Contents: confBody,
454 | Filepath: f.Name(),
455 | ServerTypeName: serverType,
456 | }, nil
457 | }
458 |
459 | // not having input from the pipe is not itself an error,
460 | // just means no input to return.
461 | return nil, nil
462 | }
463 |
464 | // Caddyfile returns the Caddyfile used to create i.
465 | func (i *Instance) Caddyfile() Input {
466 | return i.caddyfileInput
467 | }
468 |
469 | // Start starts Caddy with the given Caddyfile.
470 | //
471 | // This function blocks until all the servers are listening.
472 | func Start(cdyfile Input) (*Instance, error) {
473 | inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
474 | err := startWithListenerFds(cdyfile, inst, nil)
475 | if err != nil {
476 | return inst, err
477 | }
478 | signalSuccessToParent()
479 | if pidErr := writePidFile(); pidErr != nil {
480 | log.Printf("[ERROR] Could not write pidfile: %v", pidErr)
481 | }
482 |
483 | // Execute instantiation events
484 | EmitEvent(InstanceStartupEvent, inst)
485 |
486 | return inst, nil
487 | }
488 |
489 | func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string][]restartTriple) error {
490 | // save this instance in the list now so that
491 | // plugins can access it if need be, for example
492 | // the caddytls package, so it can perform cert
493 | // renewals while starting up; we just have to
494 | // remove the instance from the list later if
495 | // it fails
496 | instancesMu.Lock()
497 | instances = append(instances, inst)
498 | instancesMu.Unlock()
499 | var err error
500 | defer func() {
501 | if err != nil {
502 | instancesMu.Lock()
503 | for i, otherInst := range instances {
504 | if otherInst == inst {
505 | instances = append(instances[:i], instances[i+1:]...)
506 | break
507 | }
508 | }
509 | instancesMu.Unlock()
510 | }
511 | }()
512 |
513 | if cdyfile == nil {
514 | cdyfile = CaddyfileInput{}
515 | }
516 |
517 | err = ValidateAndExecuteDirectives(cdyfile, inst, false)
518 | if err != nil {
519 | return err
520 | }
521 |
522 | slist, err := inst.context.MakeServers()
523 | if err != nil {
524 | return err
525 | }
526 |
527 | // run startup callbacks
528 | if !IsUpgrade() && restartFds == nil {
529 | // first startup means not a restart or upgrade
530 | for _, firstStartupFunc := range inst.OnFirstStartup {
531 | err = firstStartupFunc()
532 | if err != nil {
533 | return err
534 | }
535 | }
536 | }
537 | for _, startupFunc := range inst.OnStartup {
538 | err = startupFunc()
539 | if err != nil {
540 | return err
541 | }
542 | }
543 |
544 | err = startServers(slist, inst, restartFds)
545 | if err != nil {
546 | return err
547 | }
548 |
549 | // run any AfterStartup callbacks if this is not
550 | // part of a restart; then show file descriptor notice
551 | if restartFds == nil {
552 | for _, srvln := range inst.servers {
553 | if srv, ok := srvln.server.(AfterStartup); ok {
554 | srv.OnStartupComplete()
555 | }
556 | }
557 | if !Quiet {
558 | for _, srvln := range inst.servers {
559 | // only show FD notice if the listener is not nil.
560 | // This can happen when only serving UDP or TCP
561 | if srvln.listener == nil {
562 | continue
563 | }
564 | if !IsLoopback(srvln.listener.Addr().String()) {
565 | checkFdlimit()
566 | break
567 | }
568 | }
569 | }
570 | }
571 |
572 | mu.Lock()
573 | started = true
574 | mu.Unlock()
575 |
576 | return nil
577 | }
578 |
579 | // ValidateAndExecuteDirectives will load the server blocks from cdyfile
580 | // by parsing it, then execute the directives configured by it and store
581 | // the resulting server blocks into inst. If justValidate is true, parse
582 | // callbacks will not be executed between directives, since the purpose
583 | // is only to check the input for valid syntax.
584 | func ValidateAndExecuteDirectives(cdyfile Input, inst *Instance, justValidate bool) error {
585 | // If parsing only inst will be nil, create an instance for this function call only.
586 | if justValidate {
587 | inst = &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})}
588 | }
589 |
590 | stypeName := cdyfile.ServerType()
591 |
592 | stype, err := getServerType(stypeName)
593 | if err != nil {
594 | return err
595 | }
596 |
597 | inst.caddyfileInput = cdyfile
598 |
599 | sblocks, err := loadServerBlocks(stypeName, cdyfile.Path(), bytes.NewReader(cdyfile.Body()))
600 | if err != nil {
601 | return err
602 | }
603 |
604 | inst.context = stype.NewContext(inst)
605 | if inst.context == nil {
606 | return fmt.Errorf("server type %s produced a nil Context", stypeName)
607 | }
608 |
609 | sblocks, err = inst.context.InspectServerBlocks(cdyfile.Path(), sblocks)
610 | if err != nil {
611 | return fmt.Errorf("error inspecting server blocks: %v", err)
612 | }
613 |
614 | return executeDirectives(inst, cdyfile.Path(), stype.Directives(), sblocks, justValidate)
615 | }
616 |
617 | func executeDirectives(inst *Instance, filename string,
618 | directives []string, sblocks []caddyfile.ServerBlock, justValidate bool) error {
619 | // map of server block ID to map of directive name to whatever.
620 | storages := make(map[int]map[string]interface{})
621 |
622 | // It is crucial that directives are executed in the proper order.
623 | // We loop with the directives on the outer loop so we execute
624 | // a directive for all server blocks before going to the next directive.
625 | // This is important mainly due to the parsing callbacks (below).
626 | for _, dir := range directives {
627 | for i, sb := range sblocks {
628 | var once sync.Once
629 | if _, ok := storages[i]; !ok {
630 | storages[i] = make(map[string]interface{})
631 | }
632 |
633 | for j, key := range sb.Keys {
634 | // Execute directive if it is in the server block
635 | if tokens, ok := sb.Tokens[dir]; ok {
636 | controller := &Controller{
637 | instance: inst,
638 | Key: key,
639 | Dispenser: caddyfile.NewDispenserTokens(filename, tokens),
640 | OncePerServerBlock: func(f func() error) error {
641 | var err error
642 | once.Do(func() {
643 | err = f()
644 | })
645 | return err
646 | },
647 | ServerBlockIndex: i,
648 | ServerBlockKeyIndex: j,
649 | ServerBlockKeys: sb.Keys,
650 | ServerBlockStorage: storages[i][dir],
651 | }
652 |
653 | // only set up directives for the first key in a block
654 | if j > 0 {
655 | continue
656 | }
657 |
658 | setup, err := DirectiveAction(inst.serverType, dir)
659 | if err != nil {
660 | return err
661 | }
662 |
663 | err = setup(controller)
664 | if err != nil {
665 | return err
666 | }
667 |
668 | storages[i][dir] = controller.ServerBlockStorage // persist for this server block
669 | }
670 | }
671 | }
672 |
673 | if !justValidate {
674 | // See if there are any callbacks to execute after this directive
675 | if allCallbacks, ok := parsingCallbacks[inst.serverType]; ok {
676 | callbacks := allCallbacks[dir]
677 | for _, callback := range callbacks {
678 | if err := callback(inst.context); err != nil {
679 | return err
680 | }
681 | }
682 | }
683 | }
684 | }
685 |
686 | return nil
687 | }
688 |
689 | func startServers(serverList []Server, inst *Instance, restartFds map[string][]restartTriple) error {
690 | errChan := make(chan error, len(serverList))
691 |
692 | // used for signaling to error logging goroutine to terminate
693 | stopChan := make(chan struct{})
694 | // used to track termination of servers
695 | stopWg := &sync.WaitGroup{}
696 |
697 | for _, s := range serverList {
698 | var (
699 | ln net.Listener
700 | pc net.PacketConn
701 | err error
702 | )
703 |
704 | // if performing an upgrade, obtain listener file descriptors
705 | // from parent process
706 | if IsUpgrade() {
707 | if gs, ok := s.(GracefulServer); ok {
708 | addr := gs.Address()
709 | if fdIndex, ok := loadedGob.ListenerFds["tcp"+addr]; ok {
710 | file := os.NewFile(fdIndex, "")
711 | ln, err = net.FileListener(file)
712 | if err != nil {
713 | return fmt.Errorf("making listener from file: %v", err)
714 | }
715 | err = file.Close()
716 | if err != nil {
717 | return fmt.Errorf("closing copy of listener file: %v", err)
718 | }
719 | }
720 | if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok {
721 | file := os.NewFile(fdIndex, "")
722 | pc, err = net.FilePacketConn(file)
723 | if err != nil {
724 | return fmt.Errorf("making packet connection from file: %v", err)
725 | }
726 | err = file.Close()
727 | if err != nil {
728 | return fmt.Errorf("closing copy of packet connection file: %v", err)
729 | }
730 | }
731 | ln = gs.WrapListener(ln)
732 | }
733 | }
734 |
735 | // If this is a reload and s is a GracefulServer,
736 | // reuse the listener for a graceful restart.
737 | if gs, ok := s.(GracefulServer); ok && restartFds != nil {
738 | addr := gs.Address()
739 | // Multiple servers may use the same addr (SO_REUSEPORT option set), so it's important to ensure
740 | // that we don't reuse the same listener/packetconn.
741 | // We'll create new listeners in case there are no more available triples for the same address.
742 | if triples, ok := restartFds[addr]; ok && len(triples) > 0 {
743 | // Take first available triple
744 | old := triples[0]
745 | // Remove reused triple from restartFds
746 | triples[0] = triples[len(triples)-1]
747 | restartFds[addr] = triples[:len(triples)-1]
748 |
749 | // listener
750 | if old.listener != nil {
751 | file, err := old.listener.File()
752 | if err != nil {
753 | return fmt.Errorf("getting old listener file: %v", err)
754 | }
755 | ln, err = net.FileListener(file)
756 | if err != nil {
757 | return fmt.Errorf("getting file listener: %v", err)
758 | }
759 | err = file.Close()
760 | if err != nil {
761 | return fmt.Errorf("closing copy of listener file: %v", err)
762 | }
763 | }
764 | // packetconn
765 | if old.packet != nil {
766 | file, err := old.packet.File()
767 | if err != nil {
768 | return fmt.Errorf("getting old packet file: %v", err)
769 | }
770 | pc, err = net.FilePacketConn(file)
771 | if err != nil {
772 | return fmt.Errorf("getting file packet connection: %v", err)
773 | }
774 | err = file.Close()
775 | if err != nil {
776 | return fmt.Errorf("close copy of packet file: %v", err)
777 | }
778 | }
779 | ln = gs.WrapListener(ln)
780 | }
781 | }
782 |
783 | if ln == nil {
784 | ln, err = s.Listen()
785 | if err != nil {
786 | return fmt.Errorf("Listen: %v", err)
787 | }
788 | }
789 | if pc == nil {
790 | pc, err = s.ListenPacket()
791 | if err != nil {
792 | return fmt.Errorf("ListenPacket: %v", err)
793 | }
794 | }
795 |
796 | inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc})
797 | }
798 |
799 | for _, s := range inst.servers {
800 | inst.wg.Add(2)
801 | stopWg.Add(2)
802 | func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) {
803 | go func() {
804 | defer func() {
805 | inst.wg.Done()
806 | stopWg.Done()
807 | }()
808 | errChan <- s.Serve(ln)
809 | }()
810 |
811 | go func() {
812 | defer func() {
813 | inst.wg.Done()
814 | stopWg.Done()
815 | }()
816 | errChan <- s.ServePacket(pc)
817 | }()
818 | }(s.server, s.listener, s.packet, inst)
819 | }
820 |
821 | // Log errors that may be returned from Serve() calls,
822 | // these errors should only be occurring in the server loop.
823 | go func() {
824 | for {
825 | select {
826 | case err := <-errChan:
827 | if err != nil {
828 | if !strings.Contains(err.Error(), "use of closed network connection") {
829 | // this error is normal when closing the listener; see https://github.com/golang/go/issues/4373
830 | log.Println(err)
831 | }
832 | }
833 | case <-stopChan:
834 | return
835 | }
836 | }
837 | }()
838 |
839 | go func() {
840 | stopWg.Wait()
841 | stopChan <- struct{}{}
842 | }()
843 |
844 | return nil
845 | }
846 |
847 | func getServerType(serverType string) (ServerType, error) {
848 | stype, ok := serverTypes[serverType]
849 | if ok {
850 | return stype, nil
851 | }
852 | if len(serverTypes) == 0 {
853 | return ServerType{}, fmt.Errorf("no server types plugged in")
854 | }
855 | if serverType == "" {
856 | if len(serverTypes) == 1 {
857 | for _, stype := range serverTypes {
858 | return stype, nil
859 | }
860 | }
861 | return ServerType{}, fmt.Errorf("multiple server types available; must choose one")
862 | }
863 | return ServerType{}, fmt.Errorf("unknown server type '%s'", serverType)
864 | }
865 |
866 | func loadServerBlocks(serverType, filename string, input io.Reader) ([]caddyfile.ServerBlock, error) {
867 | validDirectives := ValidDirectives(serverType)
868 | serverBlocks, err := caddyfile.Parse(filename, input, validDirectives)
869 | if err != nil {
870 | return nil, err
871 | }
872 | if len(serverBlocks) == 0 && serverTypes[serverType].DefaultInput != nil {
873 | newInput := serverTypes[serverType].DefaultInput()
874 | serverBlocks, err = caddyfile.Parse(newInput.Path(),
875 | bytes.NewReader(newInput.Body()), validDirectives)
876 | if err != nil {
877 | return nil, err
878 | }
879 | }
880 | return serverBlocks, nil
881 | }
882 |
883 | // Stop stops ALL servers. It blocks until they are all stopped.
884 | // It does NOT execute shutdown callbacks, and it deletes all
885 | // instances after stopping is completed. Do not re-use any
886 | // references to old instances after calling Stop.
887 | func Stop() error {
888 | // This awkward for loop is to avoid a deadlock since
889 | // inst.Stop() also acquires the instancesMu lock.
890 | for {
891 | instancesMu.Lock()
892 | if len(instances) == 0 {
893 | instancesMu.Unlock()
894 | break
895 | }
896 | inst := instances[0]
897 | instancesMu.Unlock()
898 | // Increase the instance waitgroup so that the last wait() call in
899 | // caddymain/run.go blocks until this server instance has shut down
900 | inst.wg.Add(1)
901 | defer inst.wg.Done()
902 | if err := inst.Stop(); err != nil {
903 | log.Printf("[ERROR] Stopping %s: %v", inst.serverType, err)
904 | }
905 | }
906 | return nil
907 | }
908 |
909 | // IsLoopback returns true if the hostname of addr looks
910 | // explicitly like a common local hostname. addr must only
911 | // be a host or a host:port combination.
912 | func IsLoopback(addr string) bool {
913 | host, _, err := net.SplitHostPort(strings.ToLower(addr))
914 | if err != nil {
915 | host = addr // happens if the addr is just a hostname
916 | }
917 | return host == "localhost" ||
918 | strings.Trim(host, "[]") == "::1" ||
919 | strings.HasPrefix(host, "127.")
920 | }
921 |
922 | // IsInternal returns true if the IP of addr
923 | // belongs to a private network IP range. addr must only
924 | // be an IP or an IP:port combination.
925 | // Loopback addresses are considered false.
926 | func IsInternal(addr string) bool {
927 | privateNetworks := []string{
928 | "10.0.0.0/8",
929 | "172.16.0.0/12",
930 | "192.168.0.0/16",
931 | "fc00::/7",
932 | }
933 |
934 | host, _, err := net.SplitHostPort(addr)
935 | if err != nil {
936 | host = addr // happens if the addr is just a hostname, missing port
937 | // if we encounter an error, the brackets need to be stripped
938 | // because SplitHostPort didn't do it for us
939 | host = strings.Trim(host, "[]")
940 | }
941 | ip := net.ParseIP(host)
942 | if ip == nil {
943 | return false
944 | }
945 | for _, privateNetwork := range privateNetworks {
946 | _, ipnet, _ := net.ParseCIDR(privateNetwork)
947 | if ipnet.Contains(ip) {
948 | return true
949 | }
950 | }
951 | return false
952 | }
953 |
954 | // Started returns true if at least one instance has been
955 | // started by this package. It never gets reset to false
956 | // once it is set to true.
957 | func Started() bool {
958 | mu.Lock()
959 | defer mu.Unlock()
960 | return started
961 | }
962 |
963 | // CaddyfileInput represents a Caddyfile as input
964 | // and is simply a convenient way to implement
965 | // the Input interface.
966 | type CaddyfileInput struct {
967 | Filepath string
968 | Contents []byte
969 | ServerTypeName string
970 | }
971 |
972 | // Body returns c.Contents.
973 | func (c CaddyfileInput) Body() []byte { return c.Contents }
974 |
975 | // Path returns c.Filepath.
976 | func (c CaddyfileInput) Path() string { return c.Filepath }
977 |
978 | // ServerType returns c.ServerType.
979 | func (c CaddyfileInput) ServerType() string { return c.ServerTypeName }
980 |
981 | // Input represents a Caddyfile; its contents and file path
982 | // (which should include the file name at the end of the path).
983 | // If path does not apply (e.g. piped input) you may use
984 | // any understandable value. The path is mainly used for logging,
985 | // error messages, and debugging.
986 | type Input interface {
987 | // Gets the Caddyfile contents
988 | Body() []byte
989 |
990 | // Gets the path to the origin file
991 | Path() string
992 |
993 | // The type of server this input is intended for
994 | ServerType() string
995 | }
996 |
997 | // DefaultInput returns the default Caddyfile input
998 | // to use when it is otherwise empty or missing.
999 | // It uses the default host and port (depends on
1000 | // host, e.g. localhost is 2015, otherwise 443) and
1001 | // root.
1002 | func DefaultInput(serverType string) Input {
1003 | if _, ok := serverTypes[serverType]; !ok {
1004 | return nil
1005 | }
1006 | if serverTypes[serverType].DefaultInput == nil {
1007 | return nil
1008 | }
1009 | return serverTypes[serverType].DefaultInput()
1010 | }
1011 |
1012 | // writePidFile writes the process ID to the file at PidFile.
1013 | // It does nothing if PidFile is not set.
1014 | func writePidFile() error {
1015 | if PidFile == "" {
1016 | return nil
1017 | }
1018 | pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
1019 | return ioutil.WriteFile(PidFile, pid, 0644)
1020 | }
1021 |
1022 | type restartTriple struct {
1023 | server GracefulServer
1024 | listener Listener
1025 | packet PacketConn
1026 | }
1027 |
1028 | var (
1029 | // instances is the list of running Instances.
1030 | instances []*Instance
1031 |
1032 | // instancesMu protects instances.
1033 | instancesMu sync.Mutex
1034 | )
1035 |
1036 | var (
1037 | // DefaultConfigFile is the name of the configuration file that is loaded
1038 | // by default if no other file is specified.
1039 | DefaultConfigFile = "Caddyfile"
1040 | )
1041 |
1042 | // CtxKey is a value type for use with context.WithValue.
1043 | type CtxKey string
1044 |
--------------------------------------------------------------------------------