feat: ora è possibile scorrere gli allegati e modificare i pdf, aggiunte personalizzazioni di immagini e forme per poterle richiamare più rapidamente.
This commit is contained in:
1 parent
ec1fb25d4e
commit
719fc09c66
8 files changed
+2168
-71
No files matched your search
+111
-33
@@ -457,27 +457,75 @@ router.get('/:id', async (req, res) => {
|
||||
[id]
|
||||
);
|
||||
|
||||
const iconv = require('iconv-lite');
|
||||
|
||||
const decodeTextContent = (rawContent, contentTypeStr = '') => {
|
||||
if (!rawContent) return '';
|
||||
let rawBuf = null;
|
||||
|
||||
if (Buffer.isBuffer(rawContent)) {
|
||||
rawBuf = rawContent;
|
||||
} else if (typeof rawContent === 'string') {
|
||||
const cleaned = rawContent.replace(/\s+/g, '');
|
||||
if (cleaned.length > 20 && cleaned.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(cleaned)) {
|
||||
try {
|
||||
rawBuf = Buffer.from(cleaned, 'base64');
|
||||
} catch (_) {
|
||||
rawBuf = Buffer.from(rawContent, 'utf-8');
|
||||
}
|
||||
} else {
|
||||
rawBuf = Buffer.from(rawContent, 'utf-8');
|
||||
}
|
||||
} else {
|
||||
return String(rawContent);
|
||||
}
|
||||
|
||||
// Detect charset from content-type header
|
||||
let charset = 'utf-8';
|
||||
const charsetMatch = (contentTypeStr || '').match(/charset=["']?([^"';\s]+)/i);
|
||||
if (charsetMatch && charsetMatch[1]) {
|
||||
charset = charsetMatch[1].toLowerCase();
|
||||
}
|
||||
|
||||
let text = '';
|
||||
try {
|
||||
if (iconv.encodingExists(charset)) {
|
||||
text = iconv.decode(rawBuf, charset);
|
||||
} else {
|
||||
text = rawBuf.toString('utf-8');
|
||||
}
|
||||
} catch (_) {
|
||||
text = rawBuf.toString('utf-8');
|
||||
}
|
||||
|
||||
// If UTF-8 decoding contains replacement character U+FFFD (), retry decoding as win1252/latin1
|
||||
if (text.includes('\uFFFD') || text.includes('')) {
|
||||
try {
|
||||
const latin1Text = iconv.decode(rawBuf, 'windows-1252');
|
||||
if (latin1Text && !latin1Text.includes('\uFFFD')) {
|
||||
text = latin1Text;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Repair Double-UTF-8 / Mojibake (e.g., ’ -> ’, ù -> ù, è -> è, à -> à, à -> È)
|
||||
if (/(?:Ã[\s\xa0¡-ÿ]|â€[™œ]|–)/.test(text)) {
|
||||
try {
|
||||
const reEncoded = iconv.encode(text, 'win1252');
|
||||
const repaired = iconv.decode(reEncoded, 'utf-8');
|
||||
if (repaired && !repaired.includes('\uFFFD')) {
|
||||
text = repaired;
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
const htmlBodyMap = {};
|
||||
for (const row of htmlAttachmentsRes.rows) {
|
||||
if (row.content) {
|
||||
let contentStr = '';
|
||||
if (Buffer.isBuffer(row.content)) {
|
||||
contentStr = row.content.toString('utf-8');
|
||||
} else if (typeof row.content === 'string') {
|
||||
contentStr = row.content;
|
||||
}
|
||||
|
||||
const cleaned = contentStr.replace(/\s+/g, '');
|
||||
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
||||
if (cleaned.length > 20 && cleaned.length % 4 === 0 && base64Regex.test(cleaned)) {
|
||||
try {
|
||||
const decoded = Buffer.from(cleaned, 'base64').toString('utf-8');
|
||||
if (decoded && /<(div|p|br|table|html|body|span|img|a|style)\b/i.test(decoded)) {
|
||||
contentStr = decoded;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const contentStr = decodeTextContent(row.content, row.content_type);
|
||||
if (contentStr && /<(div|p|br|table|html|body|span|img|a|style)\b/i.test(contentStr)) {
|
||||
const fname = (row.filename || '').toLowerCase();
|
||||
if (!htmlBodyMap[row.article_id] || fname.startsWith('file-1') || fname.startsWith('file-2')) {
|
||||
@@ -488,6 +536,14 @@ router.get('/:id', async (req, res) => {
|
||||
}
|
||||
|
||||
articlesResult.rows.forEach(a => {
|
||||
// Ensure a.a_body and subject are properly decoded if legacy encoded
|
||||
if (a.a_body) {
|
||||
a.a_body = decodeTextContent(a.a_body, a.a_content_type);
|
||||
}
|
||||
if (a.a_subject) {
|
||||
a.a_subject = decodeTextContent(a.a_subject, 'text/plain; charset=utf-8');
|
||||
}
|
||||
|
||||
if (htmlBodyMap[a.article_id]) {
|
||||
a.html_body = htmlBodyMap[a.article_id];
|
||||
a.has_html = true;
|
||||
@@ -1821,34 +1877,56 @@ router.get('/attachments/:id', async (req, res) => {
|
||||
}
|
||||
const attachment = result.rows[0];
|
||||
|
||||
// Decode base64 if necessary
|
||||
let contentBuffer;
|
||||
if (attachment.content) {
|
||||
let contentStr = '';
|
||||
if (Buffer.isBuffer(attachment.content)) {
|
||||
contentStr = attachment.content.toString('utf-8');
|
||||
} else if (typeof attachment.content === 'string') {
|
||||
contentStr = attachment.content;
|
||||
}
|
||||
// Check for raw binary signatures (%PDF, JPEG, PNG, Zip)
|
||||
const isRawBinary = attachment.content.length > 4 && (
|
||||
(attachment.content[0] === 0x25 && attachment.content[1] === 0x50 && attachment.content[2] === 0x44 && attachment.content[3] === 0x46) || // %PDF
|
||||
(attachment.content[0] === 0xFF && attachment.content[1] === 0xD8) || // JPEG
|
||||
(attachment.content[0] === 0x89 && attachment.content[1] === 0x50) || // PNG
|
||||
(attachment.content[0] === 0x50 && attachment.content[1] === 0x4B) // ZIP/DOCX/XLSX
|
||||
);
|
||||
|
||||
const cleaned = contentStr.replace(/\s+/g, '');
|
||||
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
||||
if (cleaned.length % 4 === 0 && base64Regex.test(cleaned)) {
|
||||
try {
|
||||
contentBuffer = Buffer.from(cleaned, 'base64');
|
||||
} catch (e) {
|
||||
contentBuffer = Buffer.isBuffer(attachment.content) ? attachment.content : Buffer.from(attachment.content);
|
||||
if (isRawBinary) {
|
||||
contentBuffer = attachment.content;
|
||||
} else {
|
||||
const str = attachment.content.toString('utf-8');
|
||||
const cleaned = str.replace(/\s+/g, '');
|
||||
if (cleaned.length > 0 && cleaned.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(cleaned)) {
|
||||
try {
|
||||
contentBuffer = Buffer.from(cleaned, 'base64');
|
||||
} catch (_) {
|
||||
contentBuffer = attachment.content;
|
||||
}
|
||||
} else {
|
||||
contentBuffer = attachment.content;
|
||||
}
|
||||
}
|
||||
} else if (typeof attachment.content === 'string') {
|
||||
const cleaned = attachment.content.replace(/\s+/g, '');
|
||||
if (cleaned.length > 0 && cleaned.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(cleaned)) {
|
||||
try {
|
||||
contentBuffer = Buffer.from(cleaned, 'base64');
|
||||
} catch (_) {
|
||||
contentBuffer = Buffer.from(attachment.content, 'utf-8');
|
||||
}
|
||||
} else {
|
||||
contentBuffer = Buffer.from(attachment.content, 'utf-8');
|
||||
}
|
||||
} else {
|
||||
contentBuffer = Buffer.isBuffer(attachment.content) ? attachment.content : Buffer.from(attachment.content);
|
||||
contentBuffer = Buffer.from(attachment.content);
|
||||
}
|
||||
} else {
|
||||
contentBuffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
const isDownload = req.query.download === '1' || req.query.disposition === 'attachment';
|
||||
const disposition = isDownload ? 'attachment' : 'inline';
|
||||
const safeFilename = attachment.filename || 'attachment';
|
||||
|
||||
res.setHeader('Content-Type', attachment.content_type || 'application/octet-stream');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(safeFilename)}"; filename*=UTF-8''${encodeURIComponent(safeFilename)}`);
|
||||
res.setHeader('Content-Disposition', `${disposition}; filename="${encodeURIComponent(safeFilename)}"; filename*=UTF-8''${encodeURIComponent(safeFilename)}`);
|
||||
res.send(contentBuffer);
|
||||
} catch (err) {
|
||||
console.error('Errore download allegato:', err);
|
||||
|
||||
Reference in new issue
Block a user