1+ /*
2+ * HTMLPasteurizer
3+ * Copyright 2014 Jordan Milne
4+ *
5+ * Licensed under the Apache License, Version 2.0 (the "License");
6+ * you may not use this file except in compliance with the License.
7+ * You may obtain a copy of the License at
8+ *
9+ * http://www.apache.org/licenses/LICENSE-2.0
10+ *
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
16+ */
17+
18+ ( function ( window , $ ) {
19+ "use strict" ;
20+
21+ var Pasteurizer = { } ;
22+ window . Pasteurizer = Pasteurizer ;
23+
24+ // Some older browsers allow whitespace in protocols, but ignore
25+ // it during processing. Strip any weirdness out.
26+ var SCHEME_FILTER = / ( : (? ! $ ) | [ ^ : a - z 0 - 9 \. \- \+ ] ) / ig;
27+
28+ Pasteurizer . DEFAULT_CONFIG = {
29+ elemWhitelist : [
30+ 'h1' , 'h2' , 'h3' , 'h4' , 'h5' , 'h6' , 'span' , 'div' , 'code' ,
31+ 'br' , 'hr' , 'p' , 'a' , 'img' , 'pre' , 'blockquote' , 'table' ,
32+ 'thead' , 'tbody' , 'tfoot' , 'tr' , 'th' , 'td' , 'strong' , 'em' ,
33+ 'i' , 'b' , 'u' , 'ul' , 'ol' , 'li' , 'dl' , 'dt' , 'dd' ,
34+ 'font' , 'center' , 'small' , 's' , 'q' , 'sub' , 'sup' , 'del'
35+ ] ,
36+ // global attribute whitelist
37+ attrWhitelist : [
38+ 'title' , 'colspan' , 'rowspan' , 'cellspacing' , 'cellpadding' ,
39+ 'scope' , 'face' , 'color' , 'size' , 'bgcolor' , 'align'
40+ ] ,
41+ // tag-specific attribute whitelists
42+ tagAttrWhitelist : {
43+ 'img' : [ 'src' , 'alt' ] ,
44+ 'a' : [ 'href' ]
45+ } ,
46+ // Which schemes may be linked to
47+ schemeWhitelist : [
48+ "http:" , "https:" , "ftp:" , "mailto:" ,
49+ "git:" , "steam:" , "irc:" , "news:" , "mumble:" ,
50+ "ssh:" , "ircs:" , "ts3server:" , ":"
51+ ] ,
52+ // Whether or not to hoist the contents of removed nodes up the tree.
53+ hoistOrphanedContents : true ,
54+
55+ // Tags that should *not* have their contents hoisted
56+ hoistBlacklist : [ "script" , "style" ]
57+ } ;
58+
59+ Pasteurizer . scrubNode = function ( node , config ) {
60+ var jNode = $ ( node ) ;
61+ var nodeName = node . nodeName . toLowerCase ( ) ;
62+ var nodeType = node . nodeType ;
63+
64+ var validNode = false ;
65+
66+ if ( nodeType === 1 ) {
67+ validNode = config . elemWhitelist . indexOf ( nodeName ) !== - 1 ;
68+ } else if ( nodeType < 6 || nodeType === 9 || nodeType == 11 ) {
69+ validNode = true ;
70+ }
71+
72+ if ( validNode && node . nodeType === 1 ) {
73+ // Kill anchor tags with invalid hrefs.
74+ if ( nodeName === "a" ) {
75+ if ( node . protocol !== undefined ) {
76+ var scrubbedProto = node . protocol . replace ( SCHEME_FILTER , "" ) ;
77+
78+ // Only allow non-whitelisted schemes unless the document was served via
79+ // the same scheme.
80+ if ( config . schemeWhitelist . indexOf ( scrubbedProto ) === - 1 &&
81+ scrubbedProto !== document . location . protocol ) {
82+ validNode = false ;
83+ }
84+ } else {
85+ // TODO: Handle UAs that don't support a.protocol?
86+ // we may need to bundle URL.js.
87+ }
88+ }
89+ }
90+
91+ if ( validNode && node . nodeType === 1 ) {
92+ // Let's not invalidate any iterators, collect all attribute names.
93+ var attrs = $ . map ( node . attributes , function ( attr ) {
94+ return attr . nodeName ;
95+ } ) ;
96+
97+ // Remove unwanted attributes
98+ attrs . forEach ( function ( attrName ) {
99+
100+ // Is this attr allowed on any node?
101+ if ( config . attrWhitelist . indexOf ( attrName ) !== - 1 ) {
102+ return ;
103+ }
104+
105+ // is this attr allowed on *this* node?
106+ if ( nodeName in config . tagAttrWhitelist &&
107+ config . tagAttrWhitelist [ nodeName ] . indexOf ( attrName ) !== - 1 ) {
108+ return ;
109+ }
110+
111+ // jQuery.removeAttr chokes on attribute names containing quotes
112+ node . removeAttribute ( attrName ) ;
113+ } ) ;
114+ }
115+
116+ var canHoist = ( config . hoistOrphanedContents &&
117+ config . hoistBlacklist . indexOf ( nodeName ) === - 1 ) ;
118+
119+ // Cut out early if we don't need the contents
120+ if ( ! validNode && ! canHoist ) {
121+ jNode . remove ( ) ;
122+ return ;
123+ }
124+
125+ jNode . contents ( ) . each ( function ( i , child ) {
126+ Pasteurizer . scrubNode ( child , config ) ;
127+ } ) ;
128+
129+ if ( ! validNode ) {
130+ // remove the node and put its remaining contents in its place.
131+ jNode . contents ( ) . detach ( ) . insertAfter ( jNode ) ;
132+ jNode . remove ( ) ;
133+ }
134+ } ;
135+
136+ Pasteurizer . safeParseHTML = function ( html , config ) {
137+
138+ if ( ! config || $ . isEmptyObject ( config ) ) {
139+ config = Pasteurizer . DEFAULT_CONFIG ;
140+ }
141+
142+
143+ // DOMParser behaves similarly to jQuery.parseHTML, but it won't make any
144+ // requests at parse time.
145+ var parser = new DOMParser ( ) ;
146+
147+ //TODO: handle <parsererror>
148+ var parsed = parser . parseFromString ( html , "text/html" ) ;
149+
150+ // DOMParser wraps HTML fragments in body tags
151+ var body = $ ( parsed ) . find ( 'body' ) . first ( ) ;
152+
153+ body . contents ( ) . each ( function ( i , node ) {
154+ Pasteurizer . scrubNode ( node , config ) ;
155+ } ) ;
156+ return body . contents ( ) ;
157+ } ;
158+
159+ } ( window , jQuery ) ) ;
160+
161+
162+
163+ /*
164+ * DOMParser HTML extension
165+ * 2012-09-04
166+ *
167+ * By Eli Grey, http://eligrey.com
168+ * Public domain.
169+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
170+ */
171+
172+ /*! @source https://gist.github.com/1129031 */
173+ /*global document, DOMParser*/
174+
175+ ( function ( DOMParser ) {
176+ "use strict" ;
177+
178+ var DOMParser_proto = DOMParser . prototype ;
179+ var real_parseFromString = DOMParser_proto . parseFromString ;
180+
181+ // Firefox/Opera/IE throw errors on unsupported types
182+ try {
183+ // WebKit returns null on unsupported types
184+ if ( ( new DOMParser ) . parseFromString ( "" , "text/html" ) ) {
185+ // text/html parsing is natively supported
186+ return ;
187+ }
188+ } catch ( ex ) { }
189+
190+ DOMParser_proto . parseFromString = function ( markup , type ) {
191+ if ( / ^ \s * t e x t \/ h t m l \s * (?: ; | $ ) / i. test ( type ) ) {
192+ var doc = document . implementation . createHTMLDocument ( "" ) ;
193+ if ( markup . toLowerCase ( ) . indexOf ( '<!doctype' ) > - 1 ) {
194+ doc . documentElement . innerHTML = markup ;
195+ }
196+ else {
197+ doc . body . innerHTML = markup ;
198+ }
199+ return doc ;
200+ } else {
201+ return real_parseFromString . apply ( this , arguments ) ;
202+ }
203+ } ;
204+ } ( DOMParser ) ) ;
0 commit comments