diff --git a/bun.lockb b/bun.lockb index 5e3dbe4d..858989ad 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 04fc7b21..2fd6337d 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ }, "devDependencies": { "@biomejs/biome": "^1.7.0", + "@types/cli-progress": "^3.11.5", "@types/cli-table": "^0.3.4", "@types/html-to-text": "^9.0.4", "@types/ioredis": "^5.0.0", @@ -81,12 +82,12 @@ "@types/markdown-it-container": "^2.0.10", "@types/mime-types": "^2.1.4", "@types/pg": "^8.11.5", + "@types/qs": "^6.9.15", "bun-types": "latest", "drizzle-kit": "^0.20.14", + "oclif": "^4.10.4", "ts-prune": "^0.10.3", - "typescript": "latest", - "@types/cli-progress": "^3.11.5", - "oclif": "^4.10.4" + "typescript": "latest" }, "peerDependencies": { "typescript": "^5.3.2" diff --git a/server/api/api/v1/accounts/update_credentials/index.ts b/server/api/api/v1/accounts/update_credentials/index.ts index 4053b11c..56f4b5e3 100644 --- a/server/api/api/v1/accounts/update_credentials/index.ts +++ b/server/api/api/v1/accounts/update_credentials/index.ts @@ -1,4 +1,4 @@ -import { applyConfig, auth, handleZodError } from "@api"; +import { applyConfig, auth, handleZodError, qs } from "@api"; import { zValidator } from "@hono/zod-validator"; import { errorResponse, jsonResponse } from "@response"; import { sanitizeHtml, sanitizedHtmlStrip } from "@sanitization"; @@ -96,6 +96,7 @@ export default (app: Hono) => app.on( meta.allowedMethods, meta.route, + qs(), zValidator("form", schemas.form, handleZodError), auth(meta.auth), async (context) => { @@ -116,8 +117,6 @@ export default (app: Hono) => const self = user.getUser(); - const sanitizedNote = await sanitizeHtml(note ?? ""); - const sanitizedDisplayName = await sanitizedHtmlStrip( display_name ?? "", ); @@ -154,18 +153,14 @@ export default (app: Hono) => if (note && self.source) { // Check if bio doesnt match filters - if ( - config.filters.bio.some((filter) => - sanitizedNote.match(filter), - ) - ) { + if (config.filters.bio.some((filter) => note.match(filter))) { return errorResponse("Bio contains blocked words", 422); } - self.source.note = sanitizedNote; + self.source.note = note; self.note = await contentToHtml({ "text/markdown": { - content: sanitizedNote, + content: note, }, }); } @@ -270,7 +265,7 @@ export default (app: Hono) => // Parse emojis const displaynameEmojis = await parseEmojis(sanitizedDisplayName); - const noteEmojis = await parseEmojis(sanitizedNote); + const noteEmojis = await parseEmojis(self.note); self.emojis = [...displaynameEmojis, ...noteEmojis, ...fieldEmojis]; diff --git a/utils/api.ts b/utils/api.ts index ebd87632..ce04c3be 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -107,19 +107,70 @@ export const auth = (authData: APIRouteMetadata["auth"]) => }; }); +/** + * Middleware to magically unfuck forms + * Add it to random Hono routes and hope it works + * @returns + */ export const qs = () => { return createMiddleware(async (context, next) => { - const parsed = parse(await context.req.text(), { - parseArrays: true, - interpretNumericEntities: true, - }); + const contentType = context.req.header("content-type"); - context.req.parseBody = () => - Promise.resolve(parsed as T); - // @ts-ignore Very bad hack - context.req.formData = () => Promise.resolve(parsed); - // @ts-ignore I'm so sorry for this - context.req.bodyCache.formData = parsed; + if (contentType?.includes("multipart/form-data")) { + // Get it as a query format to pass on to qs, then insert back files + const formData = await context.req.formData(); + const urlparams = new URLSearchParams(); + const files = new Map(); + for (const [key, value] of [...formData.entries()]) { + if (Array.isArray(value)) { + for (const val of value) { + urlparams.append(key, val); + } + } else if (!(value instanceof File)) { + urlparams.append(key, String(value)); + } else { + if (!files.has(key)) { + files.set(key, value); + } + } + } + + const parsed = parse(urlparams.toString(), { + parseArrays: true, + interpretNumericEntities: true, + }); + + // @ts-ignore Very bad hack + context.req.parseBody = () => + Promise.resolve({ + ...parsed, + ...Object.fromEntries(files), + } as T); + + context.req.formData = () => + // @ts-ignore I'm so sorry for this + Promise.resolve({ + ...parsed, + ...Object.fromEntries(files), + }); + // @ts-ignore I'm so sorry for this + context.req.bodyCache.formData = { + ...parsed, + ...Object.fromEntries(files), + }; + } else if (contentType?.includes("application/x-www-form-urlencoded")) { + const parsed = parse(await context.req.text(), { + parseArrays: true, + interpretNumericEntities: true, + }); + + context.req.parseBody = () => + Promise.resolve(parsed as T); + // @ts-ignore Very bad hack + context.req.formData = () => Promise.resolve(parsed); + // @ts-ignore I'm so sorry for this + context.req.bodyCache.formData = parsed; + } await next(); }); };