@samitouri / QOSami-HFS / commits / 77fe46af

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