codexmobile-relay / server /app-attachments.js
Codex
deploy: CodexMobile Relay
90f0300
Raw
History Blame Contribute Delete
1.89 kB
import fs from 'node:fs';
import path from 'node:path';
import { UPLOAD_ROOT } from './app-config.js';
function attachmentError(message) {
return Object.assign(new Error(message), { statusCode: 400 });
}
function safeUploadPath(value) {
if (String(value || '').includes('\u0000')) {
throw attachmentError('invalid_attachment_path');
}
const resolvedRoot = path.resolve(UPLOAD_ROOT);
const resolvedPath = path.resolve(String(value || ''));
if (resolvedPath === resolvedRoot || !resolvedPath.startsWith(`${resolvedRoot}${path.sep}`)) {
throw attachmentError('invalid_attachment_path');
}
try {
const realRoot = fs.realpathSync(resolvedRoot);
const realPath = fs.realpathSync(resolvedPath);
if (realPath === realRoot || !realPath.startsWith(`${realRoot}${path.sep}`)) {
throw attachmentError('invalid_attachment_path');
}
} catch (error) {
if (error.statusCode) {
throw error;
}
throw attachmentError('invalid_attachment_path');
}
return resolvedPath;
}
export function normalizeAttachments(value) {
if (!Array.isArray(value)) {
return [];
}
return value
.filter((item) => item && typeof item.path === 'string' && item.path.trim())
.map((item) => ({
id: String(item.id || ''),
name: String(item.name || path.basename(item.path)),
size: Number(item.size) || 0,
mimeType: String(item.mimeType || ''),
path: safeUploadPath(item.path),
kind: item.kind === 'image' ? 'image' : 'file'
}));
}
export function withAttachmentReferences(message, attachments) {
if (!attachments.length) {
return message;
}
const lines = attachments.map((attachment) => {
const type = attachment.kind === 'image' ? '图片' : '文件';
return `- ${type}: ${attachment.name} (${attachment.path})`;
});
return `${message}\n\n附件路径:\n${lines.join('\n')}`;
}