PageSourceSearch

http://docs.deployd.com/javascripts/libs/angular-sanitize.js

js deployd.com collected 2026-10-03 20:41:11 UTC 17,349 bytes, 532 lines download raw bytes

1/**
2 * @license AngularJS v1.0.1
3 * (c) 2010-2012 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6(function(window, angular, undefined) {
7'use strict';
8
9/**
10 * @ngdoc overview
11 * @name ngSanitize
12 * @description
13 */
14
15/*
16 * HTML Parser By Misko Hevery ([email protected])
17 * based on:  HTML Parser By John Resig (ejohn.org)
18 * Original code by Erik Arvidsson, Mozilla Public License
19 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
20 *
21 * // Use like so:
22 * htmlParser(htmlString, {
23 *     start: function(tag, attrs, unary) {},
24 *     end: function(tag) {},
25 *     chars: function(text) {},
26 *     comment: function(text) {}
27 * });
28 *
29 */
30
31
32/**
33 * @ngdoc service
34 * @name ngSanitize.$sanitize
35 * @function
36 *
37 * @description
38 *   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
39 *   then serialized back to properly escaped html string. This means that no unsafe input can make
40 *   it into the returned string, however, since our parser is more strict than a typical browser
41 *   parser, it's possible that some obscure input, which would be recognized as valid HTML by a
42 *   browser, won't make it through the sanitizer.
43 *
44 * @param {string} html Html input.
45 * @returns {string} Sanitized html.
46 *
47 * @example
48   <doc:example module="ngSanitize">
49     <doc:source>
50       <script>
51         function Ctrl($scope) {
52           $scope.snippet =
53             '<p style="color:blue">an html\n' +
54             '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
55             'snippet</p>';
56         }
57       </script>
58       <div ng-controller="Ctrl">
59          Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
60           <table>
61             <tr>
62               <td>Filter</td>
63               <td>Source</td>
64               <td>Rendered</td>
65             </tr>
66             <tr id="html-filter">
67               <td>html filter</td>
68               <td>
69                 <pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
70               </td>
71               <td>
72                 <div ng-bind-html="snippet"></div>
73               </td>
74             </tr>
75             <tr id="escaped-html">
76               <td>no filter</td>
77               <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
78               <td><div ng-bind="snippet"></div></td>
79             </tr>
80             <tr id="html-unsafe-filter">
81               <td>unsafe html filter</td>
82               <td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
83               <td><div ng-bind-html-unsafe="snippet"></div></td>
84             </tr>
85           </table>
86         </div>
87     </doc:source>
88     <doc:scenario>
89       it('should sanitize the html snippet ', function() {
90         expect(using('#html-filter').element('div').html()).
91           toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
92       });
93
94       it('should escape snippet without any filter', function() {
95         expect(using('#escaped-html').element('div').html()).
96           toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
97                "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
98                "snippet&lt;/p&gt;");
99       });
100
101       it('should inline raw snippet if filtered as unsafe', function() {
102         expect(using('#html-unsafe-filter').element("div").html()).
103           toBe("<p style=\"color:blue\">an html\n" +
104                "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
105                "snippet</p>");
106       });
107
108       it('should update', function() {
109         input('snippet').enter('new <b>text</b>');
110         expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
111         expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
112         expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
113       });
114     </doc:scenario>
115   </doc:example>
116 */
117var $sanitize = function(html) {
118  var buf = [];
119    htmlParser(html, htmlSanitizeWriter(buf));
120    return buf.join('');
121};
122
123
124// Regular Expressions for parsing tags and attributes
125var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
126  END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
127  ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
128  BEGIN_TAG_REGEXP = /^</,
129  BEGING_END_TAGE_REGEXP = /^<\s*\//,
130  COMMENT_REGEXP = /<!--(.*?)-->/g,
131  CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
132  URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/,
133  NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
134
135
136// Good source of info about elements and attributes
137// http://dev.w3.org/html5/spec/Overview.html#semantics
138// http://simon.html5.org/html-elements
139
140// Safe Void Elements - HTML5
141// http://dev.w3.org/html5/spec/Overview.html#void-elements
142var voidElements = makeMap("area,br,col,hr,img,wbr");
143
144// Elements that you can, intentionally, leave open (and which close themselves)
145// http://dev.w3.org/html5/spec/Overview.html#optional-tags
146var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
147    optionalEndTagInlineElements = makeMap("rp,rt"),
148    optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
149
150// Safe Block Elements - HTML5
151var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
152        "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
153        "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
154
155// Inline Elements - HTML5
156var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
157        "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
158        "span,strike,strong,sub,sup,time,tt,u,var"));
159
160
161// Special Elements (can contain anything)
162var specialElements = makeMap("script,style");
163
164var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
165
166//Attributes that have href and hence need to be sanitized
167var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
168var validAttrs = angular.extend({}, uriAttrs, makeMap(
169    'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
vendor: 5,103 bytes, lines 170-356
170    'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
171    'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
172    'scope,scrolling,shape,span,start,summary,target,title,type,'+
173    'valign,value,vspace,width'));
174
175function makeMap(str) {
176  var obj = {}, items = str.split(','), i;
177  for (i = 0; i < items.length; i++) obj[items[i]] = true;
178  return obj;
179}
180
181
182/**
183 * @example
184 * htmlParser(htmlString, {
185 *     start: function(tag, attrs, unary) {},
186 *     end: function(tag) {},
187 *     chars: function(text) {},
188 *     comment: function(text) {}
189 * });
190 *
191 * @param {string} html string
192 * @param {object} handler
193 */
194function htmlParser( html, handler ) {
195  var index, chars, match, stack = [], last = html;
196  stack.last = function() { return stack[ stack.length - 1 ]; };
197
198  while ( html ) {
199    chars = true;
200
201    // Make sure we're not in a script or style element
202    if ( !stack.last() || !specialElements[ stack.last() ] ) {
203
204      // Comment
205      if ( html.indexOf("<!--") === 0 ) {
206        index = html.indexOf("-->");
207
208        if ( index >= 0 ) {
209          if (handler.comment) handler.comment( html.substring( 4, index ) );
210          html = html.substring( index + 3 );
211          chars = false;
212        }
213
214      // end tag
215      } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
216        match = html.match( END_TAG_REGEXP );
217
218        if ( match ) {
219          html = html.substring( match[0].length );
220          match[0].replace( END_TAG_REGEXP, parseEndTag );
221          chars = false;
222        }
223
224      // start tag
225      } else if ( BEGIN_TAG_REGEXP.test(html) ) {
226        match = html.match( START_TAG_REGEXP );
227
228        if ( match ) {
229          html = html.substring( match[0].length );
230          match[0].replace( START_TAG_REGEXP, parseStartTag );
231          chars = false;
232        }
233      }
234
235      if ( chars ) {
236        index = html.indexOf("<");
237
238        var text = index < 0 ? html : html.substring( 0, index );
239        html = index < 0 ? "" : html.substring( index );
240
241        if (handler.chars) handler.chars( decodeEntities(text) );
242      }
243
244    } else {
245      html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
246        text = text.
247          replace(COMMENT_REGEXP, "$1").
248          replace(CDATA_REGEXP, "$1");
249
250        if (handler.chars) handler.chars( decodeEntities(text) );
251
252        return "";
253      });
254
255      parseEndTag( "", stack.last() );
256    }
257
258    if ( html == last ) {
259      throw "Parse Error: " + html;
260    }
261    last = html;
262  }
263
264  // Clean up any remaining tags
265  parseEndTag();
266
267  function parseStartTag( tag, tagName, rest, unary ) {
268    tagName = angular.lowercase(tagName);
269    if ( blockElements[ tagName ] ) {
270      while ( stack.last() && inlineElements[ stack.last() ] ) {
271        parseEndTag( "", stack.last() );
272      }
273    }
274
275    if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
276      parseEndTag( "", tagName );
277    }
278
279    unary = voidElements[ tagName ] || !!unary;
280
281    if ( !unary )
282      stack.push( tagName );
283
284    var attrs = {};
285
286    rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
287      var value = doubleQuotedValue
288        || singleQoutedValue
289        || unqoutedValue
290        || '';
291
292      attrs[name] = decodeEntities(value);
293    });
294    if (handler.start) handler.start( tagName, attrs, unary );
295  }
296
297  function parseEndTag( tag, tagName ) {
298    var pos = 0, i;
299    tagName = angular.lowercase(tagName);
300    if ( tagName )
301      // Find the closest opened tag of the same type
302      for ( pos = stack.length - 1; pos >= 0; pos-- )
303        if ( stack[ pos ] == tagName )
304          break;
305
306    if ( pos >= 0 ) {
307      // Close all the open elements, up the stack
308      for ( i = stack.length - 1; i >= pos; i-- )
309        if (handler.end) handler.end( stack[ i ] );
310
311      // Remove the open elements from the stack
312      stack.length = pos;
313    }
314  }
315}
316
317/**
318 * decodes all entities into regular string
319 * @param value
320 * @returns {string} A string with decoded entities.
321 */
322var hiddenPre=document.createElement("pre");
323function decodeEntities(value) {
324  hiddenPre.innerHTML=value.replace(/</g,"&lt;");
325  return hiddenPre.innerText || hiddenPre.textContent || '';
326}
327
328/**
329 * Escapes all potentially dangerous characters, so that the
330 * resulting string can be safely inserted into attribute or
331 * element text.
332 * @param value
333 * @returns escaped text
334 */
335function encodeEntities(value) {
336  return value.
337    replace(/&/g, '&amp;').
338    replace(NON_ALPHANUMERIC_REGEXP, function(value){
339      return '&#' + value.charCodeAt(0) + ';';
340    }).
341    replace(/</g, '&lt;').
342    replace(/>/g, '&gt;');
343}
344
345/**
346 * create an HTML/XML writer which writes to buffer
347 * @param {Array} buf use buf.jain('') to get out sanitized html string
348 * @returns {object} in the form of {
349 *     start: function(tag, attrs, unary) {},
350 *     end: function(tag) {},
351 *     chars: function(text) {},
352 *     comment: function(text) {}
353 * }
354 */
355function htmlSanitizeWriter(buf){
356  var ignore = false;
357  var out = angular.bind(buf, buf.push);
358  return {
359    start: function(tag, attrs, unary){
360      tag = angular.lowercase(tag);
361      if (!ignore && specialElements[tag]) {
362        ignore = tag;
363      }
364      if (!ignore && validElements[tag] == true) {
365        out('<');
366        out(tag);
367        angular.forEach(attrs, function(value, key){
368          var lkey=angular.lowercase(key);
369          if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
370            out(' ');
371            out(key);
372            out('="');
373            out(encodeEntities(value));
374            out('"');
375          }
376        });
377        out(unary ? '/>' : '>');
378      }
379    },
380    end: function(tag){
381        tag = angular.lowercase(tag);
382        if (!ignore && validElements[tag] == true) {
383          out('</');
384          out(tag);
385          out('>');
386        }
387        if (tag == ignore) {
388          ignore = false;
389        }
390      },
391    chars: function(chars){
392        if (!ignore) {
393          out(encodeEntities(chars));
394        }
395      }
396  };
397}
398
399
400// define ngSanitize module and register $sanitize service
401angular.module('ngSanitize', []).value('$sanitize', $sanitize);
402
403/**
404 * @ngdoc directive
405 * @name ngSanitize.directive:ngBindHtml
406 *
407 * @description
408 * Creates a binding that will sanitize the result of evaluating the `expression` with the
409 * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
410 *
411 * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
412 *
413 * @element ANY
414 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
415 */
416angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
417  return function(scope, element, attr) {
418    element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
419    scope.$watch(attr.ngBindHtml, function(value) {
420      value = $sanitize(value);
421      element.html(value || '');
422    });
423  };
424}]);
425/**
426 * @ngdoc filter
427 * @name ngSanitize.filter:linky
428 * @function
429 *
430 * @description
431 *   Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
432 *   plain email address links.
433 *
434 * @param {string} text Input text.
435 * @returns {string} Html-linkified text.
436 *
437 * @example
438   <doc:example module="ngSanitize">
439     <doc:source>
440       <script>
441         function Ctrl($scope) {
442           $scope.snippet =
443             'Pretty text with some links:\n'+
444             'http://angularjs.org/,\n'+
445             'mailto:[email protected],\n'+
446             '[email protected],\n'+
447             'and one more: ftp://127.0.0.1/.';
448         }
449       </script>
450       <div ng-controller="Ctrl">
451       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
452       <table>
453         <tr>
454           <td>Filter</td>
455           <td>Source</td>
456           <td>Rendered</td>
457         </tr>
458         <tr id="linky-filter">
459           <td>linky filter</td>
460           <td>
461             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
462           </td>
463           <td>
464             <div ng-bind-html="snippet | linky"></div>
465           </td>
466         </tr>
467         <tr id="escaped-html">
468           <td>no filter</td>
469           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
470           <td><div ng-bind="snippet"></div></td>
471         </tr>
472       </table>
473     </doc:source>
474     <doc:scenario>
475       it('should linkify the snippet with urls', function() {
476         expect(using('#linky-filter').binding('snippet | linky')).
477           toBe('Pretty text with some links:&#10;' +
478                '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
479                '<a href="mailto:[email protected]">[email protected]</a>,&#10;' +
480                '<a href="mailto:[email protected]">[email protected]</a>,&#10;' +
481                'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
482       });
483
484       it ('should not linkify snippet without the linky filter', function() {
485         expect(using('#escaped-html').binding('snippet')).
486           toBe("Pretty text with some links:\n" +
487                "http://angularjs.org/,\n" +
488                "mailto:[email protected],\n" +
489                "[email protected],\n" +
490                "and one more: ftp://127.0.0.1/.");
491       });
492
493       it('should update', function() {
494         input('snippet').enter('new http://link.');
495         expect(using('#linky-filter').binding('snippet | linky')).
496           toBe('new <a href="http://link">http://link</a>.');
497         expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
498       });
499     </doc:scenario>
500   </doc:example>
501 */
502angular.module('ngSanitize').filter('linky', function() {
503  var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
504      MAILTO_REGEXP = /^mailto:/;
505
506  return function(text) {
507    if (!text) return text;
508    var match;
509    var raw = text;
510    var html = [];
511    // TODO(vojta): use $sanitize instead
512    var writer = htmlSanitizeWriter(html);
513    var url;
514    var i;
515    while ((match = raw.match(LINKY_URL_REGEXP))) {
516      // We can not end in these as they are sometimes found at the end of the sentence
517      url = match[0];
518      // if we did not match ftp/http/mailto then assume mailto
519      if (match[2] == match[3]) url = 'mailto:' + url;
520      i = match.index;
521      writer.chars(raw.substr(0, i));
522      writer.start('a', {href:url});
523      writer.chars(match[0].replace(MAILTO_REGEXP, ''));
524      writer.end('a');
525      raw = raw.substring(i + match[0].length);
526    }
527    writer.chars(raw);
528    return html.join('');
529  };
530});
531
532})(window, window.angular);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.