fix: admin/fs: changing the file of an icon wouldn't be immediately visible https://github.com/rejetto/hfs/discussions/657#discussioncomment-10108907

Massimo Melina committed Sep 28, 2024 at 16:48 UTC 00075021269068e56526a4269033dda83cc6c5fb
2 files changed +15 -5
config.md
+5 -4
@@ -120,9 +120,10 @@ Configuration can be done in several ways
120 - `dynamic_dns_url` URL to be requested to keep a domain updated with your latest IP address.
121 Optionally, you can append “>” followed by a regular expression to determine a successful answer, otherwise status code will be used.
122 Multiple URLs are supported and you can specify one for each line.
123 -- `auto_basic` automatically detect (based on user-agent) when the basic web inteface should be served, to support legacy browsers. Default is true.
124 -- `allow_session_ip_change` should requests of the same login session be allowed from different IP addresses. Default is false, to prevent cookie stealing. You can set it `true` to always allow it, or `https` to allow only on https, where stealing the cookie is harder.
125 -- `authorization_header` support Authentication HTTP header. Default is true.
123 +- `auto_basic` automatically detect (based on user-agent) when the basic web inteface should be served, to support legacy browsers. Default is true. No UI.
124 +- `allow_session_ip_change` should requests of the same login session be allowed from different IP addresses. Default is false, to prevent cookie stealing. You can set it `true` to always allow it, or `https` to allow only on https, where stealing the cookie is harder. No UI.
125 +- `authorization_header` support Authentication HTTP header. Default is true. No UI.
126 +- `cache_control_disk_files` number of seconds after which the browser should bypass the cache and check the server for an updated version of the file. Default is 5. No UI.
127 - `create-admin` special entry to quickly create an admin account. The value will be set as password. As soon as the account is created, this entry is removed.
128
129 #### Virtual File System (VFS)
@@ -136,7 +137,7 @@ Valid keys in a node are:
137 Value is a list and its entries are nodes.
138 - `rename`: similar to name, but it's from the parent node point.
139 Use this to change the name of entries that are read from the source, not listed in the VFS.
139 - Value is a dictionary, where the key is the original name.
140 + Value is a dictionary, where the key is the original name. No UI.
141 - `mime`: specify what mime to use for this resource. Use "auto" for automatic detection.
142 - `url`: when this value is present, the element is a link to the URL you specify.
143 - `target`: optional, for links only, used to [open the link in a new browser](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). E.g. `_blank`
src/serveFile.ts
+10 -1
@@ -15,6 +15,7 @@ import { getConnection, updateConnection } from './connections'
15 import { getCurrentUsername } from './auth'
16 import { sendErrorPage } from './errorPages'
17 import { Readable } from 'stream'
18 +import { createHash } from 'crypto'
19
20 const allowedReferer = defineConfig('allowed_referer', '')
21 const maxDownloads = downloadLimiter(defineConfig(CFG.max_downloads, 0), () => true)
@@ -56,6 +57,9 @@ const mimeCfg = defineConfig<Dict<string>, (name: string) => string | undefined>
57 return (name: string) => values[matchers.findIndex(matcher => matcher(name))]
58 })
59
60 +// after this number of seconds, the browser should check the server to see if there's a newer version of the file
61 +const cacheControlDiskFiles = defineConfig('cache_control_disk_files', 5)
62 +
63 export async function serveFile(ctx: Koa.Context, source:string, mime?:string, content?: string | Buffer) {
64 if (!source)
65 return
@@ -76,7 +80,9 @@ export async function serveFile(ctx: Koa.Context, source:string, mime?:string, c
80 const stats = await promisify(stat)(source) // using fs's function instead of fs/promises, because only the former is supported by pkg
81 if (!stats.isFile())
82 return ctx.status = HTTP_METHOD_NOT_ALLOWED
79 - ctx.set('Last-Modified', stats.mtime.toUTCString())
83 + const t = stats.mtime.toUTCString()
84 + ctx.set('Last-Modified', t)
85 + ctx.set('Etag', createHash('md5').update(source).update(t).digest('hex'))
86 ctx.state.fileSource = source
87 ctx.state.fileStats = stats
88 ctx.status = HTTP_OK
@@ -84,6 +90,9 @@ export async function serveFile(ctx: Koa.Context, source:string, mime?:string, c
90 return ctx.status = HTTP_NOT_MODIFIED
91 if (content !== undefined)
92 return ctx.body = content
93 + const cc = cacheControlDiskFiles.get()
94 + if (_.isNumber(cc))
95 + ctx.set('Cache-Control', `max-age=${cc}`)
96 const { size } = stats
97 const range = applyRange(ctx, size)
98 ctx.body = createReadStream(source, range)