From 971ee31d736764479583b1c2b213828309f1cc5d Mon Sep 17 00:00:00 2001 From: stringsplit <77242831+nbitzz@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:02:12 -0800 Subject: [PATCH] this hurts --- package-lock.json | 17 +++-- package.json | 3 +- src/server/lib/files.ts | 17 +++-- src/server/lib/formdata.ts | 46 ++++++++++++++ src/server/routes/api/v0/primaryApi.ts | 88 ++++---------------------- 5 files changed, 79 insertions(+), 92 deletions(-) create mode 100644 src/server/lib/formdata.ts diff --git a/package-lock.json b/package-lock.json index 6b017a8..5d00b38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.4.0-dev", "license": "Unlicense", "dependencies": { - "@hono/node-server": "^1.2.0", + "@hono/node-server": "^1.8.2", "@types/body-parser": "^1.19.2", "@types/express": "^4.17.14", "@types/multer": "^1.4.7", @@ -21,7 +21,6 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.0.2", "express": "^4.18.1", - "form-data": "^4.0.0", "hono": "^3.8.3", "multer": "^1.4.5-lts.1", "node-fetch": "^3.3.2", @@ -395,11 +394,11 @@ } }, "node_modules/@hono/node-server": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.2.0.tgz", - "integrity": "sha512-aHT8lDMLpd7ioXJ1/057+h+oE/k7rCOWmjklYDsE0jE4CoNB9XzG4f8dRHvw4s5HJFocaYDiGgYM/V0kYbQ0ww==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.8.2.tgz", + "integrity": "sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==", "engines": { - "node": ">=18.0.0" + "node": ">=18.14.1" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -2135,9 +2134,9 @@ "optional": true }, "@hono/node-server": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.2.0.tgz", - "integrity": "sha512-aHT8lDMLpd7ioXJ1/057+h+oE/k7rCOWmjklYDsE0jE4CoNB9XzG4f8dRHvw4s5HJFocaYDiGgYM/V0kYbQ0ww==" + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.8.2.tgz", + "integrity": "sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==" }, "@jridgewell/sourcemap-codec": { "version": "1.4.15", diff --git a/package.json b/package.json index 729f6c4..6d91801 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "node": ">=v16.11" }, "dependencies": { - "@hono/node-server": "^1.2.0", + "@hono/node-server": "^1.8.2", "@types/body-parser": "^1.19.2", "@types/express": "^4.17.14", "@types/multer": "^1.4.7", @@ -30,7 +30,6 @@ "cookie-parser": "^1.4.6", "dotenv": "^16.0.2", "express": "^4.18.1", - "form-data": "^4.0.0", "hono": "^3.8.3", "multer": "^1.4.5-lts.1", "node-fetch": "^3.3.2", diff --git a/src/server/lib/files.ts b/src/server/lib/files.ts index 50a2721..18cb4ca 100644 --- a/src/server/lib/files.ts +++ b/src/server/lib/files.ts @@ -172,9 +172,10 @@ export class UploadStream extends Writable { _destroy(error: Error | null) { this.error = error || undefined - + this.abort() + /* if (error instanceof WebError) return // destroyed by self - if (error) this.abort() // destroyed externally... + if (error) return // destroyed externally...*/ } /** @@ -182,7 +183,11 @@ export class UploadStream extends Writable { */ async abort() { if (!this.destroyed) this.destroy() + if (this.current) this.current.destroy(this.error) await this.files.api.deleteMessages(this.messages) + if (this.uploadId) { + delete this.files.locks[this.uploadId] + } } /** @@ -222,6 +227,7 @@ export class UploadStream extends Writable { } await this.files.write() + delete this.files.locks[this.uploadId!] return this.uploadId } @@ -257,11 +263,10 @@ export class UploadStream extends Writable { if (this.files.files[id] && this.files.files[id].owner != this.owner) return this.destroy( new WebError(403, "you don't own this file") ) - if (false /* check if locked here */) + if (this.files.locks[id]) return this.destroy( new WebError(409, "a file with this ID is already being uploaded") ) - /* lock the id */ - + this.files.locks[id] = true this.uploadId = id return this } @@ -325,6 +330,8 @@ export default class Files { files: { [key: string]: FilePointer } = {} data_directory: string = `${process.cwd()}/.data` + locks: Record = {} // I'll, like, do something more proper later + constructor(config: Configuration) { this.config = config this.api = new API(process.env.TOKEN!, config) diff --git a/src/server/lib/formdata.ts b/src/server/lib/formdata.ts new file mode 100644 index 0000000..3bf3bac --- /dev/null +++ b/src/server/lib/formdata.ts @@ -0,0 +1,46 @@ +import { Transform, Duplex } from "node:stream"; +import { TransformCallback } from "stream"; + +let content_disposition_matcher = /\s*([^=;]+)(?:=(?:"((?:\\"|[^"])*)"|([^;]*))?;?|;?)/g // probably a bad regex but IDC + +export type Headers = { + ["content-disposition"]?: (string|{key: string, value: string})[], + ["content-type"]?: string +} + +export class Field extends Duplex { + + headers: Headers = {} + + constructor(unparsedHeaders: string) { + super() + this.headers = Object.fromEntries( + unparsedHeaders.split("\r\n") + .map(e => [e.split(":")[0].trim(), e.split(":").slice(1).join(":").trim()]) + ) + + if (this.headers["content-disposition"]) + this.headers["content-disposition"] = Array.from( + (this.headers["content-disposition"] as unknown as string) + .matchAll(content_disposition_matcher)).map(e => e[2] ? {key: e[1], value: e[2]} : e[1]) + } + +} + +export default class FormDataParser extends Transform { + + readableObjectMode = true + + boundary: string + internalBuffer: Buffer | undefined + + constructor(boundary: string) { + super() + this.boundary = boundary + } + + _transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback): void { + + } + +} \ No newline at end of file diff --git a/src/server/routes/api/v0/primaryApi.ts b/src/server/routes/api/v0/primaryApi.ts index 98efd40..fb543a9 100644 --- a/src/server/routes/api/v0/primaryApi.ts +++ b/src/server/routes/api/v0/primaryApi.ts @@ -5,8 +5,8 @@ import * as Accounts from "../../../lib/accounts.js" import * as auth from "../../../lib/auth.js" import RangeParser, { type Range } from "range-parser" import ServeError from "../../../lib/errors.js" -import Files from "../../../lib/files.js" -import { getAccount } from "../../../lib/middleware.js" +import Files, { WebError } from "../../../lib/files.js" +import { getAccount, requiresPermissions } from "../../../lib/middleware.js" import {Readable} from "node:stream" export let primaryApi = new Hono<{ Variables: { @@ -27,8 +27,7 @@ export default function (files: Files) { let file = files.files[fileId] ctx.header("Access-Control-Allow-Origin", "*") ctx.header("Content-Security-Policy", "sandbox allow-scripts") - if (ctx.req.query("attachment") == "1") - ctx.header("Content-Disposition", "attachment") + ctx.header("Content-Disposition", `${ctx.req.query("attachment") == "1" ? "attachment" : "inline"}; filename="${file.filename.replaceAll("\n","\\n")}"`) if (file) { if (file.visibility == "private") { @@ -91,89 +90,26 @@ export default function (files: Files) { } } ) - - // primaryApi.head( - // ["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], - // async (ctx) => { - // let file = files.files[req.params.fileId] - - // if ( - // file.visibility == "private" && - // (ctx.get("account")?.id != file.owner || - // (auth.getType(auth.tokenFor(ctx)!) == "App" && - // auth - // .getPermissions(auth.tokenFor(ctx)!) - // ?.includes("private"))) - // ) { - // return ctx.status(403) - // } - - // ctx.header("Content-Security-Policy", "sandbox allow-scripts") - - // if (ctx.req.query("attachment") == "1") - // ctx.header("Content-Disposition", "attachment") - - // if (!file) { - // res.status(404) - // res.send() - // } else { - // ctx.header("Content-Type", file.mime) - // if (file.sizeInBytes) { - // ctx.header("Content-Length", file.sizeInBytes) - // } - // if (file.chunkSize) { - // ctx.header("Accept-Ranges", "bytes") - // } - // res.send() - // } - // } - // ) - // upload handlers - /* primaryApi.post( "/upload", requiresPermissions("upload"), - multerSetup.single("file"), async (ctx) => { let acc = ctx.get("account") as Accounts.Account - if (req.file) { - try { - let prm = req.header("monofile-params") - let params: { [key: string]: any } = {} - if (prm) { - params = JSON.parse(prm) - } + if (!ctx.req.header("Content-Type")?.startsWith("multipart/form-data")) { + ctx.status(400) + return ctx.body("[err] must be multipart/form-data") + } - files - .uploadFile( - { - owner: acc?.id, - - uploadId: params.uploadId, - filename: req.file.originalname, - mime: req.file.mimetype, - }, - req.file.buffer - ) - .then((uID) => res.send(uID)) - .catch((stat) => { - res.status(stat.status) - res.send(`[err] ${stat.message}`) - }) - } catch { - res.status(400) - res.send("[err] bad request") - } - } else { - res.status(400) - res.send("[err] bad request") + if (!ctx.req.raw.body) { + ctx.status(400) + return ctx.body("[err] body must be supplied") } } ) - +/* primaryApi.post( "/clone", requiresPermissions("upload"), @@ -212,6 +148,6 @@ export default function (files: Files) { } } ) - */ + */ return primaryApi }