angular-gem 1.2.0.1 → 1.2.1

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