fix: downloads made with IE may be not counted as downloads https://github.com/rejetto/hfs/issues/989#issuecomment-4188017664
Massimo Melina committed
Apr 11, 2026 at 18:23 UTC
77fe46af26961cd8975b614d27e59ff966232bac
1 file changed
+8
-3
src/serveFile.ts
+8
-3
@@ -7,7 +7,7 @@ import { HTTP_BAD_REQUEST, HTTP_FORBIDDEN, HTTP_METHOD_NOT_ALLOWED, HTTP_NO_CONT
7
import { getNodeName, VfsNode } from './vfs'
8
import mimetypes from 'mime-types'
9
import { defineConfig } from './config'
10
-import { CFG, Dict, makeMatcher, matches, try_, with_ } from './misc'
10
+import { CFG, Dict, makeMatcher, matches, try_ } from './misc'
11
import _ from 'lodash'
12
import { basename } from 'path'
13
import { promisify } from 'util'
@@ -23,6 +23,8 @@ const maxDownloads = downloadLimiter(defineConfig(CFG.max_downloads, 0), () => t
23
const maxDownloadsPerIp = downloadLimiter(defineConfig(CFG.max_downloads_per_ip, 0), ctx => ctx.ip)
24
const maxDownloadsPerAccount = downloadLimiter(defineConfig(CFG.max_downloads_per_account, 0), ctx => getCurrentUsername(ctx) || undefined)
25
26
+const GUI_ASSET_MIME = /^(image\/|audio\/|video\/|font\/|text\/css$|(?:text|application)\/(?:javascript|ecmascript)$|application\/x-javascript$)/i
27
+
28
function toAsciiEquivalent(s: string) {
29
return iconv.encode(iconv.decode(Buffer.from(s), 'utf-8'), 'ascii').toString().replaceAll('?', '')
30
}
@@ -52,8 +54,11 @@ export async function serveFileNode(ctx: Koa.Context, node: VfsNode) {
54
ctx.state.vfsNode = node // useful to tell service files from files shared by the user
55
const download = 'dl' in ctx.query
56
disposition(ctx, name, download)
55
- if (!download && ctx.get('referer')?.endsWith('/') && with_(ctx.get('accept'), x => x && !x.includes('text')))
56
- ctx.state.considerAsGui = true
57
+ const fetchDest = ctx.get('sec-fetch-dest')
58
+ ctx.state.considerAsGui ??= !download && ctx.get('referer')?.endsWith('/')
59
+ && (fetchDest ? fetchDest !== 'document' && fetchDest !== 'empty' // modern clients
60
+ // legacy clients often send Accept: */* for archive downloads, so the served mime is a safer signal than request headers here
61
+ : GUI_ASSET_MIME.test(mimeString || mimetypes.lookup(source||'') || ''))
62
await serveFile(ctx, source||'', mimeString)
63
64
if (await maxDownloadsPerAccount(ctx) === undefined) // returning false will not execute other limits