angular
February 15

Angular innerHTML и DomSanitizer

При разработке веб-приложения возникают ситуации, когда необходимо добавить на страницу пользовательский HTML-код. Например, при использовании редактора rich-text мы можем захотеть отобразить HTML, сохраненный в базе данных.

Однако в фреймворке Angular применяются стандартные меры безопасности, которые могут затруднить эту задачу. Если попытаться использовать функцию innerHTML DOM для добавления HTML на страницу, вы увидите сам HTML-код, а не его отображение.

В этом руководстве мы рассмотрим встроенные меры безопасности Angular, которые предотвращают прямую вставку кода, объясним, почему они важны, и узнаем, как можно безопасно вставлять HTML-содержимое в DOM, если это действительно необходимо.

Допустим, вы работаете над проектом на Angular и хотите внедрить некоторый HTML-контент в DOM .Свойство innerHTML DOM позволяет разработчикам динамически внедрять HTML-контент в DOM.

Обратите внимание, что innerHTML не является специфической директивой Angular или чем-то подобным, это стандартная функциональность DOM.

На первый взгляд можно представить такое решение:

<div [innerHTML]="htmlContent"></div>
export class AppComponent {  htmlContent = "<h1>Hello, World!</h1>";}

Проблема здесь в том, что Angular не будет рендерить это как тег h1!

Вместо этого вступят в силу механизмы экранирования символов шаблона, и мы увидим сам код, отображаемый на странице, как если бы мы использовали редактор кода:

<h1>Hello, World!</h1>

Попробуем разобраться, почему происходит именно так:

Использование innerHTML в Angular

Проблема заключается в том, что Angular по умолчанию применяет встроенные механизмы защиты от внедрения кода, также известные как механизмы защиты от XSS или межсайтового скриптинга.

По умолчанию Angular считает, что любой текст, который мы передаем в шаблон, является небезопасным, и будет соответствующим образом экранировать его в зависимости от контекста, в котором мы используем текст.

Если мы пытаемся использовать HTML-код в шаблоне, Angular попытается экранировать текст в соответствии с правилами HTML, если это CSS, он будет экранировать код согласно правилам CSS и так далее. В приведенном выше примере Angular обнаружит, что мы пытаемся внедрить текст в контекст HTML и любой символ текста, переданный в DOM, будет экранирован с использованием правил HTML.

Так, например, символ “<” будет экранирован и превращен в “<”, и так далее. Это приведет к тому, что символ “<” будет отображаться на экране вместо того, чтобы быть интерпретированным как код.

Это может показаться неожиданным при первом взгляде, но на самом деле это хорошо с точки зрения безопасности. Этот механизм защищает наше приложение от множества различных видов атак безопасности, связанных с внедрением скриптов. Иногда есть веские причины желать обойти защиту, например, в случае нашего редактора rich-text.

Обход экранирования

Сервис DomSanitizer в Angular предоставляет различные методы обхода проверок безопасности для доверенных значений.

Вот как вы можете его использовать:

  1. Импортируйте DomSanitizer из пакета @angular/platform-browser в компоненте, где вам нужно обойти экранирование.
  2. Добавьте DomSanitizer в конструктор компонента, чтобы он был доступен для использования внутри компонента.
  3. В зависимости от типа содержимого используйте один из методов чтобы обойти ограничения:
  • bypassSecurityTrustHtml для HTML.
  • bypassSecurityTrustStyle для стилей.
  • bypassSecurityTrustScript для URL-адресов скриптов.
  • bypassSecurityTrustUrl для URL-адресов/ресурсов.
  • bypassSecurityTrustResourceUrl для ресурсов, таких как источники iframe.

Рассмотрим на примере:

@Component({ selector: 'app-unsafe-component', template: `<div [innerHTML]="trustedHtml"></div>` })
export class UnsafeComponent {
  trustedHtml: any;

  constructor(private _sanitizer: DomSanitizer) {
    const unsafeHtml = '<h1>Hello World!</h1>';
    this.trustedHtml = this._sanitizer.bypassSecurityTrustHtml(sanitizedHtml);
  }
}
Важно отметить, что обходя экранирование Angular, вы берете на себя ответственность за то, чтобы содержимое действительно было безопасным для отображения как HTML.

Получилось достаточно много кода. Чтобы упростить можно создать свой Pipe

SafeHtml Pipe

Cоздадим Pipe, который будет очищать HTML-содержимое, которое принимает. Он будет возвращать очищенное HTML-содержимое вызывающему объекту для вставки в DOM. Чтобы каждый раз не импортировать DomSantitizer, пайп будет использовать его под капотом:

@Pipe({ name: 'safeHtml', standalone: true })
export class SafeHtmlPipe {
  constructor(private sanitizer: DomSanitizer) {
  }

  transform(html) {
    return this.sanitizer.bypassSecurityTrustHtml(html);
  }
}

Пример использования:

@Component({ 
standalone: true,   
imports: [SafeHtmlPipe],   
template: `   <div [innerHTML]="someHtmlContent | safeHtml">  </div> ` })
export class TestComponent {}