Skip to content

Update upstream #73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
"urlResolve": false,
"urlIsSameOrigin": false,
"urlIsSameOriginAsBaseUrl": false,
"urlIsAllowedOriginFactory": false,

/* ng/controller.js */
"identifierForController": false,
Expand Down
32 changes: 16 additions & 16 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -1369,7 +1369,7 @@ function convertTimezoneToLocal(date, timezone, reverse) {
*/
function startingTag(element) {
element = jqLite(element).clone().empty();
var elemHtml = jqLite('<div>').append(element).html();
var elemHtml = jqLite('<div></div>').append(element).html();
try {
return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) :
elemHtml.
Expand Down Expand Up @@ -1924,25 +1924,25 @@ function bindJQuery() {
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});

// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery.cleanData;
jQuery.cleanData = function(elems) {
var events;
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jQuery._data(elem, 'events');
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
originalCleanData(elems);
};
} else {
jqLite = JQLite;
}

// All nodes removed from the DOM via various jqLite/jQuery APIs like .remove()
// are passed through jqLite/jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jqLite.cleanData;
jqLite.cleanData = function(elems) {
var events;
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jqLite._data(elem).events;
if (events && events.$destroy) {
jqLite(elem).triggerHandler('$destroy');
}
}
originalCleanData(elems);
};

angular.element = jqLite;

// Prevent double-proxying.
Expand Down
40 changes: 30 additions & 10 deletions src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@
*
* - [`addClass()`](http://api.jquery.com/addClass/) - Does not support a function as first argument
* - [`after()`](http://api.jquery.com/after/)
* - [`append()`](http://api.jquery.com/append/)
* - [`append()`](http://api.jquery.com/append/) - Contrary to jQuery, this doesn't clone elements
* so will not work correctly when invoked on a jqLite object containing more than one DOM node
* - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters
* - [`bind()`](http://api.jquery.com/bind/) (_deprecated_, use [`on()`](http://api.jquery.com/on/)) - Does not support namespaces, selectors or eventData
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
Expand Down Expand Up @@ -310,6 +311,28 @@ function jqLiteDealoc(element, onlyDescendants) {
}
}

function isEmptyObject(obj) {
var name;

for (name in obj) {
return false;
}
return true;
}

function removeIfEmptyData(element) {
var expandoId = element.ng339;
var expandoStore = expandoId && jqCache[expandoId];

var events = expandoStore && expandoStore.events;
var data = expandoStore && expandoStore.data;

if ((!data || isEmptyObject(data)) && (!events || isEmptyObject(events))) {
delete jqCache[expandoId];
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
}
}

function jqLiteOff(element, type, fn, unsupported) {
if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');

Expand Down Expand Up @@ -346,6 +369,8 @@ function jqLiteOff(element, type, fn, unsupported) {
}
});
}

removeIfEmptyData(element);
}

function jqLiteRemoveData(element, name) {
Expand All @@ -355,17 +380,11 @@ function jqLiteRemoveData(element, name) {
if (expandoStore) {
if (name) {
delete expandoStore.data[name];
return;
} else {
expandoStore.data = {};
}

if (expandoStore.handle) {
if (expandoStore.events.$destroy) {
expandoStore.handle({}, '$destroy');
}
jqLiteOff(element);
}
delete jqCache[expandoId];
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
removeIfEmptyData(element);
}
}

Expand Down Expand Up @@ -615,6 +634,7 @@ forEach({
cleanData: function jqLiteCleanData(nodes) {
for (var i = 0, ii = nodes.length; i < ii; i++) {
jqLiteRemoveData(nodes[i]);
jqLiteOff(nodes[i]);
}
}
}, function(fn, name) {
Expand Down
2 changes: 1 addition & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// for call to the link function.
// Note: This will already clone the nodes...
$linkNode = jqLite(
wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
wrapTemplate(namespace, jqLite('<div></div>').append($compileNodes).html())
);
} else if (cloneConnectFn) {
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
Expand Down
98 changes: 81 additions & 17 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function $HttpParamSerializerProvider() {
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
*
* Note that serializer will sort the request parameters alphabetically.
* */
*/

this.$get = function() {
return function ngParamSerializer(params) {
Expand Down Expand Up @@ -101,7 +101,7 @@ function $HttpParamSerializerJQLikeProvider() {
* });
* ```
*
* */
*/
this.$get = function() {
return function jQueryLikeParamSerializer(params) {
if (!params) return '';
Expand Down Expand Up @@ -261,7 +261,7 @@ function isSuccess(status) {
*
* @description
* Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
* */
*/
function $HttpProvider() {
/**
* @ngdoc property
Expand Down Expand Up @@ -315,7 +315,7 @@ function $HttpProvider() {
* - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
* XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
*
**/
*/
var defaults = this.defaults = {
// transform incoming response data
transformResponse: [defaultHttpResponseTransform],
Expand Down Expand Up @@ -362,7 +362,7 @@ function $HttpProvider() {
*
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
* otherwise, returns the current configured value.
**/
*/
this.useApplyAsync = function(value) {
if (isDefined(value)) {
useApplyAsync = !!value;
Expand All @@ -383,9 +383,51 @@ function $HttpProvider() {
* array, on request, but reverse order, on response.
*
* {@link ng.$http#interceptors Interceptors detailed info}
**/
*/
var interceptorFactories = this.interceptors = [];

/**
* @ngdoc property
* @name $httpProvider#xsrfWhitelistedOrigins
* @description
*
* Array containing URLs whose origins are trusted to receive the XSRF token. See the
* {@link ng.$http#security-considerations Security Considerations} sections for more details on
* XSRF.
*
* **Note:** An "origin" consists of the [URI scheme](https://en.wikipedia.org/wiki/URI_scheme),
* the [hostname](https://en.wikipedia.org/wiki/Hostname) and the
* [port number](https://en.wikipedia.org/wiki/Port_(computer_networking). For `http:` and
* `https:`, the port number can be omitted if using th default ports (80 and 443 respectively).
* Examples: `http://example.com`, `https://api.example.com:9876`
*
* <div class="alert alert-warning">
* It is not possible to whitelist specific URLs/paths. The `path`, `query` and `fragment` parts
* of a URL will be ignored. For example, `https://foo.com/path/bar?query=baz#fragment` will be
* treated as `https://foo.com`, meaning that **all** requests to URLs starting with
* `https://foo.com/` will include the XSRF token.
* </div>
*
* @example
*
* ```js
* // App served from `https://example.com/`.
* angular.
* module('xsrfWhitelistedOriginsExample', []).
* config(['$httpProvider', function($httpProvider) {
* $httpProvider.xsrfWhitelistedOrigins.push('https://api.example.com');
* }]).
* run(['$http', function($http) {
* // The XSRF token will be sent.
* $http.get('https://api.example.com/preferences').then(...);
*
* // The XSRF token will NOT be sent.
* $http.get('https://stats.example.com/activity').then(...);
* }]);
* ```
*/
var xsrfWhitelistedOrigins = this.xsrfWhitelistedOrigins = [];

this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {

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

/**
* A function to check request URLs against a list of allowed origins.
*/
var urlIsAllowedOrigin = urlIsAllowedOriginFactory(xsrfWhitelistedOrigins);

/**
* @ngdoc service
* @kind function
Expand Down Expand Up @@ -765,25 +812,42 @@ function $HttpProvider() {
* which the attacker can trick an authenticated user into unknowingly executing actions on your
* website. AngularJS provides a mechanism to counter XSRF. When performing XHR requests, the
* $http service reads a token from a cookie (by default, `XSRF-TOKEN`) and sets it as an HTTP
* header (`X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read the
* cookie, your server can be assured that the XHR came from JavaScript running on your domain.
* The header will not be set for cross-domain requests.
* header (by default `X-XSRF-TOKEN`). Since only JavaScript that runs on your domain could read
* the cookie, your server can be assured that the XHR came from JavaScript running on your
* domain.
*
* To take advantage of this, your server needs to set a token in a JavaScript readable session
* cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the
* server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
* that only JavaScript running on your domain could have sent the request. The token must be
* unique for each user and must be verifiable by the server (to prevent the JavaScript from
* server can verify that the cookie matches the `X-XSRF-TOKEN` HTTP header, and therefore be
* sure that only JavaScript running on your domain could have sent the request. The token must
* be unique for each user and must be verifiable by the server (to prevent the JavaScript from
* making up its own tokens). We recommend that the token is a digest of your site's
* authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
* for added security.
*
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
* properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
* or the per-request config object.
* The header will &mdash; by default &mdash; **not** be set for cross-domain requests. This
* prevents unauthorized servers (e.g. malicious or compromised 3rd-party APIs) from gaining
* access to your users' XSRF tokens and exposing them to Cross Site Request Forgery. If you
* want to, you can whitelist additional origins to also receive the XSRF token, by adding them
* to {@link ng.$httpProvider#xsrfWhitelistedOrigins xsrfWhitelistedOrigins}. This might be
* useful, for example, if your application, served from `example.com`, needs to access your API
* at `api.example.com`.
* See {@link ng.$httpProvider#xsrfWhitelistedOrigins $httpProvider.xsrfWhitelistedOrigins} for
* more details.
*
* <div class="alert alert-danger">
* **Warning**<br />
* Only whitelist origins that you have control over and make sure you understand the
* implications of doing so.
* </div>
*
* The name of the cookie and the header can be specified using the `xsrfCookieName` and
* `xsrfHeaderName` properties of either `$httpProvider.defaults` at config-time,
* `$http.defaults` at run-time, or the per-request config object.
*
* In order to prevent collisions in environments where multiple AngularJS apps share the
* same domain or subdomain, we recommend that each application uses unique cookie name.
* same domain or subdomain, we recommend that each application uses a unique cookie name.
*
*
* @param {object} config Object describing the request to be made and how it should be
* processed. The object has following properties:
Expand Down Expand Up @@ -1343,7 +1407,7 @@ function $HttpProvider() {
// if we won't have the response in cache, set the xsrf headers and
// send the request to the backend
if (isUndefined(cachedResp)) {
var xsrfValue = urlIsSameOrigin(config.url)
var xsrfValue = urlIsAllowedOrigin(config.url)
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
Expand Down
Loading