From 18d3a3aad4b6ad3358159d48cd158c2cb2c71e5a Mon Sep 17 00:00:00 2001 From: Shulhan Date: Wed, 22 Jan 2025 20:49:40 +0700 Subject: lib/http: always refresh a directory on GET request On server with TryDirect is true, any GET request to a directory should always rescan the content and the generate the new index.html. While at it, return the generated time in UTC instead of local time. --- lib/http/server.go | 2 +- lib/http/server_test.go | 77 ++++++++++++++++++++++++++++++ lib/http/testdata/Server_HandleFS_test.txt | 33 +++++++++++++ lib/memfs/node.go | 26 +++++----- 4 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 lib/http/testdata/Server_HandleFS_test.txt diff --git a/lib/http/server.go b/lib/http/server.go index a9761582..5801832f 100644 --- a/lib/http/server.go +++ b/lib/http/server.go @@ -499,7 +499,7 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { responseETag = strconv.FormatInt(nodeModtime, 10) requestETag = req.Header.Get(HeaderIfNoneMatch) ) - if requestETag == responseETag { + if !node.IsDir() && requestETag == responseETag { res.WriteHeader(http.StatusNotModified) return } diff --git a/lib/http/server_test.go b/lib/http/server_test.go index 8961bace..a3a82d9e 100644 --- a/lib/http/server_test.go +++ b/lib/http/server_test.go @@ -17,6 +17,7 @@ import ( "net/http/httputil" "os" "path/filepath" + "regexp" "strings" "testing" "time" @@ -860,6 +861,82 @@ func TestStatusError(t *testing.T) { } } +func TestServer_HandleFS(t *testing.T) { + var tdata *test.Data + var err error + tdata, err = test.LoadData(`testdata/Server_HandleFS_test.txt`) + if err != nil { + t.Fatal(err) + } + + var root = t.TempDir() + var mfsOpts = memfs.Options{ + Root: root, + MaxFileSize: -1, + TryDirect: true, + } + var mfs *memfs.MemFS + mfs, err = memfs.New(&mfsOpts) + if err != nil { + t.Fatal(err) + } + + var serverOpts = ServerOptions{ + Memfs: mfs, + Address: `127.0.0.1:15213`, + EnableIndexHTML: true, + } + var httpd *Server + httpd, err = NewServer(serverOpts) + if err != nil { + t.Fatal(err) + } + + var redactTime = regexp.MustCompile(`\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d`) + var redactedTime = []byte(`0000-00-00T00:00:00`) + var simreq = libhttptest.SimulateRequest{ + Path: `/`, + } + var simres *libhttptest.SimulateResult + var exp string + var got []byte + + t.Run(`OnEmptyRoot`, func(t *testing.T) { + simres, err = libhttptest.Simulate(httpd.ServeHTTP, &simreq) + if err != nil { + t.Fatal(err) + } + + exp = string(tdata.Output[t.Name()]) + got, err = simres.DumpResponse([]string{HeaderETag}) + if err != nil { + t.Fatal(err) + } + test.Assert(t, `response`, exp, string(got)) + }) + + t.Run(`OnNewDirectory`, func(t *testing.T) { + var newDir = filepath.Join(root, `dirA`) + err = os.MkdirAll(newDir, 0755) + if err != nil { + t.Fatal(err) + } + + simres, err = libhttptest.Simulate(httpd.ServeHTTP, &simreq) + if err != nil { + t.Fatal(err) + } + + exp = string(tdata.Output[t.Name()]) + got, err = simres.DumpResponse([]string{HeaderETag}) + if err != nil { + t.Fatal(err) + } + got = redactTime.ReplaceAll(got, redactedTime) + test.Assert(t, `response`, exp, string(got)) + }) +} + // TestServer_Options_HandleFS test GET on memfs with authorization. func TestServer_Options_HandleFS(t *testing.T) { type testCase struct { diff --git a/lib/http/testdata/Server_HandleFS_test.txt b/lib/http/testdata/Server_HandleFS_test.txt new file mode 100644 index 00000000..23dd32c9 --- /dev/null +++ b/lib/http/testdata/Server_HandleFS_test.txt @@ -0,0 +1,33 @@ + +<<< TestServer_HandleFS/OnEmptyRoot +HTTP/1.1 200 OK +Connection: close +Content-Type: text/html; charset=utf-8 + + + + + + + +

Index of /

+ + +<<< TestServer_HandleFS/OnNewDirectory +HTTP/1.1 200 OK +Connection: close +Content-Type: text/html; charset=utf-8 + + + + + + + +

Index of /

+
drwxr-xr-x 0 0000-00-00T00:00:00Z dirA

+ diff --git a/lib/memfs/node.go b/lib/memfs/node.go index 6a334bbd..5de22c54 100644 --- a/lib/memfs/node.go +++ b/lib/memfs/node.go @@ -15,6 +15,7 @@ import ( "os" "path" "path/filepath" + "slices" "sort" "time" @@ -30,7 +31,8 @@ const ( body{font-family:monospace; white-space:pre;} -` + +` ) var ( @@ -157,12 +159,6 @@ func (node *Node) GenerateIndexHTML() { if !node.IsDir() { return } - if len(node.Content) != 0 { - // Either the index has been generated or the node is not - // empty. - node.size = int64(len(node.Content)) - return - } var ( buf bytes.Buffer @@ -171,23 +167,23 @@ func (node *Node) GenerateIndexHTML() { buf.WriteString(templateIndexHTMLHeader) - fmt.Fprintf(&buf, `

Index of %s

`, node.name) + fmt.Fprintf(&buf, "

Index of %s

\n", node.name) if node.Parent != nil { - buf.WriteString(`
..

`) + buf.WriteString("
..

\n") } for _, child = range node.Childs { - fmt.Fprintf(&buf, `
%s %12d %s %s

`, - child.mode, child.size, child.modTime.Format(time.RFC3339), + fmt.Fprintf(&buf, "
%s %12d %s %s

\n", + child.mode, child.size, child.modTime.UTC().Format(time.RFC3339), child.Path, child.name) } - buf.WriteString(``) + buf.WriteString("\n") node.ContentType = `text/html; charset=utf-8` node.size = int64(buf.Len()) - node.Content = libbytes.Copy(buf.Bytes()) + node.Content = slices.Clone(buf.Bytes()) } // IsDir return true if the node is a directory. @@ -522,16 +518,16 @@ func (node *Node) Update(newInfo os.FileInfo, maxFileSize int64) (err error) { } } - if !doUpdate { + if !newInfo.IsDir() && !doUpdate { return nil } node.modTime = newInfo.ModTime() - node.size = newInfo.Size() if newInfo.IsDir() { err = node.updateDir(maxFileSize) } else { + node.size = newInfo.Size() err = node.UpdateContent(maxFileSize) } if err != nil { -- cgit v1.3