1/** 2 * @license AngularJS v1.2.32 3 * (c) 2010-2014 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function(window, angular, undefined) {'use strict'; 7 8var $sanitizeMinErr = angular.$$minErr('$sanitize'); 9 10/** 11 * @ngdoc module 12 * @name ngSanitize 13 * @description 14 * 15 * # ngSanitize 16 * 17 * The `ngSanitize` module provides functionality to sanitize HTML. 18 * 19 * 20 * <div doc-module-components="ngSanitize"></div> 21 * 22 * See {@link ngSanitize.$sanitize `$sanitize`} for usage. 23 */ 24 25/* 26 * HTML Parser By Misko Hevery ([email protected]) 27 * based on: HTML Parser By John Resig (ejohn.org) 28 * Original code by Erik Arvidsson, Mozilla Public License 29 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js 30 * 31 * // Use like so: 32 * htmlParser(htmlString, { 33 * start: function(tag, attrs, unary) {}, 34 * end: function(tag) {}, 35 * chars: function(text) {}, 36 * comment: function(text) {} 37 * }); 38 * 39 */ 40 41 42/** 43 * @ngdoc service 44 * @name $sanitize 45 * @kind function 46 * 47 * @description 48 * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are 49 * then serialized back to properly escaped html string. This means that no unsafe input can make 50 * it into the returned string, however, since our parser is more strict than a typical browser 51 * parser, it's possible that some obscure input, which would be recognized as valid HTML by a 52 * browser, won't make it through the sanitizer. 53 * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and 54 * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. 55 * 56 * @param {string} html Html input. 57 * @returns {string} Sanitized html. 58 * 59 * @example 60 <example module="sanitizeExample" deps="angular-sanitize.js"> 61 <file name="index.html"> 62 <script> 63 angular.module('sanitizeExample', ['ngSanitize']) 64 .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { 65 $scope.snippet = 66 '<p style="color:blue">an html\n' + 67 '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + 68 'snippet</p>'; 69 $scope.deliberatelyTrustDangerousSnippet = function() { 70 return $sce.trustAsHtml($scope.snippet); 71 }; 72 }]); 73 </script>
74 <div ng-controller="ExampleController"> 75 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 76 <table> 77 <tr> 78 <td>Directive</td> 79 <td>How</td> 80 <td>Source</td> 81 <td>Rendered</td> 82 </tr> 83 <tr id="bind-html-with-sanitize"> 84 <td>ng-bind-html</td> 85 <td>Automatically uses $sanitize</td> 86 <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> 87 <td><div ng-bind-html="snippet"></div></td> 88 </tr> 89 <tr id="bind-html-with-trust"> 90 <td>ng-bind-html</td> 91 <td>Bypass $sanitize by explicitly trusting the dangerous value</td> 92 <td> 93 <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> 94</div></pre> 95 </td> 96 <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> 97 </tr> 98 <tr id="bind-default"> 99 <td>ng-bind</td> 100 <td>Automatically escapes</td> 101 <td><pre><div ng-bind="snippet"><br/></div></pre></td> 102 <td><div ng-bind="snippet"></div></td> 103 </tr> 104 </table> 105 </div> 106 </file> 107 <file name="protractor.js" type="protractor"> 108 it('should sanitize the html snippet by default', function() { 109 expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 110 toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); 111 }); 112 113 it('should inline raw snippet if bound to a trusted value', function() { 114 expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). 115 toBe("<p style=\"color:blue\">an html\n" + 116 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 117 "snippet</p>"); 118 }); 119 120 it('should escape snippet without any filter', function() { 121 expect(element(by.css('#bind-default div')).getInnerHtml()). 122 toBe("<p style=\"color:blue\">an html\n" + 123 "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + 124 "snippet</p>"); 125 }); 126 127 it('should update', function() { 128 element(by.model('snippet')).clear(); 129 element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); 130 expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). 131 toBe('new <b>text</b>'); 132 expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( 133 'new <b onclick="alert(1)">text</b>'); 134 expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( 135 "new <b onclick=\"alert(1)\">text</b>"); 136 }); 137 </file> 138 </example> 139 */ 140function $SanitizeProvider() { 141 this.$get = ['$$sanitizeUri', function($$sanitizeUri) { 142 return function(html) { 143 var buf = []; 144 htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { 145 return !/^unsafe/.test($$sanitizeUri(uri, isImage)); 146 })); 147 return buf.join(''); 148 }; 149 }]; 150} 151 152function sanitizeText(chars) { 153 var buf = []; 154 var writer = htmlSanitizeWriter(buf, angular.noop); 155 writer.chars(chars); 156 return buf.join(''); 157} 158 159 160// Regular Expressions for parsing tags and attributes 161var START_TAG_REGEXP = 162 /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, 163 END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, 164 ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 165 BEGIN_TAG_REGEXP = /^</, 166 BEGING_END_TAGE_REGEXP = /^<\//, 167 COMMENT_REGEXP = /<!--(.*?)-->/g, 168 DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, 169 CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, 170 SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 171 // Match everything outside of normal chars and " (quote character) 172 NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; 173 174 175// Good source of info about elements and attributes 176// http://dev.w3.org/html5/spec/Overview.html#semantics 177// http://simon.html5.org/html-elements 178 179// Safe Void Elements - HTML5 180// http://dev.w3.org/html5/spec/Overview.html#void-elements 181var voidElements = makeMap("area,br,col,hr,img,wbr"); 182 183// Elements that you can, intentionally, leave open (and which close themselves) 184// http://dev.w3.org/html5/spec/Overview.html#optional-tags 185var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), 186 optionalEndTagInlineElements = makeMap("rp,rt"), 187 optionalEndTagElements = angular.extend({}, 188 optionalEndTagInlineElements, 189 optionalEndTagBlockElements); 190 191// Safe Block Elements - HTML5 192var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + 193 "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + 194 "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); 195 196// Inline Elements - HTML5 197var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + 198 "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + 199 "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); 200 201 202// Special Elements (can contain anything) 203var specialElements = makeMap("script,style"); 204 205var validElements = angular.extend({}, 206 voidElements, 207 blockElements, 208 inlineElements, 209 optionalEndTagElements); 210 211//Attributes that have href and hence need to be sanitized 212var uriAttrs = makeMap("background,cite,href,longdesc,src"); 213var validAttrs = angular.extend({}, uriAttrs, makeMap( 214 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
vendor: 9,397 bytes, lines 215-530
215 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ 216 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ 217 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+ 218 'valign,value,vspace,width')); 219 220function makeMap(str) { 221 var obj = {}, items = str.split(','), i; 222 for (i = 0; i < items.length; i++) obj[items[i]] = true; 223 return obj; 224} 225 226 227/** 228 * @example 229 * htmlParser(htmlString, { 230 * start: function(tag, attrs, unary) {}, 231 * end: function(tag) {}, 232 * chars: function(text) {}, 233 * comment: function(text) {} 234 * }); 235 * 236 * @param {string} html string 237 * @param {object} handler 238 */ 239function htmlParser( html, handler ) { 240 if (typeof html !== 'string') { 241 if (html === null || typeof html === 'undefined') { 242 html = ''; 243 } else { 244 html = '' + html; 245 } 246 } 247 var index, chars, match, stack = [], last = html, text; 248 stack.last = function() { return stack[ stack.length - 1 ]; }; 249 250 while ( html ) { 251 text = ''; 252 chars = true; 253 254 // Make sure we're not in a script or style element 255 if ( !stack.last() || !specialElements[ stack.last() ] ) { 256 257 // Comment 258 if ( html.indexOf("<!--") === 0 ) { 259 // comments containing -- are not allowed unless they terminate the comment 260 index = html.indexOf("--", 4); 261 262 if ( index >= 0 && html.lastIndexOf("-->", index) === index) { 263 if (handler.comment) handler.comment( html.substring( 4, index ) ); 264 html = html.substring( index + 3 ); 265 chars = false; 266 } 267 // DOCTYPE 268 } else if ( DOCTYPE_REGEXP.test(html) ) { 269 match = html.match( DOCTYPE_REGEXP ); 270 271 if ( match ) { 272 html = html.replace( match[0], ''); 273 chars = false; 274 } 275 // end tag 276 } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { 277 match = html.match( END_TAG_REGEXP ); 278 279 if ( match ) { 280 html = html.substring( match[0].length ); 281 match[0].replace( END_TAG_REGEXP, parseEndTag ); 282 chars = false; 283 } 284 285 // start tag 286 } else if ( BEGIN_TAG_REGEXP.test(html) ) { 287 match = html.match( START_TAG_REGEXP ); 288 289 if ( match ) { 290 // We only have a valid start-tag if there is a '>'. 291 if ( match[4] ) { 292 html = html.substring( match[0].length ); 293 match[0].replace( START_TAG_REGEXP, parseStartTag ); 294 } 295 chars = false; 296 } else { 297 // no ending tag found --- this piece should be encoded as an entity. 298 text += '<'; 299 html = html.substring(1); 300 } 301 } 302 303 if ( chars ) { 304 index = html.indexOf("<"); 305 306 text += index < 0 ? html : html.substring( 0, index ); 307 html = index < 0 ? "" : html.substring( index ); 308 309 if (handler.chars) handler.chars( decodeEntities(text) ); 310 } 311 312 } else { 313 html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), 314 function(all, text){ 315 text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); 316 317 if (handler.chars) handler.chars( decodeEntities(text) ); 318 319 return ""; 320 }); 321 322 parseEndTag( "", stack.last() ); 323 } 324 325 if ( html == last ) { 326 throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + 327 "of html: {0}", html); 328 } 329 last = html; 330 } 331 332 // Clean up any remaining tags 333 parseEndTag(); 334 335 function parseStartTag( tag, tagName, rest, unary ) { 336 tagName = angular.lowercase(tagName); 337 if ( blockElements[ tagName ] ) { 338 while ( stack.last() && inlineElements[ stack.last() ] ) { 339 parseEndTag( "", stack.last() ); 340 } 341 } 342 343 if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { 344 parseEndTag( "", tagName ); 345 } 346 347 unary = voidElements[ tagName ] || !!unary; 348 349 if ( !unary ) 350 stack.push( tagName ); 351 352 var attrs = {}; 353 354 rest.replace(ATTR_REGEXP, 355 function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { 356 var value = doubleQuotedValue 357 || singleQuotedValue 358 || unquotedValue 359 || ''; 360 361 attrs[name] = decodeEntities(value); 362 }); 363 if (handler.start) handler.start( tagName, attrs, unary ); 364 } 365 366 function parseEndTag( tag, tagName ) { 367 var pos = 0, i; 368 tagName = angular.lowercase(tagName); 369 if ( tagName ) 370 // Find the closest opened tag of the same type 371 for ( pos = stack.length - 1; pos >= 0; pos-- ) 372 if ( stack[ pos ] == tagName ) 373 break; 374 375 if ( pos >= 0 ) { 376 // Close all the open elements, up the stack 377 for ( i = stack.length - 1; i >= pos; i-- ) 378 if (handler.end) handler.end( stack[ i ] ); 379 380 // Remove the open elements from the stack 381 stack.length = pos; 382 } 383 } 384} 385 386var hiddenPre=document.createElement("pre"); 387var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; 388/** 389 * decodes all entities into regular string 390 * @param value 391 * @returns {string} A string with decoded entities. 392 */ 393function decodeEntities(value) { 394 if (!value) { return ''; } 395 396 // Note: IE8 does not preserve spaces at the start/end of innerHTML 397 // so we must capture them and reattach them afterward 398 var parts = spaceRe.exec(value); 399 var spaceBefore = parts[1]; 400 var spaceAfter = parts[3]; 401 var content = parts[2]; 402 if (content) { 403 hiddenPre.innerHTML=content.replace(/</g,"<"); 404 // innerText depends on styling as it doesn't display hidden elements. 405 // Therefore, it's better to use textContent not to cause unnecessary 406 // reflows. However, IE<9 don't support textContent so the innerText 407 // fallback is necessary. 408 content = 'textContent' in hiddenPre ? 409 hiddenPre.textContent : hiddenPre.innerText; 410 } 411 return spaceBefore + content + spaceAfter; 412} 413 414/** 415 * Escapes all potentially dangerous characters, so that the 416 * resulting string can be safely inserted into attribute or 417 * element text. 418 * @param value 419 * @returns {string} escaped text 420 */ 421function encodeEntities(value) { 422 return value. 423 replace(/&/g, '&'). 424 replace(SURROGATE_PAIR_REGEXP, function (value) { 425 var hi = value.charCodeAt(0); 426 var low = value.charCodeAt(1); 427 return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; 428 }). 429 replace(NON_ALPHANUMERIC_REGEXP, function(value){ 430 return '&#' + value.charCodeAt(0) + ';'; 431 }). 432 replace(/</g, '<'). 433 replace(/>/g, '>'); 434} 435 436/** 437 * create an HTML/XML writer which writes to buffer 438 * @param {Array} buf use buf.jain('') to get out sanitized html string 439 * @returns {object} in the form of { 440 * start: function(tag, attrs, unary) {}, 441 * end: function(tag) {}, 442 * chars: function(text) {}, 443 * comment: function(text) {} 444 * } 445 */ 446function htmlSanitizeWriter(buf, uriValidator){ 447 var ignore = false; 448 var out = angular.bind(buf, buf.push); 449 return { 450 start: function(tag, attrs, unary){ 451 tag = angular.lowercase(tag); 452 if (!ignore && specialElements[tag]) { 453 ignore = tag; 454 } 455 if (!ignore && validElements[tag] === true) { 456 out('<'); 457 out(tag); 458 angular.forEach(attrs, function(value, key){ 459 var lkey=angular.lowercase(key); 460 var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); 461 if (validAttrs[lkey] === true && 462 (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { 463 out(' '); 464 out(key); 465 out('="'); 466 out(encodeEntities(value)); 467 out('"'); 468 } 469 }); 470 out(unary ? '/>' : '>'); 471 } 472 }, 473 end: function(tag){ 474 tag = angular.lowercase(tag); 475 if (!ignore && validElements[tag] === true) { 476 out('</'); 477 out(tag); 478 out('>'); 479 } 480 if (tag == ignore) { 481 ignore = false; 482 } 483 }, 484 chars: function(chars){ 485 if (!ignore) { 486 out(encodeEntities(chars)); 487 } 488 } 489 }; 490} 491 492 493// define ngSanitize module and register $sanitize service 494angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); 495 496/* global sanitizeText: false */ 497 498/** 499 * @ngdoc filter 500 * @name linky 501 * @kind function 502 * 503 * @description 504 * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and 505 * plain email address links. 506 * 507 * Requires the {@link ngSanitize `ngSanitize`} module to be installed. 508 * 509 * @param {string} text Input text. 510 * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. 511 * @returns {string} Html-linkified text. 512 * 513 * @usage 514 <span ng-bind-html="linky_expression | linky"></span> 515 * 516 * @example 517 <example module="linkyExample" deps="angular-sanitize.js"> 518 <file name="index.html"> 519 <script> 520 angular.module('linkyExample', ['ngSanitize']) 521 .controller('ExampleController', ['$scope', function($scope) { 522 $scope.snippet = 523 'Pretty text with some links:\n'+ 524 'http://angularjs.org/,\n'+ 525 'mailto:[email protected],\n'+ 526 '[email protected],\n'+ 527 'and one more: ftp://127.0.0.1/.'; 528 $scope.snippetWithTarget = 'http://angularjs.org/'; 529 }]); 530 </script>
531 <div ng-controller="ExampleController"> 532 Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> 533 <table> 534 <tr> 535 <td>Filter</td> 536 <td>Source</td> 537 <td>Rendered</td> 538 </tr> 539 <tr id="linky-filter"> 540 <td>linky filter</td> 541 <td> 542 <pre><div ng-bind-html="snippet | linky"><br></div></pre> 543 </td> 544 <td> 545 <div ng-bind-html="snippet | linky"></div> 546 </td> 547 </tr> 548 <tr id="linky-target"> 549 <td>linky target</td> 550 <td> 551 <pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> 552 </td> 553 <td> 554 <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div> 555 </td> 556 </tr> 557 <tr id="escaped-html"> 558 <td>no filter</td> 559 <td><pre><div ng-bind="snippet"><br></div></pre></td> 560 <td><div ng-bind="snippet"></div></td> 561 </tr> 562 </table> 563 </file> 564 <file name="protractor.js" type="protractor"> 565 it('should linkify the snippet with urls', function() { 566 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 567 toBe('Pretty text with some links: http://angularjs.org/, [email protected], ' + 568 '[email protected], and one more: ftp://127.0.0.1/.'); 569 expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); 570 }); 571 572 it('should not linkify snippet without the linky filter', function() { 573 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). 574 toBe('Pretty text with some links: http://angularjs.org/, mailto:[email protected], ' + 575 '[email protected], and one more: ftp://127.0.0.1/.'); 576 expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); 577 }); 578 579 it('should update', function() { 580 element(by.model('snippet')).clear(); 581 element(by.model('snippet')).sendKeys('new http://link.'); 582 expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). 583 toBe('new http://link.'); 584 expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); 585 expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) 586 .toBe('new http://link.'); 587 }); 588 589 it('should work with the target property', function() { 590 expect(element(by.id('linky-target')). 591 element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). 592 toBe('http://angularjs.org/'); 593 expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); 594 }); 595 </file> 596 </example> 597 */ 598angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { 599 var LINKY_URL_REGEXP = 600 /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/, 601 MAILTO_REGEXP = /^mailto:/; 602 603 return function(text, target) { 604 if (!text) return text; 605 var match; 606 var raw = text; 607 var html = []; 608 var url; 609 var i; 610 while ((match = raw.match(LINKY_URL_REGEXP))) { 611 // We can not end in these as they are sometimes found at the end of the sentence 612 url = match[0]; 613 // if we did not match ftp/http/mailto then assume mailto 614 if (match[2] == match[3]) url = 'mailto:' + url; 615 i = match.index;
vendor: 689 bytes, lines 616-647
616 addText(raw.substr(0, i)); 617 addLink(url, match[0].replace(MAILTO_REGEXP, '')); 618 raw = raw.substring(i + match[0].length); 619 } 620 addText(raw); 621 return $sanitize(html.join('')); 622 623 function addText(text) { 624 if (!text) { 625 return; 626 } 627 html.push(sanitizeText(text)); 628 } 629 630 function addLink(url, text) { 631 html.push('<a '); 632 if (angular.isDefined(target)) { 633 html.push('target="'); 634 html.push(target); 635 html.push('" '); 636 } 637 html.push('href="', 638 url.replace('"', '"'), 639 '">'); 640 addText(text); 641 html.push('</a>'); 642 } 643 }; 644}]); 645 646 647})(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.