node-tough-cookie/test/api_test.js

749 lines
23 KiB
JavaScript

/*!
* Copyright (c) 2015, Salesforce.com, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of Salesforce.com nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
const vows = require("vows");
const assert = require("assert");
const async = require("async");
const tough = require("../lib/cookie");
const Cookie = tough.Cookie;
const CookieJar = tough.CookieJar;
const atNow = Date.now();
function at(offset) {
return { now: new Date(atNow + offset) };
}
vows
.describe("API")
.addBatch({
"All defined": function() {
assert.ok(Cookie);
assert.ok(CookieJar);
}
})
.addBatch({
Version: function() {
assert.equal(tough.version, require("../package.json").version);
}
})
.addBatch({
Constructor: {
topic: function() {
return new Cookie({
key: "test",
value: "b",
maxAge: 60
});
},
"check for key property": function(c) {
assert.ok(c);
assert.equal(c.key, "test");
},
"check for value property": function(c) {
assert.equal(c.value, "b");
},
"check for maxAge": function(c) {
assert.equal(c.maxAge, 60);
},
"check for default values for unspecified properties": function(c) {
assert.equal(c.expires, "Infinity");
assert.equal(c.secure, false);
assert.equal(c.httpOnly, false);
}
}
})
.addBatch({
"CookieJar Promises": {
topic: () => new CookieJar(),
setCookie: {
topic(jar) {
jar
.setCookie("foo=bar", "http://example.com")
.then(c => this.callback(null, c), this.callback);
},
"resolves to a Cookie"(cookie) {
assert.ok(cookie instanceof Cookie);
assert.strictEqual(cookie.key, "foo");
assert.strictEqual(cookie.value, "bar");
}
},
getCookies: {
topic(jar) {
jar
.getCookies("http://example.com")
.then(cookies => this.callback(null, cookies), this.callback);
},
"resolves to an array of cookies"(cookies) {
assert.ok(Array.isArray(cookies), "not an array");
assert.ok(cookies.length > 0, "array is empty");
for (const cookie of cookies) {
assert.ok(cookie instanceof Cookie, "not instanceof Cookie");
}
}
},
getCookieString: {
topic(jar) {
jar
.getCookieString("http://example.com")
.then(cookies => this.callback(null, cookies), this.callback);
},
"resolves to a string"(cookies) {
assert.ok(typeof cookies === "string", "not a string");
}
},
getSetCookieStrings: {
topic(jar) {
jar
.getSetCookieStrings("http://example.com")
.then(cookies => this.callback(null, cookies), this.callback);
},
"resolves to a an array of strings"(cookies) {
assert.ok(Array.isArray(cookies), "not an array");
assert.ok(cookies.length > 0, "array is empty");
for (const cookie of cookies) {
assert.ok(typeof cookie === "string", "not a string");
}
}
},
removeAllCookies: {
topic(jar) {
jar.removeAllCookies().then(this.callback, this.callback);
},
"resolves to undefined"(arg) {
assert.ok(arg === undefined, "was not undefined");
}
},
serialize: {
topic(jar) {
jar
.serialize()
.then(data => this.callback(null, data), this.callback);
},
"resolves to an object"(data) {
assert.ok(data instanceof Object, "not an object");
}
}
}
})
.addBatch({
"expiry option": {
topic: function() {
const cb = this.callback;
const cj = new CookieJar();
cj.setCookie(
"near=expiry; Domain=example.com; Path=/; Max-Age=1",
"http://www.example.com",
at(-1),
(err, cookie) => {
cb(err, { cj: cj, cookie: cookie });
}
);
},
"set the cookie": function(t) {
assert.ok(t.cookie, "didn't set?!");
assert.equal(t.cookie.key, "near");
},
"then, retrieving": {
topic: function(t) {
const cb = this.callback;
setTimeout(() => {
t.cj.getCookies(
"http://www.example.com",
{ http: true, expire: false },
(err, cookies) => {
t.cookies = cookies;
cb(err, t);
}
);
}, 2000);
},
"got the cookie": function(t) {
assert.lengthOf(t.cookies, 1);
assert.equal(t.cookies[0].key, "near");
}
}
}
})
.addBatch({
"allPaths option": {
topic: function() {
const cj = new CookieJar();
const apex = "http://example.com";
const www = "http://www.example.com";
const other = "http://other.example.com";
const tasks = [
["nopath_dom=qq; Path=/; Domain=example.com", apex, {}],
["path_dom=qq; Path=/foo; Domain=example.com", apex, {}],
["nopath_host=qq; Path=/", www, {}],
["path_host=qq; Path=/foo", www, {}],
["other=qq; Path=/", other, {}],
["other2=qq; Path=/foo", `${other}/foo`, {}]
].map(args => cb => cj.setCookie(...args, cb));
const cb = this.callback;
async.parallel(tasks, (err, results) => {
cb(err, { cj: cj, cookies: results });
});
},
"all set": function(t) {
assert.equal(t.cookies.length, 6);
assert.ok(
t.cookies.every(c => {
return !!c;
})
);
},
"getting without allPaths": {
topic: function(t) {
const cb = this.callback;
const cj = t.cj;
cj.getCookies("http://www.example.com/", {}, (err, cookies) => {
cb(err, { cj: cj, cookies: cookies });
});
},
"found just two cookies": function(t) {
assert.equal(t.cookies.length, 2);
},
"all are path=/": function(t) {
assert.ok(
t.cookies.every(c => {
return c.path === "/";
})
);
},
"no 'other' cookies": function(t) {
assert.ok(
!t.cookies.some(c => {
return /^other/.test(c.name);
})
);
}
},
"getting without allPaths for /foo": {
topic: function(t) {
const cb = this.callback;
const cj = t.cj;
cj.getCookies("http://www.example.com/foo", {}, (err, cookies) => {
cb(err, { cj: cj, cookies: cookies });
});
},
"found four cookies": function(t) {
assert.equal(t.cookies.length, 4);
},
"no 'other' cookies": function(t) {
assert.ok(
!t.cookies.some(c => {
return /^other/.test(c.name);
})
);
}
},
"getting with allPaths:true": {
topic: function(t) {
const cb = this.callback;
const cj = t.cj;
cj.getCookies(
"http://www.example.com/",
{ allPaths: true },
(err, cookies) => {
cb(err, { cj: cj, cookies: cookies });
}
);
},
"found four cookies": function(t) {
assert.equal(t.cookies.length, 4);
},
"no 'other' cookies": function(t) {
assert.ok(
!t.cookies.some(c => {
return /^other/.test(c.name);
})
);
}
}
}
})
.addBatch({
"Remove cookies": {
topic: function() {
const jar = new CookieJar();
const cookie = Cookie.parse("a=b; Domain=example.com; Path=/");
const cookie2 = Cookie.parse("a=b; Domain=foo.com; Path=/");
const cookie3 = Cookie.parse("foo=bar; Domain=foo.com; Path=/");
async.parallel(
[
[cookie, "http://example.com/index.html"],
[cookie2, "http://foo.com/index.html"],
[cookie3, "http://foo.com/index.html"]
].map(args => cb => jar.setCookie(...args, cb)),
err => {
this.callback(err, jar);
}
);
},
"all from matching domain": function(jar) {
jar.store.removeCookies("example.com", null, err => {
assert(err == null);
jar.store.findCookies("example.com", null, (err, cookies) => {
assert(err == null);
assert(cookies != null);
assert(cookies.length === 0, "cookie was not removed");
});
jar.store.findCookies("foo.com", null, (err, cookies) => {
assert(err == null);
assert(cookies != null);
assert(
cookies.length === 2,
"cookies should not have been removed"
);
});
});
},
"from cookie store matching domain and key": function(jar) {
jar.store.removeCookie("foo.com", "/", "foo", err => {
assert(err == null);
jar.store.findCookies("foo.com", null, (err, cookies) => {
assert(err == null);
assert(cookies != null);
assert(cookies.length === 1, "cookie was not removed correctly");
assert(cookies[0].key === "a", "wrong cookie was removed");
});
});
}
}
})
.addBatch({
"Synchronous CookieJar": {
setCookieSync: {
topic: function() {
const jar = new CookieJar();
let cookie = Cookie.parse("a=b; Domain=example.com; Path=/");
cookie = jar.setCookieSync(cookie, "http://example.com/index.html");
return cookie;
},
"returns a copy of the cookie": function(cookie) {
assert.instanceOf(cookie, Cookie);
}
},
getCookiesSync: {
topic: function() {
const jar = new CookieJar();
const url = "http://example.com/index.html";
jar.setCookieSync("a=b; Domain=example.com; Path=/", url);
jar.setCookieSync("c=d; Domain=example.com; Path=/", url);
return jar.getCookiesSync(url);
},
"returns the cookie array": function(err, cookies) {
assert.ok(!err);
assert.ok(Array.isArray(cookies));
assert.lengthOf(cookies, 2);
cookies.forEach(cookie => {
assert.instanceOf(cookie, Cookie);
});
}
},
getCookieStringSync: {
topic: function() {
const jar = new CookieJar();
const url = "http://example.com/index.html";
jar.setCookieSync("a=b; Domain=example.com; Path=/", url);
jar.setCookieSync("c=d; Domain=example.com; Path=/", url);
return jar.getCookieStringSync(url);
},
"returns the cookie header string": function(err, str) {
assert.ok(!err);
assert.typeOf(str, "string");
}
},
getSetCookieStringsSync: {
topic: function() {
const jar = new CookieJar();
const url = "http://example.com/index.html";
jar.setCookieSync("a=b; Domain=example.com; Path=/", url);
jar.setCookieSync("c=d; Domain=example.com; Path=/", url);
return jar.getSetCookieStringsSync(url);
},
"returns the cookie header string": function(err, headers) {
assert.ok(!err);
assert.ok(Array.isArray(headers));
assert.lengthOf(headers, 2);
headers.forEach(header => {
assert.typeOf(header, "string");
});
}
},
removeAllCookiesSync: {
topic: function() {
const jar = new CookieJar();
const cookie1 = Cookie.parse("a=b; Domain=example.com; Path=/");
const cookie2 = Cookie.parse("a=b; Domain=foo.com; Path=/");
const cookie3 = Cookie.parse("foo=bar; Domain=foo.com; Path=/");
jar.setCookieSync(cookie1, "http://example.com/index.html");
jar.setCookieSync(cookie2, "http://foo.com/index.html");
jar.setCookieSync(cookie3, "http://foo.com/index.html");
jar.removeAllCookiesSync();
jar.store.getAllCookies(this.callback);
},
"no cookies in the jar": function(err, cookies) {
assert(err == null);
assert(cookies != null);
assert(cookies.length === 0, "cookies were not removed");
}
}
}
})
.addBatch({
"Synchronous API on async CookieJar": {
topic: function() {
return new tough.Store();
},
setCookieSync: {
topic: function(store) {
const jar = new CookieJar(store);
try {
jar.setCookieSync("a=b", "http://example.com/index.html");
return false;
} catch (e) {
return e;
}
},
fails: function(err) {
assert.instanceOf(err, Error);
assert.equal(
err.message,
"CookieJar store is not synchronous; use async API instead."
);
}
},
getCookiesSync: {
topic: function(store) {
const jar = new CookieJar(store);
try {
jar.getCookiesSync("http://example.com/index.html");
return false;
} catch (e) {
return e;
}
},
fails: function(err) {
assert.instanceOf(err, Error);
assert.equal(
err.message,
"CookieJar store is not synchronous; use async API instead."
);
}
},
getCookieStringSync: {
topic: function(store) {
const jar = new CookieJar(store);
try {
jar.getCookieStringSync("http://example.com/index.html");
return false;
} catch (e) {
return e;
}
},
fails: function(err) {
assert.instanceOf(err, Error);
assert.equal(
err.message,
"CookieJar store is not synchronous; use async API instead."
);
}
},
getSetCookieStringsSync: {
topic: function(store) {
const jar = new CookieJar(store);
try {
jar.getSetCookieStringsSync("http://example.com/index.html");
return false;
} catch (e) {
return e;
}
},
fails: function(err) {
assert.instanceOf(err, Error);
assert.equal(
err.message,
"CookieJar store is not synchronous; use async API instead."
);
}
},
removeAllCookies: {
topic: function(store) {
const jar = new CookieJar(store);
try {
jar.removeAllCookiesSync();
return false;
} catch (e) {
return e;
}
},
fails: function(err) {
assert.instanceOf(err, Error);
assert.equal(
err.message,
"CookieJar store is not synchronous; use async API instead."
);
}
}
}
})
.addBatch({
"loose option": {
"cookie jar with loose": {
topic: function() {
const jar = new CookieJar();
const url = "http://example.com/index.html";
return jar.setCookieSync("=b", url, { loose: true });
},
succeeds: function(err, c) {
assert.equal(err, null);
assert(c);
assert.equal(c.value, "b");
}
},
"cookie jar without loose": {
topic: function() {
const jar = new CookieJar();
const url = "http://example.com/index.html";
return jar.setCookieSync("=b", url);
},
fails: function(err, c) {
assert.instanceOf(err, Error);
assert.equal(err.message, "Cookie failed to parse");
}
},
"map doesn't default to loose": {
topic: function() {
const some = [
"=a;domain=example.com", // index 0, falsey
"=b;domain=example.com", // index 1, truthy
"c=d;domain=example.com" // index 2, truthy
];
return some.map(Cookie.parse);
},
parses: function(err, val) {
assert.equal(err, null);
assert.isArray(val);
assert.lengthOf(val, 3);
},
"doesn't parse first cookie loose": function(val) {
assert.isUndefined(val[0]);
},
"doesn't parse second cookie loose": function(val) {
assert.isUndefined(val[1]);
},
"parses third cookie normally": function(val) {
assert.instanceOf(val[2], Cookie);
assert.equal(val[2].key, "c");
assert.equal(val[2].value, "d");
}
}
}
})
.addBatch(allowSpecialUseOptionVows())
.export(module);
function allowSpecialUseOptionVows() {
const specialUseDomains = [
"local",
"example",
"invalid",
"localhost",
"test"
];
const specialTreatmentDomains = ["localhost", "invalid"];
return specialUseDomains.reduce((vows, specialUseDomain) => {
if (specialTreatmentDomains.includes(specialUseDomain)) {
vows[
`cookie jar with allowSpecialUseDomain set to the default value and domain is "${specialUseDomain}"`
] = {
topic: function() {
const cb = this.callback;
const cj = new CookieJar();
cj.setCookie(
`settingThisShouldPass=true; Domain=${specialUseDomain}; Path=/;`,
`http://${specialUseDomain}`,
at(-1),
(err, cookie) => {
cb(err, { cj: cj, cookie: cookie });
}
);
},
"set the cookie": function(t) {
assert.ok(t.cookie, "didn't set?!");
assert.equal(t.cookie.key, "settingThisShouldPass");
},
"then, retrieving": {
topic: function(t) {
const cb = this.callback;
setTimeout(() => {
t.cj.getCookies(
`http://${specialUseDomain}`,
{ http: true },
(err, cookies) => {
t.cookies = cookies;
cb(err, t);
}
);
}, 2000);
},
"got the cookie": function(t) {
assert.lengthOf(t.cookies, 1);
assert.equal(t.cookies[0].key, "settingThisShouldPass");
}
}
};
}
vows[
`cookie jar with allowSpecialUseDomain set to the default value and domain is "dev.${specialUseDomain}"`
] = {
topic: function() {
const cb = this.callback;
const cj = new CookieJar();
cj.setCookie(
`settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`,
`http://dev.${specialUseDomain}`,
at(-1),
(err, cookie) => {
cb(err, { cj: cj, cookie: cookie });
}
);
},
"set the cookie": function(t) {
assert.ok(t.cookie, "didn't set?!");
assert.equal(t.cookie.key, "settingThisShouldPass");
},
"then, retrieving": {
topic: function(t) {
const cb = this.callback;
setTimeout(() => {
t.cj.getCookies(
`http://dev.${specialUseDomain}`,
{ http: true },
(err, cookies) => {
t.cookies = cookies;
cb(err, t);
}
);
}, 2000);
},
"got the cookie": function(t) {
assert.lengthOf(t.cookies, 1);
assert.equal(t.cookies[0].key, "settingThisShouldPass");
}
}
};
vows[
`cookie jar with allowSpecialUseDomain enabled and domain is "dev.${specialUseDomain}"`
] = {
topic: function() {
const cb = this.callback;
const cj = new CookieJar(new tough.MemoryCookieStore(), {
rejectPublicSuffixes: true,
allowSpecialUseDomain: true
});
cj.setCookie(
`settingThisShouldPass=true; Domain=dev.${specialUseDomain}; Path=/;`,
`http://dev.${specialUseDomain}`,
at(-1),
(err, cookie) => {
cb(err, { cj: cj, cookie: cookie });
}
);
},
"set the cookie": function(t) {
assert.ok(t.cookie, "didn't set?!");
assert.equal(t.cookie.key, "settingThisShouldPass");
},
"then, retrieving": {
topic: function(t) {
const cb = this.callback;
setTimeout(() => {
t.cj.getCookies(
`http://dev.${specialUseDomain}`,
{ http: true },
(err, cookies) => {
t.cookies = cookies;
cb(err, t);
}
);
}, 2000);
},
"got the cookie": function(t) {
assert.lengthOf(t.cookies, 1);
assert.equal(t.cookies[0].key, "settingThisShouldPass");
}
}
};
vows[
`cookie jar with allowSpecialUseDomain disabled and domain is "dev.${specialUseDomain}"`
] = {
topic: function() {
const cj = new CookieJar(new tough.MemoryCookieStore(), {
allowSpecialUseDomain: false,
rejectPublicSuffixes: true
});
cj.setCookie(
`settingThisShouldFail=true; Domain=dev.${specialUseDomain}; Path=/;`,
`http://dev.${specialUseDomain}`,
this.callback
);
},
errors: function(err, cookie) {
assert.ok(err);
assert.ok(!cookie);
assert.equal(
err.message,
`Cookie has domain set to the public suffix "${specialUseDomain}" which is a special use domain. To allow this, configure your CookieJar with {allowSpecialUseDomain:true, rejectPublicSuffixes: false}.`
);
}
};
return vows;
}, {});
}