engine2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/Rakefile +138 -0
  4. data/conf/message.yaml +93 -0
  5. data/conf/message_pl.yaml +93 -0
  6. data/engine2.gemspec +34 -0
  7. data/lib/engine2.rb +34 -0
  8. data/lib/engine2/action.rb +217 -0
  9. data/lib/engine2/core.rb +572 -0
  10. data/lib/engine2/handler.rb +134 -0
  11. data/lib/engine2/meta.rb +969 -0
  12. data/lib/engine2/meta/decode_meta.rb +110 -0
  13. data/lib/engine2/meta/delete_meta.rb +73 -0
  14. data/lib/engine2/meta/form_meta.rb +144 -0
  15. data/lib/engine2/meta/infra_meta.rb +292 -0
  16. data/lib/engine2/meta/link_meta.rb +133 -0
  17. data/lib/engine2/meta/list_meta.rb +284 -0
  18. data/lib/engine2/meta/save_meta.rb +63 -0
  19. data/lib/engine2/meta/view_meta.rb +22 -0
  20. data/lib/engine2/model.rb +390 -0
  21. data/lib/engine2/models/Files.rb +38 -0
  22. data/lib/engine2/models/UserInfo.rb +24 -0
  23. data/lib/engine2/post_bootstrap.rb +83 -0
  24. data/lib/engine2/pre_bootstrap.rb +27 -0
  25. data/lib/engine2/scheme.rb +202 -0
  26. data/lib/engine2/templates.rb +229 -0
  27. data/lib/engine2/type_info.rb +342 -0
  28. data/lib/engine2/version.rb +9 -0
  29. data/public/assets/javascripts.js +13 -0
  30. data/public/assets/styles.css +4 -0
  31. data/public/css/angular-motion.css +1022 -0
  32. data/public/css/angular-ui-tree.min.css +1 -0
  33. data/public/css/app.css +196 -0
  34. data/public/css/bootstrap-additions.css +1560 -0
  35. data/public/css/bootstrap.min.css +11 -0
  36. data/public/css/font-awesome.min.css +4 -0
  37. data/public/favicon.ico +0 -0
  38. data/public/fonts/FontAwesome.otf +0 -0
  39. data/public/fonts/fontawesome-webfont.eot +0 -0
  40. data/public/fonts/fontawesome-webfont.svg +655 -0
  41. data/public/fonts/fontawesome-webfont.ttf +0 -0
  42. data/public/fonts/fontawesome-webfont.woff +0 -0
  43. data/public/fonts/fontawesome-webfont.woff2 +0 -0
  44. data/public/fonts/glyphicons-halflings-regular.eot +0 -0
  45. data/public/fonts/glyphicons-halflings-regular.svg +288 -0
  46. data/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  47. data/public/fonts/glyphicons-halflings-regular.woff +0 -0
  48. data/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  49. data/public/images/file.png +0 -0
  50. data/public/images/folder-closed.png +0 -0
  51. data/public/images/folder.png +0 -0
  52. data/public/images/node-closed-2.png +0 -0
  53. data/public/images/node-closed-light.png +0 -0
  54. data/public/images/node-closed.png +0 -0
  55. data/public/images/node-opened-2.png +0 -0
  56. data/public/images/node-opened-light.png +0 -0
  57. data/public/images/node-opened.png +0 -0
  58. data/public/img/ajax-loader-dark.gif +0 -0
  59. data/public/img/ajax-loader-light.gif +0 -0
  60. data/public/img/ajax-loader.gif +0 -0
  61. data/public/js/angular-animate.js +4115 -0
  62. data/public/js/angular-cookies.js +322 -0
  63. data/public/js/angular-local-storage.js +455 -0
  64. data/public/js/angular-route.js +1022 -0
  65. data/public/js/angular-sanitize.js +717 -0
  66. data/public/js/angular-strap.js +4339 -0
  67. data/public/js/angular-strap.tpl.js +43 -0
  68. data/public/js/angular-ui-tree.js +1569 -0
  69. data/public/js/angular.js +30714 -0
  70. data/public/js/i18n/angular-locale_pl.js +115 -0
  71. data/public/js/lodash.custom.min.js +97 -0
  72. data/public/js/ng-file-upload-shim.min.js +2 -0
  73. data/public/js/ng-file-upload.min.js +3 -0
  74. data/views/app.coffee +3 -0
  75. data/views/engine2.coffee +557 -0
  76. data/views/engine2actions.coffee +849 -0
  77. data/views/engine2templates.coffee +0 -0
  78. data/views/fields/blob.slim +22 -0
  79. data/views/fields/bs_select.slim +10 -0
  80. data/views/fields/bsselect_picker.slim +18 -0
  81. data/views/fields/bsselect_picker_opt.slim +22 -0
  82. data/views/fields/checkbox.slim +11 -0
  83. data/views/fields/checkbox_buttons.slim +6 -0
  84. data/views/fields/checkbox_buttons_opt.slim +8 -0
  85. data/views/fields/currency.slim +10 -0
  86. data/views/fields/date.slim +21 -0
  87. data/views/fields/date_range.slim +44 -0
  88. data/views/fields/date_time.slim +42 -0
  89. data/views/fields/datetime.slim +42 -0
  90. data/views/fields/decimal.slim +11 -0
  91. data/views/fields/decimal_date.slim +22 -0
  92. data/views/fields/decimal_time.slim +26 -0
  93. data/views/fields/email.slim +13 -0
  94. data/views/fields/file_store.slim +61 -0
  95. data/views/fields/input_text.slim +14 -0
  96. data/views/fields/integer.slim +11 -0
  97. data/views/fields/list_bsselect.slim +18 -0
  98. data/views/fields/list_bsselect_opt.slim +21 -0
  99. data/views/fields/list_buttons.slim +3 -0
  100. data/views/fields/list_buttons_opt.slim +5 -0
  101. data/views/fields/list_select.slim +11 -0
  102. data/views/fields/list_select_opt.slim +15 -0
  103. data/views/fields/password.slim +14 -0
  104. data/views/fields/radio_checkbox.slim +10 -0
  105. data/views/fields/scaffold.slim +2 -0
  106. data/views/fields/scaffold_picker.slim +20 -0
  107. data/views/fields/select_picker.slim +12 -0
  108. data/views/fields/select_picker_opt.slim +16 -0
  109. data/views/fields/text_area.slim +10 -0
  110. data/views/fields/time.slim +22 -0
  111. data/views/fields/typeahead_picker.slim +25 -0
  112. data/views/index.slim +44 -0
  113. data/views/infra/index.slim +5 -0
  114. data/views/infra/inspect.slim +81 -0
  115. data/views/modals/close_m.slim +15 -0
  116. data/views/modals/confirm_m.slim +19 -0
  117. data/views/modals/empty_m.slim +12 -0
  118. data/views/modals/menu_m.slim +13 -0
  119. data/views/modals/yes_no_m.slim +19 -0
  120. data/views/panels/menu_m.slim +9 -0
  121. data/views/scaffold/confirm.slim +3 -0
  122. data/views/scaffold/fields.slim +10 -0
  123. data/views/scaffold/form.slim +11 -0
  124. data/views/scaffold/list.slim +42 -0
  125. data/views/scaffold/message.slim +3 -0
  126. data/views/scaffold/search.slim +20 -0
  127. data/views/scaffold/view.slim +18 -0
  128. data/views/search_fields/bsmselect_picker.slim +25 -0
  129. data/views/search_fields/bsselect_picker.slim +24 -0
  130. data/views/search_fields/checkbox.slim +11 -0
  131. data/views/search_fields/checkbox2.slim +14 -0
  132. data/views/search_fields/checkbox_buttons.slim +10 -0
  133. data/views/search_fields/date_range.slim +46 -0
  134. data/views/search_fields/decimal_date_range.slim +47 -0
  135. data/views/search_fields/input_text.slim +18 -0
  136. data/views/search_fields/integer.slim +18 -0
  137. data/views/search_fields/integer_range.slim +27 -0
  138. data/views/search_fields/list_bsmselect.slim +24 -0
  139. data/views/search_fields/list_bsselect.slim +22 -0
  140. data/views/search_fields/list_buttons.slim +8 -0
  141. data/views/search_fields/list_select.slim +17 -0
  142. data/views/search_fields/scaffold_picker.slim +19 -0
  143. data/views/search_fields/select_picker.slim +17 -0
  144. data/views/search_fields/typeahead_picker.slim +25 -0
  145. metadata +327 -0
@@ -0,0 +1,717 @@
1
+ /**
2
+ * @license AngularJS v1.5.3
3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {'use strict';
7
+
8
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
9
+ * Any commits to this file should be reviewed with security in mind. *
10
+ * Changes to this file can potentially create security vulnerabilities. *
11
+ * An approval from 2 Core members with history of modifying *
12
+ * this file is required. *
13
+ * *
14
+ * Does the change somehow allow for arbitrary javascript to be executed? *
15
+ * Or allows for someone to change the prototype of built-in objects? *
16
+ * Or gives undesired access to variables likes document or window? *
17
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
18
+
19
+ var $sanitizeMinErr = angular.$$minErr('$sanitize');
20
+
21
+ /**
22
+ * @ngdoc module
23
+ * @name ngSanitize
24
+ * @description
25
+ *
26
+ * # ngSanitize
27
+ *
28
+ * The `ngSanitize` module provides functionality to sanitize HTML.
29
+ *
30
+ *
31
+ * <div doc-module-components="ngSanitize"></div>
32
+ *
33
+ * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
34
+ */
35
+
36
+ /**
37
+ * @ngdoc service
38
+ * @name $sanitize
39
+ * @kind function
40
+ *
41
+ * @description
42
+ * Sanitizes an html string by stripping all potentially dangerous tokens.
43
+ *
44
+ * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
45
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
46
+ * it into the returned string.
47
+ *
48
+ * The whitelist for URL sanitization of attribute values is configured using the functions
49
+ * `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
50
+ * `$compileProvider`}.
51
+ *
52
+ * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
53
+ *
54
+ * @param {string} html HTML input.
55
+ * @returns {string} Sanitized HTML.
56
+ *
57
+ * @example
58
+ <example module="sanitizeExample" deps="angular-sanitize.js">
59
+ <file name="index.html">
60
+ <script>
61
+ angular.module('sanitizeExample', ['ngSanitize'])
62
+ .controller('ExampleController', ['$scope', '$sce', function($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="ExampleController">
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
+ </file>
105
+ <file name="protractor.js" type="protractor">
106
+ it('should sanitize the html snippet by default', function() {
107
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
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(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
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(element(by.css('#bind-default div')).getInnerHtml()).
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
+ element(by.model('snippet')).clear();
127
+ element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
128
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
129
+ toBe('new <b>text</b>');
130
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
131
+ 'new <b onclick="alert(1)">text</b>');
132
+ expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
133
+ "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
134
+ });
135
+ </file>
136
+ </example>
137
+ */
138
+
139
+
140
+ /**
141
+ * @ngdoc provider
142
+ * @name $sanitizeProvider
143
+ *
144
+ * @description
145
+ * Creates and configures {@link $sanitize} instance.
146
+ */
147
+ function $SanitizeProvider() {
148
+ var svgEnabled = false;
149
+
150
+ this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
151
+ if (svgEnabled) {
152
+ angular.extend(validElements, svgElements);
153
+ }
154
+ return function(html) {
155
+ var buf = [];
156
+ htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
157
+ return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
158
+ }));
159
+ return buf.join('');
160
+ };
161
+ }];
162
+
163
+
164
+ /**
165
+ * @ngdoc method
166
+ * @name $sanitizeProvider#enableSvg
167
+ * @kind function
168
+ *
169
+ * @description
170
+ * Enables a subset of svg to be supported by the sanitizer.
171
+ *
172
+ * <div class="alert alert-warning">
173
+ * <p>By enabling this setting without taking other precautions, you might expose your
174
+ * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
175
+ * outside of the containing element and be rendered over other elements on the page (e.g. a login
176
+ * link). Such behavior can then result in phishing incidents.</p>
177
+ *
178
+ * <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
179
+ * tags within the sanitized content:</p>
180
+ *
181
+ * <br>
182
+ *
183
+ * <pre><code>
184
+ * .rootOfTheIncludedContent svg {
185
+ * overflow: hidden !important;
186
+ * }
187
+ * </code></pre>
188
+ * </div>
189
+ *
190
+ * @param {boolean=} regexp New regexp to whitelist urls with.
191
+ * @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
192
+ * without an argument or self for chaining otherwise.
193
+ */
194
+ this.enableSvg = function(enableSvg) {
195
+ if (angular.isDefined(enableSvg)) {
196
+ svgEnabled = enableSvg;
197
+ return this;
198
+ } else {
199
+ return svgEnabled;
200
+ }
201
+ };
202
+ }
203
+
204
+ function sanitizeText(chars) {
205
+ var buf = [];
206
+ var writer = htmlSanitizeWriter(buf, angular.noop);
207
+ writer.chars(chars);
208
+ return buf.join('');
209
+ }
210
+
211
+
212
+ // Regular Expressions for parsing tags and attributes
213
+ var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
214
+ // Match everything outside of normal chars and " (quote character)
215
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
216
+
217
+
218
+ // Good source of info about elements and attributes
219
+ // http://dev.w3.org/html5/spec/Overview.html#semantics
220
+ // http://simon.html5.org/html-elements
221
+
222
+ // Safe Void Elements - HTML5
223
+ // http://dev.w3.org/html5/spec/Overview.html#void-elements
224
+ var voidElements = toMap("area,br,col,hr,img,wbr");
225
+
226
+ // Elements that you can, intentionally, leave open (and which close themselves)
227
+ // http://dev.w3.org/html5/spec/Overview.html#optional-tags
228
+ var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
229
+ optionalEndTagInlineElements = toMap("rp,rt"),
230
+ optionalEndTagElements = angular.extend({},
231
+ optionalEndTagInlineElements,
232
+ optionalEndTagBlockElements);
233
+
234
+ // Safe Block Elements - HTML5
235
+ var blockElements = angular.extend({}, optionalEndTagBlockElements, toMap("address,article," +
236
+ "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
237
+ "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
238
+
239
+ // Inline Elements - HTML5
240
+ var inlineElements = angular.extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
241
+ "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
242
+ "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
243
+
244
+ // SVG Elements
245
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
246
+ // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
247
+ // They can potentially allow for arbitrary javascript to be executed. See #11290
248
+ var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
249
+ "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
250
+ "radialGradient,rect,stop,svg,switch,text,title,tspan");
251
+
252
+ // Blocked Elements (will be stripped)
253
+ var blockedElements = toMap("script,style");
254
+
255
+ var validElements = angular.extend({},
256
+ voidElements,
257
+ blockElements,
258
+ inlineElements,
259
+ optionalEndTagElements);
260
+
261
+ //Attributes that have href and hence need to be sanitized
262
+ var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
263
+
264
+ var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
265
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
266
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
267
+ 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
268
+ 'valign,value,vspace,width');
269
+
270
+ // SVG attributes (without "id" and "name" attributes)
271
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
272
+ var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
273
+ 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
274
+ 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
275
+ 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
276
+ 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
277
+ 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
278
+ 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
279
+ 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
280
+ 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
281
+ 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
282
+ 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
283
+ 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
284
+ 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
285
+ 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
286
+ 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
287
+
288
+ var validAttrs = angular.extend({},
289
+ uriAttrs,
290
+ svgAttrs,
291
+ htmlAttrs);
292
+
293
+ function toMap(str, lowercaseKeys) {
294
+ var obj = {}, items = str.split(','), i;
295
+ for (i = 0; i < items.length; i++) {
296
+ obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
297
+ }
298
+ return obj;
299
+ }
300
+
301
+ var inertBodyElement;
302
+ (function(window) {
303
+ var doc;
304
+ if (window.document && window.document.implementation) {
305
+ doc = window.document.implementation.createHTMLDocument("inert");
306
+ } else {
307
+ throw $sanitizeMinErr('noinert', "Can't create an inert html document");
308
+ }
309
+ var docElement = doc.documentElement || doc.getDocumentElement();
310
+ var bodyElements = docElement.getElementsByTagName('body');
311
+
312
+ // usually there should be only one body element in the document, but IE doesn't have any, so we need to create one
313
+ if (bodyElements.length === 1) {
314
+ inertBodyElement = bodyElements[0];
315
+ } else {
316
+ var html = doc.createElement('html');
317
+ inertBodyElement = doc.createElement('body');
318
+ html.appendChild(inertBodyElement);
319
+ doc.appendChild(html);
320
+ }
321
+ })(window);
322
+
323
+ /**
324
+ * @example
325
+ * htmlParser(htmlString, {
326
+ * start: function(tag, attrs) {},
327
+ * end: function(tag) {},
328
+ * chars: function(text) {},
329
+ * comment: function(text) {}
330
+ * });
331
+ *
332
+ * @param {string} html string
333
+ * @param {object} handler
334
+ */
335
+ function htmlParser(html, handler) {
336
+ if (html === null || html === undefined) {
337
+ html = '';
338
+ } else if (typeof html !== 'string') {
339
+ html = '' + html;
340
+ }
341
+ inertBodyElement.innerHTML = html;
342
+
343
+ //mXSS protection
344
+ var mXSSAttempts = 5;
345
+ do {
346
+ if (mXSSAttempts === 0) {
347
+ throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
348
+ }
349
+ mXSSAttempts--;
350
+
351
+ // strip custom-namespaced attributes on IE<=11
352
+ if (document.documentMode <= 11) {
353
+ stripCustomNsAttrs(inertBodyElement);
354
+ }
355
+ html = inertBodyElement.innerHTML; //trigger mXSS
356
+ inertBodyElement.innerHTML = html;
357
+ } while (html !== inertBodyElement.innerHTML);
358
+
359
+ var node = inertBodyElement.firstChild;
360
+ while (node) {
361
+ switch (node.nodeType) {
362
+ case 1: // ELEMENT_NODE
363
+ handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
364
+ break;
365
+ case 3: // TEXT NODE
366
+ handler.chars(node.textContent);
367
+ break;
368
+ }
369
+
370
+ var nextNode;
371
+ if (!(nextNode = node.firstChild)) {
372
+ if (node.nodeType == 1) {
373
+ handler.end(node.nodeName.toLowerCase());
374
+ }
375
+ nextNode = node.nextSibling;
376
+ if (!nextNode) {
377
+ while (nextNode == null) {
378
+ node = node.parentNode;
379
+ if (node === inertBodyElement) break;
380
+ nextNode = node.nextSibling;
381
+ if (node.nodeType == 1) {
382
+ handler.end(node.nodeName.toLowerCase());
383
+ }
384
+ }
385
+ }
386
+ }
387
+ node = nextNode;
388
+ }
389
+
390
+ while (node = inertBodyElement.firstChild) {
391
+ inertBodyElement.removeChild(node);
392
+ }
393
+ }
394
+
395
+ function attrToMap(attrs) {
396
+ var map = {};
397
+ for (var i = 0, ii = attrs.length; i < ii; i++) {
398
+ var attr = attrs[i];
399
+ map[attr.name] = attr.value;
400
+ }
401
+ return map;
402
+ }
403
+
404
+
405
+ /**
406
+ * Escapes all potentially dangerous characters, so that the
407
+ * resulting string can be safely inserted into attribute or
408
+ * element text.
409
+ * @param value
410
+ * @returns {string} escaped text
411
+ */
412
+ function encodeEntities(value) {
413
+ return value.
414
+ replace(/&/g, '&amp;').
415
+ replace(SURROGATE_PAIR_REGEXP, function(value) {
416
+ var hi = value.charCodeAt(0);
417
+ var low = value.charCodeAt(1);
418
+ return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
419
+ }).
420
+ replace(NON_ALPHANUMERIC_REGEXP, function(value) {
421
+ return '&#' + value.charCodeAt(0) + ';';
422
+ }).
423
+ replace(/</g, '&lt;').
424
+ replace(/>/g, '&gt;');
425
+ }
426
+
427
+ /**
428
+ * create an HTML/XML writer which writes to buffer
429
+ * @param {Array} buf use buf.join('') to get out sanitized html string
430
+ * @returns {object} in the form of {
431
+ * start: function(tag, attrs) {},
432
+ * end: function(tag) {},
433
+ * chars: function(text) {},
434
+ * comment: function(text) {}
435
+ * }
436
+ */
437
+ function htmlSanitizeWriter(buf, uriValidator) {
438
+ var ignoreCurrentElement = false;
439
+ var out = angular.bind(buf, buf.push);
440
+ return {
441
+ start: function(tag, attrs) {
442
+ tag = angular.lowercase(tag);
443
+ if (!ignoreCurrentElement && blockedElements[tag]) {
444
+ ignoreCurrentElement = tag;
445
+ }
446
+ if (!ignoreCurrentElement && validElements[tag] === true) {
447
+ out('<');
448
+ out(tag);
449
+ angular.forEach(attrs, function(value, key) {
450
+ var lkey=angular.lowercase(key);
451
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
452
+ if (validAttrs[lkey] === true &&
453
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
454
+ out(' ');
455
+ out(key);
456
+ out('="');
457
+ out(encodeEntities(value));
458
+ out('"');
459
+ }
460
+ });
461
+ out('>');
462
+ }
463
+ },
464
+ end: function(tag) {
465
+ tag = angular.lowercase(tag);
466
+ if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
467
+ out('</');
468
+ out(tag);
469
+ out('>');
470
+ }
471
+ if (tag == ignoreCurrentElement) {
472
+ ignoreCurrentElement = false;
473
+ }
474
+ },
475
+ chars: function(chars) {
476
+ if (!ignoreCurrentElement) {
477
+ out(encodeEntities(chars));
478
+ }
479
+ }
480
+ };
481
+ }
482
+
483
+
484
+ /**
485
+ * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
486
+ * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
487
+ * to allow any of these custom attributes. This method strips them all.
488
+ *
489
+ * @param node Root element to process
490
+ */
491
+ function stripCustomNsAttrs(node) {
492
+ if (node.nodeType === Node.ELEMENT_NODE) {
493
+ var attrs = node.attributes;
494
+ for (var i = 0, l = attrs.length; i < l; i++) {
495
+ var attrNode = attrs[i];
496
+ var attrName = attrNode.name.toLowerCase();
497
+ if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
498
+ node.removeAttributeNode(attrNode);
499
+ i--;
500
+ l--;
501
+ }
502
+ }
503
+ }
504
+
505
+ var nextNode = node.firstChild;
506
+ if (nextNode) {
507
+ stripCustomNsAttrs(nextNode);
508
+ }
509
+
510
+ nextNode = node.nextSibling;
511
+ if (nextNode) {
512
+ stripCustomNsAttrs(nextNode);
513
+ }
514
+ }
515
+
516
+
517
+
518
+ // define ngSanitize module and register $sanitize service
519
+ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
520
+
521
+ /* global sanitizeText: false */
522
+
523
+ /**
524
+ * @ngdoc filter
525
+ * @name linky
526
+ * @kind function
527
+ *
528
+ * @description
529
+ * Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
530
+ * plain email address links.
531
+ *
532
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
533
+ *
534
+ * @param {string} text Input text.
535
+ * @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in.
536
+ * @param {object|function(url)} [attributes] Add custom attributes to the link element.
537
+ *
538
+ * Can be one of:
539
+ *
540
+ * - `object`: A map of attributes
541
+ * - `function`: Takes the url as a parameter and returns a map of attributes
542
+ *
543
+ * If the map of attributes contains a value for `target`, it overrides the value of
544
+ * the target parameter.
545
+ *
546
+ *
547
+ * @returns {string} Html-linkified and {@link $sanitize sanitized} text.
548
+ *
549
+ * @usage
550
+ <span ng-bind-html="linky_expression | linky"></span>
551
+ *
552
+ * @example
553
+ <example module="linkyExample" deps="angular-sanitize.js">
554
+ <file name="index.html">
555
+ <div ng-controller="ExampleController">
556
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
557
+ <table>
558
+ <tr>
559
+ <th>Filter</th>
560
+ <th>Source</th>
561
+ <th>Rendered</th>
562
+ </tr>
563
+ <tr id="linky-filter">
564
+ <td>linky filter</td>
565
+ <td>
566
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
567
+ </td>
568
+ <td>
569
+ <div ng-bind-html="snippet | linky"></div>
570
+ </td>
571
+ </tr>
572
+ <tr id="linky-target">
573
+ <td>linky target</td>
574
+ <td>
575
+ <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
576
+ </td>
577
+ <td>
578
+ <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
579
+ </td>
580
+ </tr>
581
+ <tr id="linky-custom-attributes">
582
+ <td>linky custom attributes</td>
583
+ <td>
584
+ <pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
585
+ </td>
586
+ <td>
587
+ <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
588
+ </td>
589
+ </tr>
590
+ <tr id="escaped-html">
591
+ <td>no filter</td>
592
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
593
+ <td><div ng-bind="snippet"></div></td>
594
+ </tr>
595
+ </table>
596
+ </file>
597
+ <file name="script.js">
598
+ angular.module('linkyExample', ['ngSanitize'])
599
+ .controller('ExampleController', ['$scope', function($scope) {
600
+ $scope.snippet =
601
+ 'Pretty text with some links:\n'+
602
+ 'http://angularjs.org/,\n'+
603
+ 'mailto:us@somewhere.org,\n'+
604
+ 'another@somewhere.org,\n'+
605
+ 'and one more: ftp://127.0.0.1/.';
606
+ $scope.snippetWithSingleURL = 'http://angularjs.org/';
607
+ }]);
608
+ </file>
609
+ <file name="protractor.js" type="protractor">
610
+ it('should linkify the snippet with urls', function() {
611
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
612
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
613
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
614
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
615
+ });
616
+
617
+ it('should not linkify snippet without the linky filter', function() {
618
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
619
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
620
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
621
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
622
+ });
623
+
624
+ it('should update', function() {
625
+ element(by.model('snippet')).clear();
626
+ element(by.model('snippet')).sendKeys('new http://link.');
627
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
628
+ toBe('new http://link.');
629
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
630
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
631
+ .toBe('new http://link.');
632
+ });
633
+
634
+ it('should work with the target property', function() {
635
+ expect(element(by.id('linky-target')).
636
+ element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
637
+ toBe('http://angularjs.org/');
638
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
639
+ });
640
+
641
+ it('should optionally add custom attributes', function() {
642
+ expect(element(by.id('linky-custom-attributes')).
643
+ element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
644
+ toBe('http://angularjs.org/');
645
+ expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
646
+ });
647
+ </file>
648
+ </example>
649
+ */
650
+ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
651
+ var LINKY_URL_REGEXP =
652
+ /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
653
+ MAILTO_REGEXP = /^mailto:/i;
654
+
655
+ var linkyMinErr = angular.$$minErr('linky');
656
+ var isString = angular.isString;
657
+
658
+ return function(text, target, attributes) {
659
+ if (text == null || text === '') return text;
660
+ if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
661
+
662
+ var match;
663
+ var raw = text;
664
+ var html = [];
665
+ var url;
666
+ var i;
667
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
668
+ // We can not end in these as they are sometimes found at the end of the sentence
669
+ url = match[0];
670
+ // if we did not match ftp/http/www/mailto then assume mailto
671
+ if (!match[2] && !match[4]) {
672
+ url = (match[3] ? 'http://' : 'mailto:') + url;
673
+ }
674
+ i = match.index;
675
+ addText(raw.substr(0, i));
676
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
677
+ raw = raw.substring(i + match[0].length);
678
+ }
679
+ addText(raw);
680
+ return $sanitize(html.join(''));
681
+
682
+ function addText(text) {
683
+ if (!text) {
684
+ return;
685
+ }
686
+ html.push(sanitizeText(text));
687
+ }
688
+
689
+ function addLink(url, text) {
690
+ var key;
691
+ html.push('<a ');
692
+ if (angular.isFunction(attributes)) {
693
+ attributes = attributes(url);
694
+ }
695
+ if (angular.isObject(attributes)) {
696
+ for (key in attributes) {
697
+ html.push(key + '="' + attributes[key] + '" ');
698
+ }
699
+ } else {
700
+ attributes = {};
701
+ }
702
+ if (angular.isDefined(target) && !('target' in attributes)) {
703
+ html.push('target="',
704
+ target,
705
+ '" ');
706
+ }
707
+ html.push('href="',
708
+ url.replace(/"/g, '&quot;'),
709
+ '">');
710
+ addText(text);
711
+ html.push('</a>');
712
+ }
713
+ };
714
+ }]);
715
+
716
+
717
+ })(window, window.angular);