Skip to content

Commit

Permalink
Merge branch 'master' into fix-6144
Browse files Browse the repository at this point in the history
  • Loading branch information
mholt committed Mar 6, 2024
2 parents 19c7826 + 258d906 commit 8303073
Show file tree
Hide file tree
Showing 9 changed files with 331 additions and 26 deletions.
90 changes: 82 additions & 8 deletions caddyconfig/httpcaddyfile/directives.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,25 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)

// directiveOrder specifies the order
// to apply directives in HTTP routes.
// defaultDirectiveOrder specifies the default order
// to apply directives in HTTP routes. This must only
// consist of directives that are included in Caddy's
// standard distribution.
//
// The root directive goes first in case rewrites or
// redirects depend on existence of files, i.e. the
// file matcher, which must know the root first.
// e.g. The 'root' directive goes near the start in
// case rewrites or redirects depend on existence of
// files, i.e. the file matcher, which must know the
// root first.
//
// The header directive goes second so that headers
// can be manipulated before doing redirects.
var directiveOrder = []string{
// e.g. The 'header' directive goes before 'redir' so
// that headers can be manipulated before doing redirects.
//
// e.g. The 'respond' directive is near the end because it
// writes a response and terminates the middleware chain.
var defaultDirectiveOrder = []string{
"tracing",

// set variables that may be used by other directives
"map",
"vars",
"fs",
Expand Down Expand Up @@ -85,6 +92,11 @@ var directiveOrder = []string{
"acme_server",
}

// directiveOrder specifies the order to apply directives
// in HTTP routes, after being modified by either the
// plugins or by the user via the "order" global option.
var directiveOrder = defaultDirectiveOrder

// directiveIsOrdered returns true if dir is
// a known, ordered (sorted) directive.
func directiveIsOrdered(dir string) bool {
Expand Down Expand Up @@ -131,6 +143,58 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
})
}

// RegisterDirectiveOrder registers the default order for a
// directive from a plugin.
//
// This is useful when a plugin has a well-understood place
// it should run in the middleware pipeline, and it allows
// users to avoid having to define the order themselves.
//
// The directive dir may be placed in the position relative
// to ('before' or 'after') a directive included in Caddy's
// standard distribution. It cannot be relative to another
// plugin's directive.
//
// EXPERIMENTAL: This API may change or be removed.
func RegisterDirectiveOrder(dir string, position Positional, standardDir string) {
// check if directive was already ordered
if directiveIsOrdered(dir) {
panic("directive '" + dir + "' already ordered")
}

if position != Before && position != After {
panic("the 2nd argument must be either 'before' or 'after', got '" + position + "'")
}

// check if directive exists in standard distribution, since
// we can't allow plugins to depend on one another; we can't
// guarantee the order that plugins are loaded in.
foundStandardDir := false
for _, d := range defaultDirectiveOrder {
if d == standardDir {
foundStandardDir = true
}
}
if !foundStandardDir {
panic("the 3rd argument '" + standardDir + "' must be a directive that exists in the standard distribution of Caddy")
}

// insert directive into proper position
newOrder := directiveOrder
for i, d := range newOrder {
if d != standardDir {
continue
}
if position == Before {
newOrder = append(newOrder[:i], append([]string{dir}, newOrder[i:]...)...)
} else if position == After {
newOrder = append(newOrder[:i+1], append([]string{dir}, newOrder[i+1:]...)...)
}
break
}
directiveOrder = newOrder
}

// RegisterGlobalOption registers a unique global option opt with
// an associated unmarshaling (setup) function. When the global
// option opt is encountered in a Caddyfile, setupFunc will be
Expand Down Expand Up @@ -555,6 +619,16 @@ func (sb serverBlock) isAllHTTP() bool {
return true
}

// Positional are the supported modes for ordering directives.
type Positional string

const (
Before Positional = "before"
After Positional = "after"
First Positional = "first"
Last Positional = "last"
)

type (
// UnmarshalFunc is a function which can unmarshal Caddyfile
// tokens into zero or more config values using a Helper type.
Expand Down
14 changes: 7 additions & 7 deletions caddyconfig/httpcaddyfile/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
if !d.Next() {
return nil, d.ArgErr()
}
pos := d.Val()
pos := Positional(d.Val())

newOrder := directiveOrder

Expand All @@ -121,22 +121,22 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {

// act on the positional
switch pos {
case "first":
case First:
newOrder = append([]string{dirName}, newOrder...)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case "last":
case Last:
newOrder = append(newOrder, dirName)
if d.NextArg() {
return nil, d.ArgErr()
}
directiveOrder = newOrder
return newOrder, nil
case "before":
case "after":
case Before:
case After:
default:
return nil, d.Errf("unknown positional '%s'", pos)
}
Expand All @@ -153,9 +153,9 @@ func parseOptOrder(d *caddyfile.Dispenser, _ any) (any, error) {
// insert directive into proper position
for i, d := range newOrder {
if d == otherDir {
if pos == "before" {
if pos == Before {
newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...)
} else if pos == "after" {
} else if pos == After {
newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...)
}
break
Expand Down
91 changes: 91 additions & 0 deletions caddytest/integration/caddyfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,97 @@ func TestUriReplace(t *testing.T) {
tester.AssertGetResponse("http://localhost:9080/endpoint?test={%20content%20}", 200, "test=%7B%20content%20%7D")
}

func TestUriOps(t *testing.T) {
tester := caddytest.NewTester(t)

tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query +foo bar
uri query -baz
uri query taz test
uri query key=value example
uri query changethis>changed
respond "{query}"`, "caddyfile")

tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest&changethis=val", 200, "changed=val&foo=bar0&foo=bar&key%3Dvalue=example&taz=test")
}

func TestSetThenAddQueryParams(t *testing.T) {
tester := caddytest.NewTester(t)

tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo bar
uri query +foo baz
respond "{query}"`, "caddyfile")

tester.AssertGetResponse("http://localhost:9080/endpoint", 200, "foo=bar&foo=baz")
}

func TestSetThenDeleteParams(t *testing.T) {
tester := caddytest.NewTester(t)

tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query bar foo{query.foo}
uri query -foo
respond "{query}"`, "caddyfile")

tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=foobar")
}

func TestRenameAndOtherOps(t *testing.T) {
tester := caddytest.NewTester(t)

tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query foo>bar
uri query bar taz
uri query +bar baz
respond "{query}"`, "caddyfile")

tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar", 200, "bar=taz&bar=baz")
}

func TestUriOpsBlock(t *testing.T) {
tester := caddytest.NewTester(t)

tester.InitServer(`
{
admin localhost:2999
http_port 9080
}
:9080
uri query {
+foo bar
-baz
taz test
}
respond "{query}"`, "caddyfile")

tester.AssertGetResponse("http://localhost:9080/endpoint?foo=bar0&baz=buz&taz=nottest", 200, "foo=bar0&foo=bar&taz=test")
}

func TestHandleErrorSimpleCodes(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`{
Expand Down
8 changes: 4 additions & 4 deletions modules/caddyhttp/celmatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,24 +381,24 @@ func CELMatcherImpl(macroName, funcName string, matcherDataTypes []*cel.Type, fa
type CELMatcherFactory func(data ref.Val) (RequestMatcher, error)

// matcherCELLibrary is a simplistic configurable cel.Library implementation.
type matcherCELLibary struct {
type matcherCELLibrary struct {
envOptions []cel.EnvOption
programOptions []cel.ProgramOption
}

// NewMatcherCELLibrary creates a matcherLibrary from option setes.
func NewMatcherCELLibrary(envOptions []cel.EnvOption, programOptions []cel.ProgramOption) cel.Library {
return &matcherCELLibary{
return &matcherCELLibrary{
envOptions: envOptions,
programOptions: programOptions,
}
}

func (lib *matcherCELLibary) CompileOptions() []cel.EnvOption {
func (lib *matcherCELLibrary) CompileOptions() []cel.EnvOption {
return lib.envOptions
}

func (lib *matcherCELLibary) ProgramOptions() []cel.ProgramOption {
func (lib *matcherCELLibrary) ProgramOptions() []cel.ProgramOption {
return lib.programOptions
}

Expand Down
14 changes: 10 additions & 4 deletions modules/caddyhttp/fileserver/staticfiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -639,12 +639,18 @@ func calculateEtag(d os.FileInfo) string {
return `"` + t + s + `"`
}

func redirect(w http.ResponseWriter, r *http.Request, to string) error {
for strings.HasPrefix(to, "//") {
// redirect performs a redirect to a given path. The 'toPath' parameter
// MUST be solely a path, and MUST NOT include a query.
func redirect(w http.ResponseWriter, r *http.Request, toPath string) error {
for strings.HasPrefix(toPath, "//") {
// prevent path-based open redirects
to = strings.TrimPrefix(to, "/")
toPath = strings.TrimPrefix(toPath, "/")
}
http.Redirect(w, r, to, http.StatusPermanentRedirect)
// preserve the query string if present
if r.URL.RawQuery != "" {
toPath += "?" + r.URL.RawQuery
}
http.Redirect(w, r, toPath, http.StatusPermanentRedirect)
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion modules/caddyhttp/reverseproxy/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func init() {
// reverse proxy upstreams in the pool.
type adminUpstreams struct{}

// upstreamResults holds the status of a particular upstream
// upstreamStatus holds the status of a particular upstream
type upstreamStatus struct {
Address string `json:"address"`
NumRequests int `json:"num_requests"`
Expand Down
Loading

0 comments on commit 8303073

Please sign in to comment.