mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-05 18:44:58 +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},
|
||||
{"ext", setup.Ext},
|
||||
{"basicauth", setup.BasicAuth},
|
||||
{"internal", setup.Internal},
|
||||
{"proxy", setup.Proxy},
|
||||
{"fastcgi", setup.FastCGI},
|
||||
{"websocket", setup.WebSocket},
|
||||
|
|
|
@ -109,6 +109,10 @@ func (d *Dispenser) NextBlock() bool {
|
|||
return false
|
||||
}
|
||||
d.Next()
|
||||
if d.Val() == "}" {
|
||||
// Open and then closed right away
|
||||
return false
|
||||
}
|
||||
d.nesting++
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -149,9 +149,8 @@ func TestDispenser_NextBlock(t *testing.T) {
|
|||
assertNextBlock(true, 3, 1)
|
||||
assertNextBlock(true, 4, 1)
|
||||
assertNextBlock(false, 5, 0)
|
||||
d.Next() // foobar2
|
||||
assertNextBlock(true, 8, 1)
|
||||
assertNextBlock(false, 8, 0)
|
||||
d.Next() // foobar2
|
||||
assertNextBlock(false, 8, 0) // empty block is as if it didn't exist
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/caddy/config/parse"
|
||||
"github.com/mholt/caddy/middleware"
|
||||
"github.com/mholt/caddy/server"
|
||||
)
|
||||
|
||||
|
@ -15,3 +18,15 @@ func newTestController(input string) *Controller {
|
|||
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 (
|
||||
conf string
|
||||
http2 bool // TODO: temporary flag until http2 is standard
|
||||
quiet bool
|
||||
cpu string
|
||||
conf string
|
||||
http2 bool // TODO: temporary flag until http2 is standard
|
||||
quiet bool
|
||||
cpu string
|
||||
version bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -34,6 +35,7 @@ func init() {
|
|||
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
|
||||
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
||||
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
||||
flag.BoolVar(&version, "version", false, "Show version")
|
||||
|
||||
config.AppName = "Caddy"
|
||||
config.AppVersion = "0.6.0"
|
||||
|
@ -42,6 +44,11 @@ func init() {
|
|||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if version {
|
||||
fmt.Printf("%s %s\n", config.AppName, config.AppVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// 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