angularjs-rails 1.2.14 → 1.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);