mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-08 11:58:49 +03:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
2c7de8f328
11 changed files with 422 additions and 7 deletions
|
@ -59,6 +59,7 @@ var directiveOrder = []directive{
|
||||||
{"redir", setup.Redir},
|
{"redir", setup.Redir},
|
||||||
{"ext", setup.Ext},
|
{"ext", setup.Ext},
|
||||||
{"basicauth", setup.BasicAuth},
|
{"basicauth", setup.BasicAuth},
|
||||||
|
{"internal", setup.Internal},
|
||||||
{"proxy", setup.Proxy},
|
{"proxy", setup.Proxy},
|
||||||
{"fastcgi", setup.FastCGI},
|
{"fastcgi", setup.FastCGI},
|
||||||
{"websocket", setup.WebSocket},
|
{"websocket", setup.WebSocket},
|
||||||
|
|
|
@ -109,6 +109,10 @@ func (d *Dispenser) NextBlock() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
d.Next()
|
d.Next()
|
||||||
|
if d.Val() == "}" {
|
||||||
|
// Open and then closed right away
|
||||||
|
return false
|
||||||
|
}
|
||||||
d.nesting++
|
d.nesting++
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,9 +149,8 @@ func TestDispenser_NextBlock(t *testing.T) {
|
||||||
assertNextBlock(true, 3, 1)
|
assertNextBlock(true, 3, 1)
|
||||||
assertNextBlock(true, 4, 1)
|
assertNextBlock(true, 4, 1)
|
||||||
assertNextBlock(false, 5, 0)
|
assertNextBlock(false, 5, 0)
|
||||||
d.Next() // foobar2
|
d.Next() // foobar2
|
||||||
assertNextBlock(true, 8, 1)
|
assertNextBlock(false, 8, 0) // empty block is as if it didn't exist
|
||||||
assertNextBlock(false, 8, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDispenser_Args(t *testing.T) {
|
func TestDispenser_Args(t *testing.T) {
|
||||||
|
|
100
config/setup/basicauth_test.go
Normal file
100
config/setup/basicauth_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware/basicauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBasicAuth(t *testing.T) {
|
||||||
|
c := newTestController(`basicauth user pwd`)
|
||||||
|
|
||||||
|
mid, err := BasicAuth(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
if mid == nil {
|
||||||
|
t.Fatal("Expected middleware, was nil instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := mid(emptyNext)
|
||||||
|
myHandler, ok := handler.(basicauth.BasicAuth)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected handler to be type BasicAuth, got: %#v", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameNext(myHandler.Next, emptyNext) {
|
||||||
|
t.Error("'Next' field of handler was not set properly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasicAuthParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
shouldErr bool
|
||||||
|
expected []basicauth.Rule
|
||||||
|
}{
|
||||||
|
{`basicauth user pwd`, false, []basicauth.Rule{
|
||||||
|
{Username: "user", Password: "pwd"},
|
||||||
|
}},
|
||||||
|
{`basicauth user pwd {
|
||||||
|
}`, false, []basicauth.Rule{
|
||||||
|
{Username: "user", Password: "pwd"},
|
||||||
|
}},
|
||||||
|
{`basicauth user pwd {
|
||||||
|
/resource1
|
||||||
|
/resource2
|
||||||
|
}`, false, []basicauth.Rule{
|
||||||
|
{Username: "user", Password: "pwd", Resources: []string{"/resource1", "/resource2"}},
|
||||||
|
}},
|
||||||
|
{`basicauth /resource user pwd`, false, []basicauth.Rule{
|
||||||
|
{Username: "user", Password: "pwd", Resources: []string{"/resource"}},
|
||||||
|
}},
|
||||||
|
{`basicauth /res1 user1 pwd1
|
||||||
|
basicauth /res2 user2 pwd2`, false, []basicauth.Rule{
|
||||||
|
{Username: "user1", Password: "pwd1", Resources: []string{"/res1"}},
|
||||||
|
{Username: "user2", Password: "pwd2", Resources: []string{"/res2"}},
|
||||||
|
}},
|
||||||
|
{`basicauth user`, true, []basicauth.Rule{}},
|
||||||
|
{`basicauth`, true, []basicauth.Rule{}},
|
||||||
|
{`basicauth /resource user pwd asdf`, true, []basicauth.Rule{}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
c := newTestController(test.input)
|
||||||
|
actual, err := basicAuthParse(c)
|
||||||
|
|
||||||
|
if err == nil && test.shouldErr {
|
||||||
|
t.Errorf("Test %d didn't error, but it should have", i)
|
||||||
|
} else if err != nil && !test.shouldErr {
|
||||||
|
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actual) != len(test.expected) {
|
||||||
|
t.Fatalf("Test %d expected %d rules, but got %d",
|
||||||
|
i, len(test.expected), len(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, expectedRule := range test.expected {
|
||||||
|
actualRule := actual[j]
|
||||||
|
|
||||||
|
if actualRule.Username != expectedRule.Username {
|
||||||
|
t.Errorf("Test %d, rule %d: Expected username '%s', got '%s'",
|
||||||
|
i, j, expectedRule.Username, actualRule.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualRule.Password != expectedRule.Password {
|
||||||
|
t.Errorf("Test %d, rule %d: Expected password '%s', got '%s'",
|
||||||
|
i, j, expectedRule.Password, actualRule.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedRes := fmt.Sprintf("%v", expectedRule.Resources)
|
||||||
|
actualRes := fmt.Sprintf("%v", actualRule.Resources)
|
||||||
|
if actualRes != expectedRes {
|
||||||
|
t.Errorf("Test %d, rule %d: Expected resource list %s, but got %s",
|
||||||
|
i, j, expectedRes, actualRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mholt/caddy/config/parse"
|
"github.com/mholt/caddy/config/parse"
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
"github.com/mholt/caddy/server"
|
"github.com/mholt/caddy/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,3 +18,15 @@ func newTestController(input string) *Controller {
|
||||||
Dispenser: parse.NewDispenser("Testfile", strings.NewReader(input)),
|
Dispenser: parse.NewDispenser("Testfile", strings.NewReader(input)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emptyNext is a no-op function that can be passed into
|
||||||
|
// middleware.Middleware functions so that the assignment
|
||||||
|
// to the Next field of the Handler can be tested.
|
||||||
|
var emptyNext = middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// sameNext does a pointer comparison between next1 and next2.
|
||||||
|
func sameNext(next1, next2 middleware.Handler) bool {
|
||||||
|
return fmt.Sprintf("%p", next1) == fmt.Sprintf("%p", next2)
|
||||||
|
}
|
||||||
|
|
29
config/setup/gzip_test.go
Normal file
29
config/setup/gzip_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware/gzip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGzip(t *testing.T) {
|
||||||
|
c := newTestController(`gzip`)
|
||||||
|
|
||||||
|
mid, err := Gzip(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
if mid == nil {
|
||||||
|
t.Fatal("Expected middleware, was nil instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := mid(emptyNext)
|
||||||
|
myHandler, ok := handler.(gzip.Gzip)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected handler to be type Gzip, got: %#v", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameNext(myHandler.Next, emptyNext) {
|
||||||
|
t.Error("'Next' field of handler was not set properly")
|
||||||
|
}
|
||||||
|
}
|
31
config/setup/internal.go
Normal file
31
config/setup/internal.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
"github.com/mholt/caddy/middleware/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Internal configures a new Internal middleware instance.
|
||||||
|
func Internal(c *Controller) (middleware.Middleware, error) {
|
||||||
|
paths, err := internalParse(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
|
return internal.Internal{Next: next, Paths: paths}
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalParse(c *Controller) ([]string, error) {
|
||||||
|
var paths []string
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
if !c.NextArg() {
|
||||||
|
return paths, c.ArgErr()
|
||||||
|
}
|
||||||
|
paths = append(paths, c.Val())
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths, nil
|
||||||
|
}
|
85
config/setup/rewrite_test.go
Normal file
85
config/setup/rewrite_test.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware/rewrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRewrite(t *testing.T) {
|
||||||
|
c := newTestController(`rewrite /from /to`)
|
||||||
|
|
||||||
|
mid, err := Rewrite(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no errors, but got: %v", err)
|
||||||
|
}
|
||||||
|
if mid == nil {
|
||||||
|
t.Fatal("Expected middleware, was nil instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := mid(emptyNext)
|
||||||
|
myHandler, ok := handler.(rewrite.Rewrite)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Expected handler to be type Rewrite, got: %#v", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameNext(myHandler.Next, emptyNext) {
|
||||||
|
t.Error("'Next' field of handler was not set properly")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(myHandler.Rules) != 1 {
|
||||||
|
t.Errorf("Expected handler to have %d rule, has %d instead", 1, len(myHandler.Rules))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRewriteParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
shouldErr bool
|
||||||
|
expected []rewrite.Rule
|
||||||
|
}{
|
||||||
|
{`rewrite /from /to`, false, []rewrite.Rule{
|
||||||
|
{From: "/from", To: "/to"},
|
||||||
|
}},
|
||||||
|
{`rewrite /from /to
|
||||||
|
rewrite a b`, false, []rewrite.Rule{
|
||||||
|
{From: "/from", To: "/to"},
|
||||||
|
{From: "a", To: "b"},
|
||||||
|
}},
|
||||||
|
{`rewrite a`, true, []rewrite.Rule{}},
|
||||||
|
{`rewrite`, true, []rewrite.Rule{}},
|
||||||
|
{`rewrite a b c`, true, []rewrite.Rule{
|
||||||
|
{From: "a", To: "b"},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
c := newTestController(test.input)
|
||||||
|
actual, err := rewriteParse(c)
|
||||||
|
|
||||||
|
if err == nil && test.shouldErr {
|
||||||
|
t.Errorf("Test %d didn't error, but it should have", i)
|
||||||
|
} else if err != nil && !test.shouldErr {
|
||||||
|
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(actual) != len(test.expected) {
|
||||||
|
t.Fatalf("Test %d expected %d rules, but got %d",
|
||||||
|
i, len(test.expected), len(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, expectedRule := range test.expected {
|
||||||
|
actualRule := actual[j]
|
||||||
|
|
||||||
|
if actualRule.From != expectedRule.From {
|
||||||
|
t.Errorf("Test %d, rule %d: Expected From=%s, got %s",
|
||||||
|
i, j, expectedRule.From, actualRule.From)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualRule.To != expectedRule.To {
|
||||||
|
t.Errorf("Test %d, rule %d: Expected To=%s, got %s",
|
||||||
|
i, j, expectedRule.To, actualRule.To)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
main.go
15
main.go
|
@ -20,10 +20,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
conf string
|
conf string
|
||||||
http2 bool // TODO: temporary flag until http2 is standard
|
http2 bool // TODO: temporary flag until http2 is standard
|
||||||
quiet bool
|
quiet bool
|
||||||
cpu string
|
cpu string
|
||||||
|
version bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -34,6 +35,7 @@ func init() {
|
||||||
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
|
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
|
||||||
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
||||||
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
||||||
|
flag.BoolVar(&version, "version", false, "Show version")
|
||||||
|
|
||||||
config.AppName = "Caddy"
|
config.AppName = "Caddy"
|
||||||
config.AppVersion = "0.6.0"
|
config.AppVersion = "0.6.0"
|
||||||
|
@ -42,6 +44,11 @@ func init() {
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
if version {
|
||||||
|
fmt.Printf("%s %s\n", config.AppName, config.AppVersion)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// Set CPU cap
|
// Set CPU cap
|
||||||
|
|
91
middleware/internal/internal.go
Normal file
91
middleware/internal/internal.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// The package internal provides a simple middleware that (a) prevents access
|
||||||
|
// to internal locations and (b) allows to return files from internal location
|
||||||
|
// by setting a special header, e.g. in a proxy response.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Internal middleware protects internal locations from external requests -
|
||||||
|
// but allows access from the inside by using a special HTTP header.
|
||||||
|
type Internal struct {
|
||||||
|
Next middleware.Handler
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
redirectHeader string = "X-Accel-Redirect"
|
||||||
|
maxRedirectCount int = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
func isInternalRedirect(w http.ResponseWriter) bool {
|
||||||
|
return w.Header().Get(redirectHeader) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP implements the middlware.Handler interface.
|
||||||
|
func (i Internal) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
|
||||||
|
// Internal location requested? -> Not found.
|
||||||
|
for _, prefix := range i.Paths {
|
||||||
|
if middleware.Path(r.URL.Path).Matches(prefix) {
|
||||||
|
return http.StatusNotFound, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use internal response writer to ignore responses that will be
|
||||||
|
// redirected to internal locations
|
||||||
|
iw := internalResponseWriter{ResponseWriter: w}
|
||||||
|
status, err := i.Next.ServeHTTP(iw, r)
|
||||||
|
|
||||||
|
for c := 0; c < maxRedirectCount && isInternalRedirect(iw); c++ {
|
||||||
|
// Redirect - adapt request URL path and send it again
|
||||||
|
// "down the chain"
|
||||||
|
r.URL.Path = iw.Header().Get(redirectHeader)
|
||||||
|
iw.ClearHeader()
|
||||||
|
|
||||||
|
status, err = i.Next.ServeHTTP(iw, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isInternalRedirect(iw) {
|
||||||
|
// Too many redirect cycles
|
||||||
|
iw.ClearHeader()
|
||||||
|
return http.StatusInternalServerError, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// internalResponseWriter wraps the underlying http.ResponseWriter and ignores
|
||||||
|
// calls to Write and WriteHeader if the response should be redirected to an
|
||||||
|
// internal location.
|
||||||
|
type internalResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHeader removes all header fields that are already set.
|
||||||
|
func (w internalResponseWriter) ClearHeader() {
|
||||||
|
for k := range w.Header() {
|
||||||
|
w.Header().Del(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader ignores the call if the response should be redirected to an
|
||||||
|
// internal location.
|
||||||
|
func (w internalResponseWriter) WriteHeader(code int) {
|
||||||
|
if !isInternalRedirect(w) {
|
||||||
|
w.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write ignores the call if the response should be redirected to an internal
|
||||||
|
// location.
|
||||||
|
func (w internalResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
if isInternalRedirect(w) {
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
return w.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
}
|
53
middleware/rewrite/rewrite_test.go
Normal file
53
middleware/rewrite/rewrite_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package rewrite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRewrite(t *testing.T) {
|
||||||
|
rw := Rewrite{
|
||||||
|
Next: middleware.HandlerFunc(urlPrinter),
|
||||||
|
Rules: []Rule{
|
||||||
|
{From: "/from", To: "/to"},
|
||||||
|
{From: "/a", To: "/b"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
from string
|
||||||
|
expectedTo string
|
||||||
|
}{
|
||||||
|
{"/from", "/to"},
|
||||||
|
{"/a", "/b"},
|
||||||
|
{"/aa", "/aa"},
|
||||||
|
{"/", "/"},
|
||||||
|
{"/a?foo=bar", "/b?foo=bar"},
|
||||||
|
{"/asdf?foo=bar", "/asdf?foo=bar"},
|
||||||
|
{"/foo#bar", "/foo#bar"},
|
||||||
|
{"/a#foo", "/b#foo"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
req, err := http.NewRequest("GET", test.from, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %d: Could not create HTTP request: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
rw.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Body.String() != test.expectedTo {
|
||||||
|
t.Errorf("Test %d: Expected URL to be '%s' but was '%s'",
|
||||||
|
i, test.expectedTo, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlPrinter(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
|
fmt.Fprintf(w, r.URL.String())
|
||||||
|
return 0, nil
|
||||||
|
}
|
Loading…
Reference in a new issue