diff --git a/api/app/static/js/mappings.js b/api/app/static/js/mappings.js index bcc511a..86f5ad0 100644 --- a/api/app/static/js/mappings.js +++ b/api/app/static/js/mappings.js @@ -109,12 +109,12 @@ function renderTable(mappings, showDeleted) { const inactiveStyle = !m.activ && !m.sters ? 'opacity:0.6;' : ''; html += `
${esc(m.sku)}${kitBadge} ${esc(m.product_name || '')} ${m.sters - ? `` + ? `` : `` }
`; @@ -128,7 +128,7 @@ function renderTable(mappings, showDeleted) { ${esc(m.denumire || '')} x${m.cantitate_roa}${isKitRow ? kitPriceSlot : inlinePrice} + ${m.sters ? '' : `onclick="editFlatValue(this, '${jsAttrEsc(m.sku)}', '${jsAttrEsc(m.codmat)}', 'cantitate_roa', ${m.cantitate_roa})"`}>x${m.cantitate_roa}${isKitRow ? kitPriceSlot : inlinePrice} `; diff --git a/api/app/static/js/shared.js b/api/app/static/js/shared.js index b78ad86..fb7f866 100644 --- a/api/app/static/js/shared.js +++ b/api/app/static/js/shared.js @@ -33,6 +33,23 @@ function esc(s) { .replace(/'/g, '''); } +// JS-string-inside-HTML-attribute escape. +// HTML parser decodes entities (incl. ') BEFORE the JS parser runs the inline +// handler, so esc() is unsafe for `onclick="fn('${...}')"`. This escapes for the +// JS string layer (\\, \') first, then HTML-escapes the dangerous attribute chars +// (&, ", <, >) — leaving apostrophes literal so JS sees \' after attribute decoding. +function jsAttrEsc(s) { + if (s == null) return ''; + return String(s) + .replace(/\\/g, '\\\\') + .replace(/'/g, "\\'") + .replace(/\r?\n/g, '\\n') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); +} + // ── Date formatting ─────────────────────────────── function fmtDate(dateStr, includeSeconds) { if (!dateStr) return '-'; @@ -876,7 +893,7 @@ async function renderOrderDetailModal(orderNumber, opts) { ? item.codmat_details.map(d => `${esc(d.codmat)}${d.direct ? ' direct' : ''}`).join(' ') : `${esc(item.codmat || '–')}`; const valoare = (Number(item.price || 0) * Number(item.quantity || 0)); - const clickAttr = opts.onQuickMap ? `onclick="_sharedModalQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}',${idx})"` : ''; + const clickAttr = opts.onQuickMap ? `onclick="_sharedModalQuickMap('${jsAttrEsc(item.sku)}','${jsAttrEsc(item.product_name||'')}','${jsAttrEsc(orderNumber)}',${idx})"` : ''; return `
${esc(item.sku)} @@ -936,7 +953,7 @@ async function renderOrderDetailModal(orderNumber, opts) { // Desktop items table const clickAttrFn = (item, idx) => opts.onQuickMap - ? `onclick="_sharedModalQuickMap('${esc(item.sku)}', '${esc(item.product_name || '')}', '${esc(orderNumber)}', ${idx})" title="Click pentru mapare"` + ? `onclick="_sharedModalQuickMap('${jsAttrEsc(item.sku)}', '${jsAttrEsc(item.product_name || '')}', '${jsAttrEsc(orderNumber)}', ${idx})" title="Click pentru mapare"` : ''; let tableHtml = items.map((item, idx) => { diff --git a/api/app/templates/base.html b/api/app/templates/base.html index 91df5f8..76e272e 100644 --- a/api/app/templates/base.html +++ b/api/app/templates/base.html @@ -169,7 +169,7 @@ - + + {% endblock %} diff --git a/api/app/templates/missing_skus.html b/api/app/templates/missing_skus.html index 7341ebd..56983ac 100644 --- a/api/app/templates/missing_skus.html +++ b/api/app/templates/missing_skus.html @@ -187,7 +187,7 @@ function renderMissingSkusTable(skus, data) { tbody.innerHTML = skus.map(s => { const trAttrs = !s.resolved - ? ` style="cursor:pointer" onclick="openMapModal('${esc(s.sku)}', '${esc(s.product_name || '')}')"` + ? ` style="cursor:pointer" onclick="openMapModal('${jsAttrEsc(s.sku)}', '${jsAttrEsc(s.product_name || '')}')"` : ''; return ` ${s.resolved ? '' : ''} @@ -195,7 +195,7 @@ function renderMissingSkusTable(skus, data) { ${esc(s.product_name || '-')} ${!s.resolved - ? ` + ? ` ` : `${s.resolved_at ? new Date(s.resolved_at).toLocaleDateString('ro-RO') : ''}`} @@ -206,10 +206,10 @@ function renderMissingSkusTable(skus, data) { if (mobileList) { mobileList.innerHTML = skus.map(s => { const actionHtml = !s.resolved - ? `` + ? `` : `${s.resolved_at ? new Date(s.resolved_at).toLocaleDateString('ro-RO') : ''}`; const flatRowAttrs = !s.resolved - ? ` onclick="openMapModal('${esc(s.sku)}', '${esc(s.product_name || '')}')" style="cursor:pointer"` + ? ` onclick="openMapModal('${jsAttrEsc(s.sku)}', '${jsAttrEsc(s.product_name || '')}')" style="cursor:pointer"` : ''; return `
${s.resolved ? '' : ''}