PageSourceSearch

https://kit.ac.in/web-assets/angular/cms-angular-sanitize.js?version=1782474389

js kit.ac.in collected 2026-09-30 10:01:11 UTC 29,715 bytes, 801 lines download raw bytes

1/**
2 * @license AngularJS v1.6.9
3 * (c) 2010-2018 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6(function(window, angular) {'use strict';
7
8/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
9 *     Any commits to this file should be reviewed with security in mind.  *
10 *   Changes to this file can potentially create security vulnerabilities. *
11 *          An approval from 2 Core members with history of modifying      *
12 *                         this file is required.                          *
13 *                                                                         *
14 *  Does the change somehow allow for arbitrary javascript to be executed? *
15 *    Or allows for someone to change the prototype of built-in objects?   *
16 *     Or gives undesired access to variables likes document or window?    *
17 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
18
19var $sanitizeMinErr = angular.$$minErr('$sanitize');
20var bind;
21var extend;
22var forEach;
23var isDefined;
24var lowercase;
25var noop;
26var nodeContains;
27var htmlParser;
28var htmlSanitizeWriter;
29
30/**
31 * @ngdoc module
32 * @name ngSanitize
33 * @description
34 *
35 * The `ngSanitize` module provides functionality to sanitize HTML.
36 *
37 * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
38 */
39
40/**
41 * @ngdoc service
42 * @name $sanitize
43 * @kind function
44 *
45 * @description
46 *   Sanitizes an html string by stripping all potentially dangerous tokens.
47 *
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.
51 *
52 *   The whitelist for URL sanitization of attribute values is configured using the functions
53 *   `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
54 *   `$compileProvider`}.
55 *
56 *   The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
57 *
58 * @param {string} html HTML input.
59 * @returns {string} Sanitized HTML.
60 *
61 * @example
62   <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service">
63   <file name="index.html">
64     <script>
65         angular.module('sanitizeExample', ['ngSanitize'])
66           .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
67             $scope.snippet =
68               '<p style="color:blue">an html\n' +
69               '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
70               'snippet</p>';
71             $scope.deliberatelyTrustDangerousSnippet = function() {
72               return $sce.trustAsHtml($scope.snippet);
73             };
74           }]);
75     </script>
76     <div ng-controller="ExampleController">
77        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
78       <table>
79         <tr>
80           <td>Directive</td>
81           <td>How</td>
82           <td>Source</td>
83           <td>Rendered</td>
84         </tr>
85         <tr id="bind-html-with-sanitize">
86           <td>ng-bind-html</td>
87           <td>Automatically uses $sanitize</td>
88           <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
89           <td><div ng-bind-html="snippet"></div></td>
90         </tr>
91         <tr id="bind-html-with-trust">
92           <td>ng-bind-html</td>
93           <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
94           <td>
95           <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
96&lt;/div&gt;</pre>
97           </td>
98           <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
99         </tr>
100         <tr id="bind-default">
101           <td>ng-bind</td>
102           <td>Automatically escapes</td>
103           <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
104           <td><div ng-bind="snippet"></div></td>
105         </tr>
106       </table>
107       </div>
108   </file>
109   <file name="protractor.js" type="protractor">
110     it('should sanitize the html snippet by default', function() {
111       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
112         toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
113     });
114
115     it('should inline raw snippet if bound to a trusted value', function() {
116       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
117         toBe("<p style=\"color:blue\">an html\n" +
118              "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
119              "snippet</p>");
120     });
121
122     it('should escape snippet without any filter', function() {
123       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
124         toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
125              "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
126              "snippet&lt;/p&gt;");
127     });
128
129     it('should update', function() {
130       element(by.model('snippet')).clear();
131       element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
132       expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
133         toBe('new <b>text</b>');
134       expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
135         'new <b onclick="alert(1)">text</b>');
136       expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
137         "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
138     });
139   </file>
140   </example>
141 */
142
143
144/**
145 * @ngdoc provider
146 * @name $sanitizeProvider
147 * @this
148 *
149 * @description
150 * Creates and configures {@link $sanitize} instance.
151 */
152function $SanitizeProvider() {
153  var svgEnabled = false;
154
155  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
156    if (svgEnabled) {
157      extend(validElements, svgElements);
158    }
159    return function(html) {
160      var buf = [];
161      htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
162        return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
163      }));
164      return buf.join('');
165    };
166  }];
167
168
169  /**
170   * @ngdoc method
171   * @name $sanitizeProvider#enableSvg
172   * @kind function
173   *
174   * @description
175   * Enables a subset of svg to be supported by the sanitizer.
176   *
177   * <div class="alert alert-warning">
178   *   <p>By enabling this setting without taking other precautions, you might expose your
179   *   application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
180   *   outside of the containing element and be rendered over other elements on the page (e.g. a login
181   *   link). Such behavior can then result in phishing incidents.</p>
182   *
183   *   <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
184   *   tags within the sanitized content:</p>
185   *
186   *   <br>
187   *
188   *   <pre><code>
189   *   .rootOfTheIncludedContent svg {
190   *     overflow: hidden !important;
191   *   }
192   *   </code></pre>
193   * </div>
194   *
195   * @param {boolean=} flag Enable or disable SVG support in the sanitizer.
196   * @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
197   *    without an argument or self for chaining otherwise.
198   */
199  this.enableSvg = function(enableSvg) {
200    if (isDefined(enableSvg)) {
201      svgEnabled = enableSvg;
202      return this;
203    } else {
204      return svgEnabled;
205    }
206  };
207
208  //////////////////////////////////////////////////////////////////////////////////////////////////
209  // Private stuff
210  //////////////////////////////////////////////////////////////////////////////////////////////////
211
212  bind = angular.bind;
213  extend = angular.extend;
214  forEach = angular.forEach;
215  isDefined = angular.isDefined;
216  lowercase = angular.lowercase;
217  noop = angular.noop;
218
219  htmlParser = htmlParserImpl;
220  htmlSanitizeWriter = htmlSanitizeWriterImpl;
221
222  nodeContains = window.Node.prototype.contains || /** @this */ function(arg) {
223    // eslint-disable-next-line no-bitwise
224    return !!(this.compareDocumentPosition(arg) & 16);
225  };
226
227  // Regular Expressions for parsing tags and attributes
228  var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
229    // Match everything outside of normal chars and " (quote character)
230    NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;
231
232
233  // Good source of info about elements and attributes
234  // http://dev.w3.org/html5/spec/Overview.html#semantics
235  // http://simon.html5.org/html-elements
236
237  // Safe Void Elements - HTML5
238  // http://dev.w3.org/html5/spec/Overview.html#void-elements
239  var voidElements = toMap('area,br,col,hr,img,wbr');
240
241  // Elements that you can, intentionally, leave open (and which close themselves)
242  // http://dev.w3.org/html5/spec/Overview.html#optional-tags
243  var optionalEndTagBlockElements = toMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
244      optionalEndTagInlineElements = toMap('rp,rt'),
245      optionalEndTagElements = extend({},
246                                              optionalEndTagInlineElements,
247                                              optionalEndTagBlockElements);
248
249  // Safe Block Elements - HTML5
250  var blockElements = extend({}, optionalEndTagBlockElements, toMap('address,article,' +
251          'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
252          'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));
253
254  // Inline Elements - HTML5
255  var inlineElements = extend({}, optionalEndTagInlineElements, toMap('a,abbr,acronym,b,' +
256          'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
257          'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
258
259  // SVG Elements
260  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
261  // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
262  // They can potentially allow for arbitrary javascript to be executed. See #11290
263  var svgElements = toMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
264          'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
265          'radialGradient,rect,stop,svg,switch,text,title,tspan');
266
267  // Blocked Elements (will be stripped)
268  var blockedElements = toMap('script,style');
269
270  var validElements = extend({},
271                                     voidElements,
272                                     blockElements,
273                                     inlineElements,
274                                     optionalEndTagElements);
275
276  //Attributes that have href and hence need to be sanitized
277  var uriAttrs = toMap('background,cite,href,longdesc,src,xlink:href,xml:base');
278
279  var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
280      'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
281      'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
282      'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
283      'valign,value,vspace,width');
284
285  // SVG attributes (without "id" and "name" attributes)
286  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
287  var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
288      'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
289      'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
290      'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
291      'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
292      'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
293      'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
294      'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
295      'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
296      'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
297      'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
298      'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
299      'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
300      'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
301      'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
302
303  var validAttrs = extend({},
304                                  uriAttrs,
305                                  svgAttrs,
306                                  htmlAttrs);
307
308  function toMap(str, lowercaseKeys) {
309    var obj = {}, items = str.split(','), i;
310    for (i = 0; i < items.length; i++) {
311      obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
312    }
313    return obj;
314  }
315
316  /**
317   * Create an inert document that contains the dirty HTML that needs sanitizing
318   * Depending upon browser support we use one of three strategies for doing this.
319   * Support: Safari 10.x -> XHR strategy
320   * Support: Firefox -> DomParser strategy
321   */
322  var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
323    var inertDocument;
324    if (document && document.implementation) {
325      inertDocument = document.implementation.createHTMLDocument('inert');
326    } else {
327      throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
328    }
329    var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
330
331    // Check for the Safari 10.1 bug - which allows JS to run inside the SVG G element
332    inertBodyElement.innerHTML = '<svg><g onload="this.parentNode.remove()"></g></svg>';
333    if (!inertBodyElement.querySelector('svg')) {
334      return getInertBodyElement_XHR;
335    } else {
336      // Check for the Firefox bug - which prevents the inner img JS from being sanitized
337      inertBodyElement.innerHTML = '<svg><p><style><img src="</style><img src=x onerror=alert(1)//">';
338      if (inertBodyElement.querySelector('svg img')) {
339        return getInertBodyElement_DOMParser;
340      } else {
341        return getInertBodyElement_InertDocument;
342      }
343    }
344
345    function getInertBodyElement_XHR(html) {
346      // We add this dummy element to ensure that the rest of the content is parsed as expected
347      // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
348      html = '<remove></remove>' + html;
349      try {
350        html = encodeURI(html);
351      } catch (e) {
352        return undefined;
353      }
354      var xhr = new window.XMLHttpRequest();
355      xhr.responseType = 'document';
356      xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
357      xhr.send(null);
358      var body = xhr.response.body;
359      body.firstChild.remove();
360      return body;
361    }
362
363    function getInertBodyElement_DOMParser(html) {
364      // We add this dummy element to ensure that the rest of the content is parsed as expected
365      // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
366      html = '<remove></remove>' + html;
367      try {
368        var body = new window.DOMParser().parseFromString(html, 'text/html').body;
369        body.firstChild.remove();
370        return body;
371      } catch (e) {
372        return undefined;
373      }
374    }
375
376    function getInertBodyElement_InertDocument(html) {
377      inertBodyElement.innerHTML = html;
378
379      // Support: IE 9-11 only
380      // strip custom-namespaced attributes on IE<=11
381      if (document.documentMode) {
382        stripCustomNsAttrs(inertBodyElement);
383      }
384
385      return inertBodyElement;
386    }
387  })(window, window.document);
388
389  /**
390   * @example
391   * htmlParser(htmlString, {
392   *     start: function(tag, attrs) {},
393   *     end: function(tag) {},
394   *     chars: function(text) {},
395   *     comment: function(text) {}
396   * });
397   *
398   * @param {string} html string
399   * @param {object} handler
400   */
401  function htmlParserImpl(html, handler) {
402    if (html === null || html === undefined) {
403      html = '';
404    } else if (typeof html !== 'string') {
405      html = '' + html;
406    }
407
408    var inertBodyElement = getInertBodyElement(html);
409    if (!inertBodyElement) return '';
410
411    //mXSS protection
412    var mXSSAttempts = 5;
413    do {
414      if (mXSSAttempts === 0) {
415        throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
416      }
417      mXSSAttempts--;
418
419      // trigger mXSS if it is going to happen by reading and writing the innerHTML
420      html = inertBodyElement.innerHTML;
421      inertBodyElement = getInertBodyElement(html);
422    } while (html !== inertBodyElement.innerHTML);
423
424    var node = inertBodyElement.firstChild;
425    while (node) {
426      switch (node.nodeType) {
427        case 1: // ELEMENT_NODE
428          handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
429          break;
430        case 3: // TEXT NODE
431          handler.chars(node.textContent);
432          break;
433      }
434
435      var nextNode;
436      if (!(nextNode = node.firstChild)) {
437        if (node.nodeType === 1) {
438          handler.end(node.nodeName.toLowerCase());
439        }
440        nextNode = getNonDescendant('nextSibling', node);
441        if (!nextNode) {
442          while (nextNode == null) {
443            node = getNonDescendant('parentNode', node);
444            if (node === inertBodyElement) break;
445            nextNode = getNonDescendant('nextSibling', node);
446            if (node.nodeType === 1) {
447              handler.end(node.nodeName.toLowerCase());
448            }
449          }
450        }
451      }
452      node = nextNode;
453    }
454
455    while ((node = inertBodyElement.firstChild)) {
456      inertBodyElement.removeChild(node);
457    }
458  }
459
460  function attrToMap(attrs) {
461    var map = {};
462    for (var i = 0, ii = attrs.length; i < ii; i++) {
463      var attr = attrs[i];
464      map[attr.name] = attr.value;
465    }
466    return map;
467  }
468
469
470  /**
471   * Escapes all potentially dangerous characters, so that the
472   * resulting string can be safely inserted into attribute or
473   * element text.
474   * @param value
475   * @returns {string} escaped text
476   */
477  function encodeEntities(value) {
478    return value.
479      replace(/&/g, '&amp;').
480      replace(SURROGATE_PAIR_REGEXP, function(value) {
481        var hi = value.charCodeAt(0);
482        var low = value.charCodeAt(1);
483        return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
484      }).
485      replace(NON_ALPHANUMERIC_REGEXP, function(value) {
486        return '&#' + value.charCodeAt(0) + ';';
487      }).
488      replace(/</g, '&lt;').
489      replace(/>/g, '&gt;');
490  }
491
492  /**
493   * create an HTML/XML writer which writes to buffer
494   * @param {Array} buf use buf.join('') to get out sanitized html string
495   * @returns {object} in the form of {
496   *     start: function(tag, attrs) {},
497   *     end: function(tag) {},
498   *     chars: function(text) {},
499   *     comment: function(text) {}
500   * }
501   */
502  function htmlSanitizeWriterImpl(buf, uriValidator) {
503    var ignoreCurrentElement = false;
vendor: 3,087 bytes, lines 504-602
504    var out = bind(buf, buf.push);
505    return {
506      start: function(tag, attrs) {
507        tag = lowercase(tag);
508        if (!ignoreCurrentElement && blockedElements[tag]) {
509          ignoreCurrentElement = tag;
510        }
511        if (!ignoreCurrentElement && validElements[tag] === true) {
512          out('<');
513          out(tag);
514          forEach(attrs, function(value, key) {
515            var lkey = lowercase(key);
516            var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
517            if (validAttrs[lkey] === true &&
518              (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
519              out(' ');
520              out(key);
521              out('="');
522              out(encodeEntities(value));
523              out('"');
524            }
525          });
526          out('>');
527        }
528      },
529      end: function(tag) {
530        tag = lowercase(tag);
531        if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
532          out('</');
533          out(tag);
534          out('>');
535        }
536        // eslint-disable-next-line eqeqeq
537        if (tag == ignoreCurrentElement) {
538          ignoreCurrentElement = false;
539        }
540      },
541      chars: function(chars) {
542        if (!ignoreCurrentElement) {
543          out(encodeEntities(chars));
544        }
545      }
546    };
547  }
548
549
550  /**
551   * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
552   * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
553   * to allow any of these custom attributes. This method strips them all.
554   *
555   * @param node Root element to process
556   */
557  function stripCustomNsAttrs(node) {
558    while (node) {
559      if (node.nodeType === window.Node.ELEMENT_NODE) {
560        var attrs = node.attributes;
561        for (var i = 0, l = attrs.length; i < l; i++) {
562          var attrNode = attrs[i];
563          var attrName = attrNode.name.toLowerCase();
564          if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
565            node.removeAttributeNode(attrNode);
566            i--;
567            l--;
568          }
569        }
570      }
571
572      var nextNode = node.firstChild;
573      if (nextNode) {
574        stripCustomNsAttrs(nextNode);
575      }
576
577      node = getNonDescendant('nextSibling', node);
578    }
579  }
580
581  function getNonDescendant(propName, node) {
582    // An element is clobbered if its `propName` property points to one of its descendants
583    var nextNode = node[propName];
584    if (nextNode && nodeContains.call(node, nextNode)) {
585      throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText);
586    }
587    return nextNode;
588  }
589}
590
591function sanitizeText(chars) {
592  var buf = [];
593  var writer = htmlSanitizeWriter(buf, noop);
594  writer.chars(chars);
595  return buf.join('');
596}
597
598
599// define ngSanitize module and register $sanitize service
600angular.module('ngSanitize', [])
601  .provider('$sanitize', $SanitizeProvider)
602  .info({ angularVersion: '1.6.9
vendor: 7,334 bytes, lines 602-801
602' });
603
604/**
605 * @ngdoc filter
606 * @name linky
607 * @kind function
608 *
609 * @description
610 * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and
611 * plain email address links.
612 *
613 * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
614 *
615 * @param {string} text Input text.
616 * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in.
617 * @param {object|function(url)} [attributes] Add custom attributes to the link element.
618 *
619 *    Can be one of:
620 *
621 *    - `object`: A map of attributes
622 *    - `function`: Takes the url as a parameter and returns a map of attributes
623 *
624 *    If the map of attributes contains a value for `target`, it overrides the value of
625 *    the target parameter.
626 *
627 *
628 * @returns {string} Html-linkified and {@link $sanitize sanitized} text.
629 *
630 * @usage
631   <span ng-bind-html="linky_expression | linky"></span>
632 *
633 * @example
634   <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter">
635     <file name="index.html">
636       <div ng-controller="ExampleController">
637       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
638       <table>
639         <tr>
640           <th>Filter</th>
641           <th>Source</th>
642           <th>Rendered</th>
643         </tr>
644         <tr id="linky-filter">
645           <td>linky filter</td>
646           <td>
647             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
648           </td>
649           <td>
650             <div ng-bind-html="snippet | linky"></div>
651           </td>
652         </tr>
653         <tr id="linky-target">
654          <td>linky target</td>
655          <td>
656            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
657          </td>
658          <td>
659            <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
660          </td>
661         </tr>
662         <tr id="linky-custom-attributes">
663          <td>linky custom attributes</td>
664          <td>
665            <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
666          </td>
667          <td>
668            <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
669          </td>
670         </tr>
671         <tr id="escaped-html">
672           <td>no filter</td>
673           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
674           <td><div ng-bind="snippet"></div></td>
675         </tr>
676       </table>
677     </file>
678     <file name="script.js">
679       angular.module('linkyExample', ['ngSanitize'])
680         .controller('ExampleController', ['$scope', function($scope) {
681           $scope.snippet =
682             'Pretty text with some links:\n' +
683             'http://angularjs.org/,\n' +
684             'mailto:[email protected],\n' +
685             '[email protected],\n' +
686             'and one more: ftp://127.0.0.1/.';
687           $scope.snippetWithSingleURL = 'http://angularjs.org/';
688         }]);
689     </file>
690     <file name="protractor.js" type="protractor">
691       it('should linkify the snippet with urls', function() {
692         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
693             toBe('Pretty text with some links: http://angularjs.org/, [email protected], ' +
694                  '[email protected], and one more: ftp://127.0.0.1/.');
695         expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
696       });
697
698       it('should not linkify snippet without the linky filter', function() {
699         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
700             toBe('Pretty text with some links: http://angularjs.org/, mailto:[email protected], ' +
701                  '[email protected], and one more: ftp://127.0.0.1/.');
702         expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
703       });
704
705       it('should update', function() {
706         element(by.model('snippet')).clear();
707         element(by.model('snippet')).sendKeys('new http://link.');
708         expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
709             toBe('new http://link.');
710         expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
711         expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
712             .toBe('new http://link.');
713       });
714
715       it('should work with the target property', function() {
716        expect(element(by.id('linky-target')).
717            element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
718            toBe('http://angularjs.org/');
719        expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
720       });
721
722       it('should optionally add custom attributes', function() {
723        expect(element(by.id('linky-custom-attributes')).
724            element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
725            toBe('http://angularjs.org/');
726        expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
727       });
728     </file>
729   </example>
730 */
731angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
732  var LINKY_URL_REGEXP =
733        /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
734      MAILTO_REGEXP = /^mailto:/i;
735
736  var linkyMinErr = angular.$$minErr('linky');
737  var isDefined = angular.isDefined;
738  var isFunction = angular.isFunction;
739  var isObject = angular.isObject;
740  var isString = angular.isString;
741
742  return function(text, target, attributes) {
743    if (text == null || text === '') return text;
744    if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
745
746    var attributesFn =
747      isFunction(attributes) ? attributes :
748      isObject(attributes) ? function getAttributesObject() {return attributes;} :
749      function getEmptyAttributesObject() {return {};};
750
751    var match;
752    var raw = text;
753    var html = [];
754    var url;
755    var i;
756    while ((match = raw.match(LINKY_URL_REGEXP))) {
757      // We can not end in these as they are sometimes found at the end of the sentence
758      url = match[0];
759      // if we did not match ftp/http/www/mailto then assume mailto
760      if (!match[2] && !match[4]) {
761        url = (match[3] ? 'http://' : 'mailto:') + url;
762      }
763      i = match.index;
764      addText(raw.substr(0, i));
765      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
766      raw = raw.substring(i + match[0].length);
767    }
768    addText(raw);
769    return $sanitize(html.join(''));
770
771    function addText(text) {
772      if (!text) {
773        return;
774      }
775      html.push(sanitizeText(text));
776    }
777
778    function addLink(url, text) {
779      var key, linkAttributes = attributesFn(url);
780      html.push('<a ');
781
782      for (key in linkAttributes) {
783        html.push(key + '="' + linkAttributes[key] + '" ');
784      }
785
786      if (isDefined(target) && !('target' in linkAttributes)) {
787        html.push('target="',
788                  target,
789                  '" ');
790      }
791      html.push('href="',
792                url.replace(/"/g, '&quot;'),
793                '">');
794      addText(text);
795      html.push('</a>');
796    }
797  };
798}]);
799
800
801})(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.