angular-gem 1.2.19 → 1.2.20

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