Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 92c366d

Browse files
shahatapetebacondarwin
authored andcommitted
feat($cookies): allow passing cookie options
The `put`, `putObject` and `remove` methods now take an options parameter where you can provide additional options for the cookie value, such as `expires`, `path`, `domain` and `secure`. Closes #8324 Closes #3988 Closes #1786 Closes #950
1 parent 38fbe3e commit 92c366d

File tree

4 files changed

+140
-27
lines changed

4 files changed

+140
-27
lines changed

src/ngCookies/cookieWriter.js

+34-18
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,47 @@
99
*
1010
* @param {string} name Cookie name
1111
* @param {string=} value Cookie value (if undefined, cookie will be deleted)
12+
* @param {Object=} options Object with options that need to be stored for the cookie.
1213
*/
1314
function $$CookieWriter($document, $log, $browser) {
1415
var cookiePath = $browser.baseHref();
1516
var rawDocument = $document[0];
1617

17-
return function(name, value) {
18+
function buildCookieString(name, value, options) {
19+
var path, expires;
20+
options = options || {};
21+
expires = options.expires;
22+
path = angular.isDefined(options.path) ? options.path : cookiePath;
1823
if (value === undefined) {
19-
rawDocument.cookie = encodeURIComponent(name) + "=;path=" + cookiePath +
20-
";expires=Thu, 01 Jan 1970 00:00:00 GMT";
21-
} else {
22-
if (angular.isString(value)) {
23-
var cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) +
24-
';path=' + cookiePath).length + 1;
25-
26-
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
27-
// - 300 cookies
28-
// - 20 cookies per unique domain
29-
// - 4096 bytes per cookie
30-
if (cookieLength > 4096) {
31-
$log.warn("Cookie '" + name +
32-
"' possibly not set or overflowed because it was too large (" +
33-
cookieLength + " > 4096 bytes)!");
34-
}
35-
}
24+
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
25+
value = '';
3626
}
27+
if (angular.isString(expires)) {
28+
expires = new Date(expires);
29+
}
30+
31+
var str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
32+
str += path ? ';path=' + path : '';
33+
str += options.domain ? ';domain=' + options.domain : '';
34+
str += expires ? ';expires=' + expires.toUTCString() : '';
35+
str += options.secure ? ';secure' : '';
36+
37+
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
38+
// - 300 cookies
39+
// - 20 cookies per unique domain
40+
// - 4096 bytes per cookie
41+
var cookieLength = str.length + 1;
42+
if (cookieLength > 4096) {
43+
$log.warn("Cookie '" + name +
44+
"' possibly not set or overflowed because it was too large (" +
45+
cookieLength + " > 4096 bytes)!");
46+
}
47+
48+
return str;
49+
}
50+
51+
return function(name, value, options) {
52+
rawDocument.cookie = buildCookieString(name, value, options);
3753
};
3854
}
3955

src/ngCookies/cookies.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,20 @@ angular.module('ngCookies', ['ng']).
9696
*
9797
* @param {string} key Id for the `value`.
9898
* @param {string} value Raw value to be stored.
99+
* @param {Object=} options Object with options that need to be stored for the cookie.
100+
* The object may have following properties:
101+
*
102+
* - **path** - `{string}` - The cookie will be available only for this path and its
103+
* sub-paths. By default, this would be the URL that appears in your base tag.
104+
* - **domain** - `{string}` - The cookie will be available only for this domain and
105+
* its sub-domains. For obvious security reasons the user agent will not accept the
106+
* cookie if the current domain is not a sub domain or equals to the requested domain.
107+
* - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT"
108+
* or a Date object indicating the exact date/time this cookie will expire.
109+
* - **secure** - `{boolean}` - The cookie will be available only in secured connection.
99110
*/
100-
put: function(key, value) {
101-
$$cookieWriter(key, value);
111+
put: function(key, value, options) {
112+
$$cookieWriter(key, value, options);
102113
},
103114

104115
/**
@@ -110,9 +121,10 @@ angular.module('ngCookies', ['ng']).
110121
*
111122
* @param {string} key Id for the `value`.
112123
* @param {Object} value Value to be stored.
124+
* @param {Object=} options Options object.
113125
*/
114-
putObject: function(key, value) {
115-
$$cookieWriter(key, angular.toJson(value));
126+
putObject: function(key, value, options) {
127+
$$cookieWriter(key, angular.toJson(value), options);
116128
},
117129

118130
/**
@@ -123,9 +135,10 @@ angular.module('ngCookies', ['ng']).
123135
* Remove given cookie
124136
*
125137
* @param {string} key Id of the key-value pair to delete.
138+
* @param {Object=} options Options object.
126139
*/
127-
remove: function(key) {
128-
$$cookieWriter(key, undefined);
140+
remove: function(key, options) {
141+
$$cookieWriter(key, undefined, options);
129142
}
130143
};
131144
}]);

test/ngCookies/cookieWriterSpec.js

+67-1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,72 @@ describe('$$cookieWriter', function() {
127127
expect(document.cookie).toEqual('cookie=bender');
128128
});
129129
});
130-
131130
});
132131

132+
describe('cookie options', function() {
133+
var fakeDocument, $$cookieWriter;
134+
135+
function getLastCookieAssignment(key) {
136+
return fakeDocument[0].cookie
137+
.split(';')
138+
.reduce(function(prev, value) {
139+
var pair = value.split('=', 2);
140+
if (pair[0] === key) {
141+
if (prev === undefined) {
142+
return pair[1] === undefined ? true : pair[1];
143+
} else {
144+
throw 'duplicate key in cookie string';
145+
}
146+
} else {
147+
return prev;
148+
}
149+
}, undefined);
150+
}
151+
152+
beforeEach(function() {
153+
fakeDocument = [{cookie: ''}];
154+
module('ngCookies', {$document: fakeDocument});
155+
inject(function($browser) {
156+
$browser.$$baseHref = '/a/b';
157+
});
158+
inject(function(_$$cookieWriter_) {
159+
$$cookieWriter = _$$cookieWriter_;
160+
});
161+
});
162+
163+
it('should use baseHref as default path', function() {
164+
$$cookieWriter('name', 'value');
165+
expect(getLastCookieAssignment('path')).toBe('/a/b');
166+
});
167+
168+
it('should accept path option', function() {
169+
$$cookieWriter('name', 'value', {path: '/c/d'});
170+
expect(getLastCookieAssignment('path')).toBe('/c/d');
171+
});
172+
173+
it('should accept domain option', function() {
174+
$$cookieWriter('name', 'value', {domain: '.example.com'});
175+
expect(getLastCookieAssignment('domain')).toBe('.example.com');
176+
});
177+
178+
it('should accept secure option', function() {
179+
$$cookieWriter('name', 'value', {secure: true});
180+
expect(getLastCookieAssignment('secure')).toBe(true);
181+
});
182+
183+
it('should accept expires option on set', function() {
184+
$$cookieWriter('name', 'value', {expires: 'Fri, 19 Dec 2014 00:00:00 GMT'});
185+
expect(getLastCookieAssignment('expires')).toMatch(/^Fri, 19 Dec 2014 00:00:00 (UTC|GMT)$/);
186+
});
187+
188+
it('should always use epoch time as expire time on remove', function() {
189+
$$cookieWriter('name', undefined, {expires: 'Fri, 19 Dec 2014 00:00:00 GMT'});
190+
expect(getLastCookieAssignment('expires')).toMatch(/^Thu, 0?1 Jan 1970 00:00:00 (UTC|GMT)$/);
191+
});
192+
193+
it('should accept date object as expires option', function() {
194+
$$cookieWriter('name', 'value', {expires: new Date(Date.UTC(1981, 11, 27))});
195+
expect(getLastCookieAssignment('expires')).toMatch(/^Sun, 27 Dec 1981 00:00:00 (UTC|GMT)$/);
196+
});
197+
198+
});

test/ngCookies/cookiesSpec.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ describe('$cookies', function() {
66
beforeEach(function() {
77
mockedCookies = {};
88
module('ngCookies', {
9-
$$cookieWriter: function(name, value) {
9+
$$cookieWriter: jasmine.createSpy('$$cookieWriter').andCallFake(function(name, value) {
1010
mockedCookies[name] = value;
11-
},
11+
}),
1212
$$cookieReader: function() {
1313
return mockedCookies;
1414
}
@@ -65,4 +65,22 @@ describe('$cookies', function() {
6565
$cookies.putObject('name2', 'value2');
6666
expect($cookies.getAll()).toEqual({name: 'value', name2: '"value2"'});
6767
}));
68+
69+
70+
it('should pass options on put', inject(function($cookies, $$cookieWriter) {
71+
$cookies.put('name', 'value', {path: '/a/b'});
72+
expect($$cookieWriter).toHaveBeenCalledWith('name', 'value', {path: '/a/b'});
73+
}));
74+
75+
76+
it('should pass options on putObject', inject(function($cookies, $$cookieWriter) {
77+
$cookies.putObject('name', 'value', {path: '/a/b'});
78+
expect($$cookieWriter).toHaveBeenCalledWith('name', '"value"', {path: '/a/b'});
79+
}));
80+
81+
82+
it('should pass options on remove', inject(function($cookies, $$cookieWriter) {
83+
$cookies.remove('name', {path: '/a/b'});
84+
expect($$cookieWriter).toHaveBeenCalledWith('name', undefined, {path: '/a/b'});
85+
}));
6886
});

0 commit comments

Comments
 (0)