From 33f60da9f2d6edc5de550275f043c4262d23f6ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Abdussamet=20Ko=C3=A7ak?= <abdus@abdus.dev>
Date: Sat, 8 Oct 2022 21:56:35 +0300
Subject: [PATCH] fileserver: stop listing dir when request context is
 cancelled (#5131)

Prevents caddy from performing disk IO needlessly when the request is cancelled before the listing is finished.

Closes #5129
---
 modules/caddyhttp/fileserver/browse.go           | 7 ++++---
 modules/caddyhttp/fileserver/browsetplcontext.go | 7 ++++++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/modules/caddyhttp/fileserver/browse.go b/modules/caddyhttp/fileserver/browse.go
index 6a72e155a..a8f5e8ab5 100644
--- a/modules/caddyhttp/fileserver/browse.go
+++ b/modules/caddyhttp/fileserver/browse.go
@@ -16,6 +16,7 @@ package fileserver
 
 import (
 	"bytes"
+	"context"
 	_ "embed"
 	"encoding/json"
 	"fmt"
@@ -82,7 +83,7 @@ func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter,
 	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
 
 	// calling path.Clean here prevents weird breadcrumbs when URL paths are sketchy like /%2e%2e%2f
-	listing, err := fsrv.loadDirectoryContents(dir.(fs.ReadDirFile), root, path.Clean(r.URL.Path), repl)
+	listing, err := fsrv.loadDirectoryContents(r.Context(), dir.(fs.ReadDirFile), root, path.Clean(r.URL.Path), repl)
 	switch {
 	case os.IsPermission(err):
 		return caddyhttp.Error(http.StatusForbidden, err)
@@ -136,7 +137,7 @@ func (fsrv *FileServer) serveBrowse(root, dirPath string, w http.ResponseWriter,
 	return nil
 }
 
-func (fsrv *FileServer) loadDirectoryContents(dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (browseTemplateContext, error) {
+func (fsrv *FileServer) loadDirectoryContents(ctx context.Context, dir fs.ReadDirFile, root, urlPath string, repl *caddy.Replacer) (browseTemplateContext, error) {
 	files, err := dir.ReadDir(10000) // TODO: this limit should probably be configurable
 	if err != nil && err != io.EOF {
 		return browseTemplateContext{}, err
@@ -145,7 +146,7 @@ func (fsrv *FileServer) loadDirectoryContents(dir fs.ReadDirFile, root, urlPath
 	// user can presumably browse "up" to parent folder if path is longer than "/"
 	canGoUp := len(urlPath) > 1
 
-	return fsrv.directoryListing(files, canGoUp, root, urlPath, repl), nil
+	return fsrv.directoryListing(ctx, files, canGoUp, root, urlPath, repl), nil
 }
 
 // browseApplyQueryParams applies query parameters to the listing.
diff --git a/modules/caddyhttp/fileserver/browsetplcontext.go b/modules/caddyhttp/fileserver/browsetplcontext.go
index cd24fc232..172fa505a 100644
--- a/modules/caddyhttp/fileserver/browsetplcontext.go
+++ b/modules/caddyhttp/fileserver/browsetplcontext.go
@@ -15,6 +15,7 @@
 package fileserver
 
 import (
+	"context"
 	"io/fs"
 	"net/url"
 	"os"
@@ -30,13 +31,17 @@ import (
 	"go.uber.org/zap"
 )
 
-func (fsrv *FileServer) directoryListing(entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) browseTemplateContext {
+func (fsrv *FileServer) directoryListing(ctx context.Context, entries []fs.DirEntry, canGoUp bool, root, urlPath string, repl *caddy.Replacer) browseTemplateContext {
 	filesToHide := fsrv.transformHidePaths(repl)
 
 	var dirCount, fileCount int
 	fileInfos := []fileInfo{}
 
 	for _, entry := range entries {
+		if err := ctx.Err(); err != nil {
+			break
+		}
+
 		name := entry.Name()
 
 		if fileHidden(name, filesToHide) {