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