diff options
author | Ruben Rodriguez <ruben@gnu.org> | 2015-10-10 11:25:26 -0500 |
---|---|---|
committer | Ruben Rodriguez <ruben@gnu.org> | 2015-10-12 23:20:54 -0500 |
commit | 02da35db344679e666459b1e84946c7c9e5ba464 (patch) | |
tree | 6019eec51cd9b95256d9d66eb02dee9829f1624f /data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js | |
parent | 50557a44e8257abc03913ed5b0828e9fa509dc85 (diff) |
HTTPS-Everywhere updated to 5.1.1
Diffstat (limited to 'data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js')
-rw-r--r-- | data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js | 776 |
1 files changed, 776 insertions, 0 deletions
diff --git a/data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js b/data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js new file mode 100644 index 0000000..f100a79 --- /dev/null +++ b/data/extensions/https-everywhere-eff@eff.org/chrome/content/code/HTTPSRules.js @@ -0,0 +1,776 @@ +// Compilation of RegExps is now delayed until they are first used... + +function Rule(from, to) { + this.to = to; + this.from_c = from; // This will become a RegExp after compilation +} + +function Exclusion(pattern) { + this.pattern_c = pattern; // Will become a RegExp after compilation +} + +function CookieRule(host, cookiename) { + this.host = host; + this.name = cookiename; + + // These will be made during compilation: + + //this.host_c = new RegExp(host); + //this.name_c = new RegExp(cookiename); +} + +function RuleSet(id, name, xmlName, match_rule, default_off, platform) { + if(xmlName == "WordPress.xml" || xmlName == "Github.xml") { + this.log(NOTE, "RuleSet( name="+name+", xmlName="+xmlName+", match_rule="+match_rule+", default_off="+default_off+", platform="+platform+" )"); + } + + this.id=id; + this.on_by_default = true; + this.compiled = false; + this.name = name; + this.xmlName = xmlName; + this.notes = ""; + + if (match_rule) this.ruleset_match_c = new RegExp(match_rule); + else this.ruleset_match_c = null; + if (default_off) { + // Perhaps problematically, this currently ignores the actual content of + // the default_off XML attribute. Ideally we'd like this attribute to be + // "valueless" + this.notes = default_off; + this.on_by_default = false; + } + if (platform) + if (platform.search(HTTPSRules.localPlatformRegexp) == -1) { + this.on_by_default = false; + this.notes = "Only for " + platform; + } + + this.rules = []; + this.exclusions = []; + this.cookierules = []; + + this.rule_toggle_prefs = HTTPSEverywhere.instance.rule_toggle_prefs; + + try { + // if this pref exists, use it + this.active = this.rule_toggle_prefs.getBoolPref(name); + } catch(e) { + // if not, use the default + this.active = this.on_by_default; + } +} + +var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); + +RuleSet.prototype = { + + ensureCompiled: function() { + // Postpone compilation of exclusions, rules and cookies until now, to accelerate + // browser load time. + if (this.compiled) return; + var i; + + for (i = 0; i < this.exclusions.length; ++i) { + this.exclusions[i].pattern_c = new RegExp(this.exclusions[i].pattern_c); + } + for (i = 0; i < this.rules.length; ++i) { + this.rules[i].from_c = new RegExp(this.rules[i].from_c); + } + + for (i = 0; i < this.cookierules.length; i++) { + var cr = this.cookierules[i]; + cr.host_c = new RegExp(cr.host); + cr.name_c = new RegExp(cr.name); + } + + this.compiled = true; + }, + + apply: function(urispec) { + // return null if it does not apply + // and the new url if it does apply + var i; + var returl = null; + this.ensureCompiled(); + // If a rulset has a match_rule and it fails, go no further + if (this.ruleset_match_c && !this.ruleset_match_c.test(urispec)) { + this.log(VERB, "ruleset_match_c excluded " + urispec); + return null; + } + // Even so, if we're covered by an exclusion, go home + for (i = 0; i < this.exclusions.length; ++i) { + if (this.exclusions[i].pattern_c.test(urispec)) { + this.log(DBUG,"excluded uri " + urispec); + return null; + } + } + // Okay, now find the first rule that triggers + for (i = 0; i < this.rules.length; ++i) { + // This is just for displaying inactive rules + returl = urispec.replace(this.rules[i].from_c, this.rules[i].to); + if (returl != urispec) { + // we rewrote the uri + this.log(DBUG, "Rewrote " + urispec + " -> " + returl + " using " + this.xmlName + ": " + this.rules[i].from_c + " -> " + this.rules[i].to); + return returl; + } + } + + return null; + }, + log: function(level, msg) { + https_everywhereLog(level, msg); + }, + + wouldMatch: function(hypothetical_uri, alist) { + // return true if this ruleset would match the uri, assuming it were http + // used for judging moot / inactive rulesets + // alist is optional + + // if the ruleset is already somewhere in this applicable list, we don't + // care about hypothetical wouldMatch questions + if (alist && (this.name in alist.all)) return false; + + this.log(DBUG,"Would " +this.name + " match " +hypothetical_uri.spec + + "? serial " + (alist && alist.serial)); + + var uri = hypothetical_uri.clone(); + if (uri.scheme == "https") uri.scheme = "http"; + var urispec = uri.spec; + + this.ensureCompiled(); + + if (this.ruleset_match_c && !this.ruleset_match_c.test(urispec)) + return false; + + for (var i = 0; i < this.exclusions.length; ++i) + if (this.exclusions[i].pattern_c.test(urispec)) return false; + + for (var i = 0; i < this.rules.length; ++i) + if (this.rules[i].from_c.test(urispec)) return true; + return false; + }, + + transformURI: function(uri) { + // If no rule applies, return null; if a rule would have applied but was + // inactive, return 0; otherwise, return a fresh uri instance + // for the target + var newurl = this.apply(uri.spec); + if (null == newurl) + return null; + var newuri = Components.classes["@mozilla.org/network/standard-url;1"]. + createInstance(CI.nsIStandardURL); + newuri.init(CI.nsIStandardURL.URLTYPE_STANDARD, 80, + newurl, uri.originCharset, null); + newuri = newuri.QueryInterface(CI.nsIURI); + return newuri; + }, + + enable: function() { + // Enable us. + this.rule_toggle_prefs.setBoolPref(this.name, true); + this.active = true; + }, + + disable: function() { + // Disable us. + this.rule_toggle_prefs.setBoolPref(this.name, false); + this.active = false; + }, + + toggle: function() { + this.active = !this.active; + this.rule_toggle_prefs.setBoolPref(this.name, this.active); + }, + + clear: function() { + try { + this.rule_toggle_prefs.clearUserPref(this.name); + } catch(e) { + // this ruleset has never been toggled + } + this.active = this.on_by_default; + } +}; + +const RuleWriter = { + + getCustomRuleDir: function() { + var loc = "ProfD"; // profile directory + var file = + CC["@mozilla.org/file/directory_service;1"] + .getService(CI.nsIProperties) + .get(loc, CI.nsILocalFile) + .clone(); + file.append("HTTPSEverywhereUserRules"); + // Check for existence, if not, create. + if (!file.exists()) { + file.create(CI.nsIFile.DIRECTORY_TYPE, 0700); + } + if (!file.isDirectory()) { + // XXX: Arg, death! + } + return file; + }, + + chromeToPath: function (aPath) { + if (!aPath || !(/^chrome:/.test(aPath))) + return; //not a chrome url + + var ios = + CC['@mozilla.org/network/io-service;1'] + .getService(CI.nsIIOService); + var uri = ios.newURI(aPath, "UTF-8", null); + var cr = + CC['@mozilla.org/chrome/chrome-registry;1'] + .getService(CI.nsIChromeRegistry); + var rv = cr.convertChromeURL(uri).spec; + + if (/^file:/.test(rv)) + rv = this.urlToPath(rv); + else + rv = this.urlToPath("file://"+rv); + + return rv; + }, + + urlToPath: function (aPath) { + if (!aPath || !/^file:/.test(aPath)) + return ; + + var ph = + CC["@mozilla.org/network/protocol;1?name=file"] + .createInstance(CI.nsIFileProtocolHandler); + var rv = ph.getFileFromURLSpec(aPath).path; + + return rv; + }, + + read: function(file) { + if (!file.exists()) + return null; + var data = ""; + var fstream = CC["@mozilla.org/network/file-input-stream;1"] + .createInstance(CI.nsIFileInputStream); + var sstream = CC["@mozilla.org/scriptableinputstream;1"] + .createInstance(CI.nsIScriptableInputStream); + fstream.init(file, -1, 0, 0); + sstream.init(fstream); + + var str = sstream.read(4096); + while (str.length > 0) { + data += str; + str = sstream.read(4096); + } + + sstream.close(); + fstream.close(); + return data; + }, + + write: function(file, data) { + //if (!file.exists()) + // return null; + this.log(DBUG, "Opening " + file.path + " for writing"); + var fstream = CC["@mozilla.org/network/file-output-stream;1"] + .createInstance(CI.nsIFileOutputStream); + fstream.init(file, -1, -1, 0); + + var retval = fstream.write(data, data.length); + this.log(DBUG, "Got retval " + retval); + fstream.close(); + return data; + }, + + rulesetFromFile: function(file, rule_store, ruleset_id) { + if ((rule_store.targets == null) && (rule_store.targets != {})) + this.log(WARN, "TARGETS IS NULL"); + var data = this.read(file); + if (!data) return null; + return this.readFromString(data, rule_store, ruleset_id); + }, + + readFromString: function(data, rule_store, ruleset_id) { + try { + var xmlruleset = dom_parser.parseFromString(data, "text/xml"); + } catch(e) { // file has been corrupted; XXX: handle error differently + this.log(WARN,"Error in XML data: " + e + "\n" + data); + return null; + } + this.parseOneRuleset(xmlruleset.documentElement, rule_store, ruleset_id); + }, + + parseOneRuleset: function(xmlruleset, rule_store, ruleset_id) { + // Extract an xmlruleset into the rulestore + if (!xmlruleset.getAttribute("name")) { + this.log(WARN, "This blob: '" + xmlruleset + "' is not a ruleset\n"); + return null; + } + + this.log(DBUG, "Parsing " + xmlruleset.getAttribute("name")); + + var match_rl = xmlruleset.getAttribute("match_rule"); + var dflt_off = xmlruleset.getAttribute("default_off"); + var platform = xmlruleset.getAttribute("platform"); + var rs = new RuleSet(ruleset_id, xmlruleset.getAttribute("name"), xmlruleset.getAttribute("f"), match_rl, dflt_off, platform); + + // see if this ruleset has the same name as an existing ruleset; + // if so, this ruleset is ignored; DON'T add or return it. + if (rs.name in rule_store.rulesetsByName) { + this.log(WARN, "Error: found duplicate rule name " + rs.name); + return null; + } + + // Add this ruleset id into HTTPSRules.targets if it's not already there. + // This should only happen for custom user rules. Built-in rules get + // their ids preloaded into the targets map, and have their <target> + // tags stripped when the sqlite database is built. + var targets = xmlruleset.getElementsByTagName("target"); + for (var i = 0; i < targets.length; i++) { + var host = targets[i].getAttribute("host"); + if (!host) { + this.log(WARN, "<target> missing host in " + xmlruleset.getAttribute("name")); + return null; + } + if (! rule_store.targets[host]) + rule_store.targets[host] = []; + this.log(DBUG, "Adding " + host + " to targets, pointing at " + ruleset_id); + rule_store.targets[host].push(ruleset_id); + } + + var exclusions = xmlruleset.getElementsByTagName("exclusion"); + for (var i = 0; i < exclusions.length; i++) { + var exclusion = new Exclusion(exclusions[i].getAttribute("pattern")); + rs.exclusions.push(exclusion); + } + + var rules = xmlruleset.getElementsByTagName("rule"); + for (var i = 0; i < rules.length; i++) { + var rule = new Rule(rules[i].getAttribute("from"), + rules[i].getAttribute("to")); + rs.rules.push(rule); + } + + var securecookies = xmlruleset.getElementsByTagName("securecookie"); + for (var i = 0; i < securecookies.length; i++) { + var c_rule = new CookieRule(securecookies[i].getAttribute("host"), + securecookies[i].getAttribute("name")); + rs.cookierules.push(c_rule); + this.log(DBUG,"Cookie rule "+ c_rule.host+ " " +c_rule.name); + } + + rule_store.rulesets.push(rs); + rule_store.rulesetsByID[rs.id] = rs; + rule_store.rulesetsByName[rs.name] = rs; + }, + + enumerate: function(dir) { + // file is the given directory (nsIFile) + var entries = dir.directoryEntries; + var ret = []; + while(entries.hasMoreElements()) { + var entry = entries.getNext(); + entry.QueryInterface(Components.interfaces.nsIFile); + ret.push(entry); + } + return ret; + }, +}; + + + +const HTTPSRules = { + init: function() { + try { + this.rulesets = []; + this.targets = {}; // dict mapping target host pattern -> list of + // applicable ruleset ids + this.rulesetsByID = {}; + this.rulesetsByName = {}; + var t1 = new Date().getTime(); + this.checkMixedContentHandling(); + var rulefiles = RuleWriter.enumerate(RuleWriter.getCustomRuleDir()); + this.scanRulefiles(rulefiles); + + // Initialize database connection. + var dbFile = new FileUtils.File(RuleWriter.chromeToPath("chrome://https-everywhere/content/rulesets.sqlite")); + var rulesetDBConn = Services.storage.openDatabase(dbFile); + this.queryForRuleset = rulesetDBConn.createStatement( + "select contents from rulesets where id = :id"); + + // Preload the mapping of hostname target -> ruleset ID from DB. + // This is a little slow (287 ms on a Core2 Duo @ 2.2GHz with SSD), + // but is faster than loading all of the rulesets. If this becomes a + // bottleneck, change it to load in a background webworker, or load + // a smaller bloom filter instead. + var targetsQuery = rulesetDBConn.createStatement("select host, ruleset_id from targets"); + this.log(DBUG, "Loading targets..."); + while (targetsQuery.executeStep()) { + var host = targetsQuery.row.host; + var id = targetsQuery.row.ruleset_id; + if (!this.targets[host]) { + this.targets[host] = [id]; + } else { + this.targets[host].push(id); + } + } + this.log(DBUG, "Loading adding targets."); + } catch(e) { + this.log(DBUG,"Rules Failed: "+e); + } + var t2 = new Date().getTime(); + this.log(NOTE,"Loading targets took " + (t2 - t1) / 1000.0 + " seconds"); + + return; + }, + + checkMixedContentHandling: function() { + // Firefox 23+ blocks mixed content by default, so rulesets that create + // mixed content situations should be disabled there + var appInfo = CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo); + var platformVer = appInfo.platformVersion; + var versionChecker = CC["@mozilla.org/xpcom/version-comparator;1"] + .getService(CI.nsIVersionComparator); + var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService).getBranch(""); + + + // If mixed content is present and enabled, and the user hasn't opted to enable + // mixed content triggering rules, leave them out. Otherwise add them in. + if(versionChecker.compare(appInfo.version, "23.0a1") >= 0 + && prefs.getBoolPref("security.mixed_content.block_active_content") + && !prefs.getBoolPref("extensions.https_everywhere.enable_mixed_rulesets")) { + this.log(INFO, "Not activating rules that trigger mixed content errors."); + this.localPlatformRegexp = new RegExp("firefox"); + } else { + this.log(INFO, "Activating rules that would normally trigger mixed content"); + this.localPlatformRegexp = new RegExp("(firefox|mixedcontent)"); + } + }, + + scanRulefiles: function(rulefiles) { + var i = 0; + var r = null; + for(i = 0; i < rulefiles.length; ++i) { + try { + this.log(DBUG,"Loading ruleset file: "+rulefiles[i].path); + var ruleset_id = "custom_" + i; + RuleWriter.rulesetFromFile(rulefiles[i], this, ruleset_id); + } catch(e) { + this.log(WARN, "Error in ruleset file: " + e); + if (e.lineNumber) + this.log(WARN, "(line number: " + e.lineNumber + ")"); + } + } + }, + + resetRulesetsToDefaults: function() { + // Callable from within the prefs UI and also for cleaning up buggy + // configurations... + for (var i in this.rulesets) { + this.rulesets[i].clear(); + } + }, + + + rewrittenURI: function(alist, input_uri) { + // This function oversees the task of working out if a uri should be + // rewritten, what it should be rewritten to, and recordkeeping of which + // applicable rulesets are and aren't active. Previously this returned + // the new uri if there was a rewrite. Now it returns a JS object with a + // newuri attribute and an applied_ruleset attribute (or null if there's + // no rewrite). + var i = 0; + userpass_present = false; // Global so that sanitiseURI can tweak it. + // Why does JS have no tuples, again? + var blob = {}; blob.newuri = null; + if (!alist) this.log(DBUG, "No applicable list rewriting " + input_uri.spec); + this.log(DBUG, "Processing " + input_uri.spec); + + var uri = this.sanitiseURI(input_uri); + + // Get the list of rulesets that target this host + try { + var rs = this.potentiallyApplicableRulesets(uri.host); + } catch(e) { + this.log(NOTE, 'Could not check applicable rules for '+uri.spec + '\n'+e); + return null; + } + + // ponder each potentially applicable ruleset, working out if it applies + // and recording it as active/inactive/moot/breaking in the applicable list + for (i = 0; i < rs.length; ++i) { + if (!rs[i].active) { + if (alist && rs[i].wouldMatch(uri, alist)) + alist.inactive_rule(rs[i]); + continue; + } + blob.newuri = rs[i].transformURI(uri); + if (blob.newuri) { + if (alist) { + if (uri.spec in https_everywhere_blacklist) + alist.breaking_rule(rs[i]); + else + alist.active_rule(rs[i]); + } + if (userpass_present) blob.newuri.userPass = input_uri.userPass; + blob.applied_ruleset = rs[i]; + return blob; + } + if (uri.scheme == "https" && alist) { + // we didn't rewrite but the rule applies to this domain and the + // requests are going over https + if (rs[i].wouldMatch(uri, alist)) alist.moot_rule(rs[i]); + continue; + } + } + return null; + }, + + sanitiseURI: function(input_uri) { + // Rulesets shouldn't try to parse usernames and passwords. If we find + // those, apply the ruleset without them (and then add them back later). + // When .userPass is absent, sometimes it is false and sometimes trying + // to read it raises an exception (probably depending on the URI type). + var uri = input_uri; + try { + if (input_uri.userPass) { + uri = input_uri.clone(); + userpass_present = true; // tweaking a global in our caller :( + uri.userPass = null; + } + } catch(e) {} + + // example.com. is equivalent to example.com + // example.com.. is invalid, but firefox would load it anyway + try { + if (uri.host) + try { + var h = uri.host; + if (h.charAt(h.length - 1) == ".") { + while (h.charAt(h.length - 1) == ".") + h = h.slice(0,-1); + uri = uri.clone(); + uri.host = h; + } + } catch(e) { + this.log(WARN, "Failed to normalise domain: "); + try {this.log(WARN, input_uri.host);} + catch(e2) {this.log(WARN, "bang" + e + " & " + e2 + " & "+ input_uri);} + } + } catch(e3) { + this.log(INFO, "uri.host is explosive!"); + try { this.log(INFO, "(" + uri.spec + ")"); } // happens for about: uris and soforth + catch(e4) { this.log(WARN, "(and unprintable!!!!!!)"); } + } + return uri; + }, + + setInsert: function(intoList, fromList) { + // Insert any elements from fromList into intoList, if they are not + // already there. fromList may be null. + if (!fromList) return; + for (var i = 0; i < fromList.length; i++) + if (intoList.indexOf(fromList[i]) == -1) + intoList.push(fromList[i]); + }, + + // Load a ruleset by numeric id, e.g. 234 + // NOTE: This call runs synchronously, which can lock up the browser UI. Is + // there any way to fix that, given that we need to run blocking in the request + // flow? Perhaps we can preload all targets from the DB into memory at startup + // so we only hit the DB when we know there is something to be had. + loadRulesetById: function(ruleset_id) { + this.queryForRuleset.params.id = ruleset_id; + + try { + if (this.queryForRuleset.executeStep()) { + RuleWriter.readFromString(this.queryForRuleset.row.contents, this, ruleset_id); + } else { + this.log(WARN,"Couldn't find ruleset for id " + ruleset_id); + } + } finally { + this.queryForRuleset.reset(); + } + }, + + // Get all rulesets matching a given target, lazy-loading from DB as necessary. + rulesetsByTarget: function(target) { + var rulesetIds = this.targets[target]; + + var output = []; + if (rulesetIds) { + this.log(INFO, "For target " + target + ", found ids " + rulesetIds.toString()); + for (var i = 0; i < rulesetIds.length; i++) { + var id = rulesetIds[i]; + if (!this.rulesetsByID[id]) { + this.loadRulesetById(id); + } + if (this.rulesetsByID[id]) { + output.push(this.rulesetsByID[id]); + } + } + } else { + this.log(DBUG, "For target " + target + ", found no ids in DB"); + } + return output; + }, + + /** + * Return a list of rulesets that declare targets matching a given hostname. + * The returned rulesets include those that are disabled for various reasons. + * This function is only defined for fully-qualified hostnames. Wildcards and + * cookie-style domain attributes with a leading dot are not permitted. + * @param host {string} + * @return {Array.<RuleSet>} + */ + potentiallyApplicableRulesets: function(host) { + var i, tmp, t; + var results = []; + + var attempt = function(target) { + this.setInsert(results, this.rulesetsByTarget(target)); + }.bind(this); + + attempt(host); + + // replace each portion of the domain with a * in turn + var segmented = host.split("."); + for (i = 0; i < segmented.length; ++i) { + tmp = segmented[i]; + if (tmp.length === 0) { + this.log(WARN,"Malformed host passed to potentiallyApplicableRulesets: " + host); + return null; + } + segmented[i] = "*"; + t = segmented.join("."); + segmented[i] = tmp; + attempt(t); + } + // now eat away from the left, with *, so that for x.y.z.google.com we + // check *.z.google.com and *.google.com (we did *.y.z.google.com above) + for (i = 2; i <= segmented.length - 2; ++i) { + t = "*." + segmented.slice(i,segmented.length).join("."); + attempt(t); + } + this.log(DBUG,"Potentially applicable rules for " + host + ":"); + for (i = 0; i < results.length; ++i) + this.log(DBUG, " " + results[i].name); + return results; + }, + + /** + * If a cookie's domain attribute has a leading dot to indicate it should be + * sent for all subdomains (".example.com"), return the actual host part (the + * part after the dot). + * + * @param cookieDomain {string} A cookie domain to strip a leading dot from. + * @return {string} a fully qualified hostname. + */ + hostFromCookieDomain: function(cookieDomain) { + if (cookieDomain.length > 0 && cookieDomain[0] == ".") { + return cookieDomain.slice(1); + } else { + return cookieDomain; + } + }, + + /** + * Check to see if the Cookie object c meets any of our cookierule citeria + * for being marked as secure. + * + * @param applicable_list {ApplicableList} an ApplicableList for record keeping + * @param c {nsICookie2} The cookie we might secure. + * @param known_https {boolean} True if the cookie appeared in an HTTPS request and + * so we know it is okay to mark it secure (assuming a cookierule matches it. + * TODO(jsha): Double-check that the code calling this actually does that. + * @return {boolean} True if the cookie in question should have the 'secure' + * flag set to true. + */ + shouldSecureCookie: function(applicable_list, c, known_https) { + this.log(DBUG," rawhost: " + c.rawHost + " name: " + c.name + " host" + c.host); + var i,j; + // potentiallyApplicableRulesets is defined on hostnames not cookie-style + // "domain" attributes, so we strip a leading dot before calling. + var rs = this.potentiallyApplicableRulesets(this.hostFromCookieDomain(c.host)); + for (i = 0; i < rs.length; ++i) { + var ruleset = rs[i]; + if (ruleset.active) { + ruleset.ensureCompiled(); + // Never secure a cookie if this page might be HTTP + if (!(known_https || this.safeToSecureCookie(c.rawHost))) { + continue; + } + for (j = 0; j < ruleset.cookierules.length; j++) { + var cr = ruleset.cookierules[j]; + if (cr.host_c.test(c.host) && cr.name_c.test(c.name)) { + if (applicable_list) applicable_list.active_rule(ruleset); + this.log(INFO,"Active cookie rule " + ruleset.name); + return true; + } + } + if (ruleset.cookierules.length > 0 && applicable_list) { + applicable_list.moot_rule(ruleset); + } + } else if (ruleset.cookierules.length > 0) { + if (applicable_list) { + applicable_list.inactive_rule(ruleset); + } + this.log(INFO,"Inactive cookie rule " + ruleset.name); + } + } + return false; + }, + + /** + * Check if the domain might be being served over HTTP. If so, it isn't + * safe to secure a cookie! We can't always know this for sure because + * observing cookie-changed doesn't give us enough context to know the + * full origin URI. In particular, if cookies are set from Javascript (as + * opposed to HTTP/HTTPS responses), we don't know what page context that + * Javascript ran in. + + * First, if there are any redirect loops on this domain, don't secure + * cookies. XXX This is not a very satisfactory heuristic. Sometimes we + * would want to secure the cookie anyway, because the URLs that loop are + * not authenticated or not important. Also by the time the loop has been + * observed and the domain blacklisted, a cookie might already have been + * flagged as secure. + * + * @param domain {string} The cookie's 'domain' attribute. + * @return {boolean} True if it's safe to secure a cookie on that domain. + */ + safeToSecureCookie: function(domain) { + if (domain in https_blacklist_domains) { + this.log(INFO, "cookies for " + domain + "blacklisted"); + return false; + } + + // If we passed that test, make up a random URL on the domain, and see if + // we would HTTPSify that. + try { + var nonce_path = "/" + Math.random().toString(); + nonce_path = nonce_path + nonce_path; + var test_uri = "http://" + domain + nonce_path; + } catch (e) { + this.log(WARN, "explosion in safeToSecureCookie for " + domain + "\n" + + "(" + e + ")"); + return false; + } + + this.log(DBUG, "Testing securecookie applicability with " + test_uri); + // potentiallyApplicableRulesets is defined on hostnames not cookie-style + // "domain" attributes, so we strip a leading dot before calling. + var rs = this.potentiallyApplicableRulesets(this.hostFromCookieDomain(domain)); + for (var i = 0; i < rs.length; ++i) { + if (!rs[i].active) continue; + var rewrite = rs[i].apply(test_uri); + if (rewrite) { + this.log(DBUG, "Safe to secure cookie for " + test_uri + ": " + rewrite); + return true; + } + } + this.log(DBUG, "Unsafe to secure cookie for " + test_uri); + return false; + } +}; |