fix(api): 🎨 Do wizardry on qs middleware to also work on multipart formData

This commit is contained in:
Jesse Wierzbinski 2024-05-08 01:16:16 -10:00
parent 2acd281c76
commit a9629b825b
No known key found for this signature in database
4 changed files with 71 additions and 24 deletions

BIN
bun.lockb

Binary file not shown.

View file

@ -74,6 +74,7 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.7.0", "@biomejs/biome": "^1.7.0",
"@types/cli-progress": "^3.11.5",
"@types/cli-table": "^0.3.4", "@types/cli-table": "^0.3.4",
"@types/html-to-text": "^9.0.4", "@types/html-to-text": "^9.0.4",
"@types/ioredis": "^5.0.0", "@types/ioredis": "^5.0.0",
@ -81,12 +82,12 @@
"@types/markdown-it-container": "^2.0.10", "@types/markdown-it-container": "^2.0.10",
"@types/mime-types": "^2.1.4", "@types/mime-types": "^2.1.4",
"@types/pg": "^8.11.5", "@types/pg": "^8.11.5",
"@types/qs": "^6.9.15",
"bun-types": "latest", "bun-types": "latest",
"drizzle-kit": "^0.20.14", "drizzle-kit": "^0.20.14",
"oclif": "^4.10.4",
"ts-prune": "^0.10.3", "ts-prune": "^0.10.3",
"typescript": "latest", "typescript": "latest"
"@types/cli-progress": "^3.11.5",
"oclif": "^4.10.4"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.3.2" "typescript": "^5.3.2"

View file

@ -1,4 +1,4 @@
import { applyConfig, auth, handleZodError } from "@api"; import { applyConfig, auth, handleZodError, qs } from "@api";
import { zValidator } from "@hono/zod-validator"; import { zValidator } from "@hono/zod-validator";
import { errorResponse, jsonResponse } from "@response"; import { errorResponse, jsonResponse } from "@response";
import { sanitizeHtml, sanitizedHtmlStrip } from "@sanitization"; import { sanitizeHtml, sanitizedHtmlStrip } from "@sanitization";
@ -96,6 +96,7 @@ export default (app: Hono) =>
app.on( app.on(
meta.allowedMethods, meta.allowedMethods,
meta.route, meta.route,
qs(),
zValidator("form", schemas.form, handleZodError), zValidator("form", schemas.form, handleZodError),
auth(meta.auth), auth(meta.auth),
async (context) => { async (context) => {
@ -116,8 +117,6 @@ export default (app: Hono) =>
const self = user.getUser(); const self = user.getUser();
const sanitizedNote = await sanitizeHtml(note ?? "");
const sanitizedDisplayName = await sanitizedHtmlStrip( const sanitizedDisplayName = await sanitizedHtmlStrip(
display_name ?? "", display_name ?? "",
); );
@ -154,18 +153,14 @@ export default (app: Hono) =>
if (note && self.source) { if (note && self.source) {
// Check if bio doesnt match filters // Check if bio doesnt match filters
if ( if (config.filters.bio.some((filter) => note.match(filter))) {
config.filters.bio.some((filter) =>
sanitizedNote.match(filter),
)
) {
return errorResponse("Bio contains blocked words", 422); return errorResponse("Bio contains blocked words", 422);
} }
self.source.note = sanitizedNote; self.source.note = note;
self.note = await contentToHtml({ self.note = await contentToHtml({
"text/markdown": { "text/markdown": {
content: sanitizedNote, content: note,
}, },
}); });
} }
@ -270,7 +265,7 @@ export default (app: Hono) =>
// Parse emojis // Parse emojis
const displaynameEmojis = await parseEmojis(sanitizedDisplayName); const displaynameEmojis = await parseEmojis(sanitizedDisplayName);
const noteEmojis = await parseEmojis(sanitizedNote); const noteEmojis = await parseEmojis(self.note);
self.emojis = [...displaynameEmojis, ...noteEmojis, ...fieldEmojis]; self.emojis = [...displaynameEmojis, ...noteEmojis, ...fieldEmojis];

View file

@ -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 = () => { export const qs = () => {
return createMiddleware(async (context, next) => { return createMiddleware(async (context, next) => {
const parsed = parse(await context.req.text(), { const contentType = context.req.header("content-type");
parseArrays: true,
interpretNumericEntities: true,
});
context.req.parseBody = <T extends BodyData = BodyData>() => if (contentType?.includes("multipart/form-data")) {
Promise.resolve(parsed as T); // Get it as a query format to pass on to qs, then insert back files
// @ts-ignore Very bad hack const formData = await context.req.formData();
context.req.formData = () => Promise.resolve(parsed); const urlparams = new URLSearchParams();
// @ts-ignore I'm so sorry for this const files = new Map<string, File>();
context.req.bodyCache.formData = parsed; 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 = <T extends BodyData = BodyData>() =>
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 = <T extends BodyData = BodyData>() =>
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(); await next();
}); });
}; };