93 lines
3.7 KiB
JavaScript
93 lines
3.7 KiB
JavaScript
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import crypto from 'crypto';
|
|
import lib from './inc/lib.mjs';
|
|
import cfg from './inc/config.mjs';
|
|
|
|
function parseMultipart(req) {
|
|
return new Promise((resolve, reject) => {
|
|
let body = [];
|
|
req.on('data', chunk => body.push(chunk));
|
|
req.on('end', () => {
|
|
const buffer = Buffer.concat(body);
|
|
const boundaryMatch = req.headers['content-type']?.match(/boundary=(.+)$/i);
|
|
if (!boundaryMatch) return reject(new Error('No boundary'));
|
|
const boundary = '--' + boundaryMatch[1];
|
|
let start = buffer.indexOf(boundary) + boundary.length + 2;
|
|
let fileData = null;
|
|
let filename = null;
|
|
let csrfToken = null;
|
|
|
|
while (start < buffer.length) {
|
|
const headerEnd = buffer.indexOf('\r\n\r\n', start);
|
|
if (headerEnd === -1) break;
|
|
const headerStr = buffer.slice(start, headerEnd).toString();
|
|
const nextBoundary = buffer.indexOf(boundary, headerEnd);
|
|
if (nextBoundary === -1) break;
|
|
const chunkData = buffer.slice(headerEnd + 4, nextBoundary - 2);
|
|
|
|
if (headerStr.includes('name="file"')) {
|
|
const fnMatch = headerStr.match(/filename="(.+?)"/);
|
|
if (fnMatch) filename = fnMatch[1];
|
|
fileData = chunkData;
|
|
} else if (headerStr.includes('name="csrf_token"')) {
|
|
csrfToken = chunkData.toString();
|
|
}
|
|
start = nextBoundary + boundary.length + 2;
|
|
}
|
|
resolve({ fileData, filename, csrfToken });
|
|
});
|
|
req.on('error', reject);
|
|
});
|
|
}
|
|
|
|
import db from './inc/sql.mjs';
|
|
|
|
export async function handleKoepfeUpload(req, res) {
|
|
// Manual Session Lookup since this is a bypass middleware
|
|
let user = [];
|
|
if (req.cookies && req.cookies.session) {
|
|
user = await db`
|
|
select "user".id, "user".login, "user".user, "user".admin, "user_sessions".csrf_token
|
|
from "user_sessions"
|
|
left join "user" on "user".id = "user_sessions".user_id
|
|
where "user_sessions".session = ${lib.sha256(req.cookies.session)}
|
|
limit 1
|
|
`;
|
|
}
|
|
|
|
if (user.length === 0 || !user[0].admin) {
|
|
return res.reply ? res.reply({ code: 403, body: JSON.stringify({ success: false, msg: 'Forbidden' }) }) : res.writeHead(403, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: 'Forbidden' }));
|
|
}
|
|
|
|
req.session = user[0];
|
|
|
|
try {
|
|
const { fileData, filename, csrfToken } = await parseMultipart(req);
|
|
|
|
if (!req.session.csrf_token || csrfToken !== req.session.csrf_token) {
|
|
return res.writeHead(403, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: 'Invalid CSRF' }));
|
|
}
|
|
|
|
if (!fileData || fileData.length === 0) {
|
|
throw new Error('No file data');
|
|
}
|
|
|
|
const ext = path.extname(filename || '.png').toLowerCase();
|
|
if (!['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif'].includes(ext)) {
|
|
throw new Error('Unsupported format');
|
|
}
|
|
|
|
const newName = crypto.randomBytes(8).toString('hex') + ext;
|
|
const targetPath = path.join(cfg.paths.koepfe, newName);
|
|
|
|
await fs.writeFile(targetPath, fileData);
|
|
|
|
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: true, file: '/s/koepfe/' + newName }));
|
|
|
|
} catch (e) {
|
|
console.error(e);
|
|
return res.writeHead(500, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: e.message }));
|
|
}
|
|
}
|