fix: harden inline handlers against id injection, fix stale version label
Some checks failed
Caterium QA / qa (push) Has been cancelled
Some checks failed
Caterium QA / qa (push) Has been cancelled
Inline handlers built as onclick="fn('${esc(id)}')" were injectable:
esc() turns ' into ', which the browser decodes back to ' before
the JS runs, so an id like x');alert(1);// broke out of the string.
Ids can come from a restored backup file or a synced catalog. Add
SunSafe.jsArg (JSON.stringify + HTML escape) and use it in all 23
handlers in app-runtime.js and index.html. Verified in a browser: a
payload id is passed through as a plain string and nothing executes.
Also replace the Settings version label that still showed
v17.6.0 · 2026.09.07, and bump the cache-busting version of the two
changed scripts (sun-safe.js, app-runtime.js).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
716a79c58d
commit
eab678adbe
@ -429,7 +429,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
||||
function renderOrderLines(){
|
||||
if(!$('lines'))return;
|
||||
if(!(draft.lines||[]).length){$('lines').innerHTML='<p class="empty">Выберите позиции из каталога.</p>';return}
|
||||
const rows=draft.lines.map((line,index)=>{const item=boxes.find(x=>String(x.id)===String(line.id));if(!item)return'';const qty=Math.max(1,Number(line.qty||1)),price=lineUnitPrice(line),isBox=[0,5,6].includes(Number(item.category||0));return `<div class="sun-order-line-row" data-line-index="${index}"><div class="sun-order-line-name" title="${esc(item.name)}">${esc(String(item.name||'').replace(/^(?:Фуршетный\s+)?бокс\s*№\s*(\d+)\s*(?:[—–-]\s*)?/i,'№ $1 '))}${isBox&&(item.weight||inferBoxPieces(item))?`<small>${item.weight?`<b>Вес:</b> ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`<b>Количество:</b> ${inferBoxPieces(item)} шт.`:''}</small>`:''}</div><div class="sun-order-line-qty"><button type="button" onclick="qty('${esc(line.id)}',${qty-1})">−</button><input type="number" min="1" value="${qty}" onchange="qty('${esc(line.id)}',+this.value)"><button type="button" onclick="qty('${esc(line.id)}',${qty+1})">+</button></div><div class="sun-order-line-price"><input type="text" inputmode="decimal" autocomplete="off" value="${price}" onchange="sunOrderLinePriceChanged(${index},this)"></div><div class="sun-order-line-sum"><b>${money(price*qty)}</b></div></div>`}).join('');
|
||||
const rows=draft.lines.map((line,index)=>{const item=boxes.find(x=>String(x.id)===String(line.id));if(!item)return'';const qty=Math.max(1,Number(line.qty||1)),price=lineUnitPrice(line),isBox=[0,5,6].includes(Number(item.category||0));return `<div class="sun-order-line-row" data-line-index="${index}"><div class="sun-order-line-name" title="${esc(item.name)}">${esc(String(item.name||'').replace(/^(?:Фуршетный\s+)?бокс\s*№\s*(\d+)\s*(?:[—–-]\s*)?/i,'№ $1 '))}${isBox&&(item.weight||inferBoxPieces(item))?`<small>${item.weight?`<b>Вес:</b> ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`<b>Количество:</b> ${inferBoxPieces(item)} шт.`:''}</small>`:''}</div><div class="sun-order-line-qty"><button type="button" onclick="qty(${SunSafe.jsArg(line.id)},${qty-1})">−</button><input type="number" min="1" value="${qty}" onchange="qty(${SunSafe.jsArg(line.id)},+this.value)"><button type="button" onclick="qty(${SunSafe.jsArg(line.id)},${qty+1})">+</button></div><div class="sun-order-line-price"><input type="text" inputmode="decimal" autocomplete="off" value="${price}" onchange="sunOrderLinePriceChanged(${index},this)"></div><div class="sun-order-line-sum"><b>${money(price*qty)}</b></div></div>`}).join('');
|
||||
$('lines').innerHTML=`<div class="sun-order-lines-table"><div class="sun-order-line-head"><span>Наименование</span><span>Кол.</span><span>Цена</span><span>Стоимость</span></div><div class="sun-order-line-body">${rows}</div><div class="sun-order-line-total"><span>Стоимость позиций</span><b>${money(baseTotal(draft))}</b></div></div>`;
|
||||
}
|
||||
window.sunOrderLinePriceChanged=(index,input)=>{const line=draft.lines?.[index];if(!line)return;const value=Number(String(input.value||0).replace(',','.'));if(!Number.isFinite(value)){input.value=String(lineUnitPrice(line));return;}line.price=Math.max(0,Math.round(value*100)/100);const row=input.closest('.sun-order-line-row'),sum=row?.querySelector('.sun-order-line-sum b');if(sum)sum.textContent=money(line.price*Math.max(0,Number(line.qty||0)));const total=$('lines')?.querySelector('.sun-order-line-total b');if(total)total.textContent=money(baseTotal(draft));updateOrderSummary();};
|
||||
@ -445,7 +445,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
||||
renderCategoryTabs();ensureCatalogTools();const catalogSearch=$('sunCatalogSearch');if(catalogSearch){const boxMode=[0,5].includes(Number(activeCat));catalogSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';catalogSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#new .catalog h1');if(title)title.textContent=catName(activeCat);
|
||||
const all=boxes.filter(item=>(window.CateriumTrainingCatalog?.visible(item)??(item.hidden!==true))&&Number(item.category||0)===Number(activeCat)),items=all.filter(item=>itemMatches(item,catalogQuery));const cat=catById(activeCat)||{name:'Каталог',prep:true};
|
||||
if(Number(activeCat)===BANQUET_CATEGORY){$('tiles').innerHTML=renderBanquetView(items);window.CateriumBanquet.bind($('tiles'),{catalog:banquetCatalog(),draft,rerender:renderCatalogV5});if(!catalogOnly){renderOrderLines();updateOrderSummary();}return;}
|
||||
const cards=items.map(item=>`<button class="tile" type="button" onclick="add('${esc(item.id)}')" title="Добавить в заказ">${item.photo?`<img src="${esc(window.SunSafe.imageAssetSrc(item.photo))}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='${window.CateriumBranding.logoHTML()}'">`:("<div class=\"ph\"><img src=\""+window.CateriumBranding.logoHTML()+"\" class=\"sun-ph-logo\" alt=\"Логотип "+window.CateriumBranding.nameHTML()+"\"></div>")}<span>${esc(item.name)}${window.CateriumTrainingCatalog?.badge(item)||''}</span>${item.catalogSection?`<small class="tile-section">${esc(item.catalogSection)}</small>`:''}${(item.weight||inferBoxPieces(item))?`<small class="tile-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}<span class="tile-price-wrap"><small class="tile-price">${money(window.CateriumPricing.price(item))}</small></span>${window.CateriumPricing.badge(item)}</button>`).join('');
|
||||
const cards=items.map(item=>`<button class="tile" type="button" onclick="add(${SunSafe.jsArg(item.id)})" title="Добавить в заказ">${item.photo?`<img src="${esc(window.SunSafe.imageAssetSrc(item.photo))}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='${window.CateriumBranding.logoHTML()}'">`:("<div class=\"ph\"><img src=\""+window.CateriumBranding.logoHTML()+"\" class=\"sun-ph-logo\" alt=\"Логотип "+window.CateriumBranding.nameHTML()+"\"></div>")}<span>${esc(item.name)}${window.CateriumTrainingCatalog?.badge(item)||''}</span>${item.catalogSection?`<small class="tile-section">${esc(item.catalogSection)}</small>`:''}${(item.weight||inferBoxPieces(item))?`<small class="tile-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}<span class="tile-price-wrap"><small class="tile-price">${money(window.CateriumPricing.price(item))}</small></span>${window.CateriumPricing.badge(item)}</button>`).join('');
|
||||
const addText=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию');
|
||||
const empty=items.length?'':`<div class="catalog-empty">${catalogQuery?'По вашему запросу ничего не найдено.':`В разделе «${esc(cat.name)}» пока нет позиций.`}</div>`;
|
||||
$('tiles').innerHTML=`<button class="tile add" type="button" onclick="editBox(null)">+<br>${addText}</button>${cards}${empty}`;
|
||||
@ -467,7 +467,7 @@ window.SUN_LEGACY_CATALOG_V175=[];
|
||||
function renderManagerV5(){
|
||||
ensureManagerToolbar();const managerSearch=$('sunManagerSearch');if(managerSearch){const boxMode=[0,5].includes(Number(activeCat));managerSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';managerSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#manager .dialog-head h2'),add=document.querySelector('#manager .sun-manager-toolbar .primary')||document.querySelector('#manager .primary');if(title)title.textContent=catName(activeCat);if(add){add.textContent=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию');add.onclick=()=>window.editBox(null)}
|
||||
const items=boxes.filter(i=>(window.CateriumTrainingCatalog?.visible(i)??(i.hidden!==true))&&Number(i.category||0)===Number(activeCat)&&itemMatches(i,managerQuery)),cat=catById(activeCat)||{prep:true};
|
||||
$('managerList').innerHTML=items.length?items.map(item=>`<button type="button" onclick="editBox('${esc(item.id)}')">${item.photo?`<img src="${esc(window.SunSafe.imageAssetSrc(item.photo))}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='${window.CateriumBranding.logoHTML()}'">`:("<div class=\"ph\"><img src=\""+window.CateriumBranding.logoHTML()+"\" class=\"sun-ph-logo\" alt=\"Логотип "+window.CateriumBranding.nameHTML()+"\"></div>")}<b>${esc(item.name)}</b><span class="manager-price-wrap"><small class="manager-card-price">${money(window.CateriumPricing.price(item))}</small></span>${window.CateriumPricing.badge(item)}${(item.weight||inferBoxPieces(item))?`<small class="manager-card-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}${cat.prep?`<small>${(item.ingredients||[]).length} позиций в составе</small>`:''}</button>`).join(''):'<p class="empty">Ничего не найдено.</p>';
|
||||
$('managerList').innerHTML=items.length?items.map(item=>`<button type="button" onclick="editBox(${SunSafe.jsArg(item.id)})">${item.photo?`<img src="${esc(window.SunSafe.imageAssetSrc(item.photo))}" alt="" loading="lazy" decoding="async" onerror="this.onerror=null;this.src='${window.CateriumBranding.logoHTML()}'">`:("<div class=\"ph\"><img src=\""+window.CateriumBranding.logoHTML()+"\" class=\"sun-ph-logo\" alt=\"Логотип "+window.CateriumBranding.nameHTML()+"\"></div>")}<b>${esc(item.name)}</b><span class="manager-price-wrap"><small class="manager-card-price">${money(window.CateriumPricing.price(item))}</small></span>${window.CateriumPricing.badge(item)}${(item.weight||inferBoxPieces(item))?`<small class="manager-card-meta">${item.weight?`Вес: ${esc(item.weight)}`:''}${item.weight&&inferBoxPieces(item)?' · ':''}${inferBoxPieces(item)?`${inferBoxPieces(item)} шт.`:''}</small>`:''}${cat.prep?`<small>${(item.ingredients||[]).length} позиций в составе</small>`:''}</button>`).join(''):'<p class="empty">Ничего не найдено.</p>';
|
||||
}
|
||||
window.openManager=()=>{managerQuery='';ensureManagerToolbar();if($('sunManagerSearch'))$('sunManagerSearch').value='';renderManagerV5();window.modal?.('manager');};
|
||||
window.editBox=id=>{
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
})[ch]);
|
||||
const escapeAttr=escapeHTML;
|
||||
const idToken=value=>String(value??'').replace(/[^a-zA-Z0-9_-]/g,'');
|
||||
const jsArg=value=>escapeHTML(JSON.stringify(String(value??'')));
|
||||
// Existing trial databases and saved offers still reference the PNG originals.
|
||||
// Resolve only our ten bundled demo assets; never rewrite customer photos.
|
||||
const demoNames='berry-dessert|bruschetta-tomato|caprese|cheese-fruit|chicken-sandwich|meat-assortment|mushroom-tartlet|salmon-cream|turkey-wrap|vegetables-hummus';
|
||||
@ -28,7 +29,7 @@
|
||||
if(reference&&reference.parentNode===parent)parent.insertBefore(node,reference);else parent.appendChild(node);
|
||||
return node;
|
||||
};
|
||||
window.SunSafe=Object.freeze({escapeHTML,escapeAttr,idToken,safeImageSrc,imageAssetSrc,setText,insertBefore});
|
||||
window.SunSafe=Object.freeze({escapeHTML,escapeAttr,idToken,jsArg,safeImageSrc,imageAssetSrc,setText,insertBefore});
|
||||
|
||||
// Small bootstrap for account/profile UI. Keeping it here makes the account
|
||||
// center available on every Caterium screen without touching the legacy monolith.
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user