GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

third-party/angularjs/angular-1.6.4/angular-sanitize.js   F
last analyzed

Complexity

Total Complexity 71
Complexity/F 2.54

Size

Lines of Code 751
Function Count 28

Duplication

Duplicated Lines 180
Ratio 23.97 %

Importance

Changes 0
Metric Value
eloc 275
dl 180
loc 751
rs 2.7199
c 0
b 0
f 0
wmc 71
mnd 43
bc 43
fnc 28
bpm 1.5357
cpm 2.5357
noi 2

6 Functions

Rating   Name   Duplication   Size   Complexity  
B angular-sanitize.js ➔ getAttributesObject 1 1 7
A angular-sanitize.js ➔ sanitizeText 0 6 1
A angular-sanitize.js ➔ getEmptyAttributesObject 1 1 1
A angular-sanitize.js ➔ addText 6 6 5
F angular-sanitize.js ➔ $SanitizeProvider 112 388 54
A angular-sanitize.js ➔ addLink 19 19 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like third-party/angularjs/angular-1.6.4/angular-sanitize.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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