From 7243454a96d6dcbe504af7287de1e68ab4b80c83 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Mon, 18 May 2020 14:15:38 -0400 Subject: [PATCH] fastcgi: `php_fastcgi` subdirectives to override shortcut behaviour (#3255) * fastcgi: Add new php_fastcgi subdirectives to override the shortcut * fastcgi: Support "index off" to disable redir and try_files * fastcgi: Remove whitespace to satisfy linter * fastcgi: Run gofmt * fastcgi: Make a new dispenser instead of using rewind * fastcgi: Some fmt * fastcgi: Add a couple adapt tests * fastcgi: Clean up for loops * fastcgi: Move adapt tests to separate files --- .../caddyfile_adapt/php_fastcgi_index_off.txt | 66 +++++++ .../php_fastcgi_subdirectives.txt | 118 +++++++++++++ .../reverseproxy/fastcgi/caddyfile.go | 161 +++++++++++++----- 3 files changed, 306 insertions(+), 39 deletions(-) create mode 100644 caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.txt create mode 100644 caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.txt diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.txt b/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.txt new file mode 100644 index 00000000..6d939b4d --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_index_off.txt @@ -0,0 +1,66 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + index off + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ] + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.txt b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.txt new file mode 100644 index 00000000..6733a9e2 --- /dev/null +++ b/caddytest/integration/caddyfile_adapt/php_fastcgi_subdirectives.txt @@ -0,0 +1,118 @@ +:8884 + +php_fastcgi localhost:9000 { + # some php_fastcgi-specific subdirectives + split .php .php5 + env VAR1 value1 + env VAR2 value2 + root /var/www + index index.php5 + + # passed through to reverse_proxy (directive order doesn't matter!) + lb_policy random +} +---------- +{ + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":8884" + ], + "routes": [ + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}/index.php5" + ] + }, + "not": [ + { + "path": [ + "*/" + ] + } + ] + } + ], + "handle": [ + { + "handler": "static_response", + "headers": { + "Location": [ + "{http.request.uri.path}/" + ] + }, + "status_code": 308 + } + ] + }, + { + "match": [ + { + "file": { + "try_files": [ + "{http.request.uri.path}", + "{http.request.uri.path}/index.php5", + "index.php5" + ], + "split_path": [ + ".php", + ".php5" + ] + } + } + ], + "handle": [ + { + "handler": "rewrite", + "uri": "{http.matchers.file.relative}" + } + ] + }, + { + "match": [ + { + "path": [ + "*.php", + "*.php5" + ] + } + ], + "handle": [ + { + "handler": "reverse_proxy", + "load_balancing": { + "selection_policy": { + "policy": "random" + } + }, + "transport": { + "env": { + "VAR1": "value1", + "VAR2": "value2" + }, + "protocol": "fastcgi", + "root": "/var/www", + "split_path": [ + ".php", + ".php5" + ] + }, + "upstreams": [ + { + "dial": "localhost:9000" + } + ] + } + ] + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index 8a5e27ee..7c06adcf 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -123,47 +123,133 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, h.ArgErr() } - // route to redirect to canonical path if index PHP file - redirMatcherSet := caddy.ModuleMap{ - "file": h.JSON(fileserver.MatchFile{ - TryFiles: []string{"{http.request.uri.path}/index.php"}, - }), - "not": h.JSON(caddyhttp.MatchNot{ - MatcherSetsRaw: []caddy.ModuleMap{ - { - "path": h.JSON(caddyhttp.MatchPath{"*/"}), - }, - }, - }), - } - redirHandler := caddyhttp.StaticResponse{ - StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)), - Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}}, - } - redirRoute := caddyhttp.Route{ - MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet}, - HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)}, + // set up the transport for FastCGI, and specifically PHP + fcgiTransport := Transport{} + + // set up the set of file extensions allowed to execute PHP code + extensions := []string{".php"} + + // set the default index file for the try_files rewrites + indexFile := "index.php" + + // make a new dispenser from the remaining tokens so that we + // can reset the dispenser back to this point for the + // reverse_proxy unmarshaler to read from it as well + dispenser := h.NewFromNextSegment() + + // read the subdirectives that we allow as overrides to + // the php_fastcgi shortcut + // NOTE: we delete the tokens as we go so that the reverse_proxy + // unmarshal doesn't see these subdirectives which it cannot handle + for dispenser.Next() { + for dispenser.NextBlock(0) { + switch dispenser.Val() { + case "root": + if !dispenser.NextArg() { + return nil, dispenser.ArgErr() + } + fcgiTransport.Root = dispenser.Val() + dispenser.Delete() + dispenser.Delete() + + case "split": + extensions = dispenser.RemainingArgs() + dispenser.Delete() + for range extensions { + dispenser.Delete() + } + if len(extensions) == 0 { + return nil, dispenser.ArgErr() + } + + case "env": + args := dispenser.RemainingArgs() + dispenser.Delete() + for range args { + dispenser.Delete() + } + if len(args) != 2 { + return nil, dispenser.ArgErr() + } + if fcgiTransport.EnvVars == nil { + fcgiTransport.EnvVars = make(map[string]string) + } + fcgiTransport.EnvVars[args[0]] = args[1] + + case "index": + args := dispenser.RemainingArgs() + dispenser.Delete() + for range args { + dispenser.Delete() + } + if len(args) != 1 { + return nil, dispenser.ArgErr() + } + indexFile = args[0] + } + } } - // route to rewrite to PHP index file - rewriteMatcherSet := caddy.ModuleMap{ - "file": h.JSON(fileserver.MatchFile{ - TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/index.php", "index.php"}, - SplitPath: []string{".php"}, - }), - } - rewriteHandler := rewrite.Rewrite{ - URI: "{http.matchers.file.relative}", - } - rewriteRoute := caddyhttp.Route{ - MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet}, - HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, + // reset the dispenser after we're done so that the reverse_proxy + // unmarshaler can read it from the start + dispenser.Reset() + + // set up a route list that we'll append to + routes := caddyhttp.RouteList{} + + // set the list of allowed path segments on which to split + fcgiTransport.SplitPath = extensions + + // if the index is turned off, we skip the redirect and try_files + if indexFile != "off" { + // route to redirect to canonical path if index PHP file + redirMatcherSet := caddy.ModuleMap{ + "file": h.JSON(fileserver.MatchFile{ + TryFiles: []string{"{http.request.uri.path}/" + indexFile}, + }), + "not": h.JSON(caddyhttp.MatchNot{ + MatcherSetsRaw: []caddy.ModuleMap{ + { + "path": h.JSON(caddyhttp.MatchPath{"*/"}), + }, + }, + }), + } + redirHandler := caddyhttp.StaticResponse{ + StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)), + Headers: http.Header{"Location": []string{"{http.request.uri.path}/"}}, + } + redirRoute := caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)}, + } + + // route to rewrite to PHP index file + rewriteMatcherSet := caddy.ModuleMap{ + "file": h.JSON(fileserver.MatchFile{ + TryFiles: []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}, + SplitPath: extensions, + }), + } + rewriteHandler := rewrite.Rewrite{ + URI: "{http.matchers.file.relative}", + } + rewriteRoute := caddyhttp.Route{ + MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet}, + HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)}, + } + + routes = append(routes, redirRoute, rewriteRoute) } // route to actually reverse proxy requests to PHP files; // match only requests that are for PHP files + pathList := []string{} + for _, ext := range extensions { + pathList = append(pathList, "*"+ext) + } rpMatcherSet := caddy.ModuleMap{ - "path": h.JSON([]string{"*.php"}), + "path": h.JSON(pathList), } // if the user specified a matcher token, use that @@ -176,9 +262,6 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error return nil, err } - // set up the transport for FastCGI, and specifically PHP - fcgiTransport := Transport{SplitPath: []string{".php"}} - // create the reverse proxy handler which uses our FastCGI transport rpHandler := &reverseproxy.Handler{ TransportRaw: caddyconfig.JSONModuleObject(fcgiTransport, "protocol", "fastcgi", nil), @@ -188,7 +271,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error // using the reverse_proxy directive syntax // TODO: this can overwrite our fcgiTransport that we encoded and // set on the rpHandler... even with a non-fastcgi transport! - err = rpHandler.UnmarshalCaddyfile(h.Dispenser) + err = rpHandler.UnmarshalCaddyfile(dispenser) if err != nil { return nil, err } @@ -201,7 +284,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error } subroute := caddyhttp.Subroute{ - Routes: caddyhttp.RouteList{redirRoute, rewriteRoute, rpRoute}, + Routes: append(routes, rpRoute), } // the user's matcher is a prerequisite for ours, so