diff --git a/lib/package.json b/lib/package.json index eabd4626..c0116f03 100644 --- a/lib/package.json +++ b/lib/package.json @@ -30,6 +30,7 @@ "peerDependencies": { "@angular/common": "^6.0.0-rc.0 || ^6.0.0", "@angular/core": "^6.0.0-rc.0 || ^6.0.0", + "@angular/platform-browser": "^6.0.0-rc.0 || ^6.0.0", "core-js": "^2.5.4", "rxjs": "^6.0.0", "zone.js": "^0.8.26" diff --git a/lib/src/markdown.service.spec.ts b/lib/src/markdown.service.spec.ts index 133da0b3..1fb27e57 100644 --- a/lib/src/markdown.service.spec.ts +++ b/lib/src/markdown.service.spec.ts @@ -1,7 +1,9 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { BrowserModule, DomSanitizer } from '@angular/platform-browser'; import { parse } from 'marked'; +import { SecurityContext } from '@angular/core'; import { MarkdownService } from './markdown.service'; import { MarkedOptions } from './marked-options'; @@ -9,12 +11,16 @@ import { MarkedOptions } from './marked-options'; declare var Prism: any; describe('MarkdowService', () => { + let domSanitizer: DomSanitizer; let http: HttpTestingController; let markdownService: MarkdownService; beforeEach(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], + imports: [ + BrowserModule, + HttpClientTestingModule, + ], providers: [ { provide: MarkedOptions, useValue: {} }, MarkdownService, @@ -23,6 +29,7 @@ describe('MarkdowService', () => { }); beforeEach(() => { + domSanitizer = TestBed.get(DomSanitizer); http = TestBed.get(HttpTestingController); markdownService = TestBed.get(MarkdownService); }); @@ -108,6 +115,35 @@ describe('MarkdowService', () => { expect(markdownService.compile(mockRaw, null)).toBe(expected); expect(markdownService.compile(mockRaw, undefined)).toBe(expected); }); + + it('should sanitize when markedOptions.sanitize is true and no sanitizer function is provided', () => { + + const markedOptions: MarkedOptions = { sanitize: true }; + const mockRaw = '### Markdown-x'; + const sanitized = domSanitizer.sanitize(SecurityContext.HTML, parse(mockRaw)); + const unsanitized = parse(mockRaw); + + expect(markdownService.compile(mockRaw, false, markedOptions)).toBe(sanitized); + expect(markdownService.compile(mockRaw, false, markedOptions)).not.toBe(unsanitized); + }); + + it('should not sanitize when markedOptions.sanitize is true but a sanitizer function is provided', () => { + + const markedOptions: MarkedOptions = { sanitize: true, sanitizer: () => null }; + const mockRaw = '### Markdown-x'; + const expected = parse(mockRaw); + + expect(markdownService.compile(mockRaw, false, markedOptions)).toBe(expected); + }); + + it('should not sanitize when markedOptions.sanitize is false regardless of whether a sanitizer function is provided or not', () => { + + const mockRaw = '### Markdown-x'; + const expected = parse(mockRaw); + + expect(markdownService.compile(mockRaw, false, { sanitize: false })).toBe(expected); + expect(markdownService.compile(mockRaw, false, { sanitize: false, sanitizer: () => null })).toBe(expected); + }); }); describe('getSource', () => { diff --git a/lib/src/markdown.service.ts b/lib/src/markdown.service.ts index 2bb49c75..3864fcad 100644 --- a/lib/src/markdown.service.ts +++ b/lib/src/markdown.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable, Optional } from '@angular/core'; +import { Injectable, Optional, SecurityContext } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; import { parse, Renderer } from 'marked'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -15,15 +16,14 @@ export const errorSrcWithoutHttpClient = '[ngx-markdown] When using the [src] at @Injectable() export class MarkdownService { - get renderer(): Renderer { - return this.options.renderer; - } + get renderer(): Renderer { return this.options.renderer; } set renderer(value: marked.Renderer) { this.options.renderer = value; } constructor( @Optional() private http: HttpClient, + private domSanitizer: DomSanitizer, public options: MarkedOptions, ) { if (!this.renderer) { @@ -33,16 +33,18 @@ export class MarkdownService { compile(markdown: string, decodeHtml = false, markedOptions = this.options): string { const precompiled = this.precompile(markdown); - return parse( + const compiled = parse( decodeHtml ? this.decodeHtml(precompiled) : precompiled, markedOptions); + return markedOptions.sanitize && !markedOptions.sanitizer + ? this.domSanitizer.sanitize(SecurityContext.HTML, compiled) + : compiled; } getSource(src: string): Observable { if (!this.http) { throw new Error(errorSrcWithoutHttpClient); } - return this.http .get(src, { responseType: 'text' }) .pipe(map(markdown => this.handleExtension(src, markdown))); diff --git a/package.json b/package.json index 3075694e..ca2d6dd6 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "build:lib": "ng build lib", "postbuild:lib": "cpx ./README.md ./dist/lib", "link:lib": "rimraf node_modules/ngx-markdown && linklocal", + "lint": "yarn lint:lib && yarn lint:demo", "lint:demo": "ng lint demo", "lint:lib": "ng lint lib", "lint:ci": "ng lint lib --format checkstyle > tslint.xml",