material_raingular 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license AngularJS v1.4.0
3
- * (c) 2010-2015 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.5.3
3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular, undefined) {'use strict';
@@ -22,7 +22,11 @@
22
22
  */
23
23
  /* global -ngRouteModule */
24
24
  var ngRouteModule = angular.module('ngRoute', ['ng']).
25
- provider('$route', $RouteProvider),
25
+ provider('$route', $RouteProvider).
26
+ // Ensure `$route` will be instantiated in time to capture the initial
27
+ // `$locationChangeSuccess` event. This is necessary in case `ngView` is
28
+ // included in an asynchronously loaded template.
29
+ run(['$route', angular.noop]),
26
30
  $routeMinErr = angular.$$minErr('ngRoute');
27
31
 
28
32
  /**
@@ -105,8 +109,17 @@ function $RouteProvider() {
105
109
  * If all the promises are resolved successfully, the values of the resolved promises are
106
110
  * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
107
111
  * fired. If any of the promises are rejected the
108
- * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
109
- * is:
112
+ * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
113
+ * For easier access to the resolved dependencies from the template, the `resolve` map will
114
+ * be available on the scope of the route, under `$resolve` (by default) or a custom name
115
+ * specified by the `resolveAs` property (see below). This can be particularly useful, when
116
+ * working with {@link angular.Module#component components} as route templates.<br />
117
+ * <div class="alert alert-warning">
118
+ * **Note:** If your scope already contains a property with this name, it will be hidden
119
+ * or overwritten. Make sure, you specify an appropriate name for this property, that
120
+ * does not collide with other properties on the scope.
121
+ * </div>
122
+ * The map object is:
110
123
  *
111
124
  * - `key` – `{string}`: a name of a dependency to be injected into the controller.
112
125
  * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
@@ -116,7 +129,10 @@ function $RouteProvider() {
116
129
  * `ngRoute.$routeParams` will still refer to the previous route within these resolve
117
130
  * functions. Use `$route.current.params` to access the new route parameters, instead.
118
131
  *
119
- * - `redirectTo` {(string|function())=} value to update
132
+ * - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
133
+ * the scope of the route. If omitted, defaults to `$resolve`.
134
+ *
135
+ * - `redirectTo` – `{(string|function())=}` – value to update
120
136
  * {@link ng.$location $location} path with and trigger route redirection.
121
137
  *
122
138
  * If `redirectTo` is a function, it will be called with the following parameters:
@@ -129,13 +145,13 @@ function $RouteProvider() {
129
145
  * The custom `redirectTo` function is expected to return a string which will be used
130
146
  * to update `$location.path()` and `$location.search()`.
131
147
  *
132
- * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
148
+ * - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
133
149
  * or `$location.hash()` changes.
134
150
  *
135
151
  * If the option is set to `false` and url in the browser changes, then
136
152
  * `$routeUpdate` event is broadcasted on the root scope.
137
153
  *
138
- * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
154
+ * - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
139
155
  *
140
156
  * If the option is set to `true`, then the particular route can be matched without being
141
157
  * case sensitive
@@ -206,9 +222,9 @@ function $RouteProvider() {
206
222
 
207
223
  path = path
208
224
  .replace(/([().])/g, '\\$1')
209
- .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) {
210
- var optional = option === '?' ? option : null;
211
- var star = option === '*' ? option : null;
225
+ .replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g, function(_, slash, key, option) {
226
+ var optional = (option === '?' || option === '*?') ? '?' : null;
227
+ var star = (option === '*' || option === '*?') ? '*' : null;
212
228
  keys.push({ name: key, optional: !!optional });
213
229
  slash = slash || '';
214
230
  return ''
@@ -265,7 +281,7 @@ function $RouteProvider() {
265
281
  * @property {Object} current Reference to the current route definition.
266
282
  * The route definition contains:
267
283
  *
268
- * - `controller`: The controller constructor as define in route definition.
284
+ * - `controller`: The controller constructor as defined in the route definition.
269
285
  * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
270
286
  * controller instantiation. The `locals` contain
271
287
  * the resolved values of the `resolve` map. Additionally the `locals` also contain:
@@ -273,6 +289,10 @@ function $RouteProvider() {
273
289
  * - `$scope` - The current route scope.
274
290
  * - `$template` - The current route template HTML.
275
291
  *
292
+ * The `locals` will be assigned to the route scope's `$resolve` property. You can override
293
+ * the property name, using `resolveAs` in the route definition. See
294
+ * {@link ngRoute.$routeProvider $routeProvider} for more info.
295
+ *
276
296
  * @property {Object} routes Object with all route configuration Objects as its properties.
277
297
  *
278
298
  * @description
@@ -412,7 +432,9 @@ function $RouteProvider() {
412
432
  * @name $route#$routeChangeSuccess
413
433
  * @eventType broadcast on root scope
414
434
  * @description
415
- * Broadcasted after a route dependencies are resolved.
435
+ * Broadcasted after a route change has happened successfully.
436
+ * The `resolve` dependencies are now available in the `current.locals` property.
437
+ *
416
438
  * {@link ngRoute.directive:ngView ngView} listens for the directive
417
439
  * to instantiate the controller and render the view.
418
440
  *
@@ -466,10 +488,18 @@ function $RouteProvider() {
466
488
  */
467
489
  reload: function() {
468
490
  forceReload = true;
491
+
492
+ var fakeLocationEvent = {
493
+ defaultPrevented: false,
494
+ preventDefault: function fakePreventDefault() {
495
+ this.defaultPrevented = true;
496
+ forceReload = false;
497
+ }
498
+ };
499
+
469
500
  $rootScope.$evalAsync(function() {
470
- // Don't support cancellation of a reload for now...
471
- prepareRoute();
472
- commitRoute();
501
+ prepareRoute(fakeLocationEvent);
502
+ if (!fakeLocationEvent.defaultPrevented) commitRoute();
473
503
  });
474
504
  },
475
505
 
@@ -596,9 +626,8 @@ function $RouteProvider() {
596
626
  if (angular.isFunction(templateUrl)) {
597
627
  templateUrl = templateUrl(nextRoute.params);
598
628
  }
599
- templateUrl = $sce.getTrustedResourceUrl(templateUrl);
600
629
  if (angular.isDefined(templateUrl)) {
601
- nextRoute.loadedTemplateUrl = templateUrl;
630
+ nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl);
602
631
  template = $templateRequest(templateUrl);
603
632
  }
604
633
  }
@@ -724,8 +753,10 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
724
753
  * Requires the {@link ngRoute `ngRoute`} module to be installed.
725
754
  *
726
755
  * @animations
727
- * enter - animation is used to bring new content into the browser.
728
- * leave - animation is used to animate existing content away.
756
+ * | Animation | Occurs |
757
+ * |----------------------------------|-------------------------------------|
758
+ * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
759
+ * | {@link ng.$animate#leave leave} | when the old element is removed from to the DOM |
729
760
  *
730
761
  * The enter and leave animation occur concurrently.
731
762
  *
@@ -795,7 +826,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
795
826
  }
796
827
 
797
828
  .view-animate.ng-enter, .view-animate.ng-leave {
798
- -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
799
829
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
800
830
 
801
831
  display:block;
@@ -981,6 +1011,7 @@ function ngViewFillContentFactory($compile, $controller, $route) {
981
1011
  $element.data('$ngControllerController', controller);
982
1012
  $element.children().data('$ngControllerController', controller);
983
1013
  }
1014
+ scope[current.resolveAs || '$resolve'] = locals;
984
1015
 
985
1016
  link(scope);
986
1017
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
- * @license AngularJS v1.4.0
3
- * (c) 2010-2015 Google, Inc. http://angularjs.org
2
+ * @license AngularJS v1.5.3
3
+ * (c) 2010-2016 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
6
6
  (function(window, angular, undefined) {'use strict';
@@ -33,36 +33,23 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
33
33
  * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
34
34
  */
35
35
 
36
- /*
37
- * HTML Parser By Misko Hevery (misko@hevery.com)
38
- * based on: HTML Parser By John Resig (ejohn.org)
39
- * Original code by Erik Arvidsson, Mozilla Public License
40
- * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
41
- *
42
- * // Use like so:
43
- * htmlParser(htmlString, {
44
- * start: function(tag, attrs, unary) {},
45
- * end: function(tag) {},
46
- * chars: function(text) {},
47
- * comment: function(text) {}
48
- * });
49
- *
50
- */
51
-
52
-
53
36
  /**
54
37
  * @ngdoc service
55
38
  * @name $sanitize
56
39
  * @kind function
57
40
  *
58
41
  * @description
42
+ * Sanitizes an html string by stripping all potentially dangerous tokens.
43
+ *
59
44
  * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
60
45
  * then serialized back to properly escaped html string. This means that no unsafe input can make
61
- * it into the returned string, however, since our parser is more strict than a typical browser
62
- * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
63
- * browser, won't make it through the sanitizer. The input may also contain SVG markup.
64
- * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
65
- * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
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}.
66
53
  *
67
54
  * @param {string} html HTML input.
68
55
  * @returns {string} Sanitized HTML.
@@ -148,16 +135,70 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
148
135
  </file>
149
136
  </example>
150
137
  */
138
+
139
+
140
+ /**
141
+ * @ngdoc provider
142
+ * @name $sanitizeProvider
143
+ *
144
+ * @description
145
+ * Creates and configures {@link $sanitize} instance.
146
+ */
151
147
  function $SanitizeProvider() {
148
+ var svgEnabled = false;
149
+
152
150
  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
151
+ if (svgEnabled) {
152
+ angular.extend(validElements, svgElements);
153
+ }
153
154
  return function(html) {
154
155
  var buf = [];
155
156
  htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
156
- return !/^unsafe/.test($$sanitizeUri(uri, isImage));
157
+ return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
157
158
  }));
158
159
  return buf.join('');
159
160
  };
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
+ };
161
202
  }
162
203
 
163
204
  function sanitizeText(chars) {
@@ -169,18 +210,9 @@ function sanitizeText(chars) {
169
210
 
170
211
 
171
212
  // Regular Expressions for parsing tags and attributes
172
- var START_TAG_REGEXP =
173
- /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
174
- END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
175
- ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
176
- BEGIN_TAG_REGEXP = /^</,
177
- BEGING_END_TAGE_REGEXP = /^<\//,
178
- COMMENT_REGEXP = /<!--(.*?)-->/g,
179
- DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
180
- CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
181
- SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
213
+ var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
182
214
  // Match everything outside of normal chars and " (quote character)
183
- NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
215
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
184
216
 
185
217
 
186
218
  // Good source of info about elements and attributes
@@ -189,23 +221,23 @@ var START_TAG_REGEXP =
189
221
 
190
222
  // Safe Void Elements - HTML5
191
223
  // http://dev.w3.org/html5/spec/Overview.html#void-elements
192
- var voidElements = makeMap("area,br,col,hr,img,wbr");
224
+ var voidElements = toMap("area,br,col,hr,img,wbr");
193
225
 
194
226
  // Elements that you can, intentionally, leave open (and which close themselves)
195
227
  // http://dev.w3.org/html5/spec/Overview.html#optional-tags
196
- var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
197
- optionalEndTagInlineElements = makeMap("rp,rt"),
228
+ var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
229
+ optionalEndTagInlineElements = toMap("rp,rt"),
198
230
  optionalEndTagElements = angular.extend({},
199
231
  optionalEndTagInlineElements,
200
232
  optionalEndTagBlockElements);
201
233
 
202
234
  // Safe Block Elements - HTML5
203
- var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
235
+ var blockElements = angular.extend({}, optionalEndTagBlockElements, toMap("address,article," +
204
236
  "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
205
- "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
237
+ "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
206
238
 
207
239
  // Inline Elements - HTML5
208
- var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
240
+ var inlineElements = angular.extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
209
241
  "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
210
242
  "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
211
243
 
@@ -213,32 +245,31 @@ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a
213
245
  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
214
246
  // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
215
247
  // They can potentially allow for arbitrary javascript to be executed. See #11290
216
- var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
248
+ var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
217
249
  "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
218
- "radialGradient,rect,stop,svg,switch,text,title,tspan,use");
250
+ "radialGradient,rect,stop,svg,switch,text,title,tspan");
219
251
 
220
- // Special Elements (can contain anything)
221
- var specialElements = makeMap("script,style");
252
+ // Blocked Elements (will be stripped)
253
+ var blockedElements = toMap("script,style");
222
254
 
223
255
  var validElements = angular.extend({},
224
256
  voidElements,
225
257
  blockElements,
226
258
  inlineElements,
227
- optionalEndTagElements,
228
- svgElements);
259
+ optionalEndTagElements);
229
260
 
230
261
  //Attributes that have href and hence need to be sanitized
231
- var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
262
+ var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
232
263
 
233
- var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
264
+ var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
234
265
  'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
235
266
  'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
236
- 'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
267
+ 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
237
268
  'valign,value,vspace,width');
238
269
 
239
270
  // SVG attributes (without "id" and "name" attributes)
240
271
  // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
241
- var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
272
+ var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
242
273
  'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
243
274
  'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
244
275
  'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
@@ -259,7 +290,7 @@ var validAttrs = angular.extend({},
259
290
  svgAttrs,
260
291
  htmlAttrs);
261
292
 
262
- function makeMap(str, lowercaseKeys) {
293
+ function toMap(str, lowercaseKeys) {
263
294
  var obj = {}, items = str.split(','), i;
264
295
  for (i = 0; i < items.length; i++) {
265
296
  obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
@@ -267,11 +298,32 @@ function makeMap(str, lowercaseKeys) {
267
298
  return obj;
268
299
  }
269
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);
270
322
 
271
323
  /**
272
324
  * @example
273
325
  * htmlParser(htmlString, {
274
- * start: function(tag, attrs, unary) {},
326
+ * start: function(tag, attrs) {},
275
327
  * end: function(tag) {},
276
328
  * chars: function(text) {},
277
329
  * comment: function(text) {}
@@ -281,169 +333,74 @@ function makeMap(str, lowercaseKeys) {
281
333
  * @param {object} handler
282
334
  */
283
335
  function htmlParser(html, handler) {
284
- if (typeof html !== 'string') {
285
- if (html === null || typeof html === 'undefined') {
286
- html = '';
287
- } else {
288
- html = '' + html;
289
- }
336
+ if (html === null || html === undefined) {
337
+ html = '';
338
+ } else if (typeof html !== 'string') {
339
+ html = '' + html;
290
340
  }
291
- var index, chars, match, stack = [], last = html, text;
292
- stack.last = function() { return stack[stack.length - 1]; };
341
+ inertBodyElement.innerHTML = html;
293
342
 
294
- while (html) {
295
- text = '';
296
- chars = true;
297
-
298
- // Make sure we're not in a script or style element
299
- if (!stack.last() || !specialElements[stack.last()]) {
300
-
301
- // Comment
302
- if (html.indexOf("<!--") === 0) {
303
- // comments containing -- are not allowed unless they terminate the comment
304
- index = html.indexOf("--", 4);
305
-
306
- if (index >= 0 && html.lastIndexOf("-->", index) === index) {
307
- if (handler.comment) handler.comment(html.substring(4, index));
308
- html = html.substring(index + 3);
309
- chars = false;
310
- }
311
- // DOCTYPE
312
- } else if (DOCTYPE_REGEXP.test(html)) {
313
- match = html.match(DOCTYPE_REGEXP);
314
-
315
- if (match) {
316
- html = html.replace(match[0], '');
317
- chars = false;
318
- }
319
- // end tag
320
- } else if (BEGING_END_TAGE_REGEXP.test(html)) {
321
- match = html.match(END_TAG_REGEXP);
322
-
323
- if (match) {
324
- html = html.substring(match[0].length);
325
- match[0].replace(END_TAG_REGEXP, parseEndTag);
326
- chars = false;
327
- }
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--;
328
350
 
329
- // start tag
330
- } else if (BEGIN_TAG_REGEXP.test(html)) {
331
- match = html.match(START_TAG_REGEXP);
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
+ }
332
369
 
333
- if (match) {
334
- // We only have a valid start-tag if there is a '>'.
335
- if (match[4]) {
336
- html = html.substring(match[0].length);
337
- match[0].replace(START_TAG_REGEXP, parseStartTag);
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());
338
383
  }
339
- chars = false;
340
- } else {
341
- // no ending tag found --- this piece should be encoded as an entity.
342
- text += '<';
343
- html = html.substring(1);
344
384
  }
345
385
  }
346
-
347
- if (chars) {
348
- index = html.indexOf("<");
349
-
350
- text += index < 0 ? html : html.substring(0, index);
351
- html = index < 0 ? "" : html.substring(index);
352
-
353
- if (handler.chars) handler.chars(decodeEntities(text));
354
- }
355
-
356
- } else {
357
- // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
358
- html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
359
- function(all, text) {
360
- text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
361
-
362
- if (handler.chars) handler.chars(decodeEntities(text));
363
-
364
- return "";
365
- });
366
-
367
- parseEndTag("", stack.last());
368
- }
369
-
370
- if (html == last) {
371
- throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
372
- "of html: {0}", html);
373
386
  }
374
- last = html;
387
+ node = nextNode;
375
388
  }
376
389
 
377
- // Clean up any remaining tags
378
- parseEndTag();
379
-
380
- function parseStartTag(tag, tagName, rest, unary) {
381
- tagName = angular.lowercase(tagName);
382
- if (blockElements[tagName]) {
383
- while (stack.last() && inlineElements[stack.last()]) {
384
- parseEndTag("", stack.last());
385
- }
386
- }
387
-
388
- if (optionalEndTagElements[tagName] && stack.last() == tagName) {
389
- parseEndTag("", tagName);
390
- }
391
-
392
- unary = voidElements[tagName] || !!unary;
393
-
394
- if (!unary) {
395
- stack.push(tagName);
396
- }
397
-
398
- var attrs = {};
399
-
400
- rest.replace(ATTR_REGEXP,
401
- function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
402
- var value = doubleQuotedValue
403
- || singleQuotedValue
404
- || unquotedValue
405
- || '';
406
-
407
- attrs[name] = decodeEntities(value);
408
- });
409
- if (handler.start) handler.start(tagName, attrs, unary);
390
+ while (node = inertBodyElement.firstChild) {
391
+ inertBodyElement.removeChild(node);
410
392
  }
393
+ }
411
394
 
412
- function parseEndTag(tag, tagName) {
413
- var pos = 0, i;
414
- tagName = angular.lowercase(tagName);
415
- if (tagName) {
416
- // Find the closest opened tag of the same type
417
- for (pos = stack.length - 1; pos >= 0; pos--) {
418
- if (stack[pos] == tagName) break;
419
- }
420
- }
421
-
422
- if (pos >= 0) {
423
- // Close all the open elements, up the stack
424
- for (i = stack.length - 1; i >= pos; i--)
425
- if (handler.end) handler.end(stack[i]);
426
-
427
- // Remove the open elements from the stack
428
- stack.length = pos;
429
- }
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;
430
400
  }
401
+ return map;
431
402
  }
432
403
 
433
- var hiddenPre=document.createElement("pre");
434
- /**
435
- * decodes all entities into regular string
436
- * @param value
437
- * @returns {string} A string with decoded entities.
438
- */
439
- function decodeEntities(value) {
440
- if (!value) { return ''; }
441
-
442
- hiddenPre.innerHTML = value.replace(/</g,"&lt;");
443
- // innerText depends on styling as it doesn't display hidden elements.
444
- // Therefore, it's better to use textContent not to cause unnecessary reflows.
445
- return hiddenPre.textContent;
446
- }
447
404
 
448
405
  /**
449
406
  * Escapes all potentially dangerous characters, so that the
@@ -469,24 +426,24 @@ function encodeEntities(value) {
469
426
 
470
427
  /**
471
428
  * create an HTML/XML writer which writes to buffer
472
- * @param {Array} buf use buf.jain('') to get out sanitized html string
429
+ * @param {Array} buf use buf.join('') to get out sanitized html string
473
430
  * @returns {object} in the form of {
474
- * start: function(tag, attrs, unary) {},
431
+ * start: function(tag, attrs) {},
475
432
  * end: function(tag) {},
476
433
  * chars: function(text) {},
477
434
  * comment: function(text) {}
478
435
  * }
479
436
  */
480
437
  function htmlSanitizeWriter(buf, uriValidator) {
481
- var ignore = false;
438
+ var ignoreCurrentElement = false;
482
439
  var out = angular.bind(buf, buf.push);
483
440
  return {
484
- start: function(tag, attrs, unary) {
441
+ start: function(tag, attrs) {
485
442
  tag = angular.lowercase(tag);
486
- if (!ignore && specialElements[tag]) {
487
- ignore = tag;
443
+ if (!ignoreCurrentElement && blockedElements[tag]) {
444
+ ignoreCurrentElement = tag;
488
445
  }
489
- if (!ignore && validElements[tag] === true) {
446
+ if (!ignoreCurrentElement && validElements[tag] === true) {
490
447
  out('<');
491
448
  out(tag);
492
449
  angular.forEach(attrs, function(value, key) {
@@ -501,29 +458,63 @@ function htmlSanitizeWriter(buf, uriValidator) {
501
458
  out('"');
502
459
  }
503
460
  });
504
- out(unary ? '/>' : '>');
461
+ out('>');
505
462
  }
506
463
  },
507
464
  end: function(tag) {
508
- tag = angular.lowercase(tag);
509
- if (!ignore && validElements[tag] === true) {
510
- out('</');
511
- out(tag);
512
- out('>');
513
- }
514
- if (tag == ignore) {
515
- ignore = false;
516
- }
517
- },
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
+ },
518
475
  chars: function(chars) {
519
- if (!ignore) {
520
- out(encodeEntities(chars));
521
- }
476
+ if (!ignoreCurrentElement) {
477
+ out(encodeEntities(chars));
522
478
  }
479
+ }
523
480
  };
524
481
  }
525
482
 
526
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
+
527
518
  // define ngSanitize module and register $sanitize service
528
519
  angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
529
520
 
@@ -535,14 +526,25 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
535
526
  * @kind function
536
527
  *
537
528
  * @description
538
- * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
529
+ * Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
539
530
  * plain email address links.
540
531
  *
541
532
  * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
542
533
  *
543
534
  * @param {string} text Input text.
544
- * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
545
- * @returns {string} Html-linkified 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.
546
548
  *
547
549
  * @usage
548
550
  <span ng-bind-html="linky_expression | linky"></span>
@@ -550,25 +552,13 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
550
552
  * @example
551
553
  <example module="linkyExample" deps="angular-sanitize.js">
552
554
  <file name="index.html">
553
- <script>
554
- angular.module('linkyExample', ['ngSanitize'])
555
- .controller('ExampleController', ['$scope', function($scope) {
556
- $scope.snippet =
557
- 'Pretty text with some links:\n'+
558
- 'http://angularjs.org/,\n'+
559
- 'mailto:us@somewhere.org,\n'+
560
- 'another@somewhere.org,\n'+
561
- 'and one more: ftp://127.0.0.1/.';
562
- $scope.snippetWithTarget = 'http://angularjs.org/';
563
- }]);
564
- </script>
565
555
  <div ng-controller="ExampleController">
566
556
  Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
567
557
  <table>
568
558
  <tr>
569
- <td>Filter</td>
570
- <td>Source</td>
571
- <td>Rendered</td>
559
+ <th>Filter</th>
560
+ <th>Source</th>
561
+ <th>Rendered</th>
572
562
  </tr>
573
563
  <tr id="linky-filter">
574
564
  <td>linky filter</td>
@@ -582,10 +572,19 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
582
572
  <tr id="linky-target">
583
573
  <td>linky target</td>
584
574
  <td>
585
- <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
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>
586
585
  </td>
587
586
  <td>
588
- <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
587
+ <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
589
588
  </td>
590
589
  </tr>
591
590
  <tr id="escaped-html">
@@ -595,6 +594,18 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
595
594
  </tr>
596
595
  </table>
597
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>
598
609
  <file name="protractor.js" type="protractor">
599
610
  it('should linkify the snippet with urls', function() {
600
611
  expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
@@ -622,20 +633,32 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
622
633
 
623
634
  it('should work with the target property', function() {
624
635
  expect(element(by.id('linky-target')).
625
- element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
636
+ element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
626
637
  toBe('http://angularjs.org/');
627
638
  expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
628
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
+ });
629
647
  </file>
630
648
  </example>
631
649
  */
632
650
  angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
633
651
  var LINKY_URL_REGEXP =
634
- /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
635
- MAILTO_REGEXP = /^mailto:/;
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);
636
661
 
637
- return function(text, target) {
638
- if (!text) return text;
639
662
  var match;
640
663
  var raw = text;
641
664
  var html = [];
@@ -664,8 +687,19 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
664
687
  }
665
688
 
666
689
  function addLink(url, text) {
690
+ var key;
667
691
  html.push('<a ');
668
- if (angular.isDefined(target)) {
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)) {
669
703
  html.push('target="',
670
704
  target,
671
705
  '" ');