summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2020-04-27 16:46:46 -0400
committerGitHub <noreply@github.com>2020-04-27 14:46:46 -0600
commit5ae1a5617c4bb1deef22cb3658ee581bb7dbf367 (patch)
tree84bb5582a6b1f961ad670f290d020b747ff26cee /modules
parent83c85c53f583906e438bb9eb30fc0ab57bf59108 (diff)
caddyhttp: Add split_path to file matcher (used by php_fastcgi) (#3302)
* matcher: Add `split_path` option to file matcher; used in php_fastcgi * matcher: Skip try_files split if not the final part of the filename * matcher: Add MatchFile tests * matcher: Clarify SplitPath godoc
Diffstat (limited to 'modules')
-rw-r--r--modules/caddyhttp/fileserver/matcher.go44
-rw-r--r--modules/caddyhttp/fileserver/matcher_test.go102
-rw-r--r--modules/caddyhttp/fileserver/testdata/foo.php.php/index.php1
-rw-r--r--modules/caddyhttp/fileserver/testdata/index.php1
-rw-r--r--modules/caddyhttp/fileserver/testdata/notphp.php.txt1
-rw-r--r--modules/caddyhttp/fileserver/testdata/remote.php1
-rw-r--r--modules/caddyhttp/replacer.go11
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go1
8 files changed, 158 insertions, 4 deletions
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go
index 1915fb7..1beb8ba 100644
--- a/modules/caddyhttp/fileserver/matcher.go
+++ b/modules/caddyhttp/fileserver/matcher.go
@@ -68,6 +68,17 @@ type MatchFile struct {
//
// Default is first_exist.
TryPolicy string `json:"try_policy,omitempty"`
+
+ // A list of delimiters to use to split the path in two
+ // when trying files. If empty, no splitting will
+ // occur, and the path will be tried as-is. For each
+ // split value, the left-hand side of the split,
+ // including the split value, will be the path tried.
+ // For example, the path `/remote.php/dav/` using the
+ // split value `.php` would try the file `/remote.php`.
+ // Each delimiter must appear at the end of a URI path
+ // component in order to be used as a split delimiter.
+ SplitPath []string `json:"split_path,omitempty"`
}
// CaddyModule returns the Caddy module information.
@@ -105,6 +116,11 @@ func (m *MatchFile) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return d.ArgErr()
}
m.TryPolicy = d.Val()
+ case "split":
+ m.SplitPath = d.RemainingArgs()
+ if len(m.SplitPath) == 0 {
+ return d.ArgErr()
+ }
}
}
}
@@ -167,7 +183,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
switch m.TryPolicy {
case "", tryPolicyFirstExist:
for _, f := range m.TryFiles {
- suffix := path.Clean(repl.ReplaceAll(f, ""))
+ suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
fullpath := sanitizedPathJoin(root, suffix)
if strictFileExists(fullpath) {
return suffix, fullpath, true
@@ -179,7 +195,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
var largestFilename string
var largestSuffix string
for _, f := range m.TryFiles {
- suffix := path.Clean(repl.ReplaceAll(f, ""))
+ suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
fullpath := sanitizedPathJoin(root, suffix)
info, err := os.Stat(fullpath)
if err == nil && info.Size() > largestSize {
@@ -195,7 +211,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
var smallestFilename string
var smallestSuffix string
for _, f := range m.TryFiles {
- suffix := path.Clean(repl.ReplaceAll(f, ""))
+ suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
fullpath := sanitizedPathJoin(root, suffix)
info, err := os.Stat(fullpath)
if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
@@ -211,7 +227,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {
var recentFilename string
var recentSuffix string
for _, f := range m.TryFiles {
- suffix := path.Clean(repl.ReplaceAll(f, ""))
+ suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, "")))
fullpath := sanitizedPathJoin(root, suffix)
info, err := os.Stat(fullpath)
if err == nil &&
@@ -256,6 +272,26 @@ func strictFileExists(file string) bool {
return !stat.IsDir()
}
+// firstSplit returns the first result where the path
+// can be split in two by a value in m.SplitPath. The
+// result is the first piece of the path that ends with
+// in the split value. Returns the path as-is if the
+// path cannot be split.
+func (m MatchFile) firstSplit(path string) string {
+ lowerPath := strings.ToLower(path)
+ for _, split := range m.SplitPath {
+ if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
+ pos := idx + len(split)
+ // skip the split if it's not the final part of the filename
+ if pos != len(path) && !strings.HasPrefix(path[pos:], "/") {
+ continue
+ }
+ return path[:pos]
+ }
+ }
+ return path
+}
+
const (
tryPolicyFirstExist = "first_exist"
tryPolicyLargestSize = "largest_size"
diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go
new file mode 100644
index 0000000..aa84900
--- /dev/null
+++ b/modules/caddyhttp/fileserver/matcher_test.go
@@ -0,0 +1,102 @@
+// Copyright 2015 Matthew Holt and The Caddy Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fileserver
+
+import (
+ "net/http"
+ "net/url"
+ "testing"
+
+ "github.com/caddyserver/caddy/v2/modules/caddyhttp"
+)
+
+func TestPhpFileMatcher(t *testing.T) {
+
+ for i, tc := range []struct {
+ path string
+ expectedPath string
+ matched bool
+ }{
+ {
+ path: "/index.php",
+ expectedPath: "/index.php",
+ matched: true,
+ },
+ {
+ path: "/index.php/somewhere",
+ expectedPath: "/index.php",
+ matched: true,
+ },
+ {
+ path: "/remote.php",
+ expectedPath: "/remote.php",
+ matched: true,
+ },
+ {
+ path: "/remote.php/somewhere",
+ expectedPath: "/remote.php",
+ matched: true,
+ },
+ {
+ path: "/missingfile.php",
+ matched: false,
+ },
+ {
+ path: "/notphp.php.txt",
+ expectedPath: "/notphp.php.txt",
+ matched: true,
+ },
+ {
+ path: "/notphp.php.txt/",
+ expectedPath: "/notphp.php.txt",
+ matched: true,
+ },
+ {
+ path: "/notphp.php.txt.suffixed",
+ matched: false,
+ },
+ {
+ path: "/foo.php.php/index.php",
+ expectedPath: "/foo.php.php/index.php",
+ matched: true,
+ },
+ } {
+ m := &MatchFile{
+ Root: "./testdata",
+ TryFiles: []string{"{http.request.uri.path}"},
+ SplitPath: []string{".php"},
+ }
+
+ req := &http.Request{URL: &url.URL{Path: tc.path}}
+ repl := caddyhttp.NewTestReplacer(req)
+
+ result := m.Match(req)
+ if result != tc.matched {
+ t.Fatalf("Test %d: match bool result: %v, expected: %v", i, result, tc.matched)
+ }
+
+ rel, ok := repl.Get("http.matchers.file.relative")
+ if !ok && result {
+ t.Fatalf("Test %d: expected replacer value", i)
+ }
+ if !result {
+ continue
+ }
+
+ if rel != tc.expectedPath {
+ t.Fatalf("Test %d: actual path: %v, expected: %v", i, rel, tc.expectedPath)
+ }
+ }
+} \ No newline at end of file
diff --git a/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php b/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php
new file mode 100644
index 0000000..4a2ac6b
--- /dev/null
+++ b/modules/caddyhttp/fileserver/testdata/foo.php.php/index.php
@@ -0,0 +1 @@
+foo.php.php/index.php
diff --git a/modules/caddyhttp/fileserver/testdata/index.php b/modules/caddyhttp/fileserver/testdata/index.php
new file mode 100644
index 0000000..0012f7d
--- /dev/null
+++ b/modules/caddyhttp/fileserver/testdata/index.php
@@ -0,0 +1 @@
+index.php \ No newline at end of file
diff --git a/modules/caddyhttp/fileserver/testdata/notphp.php.txt b/modules/caddyhttp/fileserver/testdata/notphp.php.txt
new file mode 100644
index 0000000..eba1876
--- /dev/null
+++ b/modules/caddyhttp/fileserver/testdata/notphp.php.txt
@@ -0,0 +1 @@
+notphp.php.txt
diff --git a/modules/caddyhttp/fileserver/testdata/remote.php b/modules/caddyhttp/fileserver/testdata/remote.php
new file mode 100644
index 0000000..78f06a2
--- /dev/null
+++ b/modules/caddyhttp/fileserver/testdata/remote.php
@@ -0,0 +1 @@
+remote.php \ No newline at end of file
diff --git a/modules/caddyhttp/replacer.go b/modules/caddyhttp/replacer.go
index b83977c..670b2e3 100644
--- a/modules/caddyhttp/replacer.go
+++ b/modules/caddyhttp/replacer.go
@@ -15,6 +15,7 @@
package caddyhttp
import (
+ "context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
@@ -30,6 +31,16 @@ import (
"github.com/caddyserver/caddy/v2/modules/caddytls"
)
+// NewTestReplacer creates a replacer for an http.Request
+// for use in tests that are not in this package
+func NewTestReplacer(req *http.Request) *caddy.Replacer {
+ repl := caddy.NewReplacer()
+ ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
+ *req = *req.WithContext(ctx)
+ addHTTPVarsToReplacer(repl, req, nil)
+ return repl
+}
+
func addHTTPVarsToReplacer(repl *caddy.Replacer, req *http.Request, w http.ResponseWriter) {
httpVars := func(key string) (interface{}, bool) {
if req != nil {
diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
index 8924a6d..8a5e27e 100644
--- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
@@ -149,6 +149,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
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{