angular-gem 1.2.10 → 1.2.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,625 @@
1
+ /**
2
+ * @license AngularJS v1.2.11
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 overview
12
+ * @name ngSanitize
13
+ * @description
14
+ *
15
+ * # ngSanitize
16
+ *
17
+ * The `ngSanitize` module provides functionality to sanitize HTML.
18
+ *
19
+ * {@installModule sanitize}
20
+ *
21
+ * <div doc-module-components="ngSanitize"></div>
22
+ *
23
+ * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
24
+ */
25
+
26
+ /*
27
+ * HTML Parser By Misko Hevery (misko@hevery.com)
28
+ * based on: HTML Parser By John Resig (ejohn.org)
29
+ * Original code by Erik Arvidsson, Mozilla Public License
30
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
31
+ *
32
+ * // Use like so:
33
+ * htmlParser(htmlString, {
34
+ * start: function(tag, attrs, unary) {},
35
+ * end: function(tag) {},
36
+ * chars: function(text) {},
37
+ * comment: function(text) {}
38
+ * });
39
+ *
40
+ */
41
+
42
+
43
+ /**
44
+ * @ngdoc service
45
+ * @name ngSanitize.$sanitize
46
+ * @function
47
+ *
48
+ * @description
49
+ * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
50
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
51
+ * it into the returned string, however, since our parser is more strict than a typical browser
52
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
53
+ * browser, won't make it through the sanitizer.
54
+ * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
55
+ * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
56
+ *
57
+ * @param {string} html Html input.
58
+ * @returns {string} Sanitized html.
59
+ *
60
+ * @example
61
+ <doc:example module="ngSanitize">
62
+ <doc:source>
63
+ <script>
64
+ function Ctrl($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="Ctrl">
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
+ </doc:source>
107
+ <doc: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
+ </doc:protractor>
138
+ </doc: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
+ // 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 escaped text
404
+ */
405
+ function encodeEntities(value) {
406
+ return value.
407
+ replace(/&/g, '&amp;').
408
+ replace(NON_ALPHANUMERIC_REGEXP, function(value){
409
+ return '&#' + value.charCodeAt(0) + ';';
410
+ }).
411
+ replace(/</g, '&lt;').
412
+ replace(/>/g, '&gt;');
413
+ }
414
+
415
+ /**
416
+ * create an HTML/XML writer which writes to buffer
417
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
418
+ * @returns {object} in the form of {
419
+ * start: function(tag, attrs, unary) {},
420
+ * end: function(tag) {},
421
+ * chars: function(text) {},
422
+ * comment: function(text) {}
423
+ * }
424
+ */
425
+ function htmlSanitizeWriter(buf, uriValidator){
426
+ var ignore = false;
427
+ var out = angular.bind(buf, buf.push);
428
+ return {
429
+ start: function(tag, attrs, unary){
430
+ tag = angular.lowercase(tag);
431
+ if (!ignore && specialElements[tag]) {
432
+ ignore = tag;
433
+ }
434
+ if (!ignore && validElements[tag] === true) {
435
+ out('<');
436
+ out(tag);
437
+ angular.forEach(attrs, function(value, key){
438
+ var lkey=angular.lowercase(key);
439
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
440
+ if (validAttrs[lkey] === true &&
441
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
442
+ out(' ');
443
+ out(key);
444
+ out('="');
445
+ out(encodeEntities(value));
446
+ out('"');
447
+ }
448
+ });
449
+ out(unary ? '/>' : '>');
450
+ }
451
+ },
452
+ end: function(tag){
453
+ tag = angular.lowercase(tag);
454
+ if (!ignore && validElements[tag] === true) {
455
+ out('</');
456
+ out(tag);
457
+ out('>');
458
+ }
459
+ if (tag == ignore) {
460
+ ignore = false;
461
+ }
462
+ },
463
+ chars: function(chars){
464
+ if (!ignore) {
465
+ out(encodeEntities(chars));
466
+ }
467
+ }
468
+ };
469
+ }
470
+
471
+
472
+ // define ngSanitize module and register $sanitize service
473
+ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
474
+
475
+ /* global sanitizeText: false */
476
+
477
+ /**
478
+ * @ngdoc filter
479
+ * @name ngSanitize.filter:linky
480
+ * @function
481
+ *
482
+ * @description
483
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
484
+ * plain email address links.
485
+ *
486
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
487
+ *
488
+ * @param {string} text Input text.
489
+ * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
490
+ * @returns {string} Html-linkified text.
491
+ *
492
+ * @usage
493
+ <span ng-bind-html="linky_expression | linky"></span>
494
+ *
495
+ * @example
496
+ <doc:example module="ngSanitize">
497
+ <doc:source>
498
+ <script>
499
+ function Ctrl($scope) {
500
+ $scope.snippet =
501
+ 'Pretty text with some links:\n'+
502
+ 'http://angularjs.org/,\n'+
503
+ 'mailto:us@somewhere.org,\n'+
504
+ 'another@somewhere.org,\n'+
505
+ 'and one more: ftp://127.0.0.1/.';
506
+ $scope.snippetWithTarget = 'http://angularjs.org/';
507
+ }
508
+ </script>
509
+ <div ng-controller="Ctrl">
510
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
511
+ <table>
512
+ <tr>
513
+ <td>Filter</td>
514
+ <td>Source</td>
515
+ <td>Rendered</td>
516
+ </tr>
517
+ <tr id="linky-filter">
518
+ <td>linky filter</td>
519
+ <td>
520
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
521
+ </td>
522
+ <td>
523
+ <div ng-bind-html="snippet | linky"></div>
524
+ </td>
525
+ </tr>
526
+ <tr id="linky-target">
527
+ <td>linky target</td>
528
+ <td>
529
+ <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
530
+ </td>
531
+ <td>
532
+ <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
533
+ </td>
534
+ </tr>
535
+ <tr id="escaped-html">
536
+ <td>no filter</td>
537
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
538
+ <td><div ng-bind="snippet"></div></td>
539
+ </tr>
540
+ </table>
541
+ </doc:source>
542
+ <doc:protractor>
543
+ it('should linkify the snippet with urls', function() {
544
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
545
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
546
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
547
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
548
+ });
549
+
550
+ it('should not linkify snippet without the linky filter', function() {
551
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
552
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
553
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
554
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
555
+ });
556
+
557
+ it('should update', function() {
558
+ element(by.model('snippet')).clear();
559
+ element(by.model('snippet')).sendKeys('new http://link.');
560
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
561
+ toBe('new http://link.');
562
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
563
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
564
+ .toBe('new http://link.');
565
+ });
566
+
567
+ it('should work with the target property', function() {
568
+ expect(element(by.id('linky-target')).
569
+ element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
570
+ toBe('http://angularjs.org/');
571
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
572
+ });
573
+ </doc:protractor>
574
+ </doc:example>
575
+ */
576
+ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
577
+ var LINKY_URL_REGEXP =
578
+ /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
579
+ MAILTO_REGEXP = /^mailto:/;
580
+
581
+ return function(text, target) {
582
+ if (!text) return text;
583
+ var match;
584
+ var raw = text;
585
+ var html = [];
586
+ var url;
587
+ var i;
588
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
589
+ // We can not end in these as they are sometimes found at the end of the sentence
590
+ url = match[0];
591
+ // if we did not match ftp/http/mailto then assume mailto
592
+ if (match[2] == match[3]) url = 'mailto:' + url;
593
+ i = match.index;
594
+ addText(raw.substr(0, i));
595
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
596
+ raw = raw.substring(i + match[0].length);
597
+ }
598
+ addText(raw);
599
+ return $sanitize(html.join(''));
600
+
601
+ function addText(text) {
602
+ if (!text) {
603
+ return;
604
+ }
605
+ html.push(sanitizeText(text));
606
+ }
607
+
608
+ function addLink(url, text) {
609
+ html.push('<a ');
610
+ if (angular.isDefined(target)) {
611
+ html.push('target="');
612
+ html.push(target);
613
+ html.push('" ');
614
+ }
615
+ html.push('href="');
616
+ html.push(url);
617
+ html.push('">');
618
+ addText(text);
619
+ html.push('</a>');
620
+ }
621
+ };
622
+ }]);
623
+
624
+
625
+ })(window, window.angular);