Skip to content

Commit 0d08754

Browse files
Merge pull request #73 from angular/master
Update upstream
2 parents bfedfaa + 059d4f6 commit 0d08754

12 files changed

+720
-144
lines changed

src/.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
"urlResolve": false,
162162
"urlIsSameOrigin": false,
163163
"urlIsSameOriginAsBaseUrl": false,
164+
"urlIsAllowedOriginFactory": false,
164165

165166
/* ng/controller.js */
166167
"identifierForController": false,

src/Angular.js

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,7 @@ function convertTimezoneToLocal(date, timezone, reverse) {
13691369
*/
13701370
function startingTag(element) {
13711371
element = jqLite(element).clone().empty();
1372-
var elemHtml = jqLite('<div>').append(element).html();
1372+
var elemHtml = jqLite('<div></div>').append(element).html();
13731373
try {
13741374
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
13751375
elemHtml.
@@ -1924,25 +1924,25 @@ function bindJQuery() {
19241924
injector: JQLitePrototype.injector,
19251925
inheritedData: JQLitePrototype.inheritedData
19261926
});
1927-
1928-
// All nodes removed from the DOM via various jQuery APIs like .remove()
1929-
// are passed through jQuery.cleanData. Monkey-patch this method to fire
1930-
// the $destroy event on all removed nodes.
1931-
originalCleanData = jQuery.cleanData;
1932-
jQuery.cleanData = function(elems) {
1933-
var events;
1934-
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1935-
events = jQuery._data(elem, 'events');
1936-
if (events && events.$destroy) {
1937-
jQuery(elem).triggerHandler('$destroy');
1938-
}
1939-
}
1940-
originalCleanData(elems);
1941-
};
19421927
} else {
19431928
jqLite = JQLite;
19441929
}
19451930

1931+
// All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
1932+
// are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
1933+
// the $destroy event on all removed nodes.
1934+
originalCleanData = jqLite.cleanData;
1935+
jqLite.cleanData = function(elems) {
1936+
var events;
1937+
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
1938+
events = jqLite._data(elem).events;
1939+
if (events && events.$destroy) {
1940+
jqLite(elem).triggerHandler('$destroy');
1941+
}
1942+
}
1943+
originalCleanData(elems);
1944+
};
1945+
19461946
angular.element = jqLite;
19471947

19481948
// Prevent double-proxying.

src/jqLite.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
*
5555
* - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
5656
* - [`after()`](http://api.jquery.com/after/)
57-
* - [`append()`](http://api.jquery.com/append/)
57+
* - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements
58+
* so will not work correctly when invoked on a jqLite object containing more than one DOM node
5859
* - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
5960
* - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
6061
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
@@ -310,6 +311,28 @@ function jqLiteDealoc(element, onlyDescendants) {
310311
}
311312
}
312313

314+
function isEmptyObject(obj) {
315+
var name;
316+
317+
for (name in obj) {
318+
return false;
319+
}
320+
return true;
321+
}
322+
323+
function removeIfEmptyData(element) {
324+
var expandoId = element.ng339;
325+
var expandoStore = expandoId && jqCache[expandoId];
326+
327+
var events = expandoStore && expandoStore.events;
328+
var data = expandoStore && expandoStore.data;
329+
330+
if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
331+
delete jqCache[expandoId];
332+
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
333+
}
334+
}
335+
313336
function jqLiteOff(element, type, fn, unsupported) {
314337
if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
315338

@@ -346,6 +369,8 @@ function jqLiteOff(element, type, fn, unsupported) {
346369
}
347370
});
348371
}
372+
373+
removeIfEmptyData(element);
349374
}
350375

351376
function jqLiteRemoveData(element, name) {
@@ -355,17 +380,11 @@ function jqLiteRemoveData(element, name) {
355380
if (expandoStore) {
356381
if (name) {
357382
delete expandoStore.data[name];
358-
return;
383+
} else {
384+
expandoStore.data = {};
359385
}
360386

361-
if (expandoStore.handle) {
362-
if (expandoStore.events.$destroy) {
363-
expandoStore.handle({}, '$destroy');
364-
}
365-
jqLiteOff(element);
366-
}
367-
delete jqCache[expandoId];
368-
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
387+
removeIfEmptyData(element);
369388
}
370389
}
371390

@@ -615,6 +634,7 @@ forEach({
615634
cleanData: function jqLiteCleanData(nodes) {
616635
for (var i = 0, ii = nodes.length; i < ii; i++) {
617636
jqLiteRemoveData(nodes[i]);
637+
jqLiteOff(nodes[i]);
618638
}
619639
}
620640
}, function(fn, name) {

src/ng/compile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1941,7 +1941,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
19411941
// for call to the link function.
19421942
// Note: This will already clone the nodes...
19431943
$linkNode = jqLite(
1944-
wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
1944+
wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html())
19451945
);
19461946
} else if (cloneConnectFn) {
19471947
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart

src/ng/http.js

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ function $HttpParamSerializerProvider() {
3434
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
3535
*
3636
* Note that serializer will sort the request parameters alphabetically.
37-
* */
37+
*/
3838

3939
this.$get = function() {
4040
return function ngParamSerializer(params) {
@@ -101,7 +101,7 @@ function $HttpParamSerializerJQLikeProvider() {
101101
* });
102102
* ```
103103
*
104-
* */
104+
*/
105105
this.$get = function() {
106106
return function jQueryLikeParamSerializer(params) {
107107
if (!params) return '';
@@ -261,7 +261,7 @@ function isSuccess(status) {
261261
*
262262
* @description
263263
* Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
264-
* */
264+
*/
265265
function $HttpProvider() {
266266
/**
267267
* @ngdoc property
@@ -315,7 +315,7 @@ function $HttpProvider() {
315315
* - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
316316
* XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
317317
*
318-
**/
318+
*/
319319
var defaults = this.defaults = {
320320
// transform incoming response data
321321
transformResponse: [defaultHttpResponseTransform],
@@ -362,7 +362,7 @@ function $HttpProvider() {
362362
*
363363
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
364364
* otherwise, returns the current configured value.
365-
**/
365+
*/
366366
this.useApplyAsync = function(value) {
367367
if (isDefined(value)) {
368368
useApplyAsync = !!value;
@@ -383,9 +383,51 @@ function $HttpProvider() {
383383
* array, on request, but reverse order, on response.
384384
*
385385
* {@link ng.$http#interceptors Interceptors detailed info}
386-
**/
386+
*/
387387
var interceptorFactories = this.interceptors = [];
388388

389+
/**
390+
* @ngdoc property
391+
* @name $httpProvider#xsrfWhitelistedOrigins
392+
* @description
393+
*
394+
* Array containing URLs whose origins are trusted to receive the XSRF token. See the
395+
* {@link ng.$http#security-considerations Security Considerations} sections for more details on
396+
* XSRF.
397+
*
398+
* **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme),
399+
* the [hostname](https://en.wikipedia.org/wiki/Hostname) and the
400+
* [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and
401+
* `https:`, the port number can be omitted if using th default ports (80 and 443 respectively).
402+
* Examples: `http://example.com`, `https://api.example.com:9876`
403+
*
404+
* <div class="alert alert-warning">
405+
* It is not possible to whitelist specific URLs/paths. The `path`, `query` and `fragment` parts
406+
* of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be
407+
* treated as `https://foo.com`, meaning that **all** requests to URLs starting with
408+
* `https://foo.com/` will include the XSRF token.
409+
* </div>
410+
*
411+
* @example
412+
*
413+
* ```js
414+
* // App served from `https://example.com/`.
415+
* angular.
416+
* module('xsrfWhitelistedOriginsExample', []).
417+
* config(['$httpProvider', function($httpProvider) {
418+
* $httpProvider.xsrfWhitelistedOrigins.push('https://api.example.com');
419+
* }]).
420+
* run(['$http', function($http) {
421+
* // The XSRF token will be sent.
422+
* $http.get('https://api.example.com/preferences').then(...);
423+
*
424+
* // The XSRF token will NOT be sent.
425+
* $http.get('https://stats.example.com/activity').then(...);
426+
* }]);
427+
* ```
428+
*/
429+
var xsrfWhitelistedOrigins = this.xsrfWhitelistedOrigins = [];
430+
389431
this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
390432
function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
391433

@@ -409,6 +451,11 @@ function $HttpProvider() {
409451
? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
410452
});
411453

454+
/**
455+
* A function to check request URLs against a list of allowed origins.
456+
*/
457+
var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfWhitelistedOrigins);
458+
412459
/**
413460
* @ngdoc service
414461
* @kind function
@@ -765,25 +812,42 @@ function $HttpProvider() {
765812
* which the attacker can trick an authenticated user into unknowingly executing actions on your
766813
* website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the
767814
* $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
768-
* header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
769-
* cookie, your server can be assured that the XHR came from JavaScript running on your domain.
770-
* The header will not be set for cross-domain requests.
815+
* header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read
816+
* the cookie, your server can be assured that the XHR came from JavaScript running on your
817+
* domain.
771818
*
772819
* To take advantage of this, your server needs to set a token in a JavaScript readable session
773820
* cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
774-
* server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
775-
* that only JavaScript running on your domain could have sent the request. The token must be
776-
* unique for each user and must be verifiable by the server (to prevent the JavaScript from
821+
* server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be
822+
* sure that only JavaScript running on your domain could have sent the request. The token must
823+
* be unique for each user and must be verifiable by the server (to prevent the JavaScript from
777824
* making up its own tokens). We recommend that the token is a digest of your site's
778825
* authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
779826
* for added security.
780827
*
781-
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
782-
* properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
783-
* or the per-request config object.
828+
* The header will &mdash; by default &mdash; **not** be set for cross-domain requests. This
829+
* prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining
830+
* access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you
831+
* want to, you can whitelist additional origins to also receive the XSRF token, by adding them
832+
* to {@link ng.$httpProvider#xsrfWhitelistedOrigins xsrfWhitelistedOrigins}. This might be
833+
* useful, for example, if your application, served from `example.com`, needs to access your API
834+
* at `api.example.com`.
835+
* See {@link ng.$httpProvider#xsrfWhitelistedOrigins $httpProvider.xsrfWhitelistedOrigins} for
836+
* more details.
837+
*
838+
* <div class="alert alert-danger">
839+
* **Warning**<br />
840+
* Only whitelist origins that you have control over and make sure you understand the
841+
* implications of doing so.
842+
* </div>
843+
*
844+
* The name of the cookie and the header can be specified using the `xsrfCookieName` and
845+
* `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time,
846+
* `$http.defaults` at run-time, or the per-request config object.
784847
*
785848
* In order to prevent collisions in environments where multiple AngularJS apps share the
786-
* same domain or subdomain, we recommend that each application uses unique cookie name.
849+
* same domain or subdomain, we recommend that each application uses a unique cookie name.
850+
*
787851
*
788852
* @param {object} config Object describing the request to be made and how it should be
789853
* processed. The object has following properties:
@@ -1343,7 +1407,7 @@ function $HttpProvider() {
13431407
// if we won't have the response in cache, set the xsrf headers and
13441408
// send the request to the backend
13451409
if (isUndefined(cachedResp)) {
1346-
var xsrfValue = urlIsSameOrigin(config.url)
1410+
var xsrfValue = urlIsAllowedOrigin(config.url)
13471411
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
13481412
: undefined;
13491413
if (xsrfValue) {

0 commit comments

Comments
 (0)