Skip to content

Commit a4ed23c

Browse files
committed
feat(ngCookies): support passing path/expires attributes to cookies
$cookieStore service had no way to set cookie attributes (such as expires). The api for $cookieStore.put and $cookieStore.remove now has a third argument- options,which allows setting cookie attributes, or deleting cookies under a specific path. - to change the path and/or expiration date of a cookie, you can pass an options object as the third argument to cookies(name,value). the options object can override Path and Expires when adding a value. You can pass either path, expires,none or both. - the path option is checked to be a string and be partial to window.location otherwise, the default path is used. (warning is logged) - the expires option is checked to be a Date and in the future, otherwise no expiration is set. (warning is logged) - you can now delete a cookie on a specific path by calling cookie(name,undefined,{path:x}) The $cookie service remains unchanged since supporting cookie attributes would mean serious backward comptability issues. Foundations were put in place to make supporting $cookie easier. GENERIC TEST CHANGE: To support checking cookie path, all unit-tests now run on path "/karma/tests" Documentation was updated for both $cookie and $cookieStore with examples. closes angular#1786, angular#1320 BREAKING CHANGE: As part of the change, deleting a cookie now deletes cookies set in multiple paths (and duplicate cookies if exists). Previously only cookies set in the / path were deleted. Since it is not intutive, if this change breaks someones code, it is probably as an accidental side-effect. It is reasonable to assume that most people actually wanted to delete the cookie even if it wasn't set in the same path (since they can see it). So while this is a breaking change, it fixes bad behaviour. If needed, you can delete the cookie in a specific path using $cookieStore.
1 parent 695c54c commit a4ed23c

File tree

9 files changed

+1028
-676
lines changed

9 files changed

+1028
-676
lines changed

Gruntfile.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ module.exports = function(grunt) {
137137
resource: 'build/angular-resource.js',
138138
sanitize: 'build/angular-sanitize.js',
139139
bootstrap: 'build/angular-bootstrap.js',
140-
bootstrapPrettify: 'build/angular-bootstrap-prettify.js',
140+
bootstrapPrettify: 'build/angular-bootstrap-prettify.js'
141141
},
142142

143143

karma-jqlite.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ autoWatch = true;
77
logLevel = LOG_INFO;
88
logColors = true;
99
browsers = ['Chrome'];
10+
urlRoot = "/karma/tests/"
1011

1112
junitReporter = {
1213
outputFile: 'test_out/jqlite.xml',

karma-jquery.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ autoWatch = true;
77
logLevel = LOG_INFO;
88
logColors = true;
99
browsers = ['Chrome'];
10+
urlRoot = "/karma/tests/"
1011

1112
junitReporter = {
1213
outputFile: 'test_out/jquery.xml',

karma-modules.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ autoWatch = true;
77
logLevel = LOG_INFO;
88
logColors = true;
99
browsers = ['Chrome'];
10+
urlRoot = "/karma/tests/"
1011

1112
junitReporter = {
1213
outputFile: 'test_out/modules.xml',

src/ng/browser.js

+109-41
Original file line numberDiff line numberDiff line change
@@ -245,15 +245,19 @@ function Browser(window, document, $log, $sniffer) {
245245
//////////////////////////////////////////////////////////////
246246
var lastCookies = {};
247247
var lastCookieString = '';
248-
var cookiePath = self.baseHref();
249248

250249
/**
251250
* @name ng.$browser#cookies
252251
* @methodOf ng.$browser
253252
*
254253
* @param {string=} name Cookie name
255254
* @param {string=} value Cookie value
256-
*
255+
* @param {object} options Object allowing additional control on how a cookie is created
256+
* - **expires** - `{date}` - date for cookie to expire.
257+
* If not passed, the object is not a date or the date is in the past, the cookie expiration
258+
* date will not be set, so that the cookie will expire at the end of the session.
259+
* - **path** - `{string}` - the path to set the cookie on.
260+
* Defaults to {@link #baseHref baseHref}
257261
* @description
258262
* The cookies method provides a 'private' low level access to browser cookies.
259263
* It is not meant to be used directly, use the $cookie service instead.
@@ -262,49 +266,113 @@ function Browser(window, document, $log, $sniffer) {
262266
* <ul>
263267
* <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li>
264268
* <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li>
269+
* <li>cookies(name, value,options) -> same as (name, value), but allows more granular control of the cookie</li>
270+
* <li>cookies(name,undefined,options) -> deletes a cookie. allows passing path in options to delete only
271+
* cookie under that path</li>
265272
* <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li>
266273
* </ul>
267274
*
268275
* @returns {Object} Hash of all cookies (if called without any parameter)
269-
*/
270-
self.cookies = function(name, value) {
271-
var cookieLength, cookieArray, cookie, i, index;
272-
273-
if (name) {
274-
if (value === undefined) {
275-
rawDocument.cookie = escape(name) + "=;path=" + cookiePath + ";expires=Thu, 01 Jan 1970 00:00:00 GMT";
276-
} else {
277-
if (isString(value)) {
278-
cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + ';path=' + cookiePath).length + 1;
279-
280-
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
281-
// - 300 cookies
282-
// - 20 cookies per unique domain
283-
// - 4096 bytes per cookie
284-
if (cookieLength > 4096) {
285-
$log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
286-
cookieLength + " > 4096 bytes)!");
287-
}
288-
}
289-
}
290-
} else {
291-
if (rawDocument.cookie !== lastCookieString) {
292-
lastCookieString = rawDocument.cookie;
293-
cookieArray = lastCookieString.split("; ");
294-
lastCookies = {};
295-
296-
for (i = 0; i < cookieArray.length; i++) {
297-
cookie = cookieArray[i];
298-
index = cookie.indexOf('=');
299-
if (index > 0) { //ignore nameless cookies
300-
lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
301-
}
302-
}
303-
}
304-
return lastCookies;
305-
}
306-
};
307-
276+
*/
277+
self.cookies = (function () {
278+
var cookies = function (name, value, options) {
279+
if (!angular.isDefined(options) || options == null) options = {};
280+
if (name) {
281+
if (value === undefined) {
282+
deleteCookie(name, options);
283+
} else {
284+
if (isString(value)) {
285+
setCookie(name, value, options);
286+
}
287+
}
288+
} else {
289+
return getAllCookies();
290+
}
291+
}
292+
293+
var defaultPath = self.baseHref;
294+
295+
function deleteCookie(name, options) {
296+
if (options.path) {
297+
cookies._setCookie(escape(name) + "=;path=" + options.path + ";expires=Thu, 01 Jan 1970 00:00:00 GMT");
298+
} else {
299+
cookies._setCookie(escape(name) + "=;path=" + defaultPath() + ";expires=Thu, 01 Jan 1970 00:00:00 GMT");
300+
var path = location.pathname;
301+
//delete cookies under different paths
302+
while (rawDocument.cookie.indexOf(name + "=") >= 0 && path && path != '') {
303+
cookies._setCookie(escape(name) + "=;path=" + path + ";expires=Thu, 01 Jan 1970 00:00:00 GMT");
304+
path = path.replace(/\/$|[^\/]*[^\/]$/, "");
305+
}
306+
}
307+
}
308+
function setCookie(name, value, options) {
309+
var newCookie = escape(name) + '=' + escape(value)
310+
+ resolvePathString(name, options)
311+
+ resolveExpirationString(name, options);
312+
313+
cookies._setCookie(newCookie);
314+
alertOnLength(name, newCookie);
315+
}
316+
function resolvePathString(name, options) {
317+
var path = defaultPath();
318+
if (options.path) {
319+
if (angular.isString(options.path) && location.pathname.indexOf(options.path) >= 0) {
320+
path = options.path;
321+
} else {
322+
$log.warn("Cookie '" + name + "' was not set with requested path '" + options.path +
323+
"' since path is not a String or not partial to window.location, which is " + location.pathname)
324+
}
325+
}
326+
return ";path=" + path;
327+
}
328+
function resolveExpirationString(name, options) {
329+
if (options.expires) {
330+
if (angular.isDate(options.expires) && options.expires > new Date()) {
331+
return ";expires=" + options.expires.toUTCString();
332+
} else {
333+
$log.warn("Cookie '" + name + "' was not set with requested expiration '" + options.expires +
334+
"' since date is in the past or object is not a date")
335+
}
336+
}
337+
return "";
338+
}
339+
function alertOnLength(name, cookieString) {
340+
var cookieLength = (cookieString).length + 1;
341+
342+
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
343+
// - 300 cookies
344+
// - 20 cookies per unique domain
345+
// - 4096 bytes per cookie
346+
if (cookieLength > 4096) {
347+
$log.warn("Cookie '" + name + "' possibly not set or overflowed because it was too large (" +
348+
cookieLength + " > 4096 bytes)!");
349+
}
350+
}
351+
function getAllCookies() {
352+
if (rawDocument.cookie !== lastCookieString) {
353+
lastCookieString = rawDocument.cookie;
354+
var cookieArray = lastCookieString.split("; ");
355+
lastCookies = {};
356+
var cookie, i, index;
357+
for (i = 0; i < cookieArray.length; i++) {
358+
cookie = cookieArray[i];
359+
index = cookie.indexOf('=');
360+
if (index > 0) { //ignore nameless cookies
361+
lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
362+
}
363+
}
364+
}
365+
return lastCookies;
366+
}
367+
368+
//shameless plug for unit testing, since there's no way to access a cookie's path and expiration date
369+
//and mocking the document.cookie object is too troublesome.
370+
//Always use this function to set a cookie.
371+
cookies._setCookie = function (value) {
372+
rawDocument.cookie = value;
373+
}
374+
return cookies;
375+
})();
308376

309377
/**
310378
* @name ng.$browser#defer

0 commit comments

Comments
 (0)