@autonomys/file-server 1.6.2-beta.3 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,6 @@
1
1
  import { Request, Response } from 'express';
2
2
  import { ByteRange, DownloadMetadata, DownloadOptions } from '../models.js';
3
- export type DownloadHeaderResult = {
4
- shouldDecompressBody: boolean;
5
- };
6
- export declare const handleDownloadResponseHeaders: (req: Request, res: Response, metadata: DownloadMetadata, { byteRange, rawMode }: DownloadOptions) => DownloadHeaderResult;
3
+ export declare const handleDownloadResponseHeaders: (req: Request, res: Response, metadata: DownloadMetadata, options: DownloadOptions) => void;
7
4
  export declare const handleS3DownloadResponseHeaders: (req: Request, res: Response, metadata: DownloadMetadata) => void;
8
5
  export declare const getByteRange: (req: Request) => ByteRange | undefined;
9
6
  //# sourceMappingURL=headers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/http/headers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAqF3E,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,OAAO,CAAA;CAC9B,CAAA;AAED,eAAO,MAAM,6BAA6B,GACxC,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,UAAU,gBAAgB,EAC1B,wBAA4C,eAAe,KAC1D,oBAgEF,CAAA;AAED,eAAO,MAAM,+BAA+B,GAC1C,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,UAAU,gBAAgB,SAS3B,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,SAAS,GAAG,SAgBvD,CAAA"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/http/headers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAS3E,eAAO,MAAM,6BAA6B,GACxC,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,UAAU,gBAAgB,EAC1B,SAAS,eAAe,SAsBzB,CAAA;AA+CD,eAAO,MAAM,+BAA+B,GAC1C,KAAK,OAAO,EACZ,KAAK,QAAQ,EACb,UAAU,gBAAgB,SAS3B,CAAA;AAED,eAAO,MAAM,YAAY,GAAI,KAAK,OAAO,KAAG,SAAS,GAAG,SAgBvD,CAAA"}
@@ -1,130 +1,45 @@
1
1
  import { CompressionAlgorithm, EncryptionAlgorithm } from '@autonomys/auto-dag-data';
2
- import { inferMimeType } from '../utils.js';
3
- // Generic mimetypes that should trigger extension-based fallback
4
- const GENERIC_MIME_TYPES = new Set(['application/octet-stream', 'binary/octet-stream']);
5
- // Get the best mimetype for a file, falling back to extension-based inference
6
- // when the stored mimetype is missing or generic
7
- const getMimeType = (metadata) => {
8
- var _a;
9
- const storedMime = (_a = metadata.mimeType) === null || _a === void 0 ? void 0 : _a.toLowerCase();
10
- // If we have a meaningful mimetype, use it
11
- if (storedMime && !GENERIC_MIME_TYPES.has(storedMime)) {
12
- return metadata.mimeType;
13
- }
14
- // Otherwise, infer from filename extension
15
- return inferMimeType(metadata.name);
16
- };
17
- // Check if this is actually a document navigation (based on headers only)
18
- // Used to determine if browser will auto-decompress Content-Encoding
19
- const isDocumentNavigation = (req) => {
20
- const destHeader = req.headers['sec-fetch-dest'];
21
- const dest = (Array.isArray(destHeader) ? destHeader[0] : (destHeader !== null && destHeader !== void 0 ? destHeader : '')).toLowerCase();
22
- if (dest && dest !== 'document')
23
- return false; // e.g. <img>, <video>, fetch(), etc.
24
- const modeHeader = req.headers['sec-fetch-mode'];
25
- const mode = (Array.isArray(modeHeader) ? modeHeader[0] : (modeHeader !== null && modeHeader !== void 0 ? modeHeader : '')).toLowerCase();
26
- if (mode && mode !== 'navigate')
27
- return false; // programmatic fetch / subresource
28
- return true;
29
- };
30
- // Decide if this file type is something browsers can usually render inline via URL
31
- // (mirrors the logic in canDisplayDirectly but on DownloadMetadata)
32
- const isPreviewableInline = (metadata) => {
33
- if (metadata.isEncrypted)
34
- return false;
35
- // Use getMimeType to get the best available mimetype (with extension fallback)
36
- const mimeType = getMimeType(metadata).toLowerCase();
37
- const directDisplayTypes = ['image/', 'video/', 'audio/'];
38
- return (directDisplayTypes.some((type) => mimeType.startsWith(type)) || mimeType === 'application/pdf');
2
+ const isExpectedDocument = (req) => {
3
+ return (req.headers['sec-fetch-site'] === 'none' ||
4
+ (req.headers['sec-fetch-site'] === 'same-site' && req.headers['sec-fetch-mode'] === 'navigate'));
39
5
  };
40
- const isInlineDisposition = (req, metadata) => {
41
- // Explicit query overrides - treat presence as boolean flag
42
- // ?download or ?download=true triggers attachment, ?download=false is ignored
43
- if (req.query.download === 'true' || req.query.download === '')
44
- return false;
45
- // ?inline or ?inline=true triggers inline, ?inline=false is ignored
46
- if (req.query.inline === 'true' || req.query.inline === '')
47
- return true;
48
- // Folders (served as zip) should default to attachment
49
- if (metadata.type !== 'file')
50
- return false;
51
- // For media / PDFs that browsers can render directly, prefer inline even for subresources
52
- if (isPreviewableInline(metadata))
53
- return true;
54
- // Fallback to header-based detection: top-level document navigations are inline
55
- return isDocumentNavigation(req);
56
- };
57
- // Helper to create an ASCII-safe fallback for filename parameter (RFC 2183/6266)
58
- const toAsciiFallback = (name) => name
59
- .replace(/[^\x20-\x7E]+/g, '_') // replace non-ASCII with underscore
60
- .replace(/["\\]/g, '\\$&'); // escape quotes and backslashes
61
- // RFC 5987 encoding for filename* parameter
62
- const rfc5987Encode = (str) => encodeURIComponent(str)
63
- .replace(/['()]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())
64
- .replace(/\*/g, '%2A');
65
- const buildDisposition = (req, metadata, filename) => {
66
- const fallbackName = toAsciiFallback(filename || 'download');
67
- const encoded = rfc5987Encode(filename || 'download');
68
- const type = isInlineDisposition(req, metadata) ? 'inline' : 'attachment';
69
- return `${type}; filename="${fallbackName}"; filename*=UTF-8''${encoded}`;
70
- };
71
- export const handleDownloadResponseHeaders = (req, res, metadata, { byteRange = undefined, rawMode = false }) => {
72
- var _a;
73
- const baseName = metadata.name || 'download';
74
- const fileName = metadata.type === 'file' ? baseName : `${baseName}.zip`;
75
- let shouldDecompressBody = false;
6
+ export const handleDownloadResponseHeaders = (req, res, metadata, options) => {
7
+ const safeName = encodeURIComponent(metadata.name || 'download');
8
+ const documentExpected = isExpectedDocument(req);
9
+ const shouldHandleEncoding = req.query.ignoreEncoding
10
+ ? req.query.ignoreEncoding !== 'true'
11
+ : documentExpected;
12
+ const isEncrypted = metadata.isEncrypted;
76
13
  if (metadata.type === 'file') {
77
- const contentType = !metadata.isEncrypted && !rawMode ? getMimeType(metadata) : 'application/octet-stream';
78
- res.set('Content-Type', contentType);
79
- const compressedButNotEncrypted = metadata.isCompressed && !metadata.isEncrypted;
80
- // Only set Content-Encoding for document navigations where browsers auto-decompress
81
- // Don't set it for <img>, <video>, fetch(), etc. as browsers won't auto-decompress those
82
- const shouldHandleEncoding = req.query.ignoreEncoding
83
- ? req.query.ignoreEncoding !== 'true'
84
- : isDocumentNavigation(req);
85
- const mimeType = contentType.toLowerCase();
86
- const isMediaType = mimeType.startsWith('video/') || mimeType.startsWith('audio/');
87
- const mustDecompress = compressedButNotEncrypted &&
88
- (!shouldHandleEncoding || rawMode || byteRange != null || isMediaType);
89
- const canAdvertiseRanges = metadata.size != null && !mustDecompress;
90
- if (canAdvertiseRanges) {
91
- res.set('Accept-Ranges', 'bytes');
92
- }
93
- else {
94
- res.set('Accept-Ranges', 'none');
95
- }
96
- if (compressedButNotEncrypted &&
97
- shouldHandleEncoding &&
98
- !rawMode &&
99
- !byteRange &&
100
- !isMediaType) {
101
- res.set('Content-Encoding', 'deflate');
102
- }
103
- else if (mustDecompress) {
104
- shouldDecompressBody = true;
105
- }
106
- if (byteRange && !mustDecompress) {
107
- res.status(206);
108
- res.set('Content-Range', `bytes ${byteRange[0]}-${byteRange[1]}/${metadata.size}`);
109
- const upperBound = (_a = byteRange[1]) !== null && _a !== void 0 ? _a : Number(metadata.size) - 1;
110
- res.set('Content-Length', (upperBound - byteRange[0] + 1).toString());
111
- }
112
- else if (byteRange && mustDecompress && metadata.size != null) {
113
- const maxIndex = Number(metadata.size) - 1;
114
- res.status(206);
115
- res.set('Content-Range', `bytes 0-${maxIndex}/${metadata.size}`);
116
- res.set('Content-Length', metadata.size.toString());
117
- }
118
- else if (metadata.size != null) {
119
- res.set('Content-Length', metadata.size.toString());
120
- }
14
+ setFileResponseHeaders(res, metadata, isEncrypted, documentExpected, shouldHandleEncoding, safeName, options);
121
15
  }
122
16
  else {
123
- const contentType = metadata.isEncrypted ? 'application/octet-stream' : 'application/zip';
124
- res.set('Content-Type', contentType);
17
+ setFolderResponseHeaders(res, isEncrypted, documentExpected, safeName);
125
18
  }
126
- res.set('Content-Disposition', buildDisposition(req, metadata, fileName));
127
- return { shouldDecompressBody };
19
+ };
20
+ const setFileResponseHeaders = (res, metadata, isEncrypted, isExpectedDocument, shouldHandleEncoding, safeName, { byteRange = undefined, rawMode = false }) => {
21
+ var _a;
22
+ const contentType = (!isEncrypted && !rawMode && metadata.mimeType) || 'application/octet-stream';
23
+ res.set('Content-Type', contentType);
24
+ res.set('Content-Disposition', `${isExpectedDocument ? 'inline' : 'attachment'}; filename="${safeName}"`);
25
+ const compressedButNoEncrypted = metadata.isCompressed && !isEncrypted;
26
+ if (compressedButNoEncrypted && shouldHandleEncoding && !rawMode && !byteRange) {
27
+ res.set('Content-Encoding', 'deflate');
28
+ }
29
+ if (byteRange) {
30
+ res.status(206);
31
+ res.set('Content-Range', `bytes ${byteRange[0]}-${byteRange[1]}/${metadata.size}`);
32
+ const upperBound = (_a = byteRange[1]) !== null && _a !== void 0 ? _a : Number(metadata.size) - 1;
33
+ res.set('Content-Length', (upperBound - byteRange[0] + 1).toString());
34
+ }
35
+ else if (metadata.size) {
36
+ res.set('Content-Length', metadata.size.toString());
37
+ }
38
+ };
39
+ const setFolderResponseHeaders = (res, isEncrypted, isExpectedDocument, safeName) => {
40
+ const contentType = isEncrypted ? 'application/octet-stream' : 'application/zip';
41
+ res.set('Content-Type', contentType);
42
+ res.set('Content-Disposition', `${isExpectedDocument ? 'inline' : 'attachment'}; filename="${safeName}.zip"`);
128
43
  };
129
44
  export const handleS3DownloadResponseHeaders = (req, res, metadata) => {
130
45
  if (metadata.isEncrypted) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@autonomys/file-server",
3
3
  "packageManager": "yarn@4.7.0",
4
- "version": "1.6.2-beta.3",
4
+ "version": "1.6.3",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -19,8 +19,7 @@
19
19
  "node": ">=20.8.0"
20
20
  },
21
21
  "scripts": {
22
- "build": "yarn tsc",
23
- "test": "node --experimental-vm-modules ../../../node_modules/jest/bin/jest.js --config ./jest.config.cjs"
22
+ "build": "yarn tsc"
24
23
  },
25
24
  "main": "./dist/index.js",
26
25
  "types": "./dist/index.d.ts",
@@ -48,19 +47,19 @@
48
47
  "typescript": "^5.8.3"
49
48
  },
50
49
  "dependencies": {
51
- "@autonomys/asynchronous": "^1.6.2-beta.3",
52
- "@autonomys/auto-dag-data": "^1.6.2-beta.3",
53
- "@autonomys/auto-utils": "^1.6.2-beta.3",
50
+ "@autonomys/asynchronous": "^1.6.3",
51
+ "@autonomys/auto-dag-data": "^1.6.3",
52
+ "@autonomys/auto-utils": "^1.6.3",
54
53
  "@keyvhq/sqlite": "^2.1.7",
55
54
  "cache-manager": "^6.4.2",
56
55
  "express": "^4.19.2",
57
56
  "jszip": "^3.10.1",
58
57
  "keyv": "^5.3.2",
59
- "mime-types": "^3.0.1",
58
+ "mime-types": "^3.0.2",
60
59
  "process": "^0.11.10",
61
60
  "stream": "^0.0.3",
62
61
  "uuid": "^11.1.0",
63
62
  "zod": "^3.24.2"
64
63
  },
65
- "gitHead": "6cc5267bcbc3b75b9897d098287552c3e1e5e00a"
64
+ "gitHead": "b61097a2c527e541085e618db096d517dfa29c78"
66
65
  }