//@line 37 "/builds/slave/linux_build/build/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" // We wastefully reload the same JS files across components. This puts all // the common JS files used by safebrowsing and url-classifier into a // single component. const Cc = Components.classes; const Ci = Components.interfaces; const G_GDEBUG = false; //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/lang.js" /** * lang.js - Some missing JavaScript language features */ /** * Partially applies a function to a particular "this object" and zero or * more arguments. The result is a new function with some arguments of the first * function pre-filled and the value of |this| "pre-specified". * * Remaining arguments specified at call-time are appended to the pre- * specified ones. * * Usage: * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2"); * barMethBound("arg3", "arg4"); * * @param fn {string} Reference to the function to be bound * * @param self {object} Specifies the object which |this| should point to * when the function is run. If the value is null or undefined, it will default * to the global object. * * @returns {function} A partially-applied form of the speficied function. */ function BindToObject(fn, self, opt_args) { var boundargs = fn.boundArgs_ || []; boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length)); if (fn.boundSelf_) self = fn.boundSelf_; if (fn.boundFn_) fn = fn.boundFn_; var newfn = function() { // Combine the static args and the new args into one big array var args = boundargs.concat(Array.slice(arguments)); return fn.apply(self, args); } newfn.boundArgs_ = boundargs; newfn.boundSelf_ = self; newfn.boundFn_ = fn; return newfn; } /** * Inherit the prototype methods from one constructor into another. * * Usage: * * function ParentClass(a, b) { } * ParentClass.prototype.foo = function(a) { } * * function ChildClass(a, b, c) { * ParentClass.call(this, a, b); * } * * ChildClass.inherits(ParentClass); * * var child = new ChildClass("a", "b", "see"); * child.foo(); // works * * In addition, a superclass' implementation of a method can be invoked * as follows: * * ChildClass.prototype.foo = function(a) { * ChildClass.superClass_.foo.call(this, a); * // other code * }; */ Function.prototype.inherits = function(parentCtor) { var tempCtor = function(){}; tempCtor.prototype = parentCtor.prototype; this.superClass_ = parentCtor.prototype; this.prototype = new tempCtor(); } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/preferences.js" // Class for manipulating preferences. Aside from wrapping the pref // service, useful functionality includes: // // - abstracting prefobserving so that you can observe preferences // without implementing nsIObserver // // - getters that return a default value when the pref doesn't exist // (instead of throwing) // // - get-and-set getters // // Example: // // var p = new PROT_Preferences(); // dump(p.getPref("some-true-pref")); // shows true // dump(p.getPref("no-such-pref", true)); // shows true // dump(p.getPref("no-such-pref", null)); // shows null // // function observe(prefThatChanged) { // dump("Pref changed: " + prefThatChanged); // }; // // p.addObserver("somepref", observe); // p.setPref("somepref", true); // dumps // p.removeObserver("somepref", observe); // // TODO: should probably have the prefobserver pass in the new and old // values // TODO(tc): Maybe remove this class and just call natively since we're no // longer an extension. /** * A class that wraps the preferences service. * * @param opt_startPoint A starting point on the prefs tree to resolve * names passed to setPref and getPref. * * @param opt_useDefaultPranch Set to true to work against the default * preferences tree instead of the profile one. * * @constructor */ function G_Preferences(opt_startPoint, opt_getDefaultBranch) { this.debugZone = "prefs"; this.observers_ = {}; this.getDefaultBranch_ = !!opt_getDefaultBranch; this.startPoint_ = opt_startPoint || null; } G_Preferences.setterMap_ = { "string": "setCharPref", "boolean": "setBoolPref", "number": "setIntPref" }; G_Preferences.getterMap_ = {}; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_STRING] = "getCharPref"; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_BOOL] = "getBoolPref"; G_Preferences.getterMap_[Ci.nsIPrefBranch.PREF_INT] = "getIntPref"; G_Preferences.prototype.__defineGetter__('prefs_', function() { var prefs; var prefSvc = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefService); if (this.getDefaultBranch_) { prefs = prefSvc.getDefaultBranch(this.startPoint_); } else { prefs = prefSvc.getBranch(this.startPoint_); } // QI to prefs in case we want to add observers prefs.QueryInterface(Ci.nsIPrefBranchInternal); return prefs; }); /** * Stores a key/value in a user preference. Valid types for val are string, * boolean, and number. Complex values are not yet supported (but feel free to * add them!). */ G_Preferences.prototype.setPref = function(key, val) { var datatype = typeof(val); if (datatype == "number" && (val % 1 != 0)) { throw new Error("Cannot store non-integer numbers in preferences."); } var meth = G_Preferences.setterMap_[datatype]; if (!meth) { throw new Error("Pref datatype {" + datatype + "} not supported."); } return this.prefs_[meth](key, val); } /** * Retrieves a user preference. Valid types for the value are the same as for * setPref. If the preference is not found, opt_default will be returned * instead. */ G_Preferences.prototype.getPref = function(key, opt_default) { var type = this.prefs_.getPrefType(key); // zero means that the specified pref didn't exist if (type == Ci.nsIPrefBranch.PREF_INVALID) { return opt_default; } var meth = G_Preferences.getterMap_[type]; if (!meth) { throw new Error("Pref datatype {" + type + "} not supported."); } // If a pref has been cleared, it will have a valid type but won't // be gettable, so this will throw. try { return this.prefs_[meth](key); } catch(e) { return opt_default; } } /** * Delete a preference. * * @param which Name of preference to obliterate */ G_Preferences.prototype.clearPref = function(which) { try { // This throws if the pref doesn't exist, which is fine because a // non-existent pref is cleared this.prefs_.clearUserPref(which); } catch(e) {} } /** * Add an observer for a given pref. * * @param which String containing the pref to listen to * @param callback Function to be called when the pref changes. This * function will receive a single argument, a string * holding the preference name that changed */ G_Preferences.prototype.addObserver = function(which, callback) { // Need to store the observer we create so we can eventually unregister it if (!this.observers_[which]) this.observers_[which] = { callbacks: [], observers: [] }; /* only add an observer if the callback hasn't been registered yet */ if (this.observers_[which].callbacks.indexOf(callback) == -1) { var observer = new G_PreferenceObserver(callback); this.observers_[which].callbacks.push(callback); this.observers_[which].observers.push(observer); this.prefs_.addObserver(which, observer, false /* strong reference */); } } /** * Remove an observer for a given pref. * * @param which String containing the pref to stop listening to * @param callback Function to remove as an observer */ G_Preferences.prototype.removeObserver = function(which, callback) { var ix = this.observers_[which].callbacks.indexOf(callback); G_Assert(this, ix != -1, "Tried to unregister a nonexistant observer"); this.observers_[which].callbacks.splice(ix, 1); var observer = this.observers_[which].observers.splice(ix, 1)[0]; this.prefs_.removeObserver(which, observer); } /** * Remove all preference observers registered through this object. */ G_Preferences.prototype.removeAllObservers = function() { for (var which in this.observers_) { for each (var observer in this.observers_[which].observers) { this.prefs_.removeObserver(which, observer); } } this.observers_ = {}; } /** * Helper class that knows how to observe preference changes and * invoke a callback when they do * * @constructor * @param callback Function to call when the preference changes */ function G_PreferenceObserver(callback) { this.debugZone = "prefobserver"; this.callback_ = callback; } /** * Invoked by the pref system when a preference changes. Passes the * message along to the callback. * * @param subject The nsIPrefBranch that changed * @param topic String "nsPref:changed" (aka * NS_PREFBRANCH_PREFCHANGE_OBSERVER_ID -- but where does it * live???) * @param data Name of the pref that changed */ G_PreferenceObserver.prototype.observe = function(subject, topic, data) { G_Debug(this, "Observed pref change: " + data); this.callback_(data); } /** * XPCOM cruft * * @param iid Interface id of the interface the caller wants */ G_PreferenceObserver.prototype.QueryInterface = function(iid) { if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver) || iid.equals(Ci.nsISupportsWeakReference)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } //@line 38 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/debug.js" //@line 868 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/debug.js" // Stubs for the debugging aids scattered through this component. // They will be expanded if you compile yourself a debug build. function G_Debug(who, msg) { } function G_Assert(who, condition, msg) { } function G_Error(who, msg) { } var G_debugService = { __noSuchMethod__: function() { } }; //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/alarm.js" // An Alarm fires a callback after a certain amount of time, or at // regular intervals. It's a convenient replacement for // setTimeout/Interval when you don't want to bind to a specific // window. // // The ConditionalAlarm is an Alarm that cancels itself if its callback // returns a value that type-converts to true. // // Example: // // function foo() { dump('hi'); }; // new G_Alarm(foo, 10*1000); // Fire foo in 10 seconds // new G_Alarm(foo, 10*1000, true /*repeat*/); // Fire foo every 10 seconds // new G_Alarm(foo, 10*1000, true, 7); // Fire foo every 10 seconds // // seven times // new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true // // // Fire foo every 10 seconds until foo returns true or until it fires seven // // times, whichever happens first. // new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7); // // TODO: maybe pass an isFinal flag to the callback if they opted to // set maxTimes and this is the last iteration? /** * Set an alarm to fire after a given amount of time, or at specific * intervals. * * @param callback Function to call when the alarm fires * @param delayMS Number indicating the length of the alarm period in ms * @param opt_repeating Boolean indicating whether this should fire * periodically * @param opt_maxTimes Number indicating a maximum number of times to * repeat (obviously only useful when opt_repeating==true) */ function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) { this.debugZone = "alarm"; this.callback_ = callback; this.repeating_ = !!opt_repeating; this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); var type = opt_repeating ? this.timer_.TYPE_REPEATING_SLACK : this.timer_.TYPE_ONE_SHOT; this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null; this.nTimes_ = 0; this.observerServiceObserver_ = new G_ObserverServiceObserver( 'xpcom-shutdown', BindToObject(this.cancel, this)); // Ask the timer to use nsITimerCallback (.notify()) when ready this.timer_.initWithCallback(this, delayMS, type); } /** * Cancel this timer */ G_Alarm.prototype.cancel = function() { if (!this.timer_) { return; } this.timer_.cancel(); // Break circular reference created between this.timer_ and the G_Alarm // instance (this) this.timer_ = null; this.callback_ = null; // We don't need the shutdown observer anymore this.observerServiceObserver_.unregister(); } /** * Invoked by the timer when it fires * * @param timer Reference to the nsITimer which fired (not currently * passed along) */ G_Alarm.prototype.notify = function(timer) { // fire callback and save results var ret = this.callback_(); // If they've given us a max number of times to fire, enforce it this.nTimes_++; if (this.repeating_ && typeof this.maxTimes_ == "number" && this.nTimes_ >= this.maxTimes_) { this.cancel(); } else if (!this.repeating_) { // Clear out the callback closure for TYPE_ONE_SHOT timers this.cancel(); } // We don't cancel/cleanup timers that repeat forever until either // xpcom-shutdown occurs or cancel() is called explicitly. return ret; } G_Alarm.prototype.setDelay = function(delay) { this.timer_.delay = delay; } /** * XPCOM cruft */ G_Alarm.prototype.QueryInterface = function(iid) { if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsITimerCallback)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } /** * An alarm with the additional property that it cancels itself if its * callback returns true. * * For parameter documentation, see G_Alarm */ function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) { G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes); this.debugZone = "conditionalalarm"; } G_ConditionalAlarm.inherits(G_Alarm); /** * Invoked by the timer when it fires * * @param timer Reference to the nsITimer which fired (not currently * passed along) */ G_ConditionalAlarm.prototype.notify = function(timer) { // Call G_Alarm::notify var rv = G_Alarm.prototype.notify.call(this, timer); if (this.repeating_ && rv) { G_Debug(this, "Callback of a repeating alarm returned true; cancelling."); this.cancel(); } } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/cryptohasher.js" // A very thin wrapper around nsICryptoHash. It's not strictly // necessary, but makes the code a bit cleaner and gives us the // opportunity to verify that our implementations give the results that // we expect, for example if we have to interoperate with a server. // // The digest* methods reset the state of the hasher, so it's // necessary to call init() explicitly after them. // // Works only in Firefox 1.5+. // // IMPORTANT NOTE: Due to https://bugzilla.mozilla.org/show_bug.cgi?id=321024 // you cannot use the cryptohasher before app-startup. The symptom of doing // so is a segfault in NSS. /** * Instantiate a new hasher. You must explicitly call init() before use! */ function G_CryptoHasher() { this.debugZone = "cryptohasher"; this.hasher_ = null; } G_CryptoHasher.algorithms = { MD2: Ci.nsICryptoHash.MD2, MD5: Ci.nsICryptoHash.MD5, SHA1: Ci.nsICryptoHash.SHA1, SHA256: Ci.nsICryptoHash.SHA256, SHA384: Ci.nsICryptoHash.SHA384, SHA512: Ci.nsICryptoHash.SHA512, }; /** * Initialize the hasher. This function must be called after every call * to one of the digest* methods. * * @param algorithm Constant from G_CryptoHasher.algorithms specifying the * algorithm this hasher will use */ G_CryptoHasher.prototype.init = function(algorithm) { var validAlgorithm = false; for (var alg in G_CryptoHasher.algorithms) if (algorithm == G_CryptoHasher.algorithms[alg]) validAlgorithm = true; if (!validAlgorithm) throw new Error("Invalid algorithm: " + algorithm); this.hasher_ = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); this.hasher_.init(algorithm); } /** * Update the hash's internal state with input given in a string. Can be * called multiple times for incrementeal hash updates. * * @param input String containing data to hash. */ G_CryptoHasher.prototype.updateFromString = function(input) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); var stream = Cc['@mozilla.org/io/string-input-stream;1'] .createInstance(Ci.nsIStringInputStream); stream.setData(input, input.length); this.updateFromStream(stream); } /** * Update the hash's internal state with input given in an array. Can be * called multiple times for incremental hash updates. * * @param input Array containing data to hash. */ G_CryptoHasher.prototype.updateFromArray = function(input) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); this.hasher_.update(input, input.length); } /** * Update the hash's internal state with input given in a stream. Can be * called multiple times from incremental hash updates. */ G_CryptoHasher.prototype.updateFromStream = function(stream) { if (!this.hasher_) throw new Error("You must initialize the hasher first!"); if (stream.available()) this.hasher_.updateFromStream(stream, stream.available()); } /** * @returns The hash value as a string (sequence of 8-bit values) */ G_CryptoHasher.prototype.digestRaw = function() { var digest = this.hasher_.finish(false /* not b64 encoded */); this.hasher_ = null; return digest; } /** * @returns The hash value as a base64-encoded string */ G_CryptoHasher.prototype.digestBase64 = function() { var digest = this.hasher_.finish(true /* b64 encoded */); this.hasher_ = null; return digest; } /** * @returns The hash value as a hex-encoded string */ G_CryptoHasher.prototype.digestHex = function() { var raw = this.digestRaw(); return this.toHex_(raw); } /** * Converts a sequence of values to a hex-encoded string. The input is a * a string, so you can stick 16-bit values in each character. * * @param str String to conver to hex. (Often this is just a sequence of * 16-bit values) * * @returns String containing the hex representation of the input */ G_CryptoHasher.prototype.toHex_ = function(str) { var hexchars = '0123456789ABCDEF'; var hexrep = new Array(str.length * 2); for (var i = 0; i < str.length; ++i) { hexrep[i * 2] = hexchars.charAt((str.charCodeAt(i) >> 4) & 15); hexrep[i * 2 + 1] = hexchars.charAt(str.charCodeAt(i) & 15); } return hexrep.join(''); } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/observer.js" // A couple of classes to simplify creating observers. // // // Example1: // // function doSomething() { ... } // var observer = new G_ObserverWrapper(topic, doSomething); // someObj.addObserver(topic, observer); // // // Example2: // // function doSomething() { ... } // new G_ObserverServiceObserver("profile-after-change", // doSomething, // true /* run only once */); /** * This class abstracts the admittedly simple boilerplate required of * an nsIObserver. It saves you the trouble of implementing the * indirection of your own observe() function. * * @param topic String containing the topic the observer will filter for * * @param observeFunction Reference to the function to call when the * observer fires * * @constructor */ function G_ObserverWrapper(topic, observeFunction) { this.debugZone = "observer"; this.topic_ = topic; this.observeFunction_ = observeFunction; } /** * XPCOM */ G_ObserverWrapper.prototype.QueryInterface = function(iid) { if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } /** * Invoked by the thingy being observed */ G_ObserverWrapper.prototype.observe = function(subject, topic, data) { if (topic == this.topic_) this.observeFunction_(subject, topic, data); } /** * This class abstracts the admittedly simple boilerplate required of * observing an observerservice topic. It implements the indirection * required, and automatically registers to hear the topic. * * @param topic String containing the topic the observer will filter for * * @param observeFunction Reference to the function to call when the * observer fires * * @param opt_onlyOnce Boolean indicating if the observer should unregister * after it has fired * * @constructor */ function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) { this.debugZone = "observerserviceobserver"; this.topic_ = topic; this.observeFunction_ = observeFunction; this.onlyOnce_ = !!opt_onlyOnce; this.observer_ = new G_ObserverWrapper(this.topic_, BindToObject(this.observe_, this)); this.observerService_ = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); this.observerService_.addObserver(this.observer_, this.topic_, false); } /** * Unregister the observer from the observerservice */ G_ObserverServiceObserver.prototype.unregister = function() { this.observerService_.removeObserver(this.observer_, this.topic_); this.observerService_ = null; } /** * Invoked by the observerservice */ G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) { this.observeFunction_(subject, topic, data); if (this.onlyOnce_) this.unregister(); } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/moz/protocol4.js" // A helper class that knows how to parse from and serialize to // protocol4. This is a simple, historical format used by some Google // interfaces, for example the Toolbar (i.e., ancient services). // // Protocol4 consists of a newline-separated sequence of name/value // pairs (strings). Each line consists of the name, the value length, // and the value itself, all separated by colons. Example: // // foo:6:barbaz\n // fritz:33:issickofdynamicallytypedlanguages\n /** * This class knows how to serialize/deserialize maps to/from their * protocol4 representation. * * @constructor */ function G_Protocol4Parser() { this.debugZone = "protocol4"; this.protocol4RegExp_ = new RegExp("([^:]+):\\d+:(.*)$"); this.newlineRegExp_ = new RegExp("(\\r)?\\n"); } /** * Create a map from a protocol4 string. Silently skips invalid lines. * * @param text String holding the protocol4 representation * * @returns Object as an associative array with keys and values * given in text. The empty object is returned if none * are parsed. */ G_Protocol4Parser.prototype.parse = function(text) { var response = {}; if (!text) return response; // Responses are protocol4: (repeated) name:numcontentbytes:content\n var lines = text.split(this.newlineRegExp_); for (var i = 0; i < lines.length; i++) if (this.protocol4RegExp_.exec(lines[i])) response[RegExp.$1] = RegExp.$2; return response; } /** * Create a protocol4 string from a map (object). Throws an error on * an invalid input. * * @param map Object as an associative array with keys and values * given as strings. * * @returns text String holding the protocol4 representation */ G_Protocol4Parser.prototype.serialize = function(map) { if (typeof map != "object") throw new Error("map must be an object"); var text = ""; for (var key in map) { if (typeof map[key] != "string") throw new Error("Keys and values must be strings"); text += key + ":" + map[key].length + ":" + map[key] + "\n"; } return text; } //@line 53 "/builds/slave/linux_build/build/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Google Safe Browsing. * * The Initial Developer of the Original Code is Google Inc. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Tony Chang (original author) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // This implements logic for stopping requests if the server starts to return // too many errors. If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we // back off for TIMEOUT_INCREMENT minutes. If we get another error // immediately after we restart, we double the timeout and add // TIMEOUT_INCREMENT minutes, etc. // // This is similar to the logic used by the search suggestion service. // HTTP responses that count as an error. We also include any 5xx response // as an error. const HTTP_FOUND = 302; const HTTP_SEE_OTHER = 303; const HTTP_TEMPORARY_REDIRECT = 307; /** * @param maxErrors Number of times to request before backing off. * @param retryIncrement Time (ms) for each retry before backing off. * @param maxRequests Number the number of requests needed to trigger backoff * @param requestPeriod Number time (ms) in which maxRequests have to occur to * trigger the backoff behavior * @param timeoutIncrement Number time (ms) the starting timeout period * we double this time for consecutive errors * @param maxTimeout Number time (ms) maximum timeout period */ function RequestBackoff(maxErrors, retryIncrement, maxRequests, requestPeriod, timeoutIncrement, maxTimeout) { this.MAX_ERRORS_ = maxErrors; this.RETRY_INCREMENT_ = retryIncrement; this.MAX_REQUESTS_ = maxRequests; this.REQUEST_PERIOD_ = requestPeriod; this.TIMEOUT_INCREMENT_ = timeoutIncrement; this.MAX_TIMEOUT_ = maxTimeout; // Queue of ints keeping the time of all requests this.requestTimes_ = []; this.numErrors_ = 0; this.errorTimeout_ = 0; this.nextRequestTime_ = 0; } /** * Reset the object for reuse. */ RequestBackoff.prototype.reset = function() { this.numErrors_ = 0; this.errorTimeout_ = 0; this.nextRequestTime_ = 0; } /** * Check to see if we can make a request. */ RequestBackoff.prototype.canMakeRequest = function() { var now = Date.now(); if (now < this.nextRequestTime_) { return false; } return (this.requestTimes_.length < this.MAX_REQUESTS_ || (now - this.requestTimes_[0]) > this.REQUEST_PERIOD_); } RequestBackoff.prototype.noteRequest = function() { var now = Date.now(); this.requestTimes_.push(now); // We only care about keeping track of MAX_REQUESTS if (this.requestTimes_.length > this.MAX_REQUESTS_) this.requestTimes_.shift(); } RequestBackoff.prototype.nextRequestDelay = function() { return Math.max(0, this.nextRequestTime_ - Date.now()); } /** * Notify this object of the last server response. If it's an error, */ RequestBackoff.prototype.noteServerResponse = function(status) { if (this.isErrorStatus(status)) { this.numErrors_++; if (this.numErrors_ < this.MAX_ERRORS_) this.errorTimeout_ = this.RETRY_INCREMENT_; else if (this.numErrors_ == this.MAX_ERRORS_) this.errorTimeout_ = this.TIMEOUT_INCREMENT_; else this.errorTimeout_ *= 2; this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_); this.nextRequestTime_ = Date.now() + this.errorTimeout_; } else { // Reset error timeout, allow requests to go through. this.reset(); } } /** * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors. * @param status Number http status * @return Boolean true if we consider this http status an error */ RequestBackoff.prototype.isErrorStatus = function(status) { return ((400 <= status && status <= 599) || HTTP_FOUND == status || HTTP_SEE_OTHER == status || HTTP_TEMPORARY_REDIRECT == status); } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/url-crypto-key-manager.js" // This file implements the tricky business of managing the keys for our // URL encryption. The protocol is: // // - Server generates secret key K_S // - Client starts up and requests a new key K_C from the server via HTTPS // - Server generates K_C and WrappedKey, which is K_C encrypted with K_S // - Server resonse with K_C and WrappedKey // - When client wants to encrypt a URL, it encrypts it with K_C and sends // the encrypted URL along with WrappedKey // - Server decrypts WrappedKey with K_S to get K_C, and the URL with K_C // // This is, however, trickier than it sounds for two reasons. First, // we want to keep the number of HTTPS requests to an aboslute minimum // (like 1 or 2 per browser session). Second, the HTTPS request at // startup might fail, for example the user might be offline or a URL // fetch might need to be issued before the HTTPS request has // completed. // // We implement the following policy: // // - Firefox will issue at most two HTTPS getkey requests per session // - Firefox will issue one HTTPS getkey request at startup if more than 24 // hours has passed since the last getkey request. // - Firefox will serialize to disk any key it gets // - Firefox will fall back on this serialized key until it has a // fresh key // - The front-end can respond with a flag in a lookup request that tells // the client to re-key. Firefox will issue a new HTTPS getkey request // at this time if it has only issued one before // We store the user key in this file. The key can be used to verify signed // server updates. const kKeyFilename = "urlclassifierkey3.txt"; /** * A key manager for UrlCrypto. There should be exactly one of these * per appplication, and all UrlCrypto's should share it. This is * currently implemented by having the manager attach itself to the * UrlCrypto's prototype at startup. We could've opted for a global * instead, but I like this better, even though it is spooky action * at a distance. * XXX: Should be an XPCOM service * * @param opt_keyFilename String containing the name of the * file we should serialize keys to/from. Used * mostly for testing. * * @param opt_testing Boolean indicating whether we are testing. If we * are, then we skip trying to read the old key from * file and automatically trying to rekey; presumably * the tester will drive these manually. * * @constructor */ function PROT_UrlCryptoKeyManager(opt_keyFilename, opt_testing) { this.debugZone = "urlcryptokeymanager"; this.testing_ = !!opt_testing; this.clientKey_ = null; // Base64-encoded, as fetched from server this.clientKeyArray_ = null; // Base64-decoded into an array of numbers this.wrappedKey_ = null; // Opaque websafe base64-encoded server key this.rekeyTries_ = 0; this.updating_ = false; // Don't do anything until keyUrl_ is set. this.keyUrl_ = null; this.keyFilename_ = opt_keyFilename ? opt_keyFilename : kKeyFilename; this.onNewKey_ = null; // Convenience properties this.MAX_REKEY_TRIES = PROT_UrlCryptoKeyManager.MAX_REKEY_TRIES; this.CLIENT_KEY_NAME = PROT_UrlCryptoKeyManager.CLIENT_KEY_NAME; this.WRAPPED_KEY_NAME = PROT_UrlCryptoKeyManager.WRAPPED_KEY_NAME; if (!this.testing_) { this.maybeLoadOldKey(); } } // Do ***** NOT ***** set this higher; HTTPS is expensive PROT_UrlCryptoKeyManager.MAX_REKEY_TRIES = 2; // Base pref for keeping track of when we updated our key. // We store the time as seconds since the epoch. PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF = "urlclassifier.keyupdatetime."; // Once every 30 days (interval in seconds) PROT_UrlCryptoKeyManager.KEY_MIN_UPDATE_TIME = 30 * 24 * 60 * 60; // These are the names the server will respond with in protocol4 format PROT_UrlCryptoKeyManager.CLIENT_KEY_NAME = "clientkey"; PROT_UrlCryptoKeyManager.WRAPPED_KEY_NAME = "wrappedkey"; /** * Called to get ClientKey * @returns urlsafe-base64-encoded client key or null if we haven't gotten one. */ PROT_UrlCryptoKeyManager.prototype.getClientKey = function() { return this.clientKey_; } /** * Called by a UrlCrypto to get the current K_C * * @returns Array of numbers making up the client key or null if we * have no key */ PROT_UrlCryptoKeyManager.prototype.getClientKeyArray = function() { return this.clientKeyArray_; } /** * Called by a UrlCrypto to get WrappedKey * * @returns Opaque base64-encoded WrappedKey or null if we haven't * gotten one */ PROT_UrlCryptoKeyManager.prototype.getWrappedKey = function() { return this.wrappedKey_; } /** * Change the key url. When we do this, we go ahead and rekey. * @param keyUrl String */ PROT_UrlCryptoKeyManager.prototype.setKeyUrl = function(keyUrl) { // If it's the same key url, do nothing. if (keyUrl == this.keyUrl_) return; this.keyUrl_ = keyUrl; this.rekeyTries_ = 0; // Check to see if we should make a new getkey request. var prefs = new G_Preferences(PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF); var nextRekey = prefs.getPref(this.getPrefName_(this.keyUrl_), 0); if (nextRekey < parseInt(Date.now() / 1000, 10)) { this.reKey(); } } /** * Given a url, return the pref value to use (pref contains last update time). * We basically use the url up until query parameters. This avoids duplicate * pref entries as version number changes over time. * @param url String getkey URL */ PROT_UrlCryptoKeyManager.prototype.getPrefName_ = function(url) { var queryParam = url.indexOf("?"); if (queryParam != -1) { return url.substring(0, queryParam); } return url; } /** * Tell the manager to re-key. For safety, this method still obeys the * max-tries limit. Clients should generally use maybeReKey() if they * want to try a re-keying: it's an error to call reKey() after we've * hit max-tries, but not an error to call maybeReKey(). */ PROT_UrlCryptoKeyManager.prototype.reKey = function() { if (this.updating_) { G_Debug(this, "Already re-keying, ignoring this request"); return true; } if (this.rekeyTries_ > this.MAX_REKEY_TRIES) throw new Error("Have already rekeyed " + this.rekeyTries_ + " times"); this.rekeyTries_++; G_Debug(this, "Attempting to re-key"); // If the keyUrl isn't set, we don't do anything. if (!this.testing_ && this.keyUrl_) { this.fetcher_ = new PROT_XMLFetcher(); this.fetcher_.get(this.keyUrl_, BindToObject(this.onGetKeyResponse, this)); this.updating_ = true; // Calculate the next time we're allowed to re-key. var prefs = new G_Preferences(PROT_UrlCryptoKeyManager.NEXT_REKEY_PREF); var nextRekey = parseInt(Date.now() / 1000, 10) + PROT_UrlCryptoKeyManager.KEY_MIN_UPDATE_TIME; prefs.setPref(this.getPrefName_(this.keyUrl_), nextRekey); } } /** * Try to re-key if we haven't already hit our limit. It's OK to call * this method multiple times, even if we've already tried to rekey * more than the max. It will simply refuse to do so. * * @returns Boolean indicating if it actually issued a rekey request (that * is, if we haven' already hit the max) */ PROT_UrlCryptoKeyManager.prototype.maybeReKey = function() { if (this.rekeyTries_ > this.MAX_REKEY_TRIES) { G_Debug(this, "Not re-keying; already at max"); return false; } this.reKey(); return true; } /** * Drop the existing set of keys. Resets the rekeyTries variable to * allow a rekey to succeed. */ PROT_UrlCryptoKeyManager.prototype.dropKey = function() { this.rekeyTries_ = 0; this.replaceKey_(null, null); } /** * @returns Boolean indicating if we have a key we can use */ PROT_UrlCryptoKeyManager.prototype.hasKey = function() { return this.clientKey_ != null && this.wrappedKey_ != null; } PROT_UrlCryptoKeyManager.prototype.unUrlSafe = function(key) { return key ? key.replace("-", "+").replace("_", "/") : ""; } /** * Set a new key and serialize it to disk. * * @param clientKey String containing the base64-encoded client key * we wish to use * * @param wrappedKey String containing the opaque base64-encoded WrappedKey * the server gave us (i.e., K_C encrypted with K_S) */ PROT_UrlCryptoKeyManager.prototype.replaceKey_ = function(clientKey, wrappedKey) { if (this.clientKey_) G_Debug(this, "Replacing " + this.clientKey_ + " with " + clientKey); this.clientKey_ = clientKey; this.clientKeyArray_ = Array.map(atob(this.unUrlSafe(clientKey)), function(c) { return c.charCodeAt(0); }); this.wrappedKey_ = wrappedKey; this.serializeKey_(this.clientKey_, this.wrappedKey_); if (this.onNewKey_) { this.onNewKey_(); } } /** * Try to write the key to disk so we can fall back on it. Fail * silently if we cannot. The keys are serialized in protocol4 format. * * @returns Boolean indicating whether we succeeded in serializing */ PROT_UrlCryptoKeyManager.prototype.serializeKey_ = function() { var map = {}; map[this.CLIENT_KEY_NAME] = this.clientKey_; map[this.WRAPPED_KEY_NAME] = this.wrappedKey_; try { var keyfile = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties) .get("ProfD", Ci.nsILocalFile); /* profile directory */ keyfile.append(this.keyFilename_); if (!this.clientKey_ || !this.wrappedKey_) { keyfile.remove(true); return; } var data = (new G_Protocol4Parser()).serialize(map); try { var stream = Cc["@mozilla.org/network/file-output-stream;1"] .createInstance(Ci.nsIFileOutputStream); stream.init(keyfile, 0x02 | 0x08 | 0x20 /* PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE */, -1 /* default perms */, 0 /* no special behavior */); stream.write(data, data.length); } finally { stream.close(); } return true; } catch(e) { G_Error(this, "Failed to serialize new key: " + e); return false; } } /** * Invoked when we've received a protocol4 response to our getkey * request. Try to parse it and set this key as the new one if we can. * * @param responseText String containing the protocol4 getkey response */ PROT_UrlCryptoKeyManager.prototype.onGetKeyResponse = function(responseText) { var response = (new G_Protocol4Parser).parse(responseText); var clientKey = response[this.CLIENT_KEY_NAME]; var wrappedKey = response[this.WRAPPED_KEY_NAME]; this.updating_ = false; this.fetcher_ = null; if (response && clientKey && wrappedKey) { G_Debug(this, "Got new key from: " + responseText); this.replaceKey_(clientKey, wrappedKey); } else { G_Debug(this, "Not a valid response for /newkey"); } } /** * Set the callback to be called whenever we get a new key. * * @param callback The callback. */ PROT_UrlCryptoKeyManager.prototype.onNewKey = function(callback) { this.onNewKey_ = callback; } /** * Attempt to read a key we've previously serialized from disk, so * that we can fall back on it in case we can't get one from the * server. If we get a key, only use it if we don't already have one * (i.e., if our startup HTTPS request died or hasn't yet completed). * * This method should be invoked early, like when the user's profile * becomes available. */ PROT_UrlCryptoKeyManager.prototype.maybeLoadOldKey = function() { var oldKey = null; try { var keyfile = Cc["@mozilla.org/file/directory_service;1"] .getService(Ci.nsIProperties) .get("ProfD", Ci.nsILocalFile); /* profile directory */ keyfile.append(this.keyFilename_); if (keyfile.exists()) { try { var fis = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); fis.init(keyfile, 0x01 /* PR_RDONLY */, 0444, 0); var stream = Cc["@mozilla.org/scriptableinputstream;1"] .createInstance(Ci.nsIScriptableInputStream); stream.init(fis); oldKey = stream.read(stream.available()); } finally { if (stream) stream.close(); } } } catch(e) { G_Debug(this, "Caught " + e + " trying to read keyfile"); return; } if (!oldKey) { G_Debug(this, "Couldn't find old key."); return; } oldKey = (new G_Protocol4Parser).parse(oldKey); var clientKey = oldKey[this.CLIENT_KEY_NAME]; var wrappedKey = oldKey[this.WRAPPED_KEY_NAME]; if (oldKey && clientKey && wrappedKey && !this.hasKey()) { G_Debug(this, "Read old key from disk."); this.replaceKey_(clientKey, wrappedKey); } } PROT_UrlCryptoKeyManager.prototype.shutdown = function() { if (this.fetcher_) { this.fetcher_.cancel(); this.fetcher_ = null; } } //@line 36 "/builds/slave/linux_build/build/toolkit/components/url-classifier/content/xml-fetcher.js" // A simple class that encapsulates a request. You'll notice the // style here is different from the rest of the extension; that's // because this was re-used from really old code we had. At some // point it might be nice to replace this with something better // (e.g., something that has explicit onerror handler, ability // to set headers, and so on). // // The only interesting thing here is its ability to strip cookies // from the request. /** * Because we might be in a component, we can't just assume that * XMLHttpRequest exists. So we use this tiny factory function to wrap the * XPCOM version. * * @return XMLHttpRequest object */ function PROT_NewXMLHttpRequest() { var Cc = Components.classes; var Ci = Components.interfaces; var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); // Need the following so we get onerror/load/progresschange request.QueryInterface(Ci.nsIJSXMLHttpRequest); return request; } /** * A helper class that does HTTP GETs and calls back a function with * the content it receives. Asynchronous, so uses a closure for the * callback. * * @param opt_stripCookies Boolean indicating whether we should strip * cookies from this request * * @constructor */ function PROT_XMLFetcher(opt_stripCookies) { this.debugZone = "xmlfetcher"; this._request = PROT_NewXMLHttpRequest(); this._stripCookies = !!opt_stripCookies; } PROT_XMLFetcher.prototype = { /** * Function that will be called back upon fetch completion. */ _callback: null, /** * Fetches some content. * * @param page URL to fetch * @param callback Function to call back when complete. */ get: function(page, callback) { this._request.abort(); // abort() is asynchronous, so this._request = PROT_NewXMLHttpRequest(); this._callback = callback; var asynchronous = true; this._request.open("GET", page, asynchronous); this._request.channel.notificationCallbacks = this; if (this._stripCookies) new PROT_CookieStripper(this._request.channel); // Create a closure var self = this; this._request.onreadystatechange = function() { self.readyStateChange(self); } this._request.send(null); }, cancel: function() { this._request.onreadystatechange = null; this._request.abort(); this._request = null; }, /** * Called periodically by the request to indicate some state change. 4 * means content has been received. */ readyStateChange: function(fetcher) { if (fetcher._request.readyState != 4) return; // If the request fails, on trunk we get status set to // NS_ERROR_NOT_AVAILABLE. On 1.8.1 branch we get an exception // forwarded from nsIHttpChannel::GetResponseStatus. To be consistent // between branch and trunk, we send back NS_ERROR_NOT_AVAILABLE for // http failures. var responseText = null; var status = Components.results.NS_ERROR_NOT_AVAILABLE; try { G_Debug(this, "xml fetch status code: \"" + fetcher._request.status + "\""); status = fetcher._request.status; responseText = fetcher._request.responseText; } catch(e) { G_Debug(this, "Caught exception trying to read xmlhttprequest " + "status/response."); G_Debug(this, e); } if (fetcher._callback) fetcher._callback(responseText, status); }, // Suppress any certificate errors notifyCertProblem: function(socketInfo, status, targetSite) { return true; }, // Suppress any ssl errors notifySSLError: function(socketInfo, error, targetSite) { return true; }, // nsIInterfaceRequestor getInterface: function(iid) { return this.QueryInterface(iid); }, QueryInterface: function(iid) { if (!iid.equals(Components.interfaces.nsIBadCertListener2) && !iid.equals(Components.interfaces.nsISSLErrorListener) && !iid.equals(Components.interfaces.nsIInterfaceRequestor) && !iid.equals(Components.interfaces.nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } }; /** * This class knows how to strip cookies from an HTTP request. It * listens for http-on-modify-request, and modifies the request * accordingly. We can't do this using xmlhttprequest.setHeader() or * nsIChannel.setRequestHeader() before send()ing because the cookie * service is called after send(). * * @param channel nsIChannel in which the request is happening * @constructor */ function PROT_CookieStripper(channel) { this.debugZone = "cookiestripper"; this.topic_ = "http-on-modify-request"; this.channel_ = channel; var Cc = Components.classes; var Ci = Components.interfaces; this.observerService_ = Cc["@mozilla.org/observer-service;1"] .getService(Ci.nsIObserverService); this.observerService_.addObserver(this, this.topic_, false); // If the request doesn't issue, don't hang around forever var twentySeconds = 20 * 1000; this.alarm_ = new G_Alarm(BindToObject(this.stopObserving, this), twentySeconds); } /** * Invoked by the observerservice. See nsIObserve. */ PROT_CookieStripper.prototype.observe = function(subject, topic, data) { if (topic != this.topic_ || subject != this.channel_) return; G_Debug(this, "Stripping cookies for channel."); this.channel_.QueryInterface(Components.interfaces.nsIHttpChannel); this.channel_.setRequestHeader("Cookie", "", false /* replace, not add */); this.alarm_.cancel(); this.stopObserving(); } /** * Remove us from the observerservice */ PROT_CookieStripper.prototype.stopObserving = function() { G_Debug(this, "Removing observer"); this.observerService_.removeObserver(this, this.topic_); this.channel_ = this.alarm_ = this.observerService_ = null; } /** * XPCOM cruft */ PROT_CookieStripper.prototype.QueryInterface = function(iid) { var Ci = Components.interfaces; if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserve)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } //@line 57 "/builds/slave/linux_build/build/toolkit/components/url-classifier/src/nsUrlClassifierLib.js" // Expose this whole component. var lib = this; function UrlClassifierLib() { this.wrappedJSObject = lib; } // Module object function UrlClassifierLibMod() { this.firstTime = true; this.cid = Components.ID("{26a4a019-2827-4a89-a85c-5931a678823a}"); this.progid = "@mozilla.org/url-classifier/jslib;1"; } UrlClassifierLibMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) { if (this.firstTime) { this.firstTime = false; throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN; } compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar); compMgr.registerFactoryLocation(this.cid, "UrlClassifier JS Lib", this.progid, fileSpec, loc, type); }; UrlClassifierLibMod.prototype.getClassObject = function(compMgr, cid, iid) { if (!cid.equals(this.cid)) throw Components.results.NS_ERROR_NO_INTERFACE; if (!iid.equals(Ci.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED; return this.factory; } UrlClassifierLibMod.prototype.canUnload = function(compMgr) { return true; } UrlClassifierLibMod.prototype.factory = { createInstance: function(outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return new UrlClassifierLib(); } }; var LibModInst = new UrlClassifierLibMod(); function NSGetModule(compMgr, fileSpec) { return LibModInst; }