fastcgi: Implement / redirect for index.php with php_fastcgi directive (#2754)

* fastcgi: Implement / redirect for index.php with php_fastcgi directive

See #2752 and https://caddy.community/t/v2-redirect-path-to-path-index-php-with-assets/6196?u=matt

* caddyhttp: MatchNegate implements json.Marshaler

* fastcgi: Add /index.php element to try_files matcher

* fastcgi: Make /index.php redirect permanent
This commit is contained in:
Matt Holt 2019-09-17 15:16:17 -06:00 committed by GitHub
parent d030bfdae0
commit 484cee1ac1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 9 deletions

View file

@ -66,9 +66,9 @@ type (
// MatchNegate matches requests by negating its matchers' results. // MatchNegate matches requests by negating its matchers' results.
MatchNegate struct { MatchNegate struct {
matchersRaw map[string]json.RawMessage MatchersRaw map[string]json.RawMessage `json:"-"`
matchers MatcherSet Matchers MatcherSet `json:"-"`
} }
// MatchStarlarkExpr matches requests by evaluating a Starlark expression. // MatchStarlarkExpr matches requests by evaluating a Starlark expression.
@ -400,7 +400,12 @@ func (MatchNegate) CaddyModule() caddy.ModuleInfo {
// the struct, but we need a struct because we need another // the struct, but we need a struct because we need another
// field just for the provisioned modules. // field just for the provisioned modules.
func (m *MatchNegate) UnmarshalJSON(data []byte) error { func (m *MatchNegate) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &m.matchersRaw) return json.Unmarshal(data, &m.MatchersRaw)
}
// MarshalJSON marshals m's matchers.
func (m MatchNegate) MarshalJSON() ([]byte, error) {
return json.Marshal(m.MatchersRaw)
} }
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
@ -411,21 +416,21 @@ func (m *MatchNegate) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// Provision loads the matcher modules to be negated. // Provision loads the matcher modules to be negated.
func (m *MatchNegate) Provision(ctx caddy.Context) error { func (m *MatchNegate) Provision(ctx caddy.Context) error {
for modName, rawMsg := range m.matchersRaw { for modName, rawMsg := range m.MatchersRaw {
val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) val, err := ctx.LoadModule("http.matchers."+modName, rawMsg)
if err != nil { if err != nil {
return fmt.Errorf("loading matcher module '%s': %v", modName, err) return fmt.Errorf("loading matcher module '%s': %v", modName, err)
} }
m.matchers = append(m.matchers, val.(RequestMatcher)) m.Matchers = append(m.Matchers, val.(RequestMatcher))
} }
m.matchersRaw = nil // allow GC to deallocate m.MatchersRaw = nil // allow GC to deallocate
return nil return nil
} }
// Match returns true if r matches m. Since this matcher negates the // Match returns true if r matches m. Since this matcher negates the
// embedded matchers, false is returned if any of its matchers match. // embedded matchers, false is returned if any of its matchers match.
func (m MatchNegate) Match(r *http.Request) bool { func (m MatchNegate) Match(r *http.Request) bool {
return !m.matchers.Match(r) return !m.Matchers.Match(r)
} }
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
@ -686,4 +691,7 @@ var (
_ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil) _ caddyfile.Unmarshaler = (*MatchHeaderRE)(nil)
_ caddyfile.Unmarshaler = (*MatchProtocol)(nil) _ caddyfile.Unmarshaler = (*MatchProtocol)(nil)
_ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil) _ caddyfile.Unmarshaler = (*MatchRemoteIP)(nil)
_ json.Marshaler = (*MatchNegate)(nil)
_ json.Unmarshaler = (*MatchNegate)(nil)
) )

View file

@ -16,6 +16,7 @@ package fastcgi
import ( import (
"encoding/json" "encoding/json"
"net/http"
"github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
@ -114,10 +115,30 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
return nil, h.ArgErr() return nil, h.ArgErr()
} }
// route to redirect to canonical path if index PHP file
redirMatcherSet := map[string]json.RawMessage{
"file": h.JSON(fileserver.MatchFile{
TryFiles: []string{"{http.request.uri.path}/index.php"},
}, nil),
"not": h.JSON(caddyhttp.MatchNegate{
MatchersRaw: map[string]json.RawMessage{
"path": h.JSON(caddyhttp.MatchPath{"*/"}, nil),
},
}, nil),
}
redirHandler := caddyhttp.StaticResponse{
StatusCode: caddyhttp.WeakString("308"),
Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}},
}
redirRoute := caddyhttp.Route{
MatcherSetsRaw: []map[string]json.RawMessage{redirMatcherSet},
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
}
// route to rewrite to PHP index file // route to rewrite to PHP index file
rewriteMatcherSet := map[string]json.RawMessage{ rewriteMatcherSet := map[string]json.RawMessage{
"file": h.JSON(fileserver.MatchFile{ "file": h.JSON(fileserver.MatchFile{
TryFiles: []string{"{http.request.uri.path}", "index.php"}, TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php"},
}, nil), }, nil),
} }
rewriteHandler := rewrite.Rewrite{ rewriteHandler := rewrite.Rewrite{
@ -175,7 +196,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// wrap ours in a subroute and return that // wrap ours in a subroute and return that
if hasUserMatcher { if hasUserMatcher {
subroute := caddyhttp.Subroute{ subroute := caddyhttp.Subroute{
Routes: caddyhttp.RouteList{rewriteRoute, rpRoute}, Routes: caddyhttp.RouteList{redirRoute, rewriteRoute, rpRoute},
} }
return []httpcaddyfile.ConfigValue{ return []httpcaddyfile.ConfigValue{
{ {
@ -191,6 +212,10 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
// if the user did not specify a matcher, then // if the user did not specify a matcher, then
// we can just use our own matchers // we can just use our own matchers
return []httpcaddyfile.ConfigValue{ return []httpcaddyfile.ConfigValue{
{
Class: "route",
Value: redirRoute,
},
{ {
Class: "route", Class: "route",
Value: rewriteRoute, Value: rewriteRoute,