fastify-txstate
Advanced tools
| import { type Readable } from 'stream'; | ||
| export interface FileHandler { | ||
| init: () => Promise<void>; | ||
| put: (stream: Readable) => Promise<{ | ||
| checksum: string; | ||
| size: number; | ||
| }>; | ||
| get: (checksum: string) => Readable; | ||
| remove: (checksum: string) => Promise<void>; | ||
| } | ||
| export declare class FileSystemHandler implements FileHandler { | ||
| #private; | ||
| options: { | ||
| tmpdir: string; | ||
| permdir: string; | ||
| }; | ||
| constructor(options?: { | ||
| tmpdir?: string; | ||
| permdir?: string; | ||
| }); | ||
| init(): Promise<void>; | ||
| get(checksum: string): import("fs").ReadStream; | ||
| exists(checksum: string): Promise<boolean>; | ||
| fileSize(checksum: string): Promise<number>; | ||
| put(stream: Readable): Promise<{ | ||
| checksum: string; | ||
| size: number; | ||
| }>; | ||
| remove(checksum: string): Promise<void>; | ||
| } | ||
| export declare const fileHandler: FileSystemHandler; |
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.fileHandler = exports.FileSystemHandler = void 0; | ||
| const crypto_1 = require("crypto"); | ||
| const fs_1 = require("fs"); | ||
| const promises_1 = require("fs/promises"); | ||
| const path_1 = require("path"); | ||
| const promises_2 = require("stream/promises"); | ||
| const txstate_utils_1 = require("txstate-utils"); | ||
| class FileSystemHandler { | ||
| options; | ||
| constructor(options = {}) { | ||
| this.options = { | ||
| tmpdir: options.tmpdir ?? '/files/tmp/', | ||
| permdir: options.permdir ?? '/files/storage/' | ||
| }; | ||
| if (!this.options.tmpdir.endsWith('/')) | ||
| this.options.tmpdir += '/'; | ||
| if (!this.options.permdir.endsWith('/')) | ||
| this.options.permdir += '/'; | ||
| } | ||
| #getTmpLocation() { | ||
| return `${this.options.tmpdir}${(0, txstate_utils_1.randomid)(12)}`; | ||
| } | ||
| #getFileLocation(checksum) { | ||
| return `${this.options.permdir}${checksum.slice(0, 1)}/${checksum.slice(1, 2)}/${checksum.slice(2)}`; | ||
| } | ||
| async #moveToPerm(tmp, checksum) { | ||
| const checksumpath = this.#getFileLocation(checksum); | ||
| await (0, promises_1.mkdir)((0, path_1.dirname)(checksumpath), { recursive: true }); | ||
| await (0, promises_1.rename)(tmp, checksumpath); | ||
| } | ||
| async init() { | ||
| await (0, promises_1.mkdir)(this.options.tmpdir, { recursive: true }); | ||
| await (0, promises_1.mkdir)(this.options.permdir, { recursive: true }); | ||
| } | ||
| get(checksum) { | ||
| const filepath = this.#getFileLocation(checksum); | ||
| const stream = (0, fs_1.createReadStream)(filepath); | ||
| return stream; | ||
| } | ||
| async exists(checksum) { | ||
| const filepath = this.#getFileLocation(checksum); | ||
| return (await (0, txstate_utils_1.rescue)((0, promises_1.access)(filepath, promises_1.constants.R_OK), false)) ?? true; | ||
| } | ||
| async fileSize(checksum) { | ||
| const filepath = this.#getFileLocation(checksum); | ||
| const info = await (0, promises_1.stat)(filepath); | ||
| return info.size; | ||
| } | ||
| async put(stream) { | ||
| const tmp = this.#getTmpLocation(); | ||
| const hash = (0, crypto_1.createHash)('sha256'); | ||
| let size = 0; | ||
| stream.on('data', (data) => { hash.update(data); size += data.length; }); | ||
| try { | ||
| const out = (0, fs_1.createWriteStream)(tmp); | ||
| const flushedPromise = new Promise((resolve, reject) => { | ||
| out.on('close', resolve); | ||
| out.on('error', reject); | ||
| }); | ||
| await (0, promises_2.pipeline)(stream, out); | ||
| await flushedPromise; | ||
| const checksum = hash.digest('base64url'); | ||
| const rereadhash = (0, crypto_1.createHash)('sha256'); | ||
| const read = (0, fs_1.createReadStream)(tmp); | ||
| for await (const chunk of read) { | ||
| rereadhash.update(chunk); | ||
| } | ||
| const rereadsum = rereadhash.digest('base64url'); | ||
| if (rereadsum !== checksum) | ||
| throw new Error('File did not write to disk correctly. Please try uploading again.'); | ||
| await this.#moveToPerm(tmp, checksum); | ||
| return { checksum, size }; | ||
| } | ||
| catch (e) { | ||
| await (0, txstate_utils_1.rescue)((0, promises_1.unlink)(tmp)); | ||
| throw e; | ||
| } | ||
| } | ||
| async remove(checksum) { | ||
| const filepath = this.#getFileLocation(checksum); | ||
| try { | ||
| await (0, promises_1.unlink)(filepath); | ||
| } | ||
| catch (e) { | ||
| if (e.code === 'ENOENT') | ||
| console.warn('Tried to delete file with checksum', checksum, 'but it did not exist.'); | ||
| else | ||
| console.warn(e); | ||
| } | ||
| } | ||
| } | ||
| exports.FileSystemHandler = FileSystemHandler; | ||
| exports.fileHandler = new FileSystemHandler(); |
+2
-0
@@ -18,2 +18,4 @@ import ftxst from '../lib/index.js' | ||
| export const readableToWebReadable = ftxst.readableToWebReadable | ||
| export const FileSystemHandler = ftxst.FileSystemHandler | ||
| export const fileHandler = ftxst.fileHandler | ||
| export default ftxst.default |
+1
-0
@@ -192,3 +192,4 @@ import { type FastifyDynamicSwaggerOptions } from '@fastify/swagger'; | ||
| export * from './error'; | ||
| export * from './filestorage'; | ||
| export * from './unified-auth'; | ||
| export * from './postformdata'; |
+1
-0
@@ -420,3 +420,4 @@ "use strict"; | ||
| __exportStar(require("./error"), exports); | ||
| __exportStar(require("./filestorage"), exports); | ||
| __exportStar(require("./unified-auth"), exports); | ||
| __exportStar(require("./postformdata"), exports); |
+2
-2
| { | ||
| "name": "fastify-txstate", | ||
| "version": "3.6.7", | ||
| "version": "3.6.8", | ||
| "description": "A small wrapper for fastify providing a set of common conventions & utility functions we use.", | ||
@@ -40,3 +40,3 @@ "exports": { | ||
| "@types/mocha": "^10.0.0", | ||
| "@types/node": "^22.0.0", | ||
| "@types/node": "^24.0.0", | ||
| "axios": "^1.6.8", | ||
@@ -43,0 +43,0 @@ "chai": "^4.2.0", |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 18 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 18 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
75856
6.88%18
12.5%1532
9.27%39
5.41%