{"version":3,"file":"getSanitizedHTML.mjs","sources":[""],"sourcesContent":["import xssModule from \"xss\";\nimport type { IFilterXSSOptions } from \"xss\";\n\ninterface IFilterXSSInstance {\n  process(html: string): string;\n}\n\ninterface IXssModuleShapes {\n  FilterXSS: new (options?: IFilterXSSOptions) => IFilterXSSInstance;\n  getDefaultWhiteList: () => Record<string, string[]>;\n  safeAttrValue: (tag: string, name: string, value: string) => string;\n}\n\nconst {\n  FilterXSS,\n  getDefaultWhiteList,\n  safeAttrValue,\n} = xssModule as unknown as IXssModuleShapes;\n\n// Разрешённые протоколы для href и src (строгий белый список)\nconst SAFE_PROTOCOLS = [ \"http:\", \"https:\", \"mailto:\", \"tel:\" ];\n\n// Все атрибуты, начинающиеся с on (игнорируем регистр), опасны\nconst isEventHandlerAttr = (name: string) => /^on/i.test(name);\n\n// Проверка, является ли URL безопасным (относительным или с разрешённым протоколом)\nfunction isSafeURL(value: string): boolean {\n  const lower = value.trim().toLowerCase();\n  if (lower.startsWith(\"//\")) {\n    return false;\n  } // protocol-relative запрещены\n  if (\n    // eslint-disable-next-line no-script-url\n    lower.startsWith(\"javascript:\") ||\n    lower.startsWith(\"data:\") ||\n    lower.startsWith(\"vbscript:\")\n  ) {\n    return false;\n  }\n\n  // Разрешённые протоколы и якоря\n  if (SAFE_PROTOCOLS.some((p) => lower.startsWith(p)) || lower.startsWith(\"#\")) {\n    return true;\n  }\n\n  // Относительные пути (без ':' до первого слеша) разрешены\n  const firstSlash = lower.indexOf(\"/\");\n  const colonPos = lower.indexOf(\":\");\n  return colonPos === -1 || (firstSlash !== -1 && colonPos > firstSlash);\n}\n\nconst safeOnTagAttr = (tag: string, name: string, value: string): string => {\n  const attrNameLower = name.toLowerCase();\n\n  // Запрещаем ВСЕ атрибуты, начинающиеся с on (onclick, onerror, onload, …)\n  if (isEventHandlerAttr(attrNameLower)) {\n    return \"\";\n  }\n\n  // Для ссылок и источников проверяем протокол\n  if (attrNameLower === \"href\" || attrNameLower === \"src\") {\n    if (!isSafeURL(value)) {\n      return \"\";\n    }\n  }\n\n  // safeAttrValue возвращает только значение, экранируя спецсимволы.\n  // Собираем полный атрибут сами.\n  return `${name}=\"${safeAttrValue(tag, name, value)}\"`;\n};\n\n// 1. Получаем дефолтный список тегов библиотеки xss\nconst defaultList = getDefaultWhiteList();\n\n// 2. Добавляем все нужные современные HTML5, SVG, компонентные и форменные теги\nconst modernTags = [\n  // SVG Графика и Спрайты\n  \"svg\", \"defs\", \"symbol\", \"path\", \"use\", \"g\",\n  \"circle\", \"rect\", \"line\", \"polyline\", \"polygon\", \"ellipse\",\n  \"lineargradient\", \"radialgradient\", \"stop\", \"mask\", \"clippath\",\n  \"text\", \"tspan\", \"pattern\", \"filter\", \"feGaussianBlur\", \"feMerge\", \"feMergeNode\",\n\n  // Веб-компоненты и шаблонизация\n  \"template\", \"slot\",\n\n  // Современные интерактивные и структурные HTML5-теги\n  \"dialog\", \"picture\", \"source\", \"main\", \"header\", \"footer\", \"section\",\n  \"article\", \"aside\", \"nav\", \"figure\", \"figcaption\", \"hgroup\",\n  \"mark\", \"time\", \"meter\", \"progress\", \"output\", \"details\", \"summary\",\n\n  // Медиа-контент\n  \"video\", \"audio\", \"track\", \"canvas\",\n\n  // Дополнительные встроенные элементы разметки\n  \"ins\",\n\n  // === Формы и элементы ввода ===\n  \"form\", \"input\", \"button\", \"select\", \"textarea\", \"option\",\n  \"optgroup\", \"fieldset\", \"legend\", \"label\", \"datalist\",\n];\n\n// Объединяем дефолтные теги xss и наши новые modernTags в один плоский список\nconst allAllowedTags = Array.from(\n  new Set([\n    ...Object.keys(defaultList),\n    ...modernTags.map((t) => t.toLowerCase()),\n  ])\n);\n\n// 3. Создаем финальный объект белого списка, где у всех тегов атрибуты не ограничены\n// (фильтрация опасных атрибутов выполняется в onTagAttr)\nconst customWhiteList: Record<string, string[]> = {};\nallAllowedTags.forEach((tag) => {\n  customWhiteList[tag] = [];\n});\n\n// Экземпляр санитайзера с безопасными настройками по умолчанию\nconst xssInstance = new FilterXSS({\n  whiteList: customWhiteList,\n\n  // Удаляем содержимое тегов script\n  stripIgnoreTagBody: [ \"script\" ],\n\n  // Полностью удаляем любые теги, не попавшие в белый список\n  stripIgnoreTag: true,\n\n  onTagAttr: safeOnTagAttr,\n});\n\n/**\n * Получает очищенную строку HTML.\n * Если передан config, он применяется ПОВЕРХ базовых безопасных настроек,\n * но критичные фильтры (onTagAttr, экранирование) НЕ могут быть переопределены.\n * @returns {string}\n */\nexport const getSanitizedHTML = (source: string, config?: IFilterXSSOptions): string => {\n  const html = typeof source === \"string\" ? source : `${source ?? \"\"}`;\n  if (!html) {\n    return \"\";\n  }\n\n  if (config) {\n    // Безопасный мерж: пользователь может добавить теги/атрибуты,\n    // но опасные переопределения запрещены\n    const customXSS = new FilterXSS({\n      whiteList: customWhiteList,\n      stripIgnoreTagBody: [ \"script\" ],\n      stripIgnoreTag: true,\n      ...config,\n      onTagAttr: safeOnTagAttr, // всегда наш обработчик атрибутов\n      onIgnoreTag: undefined, // удаляем неизвестные теги (перестраховка)\n    });\n    return customXSS.process(html);\n  }\n\n  return xssInstance.process(html);\n};\n"],"names":["FilterXSS","getDefaultWhiteList","safeAttrValue","xssModule","SAFE_PROTOCOLS","isEventHandlerAttr","name","test","isSafeURL","value","lower","trim","toLowerCase","startsWith","some","p","firstSlash","indexOf","colonPos","safeOnTagAttr","tag","attrNameLower","defaultList","modernTags","allAllowedTags","Array","from","Set","Object","keys","map","t","customWhiteList","forEach","xssInstance","whiteList","stripIgnoreTagBody","stripIgnoreTag","onTagAttr","getSanitizedHTML","source","config","html","customXSS","onIgnoreTag","undefined","process"],"mappings":"2BAaA,MAAMA,UACJA,UAASC,oBACTA,oBAAmBC,cACnBA,eACEC;8DAGJ;MAAMC,eAAiB,CAAE,QAAS,SAAU,UAAW;+DAGvD;MAAMC,mBAAsBC,MAAiB,OAAOC,KAAKD;oFAGzD;SAASE,UAAUC,OACjB,MAAMC,MAAQD,MAAME,OAAOC,cAC3B,GAAIF,MAAMG,WAAW,MACnB,OAAO,oCAET;;;AAEEH,MAAMG,WAAW,gBACjBH,MAAMG,WAAW,UACjBH,MAAMG,WAAW,aAEjB,OAAO;gCAIT;GAAIT,eAAeU,KAAMC,GAAML,MAAMG,WAAWE,KAAOL,MAAMG,WAAW,KACtE,OAAO;0DAIT;MAAMG,WAAaN,MAAMO,QAAQ,KACjC,MAAMC,SAAWR,MAAMO,QAAQ,KAC/B,OAAOC,YAAa,GAAOF,cAAe,GAAME,SAAWF,UAC7D,CAEA,MAAMG,cAAgB,CAACC,IAAad,KAAcG,SAChD,MAAMY,cAAgBf,KAAKM;0EAG3B;GAAIP,mBAAmBgB,eACrB,MAAO;6CAIT;GAAIA,gBAAkB,QAAUA,gBAAkB,MAChD,IAAKb,UAAUC,OACb,MAAO;;gCAMX;MAAO,GAAGH,SAASJ,cAAckB,IAAKd,KAAMG;oDAI9C;MAAMa,YAAcrB;gFAGpB;MAAMsB,WAAa;;AAEjB,MAAO,OAAQ,SAAU,OAAQ,MAAO,IACxC,SAAU,OAAQ,OAAQ,WAAY,UAAW,UACjD,iBAAkB,iBAAkB,OAAQ,OAAQ,WACpD,OAAQ,QAAS,UAAW,SAAU,iBAAkB,UAAW;;AAGnE,WAAY;;AAGZ,SAAU,UAAW,SAAU,OAAQ,SAAU,SAAU,UAC3D,UAAW,QAAS,MAAO,SAAU,aAAc,SACnD,OAAQ,OAAQ,QAAS,WAAY,SAAU,UAAW;;AAG1D,QAAS,QAAS,QAAS;;AAG3B;;AAGA,OAAQ,QAAS,SAAU,SAAU,WAAY,SACjD,WAAY,WAAY,SAAU,QAAS;8EAI7C;MAAMC,eAAiBC,MAAMC,KAC3B,IAAIC,IAAI,IACHC,OAAOC,KAAKP,gBACZC,WAAWO,IAAKC,GAAMA,EAAEnB;;yDAM/B;MAAMoB,gBAA4C,CAAA,EAClDR,eAAeS,QAASb,MACtBY,gBAAgBZ,KAAO;+DAIzB;MAAMc,YAAc,IAAIlC,UAAU,CAChCmC,UAAWH;;AAGXI,mBAAoB,CAAE;;AAGtBC,eAAgB,KAEhBC,UAAWnB;;;;;;SASAoB,iBAAmB,CAACC,OAAgBC,UAC/C,MAAMC,YAAcF,SAAW,SAAWA,OAAS,GAAGA,QAAU,KAChE,IAAKE,KACH,MAAO,GAGT,GAAID,OAAQ;;;AAGV,MAAME,UAAY,IAAI3C,UAAU,CAC9BmC,UAAWH,gBACXI,mBAAoB,CAAE,UACtBC,eAAgB,QACbI,OACHH,UAAWnB;;AACXyB,iBAAaC,IAEf,OAAOF,UAAUG,QAAQJ,KAC3B,CAEA,OAAOR,YAAYY,QAAQJ"}