Skip to content
This repository has been archived by the owner on Nov 6, 2023. It is now read-only.

Refactor and enhance trivialize-cookie-rules.js #17438

Merged
merged 3 commits into from
Mar 11, 2019
Merged
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
176 changes: 101 additions & 75 deletions utils/trivialize-rules/trivialize-cookie-rules.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use strict';
"use strict";

/**
* For future contributors, this script was written to trivialize a special
Expand Down Expand Up @@ -28,89 +28,115 @@
* tailing $
*/

let util = require('util');
let path = require('path');
let xml2js = require('xml2js');
let util = require("util");
let path = require("path");
let xml2js = require("xml2js");

let fs = require('graceful-fs');
let fs = require("graceful-fs");
let readdir = util.promisify(fs.readdir);
let readFile = util.promisify(fs.readFile);
let parseString = util.promisify(xml2js.parseString);

const rulesDir = 'src/chrome/content/rules';
let rulesDir = "src/chrome/content/rules";

const isTrivial = (securecookie) => {
return securecookie.host === '.+' && securecookie.name === '.+';
let trivialSecureCookieLiteral = `\n$1<securecookie host=".+"$3name=".+"/>\n`;
let secureCookieRegExp = new RegExp(
`\n([\t ]*)<securecookie\\s*host=\\s*"([^"]+)"(\\s*)name=\\s*"([^"]+)"\\s*?/>[\t ]*\n`
);

let isTrivial = securecookie => {
return securecookie.host === ".+" && securecookie.name === ".+";
};

(async () => {
let readFilePromises = null;

await readdir(rulesDir)
.then(filenames => {
return filenames.filter(filename => filename.endsWith('.xml'));
})
.then(filenames => {
readFilePromises = filenames.map(async (filename) => {
let content = null;

return readFile(path.join(rulesDir, filename), 'utf8')
.then(body => {
content = body;
return parseString(content);
})
.then(ruleset => ruleset.ruleset)
.then(ruleset => {
let rules = ruleset.rule.map(rule => rule.$);
let targets = ruleset.target.map(target => target.$.host);
let securecookies = ruleset.securecookie ? ruleset.securecookie.map(sc => sc.$) : null;

if (!(rules && rules.length === 1)) {
return;
}

if (securecookies && securecookies.length === 1 && !isTrivial(securecookies[0])) {
let securecookie = securecookies[0];
if (!securecookie.host.endsWith('$')) {
return;
}

if (!securecookie.host.startsWith('^.+') &&
!securecookie.host.startsWith('^.*') &&
!securecookie.host.startsWith('.+') &&
!securecookie.host.startsWith('.*') &&
!securecookie.host.startsWith('^(?:.*\\.)?') &&
!securecookie.host.startsWith('^(?:.+\\.)?')) {
return;
}

let hostRegex = new RegExp(securecookie.host);
for (let target of targets) {
if (target.includes('.*')) {
return;
}

target = target.replace('*.', 'www.');
if (!hostRegex.test(target)) {
return;
}
}

let scReSrc = `\n([\t ]*)<securecookie\\s*host=\\s*"([^"]+)"(\\s*)name=\\s*"([^"]+)"\\s*?/>[\t ]*\n`;
let scRe = new RegExp(scReSrc);
let source = content.replace(scRe, '\n$1<securecookie host=".+"$3name="$4" />\n');

fs.writeFileSync(path.join(rulesDir, filename), source, 'utf8');
}
});
let filenames = (await readdir(rulesDir)).filter(fn => fn.endsWith(".xml"));
let filePromises = filenames.map(async filename => {
let content = await readFile(path.join(rulesDir, filename), "utf8");
let { ruleset } = await parseString(content);

let targets = ruleset.target.map(target => target.$.host);
let securecookies = ruleset.securecookie
? ruleset.securecookie.map(sc => sc.$)
: [];

// make sure there is at least one non-trivial securecookie
if (!securecookies.length || securecookies.some(isTrivial)) {
return;
}

for (let securecookie of securecookies) {
if (!securecookie.name === ".+") {
return;
}

if (
!securecookie.host.startsWith("^.+") &&
!securecookie.host.startsWith("^.*") &&
!securecookie.host.startsWith(".+") &&
!securecookie.host.startsWith(".*") &&
!securecookie.host.startsWith("(?:.*\\.)?") &&
!securecookie.host.startsWith("(?:.+\\.)?") &&
!securecookie.host.startsWith("^(?:.*\\.)?") &&
!securecookie.host.startsWith("^(?:.+\\.)?")
) {
return;
}
}

// make sure each domains and its subdomains are covered by at least
// one securecookie rule
let securedDomains = new Map();
for (let target of targets) {
// we cannot handle the right-wildcards based on the argument above
if (target.includes(".*")) {
return;
}
// replace left-wildcard with www is an implementation detail
// see https://github.com/EFForg/https-everywhere/blob/
// 260cd8d402fb8069c55cda311e1be7a60db7339d/chromium/background-scripts/background.js#L595
if (target.includes("*.")) {
target = target.replace("*.", "www.");
}
securedDomains.set(target, false);
securedDomains.set("." + target, false);
}

for (let securecookie of securecookies) {
let pattern = new RegExp(securecookie.host);
securedDomains.forEach((val, key, map) => {
if (pattern.test(key)) {
map.set(key, true);
}
});
}

// If any value of ${securedDomains} is false, return.
if (![...securedDomains.values()].every(secured => secured)) {
return;
}

// remove the securecookie tag except the last one
// replace the last securecookie tag with a trivial one
for (let occurrence = securecookies.length; occurrence; --occurrence) {
content = content.replace(
secureCookieRegExp,
occurrence == 1 ? trivialSecureCookieLiteral : ""
);
}

return new Promise((resolve, reject) => {
fs.writeFile(path.join(rulesDir, filename), content, "utf8", (err) => {
if (err) {
reject(err);
}
resolve();
});
})
.catch(error => {
console.log(error);
});

await Promise.all(readFilePromises)
.catch(error => {
console.log(error);
});
});


// use for-loop to await too many file opened error
for (let fp of filePromises) {
await fp.catch(error => console.log(error));
}
})();