Skip to content

Commit

Permalink
Merge pull request #187 from salesforce/new-embed-ip-regex
Browse files Browse the repository at this point in the history
New embed ip regex
  • Loading branch information
wtfismyip committed Mar 5, 2020
2 parents b95d536 + c0049e2 commit 6749a2f
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 58 deletions.
39 changes: 24 additions & 15 deletions lib/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
const punycode = require("punycode");
const urlParse = require("url").parse;
const util = require("util");
const ipRegex = require("ip-regex")({ exact: true });
const pubsuffix = require("./pubsuffix-psl");
const Store = require("./store").Store;
const MemoryCookieStore = require("./memstore").MemoryCookieStore;
Expand Down Expand Up @@ -94,6 +93,12 @@ const PrefixSecurityEnum = Object.freeze({
DISABLED: "unsafe-disabled"
});

// Dumped from ip-regex@4.0.0, 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 @@ -325,38 +330,42 @@ 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" */
const 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;
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." */
if (str.substr(idx-1,1) !== '.') {
return false; // doesn't align on "."
}

/* "* 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;
/* " * 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
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@
"vows": "^0.8.2"
},
"dependencies": {
"ip-regex": "^2.1.0",
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.1.2"
Expand Down
117 changes: 75 additions & 42 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) {
const theVows = {};
table.forEach(item => {
const str = item[0];
const expect = item[1];
const label = `${str} gives ${expect}`;
theVows[label] = function() {
assert.equal(tough.defaultPath(str), expect);
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(fn(str), expect);
};
});
return theVows;
Expand All @@ -65,56 +68,86 @@ function defaultPathVows(table) {
vows
.describe("Domain and Path")
.addBatch({
"domain normalization": {
simple: function() {
const c = new Cookie();
c.domain = "EXAMPLE.com";
assert.equal(c.canonicalizedDomain(), "example.com");
},
"extra dots": function() {
const c = new Cookie();
c.domain = ".EXAMPLE.com";
assert.equal(c.cdomain(), "example.com");
},
"weird trailing dot": function() {
const c = new Cookie();
c.domain = "EXAMPLE.ca.";
assert.equal(c.canonicalizedDomain(), "example.ca.");
},
"weird internal dots": function() {
const c = new Cookie();
c.domain = "EXAMPLE...ca.";
assert.equal(c.canonicalizedDomain(), "example...ca.");
},
IDN: function() {
const 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]
[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

0 comments on commit 6749a2f

Please sign in to comment.