├── caddyhttp
├── rewrite
│ ├── testdata
│ │ ├── testdir
│ │ │ └── empty
│ │ └── testfile
│ ├── to_test.go
│ ├── to.go
│ └── setup.go
├── browse
│ ├── testdata
│ │ ├── header.html
│ │ ├── photos
│ │ │ ├── hidden.html
│ │ │ ├── test3.html
│ │ │ ├── test.html
│ │ │ ├── test2.html
│ │ │ └── test1
│ │ │ │ └── test.html
│ │ └── photos.tpl
│ └── setup_test.go
├── templates
│ ├── testdata
│ │ ├── header.html
│ │ ├── images
│ │ │ ├── header.html
│ │ │ ├── img.htm
│ │ │ └── img2.htm
│ │ ├── root.html
│ │ └── photos
│ │ │ └── test.html
│ ├── setup.go
│ └── templates.go
├── markdown
│ ├── summary
│ │ ├── summary_test.go
│ │ └── summary.go
│ ├── metadata
│ │ ├── metadata_yaml.go
│ │ ├── metadata_none.go
│ │ ├── metadata_toml.go
│ │ ├── metadata_json.go
│ │ └── metadata.go
│ ├── process_test.go
│ ├── template.go
│ └── process.go
├── websocket
│ ├── websocket_test.go
│ ├── setup.go
│ └── setup_test.go
├── bind
│ ├── bind.go
│ └── bind_test.go
├── push
│ ├── push.go
│ └── handler.go
├── caddyhttp_test.go
├── index
│ ├── index.go
│ └── index_test.go
├── pprof
│ ├── setup_test.go
│ ├── setup.go
│ ├── pprof_test.go
│ └── pprof.go
├── httpserver
│ ├── path.go
│ ├── error.go
│ ├── recorder_test.go
│ ├── middleware_test.go
│ ├── pathcleaner.go
│ └── roller.go
├── proxy
│ ├── setup.go
│ ├── body.go
│ └── body_test.go
├── mime
│ ├── mime.go
│ ├── mime_test.go
│ ├── setup_test.go
│ └── setup.go
├── internalsrv
│ ├── setup.go
│ ├── setup_test.go
│ └── internal.go
├── expvar
│ ├── expvar_test.go
│ ├── setup_test.go
│ ├── expvar.go
│ └── setup.go
├── extensions
│ ├── setup.go
│ ├── ext.go
│ └── setup_test.go
├── caddyhttp.go
├── root
│ └── root.go
├── status
│ ├── status.go
│ ├── setup.go
│ ├── status_test.go
│ └── setup_test.go
├── redirect
│ └── redirect.go
├── header
│ ├── setup.go
│ ├── header_test.go
│ └── setup_test.go
├── fastcgi
│ ├── fcgi_test.php
│ ├── dialer.go
│ └── dialer_test.go
├── basicauth
│ └── setup.go
├── log
│ ├── log.go
│ └── setup.go
├── gzip
│ ├── requestfilter.go
│ ├── requestfilter_test.go
│ ├── responsefilter.go
│ ├── responsefilter_test.go
│ ├── setup_test.go
│ ├── gzip_test.go
│ └── setup.go
├── timeouts
│ └── timeouts.go
└── errors
│ └── setup.go
├── caddyfile
├── testdata
│ ├── import_test1.txt
│ ├── import_test2.txt
│ ├── import_glob2.txt
│ ├── import_glob1.txt
│ └── import_glob0.txt
└── lexer.go
├── caddytls
├── client_test.go
├── storagetest
│ ├── memorystorage_test.go
│ └── storagetest_test.go
├── filestorage_test.go
├── httphandler.go
├── httphandler_test.go
├── handshake_test.go
└── certificates_test.go
├── sigtrap_windows.go
├── dist
├── gitcookie.sh.enc
├── automate_test.go
├── init
│ ├── linux-sysvinit
│ │ ├── README.md
│ │ └── caddy
│ ├── mac-launchd
│ │ ├── README.md
│ │ └── com.caddyserver.web.plist
│ ├── linux-upstart
│ │ ├── caddy.conf
│ │ ├── README.md
│ │ ├── caddy.conf.ubuntu-12.04
│ │ └── caddy.conf.centos-6
│ ├── README.md
│ ├── linux-systemd
│ │ └── caddy.service
│ └── freebsd
│ │ └── caddy
└── README.txt
├── rlimit_windows.go
├── .gitignore
├── caddy
├── main_test.go
├── main.go
├── caddymain
│ └── run_test.go
└── build.bash
├── assets_test.go
├── rlimit_posix.go
├── appveyor.yml
├── .gitattributes
├── assets.go
├── .travis.yml
├── ISSUE_TEMPLATE
├── startupshutdown
├── startupshutdown.go
└── startupshutdown_test.go
├── caddy_test.go
├── sigtrap_posix.go
├── sigtrap.go
└── commands.go
/caddyhttp/rewrite/testdata/testdir/empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/caddyhttp/rewrite/testdata/testfile:
--------------------------------------------------------------------------------
1 | empty
--------------------------------------------------------------------------------
/caddyfile/testdata/import_test1.txt:
--------------------------------------------------------------------------------
1 | dir2 arg1 arg2
2 | dir3
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/header.html:
--------------------------------------------------------------------------------
1 |
Header
2 |
--------------------------------------------------------------------------------
/caddytls/client_test.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | // TODO
4 |
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos/hidden.html:
--------------------------------------------------------------------------------
1 | Should be hidden
2 |
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/header.html:
--------------------------------------------------------------------------------
1 | Header title
2 |
--------------------------------------------------------------------------------
/caddyfile/testdata/import_test2.txt:
--------------------------------------------------------------------------------
1 | host1 {
2 | dir1
3 | dir2 arg1
4 | }
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/images/header.html:
--------------------------------------------------------------------------------
1 | Header title
2 |
--------------------------------------------------------------------------------
/sigtrap_windows.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | func trapSignalsPosix() {}
4 |
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob2.txt:
--------------------------------------------------------------------------------
1 | glob2.host0 {
2 | dir2 arg1
3 | }
4 |
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos/test3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob1.txt:
--------------------------------------------------------------------------------
1 | glob1.host0 {
2 | dir1
3 | dir2 arg1
4 | }
5 |
--------------------------------------------------------------------------------
/dist/gitcookie.sh.enc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alex/caddy/master/dist/gitcookie.sh.enc
--------------------------------------------------------------------------------
/caddyfile/testdata/import_glob0.txt:
--------------------------------------------------------------------------------
1 | glob0.host0 {
2 | dir2 arg1
3 | }
4 |
5 | glob0.host1 {
6 | }
7 |
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/root.html:
--------------------------------------------------------------------------------
1 | root{{.Include "header.html"}}
2 |
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/images/img.htm:
--------------------------------------------------------------------------------
1 | img{%.Include "header.html"%}
2 |
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/images/img2.htm:
--------------------------------------------------------------------------------
1 | img{{.Include "header.html"}}
2 |
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos/test2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test 2
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/caddyhttp/templates/testdata/photos/test.html:
--------------------------------------------------------------------------------
1 | test page{{.Include "../header.html"}}
2 |
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos/test1/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Test
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/rlimit_windows.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | // checkFdlimit issues a warning if the OS limit for
4 | // max file descriptors is below a recommended minimum.
5 | func checkFdlimit() {
6 | }
7 |
--------------------------------------------------------------------------------
/.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 |
17 | og_static/
18 |
19 | .vscode/
--------------------------------------------------------------------------------
/caddyhttp/browse/testdata/photos.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Template
5 |
6 |
7 | {{.Include "header.html"}}
8 | {{.Path}}
9 | {{range .Items}}
10 | {{.Name}}
11 | {{end}}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/caddytls/storagetest/memorystorage_test.go:
--------------------------------------------------------------------------------
1 | package storagetest
2 |
3 | import "testing"
4 |
5 | func TestMemoryStorage(t *testing.T) {
6 | storage := NewInMemoryStorage()
7 | storageTest := &StorageTest{
8 | Storage: storage,
9 | PostTest: storage.Clear,
10 | }
11 | storageTest.Test(t, false)
12 | }
13 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/summary/summary_test.go:
--------------------------------------------------------------------------------
1 | package summary
2 |
3 | import "testing"
4 |
5 | func TestMarkdown(t *testing.T) {
6 | input := []byte(`Testing with just a few words.`)
7 | got := string(Markdown(input, 3))
8 | if want := "Testing with just"; want != got {
9 | t.Errorf("Expected '%s' but got '%s'", want, got)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/caddytls/filestorage_test.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | // *********************************** NOTE ********************************
4 | // Due to circular package dependencies with the storagetest sub package and
5 | // the fact that we want to use that harness to test file storage, the tests
6 | // for file storage are done in the storagetest package.
7 |
--------------------------------------------------------------------------------
/dist/automate_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "runtime"
5 | "testing"
6 | )
7 |
8 | func TestNumProcs(t *testing.T) {
9 | num := runtime.NumCPU()
10 | n := numProcs()
11 | if n > num || n < 1 {
12 | t.Errorf("Expected numProcs() to return max(NumCPU-1, 1) or at least some "+
13 | "reasonable value (depending on CI environment), but got n=%d (NumCPU=%d)", n, num)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/caddy/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "testing"
4 |
5 | // This works because it does not have the same signature as the
6 | // conventional "TestMain" function described in the testing package
7 | // godoc.
8 | func TestMain(t *testing.T) {
9 | var ran bool
10 | run = func() {
11 | ran = true
12 | }
13 | main()
14 | if !ran {
15 | t.Error("Expected Run() to be called, but it wasn't")
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/caddy/main.go:
--------------------------------------------------------------------------------
1 | // By moving the application's package main logic into
2 | // a package other than main, it becomes much easier to
3 | // wrap caddy for custom builds that are go-gettable.
4 | // https://forum.caddyserver.com/t/my-wish-for-0-9-go-gettable-custom-builds/59?u=matt
5 |
6 | package main
7 |
8 | import "github.com/mholt/caddy/caddy/caddymain"
9 |
10 | var run = caddymain.Run // replaced for tests
11 |
12 | func main() {
13 | run()
14 | }
15 |
--------------------------------------------------------------------------------
/assets_test.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | import (
4 | "os"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func TestAssetsPath(t *testing.T) {
10 | if actual := AssetsPath(); !strings.HasSuffix(actual, ".caddy") {
11 | t.Errorf("Expected path to be a .caddy folder, got: %v", actual)
12 | }
13 |
14 | os.Setenv("CADDYPATH", "testpath")
15 | if actual, expected := AssetsPath(), "testpath"; actual != expected {
16 | t.Errorf("Expected path to be %v, got: %v", expected, actual)
17 | }
18 | os.Setenv("CADDYPATH", "")
19 | }
20 |
--------------------------------------------------------------------------------
/dist/init/linux-sysvinit/README.md:
--------------------------------------------------------------------------------
1 | SysVinit conf for Caddy
2 | =======================
3 |
4 | Usage
5 | -----
6 |
7 | * Download the appropriate Caddy binary in `/usr/local/bin/caddy` or use `curl https://getcaddy.com | bash`.
8 | * Save the SysVinit config file in `/etc/init.d/caddy`.
9 | * Ensure that the folder `/etc/caddy` exists and that the folder `/etc/ssl/caddy` is owned by `www-data`.
10 | * Create a Caddyfile in `/etc/caddy/Caddyfile`
11 | * Now you can use `service caddy start|stop|restart|reload|status` as `root`.
12 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/summary/summary.go:
--------------------------------------------------------------------------------
1 | package summary
2 |
3 | import (
4 | "bytes"
5 |
6 | "github.com/russross/blackfriday"
7 | )
8 |
9 | // Markdown formats input using a plain-text renderer, and
10 | // then returns up to the first `wordcount` words as a summary.
11 | func Markdown(input []byte, wordcount int) []byte {
12 | words := bytes.Fields(blackfriday.Markdown(input, renderer{}, 0))
13 | if wordcount > len(words) {
14 | wordcount = len(words)
15 | }
16 | return bytes.Join(words[0:wordcount], []byte{' '})
17 | }
18 |
--------------------------------------------------------------------------------
/caddyhttp/websocket/websocket_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 | )
7 |
8 | func TestBuildEnv(t *testing.T) {
9 | req, err := http.NewRequest("GET", "http://localhost", nil)
10 | if err != nil {
11 | t.Fatal("Error setting up request:", err)
12 | }
13 | req.RemoteAddr = "localhost:50302"
14 |
15 | env, err := buildEnv("/bin/command", req)
16 | if err != nil {
17 | t.Fatal("Didn't expect an error:", err)
18 | }
19 | if len(env) == 0 {
20 | t.Fatalf("Expected non-empty environment; got %#v", env)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/caddyhttp/bind/bind.go:
--------------------------------------------------------------------------------
1 | package bind
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("bind", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setupBind,
12 | })
13 | }
14 |
15 | func setupBind(c *caddy.Controller) error {
16 | config := httpserver.GetConfig(c)
17 | for c.Next() {
18 | if !c.Args(&config.ListenHost) {
19 | return c.ArgErr()
20 | }
21 | config.TLS.ListenHost = config.ListenHost // necessary for ACME challenges, see issue #309
22 | }
23 | return nil
24 | }
25 |
--------------------------------------------------------------------------------
/dist/init/mac-launchd/README.md:
--------------------------------------------------------------------------------
1 | launchd service for macOS
2 | =========================
3 |
4 | This is a sample file for a *launchd* service on Mac.
5 | Edit the paths and email in the plist file to match your info.
6 |
7 | Start and Stop the Caddy launchd service using the following commands:
8 |
9 | $ launchctl load ~/Library/LaunchAgents/com.caddyserver.web.plist
10 | $ launchctl unload ~/Library/LaunchAgents/com.caddyserver.web.plist
11 |
12 | More information can be found in this blogpost: [Running Caddy as a service on macOS X server](https://denbeke.be/blog/software/running-caddy-as-a-service-on-macos-os-x-server/)
--------------------------------------------------------------------------------
/caddyhttp/push/push.go:
--------------------------------------------------------------------------------
1 | package push
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/mholt/caddy/caddyhttp/httpserver"
7 | )
8 |
9 | type (
10 | // Rule describes conditions on which resources will be pushed
11 | Rule struct {
12 | Path string
13 | Resources []Resource
14 | }
15 |
16 | // Resource describes resource to be pushed
17 | Resource struct {
18 | Path string
19 | Method string
20 | Header http.Header
21 | }
22 |
23 | // Middleware supports pushing resources to clients
24 | Middleware struct {
25 | Next httpserver.Handler
26 | Rules []Rule
27 | }
28 |
29 | ruleOp func([]Resource)
30 | )
31 |
--------------------------------------------------------------------------------
/dist/init/linux-upstart/caddy.conf:
--------------------------------------------------------------------------------
1 | description "Caddy HTTP/2 web server"
2 |
3 | start on runlevel [2345]
4 | stop on runlevel [016]
5 |
6 | console log
7 |
8 | setuid www-data
9 | setgid www-data
10 |
11 | respawn
12 | respawn limit 10 5
13 |
14 | reload signal SIGUSR1
15 |
16 | # Let's Encrypt certificates will be written to this directory.
17 | env CADDYPATH=/etc/ssl/caddy
18 |
19 | limit nofile 1048576 1048576
20 |
21 | script
22 | cd /etc/ssl/caddy
23 | rootdir="$(mktemp -d -t "caddy-run.XXXXXX")"
24 | exec /usr/local/bin/caddy -agree -log=stdout -conf=/etc/caddy/Caddyfile -root=$rootdir
25 | end script
26 |
--------------------------------------------------------------------------------
/caddyhttp/caddyhttp_test.go:
--------------------------------------------------------------------------------
1 | package caddyhttp
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/mholt/caddy"
8 | )
9 |
10 | // TODO: this test could be improved; the purpose is to
11 | // ensure that the standard plugins are in fact plugged in
12 | // and registered properly; this is a quick/naive way to do it.
13 | func TestStandardPlugins(t *testing.T) {
14 | numStandardPlugins := 31 // importing caddyhttp plugs in this many plugins
15 | s := caddy.DescribePlugins()
16 | if got, want := strings.Count(s, "\n"), numStandardPlugins+5; got != want {
17 | t.Errorf("Expected all standard plugins to be plugged in, got:\n%s", s)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/rlimit_posix.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package caddy
4 |
5 | import (
6 | "fmt"
7 | "syscall"
8 | )
9 |
10 | // checkFdlimit issues a warning if the OS limit for
11 | // max file descriptors is below a recommended minimum.
12 | func checkFdlimit() {
13 | const min = 8192
14 |
15 | // Warn if ulimit is too low for production sites
16 | rlimit := &syscall.Rlimit{}
17 | err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, rlimit)
18 | if err == nil && rlimit.Cur < min {
19 | fmt.Printf("WARNING: File descriptor limit %d is too low for production servers. "+
20 | "At least %d is recommended. Fix with \"ulimit -n %d\".\n", rlimit.Cur, min, min)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/caddyhttp/index/index.go:
--------------------------------------------------------------------------------
1 | package index
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/staticfiles"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("index", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setupIndex,
12 | })
13 | }
14 |
15 | func setupIndex(c *caddy.Controller) error {
16 | var index []string
17 |
18 | for c.Next() {
19 | args := c.RemainingArgs()
20 |
21 | if len(args) == 0 {
22 | return c.Errf("Expected at least one index")
23 | }
24 |
25 | for _, in := range args {
26 | index = append(index, in)
27 | }
28 |
29 | staticfiles.IndexPages = index
30 | }
31 |
32 | return nil
33 | }
34 |
--------------------------------------------------------------------------------
/dist/init/linux-upstart/README.md:
--------------------------------------------------------------------------------
1 | Upstart conf for Caddy
2 | =====================
3 |
4 | Usage
5 | -----
6 |
7 | Usage in this blogpost: [Running Caddy Server as a service with Upstart](https://denbeke.be/blog/servers/running-caddy-server-as-a-service/).
8 | Short recap:
9 |
10 | * Download Caddy in `/usr/local/bin/caddy` and execute `sudo setcap cap_net_bind_service=+ep /usr/local/bin/caddy`.
11 | * Save the appropriate upstart config file in `/etc/init/caddy.conf`.
12 | * Ensure that the folder `/etc/caddy` exists and that the subfolder .caddy is owned by `www-data`.
13 | * Create a Caddyfile in `/etc/caddy/Caddyfile`.
14 | * Now you can use `sudo service caddy start|stop|restart`.
15 |
--------------------------------------------------------------------------------
/dist/init/linux-upstart/caddy.conf.ubuntu-12.04:
--------------------------------------------------------------------------------
1 | description "Caddy HTTP/2 web server"
2 |
3 | start on runlevel [2345]
4 | stop on runlevel [016]
5 |
6 | console log
7 |
8 | setuid www-data
9 | setgid www-data
10 |
11 | respawn
12 | respawn limit 10 5
13 |
14 | # 12.04 upstart version does not support reload
15 | #reload signal SIGUSR1
16 |
17 | # Let's Encrypt certificates will be written to this directory.
18 | env CADDYPATH=/etc/ssl/caddy
19 |
20 | limit nofile 1048576 1048576
21 |
22 | script
23 | cd /etc/ssl/caddy
24 | rootdir="$(mktemp -d -t "caddy-run.XXXXXX")"
25 | exec /usr/local/bin/caddy -agree -log=stdout -conf=/etc/caddy/Caddyfile -root=$rootdir
26 | end script
27 |
--------------------------------------------------------------------------------
/caddyhttp/bind/bind_test.go:
--------------------------------------------------------------------------------
1 | package bind
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetupBind(t *testing.T) {
11 | c := caddy.NewTestController("http", `bind 1.2.3.4`)
12 | err := setupBind(c)
13 | if err != nil {
14 | t.Fatalf("Expected no errors, but got: %v", err)
15 | }
16 |
17 | cfg := httpserver.GetConfig(c)
18 | if got, want := cfg.ListenHost, "1.2.3.4"; got != want {
19 | t.Errorf("Expected the config's ListenHost to be %s, was %s", want, got)
20 | }
21 | if got, want := cfg.TLS.ListenHost, "1.2.3.4"; got != want {
22 | t.Errorf("Expected the TLS config's ListenHost to be %s, was %s", want, got)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: "{build}"
2 |
3 | os: Windows Server 2012 R2
4 |
5 | clone_folder: c:\gopath\src\github.com\mholt\caddy
6 |
7 | environment:
8 | GOPATH: c:\gopath
9 |
10 | install:
11 | - rmdir c:\go /s /q
12 | - appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.windows-amd64.zip
13 | - 7z x go1.8.windows-amd64.zip -y -oC:\ > NUL
14 | - go version
15 | - go env
16 | - go get -t ./...
17 | - go get github.com/golang/lint/golint
18 | - go get github.com/gordonklaus/ineffassign
19 | - set PATH=%GOPATH%\bin;%PATH%
20 |
21 | build: off
22 |
23 | test_script:
24 | - go vet ./...
25 | - go test -race ./...
26 | - ineffassign .
27 |
28 | after_test:
29 | - golint ./...
30 |
31 | deploy: off
32 |
--------------------------------------------------------------------------------
/caddyhttp/pprof/setup_test.go:
--------------------------------------------------------------------------------
1 | package pprof
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | )
8 |
9 | func TestSetup(t *testing.T) {
10 | tests := []struct {
11 | input string
12 | shouldErr bool
13 | }{
14 | {`pprof`, false},
15 | {`pprof {}`, true},
16 | {`pprof /foo`, true},
17 | {`pprof {
18 | a b
19 | }`, true},
20 | {`pprof
21 | pprof`, true},
22 | }
23 | for i, test := range tests {
24 | c := caddy.NewTestController("http", test.input)
25 | err := setup(c)
26 | if test.shouldErr && err == nil {
27 | t.Errorf("Test %v: Expected error but found nil", i)
28 | } else if !test.shouldErr && err != nil {
29 | t.Errorf("Test %v: Expected no error but found error: %v", i, err)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/dist/init/linux-upstart/caddy.conf.centos-6:
--------------------------------------------------------------------------------
1 | description "Caddy HTTP/2 web server"
2 |
3 | start on runlevel [2345]
4 | stop on runlevel [016]
5 |
6 | # centos 6 upstart version does not support console
7 | console log
8 |
9 | # centos 6 upstart version does not support setuid/setgid
10 | setuid www-data
11 | setgid www-data
12 |
13 | respawn
14 | respawn limit 10 5
15 |
16 | # centos 6 upstart version does not support reload
17 | reload signal SIGUSR1
18 |
19 | # Let's Encrypt certificates will be written to this directory.
20 | env CADDYPATH=/etc/ssl/caddy
21 |
22 | limit nofile 1048576 1048576
23 |
24 | script
25 | cd /etc/ssl/caddy
26 | rootdir="$(mktemp -d -t "caddy-run.XXXXXX")"
27 | exec /usr/local/bin/caddy -agree -log=stdout -conf=/etc/caddy/Caddyfile -root=$rootdir
28 | end script
29 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/path.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 | )
7 |
8 | // Path represents a URI path.
9 | type Path string
10 |
11 | // Matches checks to see if other matches p.
12 | //
13 | // Path matching will probably not always be a direct
14 | // comparison; this method assures that paths can be
15 | // easily and consistently matched.
16 | func (p Path) Matches(other string) bool {
17 | if CaseSensitivePath {
18 | return strings.HasPrefix(string(p), other)
19 | }
20 | return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other))
21 | }
22 |
23 | // PathMatcher is a Path RequestMatcher.
24 | type PathMatcher string
25 |
26 | // Match satisfies RequestMatcher.
27 | func (p PathMatcher) Match(r *http.Request) bool {
28 | return Path(r.URL.Path).Matches(string(p))
29 | }
30 |
--------------------------------------------------------------------------------
/caddyhttp/proxy/setup.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("proxy", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setup,
12 | })
13 | }
14 |
15 | // setup configures a new Proxy middleware instance.
16 | func setup(c *caddy.Controller) error {
17 | upstreams, err := NewStaticUpstreams(c.Dispenser, httpserver.GetConfig(c).Host())
18 | if err != nil {
19 | return err
20 | }
21 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
22 | return Proxy{Next: next, Upstreams: upstreams}
23 | })
24 |
25 | // Register shutdown handlers.
26 | for _, upstream := range upstreams {
27 | c.OnShutdown(upstream.Stop)
28 | }
29 |
30 | return nil
31 | }
32 |
--------------------------------------------------------------------------------
/caddyhttp/pprof/setup.go:
--------------------------------------------------------------------------------
1 | package pprof
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("pprof", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setup,
12 | })
13 | }
14 |
15 | // setup returns a new instance of a pprof handler. It accepts no arguments or options.
16 | func setup(c *caddy.Controller) error {
17 | found := false
18 |
19 | for c.Next() {
20 | if found {
21 | return c.Err("pprof can only be specified once")
22 | }
23 | if len(c.RemainingArgs()) != 0 {
24 | return c.ArgErr()
25 | }
26 | if c.NextBlock() {
27 | return c.ArgErr()
28 | }
29 | found = true
30 | }
31 |
32 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
33 | return &Handler{Next: next, Mux: NewMux()}
34 | })
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/caddyhttp/proxy/body.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | )
8 |
9 | type bufferedBody struct {
10 | *bytes.Reader
11 | }
12 |
13 | func (*bufferedBody) Close() error {
14 | return nil
15 | }
16 |
17 | // rewind allows bufferedBody to be read again.
18 | func (b *bufferedBody) rewind() error {
19 | if b == nil {
20 | return nil
21 | }
22 | _, err := b.Seek(0, io.SeekStart)
23 | return err
24 | }
25 |
26 | // newBufferedBody returns *bufferedBody to use in place of src. Closes src
27 | // and returns Read error on src. All content from src is buffered.
28 | func newBufferedBody(src io.ReadCloser) (*bufferedBody, error) {
29 | if src == nil {
30 | return nil, nil
31 | }
32 | b, err := ioutil.ReadAll(src)
33 | src.Close()
34 | if err != nil {
35 | return nil, err
36 | }
37 | return &bufferedBody{
38 | Reader: bytes.NewReader(b),
39 | }, nil
40 | }
41 |
--------------------------------------------------------------------------------
/caddyhttp/mime/mime.go:
--------------------------------------------------------------------------------
1 | package mime
2 |
3 | import (
4 | "net/http"
5 | "path"
6 |
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | // Config represent a mime config. Map from extension to mime-type.
11 | // Note, this should be safe with concurrent read access, as this is
12 | // not modified concurrently.
13 | type Config map[string]string
14 |
15 | // Mime sets Content-Type header of requests based on configurations.
16 | type Mime struct {
17 | Next httpserver.Handler
18 | Configs Config
19 | }
20 |
21 | // ServeHTTP implements the httpserver.Handler interface.
22 | func (e Mime) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
23 | // Get a clean /-path, grab the extension
24 | ext := path.Ext(path.Clean(r.URL.Path))
25 |
26 | if contentType, ok := e.Configs[ext]; ok {
27 | w.Header().Set("Content-Type", contentType)
28 | }
29 |
30 | return e.Next.ServeHTTP(w, r)
31 | }
32 |
--------------------------------------------------------------------------------
/.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 |
13 | *.yml text eol=lf core.whitespace whitespace=tab-in-indent,trailing-space,tabwidth=2
14 | .git* text eol=auto core.whitespace whitespace=trailing-space
15 |
--------------------------------------------------------------------------------
/assets.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "runtime"
7 | )
8 |
9 | // AssetsPath returns the path to the folder
10 | // where the application may store data. If
11 | // CADDYPATH env variable is set, that value
12 | // is used. Otherwise, the path is the result
13 | // of evaluating "$HOME/.caddy".
14 | func AssetsPath() string {
15 | if caddyPath := os.Getenv("CADDYPATH"); caddyPath != "" {
16 | return caddyPath
17 | }
18 | return filepath.Join(userHomeDir(), ".caddy")
19 | }
20 |
21 | // userHomeDir returns the user's home directory according to
22 | // environment variables.
23 | //
24 | // Credit: http://stackoverflow.com/a/7922977/1048862
25 | func userHomeDir() string {
26 | if runtime.GOOS == "windows" {
27 | home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
28 | if home == "" {
29 | home = os.Getenv("USERPROFILE")
30 | }
31 | return home
32 | }
33 | return os.Getenv("HOME")
34 | }
35 |
--------------------------------------------------------------------------------
/caddyhttp/internalsrv/setup.go:
--------------------------------------------------------------------------------
1 | package internalsrv
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("internal", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setup,
12 | })
13 | }
14 |
15 | // Internal configures a new Internal middleware instance.
16 | func setup(c *caddy.Controller) error {
17 | paths, err := internalParse(c)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
23 | return Internal{Next: next, Paths: paths}
24 | })
25 |
26 | return nil
27 | }
28 |
29 | func internalParse(c *caddy.Controller) ([]string, error) {
30 | var paths []string
31 |
32 | for c.Next() {
33 | if !c.NextArg() {
34 | return paths, c.ArgErr()
35 | }
36 | paths = append(paths, c.Val())
37 | }
38 |
39 | return paths, nil
40 | }
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.8
5 | - tip
6 |
7 | matrix:
8 | allow_failures:
9 | - go: tip
10 | fast_finish: true
11 |
12 | before_install:
13 | # Decrypts a script that installs an authenticated cookie
14 | # for git to use when cloning from googlesource.com.
15 | # Bypasses "bandwidth limit exceeded" errors.
16 | # See github.com/golang/go/issues/12933
17 | - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then openssl aes-256-cbc -K $encrypted_3df18f9af81d_key -iv $encrypted_3df18f9af81d_iv -in dist/gitcookie.sh.enc -out dist/gitcookie.sh -d; fi
18 |
19 | install:
20 | - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then bash dist/gitcookie.sh; fi
21 | - go get -t ./...
22 | - go get github.com/golang/lint/golint
23 | - go get github.com/gordonklaus/ineffassign
24 | - go get github.com/client9/misspell/cmd/misspell
25 |
26 | script:
27 | - diff <(echo -n) <(gofmt -s -d .)
28 | - ineffassign .
29 | - misspell -error .
30 | - go vet ./...
31 | - go test -race ./...
32 |
33 | after_script:
34 | - golint ./...
35 |
--------------------------------------------------------------------------------
/caddyhttp/index/index_test.go:
--------------------------------------------------------------------------------
1 | package index
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/staticfiles"
8 | )
9 |
10 | func TestIndexIncompleteParams(t *testing.T) {
11 | c := caddy.NewTestController("", "index")
12 |
13 | err := setupIndex(c)
14 | if err == nil {
15 | t.Error("Expected an error, but didn't get one")
16 | }
17 | }
18 |
19 | func TestIndex(t *testing.T) {
20 | c := caddy.NewTestController("", "index a.html b.html c.html")
21 |
22 | err := setupIndex(c)
23 | if err != nil {
24 | t.Errorf("Expected no errors, got: %v", err)
25 | }
26 |
27 | expectedIndex := []string{"a.html", "b.html", "c.html"}
28 |
29 | if len(staticfiles.IndexPages) != 3 {
30 | t.Errorf("Expected 3 values, got %v", len(staticfiles.IndexPages))
31 | }
32 |
33 | // Ensure ordering is correct
34 | for i, actual := range staticfiles.IndexPages {
35 | if actual != expectedIndex[i] {
36 | t.Errorf("Expected value in position %d to be %v, got %v", i, expectedIndex[i], actual)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/caddyhttp/expvar/expvar_test.go:
--------------------------------------------------------------------------------
1 | package expvar
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func TestExpVar(t *testing.T) {
13 | rw := ExpVar{
14 | Next: httpserver.HandlerFunc(contentHandler),
15 | Resource: "/d/v",
16 | }
17 |
18 | tests := []struct {
19 | from string
20 | result int
21 | }{
22 | {"/d/v", 0},
23 | {"/x/y", http.StatusOK},
24 | }
25 |
26 | for i, test := range tests {
27 | req, err := http.NewRequest("GET", test.from, nil)
28 | if err != nil {
29 | t.Fatalf("Test %d: Could not create HTTP request %v", i, err)
30 | }
31 | rec := httptest.NewRecorder()
32 | result, err := rw.ServeHTTP(rec, req)
33 | if err != nil {
34 | t.Fatalf("Test %d: Could not ServeHTTP %v", i, err)
35 | }
36 | if result != test.result {
37 | t.Errorf("Test %d: Expected Header '%d' but was '%d'",
38 | i, test.result, result)
39 | }
40 | }
41 | }
42 |
43 | func contentHandler(w http.ResponseWriter, r *http.Request) (int, error) {
44 | fmt.Fprintf(w, r.URL.String())
45 | return http.StatusOK, nil
46 | }
47 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/metadata/metadata_yaml.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "bytes"
5 |
6 | "gopkg.in/yaml.v2"
7 | )
8 |
9 | // YAMLParser is the Parser for YAML
10 | type YAMLParser struct {
11 | metadata Metadata
12 | markdown *bytes.Buffer
13 | }
14 |
15 | // Type returns the kind of metadata parser.
16 | func (y *YAMLParser) Type() string {
17 | return "YAML"
18 | }
19 |
20 | // Init prepares the metadata parser for parsing.
21 | func (y *YAMLParser) Init(b *bytes.Buffer) bool {
22 | meta, data := splitBuffer(b, "---")
23 | if meta == nil || data == nil {
24 | return false
25 | }
26 | y.markdown = data
27 |
28 | m := make(map[string]interface{})
29 | if err := yaml.Unmarshal(meta.Bytes(), &m); err != nil {
30 | return false
31 | }
32 | y.metadata = NewMetadata(m)
33 |
34 | return true
35 | }
36 |
37 | // Metadata returns parsed metadata. It should be called
38 | // only after a call to Parse returns without error.
39 | func (y *YAMLParser) Metadata() Metadata {
40 | return y.metadata
41 | }
42 |
43 | // Markdown renders the text as a byte array
44 | func (y *YAMLParser) Markdown() []byte {
45 | return y.markdown.Bytes()
46 | }
47 |
--------------------------------------------------------------------------------
/dist/README.txt:
--------------------------------------------------------------------------------
1 | CADDY 0.9.5
2 |
3 | Website
4 | https://caddyserver.com
5 |
6 | Community
7 | https://forum.caddyserver.com
8 |
9 | Twitter
10 | @caddyserver
11 |
12 | Source Code
13 | https://github.com/mholt/caddy
14 | https://github.com/caddyserver
15 |
16 |
17 | For instructions on using Caddy, please see the user guide on
18 | the website. For a list of what's new in this version, see
19 | CHANGES.txt.
20 |
21 | The Caddy project accepts pull requests. That means you can make
22 | changes to the code and submit it for review, and if it's good,
23 | we'll use it! You can help thousands of Caddy users and level
24 | up your Go programming game by contributing to Caddy's source.
25 |
26 | To report bugs or request features, open an issue on GitHub.
27 |
28 | Want to support the project financially? Consider donating,
29 | especially if your company is using Caddy. Believe me, your
30 | contributions do not go unnoticed! We also have sponsorship
31 | opportunities available.
32 |
33 | For a good time, follow @mholt6 on Twitter.
34 |
35 | And thanks - you're awesome!
36 |
37 |
38 | ---
39 | (c) 2015-2017 Matthew Holt
40 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/metadata/metadata_none.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "bytes"
5 | )
6 |
7 | // NoneParser is the parser for plaintext markdown with no metadata.
8 | type NoneParser struct {
9 | metadata Metadata
10 | markdown *bytes.Buffer
11 | }
12 |
13 | // Type returns the kind of parser this struct is.
14 | func (n *NoneParser) Type() string {
15 | return "None"
16 | }
17 |
18 | // Init prepases and parses the metadata and markdown file
19 | func (n *NoneParser) Init(b *bytes.Buffer) bool {
20 | m := make(map[string]interface{})
21 | n.metadata = NewMetadata(m)
22 | n.markdown = bytes.NewBuffer(b.Bytes())
23 |
24 | return true
25 | }
26 |
27 | // Parse the metadata
28 | func (n *NoneParser) Parse(b []byte) ([]byte, error) {
29 | return nil, nil
30 | }
31 |
32 | // Metadata returns parsed metadata. It should be called
33 | // only after a call to Parse returns without error.
34 | func (n *NoneParser) Metadata() Metadata {
35 | return n.metadata
36 | }
37 |
38 | // Markdown returns parsed markdown. It should be called
39 | // only after a call to Parse returns without error.
40 | func (n *NoneParser) Markdown() []byte {
41 | return n.markdown.Bytes()
42 | }
43 |
--------------------------------------------------------------------------------
/caddyhttp/expvar/setup_test.go:
--------------------------------------------------------------------------------
1 | package expvar
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `expvar`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, got: %v", err)
15 | }
16 | mids := httpserver.GetConfig(c).Middleware()
17 | if len(mids) == 0 {
18 | t.Fatal("Expected middleware, got 0 instead")
19 | }
20 |
21 | c = caddy.NewTestController("http", `expvar /d/v`)
22 | err = setup(c)
23 | if err != nil {
24 | t.Errorf("Expected no errors, got: %v", err)
25 | }
26 | mids = httpserver.GetConfig(c).Middleware()
27 | if len(mids) == 0 {
28 | t.Fatal("Expected middleware, got 0 instead")
29 | }
30 |
31 | handler := mids[0](httpserver.EmptyNext)
32 | myHandler, ok := handler.(ExpVar)
33 | if !ok {
34 | t.Fatalf("Expected handler to be type ExpVar, got: %#v", handler)
35 | }
36 | if myHandler.Resource != "/d/v" {
37 | t.Errorf("Expected /d/v as expvar resource")
38 | }
39 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
40 | t.Error("'Next' field of handler was not set properly")
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/metadata/metadata_toml.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "bytes"
5 |
6 | "github.com/naoina/toml"
7 | )
8 |
9 | // TOMLParser is the Parser for TOML
10 | type TOMLParser struct {
11 | metadata Metadata
12 | markdown *bytes.Buffer
13 | }
14 |
15 | // Type returns the kind of parser this struct is.
16 | func (t *TOMLParser) Type() string {
17 | return "TOML"
18 | }
19 |
20 | // Init prepares and parses the metadata and markdown file itself
21 | func (t *TOMLParser) Init(b *bytes.Buffer) bool {
22 | meta, data := splitBuffer(b, "+++")
23 | if meta == nil || data == nil {
24 | return false
25 | }
26 | t.markdown = data
27 |
28 | m := make(map[string]interface{})
29 | if err := toml.Unmarshal(meta.Bytes(), &m); err != nil {
30 | return false
31 | }
32 | t.metadata = NewMetadata(m)
33 |
34 | return true
35 | }
36 |
37 | // Metadata returns parsed metadata. It should be called
38 | // only after a call to Parse returns without error.
39 | func (t *TOMLParser) Metadata() Metadata {
40 | return t.metadata
41 | }
42 |
43 | // Markdown returns parser markdown. It should be called only after a call to Parse returns without error.
44 | func (t *TOMLParser) Markdown() []byte {
45 | return t.markdown.Bytes()
46 | }
47 |
--------------------------------------------------------------------------------
/dist/init/mac-launchd/com.caddyserver.web.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Label
6 | com.caddyserver.web
7 | EnvironmentVariables
8 |
9 | HOME
10 | /Users/mathias
11 |
12 | ProgramArguments
13 |
14 | sh
15 | -c
16 | ulimit -n 8192; cd /Users/mathias/Sites; ./caddy -agree -email my_email@domain.com -conf=/Users/mathias/Sites/Caddyfile
17 |
18 | UserName
19 | www
20 | RunAtLoad
21 |
22 | KeepAlive
23 |
24 | WorkingDirectory
25 | /Users/mathias/Sites
26 | StandardOutPath
27 | /Users/mathias/Sites/caddy.log
28 | StandardErrorPath
29 | /Users/mathias/Sites/caddy_error.log
30 |
31 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/error.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | var (
8 | _ error = NonHijackerError{}
9 | _ error = NonFlusherError{}
10 | _ error = NonCloseNotifierError{}
11 | )
12 |
13 | // NonHijackerError is more descriptive error caused by a non hijacker
14 | type NonHijackerError struct {
15 | // underlying type which doesn't implement Hijack
16 | Underlying interface{}
17 | }
18 |
19 | // Implement Error
20 | func (h NonHijackerError) Error() string {
21 | return fmt.Sprintf("%T is not a hijacker", h.Underlying)
22 | }
23 |
24 | // NonFlusherError is more descriptive error caused by a non flusher
25 | type NonFlusherError struct {
26 | // underlying type which doesn't implement Flush
27 | Underlying interface{}
28 | }
29 |
30 | // Implement Error
31 | func (f NonFlusherError) Error() string {
32 | return fmt.Sprintf("%T is not a flusher", f.Underlying)
33 | }
34 |
35 | // NonCloseNotifierError is more descriptive error caused by a non closeNotifier
36 | type NonCloseNotifierError struct {
37 | // underlying type which doesn't implement CloseNotify
38 | Underlying interface{}
39 | }
40 |
41 | // Implement Error
42 | func (c NonCloseNotifierError) Error() string {
43 | return fmt.Sprintf("%T is not a closeNotifier", c.Underlying)
44 | }
45 |
--------------------------------------------------------------------------------
/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 | (Are you asking for help with using Caddy? Please use our forum instead: https://forum.caddyserver.com. If you are filing a bug report, please take a few minutes to carefully answer the following questions. If your issue is not a bug report, you do not need to use this template. Thanks!)
2 |
3 | ### 1. What version of Caddy are you running (`caddy -version`)?
4 |
5 |
6 | ### 2. What are you trying to do?
7 |
8 |
9 | ### 3. What is your entire Caddyfile?
10 | ```text
11 | (Put Caddyfile here)
12 | ```
13 |
14 | ### 4. How did you run Caddy (give the full command and describe the execution environment)?
15 |
16 |
17 | ### 5. Please paste any relevant HTTP request(s) here.
18 |
19 | (paste curl command, or full HTTP request including headers and body, here)
20 |
21 |
22 | ### 6. What did you expect to see?
23 |
24 |
25 | ### 7. What did you see instead (give full error messages and/or log)?
26 |
27 |
28 | ### 8. How can someone who is starting from scratch reproduce the bug as minimally as possible?
29 |
30 | (Please strip away any extra infrastructure such as containers, reverse proxies, upstream apps, dependencies, etc, to prove this is a bug in Caddy and not an external misconfiguration. Your chances of getting this bug fixed go way up the easier it is to replicate. Thank you!)
31 |
--------------------------------------------------------------------------------
/caddy/caddymain/run_test.go:
--------------------------------------------------------------------------------
1 | package caddymain
2 |
3 | import (
4 | "runtime"
5 | "testing"
6 | )
7 |
8 | func TestSetCPU(t *testing.T) {
9 | currentCPU := runtime.GOMAXPROCS(-1)
10 | maxCPU := runtime.NumCPU()
11 | halfCPU := int(0.5 * float32(maxCPU))
12 | if halfCPU < 1 {
13 | halfCPU = 1
14 | }
15 | for i, test := range []struct {
16 | input string
17 | output int
18 | shouldErr bool
19 | }{
20 | {"1", 1, false},
21 | {"-1", currentCPU, true},
22 | {"0", currentCPU, true},
23 | {"100%", maxCPU, false},
24 | {"50%", halfCPU, false},
25 | {"110%", currentCPU, true},
26 | {"-10%", currentCPU, true},
27 | {"invalid input", currentCPU, true},
28 | {"invalid input%", currentCPU, true},
29 | {"9999", maxCPU, false}, // over available CPU
30 | } {
31 | err := setCPU(test.input)
32 | if test.shouldErr && err == nil {
33 | t.Errorf("Test %d: Expected error, but there wasn't any", i)
34 | }
35 | if !test.shouldErr && err != nil {
36 | t.Errorf("Test %d: Expected no error, but there was one: %v", i, err)
37 | }
38 | if actual, expected := runtime.GOMAXPROCS(-1), test.output; actual != expected {
39 | t.Errorf("Test %d: GOMAXPROCS was %d but expected %d", i, actual, expected)
40 | }
41 | // teardown
42 | runtime.GOMAXPROCS(currentCPU)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/process_test.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "os"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | )
10 |
11 | func TestConfig_Markdown(t *testing.T) {
12 | tests := []map[string]string{
13 | {"author": "authorVal"},
14 | {"copyright": "copyrightVal"},
15 | {"description": "descriptionVal"},
16 | {"subject": "subjectVal"},
17 | {"author": "authorVal", "copyright": "copyrightVal"},
18 | {"author": "authorVal", "copyright": "copyrightVal", "description": "descriptionVal"},
19 | {"author": "authorVal", "copyright": "copyrightVal", "description": "descriptionVal", "subject": "subjectVal"},
20 | }
21 |
22 | for i, meta := range tests {
23 | config := &Config{
24 | Template: GetDefaultTemplate(),
25 | }
26 |
27 | toml := "+++"
28 | for key, val := range meta {
29 | toml = toml + "\n" + key + "= \"" + val + "\""
30 | }
31 | toml = toml + "\n+++"
32 |
33 | res, _ := config.Markdown("Test title", strings.NewReader(toml), []os.FileInfo{}, httpserver.Context{})
34 | sRes := string(res)
35 |
36 | for key, val := range meta {
37 | c := strings.Contains(sRes, "")
38 | if !c {
39 | t.Error("Test case", i, "should contain meta", key, val)
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/caddyhttp/expvar/expvar.go:
--------------------------------------------------------------------------------
1 | package expvar
2 |
3 | import (
4 | "expvar"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | )
10 |
11 | // ExpVar is a simple struct to hold expvar's configuration
12 | type ExpVar struct {
13 | Next httpserver.Handler
14 | Resource Resource
15 | }
16 |
17 | // ServeHTTP handles requests to expvar's configured entry point with
18 | // expvar, or passes all other requests up the chain.
19 | func (e ExpVar) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
20 | if httpserver.Path(r.URL.Path).Matches(string(e.Resource)) {
21 | expvarHandler(w, r)
22 | return 0, nil
23 | }
24 | return e.Next.ServeHTTP(w, r)
25 | }
26 |
27 | // expvarHandler returns a JSON object will all the published variables.
28 | //
29 | // This is lifted straight from the expvar package.
30 | func expvarHandler(w http.ResponseWriter, r *http.Request) {
31 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
32 | fmt.Fprintf(w, "{\n")
33 | first := true
34 | expvar.Do(func(kv expvar.KeyValue) {
35 | if !first {
36 | fmt.Fprintf(w, ",\n")
37 | }
38 | first = false
39 | fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
40 | })
41 | fmt.Fprintf(w, "\n}\n")
42 | }
43 |
44 | // Resource contains the path to the expvar entry point
45 | type Resource string
46 |
--------------------------------------------------------------------------------
/caddyhttp/extensions/setup.go:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("ext", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setup,
12 | })
13 | }
14 |
15 | // setup configures a new instance of 'extensions' middleware for clean URLs.
16 | func setup(c *caddy.Controller) error {
17 | cfg := httpserver.GetConfig(c)
18 | root := cfg.Root
19 |
20 | exts, err := extParse(c)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
26 | return Ext{
27 | Next: next,
28 | Extensions: exts,
29 | Root: root,
30 | }
31 | })
32 |
33 | return nil
34 | }
35 |
36 | // extParse sets up an instance of extension middleware
37 | // from a middleware controller and returns a list of extensions.
38 | func extParse(c *caddy.Controller) ([]string, error) {
39 | var exts []string
40 |
41 | for c.Next() {
42 | // At least one extension is required
43 | if !c.NextArg() {
44 | return exts, c.ArgErr()
45 | }
46 | exts = append(exts, c.Val())
47 |
48 | // Tack on any other extensions that may have been listed
49 | exts = append(exts, c.RemainingArgs()...)
50 | }
51 |
52 | return exts, nil
53 | }
54 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/recorder_test.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 | )
8 |
9 | func TestNewResponseRecorder(t *testing.T) {
10 | w := httptest.NewRecorder()
11 | recordRequest := NewResponseRecorder(w)
12 | if !(recordRequest.ResponseWriter == w) {
13 | t.Fatalf("Expected Response writer in the Recording to be same as the one sent\n")
14 | }
15 | if recordRequest.status != http.StatusOK {
16 | t.Fatalf("Expected recorded status to be http.StatusOK (%d) , but found %d\n ", http.StatusOK, recordRequest.status)
17 | }
18 | }
19 | func TestWriteHeader(t *testing.T) {
20 | w := httptest.NewRecorder()
21 | recordRequest := NewResponseRecorder(w)
22 | recordRequest.WriteHeader(401)
23 | if w.Code != 401 || recordRequest.status != 401 {
24 | t.Fatalf("Expected Response status to be set to 401, but found %d\n", recordRequest.status)
25 | }
26 | }
27 |
28 | func TestWrite(t *testing.T) {
29 | w := httptest.NewRecorder()
30 | responseTestString := "test"
31 | recordRequest := NewResponseRecorder(w)
32 | buf := []byte(responseTestString)
33 | recordRequest.Write(buf)
34 | if recordRequest.size != len(buf) {
35 | t.Fatalf("Expected the bytes written counter to be %d, but instead found %d\n", len(buf), recordRequest.size)
36 | }
37 | if w.Body.String() != responseTestString {
38 | t.Fatalf("Expected Response Body to be %s , but found %s\n", responseTestString, w.Body.String())
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/metadata/metadata_json.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | )
7 |
8 | // JSONParser is the MetadataParser for JSON
9 | type JSONParser struct {
10 | metadata Metadata
11 | markdown *bytes.Buffer
12 | }
13 |
14 | // Type returns the kind of metadata parser implemented by this struct.
15 | func (j *JSONParser) Type() string {
16 | return "JSON"
17 | }
18 |
19 | // Init prepares the metadata metadata/markdown file and parses it
20 | func (j *JSONParser) Init(b *bytes.Buffer) bool {
21 | m := make(map[string]interface{})
22 |
23 | err := json.Unmarshal(b.Bytes(), &m)
24 | if err != nil {
25 | var offset int
26 |
27 | jerr, ok := err.(*json.SyntaxError)
28 | if !ok {
29 | return false
30 | }
31 |
32 | offset = int(jerr.Offset)
33 |
34 | m = make(map[string]interface{})
35 | err = json.Unmarshal(b.Next(offset-1), &m)
36 | if err != nil {
37 | return false
38 | }
39 | }
40 |
41 | j.metadata = NewMetadata(m)
42 | j.markdown = bytes.NewBuffer(b.Bytes())
43 |
44 | return true
45 | }
46 |
47 | // Metadata returns parsed metadata. It should be called
48 | // only after a call to Parse returns without error.
49 | func (j *JSONParser) Metadata() Metadata {
50 | return j.metadata
51 | }
52 |
53 | // Markdown returns the markdown text. It should be called only after a call to Parse returns without error.
54 | func (j *JSONParser) Markdown() []byte {
55 | return j.markdown.Bytes()
56 | }
57 |
--------------------------------------------------------------------------------
/caddyhttp/caddyhttp.go:
--------------------------------------------------------------------------------
1 | package caddyhttp
2 |
3 | import (
4 | // plug in the server
5 | _ "github.com/mholt/caddy/caddyhttp/httpserver"
6 |
7 | // plug in the standard directives
8 | _ "github.com/mholt/caddy/caddyhttp/basicauth"
9 | _ "github.com/mholt/caddy/caddyhttp/bind"
10 | _ "github.com/mholt/caddy/caddyhttp/browse"
11 | _ "github.com/mholt/caddy/caddyhttp/errors"
12 | _ "github.com/mholt/caddy/caddyhttp/expvar"
13 | _ "github.com/mholt/caddy/caddyhttp/extensions"
14 | _ "github.com/mholt/caddy/caddyhttp/fastcgi"
15 | _ "github.com/mholt/caddy/caddyhttp/gzip"
16 | _ "github.com/mholt/caddy/caddyhttp/header"
17 | _ "github.com/mholt/caddy/caddyhttp/index"
18 | _ "github.com/mholt/caddy/caddyhttp/internalsrv"
19 | _ "github.com/mholt/caddy/caddyhttp/log"
20 | _ "github.com/mholt/caddy/caddyhttp/markdown"
21 | _ "github.com/mholt/caddy/caddyhttp/maxrequestbody"
22 | _ "github.com/mholt/caddy/caddyhttp/mime"
23 | _ "github.com/mholt/caddy/caddyhttp/pprof"
24 | _ "github.com/mholt/caddy/caddyhttp/proxy"
25 | _ "github.com/mholt/caddy/caddyhttp/push"
26 | _ "github.com/mholt/caddy/caddyhttp/redirect"
27 | _ "github.com/mholt/caddy/caddyhttp/rewrite"
28 | _ "github.com/mholt/caddy/caddyhttp/root"
29 | _ "github.com/mholt/caddy/caddyhttp/status"
30 | _ "github.com/mholt/caddy/caddyhttp/templates"
31 | _ "github.com/mholt/caddy/caddyhttp/timeouts"
32 | _ "github.com/mholt/caddy/caddyhttp/websocket"
33 | _ "github.com/mholt/caddy/startupshutdown"
34 | )
35 |
--------------------------------------------------------------------------------
/caddyhttp/root/root.go:
--------------------------------------------------------------------------------
1 | package root
2 |
3 | import (
4 | "log"
5 | "os"
6 |
7 | "github.com/mholt/caddy"
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | )
10 |
11 | func init() {
12 | caddy.RegisterPlugin("root", caddy.Plugin{
13 | ServerType: "http",
14 | Action: setupRoot,
15 | })
16 | }
17 |
18 | func setupRoot(c *caddy.Controller) error {
19 | config := httpserver.GetConfig(c)
20 |
21 | for c.Next() {
22 | if !c.NextArg() {
23 | return c.ArgErr()
24 | }
25 | config.Root = c.Val()
26 | if c.NextArg() {
27 | // only one argument allowed
28 | return c.ArgErr()
29 | }
30 | }
31 | //first check that the path is not a symlink, os.Stat panics when this is true
32 | info, _ := os.Lstat(config.Root)
33 | if info != nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
34 | //just print out info, delegate responsibility for symlink validity to
35 | //underlying Go framework, no need to test / verify twice
36 | log.Printf("[INFO] Root path is symlink: %s", config.Root)
37 | } else {
38 | // Check if root path exists
39 | _, err := os.Stat(config.Root)
40 | if err != nil {
41 | if os.IsNotExist(err) {
42 | // Allow this, because the folder might appear later.
43 | // But make sure the user knows!
44 | log.Printf("[WARNING] Root path does not exist: %s", config.Root)
45 | } else {
46 | return c.Errf("Unable to access root path '%s': %v", config.Root, err)
47 | }
48 | }
49 | }
50 |
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/caddytls/httphandler.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "net/http/httputil"
9 | "net/url"
10 | "strings"
11 | )
12 |
13 | const challengeBasePath = "/.well-known/acme-challenge"
14 |
15 | // HTTPChallengeHandler proxies challenge requests to ACME client if the
16 | // request path starts with challengeBasePath. It returns true if it
17 | // handled the request and no more needs to be done; it returns false
18 | // if this call was a no-op and the request still needs handling.
19 | func HTTPChallengeHandler(w http.ResponseWriter, r *http.Request, listenHost, altPort string) bool {
20 | if !strings.HasPrefix(r.URL.Path, challengeBasePath) {
21 | return false
22 | }
23 | if DisableHTTPChallenge {
24 | return false
25 | }
26 | if !namesObtaining.Has(r.Host) {
27 | return false
28 | }
29 |
30 | scheme := "http"
31 | if r.TLS != nil {
32 | scheme = "https"
33 | }
34 |
35 | if listenHost == "" {
36 | listenHost = "localhost"
37 | }
38 |
39 | upstream, err := url.Parse(fmt.Sprintf("%s://%s:%s", scheme, listenHost, altPort))
40 | if err != nil {
41 | w.WriteHeader(http.StatusInternalServerError)
42 | log.Printf("[ERROR] ACME proxy handler: %v", err)
43 | return true
44 | }
45 |
46 | proxy := httputil.NewSingleHostReverseProxy(upstream)
47 | proxy.Transport = &http.Transport{
48 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
49 | }
50 | proxy.ServeHTTP(w, r)
51 |
52 | return true
53 | }
54 |
--------------------------------------------------------------------------------
/caddytls/storagetest/storagetest_test.go:
--------------------------------------------------------------------------------
1 | package storagetest
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "testing"
8 | "time"
9 |
10 | "github.com/mholt/caddy/caddytls"
11 | )
12 |
13 | // TestFileStorage tests the file storage set with the test harness in this
14 | // package.
15 | func TestFileStorage(t *testing.T) {
16 | emailCounter := 0
17 | storageTest := &StorageTest{
18 | Storage: &caddytls.FileStorage{Path: "./testdata"}, // nameLocks isn't made here, but it's okay because the tests don't call TryLock or Unlock
19 | PostTest: func() { os.RemoveAll("./testdata") },
20 | AfterUserEmailStore: func(email string) error {
21 | // We need to change the dir mod time to show a
22 | // that certain dirs are newer.
23 | emailCounter++
24 | fp := filepath.Join("./testdata", "users", email)
25 |
26 | // What we will do is subtract 10 days from today and
27 | // then add counter * seconds to make the later
28 | // counters newer. We accept that this isn't exactly
29 | // how the file storage works because it only changes
30 | // timestamps on *newly seen* users, but it achieves
31 | // the result that the harness expects.
32 | chTime := time.Now().AddDate(0, 0, -10).Add(time.Duration(emailCounter) * time.Second)
33 | if err := os.Chtimes(fp, chTime, chTime); err != nil {
34 | return fmt.Errorf("Unable to change file time for %v: %v", fp, err)
35 | }
36 | return nil
37 | },
38 | }
39 | storageTest.Test(t, false)
40 | }
41 |
--------------------------------------------------------------------------------
/caddyhttp/pprof/pprof_test.go:
--------------------------------------------------------------------------------
1 | package pprof
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func TestServeHTTP(t *testing.T) {
13 | h := Handler{
14 | Next: httpserver.HandlerFunc(nextHandler),
15 | Mux: NewMux(),
16 | }
17 |
18 | w := httptest.NewRecorder()
19 | r, err := http.NewRequest("GET", "/debug/pprof", nil)
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | status, err := h.ServeHTTP(w, r)
24 |
25 | if status != 0 {
26 | t.Errorf("Expected status %d but got %d", 0, status)
27 | }
28 | if err != nil {
29 | t.Errorf("Expected nil error, but got: %v", err)
30 | }
31 | if w.Body.String() == "content" {
32 | t.Errorf("Expected pprof to handle request, but it didn't")
33 | }
34 |
35 | w = httptest.NewRecorder()
36 | r, err = http.NewRequest("GET", "/foo", nil)
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 | status, err = h.ServeHTTP(w, r)
41 | if status != http.StatusNotFound {
42 | t.Errorf("Test two: Expected status %d but got %d", http.StatusNotFound, status)
43 | }
44 | if err != nil {
45 | t.Errorf("Test two: Expected nil error, but got: %v", err)
46 | }
47 | if w.Body.String() != "content" {
48 | t.Errorf("Expected pprof to pass the request through, but it didn't; got: %s", w.Body.String())
49 | }
50 | }
51 |
52 | func nextHandler(w http.ResponseWriter, r *http.Request) (int, error) {
53 | fmt.Fprintf(w, "content")
54 | return http.StatusNotFound, nil
55 | }
56 |
--------------------------------------------------------------------------------
/caddyhttp/rewrite/to_test.go:
--------------------------------------------------------------------------------
1 | package rewrite
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "testing"
7 | )
8 |
9 | func TestTo(t *testing.T) {
10 | fs := http.Dir("testdata")
11 | tests := []struct {
12 | url string
13 | to string
14 | expected string
15 | }{
16 | {"/", "/somefiles", "/somefiles"},
17 | {"/somefiles", "/somefiles /index.php{uri}", "/index.php/somefiles"},
18 | {"/somefiles", "/testfile /index.php{uri}", "/testfile"},
19 | {"/somefiles", "/testfile/ /index.php{uri}", "/index.php/somefiles"},
20 | {"/somefiles", "/somefiles /index.php{uri}", "/index.php/somefiles"},
21 | {"/?a=b", "/somefiles /index.php?{query}", "/index.php?a=b"},
22 | {"/?a=b", "/testfile /index.php?{query}", "/testfile?a=b"},
23 | {"/?a=b", "/testdir /index.php?{query}", "/index.php?a=b"},
24 | {"/?a=b", "/testdir/ /index.php?{query}", "/testdir/?a=b"},
25 | {"/test?url=http://", " /p/{path}?{query}", "/p/test?url=http://"},
26 | {"/test?url=http://", " /p/{rewrite_path}?{query}", "/p/test?url=http://"},
27 | {"/test/?url=http://", " /{uri}", "/test/?url=http://"},
28 | }
29 |
30 | uri := func(r *url.URL) string {
31 | uri := r.Path
32 | if r.RawQuery != "" {
33 | uri += "?" + r.RawQuery
34 | }
35 | return uri
36 | }
37 | for i, test := range tests {
38 | r, err := http.NewRequest("GET", test.url, nil)
39 | if err != nil {
40 | t.Error(err)
41 | }
42 | To(fs, r, test.to, newReplacer(r))
43 | if uri(r.URL) != test.expected {
44 | t.Errorf("Test %v: expected %v found %v", i, test.expected, uri(r.URL))
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/middleware_test.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "os"
5 | "testing"
6 | )
7 |
8 | func TestPathCaseSensitivity(t *testing.T) {
9 | tests := []struct {
10 | basePath string
11 | path string
12 | caseSensitive bool
13 | expected bool
14 | }{
15 | {"/", "/file", true, true},
16 | {"/a", "/file", true, false},
17 | {"/f", "/file", true, true},
18 | {"/f", "/File", true, false},
19 | {"/f", "/File", false, true},
20 | {"/file", "/file", true, true},
21 | {"/file", "/file", false, true},
22 | {"/files", "/file", false, false},
23 | {"/files", "/file", true, false},
24 | {"/folder", "/folder/file.txt", true, true},
25 | {"/folders", "/folder/file.txt", true, false},
26 | {"/folder", "/Folder/file.txt", false, true},
27 | {"/folders", "/Folder/file.txt", false, false},
28 | }
29 |
30 | for i, test := range tests {
31 | CaseSensitivePath = test.caseSensitive
32 | valid := Path(test.path).Matches(test.basePath)
33 | if test.expected != valid {
34 | t.Errorf("Test %d: Expected %v, found %v", i, test.expected, valid)
35 | }
36 | }
37 | }
38 |
39 | func TestPathCaseSensitiveEnv(t *testing.T) {
40 | tests := []struct {
41 | envValue string
42 | expected bool
43 | }{
44 | {"1", true},
45 | {"0", false},
46 | {"false", false},
47 | {"true", true},
48 | {"", true},
49 | }
50 |
51 | for i, test := range tests {
52 | os.Setenv(caseSensitivePathEnv, test.envValue)
53 | initCaseSettings()
54 | if test.expected != CaseSensitivePath {
55 | t.Errorf("Test %d: Expected %v, found %v", i, test.expected, CaseSensitivePath)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/caddyhttp/extensions/ext.go:
--------------------------------------------------------------------------------
1 | // Package extensions contains middleware for clean URLs.
2 | //
3 | // The root path of the site is passed in as well as possible extensions
4 | // to try internally for paths requested that don't match an existing
5 | // resource. The first path+ext combination that matches a valid file
6 | // will be used.
7 | package extensions
8 |
9 | import (
10 | "net/http"
11 | "os"
12 | "path"
13 | "strings"
14 |
15 | "github.com/mholt/caddy/caddyhttp/httpserver"
16 | )
17 |
18 | // Ext can assume an extension from clean URLs.
19 | // It tries extensions in the order listed in Extensions.
20 | type Ext struct {
21 | // Next handler in the chain
22 | Next httpserver.Handler
23 |
24 | // Path to site root
25 | Root string
26 |
27 | // List of extensions to try
28 | Extensions []string
29 | }
30 |
31 | // ServeHTTP implements the httpserver.Handler interface.
32 | func (e Ext) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
33 | urlpath := strings.TrimSuffix(r.URL.Path, "/")
34 | if path.Ext(urlpath) == "" && len(r.URL.Path) > 0 && r.URL.Path[len(r.URL.Path)-1] != '/' {
35 | for _, ext := range e.Extensions {
36 | if resourceExists(e.Root, urlpath+ext) {
37 | r.URL.Path = urlpath + ext
38 | break
39 | }
40 | }
41 | }
42 | return e.Next.ServeHTTP(w, r)
43 | }
44 |
45 | // resourceExists returns true if the file specified at
46 | // root + path exists; false otherwise.
47 | func resourceExists(root, path string) bool {
48 | _, err := os.Stat(root + path)
49 | // technically we should use os.IsNotExist(err)
50 | // but we don't handle any other kinds of errors anyway
51 | return err == nil
52 | }
53 |
--------------------------------------------------------------------------------
/caddyhttp/proxy/body_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 | )
11 |
12 | func TestBodyRetry(t *testing.T) {
13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14 | io.Copy(w, r.Body)
15 | r.Body.Close()
16 | }))
17 | defer ts.Close()
18 |
19 | testcase := "test content"
20 | req, err := http.NewRequest(http.MethodPost, ts.URL, bytes.NewBufferString(testcase))
21 | if err != nil {
22 | t.Fatal(err)
23 | }
24 |
25 | body, err := newBufferedBody(req.Body)
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | if body != nil {
30 | req.Body = body
31 | }
32 |
33 | // simulate fail request
34 | host := req.URL.Host
35 | req.URL.Host = "example.com"
36 | body.rewind()
37 | _, _ = http.DefaultTransport.RoundTrip(req)
38 |
39 | // retry request
40 | req.URL.Host = host
41 | body.rewind()
42 | resp, err := http.DefaultTransport.RoundTrip(req)
43 | if err != nil {
44 | t.Fatal(err)
45 | }
46 | result, err := ioutil.ReadAll(resp.Body)
47 | if err != nil {
48 | t.Fatal(err)
49 | }
50 | resp.Body.Close()
51 | if string(result) != testcase {
52 | t.Fatalf("result = %s, want %s", result, testcase)
53 | }
54 |
55 | // try one more time for body reuse
56 | body.rewind()
57 | resp, err = http.DefaultTransport.RoundTrip(req)
58 | if err != nil {
59 | t.Fatal(err)
60 | }
61 | result, err = ioutil.ReadAll(resp.Body)
62 | if err != nil {
63 | t.Fatal(err)
64 | }
65 | resp.Body.Close()
66 | if string(result) != testcase {
67 | t.Fatalf("result = %s, want %s", result, testcase)
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/caddy/build.bash:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Caddy build script. Automates proper versioning.
4 | #
5 | # Usage:
6 | #
7 | # $ ./build.bash [output_filename] [git_repo]
8 | #
9 | # Outputs compiled program in current directory.
10 | # Default git repo is current directory.
11 | # Builds always take place from current directory.
12 |
13 | set -euo pipefail
14 |
15 | : ${output_filename:="${1:-}"}
16 | : ${output_filename:=""}
17 |
18 | : ${git_repo:="${2:-}"}
19 | : ${git_repo:="."}
20 |
21 | pkg=github.com/mholt/caddy/caddy/caddymain
22 | ldflags=()
23 |
24 | # Timestamp of build
25 | name="${pkg}.buildDate"
26 | value=$(date -u +"%a %b %d %H:%M:%S %Z %Y")
27 | ldflags+=("-X" "\"${name}=${value}\"")
28 |
29 | # Current tag, if HEAD is on a tag
30 | name="${pkg}.gitTag"
31 | set +e
32 | value="$(git -C "${git_repo}" describe --exact-match HEAD 2>/dev/null)"
33 | set -e
34 | ldflags+=("-X" "\"${name}=${value}\"")
35 |
36 | # Nearest tag on branch
37 | name="${pkg}.gitNearestTag"
38 | value="$(git -C "${git_repo}" describe --abbrev=0 --tags HEAD)"
39 | ldflags+=("-X" "\"${name}=${value}\"")
40 |
41 | # Commit SHA
42 | name="${pkg}.gitCommit"
43 | value="$(git -C "${git_repo}" rev-parse --short HEAD)"
44 | ldflags+=("-X" "\"${name}=${value}\"")
45 |
46 | # Summary of uncommitted changes
47 | name="${pkg}.gitShortStat"
48 | value="$(git -C "${git_repo}" diff-index --shortstat HEAD)"
49 | ldflags+=("-X" "\"${name}=${value}\"")
50 |
51 | # List of modified files
52 | name="${pkg}.gitFilesModified"
53 | value="$(git -C "${git_repo}" diff-index --name-only HEAD)"
54 | ldflags+=("-X" "\"${name}=${value}\"")
55 |
56 | go build -ldflags "${ldflags[*]}" -o "${output_filename}"
57 |
--------------------------------------------------------------------------------
/caddyhttp/pprof/pprof.go:
--------------------------------------------------------------------------------
1 | package pprof
2 |
3 | import (
4 | "net/http"
5 | pp "net/http/pprof"
6 |
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | // BasePath is the base path to match for all pprof requests.
11 | const BasePath = "/debug/pprof"
12 |
13 | // Handler is a simple struct whose ServeHTTP will delegate pprof
14 | // endpoints to their equivalent net/http/pprof handlers.
15 | type Handler struct {
16 | Next httpserver.Handler
17 | Mux *http.ServeMux
18 | }
19 |
20 | // ServeHTTP handles requests to BasePath with pprof, or passes
21 | // all other requests up the chain.
22 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
23 | if httpserver.Path(r.URL.Path).Matches(BasePath) {
24 | h.Mux.ServeHTTP(w, r)
25 | return 0, nil
26 | }
27 | return h.Next.ServeHTTP(w, r)
28 | }
29 |
30 | // NewMux returns a new http.ServeMux that routes pprof requests.
31 | // It pretty much copies what the std lib pprof does on init:
32 | // https://golang.org/src/net/http/pprof/pprof.go#L67
33 | func NewMux() *http.ServeMux {
34 | mux := http.NewServeMux()
35 | mux.HandleFunc(BasePath+"/", func(w http.ResponseWriter, r *http.Request) {
36 | // this endpoint, as implemented in the standard library, doesn't set
37 | // its Content-Type header, so using this can confuse clients, especially
38 | // if gzipping...
39 | w.Header().Set("Content-Type", "text/html; charset=utf-8")
40 | pp.Index(w, r)
41 | })
42 | mux.HandleFunc(BasePath+"/cmdline", pp.Cmdline)
43 | mux.HandleFunc(BasePath+"/profile", pp.Profile)
44 | mux.HandleFunc(BasePath+"/symbol", pp.Symbol)
45 | mux.HandleFunc(BasePath+"/trace", pp.Trace)
46 | return mux
47 | }
48 |
--------------------------------------------------------------------------------
/caddyhttp/status/status.go:
--------------------------------------------------------------------------------
1 | // Package status is middleware for returning status code for requests
2 | package status
3 |
4 | import (
5 | "net/http"
6 |
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | // Rule describes status rewriting rule
11 | type Rule struct {
12 | // Base path. Request to this path and sub-paths will be answered with StatusCode
13 | Base string
14 |
15 | // Status code to return
16 | StatusCode int
17 |
18 | // Request matcher
19 | httpserver.RequestMatcher
20 | }
21 |
22 | // NewRule creates new Rule.
23 | func NewRule(basePath string, status int) *Rule {
24 | return &Rule{
25 | Base: basePath,
26 | StatusCode: status,
27 | RequestMatcher: httpserver.PathMatcher(basePath),
28 | }
29 | }
30 |
31 | // BasePath implements httpserver.HandlerConfig interface
32 | func (rule *Rule) BasePath() string {
33 | return rule.Base
34 | }
35 |
36 | // Status is a middleware to return status code for request
37 | type Status struct {
38 | Rules []httpserver.HandlerConfig
39 | Next httpserver.Handler
40 | }
41 |
42 | // ServeHTTP implements the httpserver.Handler interface
43 | func (status Status) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
44 | if cfg := httpserver.ConfigSelector(status.Rules).Select(r); cfg != nil {
45 | rule := cfg.(*Rule)
46 |
47 | if rule.StatusCode < 400 {
48 | // There's no ability to return response body --
49 | // write the response status code in header and signal
50 | // to other handlers that response is already handled
51 | w.WriteHeader(rule.StatusCode)
52 | return 0, nil
53 | }
54 |
55 | return rule.StatusCode, nil
56 | }
57 |
58 | return status.Next.ServeHTTP(w, r)
59 | }
60 |
--------------------------------------------------------------------------------
/caddyhttp/expvar/setup.go:
--------------------------------------------------------------------------------
1 | package expvar
2 |
3 | import (
4 | "expvar"
5 | "runtime"
6 | "sync"
7 |
8 | "github.com/mholt/caddy"
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func init() {
13 | caddy.RegisterPlugin("expvar", caddy.Plugin{
14 | ServerType: "http",
15 | Action: setup,
16 | })
17 | }
18 |
19 | // setup configures a new ExpVar middleware instance.
20 | func setup(c *caddy.Controller) error {
21 | resource, err := expVarParse(c)
22 | if err != nil {
23 | return err
24 | }
25 |
26 | // publish any extra information/metrics we may want to capture
27 | publishExtraVars()
28 |
29 | ev := ExpVar{Resource: resource}
30 |
31 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
32 | ev.Next = next
33 | return ev
34 | })
35 |
36 | return nil
37 | }
38 |
39 | func expVarParse(c *caddy.Controller) (Resource, error) {
40 | var resource Resource
41 | var err error
42 |
43 | for c.Next() {
44 | args := c.RemainingArgs()
45 | switch len(args) {
46 | case 0:
47 | resource = Resource(defaultExpvarPath)
48 | case 1:
49 | resource = Resource(args[0])
50 | default:
51 | return resource, c.ArgErr()
52 | }
53 | }
54 |
55 | return resource, err
56 | }
57 |
58 | func publishExtraVars() {
59 | // By using sync.Once instead of an init() function, we don't clutter
60 | // the app's expvar export unnecessarily, or risk colliding with it.
61 | publishOnce.Do(func() {
62 | expvar.Publish("Goroutines", expvar.Func(func() interface{} {
63 | return runtime.NumGoroutine()
64 | }))
65 | })
66 | }
67 |
68 | var publishOnce sync.Once // publishing variables should only be done once
69 | var defaultExpvarPath = "/debug/vars"
70 |
--------------------------------------------------------------------------------
/caddyhttp/mime/mime_test.go:
--------------------------------------------------------------------------------
1 | package mime
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func TestMimeHandler(t *testing.T) {
13 | mimes := Config{
14 | ".html": "text/html",
15 | ".txt": "text/plain",
16 | ".swf": "application/x-shockwave-flash",
17 | }
18 |
19 | m := Mime{Configs: mimes}
20 |
21 | w := httptest.NewRecorder()
22 | exts := []string{
23 | ".html", ".txt", ".swf",
24 | }
25 | for _, e := range exts {
26 | url := "/file" + e
27 | r, err := http.NewRequest("GET", url, nil)
28 | if err != nil {
29 | t.Error(err)
30 | }
31 | m.Next = nextFunc(true, mimes[e])
32 | _, err = m.ServeHTTP(w, r)
33 | if err != nil {
34 | t.Error(err)
35 | }
36 | }
37 |
38 | w = httptest.NewRecorder()
39 | exts = []string{
40 | ".htm1", ".abc", ".mdx",
41 | }
42 | for _, e := range exts {
43 | url := "/file" + e
44 | r, err := http.NewRequest("GET", url, nil)
45 | if err != nil {
46 | t.Error(err)
47 | }
48 | m.Next = nextFunc(false, "")
49 | _, err = m.ServeHTTP(w, r)
50 | if err != nil {
51 | t.Error(err)
52 | }
53 | }
54 | }
55 |
56 | func nextFunc(shouldMime bool, contentType string) httpserver.Handler {
57 | return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
58 | if shouldMime {
59 | if w.Header().Get("Content-Type") != contentType {
60 | return 0, fmt.Errorf("expected Content-Type: %v, found %v", contentType, r.Header.Get("Content-Type"))
61 | }
62 | return 0, nil
63 | }
64 | if w.Header().Get("Content-Type") != "" {
65 | return 0, fmt.Errorf("Content-Type header not expected")
66 | }
67 | return 0, nil
68 | })
69 | }
70 |
--------------------------------------------------------------------------------
/dist/init/README.md:
--------------------------------------------------------------------------------
1 | Init/Service Scripts
2 | ====================
3 |
4 | This folder contains init/service scripts for using Caddy on various Linux and BSD distributions. They are created and maintained by the community.
5 |
6 | ## Getting Help
7 |
8 | Different scripts have different maintainers; please consult the comments in the file and any README for assistance setting it up. Do not open an issue on the Caddy project about these scripts; instead, to ask a question or suggest a change, please contact the maintainer of the script directly.
9 |
10 | ## Disclaimer
11 |
12 | The files contained herein are not officially supported by the Caddy project author and/or contributors, and as such, the files are not endorsed by the same. The Caddy project author and its contributors are not responsible for the function or malfunction of these scripts/files, or any unintended consequences to your system or website in attempting to set up Caddy. Users are expected to know how to administer their system, and these files should be considered as only a guide or suggestion for using Caddy in certain environments.
13 |
14 | ## Guidelines
15 |
16 | The files distributed here should adhere to these principles where relevant (adjust accordingly for each system/platform):
17 |
18 | - Don't run as root.
19 | - Create a no-shell default user to run it.
20 | - Raise file descriptor limits.
21 | - Don't restart endlessly; if Caddy fails to start, there's a reason -- fix it, don't hammer it.
22 | - Allow Caddy to re-use the same, persistent folder for storage.
23 | - Stay as simple and minimal as possible.
24 | - Be idempotent.
25 | - Use comments to explain unexpected or unusual lines/patterns.
26 | - Be secure by default.
27 |
28 | Thank you for using Caddy! May it serve you well.
29 |
--------------------------------------------------------------------------------
/caddyhttp/mime/setup_test.go:
--------------------------------------------------------------------------------
1 | package mime
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `mime .txt text/plain`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, but got: %v", err)
15 | }
16 | mids := httpserver.GetConfig(c).Middleware()
17 | if len(mids) == 0 {
18 | t.Fatal("Expected middleware, but had 0 instead")
19 | }
20 |
21 | handler := mids[0](httpserver.EmptyNext)
22 | myHandler, ok := handler.(Mime)
23 | if !ok {
24 | t.Fatalf("Expected handler to be type Mime, got: %#v", handler)
25 | }
26 |
27 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
28 | t.Error("'Next' field of handler was not set properly")
29 | }
30 |
31 | tests := []struct {
32 | input string
33 | shouldErr bool
34 | }{
35 | {`mime {`, true},
36 | {`mime {}`, true},
37 | {`mime a b`, true},
38 | {`mime a {`, true},
39 | {`mime { txt f } `, true},
40 | {`mime { html } `, true},
41 | {`mime {
42 | .html text/html
43 | .txt text/plain
44 | } `, false},
45 | {`mime {
46 | .foo text/foo
47 | .bar text/bar
48 | .foo text/foobar
49 | } `, true},
50 | {`mime { .html text/html } `, false},
51 | {`mime { .html
52 | } `, true},
53 | {`mime .txt text/plain`, false},
54 | }
55 | for i, test := range tests {
56 | m, err := mimeParse(caddy.NewTestController("http", test.input))
57 | if test.shouldErr && err == nil {
58 | t.Errorf("Test %v: Expected error but found nil %v", i, m)
59 | } else if !test.shouldErr && err != nil {
60 | t.Errorf("Test %v: Expected no error but found error: %v", i, err)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/caddyhttp/mime/setup.go:
--------------------------------------------------------------------------------
1 | package mime
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/mholt/caddy"
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | )
10 |
11 | func init() {
12 | caddy.RegisterPlugin("mime", caddy.Plugin{
13 | ServerType: "http",
14 | Action: setup,
15 | })
16 | }
17 |
18 | // setup configures a new mime middleware instance.
19 | func setup(c *caddy.Controller) error {
20 | configs, err := mimeParse(c)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
26 | return Mime{Next: next, Configs: configs}
27 | })
28 |
29 | return nil
30 | }
31 |
32 | func mimeParse(c *caddy.Controller) (Config, error) {
33 | configs := Config{}
34 |
35 | for c.Next() {
36 | // At least one extension is required
37 |
38 | args := c.RemainingArgs()
39 | switch len(args) {
40 | case 2:
41 | if err := validateExt(configs, args[0]); err != nil {
42 | return configs, err
43 | }
44 | configs[args[0]] = args[1]
45 | case 1:
46 | return configs, c.ArgErr()
47 | case 0:
48 | for c.NextBlock() {
49 | ext := c.Val()
50 | if err := validateExt(configs, ext); err != nil {
51 | return configs, err
52 | }
53 | if !c.NextArg() {
54 | return configs, c.ArgErr()
55 | }
56 | configs[ext] = c.Val()
57 | }
58 | }
59 |
60 | }
61 |
62 | return configs, nil
63 | }
64 |
65 | // validateExt checks for valid file name extension.
66 | func validateExt(configs Config, ext string) error {
67 | if !strings.HasPrefix(ext, ".") {
68 | return fmt.Errorf(`mime: invalid extension "%v" (must start with dot)`, ext)
69 | }
70 | if _, ok := configs[ext]; ok {
71 | return fmt.Errorf(`mime: duplicate extension "%v" found`, ext)
72 | }
73 | return nil
74 | }
75 |
--------------------------------------------------------------------------------
/caddyhttp/redirect/redirect.go:
--------------------------------------------------------------------------------
1 | // Package redirect is middleware for redirecting certain requests
2 | // to other locations.
3 | package redirect
4 |
5 | import (
6 | "fmt"
7 | "html"
8 | "net/http"
9 |
10 | "github.com/mholt/caddy/caddyhttp/httpserver"
11 | )
12 |
13 | // Redirect is middleware to respond with HTTP redirects
14 | type Redirect struct {
15 | Next httpserver.Handler
16 | Rules []Rule
17 | }
18 |
19 | // ServeHTTP implements the httpserver.Handler interface.
20 | func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
21 | for _, rule := range rd.Rules {
22 | if (rule.FromPath == "/" || r.URL.Path == rule.FromPath) && schemeMatches(rule, r) && rule.Match(r) {
23 | to := httpserver.NewReplacer(r, nil, "").Replace(rule.To)
24 | if rule.Meta {
25 | safeTo := html.EscapeString(to)
26 | fmt.Fprintf(w, metaRedir, safeTo, safeTo)
27 | } else {
28 | http.Redirect(w, r, to, rule.Code)
29 | }
30 | return 0, nil
31 | }
32 | }
33 | return rd.Next.ServeHTTP(w, r)
34 | }
35 |
36 | func schemeMatches(rule Rule, req *http.Request) bool {
37 | return (rule.FromScheme() == "https" && req.TLS != nil) ||
38 | (rule.FromScheme() != "https" && req.TLS == nil)
39 | }
40 |
41 | // Rule describes an HTTP redirect rule.
42 | type Rule struct {
43 | FromScheme func() string
44 | FromPath, To string
45 | Code int
46 | Meta bool
47 | httpserver.RequestMatcher
48 | }
49 |
50 | // Script tag comes first since that will better imitate a redirect in the browser's
51 | // history, but the meta tag is a fallback for most non-JS clients.
52 | const metaRedir = `
53 |
54 |
55 |
56 |
57 |
58 | Redirecting...
59 |
60 | `
61 |
--------------------------------------------------------------------------------
/dist/init/linux-systemd/caddy.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Caddy HTTP/2 web server
3 | Documentation=https://caddyserver.com/docs
4 | After=network-online.target
5 | Wants=network-online.target systemd-networkd-wait-online.service
6 |
7 | [Service]
8 | Restart=on-failure
9 | StartLimitInterval=86400
10 | StartLimitBurst=5
11 |
12 | ; User and group the process will run as.
13 | User=www-data
14 | Group=www-data
15 |
16 | ; Letsencrypt-issued certificates will be written to this directory.
17 | Environment=CADDYPATH=/etc/ssl/caddy
18 |
19 | ; Always set "-root" to something safe in case it gets forgotten in the Caddyfile.
20 | ExecStart=/usr/local/bin/caddy -log stdout -agree=true -conf=/etc/caddy/Caddyfile -root=/var/tmp
21 | ExecReload=/bin/kill -USR1 $MAINPID
22 |
23 | ; Limit the number of file descriptors; see `man systemd.exec` for more limit settings.
24 | LimitNOFILE=1048576
25 | ; Unmodified caddy is not expected to use more than that.
26 | LimitNPROC=64
27 |
28 | ; Use private /tmp and /var/tmp, which are discarded after caddy stops.
29 | PrivateTmp=true
30 | ; Use a minimal /dev
31 | PrivateDevices=true
32 | ; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
33 | ProtectHome=true
34 | ; Make /usr, /boot, /etc and possibly some more folders read-only.
35 | ProtectSystem=full
36 | ; … except /etc/ssl/caddy, because we want Letsencrypt-certificates there.
37 | ; This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
38 | ReadWriteDirectories=/etc/ssl/caddy
39 |
40 | ; The following additional security directives only work with systemd v229 or later.
41 | ; They further retrict privileges that can be gained by caddy. Uncomment if you like.
42 | ; Note that you may have to add capabilities required by any plugins in use.
43 | ;CapabilityBoundingSet=CAP_NET_BIND_SERVICE
44 | ;AmbientCapabilities=CAP_NET_BIND_SERVICE
45 | ;NoNewPrivileges=true
46 |
47 | [Install]
48 | WantedBy=multi-user.target
49 |
--------------------------------------------------------------------------------
/startupshutdown/startupshutdown.go:
--------------------------------------------------------------------------------
1 | package startupshutdown
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/exec"
7 | "strings"
8 |
9 | "github.com/mholt/caddy"
10 | )
11 |
12 | func init() {
13 | caddy.RegisterPlugin("startup", caddy.Plugin{Action: Startup})
14 | caddy.RegisterPlugin("shutdown", caddy.Plugin{Action: Shutdown})
15 | }
16 |
17 | // Startup registers a startup callback to execute during server start.
18 | func Startup(c *caddy.Controller) error {
19 | return registerCallback(c, c.OnFirstStartup)
20 | }
21 |
22 | // Shutdown registers a shutdown callback to execute during server stop.
23 | func Shutdown(c *caddy.Controller) error {
24 | return registerCallback(c, c.OnFinalShutdown)
25 | }
26 |
27 | // registerCallback registers a callback function to execute by
28 | // using c to parse the directive. It registers the callback
29 | // to be executed using registerFunc.
30 | func registerCallback(c *caddy.Controller, registerFunc func(func() error)) error {
31 | var funcs []func() error
32 |
33 | for c.Next() {
34 | args := c.RemainingArgs()
35 | if len(args) == 0 {
36 | return c.ArgErr()
37 | }
38 |
39 | nonblock := false
40 | if len(args) > 1 && args[len(args)-1] == "&" {
41 | // Run command in background; non-blocking
42 | nonblock = true
43 | args = args[:len(args)-1]
44 | }
45 |
46 | command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
47 | if err != nil {
48 | return c.Err(err.Error())
49 | }
50 |
51 | fn := func() error {
52 | cmd := exec.Command(command, args...)
53 | cmd.Stdin = os.Stdin
54 | cmd.Stdout = os.Stdout
55 | cmd.Stderr = os.Stderr
56 | if nonblock {
57 | log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " "))
58 | return cmd.Start()
59 | }
60 | log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " "))
61 | return cmd.Run()
62 | }
63 |
64 | funcs = append(funcs, fn)
65 | }
66 |
67 | return c.OncePerServerBlock(func() error {
68 | for _, fn := range funcs {
69 | registerFunc(fn)
70 | }
71 | return nil
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/startupshutdown/startupshutdown_test.go:
--------------------------------------------------------------------------------
1 | package startupshutdown
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strconv"
7 | "testing"
8 | "time"
9 |
10 | "github.com/mholt/caddy"
11 | )
12 |
13 | // The Startup function's tests are symmetrical to Shutdown tests,
14 | // because the Startup and Shutdown functions share virtually the
15 | // same functionality
16 | func TestStartup(t *testing.T) {
17 | tempDirPath := os.TempDir()
18 |
19 | testDir := filepath.Join(tempDirPath, "temp_dir_for_testing_startupshutdown")
20 | defer func() {
21 | // clean up after non-blocking startup function quits
22 | time.Sleep(500 * time.Millisecond)
23 | os.RemoveAll(testDir)
24 | }()
25 | osSenitiveTestDir := filepath.FromSlash(testDir)
26 | os.RemoveAll(osSenitiveTestDir) // start with a clean slate
27 |
28 | var registeredFunction func() error
29 | fakeRegister := func(fn func() error) {
30 | registeredFunction = fn
31 | }
32 |
33 | tests := []struct {
34 | input string
35 | shouldExecutionErr bool
36 | shouldRemoveErr bool
37 | }{
38 | // test case #0 tests proper functionality blocking commands
39 | {"startup mkdir " + osSenitiveTestDir, false, false},
40 |
41 | // test case #1 tests proper functionality of non-blocking commands
42 | {"startup mkdir " + osSenitiveTestDir + " &", false, true},
43 |
44 | // test case #2 tests handling of non-existent commands
45 | {"startup " + strconv.Itoa(int(time.Now().UnixNano())), true, true},
46 | }
47 |
48 | for i, test := range tests {
49 | c := caddy.NewTestController("", test.input)
50 | err := registerCallback(c, fakeRegister)
51 | if err != nil {
52 | t.Errorf("Expected no errors, got: %v", err)
53 | }
54 | if registeredFunction == nil {
55 | t.Fatalf("Expected function to be registered, but it wasn't")
56 | }
57 | err = registeredFunction()
58 | if err != nil && !test.shouldExecutionErr {
59 | t.Errorf("Test %d received an error of:\n%v", i, err)
60 | }
61 | err = os.Remove(osSenitiveTestDir)
62 | if err != nil && !test.shouldRemoveErr {
63 | t.Errorf("Test %d received an error of:\n%v", i, err)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/caddyhttp/internalsrv/setup_test.go:
--------------------------------------------------------------------------------
1 | package internalsrv
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `internal /internal`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, got: %v", err)
15 | }
16 | mids := httpserver.GetConfig(c).Middleware()
17 | if len(mids) == 0 {
18 | t.Fatal("Expected middleware, got 0 instead")
19 | }
20 |
21 | handler := mids[0](httpserver.EmptyNext)
22 | myHandler, ok := handler.(Internal)
23 |
24 | if !ok {
25 | t.Fatalf("Expected handler to be type Internal, got: %#v", handler)
26 | }
27 |
28 | if myHandler.Paths[0] != "/internal" {
29 | t.Errorf("Expected internal in the list of internal Paths")
30 | }
31 |
32 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
33 | t.Error("'Next' field of handler was not set properly")
34 | }
35 |
36 | }
37 |
38 | func TestInternalParse(t *testing.T) {
39 | tests := []struct {
40 | inputInternalPaths string
41 | shouldErr bool
42 | expectedInternalPaths []string
43 | }{
44 | {`internal /internal`, false, []string{"/internal"}},
45 |
46 | {`internal /internal1
47 | internal /internal2`, false, []string{"/internal1", "/internal2"}},
48 | }
49 | for i, test := range tests {
50 | actualInternalPaths, err := internalParse(caddy.NewTestController("http", test.inputInternalPaths))
51 |
52 | if err == nil && test.shouldErr {
53 | t.Errorf("Test %d didn't error, but it should have", i)
54 | } else if err != nil && !test.shouldErr {
55 | t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
56 | }
57 |
58 | if len(actualInternalPaths) != len(test.expectedInternalPaths) {
59 | t.Fatalf("Test %d expected %d InternalPaths, but got %d",
60 | i, len(test.expectedInternalPaths), len(actualInternalPaths))
61 | }
62 | for j, actualInternalPath := range actualInternalPaths {
63 | if actualInternalPath != test.expectedInternalPaths[j] {
64 | t.Fatalf("Test %d expected %dth Internal Path to be %s , but got %s",
65 | i, j, test.expectedInternalPaths[j], actualInternalPath)
66 | }
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/caddyhttp/header/setup.go:
--------------------------------------------------------------------------------
1 | package header
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func init() {
11 | caddy.RegisterPlugin("header", caddy.Plugin{
12 | ServerType: "http",
13 | Action: setup,
14 | })
15 | }
16 |
17 | // setup configures a new Headers middleware instance.
18 | func setup(c *caddy.Controller) error {
19 | rules, err := headersParse(c)
20 | if err != nil {
21 | return err
22 | }
23 |
24 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
25 | return Headers{Next: next, Rules: rules}
26 | })
27 |
28 | return nil
29 | }
30 |
31 | func headersParse(c *caddy.Controller) ([]Rule, error) {
32 | var rules []Rule
33 |
34 | for c.NextLine() {
35 | var head Rule
36 | head.Headers = http.Header{}
37 | var isNewPattern bool
38 |
39 | if !c.NextArg() {
40 | return rules, c.ArgErr()
41 | }
42 | pattern := c.Val()
43 |
44 | // See if we already have a definition for this Path pattern...
45 | for _, h := range rules {
46 | if h.Path == pattern {
47 | head = h
48 | break
49 | }
50 | }
51 |
52 | // ...otherwise, this is a new pattern
53 | if head.Path == "" {
54 | head.Path = pattern
55 | isNewPattern = true
56 | }
57 |
58 | for c.NextBlock() {
59 | // A block of headers was opened...
60 | name := c.Val()
61 | value := ""
62 |
63 | args := c.RemainingArgs()
64 |
65 | if len(args) > 1 {
66 | return rules, c.ArgErr()
67 | } else if len(args) == 1 {
68 | value = args[0]
69 | }
70 |
71 | head.Headers.Add(name, value)
72 | }
73 | if c.NextArg() {
74 | // ... or single header was defined as an argument instead.
75 |
76 | name := c.Val()
77 | value := c.Val()
78 |
79 | if c.NextArg() {
80 | value = c.Val()
81 | }
82 |
83 | head.Headers.Add(name, value)
84 | }
85 |
86 | if isNewPattern {
87 | rules = append(rules, head)
88 | } else {
89 | for i := 0; i < len(rules); i++ {
90 | if rules[i].Path == pattern {
91 | rules[i] = head
92 | break
93 | }
94 | }
95 | }
96 | }
97 |
98 | return rules, nil
99 | }
100 |
--------------------------------------------------------------------------------
/caddyhttp/status/setup.go:
--------------------------------------------------------------------------------
1 | package status
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | // init registers Status plugin
11 | func init() {
12 | caddy.RegisterPlugin("status", caddy.Plugin{
13 | ServerType: "http",
14 | Action: setup,
15 | })
16 | }
17 |
18 | // setup configures new Status middleware instance.
19 | func setup(c *caddy.Controller) error {
20 | rules, err := statusParse(c)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | cfg := httpserver.GetConfig(c)
26 | mid := func(next httpserver.Handler) httpserver.Handler {
27 | return Status{Rules: rules, Next: next}
28 | }
29 | cfg.AddMiddleware(mid)
30 |
31 | return nil
32 | }
33 |
34 | // statusParse parses status directive
35 | func statusParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) {
36 | var rules []httpserver.HandlerConfig
37 |
38 | for c.Next() {
39 | hadBlock := false
40 | args := c.RemainingArgs()
41 |
42 | switch len(args) {
43 | case 1:
44 | status, err := strconv.Atoi(args[0])
45 | if err != nil {
46 | return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0])
47 | }
48 |
49 | for c.NextBlock() {
50 | hadBlock = true
51 | basePath := c.Val()
52 |
53 | for _, cfg := range rules {
54 | rule := cfg.(*Rule)
55 | if rule.Base == basePath {
56 | return rules, c.Errf("Duplicate path: '%s'", basePath)
57 | }
58 | }
59 |
60 | rule := NewRule(basePath, status)
61 | rules = append(rules, rule)
62 |
63 | if c.NextArg() {
64 | return rules, c.ArgErr()
65 | }
66 | }
67 |
68 | if !hadBlock {
69 | return rules, c.ArgErr()
70 | }
71 | case 2:
72 | status, err := strconv.Atoi(args[0])
73 | if err != nil {
74 | return rules, c.Errf("Expecting a numeric status code, got '%s'", args[0])
75 | }
76 |
77 | basePath := args[1]
78 | for _, cfg := range rules {
79 | rule := cfg.(*Rule)
80 | if rule.Base == basePath {
81 | return rules, c.Errf("Duplicate path: '%s'", basePath)
82 | }
83 | }
84 |
85 | rule := NewRule(basePath, status)
86 | rules = append(rules, rule)
87 | default:
88 | return rules, c.ArgErr()
89 | }
90 | }
91 |
92 | return rules, nil
93 | }
94 |
--------------------------------------------------------------------------------
/caddyhttp/status/status_test.go:
--------------------------------------------------------------------------------
1 | package status
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func TestStatus(t *testing.T) {
13 | status := Status{
14 | Rules: []httpserver.HandlerConfig{
15 | NewRule("/foo", http.StatusNotFound),
16 | NewRule("/teapot", http.StatusTeapot),
17 | NewRule("/foo/bar1", http.StatusInternalServerError),
18 | NewRule("/temporary-redirected", http.StatusTemporaryRedirect),
19 | },
20 | Next: httpserver.HandlerFunc(urlPrinter),
21 | }
22 |
23 | tests := []struct {
24 | path string
25 | statusExpected bool
26 | status int
27 | }{
28 | {"/foo", true, http.StatusNotFound},
29 | {"/teapot", true, http.StatusTeapot},
30 | {"/foo/bar", true, http.StatusNotFound},
31 | {"/foo/bar1", true, http.StatusInternalServerError},
32 | {"/someotherpath", false, 0},
33 | {"/temporary-redirected", false, http.StatusTemporaryRedirect},
34 | }
35 |
36 | for i, test := range tests {
37 | req, err := http.NewRequest("GET", test.path, nil)
38 | if err != nil {
39 | t.Fatalf("Test %d: Could not create HTTP request: %v",
40 | i, err)
41 | }
42 |
43 | rec := httptest.NewRecorder()
44 | actualStatus, err := status.ServeHTTP(rec, req)
45 | if err != nil {
46 | t.Fatalf("Test %d: Serving request failed with error %v",
47 | i, err)
48 | }
49 |
50 | if test.statusExpected {
51 | if test.status != actualStatus {
52 | t.Errorf("Test %d: Expected status code %d, got %d",
53 | i, test.status, actualStatus)
54 | }
55 | if rec.Body.String() != "" {
56 | t.Errorf("Test %d: Expected empty body, got '%s'",
57 | i, rec.Body.String())
58 | }
59 | } else {
60 | if test.status != 0 { // Expecting status in response
61 | if test.status != rec.Code {
62 | t.Errorf("Test %d: Expected status code %d, got %d",
63 | i, test.status, rec.Code)
64 | }
65 | } else if rec.Body.String() != test.path {
66 | t.Errorf("Test %d: Expected body '%s', got '%s'",
67 | i, test.path, rec.Body.String())
68 | }
69 | }
70 | }
71 | }
72 |
73 | func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) {
74 | fmt.Fprint(w, r.URL.String())
75 | return 0, nil
76 | }
77 |
--------------------------------------------------------------------------------
/caddyhttp/fastcgi/fcgi_test.php:
--------------------------------------------------------------------------------
1 | $val) {
16 | $md5 = md5($val);
17 |
18 | if ($key != $md5) {
19 | $stat = "FAILED";
20 | echo "server:err ".$md5." != ".$key."\n";
21 | }
22 |
23 | $length += strlen($key) + strlen($val);
24 |
25 | $ret .= $key."(".strlen($key).") ";
26 | }
27 | $ret .= "] [";
28 | foreach ($_FILES as $k0 => $val) {
29 |
30 | $error = $val["error"];
31 | if ($error == UPLOAD_ERR_OK) {
32 | $tmp_name = $val["tmp_name"];
33 | $name = $val["name"];
34 | $datafile = "/tmp/test.go";
35 | move_uploaded_file($tmp_name, $datafile);
36 | $md5 = md5_file($datafile);
37 |
38 | if ($k0 != $md5) {
39 | $stat = "FAILED";
40 | echo "server:err ".$md5." != ".$key."\n";
41 | }
42 |
43 | $length += strlen($k0) + filesize($datafile);
44 |
45 | unlink($datafile);
46 | $ret .= $k0."(".strlen($k0).") ";
47 | }
48 | else{
49 | $stat = "FAILED";
50 | echo "server:file err ".file_upload_error_message($error)."\n";
51 | }
52 | }
53 | $ret .= "]";
54 | echo "server:got data length " .$length."\n";
55 | }
56 |
57 |
58 | echo "-{$stat}-POST(".count($_POST).") FILE(".count($_FILES).")\n";
59 |
60 | function file_upload_error_message($error_code) {
61 | switch ($error_code) {
62 | case UPLOAD_ERR_INI_SIZE:
63 | return 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
64 | case UPLOAD_ERR_FORM_SIZE:
65 | return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
66 | case UPLOAD_ERR_PARTIAL:
67 | return 'The uploaded file was only partially uploaded';
68 | case UPLOAD_ERR_NO_FILE:
69 | return 'No file was uploaded';
70 | case UPLOAD_ERR_NO_TMP_DIR:
71 | return 'Missing a temporary folder';
72 | case UPLOAD_ERR_CANT_WRITE:
73 | return 'Failed to write file to disk';
74 | case UPLOAD_ERR_EXTENSION:
75 | return 'File upload stopped by extension';
76 | default:
77 | return 'Unknown upload error';
78 | }
79 | }
--------------------------------------------------------------------------------
/caddytls/httphandler_test.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | import (
4 | "net"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 | )
9 |
10 | func TestHTTPChallengeHandlerNoOp(t *testing.T) {
11 | namesObtaining.Add([]string{"localhost"})
12 |
13 | // try base paths and host names that aren't
14 | // handled by this handler
15 | for _, url := range []string{
16 | "http://localhost/",
17 | "http://localhost/foo.html",
18 | "http://localhost/.git",
19 | "http://localhost/.well-known/",
20 | "http://localhost/.well-known/acme-challenging",
21 | "http://other/.well-known/acme-challenge/foo",
22 | } {
23 | req, err := http.NewRequest("GET", url, nil)
24 | if err != nil {
25 | t.Fatalf("Could not craft request, got error: %v", err)
26 | }
27 | rw := httptest.NewRecorder()
28 | if HTTPChallengeHandler(rw, req, "", DefaultHTTPAlternatePort) {
29 | t.Errorf("Got true with this URL, but shouldn't have: %s", url)
30 | }
31 | }
32 | }
33 |
34 | func TestHTTPChallengeHandlerSuccess(t *testing.T) {
35 | expectedPath := challengeBasePath + "/asdf"
36 |
37 | // Set up fake acme handler backend to make sure proxying succeeds
38 | var proxySuccess bool
39 | ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40 | proxySuccess = true
41 | if r.URL.Path != expectedPath {
42 | t.Errorf("Expected path '%s' but got '%s' instead", expectedPath, r.URL.Path)
43 | }
44 | }))
45 |
46 | // Custom listener that uses the port we expect
47 | ln, err := net.Listen("tcp", "127.0.0.1:"+DefaultHTTPAlternatePort)
48 | if err != nil {
49 | t.Fatalf("Unable to start test server listener: %v", err)
50 | }
51 | ts.Listener = ln
52 |
53 | // Tell this package that we are handling a challenge for 127.0.0.1
54 | namesObtaining.Add([]string{"127.0.0.1"})
55 |
56 | // Start our engines and run the test
57 | ts.Start()
58 | defer ts.Close()
59 | req, err := http.NewRequest("GET", "http://127.0.0.1:"+DefaultHTTPAlternatePort+expectedPath, nil)
60 | if err != nil {
61 | t.Fatalf("Could not craft request, got error: %v", err)
62 | }
63 | rw := httptest.NewRecorder()
64 |
65 | HTTPChallengeHandler(rw, req, "", DefaultHTTPAlternatePort)
66 |
67 | if !proxySuccess {
68 | t.Fatal("Expected request to be proxied, but it wasn't")
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/caddyhttp/extensions/setup_test.go:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `ext .html .htm .php`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Fatalf("Expected no errors, got: %v", err)
15 | }
16 |
17 | mids := httpserver.GetConfig(c).Middleware()
18 | if len(mids) == 0 {
19 | t.Fatal("Expected middleware, had 0 instead")
20 | }
21 |
22 | handler := mids[0](httpserver.EmptyNext)
23 | myHandler, ok := handler.(Ext)
24 |
25 | if !ok {
26 | t.Fatalf("Expected handler to be type Ext, got: %#v", handler)
27 | }
28 |
29 | if myHandler.Extensions[0] != ".html" {
30 | t.Errorf("Expected .html in the list of Extensions")
31 | }
32 | if myHandler.Extensions[1] != ".htm" {
33 | t.Errorf("Expected .htm in the list of Extensions")
34 | }
35 | if myHandler.Extensions[2] != ".php" {
36 | t.Errorf("Expected .php in the list of Extensions")
37 | }
38 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
39 | t.Error("'Next' field of handler was not set properly")
40 | }
41 |
42 | }
43 |
44 | func TestExtParse(t *testing.T) {
45 | tests := []struct {
46 | inputExts string
47 | shouldErr bool
48 | expectedExts []string
49 | }{
50 | {`ext .html .htm .php`, false, []string{".html", ".htm", ".php"}},
51 | {`ext .php .html .xml`, false, []string{".php", ".html", ".xml"}},
52 | {`ext .txt .php .xml`, false, []string{".txt", ".php", ".xml"}},
53 | }
54 | for i, test := range tests {
55 | actualExts, err := extParse(caddy.NewTestController("http", test.inputExts))
56 |
57 | if err == nil && test.shouldErr {
58 | t.Errorf("Test %d didn't error, but it should have", i)
59 | } else if err != nil && !test.shouldErr {
60 | t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
61 | }
62 |
63 | if len(actualExts) != len(test.expectedExts) {
64 | t.Fatalf("Test %d expected %d rules, but got %d",
65 | i, len(test.expectedExts), len(actualExts))
66 | }
67 | for j, actualExt := range actualExts {
68 | if actualExt != test.expectedExts[j] {
69 | t.Fatalf("Test %d expected %dth extension to be %s , but got %s",
70 | i, j, test.expectedExts[j], actualExt)
71 | }
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/caddyhttp/rewrite/to.go:
--------------------------------------------------------------------------------
1 | package rewrite
2 |
3 | import (
4 | "context"
5 | "log"
6 | "net/http"
7 | "net/url"
8 | "path"
9 | "strings"
10 |
11 | "github.com/mholt/caddy/caddyhttp/httpserver"
12 | )
13 |
14 | // To attempts rewrite. It attempts to rewrite to first valid path
15 | // or the last path if none of the paths are valid.
16 | func To(fs http.FileSystem, r *http.Request, to string, replacer httpserver.Replacer) Result {
17 | tos := strings.Fields(to)
18 |
19 | // try each rewrite paths
20 | t := ""
21 | query := ""
22 | for _, v := range tos {
23 | t = replacer.Replace(v)
24 | tparts := strings.SplitN(t, "?", 2)
25 | t = path.Clean(tparts[0])
26 |
27 | if len(tparts) > 1 {
28 | query = tparts[1]
29 | }
30 |
31 | // add trailing slash for directories, if present
32 | if strings.HasSuffix(tparts[0], "/") && !strings.HasSuffix(t, "/") {
33 | t += "/"
34 | }
35 |
36 | // validate file
37 | if validFile(fs, t) {
38 | break
39 | }
40 | }
41 |
42 | // validate resulting path
43 | u, err := url.Parse(t)
44 | if err != nil {
45 | // Let the user know we got here. Rewrite is expected but
46 | // the resulting url is invalid.
47 | log.Printf("[ERROR] rewrite: resulting path '%v' is invalid. error: %v", t, err)
48 | return RewriteIgnored
49 | }
50 |
51 | // take note of this rewrite for internal use by fastcgi
52 | // all we need is the URI, not full URL
53 | *r = *r.WithContext(context.WithValue(r.Context(), httpserver.URIxRewriteCtxKey, r.URL.RequestURI()))
54 |
55 | // perform rewrite
56 | r.URL.Path = u.Path
57 | if query != "" {
58 | // overwrite query string if present
59 | r.URL.RawQuery = query
60 | }
61 | if u.Fragment != "" {
62 | // overwrite fragment if present
63 | r.URL.Fragment = u.Fragment
64 | }
65 |
66 | return RewriteDone
67 | }
68 |
69 | // validFile checks if file exists on the filesystem.
70 | // if file ends with `/`, it is validated as a directory.
71 | func validFile(fs http.FileSystem, file string) bool {
72 | if fs == nil {
73 | return false
74 | }
75 |
76 | f, err := fs.Open(file)
77 | if err != nil {
78 | return false
79 | }
80 | defer f.Close()
81 |
82 | stat, err := f.Stat()
83 | if err != nil {
84 | return false
85 | }
86 |
87 | // directory
88 | if strings.HasSuffix(file, "/") {
89 | return stat.IsDir()
90 | }
91 |
92 | // file
93 | return !stat.IsDir()
94 | }
95 |
--------------------------------------------------------------------------------
/caddyhttp/websocket/setup.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | func init() {
9 | caddy.RegisterPlugin("websocket", caddy.Plugin{
10 | ServerType: "http",
11 | Action: setup,
12 | })
13 | }
14 |
15 | // setup configures a new WebSocket middleware instance.
16 | func setup(c *caddy.Controller) error {
17 | websocks, err := webSocketParse(c)
18 | if err != nil {
19 | return err
20 | }
21 |
22 | GatewayInterface = caddy.AppName + "-CGI/1.1"
23 | ServerSoftware = caddy.AppName + "/" + caddy.AppVersion
24 |
25 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
26 | return WebSocket{Next: next, Sockets: websocks}
27 | })
28 |
29 | return nil
30 | }
31 |
32 | func webSocketParse(c *caddy.Controller) ([]Config, error) {
33 | var websocks []Config
34 | var respawn bool
35 |
36 | optionalBlock := func() (hadBlock bool, err error) {
37 | for c.NextBlock() {
38 | hadBlock = true
39 | if c.Val() == "respawn" {
40 | respawn = true
41 | } else {
42 | return true, c.Err("Expected websocket configuration parameter in block")
43 | }
44 | }
45 | return
46 | }
47 |
48 | for c.Next() {
49 | var val, path, command string
50 |
51 | // Path or command; not sure which yet
52 | if !c.NextArg() {
53 | return nil, c.ArgErr()
54 | }
55 | val = c.Val()
56 |
57 | // Extra configuration may be in a block
58 | hadBlock, err := optionalBlock()
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | if !hadBlock {
64 | // The next argument on this line will be the command or an open curly brace
65 | if c.NextArg() {
66 | path = val
67 | command = c.Val()
68 | } else {
69 | path = "/"
70 | command = val
71 | }
72 |
73 | // Okay, check again for optional block
74 | _, err = optionalBlock()
75 | if err != nil {
76 | return nil, err
77 | }
78 | }
79 |
80 | // Split command into the actual command and its arguments
81 | cmd, args, err := caddy.SplitCommandAndArgs(command)
82 | if err != nil {
83 | return nil, err
84 | }
85 |
86 | websocks = append(websocks, Config{
87 | Path: path,
88 | Command: cmd,
89 | Arguments: args,
90 | Respawn: respawn, // TODO: This isn't used currently
91 | })
92 | }
93 |
94 | return websocks, nil
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/caddyhttp/templates/setup.go:
--------------------------------------------------------------------------------
1 | package templates
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func init() {
11 | caddy.RegisterPlugin("templates", caddy.Plugin{
12 | ServerType: "http",
13 | Action: setup,
14 | })
15 | }
16 |
17 | // setup configures a new Templates middleware instance.
18 | func setup(c *caddy.Controller) error {
19 | rules, err := templatesParse(c)
20 | if err != nil {
21 | return err
22 | }
23 |
24 | cfg := httpserver.GetConfig(c)
25 |
26 | tmpls := Templates{
27 | Rules: rules,
28 | Root: cfg.Root,
29 | FileSys: http.Dir(cfg.Root),
30 | }
31 |
32 | cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
33 | tmpls.Next = next
34 | return tmpls
35 | })
36 |
37 | return nil
38 | }
39 |
40 | func templatesParse(c *caddy.Controller) ([]Rule, error) {
41 | var rules []Rule
42 |
43 | for c.Next() {
44 | var rule Rule
45 |
46 | rule.Path = defaultTemplatePath
47 | rule.Extensions = defaultTemplateExtensions
48 |
49 | args := c.RemainingArgs()
50 |
51 | switch len(args) {
52 | case 0:
53 | // Optional block
54 | for c.NextBlock() {
55 | switch c.Val() {
56 | case "path":
57 | args := c.RemainingArgs()
58 | if len(args) != 1 {
59 | return nil, c.ArgErr()
60 | }
61 | rule.Path = args[0]
62 |
63 | case "ext":
64 | args := c.RemainingArgs()
65 | if len(args) == 0 {
66 | return nil, c.ArgErr()
67 | }
68 | rule.Extensions = args
69 |
70 | case "between":
71 | args := c.RemainingArgs()
72 | if len(args) != 2 {
73 | return nil, c.ArgErr()
74 | }
75 | rule.Delims[0] = args[0]
76 | rule.Delims[1] = args[1]
77 | }
78 | }
79 | default:
80 | // First argument would be the path
81 | rule.Path = args[0]
82 |
83 | // Any remaining arguments are extensions
84 | rule.Extensions = args[1:]
85 | if len(rule.Extensions) == 0 {
86 | rule.Extensions = defaultTemplateExtensions
87 | }
88 | }
89 |
90 | for _, ext := range rule.Extensions {
91 | rule.IndexFiles = append(rule.IndexFiles, "index"+ext)
92 | }
93 |
94 | rules = append(rules, rule)
95 | }
96 | return rules, nil
97 | }
98 |
99 | const defaultTemplatePath = "/"
100 |
101 | var defaultTemplateExtensions = []string{".html", ".htm", ".tmpl", ".tpl", ".txt"}
102 |
--------------------------------------------------------------------------------
/dist/init/linux-sysvinit/caddy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | ### BEGIN INIT INFO
3 | # Provides: caddy
4 | # Required-Start: $local_fs $network $named $time $syslog
5 | # Required-Stop: $local_fs $network $named $time $syslog
6 | # Default-Start: 2 3 4 5
7 | # Default-Stop: 0 1 6
8 | # Short-Description: starts the caddy web server
9 | # Description: starts caddy using start-stop-daemon
10 | ### END INIT INFO
11 |
12 | # Original Author: Frédéric Galusik (fredg)
13 | # Maintainer: Daniel van Dorp (djvdorp)
14 |
15 | DESC="the caddy web server"
16 | NAME=caddy
17 | DAEMON=$(which caddy)
18 |
19 | DAEMONUSER=www-data
20 | PIDFILE=/var/run/$NAME.pid
21 | LOGFILE=/var/log/$NAME.log
22 | CONFIGFILE=/etc/caddy/Caddyfile
23 | DAEMONOPTS="-agree=true -pidfile=$PIDFILE -log=$LOGFILE -conf=$CONFIGFILE"
24 |
25 | USERBIND="setcap cap_net_bind_service=+ep"
26 | STOP_SCHEDULE="${STOP_SCHEDULE:-QUIT/5/TERM/5/KILL/5}"
27 |
28 | test -x $DAEMON || exit 0
29 |
30 | # Set the CADDYPATH; Let's Encrypt certificates will be written to this directory.
31 | export CADDYPATH=/etc/ssl/caddy
32 |
33 | # Set the ulimits
34 | ulimit -n 8192
35 |
36 |
37 | start() {
38 | $USERBIND $DAEMON
39 | start-stop-daemon --start --quiet --make-pidfile --pidfile $PIDFILE \
40 | --background --chuid $DAEMONUSER --oknodo --exec $DAEMON -- $DAEMONOPTS
41 | }
42 |
43 | stop() {
44 | start-stop-daemon --stop --quiet --pidfile $PIDFILE --retry=$STOP_SCHEDULE \
45 | --name $NAME --oknodo
46 | rm -f $PIDFILE
47 | }
48 |
49 | reload() {
50 | start-stop-daemon --stop --quiet --signal USR1 --pidfile $PIDFILE \
51 | --name $NAME
52 | }
53 |
54 | status() {
55 | if [ -f $PIDFILE ]; then
56 | if kill -0 $(cat "$PIDFILE"); then
57 | echo "$NAME is running"
58 | else
59 | echo "$NAME process is dead, but pidfile exists"
60 | fi
61 | else
62 | echo "$NAME is not running"
63 | fi
64 | }
65 |
66 | case "$1" in
67 | start)
68 | echo "Starting $NAME"
69 | start
70 | ;;
71 | stop)
72 | echo "Stopping $NAME"
73 | stop
74 | ;;
75 | restart)
76 | echo "Restarting $NAME"
77 | stop
78 | start
79 | ;;
80 | reload)
81 | echo "Reloading $NAME configuration"
82 | reload
83 | ;;
84 | status)
85 | status
86 | ;;
87 | *)
88 | echo "Usage: $0 {start|stop|restart|reload|status}"
89 | exit 2
90 | ;;
91 | esac
92 |
93 | exit 0
94 |
--------------------------------------------------------------------------------
/caddyhttp/fastcgi/dialer.go:
--------------------------------------------------------------------------------
1 | package fastcgi
2 |
3 | import (
4 | "errors"
5 | "sync"
6 | "sync/atomic"
7 | "time"
8 | )
9 |
10 | type dialer interface {
11 | Dial() (Client, error)
12 | Close(Client) error
13 | }
14 |
15 | // basicDialer is a basic dialer that wraps default fcgi functions.
16 | type basicDialer struct {
17 | network string
18 | address string
19 | timeout time.Duration
20 | }
21 |
22 | func (b basicDialer) Dial() (Client, error) {
23 | return DialTimeout(b.network, b.address, b.timeout)
24 | }
25 |
26 | func (b basicDialer) Close(c Client) error { return c.Close() }
27 |
28 | // persistentDialer keeps a pool of fcgi connections.
29 | // connections are not closed after use, rather added back to the pool for reuse.
30 | type persistentDialer struct {
31 | size int
32 | network string
33 | address string
34 | timeout time.Duration
35 | pool []Client
36 | sync.Mutex
37 | }
38 |
39 | func (p *persistentDialer) Dial() (Client, error) {
40 | p.Lock()
41 | // connection is available, return first one.
42 | if len(p.pool) > 0 {
43 | client := p.pool[0]
44 | p.pool = p.pool[1:]
45 | p.Unlock()
46 |
47 | return client, nil
48 | }
49 |
50 | p.Unlock()
51 |
52 | // no connection available, create new one
53 | return DialTimeout(p.network, p.address, p.timeout)
54 | }
55 |
56 | func (p *persistentDialer) Close(client Client) error {
57 | p.Lock()
58 | if len(p.pool) < p.size {
59 | // pool is not full yet, add connection for reuse
60 | p.pool = append(p.pool, client)
61 | p.Unlock()
62 |
63 | return nil
64 | }
65 |
66 | p.Unlock()
67 |
68 | // otherwise, close the connection.
69 | return client.Close()
70 | }
71 |
72 | type loadBalancingDialer struct {
73 | current int64
74 | dialers []dialer
75 | }
76 |
77 | func (m *loadBalancingDialer) Dial() (Client, error) {
78 | nextDialerIndex := atomic.AddInt64(&m.current, 1) % int64(len(m.dialers))
79 | currentDialer := m.dialers[nextDialerIndex]
80 |
81 | client, err := currentDialer.Dial()
82 |
83 | if err != nil {
84 | return nil, err
85 | }
86 |
87 | return &dialerAwareClient{Client: client, dialer: currentDialer}, nil
88 | }
89 |
90 | func (m *loadBalancingDialer) Close(c Client) error {
91 | // Close the client according to dialer behaviour
92 | if da, ok := c.(*dialerAwareClient); ok {
93 | return da.dialer.Close(c)
94 | }
95 |
96 | return errors.New("Cannot close client")
97 | }
98 |
99 | type dialerAwareClient struct {
100 | Client
101 | dialer dialer
102 | }
103 |
--------------------------------------------------------------------------------
/caddy_test.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | import (
4 | "net"
5 | "strconv"
6 | "testing"
7 | )
8 |
9 | /*
10 | // TODO
11 | func TestCaddyStartStop(t *testing.T) {
12 | caddyfile := "localhost:1984"
13 |
14 | for i := 0; i < 2; i++ {
15 | _, err := Start(CaddyfileInput{Contents: []byte(caddyfile)})
16 | if err != nil {
17 | t.Fatalf("Error starting, iteration %d: %v", i, err)
18 | }
19 |
20 | client := http.Client{
21 | Timeout: time.Duration(2 * time.Second),
22 | }
23 | resp, err := client.Get("http://localhost:1984")
24 | if err != nil {
25 | t.Fatalf("Expected GET request to succeed (iteration %d), but it failed: %v", i, err)
26 | }
27 | resp.Body.Close()
28 |
29 | err = Stop()
30 | if err != nil {
31 | t.Fatalf("Error stopping, iteration %d: %v", i, err)
32 | }
33 | }
34 | }
35 | */
36 |
37 | func TestIsLoopback(t *testing.T) {
38 | for i, test := range []struct {
39 | input string
40 | expect bool
41 | }{
42 | {"example.com", false},
43 | {"localhost", true},
44 | {"localhost:1234", true},
45 | {"localhost:", true},
46 | {"127.0.0.1", true},
47 | {"127.0.0.1:443", true},
48 | {"127.0.1.5", true},
49 | {"10.0.0.5", false},
50 | {"12.7.0.1", false},
51 | {"[::1]", true},
52 | {"[::1]:1234", true},
53 | {"::1", true},
54 | {"::", false},
55 | {"[::]", false},
56 | {"local", false},
57 | } {
58 | if got, want := IsLoopback(test.input), test.expect; got != want {
59 | t.Errorf("Test %d (%s): expected %v but was %v", i, test.input, want, got)
60 | }
61 | }
62 | }
63 |
64 | func TestListenerAddrEqual(t *testing.T) {
65 | ln1, err := net.Listen("tcp", "[::]:0")
66 | if err != nil {
67 | t.Fatal(err)
68 | }
69 | defer ln1.Close()
70 | ln1port := strconv.Itoa(ln1.Addr().(*net.TCPAddr).Port)
71 |
72 | ln2, err := net.Listen("tcp", "127.0.0.1:0")
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 | defer ln2.Close()
77 | ln2port := strconv.Itoa(ln2.Addr().(*net.TCPAddr).Port)
78 |
79 | for i, test := range []struct {
80 | ln net.Listener
81 | addr string
82 | expect bool
83 | }{
84 | {ln1, ":1234", false},
85 | {ln1, "0.0.0.0:1234", false},
86 | {ln1, "0.0.0.0", false},
87 | {ln1, ":" + ln1port, true},
88 | {ln1, "0.0.0.0:" + ln1port, true},
89 | {ln2, ":" + ln2port, false},
90 | {ln2, "127.0.0.1:1234", false},
91 | {ln2, "127.0.0.1", false},
92 | {ln2, "127.0.0.1:" + ln2port, true},
93 | } {
94 | if got, want := listenerAddrEqual(test.ln, test.addr), test.expect; got != want {
95 | t.Errorf("Test %d (%s == %s): expected %v but was %v", i, test.addr, test.ln.Addr().String(), want, got)
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/sigtrap_posix.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package caddy
4 |
5 | import (
6 | "log"
7 | "os"
8 | "os/signal"
9 | "syscall"
10 | )
11 |
12 | // trapSignalsPosix captures POSIX-only signals.
13 | func trapSignalsPosix() {
14 | go func() {
15 | sigchan := make(chan os.Signal, 1)
16 | signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGUSR1)
17 |
18 | for sig := range sigchan {
19 | switch sig {
20 | case syscall.SIGTERM:
21 | log.Println("[INFO] SIGTERM: Terminating process")
22 | if PidFile != "" {
23 | os.Remove(PidFile)
24 | }
25 | os.Exit(0)
26 |
27 | case syscall.SIGQUIT:
28 | log.Println("[INFO] SIGQUIT: Shutting down")
29 | exitCode := executeShutdownCallbacks("SIGQUIT")
30 | err := Stop()
31 | if err != nil {
32 | log.Printf("[ERROR] SIGQUIT stop: %v", err)
33 | exitCode = 1
34 | }
35 | if PidFile != "" {
36 | os.Remove(PidFile)
37 | }
38 | os.Exit(exitCode)
39 |
40 | case syscall.SIGHUP:
41 | log.Println("[INFO] SIGHUP: Hanging up")
42 | err := Stop()
43 | if err != nil {
44 | log.Printf("[ERROR] SIGHUP stop: %v", err)
45 | }
46 |
47 | case syscall.SIGUSR1:
48 | log.Println("[INFO] SIGUSR1: Reloading")
49 |
50 | // Start with the existing Caddyfile
51 | instancesMu.Lock()
52 | if len(instances) == 0 {
53 | instancesMu.Unlock()
54 | log.Println("[ERROR] SIGUSR1: No server instances are fully running")
55 | continue
56 | }
57 | inst := instances[0] // we only support one instance at this time
58 | instancesMu.Unlock()
59 |
60 | updatedCaddyfile := inst.caddyfileInput
61 | if updatedCaddyfile == nil {
62 | // Hmm, did spawing process forget to close stdin? Anyhow, this is unusual.
63 | log.Println("[ERROR] SIGUSR1: no Caddyfile to reload (was stdin left open?)")
64 | continue
65 | }
66 | if loaderUsed.loader == nil {
67 | // This also should never happen
68 | log.Println("[ERROR] SIGUSR1: no Caddyfile loader with which to reload Caddyfile")
69 | continue
70 | }
71 |
72 | // Load the updated Caddyfile
73 | newCaddyfile, err := loaderUsed.loader.Load(inst.serverType)
74 | if err != nil {
75 | log.Printf("[ERROR] SIGUSR1: loading updated Caddyfile: %v", err)
76 | continue
77 | }
78 | if newCaddyfile != nil {
79 | updatedCaddyfile = newCaddyfile
80 | }
81 |
82 | // Kick off the restart; our work is done
83 | inst, err = inst.Restart(updatedCaddyfile)
84 | if err != nil {
85 | log.Printf("[ERROR] SIGUSR1: %v", err)
86 | }
87 | }
88 | }
89 | }()
90 | }
91 |
--------------------------------------------------------------------------------
/caddyhttp/rewrite/setup.go:
--------------------------------------------------------------------------------
1 | package rewrite
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/mholt/caddy"
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | )
10 |
11 | func init() {
12 | caddy.RegisterPlugin("rewrite", caddy.Plugin{
13 | ServerType: "http",
14 | Action: setup,
15 | })
16 | }
17 |
18 | // setup configures a new Rewrite middleware instance.
19 | func setup(c *caddy.Controller) error {
20 | rewrites, err := rewriteParse(c)
21 | if err != nil {
22 | return err
23 | }
24 |
25 | cfg := httpserver.GetConfig(c)
26 |
27 | cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
28 | return Rewrite{
29 | Next: next,
30 | FileSys: http.Dir(cfg.Root),
31 | Rules: rewrites,
32 | }
33 | })
34 |
35 | return nil
36 | }
37 |
38 | func rewriteParse(c *caddy.Controller) ([]httpserver.HandlerConfig, error) {
39 | var rules []httpserver.HandlerConfig
40 |
41 | for c.Next() {
42 | var rule Rule
43 | var err error
44 | var base = "/"
45 | var pattern, to string
46 | var ext []string
47 |
48 | args := c.RemainingArgs()
49 |
50 | var matcher httpserver.RequestMatcher
51 |
52 | switch len(args) {
53 | case 1:
54 | base = args[0]
55 | fallthrough
56 | case 0:
57 | // Integrate request matcher for 'if' conditions.
58 | matcher, err = httpserver.SetupIfMatcher(c)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | for c.NextBlock() {
64 | if httpserver.IfMatcherKeyword(c) {
65 | continue
66 | }
67 | switch c.Val() {
68 | case "r", "regexp":
69 | if !c.NextArg() {
70 | return nil, c.ArgErr()
71 | }
72 | pattern = c.Val()
73 | case "to":
74 | args1 := c.RemainingArgs()
75 | if len(args1) == 0 {
76 | return nil, c.ArgErr()
77 | }
78 | to = strings.Join(args1, " ")
79 | case "ext":
80 | args1 := c.RemainingArgs()
81 | if len(args1) == 0 {
82 | return nil, c.ArgErr()
83 | }
84 | ext = args1
85 | default:
86 | return nil, c.ArgErr()
87 | }
88 | }
89 | // ensure to is specified
90 | if to == "" {
91 | return nil, c.ArgErr()
92 | }
93 | if rule, err = NewComplexRule(base, pattern, to, ext, matcher); err != nil {
94 | return nil, err
95 | }
96 | rules = append(rules, rule)
97 |
98 | // the only unhandled case is 2 and above
99 | default:
100 | rule = NewSimpleRule(args[0], strings.Join(args[1:], " "))
101 | rules = append(rules, rule)
102 | }
103 |
104 | }
105 |
106 | return rules, nil
107 | }
108 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/template.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "bytes"
5 | "io/ioutil"
6 | "text/template"
7 |
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | "github.com/mholt/caddy/caddyhttp/markdown/metadata"
10 | )
11 |
12 | // Data represents a markdown document.
13 | type Data struct {
14 | httpserver.Context
15 | Doc map[string]interface{}
16 | Styles []string
17 | Scripts []string
18 | Meta map[string]string
19 | Files []FileInfo
20 | }
21 |
22 | // Include "overrides" the embedded httpserver.Context's Include()
23 | // method so that included files have access to d's fields.
24 | // Note: using {{template 'template-name' .}} instead might be better.
25 | func (d Data) Include(filename string) (string, error) {
26 | return httpserver.ContextInclude(filename, d, d.Root)
27 | }
28 |
29 | // execTemplate executes a template given a requestPath, template, and metadata
30 | func execTemplate(c *Config, mdata metadata.Metadata, meta map[string]string, files []FileInfo, ctx httpserver.Context) ([]byte, error) {
31 | mdData := Data{
32 | Context: ctx,
33 | Doc: mdata.Variables,
34 | Styles: c.Styles,
35 | Scripts: c.Scripts,
36 | Meta: meta,
37 | Files: files,
38 | }
39 |
40 | b := new(bytes.Buffer)
41 | if err := c.Template.ExecuteTemplate(b, mdata.Template, mdData); err != nil {
42 | return nil, err
43 | }
44 |
45 | return b.Bytes(), nil
46 | }
47 |
48 | // SetTemplate reads in the template with the filename provided. If the file does not exist or is not parsable, it will return an error.
49 | func SetTemplate(t *template.Template, name, filename string) error {
50 |
51 | // Read template
52 | buf, err := ioutil.ReadFile(filename)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | // Update if exists
58 | if tt := t.Lookup(name); tt != nil {
59 | _, err = tt.Parse(string(buf))
60 | return err
61 | }
62 |
63 | // Allocate new name if not
64 | _, err = t.New(name).Parse(string(buf))
65 | return err
66 | }
67 |
68 | // GetDefaultTemplate returns the default template.
69 | func GetDefaultTemplate() *template.Template {
70 | return template.Must(template.New("").Parse(defaultTemplate))
71 | }
72 |
73 | const (
74 | defaultTemplate = `
75 |
76 |
77 | {{.Doc.title}}
78 |
79 | {{range $key, $val := .Meta}}
80 |
81 | {{end}}
82 | {{- range .Styles}}
83 |
84 | {{- end}}
85 | {{- range .Scripts}}
86 |
87 | {{- end}}
88 |
89 |
90 | {{.Doc.body}}
91 |
92 | `
93 | )
94 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/process.go:
--------------------------------------------------------------------------------
1 | package markdown
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | "os"
7 |
8 | "github.com/mholt/caddy/caddyhttp/httpserver"
9 | "github.com/mholt/caddy/caddyhttp/markdown/metadata"
10 | "github.com/mholt/caddy/caddyhttp/markdown/summary"
11 | "github.com/russross/blackfriday"
12 | )
13 |
14 | // FileInfo represents a file in a particular server context. It wraps the os.FileInfo struct.
15 | type FileInfo struct {
16 | os.FileInfo
17 | ctx httpserver.Context
18 | }
19 |
20 | var recognizedMetaTags = []string{
21 | "author",
22 | "copyright",
23 | "description",
24 | "subject",
25 | }
26 |
27 | // Summarize returns an abbreviated string representation of the markdown stored in this file.
28 | // wordcount is the number of words returned in the summary.
29 | func (f FileInfo) Summarize(wordcount int) (string, error) {
30 | fp, err := f.ctx.Root.Open(f.Name())
31 | if err != nil {
32 | return "", err
33 | }
34 | defer fp.Close()
35 |
36 | buf, err := ioutil.ReadAll(fp)
37 | if err != nil {
38 | return "", err
39 | }
40 |
41 | return string(summary.Markdown(buf, wordcount)), nil
42 | }
43 |
44 | // Markdown processes the contents of a page in b. It parses the metadata
45 | // (if any) and uses the template (if found).
46 | func (c *Config) Markdown(title string, r io.Reader, dirents []os.FileInfo, ctx httpserver.Context) ([]byte, error) {
47 | body, err := ioutil.ReadAll(r)
48 | if err != nil {
49 | return nil, err
50 | }
51 |
52 | parser := metadata.GetParser(body)
53 | markdown := parser.Markdown()
54 | mdata := parser.Metadata()
55 |
56 | // process markdown
57 | extns := 0
58 | extns |= blackfriday.EXTENSION_TABLES
59 | extns |= blackfriday.EXTENSION_FENCED_CODE
60 | extns |= blackfriday.EXTENSION_STRIKETHROUGH
61 | extns |= blackfriday.EXTENSION_DEFINITION_LISTS
62 | html := blackfriday.Markdown(markdown, c.Renderer, extns)
63 |
64 | // set it as body for template
65 | mdata.Variables["body"] = string(html)
66 |
67 | // fixup title
68 | mdata.Variables["title"] = mdata.Title
69 | if mdata.Variables["title"] == "" {
70 | mdata.Variables["title"] = title
71 | }
72 |
73 | // move available and valid front matters to the meta values
74 | meta := make(map[string]string)
75 | for _, val := range recognizedMetaTags {
76 | if mVal, ok := mdata.Variables[val]; ok {
77 | meta[val] = mVal.(string)
78 | }
79 | }
80 |
81 | // massage possible files
82 | files := []FileInfo{}
83 | for _, ent := range dirents {
84 | file := FileInfo{
85 | FileInfo: ent,
86 | ctx: ctx,
87 | }
88 | files = append(files, file)
89 | }
90 |
91 | return execTemplate(c, mdata, meta, files, ctx)
92 | }
93 |
--------------------------------------------------------------------------------
/caddytls/handshake_test.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "testing"
7 | )
8 |
9 | func TestGetCertificate(t *testing.T) {
10 | defer func() { certCache = make(map[string]Certificate) }()
11 |
12 | cfg := new(Config)
13 |
14 | hello := &tls.ClientHelloInfo{ServerName: "example.com"}
15 | helloSub := &tls.ClientHelloInfo{ServerName: "sub.example.com"}
16 | helloNoSNI := &tls.ClientHelloInfo{}
17 | helloNoMatch := &tls.ClientHelloInfo{ServerName: "nomatch"}
18 |
19 | // When cache is empty
20 | if cert, err := cfg.GetCertificate(hello); err == nil {
21 | t.Errorf("GetCertificate should return error when cache is empty, got: %v", cert)
22 | }
23 | if cert, err := cfg.GetCertificate(helloNoSNI); err == nil {
24 | t.Errorf("GetCertificate should return error when cache is empty even if server name is blank, got: %v", cert)
25 | }
26 |
27 | // When cache has one certificate in it (also is default)
28 | defaultCert := Certificate{Names: []string{"example.com", ""}, Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"example.com"}}}}
29 | certCache[""] = defaultCert
30 | certCache["example.com"] = defaultCert
31 | if cert, err := cfg.GetCertificate(hello); err != nil {
32 | t.Errorf("Got an error but shouldn't have, when cert exists in cache: %v", err)
33 | } else if cert.Leaf.DNSNames[0] != "example.com" {
34 | t.Errorf("Got wrong certificate with exact match; expected 'example.com', got: %v", cert)
35 | }
36 | if cert, err := cfg.GetCertificate(helloNoSNI); err != nil {
37 | t.Errorf("Got an error with no SNI but shouldn't have, when cert exists in cache: %v", err)
38 | } else if cert.Leaf.DNSNames[0] != "example.com" {
39 | t.Errorf("Got wrong certificate for no SNI; expected 'example.com' as default, got: %v", cert)
40 | }
41 |
42 | // When retrieving wildcard certificate
43 | certCache["*.example.com"] = Certificate{Names: []string{"*.example.com"}, Certificate: tls.Certificate{Leaf: &x509.Certificate{DNSNames: []string{"*.example.com"}}}}
44 | if cert, err := cfg.GetCertificate(helloSub); err != nil {
45 | t.Errorf("Didn't get wildcard cert, got: cert=%v, err=%v ", cert, err)
46 | } else if cert.Leaf.DNSNames[0] != "*.example.com" {
47 | t.Errorf("Got wrong certificate, expected wildcard: %v", cert)
48 | }
49 |
50 | // When no certificate matches, the default is returned
51 | if cert, err := cfg.GetCertificate(helloNoMatch); err != nil {
52 | t.Errorf("Expected default certificate with no error when no matches, got err: %v", err)
53 | } else if cert.Leaf.DNSNames[0] != "example.com" {
54 | t.Errorf("Expected default cert with no matches, got: %v", cert)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/caddyhttp/basicauth/setup.go:
--------------------------------------------------------------------------------
1 | package basicauth
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func init() {
11 | caddy.RegisterPlugin("basicauth", caddy.Plugin{
12 | ServerType: "http",
13 | Action: setup,
14 | })
15 | }
16 |
17 | // setup configures a new BasicAuth middleware instance.
18 | func setup(c *caddy.Controller) error {
19 | cfg := httpserver.GetConfig(c)
20 | root := cfg.Root
21 |
22 | rules, err := basicAuthParse(c)
23 | if err != nil {
24 | return err
25 | }
26 |
27 | basic := BasicAuth{Rules: rules}
28 |
29 | cfg.AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
30 | basic.Next = next
31 | basic.SiteRoot = root
32 | return basic
33 | })
34 |
35 | return nil
36 | }
37 |
38 | func basicAuthParse(c *caddy.Controller) ([]Rule, error) {
39 | var rules []Rule
40 | cfg := httpserver.GetConfig(c)
41 |
42 | var err error
43 | for c.Next() {
44 | var rule Rule
45 |
46 | args := c.RemainingArgs()
47 |
48 | switch len(args) {
49 | case 2:
50 | rule.Username = args[0]
51 | if rule.Password, err = passwordMatcher(rule.Username, args[1], cfg.Root); err != nil {
52 | return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
53 | }
54 | case 3:
55 | rule.Resources = append(rule.Resources, args[0])
56 | rule.Username = args[1]
57 | if rule.Password, err = passwordMatcher(rule.Username, args[2], cfg.Root); err != nil {
58 | return rules, c.Errf("Get password matcher from %s: %v", c.Val(), err)
59 | }
60 | default:
61 | return rules, c.ArgErr()
62 | }
63 |
64 | // If nested block is present, process it here
65 | for c.NextBlock() {
66 | val := c.Val()
67 | args = c.RemainingArgs()
68 | switch len(args) {
69 | case 0:
70 | // Assume single argument is path resource
71 | rule.Resources = append(rule.Resources, val)
72 | case 1:
73 | if val == "realm" {
74 | if rule.Realm == "" {
75 | rule.Realm = strings.Replace(args[0], `"`, `\"`, -1)
76 | } else {
77 | return rules, c.Errf("\"realm\" subdirective can only be specified once")
78 | }
79 | } else {
80 | return rules, c.Errf("expecting \"realm\", got \"%s\"", val)
81 | }
82 | default:
83 | return rules, c.ArgErr()
84 | }
85 | }
86 |
87 | rules = append(rules, rule)
88 | }
89 |
90 | return rules, nil
91 | }
92 |
93 | func passwordMatcher(username, passw, siteRoot string) (PasswordMatcher, error) {
94 | if !strings.HasPrefix(passw, "htpasswd=") {
95 | return PlainMatcher(passw), nil
96 | }
97 | return GetHtpasswdMatcher(passw[9:], username, siteRoot)
98 | }
99 |
--------------------------------------------------------------------------------
/sigtrap.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | import (
4 | "log"
5 | "os"
6 | "os/signal"
7 | "sync"
8 | )
9 |
10 | // TrapSignals create signal handlers for all applicable signals for this
11 | // system. If your Go program uses signals, this is a rather invasive
12 | // function; best to implement them yourself in that case. Signals are not
13 | // required for the caddy package to function properly, but this is a
14 | // convenient way to allow the user to control this part of your program.
15 | func TrapSignals() {
16 | trapSignalsCrossPlatform()
17 | trapSignalsPosix()
18 | }
19 |
20 | // trapSignalsCrossPlatform captures SIGINT, which triggers forceful
21 | // shutdown that executes shutdown callbacks first. A second interrupt
22 | // signal will exit the process immediately.
23 | func trapSignalsCrossPlatform() {
24 | go func() {
25 | shutdown := make(chan os.Signal, 1)
26 | signal.Notify(shutdown, os.Interrupt)
27 |
28 | for i := 0; true; i++ {
29 | <-shutdown
30 |
31 | if i > 0 {
32 | log.Println("[INFO] SIGINT: Force quit")
33 | if PidFile != "" {
34 | os.Remove(PidFile)
35 | }
36 | os.Exit(1)
37 | }
38 |
39 | log.Println("[INFO] SIGINT: Shutting down")
40 |
41 | if PidFile != "" {
42 | os.Remove(PidFile)
43 | }
44 |
45 | go os.Exit(executeShutdownCallbacks("SIGINT"))
46 | }
47 | }()
48 | }
49 |
50 | // executeShutdownCallbacks executes the shutdown callbacks as initiated
51 | // by signame. It logs any errors and returns the recommended exit status.
52 | // This function is idempotent; subsequent invocations always return 0.
53 | func executeShutdownCallbacks(signame string) (exitCode int) {
54 | shutdownCallbacksOnce.Do(func() {
55 | // execute third-party shutdown hooks
56 | EmitEvent(ShutdownEvent)
57 |
58 | errs := allShutdownCallbacks()
59 | if len(errs) > 0 {
60 | for _, err := range errs {
61 | log.Printf("[ERROR] %s shutdown: %v", signame, err)
62 | }
63 | exitCode = 1
64 | }
65 | })
66 | return
67 | }
68 |
69 | // allShutdownCallbacks executes all the shutdown callbacks
70 | // for all the instances, and returns all the errors generated
71 | // during their execution. An error executing one shutdown
72 | // callback does not stop execution of others. Only one shutdown
73 | // callback is executed at a time.
74 | func allShutdownCallbacks() []error {
75 | var errs []error
76 | instancesMu.Lock()
77 | for _, inst := range instances {
78 | errs = append(errs, inst.ShutdownCallbacks()...)
79 | }
80 | instancesMu.Unlock()
81 | return errs
82 | }
83 |
84 | // shutdownCallbacksOnce ensures that shutdown callbacks
85 | // for all instances are only executed once.
86 | var shutdownCallbacksOnce sync.Once
87 |
--------------------------------------------------------------------------------
/caddyhttp/header/header_test.go:
--------------------------------------------------------------------------------
1 | package header
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "net/http/httptest"
7 | "os"
8 | "reflect"
9 | "sort"
10 | "testing"
11 |
12 | "github.com/mholt/caddy/caddyhttp/httpserver"
13 | )
14 |
15 | func TestHeader(t *testing.T) {
16 | hostname, err := os.Hostname()
17 | if err != nil {
18 | t.Fatalf("Could not determine hostname: %v", err)
19 | }
20 | for i, test := range []struct {
21 | from string
22 | name string
23 | value string
24 | }{
25 | {"/a", "Foo", "Bar"},
26 | {"/a", "Bar", ""},
27 | {"/a", "Baz", ""},
28 | {"/a", "Server", ""},
29 | {"/a", "ServerName", hostname},
30 | {"/b", "Foo", ""},
31 | {"/b", "Bar", "Removed in /a"},
32 | } {
33 | he := Headers{
34 | Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
35 | w.Header().Set("Bar", "Removed in /a")
36 | w.WriteHeader(http.StatusOK)
37 | return 0, nil
38 | }),
39 | Rules: []Rule{
40 | {Path: "/a", Headers: http.Header{
41 | "Foo": []string{"Bar"},
42 | "ServerName": []string{"{hostname}"},
43 | "-Bar": []string{""},
44 | "-Server": []string{},
45 | }},
46 | },
47 | }
48 |
49 | req, err := http.NewRequest("GET", test.from, nil)
50 | if err != nil {
51 | t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
52 | }
53 |
54 | rec := httptest.NewRecorder()
55 | // preset header
56 | rec.Header().Set("Server", "Caddy")
57 |
58 | he.ServeHTTP(rec, req)
59 |
60 | if got := rec.Header().Get(test.name); got != test.value {
61 | t.Errorf("Test %d: Expected %s header to be %q but was %q",
62 | i, test.name, test.value, got)
63 | }
64 | }
65 | }
66 |
67 | func TestMultipleHeaders(t *testing.T) {
68 | he := Headers{
69 | Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
70 | fmt.Fprint(w, "This is a test")
71 | return 0, nil
72 | }),
73 | Rules: []Rule{
74 | {Path: "/a", Headers: http.Header{
75 | "+Link": []string{"; rel=preload", "; rel=preload"},
76 | }},
77 | },
78 | }
79 |
80 | req, err := http.NewRequest("GET", "/a", nil)
81 | if err != nil {
82 | t.Fatalf("Could not create HTTP request: %v", err)
83 | }
84 |
85 | rec := httptest.NewRecorder()
86 | he.ServeHTTP(rec, req)
87 |
88 | desiredHeaders := []string{"; rel=preload", "; rel=preload"}
89 | actualHeaders := rec.HeaderMap[http.CanonicalHeaderKey("Link")]
90 | sort.Strings(actualHeaders)
91 |
92 | if !reflect.DeepEqual(desiredHeaders, actualHeaders) {
93 | t.Errorf("Expected header to contain: %v but got: %v", desiredHeaders, actualHeaders)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/pathcleaner.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "math/rand"
5 | "path"
6 | "strings"
7 | "time"
8 | )
9 |
10 | // CleanMaskedPath prevents one or more of the path cleanup operations:
11 | // - collapse multiple slashes into one
12 | // - eliminate "/." (current directory)
13 | // - eliminate "/.."
14 | // by masking certain patterns in the path with a temporary random string.
15 | // This could be helpful when certain patterns in the path are desired to be preserved
16 | // that would otherwise be changed by path.Clean().
17 | // One such use case is the presence of the double slashes as protocol separator
18 | // (e.g., /api/endpoint/http://example.com).
19 | // This is a common pattern in many applications to allow passing URIs as path argument.
20 | func CleanMaskedPath(reqPath string, masks ...string) string {
21 | var replacerVal string
22 | maskMap := make(map[string]string)
23 |
24 | // Iterate over supplied masks and create temporary replacement strings
25 | // only for the masks that are present in the path, then replace all occurrences
26 | for _, mask := range masks {
27 | if strings.Index(reqPath, mask) >= 0 {
28 | replacerVal = "/_caddy" + generateRandomString() + "__"
29 | maskMap[mask] = replacerVal
30 | reqPath = strings.Replace(reqPath, mask, replacerVal, -1)
31 | }
32 | }
33 |
34 | reqPath = path.Clean(reqPath)
35 |
36 | // Revert the replaced masks after path cleanup
37 | for mask, replacerVal := range maskMap {
38 | reqPath = strings.Replace(reqPath, replacerVal, mask, -1)
39 | }
40 | return reqPath
41 | }
42 |
43 | // CleanPath calls CleanMaskedPath() with the default mask of "://"
44 | // to preserve double slashes of protocols
45 | // such as "http://", "https://", and "ftp://" etc.
46 | func CleanPath(reqPath string) string {
47 | return CleanMaskedPath(reqPath, "://")
48 | }
49 |
50 | // An efficient and fast method for random string generation.
51 | // Inspired by http://stackoverflow.com/a/31832326.
52 | const randomStringLength = 4
53 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
54 | const (
55 | letterIdxBits = 6
56 | letterIdxMask = 1<= 0; {
65 | if remain == 0 {
66 | cache, remain = src.Int63(), letterIdxMax
67 | }
68 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
69 | b[i] = letterBytes[idx]
70 | i--
71 | }
72 | cache >>= letterIdxBits
73 | remain--
74 | }
75 | return string(b)
76 | }
77 |
--------------------------------------------------------------------------------
/dist/init/freebsd/caddy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # PROVIDE: caddy
4 | # REQUIRE: networking
5 | # KEYWORD: shutdown
6 |
7 | #
8 | # Add the following lines to /etc/rc.conf to enable caddy:
9 | # caddy_enable (bool): Set to "NO" by default.
10 | # Set it to "YES" to enable caddy
11 | #
12 | # caddy_cert_email (str): Set to "" by default.
13 | # Defines the SSL certificate issuer email. By providing an
14 | # email address you automatically agree to letsencrypt.org's
15 | # general terms and conditions
16 | #
17 | # caddy_bin_path (str): Set to "/usr/local/bin/caddy" by default.
18 | # Provides the path to the caddy server executable
19 | #
20 | # caddy_cpu (str): Set to "99%" by default.
21 | # Configures, how much CPU capacity caddy may gain
22 | #
23 | # caddy_config_path (str): Set to "/usr/local/www/Caddyfile" by default.
24 | # Defines the path for the configuration file caddy will load on boot
25 | #
26 | # caddy_run_user (str): Set to "root" by default.
27 | # Defines the user that caddy will run on
28 | #
29 |
30 | . /etc/rc.subr
31 |
32 | name="caddy"
33 | rcvar="${name}_enable"
34 |
35 | load_rc_config $name
36 | : ${caddy_enable:=no}
37 | : ${caddy_cert_email=""}
38 | : ${caddy_bin_path="/usr/local/bin/caddy"}
39 | : ${caddy_cpu="99%"} # was a bug for me that caused a crash within jails
40 | : ${caddy_config_path="/usr/local/www/Caddyfile"}
41 | : ${caddy_run_user="root"}
42 |
43 | if [ "$caddy_cert_email" = "" ]
44 | then
45 | echo "rc variable \$caddy_cert_email is not set. Please provide a valid SSL certificate issuer email."
46 | exit 1
47 | fi
48 |
49 | pidfile="/var/run/caddy.pid"
50 | logfile="/var/log/caddy.log"
51 |
52 | command="${caddy_bin_path} -log ${logfile} -cpu ${caddy_cpu} -conf ${caddy_config_path} -agree -email ${caddy_cert_email}"
53 |
54 | start_cmd="caddy_start"
55 | status_cmd="caddy_status"
56 | stop_cmd="caddy_stop"
57 |
58 | caddy_start() {
59 | echo "Starting ${name}..."
60 | /usr/sbin/daemon -u ${caddy_run_user} -c -p ${pidfile} -f ${command}
61 | }
62 |
63 | caddy_status() {
64 | if [ -f ${pidfile} ]; then
65 | echo "${name} is running as $(cat $pidfile)."
66 | else
67 | echo "${name} is not running."
68 | return 1
69 | fi
70 | }
71 |
72 | caddy_stop() {
73 | if [ ! -f ${pidfile} ]; then
74 | echo "${name} is not running."
75 | return 1
76 | fi
77 |
78 | echo -n "Stopping ${name}..."
79 | kill -KILL $(cat $pidfile) 2> /dev/null && echo "stopped"
80 | rm -f ${pidfile}
81 | }
82 |
83 | run_rc_command "$1"
84 |
--------------------------------------------------------------------------------
/caddyhttp/push/handler.go:
--------------------------------------------------------------------------------
1 | package push
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func (h Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
11 | pusher, hasPusher := w.(http.Pusher)
12 |
13 | // no push possible, carry on
14 | if !hasPusher {
15 | return h.Next.ServeHTTP(w, r)
16 | }
17 |
18 | // check if this is a request for the pushed resource (avoid recursion)
19 | if _, exists := r.Header[pushHeader]; exists {
20 | return h.Next.ServeHTTP(w, r)
21 | }
22 |
23 | headers := h.filterProxiedHeaders(r.Header)
24 |
25 | // push first
26 | outer:
27 | for _, rule := range h.Rules {
28 | if httpserver.Path(r.URL.Path).Matches(rule.Path) {
29 | for _, resource := range rule.Resources {
30 | pushErr := pusher.Push(resource.Path, &http.PushOptions{
31 | Method: resource.Method,
32 | Header: h.mergeHeaders(headers, resource.Header),
33 | })
34 | if pushErr != nil {
35 | // if we cannot push (either not supported or concurrent streams are full - break)
36 | break outer
37 | }
38 | }
39 | }
40 | }
41 |
42 | // serve later
43 | code, err := h.Next.ServeHTTP(w, r)
44 |
45 | // push resources returned in Link headers from upstream middlewares or proxied apps
46 | if links, exists := w.Header()["Link"]; exists {
47 | h.servePreloadLinks(pusher, headers, links)
48 | }
49 |
50 | return code, err
51 | }
52 |
53 | func (h Middleware) servePreloadLinks(pusher http.Pusher, headers http.Header, links []string) {
54 | for _, link := range links {
55 | parts := strings.Split(link, ";")
56 |
57 | if link == "" || strings.HasSuffix(link, "nopush") {
58 | continue
59 | }
60 |
61 | target := strings.TrimSuffix(strings.TrimPrefix(parts[0], "<"), ">")
62 |
63 | err := pusher.Push(target, &http.PushOptions{
64 | Method: http.MethodGet,
65 | Header: headers,
66 | })
67 |
68 | if err != nil {
69 | break
70 | }
71 | }
72 | }
73 |
74 | func (h Middleware) mergeHeaders(l, r http.Header) http.Header {
75 | out := http.Header{}
76 |
77 | for k, v := range l {
78 | out[k] = v
79 | }
80 |
81 | for k, vv := range r {
82 | for _, v := range vv {
83 | out.Add(k, v)
84 | }
85 | }
86 |
87 | return out
88 | }
89 |
90 | func (h Middleware) filterProxiedHeaders(headers http.Header) http.Header {
91 | filter := http.Header{}
92 |
93 | for _, header := range proxiedHeaders {
94 | if val, ok := headers[header]; ok {
95 | filter[header] = val
96 | }
97 | }
98 |
99 | return filter
100 | }
101 |
102 | var proxiedHeaders = []string{
103 | "Accept-Encoding",
104 | "Accept-Language",
105 | "Cache-Control",
106 | "Host",
107 | "User-Agent",
108 | }
109 |
--------------------------------------------------------------------------------
/caddyhttp/browse/setup_test.go:
--------------------------------------------------------------------------------
1 | package browse
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "path/filepath"
7 | "strconv"
8 | "testing"
9 | "time"
10 |
11 | "github.com/mholt/caddy"
12 | "github.com/mholt/caddy/caddyhttp/httpserver"
13 | )
14 |
15 | func TestSetup(t *testing.T) {
16 | tempDirPath := os.TempDir()
17 | _, err := os.Stat(tempDirPath)
18 | if err != nil {
19 | t.Fatalf("BeforeTest: Failed to find an existing directory for testing! Error was: %v", err)
20 | }
21 | nonExistentDirPath := filepath.Join(tempDirPath, strconv.Itoa(int(time.Now().UnixNano())))
22 |
23 | tempTemplate, err := ioutil.TempFile(".", "tempTemplate")
24 | if err != nil {
25 | t.Fatalf("BeforeTest: Failed to create a temporary file in the working directory! Error was: %v", err)
26 | }
27 | defer os.Remove(tempTemplate.Name())
28 |
29 | tempTemplatePath := filepath.Join(".", tempTemplate.Name())
30 |
31 | for i, test := range []struct {
32 | input string
33 | expectedPathScope []string
34 | shouldErr bool
35 | }{
36 | // test case #0 tests handling of multiple pathscopes
37 | {"browse " + tempDirPath + "\n browse .", []string{tempDirPath, "."}, false},
38 |
39 | // test case #1 tests instantiation of Config with default values
40 | {"browse /", []string{"/"}, false},
41 |
42 | // test case #2 tests detectaction of custom template
43 | {"browse . " + tempTemplatePath, []string{"."}, false},
44 |
45 | // test case #3 tests detection of non-existent template
46 | {"browse . " + nonExistentDirPath, nil, true},
47 |
48 | // test case #4 tests detection of duplicate pathscopes
49 | {"browse " + tempDirPath + "\n browse " + tempDirPath, nil, true},
50 | } {
51 |
52 | c := caddy.NewTestController("http", test.input)
53 | err := setup(c)
54 | if err != nil && !test.shouldErr {
55 | t.Errorf("Test case #%d received an error of %v", i, err)
56 | }
57 | if test.expectedPathScope == nil {
58 | continue
59 | }
60 | mids := httpserver.GetConfig(c).Middleware()
61 | mid := mids[len(mids)-1]
62 | receivedConfigs := mid(nil).(Browse).Configs
63 | for j, config := range receivedConfigs {
64 | if config.PathScope != test.expectedPathScope[j] {
65 | t.Errorf("Test case #%d expected a pathscope of %v, but got %v", i, test.expectedPathScope, config.PathScope)
66 | }
67 | }
68 | }
69 |
70 | // test case #6 tests startup with missing root directory in combination with default browse settings
71 | controller := caddy.NewTestController("http", "browse")
72 | cfg := httpserver.GetConfig(controller)
73 |
74 | // Make sure non-existent root path doesn't return error
75 | cfg.Root = nonExistentDirPath
76 | err = setup(controller)
77 |
78 | if err != nil {
79 | t.Errorf("Test for non-existent browse path received an error, but shouldn't have: %v", err)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/caddyhttp/log/log.go:
--------------------------------------------------------------------------------
1 | // Package log implements request (access) logging middleware.
2 | package log
3 |
4 | import (
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/mholt/caddy"
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | func init() {
13 | caddy.RegisterPlugin("log", caddy.Plugin{
14 | ServerType: "http",
15 | Action: setup,
16 | })
17 | }
18 |
19 | // Logger is a basic request logging middleware.
20 | type Logger struct {
21 | Next httpserver.Handler
22 | Rules []*Rule
23 | ErrorFunc func(http.ResponseWriter, *http.Request, int) // failover error handler
24 | }
25 |
26 | func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
27 | for _, rule := range l.Rules {
28 | if httpserver.Path(r.URL.Path).Matches(rule.PathScope) {
29 | // Record the response
30 | responseRecorder := httpserver.NewResponseRecorder(w)
31 |
32 | // Attach the Replacer we'll use so that other middlewares can
33 | // set their own placeholders if they want to.
34 | rep := httpserver.NewReplacer(r, responseRecorder, CommonLogEmptyValue)
35 | responseRecorder.Replacer = rep
36 |
37 | // Bon voyage, request!
38 | status, err := l.Next.ServeHTTP(responseRecorder, r)
39 |
40 | if status >= 400 {
41 | // There was an error up the chain, but no response has been written yet.
42 | // The error must be handled here so the log entry will record the response size.
43 | if l.ErrorFunc != nil {
44 | l.ErrorFunc(responseRecorder, r, status)
45 | } else {
46 | // Default failover error handler
47 | responseRecorder.WriteHeader(status)
48 | fmt.Fprintf(responseRecorder, "%d %s", status, http.StatusText(status))
49 | }
50 | status = 0
51 | }
52 |
53 | // Write log entries
54 | for _, e := range rule.Entries {
55 | e.Log.Println(rep.Replace(e.Format))
56 | }
57 |
58 | return status, err
59 | }
60 | }
61 | return l.Next.ServeHTTP(w, r)
62 | }
63 |
64 | // Entry represents a log entry under a path scope
65 | type Entry struct {
66 | Format string
67 | Log *httpserver.Logger
68 | }
69 |
70 | // Rule configures the logging middleware.
71 | type Rule struct {
72 | PathScope string
73 | Entries []*Entry
74 | }
75 |
76 | const (
77 | // DefaultLogFilename is the default log filename.
78 | DefaultLogFilename = "access.log"
79 | // CommonLogFormat is the common log format.
80 | CommonLogFormat = `{remote} ` + CommonLogEmptyValue + " " + CommonLogEmptyValue + ` [{when}] "{method} {uri} {proto}" {status} {size}`
81 | // CommonLogEmptyValue is the common empty log value.
82 | CommonLogEmptyValue = "-"
83 | // CombinedLogFormat is the combined log format.
84 | CombinedLogFormat = CommonLogFormat + ` "{>Referer}" "{>User-Agent}"`
85 | // DefaultLogFormat is the default log format.
86 | DefaultLogFormat = CommonLogFormat
87 | )
88 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/requestfilter.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "net/http"
5 | "path"
6 |
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | // RequestFilter determines if a request should be gzipped.
11 | type RequestFilter interface {
12 | // ShouldCompress tells if gzip compression
13 | // should be done on the request.
14 | ShouldCompress(*http.Request) bool
15 | }
16 |
17 | // defaultExtensions is the list of default extensions for which to enable gzipping.
18 | var defaultExtensions = []string{"", ".txt", ".htm", ".html", ".css", ".php", ".js", ".json",
19 | ".md", ".mdown", ".xml", ".svg", ".go", ".cgi", ".py", ".pl", ".aspx", ".asp"}
20 |
21 | // DefaultExtFilter creates an ExtFilter with default extensions.
22 | func DefaultExtFilter() ExtFilter {
23 | m := ExtFilter{Exts: make(Set)}
24 | for _, extension := range defaultExtensions {
25 | m.Exts.Add(extension)
26 | }
27 | return m
28 | }
29 |
30 | // ExtFilter is RequestFilter for file name extensions.
31 | type ExtFilter struct {
32 | // Exts is the file name extensions to accept
33 | Exts Set
34 | }
35 |
36 | // ExtWildCard is the wildcard for extensions.
37 | const ExtWildCard = "*"
38 |
39 | // ShouldCompress checks if the request file extension matches any
40 | // of the registered extensions. It returns true if the extension is
41 | // found and false otherwise.
42 | func (e ExtFilter) ShouldCompress(r *http.Request) bool {
43 | ext := path.Ext(r.URL.Path)
44 | return e.Exts.Contains(ExtWildCard) || e.Exts.Contains(ext)
45 | }
46 |
47 | // PathFilter is RequestFilter for request path.
48 | type PathFilter struct {
49 | // IgnoredPaths is the paths to ignore
50 | IgnoredPaths Set
51 | }
52 |
53 | // ShouldCompress checks if the request path matches any of the
54 | // registered paths to ignore. It returns false if an ignored path
55 | // is found and true otherwise.
56 | func (p PathFilter) ShouldCompress(r *http.Request) bool {
57 | return !p.IgnoredPaths.ContainsFunc(func(value string) bool {
58 | return httpserver.Path(r.URL.Path).Matches(value)
59 | })
60 | }
61 |
62 | // Set stores distinct strings.
63 | type Set map[string]struct{}
64 |
65 | // Add adds an element to the set.
66 | func (s Set) Add(value string) {
67 | s[value] = struct{}{}
68 | }
69 |
70 | // Remove removes an element from the set.
71 | func (s Set) Remove(value string) {
72 | delete(s, value)
73 | }
74 |
75 | // Contains check if the set contains value.
76 | func (s Set) Contains(value string) bool {
77 | _, ok := s[value]
78 | return ok
79 | }
80 |
81 | // ContainsFunc is similar to Contains. It iterates all the
82 | // elements in the set and passes each to f. It returns true
83 | // on the first call to f that returns true and false otherwise.
84 | func (s Set) ContainsFunc(f func(string) bool) bool {
85 | for k := range s {
86 | if f(k) {
87 | return true
88 | }
89 | }
90 | return false
91 | }
92 |
--------------------------------------------------------------------------------
/caddyhttp/header/setup_test.go:
--------------------------------------------------------------------------------
1 | package header
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "reflect"
7 | "testing"
8 |
9 | "github.com/mholt/caddy"
10 | "github.com/mholt/caddy/caddyhttp/httpserver"
11 | )
12 |
13 | func TestSetup(t *testing.T) {
14 | c := caddy.NewTestController("http", `header / Foo Bar`)
15 | err := setup(c)
16 | if err != nil {
17 | t.Errorf("Expected no errors, but got: %v", err)
18 | }
19 |
20 | mids := httpserver.GetConfig(c).Middleware()
21 | if len(mids) == 0 {
22 | t.Fatal("Expected middleware, had 0 instead")
23 | }
24 |
25 | handler := mids[0](httpserver.EmptyNext)
26 | myHandler, ok := handler.(Headers)
27 | if !ok {
28 | t.Fatalf("Expected handler to be type Headers, got: %#v", handler)
29 | }
30 |
31 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
32 | t.Error("'Next' field of handler was not set properly")
33 | }
34 | }
35 |
36 | func TestHeadersParse(t *testing.T) {
37 | tests := []struct {
38 | input string
39 | shouldErr bool
40 | expected []Rule
41 | }{
42 | {`header /foo Foo "Bar Baz"`,
43 | false, []Rule{
44 | {Path: "/foo", Headers: http.Header{
45 | "Foo": []string{"Bar Baz"},
46 | }},
47 | }},
48 | {`header /bar {
49 | Foo "Bar Baz"
50 | Baz Qux
51 | Foobar
52 | }`,
53 | false, []Rule{
54 | {Path: "/bar", Headers: http.Header{
55 | "Foo": []string{"Bar Baz"},
56 | "Baz": []string{"Qux"},
57 | "Foobar": []string{""},
58 | }},
59 | }},
60 | {`header /foo {
61 | Foo Bar Baz
62 | }`, true,
63 | []Rule{}},
64 | {`header /foo {
65 | Test "max-age=1814400";
66 | }`, true, []Rule{}},
67 | }
68 |
69 | for i, test := range tests {
70 | actual, err := headersParse(caddy.NewTestController("http", test.input))
71 |
72 | if err == nil && test.shouldErr {
73 | t.Errorf("Test %d didn't error, but it should have", i)
74 | } else if err != nil && !test.shouldErr {
75 | t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
76 | }
77 |
78 | if len(actual) != len(test.expected) {
79 | t.Fatalf("Test %d expected %d rules, but got %d",
80 | i, len(test.expected), len(actual))
81 | }
82 |
83 | for j, expectedRule := range test.expected {
84 | actualRule := actual[j]
85 |
86 | if actualRule.Path != expectedRule.Path {
87 | t.Errorf("Test %d, rule %d: Expected path %s, but got %s",
88 | i, j, expectedRule.Path, actualRule.Path)
89 | }
90 |
91 | expectedHeaders := fmt.Sprintf("%v", expectedRule.Headers)
92 | actualHeaders := fmt.Sprintf("%v", actualRule.Headers)
93 |
94 | if !reflect.DeepEqual(actualRule.Headers, expectedRule.Headers) {
95 | t.Errorf("Test %d, rule %d: Expected headers %s, but got %s",
96 | i, j, expectedHeaders, actualHeaders)
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/caddyhttp/log/setup.go:
--------------------------------------------------------------------------------
1 | package log
2 |
3 | import (
4 | "github.com/mholt/caddy"
5 | "github.com/mholt/caddy/caddyhttp/httpserver"
6 | )
7 |
8 | // setup sets up the logging middleware.
9 | func setup(c *caddy.Controller) error {
10 | rules, err := logParse(c)
11 | if err != nil {
12 | return err
13 | }
14 |
15 | for _, rule := range rules {
16 | for _, entry := range rule.Entries {
17 | entry.Log.Attach(c)
18 | }
19 | }
20 |
21 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
22 | return Logger{Next: next, Rules: rules, ErrorFunc: httpserver.DefaultErrorFunc}
23 | })
24 |
25 | return nil
26 | }
27 |
28 | func logParse(c *caddy.Controller) ([]*Rule, error) {
29 | var rules []*Rule
30 |
31 | for c.Next() {
32 | args := c.RemainingArgs()
33 |
34 | var logRoller *httpserver.LogRoller
35 | logRoller = httpserver.DefaultLogRoller()
36 |
37 | for c.NextBlock() {
38 | what := c.Val()
39 | if !c.NextArg() {
40 | return nil, c.ArgErr()
41 | }
42 | where := c.Val()
43 |
44 | // only support roller related options inside a block
45 | if !httpserver.IsLogRollerSubdirective(what) {
46 | return nil, c.ArgErr()
47 | }
48 |
49 | if err := httpserver.ParseRoller(logRoller, what, where); err != nil {
50 | return nil, err
51 | }
52 | }
53 |
54 | if len(args) == 0 {
55 | // Nothing specified; use defaults
56 | rules = appendEntry(rules, "/", &Entry{
57 | Log: &httpserver.Logger{
58 | Output: DefaultLogFilename,
59 | Roller: logRoller,
60 | },
61 | Format: DefaultLogFormat,
62 | })
63 | } else if len(args) == 1 {
64 | // Only an output file specified
65 | rules = appendEntry(rules, "/", &Entry{
66 | Log: &httpserver.Logger{
67 | Output: args[0],
68 | Roller: logRoller,
69 | },
70 | Format: DefaultLogFormat,
71 | })
72 | } else {
73 | // Path scope, output file, and maybe a format specified
74 |
75 | format := DefaultLogFormat
76 |
77 | if len(args) > 2 {
78 | switch args[2] {
79 | case "{common}":
80 | format = CommonLogFormat
81 | case "{combined}":
82 | format = CombinedLogFormat
83 | default:
84 | format = args[2]
85 | }
86 | }
87 |
88 | rules = appendEntry(rules, args[0], &Entry{
89 | Log: &httpserver.Logger{
90 | Output: args[1],
91 | Roller: logRoller,
92 | },
93 | Format: format,
94 | })
95 | }
96 | }
97 |
98 | return rules, nil
99 | }
100 |
101 | func appendEntry(rules []*Rule, pathScope string, entry *Entry) []*Rule {
102 | for _, rule := range rules {
103 | if rule.PathScope == pathScope {
104 | rule.Entries = append(rule.Entries, entry)
105 | return rules
106 | }
107 | }
108 |
109 | rules = append(rules, &Rule{
110 | PathScope: pathScope,
111 | Entries: []*Entry{entry},
112 | })
113 |
114 | return rules
115 | }
116 |
--------------------------------------------------------------------------------
/caddytls/certificates_test.go:
--------------------------------------------------------------------------------
1 | package caddytls
2 |
3 | import "testing"
4 |
5 | func TestUnexportedGetCertificate(t *testing.T) {
6 | defer func() { certCache = make(map[string]Certificate) }()
7 |
8 | // When cache is empty
9 | if _, matched, defaulted := getCertificate("example.com"); matched || defaulted {
10 | t.Errorf("Got a certificate when cache was empty; matched=%v, defaulted=%v", matched, defaulted)
11 | }
12 |
13 | // When cache has one certificate in it (also is default)
14 | defaultCert := Certificate{Names: []string{"example.com", ""}}
15 | certCache[""] = defaultCert
16 | certCache["example.com"] = defaultCert
17 | if cert, matched, defaulted := getCertificate("Example.com"); !matched || defaulted || cert.Names[0] != "example.com" {
18 | t.Errorf("Didn't get a cert for 'Example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
19 | }
20 | if cert, matched, defaulted := getCertificate(""); !matched || defaulted || cert.Names[0] != "example.com" {
21 | t.Errorf("Didn't get a cert for '' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
22 | }
23 |
24 | // When retrieving wildcard certificate
25 | certCache["*.example.com"] = Certificate{Names: []string{"*.example.com"}}
26 | if cert, matched, defaulted := getCertificate("sub.example.com"); !matched || defaulted || cert.Names[0] != "*.example.com" {
27 | t.Errorf("Didn't get wildcard cert for 'sub.example.com' or got the wrong one: %v, matched=%v, defaulted=%v", cert, matched, defaulted)
28 | }
29 |
30 | // When no certificate matches, the default is returned
31 | if cert, matched, defaulted := getCertificate("nomatch"); matched || !defaulted {
32 | t.Errorf("Expected matched=false, defaulted=true; but got matched=%v, defaulted=%v (cert: %v)", matched, defaulted, cert)
33 | } else if cert.Names[0] != "example.com" {
34 | t.Errorf("Expected default cert, got: %v", cert)
35 | }
36 | }
37 |
38 | func TestCacheCertificate(t *testing.T) {
39 | defer func() { certCache = make(map[string]Certificate) }()
40 |
41 | cacheCertificate(Certificate{Names: []string{"example.com", "sub.example.com"}})
42 | if _, ok := certCache["example.com"]; !ok {
43 | t.Error("Expected first cert to be cached by key 'example.com', but it wasn't")
44 | }
45 | if _, ok := certCache["sub.example.com"]; !ok {
46 | t.Error("Expected first cert to be cached by key 'sub.example.com', but it wasn't")
47 | }
48 | if cert, ok := certCache[""]; !ok || cert.Names[2] != "" {
49 | t.Error("Expected first cert to be cached additionally as the default certificate with empty name added, but it wasn't")
50 | }
51 |
52 | cacheCertificate(Certificate{Names: []string{"example2.com"}})
53 | if _, ok := certCache["example2.com"]; !ok {
54 | t.Error("Expected second cert to be cached by key 'exmaple2.com', but it wasn't")
55 | }
56 | if cert, ok := certCache[""]; ok && cert.Names[0] == "example2.com" {
57 | t.Error("Expected second cert to NOT be cached as default, but it was")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/requestfilter_test.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 | )
7 |
8 | func TestSet(t *testing.T) {
9 | set := make(Set)
10 | set.Add("a")
11 | if len(set) != 1 {
12 | t.Errorf("Expected 1 found %v", len(set))
13 | }
14 | set.Add("a")
15 | if len(set) != 1 {
16 | t.Errorf("Expected 1 found %v", len(set))
17 | }
18 | set.Add("b")
19 | if len(set) != 2 {
20 | t.Errorf("Expected 2 found %v", len(set))
21 | }
22 | if !set.Contains("a") {
23 | t.Errorf("Set should contain a")
24 | }
25 | if !set.Contains("b") {
26 | t.Errorf("Set should contain a")
27 | }
28 | set.Add("c")
29 | if len(set) != 3 {
30 | t.Errorf("Expected 3 found %v", len(set))
31 | }
32 | if !set.Contains("c") {
33 | t.Errorf("Set should contain c")
34 | }
35 | set.Remove("a")
36 | if len(set) != 2 {
37 | t.Errorf("Expected 2 found %v", len(set))
38 | }
39 | if set.Contains("a") {
40 | t.Errorf("Set should not contain a")
41 | }
42 | if !set.ContainsFunc(func(v string) bool {
43 | return v == "c"
44 | }) {
45 | t.Errorf("ContainsFunc should return true")
46 | }
47 | }
48 |
49 | func TestExtFilter(t *testing.T) {
50 | var filter RequestFilter = ExtFilter{make(Set)}
51 | for _, e := range []string{".txt", ".html", ".css", ".md"} {
52 | filter.(ExtFilter).Exts.Add(e)
53 | }
54 | r := urlRequest("file.txt")
55 | if !filter.ShouldCompress(r) {
56 | t.Errorf("Should be valid filter")
57 | }
58 | var exts = []string{
59 | ".html", ".css", ".md",
60 | }
61 | for i, e := range exts {
62 | r := urlRequest("file" + e)
63 | if !filter.ShouldCompress(r) {
64 | t.Errorf("Test %v: Should be valid filter", i)
65 | }
66 | }
67 | exts = []string{
68 | ".htm1", ".abc", ".mdx",
69 | }
70 | for i, e := range exts {
71 | r := urlRequest("file" + e)
72 | if filter.ShouldCompress(r) {
73 | t.Errorf("Test %v: Should not be valid filter", i)
74 | }
75 | }
76 | filter.(ExtFilter).Exts.Add(ExtWildCard)
77 | for i, e := range exts {
78 | r := urlRequest("file" + e)
79 | if !filter.ShouldCompress(r) {
80 | t.Errorf("Test %v: Should be valid filter. Wildcard used.", i)
81 | }
82 | }
83 | }
84 |
85 | func TestPathFilter(t *testing.T) {
86 | paths := []string{
87 | "/a", "/b", "/c", "/de",
88 | }
89 | var filter RequestFilter = PathFilter{make(Set)}
90 | for _, p := range paths {
91 | filter.(PathFilter).IgnoredPaths.Add(p)
92 | }
93 | for i, p := range paths {
94 | r := urlRequest(p)
95 | if filter.ShouldCompress(r) {
96 | t.Errorf("Test %v: Should not be valid filter", i)
97 | }
98 | }
99 | paths = []string{
100 | "/f", "/g", "/h", "/ed",
101 | }
102 | for i, p := range paths {
103 | r := urlRequest(p)
104 | if !filter.ShouldCompress(r) {
105 | t.Errorf("Test %v: Should be valid filter", i)
106 | }
107 | }
108 | }
109 |
110 | func urlRequest(url string) *http.Request {
111 | r, _ := http.NewRequest("GET", url, nil)
112 | return r
113 | }
114 |
--------------------------------------------------------------------------------
/caddyhttp/timeouts/timeouts.go:
--------------------------------------------------------------------------------
1 | package timeouts
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func init() {
11 | caddy.RegisterPlugin("timeouts", caddy.Plugin{
12 | ServerType: "http",
13 | Action: setupTimeouts,
14 | })
15 | }
16 |
17 | func setupTimeouts(c *caddy.Controller) error {
18 | config := httpserver.GetConfig(c)
19 |
20 | for c.Next() {
21 | var hasOptionalBlock bool
22 | for c.NextBlock() {
23 | hasOptionalBlock = true
24 |
25 | // ensure the kind of timeout is recognized
26 | kind := c.Val()
27 | if kind != "read" && kind != "header" && kind != "write" && kind != "idle" {
28 | return c.Errf("unknown timeout '%s': must be read, header, write, or idle", kind)
29 | }
30 |
31 | // parse the timeout duration
32 | if !c.NextArg() {
33 | return c.ArgErr()
34 | }
35 | if c.NextArg() {
36 | // only one value permitted
37 | return c.ArgErr()
38 | }
39 | var dur time.Duration
40 | if c.Val() != "none" {
41 | var err error
42 | dur, err = time.ParseDuration(c.Val())
43 | if err != nil {
44 | return c.Errf("%v", err)
45 | }
46 | if dur < 0 {
47 | return c.Err("non-negative duration required for timeout value")
48 | }
49 | }
50 |
51 | // set this timeout's duration
52 | switch kind {
53 | case "read":
54 | config.Timeouts.ReadTimeout = dur
55 | config.Timeouts.ReadTimeoutSet = true
56 | case "header":
57 | config.Timeouts.ReadHeaderTimeout = dur
58 | config.Timeouts.ReadHeaderTimeoutSet = true
59 | case "write":
60 | config.Timeouts.WriteTimeout = dur
61 | config.Timeouts.WriteTimeoutSet = true
62 | case "idle":
63 | config.Timeouts.IdleTimeout = dur
64 | config.Timeouts.IdleTimeoutSet = true
65 | }
66 | }
67 | if !hasOptionalBlock {
68 | // set all timeouts to the same value
69 |
70 | if !c.NextArg() {
71 | return c.ArgErr()
72 | }
73 | if c.NextArg() {
74 | // only one value permitted
75 | return c.ArgErr()
76 | }
77 | val := c.Val()
78 |
79 | config.Timeouts.ReadTimeoutSet = true
80 | config.Timeouts.ReadHeaderTimeoutSet = true
81 | config.Timeouts.WriteTimeoutSet = true
82 | config.Timeouts.IdleTimeoutSet = true
83 |
84 | if val == "none" {
85 | config.Timeouts.ReadTimeout = 0
86 | config.Timeouts.ReadHeaderTimeout = 0
87 | config.Timeouts.WriteTimeout = 0
88 | config.Timeouts.IdleTimeout = 0
89 | } else {
90 | dur, err := time.ParseDuration(val)
91 | if err != nil {
92 | return c.Errf("unknown timeout duration: %v", err)
93 | }
94 | if dur < 0 {
95 | return c.Err("non-negative duration required for timeout value")
96 | }
97 | config.Timeouts.ReadTimeout = dur
98 | config.Timeouts.ReadHeaderTimeout = dur
99 | config.Timeouts.WriteTimeout = dur
100 | config.Timeouts.IdleTimeout = dur
101 | }
102 | }
103 | }
104 |
105 | return nil
106 | }
107 |
--------------------------------------------------------------------------------
/caddyhttp/errors/setup.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import (
4 | "log"
5 | "os"
6 | "path/filepath"
7 | "strconv"
8 |
9 | "github.com/mholt/caddy"
10 | "github.com/mholt/caddy/caddyhttp/httpserver"
11 | )
12 |
13 | // setup configures a new errors middleware instance.
14 | func setup(c *caddy.Controller) error {
15 | handler, err := errorsParse(c)
16 |
17 | if err != nil {
18 | return err
19 | }
20 |
21 | handler.Log.Attach(c)
22 |
23 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
24 | handler.Next = next
25 | return handler
26 | })
27 |
28 | return nil
29 | }
30 |
31 | func errorsParse(c *caddy.Controller) (*ErrorHandler, error) {
32 |
33 | // Very important that we make a pointer because the startup
34 | // function that opens the log file must have access to the
35 | // same instance of the handler, not a copy.
36 | handler := &ErrorHandler{
37 | ErrorPages: make(map[int]string),
38 | Log: &httpserver.Logger{},
39 | }
40 |
41 | cfg := httpserver.GetConfig(c)
42 |
43 | optionalBlock := func() error {
44 | for c.NextBlock() {
45 |
46 | what := c.Val()
47 | if !c.NextArg() {
48 | return c.ArgErr()
49 | }
50 | where := c.Val()
51 |
52 | if httpserver.IsLogRollerSubdirective(what) {
53 | var err error
54 | err = httpserver.ParseRoller(handler.Log.Roller, what, where)
55 | if err != nil {
56 | return err
57 | }
58 | } else {
59 | // Error page; ensure it exists
60 | if !filepath.IsAbs(where) {
61 | where = filepath.Join(cfg.Root, where)
62 | }
63 |
64 | f, err := os.Open(where)
65 | if err != nil {
66 | log.Printf("[WARNING] Unable to open error page '%s': %v", where, err)
67 | }
68 | f.Close()
69 |
70 | if what == "*" {
71 | if handler.GenericErrorPage != "" {
72 | return c.Errf("Duplicate status code entry: %s", what)
73 | }
74 | handler.GenericErrorPage = where
75 | } else {
76 | whatInt, err := strconv.Atoi(what)
77 | if err != nil {
78 | return c.Err("Expecting a numeric status code or '*', got '" + what + "'")
79 | }
80 |
81 | if _, exists := handler.ErrorPages[whatInt]; exists {
82 | return c.Errf("Duplicate status code entry: %s", what)
83 | }
84 |
85 | handler.ErrorPages[whatInt] = where
86 | }
87 | }
88 | }
89 | return nil
90 | }
91 |
92 | for c.Next() {
93 | // weird hack to avoid having the handler values overwritten.
94 | if c.Val() == "}" {
95 | continue
96 | }
97 |
98 | args := c.RemainingArgs()
99 |
100 | if len(args) == 1 {
101 | switch args[0] {
102 | case "visible":
103 | handler.Debug = true
104 | default:
105 | handler.Log.Output = args[0]
106 | handler.Log.Roller = httpserver.DefaultLogRoller()
107 | }
108 | }
109 |
110 | // Configuration may be in a block
111 | err := optionalBlock()
112 | if err != nil {
113 | return handler, err
114 | }
115 | }
116 |
117 | return handler, nil
118 | }
119 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/responsefilter.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "compress/gzip"
5 | "net/http"
6 | "strconv"
7 | )
8 |
9 | // ResponseFilter determines if the response should be gzipped.
10 | type ResponseFilter interface {
11 | ShouldCompress(http.ResponseWriter) bool
12 | }
13 |
14 | // LengthFilter is ResponseFilter for minimum content length.
15 | type LengthFilter int64
16 |
17 | // ShouldCompress returns if content length is greater than or
18 | // equals to minimum length.
19 | func (l LengthFilter) ShouldCompress(w http.ResponseWriter) bool {
20 | contentLength := w.Header().Get("Content-Length")
21 | length, err := strconv.ParseInt(contentLength, 10, 64)
22 | if err != nil || length == 0 {
23 | return false
24 | }
25 | return l != 0 && int64(l) <= length
26 | }
27 |
28 | // SkipCompressedFilter is ResponseFilter that will discard already compressed responses
29 | type SkipCompressedFilter struct{}
30 |
31 | // ShouldCompress returns true if served file is not already compressed
32 | // encodings via https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
33 | func (n SkipCompressedFilter) ShouldCompress(w http.ResponseWriter) bool {
34 | switch w.Header().Get("Content-Encoding") {
35 | case "gzip", "compress", "deflate", "br":
36 | return false
37 | default:
38 | return true
39 | }
40 | }
41 |
42 | // ResponseFilterWriter validates ResponseFilters. It writes
43 | // gzip compressed data if ResponseFilters are satisfied or
44 | // uncompressed data otherwise.
45 | type ResponseFilterWriter struct {
46 | filters []ResponseFilter
47 | shouldCompress bool
48 | statusCodeWritten bool
49 | *gzipResponseWriter
50 | }
51 |
52 | // NewResponseFilterWriter creates and initializes a new ResponseFilterWriter.
53 | func NewResponseFilterWriter(filters []ResponseFilter, gz *gzipResponseWriter) *ResponseFilterWriter {
54 | return &ResponseFilterWriter{filters: filters, gzipResponseWriter: gz}
55 | }
56 |
57 | // WriteHeader wraps underlying WriteHeader method and
58 | // compresses if filters are satisfied.
59 | func (r *ResponseFilterWriter) WriteHeader(code int) {
60 | // Determine if compression should be used or not.
61 | r.shouldCompress = true
62 | for _, filter := range r.filters {
63 | if !filter.ShouldCompress(r) {
64 | r.shouldCompress = false
65 | break
66 | }
67 | }
68 |
69 | if r.shouldCompress {
70 | // replace discard writer with ResponseWriter
71 | if gzWriter, ok := r.gzipResponseWriter.Writer.(*gzip.Writer); ok {
72 | gzWriter.Reset(r.ResponseWriter)
73 | }
74 | // use gzip WriteHeader to include and delete
75 | // necessary headers
76 | r.gzipResponseWriter.WriteHeader(code)
77 | } else {
78 | r.ResponseWriter.WriteHeader(code)
79 | }
80 | r.statusCodeWritten = true
81 | }
82 |
83 | // Write wraps underlying Write method and compresses if filters
84 | // are satisfied
85 | func (r *ResponseFilterWriter) Write(b []byte) (int, error) {
86 | if !r.statusCodeWritten {
87 | r.WriteHeader(http.StatusOK)
88 | }
89 | if r.shouldCompress {
90 | return r.gzipResponseWriter.Write(b)
91 | }
92 | return r.ResponseWriter.Write(b)
93 | }
94 |
--------------------------------------------------------------------------------
/caddyhttp/httpserver/roller.go:
--------------------------------------------------------------------------------
1 | package httpserver
2 |
3 | import (
4 | "io"
5 | "path/filepath"
6 | "strconv"
7 |
8 | "gopkg.in/natefinch/lumberjack.v2"
9 | )
10 |
11 | // LogRoller implements a type that provides a rolling logger.
12 | type LogRoller struct {
13 | Filename string
14 | MaxSize int
15 | MaxAge int
16 | MaxBackups int
17 | LocalTime bool
18 | }
19 |
20 | // GetLogWriter returns an io.Writer that writes to a rolling logger.
21 | // This should be called only from the main goroutine (like during
22 | // server setup) because this method is not thread-safe; it is careful
23 | // to create only one log writer per log file, even if the log file
24 | // is shared by different sites or middlewares. This ensures that
25 | // rolling is synchronized, since a process (or multiple processes)
26 | // should not create more than one roller on the same file at the
27 | // same time. See issue #1363.
28 | func (l LogRoller) GetLogWriter() io.Writer {
29 | absPath, err := filepath.Abs(l.Filename)
30 | if err != nil {
31 | absPath = l.Filename // oh well, hopefully they're consistent in how they specify the filename
32 | }
33 | lj, has := lumberjacks[absPath]
34 | if !has {
35 | lj = &lumberjack.Logger{
36 | Filename: l.Filename,
37 | MaxSize: l.MaxSize,
38 | MaxAge: l.MaxAge,
39 | MaxBackups: l.MaxBackups,
40 | LocalTime: l.LocalTime,
41 | }
42 | lumberjacks[absPath] = lj
43 | }
44 | return lj
45 | }
46 |
47 | // IsLogRollerSubdirective is true if the subdirective is for the log roller.
48 | func IsLogRollerSubdirective(subdir string) bool {
49 | return subdir == directiveRotateSize ||
50 | subdir == directiveRotateAge ||
51 | subdir == directiveRotateKeep
52 | }
53 |
54 | // ParseRoller parses roller contents out of c.
55 | func ParseRoller(l *LogRoller, what string, where string) error {
56 | if l == nil {
57 | l = DefaultLogRoller()
58 | }
59 | var value int
60 | var err error
61 | value, err = strconv.Atoi(where)
62 | if err != nil {
63 | return err
64 | }
65 | switch what {
66 | case directiveRotateSize:
67 | l.MaxSize = value
68 | case directiveRotateAge:
69 | l.MaxAge = value
70 | case directiveRotateKeep:
71 | l.MaxBackups = value
72 | }
73 | return nil
74 | }
75 |
76 | // DefaultLogRoller will roll logs by default.
77 | func DefaultLogRoller() *LogRoller {
78 | return &LogRoller{
79 | MaxSize: defaultRotateSize,
80 | MaxAge: defaultRotateAge,
81 | MaxBackups: defaultRotateKeep,
82 | LocalTime: true,
83 | }
84 | }
85 |
86 | const (
87 | // defaultRotateSize is 100 MB.
88 | defaultRotateSize = 100
89 | // defaultRotateAge is 14 days.
90 | defaultRotateAge = 14
91 | // defaultRotateKeep is 10 files.
92 | defaultRotateKeep = 10
93 | directiveRotateSize = "rotate_size"
94 | directiveRotateAge = "rotate_age"
95 | directiveRotateKeep = "rotate_keep"
96 | )
97 |
98 | // lumberjacks maps log filenames to the logger
99 | // that is being used to keep them rolled/maintained.
100 | var lumberjacks = make(map[string]*lumberjack.Logger)
101 |
--------------------------------------------------------------------------------
/caddyfile/lexer.go:
--------------------------------------------------------------------------------
1 | package caddyfile
2 |
3 | import (
4 | "bufio"
5 | "io"
6 | "unicode"
7 | )
8 |
9 | type (
10 | // lexer is a utility which can get values, token by
11 | // token, from a Reader. A token is a word, and tokens
12 | // are separated by whitespace. A word can be enclosed
13 | // in quotes if it contains whitespace.
14 | lexer struct {
15 | reader *bufio.Reader
16 | token Token
17 | line int
18 | }
19 |
20 | // Token represents a single parsable unit.
21 | Token struct {
22 | File string
23 | Line int
24 | Text string
25 | }
26 | )
27 |
28 | // load prepares the lexer to scan an input for tokens.
29 | // It discards any leading byte order mark.
30 | func (l *lexer) load(input io.Reader) error {
31 | l.reader = bufio.NewReader(input)
32 | l.line = 1
33 |
34 | // discard byte order mark, if present
35 | firstCh, _, err := l.reader.ReadRune()
36 | if err != nil {
37 | return err
38 | }
39 | if firstCh != 0xFEFF {
40 | err := l.reader.UnreadRune()
41 | if err != nil {
42 | return err
43 | }
44 | }
45 |
46 | return nil
47 | }
48 |
49 | // next loads the next token into the lexer.
50 | // A token is delimited by whitespace, unless
51 | // the token starts with a quotes character (")
52 | // in which case the token goes until the closing
53 | // quotes (the enclosing quotes are not included).
54 | // Inside quoted strings, quotes may be escaped
55 | // with a preceding \ character. No other chars
56 | // may be escaped. The rest of the line is skipped
57 | // if a "#" character is read in. Returns true if
58 | // a token was loaded; false otherwise.
59 | func (l *lexer) next() bool {
60 | var val []rune
61 | var comment, quoted, escaped bool
62 |
63 | makeToken := func() bool {
64 | l.token.Text = string(val)
65 | return true
66 | }
67 |
68 | for {
69 | ch, _, err := l.reader.ReadRune()
70 | if err != nil {
71 | if len(val) > 0 {
72 | return makeToken()
73 | }
74 | if err == io.EOF {
75 | return false
76 | }
77 | panic(err)
78 | }
79 |
80 | if quoted {
81 | if !escaped {
82 | if ch == '\\' {
83 | escaped = true
84 | continue
85 | } else if ch == '"' {
86 | quoted = false
87 | return makeToken()
88 | }
89 | }
90 | if ch == '\n' {
91 | l.line++
92 | }
93 | if escaped {
94 | // only escape quotes
95 | if ch != '"' {
96 | val = append(val, '\\')
97 | }
98 | }
99 | val = append(val, ch)
100 | escaped = false
101 | continue
102 | }
103 |
104 | if unicode.IsSpace(ch) {
105 | if ch == '\r' {
106 | continue
107 | }
108 | if ch == '\n' {
109 | l.line++
110 | comment = false
111 | }
112 | if len(val) > 0 {
113 | return makeToken()
114 | }
115 | continue
116 | }
117 |
118 | if ch == '#' {
119 | comment = true
120 | }
121 |
122 | if comment {
123 | continue
124 | }
125 |
126 | if len(val) == 0 {
127 | l.token = Token{Line: l.line}
128 | if ch == '"' {
129 | quoted = true
130 | continue
131 | }
132 | }
133 |
134 | val = append(val, ch)
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/responsefilter_test.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "compress/gzip"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | "github.com/mholt/caddy/caddyhttp/httpserver"
11 | )
12 |
13 | func TestLengthFilter(t *testing.T) {
14 | var filters = []ResponseFilter{
15 | LengthFilter(100),
16 | LengthFilter(1000),
17 | LengthFilter(0),
18 | }
19 |
20 | var tests = []struct {
21 | length int64
22 | shouldCompress [3]bool
23 | }{
24 | {20, [3]bool{false, false, false}},
25 | {50, [3]bool{false, false, false}},
26 | {100, [3]bool{true, false, false}},
27 | {500, [3]bool{true, false, false}},
28 | {1000, [3]bool{true, true, false}},
29 | {1500, [3]bool{true, true, false}},
30 | }
31 |
32 | for i, ts := range tests {
33 | for j, filter := range filters {
34 | r := httptest.NewRecorder()
35 | r.Header().Set("Content-Length", fmt.Sprint(ts.length))
36 | wWriter := NewResponseFilterWriter([]ResponseFilter{filter}, &gzipResponseWriter{gzip.NewWriter(r), r, false})
37 | if filter.ShouldCompress(wWriter) != ts.shouldCompress[j] {
38 | t.Errorf("Test %v: Expected %v found %v", i, ts.shouldCompress[j], filter.ShouldCompress(r))
39 | }
40 | }
41 | }
42 | }
43 |
44 | func TestResponseFilterWriter(t *testing.T) {
45 | tests := []struct {
46 | body string
47 | shouldCompress bool
48 | }{
49 | {"Hello\t\t\t\n", false},
50 | {"Hello the \t\t\t world is\n\n\n great", true},
51 | {"Hello \t\t\nfrom gzip", true},
52 | {"Hello gzip\n", false},
53 | }
54 |
55 | filters := []ResponseFilter{
56 | LengthFilter(15),
57 | }
58 |
59 | server := Gzip{Configs: []Config{
60 | {ResponseFilters: filters},
61 | }}
62 |
63 | for i, ts := range tests {
64 | server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
65 | w.Header().Set("Content-Length", fmt.Sprint(len(ts.body)))
66 | w.Write([]byte(ts.body))
67 | return 200, nil
68 | })
69 |
70 | r := urlRequest("/")
71 | r.Header.Set("Accept-Encoding", "gzip")
72 |
73 | w := httptest.NewRecorder()
74 |
75 | server.ServeHTTP(w, r)
76 |
77 | resp := w.Body.String()
78 |
79 | if !ts.shouldCompress {
80 | if resp != ts.body {
81 | t.Errorf("Test %v: No compression expected, found %v", i, resp)
82 | }
83 | } else {
84 | if resp == ts.body {
85 | t.Errorf("Test %v: Compression expected, found %v", i, resp)
86 | }
87 | }
88 | }
89 | }
90 |
91 | func TestResponseGzippedOutput(t *testing.T) {
92 | server := Gzip{Configs: []Config{
93 | {ResponseFilters: []ResponseFilter{SkipCompressedFilter{}}},
94 | }}
95 |
96 | server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
97 | w.Header().Set("Content-Encoding", "gzip")
98 | w.Write([]byte("gzipped"))
99 | return 200, nil
100 | })
101 |
102 | r := urlRequest("/")
103 | r.Header.Set("Accept-Encoding", "gzip")
104 |
105 | w := httptest.NewRecorder()
106 | server.ServeHTTP(w, r)
107 | resp := w.Body.String()
108 |
109 | if resp != "gzipped" {
110 | t.Errorf("Expected output not to be gzipped")
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/setup_test.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `gzip`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, but got: %v", err)
15 | }
16 | mids := httpserver.GetConfig(c).Middleware()
17 | if mids == nil {
18 | t.Fatal("Expected middleware, was nil instead")
19 | }
20 |
21 | handler := mids[0](httpserver.EmptyNext)
22 | myHandler, ok := handler.(Gzip)
23 | if !ok {
24 | t.Fatalf("Expected handler to be type Gzip, got: %#v", handler)
25 | }
26 |
27 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
28 | t.Error("'Next' field of handler was not set properly")
29 | }
30 |
31 | tests := []struct {
32 | input string
33 | shouldErr bool
34 | }{
35 | {`gzip {`, true},
36 | {`gzip {}`, true},
37 | {`gzip a b`, true},
38 | {`gzip a {`, true},
39 | {`gzip { not f } `, true},
40 | {`gzip { not } `, true},
41 | {`gzip { not /file
42 | ext .html
43 | level 1
44 | } `, false},
45 | {`gzip { level 9 } `, false},
46 | {`gzip { ext } `, true},
47 | {`gzip { ext /f
48 | } `, true},
49 | {`gzip { not /file
50 | ext .html
51 | level 1
52 | }
53 | gzip`, false},
54 | {`gzip {
55 | ext ""
56 | }`, false},
57 | {`gzip { not /file
58 | ext .html
59 | level 1
60 | }
61 | gzip { not /file1
62 | ext .htm
63 | level 3
64 | }
65 | `, false},
66 | {`gzip { not /file
67 | ext .html
68 | level 1
69 | }
70 | gzip { not /file1
71 | ext .htm
72 | level 3
73 | }
74 | `, false},
75 | {`gzip { not /file
76 | ext *
77 | level 1
78 | }
79 | `, false},
80 | {`gzip { not /file
81 | ext *
82 | level 1
83 | min_length ab
84 | }
85 | `, true},
86 | {`gzip { not /file
87 | ext *
88 | level 1
89 | min_length 1000
90 | }
91 | `, false},
92 | }
93 | for i, test := range tests {
94 | _, err := gzipParse(caddy.NewTestController("http", test.input))
95 | if test.shouldErr && err == nil {
96 | t.Errorf("Test %v: Expected error but found nil", i)
97 | } else if !test.shouldErr && err != nil {
98 | t.Errorf("Test %v: Expected no error but found error: %v", i, err)
99 | }
100 | }
101 | }
102 |
103 | func TestShouldAddResponseFilters(t *testing.T) {
104 | configs, err := gzipParse(caddy.NewTestController("http", `gzip { min_length 654 }`))
105 |
106 | if err != nil {
107 | t.Errorf("Test expected no error but found: %v", err)
108 | }
109 | filters := 0
110 |
111 | for _, config := range configs {
112 | for _, filter := range config.ResponseFilters {
113 | switch filter.(type) {
114 | case SkipCompressedFilter:
115 | filters++
116 | case LengthFilter:
117 | filters++
118 |
119 | if filter != LengthFilter(654) {
120 | t.Errorf("Expected LengthFilter to have length 654, got: %v", filter)
121 | }
122 | }
123 | }
124 |
125 | if filters != 2 {
126 | t.Errorf("Expected 2 response filters to be registered, got: %v", filters)
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/caddyhttp/internalsrv/internal.go:
--------------------------------------------------------------------------------
1 | // Package internalsrv provides a simple middleware that (a) prevents access
2 | // to internal locations and (b) allows to return files from internal location
3 | // by setting a special header, e.g. in a proxy response.
4 | //
5 | // The package is named internalsrv so as not to conflict with Go tooling
6 | // convention which treats folders called "internal" differently.
7 | package internalsrv
8 |
9 | import (
10 | "net/http"
11 |
12 | "github.com/mholt/caddy/caddyhttp/httpserver"
13 | )
14 |
15 | // Internal middleware protects internal locations from external requests -
16 | // but allows access from the inside by using a special HTTP header.
17 | type Internal struct {
18 | Next httpserver.Handler
19 | Paths []string
20 | }
21 |
22 | const (
23 | redirectHeader string = "X-Accel-Redirect"
24 | contentLengthHeader string = "Content-Length"
25 | contentEncodingHeader string = "Content-Encoding"
26 | maxRedirectCount int = 10
27 | )
28 |
29 | func isInternalRedirect(w http.ResponseWriter) bool {
30 | return w.Header().Get(redirectHeader) != ""
31 | }
32 |
33 | // ServeHTTP implements the httpserver.Handler interface.
34 | func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
35 |
36 | // Internal location requested? -> Not found.
37 | for _, prefix := range i.Paths {
38 | if httpserver.Path(r.URL.Path).Matches(prefix) {
39 | return http.StatusNotFound, nil
40 | }
41 | }
42 |
43 | // Use internal response writer to ignore responses that will be
44 | // redirected to internal locations
45 | iw := internalResponseWriter{ResponseWriter: w}
46 | status, err := i.Next.ServeHTTP(iw, r)
47 |
48 | for c := 0; c < maxRedirectCount && isInternalRedirect(iw); c++ {
49 | // Redirect - adapt request URL path and send it again
50 | // "down the chain"
51 | r.URL.Path = iw.Header().Get(redirectHeader)
52 | iw.ClearHeader()
53 |
54 | status, err = i.Next.ServeHTTP(iw, r)
55 | }
56 |
57 | if isInternalRedirect(iw) {
58 | // Too many redirect cycles
59 | iw.ClearHeader()
60 | return http.StatusInternalServerError, nil
61 | }
62 |
63 | return status, err
64 | }
65 |
66 | // internalResponseWriter wraps the underlying http.ResponseWriter and ignores
67 | // calls to Write and WriteHeader if the response should be redirected to an
68 | // internal location.
69 | type internalResponseWriter struct {
70 | http.ResponseWriter
71 | }
72 |
73 | // ClearHeader removes script headers that would interfere with follow up
74 | // redirect requests.
75 | func (w internalResponseWriter) ClearHeader() {
76 | w.Header().Del(redirectHeader)
77 | w.Header().Del(contentLengthHeader)
78 | w.Header().Del(contentEncodingHeader)
79 | }
80 |
81 | // WriteHeader ignores the call if the response should be redirected to an
82 | // internal location.
83 | func (w internalResponseWriter) WriteHeader(code int) {
84 | if !isInternalRedirect(w) {
85 | w.ResponseWriter.WriteHeader(code)
86 | }
87 | }
88 |
89 | // Write ignores the call if the response should be redirected to an internal
90 | // location.
91 | func (w internalResponseWriter) Write(b []byte) (int, error) {
92 | if isInternalRedirect(w) {
93 | return 0, nil
94 | }
95 | return w.ResponseWriter.Write(b)
96 | }
97 |
--------------------------------------------------------------------------------
/caddyhttp/websocket/setup_test.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestWebSocket(t *testing.T) {
11 | c := caddy.NewTestController("http", `websocket cat`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, got: %v", err)
15 | }
16 | mids := httpserver.GetConfig(c).Middleware()
17 | if len(mids) == 0 {
18 | t.Fatal("Expected middleware, got 0 instead")
19 | }
20 |
21 | handler := mids[0](httpserver.EmptyNext)
22 | myHandler, ok := handler.(WebSocket)
23 |
24 | if !ok {
25 | t.Fatalf("Expected handler to be type WebSocket, got: %#v", handler)
26 | }
27 |
28 | if myHandler.Sockets[0].Path != "/" {
29 | t.Errorf("Expected / as the default Path")
30 | }
31 | if myHandler.Sockets[0].Command != "cat" {
32 | t.Errorf("Expected %s as the command", "cat")
33 | }
34 |
35 | }
36 | func TestWebSocketParse(t *testing.T) {
37 | tests := []struct {
38 | inputWebSocketConfig string
39 | shouldErr bool
40 | expectedWebSocketConfig []Config
41 | }{
42 | {`websocket /api1 cat`, false, []Config{{
43 | Path: "/api1",
44 | Command: "cat",
45 | }}},
46 |
47 | {`websocket /api3 cat
48 | websocket /api4 cat `, false, []Config{{
49 | Path: "/api3",
50 | Command: "cat",
51 | }, {
52 | Path: "/api4",
53 | Command: "cat",
54 | }}},
55 |
56 | {`websocket /api5 "cmd arg1 arg2 arg3"`, false, []Config{{
57 | Path: "/api5",
58 | Command: "cmd",
59 | Arguments: []string{"arg1", "arg2", "arg3"},
60 | }}},
61 |
62 | // accept respawn
63 | {`websocket /api6 cat {
64 | respawn
65 | }`, false, []Config{{
66 | Path: "/api6",
67 | Command: "cat",
68 | }}},
69 |
70 | // invalid configuration
71 | {`websocket /api7 cat {
72 | invalid
73 | }`, true, []Config{}},
74 | }
75 | for i, test := range tests {
76 | c := caddy.NewTestController("http", test.inputWebSocketConfig)
77 | actualWebSocketConfigs, err := webSocketParse(c)
78 |
79 | if err == nil && test.shouldErr {
80 | t.Errorf("Test %d didn't error, but it should have", i)
81 | } else if err != nil && !test.shouldErr {
82 | t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
83 | }
84 | if len(actualWebSocketConfigs) != len(test.expectedWebSocketConfig) {
85 | t.Fatalf("Test %d expected %d no of WebSocket configs, but got %d ",
86 | i, len(test.expectedWebSocketConfig), len(actualWebSocketConfigs))
87 | }
88 | for j, actualWebSocketConfig := range actualWebSocketConfigs {
89 |
90 | if actualWebSocketConfig.Path != test.expectedWebSocketConfig[j].Path {
91 | t.Errorf("Test %d expected %dth WebSocket Config Path to be %s , but got %s",
92 | i, j, test.expectedWebSocketConfig[j].Path, actualWebSocketConfig.Path)
93 | }
94 |
95 | if actualWebSocketConfig.Command != test.expectedWebSocketConfig[j].Command {
96 | t.Errorf("Test %d expected %dth WebSocket Config Command to be %s , but got %s",
97 | i, j, test.expectedWebSocketConfig[j].Command, actualWebSocketConfig.Command)
98 | }
99 |
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/caddyhttp/templates/templates.go:
--------------------------------------------------------------------------------
1 | // Package templates implements template execution for files to be
2 | // dynamically rendered for the client.
3 | package templates
4 |
5 | import (
6 | "bytes"
7 | "mime"
8 | "net/http"
9 | "os"
10 | "path"
11 | "path/filepath"
12 | "text/template"
13 |
14 | "github.com/mholt/caddy/caddyhttp/httpserver"
15 | )
16 |
17 | // ServeHTTP implements the httpserver.Handler interface.
18 | func (t Templates) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
19 | for _, rule := range t.Rules {
20 | if !httpserver.Path(r.URL.Path).Matches(rule.Path) {
21 | continue
22 | }
23 |
24 | // Check for index files
25 | fpath := r.URL.Path
26 | if idx, ok := httpserver.IndexFile(t.FileSys, fpath, rule.IndexFiles); ok {
27 | fpath = idx
28 | }
29 |
30 | // Check the extension
31 | reqExt := path.Ext(fpath)
32 |
33 | for _, ext := range rule.Extensions {
34 | if reqExt == ext {
35 | // Create execution context
36 | ctx := httpserver.Context{Root: t.FileSys, Req: r, URL: r.URL}
37 |
38 | // New template
39 | templateName := filepath.Base(fpath)
40 | tpl := template.New(templateName)
41 |
42 | // Set delims
43 | if rule.Delims != [2]string{} {
44 | tpl.Delims(rule.Delims[0], rule.Delims[1])
45 | }
46 |
47 | // Build the template
48 | templatePath := filepath.Join(t.Root, fpath)
49 | tpl, err := tpl.ParseFiles(templatePath)
50 | if err != nil {
51 | if os.IsNotExist(err) {
52 | return http.StatusNotFound, nil
53 | } else if os.IsPermission(err) {
54 | return http.StatusForbidden, nil
55 | }
56 | return http.StatusInternalServerError, err
57 | }
58 |
59 | // Execute it
60 | var buf bytes.Buffer
61 | err = tpl.Execute(&buf, ctx)
62 | if err != nil {
63 | return http.StatusInternalServerError, err
64 | }
65 |
66 | // If Content-Type isn't set here, http.ResponseWriter.Write
67 | // will set it according to response body. But other middleware
68 | // such as gzip can modify response body, then Content-Type
69 | // detected by http.ResponseWriter.Write is wrong.
70 | ctype := mime.TypeByExtension(ext)
71 | if ctype == "" {
72 | ctype = http.DetectContentType(buf.Bytes())
73 | }
74 | w.Header().Set("Content-Type", ctype)
75 |
76 | templateInfo, err := os.Stat(templatePath)
77 | if err == nil {
78 | // add the Last-Modified header if we were able to read the stamp
79 | httpserver.SetLastModifiedHeader(w, templateInfo.ModTime())
80 | }
81 | buf.WriteTo(w)
82 |
83 | return http.StatusOK, nil
84 | }
85 | }
86 | }
87 |
88 | return t.Next.ServeHTTP(w, r)
89 | }
90 |
91 | // Templates is middleware to render templated files as the HTTP response.
92 | type Templates struct {
93 | Next httpserver.Handler
94 | Rules []Rule
95 | Root string
96 | FileSys http.FileSystem
97 | }
98 |
99 | // Rule represents a template rule. A template will only execute
100 | // with this rule if the request path matches the Path specified
101 | // and requests a resource with one of the extensions specified.
102 | type Rule struct {
103 | Path string
104 | Extensions []string
105 | IndexFiles []string
106 | Delims [2]string
107 | }
108 |
--------------------------------------------------------------------------------
/commands.go:
--------------------------------------------------------------------------------
1 | package caddy
2 |
3 | import (
4 | "errors"
5 | "runtime"
6 | "unicode"
7 |
8 | "github.com/flynn/go-shlex"
9 | )
10 |
11 | var runtimeGoos = runtime.GOOS
12 |
13 | // SplitCommandAndArgs takes a command string and parses it shell-style into the
14 | // command and its separate arguments.
15 | func SplitCommandAndArgs(command string) (cmd string, args []string, err error) {
16 | var parts []string
17 |
18 | if runtimeGoos == "windows" {
19 | parts = parseWindowsCommand(command) // parse it Windows-style
20 | } else {
21 | parts, err = parseUnixCommand(command) // parse it Unix-style
22 | if err != nil {
23 | err = errors.New("error parsing command: " + err.Error())
24 | return
25 | }
26 | }
27 |
28 | if len(parts) == 0 {
29 | err = errors.New("no command contained in '" + command + "'")
30 | return
31 | }
32 |
33 | cmd = parts[0]
34 | if len(parts) > 1 {
35 | args = parts[1:]
36 | }
37 |
38 | return
39 | }
40 |
41 | // parseUnixCommand parses a unix style command line and returns the
42 | // command and its arguments or an error
43 | func parseUnixCommand(cmd string) ([]string, error) {
44 | return shlex.Split(cmd)
45 | }
46 |
47 | // parseWindowsCommand parses windows command lines and
48 | // returns the command and the arguments as an array. It
49 | // should be able to parse commonly used command lines.
50 | // Only basic syntax is supported:
51 | // - spaces in double quotes are not token delimiters
52 | // - double quotes are escaped by either backspace or another double quote
53 | // - except for the above case backspaces are path separators (not special)
54 | //
55 | // Many sources point out that escaping quotes using backslash can be unsafe.
56 | // Use two double quotes when possible. (Source: http://stackoverflow.com/a/31413730/2616179 )
57 | //
58 | // This function has to be used on Windows instead
59 | // of the shlex package because this function treats backslash
60 | // characters properly.
61 | func parseWindowsCommand(cmd string) []string {
62 | const backslash = '\\'
63 | const quote = '"'
64 |
65 | var parts []string
66 | var part string
67 | var inQuotes bool
68 | var lastRune rune
69 |
70 | for i, ch := range cmd {
71 |
72 | if i != 0 {
73 | lastRune = rune(cmd[i-1])
74 | }
75 |
76 | if ch == backslash {
77 | // put it in the part - for now we don't know if it's an
78 | // escaping char or path separator
79 | part += string(ch)
80 | continue
81 | }
82 |
83 | if ch == quote {
84 | if lastRune == backslash {
85 | // remove the backslash from the part and add the escaped quote instead
86 | part = part[:len(part)-1]
87 | part += string(ch)
88 | continue
89 | }
90 |
91 | if lastRune == quote {
92 | // revert the last change of the inQuotes state
93 | // it was an escaping quote
94 | inQuotes = !inQuotes
95 | part += string(ch)
96 | continue
97 | }
98 |
99 | // normal escaping quotes
100 | inQuotes = !inQuotes
101 | continue
102 |
103 | }
104 |
105 | if unicode.IsSpace(ch) && !inQuotes && len(part) > 0 {
106 | parts = append(parts, part)
107 | part = ""
108 | continue
109 | }
110 |
111 | part += string(ch)
112 | }
113 |
114 | if len(part) > 0 {
115 | parts = append(parts, part)
116 | }
117 |
118 | return parts
119 | }
120 |
--------------------------------------------------------------------------------
/caddyhttp/status/setup_test.go:
--------------------------------------------------------------------------------
1 | package status
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/mholt/caddy"
7 | "github.com/mholt/caddy/caddyhttp/httpserver"
8 | )
9 |
10 | func TestSetup(t *testing.T) {
11 | c := caddy.NewTestController("http", `status 404 /foo`)
12 | err := setup(c)
13 | if err != nil {
14 | t.Errorf("Expected no errors, but got: %v", err)
15 | }
16 |
17 | mids := httpserver.GetConfig(c).Middleware()
18 | if len(mids) == 0 {
19 | t.Fatal("Expected middleware, had 0 instead")
20 | }
21 |
22 | handler := mids[0](httpserver.EmptyNext)
23 | myHandler, ok := handler.(Status)
24 | if !ok {
25 | t.Fatalf("Expected handler to be type Status, got: %#v",
26 | handler)
27 | }
28 |
29 | if !httpserver.SameNext(myHandler.Next, httpserver.EmptyNext) {
30 | t.Error("'Next' field of handler was not set properly")
31 | }
32 |
33 | if len(myHandler.Rules) != 1 {
34 | t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules))
35 | }
36 | }
37 |
38 | func TestStatusParse(t *testing.T) {
39 | tests := []struct {
40 | input string
41 | shouldErr bool
42 | expected []*Rule
43 | }{
44 | {`status`, true, []*Rule{}},
45 | {`status /foo`, true, []*Rule{}},
46 | {`status bar /foo`, true, []*Rule{}},
47 | {`status 404 /foo bar`, true, []*Rule{}},
48 | {`status 404 /foo`, false, []*Rule{
49 | {Base: "/foo", StatusCode: 404},
50 | },
51 | },
52 | {`status {
53 | }`,
54 | true,
55 | []*Rule{},
56 | },
57 | {`status 404 {
58 | }`,
59 | true,
60 | []*Rule{},
61 | },
62 | {`status 404 {
63 | /foo
64 | /foo
65 | }`,
66 | true,
67 | []*Rule{},
68 | },
69 | {`status 404 {
70 | 404 /foo
71 | }`,
72 | true,
73 | []*Rule{},
74 | },
75 | {`status 404 {
76 | /foo
77 | /bar
78 | }`,
79 | false,
80 | []*Rule{
81 | {Base: "/foo", StatusCode: 404},
82 | {Base: "/bar", StatusCode: 404},
83 | },
84 | },
85 | }
86 |
87 | for i, test := range tests {
88 | actual, err := statusParse(caddy.NewTestController("http",
89 | test.input))
90 |
91 | if err == nil && test.shouldErr {
92 | t.Errorf("Test %d didn't error, but it should have", i)
93 | } else if err != nil && !test.shouldErr {
94 | t.Errorf("Test %d errored, but it shouldn't have; got '%v'",
95 | i, err)
96 | } else if err != nil && test.shouldErr {
97 | continue
98 | }
99 |
100 | if len(actual) != len(test.expected) {
101 | t.Fatalf("Test %d expected %d rules, but got %d",
102 | i, len(test.expected), len(actual))
103 | }
104 |
105 | findRule := func(basePath string) (bool, *Rule) {
106 | for _, cfg := range actual {
107 | actualRule := cfg.(*Rule)
108 |
109 | if actualRule.Base == basePath {
110 | return true, actualRule
111 | }
112 | }
113 |
114 | return false, nil
115 | }
116 |
117 | for _, expectedRule := range test.expected {
118 | found, actualRule := findRule(expectedRule.Base)
119 |
120 | if !found {
121 | t.Errorf("Test %d: Missing rule for path '%s'",
122 | i, expectedRule.Base)
123 | }
124 |
125 | if actualRule.StatusCode != expectedRule.StatusCode {
126 | t.Errorf("Test %d: Expected status code %d for path '%s'. Got %d",
127 | i, expectedRule.StatusCode, expectedRule.Base, actualRule.StatusCode)
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/gzip_test.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/http"
7 | "net/http/httptest"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/mholt/caddy/caddyhttp/httpserver"
12 | )
13 |
14 | func TestGzipHandler(t *testing.T) {
15 | pathFilter := PathFilter{make(Set)}
16 | badPaths := []string{"/bad", "/nogzip", "/nongzip"}
17 | for _, p := range badPaths {
18 | pathFilter.IgnoredPaths.Add(p)
19 | }
20 | extFilter := ExtFilter{make(Set)}
21 | for _, e := range []string{".txt", ".html", ".css", ".md"} {
22 | extFilter.Exts.Add(e)
23 | }
24 | gz := Gzip{Configs: []Config{
25 | {RequestFilters: []RequestFilter{pathFilter, extFilter}},
26 | }}
27 |
28 | w := httptest.NewRecorder()
29 | gz.Next = nextFunc(true)
30 | var exts = []string{
31 | ".html", ".css", ".md",
32 | }
33 | for _, e := range exts {
34 | url := "/file" + e
35 | r, err := http.NewRequest("GET", url, nil)
36 | if err != nil {
37 | t.Error(err)
38 | }
39 | r.Header.Set("Accept-Encoding", "gzip")
40 | _, err = gz.ServeHTTP(w, r)
41 | if err != nil {
42 | t.Error(err)
43 | }
44 | }
45 |
46 | w = httptest.NewRecorder()
47 | gz.Next = nextFunc(false)
48 | for _, p := range badPaths {
49 | for _, e := range exts {
50 | url := p + "/file" + e
51 | r, err := http.NewRequest("GET", url, nil)
52 | if err != nil {
53 | t.Error(err)
54 | }
55 | r.Header.Set("Accept-Encoding", "gzip")
56 | _, err = gz.ServeHTTP(w, r)
57 | if err != nil {
58 | t.Error(err)
59 | }
60 | }
61 | }
62 |
63 | w = httptest.NewRecorder()
64 | gz.Next = nextFunc(false)
65 | exts = []string{
66 | ".htm1", ".abc", ".mdx",
67 | }
68 | for _, e := range exts {
69 | url := "/file" + e
70 | r, err := http.NewRequest("GET", url, nil)
71 | if err != nil {
72 | t.Error(err)
73 | }
74 | r.Header.Set("Accept-Encoding", "gzip")
75 | _, err = gz.ServeHTTP(w, r)
76 | if err != nil {
77 | t.Error(err)
78 | }
79 | }
80 | }
81 |
82 | func nextFunc(shouldGzip bool) httpserver.Handler {
83 | return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
84 | // write a relatively large text file
85 | b, err := ioutil.ReadFile("testdata/test.txt")
86 | if err != nil {
87 | return 500, err
88 | }
89 | if _, err := w.Write(b); err != nil {
90 | return 500, err
91 | }
92 |
93 | if shouldGzip {
94 | if w.Header().Get("Content-Encoding") != "gzip" {
95 | return 0, fmt.Errorf("Content-Encoding must be gzip, found %v", r.Header.Get("Content-Encoding"))
96 | }
97 | if w.Header().Get("Vary") != "Accept-Encoding" {
98 | return 0, fmt.Errorf("Vary must be Accept-Encoding, found %v", r.Header.Get("Vary"))
99 | }
100 | if _, ok := w.(*gzipResponseWriter); !ok {
101 | return 0, fmt.Errorf("ResponseWriter should be gzipResponseWriter, found %T", w)
102 | }
103 | if strings.Contains(w.Header().Get("Content-Type"), "application/x-gzip") {
104 | return 0, fmt.Errorf("Content-Type should not be gzip")
105 | }
106 | return 0, nil
107 | }
108 | if r.Header.Get("Accept-Encoding") == "" {
109 | return 0, fmt.Errorf("Accept-Encoding header expected")
110 | }
111 | if w.Header().Get("Content-Encoding") == "gzip" {
112 | return 0, fmt.Errorf("Content-Encoding must not be gzip, found gzip")
113 | }
114 | if _, ok := w.(*gzipResponseWriter); ok {
115 | return 0, fmt.Errorf("ResponseWriter should not be gzipResponseWriter")
116 | }
117 | return 0, nil
118 | })
119 | }
120 |
--------------------------------------------------------------------------------
/caddyhttp/fastcgi/dialer_test.go:
--------------------------------------------------------------------------------
1 | package fastcgi
2 |
3 | import (
4 | "errors"
5 | "testing"
6 | )
7 |
8 | func TestLoadbalancingDialer(t *testing.T) {
9 | // given
10 | runs := 100
11 | mockDialer1 := new(mockDialer)
12 | mockDialer2 := new(mockDialer)
13 |
14 | dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1, mockDialer2}}
15 |
16 | // when
17 | for i := 0; i < runs; i++ {
18 | client, err := dialer.Dial()
19 | dialer.Close(client)
20 |
21 | if err != nil {
22 | t.Errorf("Expected error to be nil")
23 | }
24 | }
25 |
26 | // then
27 | if mockDialer1.dialCalled != mockDialer2.dialCalled && mockDialer1.dialCalled != 50 {
28 | t.Errorf("Expected dialer to call Dial() on multiple backend dialers %d times [actual: %d, %d]", 50, mockDialer1.dialCalled, mockDialer2.dialCalled)
29 | }
30 |
31 | if mockDialer1.closeCalled != mockDialer2.closeCalled && mockDialer1.closeCalled != 50 {
32 | t.Errorf("Expected dialer to call Close() on multiple backend dialers %d times [actual: %d, %d]", 50, mockDialer1.closeCalled, mockDialer2.closeCalled)
33 | }
34 | }
35 |
36 | func TestLoadBalancingDialerShouldReturnDialerAwareClient(t *testing.T) {
37 | // given
38 | mockDialer1 := new(mockDialer)
39 | dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1}}
40 |
41 | // when
42 | client, err := dialer.Dial()
43 |
44 | // then
45 | if err != nil {
46 | t.Errorf("Expected error to be nil")
47 | }
48 |
49 | if awareClient, ok := client.(*dialerAwareClient); !ok {
50 | t.Error("Expected dialer to wrap client")
51 | } else {
52 | if awareClient.dialer != mockDialer1 {
53 | t.Error("Expected wrapped client to have reference to dialer")
54 | }
55 | }
56 | }
57 |
58 | func TestLoadBalancingDialerShouldUnderlyingReturnDialerError(t *testing.T) {
59 | // given
60 | mockDialer1 := new(errorReturningDialer)
61 | dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1}}
62 |
63 | // when
64 | _, err := dialer.Dial()
65 |
66 | // then
67 | if err.Error() != "Error during dial" {
68 | t.Errorf("Expected 'Error during dial', got: '%s'", err.Error())
69 | }
70 | }
71 |
72 | func TestLoadBalancingDialerShouldCloseClient(t *testing.T) {
73 | // given
74 | mockDialer1 := new(mockDialer)
75 | mockDialer2 := new(mockDialer)
76 |
77 | dialer := &loadBalancingDialer{dialers: []dialer{mockDialer1, mockDialer2}}
78 | client, _ := dialer.Dial()
79 |
80 | // when
81 | err := dialer.Close(client)
82 |
83 | // then
84 | if err != nil {
85 | t.Error("Expected error not to occur")
86 | }
87 |
88 | // load balancing starts from index 1
89 | if mockDialer2.client != client {
90 | t.Errorf("Expected Close() to be called on referenced dialer")
91 | }
92 | }
93 |
94 | type mockDialer struct {
95 | dialCalled int
96 | closeCalled int
97 | client Client
98 | }
99 |
100 | type mockClient struct {
101 | Client
102 | }
103 |
104 | func (m *mockDialer) Dial() (Client, error) {
105 | m.dialCalled++
106 | return mockClient{Client: &FCGIClient{}}, nil
107 | }
108 |
109 | func (m *mockDialer) Close(c Client) error {
110 | m.client = c
111 | m.closeCalled++
112 | return nil
113 | }
114 |
115 | type errorReturningDialer struct {
116 | client Client
117 | }
118 |
119 | func (m *errorReturningDialer) Dial() (Client, error) {
120 | return mockClient{Client: &FCGIClient{}}, errors.New("Error during dial")
121 | }
122 |
123 | func (m *errorReturningDialer) Close(c Client) error {
124 | m.client = c
125 | return errors.New("Error during close")
126 | }
127 |
--------------------------------------------------------------------------------
/caddyhttp/gzip/setup.go:
--------------------------------------------------------------------------------
1 | package gzip
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/mholt/caddy"
9 | "github.com/mholt/caddy/caddyhttp/httpserver"
10 | )
11 |
12 | // setup configures a new gzip middleware instance.
13 | func setup(c *caddy.Controller) error {
14 | configs, err := gzipParse(c)
15 | if err != nil {
16 | return err
17 | }
18 |
19 | httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
20 | return Gzip{Next: next, Configs: configs}
21 | })
22 |
23 | return nil
24 | }
25 |
26 | func gzipParse(c *caddy.Controller) ([]Config, error) {
27 | var configs []Config
28 |
29 | for c.Next() {
30 | config := Config{}
31 |
32 | // Request Filters
33 | pathFilter := PathFilter{IgnoredPaths: make(Set)}
34 | extFilter := ExtFilter{Exts: make(Set)}
35 |
36 | // Response Filters
37 | lengthFilter := LengthFilter(0)
38 |
39 | // No extra args expected
40 | if len(c.RemainingArgs()) > 0 {
41 | return configs, c.ArgErr()
42 | }
43 |
44 | for c.NextBlock() {
45 | switch c.Val() {
46 | case "ext":
47 | exts := c.RemainingArgs()
48 | if len(exts) == 0 {
49 | return configs, c.ArgErr()
50 | }
51 | for _, e := range exts {
52 | if !strings.HasPrefix(e, ".") && e != ExtWildCard && e != "" {
53 | return configs, fmt.Errorf(`gzip: invalid extension "%v" (must start with dot)`, e)
54 | }
55 | extFilter.Exts.Add(e)
56 | }
57 | case "not":
58 | paths := c.RemainingArgs()
59 | if len(paths) == 0 {
60 | return configs, c.ArgErr()
61 | }
62 | for _, p := range paths {
63 | if p == "/" {
64 | return configs, fmt.Errorf(`gzip: cannot exclude path "/" - remove directive entirely instead`)
65 | }
66 | if !strings.HasPrefix(p, "/") {
67 | return configs, fmt.Errorf(`gzip: invalid path "%v" (must start with /)`, p)
68 | }
69 | pathFilter.IgnoredPaths.Add(p)
70 | }
71 | case "level":
72 | if !c.NextArg() {
73 | return configs, c.ArgErr()
74 | }
75 | level, _ := strconv.Atoi(c.Val())
76 | config.Level = level
77 | case "min_length":
78 | if !c.NextArg() {
79 | return configs, c.ArgErr()
80 | }
81 | length, err := strconv.ParseInt(c.Val(), 10, 64)
82 | if err != nil {
83 | return configs, err
84 | } else if length == 0 {
85 | return configs, fmt.Errorf(`gzip: min_length must be greater than 0`)
86 | }
87 | lengthFilter = LengthFilter(length)
88 | default:
89 | return configs, c.ArgErr()
90 | }
91 | }
92 |
93 | // Request Filters
94 | config.RequestFilters = []RequestFilter{}
95 |
96 | // If ignored paths are specified, put in front to filter with path first
97 | if len(pathFilter.IgnoredPaths) > 0 {
98 | config.RequestFilters = []RequestFilter{pathFilter}
99 | }
100 |
101 | // Then, if extensions are specified, use those to filter.
102 | // Otherwise, use default extensions filter.
103 | if len(extFilter.Exts) > 0 {
104 | config.RequestFilters = append(config.RequestFilters, extFilter)
105 | } else {
106 | config.RequestFilters = append(config.RequestFilters, DefaultExtFilter())
107 | }
108 |
109 | config.ResponseFilters = append(config.ResponseFilters, SkipCompressedFilter{})
110 |
111 | // Response Filters
112 | // If min_length is specified, use it.
113 | if int64(lengthFilter) != 0 {
114 | config.ResponseFilters = append(config.ResponseFilters, lengthFilter)
115 | }
116 |
117 | configs = append(configs, config)
118 | }
119 |
120 | return configs, nil
121 | }
122 |
--------------------------------------------------------------------------------
/caddyhttp/markdown/metadata/metadata.go:
--------------------------------------------------------------------------------
1 | package metadata
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "time"
7 | )
8 |
9 | var (
10 | // Date format YYYY-MM-DD HH:MM:SS or YYYY-MM-DD
11 | timeLayout = []string{
12 | `2006-01-02 15:04:05-0700`,
13 | `2006-01-02 15:04:05`,
14 | `2006-01-02`,
15 | }
16 | )
17 |
18 | // Metadata stores a page's metadata
19 | type Metadata struct {
20 | // Page title
21 | Title string
22 |
23 | // Page template
24 | Template string
25 |
26 | // Publish date
27 | Date time.Time
28 |
29 | // Variables to be used with Template
30 | Variables map[string]interface{}
31 | }
32 |
33 | // NewMetadata returns a new Metadata struct, loaded with the given map
34 | func NewMetadata(parsedMap map[string]interface{}) Metadata {
35 | md := Metadata{
36 | Variables: make(map[string]interface{}),
37 | }
38 | md.load(parsedMap)
39 |
40 | return md
41 | }
42 |
43 | // load loads parsed values in parsedMap into Metadata
44 | func (m *Metadata) load(parsedMap map[string]interface{}) {
45 |
46 | // Pull top level things out
47 | if title, ok := parsedMap["title"]; ok {
48 | m.Title, _ = title.(string)
49 | }
50 | if template, ok := parsedMap["template"]; ok {
51 | m.Template, _ = template.(string)
52 | }
53 | if date, ok := parsedMap["date"].(string); ok {
54 | for _, layout := range timeLayout {
55 | if t, err := time.Parse(layout, date); err == nil {
56 | m.Date = t
57 | break
58 | }
59 | }
60 | }
61 |
62 | m.Variables = parsedMap
63 | }
64 |
65 | // Parser is a an interface that must be satisfied by each parser
66 | type Parser interface {
67 | // Initialize a parser
68 | Init(b *bytes.Buffer) bool
69 |
70 | // Type of metadata
71 | Type() string
72 |
73 | // Parsed metadata.
74 | Metadata() Metadata
75 |
76 | // Raw markdown.
77 | Markdown() []byte
78 | }
79 |
80 | // GetParser returns a parser for the given data
81 | func GetParser(buf []byte) Parser {
82 | for _, p := range parsers() {
83 | b := bytes.NewBuffer(buf)
84 | if p.Init(b) {
85 | return p
86 | }
87 | }
88 |
89 | return nil
90 | }
91 |
92 | // parsers returns all available parsers
93 | func parsers() []Parser {
94 | return []Parser{
95 | &TOMLParser{},
96 | &YAMLParser{},
97 | &JSONParser{},
98 |
99 | // This one must be last
100 | &NoneParser{},
101 | }
102 | }
103 |
104 | // Split out prefixed/suffixed metadata with given delimiter
105 | func splitBuffer(b *bytes.Buffer, delim string) (*bytes.Buffer, *bytes.Buffer) {
106 | scanner := bufio.NewScanner(b)
107 |
108 | // Read and check first line
109 | if !scanner.Scan() {
110 | return nil, nil
111 | }
112 | if string(bytes.TrimSpace(scanner.Bytes())) != delim {
113 | return nil, nil
114 | }
115 |
116 | // Accumulate metadata, until delimiter
117 | meta := bytes.NewBuffer(nil)
118 | for scanner.Scan() {
119 | if string(bytes.TrimSpace(scanner.Bytes())) == delim {
120 | break
121 | }
122 | if _, err := meta.Write(scanner.Bytes()); err != nil {
123 | return nil, nil
124 | }
125 | if _, err := meta.WriteRune('\n'); err != nil {
126 | return nil, nil
127 | }
128 | }
129 | // Make sure we saw closing delimiter
130 | if string(bytes.TrimSpace(scanner.Bytes())) != delim {
131 | return nil, nil
132 | }
133 |
134 | // The rest is markdown
135 | markdown := new(bytes.Buffer)
136 | for scanner.Scan() {
137 | if _, err := markdown.Write(scanner.Bytes()); err != nil {
138 | return nil, nil
139 | }
140 | if _, err := markdown.WriteRune('\n'); err != nil {
141 | return nil, nil
142 | }
143 | }
144 |
145 | return meta, markdown
146 | }
147 |
--------------------------------------------------------------------------------