mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 08:28:19 +01:00
fix(api): 🎨 Do wizardry on qs middleware to also work on multipart formData
This commit is contained in:
parent
2acd281c76
commit
a9629b825b
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
||||||
71
utils/api.ts
71
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 = () => {
|
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();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue