angular-gem 1.2.18.1 → 1.2.19

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