PageSourceSearch

https://nycsca.org/DesktopModules/NYCSCA/Achievements/resources/js/angular-sanitize.js?cdv=1257

js nycsca.org collected 2026-10-02 06:54:45 UTC 25,655 bytes, 717 lines download raw bytes

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