aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2025-01-22 20:49:40 +0700
committerShulhan <ms@kilabit.info>2025-01-22 21:34:16 +0700
commit18d3a3aad4b6ad3358159d48cd158c2cb2c71e5a (patch)
tree85d4d09371e4077091ba94988e3f8c5d799dc155
parentb9cff7d8f66d679218d1eaf4b015febbc48abc9e (diff)
downloadpakakeh.go-18d3a3aad4b6ad3358159d48cd158c2cb2c71e5a.tar.xz
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.
-rw-r--r--lib/http/server.go2
-rw-r--r--lib/http/server_test.go77
-rw-r--r--lib/http/testdata/Server_HandleFS_test.txt33
-rw-r--r--lib/memfs/node.go26
4 files changed, 122 insertions, 16 deletions
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
+
+<!DOCTYPE html><html>
+<head>
+<meta name="viewport" content="width=device-width">
+<style>
+body{font-family:monospace; white-space:pre;}
+</style>
+</head>
+<body>
+<h3>Index of /</h3>
+</body></html>
+
+<<< TestServer_HandleFS/OnNewDirectory
+HTTP/1.1 200 OK
+Connection: close
+Content-Type: text/html; charset=utf-8
+
+<!DOCTYPE html><html>
+<head>
+<meta name="viewport" content="width=device-width">
+<style>
+body{font-family:monospace; white-space:pre;}
+</style>
+</head>
+<body>
+<h3>Index of /</h3>
+<div>drwxr-xr-x <tt> 0</tt> 0000-00-00T00:00:00Z <a href="/dirA">dirA</a></div><br/>
+</body></html>
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;}
</style>
</head>
-<body>`
+<body>
+`
)
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, `<h3>Index of %s</h3>`, node.name)
+ fmt.Fprintf(&buf, "<h3>Index of %s</h3>\n", node.name)
if node.Parent != nil {
- buf.WriteString(`<div><a href="..">..</a></div><br/>`)
+ buf.WriteString("<div><a href='..'>..</a></div><br/>\n")
}
for _, child = range node.Childs {
- fmt.Fprintf(&buf, `<div>%s <tt>%12d</tt> %s <a href=%q>%s</a></div><br/>`,
- child.mode, child.size, child.modTime.Format(time.RFC3339),
+ fmt.Fprintf(&buf, "<div>%s <tt>%12d</tt> %s <a href=%q>%s</a></div><br/>\n",
+ child.mode, child.size, child.modTime.UTC().Format(time.RFC3339),
child.Path, child.name)
}
- buf.WriteString(`</body></html>`)
+ buf.WriteString("</body></html>\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 {