|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function escapeHtml(text, forAttribute = false) {
|
|
|
if (text === null || text === undefined) {
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
const str = String(text);
|
|
|
|
|
|
const map = {
|
|
|
'&': '&',
|
|
|
'<': '<',
|
|
|
'>': '>',
|
|
|
'"': '"',
|
|
|
"'": '''
|
|
|
};
|
|
|
|
|
|
let escaped = str.replace(/[&<>"']/g, m => map[m]);
|
|
|
|
|
|
|
|
|
if (forAttribute) {
|
|
|
escaped = escaped.replace(/"/g, '"').replace(/'/g, ''');
|
|
|
}
|
|
|
|
|
|
return escaped;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function safeSetInnerHTML(element, html) {
|
|
|
if (!element || !(element instanceof HTMLElement)) {
|
|
|
console.warn('[Sanitizer] Invalid element provided to safeSetInnerHTML');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
if (!html.includes('<') && !html.includes('>')) {
|
|
|
element.textContent = html;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const temp = document.createElement('div');
|
|
|
temp.innerHTML = html;
|
|
|
|
|
|
|
|
|
const walker = document.createTreeWalker(
|
|
|
temp,
|
|
|
NodeFilter.SHOW_TEXT,
|
|
|
null,
|
|
|
false
|
|
|
);
|
|
|
|
|
|
let node;
|
|
|
while (node = walker.nextNode()) {
|
|
|
if (node.textContent) {
|
|
|
node.textContent = node.textContent;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
element.innerHTML = '';
|
|
|
while (temp.firstChild) {
|
|
|
element.appendChild(temp.firstChild);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function sanitizeObject(obj, depth = 5) {
|
|
|
if (depth <= 0) {
|
|
|
return '[Max Depth Reached]';
|
|
|
}
|
|
|
|
|
|
if (obj === null || obj === undefined) {
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
if (typeof obj === 'string') {
|
|
|
return escapeHtml(obj);
|
|
|
}
|
|
|
|
|
|
if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
|
return obj;
|
|
|
}
|
|
|
|
|
|
if (Array.isArray(obj)) {
|
|
|
return obj.map(item => sanitizeObject(item, depth - 1));
|
|
|
}
|
|
|
|
|
|
if (typeof obj === 'object') {
|
|
|
const sanitized = {};
|
|
|
for (const key in obj) {
|
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
|
sanitized[key] = sanitizeObject(obj[key], depth - 1);
|
|
|
}
|
|
|
}
|
|
|
return sanitized;
|
|
|
}
|
|
|
|
|
|
return String(obj);
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function safeFormatNumber(value, options = {}) {
|
|
|
if (value === null || value === undefined || isNaN(value)) {
|
|
|
return '—';
|
|
|
}
|
|
|
|
|
|
const num = Number(value);
|
|
|
if (isNaN(num)) {
|
|
|
return '—';
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
return num.toLocaleString('en-US', {
|
|
|
minimumFractionDigits: options.minimumFractionDigits || 2,
|
|
|
maximumFractionDigits: options.maximumFractionDigits || 2,
|
|
|
...options
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.warn('[Sanitizer] Number formatting error:', error);
|
|
|
return String(num);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function safeFormatCurrency(value, currency = 'USD') {
|
|
|
if (value === null || value === undefined || isNaN(value)) {
|
|
|
return '—';
|
|
|
}
|
|
|
|
|
|
const num = Number(value);
|
|
|
if (isNaN(num)) {
|
|
|
return '—';
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
return new Intl.NumberFormat('en-US', {
|
|
|
style: 'currency',
|
|
|
currency: currency,
|
|
|
minimumFractionDigits: 2,
|
|
|
maximumFractionDigits: 2
|
|
|
}).format(num);
|
|
|
} catch (error) {
|
|
|
console.warn('[Sanitizer] Currency formatting error:', error);
|
|
|
return `$${num.toFixed(2)}`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|