Skip to content
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

Embed IP address regex and thoroughly test #152

Closed
wants to merge 1 commit into from
Closed
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
43 changes: 27 additions & 16 deletions lib/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
'use strict';
var urlParse = require('url').parse;
var util = require('util');
var ipRegex = require('ip-regex')({ exact: true });
var pubsuffix = require('./pubsuffix-psl');
var Store = require('./store').Store;
var MemoryCookieStore = require('./memstore').MemoryCookieStore;
Expand Down Expand Up @@ -78,6 +77,12 @@ var NUM_TO_DAY = [
var MAX_TIME = 2147483647000; // 31-bit max
var MIN_TIME = 0; // 31-bit min

// Dumped from [email protected], with the following changes:
// * all capturing groups converted to non-capturing -- "(?:)"
// * support for IPv6 Scoped Literal ("%eth1") removed
// * lowercase hexadecimal only
var IP_REGEX_LOWERCASE =/(?:^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$)|(?:^(?:(?:[a-f\d]{1,4}:){7}(?:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-f\d]{1,4}|:)|(?:[a-f\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,2}|:)|(?:[a-f\d]{1,4}:){4}(?:(?::[a-f\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,3}|:)|(?:[a-f\d]{1,4}:){3}(?:(?::[a-f\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,4}|:)|(?:[a-f\d]{1,4}:){2}(?:(?::[a-f\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,5}|:)|(?:[a-f\d]{1,4}:){1}(?:(?::[a-f\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,6}|:)|(?::(?:(?::[a-f\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-f\d]{1,4}){1,7}|:)))$)/;

/*
* Parses a Natural number (i.e., non-negative integer) with either the
* <min>*<max>DIGIT ( non-digit *OCTET )
Expand Down Expand Up @@ -309,37 +314,43 @@ function domainMatch(str, domStr, canonicalize) {
}

/*
* "The domain string and the string are identical. (Note that both the
* S5.1.3:
* "A string domain-matches a given domain string if at least one of the
* following conditions hold:"
*
* " o The domain string and the string are identical. (Note that both the
* domain string and the string will have been canonicalized to lower case at
* this point)"
*/
if (str == domStr) {
return true;
}

/* "All of the following [three] conditions hold:" (order adjusted from the RFC) */

/* "* The string is a host name (i.e., not an IP address)." */
if (ipRegex.test(str)) {
return false;
}
/* " o All of the following [three] conditions hold:" */

/* "* The domain string is a suffix of the string" */
/* " * The domain string is a suffix of the string" */
// first, check if substring:
var idx = str.indexOf(domStr);
if (idx <= 0) {
return false; // it's a non-match (-1) or prefix (0)
}

// e.g "a.b.c".indexOf("b.c") === 2
// next, check it's a proper suffix
// e.g., "a.b.c".indexOf("b.c") === 2
// 5 === 3+2
if (str.length !== domStr.length + idx) { // it's not a suffix
return false;
if (str.length !== domStr.length + idx) {
return false; // it's not a suffix
}

/* "* The last character of the string that is not included in the domain
* string is a %x2E (".") character." */
/* " * The last character of the string that is not included in the
* domain string is a %x2E (".") character." */
if (str.substr(idx-1,1) !== '.') {
return false;
return false; // doesn't align on "."
}

/* " * The string is a host name (i.e., not an IP address)." */
if (IP_REGEX_LOWERCASE.test(str)) {
return false; // it's an IP address
}

return true;
Expand Down Expand Up @@ -1015,7 +1026,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {
}
}
else if (!(cookie instanceof Cookie)) {
// If you're seeing this error, and are passing in a Cookie object,
// If you're seeing this error, and are passing in a Cookie object,
// it *might* be a Cookie object from another loaded version of tough-cookie.
err = new Error("First argument to setCookie must be a Cookie object or string");
return cb(options.ignoreError ? null : err);
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@
"vows": "^0.8.2"
},
"dependencies": {
"ip-regex": "^2.1.0",
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
Expand Down
103 changes: 68 additions & 35 deletions test/domain_and_path_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ function matchVows(func, table) {
return theVows;
}

function defaultPathVows(table) {
function transformVows(fn, table) {
var theVows = {};
table.forEach(function (item) {
var str = item[0];
var expect = item[1];
var label = str + " gives " + expect;
if (item.length >= 3) {
label += " (" + item[2] + ")";
}
theVows[label] = function () {
assert.equal(tough.defaultPath(str), expect);
assert.equal(fn(str), expect);
};
});
return theVows;
Expand All @@ -65,56 +68,86 @@ function defaultPathVows(table) {
vows
.describe('Domain and Path')
.addBatch({
"domain normalization": {
"simple": function () {
var c = new Cookie();
c.domain = "EXAMPLE.com";
assert.equal(c.canonicalizedDomain(), "example.com");
},
"extra dots": function () {
var c = new Cookie();
c.domain = ".EXAMPLE.com";
assert.equal(c.cdomain(), "example.com");
},
"weird trailing dot": function () {
var c = new Cookie();
c.domain = "EXAMPLE.ca.";
assert.equal(c.canonicalizedDomain(), "example.ca.");
},
"weird internal dots": function () {
var c = new Cookie();
c.domain = "EXAMPLE...ca.";
assert.equal(c.canonicalizedDomain(), "example...ca.");
},
"IDN": function () {
var c = new Cookie();
c.domain = "δοκιμή.δοκιμή"; // "test.test" in greek
assert.equal(c.canonicalizedDomain(), "xn--jxalpdlp.xn--jxalpdlp");
}
}
"domain normalization": transformVows(tough.canonicalDomain, [
["example.com", "example.com", "already canonical"],
["EXAMPLE.com", "example.com", "simple"],
[".EXAMPLE.com", "example.com", "leading dot stripped"],
["EXAMPLE.com.", "example.com.", "trailing dot"],
[".EXAMPLE.com.", "example.com.", "leading and trailing dot"],
[".EXAMPLE...com.", "example...com.", "internal dots"],
["δοκιμή.δοκιμή","xn--jxalpdlp.xn--jxalpdlp", "IDN: test.test in greek"],
])
})
.addBatch({
"Domain Match": matchVows(tough.domainMatch, [
// str, dom, expect
["example.com", "example.com", true],
["eXaMpLe.cOm", "ExAmPlE.CoM", true],
["example.com", "example.com", true], // identical
["eXaMpLe.cOm", "ExAmPlE.CoM", true], // both canonicalized
["no.ca", "yes.ca", false],
["wwwexample.com", "example.com", false],
["www.example.com", "example.com", true],
["example.com", "www.example.com", false],
["www.subdom.example.com", "example.com", true],
["www.subdom.example.com", "subdom.example.com", true],
["example.com", "example.com.", false], // RFC6265 S4.1.2.3
["192.168.0.1", "168.0.1", false], // S5.1.3 "The string is a host name"

// nulls and undefineds
[null, "example.com", null],
["example.com", null, null],
[null, null, null],
[undefined, undefined, null],

// suffix matching:
["www.example.com", "example.com", true], // substr AND suffix
["www.example.com.org", "example.com", false], // substr but not suffix
["example.com", "www.example.com.org", false], // neither
["example.com", "www.example.com", false], // super-str
["aaa.com", "aaaa.com", false], // str can't be suffix of domain
["aaaa.com", "aaa.com", false], // dom is suffix, but has to match on "." boundary!
["www.aaaa.com", "aaa.com", false],
["www.aaa.com", "aaa.com", true],
["www.aexample.com", "example.com", false], // has to match on "." boundary

// S5.1.3 "The string is a host name (i.e., not an IP address)"
["192.168.0.1", "168.0.1", false], // because str is an IP (v4)
["100.192.168.0.1", "168.0.1", true], // WEIRD: because str is not a valid IPv4
["100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not a valid IPv4
["::ffff:192.168.0.1", "168.0.1", false], // because str is an IP (v6)
["::ffff:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6)
["::FFFF:192.168.0.1", "192.168.0.1", false], // because str is an IP (v6)
["::192.168.0.1", "192.168.0.1", false], // because str is an IP (yes, v6!)
[":192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6
[":ffff:100.192.168.0.1", "192.168.0.1", true], // WEIRD: because str is not valid IPv6
[":ffff:192.168.0.1", "192.168.0.1", false],
[":ffff:192.168.0.1", "168.0.1", true], // WEIRD: because str is not valid IPv6
["::Fxxx:192.168.0.1", "168.0.1", true], // WEIRD: because str isnt IPv6
["192.168.0.1", "68.0.1", false],
["192.168.0.1", "2.68.0.1", false],
["192.168.0.1", "92.68.0.1", false],
["10.1.2.3", "210.1.2.3", false],
["2008::1", "::1", false],
["::1", "2008::1", false],
["::1", "::1", true], // "are identical" rule, despite IPv6
["::3xam:1e", "2008::3xam:1e", false], // malformed IPv6
["::3Xam:1e", "::3xaM:1e", true], // identical, even though malformed
["3xam::1e", "3xam::1e", true], // identical
["::3xam::1e", "3xam::1e", false],
["3xam::1e", "::3xam:1e", false],
["::f00f:10.0.0.1", "10.0.0.1", false],
["10.0.0.1", "::f00f:10.0.0.1", false],

// "IP like" hostnames:
["1.example.com", "example.com", true],
["11.example.com", "example.com", true],
["192.168.0.1.example.com", "example.com", true],

// exact length "TLD" tests:
["com", "net", false], // same len, non-match
["com", "com", true], // "are identical" rule
["NOTATLD", "notaTLD", true], // "are identical" rule (after canonicalization)
])
})

.addBatch({
"default-path": defaultPathVows([
"default-path": transformVows(tough.defaultPath,[
[null, "/"],
["/", "/"],
["/file", "/"],
Expand Down