angularjs-rails-gem 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,556 @@
1
+ /**
2
+ * @license AngularJS v1.1.2
3
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {
7
+ 'use strict';
8
+
9
+ /**
10
+ * @ngdoc overview
11
+ * @name ngSanitize
12
+ * @description
13
+ */
14
+
15
+ /*
16
+ * HTML Parser By Misko Hevery (misko@hevery.com)
17
+ * based on: HTML Parser By John Resig (ejohn.org)
18
+ * Original code by Erik Arvidsson, Mozilla Public License
19
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
20
+ *
21
+ * // Use like so:
22
+ * htmlParser(htmlString, {
23
+ * start: function(tag, attrs, unary) {},
24
+ * end: function(tag) {},
25
+ * chars: function(text) {},
26
+ * comment: function(text) {}
27
+ * });
28
+ *
29
+ */
30
+
31
+
32
+ /**
33
+ * @ngdoc service
34
+ * @name ngSanitize.$sanitize
35
+ * @function
36
+ *
37
+ * @description
38
+ * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
39
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
40
+ * it into the returned string, however, since our parser is more strict than a typical browser
41
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
42
+ * browser, won't make it through the sanitizer.
43
+ *
44
+ * @param {string} html Html input.
45
+ * @returns {string} Sanitized html.
46
+ *
47
+ * @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>
116
+ */
117
+ var $sanitize = function(html) {
118
+ var buf = [];
119
+ htmlParser(html, htmlSanitizeWriter(buf));
120
+ return buf.join('');
121
+ };
122
+
123
+
124
+ // Regular Expressions for parsing tags and attributes
125
+ var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
126
+ END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
127
+ ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
128
+ BEGIN_TAG_REGEXP = /^</,
129
+ BEGING_END_TAGE_REGEXP = /^<\s*\//,
130
+ COMMENT_REGEXP = /<!--(.*?)-->/g,
131
+ 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)
134
+
135
+
136
+ // Good source of info about elements and attributes
137
+ // http://dev.w3.org/html5/spec/Overview.html#semantics
138
+ // http://simon.html5.org/html-elements
139
+
140
+ // Safe Void Elements - HTML5
141
+ // http://dev.w3.org/html5/spec/Overview.html#void-elements
142
+ var voidElements = makeMap("area,br,col,hr,img,wbr");
143
+
144
+ // Elements that you can, intentionally, leave open (and which close themselves)
145
+ // http://dev.w3.org/html5/spec/Overview.html#optional-tags
146
+ var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
147
+ optionalEndTagInlineElements = makeMap("rp,rt"),
148
+ optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements);
149
+
150
+ // 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"));
154
+
155
+ // 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"));
159
+
160
+
161
+ // Special Elements (can contain anything)
162
+ var specialElements = makeMap("script,style");
163
+
164
+ var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements);
165
+
166
+ //Attributes that have href and hence need to be sanitized
167
+ var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
168
+ var validAttrs = angular.extend({}, uriAttrs, makeMap(
169
+ 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
170
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
171
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
172
+ 'scope,scrolling,shape,span,start,summary,target,title,type,'+
173
+ 'valign,value,vspace,width'));
174
+
175
+ function makeMap(str) {
176
+ var obj = {}, items = str.split(','), i;
177
+ for (i = 0; i < items.length; i++) obj[items[i]] = true;
178
+ return obj;
179
+ }
180
+
181
+
182
+ /**
183
+ * @example
184
+ * htmlParser(htmlString, {
185
+ * start: function(tag, attrs, unary) {},
186
+ * end: function(tag) {},
187
+ * chars: function(text) {},
188
+ * comment: function(text) {}
189
+ * });
190
+ *
191
+ * @param {string} html string
192
+ * @param {object} handler
193
+ */
194
+ function htmlParser( html, handler ) {
195
+ var index, chars, match, stack = [], last = html;
196
+ stack.last = function() { return stack[ stack.length - 1 ]; };
197
+
198
+ while ( html ) {
199
+ chars = true;
200
+
201
+ // Make sure we're not in a script or style element
202
+ if ( !stack.last() || !specialElements[ stack.last() ] ) {
203
+
204
+ // Comment
205
+ if ( html.indexOf("<!--") === 0 ) {
206
+ index = html.indexOf("-->");
207
+
208
+ if ( index >= 0 ) {
209
+ if (handler.comment) handler.comment( html.substring( 4, index ) );
210
+ html = html.substring( index + 3 );
211
+ chars = false;
212
+ }
213
+
214
+ // end tag
215
+ } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
216
+ match = html.match( END_TAG_REGEXP );
217
+
218
+ if ( match ) {
219
+ html = html.substring( match[0].length );
220
+ match[0].replace( END_TAG_REGEXP, parseEndTag );
221
+ chars = false;
222
+ }
223
+
224
+ // start tag
225
+ } else if ( BEGIN_TAG_REGEXP.test(html) ) {
226
+ match = html.match( START_TAG_REGEXP );
227
+
228
+ if ( match ) {
229
+ html = html.substring( match[0].length );
230
+ match[0].replace( START_TAG_REGEXP, parseStartTag );
231
+ chars = false;
232
+ }
233
+ }
234
+
235
+ if ( chars ) {
236
+ index = html.indexOf("<");
237
+
238
+ var text = index < 0 ? html : html.substring( 0, index );
239
+ html = index < 0 ? "" : html.substring( index );
240
+
241
+ if (handler.chars) handler.chars( decodeEntities(text) );
242
+ }
243
+
244
+ } 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");
249
+
250
+ if (handler.chars) handler.chars( decodeEntities(text) );
251
+
252
+ return "";
253
+ });
254
+
255
+ parseEndTag( "", stack.last() );
256
+ }
257
+
258
+ if ( html == last ) {
259
+ throw "Parse Error: " + html;
260
+ }
261
+ last = html;
262
+ }
263
+
264
+ // Clean up any remaining tags
265
+ parseEndTag();
266
+
267
+ function parseStartTag( tag, tagName, rest, unary ) {
268
+ tagName = angular.lowercase(tagName);
269
+ if ( blockElements[ tagName ] ) {
270
+ while ( stack.last() && inlineElements[ stack.last() ] ) {
271
+ parseEndTag( "", stack.last() );
272
+ }
273
+ }
274
+
275
+ if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
276
+ parseEndTag( "", tagName );
277
+ }
278
+
279
+ unary = voidElements[ tagName ] || !!unary;
280
+
281
+ if ( !unary )
282
+ stack.push( tagName );
283
+
284
+ var attrs = {};
285
+
286
+ rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQoutedValue, unqoutedValue) {
287
+ var value = doubleQuotedValue
288
+ || singleQoutedValue
289
+ || unqoutedValue
290
+ || '';
291
+
292
+ attrs[name] = decodeEntities(value);
293
+ });
294
+ if (handler.start) handler.start( tagName, attrs, unary );
295
+ }
296
+
297
+ function parseEndTag( tag, tagName ) {
298
+ var pos = 0, i;
299
+ tagName = angular.lowercase(tagName);
300
+ if ( tagName )
301
+ // Find the closest opened tag of the same type
302
+ for ( pos = stack.length - 1; pos >= 0; pos-- )
303
+ if ( stack[ pos ] == tagName )
304
+ break;
305
+
306
+ if ( pos >= 0 ) {
307
+ // Close all the open elements, up the stack
308
+ for ( i = stack.length - 1; i >= pos; i-- )
309
+ if (handler.end) handler.end( stack[ i ] );
310
+
311
+ // Remove the open elements from the stack
312
+ stack.length = pos;
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * decodes all entities into regular string
319
+ * @param value
320
+ * @returns {string} A string with decoded entities.
321
+ */
322
+ var hiddenPre=document.createElement("pre");
323
+ function decodeEntities(value) {
324
+ hiddenPre.innerHTML=value.replace(/</g,"&lt;");
325
+ return hiddenPre.innerText || hiddenPre.textContent || '';
326
+ }
327
+
328
+ /**
329
+ * Escapes all potentially dangerous characters, so that the
330
+ * resulting string can be safely inserted into attribute or
331
+ * element text.
332
+ * @param value
333
+ * @returns escaped text
334
+ */
335
+ function encodeEntities(value) {
336
+ return value.
337
+ replace(/&/g, '&amp;').
338
+ replace(NON_ALPHANUMERIC_REGEXP, function(value){
339
+ return '&#' + value.charCodeAt(0) + ';';
340
+ }).
341
+ replace(/</g, '&lt;').
342
+ replace(/>/g, '&gt;');
343
+ }
344
+
345
+ /**
346
+ * create an HTML/XML writer which writes to buffer
347
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
348
+ * @returns {object} in the form of {
349
+ * start: function(tag, attrs, unary) {},
350
+ * end: function(tag) {},
351
+ * chars: function(text) {},
352
+ * comment: function(text) {}
353
+ * }
354
+ */
355
+ function htmlSanitizeWriter(buf){
356
+ var ignore = false;
357
+ var out = angular.bind(buf, buf.push);
358
+ return {
359
+ start: function(tag, attrs, unary){
360
+ tag = angular.lowercase(tag);
361
+ if (!ignore && specialElements[tag]) {
362
+ ignore = tag;
363
+ }
364
+ if (!ignore && validElements[tag] == true) {
365
+ out('<');
366
+ out(tag);
367
+ angular.forEach(attrs, function(value, key){
368
+ var lkey=angular.lowercase(key);
369
+ if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) {
370
+ out(' ');
371
+ out(key);
372
+ out('="');
373
+ out(encodeEntities(value));
374
+ out('"');
375
+ }
376
+ });
377
+ out(unary ? '/>' : '>');
378
+ }
379
+ },
380
+ end: function(tag){
381
+ tag = angular.lowercase(tag);
382
+ if (!ignore && validElements[tag] == true) {
383
+ out('</');
384
+ out(tag);
385
+ out('>');
386
+ }
387
+ if (tag == ignore) {
388
+ ignore = false;
389
+ }
390
+ },
391
+ chars: function(chars){
392
+ if (!ignore) {
393
+ out(encodeEntities(chars));
394
+ }
395
+ }
396
+ };
397
+ }
398
+
399
+
400
+ // define ngSanitize module and register $sanitize service
401
+ angular.module('ngSanitize', []).value('$sanitize', $sanitize);
402
+
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
+ }]);
425
+ /**
426
+ * @ngdoc filter
427
+ * @name ngSanitize.filter:linky
428
+ * @function
429
+ *
430
+ * @description
431
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
432
+ * plain email address links.
433
+ *
434
+ * @param {string} text Input text.
435
+ * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
436
+ * @returns {string} Html-linkified text.
437
+ *
438
+ * @usage
439
+ <span ng-bind-html="linky_expression | linky"></span>
440
+ *
441
+ * @example
442
+ <doc:example module="ngSanitize">
443
+ <doc:source>
444
+ <script>
445
+ function Ctrl($scope) {
446
+ $scope.snippet =
447
+ 'Pretty text with some links:\n'+
448
+ 'http://angularjs.org/,\n'+
449
+ 'mailto:us@somewhere.org,\n'+
450
+ 'another@somewhere.org,\n'+
451
+ 'and one more: ftp://127.0.0.1/.';
452
+ $scope.snippetWithTarget = 'http://angularjs.org/';
453
+ }
454
+ </script>
455
+ <div ng-controller="Ctrl">
456
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
457
+ <table>
458
+ <tr>
459
+ <td>Filter</td>
460
+ <td>Source</td>
461
+ <td>Rendered</td>
462
+ </tr>
463
+ <tr id="linky-filter">
464
+ <td>linky filter</td>
465
+ <td>
466
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
467
+ </td>
468
+ <td>
469
+ <div ng-bind-html="snippet | linky"></div>
470
+ </td>
471
+ </tr>
472
+ <tr id="linky-target">
473
+ <td>linky target</td>
474
+ <td>
475
+ <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
476
+ </td>
477
+ <td>
478
+ <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
479
+ </td>
480
+ </tr>
481
+ <tr id="escaped-html">
482
+ <td>no filter</td>
483
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
484
+ <td><div ng-bind="snippet"></div></td>
485
+ </tr>
486
+ </table>
487
+ </doc:source>
488
+ <doc:scenario>
489
+ it('should linkify the snippet with urls', function() {
490
+ expect(using('#linky-filter').binding('snippet | linky')).
491
+ toBe('Pretty text with some links:&#10;' +
492
+ '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
493
+ '<a href="mailto:us@somewhere.org">us@somewhere.org</a>,&#10;' +
494
+ '<a href="mailto:another@somewhere.org">another@somewhere.org</a>,&#10;' +
495
+ 'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
496
+ });
497
+
498
+ it ('should not linkify snippet without the linky filter', function() {
499
+ expect(using('#escaped-html').binding('snippet')).
500
+ toBe("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
+ });
506
+
507
+ it('should update', function() {
508
+ input('snippet').enter('new http://link.');
509
+ expect(using('#linky-filter').binding('snippet | linky')).
510
+ toBe('new <a href="http://link">http://link</a>.');
511
+ expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
512
+ });
513
+
514
+ it('should work with the target property', function() {
515
+ expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
516
+ toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
517
+ });
518
+ </doc:scenario>
519
+ </doc:example>
520
+ */
521
+ angular.module('ngSanitize').filter('linky', function() {
522
+ var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
523
+ MAILTO_REGEXP = /^mailto:/;
524
+
525
+ return function(text, target) {
526
+ if (!text) return text;
527
+ var match;
528
+ var raw = text;
529
+ var html = [];
530
+ // TODO(vojta): use $sanitize instead
531
+ var writer = htmlSanitizeWriter(html);
532
+ var url;
533
+ var i;
534
+ var properties = {};
535
+ if (angular.isDefined(target)) {
536
+ properties.target = target;
537
+ }
538
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
539
+ // We can not end in these as they are sometimes found at the end of the sentence
540
+ url = match[0];
541
+ // if we did not match ftp/http/mailto then assume mailto
542
+ if (match[2] == match[3]) url = 'mailto:' + url;
543
+ i = match.index;
544
+ writer.chars(raw.substr(0, i));
545
+ properties.href = url;
546
+ writer.start('a', properties);
547
+ writer.chars(match[0].replace(MAILTO_REGEXP, ''));
548
+ writer.end('a');
549
+ raw = raw.substring(i + match[0].length);
550
+ }
551
+ writer.chars(raw);
552
+ return html.join('');
553
+ };
554
+ });
555
+
556
+ })(window, window.angular);