1/** 2 * @license AngularJS v1.0.3 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><div ng-bind-html="snippet"><br/></div></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><div ng-bind="snippet"><br/></div></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><div ng-bind-html-unsafe="snippet"><br/></div></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("<p style=\"color:blue\">an html\n" + 97 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 98 "snippet</p>"); 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 <b>text</b>"); 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,"<"); 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, '&'). 338 replace(NON_ALPHANUMERIC_REGEXP, function(value){ 339 return '&#' + value.charCodeAt(0) + ';'; 340 }). 341 replace(/</g, '<'). 342 replace(/>/g, '>'); 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 ngBindHtmlWatchAction(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 * @usage 438 <span ng-bind-html="linky_expression | linky"></span> 439 * 440 * @example 441 <doc:example module="ngSanitize"> 442 <doc:source> 443 <script> 444 function Ctrl($scope) { 445 $scope.snippet = 446 'Pretty text with some links:\n'+ 447 'http://angularjs.org/,\n'+ 448 'mailto:[email protected],\n'+ 449 '[email protected],\n'+ 450 'and one more: ftp://127.0.0.1/.'; 451 } 452 </script> 453 <div ng-controller="Ctrl"> 454 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 455 <table> 456 <tr> 457 <td>Filter</td> 458 <td>Source</td> 459 <td>Rendered</td> 460 </tr> 461 <tr id="linky-filter"> 462 <td>linky filter</td> 463 <td> 464 <pre><div ng-bind-html="snippet | linky"><br></div></pre> 465 </td> 466 <td> 467 <div ng-bind-html="snippet | linky"></div> 468 </td> 469 </tr> 470 <tr id="escaped-html"> 471 <td>no filter</td> 472 <td><pre><div ng-bind="snippet"><br></div></pre></td> 473 <td><div ng-bind="snippet"></div></td> 474 </tr> 475 </table>
476 </doc:source> 477 <doc:scenario> 478 it('should linkify the snippet with urls', function() { 479 expect(using('#linky-filter').binding('snippet | linky')). 480 toBe('Pretty text with some links: ' + 481 '<a href="http://angularjs.org/">http://angularjs.org/</a>, ' + 482 '<a href="mailto:[email protected]">[email protected]</a>, ' + 483 '<a href="mailto:[email protected]">[email protected]</a>, ' + 484 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.'); 485 }); 486 487 it ('should not linkify snippet without the linky filter', function() { 488 expect(using('#escaped-html').binding('snippet')). 489 toBe("Pretty text with some links:\n" + 490 "http://angularjs.org/,\n" + 491 "mailto:[email protected],\n" + 492 "[email protected],\n" + 493 "and one more: ftp://127.0.0.1/."); 494 }); 495 496 it('should update', function() { 497 input('snippet').enter('new http://link.'); 498 expect(using('#linky-filter').binding('snippet | linky')). 499 toBe('new <a href="http://link">http://link</a>.'); 500 expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); 501 }); 502 </doc:scenario> 503 </doc:example> 504 */ 505angular.module('ngSanitize').filter('linky', function() { 506 var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, 507 MAILTO_REGEXP = /^mailto:/; 508 509 return function(text) { 510 if (!text) return text; 511 var match; 512 var raw = text; 513 var html = []; 514 // TODO(vojta): use $sanitize instead 515 var writer = htmlSanitizeWriter(html); 516 var url; 517 var i; 518 while ((match = raw.match(LINKY_URL_REGEXP))) { 519 // We can not end in these as they are sometimes found at the end of the sentence 520 url = match[0]; 521 // if we did not match ftp/http/mailto then assume mailto 522 if (match[2] == match[3]) url = 'mailto:' + url; 523 i = match.index;
524 writer.chars(raw.substr(0, i)); 525 writer.start('a', {href:url}); 526 writer.chars(match[0].replace(MAILTO_REGEXP, '')); 527 writer.end('a'); 528 raw = raw.substring(i + match[0].length); 529 } 530 writer.chars(raw); 531 return html.join(''); 532 }; 533}); 534 535})(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.