angular-gem 1.2.15 → 1.2.16

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