From a4581868cd315d8a34f8f535ec3a26154588e21b Mon Sep 17 00:00:00 2001 From: a Date: Thu, 21 Sep 2023 13:04:41 -0500 Subject: [PATCH 1/4] try_files --- modules/caddyfs/filesystem.go | 2 ++ modules/caddyhttp/fileserver/matcher.go | 28 ++++++++++++++------- modules/caddyhttp/fileserver/staticfiles.go | 4 ++- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/modules/caddyfs/filesystem.go b/modules/caddyfs/filesystem.go index bdfeae405f1c..51993be19356 100644 --- a/modules/caddyfs/filesystem.go +++ b/modules/caddyfs/filesystem.go @@ -9,6 +9,7 @@ import ( "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" + "go.uber.org/zap" ) func init() { @@ -66,6 +67,7 @@ func (xs *Filesystems) Provision(ctx caddy.Context) error { f.fileSystem = mod.(fs.FS) } // register that module + ctx.Logger().Debug("registering fs", zap.String("fs", f.Key)) ctx.Filesystems().Register(f.Key, f.fileSystem) // remember to unregister the module when we are done xs.defers = append(xs.defers, func() { diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 688e7707f127..c11946fbf05f 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -35,6 +35,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" + "github.com/caddyserver/caddy/v2/internal/filesystems" "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) @@ -64,7 +65,6 @@ type MatchFile struct { // The file system implementation to use. By default, the // local disk file system will be used. FileSystem string `json:"fs,omitempty"` - fileSystem fs.FS // The root directory, used for creating absolute // file paths, and required when working with @@ -107,6 +107,8 @@ type MatchFile struct { // component in order to be used as a split delimiter. SplitPath []string `json:"split_path,omitempty"` + fsmap *filesystems.FilesystemMap + logger *zap.Logger } @@ -269,12 +271,16 @@ func celFileMatcherMacroExpander() parser.MacroExpander { func (m *MatchFile) Provision(ctx caddy.Context) error { m.logger = ctx.Logger() - m.fileSystem, _ = ctx.Filesystems().Get(m.FileSystem) + m.fsmap = ctx.Filesystems() if m.Root == "" { m.Root = "{http.vars.root}" } + if m.FileSystem == "" { + m.FileSystem = "{http.vars.fs}" + } + // if list of files to try was omitted entirely, assume URL path // (use placeholder instead of r.URL.Path; see issue #4146) if m.TryFiles == nil { @@ -315,6 +321,10 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { root := filepath.Clean(repl.ReplaceAll(m.Root, ".")) + fsName := repl.ReplaceAll(m.FileSystem, "") + + fileSystem, _ := m.fsmap.Get(fsName) + type matchCandidate struct { fullpath, relative, splitRemainder string } @@ -363,7 +373,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { if runtime.GOOS == "windows" { globResults = []string{fullPattern} // precious Windows } else { - globResults, err = fs.Glob(m.fileSystem, fullPattern) + globResults, err = fs.Glob(fileSystem, fullPattern) if err != nil { m.logger.Error("expanding glob", zap.Error(err)) } @@ -405,7 +415,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { } candidates := makeCandidates(pattern) for _, c := range candidates { - if info, exists := m.strictFileExists(c.fullpath); exists { + if info, exists := m.strictFileExists(fileSystem, c.fullpath); exists { setPlaceholders(c, info) return true } @@ -419,7 +429,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { for _, pattern := range m.TryFiles { candidates := makeCandidates(pattern) for _, c := range candidates { - info, err := fs.Stat(m.fileSystem, c.fullpath) + info, err := fs.Stat(fileSystem, c.fullpath) if err == nil && info.Size() > largestSize { largestSize = info.Size() largest = c @@ -440,7 +450,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { for _, pattern := range m.TryFiles { candidates := makeCandidates(pattern) for _, c := range candidates { - info, err := fs.Stat(m.fileSystem, c.fullpath) + info, err := fs.Stat(fileSystem, c.fullpath) if err == nil && (smallestSize == 0 || info.Size() < smallestSize) { smallestSize = info.Size() smallest = c @@ -460,7 +470,7 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { for _, pattern := range m.TryFiles { candidates := makeCandidates(pattern) for _, c := range candidates { - info, err := fs.Stat(m.fileSystem, c.fullpath) + info, err := fs.Stat(fileSystem, c.fullpath) if err == nil && (recentInfo == nil || info.ModTime().After(recentInfo.ModTime())) { recent = c @@ -498,8 +508,8 @@ func parseErrorCode(input string) error { // the file must also be a directory; if it does // NOT end in a forward slash, the file must NOT // be a directory. -func (m MatchFile) strictFileExists(file string) (os.FileInfo, bool) { - info, err := fs.Stat(m.fileSystem, file) +func (m MatchFile) strictFileExists(fileSystem fs.FS, file string) (os.FileInfo, bool) { + info, err := fs.Stat(fileSystem, file) if err != nil { // in reality, this can be any error // such as permission or even obscure diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index 47631d78eef3..de4ba0971eb8 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -252,14 +252,16 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c filesToHide := fsrv.transformHidePaths(repl) root := repl.ReplaceAll(fsrv.Root, ".") + fsName := repl.ReplaceAll(fsrv.FileSystem, "") - fileSystem, _ := fsrv.fsmap.Get(repl.ReplaceAll(fsrv.FileSystem, "")) + fileSystem, _ := fsrv.fsmap.Get(fsName) // remove any trailing `/` as it breaks fs.ValidPath() in the stdlib filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/") fsrv.logger.Debug("sanitized path join", zap.String("site_root", root), + zap.String("fs", fsName), zap.String("request_path", r.URL.Path), zap.String("result", filename)) From fc80a5d8e428b0cead1c99988ee678f33439f680 Mon Sep 17 00:00:00 2001 From: a Date: Thu, 21 Sep 2023 13:24:36 -0500 Subject: [PATCH 2/4] remove unused --- internal/filesystems/map.go | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/internal/filesystems/map.go b/internal/filesystems/map.go index 7de5a94d627a..e9fe751c3096 100644 --- a/internal/filesystems/map.go +++ b/internal/filesystems/map.go @@ -2,6 +2,7 @@ package filesystems import ( "io/fs" + "sync" ) const ( @@ -9,7 +10,7 @@ const ( ) var ( - DefaultFilesystem = OsFS{} + DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}} ) type emptyFs struct { @@ -19,6 +20,7 @@ func (emptyFs) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist } +// wrapperFs exists so can easily add to wrapperFs down the line type wrapperFs struct { key string fs.FS @@ -28,58 +30,56 @@ type wrapperFs struct { // the empty key will be overwritten to be the default key // it includes a default filesystem, based off the os fs type FilesystemMap struct { - m map[string]*wrapperFs + m sync.Map } -func (f *FilesystemMap) get(k string) *wrapperFs { - if f.m == nil { - f.m = map[string]*wrapperFs{ - DefaultFilesystemKey: {key: DefaultFilesystemKey, FS: DefaultFilesystem}, - } - } +// note that the first invocation of key cannot be called in a racy context. +func (f *FilesystemMap) key(k string) string { if k == "" { k = DefaultFilesystemKey } - _, ok := f.m[k] - if !ok { - f.m[k] = &wrapperFs{ - key: k, - FS: emptyFs{}, - } - } - return f.m[k] + return k } // Register will add the filesystem with key to later be retrieved // A call with a nil fs will call unregister, ensuring that a call to Default() will never be nil func (f *FilesystemMap) Register(k string, v fs.FS) { + k = f.key(k) if v == nil { f.Unregister(k) return } - c := f.get(k) - c.FS = v + f.m.Store(k, &wrapperFs{key: k, FS: v}) } // Unregister will remove the filesystem with key from the filesystem map // if the key is the default key, it will set the default to the osFS instead of deleting it // modules should call this on cleanup to be safe func (f *FilesystemMap) Unregister(k string) { - c := f.get(k) + k = f.key(k) if k == DefaultFilesystemKey { - c.FS = DefaultFilesystem + f.m.Store(k, DefaultFilesystem) } else { - delete(f.m, k) + f.m.Delete(k) } } // Get will get a filesystem with a given key func (f *FilesystemMap) Get(k string) (v fs.FS, ok bool) { - c := f.get(k) - return c, c.FS != nil + k = f.key(k) + c, ok := f.m.Load(k) + if !ok { + if k == DefaultFilesystemKey { + f.m.Store(k, DefaultFilesystem) + return DefaultFilesystem, true + } + return nil, ok + } + return c.(fs.FS), false } // Default will get the default filesystem in the filesystem map func (f *FilesystemMap) Default() fs.FS { - return f.get(DefaultFilesystemKey) + val, _ := f.Get(DefaultFilesystemKey) + return val } From ec62f5f60efbf512643d0363c94e890b93609776 Mon Sep 17 00:00:00 2001 From: a Date: Thu, 21 Sep 2023 13:27:34 -0500 Subject: [PATCH 3/4] error on no fs --- modules/caddyhttp/fileserver/matcher.go | 7 +++++-- modules/caddyhttp/fileserver/staticfiles.go | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index c11946fbf05f..163c9941e609 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -323,8 +323,11 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { fsName := repl.ReplaceAll(m.FileSystem, "") - fileSystem, _ := m.fsmap.Get(fsName) - + fileSystem, ok := m.fsmap.Get(fsName) + if !ok { + m.logger.Error("use of unregistered filesystem", zap.String("fs", "fsName")) + return false + } type matchCandidate struct { fullpath, relative, splitRemainder string } diff --git a/modules/caddyhttp/fileserver/staticfiles.go b/modules/caddyhttp/fileserver/staticfiles.go index de4ba0971eb8..3051db82b8b6 100644 --- a/modules/caddyhttp/fileserver/staticfiles.go +++ b/modules/caddyhttp/fileserver/staticfiles.go @@ -254,7 +254,10 @@ func (fsrv *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request, next c root := repl.ReplaceAll(fsrv.Root, ".") fsName := repl.ReplaceAll(fsrv.FileSystem, "") - fileSystem, _ := fsrv.fsmap.Get(fsName) + fileSystem, ok := fsrv.fsmap.Get(fsName) + if !ok { + return caddyhttp.Error(http.StatusNotFound, fmt.Errorf("filesystem not found")) + } // remove any trailing `/` as it breaks fs.ValidPath() in the stdlib filename := strings.TrimSuffix(caddyhttp.SanitizedPathJoin(root, r.URL.Path), "/") From 50158703ae6c4a4df167a444f9989811c20dad28 Mon Sep 17 00:00:00 2001 From: a Date: Thu, 21 Sep 2023 13:27:40 -0500 Subject: [PATCH 4/4] a --- internal/filesystems/map.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/filesystems/map.go b/internal/filesystems/map.go index e9fe751c3096..286f4e2fcc21 100644 --- a/internal/filesystems/map.go +++ b/internal/filesystems/map.go @@ -13,13 +13,6 @@ var ( DefaultFilesystem = &wrapperFs{key: DefaultFilesystemKey, FS: OsFS{}} ) -type emptyFs struct { -} - -func (emptyFs) Open(name string) (fs.File, error) { - return nil, fs.ErrNotExist -} - // wrapperFs exists so can easily add to wrapperFs down the line type wrapperFs struct { key string