| | |
| | | /*! |
| | | * shared v9.14.5 |
| | | * shared v9.14.4 |
| | | * (c) 2025 kazuya kawaguchi |
| | | * Released under the MIT License. |
| | | */ |
| | | 'use strict'; |
| | | |
| | | function warn(msg, err) { |
| | | if (typeof console !== 'undefined') { |
| | | console.warn(`[intlify] ` + msg); |
| | | /* istanbul ignore if */ |
| | | if (err) { |
| | | console.warn(err.stack); |
| | | } |
| | | } |
| | | } |
| | | const hasWarned = {}; |
| | | function warnOnce(msg) { |
| | | if (!hasWarned[msg]) { |
| | | hasWarned[msg] = true; |
| | | warn(msg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Original Utilities |
| | |
| | | }; |
| | | function escapeHtml(rawText) { |
| | | return rawText |
| | | .replace(/&/g, '&') // escape `&` first to avoid double escaping |
| | | .replace(/</g, '<') |
| | | .replace(/>/g, '>') |
| | | .replace(/"/g, '"') |
| | | .replace(/'/g, ''') |
| | | .replace(/\//g, '/') // escape `/` to prevent closing tags or JavaScript URLs |
| | | .replace(/=/g, '='); // escape `=` to prevent attribute injection |
| | | } |
| | | function escapeAttributeValue(value) { |
| | | return value |
| | | .replace(/&(?![a-zA-Z0-9#]{2,6};)/g, '&') // escape unescaped `&` |
| | | .replace(/"/g, '"') |
| | | .replace(/'/g, ''') |
| | | .replace(/</g, '<') |
| | | .replace(/>/g, '>'); |
| | | } |
| | | function sanitizeTranslatedHtml(html) { |
| | | // Escape dangerous characters in attribute values |
| | | // Process attributes with double quotes |
| | | html = html.replace(/(\w+)\s*=\s*"([^"]*)"/g, (_, attrName, attrValue) => `${attrName}="${escapeAttributeValue(attrValue)}"`); |
| | | // Process attributes with single quotes |
| | | html = html.replace(/(\w+)\s*=\s*'([^']*)'/g, (_, attrName, attrValue) => `${attrName}='${escapeAttributeValue(attrValue)}'`); |
| | | // Detect and neutralize event handler attributes |
| | | const eventHandlerPattern = /\s*on\w+\s*=\s*["']?[^"'>]+["']?/gi; |
| | | if (eventHandlerPattern.test(html)) { |
| | | { |
| | | warn('Potentially dangerous event handlers detected in translation. ' + |
| | | 'Consider removing onclick, onerror, etc. from your translation messages.'); |
| | | } |
| | | // Neutralize event handler attributes by escaping 'on' |
| | | html = html.replace(/(\s+)(on)(\w+\s*=)/gi, '$1on$3'); |
| | | } |
| | | // Disable javascript: URLs in various contexts |
| | | const javascriptUrlPattern = [ |
| | | // In href, src, action, formaction attributes |
| | | /(\s+(?:href|src|action|formaction)\s*=\s*["']?)\s*javascript:/gi, |
| | | // In style attributes within url() |
| | | /(style\s*=\s*["'][^"']*url\s*\(\s*)javascript:/gi |
| | | ]; |
| | | javascriptUrlPattern.forEach(pattern => { |
| | | html = html.replace(pattern, '$1javascript:'); |
| | | }); |
| | | return html; |
| | | .replace(/'/g, '''); |
| | | } |
| | | const hasOwnProperty = Object.prototype.hasOwnProperty; |
| | | function hasOwn(obj, key) { |
| | |
| | | function incrementer(code) { |
| | | let current = code; |
| | | return () => ++current; |
| | | } |
| | | |
| | | function warn(msg, err) { |
| | | if (typeof console !== 'undefined') { |
| | | console.warn(`[intlify] ` + msg); |
| | | /* istanbul ignore if */ |
| | | if (err) { |
| | | console.warn(err.stack); |
| | | } |
| | | } |
| | | } |
| | | const hasWarned = {}; |
| | | function warnOnce(msg) { |
| | | if (!hasWarned[msg]) { |
| | | hasWarned[msg] = true; |
| | | warn(msg); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | exports.join = join; |
| | | exports.makeSymbol = makeSymbol; |
| | | exports.objectToString = objectToString; |
| | | exports.sanitizeTranslatedHtml = sanitizeTranslatedHtml; |
| | | exports.toDisplayString = toDisplayString; |
| | | exports.toTypeString = toTypeString; |
| | | exports.warn = warn; |