angularjs-rails 1.2.14 → 1.2.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,25 @@
1
1
  /**
2
- * @license AngularJS v1.1.5
3
- * (c) 2010-2012 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.3.0-beta.3
3
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
- (function(window, angular, undefined) {
7
- 'use strict';
6
+ (function(window, angular, undefined) {'use strict';
7
+
8
+ var $sanitizeMinErr = angular.$$minErr('$sanitize');
8
9
 
9
10
  /**
10
- * @ngdoc overview
11
+ * @ngdoc module
11
12
  * @name ngSanitize
12
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.
13
23
  */
14
24
 
15
25
  /*
@@ -31,7 +41,7 @@
31
41
 
32
42
  /**
33
43
  * @ngdoc service
34
- * @name ngSanitize.$sanitize
44
+ * @name $sanitize
35
45
  * @function
36
46
  *
37
47
  * @description
@@ -40,97 +50,124 @@
40
50
  * it into the returned string, however, since our parser is more strict than a typical browser
41
51
  * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
42
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`}.
43
55
  *
44
56
  * @param {string} html Html input.
45
57
  * @returns {string} Sanitized html.
46
58
  *
47
59
  * @example
48
- <doc:example module="ngSanitize">
49
- <doc:source>
50
- <script>
51
- function Ctrl($scope) {
52
- $scope.snippet =
53
- '<p style="color:blue">an html\n' +
54
- '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
55
- 'snippet</p>';
56
- }
57
- </script>
58
- <div ng-controller="Ctrl">
59
- Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
60
- <table>
61
- <tr>
62
- <td>Filter</td>
63
- <td>Source</td>
64
- <td>Rendered</td>
65
- </tr>
66
- <tr id="html-filter">
67
- <td>html filter</td>
68
- <td>
69
- <pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre>
70
- </td>
71
- <td>
72
- <div ng-bind-html="snippet"></div>
73
- </td>
74
- </tr>
75
- <tr id="escaped-html">
76
- <td>no filter</td>
77
- <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
78
- <td><div ng-bind="snippet"></div></td>
79
- </tr>
80
- <tr id="html-unsafe-filter">
81
- <td>unsafe html filter</td>
82
- <td><pre>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
83
- <td><div ng-bind-html-unsafe="snippet"></div></td>
84
- </tr>
85
- </table>
86
- </div>
87
- </doc:source>
88
- <doc:scenario>
89
- it('should sanitize the html snippet ', function() {
90
- expect(using('#html-filter').element('div').html()).
91
- toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
92
- });
93
-
94
- it('should escape snippet without any filter', function() {
95
- expect(using('#escaped-html').element('div').html()).
96
- toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
97
- "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
98
- "snippet&lt;/p&gt;");
99
- });
100
-
101
- it('should inline raw snippet if filtered as unsafe', function() {
102
- expect(using('#html-unsafe-filter').element("div").html()).
103
- toBe("<p style=\"color:blue\">an html\n" +
104
- "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
105
- "snippet</p>");
106
- });
107
-
108
- it('should update', function() {
109
- input('snippet').enter('new <b>text</b>');
110
- expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>');
111
- expect(using('#escaped-html').element('div').html()).toBe("new &lt;b&gt;text&lt;/b&gt;");
112
- expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>');
113
- });
114
- </doc:scenario>
115
- </doc: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>
116
138
  */
117
- var $sanitize = function(html) {
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) {
118
152
  var buf = [];
119
- htmlParser(html, htmlSanitizeWriter(buf));
120
- return buf.join('');
121
- };
153
+ var writer = htmlSanitizeWriter(buf, angular.noop);
154
+ writer.chars(chars);
155
+ return buf.join('');
156
+ }
122
157
 
123
158
 
124
159
  // Regular Expressions for parsing tags and attributes
125
- var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
160
+ var START_TAG_REGEXP =
161
+ /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
126
162
  END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
127
163
  ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
128
164
  BEGIN_TAG_REGEXP = /^</,
129
165
  BEGING_END_TAGE_REGEXP = /^<\s*\//,
130
166
  COMMENT_REGEXP = /<!--(.*?)-->/g,
167
+ DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
131
168
  CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
132
- URI_REGEXP = /^((ftp|https?):\/\/|mailto:|tel:|#)/,
133
- NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character)
169
+ // Match everything outside of normal chars and " (quote character)
170
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
134
171
 
135
172
 
136
173
  // Good source of info about elements and attributes
@@ -145,23 +182,29 @@ var voidElements = makeMap("area,br,col,hr,img,wbr");
145
182
  // http://dev.w3.org/html5/spec/Overview.html#optional-tags
146
183
  var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
147
184
  optionalEndTagInlineElements = makeMap("rp,rt"),
148
- optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
185
+ optionalEndTagElements = angular.extend({},
186
+ optionalEndTagInlineElements,
187
+ optionalEndTagBlockElements);
149
188
 
150
189
  // Safe Block Elements - HTML5
151
- var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," +
152
- "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," +
153
- "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
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"));
154
193
 
155
194
  // Inline Elements - HTML5
156
- var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," +
157
- "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," +
158
- "span,strike,strong,sub,sup,time,tt,u,var"));
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"));
159
198
 
160
199
 
161
200
  // Special Elements (can contain anything)
162
201
  var specialElements = makeMap("script,style");
163
202
 
164
- var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
203
+ var validElements = angular.extend({},
204
+ voidElements,
205
+ blockElements,
206
+ inlineElements,
207
+ optionalEndTagElements);
165
208
 
166
209
  //Attributes that have href and hence need to be sanitized
167
210
  var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
@@ -169,7 +212,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
169
212
  'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
170
213
  'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
171
214
  'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
172
- 'scope,scrolling,shape,span,start,summary,target,title,type,'+
215
+ 'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
173
216
  'valign,value,vspace,width'));
174
217
 
175
218
  function makeMap(str) {
@@ -203,14 +246,22 @@ function htmlParser( html, handler ) {
203
246
 
204
247
  // Comment
205
248
  if ( html.indexOf("<!--") === 0 ) {
206
- index = html.indexOf("-->");
249
+ // comments containing -- are not allowed unless they terminate the comment
250
+ index = html.indexOf("--", 4);
207
251
 
208
- if ( index >= 0 ) {
252
+ if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
209
253
  if (handler.comment) handler.comment( html.substring( 4, index ) );
210
254
  html = html.substring( index + 3 );
211
255
  chars = false;
212
256
  }
257
+ // DOCTYPE
258
+ } else if ( DOCTYPE_REGEXP.test(html) ) {
259
+ match = html.match( DOCTYPE_REGEXP );
213
260
 
261
+ if ( match ) {
262
+ html = html.replace( match[0], '');
263
+ chars = false;
264
+ }
214
265
  // end tag
215
266
  } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
216
267
  match = html.match( END_TAG_REGEXP );
@@ -242,21 +293,21 @@ function htmlParser( html, handler ) {
242
293
  }
243
294
 
244
295
  } else {
245
- html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){
246
- text = text.
247
- replace(COMMENT_REGEXP, "$1").
248
- replace(CDATA_REGEXP, "$1");
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");
249
299
 
250
- if (handler.chars) handler.chars( decodeEntities(text) );
300
+ if (handler.chars) handler.chars( decodeEntities(text) );
251
301
 
252
- return "";
302
+ return "";
253
303
  });
254
304
 
255
305
  parseEndTag( "", stack.last() );
256
306
  }
257
307
 
258
308
  if ( html == last ) {
259
- throw "Parse Error: " + html;
309
+ throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
310
+ "of html: {0}", html);
260
311
  }
261
312
  last = html;
262
313
  }
@@ -283,13 +334,14 @@ function htmlParser( html, handler ) {
283
334
 
284
335
  var attrs = {};
285
336
 
286
- rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
287
- var value = doubleQuotedValue
288
- || singleQoutedValue
289
- || unqoutedValue
290
- || '';
337
+ rest.replace(ATTR_REGEXP,
338
+ function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
339
+ var value = doubleQuotedValue
340
+ || singleQuotedValue
341
+ || unquotedValue
342
+ || '';
291
343
 
292
- attrs[name] = decodeEntities(value);
344
+ attrs[name] = decodeEntities(value);
293
345
  });
294
346
  if (handler.start) handler.start( tagName, attrs, unary );
295
347
  }
@@ -314,15 +366,32 @@ function htmlParser( html, handler ) {
314
366
  }
315
367
  }
316
368
 
369
+ var hiddenPre=document.createElement("pre");
370
+ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
317
371
  /**
318
372
  * decodes all entities into regular string
319
373
  * @param value
320
374
  * @returns {string} A string with decoded entities.
321
375
  */
322
- var hiddenPre=document.createElement("pre");
323
376
  function decodeEntities(value) {
324
- hiddenPre.innerHTML=value.replace(/</g,"&lt;");
325
- return hiddenPre.innerText || hiddenPre.textContent || '';
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;
326
395
  }
327
396
 
328
397
  /**
@@ -330,7 +399,7 @@ function decodeEntities(value) {
330
399
  * resulting string can be safely inserted into attribute or
331
400
  * element text.
332
401
  * @param value
333
- * @returns escaped text
402
+ * @returns {string} escaped text
334
403
  */
335
404
  function encodeEntities(value) {
336
405
  return value.
@@ -352,7 +421,7 @@ function encodeEntities(value) {
352
421
  * comment: function(text) {}
353
422
  * }
354
423
  */
355
- function htmlSanitizeWriter(buf){
424
+ function htmlSanitizeWriter(buf, uriValidator){
356
425
  var ignore = false;
357
426
  var out = angular.bind(buf, buf.push);
358
427
  return {
@@ -361,12 +430,14 @@ function htmlSanitizeWriter(buf){
361
430
  if (!ignore && specialElements[tag]) {
362
431
  ignore = tag;
363
432
  }
364
- if (!ignore && validElements[tag] == true) {
433
+ if (!ignore && validElements[tag] === true) {
365
434
  out('<');
366
435
  out(tag);
367
436
  angular.forEach(attrs, function(value, key){
368
437
  var lkey=angular.lowercase(key);
369
- if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
438
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
439
+ if (validAttrs[lkey] === true &&
440
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
370
441
  out(' ');
371
442
  out(key);
372
443
  out('="');
@@ -379,7 +450,7 @@ function htmlSanitizeWriter(buf){
379
450
  },
380
451
  end: function(tag){
381
452
  tag = angular.lowercase(tag);
382
- if (!ignore && validElements[tag] == true) {
453
+ if (!ignore && validElements[tag] === true) {
383
454
  out('</');
384
455
  out(tag);
385
456
  out('>');
@@ -398,39 +469,20 @@ function htmlSanitizeWriter(buf){
398
469
 
399
470
 
400
471
  // define ngSanitize module and register $sanitize service
401
- angular.module('ngSanitize', []).value('$sanitize', $sanitize);
472
+ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
402
473
 
403
- /**
404
- * @ngdoc directive
405
- * @name ngSanitize.directive:ngBindHtml
406
- *
407
- * @description
408
- * Creates a binding that will sanitize the result of evaluating the `expression` with the
409
- * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
410
- *
411
- * See {@link ngSanitize.$sanitize $sanitize} docs for examples.
412
- *
413
- * @element ANY
414
- * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
415
- */
416
- angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
417
- return function(scope, element, attr) {
418
- element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
419
- scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
420
- value = $sanitize(value);
421
- element.html(value || '');
422
- });
423
- };
424
- }]);
474
+ /* global sanitizeText: false */
425
475
 
426
476
  /**
427
477
  * @ngdoc filter
428
- * @name ngSanitize.filter:linky
478
+ * @name linky
429
479
  * @function
430
480
  *
431
481
  * @description
432
- * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
433
- * plain email address links.
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.
434
486
  *
435
487
  * @param {string} text Input text.
436
488
  * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
@@ -440,8 +492,8 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
440
492
  <span ng-bind-html="linky_expression | linky"></span>
441
493
  *
442
494
  * @example
443
- <doc:example module="ngSanitize">
444
- <doc:source>
495
+ <example module="ngSanitize" deps="angular-sanitize.js">
496
+ <file name="index.html">
445
497
  <script>
446
498
  function Ctrl($scope) {
447
499
  $scope.snippet =
@@ -485,42 +537,44 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
485
537
  <td><div ng-bind="snippet"></div></td>
486
538
  </tr>
487
539
  </table>
488
- </doc:source>
489
- <doc:scenario>
540
+ </file>
541
+ <file name="protractor.js" type="protractor">
490
542
  it('should linkify the snippet with urls', function() {
491
- expect(using('#linky-filter').binding('snippet | linky')).
492
- toBe('Pretty text with some links:&#10;' +
493
- '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
494
- '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
495
- '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
496
- 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
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);
497
547
  });
498
548
 
499
- it ('should not linkify snippet without the linky filter', function() {
500
- expect(using('#escaped-html').binding('snippet')).
501
- toBe("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/.");
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);
506
554
  });
507
555
 
508
556
  it('should update', function() {
509
- input('snippet').enter('new http://link.');
510
- expect(using('#linky-filter').binding('snippet | linky')).
511
- toBe('new <a href="http://link">http://link</a>.');
512
- expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
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.');
513
564
  });
514
565
 
515
566
  it('should work with the target property', function() {
516
- expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
517
- toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
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');
518
571
  });
519
- </doc:scenario>
520
- </doc:example>
572
+ </file>
573
+ </example>
521
574
  */
522
- angular.module('ngSanitize').filter('linky', function() {
523
- var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
575
+ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
576
+ var LINKY_URL_REGEXP =
577
+ /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
524
578
  MAILTO_REGEXP = /^mailto:/;
525
579
 
526
580
  return function(text, target) {
@@ -528,31 +582,43 @@ angular.module('ngSanitize').filter('linky', function() {
528
582
  var match;
529
583
  var raw = text;
530
584
  var html = [];
531
- // TODO(vojta): use $sanitize instead
532
- var writer = htmlSanitizeWriter(html);
533
585
  var url;
534
586
  var i;
535
- var properties = {};
536
- if (angular.isDefined(target)) {
537
- properties.target = target;
538
- }
539
587
  while ((match = raw.match(LINKY_URL_REGEXP))) {
540
588
  // We can not end in these as they are sometimes found at the end of the sentence
541
589
  url = match[0];
542
590
  // if we did not match ftp/http/mailto then assume mailto
543
591
  if (match[2] == match[3]) url = 'mailto:' + url;
544
592
  i = match.index;
545
- writer.chars(raw.substr(0, i));
546
- properties.href = url;
547
- writer.start('a', properties);
548
- writer.chars(match[0].replace(MAILTO_REGEXP, ''));
549
- writer.end('a');
593
+ addText(raw.substr(0, i));
594
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
550
595
  raw = raw.substring(i + match[0].length);
551
596
  }
552
- writer.chars(raw);
553
- return html.join('');
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
+ }
554
620
  };
555
- });
621
+ }]);
556
622
 
557
623
 
558
624
  })(window, window.angular);