Mikhail Filchushkin
Mikhail Filchushkin

Reputation: 330

Angular 7 DOM sanitizing of mixed content

I need to make a private service like codepen.io. User must have the possibility of creating mixed content with included scripts, styles, inline-scripts, and inline-styles (yes, XSS-unsafe content with a lot of vulnerabilities).

Service screenshot

My service has an iframe and Monaco code editor. How should I insert user-generated HTML, styles, and scripts to the iframe without Angular XSS-defence and sanitizing?

How can I do it with Angular 7?

Maybe exists a method of manually marking user-generated content as sanitized?

Important pieces of code.

view.component.html:

<iframe id="view" [srcdoc]="resultPage | safe: 'html'"></iframe>

view.component.ts:

@Component({
  selector: 'app-view',
  templateUrl: './view.component.html',
  styleUrls: ['./view.component.scss']
})
export class ViewComponent implements OnInit {

  resultPage = '';

  _task: Task;
  set task(value: Task) {
    this._task = value;
    if (value) {
      this.refreshPage();
    }
  }
  get task() {
    return this._task;
  }

  constructor(private store: Store<RootStoreState.State>) { }

  ngOnInit() {
    this.store.pipe(select(selectSelectedTask))
      .subscribe((task) => {
        this.task = task;
      });
  }

  refreshPage() {
    if (!this.task) {return; }
    const htmlFile = this.task.files.find((item) => item.language === 'html')
    const cssFile = this.task.files.find((item) => item.language === 'css')
    const jsFile = this.task.files.find((item) => item.language === 'js')
    let html = htmlFile ? htmlFile.content : null;
    if (!html) {
      return;
    }
    let css = cssFile ? cssFile.content : null;
    let js = jsFile ? jsFile.content : null;
    html = this.insertCss(html, css);
    this.resultPage = html;
  }

  insertCss(html: string, css: string): string {
    const cssPlaceholder = `<style>${css}</style>`;
    const closedHead = html.indexOf('</head>');
    if (closedHead > -1) {
      html.replace('</head>', cssPlaceholder + '</head>');
      console.log(1, html);
      return html;
    }
    const openedBody = html.indexOf('<body>');
    if (openedBody > -1) {
      html.replace('<body>', '<body>' + cssPlaceholder);
      console.log(2, html);
      return html;
    }
    html += css;
    console.log(3, html);
    return html;
  }

}

Upvotes: 1

Views: 1767

Answers (2)

Serene Abraham Mathew
Serene Abraham Mathew

Reputation: 388

I have encountered a similiar issue. I found a work around as follows.

Instead of using srcdoc directive provided by angular, I created a custom directive to set srcdoc attribute of iframe element

Here is the code

srcdoc.directive.ts

import { Directive, Input, ElementRef, Renderer2, OnChanges, SimpleChanges } from "@angular/core";

@Directive({
  selector : '[app-srcdoc]'
})
export class SrcdocDirective implements OnChanges{
  // add data binding to directive itself
  @Input("app-srcdoc") source:string;
 
  constructor(private elementRef:ElementRef,private renderer:Renderer2) {}

  // update the srcdoc attribute whenever the binding changes
  ngOnChanges(changes: SimpleChanges): void {
    this.renderer.setAttribute(this.elementRef.nativeElement,"srcdoc",changes.source.currentValue);
  }

}

use it your component html as follows

<iframe id="output" [app-srcdoc]="iframeContent" ></iframe>

Upvotes: 1

GonzaH
GonzaH

Reputation: 347

Try using DomSanitizer

Calling any of the bypassSecurityTrust... APIs disables Angular's built-in sanitization for the value passed in

Upvotes: 0

Related Questions