this hurts

This commit is contained in:
May 2024-03-03 22:02:12 -08:00
parent fd4441633e
commit 971ee31d73
5 changed files with 79 additions and 92 deletions

17
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "1.4.0-dev", "version": "1.4.0-dev",
"license": "Unlicense", "license": "Unlicense",
"dependencies": { "dependencies": {
"@hono/node-server": "^1.2.0", "@hono/node-server": "^1.8.2",
"@types/body-parser": "^1.19.2", "@types/body-parser": "^1.19.2",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
@ -21,7 +21,6 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"express": "^4.18.1", "express": "^4.18.1",
"form-data": "^4.0.0",
"hono": "^3.8.3", "hono": "^3.8.3",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
@ -395,11 +394,11 @@
} }
}, },
"node_modules/@hono/node-server": { "node_modules/@hono/node-server": {
"version": "1.2.0", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.8.2.tgz",
"integrity": "sha512-aHT8lDMLpd7ioXJ1/057+h+oE/k7rCOWmjklYDsE0jE4CoNB9XzG4f8dRHvw4s5HJFocaYDiGgYM/V0kYbQ0ww==", "integrity": "sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA==",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.14.1"
} }
}, },
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
@ -2135,9 +2134,9 @@
"optional": true "optional": true
}, },
"@hono/node-server": { "@hono/node-server": {
"version": "1.2.0", "version": "1.8.2",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.8.2.tgz",
"integrity": "sha512-aHT8lDMLpd7ioXJ1/057+h+oE/k7rCOWmjklYDsE0jE4CoNB9XzG4f8dRHvw4s5HJFocaYDiGgYM/V0kYbQ0ww==" "integrity": "sha512-h8l2TBLCPHZBUrrkosZ6L5CpBLj6zdESyF4B+zngiCDF7aZFQJ0alVbLx7jn8PCVi9EyoFf8a4hOZFi1tD95EA=="
}, },
"@jridgewell/sourcemap-codec": { "@jridgewell/sourcemap-codec": {
"version": "1.4.15", "version": "1.4.15",

View file

@ -18,7 +18,7 @@
"node": ">=v16.11" "node": ">=v16.11"
}, },
"dependencies": { "dependencies": {
"@hono/node-server": "^1.2.0", "@hono/node-server": "^1.8.2",
"@types/body-parser": "^1.19.2", "@types/body-parser": "^1.19.2",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
@ -30,7 +30,6 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"express": "^4.18.1", "express": "^4.18.1",
"form-data": "^4.0.0",
"hono": "^3.8.3", "hono": "^3.8.3",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",

View file

@ -172,9 +172,10 @@ export class UploadStream extends Writable {
_destroy(error: Error | null) { _destroy(error: Error | null) {
this.error = error || undefined this.error = error || undefined
this.abort()
/*
if (error instanceof WebError) return // destroyed by self 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() { async abort() {
if (!this.destroyed) this.destroy() if (!this.destroyed) this.destroy()
if (this.current) this.current.destroy(this.error)
await this.files.api.deleteMessages(this.messages) 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() await this.files.write()
delete this.files.locks[this.uploadId!]
return 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) if (this.files.files[id] && this.files.files[id].owner != this.owner)
return this.destroy( new WebError(403, "you don't own this file") ) 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") ) 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 this.uploadId = id
return this return this
} }
@ -325,6 +330,8 @@ export default class Files {
files: { [key: string]: FilePointer } = {} files: { [key: string]: FilePointer } = {}
data_directory: string = `${process.cwd()}/.data` data_directory: string = `${process.cwd()}/.data`
locks: Record<string, boolean> = {} // I'll, like, do something more proper later
constructor(config: Configuration) { constructor(config: Configuration) {
this.config = config this.config = config
this.api = new API(process.env.TOKEN!, config) this.api = new API(process.env.TOKEN!, config)

View file

@ -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 {
}
}

View file

@ -5,8 +5,8 @@ import * as Accounts from "../../../lib/accounts.js"
import * as auth from "../../../lib/auth.js" import * as auth from "../../../lib/auth.js"
import RangeParser, { type Range } from "range-parser" import RangeParser, { type Range } from "range-parser"
import ServeError from "../../../lib/errors.js" import ServeError from "../../../lib/errors.js"
import Files from "../../../lib/files.js" import Files, { WebError } from "../../../lib/files.js"
import { getAccount } from "../../../lib/middleware.js" import { getAccount, requiresPermissions } from "../../../lib/middleware.js"
import {Readable} from "node:stream" import {Readable} from "node:stream"
export let primaryApi = new Hono<{ export let primaryApi = new Hono<{
Variables: { Variables: {
@ -27,8 +27,7 @@ export default function (files: Files) {
let file = files.files[fileId] let file = files.files[fileId]
ctx.header("Access-Control-Allow-Origin", "*") ctx.header("Access-Control-Allow-Origin", "*")
ctx.header("Content-Security-Policy", "sandbox allow-scripts") ctx.header("Content-Security-Policy", "sandbox allow-scripts")
if (ctx.req.query("attachment") == "1") ctx.header("Content-Disposition", `${ctx.req.query("attachment") == "1" ? "attachment" : "inline"}; filename="${file.filename.replaceAll("\n","\\n")}"`)
ctx.header("Content-Disposition", "attachment")
if (file) { if (file) {
if (file.visibility == "private") { 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 // upload handlers
/*
primaryApi.post( primaryApi.post(
"/upload", "/upload",
requiresPermissions("upload"), requiresPermissions("upload"),
multerSetup.single("file"),
async (ctx) => { async (ctx) => {
let acc = ctx.get("account") as Accounts.Account let acc = ctx.get("account") as Accounts.Account
if (req.file) { if (!ctx.req.header("Content-Type")?.startsWith("multipart/form-data")) {
try { ctx.status(400)
let prm = req.header("monofile-params") return ctx.body("[err] must be multipart/form-data")
let params: { [key: string]: any } = {}
if (prm) {
params = JSON.parse(prm)
} }
files if (!ctx.req.raw.body) {
.uploadFile( ctx.status(400)
{ return ctx.body("[err] body must be supplied")
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")
} }
} }
) )
/*
primaryApi.post( primaryApi.post(
"/clone", "/clone",
requiresPermissions("upload"), requiresPermissions("upload"),