Skip to content

Commit 2a7a7e8

Browse files
Chan Chak Shingzoracon
authored andcommitted
Refactor and enhance trivialize-cookie-rules.js (EFForg#17438)
* Refactor and enhance trivialize-cookie-rules.js * With help from @pipboy96 on some syntax issues, re-open EFForg#17420 * Update comments and fix typo * Avoid fs.writeFileSync (too many files opened error)
1 parent 83642b8 commit 2a7a7e8

File tree

1 file changed

+101
-75
lines changed

1 file changed

+101
-75
lines changed
Lines changed: 101 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use strict';
1+
"use strict";
22

33
/**
44
* For future contributors, this script was written to trivialize a special
@@ -28,89 +28,115 @@
2828
* tailing $
2929
*/
3030

31-
let util = require('util');
32-
let path = require('path');
33-
let xml2js = require('xml2js');
31+
let util = require("util");
32+
let path = require("path");
33+
let xml2js = require("xml2js");
3434

35-
let fs = require('graceful-fs');
35+
let fs = require("graceful-fs");
3636
let readdir = util.promisify(fs.readdir);
3737
let readFile = util.promisify(fs.readFile);
3838
let parseString = util.promisify(xml2js.parseString);
3939

40-
const rulesDir = 'src/chrome/content/rules';
40+
let rulesDir = "src/chrome/content/rules";
4141

42-
const isTrivial = (securecookie) => {
43-
return securecookie.host === '.+' && securecookie.name === '.+';
42+
let trivialSecureCookieLiteral = `\n$1<securecookie host=".+"$3name=".+"/>\n`;
43+
let secureCookieRegExp = new RegExp(
44+
`\n([\t ]*)<securecookie\\s*host=\\s*"([^"]+)"(\\s*)name=\\s*"([^"]+)"\\s*?/>[\t ]*\n`
45+
);
46+
47+
let isTrivial = securecookie => {
48+
return securecookie.host === ".+" && securecookie.name === ".+";
4449
};
4550

4651
(async () => {
47-
let readFilePromises = null;
48-
49-
await readdir(rulesDir)
50-
.then(filenames => {
51-
return filenames.filter(filename => filename.endsWith('.xml'));
52-
})
53-
.then(filenames => {
54-
readFilePromises = filenames.map(async (filename) => {
55-
let content = null;
56-
57-
return readFile(path.join(rulesDir, filename), 'utf8')
58-
.then(body => {
59-
content = body;
60-
return parseString(content);
61-
})
62-
.then(ruleset => ruleset.ruleset)
63-
.then(ruleset => {
64-
let rules = ruleset.rule.map(rule => rule.$);
65-
let targets = ruleset.target.map(target => target.$.host);
66-
let securecookies = ruleset.securecookie ? ruleset.securecookie.map(sc => sc.$) : null;
67-
68-
if (!(rules && rules.length === 1)) {
69-
return;
70-
}
71-
72-
if (securecookies && securecookies.length === 1 && !isTrivial(securecookies[0])) {
73-
let securecookie = securecookies[0];
74-
if (!securecookie.host.endsWith('$')) {
75-
return;
76-
}
77-
78-
if (!securecookie.host.startsWith('^.+') &&
79-
!securecookie.host.startsWith('^.*') &&
80-
!securecookie.host.startsWith('.+') &&
81-
!securecookie.host.startsWith('.*') &&
82-
!securecookie.host.startsWith('^(?:.*\\.)?') &&
83-
!securecookie.host.startsWith('^(?:.+\\.)?')) {
84-
return;
85-
}
86-
87-
let hostRegex = new RegExp(securecookie.host);
88-
for (let target of targets) {
89-
if (target.includes('.*')) {
90-
return;
91-
}
92-
93-
target = target.replace('*.', 'www.');
94-
if (!hostRegex.test(target)) {
95-
return;
96-
}
97-
}
98-
99-
let scReSrc = `\n([\t ]*)<securecookie\\s*host=\\s*"([^"]+)"(\\s*)name=\\s*"([^"]+)"\\s*?/>[\t ]*\n`;
100-
let scRe = new RegExp(scReSrc);
101-
let source = content.replace(scRe, '\n$1<securecookie host=".+"$3name="$4" />\n');
102-
103-
fs.writeFileSync(path.join(rulesDir, filename), source, 'utf8');
104-
}
105-
});
52+
let filenames = (await readdir(rulesDir)).filter(fn => fn.endsWith(".xml"));
53+
let filePromises = filenames.map(async filename => {
54+
let content = await readFile(path.join(rulesDir, filename), "utf8");
55+
let { ruleset } = await parseString(content);
56+
57+
let targets = ruleset.target.map(target => target.$.host);
58+
let securecookies = ruleset.securecookie
59+
? ruleset.securecookie.map(sc => sc.$)
60+
: [];
61+
62+
// make sure there is at least one non-trivial securecookie
63+
if (!securecookies.length || securecookies.some(isTrivial)) {
64+
return;
65+
}
66+
67+
for (let securecookie of securecookies) {
68+
if (!securecookie.name === ".+") {
69+
return;
70+
}
71+
72+
if (
73+
!securecookie.host.startsWith("^.+") &&
74+
!securecookie.host.startsWith("^.*") &&
75+
!securecookie.host.startsWith(".+") &&
76+
!securecookie.host.startsWith(".*") &&
77+
!securecookie.host.startsWith("(?:.*\\.)?") &&
78+
!securecookie.host.startsWith("(?:.+\\.)?") &&
79+
!securecookie.host.startsWith("^(?:.*\\.)?") &&
80+
!securecookie.host.startsWith("^(?:.+\\.)?")
81+
) {
82+
return;
83+
}
84+
}
85+
86+
// make sure each domains and its subdomains are covered by at least
87+
// one securecookie rule
88+
let securedDomains = new Map();
89+
for (let target of targets) {
90+
// we cannot handle the right-wildcards based on the argument above
91+
if (target.includes(".*")) {
92+
return;
93+
}
94+
// replace left-wildcard with www is an implementation detail
95+
// see https://github.com/EFForg/https-everywhere/blob/
96+
// 260cd8d402fb8069c55cda311e1be7a60db7339d/chromium/background-scripts/background.js#L595
97+
if (target.includes("*.")) {
98+
target = target.replace("*.", "www.");
99+
}
100+
securedDomains.set(target, false);
101+
securedDomains.set("." + target, false);
102+
}
103+
104+
for (let securecookie of securecookies) {
105+
let pattern = new RegExp(securecookie.host);
106+
securedDomains.forEach((val, key, map) => {
107+
if (pattern.test(key)) {
108+
map.set(key, true);
109+
}
110+
});
111+
}
112+
113+
// If any value of ${securedDomains} is false, return.
114+
if (![...securedDomains.values()].every(secured => secured)) {
115+
return;
116+
}
117+
118+
// remove the securecookie tag except the last one
119+
// replace the last securecookie tag with a trivial one
120+
for (let occurrence = securecookies.length; occurrence; --occurrence) {
121+
content = content.replace(
122+
secureCookieRegExp,
123+
occurrence == 1 ? trivialSecureCookieLiteral : ""
124+
);
125+
}
126+
127+
return new Promise((resolve, reject) => {
128+
fs.writeFile(path.join(rulesDir, filename), content, "utf8", (err) => {
129+
if (err) {
130+
reject(err);
131+
}
132+
resolve();
106133
});
107134
})
108-
.catch(error => {
109-
console.log(error);
110-
});
111-
112-
await Promise.all(readFilePromises)
113-
.catch(error => {
114-
console.log(error);
115-
});
135+
});
136+
137+
138+
// use for-loop to await too many file opened error
139+
for (let fp of filePromises) {
140+
await fp.catch(error => console.log(error));
141+
}
116142
})();

0 commit comments

Comments
 (0)