first commit

This commit is contained in:
ysj7705 committed 2026-07-15 17:29:25 +09:00
commit a74fe7a388
42 files changed
+6058

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
const HANGUL_RE = /[\u1100-\u11FF\u3130-\u318F\uAC00-\uD7A3]/;
/**
* Multer/busboy often decodes multipart filenames as Latin-1.
* UTF-8 Hangul then appears as mojibake (e.g. 오…).
* Prefer an explicit UTF-8 `filename` form field from the client when present.
*/
export function decodeUploadFilename(name: string): string {
if (!name) return name;
if (HANGUL_RE.test(name)) return name;
try {
const decoded = Buffer.from(name, 'latin1').toString('utf8');
if (decoded.includes('\uFFFD')) return name;
if (HANGUL_RE.test(decoded)) return decoded;
return name;
} catch {
return name;
}
}
+83
View File
@@ -0,0 +1,83 @@
import type { DocumentType } from '../types/session.js';
const WORD = new Set([
'doc',
'docm',
'docx',
'dot',
'dotm',
'dotx',
'epub',
'fodt',
'htm',
'html',
'odt',
'ott',
'rtf',
'txt',
'xml',
]);
const CELL = new Set([
'csv',
'fods',
'ods',
'ots',
'xls',
'xlsb',
'xlsm',
'xlsx',
'xlt',
'xltm',
'xltx',
]);
const SLIDE = new Set([
'fodp',
'odp',
'otp',
'pot',
'potm',
'potx',
'pps',
'ppsm',
'ppsx',
'ppt',
'pptm',
'pptx',
]);
const PDF = new Set(['pdf', 'djvu', 'xps', 'oxps']);
export function extensionOf(filename: string): string {
const idx = filename.lastIndexOf('.');
if (idx < 0) return '';
return filename.slice(idx + 1).toLowerCase();
}
export function documentTypeFor(ext: string): DocumentType {
if (PDF.has(ext)) return 'pdf';
if (CELL.has(ext)) return 'cell';
if (SLIDE.has(ext)) return 'slide';
if (WORD.has(ext)) return 'word';
return 'word';
}
export function mimeFor(ext: string): string {
const map: Record<string, string> = {
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
pdf: 'application/pdf',
odt: 'application/vnd.oasis.opendocument.text',
ods: 'application/vnd.oasis.opendocument.spreadsheet',
odp: 'application/vnd.oasis.opendocument.presentation',
doc: 'application/msword',
xls: 'application/vnd.ms-excel',
ppt: 'application/vnd.ms-powerpoint',
rtf: 'application/rtf',
txt: 'text/plain',
csv: 'text/csv',
};
return map[ext] ?? 'application/octet-stream';
}
+31
View File
@@ -0,0 +1,31 @@
import jwt from 'jsonwebtoken';
import { config } from '../config.js';
export function signOnlyOfficePayload(payload: Record<string, unknown>): string {
return jwt.sign(payload, config.jwtSecret, { algorithm: 'HS256' });
}
export function verifyOnlyOfficeToken(token: string): Record<string, unknown> {
return jwt.verify(token, config.jwtSecret, {
algorithms: ['HS256'],
}) as Record<string, unknown>;
}
/** Extract JWT from Authorization header or body.token (OnlyOffice callback styles). */
export function extractToken(req: {
headers: { authorization?: string };
body?: { token?: string };
query?: { token?: string };
}): string | null {
const auth = req.headers.authorization;
if (auth?.startsWith('Bearer ')) {
return auth.slice(7);
}
if (typeof req.body?.token === 'string') {
return req.body.token;
}
if (typeof req.query?.token === 'string') {
return req.query.token;
}
return null;
}