333062 - move anti-phishing list manager and trtable into toolkit/components/protection patch by tony chang <tony@ponderer.org> r=ben@mozilla.org
git-svn-id: svn://10.0.0.236/trunk@194537 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
53
mozilla/toolkit/components/protection/Makefile.in
Normal file
53
mozilla/toolkit/components/protection/Makefile.in
Normal file
@@ -0,0 +1,53 @@
|
||||
#
|
||||
# ***** 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 mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Netscape Communications Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 1998
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
#
|
||||
# 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 *****
|
||||
|
||||
|
||||
DEPTH = ../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
DIRS = public src
|
||||
|
||||
ifdef ENABLE_TESTS
|
||||
DIRS += tests
|
||||
endif
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
73
mozilla/toolkit/components/protection/content/application.js
Normal file
73
mozilla/toolkit/components/protection/content/application.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
/**
|
||||
* A simple helper class that enables us to get or create the
|
||||
* directory in which our app will store stuff.
|
||||
*/
|
||||
function PROT_ApplicationDirectory() {
|
||||
this.debugZone = "appdir";
|
||||
this.appDir_ = G_File.getProfileFile();
|
||||
this.appDir_.append(PROT_GlobalStore.getAppDirectoryName());
|
||||
G_Debug(this, "Application directory is " + this.appDir_.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating if the directory exists
|
||||
*/
|
||||
PROT_ApplicationDirectory.prototype.exists = function() {
|
||||
return this.appDir_.exists() && this.appDir_.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the directory
|
||||
*/
|
||||
PROT_ApplicationDirectory.prototype.create = function() {
|
||||
G_Debug(this, "Creating app directory: " + this.appDir_.path);
|
||||
try {
|
||||
this.appDir_.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
|
||||
} catch(e) {
|
||||
G_Error(this, this.appDir_.path + " couldn't be created.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The nsIFile interface of the directory
|
||||
*/
|
||||
PROT_ApplicationDirectory.prototype.getAppDirFileInterface = function() {
|
||||
return this.appDir_;
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 is the code used to interact with data encoded in the
|
||||
// goog-black-enchash format. The format is basically a map from
|
||||
// hashed hostnames to encrypted sequences of regular expressions
|
||||
// where the encryption key is derived from the hashed
|
||||
// hostname. Encoding lists like this raises the bar slightly on
|
||||
// deriving complete table data from the db. This data format is NOT
|
||||
// our idea; we would've raise the bar higher :)
|
||||
//
|
||||
// Anyway, this code is a port of the original C++ implementation by
|
||||
// Garret. To ease verification, I mirrored that code as closely as
|
||||
// possible. As a result, you'll see some C++-style variable naming
|
||||
// and roundabout (C++) ways of doing things. Additionally, I've
|
||||
// omitted the comments.
|
||||
//
|
||||
// This code should not change, except to fix bugs.
|
||||
//
|
||||
// TODO: verify that using encodeURI() in getCanonicalHost is OK
|
||||
// TODO: accommodate other kinds of perl-but-not-javascript qualifiers
|
||||
|
||||
|
||||
/**
|
||||
* This thing knows how to generate lookup keys and decrypt values found in
|
||||
* a table of type enchash.
|
||||
*/
|
||||
function PROT_EnchashDecrypter() {
|
||||
this.debugZone = "enchashdecrypter";
|
||||
this.REs_ = PROT_EnchashDecrypter.REs;
|
||||
this.hasher_ = new G_CryptoHasher();
|
||||
this.base64_ = new G_Base64();
|
||||
this.rc4_ = new ARC4();
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.DATABASE_SALT = "oU3q.72p";
|
||||
PROT_EnchashDecrypter.SALT_LENGTH = PROT_EnchashDecrypter.DATABASE_SALT.length;
|
||||
|
||||
PROT_EnchashDecrypter.MAX_DOTS = 5;
|
||||
|
||||
PROT_EnchashDecrypter.REs = {};
|
||||
PROT_EnchashDecrypter.REs.FIND_DODGY_CHARS =
|
||||
new RegExp("[\x01-\x1f\x7f-\xff]+");
|
||||
PROT_EnchashDecrypter.REs.FIND_DODGY_CHARS_GLOBAL =
|
||||
new RegExp("[\x01-\x1f\x7f-\xff]+", "g");
|
||||
PROT_EnchashDecrypter.REs.FIND_END_DOTS = new RegExp("^\\.+|\\.+$");
|
||||
PROT_EnchashDecrypter.REs.FIND_END_DOTS_GLOBAL =
|
||||
new RegExp("^\\.+|\\.+$", "g");
|
||||
PROT_EnchashDecrypter.REs.FIND_MULTIPLE_DOTS = new RegExp("\\.{2,}");
|
||||
PROT_EnchashDecrypter.REs.FIND_MULTIPLE_DOTS_GLOBAL =
|
||||
new RegExp("\\.{2,}", "g");
|
||||
PROT_EnchashDecrypter.REs.FIND_TRAILING_DOTS = new RegExp("\\.+$");
|
||||
PROT_EnchashDecrypter.REs.POSSIBLE_IP =
|
||||
new RegExp("^((?:0x[0-9a-f]+|[0-9\\.])+)$", "i");
|
||||
PROT_EnchashDecrypter.REs.FIND_BAD_OCTAL = new RegExp("(^|\\.)0\\d*[89]");
|
||||
PROT_EnchashDecrypter.REs.IS_OCTAL = new RegExp("^0[0-7]*$");
|
||||
PROT_EnchashDecrypter.REs.IS_DECIMAL = new RegExp("^[0-9]+$");
|
||||
PROT_EnchashDecrypter.REs.IS_HEX = new RegExp("^0[xX]([0-9a-fA-F]+)$");
|
||||
|
||||
// Regexps are given in perl regexp format. Unfortunately, JavaScript's
|
||||
// library isn't completely compatible. For example, you can't specify
|
||||
// case-insensitive matching by using (?i) in the expression text :(
|
||||
// So we manually set this bit with the help of this regular expression.
|
||||
PROT_EnchashDecrypter.REs.CASE_INSENSITIVE = /\(\?i\)/g;
|
||||
|
||||
/**
|
||||
* Helper function
|
||||
*
|
||||
* @param str String to get chars from
|
||||
*
|
||||
* @param n Number of characters to get
|
||||
*
|
||||
* @returns String made up of the last n characters of str
|
||||
*/
|
||||
PROT_EnchashDecrypter.prototype.lastNChars_ = function(str, n) {
|
||||
n = -n;
|
||||
return str.substr(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* We have to have our own hex-decoder because decodeURIComponent
|
||||
* expects UTF-8 (so it will barf on invalid UTF-8 sequences).
|
||||
*
|
||||
* @param str String to decode
|
||||
*
|
||||
* @returns The decoded string
|
||||
*/
|
||||
PROT_EnchashDecrypter.prototype.hexDecode_ = function(str) {
|
||||
var output = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < str.length) {
|
||||
var c = str.charAt(i);
|
||||
|
||||
if (c == "%" && i + 2 < str.length) {
|
||||
|
||||
var asciiVal = Number("0x" + str.charAt(i + 1) + str.charAt(i + 2));
|
||||
|
||||
if (!isNaN(asciiVal)) {
|
||||
i += 2;
|
||||
c = String.fromCharCode(asciiVal);
|
||||
}
|
||||
}
|
||||
|
||||
output[output.length] = c;
|
||||
++i;
|
||||
}
|
||||
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a plaintext enchash value into regular expressions
|
||||
*
|
||||
* @param data String containing a decrypted enchash db entry
|
||||
*
|
||||
* @returns An array of RegExps
|
||||
*/
|
||||
PROT_EnchashDecrypter.prototype.parseRegExps = function(data) {
|
||||
var res = data.split("\t");
|
||||
|
||||
G_Debug(this, "Got " + res.length + " regular rexpressions");
|
||||
|
||||
for (var i = 0; i < res.length; i++) {
|
||||
// Could have leading (?i); if so, set the flag and strip it
|
||||
var flags = (this.REs_.CASE_INSENSITIVE.test(res[i])) ? "i" : "";
|
||||
res[i] = res[i].replace(this.REs_.CASE_INSENSITIVE, "");
|
||||
res[i] = new RegExp(res[i], flags);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.prototype.getCanonicalHost = function(str) {
|
||||
var urlObj = Cc["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Ci.nsIURL);
|
||||
urlObj.spec = str;
|
||||
var asciiHost = urlObj.asciiHost;
|
||||
|
||||
var unescaped = this.hexDecode_(asciiHost);
|
||||
|
||||
unescaped = unescaped.replace(this.REs_.FIND_DODGY_CHARS_GLOBAL, "")
|
||||
.replace(this.REs_.FIND_END_DOTS_GLOBAL, "")
|
||||
.replace(this.REs_.FIND_MULTIPLE_DOTS_GLOBAL, ".");
|
||||
|
||||
var temp = this.parseIPAddress_(unescaped);
|
||||
if (temp)
|
||||
unescaped = temp;
|
||||
|
||||
// TODO: what, exactly is it supposed to escape? This doesn't esecape
|
||||
// ":", "/", ";", and "?"
|
||||
var escaped = encodeURI(unescaped);
|
||||
|
||||
var k;
|
||||
var index = escaped.length;
|
||||
for (k = 0; k < PROT_EnchashDecrypter.MAX_DOTS + 1; k++) {
|
||||
temp = escaped.lastIndexOf(".", index - 1);
|
||||
if (temp == -1) {
|
||||
break;
|
||||
} else {
|
||||
index = temp;
|
||||
}
|
||||
}
|
||||
|
||||
if (k == PROT_EnchashDecrypter.MAX_DOTS + 1 && index != -1) {
|
||||
escaped = escaped.substring(index + 1);
|
||||
}
|
||||
|
||||
escaped = escaped.toLowerCase();
|
||||
return escaped;
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.prototype.parseIPAddress_ = function(host) {
|
||||
|
||||
host = host.replace(this.REs_.FIND_TRAILING_DOTS_GLOBAL, "");
|
||||
|
||||
if (!this.REs_.POSSIBLE_IP.test(host))
|
||||
return "";
|
||||
|
||||
var parts = host.split(".");
|
||||
if (parts.length > 4)
|
||||
return "";
|
||||
|
||||
var allowOctal = !this.REs_.FIND_BAD_OCTAL.test(host);
|
||||
|
||||
for (var k = 0; k < parts.length; k++) {
|
||||
var canon;
|
||||
if (k == parts.length - 1) {
|
||||
canon = this.canonicalNum_(parts[k], 5 - parts.length, allowOctal);
|
||||
} else {
|
||||
canon = this.canonicalNum_(parts[k], 1, allowOctal);
|
||||
}
|
||||
if (canon != "")
|
||||
parts[k] = canon;
|
||||
}
|
||||
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.prototype.canonicalNum_ = function(num, bytes, octal) {
|
||||
|
||||
if (bytes < 0)
|
||||
return "";
|
||||
var temp_num;
|
||||
|
||||
if (octal && this.REs_.IS_OCTAL.test(num)) {
|
||||
|
||||
num = this.lastNChars_(num, 11);
|
||||
|
||||
temp_num = parseInt(num, 8);
|
||||
if (isNaN(temp_num))
|
||||
temp_num = -1;
|
||||
|
||||
} else if (this.REs_.IS_DECIMAL.test(num)) {
|
||||
|
||||
num = this.lastNChars_(num, 32);
|
||||
|
||||
temp_num = parseInt(num, 10);
|
||||
if (isNaN(temp_num))
|
||||
temp_num = -1;
|
||||
|
||||
} else if (this.REs_.IS_HEX.test(num)) {
|
||||
|
||||
num = this.lastNChars_(num, 8);
|
||||
|
||||
temp_num = parseInt(num, 16);
|
||||
if (isNaN(temp_num))
|
||||
temp_num = -1;
|
||||
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (temp_num == -1)
|
||||
return "";
|
||||
|
||||
var parts = [];
|
||||
while (bytes--) {
|
||||
parts.push("" + (temp_num % 256));
|
||||
temp_num -= temp_num % 256;
|
||||
temp_num /= 256;
|
||||
}
|
||||
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.prototype.getLookupKey = function(host) {
|
||||
var dataKey = PROT_EnchashDecrypter.DATABASE_SALT + host;
|
||||
dataKey = this.base64_.arrayifyString(dataKey);
|
||||
|
||||
this.hasher_.init(G_CryptoHasher.algorithms.MD5);
|
||||
var lookupDigest = this.hasher_.updateFromArray(dataKey);
|
||||
var lookupKey = this.hasher_.digestHex();
|
||||
|
||||
return lookupKey.toUpperCase();
|
||||
}
|
||||
|
||||
PROT_EnchashDecrypter.prototype.decryptData = function(data, host) {
|
||||
|
||||
var ascii = this.base64_.decodeString(data);
|
||||
var random_salt = ascii.slice(0, PROT_EnchashDecrypter.SALT_LENGTH);
|
||||
var encrypted_data = ascii.slice(PROT_EnchashDecrypter.SALT_LENGTH);
|
||||
var temp_decryption_key =
|
||||
this.base64_.arrayifyString(PROT_EnchashDecrypter.DATABASE_SALT);
|
||||
temp_decryption_key = temp_decryption_key.concat(random_salt);
|
||||
temp_decryption_key =
|
||||
temp_decryption_key.concat(this.base64_.arrayifyString(host));
|
||||
|
||||
this.hasher_.init(G_CryptoHasher.algorithms.MD5);
|
||||
this.hasher_.updateFromArray(temp_decryption_key);
|
||||
var decryption_key = this.base64_.arrayifyString(this.hasher_.digestRaw());
|
||||
|
||||
this.rc4_.setKey(decryption_key, decryption_key.length);
|
||||
this.rc4_.crypt(encrypted_data, encrypted_data.length); // Works in-place
|
||||
|
||||
return this.base64_.stringifyArray(encrypted_data);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lame unittesting function
|
||||
*/
|
||||
function TEST_PROT_EnchashDecrypter() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "enchash UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
// Yes this defies our naming convention, but we copy verbatim from
|
||||
// the C++ unittest, so lets just keep things clear.
|
||||
var no_dots = "abcd123;[]";
|
||||
var one_dot = "abc.123";
|
||||
var two_dots = "two..dots";
|
||||
var lots_o_dots = "I have a lovely .... bunch of dots";
|
||||
var multi_dots = "dots ... and ... more .... dots";
|
||||
var leading_dot = ".leading";
|
||||
var trailing_dot = "trailing.";
|
||||
var trailing_dots = "I love trailing dots....";
|
||||
var end_dots = ".dots.";
|
||||
|
||||
var decimal = "1234567890";
|
||||
var hex = "0x123452FAf";
|
||||
var bad_hex = "0xFF0xGG";
|
||||
var octal = "012034056";
|
||||
var bad_octal = "012034089";
|
||||
var garbage = "lk,.:asdfa-=";
|
||||
var mixed = "1230x78034";
|
||||
var spaces = "123 0xFA 045";
|
||||
|
||||
var longstr = "";
|
||||
for(var k = 0; k < 100; k++) {
|
||||
longstr += "a";
|
||||
}
|
||||
|
||||
var shortstr = "short";
|
||||
|
||||
var r = PROT_EnchashDecrypter.REs;
|
||||
var l = new PROT_EnchashDecrypter();
|
||||
|
||||
// Test regular expressions
|
||||
function testRE(re, inputValPairs) {
|
||||
for (var i = 0; i < inputValPairs.length; i += 2)
|
||||
G_Assert(z, re.test(inputValPairs[i]) == inputValPairs[i + 1],
|
||||
"RegExp broken: " + re + " (input: " + inputValPairs[i] + ")");
|
||||
};
|
||||
|
||||
var tests =
|
||||
["", false,
|
||||
"normal chars;!@#$%^&*&(", false,
|
||||
"MORE NORMAL ,./<>?;':{}", false,
|
||||
"Slightly less\2 normal", true,
|
||||
"\245 stuff \45", true,
|
||||
"\31", true];
|
||||
testRE(r.FIND_DODGY_CHARS, tests);
|
||||
|
||||
tests =
|
||||
[no_dots, false,
|
||||
one_dot, false,
|
||||
leading_dot, true,
|
||||
trailing_dots, true,
|
||||
end_dots, true];
|
||||
testRE(r.FIND_END_DOTS, tests);
|
||||
|
||||
tests =
|
||||
[no_dots, false,
|
||||
one_dot, false,
|
||||
two_dots, true,
|
||||
lots_o_dots, true,
|
||||
multi_dots, true];
|
||||
testRE(r.FIND_MULTIPLE_DOTS, tests);
|
||||
|
||||
tests =
|
||||
[no_dots, false,
|
||||
one_dot, false,
|
||||
trailing_dot, true,
|
||||
trailing_dots, true];
|
||||
testRE(r.FIND_TRAILING_DOTS, tests);
|
||||
|
||||
tests =
|
||||
["random junk", false,
|
||||
"123.45.6-7.89", false,
|
||||
"012.12.123", true,
|
||||
"0x12.0xff.123", true,
|
||||
"225.0.0.1", true];
|
||||
testRE(r.POSSIBLE_IP, tests);
|
||||
|
||||
tests =
|
||||
[decimal, false,
|
||||
hex, false,
|
||||
octal, false,
|
||||
bad_octal, true];
|
||||
testRE(r.FIND_BAD_OCTAL, tests);
|
||||
|
||||
tests =
|
||||
[decimal, false,
|
||||
hex, false,
|
||||
bad_octal, false,
|
||||
garbage, false,
|
||||
mixed, false,
|
||||
spaces, false,
|
||||
octal, true];
|
||||
testRE(r.IS_OCTAL, tests);
|
||||
|
||||
tests =
|
||||
[hex, false,
|
||||
garbage, false,
|
||||
mixed, false,
|
||||
spaces, false,
|
||||
octal, true,
|
||||
bad_octal, true,
|
||||
decimal, true];
|
||||
testRE(r.IS_DECIMAL, tests);
|
||||
|
||||
tests =
|
||||
[decimal, false,
|
||||
octal, false,
|
||||
bad_octal, false,
|
||||
garbage, false,
|
||||
mixed, false,
|
||||
spaces, false,
|
||||
bad_hex, false,
|
||||
hex, true];
|
||||
testRE(r.IS_HEX, tests);
|
||||
|
||||
// Test find last N
|
||||
var val = l.lastNChars_(longstr, 8);
|
||||
G_Assert(z, val.length == 8, "find last eight broken on long str");
|
||||
val = l.lastNChars_(shortstr, 8);
|
||||
G_Assert(z, val.length == 5, "find last 11 broken on short str");
|
||||
|
||||
// Test canonical num
|
||||
tests =
|
||||
["", "", 1, true,
|
||||
"", "10", 0, true,
|
||||
"", "0x45", -1, true,
|
||||
"45", "45", 1, true,
|
||||
"16", "0x10", 1, true,
|
||||
"111.1", "367", 2, true,
|
||||
"229.20.0", "012345", 3, true,
|
||||
"123", "0173", 1, true,
|
||||
"9", "09", 1, false,
|
||||
"", "0x120x34", 2, true,
|
||||
"252.18", "0x12fc", 2, true];
|
||||
for (var i = 0; i < tests.length; i+= 4)
|
||||
G_Assert(z, tests[i] === l.canonicalNum_(tests[i + 1],
|
||||
tests[i + 2],
|
||||
tests[i + 3]),
|
||||
"canonicalNum broken on: " + tests[i + 1]);
|
||||
|
||||
// Test parseIPAddress
|
||||
var testing = {};
|
||||
testing["123.123.0.0.1"] = "";
|
||||
testing["255.0.0.1"] = "255.0.0.1";
|
||||
testing["12.0x12.01234"] = "12.18.156.2";
|
||||
testing["276.2.3"] = "20.2.3.0";
|
||||
testing["012.034.01.055"] = "10.28.1.45";
|
||||
testing["0x12.0x43.0x44.0x01"] = "18.67.68.1";
|
||||
|
||||
for (var key in testing)
|
||||
G_Assert(z, l.parseIPAddress_(key) === testing[key],
|
||||
"parseIPAddress broken on " + key + "(got: " +
|
||||
l.parseIPAddress_(key));
|
||||
|
||||
// Test getCanonicalHost
|
||||
var testing = {};
|
||||
testing["completely.bogus.url.with.a.whole.lot.of.dots"] =
|
||||
"with.a.whole.lot.of.dots";
|
||||
testing["http://poseidon.marinet.gr/~elani"] = "poseidon.marinet.gr";
|
||||
testing["http://www.google.com.."] = "www.google.com";
|
||||
testing["https://www.yaho%6F.com"] = "www.yahoo.com";
|
||||
testing["http://012.034.01.0xa"] = "10.28.1.10";
|
||||
testing["ftp://wierd..chars...%0f,%fa"] = "wierd.chars.,";
|
||||
for (var key in testing)
|
||||
G_Assert(z, l.getCanonicalHost(key) == testing[key],
|
||||
"getCanonicalHost broken on: " + key +
|
||||
"(got: " + l.getCanonicalHost(key) + ")");
|
||||
|
||||
// Test getlookupkey
|
||||
var testing = {};
|
||||
testing["www.google.com"] = "AF5638A09FDDDAFF5B7A6013B1BE69A9";
|
||||
testing["poseidon.marinet.gr"] = "01844755C8143C4579BB28DD59C23747";
|
||||
testing["80.53.164.26"] = "B775DDC22DEBF8BEBFEAC24CE40A1FBF";
|
||||
|
||||
for (var key in testing)
|
||||
G_Assert(z, l.getLookupKey(key) === testing[key],
|
||||
"getlookupkey broken on " + key + " (got: " +
|
||||
l.getLookupKey(key) + ", expected: " +
|
||||
testing[key] + ")");
|
||||
|
||||
// Test decryptdata
|
||||
var tests =
|
||||
[ "bGtEQWJuMl/z2ZxSBB2hsuWI8geMAwfSh3YBfYPejQ1O+wyRAJeJ1UW3V56zm" +
|
||||
"EpUvnaEiECN1pndxW5rEMNzE+gppPeel7PvH+OuabL3NXlspcP0xnpK8rzNgB1" +
|
||||
"JT1KcajQ9K3CCl24T9r8VGb0M3w==",
|
||||
"80.53.164.26",
|
||||
"^(?i)http\\:\\/\\/80\\.53\\.164\\.26(?:\\:80)?\\/\\.PayPal" +
|
||||
"\\.com\\/webscr\\-id\\/secure\\-SSL\\/cmd\\-run\\=\\/login\\.htm$",
|
||||
|
||||
"ZTMzZjVnb3WW1Yc2ABorgQGAwYfcaCb/BG3sMFLTMDvOQxH8LkdGGWqp2tI5SK" +
|
||||
"uNrXIHNf2cyzcVocTqUIUkt1Ud1GKieINcp4tWcU53I0VZ0ZZHCjGObDCbv9Wb" +
|
||||
"CPSx1eS8vMREDv8Jj+UVL1yaZQ==",
|
||||
"80.53.164.26",
|
||||
"^(?i)http\\:\\/\\/80\\.53\\.164\\.26(?:\\:80)?\\/\\.PayPal\\.com" +
|
||||
"\\/webscr\\-id\\/secure\\-SSL\\/cmd\\-run\\=\\/login\\.htm$",
|
||||
|
||||
"ZTMzZjVnb3WVb6VqoJ44hVo4V77XjDRcXTxOc2Zpn4yIHcpS0AQ0nn1TVlX4MY" +
|
||||
"IeNL/6ggzCmcJSWOOkj06Mpo56LNLrbxNxTBuoy9GF+xcm",
|
||||
"poseidon.marinet.gr",
|
||||
"^(?i)http\\:\\/\\/poseidon\\.marinet\\.gr(?:\\:80)?\\/\\~eleni" +
|
||||
"\\/eBay\\/index\\.php$",
|
||||
|
||||
"bGtEQWJuMl9FA3Kl5RiXMpgFU8nDJl9J0hXjUck9+mMUQwAN6llf0gJeY5DIPP" +
|
||||
"c2f+a8MSBFJN17ANGJZl5oZVsQfSW4i12rlScsx4tweZAE",
|
||||
"poseidon.marinet.gr",
|
||||
"^(?i)http\\:\\/\\/poseidon\\.marinet\\.gr(?:\\:80)?\\/\\~eleni" +
|
||||
"\\/eBay\\/index\\.php$"];
|
||||
|
||||
for (var i = 0; i < tests.length; i += 3) {
|
||||
var dec = l.decryptData(tests[i], tests[i + 1]);
|
||||
G_Assert(z, dec === tests[i + 2],
|
||||
"decryptdata broken on " + tests[i] + " (got: " + dec +
|
||||
", expected: " + tests[i + 2] + ")");
|
||||
}
|
||||
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
|
||||
100
mozilla/toolkit/components/protection/content/globalstore.js
Normal file
100
mozilla/toolkit/components/protection/content/globalstore.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// A class that encapsulates globals such as the names of things. We
|
||||
// centralize everything here mainly so as to ease their modification,
|
||||
// but also in case we want to version.
|
||||
//
|
||||
// This class does _not_ embody semantics, defaults, or the like. If we
|
||||
// need something that does, we'll add our own preference registry.
|
||||
//
|
||||
// TODO: The code needs to fail more gracefully if these values aren't set
|
||||
// E.g., createInstance should fail for listmanager without these.
|
||||
|
||||
/**
|
||||
* A clearinghouse for globals. All interfaces are read-only.
|
||||
*/
|
||||
function PROT_GlobalStore() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a pref value
|
||||
*/
|
||||
PROT_GlobalStore.getPref_ = function(prefname) {
|
||||
var pref = new G_Preferences();
|
||||
return pref.getPref(prefname);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The name of the directory in which we should store data, e.g.,
|
||||
* protection_data
|
||||
*/
|
||||
PROT_GlobalStore.getAppDirectoryName = function() {
|
||||
return PROT_GlobalStore.getPref_("protection.datadirectory");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns String giving url to use for updates, e.g.,
|
||||
* http://www.google.com/safebrowsing/update?
|
||||
*/
|
||||
PROT_GlobalStore.getUpdateserverURL = function() {
|
||||
// TODO: handle multiple providers
|
||||
return PROT_GlobalStore.getPref_("protection.provider.0.updateurl");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns String giving url to use for re-keying, e.g.,
|
||||
* https://www.google.com/safebrowsing/getkey?
|
||||
*/
|
||||
PROT_GlobalStore.getGetKeyURL = function() {
|
||||
return PROT_GlobalStore.getPref_("protection.provider.0.keyurl");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns String giving filename to use to store keys
|
||||
*/
|
||||
PROT_GlobalStore.getKeyFilename = function() {
|
||||
return "kf.txt";
|
||||
}
|
||||
|
||||
/**
|
||||
* For running unittests.
|
||||
* TODO: make this automatic based on build rules
|
||||
*/
|
||||
PROT_GlobalStore.isTesting = function() {
|
||||
return true;
|
||||
}
|
||||
125
mozilla/toolkit/components/protection/content/js/arc4.js
Normal file
125
mozilla/toolkit/components/protection/content/js/arc4.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/* ***** 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):
|
||||
* Marius Schilder <mschilder@google.com> (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 ***** */
|
||||
|
||||
|
||||
/**
|
||||
* ARC4 streamcipher implementation
|
||||
* @constructor
|
||||
*/
|
||||
function ARC4() {
|
||||
this._S = new Array(256);
|
||||
this._i = 0;
|
||||
this._j = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the cipher for use with new key.
|
||||
* @param {byte[]} key is byte array containing key
|
||||
* @param {int} opt_length indicates # of bytes to take from key
|
||||
*/
|
||||
ARC4.prototype.setKey = function(key, opt_length) {
|
||||
if (!isArray(key)) {
|
||||
throw new Error("Key parameter must be a byte array");
|
||||
}
|
||||
|
||||
if (!opt_length) {
|
||||
opt_length = key.length;
|
||||
}
|
||||
|
||||
var S = this._S;
|
||||
|
||||
for (var i = 0; i < 256; ++i) {
|
||||
S[i] = i;
|
||||
}
|
||||
|
||||
var j = 0;
|
||||
for (var i = 0; i < 256; ++i) {
|
||||
j = (j + S[i] + key[i % opt_length]) & 255;
|
||||
|
||||
var tmp = S[i];
|
||||
S[i] = S[j];
|
||||
S[j] = tmp;
|
||||
}
|
||||
|
||||
this._i = 0;
|
||||
this._j = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard n bytes of the keystream.
|
||||
* These days 1536 is considered a decent amount to drop to get
|
||||
* the key state warmed-up enough for secure usage.
|
||||
* This is not done in the constructor to preserve efficiency for
|
||||
* use cases that do not need this.
|
||||
* @param {int} n is # of bytes to disregard from stream
|
||||
*/
|
||||
ARC4.prototype.discard = function(n) {
|
||||
var devnul = new Array(n);
|
||||
this.crypt(devnul);
|
||||
}
|
||||
|
||||
/**
|
||||
* En- or decrypt (same operation for streamciphers like ARC4)
|
||||
* @param {byte[]} data gets xor-ed in place
|
||||
* @param {int} opt_length indicated # of bytes to crypt
|
||||
*/
|
||||
ARC4.prototype.crypt = function(data, opt_length) {
|
||||
if (!opt_length) {
|
||||
opt_length = data.length;
|
||||
}
|
||||
|
||||
if (!isArray(data)) {
|
||||
throw new Error("Data parameter must be a byte array");
|
||||
}
|
||||
|
||||
var i = this._i;
|
||||
var j = this._j;
|
||||
var S = this._S;
|
||||
|
||||
for (var n = 0; n < opt_length; ++n) {
|
||||
i = (i + 1) & 255;
|
||||
j = (j + S[i]) & 255;
|
||||
|
||||
var tmp = S[i];
|
||||
S[i] = S[j];
|
||||
S[j] = tmp;
|
||||
|
||||
data[n] ^= S[(S[i] + S[j]) & 255];
|
||||
}
|
||||
|
||||
this._i = i;
|
||||
this._j = j;
|
||||
}
|
||||
362
mozilla/toolkit/components/protection/content/js/lang.js
Normal file
362
mozilla/toolkit/components/protection/content/js/lang.js
Normal file
@@ -0,0 +1,362 @@
|
||||
/* ***** 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):
|
||||
* Aaron Boodman <aa@google.com> (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 file has pure js helper functions. Hence you'll find metion
|
||||
// of browser-specific features in here.
|
||||
|
||||
|
||||
/**
|
||||
* lang.js - The missing JavaScript language features
|
||||
*
|
||||
* WARNING: This class adds members to the prototypes of String, Array, and
|
||||
* Function for convenience.
|
||||
*
|
||||
* The tradeoff is that the for/in statement will not work properly for those
|
||||
* objects when this library is used.
|
||||
*
|
||||
* To work around this for Arrays, you may want to use the forEach() method,
|
||||
* which is more fun and easier to read.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is not |undefined|.
|
||||
*/
|
||||
function isDef(val) {
|
||||
return typeof val != "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is |null|
|
||||
*/
|
||||
function isNull(val) {
|
||||
return val === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is an array
|
||||
*/
|
||||
function isArray(val) {
|
||||
return isObject(val) && val.constructor == Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is a string
|
||||
*/
|
||||
function isString(val) {
|
||||
return typeof val == "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is a boolean
|
||||
*/
|
||||
function isBoolean(val) {
|
||||
return typeof val == "boolean";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is a number
|
||||
*/
|
||||
function isNumber(val) {
|
||||
return typeof val == "number";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is a function
|
||||
*/
|
||||
function isFunction(val) {
|
||||
return typeof val == "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is an object
|
||||
*/
|
||||
function isObject(val) {
|
||||
return val && typeof val == "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the properties defined on an object
|
||||
*/
|
||||
function getObjectProps(obj) {
|
||||
var ret = [];
|
||||
|
||||
for (var p in obj) {
|
||||
ret.push(p);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified value is an object which has no properties
|
||||
* defined.
|
||||
*/
|
||||
function isEmptyObject(val) {
|
||||
if (!isObject(val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var p in val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var getHashCode;
|
||||
var removeHashCode;
|
||||
|
||||
(function () {
|
||||
var hashCodeProperty = "lang_hashCode_";
|
||||
|
||||
/**
|
||||
* Adds a lang_hashCode_ field to an object. The hash code is unique for the
|
||||
* given object.
|
||||
* @param obj {Object} The object to get the hash code for
|
||||
* @returns {Number} The hash code for the object
|
||||
*/
|
||||
getHashCode = function(obj) {
|
||||
// In IE, DOM nodes do not extend Object so they do not have this method.
|
||||
// we need to check hasOwnProperty because the proto might have this set.
|
||||
if (obj.hasOwnProperty && obj.hasOwnProperty(hashCodeProperty)) {
|
||||
return obj[hashCodeProperty];
|
||||
}
|
||||
if (!obj[hashCodeProperty]) {
|
||||
obj[hashCodeProperty] = ++getHashCode.hashCodeCounter_;
|
||||
}
|
||||
return obj[hashCodeProperty];
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the lang_hashCode_ field from an object.
|
||||
* @param obj {Object} The object to remove the field from.
|
||||
*/
|
||||
removeHashCode = function(obj) {
|
||||
obj.removeAttribute(hashCodeProperty);
|
||||
};
|
||||
|
||||
getHashCode.hashCodeCounter_ = 0;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Fast prefix-checker.
|
||||
*/
|
||||
String.prototype.startsWith = function(prefix) {
|
||||
if (this.length < prefix.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.substring(0, prefix.length) == prefix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes whitespace from the beginning and end of the string
|
||||
*/
|
||||
String.prototype.trim = function() {
|
||||
return this.replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Does simple python-style string substitution.
|
||||
* "foo%s hot%s".subs("bar", "dog") becomes "foobar hotdot".
|
||||
* For more fully-featured templating, see template.js.
|
||||
*/
|
||||
String.prototype.subs = function() {
|
||||
var ret = this;
|
||||
|
||||
// this appears to be slow, but testing shows it compares more or less equiv.
|
||||
// to the regex.exec method.
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
ret = ret.replace(/\%s/, String(arguments[i]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last element on an array without removing it.
|
||||
*/
|
||||
Array.prototype.peek = function() {
|
||||
return this[this.length - 1];
|
||||
}
|
||||
|
||||
// TODO(anyone): add splice the first time someone needs it and then implement
|
||||
// push, pop, shift, unshift in terms of it where possible.
|
||||
|
||||
// TODO(anyone): add the other neat-o functional methods like map(), etc.
|
||||
|
||||
/**
|
||||
* Partially applies this 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.
|
||||
*
|
||||
* Also see: partial().
|
||||
*
|
||||
* Note that bind and partial are optimized such that repeated calls to it do
|
||||
* not create more than one function object, so there is no additional cost for
|
||||
* something like:
|
||||
*
|
||||
* var g = bind(f, obj);
|
||||
* var h = partial(g, 1, 2, 3);
|
||||
* var k = partial(h, a, b, c);
|
||||
*
|
||||
* Usage:
|
||||
* var barMethBound = bind(myFunction, myObj, "arg1", "arg2");
|
||||
* barMethBound("arg3", "arg4");
|
||||
*
|
||||
* @param thisObj {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 function bind() was
|
||||
* invoked as a method of.
|
||||
*/
|
||||
function bind(fn, self, opt_args) {
|
||||
var boundargs = isDef(fn.boundArgs_) ? fn.boundArgs_ : [];
|
||||
boundargs = boundargs.concat(Array.prototype.slice.call(arguments, 2));
|
||||
|
||||
if (isDef(fn.boundSelf_)) {
|
||||
self = fn.boundSelf_;
|
||||
}
|
||||
|
||||
if (isDef(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.prototype.slice.call(arguments));
|
||||
return fn.apply(self, args);
|
||||
}
|
||||
|
||||
newfn.boundArgs_ = boundargs;
|
||||
newfn.boundSelf_ = self;
|
||||
newfn.boundFn_ = fn;
|
||||
|
||||
return newfn;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias to the bind() global function.
|
||||
*
|
||||
* Usage:
|
||||
* var g = f.bind(obj, arg1, arg2);
|
||||
* g(arg3, arg4);
|
||||
*/
|
||||
Function.prototype.bind = function(self, opt_args) {
|
||||
return bind.apply(
|
||||
null, [this, self].concat(Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Like bind(), except that a "this object" is not required. Useful when the
|
||||
* target function is already bound.
|
||||
*
|
||||
* Usage:
|
||||
* var g = partial(f, arg1, arg2);
|
||||
* g(arg3, arg4);
|
||||
*/
|
||||
function partial(fn, opt_args) {
|
||||
return bind.apply(
|
||||
null, [fn, null].concat(Array.prototype.slice.call(arguments, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias to the partial() global function.
|
||||
*
|
||||
* Usage:
|
||||
* var g = f.partial(arg1, arg2);
|
||||
* g(arg3, arg4);
|
||||
*/
|
||||
Function.prototype.partial = function(opt_args) {
|
||||
return bind.apply(
|
||||
null, [this, null].concat(Array.prototype.slice.call(arguments)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience. Binds all the methods of obj to itself. Calling this in the
|
||||
* constructor before referencing any methods makes things a little more like
|
||||
* Java or Python where methods are intrinsically bound to their instance.
|
||||
*/
|
||||
function bindMethods(obj) {
|
||||
for (var p in obj) {
|
||||
if (isFunction(obj[p])) {
|
||||
obj[p] = obj[p].bind(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit the prototype methods from one constructor into another.
|
||||
*
|
||||
* Usage:
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*
|
||||
* In addition, a superclass' implementation of a method can be invoked
|
||||
* as follows:
|
||||
*
|
||||
* <pre>
|
||||
* ChildClass.prototype.foo = function(a) {
|
||||
* ChildClass.superClass_.foo.call(this, a);
|
||||
* // other code
|
||||
* };
|
||||
* </pre>
|
||||
*/
|
||||
Function.prototype.inherits = function(parentCtor) {
|
||||
var tempCtor = function(){};
|
||||
tempCtor.prototype = parentCtor.prototype;
|
||||
this.superClass_ = parentCtor.prototype;
|
||||
this.prototype = new tempCtor();
|
||||
}
|
||||
221
mozilla/toolkit/components/protection/content/js/thread-queue.js
Normal file
221
mozilla/toolkit/components/protection/content/js/thread-queue.js
Normal file
@@ -0,0 +1,221 @@
|
||||
/* ***** 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):
|
||||
* Ojan Vafai <ojan@google.com> (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 ***** */
|
||||
|
||||
|
||||
/**
|
||||
* Simulates a thread in JavaScript. However, since there is no preemption in
|
||||
* it's not a proper thread. Specifically, any worker function that takes
|
||||
* longer than the allotted runtime will simply take up that extra time. It is
|
||||
* the responsibility of the user of this class to ensure that each iteration
|
||||
* of a worker function runs in less time than the necessary runtime.
|
||||
*
|
||||
* The delay and runtime (ms) settings control how much the thread affects the
|
||||
* user experience. Larger runtime will mean the computation happens faster,
|
||||
* but the user will notice more jumpiness. A larger delay can mitigate
|
||||
* jumpiness significantly and causes the computation to take longer.
|
||||
*
|
||||
* The interleave setting means that the thread interleaves the running of
|
||||
* the workers that have been added to it. If this value is false, each worker
|
||||
* runs to completion before the next worker starts running (a worker
|
||||
* that does not complete will cause all subsequent workers to never run).
|
||||
*
|
||||
* Dependencies:
|
||||
* - alarm.js (only when used inside a Firefox service)
|
||||
*
|
||||
* @param opt_config Configuration object with optional settings for runtime,
|
||||
* delay and noInterleave.
|
||||
*/
|
||||
function TH_ThreadQueue(opt_config) {
|
||||
// default values
|
||||
this.runtime_ = 100;
|
||||
this.delay_ = 25;
|
||||
this.interleave_ = true;
|
||||
|
||||
if (opt_config) {
|
||||
this.interleave_ = (opt_config.interleave === true);
|
||||
if (opt_config.runtime) this.runtime_ = opt_config.runtime;
|
||||
if (opt_config.delay) this.delay_ = opt_config.delay;
|
||||
}
|
||||
|
||||
this.workers_ = [];
|
||||
this.guid_ = 0;
|
||||
|
||||
// Bind this here so that it doesn't need to be bound on every iteration.
|
||||
// We are using this clugy way of calling the method becuase of a Firefox
|
||||
// XUL bug <https://bugzilla.mozilla.org/show_bug.cgi?id=300079>. This used
|
||||
// to use BindToObject but that has been depreciated.
|
||||
this.iterate_ = Function.prototype.bind.call(this.runIteration_, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a worker function to the thread. Adding more workers means that each
|
||||
* worker runs slower and the user will notice more jumpiness.
|
||||
*
|
||||
* @param worker Function that iteratively computes the task. Must not
|
||||
* already be a worker in the thread.
|
||||
* @param opt_isComplete Function that identifies if this worker is complete.
|
||||
* If it is complete, then this worker is removed from
|
||||
* the thread. If omitted, this worker will never
|
||||
* complete.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.addWorker = function(worker, opt_isComplete) {
|
||||
if (worker.__workerId) {
|
||||
throw new Error("Cannot add a worker to a thread while it is currently " +
|
||||
"running in the thread.");
|
||||
}
|
||||
|
||||
worker.__isComplete = opt_isComplete || function() { return false; };
|
||||
worker.__workerId = this.guid_++;
|
||||
this.workers_.push(worker);
|
||||
if (this.workers_.length == 1) {
|
||||
this.setCurrentWorkerByIndex_(0);
|
||||
// If thread is paused iterate_ is a noop.
|
||||
this.iterate_();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a worker from this thread.
|
||||
*
|
||||
* @param worker The worker function to remove from the thread. This must be
|
||||
* the same function object that was passed into add worker,
|
||||
* not just the same function.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.removeWorker = function(worker) {
|
||||
for (var i = 0; i < this.workers_.length; i++) {
|
||||
if (worker.__workerId == this.workers_[i].__workerId) {
|
||||
this.removeWorkerByIndex_(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a worker based on its index in the workers array.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.removeWorkerByIndex_ = function(workerIndex) {
|
||||
var worker = this.workers_[workerIndex];
|
||||
delete(worker.__isComplete);
|
||||
delete(worker.__workerId);
|
||||
|
||||
this.workers_.splice(workerIndex, 1);
|
||||
|
||||
var newWorkerIndex = this.currentWorkerIndex_ % this.workers_.length;
|
||||
this.setCurrentWorkerByIndex_(newWorkerIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the delay between iterations of the thread.
|
||||
*
|
||||
* @param delay Delay in milliseconds
|
||||
*/
|
||||
TH_ThreadQueue.prototype.setDelay = function(delay) {
|
||||
this.delay_ = delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the runtime of each iteration of the thread. Note that this runtime
|
||||
* cannot be 100% assured and will definitely not be kept if any worker takes
|
||||
* longer than the runtime to run an iteration.
|
||||
*
|
||||
* @param runtime Runtime in milliseconds
|
||||
*/
|
||||
TH_ThreadQueue.prototype.setRuntime = function(runtime) {
|
||||
this.runtime_ = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the thread running.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.run = function() {
|
||||
this.running_ = true;
|
||||
this.iterate_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the thread.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.pause = function() {
|
||||
this.running_ = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an iteration of the thread.
|
||||
* Does nothing if there are no workers, or the thread is paused.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.runIteration_ = function() {
|
||||
if (!this.running_ || this.workers_.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var startTime = (new Date()).getTime();
|
||||
|
||||
while ((new Date()).getTime() - startTime < this.runtime_) {
|
||||
if (this.currentWorker_.__isComplete()) {
|
||||
this.removeWorkerByIndex_(this.currentWorkerIndex_);
|
||||
break;
|
||||
}
|
||||
|
||||
this.currentWorker_();
|
||||
if (this.interleave_) this.nextWorker_();
|
||||
}
|
||||
|
||||
if (typeof top != "undefined" && top.setTimeout) {
|
||||
top.setTimeout(this.iterate_, this.delay_);
|
||||
} else if (G_Alarm) {
|
||||
new G_Alarm(this.iterate_, this.delay_);
|
||||
} else {
|
||||
throw new Error("Could not find a mechanism to start a timeout. Need " +
|
||||
"window.setTimeout or G_Alarm.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current worker to be the worker the currentWorkerIndex_ points to.
|
||||
*
|
||||
* @param workerIndex Index of the worker into the workers_ array.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.setCurrentWorkerByIndex_ = function(workerIndex) {
|
||||
this.currentWorkerIndex_ = workerIndex;
|
||||
this.currentWorker_ = this.workers_[workerIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate to the next worker function.
|
||||
*/
|
||||
TH_ThreadQueue.prototype.nextWorker_ = function() {
|
||||
var nextWorkerIndex = (this.currentWorkerIndex_ + 1) % this.workers_.length;
|
||||
this.setCurrentWorkerByIndex_(nextWorkerIndex);
|
||||
}
|
||||
225
mozilla/toolkit/components/protection/content/list-warden.js
Normal file
225
mozilla/toolkit/components/protection/content/list-warden.js
Normal file
@@ -0,0 +1,225 @@
|
||||
/* ***** 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):
|
||||
* Niels Provos <niels@google.com> (original author)d
|
||||
|
||||
* Fritz Schneider <fritz@google.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
// A warden that knows how to register lists with a listmanager and keep them
|
||||
// updated if necessary. The ListWarden also provides a simple interface to
|
||||
// check if a URL is evil or not. Specialized wardens like the PhishingWarden
|
||||
// inherit from it.
|
||||
//
|
||||
// Classes that inherit from ListWarden are responsible for calling
|
||||
// enableTableUpdates or disableTableUpdates. This usually entails
|
||||
// registering prefObservers and calling enable or disable in the base
|
||||
// class as appropriate.
|
||||
//
|
||||
|
||||
/**
|
||||
* Abtracts the checking of user/browser actions for signs of
|
||||
* phishing.
|
||||
*
|
||||
* @param ListManager ListManager that allows us to check against white
|
||||
* and black lists.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_ListWarden(listManager) {
|
||||
this.debugZone = "listwarden";
|
||||
this.listManager_ = listManager;
|
||||
|
||||
// Use this to query preferences
|
||||
this.prefs_ = new G_Preferences();
|
||||
|
||||
// Once we register tables, their respective names will be listed here.
|
||||
this.blackTables_ = [];
|
||||
this.whiteTables_ = [];
|
||||
this.sandboxUpdates_ = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the ListManger to keep all of our tables updated
|
||||
*/
|
||||
|
||||
PROT_ListWarden.prototype.enableTableUpdates = function() {
|
||||
this.listManager_.enableUpdateTables(this.blackTables_);
|
||||
this.listManager_.enableUpdateTables(this.whiteTables_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the ListManager to stop updating our tables
|
||||
*/
|
||||
|
||||
PROT_ListWarden.prototype.disableTableUpdates = function() {
|
||||
this.listManager_.disableUpdateTables(this.blackTables_);
|
||||
this.listManager_.disableUpdateTables(this.whiteTables_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the ListManager to keep sandbox updated. Don't tie this to the wl/bl
|
||||
* updates.
|
||||
*/
|
||||
PROT_ListWarden.prototype.enableSandboxUpdates = function() {
|
||||
this.listManager_.enableUpdateTables(this.sandboxUpdates_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the ListManager not to keep sandbox updated. Don't tie this to the
|
||||
* wl/bl updates.
|
||||
*/
|
||||
PROT_ListWarden.prototype.disableSandboxUpdates = function() {
|
||||
this.listManager_.disableUpdateTables(this.sandboxUpdates_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new black list table with the list manager
|
||||
* @param tableName - name of the table to register
|
||||
* @returns true if the table could be registered, false otherwise
|
||||
*/
|
||||
|
||||
PROT_ListWarden.prototype.registerBlackTable = function(tableName) {
|
||||
var result = this.listManager_.registerTable(tableName);
|
||||
if (result)
|
||||
this.blackTables_.push(tableName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new white list table with the list manager
|
||||
* @param tableName - name of the table to register
|
||||
* @returns true if the table could be registered, false otherwise
|
||||
*/
|
||||
|
||||
PROT_ListWarden.prototype.registerWhiteTable = function(tableName) {
|
||||
var result = this.listManager_.registerTable(tableName);
|
||||
if (result)
|
||||
this.whiteTables_.push(tableName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that looks up a url in a set of tables
|
||||
*
|
||||
* @param tables array of table names
|
||||
* @param url URL to look up
|
||||
* @returns Boolean indicating if the url is in one of the tables
|
||||
*/
|
||||
PROT_ListWarden.prototype.isURLInTables_ = function(tables, url) {
|
||||
for (var i = 0; i < tables.length; ++i) {
|
||||
if (this.listManager_.safeLookup(tables[i], url))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that looks up a url in the white list.
|
||||
*
|
||||
* @param url URL to look up
|
||||
* @returns Boolean indicating if the url is on our whitelist
|
||||
*/
|
||||
PROT_ListWarden.prototype.isWhiteURL_ = function(url) {
|
||||
if (!this.listManager_)
|
||||
return false;
|
||||
|
||||
var whitelisted = this.isURLInTables_(this.whiteTables_, url);
|
||||
|
||||
if (whitelisted)
|
||||
G_Debug(this, "Whitelist hit: " + url);
|
||||
|
||||
return whitelisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that looks up a url in the black lists.
|
||||
*
|
||||
* @param url URL to look up
|
||||
* @returns Boolean indicating if the url is on our blacklist(s)
|
||||
*/
|
||||
PROT_ListWarden.prototype.isBlackURL_ = function(url) {
|
||||
if (!this.listManager_)
|
||||
return false;
|
||||
|
||||
var blacklisted = this.isURLInTables_(this.blackTables_, url);
|
||||
|
||||
if (blacklisted)
|
||||
G_Debug(this, "Blacklist hit: " + url);
|
||||
|
||||
return blacklisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method that looks up a url in both the white and black lists.
|
||||
*
|
||||
* If there is conflict, the white list has precedence over the black list.
|
||||
*
|
||||
* @param url URL to look up
|
||||
* @returns Boolean indicating if the url is phishy.
|
||||
*/
|
||||
PROT_ListWarden.prototype.isEvilURL_ = function(url) {
|
||||
return !this.isWhiteURL_(url) && this.isBlackURL_(url);
|
||||
}
|
||||
|
||||
// Some unittests
|
||||
// TODO something more appropriate
|
||||
|
||||
function TEST_PROT_ListWarden() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "listwarden UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var threadQueue = new TH_ThreadQueue();
|
||||
var listManager = new PROT_ListManager(threadQueue, true /* testing */);
|
||||
|
||||
var warden = new PROT_ListWarden(listManager);
|
||||
|
||||
// Just some really simple test
|
||||
G_Assert(z, warden.registerWhiteTable("test-white-domain"),
|
||||
"Failed to register test-white-domain table");
|
||||
G_Assert(z, warden.registerWhiteTable("test-black-url"),
|
||||
"Failed to register test-black-url table");
|
||||
listManager.safeInsert("test-white-domain", "http://foo.com/good", "1");
|
||||
listManager.safeInsert("test-black-url", "http://foo.com/good/1", "1");
|
||||
listManager.safeInsert("test-black-url", "http://foo.com/bad/1", "1");
|
||||
|
||||
G_Assert(z, !warden.isEvilURL_("http://foo.com/good/1"),
|
||||
"White listing is not working.");
|
||||
G_Assert(z, warden.isEvilURL_("http://foo.com/bad/1"),
|
||||
"Black listing is not working.");
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
747
mozilla/toolkit/components/protection/content/listmanager.js
Normal file
747
mozilla/toolkit/components/protection/content/listmanager.js
Normal file
@@ -0,0 +1,747 @@
|
||||
/* ***** 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):
|
||||
* Niels Provos <niels@google.com> (original author)
|
||||
* Fritz Schneider <fritz@google.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
|
||||
// A class that manages lists, namely white and black lists for
|
||||
// phishing or malware protection. The ListManager knows how to fetch,
|
||||
// update, and store lists, and knows the "kind" of list each is (is
|
||||
// it a whitelist? a blacklist? etc). However it doesn't know how the
|
||||
// lists are serialized or deserialized (the wireformat classes know
|
||||
// this) nor the specific format of each list. For example, the list
|
||||
// could be a map of domains to "1" if the domain is phishy. Or it
|
||||
// could be a map of hosts to regular expressions to match, who knows?
|
||||
// Answer: the trtable knows. List are serialized/deserialized by the
|
||||
// wireformat reader from/to trtables, and queried by the listmanager.
|
||||
//
|
||||
// There is a single listmanager for the whole application.
|
||||
//
|
||||
// The listmanager is used only in privacy mode; in advanced protection
|
||||
// mode a remote server is queried.
|
||||
//
|
||||
// How to add a new table:
|
||||
// 1) get it up on the server
|
||||
// 2) add it to tablesKnown
|
||||
// 3) if it is not a known table type (trtable.js), add an implementation
|
||||
// for it in trtable.js
|
||||
// 4) add a check for it in the phishwarden's isXY() method, for example
|
||||
// isBlackURL()
|
||||
//
|
||||
// TODO: obviously the way this works could use a lot of improvement. In
|
||||
// particular adding a list should just be a matter of adding
|
||||
// its name to the listmanager and an implementation to trtable
|
||||
// (or not if a talbe of that type exists). The format and semantics
|
||||
// of the list comprise its name, so the listmanager should easily
|
||||
// be able to figure out what to do with what list (i.e., no
|
||||
// need for step 4).
|
||||
// TODO reading, writing, and whatnot code should be cleaned up
|
||||
// TODO read/write asynchronously
|
||||
// TODO more comprehensive update tests, for example add unittest check
|
||||
// that the listmanagers tables are properly written on updates
|
||||
|
||||
|
||||
/**
|
||||
* A ListManager keeps track of black and white lists and knows
|
||||
* how to update them.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_ListManager() {
|
||||
this.debugZone = "listmanager";
|
||||
G_debugService.enableZone(this.debugZone);
|
||||
|
||||
// We run long-lived operations on "threads" (user-level
|
||||
// time-slices) in order to prevent locking up the UI
|
||||
var threadConfig = {
|
||||
"interleave": true,
|
||||
"runtime": 80,
|
||||
"delay": 400,
|
||||
};
|
||||
this.threadQueue_ = new TH_ThreadQueue(threadConfig);
|
||||
this.appDir_ = null; // appDir is set via a method call
|
||||
this.currentUpdateChecker_ = null; // set when we toggle updates
|
||||
this.rpcPending_ = false;
|
||||
this.readingData_ = false;
|
||||
|
||||
// We don't want to start checking against our lists until we've
|
||||
// read them. But then again, we don't want to queue URLs to check
|
||||
// forever. So if we haven't successfully read our lists after a
|
||||
// certain amount of time, just pretend.
|
||||
this.dataImportedAtLeastOnce_ = false;
|
||||
var self = this;
|
||||
new G_Alarm(function() { self.dataImportedAtLeastOnce_ = true; }, 60 * 1000);
|
||||
|
||||
// TOOD(tc): PREF
|
||||
this.updateserverURL_ = PROT_GlobalStore.getUpdateserverURL();
|
||||
|
||||
// The lists we know about and the parses we can use to read
|
||||
// them. Default all to the earlies possible version (1.-1); this
|
||||
// version will get updated when successfully read from disk or
|
||||
// fetch updates.
|
||||
this.tablesKnown_ = {};
|
||||
|
||||
if (PROT_GlobalStore.isTesting()) {
|
||||
// populate with some tables for unittesting
|
||||
this.tablesKnown_ = {
|
||||
// "goog-white-domain": new PROT_VersionParser("goog-white-domain", 1, -1),
|
||||
// "goog-black-url" : new PROT_VersionParser("goog-black-url", 1, -1),
|
||||
// "goog-black-enchash": new PROT_VersionParser("goog-black-enchash", 1, -1),
|
||||
// "goog-white-url": new PROT_VersionParser("goog-white-url", 1, -1),
|
||||
//
|
||||
// A major version of zero means local, so don't ask for updates
|
||||
"test1-foo-domain" : new PROT_VersionParser("test1-foo-domain", 0, -1),
|
||||
"test2-foo-domain" : new PROT_VersionParser("test2-foo-domain", 0, -1),
|
||||
"test-white-domain" :
|
||||
new PROT_VersionParser("test-white-domain", 0, -1, true /* require mac*/),
|
||||
"test-mac-domain" :
|
||||
new PROT_VersionParser("test-mac-domain", 0, -1, true /* require mac */)
|
||||
};
|
||||
|
||||
// expose the object for unittesting
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
this.tablesData = {};
|
||||
|
||||
this.urlCrypto_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new table table
|
||||
* @param tableName - the name of the table
|
||||
* @param opt_requireMac true if a mac is required on update, false otherwise
|
||||
* @returns true if the table could be created; false otherwise
|
||||
*/
|
||||
PROT_ListManager.prototype.registerTable = function(tableName,
|
||||
opt_requireMac) {
|
||||
var table = new PROT_VersionParser(tableName, 1, -1, opt_requireMac);
|
||||
if (!table)
|
||||
return false;
|
||||
this.tablesKnown_[tableName] = table;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable updates for some tables
|
||||
* @param tables - an array of table names that need updating
|
||||
*/
|
||||
PROT_ListManager.prototype.enableUpdateTables = function(tables) {
|
||||
var changed = false;
|
||||
for (var i = 0; i < tables.length; ++i) {
|
||||
var table = this.tablesKnown_[tables[i]];
|
||||
if (table) {
|
||||
G_Debug(this, "Enabling table updates for " + tables[i]);
|
||||
table.needsUpdate = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed === true)
|
||||
this.maybeToggleUpdateChecking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables updates for some tables
|
||||
* @param tables - an array of table names that no longer need updating
|
||||
*/
|
||||
PROT_ListManager.prototype.disableUpdateTables = function(tables) {
|
||||
var changed = false;
|
||||
for (var i = 0; i < tables.length; ++i) {
|
||||
var table = this.tablesKnown_[tables[i]];
|
||||
if (table) {
|
||||
G_Debug(this, "Disabling table updates for " + tables[i]);
|
||||
table.needsUpdate = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed === true)
|
||||
this.maybeToggleUpdateChecking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have some tables that need updating.
|
||||
*/
|
||||
PROT_ListManager.prototype.requireTableUpdates = function() {
|
||||
for (var type in this.tablesKnown_) {
|
||||
// All tables with a major of 0 are internal tables that we never
|
||||
// update remotely.
|
||||
if (this.tablesKnown_[type].major == 0)
|
||||
continue;
|
||||
|
||||
// Tables that need updating even if other tables dont require it
|
||||
if (this.tablesKnown_[type].needsUpdate)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start managing the lists we know about. We don't do this automatically
|
||||
* when the listmanager is instantiated because their profile directory
|
||||
* (where we store the lists) might not be available.
|
||||
*/
|
||||
PROT_ListManager.prototype.maybeStartManagingUpdates = function() {
|
||||
if (PROT_GlobalStore.isTesting())
|
||||
return;
|
||||
|
||||
// We might have been told about tables already, so see if we should be
|
||||
// actually updating.
|
||||
this.maybeToggleUpdateChecking();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any tables that require updating. Different
|
||||
* Wardens may call us with new tables that need to be updated.
|
||||
*/
|
||||
PROT_ListManager.prototype.maybeToggleUpdateChecking = function() {
|
||||
// If we are testing or dont have an application directory yet, we should
|
||||
// not start reading tables from disk or schedule remote updates
|
||||
if (PROT_GlobalStore.isTesting() || !this.appDir_)
|
||||
return;
|
||||
|
||||
// We update tables if we have some tables that want updates. If there
|
||||
// are no tables that want to be updated - we dont need to check anything.
|
||||
if (this.requireTableUpdates() === true) {
|
||||
G_Debug(this, "Starting managing lists");
|
||||
// Read new table data if necessary - if we already have data we just
|
||||
// skip reading from disk
|
||||
new G_Alarm(BindToObject(this.readDataFiles, this), 0);
|
||||
// Multiple warden can ask us to reenable updates at the same time, but we
|
||||
// really just need to schedule a single update.
|
||||
if (!this.currentUpdateChecker_)
|
||||
this.currentUpdateChecker_ =
|
||||
new G_Alarm(BindToObject(this.checkForUpdates, this), 3000);
|
||||
this.startUpdateChecker();
|
||||
} else {
|
||||
G_Debug(this, "Stopping managing lists (if currently active)");
|
||||
this.stopUpdateChecker(); // Cancel pending updates
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start periodic checks for updates. Idempotent.
|
||||
*/
|
||||
PROT_ListManager.prototype.startUpdateChecker = function() {
|
||||
this.stopUpdateChecker();
|
||||
|
||||
// Schedule a check for updates every so often
|
||||
// TODO(tc): PREF NEW
|
||||
var sixtyMinutes = 60 * 60 * 1000;
|
||||
this.updateChecker_ = new G_Alarm(BindToObject(this.checkForUpdates, this),
|
||||
sixtyMinutes, true /* repeat */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop checking for updates. Idempotent.
|
||||
*/
|
||||
PROT_ListManager.prototype.stopUpdateChecker = function() {
|
||||
if (this.updateChecker_) {
|
||||
this.updateChecker_.cancel();
|
||||
this.updateChecker_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the directory in which we should serialize data
|
||||
*
|
||||
* @param appDir An nsIFile pointing to our directory (should exist)
|
||||
*/
|
||||
PROT_ListManager.prototype.setAppDir = function(appDir) {
|
||||
G_Debug(this, "setAppDir: " + appDir.path);
|
||||
this.appDir_ = appDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the specified table
|
||||
* @param table Name of the table that we want to consult
|
||||
* @returns true if the table exists, false otherwise
|
||||
*/
|
||||
PROT_ListManager.prototype.clearList = function(table) {
|
||||
if (!this.tablesKnown_[table])
|
||||
return false;
|
||||
|
||||
this.tablesData[table] = newProtectionTable(this.tablesKnown_[table].type);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an exception free way to look up the data in a table. We
|
||||
* use this because at certain points our tables might not be loaded,
|
||||
* and querying them could throw.
|
||||
*
|
||||
* @param table Name of the table that we want to consult
|
||||
* @param key Key for table lookup
|
||||
* @returns false or the value in the table corresponding to key.
|
||||
* If the table name does not exist, we return false, too.
|
||||
*/
|
||||
PROT_ListManager.prototype.safeLookup = function(table, key) {
|
||||
var result = false;
|
||||
try {
|
||||
var map = this.tablesData[table];
|
||||
result = map.find(key);
|
||||
} catch(e) {
|
||||
result = false;
|
||||
G_Debug(this, "Safelookup masked failure for " + table + ", key " + key + ": " + e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an exception free way to insert data into a table.
|
||||
* @param table Name of the table that we want to consult
|
||||
* @param key Key for table insert
|
||||
* @param value Value for table insert
|
||||
* @returns true if the value could be inserted, false otherwise
|
||||
*/
|
||||
PROT_ListManager.prototype.safeInsert = function(table, key, value) {
|
||||
if (!this.tablesKnown_[table]) {
|
||||
G_Debug(this, "Unknown table: " + table);
|
||||
return false;
|
||||
}
|
||||
if (!this.tablesData[table])
|
||||
this.tablesData[table] = newProtectionTable(table);
|
||||
try {
|
||||
this.tablesData[table].insert(key, value);
|
||||
} catch (e) {
|
||||
G_Debug(this, "Cannot insert key " + key + " value " + value);
|
||||
G_Debug(this, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an exception free way to remove data from a table.
|
||||
* @param table Name of the table that we want to consult
|
||||
* @param key Key for table erase
|
||||
* @returns true if the value could be removed, false otherwise
|
||||
*/
|
||||
PROT_ListManager.prototype.safeErase = function(table, key) {
|
||||
if (!this.tablesKnown_[table]) {
|
||||
G_Debug(this, "Unknown table: " + table);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.tablesData[table])
|
||||
return false;
|
||||
|
||||
return this.tablesData[table].erase(key);
|
||||
}
|
||||
|
||||
PROT_ListManager.prototype.getTable = function(tableName) {
|
||||
return this.tablesData[tableName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of files that we should try to read. We do not return
|
||||
* tables that already have data or that we tried to read in the past.
|
||||
* @returns array of file names
|
||||
*/
|
||||
|
||||
PROT_ListManager.prototype.listUnreadDataFiles = function() {
|
||||
// Now we need to read all of our nice data files.
|
||||
var files = [];
|
||||
for (var type in this.tablesKnown_) {
|
||||
var table = this.tablesKnown_[type];
|
||||
// Do not read data for tables that we already know about
|
||||
if (this.tablesData[type] || table.didRead === true)
|
||||
continue;
|
||||
|
||||
// Tables that dont require updates are not read from disk either
|
||||
if (table.needsUpdate === false)
|
||||
continue;
|
||||
|
||||
var filename = type + ".sst";
|
||||
var file = this.appDir_.clone();
|
||||
file.append(filename);
|
||||
if (file.exists() && file.isFile() && file.isReadable()) {
|
||||
G_Debug(this, "Found saved data for: " + type);
|
||||
files.push(file);
|
||||
} else {
|
||||
G_Debug(this, "Failed to find saved data for: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all data files from storage
|
||||
* @returns true if we started reading data from disk, false otherwise.
|
||||
*/
|
||||
PROT_ListManager.prototype.readDataFiles = function() {
|
||||
if (this.readingData_ === true) {
|
||||
G_Debug(this, "Already reading data from disk");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.rpcPending_ === true) {
|
||||
G_Debug(this, "Cannot read data files while an update RPC is pending");
|
||||
new G_Alarm(BindToObject(this.readDataFiles, this), 10 * 1000);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we have no files there is nothing more for us todo
|
||||
var files = this.listUnreadDataFiles();
|
||||
if (!files.length)
|
||||
return true;
|
||||
|
||||
this.readingData_ = true;
|
||||
|
||||
// Remember that we attempted to read from all tables
|
||||
for (var type in this.tablesKnown_)
|
||||
this.tablesKnown_[type].didRead = true;
|
||||
|
||||
// TODO: Should probably break this up on a thread
|
||||
var data = "";
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
G_Debug(this, "Trying to read: " + files[i].path);
|
||||
var gFile = new G_FileReader(files[i]);
|
||||
data += gFile.read() + "\n";
|
||||
gFile.close();
|
||||
}
|
||||
|
||||
this.deserialize_(data, BindToObject(this.dataFromDisk, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WireFormatReader and calls deserialize on it.
|
||||
*/
|
||||
PROT_ListManager.prototype.deserialize_ = function(data, callback) {
|
||||
var wfr = new PROT_WireFormatReader(this.threadQueue_, this.tablesData);
|
||||
wfr.deserialize(data, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that is executed when we have read our table data from
|
||||
* disk.
|
||||
*
|
||||
* @param tablesKnown An array that maps table name to current version
|
||||
* @param tablesData An array that maps a table name to a Map which
|
||||
* contains key value pairs.
|
||||
*/
|
||||
PROT_ListManager.prototype.dataFromDisk = function(tablesKnown, tablesData) {
|
||||
G_Debug(this, "Called dataFromDisk");
|
||||
|
||||
this.importData_(tablesKnown, tablesData);
|
||||
|
||||
this.readingData_ = false;
|
||||
|
||||
// If we have more files to read schedule another round of reading
|
||||
var files = this.listUnreadDataFiles();
|
||||
if (files.length)
|
||||
new G_Alarm(BindToObject(this.readDataFiles, this), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a URL to fetch upates from. Format is a squence of
|
||||
* type:major:minor, fields
|
||||
*
|
||||
* @param url The base URL to which query parameters are appended; assumes
|
||||
* already has a trailing ?
|
||||
* @returns the URL that we should request the table update from.
|
||||
*/
|
||||
PROT_ListManager.prototype.getRequestURL_ = function(url) {
|
||||
url += "version=";
|
||||
var firstElement = true;
|
||||
var requestMac = false;
|
||||
|
||||
for(var type in this.tablesKnown_) {
|
||||
// All tables with a major of 0 are internal tables that we never
|
||||
// update remotely.
|
||||
if (this.tablesKnown_[type].major == 0)
|
||||
continue;
|
||||
|
||||
// Check if the table needs updating
|
||||
if (this.tablesKnown_[type].needsUpdate == false)
|
||||
continue;
|
||||
|
||||
if (!firstElement) {
|
||||
url += ","
|
||||
} else {
|
||||
firstElement = false;
|
||||
}
|
||||
url += type + ":" + this.tablesKnown_[type].toUrl();
|
||||
|
||||
if (this.tablesKnown_[type].requireMac)
|
||||
requestMac = true;
|
||||
}
|
||||
|
||||
// Request a mac only if at least one of the tables to be updated requires
|
||||
// it
|
||||
if (requestMac) {
|
||||
// Add the wrapped key for requesting macs
|
||||
if (!this.urlCrypto_)
|
||||
this.urlCrypto_ = new PROT_UrlCrypto();
|
||||
|
||||
url += "&wrkey=" +
|
||||
encodeURIComponent(this.urlCrypto_.getManager().getWrappedKey());
|
||||
}
|
||||
|
||||
G_Debug(this, "getRequestURL returning: " + url);
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates our internal tables from the update server
|
||||
*
|
||||
* @returns true when a new request was scheduled, false if an old request
|
||||
* was still pending.
|
||||
*/
|
||||
PROT_ListManager.prototype.checkForUpdates = function() {
|
||||
// Allow new updates to be scheduled from maybeToggleUpdateChecking()
|
||||
this.currentUpdateChecker_ = null;
|
||||
|
||||
if (this.rpcPending_) {
|
||||
G_Debug(this, 'checkForUpdates: old callback is still pending...');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.readingData_) {
|
||||
G_Debug(this, 'checkForUpdate: still reading data from disk...');
|
||||
|
||||
// Reschedule the update to happen in a little while
|
||||
new G_Alarm(BindToObject(this.checkForUpdates, this), 500);
|
||||
return false;
|
||||
}
|
||||
|
||||
G_Debug(this, 'checkForUpdates: scheduling request..');
|
||||
this.rpcPending_ = true;
|
||||
this.xmlFetcher_ = new PROT_XMLFetcher();
|
||||
this.xmlFetcher_.get(this.getRequestURL_(this.updateserverURL_),
|
||||
BindToObject(this.rpcDone, this));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that is executed when the XMLHTTP request is finished
|
||||
*
|
||||
* @param data String containing the returned data
|
||||
*/
|
||||
PROT_ListManager.prototype.rpcDone = function(data) {
|
||||
G_Debug(this, "Called rpcDone");
|
||||
/* Runs in a thread and executes the callback when ready */
|
||||
this.deserialize_(data, BindToObject(this.dataReady, this));
|
||||
|
||||
this.xmlFetcher_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating if it has read data from somewhere (e.g.,
|
||||
* disk)
|
||||
*/
|
||||
PROT_ListManager.prototype.hasData = function() {
|
||||
return !!this.dataImportedAtLeastOnce_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the mac passed, if required.
|
||||
*
|
||||
* @param oldParser The current version parser for the table
|
||||
* @param newParser A version parser returned by the WireFormatReader
|
||||
*
|
||||
* @returns true if no mac is required, or if it is and the newParser says it
|
||||
* didn't fail, false otherwise
|
||||
*/
|
||||
PROT_ListManager.prototype.maybeCheckMac = function(oldParser, newParser) {
|
||||
if (!oldParser.requireMac)
|
||||
return true;
|
||||
|
||||
if (newParser.mac && !newParser.macFailed)
|
||||
return true;
|
||||
|
||||
G_Debug(this, "mac required and it failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* We've deserialized tables from disk or the update server, now let's
|
||||
* swap them into the tables we're currently using.
|
||||
*
|
||||
* @param tablesKnown An array that maps table name to current version
|
||||
* @param tablesData An array that maps a table name to a Map which
|
||||
* contains key value pairs.
|
||||
* @returns Array of strings holding the names of tables we updated
|
||||
*/
|
||||
PROT_ListManager.prototype.importData_ = function(tablesKnown, tablesData) {
|
||||
this.dataImportedAtLeastOnce_ = true;
|
||||
|
||||
var changes = [];
|
||||
|
||||
// If our data has changed, update it
|
||||
if (tablesKnown && tablesData) {
|
||||
// Update our tables with the new data
|
||||
for (var name in tablesKnown) {
|
||||
// WireFormatReader constructs VersionParsers from scratch and doesn't
|
||||
// know about the requireMac flag, so check it now
|
||||
if (tablesKnown[name] && this.tablesKnown_[name] &&
|
||||
this.tablesKnown_[name] != tablesKnown[name] &&
|
||||
this.maybeCheckMac(this.tablesKnown_[name], tablesKnown[name])) {
|
||||
changes.push(name);
|
||||
|
||||
this.tablesKnown_[name].ImportVersion(tablesKnown[name]);
|
||||
this.tablesData[name] = tablesData[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback that is executed when we have updated the tables from
|
||||
* the server. We are provided with the new table versions and the
|
||||
* corresponding data.
|
||||
*
|
||||
* @param tablesKnown An array that maps table name to current version
|
||||
* @param tablesData An array that maps a table name to a Map which
|
||||
* contains key value pairs.
|
||||
*/
|
||||
PROT_ListManager.prototype.dataReady = function(tablesKnown, tablesData) {
|
||||
G_Debug(this, "Called dataReady");
|
||||
|
||||
// First, replace the current tables we're using
|
||||
var changes = this.importData_(tablesKnown, tablesData);
|
||||
|
||||
// Then serialize the new tables to disk
|
||||
if (changes.length) {
|
||||
G_Debug(this, "Committing " + changes.length + " changed tables to disk.");
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
this.storeTable(changes[i],
|
||||
this.tablesData[changes[i]],
|
||||
this.tablesKnown_[changes[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
this.rpcPending_ = false; // todo maybe can do away cuz asynch
|
||||
G_Debug(this, "Done writing data to disk.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a table to disk.
|
||||
*
|
||||
* @param tableName String holding the name of the table to serialize
|
||||
*
|
||||
* @param opt_table Reference to the Map holding the table (if omitted,
|
||||
* we look the table up)
|
||||
*
|
||||
* @param opt_parser Reference to the versionparser for this table (if
|
||||
* omitted we look the table up)
|
||||
*/
|
||||
PROT_ListManager.prototype.storeTable = function(tableName,
|
||||
opt_table,
|
||||
opt_parser) {
|
||||
|
||||
var table = opt_table ? opt_table : this.tablesData[tableName];
|
||||
var parser = opt_parser ? opt_parser : this.tablesKnown_[tableName];
|
||||
|
||||
if (!table || ! parser)
|
||||
G_Error(this, "Tried to serialize a non-existent table: " + tableName);
|
||||
|
||||
var wfw = new PROT_WireFormatWriter(this.threadQueue_);
|
||||
wfw.serialize(table, parser, BindToObject(this.writeDataFile,
|
||||
this,
|
||||
tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a serialized table and writes it into our application directory.
|
||||
*
|
||||
* @param tableName String containing the name of the table, used to
|
||||
* create the filename
|
||||
*
|
||||
* @param tableData Serialized version of the table
|
||||
*/
|
||||
PROT_ListManager.prototype.writeDataFile = function(tableName, tableData) {
|
||||
var filename = tableName + ".sst";
|
||||
|
||||
G_Debug(this, "Serializing to " + filename);
|
||||
|
||||
try {
|
||||
var tmpFile = G_File.createUniqueTempFile(filename);
|
||||
var tmpFileWriter = new G_FileWriter(tmpFile);
|
||||
|
||||
tmpFileWriter.write(tableData);
|
||||
tmpFileWriter.close();
|
||||
|
||||
} catch(e) {
|
||||
G_Debug(this, e);
|
||||
G_Error(this, "Couldn't write to temp file: " + filename);
|
||||
}
|
||||
|
||||
// Now overwrite!
|
||||
try {
|
||||
tmpFile.moveTo(this.appDir_, filename);
|
||||
} catch(e) {
|
||||
G_Debug(this, e);
|
||||
G_Error(this, "Couldn't overwrite existing table: " +
|
||||
tmpFile.path + ', ' +
|
||||
this.appDir_.path + ', ' + filename);
|
||||
tmpFile.remove(false /* not recursive */);
|
||||
}
|
||||
G_Debug(this, "Serializing to " + filename + " finished.");
|
||||
}
|
||||
|
||||
PROT_ListManager.prototype.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISample) ||
|
||||
iid.equals(Components.interfaces.nsIProtectionListManager))
|
||||
return this;
|
||||
|
||||
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return null;
|
||||
}
|
||||
|
||||
// A simple factory function that creates nsIProtectionTable instances based
|
||||
// on a name. The name is a string of the format
|
||||
// provider_name-semantic_type-table_type. For example, goog-white-enchash
|
||||
// or goog-black-url.
|
||||
function newProtectionTable(name) {
|
||||
G_Debug("protfactory", "Trying to create a new nsIProtectionTable: " + name);
|
||||
var tokens = name.split('-');
|
||||
var type = tokens[2];
|
||||
var table = Cc['@mozilla.org/protection/protectiontable;1?type=' + type]
|
||||
.createInstance(Ci.nsIProtectionTable);
|
||||
table.name = name;
|
||||
return table;
|
||||
}
|
||||
232
mozilla/toolkit/components/protection/content/map.js
Normal file
232
mozilla/toolkit/components/protection/content/map.js
Normal file
@@ -0,0 +1,232 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 is a map over which you can iterate (you can't get an iterator
|
||||
// for an Object used as a map). It is important to have an iterable
|
||||
// map if you have a large numbe ofkeys and you wish to process them
|
||||
// in chunks, for example on a thread.
|
||||
//
|
||||
// This Map's operations are all O(1) plus the time of the native
|
||||
// operations on its arrays and object. The tradeoff for this speed is
|
||||
// that the size of the map is always non-decreasing. That is, when an
|
||||
// item is erase()'d, it is merely marked as invalid and not actually
|
||||
// removed.
|
||||
//
|
||||
// This map is not safe if your keys are objects (they'll get
|
||||
// stringified to the same value in non-debug builds).
|
||||
//
|
||||
// Interface:
|
||||
// insert(key, value)
|
||||
// erase(key)
|
||||
// find(key)
|
||||
// getList() // This is our poor man's iterator
|
||||
// forEach(someFunc)
|
||||
// replace(otherMap) // Clones otherMap, replacing the current map
|
||||
// size // readonly attribute
|
||||
//
|
||||
// TODO: we could easily have this map periodically compact itself so as
|
||||
// to eliminate the size tradeoff.
|
||||
|
||||
/**
|
||||
* Create a new Map
|
||||
*
|
||||
* @constructor
|
||||
* @param opt_name A string used to name the map
|
||||
*/
|
||||
function G_Map(opt_name) {
|
||||
this.debugZone = "map";
|
||||
this.name = "noname";
|
||||
|
||||
// We store key/value pairs sequentially in membersArray
|
||||
this.membersArray_ = [];
|
||||
|
||||
// In membersMap we map from keys to indices in the membersArray
|
||||
this.membersMap_ = {};
|
||||
this.size_ = 0;
|
||||
|
||||
// set to true to allow list manager to update
|
||||
this.needsUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item
|
||||
*
|
||||
* @param key An key to add (overwrites previous key)
|
||||
* @param value The value to store at that key
|
||||
*/
|
||||
G_Map.prototype.insert = function(key, value) {
|
||||
if (key == null)
|
||||
throw new Error("Can't use null as a key");
|
||||
else if (typeof key == "object")
|
||||
throw new Error("This class is not objectsafe; use ObjectSafeMap");
|
||||
if (value === undefined)
|
||||
throw new Error("Can't store undefined values in this map");
|
||||
|
||||
// Get rid of the old value, if there was one, and increment size if not
|
||||
var oldIndexInArray = this.membersMap_[key];
|
||||
if (typeof oldIndexInArray == "number")
|
||||
this.membersArray_[oldIndexInArray] = undefined;
|
||||
else
|
||||
this.size_++;
|
||||
|
||||
var indexInArray = this.membersArray_.length;
|
||||
this.membersArray_.push({ "key": key, "value": value});
|
||||
this.membersMap_[key] = indexInArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key from the map
|
||||
*
|
||||
* @param key The key to remove
|
||||
* @returns Boolean indicating if the key was removed
|
||||
*/
|
||||
G_Map.prototype.erase = function(key) {
|
||||
var indexInArray = this.membersMap_[key];
|
||||
|
||||
if (indexInArray === undefined)
|
||||
return false;
|
||||
|
||||
this.size_--;
|
||||
delete this.membersMap_[key];
|
||||
// We could slice here, but that could be expensive if the map large
|
||||
this.membersArray_[indexInArray] = undefined;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Private lookup function
|
||||
*
|
||||
* @param key The key to look up
|
||||
* @returns The value at that key or undefined if it doesn't exist
|
||||
*/
|
||||
G_Map.prototype.find_ = function(key) {
|
||||
var indexInArray = this.membersMap_[key];
|
||||
return ((indexInArray === undefined) ?
|
||||
undefined :
|
||||
this.membersArray_[indexInArray].value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public lookup function
|
||||
*
|
||||
* @param key The key to look up
|
||||
* @returns The value at that key or undefined if it doesn't exist
|
||||
*/
|
||||
G_Map.prototype.find = function(key) {
|
||||
return this.find_(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of the keys we have. Use findValue to get the associated
|
||||
* value.
|
||||
*
|
||||
* TODO: its probably better to expose a real iterator, but this works
|
||||
* TODO: getting keys and values is a pain, do something more clever
|
||||
*
|
||||
* @returns Array of items in the map, some entries of which might be
|
||||
* undefined. Each item is an object with members key and
|
||||
* value
|
||||
*/
|
||||
G_Map.prototype.getKeys = function(count) {
|
||||
var ret = [];
|
||||
for (var i = 0; i < this.membersArray_.length; ++i) {
|
||||
ret.push(this.membersArray_[i].key);
|
||||
}
|
||||
count.value = ret.length;
|
||||
return ret;
|
||||
}
|
||||
|
||||
G_Map.prototype.findValue = function(key) {
|
||||
return this.find_(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace one map with the content of another
|
||||
*
|
||||
* IMPORTANT NOTE: This method copies references to objects, it does
|
||||
* NOT copy the objects themselves.
|
||||
*
|
||||
* @param other Reference to a G_Map that we should clone
|
||||
*/
|
||||
G_Map.prototype.replace = function(other) {
|
||||
this.membersMap_ = {};
|
||||
this.membersArray_ = [];
|
||||
|
||||
// TODO Make this faster, and possibly run it on a thread
|
||||
var lst = {};
|
||||
var otherList = other.getList(lst);
|
||||
for (var i = 0; i < otherList.length; i++)
|
||||
if (otherList[i]) {
|
||||
var index = this.membersArray_.length;
|
||||
this.membersArray_.push(otherList[i]);
|
||||
this.membersMap_[otherList[i].key] = index;
|
||||
}
|
||||
this.size_ = other.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a function to each of the key value pairs.
|
||||
*
|
||||
* @param func Function to apply to the map's key value pairs
|
||||
*/
|
||||
G_Map.prototype.forEach = function(func) {
|
||||
if (typeof func != "function")
|
||||
throw new Error("argument to forEach is not a function, it's a(n) " +
|
||||
typeof func);
|
||||
|
||||
for (var i = 0; i < this.membersArray_.length; i++)
|
||||
if (this.membersArray_[i])
|
||||
func(this.membersArray_[i].key, this.membersArray_[i].value);
|
||||
}
|
||||
|
||||
G_Map.prototype.QueryInterface = function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsISample) ||
|
||||
iid.equals(Components.interfaces.nsIProtectionTable))
|
||||
return this;
|
||||
|
||||
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Readonly size attribute.
|
||||
* @returns The number of keys in the map
|
||||
*/
|
||||
G_Map.prototype.__defineGetter__('size', function() {
|
||||
return this.size_;
|
||||
});
|
||||
176
mozilla/toolkit/components/protection/content/moz/alarm.js
Normal file
176
mozilla/toolkit/components/protection/content/moz/alarm.js
Normal file
@@ -0,0 +1,176 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// 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() { alert('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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
293
mozilla/toolkit/components/protection/content/moz/base64.js
Normal file
293
mozilla/toolkit/components/protection/content/moz/base64.js
Normal file
@@ -0,0 +1,293 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// Base64 en/decoding. Not much to say here except that we work with
|
||||
// decoded values in arrays of bytes. By "byte" I mean a number in [0,
|
||||
// 255].
|
||||
|
||||
|
||||
/**
|
||||
* Base64 en/decoder. Useful in contexts that don't have atob/btoa, or
|
||||
* when you need a custom encoding function (e.g., websafe base64).
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function G_Base64() {
|
||||
this.byteToCharMap_ = {};
|
||||
this.charToByteMap_ = {};
|
||||
this.byteToCharMapWebSafe_ = {};
|
||||
this.charToByteMapWebSafe_ = {};
|
||||
this.init_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Our default alphabet. Value 64 (=) is special; it means "nothing."
|
||||
*/
|
||||
G_Base64.ENCODED_VALS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789+/=";
|
||||
|
||||
/**
|
||||
* Our websafe alphabet. Value 64 (=) is special; it means "nothing."
|
||||
*/
|
||||
G_Base64.ENCODED_VALS_WEBSAFE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789-_=";
|
||||
|
||||
/**
|
||||
* We want quick mappings back and forth, so we precompute two maps.
|
||||
*/
|
||||
G_Base64.prototype.init_ = function() {
|
||||
for (var i = 0; i < G_Base64.ENCODED_VALS.length; i++) {
|
||||
this.byteToCharMap_[i] = G_Base64.ENCODED_VALS.charAt(i);
|
||||
this.charToByteMap_[this.byteToCharMap_[i]] = i;
|
||||
this.byteToCharMapWebSafe_[i] = G_Base64.ENCODED_VALS_WEBSAFE.charAt(i);
|
||||
this.charToByteMapWebSafe_[this.byteToCharMapWebSafe_[i]] = i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-encode an array of bytes.
|
||||
*
|
||||
* @param input An array of bytes (numbers with value in [0, 255]) to encode
|
||||
*
|
||||
* @param opt_webSafe Boolean indicating we should use the alternative alphabet
|
||||
*
|
||||
* @returns String containing the base64 encoding
|
||||
*/
|
||||
G_Base64.prototype.encodeByteArray = function(input, opt_webSafe) {
|
||||
|
||||
if (!(input instanceof Array))
|
||||
throw new Error("encodeByteArray takes an array as a parameter");
|
||||
|
||||
var byteToCharMap = opt_webSafe ?
|
||||
this.byteToCharMapWebSafe_ :
|
||||
this.byteToCharMap_;
|
||||
|
||||
var output = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < input.length) {
|
||||
|
||||
var byte1 = input[i];
|
||||
var haveByte2 = i + 1 < input.length;
|
||||
var byte2 = haveByte2 ? input[i + 1] : 0;
|
||||
var haveByte3 = i + 2 < input.length;
|
||||
var byte3 = haveByte3 ? input[i + 2] : 0;
|
||||
|
||||
var outByte1 = byte1 >> 2;
|
||||
var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
|
||||
var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
|
||||
var outByte4 = byte3 & 0x3F;
|
||||
|
||||
if (!haveByte3) {
|
||||
outByte4 = 64;
|
||||
|
||||
if (!haveByte2)
|
||||
outByte3 = 64;
|
||||
}
|
||||
|
||||
output.push(byteToCharMap[outByte1]);
|
||||
output.push(byteToCharMap[outByte2]);
|
||||
output.push(byteToCharMap[outByte3]);
|
||||
output.push(byteToCharMap[outByte4]);
|
||||
|
||||
i += 3;
|
||||
}
|
||||
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64-decode a string.
|
||||
*
|
||||
* @param input String to decode
|
||||
*
|
||||
* @param opt_webSafe Boolean indicating we should use the alternative alphabet
|
||||
*
|
||||
* @returns Array of bytes representing the decoded value.
|
||||
*/
|
||||
G_Base64.prototype.decodeString = function(input, opt_webSafe) {
|
||||
|
||||
if (input.length % 4)
|
||||
throw new Error("Length of b64-encoded data must be zero mod four");
|
||||
|
||||
var charToByteMap = opt_webSafe ?
|
||||
this.charToByteMapWebSafe_ :
|
||||
this.charToByteMap_;
|
||||
|
||||
var output = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < input.length) {
|
||||
|
||||
var byte1 = charToByteMap[input.charAt(i)];
|
||||
var byte2 = charToByteMap[input.charAt(i + 1)];
|
||||
var byte3 = charToByteMap[input.charAt(i + 2)];
|
||||
var byte4 = charToByteMap[input.charAt(i + 3)];
|
||||
|
||||
if (byte1 === undefined || byte2 === undefined ||
|
||||
byte3 === undefined || byte4 === undefined)
|
||||
throw new Error("String contains characters not in our alphabet: " +
|
||||
input);
|
||||
|
||||
var outByte1 = (byte1 << 2) | (byte2 >> 4);
|
||||
output.push(outByte1);
|
||||
|
||||
if (byte3 != 64) {
|
||||
var outByte2 = ((byte2 << 4) & 0xF0) | (byte3 >> 2);
|
||||
output.push(outByte2);
|
||||
|
||||
if (byte4 != 64) {
|
||||
var outByte3 = ((byte3 << 6) & 0xC0) | byte4;
|
||||
output.push(outByte3);
|
||||
}
|
||||
}
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that turns a string into an array of numbers.
|
||||
*
|
||||
* @param str String to arrify
|
||||
*
|
||||
* @returns Array holding numbers corresponding to the UCS character codes
|
||||
* of each character in str
|
||||
*/
|
||||
G_Base64.prototype.arrayifyString = function(str) {
|
||||
var output = [];
|
||||
for (var i = 0; i < str.length; i++)
|
||||
output.push(str.charCodeAt(i));
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that turns an array of numbers into the string
|
||||
* given by the concatenation of the characters to which the numbesr
|
||||
* correspond (got that?).
|
||||
*
|
||||
* @param array Array of numbers representing characters
|
||||
*
|
||||
* @returns Stringification of the array
|
||||
*/
|
||||
G_Base64.prototype.stringifyArray = function(array) {
|
||||
var output = [];
|
||||
for (var i = 0; i < array.length; i++)
|
||||
output[i] = String.fromCharCode(array[i]);
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lame unittesting function
|
||||
*/
|
||||
function TEST_G_Base64() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "base64 UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var b = new G_Base64();
|
||||
|
||||
// Let's see if it's sane by feeding it some well-known values. Index i
|
||||
// has the input and index i+1 has the expected value.
|
||||
|
||||
var tests =
|
||||
[ "", "",
|
||||
"f", "Zg==",
|
||||
"fo", "Zm8=",
|
||||
"foo", "Zm9v",
|
||||
"foob", "Zm9vYg==",
|
||||
"fooba", "Zm9vYmE=",
|
||||
"foobar", "Zm9vYmFy"];
|
||||
|
||||
for (var i = 0; i < tests.length; i += 2) {
|
||||
var enc = b.encodeByteArray(b.arrayifyString(tests[i]));
|
||||
G_Assert(z, enc === tests[i + 1],
|
||||
"Error encoding: " + tests[i] + " (got " + enc +
|
||||
" but wanted " + tests[i + 1] + ")");
|
||||
var dec = b.stringifyArray(b.decodeString(enc));
|
||||
G_Assert(z, dec === tests[i],
|
||||
"Error deocding " + enc + " (got " + dec +
|
||||
" but wanted " + tests[i] + ")");
|
||||
}
|
||||
|
||||
// Now run it through its paces
|
||||
|
||||
var numIterations = 100;
|
||||
for (var i = 0; i < numIterations; i++) {
|
||||
|
||||
var input = [];
|
||||
for (var j = 0; j < i; j++)
|
||||
input[j] = j % 256;
|
||||
|
||||
var encoded = b.encodeByteArray(input);
|
||||
var decoded = b.decodeString(encoded);
|
||||
G_Assert(z, !(encoded.length % 4), "Encoded length not a multiple of 4?");
|
||||
G_Assert(z, input.length == decoded.length,
|
||||
"Decoded length not equal to input length?");
|
||||
|
||||
for (var j = 0; j < i; j++)
|
||||
G_Assert(z, input[j] === decoded[j], "Values differ at position " + j);
|
||||
}
|
||||
|
||||
// Test non-websafe / websafe difference
|
||||
var test = ">>>???>>>???";
|
||||
var enc = b.encodeByteArray(b.arrayifyString(test));
|
||||
G_Assert(z, enc == "Pj4+Pz8/Pj4+Pz8/", "Non-websafe broken?");
|
||||
enc = b.encodeByteArray(b.arrayifyString(test), true /* websafe */);
|
||||
G_Assert(z, enc == "Pj4-Pz8_Pj4-Pz8_", "Websafe encoding broken");
|
||||
var dec = b.stringifyArray(b.decodeString(enc, true /* websafe */));
|
||||
G_Assert(z, dec === test, "Websafe dencoding broken");
|
||||
|
||||
// Test parsing malformed characters
|
||||
var caught = false;
|
||||
try {
|
||||
b.decodeString("foooooo+oooo", true /*websafe*/);
|
||||
} catch(e) {
|
||||
caught = true;
|
||||
}
|
||||
G_Assert(z, caught, "Didn't throw on malformed input");
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// 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_ = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
this.initialized_ = false;
|
||||
}
|
||||
|
||||
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.initialized_ = true;
|
||||
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.initialized_)
|
||||
throw new Error("You must initialize the hasher first!");
|
||||
this.hasher_.update(input.split(""), input.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the hash's internal state with input given in an array. Can be
|
||||
* called multiple times for incrementeal hash updates.
|
||||
*
|
||||
* @param input Array containing data to hash.
|
||||
*/
|
||||
G_CryptoHasher.prototype.updateFromArray = function(input) {
|
||||
if (!this.initialized_)
|
||||
throw new Error("You must initialize the hasher first!");
|
||||
this.hasher_.update(input, input.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The hash value as a string (sequence of 8-bit values)
|
||||
*/
|
||||
G_CryptoHasher.prototype.digestRaw = function() {
|
||||
return this.hasher_.finish(false /* not b64 encoded */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The hash value as a base64-encoded string
|
||||
*/
|
||||
G_CryptoHasher.prototype.digestBase64 = function() {
|
||||
return this.hasher_.finish(true /* b64 encoded */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Lame unittest function
|
||||
*/
|
||||
function TEST_G_CryptoHasher() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "cryptohasher UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
/* Add test vectors
|
||||
* var hasher = new G_CryptoHasher();
|
||||
* hasher.updateFromtring("0");
|
||||
* hasher.updateFromtring("1");
|
||||
* etc
|
||||
* var digest = hasher.digestHex();
|
||||
*/
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
879
mozilla/toolkit/components/protection/content/moz/debug.js
Normal file
879
mozilla/toolkit/components/protection/content/moz/debug.js
Normal file
@@ -0,0 +1,879 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (original author)
|
||||
* Annie Sullivan <sullivan@google.com>
|
||||
* Aaron Boodman <aa@google.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
// Generic logging/debugging functionality that:
|
||||
//
|
||||
// (*) when disabled compiles to no-ops at worst (for calls to the service)
|
||||
// and to nothing at best (calls to G_Debug() and similar are compiled
|
||||
// away when you use a jscompiler that strips dead code)
|
||||
//
|
||||
// (*) has dynamically configurable/creatable debugging "zones" enabling
|
||||
// selective logging
|
||||
//
|
||||
// (*) hides its plumbing so that all calls in different zones are uniform,
|
||||
// so you can drop files using this library into other apps that use it
|
||||
// without any configuration
|
||||
//
|
||||
// (*) can be controlled programmatically or via preferences. The
|
||||
// preferences that control the service and its zones are under
|
||||
// the preference branch "safebrowsing-debug-service."
|
||||
//
|
||||
// (*) outputs function call traces when the "loggifier" zone is enabled
|
||||
//
|
||||
// (*) can write output to logfiles so that you can get a call trace
|
||||
// from someone who is having a problem
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// var G_GDEBUG = true // Enable this module
|
||||
// var G_debugService = new G_DebugService(); // in global context
|
||||
//
|
||||
// // You can use it with arbitrary primitive first arguement
|
||||
// G_Debug("myzone", "Yo yo yo"); // outputs: [myzone] Yo yo yo\n
|
||||
//
|
||||
// // But it's nice to use it with an object; it will probe for the zone name
|
||||
// function Obj() {
|
||||
// this.debugZone = "someobj";
|
||||
// }
|
||||
// Obj.prototype.foo = function() {
|
||||
// G_Debug(this, "foo called");
|
||||
// }
|
||||
// (new Obj).foo(); // outputs: [someobj] foo called\n
|
||||
//
|
||||
// G_debugService.loggifier.loggify(Obj.prototype); // enable call tracing
|
||||
//
|
||||
// // En/disable specific zones programmatically (you can also use preferences)
|
||||
// G_debugService.enableZone("somezone");
|
||||
// G_debugService.disableZone("someotherzone");
|
||||
// G_debugService.enableAllZones();
|
||||
//
|
||||
// // We also have asserts and errors:
|
||||
// G_Error(this, "Some error occurred"); // will throw
|
||||
// G_Assert(this, (x > 3), "x not greater than three!"); // will throw
|
||||
//
|
||||
// See classes below for more methods.
|
||||
//
|
||||
// TODO add abililty to alert() instead of dump()? Should be easy.
|
||||
// TODO add code to set prefs when not found to the default value of a tristate
|
||||
// TODO add error level support
|
||||
// TODO add ability to turn off console output
|
||||
//
|
||||
// -------> TO START DEBUGGING: set G_GDEBUG to true
|
||||
|
||||
// These are the functions code will typically call. Everything is
|
||||
// wrapped in if's so we can compile it away when G_GDEBUG is false.
|
||||
|
||||
|
||||
if (!isDef(G_GDEBUG)) {
|
||||
throw new Error("G_GDEBUG constant must be set before loading debug.js");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write out a debugging message.
|
||||
*
|
||||
* @param who The thingy to convert into a zone name corresponding to the
|
||||
* zone to which this message belongs
|
||||
* @param msg Message to output
|
||||
*/
|
||||
function G_Debug(who, msg) {
|
||||
if (G_GDEBUG) {
|
||||
G_GetDebugZone(who).debug(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debugs loudly
|
||||
*/
|
||||
function G_DebugL(who, msg) {
|
||||
if (G_GDEBUG) {
|
||||
var zone = G_GetDebugZone(who);
|
||||
|
||||
if (zone.zoneIsEnabled()) {
|
||||
G_debugService.dump(
|
||||
G_File.LINE_END_CHAR +
|
||||
"************************************************************" +
|
||||
G_File.LINE_END_CHAR);
|
||||
|
||||
G_Debug(who, msg);
|
||||
|
||||
G_debugService.dump(
|
||||
"************************************************************" +
|
||||
G_File.LINE_END_CHAR +
|
||||
G_File.LINE_END_CHAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out a call tracing message
|
||||
*
|
||||
* @param who The thingy to convert into a zone name corresponding to the
|
||||
* zone to which this message belongs
|
||||
* @param msg Message to output
|
||||
*/
|
||||
function G_TraceCall(who, msg) {
|
||||
if (G_GDEBUG) {
|
||||
if (G_debugService.callTracingEnabled()) {
|
||||
G_debugService.dump(msg + G_File.LINE_END_CHAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out an error (and throw)
|
||||
*
|
||||
* @param who The thingy to convert into a zone name corresponding to the
|
||||
* zone to which this message belongs
|
||||
* @param msg Message to output
|
||||
*/
|
||||
function G_Error(who, msg) {
|
||||
if (G_GDEBUG) {
|
||||
G_GetDebugZone(who).error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert something as true and signal an error if it's not
|
||||
*
|
||||
* @param who The thingy to convert into a zone name corresponding to the
|
||||
* zone to which this message belongs
|
||||
* @param condition Boolean condition to test
|
||||
* @param msg Message to output
|
||||
*/
|
||||
function G_Assert(who, condition, msg) {
|
||||
if (G_GDEBUG) {
|
||||
G_GetDebugZone(who).assert(condition, msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert two things are equal (as in ==).
|
||||
*/
|
||||
function G_AssertEqual(who, expected, actual, msg) {
|
||||
if (G_GDEBUG) {
|
||||
G_GetDebugZone(who).assert(
|
||||
expected == actual,
|
||||
msg + " Expected: {%s}, got: {%s}".subs(expected, actual));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that takes input and returns the DebugZone
|
||||
* corresponding to it.
|
||||
*
|
||||
* @param who Arbitrary input that will be converted into a zone name. Most
|
||||
* likely an object that has .debugZone property, or a string.
|
||||
* @returns The DebugZone object corresponding to the input
|
||||
*/
|
||||
function G_GetDebugZone(who) {
|
||||
if (G_GDEBUG) {
|
||||
var zone = "?";
|
||||
|
||||
if (who && who.debugZone) {
|
||||
zone = who.debugZone;
|
||||
} else if (isString(who)) {
|
||||
zone = who;
|
||||
}
|
||||
|
||||
return G_debugService.getZone(zone);
|
||||
}
|
||||
}
|
||||
|
||||
// Classes that implement the functionality.
|
||||
|
||||
/**
|
||||
* A debug "zone" is a string derived from arbitrary types (but
|
||||
* typically derived from another string or an object). All debugging
|
||||
* messages using a particular zone can be enabled or disabled
|
||||
* independent of other zones. This enables you to turn on/off logging
|
||||
* of particular objects or modules. This object implements a single
|
||||
* zone and the methods required to use it.
|
||||
*
|
||||
* @constructor
|
||||
* @param service Reference to the DebugService object we use for
|
||||
* registration
|
||||
* @param prefix String indicating the unique prefix we should use
|
||||
* when creating preferences to control this zone
|
||||
* @param zone String indicating the name of the zone
|
||||
*/
|
||||
function G_DebugZone(service, prefix, zone) {
|
||||
if (G_GDEBUG) {
|
||||
this.debugService_ = service;
|
||||
this.prefix_ = prefix;
|
||||
this.zone_ = zone;
|
||||
this.zoneEnabledPrefName_ = prefix + ".zone." + this.zone_;
|
||||
this.settings_ = new G_DebugSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating if this zone is enabled
|
||||
*/
|
||||
G_DebugZone.prototype.zoneIsEnabled = function() {
|
||||
if (G_GDEBUG) {
|
||||
var explicit = this.settings_.getSetting(this.zoneEnabledPrefName_, null);
|
||||
|
||||
if (explicit !== null) {
|
||||
return explicit;
|
||||
} else {
|
||||
return this.debugService_.allZonesEnabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable this logging zone
|
||||
*/
|
||||
G_DebugZone.prototype.enableZone = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.zoneEnabledPrefName_, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this logging zone
|
||||
*/
|
||||
G_DebugZone.prototype.disableZone = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.zoneEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a debugging message to this zone
|
||||
*
|
||||
* @param msg String of message to write
|
||||
*/
|
||||
G_DebugZone.prototype.debug = function(msg) {
|
||||
if (G_GDEBUG) {
|
||||
if (this.zoneIsEnabled()) {
|
||||
this.debugService_.dump("[%s] %s%s".subs(this.zone_,
|
||||
msg,
|
||||
G_File.LINE_END_CHAR));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an error to this zone and throw
|
||||
*
|
||||
* @param msg String of error to write
|
||||
*/
|
||||
G_DebugZone.prototype.error = function(msg) {
|
||||
if (G_GDEBUG) {
|
||||
this.debugService_.dump("[%s] %s%s".subs(this.zone_,
|
||||
msg,
|
||||
G_File.LINE_END_CHAR));
|
||||
throw new Error(msg);
|
||||
debugger;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert something as true and error if it is not
|
||||
*
|
||||
* @param condition Boolean condition to test
|
||||
* @param msg String of message to write if is false
|
||||
*/
|
||||
G_DebugZone.prototype.assert = function(condition, msg) {
|
||||
if (G_GDEBUG) {
|
||||
if (condition !== true) {
|
||||
G_Error(this.zone_, "ASSERT FAILED: " + msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The debug service handles auto-registration of zones, namespacing
|
||||
* the zones preferences, and various global settings such as whether
|
||||
* all zones are enabled.
|
||||
*
|
||||
* @constructor
|
||||
* @param opt_prefix Optional string indicating the unique prefix we should
|
||||
* use when creating preferences
|
||||
*/
|
||||
function G_DebugService(opt_prefix) {
|
||||
if (G_GDEBUG) {
|
||||
this.prefix_ = opt_prefix ? opt_prefix : "safebrowsing-debug-service";
|
||||
this.consoleEnabledPrefName_ = this.prefix_ + ".alsologtoconsole";
|
||||
this.allZonesEnabledPrefName_ = this.prefix_ + ".enableallzones";
|
||||
this.callTracingEnabledPrefName_ = this.prefix_ + ".trace-function-calls";
|
||||
this.logFileEnabledPrefName_ = this.prefix_ + ".logfileenabled";
|
||||
this.logFileErrorLevelPrefName_ = this.prefix_ + ".logfile-errorlevel";
|
||||
this.zones_ = {};
|
||||
|
||||
this.loggifier = new G_Loggifier();
|
||||
this.settings_ = new G_DebugSettings();
|
||||
|
||||
// We observe the console service so that we can echo errors that get
|
||||
// reported there to the file log.
|
||||
Cc["@mozilla.org/consoleservice;1"]
|
||||
.getService(Ci.nsIConsoleService)
|
||||
.registerListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Error levels for reporting console messages to the log.
|
||||
G_DebugService.ERROR_LEVEL_INFO = "INFO";
|
||||
G_DebugService.ERROR_LEVEL_WARNING = "WARNING";
|
||||
G_DebugService.ERROR_LEVEL_EXCEPTION = "EXCEPTION";
|
||||
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating if we should send messages to the jsconsole
|
||||
*/
|
||||
G_DebugService.prototype.alsoDumpToConsole = function() {
|
||||
if (G_GDEBUG) {
|
||||
return this.settings_.getSetting(this.consoleEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns whether to log output to a file as well as the console.
|
||||
*/
|
||||
G_DebugService.prototype.logFileIsEnabled = function() {
|
||||
if (G_GDEBUG) {
|
||||
return this.settings_.getSetting(this.logFileEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on file logging. dump() output will also go to the file specified by
|
||||
* setLogFile()
|
||||
*/
|
||||
G_DebugService.prototype.enableLogFile = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.logFileEnabledPrefName_, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off file logging
|
||||
*/
|
||||
G_DebugService.prototype.disableLogFile = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.logFileEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns an nsIFile instance pointing to the current log file location
|
||||
*/
|
||||
G_DebugService.prototype.getLogFile = function() {
|
||||
if (G_GDEBUG) {
|
||||
return this.logFile_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new log file location
|
||||
*/
|
||||
G_DebugService.prototype.setLogFile = function(file) {
|
||||
if (G_GDEBUG) {
|
||||
this.logFile_ = file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables sending messages to the jsconsole
|
||||
*/
|
||||
G_DebugService.prototype.enableDumpToConsole = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.consoleEnabledPrefName_, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables sending messages to the jsconsole
|
||||
*/
|
||||
G_DebugService.prototype.disableDumpToConsole = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.consoleEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zone Name of the zone to get
|
||||
* @returns The DebugZone object corresopnding to input. If not such
|
||||
* zone exists, a new one is created and returned
|
||||
*/
|
||||
G_DebugService.prototype.getZone = function(zone) {
|
||||
if (G_GDEBUG) {
|
||||
if (!this.zones_[zone])
|
||||
this.zones_[zone] = new G_DebugZone(this, this.prefix_, zone);
|
||||
|
||||
return this.zones_[zone];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zone Zone to enable debugging for
|
||||
*/
|
||||
G_DebugService.prototype.enableZone = function(zone) {
|
||||
if (G_GDEBUG) {
|
||||
var toEnable = this.getZone(zone);
|
||||
toEnable.enableZone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zone Zone to disable debugging for
|
||||
*/
|
||||
G_DebugService.prototype.disableZone = function(zone) {
|
||||
if (G_GDEBUG) {
|
||||
var toDisable = this.getZone(zone);
|
||||
toDisable.disableZone();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating whether debugging is enabled for all zones
|
||||
*/
|
||||
G_DebugService.prototype.allZonesEnabled = function() {
|
||||
if (G_GDEBUG) {
|
||||
return this.settings_.getSetting(this.allZonesEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables all debugging zones
|
||||
*/
|
||||
G_DebugService.prototype.enableAllZones = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.allZonesEnabledPrefName_, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables all debugging zones
|
||||
*/
|
||||
G_DebugService.prototype.disableAllZones = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.allZonesEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating whether call tracing is enabled
|
||||
*/
|
||||
G_DebugService.prototype.callTracingEnabled = function() {
|
||||
if (G_GDEBUG) {
|
||||
return this.settings_.getSetting(this.callTracingEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables call tracing
|
||||
*/
|
||||
G_DebugService.prototype.enableCallTracing = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.callTracingEnabledPrefName_, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables call tracing
|
||||
*/
|
||||
G_DebugService.prototype.disableCallTracing = function() {
|
||||
if (G_GDEBUG) {
|
||||
this.settings_.setDefault(this.callTracingEnabledPrefName_, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum error that will be reported to the log.
|
||||
*/
|
||||
G_DebugService.prototype.getLogFileErrorLevel = function() {
|
||||
if (G_GDEBUG) {
|
||||
var level = this.settings_.getSetting(this.logFileErrorLevelPrefName_,
|
||||
G_DebugService.ERROR_LEVEL_EXCEPTION);
|
||||
|
||||
return level.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum error level that will be reported to the log.
|
||||
*/
|
||||
G_DebugService.prototype.setLogFileErrorLevel = function(level) {
|
||||
if (G_GDEBUG) {
|
||||
// normalize case just to make it slightly easier to not screw up.
|
||||
level = level.toUpperCase();
|
||||
|
||||
if (level != G_DebugService.ERROR_LEVEL_INFO &&
|
||||
level != G_DebugService.ERROR_LEVEL_WARNING &&
|
||||
level != G_DebugService.ERROR_LEVEL_EXCEPTION) {
|
||||
throw new Error("Invalid error level specified: {" + level + "}");
|
||||
}
|
||||
|
||||
this.settings_.setDefault(this.logFileErrorLevelPrefName_, level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal dump() method
|
||||
*
|
||||
* @param msg String of message to dump
|
||||
*/
|
||||
G_DebugService.prototype.dump = function(msg) {
|
||||
if (G_GDEBUG) {
|
||||
dump(msg);
|
||||
|
||||
if (this.alsoDumpToConsole()) {
|
||||
try {
|
||||
var console = Components.classes['@mozilla.org/consoleservice;1']
|
||||
.getService(Components.interfaces.nsIConsoleService);
|
||||
console.logStringMessage(msg);
|
||||
} catch(e) {
|
||||
dump("G_DebugZone ERROR: COULD NOT DUMP TO CONSOLE" +
|
||||
G_File.LINE_END_CHAR);
|
||||
}
|
||||
}
|
||||
|
||||
this.maybeDumpToFile(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified message to the log file, if file logging is enabled.
|
||||
*/
|
||||
G_DebugService.prototype.maybeDumpToFile = function(msg) {
|
||||
if (this.logFileIsEnabled() && this.logFile_) {
|
||||
if (!this.logWriter_) {
|
||||
this.logWriter_ = new G_FileWriter(this.logFile_, true);
|
||||
}
|
||||
|
||||
this.logWriter_.write(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements nsIConsoleListener.observe(). Gets called when an error message
|
||||
* gets reported to the console and sends it to the log file as well.
|
||||
*/
|
||||
G_DebugService.prototype.observe = function(consoleMessage) {
|
||||
if (G_GDEBUG) {
|
||||
var errorLevel = this.getLogFileErrorLevel();
|
||||
|
||||
// consoleMessage can be either nsIScriptError or nsIConsoleMessage. The
|
||||
// latter does not have things like line number, etc. So we special case
|
||||
// it first.
|
||||
if (!(consoleMessage instanceof Ci.nsIScriptError)) {
|
||||
// Only report these messages if the error level is INFO.
|
||||
if (errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
|
||||
this.maybeDumpToFile(G_DebugService.ERROR_LEVEL_INFO + ": " +
|
||||
consoleMessage.message + G_File.LINE_END_CHAR);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// We make a local copy of these fields because writing to it doesn't seem
|
||||
// to work.
|
||||
var flags = consoleMessage.flags;
|
||||
var sourceName = consoleMessage.sourceName;
|
||||
var lineNumber = consoleMessage.lineNumber;
|
||||
|
||||
// Sometimes, a scripterror instance won't have any flags set. We
|
||||
// default to exception.
|
||||
if (!flags) {
|
||||
flags = Ci.nsIScriptError.exceptionFlag;
|
||||
}
|
||||
|
||||
// Default the filename and line number if they aren't set.
|
||||
if (!sourceName) {
|
||||
sourceName = "<unknown>";
|
||||
}
|
||||
|
||||
if (!lineNumber) {
|
||||
lineNumber = "<unknown>";
|
||||
}
|
||||
|
||||
// Report the error in the log file.
|
||||
if (flags & Ci.nsIScriptError.warningFlag) {
|
||||
// Only report warnings if the error level is warning or better.
|
||||
if (errorLevel == G_DebugService.ERROR_LEVEL_WARNING ||
|
||||
errorLevel == G_DebugService.ERROR_LEVEL_INFO) {
|
||||
this.reportScriptError_(consoleMessage.message,
|
||||
sourceName,
|
||||
lineNumber,
|
||||
G_DebugService.ERROR_LEVEL_WARNING);
|
||||
}
|
||||
} else if (flags & Ci.nsIScriptError.exceptionFlag) {
|
||||
// Always report exceptions.
|
||||
this.reportScriptError_(consoleMessage.message,
|
||||
sourceName,
|
||||
lineNumber,
|
||||
G_DebugService.ERROR_LEVEL_EXCEPTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private helper to report an nsIScriptError instance to the log/console.
|
||||
*/
|
||||
G_DebugService.prototype.reportScriptError_ = function(message, sourceName,
|
||||
lineNumber, label) {
|
||||
var message = ["",
|
||||
"------------------------------------------------------------",
|
||||
label + ": " + message,
|
||||
"location: " + sourceName + ", " + "line: " + lineNumber,
|
||||
"------------------------------------------------------------",
|
||||
"",
|
||||
""].join(G_File.LINE_END_CHAR);
|
||||
|
||||
dump(message);
|
||||
this.maybeDumpToFile(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A class that instruments methods so they output a call trace,
|
||||
* including the values of their actual parameters and return value.
|
||||
* This code is mostly stolen from Aaron Boodman's original
|
||||
* implementation in clobber utils.
|
||||
*
|
||||
* Note that this class uses the "loggifier" debug zone, so you'll see
|
||||
* a complete call trace when that zone is enabled.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function G_Loggifier() {
|
||||
if (G_GDEBUG) {
|
||||
// Careful not to loggify ourselves!
|
||||
this.mark_(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks an object as having been loggified. Loggification is not
|
||||
* idempotent :)
|
||||
*
|
||||
* @param obj Object to be marked
|
||||
*/
|
||||
G_Loggifier.prototype.mark_ = function(obj) {
|
||||
if (G_GDEBUG) {
|
||||
obj.__loggified_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param obj Object to be examined
|
||||
* @returns Boolean indicating if the object has been loggified
|
||||
*/
|
||||
G_Loggifier.prototype.isLoggified = function(obj) {
|
||||
if (G_GDEBUG) {
|
||||
return !!obj.__loggified_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to extract the class name from the constructor definition.
|
||||
* Assumes the object was created using new.
|
||||
*
|
||||
* @param constructor String containing the definition of a constructor,
|
||||
* for example what you'd get by examining obj.constructor
|
||||
* @returns Name of the constructor/object if it could be found, else "???"
|
||||
*/
|
||||
G_Loggifier.prototype.getFunctionName_ = function(constructor) {
|
||||
if (G_GDEBUG) {
|
||||
return constructor.name || "???";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps all the methods in an object so that call traces are
|
||||
* automatically outputted.
|
||||
*
|
||||
* @param obj Object to loggify. SHOULD BE THE PROTOTYPE OF A USER-DEFINED
|
||||
* object. You can get into trouble if you attempt to
|
||||
* loggify something that isn't, for example the Window.
|
||||
*
|
||||
* Any additional parameters are considered method names which should not be
|
||||
* loggified.
|
||||
*
|
||||
* Usage:
|
||||
* G_debugService.loggifier.loggify(MyClass.prototype,
|
||||
* "firstMethodNotToLog",
|
||||
* "secondMethodNotToLog",
|
||||
* ... etc ...);
|
||||
*/
|
||||
G_Loggifier.prototype.loggify = function(obj) {
|
||||
if (G_GDEBUG) {
|
||||
if (!G_debugService.callTracingEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof window != "undefined" && obj == window ||
|
||||
this.isLoggified(obj)) // Don't go berserk!
|
||||
return;
|
||||
|
||||
var zone = G_GetDebugZone(obj);
|
||||
if (!zone || !zone.zoneIsEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.mark_(obj);
|
||||
|
||||
// Helper function returns an instrumented version of
|
||||
// objName.meth, with "this" bound properly. (BTW, because we're
|
||||
// in a conditional here, functions will only be defined as
|
||||
// they're encountered during execution, so declare this helper
|
||||
// before using it.)
|
||||
|
||||
function wrap(meth, objName, methName) {
|
||||
return function() {
|
||||
|
||||
// First output the call along with actual parameters
|
||||
var args = new Array(arguments.length);
|
||||
var argsString = "";
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
args[i] = arguments[i];
|
||||
argsString += (i == 0 ? "" : ", ");
|
||||
|
||||
if (isFunction(args[i])) {
|
||||
argsString += "[function]";
|
||||
} else {
|
||||
argsString += args[i];
|
||||
}
|
||||
}
|
||||
|
||||
G_TraceCall(this, "> " + objName + "." + methName + "(" +
|
||||
argsString + ")");
|
||||
|
||||
// Then run the function, capturing the return value and throws
|
||||
try {
|
||||
var retVal = meth.apply(this, arguments);
|
||||
var reportedRetVal = retVal;
|
||||
|
||||
if (typeof reportedRetVal == "undefined")
|
||||
reportedRetVal = "void";
|
||||
else if (reportedRetVal === "")
|
||||
reportedRetVal = "\"\" (empty string)";
|
||||
} catch (e) {
|
||||
if (e && !e.__logged) {
|
||||
G_TraceCall(this, "Error: " + e.message + ". " +
|
||||
e.fileName + ": " + e.lineNumber);
|
||||
try {
|
||||
e.__logged = true;
|
||||
} catch (e2) {
|
||||
// Sometimes we can't add the __logged flag because it's an
|
||||
// XPC wrapper
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
throw e; // Re-throw!
|
||||
}
|
||||
|
||||
// And spit it out already
|
||||
G_TraceCall(
|
||||
this,
|
||||
"< " + objName + "." + methName + ": " + reportedRetVal);
|
||||
|
||||
return retVal;
|
||||
};
|
||||
};
|
||||
|
||||
var ignoreLookup = {};
|
||||
|
||||
if (arguments.length > 1) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
ignoreLookup[arguments[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap each method of obj
|
||||
for (var p in obj) {
|
||||
// Work around bug in Firefox. In ffox typeof RegExp is "function",
|
||||
// so make sure this really is a function. Bug as of FFox 1.5b2.
|
||||
if (typeof obj[p] == "function" && obj[p].call && !ignoreLookup[p]) {
|
||||
var objName = this.getFunctionName_(obj.constructor);
|
||||
obj[p] = wrap(obj[p], objName, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple abstraction around debug settings. The thing with debug settings is
|
||||
* that we want to be able to specify a default in the application's startup,
|
||||
* but have that default be overridable by the user via their prefs.
|
||||
*
|
||||
* To generalize this, we package up a dictionary of defaults with the
|
||||
* preferences tree. If a setting isn't in the preferences tree, then we grab it
|
||||
* from the defaults.
|
||||
*/
|
||||
function G_DebugSettings() {
|
||||
this.defaults_ = {};
|
||||
this.prefs_ = new G_Preferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a settings, optionally defaulting to a given value if it
|
||||
* doesn't exist. If no default is specified, the default is |undefined|.
|
||||
*/
|
||||
G_DebugSettings.prototype.getSetting = function(name, opt_default) {
|
||||
var override = this.prefs_.getPref(name, null);
|
||||
|
||||
if (override !== null) {
|
||||
return override;
|
||||
} else if (isDef(this.defaults_[name])) {
|
||||
return this.defaults_[name];
|
||||
} else {
|
||||
return opt_default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value for a setting. If the user doesn't override it with a
|
||||
* preference, this is the value which will be returned by getSetting().
|
||||
*/
|
||||
G_DebugSettings.prototype.setDefault = function(name, val) {
|
||||
this.defaults_[name] = val;
|
||||
}
|
||||
|
||||
var G_debugService = new G_DebugService(); // Instantiate us!
|
||||
|
||||
if (G_GDEBUG) {
|
||||
G_debugService.enableAllZones();
|
||||
}
|
||||
307
mozilla/toolkit/components/protection/content/moz/filesystem.js
Normal file
307
mozilla/toolkit/components/protection/content/moz/filesystem.js
Normal file
@@ -0,0 +1,307 @@
|
||||
/* ***** 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):
|
||||
* Aaron Boodman <aa@google.com> (original author)
|
||||
* Raphael Moll <raphael@google.com>
|
||||
* Fritz Schneider <fritz@google.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
|
||||
// Utilities for working with nsIFile and related interfaces.
|
||||
|
||||
/**
|
||||
* Stub for an nsIFile wrapper which doesn't exist yet. Perhaps in the future
|
||||
* we could add functionality to nsILocalFile which would be useful to us here,
|
||||
* but for now, no need for such. This could be done by setting
|
||||
* __proto__ to an instance of nsIFile, for example. Neat.
|
||||
*/
|
||||
var G_File = {};
|
||||
|
||||
/**
|
||||
* Returns an nsIFile pointing to the user's home directory, or optionally, a
|
||||
* file inside that dir.
|
||||
*/
|
||||
G_File.getHomeFile = function(opt_file) {
|
||||
return this.getSpecialFile("Home", opt_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an nsIFile pointing to the current profile folder, or optionally, a
|
||||
* file inside that dir.
|
||||
*/
|
||||
G_File.getProfileFile = function(opt_file) {
|
||||
return this.getSpecialFile("ProfD", opt_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an nsIFile pointing to the temporary dir, or optionally, a file
|
||||
* inside that dir.
|
||||
*/
|
||||
G_File.getTempFile = function(opt_file) {
|
||||
return this.getSpecialFile("TmpD", opt_file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an nsIFile pointing to one of the special named directories defined
|
||||
* by Firefox, such as the user's home directory, the profile directory, etc.
|
||||
*
|
||||
* As a convenience, callers may specify the opt_file argument to get that file
|
||||
* within the special directory instead.
|
||||
*
|
||||
* http://lxr.mozilla.org/seamonkey/source/xpcom/io/nsDirectoryServiceDefs.h
|
||||
* http://kb.mozillazine.org/File_IO#Getting_special_files
|
||||
*/
|
||||
G_File.getSpecialFile = function(loc, opt_file) {
|
||||
var file = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get(loc, Ci.nsILocalFile);
|
||||
|
||||
if (opt_file) {
|
||||
file.append(opt_file);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a pointer to a unique file in the temporary directory
|
||||
* with an optional base name.
|
||||
*/
|
||||
G_File.createUniqueTempFile = function(opt_baseName) {
|
||||
var baseName = (opt_baseName || (new Date().getTime())) + ".tmp";
|
||||
|
||||
var file = this.getSpecialFile("TmpD", baseName);
|
||||
file.createUnique(file.NORMAL_FILE_TYPE, 0644);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a pointer to a unique temporary directory, with
|
||||
* an optional base name.
|
||||
*/
|
||||
G_File.createUniqueTempDir = function(opt_baseName) {
|
||||
var baseName = (opt_baseName || (new Date().getTime())) + ".tmp";
|
||||
|
||||
var dir = this.getSpecialFile("TmpD", baseName);
|
||||
dir.createUnique(dir.DIRECTORY_TYPE, 0744);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method to retrieve an nsIFile from a file:// URI.
|
||||
*/
|
||||
G_File.fromFileURI = function(uri) {
|
||||
// Ensure they use file:// url's: discourages platform-specific paths
|
||||
if (uri.indexOf("file://") != 0)
|
||||
throw new Error("File path must be a file:// URL");
|
||||
|
||||
var fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"]
|
||||
.createInstance(Ci.nsIFileProtocolHandler);
|
||||
return fileHandler.getFileFromURLSpec(uri);
|
||||
}
|
||||
|
||||
// IO Constants
|
||||
|
||||
G_File.PR_RDONLY = 0x01; // read-only
|
||||
G_File.PR_WRONLY = 0x02; // write only
|
||||
G_File.PR_RDWR = 0x04; // reading and writing
|
||||
G_File.PR_CREATE_FILE = 0x08; // create if it doesn't exist
|
||||
G_File.PR_APPEND = 0x10; // file pntr reset to end prior to writes
|
||||
G_File.PR_TRUNCATE = 0x20; // file exists its length is set to zero
|
||||
G_File.PR_SYNC = 0x40; // writes wait for data to be physically written
|
||||
G_File.PR_EXCL = 0x80; // file does not exist ? created : no action
|
||||
|
||||
// The character(s) to use for line-endings, which are platform-specific.
|
||||
// This doesn't work for mac os9, but I don't know of a good way to detect
|
||||
// OS9-ness from JS.
|
||||
G_File.LINE_END_CHAR =
|
||||
Cc["@mozilla.org/network/protocol;1?name=https"]
|
||||
.getService(Ci.nsIHttpProtocolHandler)
|
||||
.platform.toLowerCase().indexOf("win") == 0 ? "\r\n" : "\n";
|
||||
|
||||
/**
|
||||
* A class which can read a file incrementally or all at once. Parameter can be
|
||||
* either an nsIFile instance or a string file:// URI.
|
||||
* Note that this class is not compatible with non-ascii data.
|
||||
*/
|
||||
function G_FileReader(file) {
|
||||
this.file_ = isString(file) ? G_File.fromFileURI(file) : file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to read the entire contents of a file. Parameter can be either
|
||||
* an nsIFile instance or a string file:// URI.
|
||||
*/
|
||||
G_FileReader.readAll = function(file) {
|
||||
var reader = new G_FileReader(file);
|
||||
|
||||
try {
|
||||
return reader.read();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read up to opt_maxBytes from the stream. If opt_maxBytes is not specified,
|
||||
* the entire file is read.
|
||||
*/
|
||||
G_FileReader.prototype.read = function(opt_maxBytes) {
|
||||
if (!this.stream_) {
|
||||
var fs = Cc["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Ci.nsIFileInputStream);
|
||||
fs.init(this.file_, G_File.PR_RDONLY, 0444, 0);
|
||||
|
||||
this.stream_ = Cc["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Ci.nsIScriptableInputStream);
|
||||
this.stream_.init(fs);
|
||||
}
|
||||
|
||||
if (!isDef(opt_maxBytes)) {
|
||||
opt_maxBytes = this.stream_.available();
|
||||
}
|
||||
|
||||
return this.stream_.read(opt_maxBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream. This step is required when reading is done.
|
||||
*/
|
||||
G_FileReader.prototype.close = function(opt_maxBytes) {
|
||||
if (this.stream_) {
|
||||
this.stream_.close();
|
||||
this.stream_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO(anyone): Implement G_LineReader. The interface should be something like:
|
||||
// for (var line = null; line = reader.readLine();) {
|
||||
// // do something with line
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Writes a file incrementally or all at once.
|
||||
* Note that this class is not compatible with non-ascii data.
|
||||
*/
|
||||
function G_FileWriter(file, opt_append) {
|
||||
this.file_ = typeof file == "string" ? G_File.fromFileURI(file) : file;
|
||||
this.append_ = !!opt_append;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to write to a file in one step.
|
||||
*/
|
||||
G_FileWriter.writeAll = function(file, data, opt_append) {
|
||||
var writer = new G_FileWriter(file, opt_append);
|
||||
|
||||
try {
|
||||
return writer.write(data);
|
||||
} finally {
|
||||
writer.close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes out to the file. Returns the number of bytes written.
|
||||
*/
|
||||
G_FileWriter.prototype.write = function(data) {
|
||||
if (!this.stream_) {
|
||||
this.stream_ = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
|
||||
var flags = G_File.PR_WRONLY |
|
||||
G_File.PR_CREATE_FILE |
|
||||
(this.append_ ? G_File.PR_APPEND : G_File.PR_TRUNCATE);
|
||||
|
||||
|
||||
this.stream_.init(this.file_,
|
||||
flags,
|
||||
-1 /* default perms */,
|
||||
0 /* no special behavior */);
|
||||
}
|
||||
|
||||
return this.stream_.write(data, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes out to file followed by the approriate line-ending character for
|
||||
* the current platform.
|
||||
*/
|
||||
G_FileWriter.prototype.writeLine = function(data) {
|
||||
this.write(data + G_File.LINE_END_CHAR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the file. This must becalled when writing is done.
|
||||
*/
|
||||
G_FileWriter.prototype.close = function() {
|
||||
if (this.stream_) {
|
||||
this.stream_.close();
|
||||
this.stream_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function TEST_G_File() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "gfile UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
// Test G_File.createUniqueTempir
|
||||
try {
|
||||
var dir = G_File.createUniqueTempDir();
|
||||
} catch(e) {
|
||||
G_Debug(z, e);
|
||||
G_Assert(z, false, "Failed to create temp dir");
|
||||
}
|
||||
|
||||
G_Assert(z, dir.exists(), "Temp dir doesn't exist: " + dir.path);
|
||||
G_Assert(z, dir.isDirectory(), "Temp dir isn't a directory: " + dir.path);
|
||||
G_Assert(z, dir.isReadable(), "Temp dir isn't readable: " + dir.path);
|
||||
G_Assert(z, dir.isWritable(), "Temp dir isn't writable: " + dir.path);
|
||||
|
||||
dir.remove(true /* recurse */);
|
||||
|
||||
// TODO: WE NEED MORE UNITTESTS!
|
||||
|
||||
G_Debug(z, "PASS");
|
||||
}
|
||||
}
|
||||
101
mozilla/toolkit/components/protection/content/moz/lang.js
Normal file
101
mozilla/toolkit/components/protection/content/moz/lang.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/* ***** 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):
|
||||
* Aaron Boodman <aa@google.com> (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 ***** */
|
||||
|
||||
|
||||
// Firefox-specific additions to lib/js/lang.js.
|
||||
|
||||
|
||||
/**
|
||||
* The always-useful alert.
|
||||
*/
|
||||
function alert(msg, opt_title) {
|
||||
opt_title |= "message";
|
||||
|
||||
Cc["@mozilla.org/embedcomp/prompt-service;1"]
|
||||
.getService(Ci.nsIPromptService)
|
||||
.alert(null, opt_title, msg.toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The instanceof operator cannot be used on a pure js object to determine if
|
||||
* it implements a certain xpcom interface. The QueryInterface method can, but
|
||||
* it throws an error which makes things more complex.
|
||||
*/
|
||||
function jsInstanceOf(obj, iid) {
|
||||
try {
|
||||
obj.QueryInterface(iid);
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e == Components.results.NS_ERROR_NO_INTERFACE) {
|
||||
return false;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unbelievably, Function inheritence is broken in chrome in Firefox
|
||||
* (still as of FFox 1.5b1). Hence if you're working in an extension
|
||||
* and not using the subscriptloader, you can't use the method
|
||||
* above. Instead, use this global function that does roughly the same
|
||||
* thing.
|
||||
*
|
||||
***************************************************************************
|
||||
* NOTE THE REVERSED ORDER OF FUNCTION AND OBJECT REFERENCES AS bind() *
|
||||
***************************************************************************
|
||||
*
|
||||
* // Example to bind foo.bar():
|
||||
* var bound = BindToObject(bar, foo, "arg1", "arg2");
|
||||
* bound("arg3", "arg4");
|
||||
*
|
||||
* @param func {string} Reference to the function to be bound
|
||||
*
|
||||
* @param obj {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.
|
||||
*
|
||||
* @param opt_{...} Dummy optional arguments to make a jscompiler happy
|
||||
*
|
||||
* @returns {function} A partially-applied form of the speficied function.
|
||||
*/
|
||||
function BindToObject(func, obj, opt_A, opt_B, opt_C, opt_D, opt_E, opt_F) {
|
||||
// This is the sick product of Aaron's mind. Not for the feint of heart.
|
||||
var args = Array.prototype.splice.call(arguments, 1, arguments.length);
|
||||
return Function.prototype.bind.apply(func, args);
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// ObjectSafeMap is, shockingly, a Map with which it is safe to use
|
||||
// objects as keys. It currently uses parallel arrays for storage,
|
||||
// rendering it inefficient (linear) for large maps. We can always
|
||||
// swap out the implementation if this becomes a problem. Note that
|
||||
// this class uses strict equality to determine equivalent keys.
|
||||
//
|
||||
// Interface:
|
||||
//
|
||||
// insert(key, value)
|
||||
// erase(key) // Returns true if key erased, false if not found
|
||||
// find(key) // Returns undefined if key not found
|
||||
// replace(otherMap) // Clones otherMap, replacing the current map
|
||||
// forEach(func)
|
||||
// size() // Returns number of items in the map
|
||||
//
|
||||
// TODO: should probably make it iterable by implementing getList();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ObjectSafeMap.
|
||||
*
|
||||
* @param opt_name A string used to name the map
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function G_ObjectSafeMap(opt_name) {
|
||||
this.debugZone = "objectsafemap";
|
||||
this.name_ = opt_name ? opt_name : "noname";
|
||||
this.keys_ = [];
|
||||
this.values_ = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return the index of a key.
|
||||
*
|
||||
* @param key An key to find
|
||||
*
|
||||
* @returns Index in the keys array where the key is found, -1 if not
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.indexOfKey_ = function(key) {
|
||||
for (var i = 0; i < this.keys_.length; i++)
|
||||
if (this.keys_[i] === key)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item
|
||||
*
|
||||
* @param key An key to add (overwrites previous key)
|
||||
*
|
||||
* @param value The value to store at that key
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.insert = function(key, value) {
|
||||
if (key === null)
|
||||
throw new Error("Can't use null as a key");
|
||||
if (value === undefined)
|
||||
throw new Error("Can't store undefined values in this map");
|
||||
|
||||
var i = this.indexOfKey_(key);
|
||||
if (i == -1) {
|
||||
this.keys_.push(key);
|
||||
this.values_.push(value);
|
||||
} else {
|
||||
this.keys_[i] = key;
|
||||
this.values_[i] = value;
|
||||
}
|
||||
|
||||
G_Assert(this, this.keys_.length == this.values_.length,
|
||||
"Different number of keys than values!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key from the map
|
||||
*
|
||||
* @param key The key to remove
|
||||
*
|
||||
* @returns Boolean indicating if the key was removed
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.erase = function(key) {
|
||||
var keyLocation = this.indexOfKey_(key);
|
||||
var keyFound = keyLocation != -1;
|
||||
if (keyFound) {
|
||||
this.keys_.splice(keyLocation, 1);
|
||||
this.values_.splice(keyLocation, 1);
|
||||
}
|
||||
G_Assert(this, this.keys_.length == this.values_.length,
|
||||
"Different number of keys than values!");
|
||||
|
||||
return keyFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a key in the map
|
||||
*
|
||||
* @param key The key to look up
|
||||
*
|
||||
* @returns The value at that key or undefined if it doesn't exist
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.find = function(key) {
|
||||
var keyLocation = this.indexOfKey_(key);
|
||||
return keyLocation == -1 ? undefined : this.values_[keyLocation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace one map with the content of another
|
||||
*
|
||||
* @param map input map that needs to be merged into our map
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.replace = function(other) {
|
||||
this.keys_ = [];
|
||||
this.values_ = [];
|
||||
for (var i = 0; i < other.keys_.length; i++) {
|
||||
this.keys_.push(other.keys_[i]);
|
||||
this.values_.push(other.values_[i]);
|
||||
}
|
||||
|
||||
G_Assert(this, this.keys_.length == this.values_.length,
|
||||
"Different number of keys than values!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a function to each of the key value pairs.
|
||||
*
|
||||
* @param func Function to apply to the map's key value pairs
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.forEach = function(func) {
|
||||
if (typeof func != "function")
|
||||
throw new Error("argument to forEach is not a function, it's a(n) " +
|
||||
typeof func);
|
||||
|
||||
for (var i = 0; i < this.keys_.length; i++)
|
||||
func(this.keys_[i], this.values_[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The number of keys in the map
|
||||
*/
|
||||
G_ObjectSafeMap.prototype.size = function() {
|
||||
return this.keys_.length;
|
||||
}
|
||||
|
||||
// Cheesey, yes, but it's what we've got
|
||||
function TEST_G_ObjectSafeMap() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "map UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var m = new G_ObjectSafeMap();
|
||||
G_Assert(z, m.size() == 0, "Initial size not zero");
|
||||
|
||||
var o1 = new Object;
|
||||
var v1 = "1";
|
||||
var o2 = new Object;
|
||||
var v2 = "1";
|
||||
|
||||
G_Assert(z, m.find(o1) == undefined, "Found non-existent item");
|
||||
|
||||
m.insert(o1, v1);
|
||||
m.insert(o2, v2);
|
||||
|
||||
G_Assert(z, m.size() == 2, "Size not 2");
|
||||
G_Assert(z, m.find(o1) == "1", "Didn't find item 1");
|
||||
G_Assert(z, m.find(o2) == "1", "Didn't find item 1");
|
||||
|
||||
m.insert(o1, "2");
|
||||
|
||||
G_Assert(z, m.size() == 2, "Size not 2");
|
||||
G_Assert(z, m.find(o1) == "2", "Didn't find item 1");
|
||||
G_Assert(z, m.find(o2) == "1", "Didn't find item 1");
|
||||
|
||||
m.erase(o1);
|
||||
|
||||
G_Assert(z, m.size() == 1, "Size not 1");
|
||||
G_Assert(z, m.find(o1) == undefined, "Found item1");
|
||||
G_Assert(z, m.find(o2) == "1", "Didn't find item 2");
|
||||
|
||||
m.erase(o1);
|
||||
|
||||
G_Assert(z, m.size() == 1, "Size not 1");
|
||||
G_Assert(z, m.find(o1) == undefined, "Found item1");
|
||||
G_Assert(z, m.find(o2) == "1", "Didn't find item 2");
|
||||
|
||||
m.erase(o2);
|
||||
|
||||
G_Assert(z, m.size() == 0, "Size not 0");
|
||||
G_Assert(z, m.find(o2) == undefined, "Found item2");
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
174
mozilla/toolkit/components/protection/content/moz/observer.js
Normal file
174
mozilla/toolkit/components/protection/content/moz/observer.js
Normal file
@@ -0,0 +1,174 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// 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_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked by the observerservice
|
||||
*/
|
||||
G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) {
|
||||
this.observeFunction_(subject, topic, data);
|
||||
if (this.onlyOnce_)
|
||||
this.unregister();
|
||||
}
|
||||
|
||||
|
||||
function TEST_G_Observer() {
|
||||
if (G_GDEBUG) {
|
||||
|
||||
var z = "observer UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var regularObserverRan = 0;
|
||||
var observerServiceObserverRan = 0;
|
||||
|
||||
function regularObserver() {
|
||||
regularObserverRan++;
|
||||
};
|
||||
|
||||
function observerServiceObserver() {
|
||||
observerServiceObserverRan++;
|
||||
};
|
||||
|
||||
var service = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
var topic = "google-observer-test";
|
||||
|
||||
var o1 = new G_ObserverWrapper(topic, regularObserver);
|
||||
service.addObserver(o1, topic, false);
|
||||
|
||||
new G_ObserverServiceObserver(topic,
|
||||
observerServiceObserver, true /* once */);
|
||||
|
||||
// Notifications happen synchronously, so this is easy
|
||||
service.notifyObservers(null, topic, null);
|
||||
service.notifyObservers(null, topic, null);
|
||||
|
||||
G_Assert(z, regularObserverRan == 2, "Regular observer broken");
|
||||
G_Assert(z, observerServiceObserverRan == 1, "ObsServObs broken");
|
||||
|
||||
service.removeObserver(o1, topic);
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
361
mozilla/toolkit/components/protection/content/moz/preferences.js
Normal file
361
mozilla/toolkit/components/protection/content/moz/preferences.js
Normal file
@@ -0,0 +1,361 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// 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();
|
||||
// alert(p.getPref("some-true-pref")); // shows true
|
||||
// alert(p.getPref("no-such-pref", true)); // shows true
|
||||
// alert(p.getPref("no-such-pref", null)); // shows null
|
||||
//
|
||||
// function observe(prefThatChanged) {
|
||||
// alert("Pref changed: " + prefThatChanged);
|
||||
// };
|
||||
//
|
||||
// p.addObserver("somepref", observe);
|
||||
// p.setPref("somepref", true); // alerts
|
||||
// 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_ = {};
|
||||
|
||||
var startPoint = opt_startPoint || null;
|
||||
var prefSvc = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService);
|
||||
|
||||
if (opt_getDefaultBranch) {
|
||||
this.prefs_ = prefSvc.getDefaultBranch(startPoint);
|
||||
} else {
|
||||
this.prefs_ = prefSvc.getBranch(startPoint);
|
||||
}
|
||||
|
||||
// QI to prefinternal in case we want to add observers
|
||||
this.prefs_.QueryInterface(Ci.nsIPrefBranchInternal);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a boolean preference
|
||||
*
|
||||
* @param which Name of preference to set
|
||||
* @param value Boolean indicating value to set
|
||||
*
|
||||
* @deprecated Just use setPref.
|
||||
*/
|
||||
G_Preferences.prototype.setBoolPref = function(which, value) {
|
||||
return this.setPref(which, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a boolean preference. WILL THROW IF PREFERENCE DOES NOT EXIST.
|
||||
* If you don't want this behavior, use getBoolPrefOrDefault.
|
||||
*
|
||||
* @param which Name of preference to get.
|
||||
*
|
||||
* @deprecated Just use getPref.
|
||||
*/
|
||||
G_Preferences.prototype.getBoolPref = function(which) {
|
||||
return this.prefs_.getBoolPref(which);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a boolean preference or return some default value if it doesn't
|
||||
* exist. Note that the default doesn't have to be bool -- it could be
|
||||
* anything (e.g., you could pass in null and check if the return
|
||||
* value is === null to determine if the pref doesn't exist).
|
||||
*
|
||||
* @param which Name of preference to get.
|
||||
* @param def Value to return if the preference doesn't exist
|
||||
* @returns Boolean value of the pref if it exists, else def
|
||||
*
|
||||
* @deprecated Just use getPref.
|
||||
*/
|
||||
G_Preferences.prototype.getBoolPrefOrDefault = function(which, def) {
|
||||
return this.getPref(which, def);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a boolean preference if it exists. If it doesn't, set its value
|
||||
* to a default and return the default. Note that the default will be
|
||||
* coherced to a bool if it is set, but not in the return value.
|
||||
*
|
||||
* @param which Name of preference to get.
|
||||
* @param def Value to set and return if the preference doesn't exist
|
||||
* @returns Boolean value of the pref if it exists, else def
|
||||
*
|
||||
* @deprecated Just use getPref.
|
||||
*/
|
||||
G_Preferences.prototype.getBoolPrefOrDefaultAndSet = function(which, def) {
|
||||
try {
|
||||
return this.prefs_.getBoolPref(which);
|
||||
} catch(e) {
|
||||
this.prefs_.setBoolPref(which, !!def); // The !! forces boolean conversion
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var observer = new G_PreferenceObserver(callback);
|
||||
// Need to store the observer we create so we can eventually unregister it
|
||||
if (!this.observers_[which])
|
||||
this.observers_[which] = new G_ObjectSafeMap();
|
||||
this.observers_[which].insert(callback, 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 observer = this.observers_[which].find(callback);
|
||||
G_Assert(this, !!observer, "Tried to unregister a nonexistant observer");
|
||||
this.prefs_.removeObserver(which, observer);
|
||||
this.observers_[which].erase(callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
var Ci = Ci;
|
||||
if (iid.equals(Ci.nsISupports) ||
|
||||
iid.equals(Ci.nsIObserves) ||
|
||||
iid.equals(Ci.nsISupportsWeakReference))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
|
||||
|
||||
// UNITTESTS
|
||||
|
||||
function TEST_G_Preferences() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "preferences UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var p = new G_Preferences();
|
||||
|
||||
var testPref = "test-preferences-unittest";
|
||||
var noSuchPref = "test-preferences-unittest-aypabtu";
|
||||
|
||||
// Used to test observing
|
||||
var observeCount = 0;
|
||||
function observe(prefChanged) {
|
||||
G_Assert(z, prefChanged == testPref, "observer broken");
|
||||
observeCount++;
|
||||
};
|
||||
|
||||
// Test setting, getting, and observing
|
||||
p.addObserver(testPref, observe);
|
||||
p.setBoolPref(testPref, true);
|
||||
G_Assert(z, p.getBoolPref(testPref), "get or set broken");
|
||||
G_Assert(z, observeCount == 1, "observer adding not working");
|
||||
|
||||
p.removeObserver(testPref, observe);
|
||||
|
||||
p.setBoolPref(testPref, false);
|
||||
G_Assert(z, observeCount == 1, "observer removal not working");
|
||||
G_Assert(z, !p.getBoolPref(testPref), "get broken");
|
||||
try {
|
||||
p.getBoolPref(noSuchPref);
|
||||
G_Assert(z, false, "getting non-existent pref didn't throw");
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
// Try the default varieties
|
||||
G_Assert(z,
|
||||
p.getBoolPrefOrDefault(noSuchPref, true), "default borken (t)");
|
||||
G_Assert(z, !p.getBoolPrefOrDefault(noSuchPref, false), "default borken");
|
||||
|
||||
// And the default-and-set variety
|
||||
G_Assert(z, p.getBoolPrefOrDefaultAndSet(noSuchPref, true),
|
||||
"default and set broken (didnt default");
|
||||
G_Assert(z,
|
||||
p.getBoolPref(noSuchPref), "default and set broken (didnt set)");
|
||||
|
||||
// Remember to clean up the prefs we've set, and test removing prefs
|
||||
// while we're at it
|
||||
p.clearPref(noSuchPref);
|
||||
G_Assert(z, !p.getBoolPrefOrDefault(noSuchPref, false), "clear broken");
|
||||
|
||||
p.clearPref(testPref);
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
164
mozilla/toolkit/components/protection/content/moz/protocol4.js
Normal file
164
mozilla/toolkit/components/protection/content/moz/protocol4.js
Normal file
@@ -0,0 +1,164 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cheesey unittests
|
||||
*/
|
||||
function TEST_G_Protocol4Parser() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "protocol4 UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var p = new G_Protocol4Parser();
|
||||
|
||||
function isEmpty(map) {
|
||||
for (var key in map)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
G_Assert(z, isEmpty(p.parse(null)), "Parsing null broken");
|
||||
G_Assert(z, isEmpty(p.parse("")), "Parsing nothing broken");
|
||||
|
||||
var t = "foo:3:bar";
|
||||
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing one line broken");
|
||||
|
||||
t = "foo:3:bar\n";
|
||||
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing line with lf broken");
|
||||
|
||||
t = "foo:3:bar\r\n";
|
||||
G_Assert(z, p.parse(t)["foo"] === "bar", "Parsing with crlf broken");
|
||||
|
||||
|
||||
t = "foo:3:bar\nbar:3:baz\r\nbom:3:yaz\n";
|
||||
G_Assert(z, p.parse(t)["foo"] === "bar", "First in multiline");
|
||||
G_Assert(z, p.parse(t)["bar"] === "baz", "Second in multiline");
|
||||
G_Assert(z, p.parse(t)["bom"] === "yaz", "Third in multiline");
|
||||
G_Assert(z, p.parse(t)[""] === undefined, "Non-existent in multiline");
|
||||
|
||||
// Test serialization
|
||||
|
||||
var original = {
|
||||
"1": "1",
|
||||
"2": "2",
|
||||
"foobar": "baz",
|
||||
"hello there": "how are you?" ,
|
||||
};
|
||||
var deserialized = p.parse(p.serialize(original));
|
||||
for (var key in original)
|
||||
G_Assert(z, original[key] === deserialized[key],
|
||||
"Trouble (de)serializing " + key);
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 is the class we use to canonicalize URLs for TRTables of type
|
||||
// url. We maximally URL-decode the URL, treating +'s as if they're
|
||||
// not special. We then specially URL-encode it (we encode ASCII
|
||||
// values [0, 32] (whitespace or unprintable), 37 (%), [127, 255]
|
||||
// (unprintable)).
|
||||
//
|
||||
// This mapping is not a function. That is, multiple URLs can map to
|
||||
// the same canonical representation. However this is OK because
|
||||
// collisions happen only when there are weird characters (e.g.,
|
||||
// nonprintables), and the canonical representation makes us robust
|
||||
// to some weird kinds of encoding we could see.
|
||||
//
|
||||
// All members are static at this point -- this is basically a namespace.
|
||||
|
||||
|
||||
/**
|
||||
* Create a new URLCanonicalizer. Useless because members are static.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_URLCanonicalizer() {
|
||||
throw new Error("No need to instantiate a canonicalizer at this point.");
|
||||
}
|
||||
|
||||
PROT_URLCanonicalizer.debugZone = "urlcanonicalizer";
|
||||
|
||||
PROT_URLCanonicalizer.hexChars_ = "0123456789ABCDEF";
|
||||
|
||||
/**
|
||||
* Helper funciton to (maybe) convert a two-character hex string into its
|
||||
* decimal numerical equivalent
|
||||
*
|
||||
* @param hh String of length two that might be a valid hex sequence
|
||||
*
|
||||
* @returns Number: NaN if hh wasn't valid hex, else the appropriate decimal
|
||||
* value
|
||||
*/
|
||||
PROT_URLCanonicalizer.hexPairToInt_ = function(hh) {
|
||||
return Number("0x" + hh);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to hex-encode a number
|
||||
*
|
||||
* @param val Number in range [0, 255]
|
||||
*
|
||||
* @returns String containing the hex representation of that number (sans 0x)
|
||||
*/
|
||||
PROT_URLCanonicalizer.toHex_ = function(val) {
|
||||
var retVal = PROT_URLCanonicalizer.hexChars_.charAt((val >> 4) & 15) +
|
||||
PROT_URLCanonicalizer.hexChars_.charAt(val & 15);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the canonical version of the given URL for lookup in a table of
|
||||
* type -url.
|
||||
*
|
||||
* @param url String to canonicalize
|
||||
*
|
||||
* @returns String containing the canonicalized url (maximally url-decoded,
|
||||
* then specially url-encoded)
|
||||
*/
|
||||
PROT_URLCanonicalizer.canonicalizeURL_ = function(url) {
|
||||
var arrayOfASCIIVals = PROT_URLCanonicalizer.fullyDecodeURL_(url);
|
||||
return PROT_URLCanonicalizer.specialEncodeURL_(arrayOfASCIIVals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximally URL-decode a URL. This breaks the semantics of the URL, but
|
||||
* we don't care because we're using it for lookup, not for navigation.
|
||||
* We break multi-byte UTF-8 escape sequences as well, but we don't care
|
||||
* so long as they canonicalize the same way consistently (they do).
|
||||
*
|
||||
* @param url String containing the URL to maximally decode. Should ONLY
|
||||
* contain characters with UCS codepoints U+0001 to U+00FF
|
||||
* (the ASCII set minus null).
|
||||
*
|
||||
* @returns Array of ASCII values corresponding to the decoded sequence of
|
||||
* characters in the url
|
||||
*/
|
||||
PROT_URLCanonicalizer.fullyDecodeURL_ = function(url) {
|
||||
|
||||
// The goals here are: simplicity, correctness, and most of all
|
||||
// portability; we want the same implementation of canonicalization
|
||||
// wherever we use it so as to to minimize the chances of
|
||||
// inconsistency. For example, we have to do this canonicalization
|
||||
// on URLs we get from third parties, and at the lookup server when
|
||||
// we get a request.
|
||||
//
|
||||
// The following implementation should translate easily to any
|
||||
// language that supports arrays and pointers or references. Note
|
||||
// that arrays are pointer types in JavaScript, so foo = [some,
|
||||
// array] points foo at the array; it doesn't copy it. The
|
||||
// implementation is efficient (linear) so long as most %'s in the
|
||||
// url belong to valid escape sequences and there aren't too many
|
||||
// doubly-escaped values.
|
||||
|
||||
// The basic idea is to copy current input to output, decoding escape
|
||||
// sequences as we see them, until we decode a %. At that point we start
|
||||
// copying into the "next iteration buffer" instead of the output buffer;
|
||||
// we do this so we can accomodate multiply-escaped strings. When we hit
|
||||
// the end of the input, we take the "next iteration buffer" as our input,
|
||||
// and start over.
|
||||
|
||||
var nextIteration = url.split("");
|
||||
var output = [];
|
||||
|
||||
while (nextIteration.length) {
|
||||
|
||||
var decodedAPercent = false;
|
||||
var thisIteration = nextIteration;
|
||||
var nextIteration = [];
|
||||
|
||||
var i = 0;
|
||||
while (i < thisIteration.length) {
|
||||
|
||||
var c = thisIteration[i];
|
||||
if (c == "%" && i + 2 < thisIteration.length) {
|
||||
|
||||
// Peek ahead to see if we have a valid HH sequence
|
||||
var asciiVal =
|
||||
PROT_URLCanonicalizer.hexPairToInt_(thisIteration[i + 1] +
|
||||
thisIteration[i + 2]);
|
||||
if (!isNaN(asciiVal)) {
|
||||
i += 2; // Valid HH sequence; consume it
|
||||
|
||||
if (asciiVal == 0) // We special case nulls
|
||||
asciiVal = 1;
|
||||
|
||||
c = String.fromCharCode(asciiVal);
|
||||
if (c == "%")
|
||||
decodedAPercent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (decodedAPercent)
|
||||
nextIteration[nextIteration.length] = c;
|
||||
else
|
||||
output[output.length] = c.charCodeAt(0);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximally URL-decode a URL (same as fullyDecodeURL_ except that it
|
||||
* returns a string). Useful for making unittests more readable.
|
||||
*
|
||||
* @param url String containing the URL to maximally decode. Should ONLY
|
||||
* contain characters with UCS codepoints U+0001 to U+00FF
|
||||
* (the ASCII set minus null).
|
||||
*
|
||||
* @returns String containing the decoded URL
|
||||
*/
|
||||
PROT_URLCanonicalizer.fullyDecodeURLAsString_ = function(url) {
|
||||
var arrayOfASCIIVals = PROT_URLCanonicalizer.fullyDecodeURL_(url);
|
||||
var s = "";
|
||||
for (var i = 0; i < arrayOfASCIIVals.length; i++)
|
||||
s += String.fromCharCode(arrayOfASCIIVals[i]);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specially URL-encode the given array of ASCII values. We want to encode
|
||||
* the charcters: [0, 32], 37, [127, 255].
|
||||
*
|
||||
* @param arrayOfASCIIValues Array of ascii values (numbers) to encode
|
||||
*
|
||||
* @returns String corresonding to the escaped URL
|
||||
*/
|
||||
PROT_URLCanonicalizer.specialEncodeURL_ = function(arrayOfASCIIValues) {
|
||||
|
||||
var output = [];
|
||||
for (var i = 0; i < arrayOfASCIIValues.length; i++) {
|
||||
var n = arrayOfASCIIValues[i];
|
||||
|
||||
if (n <= 32 || n == 37 || n >= 127)
|
||||
output.push("%" + ((!n) ? "01" : PROT_URLCanonicalizer.toHex_(n)));
|
||||
else
|
||||
output.push(String.fromCharCode(n));
|
||||
}
|
||||
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lame unittesting function
|
||||
*/
|
||||
function TEST_PROT_URLCanonicalizer() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "urlcanonicalizer UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
|
||||
// ------ TEST HEX GOTCHA ------
|
||||
|
||||
var hexify = PROT_URLCanonicalizer.toHex_;
|
||||
var shouldHaveLeadingZeros = hexify(0) + hexify(1);
|
||||
G_Assert(z, shouldHaveLeadingZeros == "0001",
|
||||
"Need to append leading zeros to hex rep value <= 15 !")
|
||||
|
||||
|
||||
// ------ TEST DECODING ------
|
||||
|
||||
// For convenience, shorten the function name
|
||||
var dec = PROT_URLCanonicalizer.fullyDecodeURLAsString_;
|
||||
|
||||
// Test empty string
|
||||
G_Assert(z, dec("") == "", "decoding empty string");
|
||||
|
||||
// Test decoding of all characters
|
||||
var allCharsEncoded = "";
|
||||
var allCharsEncodedLowercase = "";
|
||||
var allCharsAsString = "";
|
||||
// Special case null
|
||||
allCharsEncoded += "%01";
|
||||
allCharsEncodedLowercase += "%01";
|
||||
allCharsAsString += String.fromCharCode(1);
|
||||
for (var i = 1; i < 256; i++) {
|
||||
allCharsEncoded += "%" + PROT_URLCanonicalizer.toHex_(i);
|
||||
allCharsEncodedLowercase += "%" +
|
||||
PROT_URLCanonicalizer.toHex_(i).toLowerCase();
|
||||
allCharsAsString += String.fromCharCode(i);
|
||||
}
|
||||
G_Assert(z, dec(allCharsEncoded) == allCharsAsString, "decoding escaped");
|
||||
G_Assert(z, dec(allCharsEncodedLowercase) == allCharsAsString,
|
||||
"decoding lowercase");
|
||||
|
||||
// Test %-related edge cases
|
||||
G_Assert(z, dec("%") == "%", "1 percent");
|
||||
G_Assert(z, dec("%xx") == "%xx", "1 percent, two non-hex");
|
||||
G_Assert(z, dec("%%") == "%%", "2 percent");
|
||||
G_Assert(z, dec("%%%") == "%%%", "3 percent");
|
||||
G_Assert(z, dec("%%%%") == "%%%%", "4 percent");
|
||||
G_Assert(z, dec("%1") == "%1", "1 percent, one nonhex");
|
||||
G_Assert(z, dec("%1z") == "%1z", "1 percent, two nonhex");
|
||||
G_Assert(z, dec("a%1z") == "a%1z", "nonhex, 1 percent, two nonhex");
|
||||
G_Assert(z, dec("abc%d%e%fg%hij%klmno%") == "abc%d%e%fg%hij%klmno%",
|
||||
"lots of percents, no hex");
|
||||
|
||||
// Test repeated %-decoding. Note: %25 --> %, %32 --> 2, %35 --> 5
|
||||
G_Assert(z, dec("%25") == "%", "single-encoded %");
|
||||
G_Assert(z, dec("%25%32%35") == "%", "double-encoded %");
|
||||
G_Assert(z, dec("asdf%25%32%35asd") == "asdf%asd", "double-encoded % 2");
|
||||
G_Assert(z, dec("%%%25%32%35asd%%") == "%%%asd%%", "double-encoded % 3");
|
||||
G_Assert(z, dec("%25%32%35%25%32%35%25%32%35") == "%%%",
|
||||
"sequenctial double-encoded %");
|
||||
G_Assert(z, dec("%2525252525252525") == "%", "many-encoded %");
|
||||
G_Assert(z, dec("%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A22%252833%252944_55%252B") == "~a!b@c#d$e%f^00&11*22(33)44_55+",
|
||||
"4x-encoded string");
|
||||
|
||||
|
||||
// ------ TEST ENCODING ------
|
||||
|
||||
// For convenience, shorten the function name
|
||||
var enc = PROT_URLCanonicalizer.specialEncodeURL_;
|
||||
|
||||
// Test empty string
|
||||
G_Assert(z, enc([]) == "", "encoding empty array");
|
||||
|
||||
// Test that all characters we shouldn't encode ([33-36],[38,126]) are not.
|
||||
var no = [];
|
||||
var noAsString = "";
|
||||
for (var i = 33; i < 127; i++)
|
||||
if (i != 37) { // skip %
|
||||
no.push(i);
|
||||
noAsString += String.fromCharCode(i);
|
||||
}
|
||||
G_Assert(z, enc(no) == noAsString, "chars to not encode");
|
||||
|
||||
// Test that all the chars that we should encode [0,32],37,[127,255] are
|
||||
var yes = [];
|
||||
var yesAsString = "";
|
||||
var yesExpectedString = "";
|
||||
// Special case 0
|
||||
yes.push(0);
|
||||
yesAsString += String.fromCharCode(1);
|
||||
yesExpectedString += "%01";
|
||||
for (var i = 1; i < 256; i++)
|
||||
if (i < 33 || i == 37 || i > 126) {
|
||||
yes.push(i);
|
||||
yesAsString += String.fromCharCode(i);
|
||||
var hex = i.toString(16).toUpperCase();
|
||||
yesExpectedString += "%" + ((i < 16) ? "0" : "") + hex;
|
||||
}
|
||||
G_Assert(z, enc(yes) == yesExpectedString, "chars to encode");
|
||||
// Can't use decodeURIComponent or encodeURIComponent to test b/c UTF-8
|
||||
|
||||
|
||||
// ------ TEST COMPOSITION ------
|
||||
|
||||
// For convenience, shorten function name:
|
||||
var c = PROT_URLCanonicalizer.canonicalizeURL_;
|
||||
|
||||
G_Assert(z, c("http://www.google.com") == "http://www.google.com",
|
||||
"http://www.google.com");
|
||||
G_Assert(z, c("http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") == "http://168.188.99.26/.secure/www.ebay.com/",
|
||||
"fully encoded ebay");
|
||||
G_Assert(z, c("http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/") == "http://195.127.0.11/uploads/%20%20%20%20/.verify/.eBaysecure=updateuserdataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/",
|
||||
"long url with spaces that stays same");
|
||||
|
||||
// TODO: MORE!
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 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:
|
||||
//
|
||||
// - The extension will issue at most two HTTPS getkey requests per session
|
||||
// - The extension will issue one HTTPS getkey request at startup
|
||||
// - The extension will serialize to disk any key it gets
|
||||
// - The extension 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. The client will issue a new HTTPS getkey request
|
||||
// at this time if it has only issued one before
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.base64_ = new G_Base64();
|
||||
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.keyFilename_ = opt_keyFilename ?
|
||||
opt_keyFilename :
|
||||
PROT_GlobalStore.getKeyFilename();
|
||||
|
||||
// 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_) {
|
||||
G_Assert(this, !PROT_UrlCrypto.prototype.manager_,
|
||||
"Already have manager?");
|
||||
PROT_UrlCrypto.prototype.manager_ = this;
|
||||
|
||||
this.maybeLoadOldKey();
|
||||
this.reKey();
|
||||
}
|
||||
}
|
||||
|
||||
// Do ***** NOT ***** set this higher; HTTPS is expensive
|
||||
PROT_UrlCryptoKeyManager.MAX_REKEY_TRIES = 2;
|
||||
|
||||
// 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 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_;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.rekeyTries_ > this.MAX_REKEY_TRIES)
|
||||
throw new Error("Have already rekeyed " + this.rekeyTries_ + " times");
|
||||
|
||||
this.rekeyTries_++;
|
||||
|
||||
G_Debug(this, "Attempting to re-key");
|
||||
var url = PROT_GlobalStore.getGetKeyURL();
|
||||
if (!this.testing_)
|
||||
(new PROT_XMLFetcher()).get(url,
|
||||
BindToObject(this.onGetKeyResponse, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns Boolean indicating if we have a key we can use
|
||||
*/
|
||||
PROT_UrlCryptoKeyManager.prototype.hasKey_ = function() {
|
||||
return this.clientKey_ != null && this.wrappedKey_ != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_ = this.base64_.decodeString(this.clientKey_);
|
||||
this.wrappedKey_ = wrappedKey;
|
||||
|
||||
this.serializeKey_(this.clientKey_, this.wrappedKey_);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 appDir = new PROT_ApplicationDirectory();
|
||||
if (!appDir.exists())
|
||||
appDir.create();
|
||||
var keyfile = appDir.getAppDirFileInterface();
|
||||
keyfile.append(this.keyFilename_);
|
||||
G_FileWriter.writeAll(keyfile, (new G_Protocol4Parser).serialize(map));
|
||||
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];
|
||||
|
||||
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 /getkey");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 appDir = new PROT_ApplicationDirectory();
|
||||
var keyfile = appDir.getAppDirFileInterface();
|
||||
keyfile.append(this.keyFilename_);
|
||||
if (keyfile.exists())
|
||||
oldKey = G_FileReader.readAll(keyfile);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Cheesey tests
|
||||
*/
|
||||
function TEST_PROT_UrlCryptoKeyManager() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "urlcryptokeymanager UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
// Let's not clobber any real keyfile out there
|
||||
var kf = "keytest.txt";
|
||||
|
||||
// Let's be able to clean up after ourselves
|
||||
function removeTestFile(f) {
|
||||
var appDir = new PROT_ApplicationDirectory();
|
||||
var file = appDir.getAppDirFileInterface();
|
||||
file.append(f);
|
||||
if (file.exists())
|
||||
file.remove(false /* do not recurse */);
|
||||
};
|
||||
removeTestFile(kf);
|
||||
|
||||
var km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
|
||||
|
||||
// CASE: simulate nothing on disk, then get something from server
|
||||
|
||||
G_Assert(z, !km.hasKey_(), "KM already has key?");
|
||||
km.maybeLoadOldKey();
|
||||
G_Assert(z, !km.hasKey_(), "KM loaded non-existent key?");
|
||||
km.onGetKeyResponse(null);
|
||||
G_Assert(z, !km.hasKey_(), "KM got key from null response?");
|
||||
km.onGetKeyResponse("");
|
||||
G_Assert(z, !km.hasKey_(), "KM got key from empty response?");
|
||||
km.onGetKeyResponse("aslkaslkdf:34:a230\nskdjfaljsie");
|
||||
G_Assert(z, !km.hasKey_(), "KM got key from garbage response?");
|
||||
|
||||
var realResponse = "clientkey:24:zGbaDbx1pxoYe7siZYi8VA==\n" +
|
||||
"wrappedkey:24:MTr1oDt6TSOFQDTvKCWz9PEn";
|
||||
km.onGetKeyResponse(realResponse);
|
||||
// Will have written it to file as a side effect
|
||||
G_Assert(z, km.hasKey_(), "KM couldn't get key from real response?");
|
||||
G_Assert(z, km.clientKey_ == "zGbaDbx1pxoYe7siZYi8VA==",
|
||||
"Parsed wrong client key from response?");
|
||||
G_Assert(z, km.wrappedKey_ == "MTr1oDt6TSOFQDTvKCWz9PEn",
|
||||
"Parsed wrong wrapped key from response?");
|
||||
|
||||
// CASE: simulate something on disk, then get something from server
|
||||
|
||||
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
|
||||
G_Assert(z, !km.hasKey_(), "KM already has key?");
|
||||
km.maybeLoadOldKey();
|
||||
G_Assert(z, km.hasKey_(), "KM couldn't load existing key from disk?");
|
||||
G_Assert(z, km.clientKey_ == "zGbaDbx1pxoYe7siZYi8VA==",
|
||||
"Parsed wrong client key from disk?");
|
||||
G_Assert(z, km.wrappedKey_ == "MTr1oDt6TSOFQDTvKCWz9PEn",
|
||||
"Parsed wrong wrapped key from disk?");
|
||||
var realResponse2 = "clientkey:24:dtmbEN1kgN/LmuEoYifaFw==\n" +
|
||||
"wrappedkey:24:MTpPH3pnLDKihecOci+0W5dk";
|
||||
km.onGetKeyResponse(realResponse2);
|
||||
// Will have written it to disk
|
||||
G_Assert(z, km.hasKey_(), "KM couldn't replace key from server response?");
|
||||
G_Assert(z, km.clientKey_ == "dtmbEN1kgN/LmuEoYifaFw==",
|
||||
"Replace client key from server failed?");
|
||||
G_Assert(z, km.wrappedKey_ == "MTpPH3pnLDKihecOci+0W5dk",
|
||||
"Replace wrapped key from server failed?");
|
||||
|
||||
// CASE: check overwriting a key on disk
|
||||
|
||||
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
|
||||
G_Assert(z, !km.hasKey_(), "KM already has key?");
|
||||
km.maybeLoadOldKey();
|
||||
G_Assert(z, km.hasKey_(), "KM couldn't load existing key from disk?");
|
||||
G_Assert(z, km.clientKey_ == "dtmbEN1kgN/LmuEoYifaFw==",
|
||||
"Replace client on from disk failed?");
|
||||
G_Assert(z, km.wrappedKey_ == "MTpPH3pnLDKihecOci+0W5dk",
|
||||
"Replace wrapped key on disk failed?");
|
||||
|
||||
// Test that we only fetch at most two getkey's per lifetime of the manager
|
||||
|
||||
km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
|
||||
km.reKey();
|
||||
for (var i = 0; i < km.MAX_REKEY_TRIES; i++)
|
||||
G_Assert(z, km.maybeReKey(), "Couldn't rekey?");
|
||||
G_Assert(z, !km.maybeReKey(), "Rekeyed when max hit");
|
||||
|
||||
removeTestFile(kf);
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
689
mozilla/toolkit/components/protection/content/url-crypto.js
Normal file
689
mozilla/toolkit/components/protection/content/url-crypto.js
Normal file
@@ -0,0 +1,689 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (original author)
|
||||
* Monica Chew <mmc@google.com>
|
||||
*
|
||||
* 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 file implements our query param encryption. You hand it a set
|
||||
// of query params, and it will hand you a set of (maybe) encrypted
|
||||
// query params back. It takes the query params you give it,
|
||||
// encodes and encrypts them into a encrypted query param, and adds
|
||||
// the extra query params the server will need to decrypt them
|
||||
// (e.g., the version of encryption and the decryption key).
|
||||
//
|
||||
// The key manager provides the keys we need; this class just focuses
|
||||
// on encrypting query params. See the url crypto key manager for
|
||||
// details of our protocol, but essentially encryption is
|
||||
// RC4_key(input) with key == MD5(K_C || nonce) where nonce is a
|
||||
// 32-bit integer appended big-endian and K_C is the client's key.
|
||||
//
|
||||
// If for some reason we don't have an encryption key, encrypting is the
|
||||
// identity function.
|
||||
|
||||
/**
|
||||
* This class knows how to encrypt query parameters that will be
|
||||
* understood by the lookupserver.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_UrlCrypto() {
|
||||
this.debugZone = "urlcrypto";
|
||||
this.hasher_ = new G_CryptoHasher();
|
||||
this.base64_ = new G_Base64();
|
||||
this.rc4_ = new ARC4();
|
||||
|
||||
if (!this.manager_) {
|
||||
// Create a UrlCryptoKeyManager to reads keys from profile directory if
|
||||
// one doesn't already exist. UrlCryptoKeyManager puts a reference to
|
||||
// itself on PROT_UrlCrypto.prototype (this also prevents garbage
|
||||
// collection).
|
||||
new PROT_UrlCryptoKeyManager();
|
||||
}
|
||||
|
||||
// Convenience properties
|
||||
this.VERSION = PROT_UrlCrypto.VERSION;
|
||||
this.RC4_DISCARD_BYTES = PROT_UrlCrypto.RC4_DISCARD_BYTES;
|
||||
this.VERSION_QUERY_PARAM_NAME = PROT_UrlCrypto.QPS.VERSION_QUERY_PARAM_NAME;
|
||||
this.ENCRYPTED_PARAMS_PARAM_NAME =
|
||||
PROT_UrlCrypto.QPS.ENCRYPTED_PARAMS_PARAM_NAME;
|
||||
this.COUNT_QUERY_PARAM_NAME = PROT_UrlCrypto.QPS.COUNT_QUERY_PARAM_NAME;
|
||||
this.WRAPPEDKEY_QUERY_PARAM_NAME =
|
||||
PROT_UrlCrypto.QPS.WRAPPEDKEY_QUERY_PARAM_NAME;
|
||||
|
||||
// Properties for computing macs
|
||||
this.macer_ = new G_CryptoHasher(); // don't use hasher_
|
||||
this.macInitialized_ = false;
|
||||
// Separator to prevent leakage between key and data when computing mac
|
||||
this.separator_ = ":coolgoog:";
|
||||
this.separatorArray_ = this.base64_.arrayifyString(this.separator_);
|
||||
}
|
||||
|
||||
// The version of encryption we implement
|
||||
PROT_UrlCrypto.VERSION = "1";
|
||||
|
||||
PROT_UrlCrypto.RC4_DISCARD_BYTES = 1600;
|
||||
|
||||
// The query params are we going to send to let the server know what is
|
||||
// encrypted, and how
|
||||
PROT_UrlCrypto.QPS = {};
|
||||
PROT_UrlCrypto.QPS.VERSION_QUERY_PARAM_NAME = "encver";
|
||||
PROT_UrlCrypto.QPS.ENCRYPTED_PARAMS_PARAM_NAME = "encparams";
|
||||
PROT_UrlCrypto.QPS.COUNT_QUERY_PARAM_NAME = "nonce";
|
||||
PROT_UrlCrypto.QPS.WRAPPEDKEY_QUERY_PARAM_NAME = "wrkey";
|
||||
|
||||
/**
|
||||
* @returns Reference to the keymanager (if one exists), else undefined
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.getManager = function() {
|
||||
return this.manager_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that takes a map of query params (param name ->
|
||||
* value) and turns them into a query string. Note that it encodes
|
||||
* the values as it writes the string.
|
||||
*
|
||||
* @param params Object (map) of query names to values. Values should
|
||||
* not be uriencoded.
|
||||
*
|
||||
* @returns String of query params from the map. Values will be uri
|
||||
* encoded
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.appendParams_ = function(params) {
|
||||
var queryString = "";
|
||||
for (var param in params)
|
||||
queryString += "&" + param + "=" + encodeURIComponent(params[param]);
|
||||
|
||||
return queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a set of query params if we can. If we can, we return a new
|
||||
* set of query params that should be added to a query string. The set
|
||||
* of query params WILL BE different than the input query params if we
|
||||
* can encrypt (e.g., there will be extra query params with meta-
|
||||
* information such as the version of encryption we're using). If we
|
||||
* can't encrypt, we just return the query params we're passed.
|
||||
*
|
||||
* @param params Object (map) of query param names to values. Values should
|
||||
* not be uriencoded.
|
||||
*
|
||||
* @returns Object (map) of query param names to values. Values are NOT
|
||||
* uriencoded; the caller should encode them as it writes them
|
||||
* to a proper query string.
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.maybeCryptParams = function(params) {
|
||||
if (!this.manager_)
|
||||
throw new Error("Need a key manager for UrlCrypto");
|
||||
if (typeof params != "object")
|
||||
throw new Error("params is an associative array of name/value params");
|
||||
|
||||
var clientKeyArray = this.manager_.getClientKeyArray();
|
||||
var wrappedKey = this.manager_.getWrappedKey();
|
||||
|
||||
// No keys? Can't encrypt. Damn.
|
||||
if (!clientKeyArray || !wrappedKey) {
|
||||
G_Debug(this, "No key; can't encrypt query params");
|
||||
return params;
|
||||
}
|
||||
|
||||
// Serialize query params to a query string that we will then
|
||||
// encrypt and place in a special query param the front-end knows is
|
||||
// encrypted.
|
||||
var queryString = this.appendParams_(params);
|
||||
|
||||
// Nonce, really. We want 32 bits; make it so.
|
||||
var counter = this.getCount_();
|
||||
counter = counter & 0xFFFFFFFF;
|
||||
|
||||
var encrypted = this.encryptV1(clientKeyArray,
|
||||
this.VERSION,
|
||||
counter,
|
||||
this.base64_.arrayifyString(queryString));
|
||||
|
||||
params = {};
|
||||
params[this.VERSION_QUERY_PARAM_NAME] = this.VERSION;
|
||||
params[this.COUNT_QUERY_PARAM_NAME] = counter;
|
||||
params[this.WRAPPEDKEY_QUERY_PARAM_NAME] = wrappedKey;
|
||||
params[this.ENCRYPTED_PARAMS_PARAM_NAME] = encrypted;
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt something IN PLACE. Did you hear that? It works IN PLACE.
|
||||
* That is, it replaces the plaintext with ciphertext. It also returns
|
||||
* the websafe base64-encoded ciphertext. The client key is untouched.
|
||||
*
|
||||
* This method runs in about ~5ms on a 2Ghz P4. (Turn debugging off if
|
||||
* you see it much slower).
|
||||
*
|
||||
* @param clientKeyArray Array of bytes (numbers in [0,255]) composing K_C
|
||||
*
|
||||
* @param version String indicating the version of encryption we should use.
|
||||
*
|
||||
* @param counter Number that acts as a nonce for this encryption
|
||||
*
|
||||
* @param inOutArray Array of plaintext bytes that will be replaced
|
||||
* with the array of ciphertext bytes
|
||||
*
|
||||
* @returns String containing the websafe base64-encoded ciphertext
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.encryptV1 = function(clientKeyArray,
|
||||
version,
|
||||
counter,
|
||||
inOutArray) {
|
||||
|
||||
// We're a version1 encrypter, after all
|
||||
if (version != "1")
|
||||
throw new Error("Unknown encryption version");
|
||||
|
||||
var key = this.deriveEncryptionKey(clientKeyArray, counter);
|
||||
|
||||
this.rc4_.setKey(key, key.length);
|
||||
|
||||
if (this.RC4_DISCARD_BYTES > 0)
|
||||
this.rc4_.discard(this.RC4_DISCARD_BYTES);
|
||||
|
||||
// The crypt() method works in-place
|
||||
this.rc4_.crypt(inOutArray, inOutArray.length);
|
||||
|
||||
return this.base64_.encodeByteArray(inOutArray, true /* websafe */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an encryption key from K_C and a nonce
|
||||
*
|
||||
* @param clientKeyArray Array of bytes comprising K_C
|
||||
*
|
||||
* @param count Number that acts as a nonce for this key
|
||||
*
|
||||
* @returns Array of bytes containing the encryption key
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.deriveEncryptionKey = function(clientKeyArray,
|
||||
count) {
|
||||
G_Assert(this, clientKeyArray instanceof Array,
|
||||
"Client key should be an array of bytes");
|
||||
G_Assert(this, typeof count == "number", "Count should be a number");
|
||||
|
||||
// Don't clobber the client key by appending the nonce; use another array
|
||||
var paddingArray = [];
|
||||
paddingArray.push(count >> 24);
|
||||
paddingArray.push((count >> 16) & 0xFF);
|
||||
paddingArray.push((count >> 8) & 0xFF);
|
||||
paddingArray.push(count & 0xFF);
|
||||
|
||||
this.hasher_.init(G_CryptoHasher.algorithms.MD5);
|
||||
this.hasher_.updateFromArray(clientKeyArray);
|
||||
this.hasher_.updateFromArray(paddingArray);
|
||||
|
||||
return this.base64_.arrayifyString(this.hasher_.digestRaw());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new nonce for us to use. Rather than keeping a counter and
|
||||
* the headaches that entails, just use the low ms since the epoch.
|
||||
*
|
||||
* @returns 32-bit number that is the nonce to use for this encryption
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.getCount_ = function() {
|
||||
return ((new Date).getTime() & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the mac. This function is called by WireFormatReader if the update
|
||||
* server has sent along a mac param. The caller must not call initMac again
|
||||
* before calling finishMac; instead, the caller should just use another
|
||||
* UrlCrypto object.
|
||||
*
|
||||
* @param opt_clientKeyArray Optional clientKeyArray, for testing
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.initMac = function(opt_clientKeyArray) {
|
||||
if (this.macInitialized_) {
|
||||
throw new Error("Can't interleave calls to initMac. Please use another " +
|
||||
"UrlCrypto object.");
|
||||
}
|
||||
|
||||
this.macInitialized_ = true;
|
||||
|
||||
var clientKeyArray = null;
|
||||
|
||||
if (!!opt_clientKeyArray) {
|
||||
clientKeyArray = opt_clientKeyArray;
|
||||
} else {
|
||||
clientKeyArray = this.manager_.getClientKeyArray();
|
||||
}
|
||||
|
||||
// Don't re-use this.hasher_, in case someone calls deriveEncryptionKey
|
||||
// between initMac and finishMac
|
||||
this.macer_.init(G_CryptoHasher.algorithms.MD5);
|
||||
|
||||
this.macer_.updateFromArray(clientKeyArray);
|
||||
this.macer_.updateFromArray(this.separatorArray_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a line to the mac. Called by WireFormatReader.processLine. Not thread
|
||||
* safe.
|
||||
*
|
||||
* @param s The string to add
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.updateMacFromString = function(s) {
|
||||
if (!this.macInitialized_) {
|
||||
throw new Error ("Initialize mac first");
|
||||
}
|
||||
|
||||
var arr = this.base64_.arrayifyString(s);
|
||||
this.macer_.updateFromArray(arr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish up computing the mac. Not thread safe.
|
||||
*
|
||||
* @param opt_clientKeyArray Optional clientKeyArray, for testing
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.finishMac = function(opt_clientKeyArray) {
|
||||
var clientKeyArray = null;
|
||||
if (!!opt_clientKeyArray) {
|
||||
clientKeyArray = opt_clientKeyArray;
|
||||
} else {
|
||||
clientKeyArray = this.manager_.getClientKeyArray();
|
||||
}
|
||||
|
||||
if (!this.macInitialized_) {
|
||||
throw new Error ("Initialize mac first");
|
||||
}
|
||||
this.macer_.updateFromArray(this.separatorArray_);
|
||||
this.macer_.updateFromArray(clientKeyArray);
|
||||
|
||||
this.macInitialized_ = false;
|
||||
|
||||
return this.macer_.digestBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a mac over the whole data string, and return the base64-encoded
|
||||
* string
|
||||
*
|
||||
* @param data A string
|
||||
* @param opt_outputRaw True for raw output, false for base64
|
||||
* @param opt_clientKeyArray An optional key to pass in for testing
|
||||
* @param opt_separatorArray An optional separator array to pass in for testing
|
||||
* @returns MD5(key+separator+data+separator+key)
|
||||
*/
|
||||
PROT_UrlCrypto.prototype.computeMac = function(data,
|
||||
opt_outputRaw,
|
||||
opt_clientKeyArray,
|
||||
opt_separatorArray) {
|
||||
var clientKeyArray = null;
|
||||
var separatorArray = null;
|
||||
|
||||
// Get keys and such for testing
|
||||
if (!!opt_clientKeyArray) {
|
||||
clientKeyArray = opt_clientKeyArray;
|
||||
} else {
|
||||
clientKeyArray = this.manager_.getClientKeyArray();
|
||||
}
|
||||
|
||||
if (!!opt_separatorArray) {
|
||||
separatorArray = opt_separatorArray;
|
||||
} else {
|
||||
separatorArray = this.separatorArray_;
|
||||
}
|
||||
|
||||
this.macer_.init(G_CryptoHasher.algorithms.MD5);
|
||||
|
||||
this.macer_.updateFromArray(clientKeyArray);
|
||||
this.macer_.updateFromArray(separatorArray);
|
||||
|
||||
// Note to self: calling G_CryptoHasher.updateFromString ain't the same as
|
||||
// arrayifying the string and then calling updateFromArray. Not sure if
|
||||
// that's a bug in G_CryptoHasher or not. Niels, what do you think?
|
||||
var arr = this.base64_.arrayifyString(data);
|
||||
this.macer_.updateFromArray(arr);
|
||||
|
||||
this.macer_.updateFromArray(separatorArray);
|
||||
this.macer_.updateFromArray(clientKeyArray);
|
||||
|
||||
if (!!opt_outputRaw) {
|
||||
return this.macer_.digestRaw();
|
||||
}
|
||||
return this.macer_.digestBase64();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cheeseball unittest
|
||||
*/
|
||||
function TEST_PROT_UrlCrypto() {
|
||||
if (G_GDEBUG) {
|
||||
var z = "urlcrypto UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
// We set keys on the keymanager to ensure data flows properly, so
|
||||
// make sure we can clean up after it
|
||||
|
||||
var kf = "test.txt";
|
||||
function removeTestFile(f) {
|
||||
var appDir = new PROT_ApplicationDirectory();
|
||||
var file = appDir.getAppDirFileInterface();
|
||||
file.append(f);
|
||||
if (file.exists())
|
||||
file.remove(false /* do not recurse */ );
|
||||
};
|
||||
removeTestFile(kf);
|
||||
|
||||
var km = new PROT_UrlCryptoKeyManager(kf, true /* testing */);
|
||||
|
||||
// Test operation when it's not intialized
|
||||
|
||||
var c = new PROT_UrlCrypto();
|
||||
|
||||
var fakeManager = {
|
||||
getClientKeyArray: function() { return null; },
|
||||
getWrappedKey: function() { return null; },
|
||||
};
|
||||
c.manager_ = fakeManager;
|
||||
|
||||
var params = {
|
||||
foo: "bar",
|
||||
baz: "bomb",
|
||||
};
|
||||
G_Assert(z, c.maybeCryptParams(params)["foo"] === "bar",
|
||||
"How can we encrypt if we don't have a key?");
|
||||
c.manager_ = km;
|
||||
G_Assert(z, c.maybeCryptParams(params)["foo"] === "bar",
|
||||
"Again, how can we encrypt if we don't have a key?");
|
||||
|
||||
// Now we have a key! See if we can get a crypted url
|
||||
var realResponse = "clientkey:24:dtmbEN1kgN/LmuEoYifaFw==\n" +
|
||||
"wrappedkey:24:MTpPH3pnLDKihecOci+0W5dk";
|
||||
km.onGetKeyResponse(realResponse);
|
||||
var crypted = c.maybeCryptParams(params);
|
||||
G_Assert(z, crypted["foo"] === undefined, "We have a key but can't crypt");
|
||||
G_Assert(z, crypted["bomb"] === undefined, "We have a key but can't crypt");
|
||||
|
||||
// Now check to make sure all the expected query params are there
|
||||
for (var p in PROT_UrlCrypto.QPS)
|
||||
G_Assert(z, crypted[PROT_UrlCrypto.QPS[p]] != undefined,
|
||||
"Output query params doesn't have: " + PROT_UrlCrypto.QPS[p]);
|
||||
|
||||
// Now test that encryption is determinisitic
|
||||
var b64 = new G_Base64();
|
||||
|
||||
// Some helper functions
|
||||
function arrayEquals(a1, a2) {
|
||||
if (a1.length != a2.length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < a1.length; i++)
|
||||
if (typeof a1[i] != typeof a2[i] || a1[i] != a2[i])
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
function arrayAsString(a) {
|
||||
var s = "[";
|
||||
for (var i = 0; i < a.length; i++)
|
||||
s += a[i] + ",";
|
||||
return s + "]";
|
||||
};
|
||||
|
||||
function printArray(a) {
|
||||
var s = arrayAsString(a);
|
||||
G_Debug(z, s);
|
||||
};
|
||||
|
||||
var keySizeBytes = km.clientKeyArray_.length;
|
||||
|
||||
var startCrypt = (new Date).getTime();
|
||||
var numCrypts = 0;
|
||||
|
||||
// Set this to true for extended testing
|
||||
var doLongTest = false;
|
||||
if (doLongTest) {
|
||||
|
||||
// Now run it through its paces. For a variety of keys of a
|
||||
// variety of lengths, and a variety of coutns, encrypt
|
||||
// plaintexts of different lengths
|
||||
|
||||
// For a variety of key lengths...
|
||||
for (var i = 0; i < 2 * keySizeBytes; i++) {
|
||||
var clientKeyArray = [];
|
||||
|
||||
// For a variety of keys...
|
||||
for (var j = 0; j < keySizeBytes; j++)
|
||||
clientKeyArray[j] = i + j;
|
||||
|
||||
// For a variety of counts...
|
||||
for (var count = 0; count < 40; count++) {
|
||||
|
||||
var payload = "";
|
||||
|
||||
// For a variety of plaintexts of different lengths
|
||||
for (var payloadPadding = 0; payloadPadding < count; payloadPadding++)
|
||||
payload += "a";
|
||||
|
||||
var plaintext1 = b64.arrayifyString(payload);
|
||||
var plaintext2 = b64.arrayifyString(payload);
|
||||
var plaintext3 = b64.arrayifyString(payload);
|
||||
|
||||
// Verify that encryption is deterministic given set parameters
|
||||
numCrypts++;
|
||||
var ciphertext1 = c.encryptV1(clientKeyArray,
|
||||
"1",
|
||||
count,
|
||||
plaintext1);
|
||||
|
||||
numCrypts++;
|
||||
var ciphertext2 = c.encryptV1(clientKeyArray,
|
||||
"1",
|
||||
count,
|
||||
plaintext2);
|
||||
|
||||
G_Assert(z, ciphertext1 === ciphertext2,
|
||||
"Two plaintexts having different ciphertexts:" +
|
||||
ciphertext1 + " " + ciphertext2);
|
||||
|
||||
numCrypts++;
|
||||
|
||||
// Now verify that it is symmetrical
|
||||
var ciphertext3 = c.encryptV1(clientKeyArray,
|
||||
"1",
|
||||
count,
|
||||
b64.decodeString(ciphertext2),
|
||||
true /* websafe */);
|
||||
|
||||
G_Assert(z, arrayEquals(plaintext3, b64.decodeString(ciphertext3,
|
||||
true/*websafe*/)),
|
||||
"Encryption and decryption not symmetrical");
|
||||
}
|
||||
}
|
||||
|
||||
// Output some interesting info
|
||||
var endCrypt = (new Date).getTime();
|
||||
var totalMS = endCrypt - startCrypt;
|
||||
G_Debug(z, "Info: Did " + numCrypts + " encryptions in " +
|
||||
totalMS + "ms, for an average of " +
|
||||
(totalMS / numCrypts) + "ms per crypt()");
|
||||
}
|
||||
|
||||
// Now check for compatability with C++
|
||||
|
||||
var ciphertexts = {};
|
||||
// Generated below, and tested in C++ as well. Ciphertexts is a map
|
||||
// from substring lengths to encrypted values.
|
||||
|
||||
ciphertexts[0]="";
|
||||
ciphertexts[1]="dA==";
|
||||
ciphertexts[2]="akY=";
|
||||
ciphertexts[3]="u5mV";
|
||||
ciphertexts[4]="bhtioQ==";
|
||||
ciphertexts[5]="m2wSZnQ=";
|
||||
ciphertexts[6]="zd6gWyDO";
|
||||
ciphertexts[7]="bBN0WVrlCg==";
|
||||
ciphertexts[8]="Z6U_6bMelFM=";
|
||||
ciphertexts[9]="UVoiytL-gHzp";
|
||||
ciphertexts[10]="3Xr_ZMmdmvg7zw==";
|
||||
ciphertexts[11]="PIIyif7NFRS57mY=";
|
||||
ciphertexts[12]="QEKXrRWdZ3poJVSp";
|
||||
ciphertexts[13]="T3zsAsooHuAnflNsNQ==";
|
||||
ciphertexts[14]="qgYtOJjZSIByo0KtOG0=";
|
||||
ciphertexts[15]="NsEGHGK6Ju6FjD59Byai";
|
||||
ciphertexts[16]="1RVIsC0HYoUEycoA_0UL2w==";
|
||||
ciphertexts[17]="0xXe6Lsb1tZ79T96AJTT-ps=";
|
||||
ciphertexts[18]="cVXQCYoA4RV8t1CODXuCS88y";
|
||||
ciphertexts[19]="hVf4pd4WP4wPwSyqEXRRkQZSQA==";
|
||||
ciphertexts[20]="F6Y9MHwhd1e-bDHhqNSonZbR2Sg=";
|
||||
ciphertexts[21]="TiMClYbLUdyYweW8IDytU_HD2wTM";
|
||||
ciphertexts[22]="tYQtNqz83KXE4eqn6GhAu6ZZ23SqYw==";
|
||||
ciphertexts[23]="qjL-dMpiQ2LYgkYT5IfmE1FlN36wHek=";
|
||||
ciphertexts[24]="cL7HHiOZ9PbkvZ9yrJLiv4HXcw4Nf7y7";
|
||||
ciphertexts[25]="k4I-fdR6CyzxOpR_QEG5rnvPB8IbmRnpFg==";
|
||||
ciphertexts[26]="7LjCfA1dCMjAVT_O8DpiTQ0G7igwQ1HTUMU=";
|
||||
ciphertexts[27]="CAtijc6nB-REwAkqimToMn8RC_eZAaJy9Gn4";
|
||||
ciphertexts[28]="z8sEB1lDI32wsOkgYbVZ5pxIbpCrha9BmcqxFQ==";
|
||||
ciphertexts[29]="2eysfzsfGav0vPRsSnFl8H8fg9dQCT_bSiZwno0=";
|
||||
ciphertexts[30]="2BBNlF_mtV9TB2jZHHqCAtzkJQFdVKFn7N8YxsI9";
|
||||
ciphertexts[31]="9h4-nldHAr77Boks7lPzsi8TwVCIQzSkiJp2xatbGg==";
|
||||
ciphertexts[32]="DHTB8bDTXpUIrZ2ZlAujXLi-501NoWUVIEQJLaKCpqQ=";
|
||||
ciphertexts[33]="E9Av2GgnZg_q5r-JLSzM_ShCu1yPF2VeCaQfPPXSSE4I";
|
||||
ciphertexts[34]="UJzEucVBnGEfRNBQ6tvbaro0_I_-mQeJMpU2zQnfFdBuFg==";
|
||||
ciphertexts[35]="_p0OYras-Vn2rQ9X-J0dFRnhCfytuTEjheUTU7Ueaf1rIA4=";
|
||||
ciphertexts[36]="Q0nZXFPJbpx1WZPP-lLPuSGR-pD08B4CAW-6Uf0eEkS05-oM";
|
||||
ciphertexts[37]="XeKfieZGc9bPh7nRtCgujF8OY14zbIZSK20Lwg1HTpHi9HfXVQ==";
|
||||
|
||||
var clientKeyArray = b64.decodeString("dtmbEN1kgN/LmuEoYifaFw==");
|
||||
// wrappedKey was "MTpPH3pnLDKihecOci+0W5dk"
|
||||
var count = 0xFEDCBA09;
|
||||
var plaintext = "http://www.foobar.com/this?is&some=url";
|
||||
|
||||
// For every substring of the plaintext, change the count and verify
|
||||
// that we get what we expect when we encrypt
|
||||
|
||||
for (var i = 0; i < plaintext.length; i++) {
|
||||
var plaintextArray = b64.arrayifyString(plaintext.substring(0, i));
|
||||
var crypted = c.encryptV1(clientKeyArray,
|
||||
"1",
|
||||
count + i,
|
||||
plaintextArray);
|
||||
G_Assert(z, crypted === ciphertexts[i],
|
||||
"Generated unexpected ciphertext");
|
||||
|
||||
// Uncomment to generate
|
||||
// dump("\nciphertexts[" + i + "]=\"" + crypted + "\";");
|
||||
}
|
||||
|
||||
// Cribbed from the MD5 rfc to make sure we're computing plain'ol md5
|
||||
// values correctly
|
||||
// http://www.faqs.org/rfcs/rfc1321.html
|
||||
var md5texts = [ "",
|
||||
"a",
|
||||
"abc",
|
||||
"message digest",
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
||||
"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
|
||||
];
|
||||
|
||||
var md5digests = [ "d41d8cd98f00b204e9800998ecf8427e",
|
||||
"0cc175b9c0f1b6a831c399e269772661",
|
||||
"900150983cd24fb0d6963f7d28e17f72",
|
||||
"f96b697d7cb7938d525a2f31aaf161d0",
|
||||
"c3fcd3d76192e4007dfb496cca67e13b",
|
||||
"d174ab98d277d9f5a5611c2c9f419d9f",
|
||||
"57edf4a22be3c955ac49da2e2107b67a"
|
||||
];
|
||||
|
||||
var expected_mac = [];
|
||||
expected_mac[0] = "ZOJ6Mk+ccC6R7BwseqCYQQ==";
|
||||
expected_mac[1] = "zWM7tvcsuH/MSEviNiRbOA==";
|
||||
expected_mac[2] = "ZAUVyls/6ZVN3Np8v3pX3g==";
|
||||
expected_mac[3] = "Zq6gF7RkPwKqlicuxrO4mg==";
|
||||
expected_mac[4] = "/LOJETSnqSW3q4u1hs/0Pg==";
|
||||
expected_mac[5] = "jjOEX7H2uchOznxIGuqzJg==";
|
||||
expected_mac[6] = "Tje7aP/Rk/gkSH4he0KMQQ==";
|
||||
|
||||
// Check plain'ol MD5
|
||||
var hasher = new G_CryptoHasher();
|
||||
for (var i = 0; i < md5texts.length; i++) {
|
||||
var computedMac = c.computeMac(md5texts[i], true /* output raw */, [], []);
|
||||
var hex = hasher.toHex_(computedMac).toLowerCase();
|
||||
G_Assert(z, hex == md5digests[i],
|
||||
"MD5(" + md5texts[i] + ") = " + md5digests[i] + ", not " + hex);
|
||||
}
|
||||
|
||||
for (var i = 0; i < md5texts.length; i++) {
|
||||
var computedMac = c.computeMac(md5texts[i],
|
||||
false /* output Base64 */,
|
||||
clientKeyArray);
|
||||
G_Assert(z, computedMac == expected_mac[i],
|
||||
"Wrong mac generated for " + md5texts[i]);
|
||||
// Uncomment to generate
|
||||
// dump("\nexpected_mac[" + i + "] = \"" + computedMac + "\";");
|
||||
}
|
||||
|
||||
// Now check if adding line by line is the same as computing over the whole
|
||||
// thing
|
||||
var wholeString = md5texts[0] + md5texts[1];
|
||||
var wholeStringMac = c.computeMac(wholeString,
|
||||
false /* output Base64 */,
|
||||
clientKeyArray);
|
||||
|
||||
expected_mac = "zWM7tvcsuH/MSEviNiRbOA==";
|
||||
c.initMac(clientKeyArray);
|
||||
c.updateMacFromString(md5texts[0]);
|
||||
c.updateMacFromString(md5texts[1]);
|
||||
var piecemealMac = c.finishMac(clientKeyArray);
|
||||
G_Assert(z, piecemealMac == wholeStringMac,
|
||||
"Computed different values for mac when adding line by line!");
|
||||
G_Assert(z, piecemealMac == expected_mac, "Didn't generate expected mac");
|
||||
|
||||
// Tested also in cheesey-test-frontend.sh and urlcrypto_unittest.cc on
|
||||
// the server side
|
||||
expected_mac = "iA5vLUidpXAPwfcAH9+8OQ==";
|
||||
var set3data = "";
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
set3data += "+white" + i + ".com\t1\n";
|
||||
}
|
||||
var computedMac = c.computeMac(set3data, false /*Base64*/, clientKeyArray);
|
||||
G_Assert(z, expected_mac == computedMac, "Expected " + expected_mac, " got " + computedMac);
|
||||
|
||||
removeTestFile(kf);
|
||||
|
||||
G_Debug(z, "PASS");
|
||||
}
|
||||
}
|
||||
776
mozilla/toolkit/components/protection/content/wireformat.js
Normal file
776
mozilla/toolkit/components/protection/content/wireformat.js
Normal file
@@ -0,0 +1,776 @@
|
||||
/* ***** 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):
|
||||
* Niels Provos <niels@google.com> (original author)
|
||||
* Fritz Schneider <fritz@google.com>
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
|
||||
// A class that serializes and deserializes opaque key/value string to
|
||||
// string maps to/from maps (trtables). It knows how to create
|
||||
// trtables from the serialized format, so it also understands
|
||||
// meta-information like the name of the table and the table's
|
||||
// version. See docs for the protocol description.
|
||||
//
|
||||
// TODO: wireformatreader: if you have multiple updates for one table
|
||||
// in a call to deserialize, the later ones will be merged
|
||||
// (all but the last will be ignored). To fix, merge instead
|
||||
// of replace when you have an existing table, and only do so once.
|
||||
// TODO must have blank line between successive types -- problem?
|
||||
// TODO doesn't tolerate blank lines very well
|
||||
//
|
||||
// Maybe: These classes could use a LOT more cleanup, but it's not a
|
||||
// priority at the moment. For example, the tablesData/Known
|
||||
// maps should be combined into a single object, the parser
|
||||
// for a given type should be separate from the version info,
|
||||
// and there should be synchronous interfaces for testing.
|
||||
|
||||
|
||||
/**
|
||||
* A class that knows how to serialize and deserialize meta-information.
|
||||
* This meta information is the table name and version number, and
|
||||
* in its serialized form looks like the first line below:
|
||||
*
|
||||
* [name-of-table X.Y update?]
|
||||
* ...key/value pairs to add or delete follow...
|
||||
* <blank line ends the table>
|
||||
*
|
||||
* The X.Y is the version number and the optional "update" token means
|
||||
* that the table is a differential from the curent table the extension
|
||||
* has. Its absence means that this is a full, new table.
|
||||
*/
|
||||
function PROT_VersionParser(type, opt_major, opt_minor, opt_requireMac) {
|
||||
this.debugZone = "versionparser";
|
||||
this.type = type;
|
||||
this.major = 0;
|
||||
this.minor = 0;
|
||||
|
||||
this.badHeader = false;
|
||||
|
||||
// Should the wireformatreader compute a mac?
|
||||
this.mac = false;
|
||||
this.macval = "";
|
||||
this.macFailed = false;
|
||||
this.requireMac = !!opt_requireMac;
|
||||
|
||||
this.update = false;
|
||||
// Used by ListerManager to see if we have read data for this table from
|
||||
// disk. Once we read a table from disk, we are not going to do so again
|
||||
// but instead update remotely if necessary.
|
||||
this.didRead = false;
|
||||
if (opt_major)
|
||||
this.major = parseInt(opt_major);
|
||||
if (opt_minor)
|
||||
this.minor = parseInt(opt_minor);
|
||||
}
|
||||
|
||||
/** Import the version information from another VersionParser
|
||||
* @params version a version parser object
|
||||
*/
|
||||
PROT_VersionParser.prototype.ImportVersion = function(version) {
|
||||
this.major = version.major;
|
||||
this.minor = version.minor;
|
||||
|
||||
this.mac = version.mac;
|
||||
this.macFailed = version.macFailed;
|
||||
this.macval = version.macval;
|
||||
// Don't set requireMac, since wfr creates vparsers from scratch and doesn't
|
||||
// know about it
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string like [goog-white-black 1.1] from internal information
|
||||
*
|
||||
* @returns version string
|
||||
*/
|
||||
PROT_VersionParser.prototype.toString = function() {
|
||||
var s = "[" + this.type + " " + this.major + "." + this.minor + "]";
|
||||
if (this.mac)
|
||||
s += "[mac=" + this.macval + "]";
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string like 1:1 from internal information used for
|
||||
* fetching updates from the server. Called by the listmanager.
|
||||
*
|
||||
* @returns version string
|
||||
*/
|
||||
PROT_VersionParser.prototype.toUrl = function() {
|
||||
return this.major + ":" + this.minor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the old format, [type major.minor [update]]
|
||||
*
|
||||
* @returns true if the string could be parsed, false otherwise
|
||||
*/
|
||||
PROT_VersionParser.prototype.processOldFormat_ = function(line) {
|
||||
if (line[0] != '[' || line.slice(-1) != ']')
|
||||
return false;
|
||||
|
||||
var description = line.slice(1, -1);
|
||||
|
||||
// Get the type name and version number of this table
|
||||
var tokens = description.split(" ");
|
||||
this.type = tokens[0];
|
||||
var majorminor = tokens[1].split(".");
|
||||
this.major = parseInt(majorminor[0]);
|
||||
this.minor = parseInt(majorminor[1]);
|
||||
if (isNaN(this.major) || isNaN(this.minor))
|
||||
return false;
|
||||
|
||||
if (tokens.length >= 3) {
|
||||
this.update = tokens[2] == "update";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a string like [name-of-table 1.1] and figures out the type
|
||||
* and corresponding version numbers.
|
||||
*
|
||||
* @returns true if the string could be parsed, false otherwise
|
||||
*/
|
||||
PROT_VersionParser.prototype.fromString = function(line) {
|
||||
G_Debug(this, "Calling fromString with line: " + line);
|
||||
if (line[0] != '[' || line.slice(-1) != ']')
|
||||
return false;
|
||||
|
||||
// There could be two [][], so take care of it
|
||||
var secondBracket = line.indexOf('[', 1);
|
||||
var firstPart = null;
|
||||
var secondPart = null;
|
||||
|
||||
if (secondBracket != -1) {
|
||||
firstPart = line.substring(0, secondBracket);
|
||||
secondPart = line.substring(secondBracket);
|
||||
G_Debug(this, "First part: " + firstPart + " Second part: " + secondPart);
|
||||
} else {
|
||||
firstPart = line;
|
||||
G_Debug(this, "Old format: " + firstPart);
|
||||
}
|
||||
|
||||
if (!this.processOldFormat_(firstPart))
|
||||
return false;
|
||||
|
||||
if (secondPart && !this.processOptTokens_(secondPart))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process optional tokens
|
||||
*
|
||||
* @param line A string [token1=val1 token2=val2...]
|
||||
* @returns true if the string could be parsed, false otherwise
|
||||
*/
|
||||
PROT_VersionParser.prototype.processOptTokens_ = function(line) {
|
||||
if (line[0] != '[' || line.slice(-1) != ']')
|
||||
return false;
|
||||
var description = line.slice(1, -1);
|
||||
// Get the type name and version number of this table
|
||||
var tokens = description.split(" ");
|
||||
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
G_Debug(this, "Processing optional token: " + tokens[i]);
|
||||
var tokenparts = tokens[i].split("=");
|
||||
switch(tokenparts[0]){
|
||||
case "mac":
|
||||
this.mac = true;
|
||||
if (tokenparts.length < 2) {
|
||||
G_Debug(this, "Found mac flag but not mac value!");
|
||||
return false;
|
||||
}
|
||||
// The mac value may have "=" in it, so we can't just use tokenparts[1].
|
||||
// Instead, just take the rest of tokens[i] after the first "="
|
||||
this.macval = tokens[i].substr(tokens[i].indexOf("=")+1);
|
||||
break;
|
||||
default:
|
||||
G_Debug(this, "Found unrecognized token: " + tokenparts[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A WireFormatWriter can serialize table data
|
||||
*
|
||||
* @param threadQueue A thread queue we should run on
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_WireFormatWriter(threadQueue) {
|
||||
this.threadQueue_ = threadQueue;
|
||||
this.debugZone = "wireformatwriter";
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a table to a string.
|
||||
*
|
||||
* @param tableData Reference to the table data we should serialize
|
||||
*
|
||||
* @param vParser Reference to the version parser/unparser we should use
|
||||
*
|
||||
* @param callback Reference to a function we should call when complete.
|
||||
* The callback will be invoked with a string holding
|
||||
* the serialized data as an argument
|
||||
*
|
||||
* @returns False if the serializer is busy, else true
|
||||
*
|
||||
*/
|
||||
PROT_WireFormatWriter.prototype.serialize = function(tableData,
|
||||
vParser,
|
||||
callback) {
|
||||
if (this.callback_) {
|
||||
G_Debug(this, "Serializer busy");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.callback_ = callback;
|
||||
this.current_ = 0;
|
||||
this.serialized_ = vParser.toString() + "\n";
|
||||
var cnt = {};
|
||||
this.keyList_ = tableData.getKeys(cnt);
|
||||
this.tableData_ = tableData;
|
||||
|
||||
this.threadQueue_.addWorker(BindToObject(this.doWorkUnit, this),
|
||||
BindToObject(this.isComplete, this));
|
||||
this.threadQueue_.run();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a chunk of data. This is periodically invoked by the
|
||||
* threadqueue.
|
||||
*/
|
||||
PROT_WireFormatWriter.prototype.doWorkUnit = function() {
|
||||
for (var i = 0; i < 10 && this.current_ < this.keyList_.length; i++) {
|
||||
var key = this.keyList_[this.current_];
|
||||
if (key) {
|
||||
var value = this.tableData_.findValue(key);
|
||||
this.serialized_ += "+" + key + "\t" + value + "\n";
|
||||
}
|
||||
this.current_++;
|
||||
}
|
||||
|
||||
if (this.isComplete())
|
||||
this.onComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we done serializing? Called by the threadqueue to tell when
|
||||
* to stop running this thread.
|
||||
*
|
||||
* @returns Boolean indicating if we're done serializing
|
||||
*/
|
||||
PROT_WireFormatWriter.prototype.isComplete = function() {
|
||||
return this.current_ >= this.keyList_.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* When we're done, call the callback
|
||||
*/
|
||||
PROT_WireFormatWriter.prototype.onComplete = function() {
|
||||
this.callback_(this.serialized_);
|
||||
this.callback_ = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A WireFormatReader can deserialize data.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @param threadQueue A global thread queue that we use to schedule our
|
||||
* work so that we do not take up too much time at one.
|
||||
*
|
||||
* @param opt_existingTablesData Optional reference to a map of tables
|
||||
* into which we should merge the data being deserialized
|
||||
*/
|
||||
function PROT_WireFormatReader(threadQueue, opt_existingTablesData) {
|
||||
this.debugZone = "wireformatreader";
|
||||
this.existingTablesData_ = opt_existingTablesData;
|
||||
this.threadQueue_ = threadQueue;
|
||||
this.callback_ = null;
|
||||
|
||||
// For mac'ing updates
|
||||
this.urlCrypto_ = null;
|
||||
var keyUrl = null;
|
||||
try {
|
||||
keyUrl = PROT_GlobalStore.getGetKeyURL();
|
||||
} catch (e) {
|
||||
G_Debug(this, "No key url, disabling mac'ed updates");
|
||||
}
|
||||
if (keyUrl) {
|
||||
this.urlCrypto_ = new PROT_UrlCrypto();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a thread to process the input data.
|
||||
*
|
||||
* @param tableUpdate A string that contains the data for updating the
|
||||
* tables that we know about.
|
||||
*
|
||||
* @param callback A user specificed callback that is being executed
|
||||
* when data processing completes. The callback is provided
|
||||
* with two arguments: tablesKnown and tablesData
|
||||
*
|
||||
* @returns true is new data is being process, or false on failure.
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.deserialize = function(tableUpdate, callback) {
|
||||
this.tableUpdate_ = tableUpdate;
|
||||
this.tablesKnown_ = {}; // version parsers
|
||||
this.tablesData_ = {}; // TRTables
|
||||
|
||||
if (this.callback_ != null) {
|
||||
G_Debug(this, "previous deserialize is still running");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.callback_ = callback;
|
||||
this.offset_ = 0;
|
||||
this.resetTableState_();
|
||||
|
||||
// On empty data, we just invoke the callback directly
|
||||
if (!this.tableUpdate_ || !this.tableUpdate_.length) {
|
||||
G_Debug(this, "No data. Calling back.");
|
||||
this.onComplete();
|
||||
return true;
|
||||
}
|
||||
|
||||
this.threadQueue_.addWorker(BindToObject(this.processLine_, this),
|
||||
BindToObject(this.isComplete, this));
|
||||
this.threadQueue_.run();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the per table state
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.resetTableState_ = function() {
|
||||
this.vParser_ = null;
|
||||
this.insert_ = 0;
|
||||
this.remove_ = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initalizes our state to process a new table.
|
||||
* NOTE: For performance reasons, we might have drive the replacement of
|
||||
* the table via a thread.
|
||||
*
|
||||
* @param line The input line that contains the table name and version
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.processNewTable_ = function(line) {
|
||||
this.vParser_ = new PROT_VersionParser("something");
|
||||
|
||||
// If there's an error, give up
|
||||
if (!this.vParser_.fromString(line)) {
|
||||
G_Debug(this, "Error in table header, skipping");
|
||||
this.vParser_ = null;
|
||||
return;
|
||||
}
|
||||
|
||||
G_Debug(this, "processNewTable: " + this.vParser_.type + ": " +
|
||||
this.vParser_.major + ":" + this.vParser_.minor + " update=" +
|
||||
this.vParser_.update + " mac=" + this.vParser_.mac);
|
||||
|
||||
// Create temporary table. PROT_TRTable() blows up if it doesn't like its
|
||||
// name, so mask and just set vParser to null
|
||||
try {
|
||||
this.tablesData_[this.vParser_.type] =
|
||||
newProtectionTable(this.vParser_.type);
|
||||
} catch(e) {
|
||||
G_Debug(this,
|
||||
"Unable to initialize new TRTable, because of exception: " + e);
|
||||
this.vParser_ = null;
|
||||
return;
|
||||
}
|
||||
if (this.vParser_.update && this.existingTablesData_ &&
|
||||
this.existingTablesData_[this.vParser_.type]) {
|
||||
// If we update an existing table, we need to copy the old table
|
||||
// data from the pre-existing tables
|
||||
this.tablesData_[this.vParser_.type].replace(
|
||||
this.existingTablesData_[this.vParser_.type]);
|
||||
}
|
||||
G_Debug(this, "processNewTable done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the contents of our temporary table. Exceptions should not
|
||||
* be masked here -- they're caught at a higher level.
|
||||
*
|
||||
* @param line The input line that contains the new data
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.processUpdateTable_ = function(line) {
|
||||
// Regular update to the current version
|
||||
var tokens = line.split("\t");
|
||||
var operation = tokens[0][0];
|
||||
var key = tokens[0].slice(1);
|
||||
var value = tokens[1];
|
||||
|
||||
if (operation == "+") {
|
||||
this.tablesData_[this.vParser_.type].insert(key, value);
|
||||
this.insert_++;
|
||||
} else {
|
||||
this.tablesData_[this.vParser_.type].erase(key);
|
||||
this.remove_++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish processing a table
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.endTable_ = function() {
|
||||
G_Debug(this,
|
||||
"Finished: " + this.vParser_.type + " +" +
|
||||
this.insert_ + " -" + this.remove_ + " mac'ing: " + this.vParser_.mac);
|
||||
|
||||
if (this.vParser_.mac) {
|
||||
var mac = this.urlCrypto_.finishMac();
|
||||
if (mac != this.vParser_.macval) {
|
||||
G_Debug(this, "Mac didn't match! (" + mac + " != "
|
||||
+ this.vParser_.macval + ")");
|
||||
this.vParser_.macFailed = true;
|
||||
} else {
|
||||
G_Debug(this, "Computed mac matched sent mac: " + this.vParser_.macval);
|
||||
}
|
||||
}
|
||||
|
||||
// Rollback if the mac failed.
|
||||
if (this.vParser_.mac && this.vParser_.macFailed) {
|
||||
this.tablesData_[this.vParser_.type] = undefined;
|
||||
this.tablesKnown_[this.vParser_.type] = undefined;
|
||||
G_Debug(this, "throwing away " + this.vParser_.type);
|
||||
} else {
|
||||
this.tablesKnown_[this.vParser_.type] = this.vParser_;
|
||||
}
|
||||
|
||||
this.resetTableState_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a chunk of data. TODO(MC): Make it so the mac is computed from a
|
||||
* saved begin and end offset instead of computing line by line.
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.processLine_ = function() {
|
||||
//G_Debug(this, '> processline');
|
||||
for (var count = 0;
|
||||
count < 5 && this.offset_ < this.tableUpdate_.length; count++) {
|
||||
var newOffset = this.tableUpdate_.indexOf("\n", this.offset_);
|
||||
var line = "";
|
||||
if (newOffset == -1) {
|
||||
this.offset_ = this.tableUpdate_.length;
|
||||
} else {
|
||||
line = this.tableUpdate_.slice(this.offset_, newOffset);
|
||||
this.offset_ = newOffset + 1;
|
||||
}
|
||||
|
||||
// Ignore empty lines if we currently do not have a table
|
||||
if (!this.vParser_ && (!line || line[0] != '[')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!line) {
|
||||
// End of one table - pop the results
|
||||
this.endTable_();
|
||||
} else if (line[0] == '[' && line.slice(-1) == ']') {
|
||||
// processNewTable doesn't instantiate this.vParser_ in case of malformed
|
||||
// headers, so the rest of the table lines will get skipped
|
||||
this.processNewTable_(line);
|
||||
|
||||
if (this.vParser_ && this.vParser_.mac) {
|
||||
this.urlCrypto_.initMac();
|
||||
}
|
||||
} else {
|
||||
if (!this.vParser_) {
|
||||
G_Debug(this, "Ignoring: " + line);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we try to read a data line. However the table could've
|
||||
// been corrupted on disk (e.g., the browser suddenly quit while
|
||||
// we were in the middle of writing a line). If so,
|
||||
// porcessUpdateTable() will throw. In this case we want to
|
||||
// resynch the whole table, so we skip this line and then
|
||||
// explicitly set the table's minor version to -1 (the lowest
|
||||
// possible -- not 0, which means the table is local), causing a
|
||||
// full update the next time we ask for it.
|
||||
//
|
||||
// We ignore the case where we wrote an incomplete but malformed
|
||||
// table -- it fixes itself over time as the missing keys become
|
||||
// less relevant.
|
||||
|
||||
try {
|
||||
this.processUpdateTable_(line);
|
||||
// Compute the mac over all the next lines until we finish the table
|
||||
// TODO: This is ugly! line doesn't get the final '\n', so add it back.
|
||||
// We could save the original line back up top, and use that instead,
|
||||
// but that's yet more copying...
|
||||
if (this.vParser_.mac) {
|
||||
this.urlCrypto_.updateMacFromString(line + "\n");
|
||||
}
|
||||
} catch(e) {
|
||||
G_Debug(this, "MALFORMED TABLE LINE: [" + line + "]\n" +
|
||||
"Skipping this line, and resetting table " +
|
||||
this.vParser_.type + " to version -1.\n" +
|
||||
"(This as a result of exception: " + e + ")");
|
||||
this.vParser_.minor = "-1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the table we're reading is the last, then the for loop will
|
||||
// fail, causing the table finish logic to be skipped. So here
|
||||
// ensure that we finish up whatever table we're working on.
|
||||
|
||||
if (this.vParser_ && this.offset_ >= this.tableUpdate_.length) {
|
||||
G_Debug(this,
|
||||
"Finished (final table): " + this.vParser_.type + " +" +
|
||||
this.insert_ + " -" + this.remove_);
|
||||
this.endTable_();
|
||||
}
|
||||
|
||||
if (this.isComplete()) this.onComplete();
|
||||
//G_Debug(this, "< processline");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all input data has been processed
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.isComplete = function() {
|
||||
return this.offset_ >= this.tableUpdate_.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies our caller of completion.
|
||||
*/
|
||||
PROT_WireFormatReader.prototype.onComplete = function() {
|
||||
G_Debug(this, "Processing complete. Executing callback.");
|
||||
this.callback_(this.tablesKnown_, this.tablesData_);
|
||||
this.callback_ = null;
|
||||
}
|
||||
|
||||
|
||||
function TEST_PROT_WireFormat() {
|
||||
if (G_GDEBUG) {
|
||||
// Sorry, this is incredibly ugly. What we need is continuations -- each
|
||||
// unittest that passes invokes the next.
|
||||
|
||||
var z = "wireformat UNITTEST";
|
||||
G_debugService.enableZone(z);
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
function testMalformedTables() {
|
||||
G_Debug(z, "Testing malformed tables...");
|
||||
|
||||
var wfr = new PROT_WireFormatReader(new TH_ThreadQueue());
|
||||
|
||||
// Damn these unittests are ugly. Ugh. Now test handling corrupt tables
|
||||
var data =
|
||||
"[test1-black-url 1.1]\n" +
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\n" + // Malformed
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\tbar\n" +
|
||||
"\n" +
|
||||
"[test2-black-url 1.2]\n" +
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\tbar\n" +
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\tbar\n" +
|
||||
"\n" +
|
||||
"[test3-black-url 1.3]\n" +
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\tbar\n" +
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\n" + // Malformed
|
||||
"\n" +
|
||||
"[test4-black-url 1.4]\n" +
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\tbar\n" +
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\tbar\n" +
|
||||
"\n" +
|
||||
"[test4-badheader-url 1.asfd dfui]\n" + // Malformed header
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\tbar\n" +
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\tbar\n";
|
||||
"\n" +
|
||||
"[test4-bad-name-url 1.asfd dfui]\n" + // Malformed header
|
||||
"+foo1\tbar\n" +
|
||||
"+foo2\tbar\n" +
|
||||
"+foo3\tbar\n" +
|
||||
"+foo4\tbar\n";
|
||||
|
||||
function malformedcb(tablesKnown, tablesData) {
|
||||
|
||||
// Table has malformed data
|
||||
G_Assert(z, tablesKnown["test1-black-url"].minor == "-1",
|
||||
"test table 1 didn't get reset");
|
||||
G_Assert(z, !!tablesData["test1-black-url"].find("foo1"),
|
||||
"test table 1 didn't set keys before the error");
|
||||
G_Assert(z, !!tablesData["test1-black-url"].find("foo3"),
|
||||
"test table 1 didn't set keys after the error");
|
||||
|
||||
// Table should be good
|
||||
G_Assert(z, tablesKnown["test2-black-url"].minor == "2",
|
||||
"test table 1 didnt get correct version number");
|
||||
G_Assert(z, !!tablesData["test2-black-url"].find("foo4"),
|
||||
"test table 2 didnt parse properly");
|
||||
|
||||
// Table is malformed
|
||||
G_Assert(z, tablesKnown["test3-black-url"].minor == "-1",
|
||||
"test table 3 didn't get reset");
|
||||
G_Assert(z, !tablesData["test3-black-url"].find("foo4"),
|
||||
"test table 3 somehow has its malformed line?");
|
||||
|
||||
// Table should be good
|
||||
G_Assert(z, tablesKnown["test4-black-url"].minor == "4",
|
||||
"test table 4 didn't get correct version number");
|
||||
G_Assert(z, !!tablesData["test2-black-url"].find("foo4"),
|
||||
"test table 4 didnt parse properly");
|
||||
|
||||
// Table is malformed and should be unknown
|
||||
G_Assert(z, !tablesKnown["test4-badheader-url"],
|
||||
"test table header should't be known");
|
||||
|
||||
// Table is malformed and should be unknown
|
||||
G_Assert(z, !tablesKnown["test4-bad-name-url"],
|
||||
"test table header should't be known");
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
};
|
||||
|
||||
wfr.deserialize(data, malformedcb);
|
||||
};
|
||||
|
||||
var testTablesData = {};
|
||||
|
||||
var tableName = "test-black-url";
|
||||
var data1 = "[" + tableName + " 1.5]\n";
|
||||
for (var i = 0; i < 100; i++)
|
||||
data1 += "+http://exists" + i + "\t1\n";
|
||||
data1 += "-http://exists50\t1\n";
|
||||
data1 += "-http://exists666\t1\n";
|
||||
|
||||
var data2 = "[" + tableName + " 1.7 update]\n";
|
||||
for (var i = 0; i < 100; i++)
|
||||
data2 += "-http://exists" + i + "\t1\n";
|
||||
|
||||
var set3Name = "test-white-domain";
|
||||
var set3data = "";
|
||||
// Use this string since it's the same in the update server unittest in
|
||||
// /firefox/security/scripts/test/cheese/cheesey something.sh
|
||||
for (var i = 1; i <= 3; i++) {
|
||||
set3data += "+white" + i + ".com\t1\n";
|
||||
}
|
||||
var urlCrypto = new PROT_UrlCrypto();
|
||||
var computedMac = urlCrypto.computeMac(set3data);
|
||||
|
||||
function data1cb(tablesKnown, tablesData) {
|
||||
G_Assert(z,
|
||||
tablesData[tableName] != null,
|
||||
"Didn't get our table back");
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
if (i != 50)
|
||||
G_Assert(z,
|
||||
tablesData[tableName].find("http://exists" + i) == "1",
|
||||
"Item addition broken");
|
||||
|
||||
G_Assert(z,
|
||||
!tablesData[tableName].find("http://exists50"),
|
||||
"Item removal broken");
|
||||
G_Assert(z,
|
||||
!tablesData[tableName].find("http://exists666"),
|
||||
"Non-existent item");
|
||||
|
||||
G_Assert(z, tablesKnown[tableName].major == "1", "Major parsing broke");
|
||||
G_Assert(z, tablesKnown[tableName].minor == "5", "Major parsing broke");
|
||||
|
||||
var wfr2 = new PROT_WireFormatReader(new TH_ThreadQueue(), tablesData);
|
||||
wfr2.deserialize(data2, data2cb);
|
||||
};
|
||||
|
||||
function data2cb(tablesKnown, tablesData) {
|
||||
for (var i = 0; i < 100; i++)
|
||||
G_Assert(z,
|
||||
!tablesData[tableName].find("http://exists" + i),
|
||||
"Tables merge broken");
|
||||
|
||||
G_Assert(z, tablesKnown[tableName].major == "1", "Major parsing broke");
|
||||
G_Assert(z, tablesKnown[tableName].minor == "7", "Major parsing broke");
|
||||
|
||||
var wfr3 = new PROT_WireFormatReader(new TH_ThreadQueue(), tablesData);
|
||||
var data3 = "[test-white-domain 1.1][mac=" + computedMac + "]\n" +
|
||||
set3data;
|
||||
wfr3.deserialize(data3, data3cb);
|
||||
};
|
||||
|
||||
// Test the MAC stuff
|
||||
function data3cb(tablesKnown, tablesData) {
|
||||
G_Assert(z, tablesData["test-white-domain"] != null,
|
||||
"Didn't get our table back though the mac was correct");
|
||||
|
||||
// Now do the same thing with the wrong mac
|
||||
computedMac = "012" + computedMac.substr(3);
|
||||
var data4 = "[test-foo-domain 1.1][mac=" + computedMac + "]\n" +
|
||||
set3data;
|
||||
var wfr4 = new PROT_WireFormatReader(new TH_ThreadQueue(), tablesData);
|
||||
wfr4.deserialize(data4, data4cb);
|
||||
}
|
||||
|
||||
// Test the MAC stuff
|
||||
function data4cb(tablesKnown, tablesData) {
|
||||
G_Assert(z, !tablesKnown["test-foo-domain"],
|
||||
"Deserialized though our mac was wrong");
|
||||
|
||||
G_Assert(z, !tablesData["test-foo-domain"],
|
||||
"Deserialized though our mac was wrong");
|
||||
|
||||
testMalformedTables();
|
||||
|
||||
}
|
||||
var wfr = new PROT_WireFormatReader(new TH_ThreadQueue());
|
||||
wfr.deserialize(data1, data1cb);
|
||||
}
|
||||
}
|
||||
200
mozilla/toolkit/components/protection/content/xml-fetcher.js
Normal file
200
mozilla/toolkit/components/protection/content/xml-fetcher.js
Normal file
@@ -0,0 +1,200 @@
|
||||
/* ***** 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):
|
||||
* Fritz Schneider <fritz@google.com> (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 ***** */
|
||||
|
||||
// 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 class to wrap the XPCOM
|
||||
* version.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PROT_XMLHttpRequest() {
|
||||
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 = new PROT_XMLHttpRequest();
|
||||
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 = new PROT_XMLHttpRequest();
|
||||
this._callback = callback;
|
||||
var asynchronous = true;
|
||||
this._request.open("GET", page, asynchronous);
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called periodically by the request to indicate some state change. 4
|
||||
* means content has been received.
|
||||
*/
|
||||
readyStateChange: function(fetcher) {
|
||||
if (fetcher._request.readyState != 4) // TODO: check status code 200
|
||||
return;
|
||||
|
||||
// We occasionally get an NS_ERROR_NOT_AVAILABLE (it doesn't have
|
||||
// headers) when we try to read the response. Mask the exception
|
||||
// by returning null response.
|
||||
// TODO maybe masking this should be an option?
|
||||
try {
|
||||
G_Debug(this, "xml fetch status code: \"" +
|
||||
fetcher._request.status + "\"");
|
||||
if (fetcher._callback)
|
||||
fetcher._callback(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(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
27
mozilla/toolkit/components/protection/jar.mn
Normal file
27
mozilla/toolkit/components/protection/jar.mn
Normal file
@@ -0,0 +1,27 @@
|
||||
toolkit.jar:
|
||||
+ content/global/protection/application.js (content/application.js)
|
||||
+ content/global/protection/enchash-decrypter.js (content/enchash-decrypter.js)
|
||||
+ content/global/protection/globalstore.js (content/globalstore.js)
|
||||
+ content/global/protection/listmanager.js (content/listmanager.js)
|
||||
+ content/global/protection/list-warden.js (content/list-warden.js)
|
||||
+ content/global/protection/map.js (content/map.js)
|
||||
+ content/global/protection/url-canonicalizer.js (content/url-canonicalizer.js)
|
||||
+ content/global/protection/url-crypto.js (content/url-crypto.js)
|
||||
+ content/global/protection/url-crypto-key-manager.js (content/url-crypto-key-manager.js)
|
||||
+ content/global/protection/wireformat.js (content/wireformat.js)
|
||||
+ content/global/protection/xml-fetcher.js (content/xml-fetcher.js)
|
||||
#
|
||||
+ content/global/protection/js/arc4.js (content/js/arc4.js)
|
||||
+ content/global/protection/js/lang.js (content/js/lang.js)
|
||||
+ content/global/protection/js/thread-queue.js (content/js/thread-queue.js)
|
||||
#
|
||||
+ content/global/protection/moz/alarm.js (content/moz/alarm.js)
|
||||
+ content/global/protection/moz/base64.js (content/moz/base64.js)
|
||||
+ content/global/protection/moz/cryptohasher.js (content/moz/cryptohasher.js)
|
||||
+ content/global/protection/moz/debug.js (content/moz/debug.js)
|
||||
+ content/global/protection/moz/filesystem.js (content/moz/filesystem.js)
|
||||
+ content/global/protection/moz/lang.js (content/moz/lang.js)
|
||||
+ content/global/protection/moz/objectsafemap.js (content/moz/objectsafemap.js)
|
||||
+ content/global/protection/moz/observer.js (content/moz/observer.js)
|
||||
+ content/global/protection/moz/preferences.js (content/moz/preferences.js)
|
||||
+ content/global/protection/moz/protocol4.js (content/moz/protocol4.js)
|
||||
15
mozilla/toolkit/components/protection/public/Makefile.in
Normal file
15
mozilla/toolkit/components/protection/public/Makefile.in
Normal file
@@ -0,0 +1,15 @@
|
||||
DEPTH=../../../..
|
||||
topsrcdir=@top_srcdir@
|
||||
srcdir=@srcdir@
|
||||
VPATH=@srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MODULE = protection
|
||||
XPIDL_MODULE = protection
|
||||
|
||||
XPIDLSRCS = nsIProtectionTable.idl \
|
||||
nsIProtectionListManager.idl \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
@@ -0,0 +1,87 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* ***** 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 mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Netscape Communications Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 1998
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
/**
|
||||
* Interface for a class that manages updates of multiple nsIProtectionTables.
|
||||
*/
|
||||
|
||||
interface nsIFile;
|
||||
|
||||
[scriptable, uuid(e1a80418-1bf9-4bd7-a40d-94d549c24955)]
|
||||
interface nsIProtectionListManager : nsISupports
|
||||
{
|
||||
/**
|
||||
* Set the directory to read/write tables to.
|
||||
*/
|
||||
void setAppDir(in nsIFile appDir);
|
||||
|
||||
/**
|
||||
* Add a table to the list of tables we are managing. The name is a
|
||||
* string of the format provider_name-semantic_type-table_type. For
|
||||
* example, goog-white-enchash or goog-black-url.
|
||||
*/
|
||||
boolean registerTable(in string tableName,
|
||||
in boolean requireMac);
|
||||
|
||||
/**
|
||||
* For each table that is enabled, check for updates during
|
||||
* during the scheduled interval.
|
||||
*/
|
||||
void startUpdateChecker();
|
||||
|
||||
/**
|
||||
* Stop checking for all updates.
|
||||
*/
|
||||
void stopUpdateChecker();
|
||||
|
||||
/**
|
||||
* Lookup a key in a table. Should not raise exceptions.
|
||||
*/
|
||||
boolean safeLookup(in string tableName, in string key);
|
||||
|
||||
/**
|
||||
* Insert a key in a table. Should not raise exceptions.
|
||||
*/
|
||||
boolean safeInsert(in string tableName, in string key, in string value);
|
||||
|
||||
/**
|
||||
* Remove a key from a table. Should not raise exceptions.
|
||||
*/
|
||||
boolean safeErase(in string tableName, in string key);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* ***** 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 mozilla.org code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Netscape Communications Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 1998
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
*
|
||||
* 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 ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
// A map that contains a string keys mapped to string values.
|
||||
// TODO: maybe nsISerializeable to handle reading/writing from disk?
|
||||
|
||||
[scriptable, uuid(fd1f8334-1859-472d-b01f-4ac6b1121ce4)]
|
||||
interface nsIProtectionTable : nsISupports
|
||||
{
|
||||
readonly attribute long size;
|
||||
|
||||
/**
|
||||
* The name used to identify this table
|
||||
*/
|
||||
attribute string name;
|
||||
|
||||
/**
|
||||
* Set to false if we don't want to update this table.
|
||||
*/
|
||||
attribute boolean needsUpdate;
|
||||
|
||||
/**
|
||||
* In the simple case, find just looks up the string in the
|
||||
* table and returns true if it is found. However, it could
|
||||
* do something more complex (e.g., canonicalize the url).
|
||||
*/
|
||||
boolean find(in string key);
|
||||
|
||||
/**
|
||||
* In the simple case, key is a url and value is 1. However,
|
||||
* value could be more complicated (e.g., hashed value based
|
||||
* on site domain).
|
||||
*/
|
||||
void insert(in string key, in string value);
|
||||
|
||||
boolean erase(in string key);
|
||||
|
||||
// TEMPORARY: used to help serialize the table to disk. May be removed.
|
||||
void getKeys(out unsigned long size,
|
||||
[retval,array,size_is(size)] out string keys);
|
||||
|
||||
// TEMPORARY: used to help serialize the table to disk. May be removed.
|
||||
void findValue(in string key);
|
||||
|
||||
// TODO: merge data
|
||||
// void replace(in nsIProtectionTable);
|
||||
};
|
||||
16
mozilla/toolkit/components/protection/src/Makefile.in
Normal file
16
mozilla/toolkit/components/protection/src/Makefile.in
Normal file
@@ -0,0 +1,16 @@
|
||||
DEPTH = ../../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
# EXTRA_COMPONENTS installs components written in JS to dist/bin/components
|
||||
EXTRA_COMPONENTS = protectionTableUrl.js \
|
||||
protectionTableDomain.js \
|
||||
protectionTableSite.js \
|
||||
protectionTableEnchash.js \
|
||||
protectionListManager.js \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
@@ -0,0 +1,85 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const G_GDEBUG = true;
|
||||
|
||||
// Use subscript loader to load files. The files in ../content get mapped
|
||||
// to chrome://global/content/protection/. Order matters if one file depends
|
||||
// on another file during initialization.
|
||||
const LIB_FILES = [
|
||||
"chrome://global/content/protection/js/arc4.js",
|
||||
"chrome://global/content/protection/js/lang.js",
|
||||
"chrome://global/content/protection/js/thread-queue.js",
|
||||
|
||||
"chrome://global/content/protection/moz/preferences.js",
|
||||
"chrome://global/content/protection/moz/filesystem.js",
|
||||
"chrome://global/content/protection/moz/debug.js", // dep js/lang.js moz/prefs.js moz/filesystem.js
|
||||
"chrome://global/content/protection/moz/alarm.js",
|
||||
"chrome://global/content/protection/moz/base64.js",
|
||||
"chrome://global/content/protection/moz/cryptohasher.js",
|
||||
"chrome://global/content/protection/moz/lang.js",
|
||||
"chrome://global/content/protection/moz/observer.js",
|
||||
"chrome://global/content/protection/moz/protocol4.js",
|
||||
|
||||
"chrome://global/content/protection/application.js",
|
||||
"chrome://global/content/protection/globalstore.js",
|
||||
"chrome://global/content/protection/listmanager.js",
|
||||
"chrome://global/content/protection/url-crypto.js",
|
||||
"chrome://global/content/protection/url-crypto-key-manager.js", // dep url-crypto.js
|
||||
"chrome://global/content/protection/wireformat.js",
|
||||
"chrome://global/content/protection/xml-fetcher.js",
|
||||
];
|
||||
|
||||
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
|
||||
dump('*** loading subscript ' + libFile + '\n');
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript(libFile);
|
||||
}
|
||||
|
||||
// Module object
|
||||
function ProtectionListManagerMod() {
|
||||
this.firstTime = true;
|
||||
this.cid = Components.ID("{ca168834-cc00-48f9-b83c-fd018e58cae3}");
|
||||
this.progid = "@mozilla.org/protection/protectionlistmanager;1";
|
||||
}
|
||||
|
||||
ProtectionListManagerMod.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,
|
||||
"Protection List Manager Module",
|
||||
this.progid,
|
||||
fileSpec,
|
||||
loc,
|
||||
type);
|
||||
};
|
||||
|
||||
ProtectionListManagerMod.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;
|
||||
}
|
||||
|
||||
ProtectionListManagerMod.prototype.canUnload = function(compMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtectionListManagerMod.prototype.factory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return (new PROT_ListManager()).QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
var ListManagerModInst = new ProtectionListManagerMod();
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return ListManagerModInst;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const G_GDEBUG = false;
|
||||
|
||||
// Use subscript loader to load files. The files in ../content get mapped
|
||||
// to chrome://global/content/protection/. Order matters if one file depends
|
||||
// on another file during initialization.
|
||||
const LIB_FILES = [
|
||||
"chrome://global/content/protection/js/lang.js",
|
||||
|
||||
"chrome://global/content/protection/moz/preferences.js",
|
||||
"chrome://global/content/protection/moz/filesystem.js",
|
||||
"chrome://global/content/protection/moz/debug.js", // req js/lang.js moz/prefs.js moz/filesystem.js
|
||||
|
||||
"chrome://global/content/protection/map.js",
|
||||
];
|
||||
|
||||
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
|
||||
dump('*** loading subscript ' + libFile + '\n');
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript(libFile);
|
||||
}
|
||||
|
||||
function ProtectionTableDomain() {
|
||||
G_Map.call(this);
|
||||
this.debugZone = "trtable-domain";
|
||||
}
|
||||
|
||||
ProtectionTableDomain.inherits(G_Map);
|
||||
|
||||
/**
|
||||
* Look up a URL in a domain table
|
||||
*
|
||||
* @returns Boolean true if the url domain is in the table
|
||||
*/
|
||||
ProtectionTableDomain.prototype.find = function(url) {
|
||||
var urlObj = Cc["@mozilla.org/network/standard-url;1"]
|
||||
.createInstance(Ci.nsIURL);
|
||||
urlObj.spec = url;
|
||||
var host = urlObj.host;
|
||||
var components = host.split(".");
|
||||
|
||||
// We don't have a good way map from hosts to domains, so we instead try
|
||||
// each possibility. Could probably optimize to start at the second dot?
|
||||
for (var i = 0; i < components.length - 1; i++) {
|
||||
host = components.slice(i).join(".");
|
||||
var val = this.find_(host);
|
||||
if (val)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Module object
|
||||
function ProtectionTableDomainMod() {
|
||||
this.firstTime = true;
|
||||
this.cid = Components.ID("{3b5004c6-3fcd-4b12-b311-a4dfbeaf27aa}");
|
||||
this.progid = "@mozilla.org/protection/protectiontable;1?type=domain";
|
||||
}
|
||||
|
||||
ProtectionTableDomainMod.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,
|
||||
"Protection Table Domain Module",
|
||||
this.progid,
|
||||
fileSpec,
|
||||
loc,
|
||||
type);
|
||||
};
|
||||
|
||||
ProtectionTableDomainMod.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;
|
||||
}
|
||||
|
||||
ProtectionTableDomainMod.prototype.canUnload = function(compMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtectionTableDomainMod.prototype.factory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return (new ProtectionTableDomain()).QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
var DomainModInst = new ProtectionTableDomainMod();
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return DomainModInst;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const G_GDEBUG = false;
|
||||
|
||||
// Use subscript loader to load files. The files in ../content get mapped
|
||||
// to chrome://global/content/protection/. Order matters if one file depends
|
||||
// on another file during initialization.
|
||||
const LIB_FILES = [
|
||||
"chrome://global/content/protection/js/arc4.js",
|
||||
"chrome://global/content/protection/js/lang.js",
|
||||
|
||||
"chrome://global/content/protection/moz/preferences.js",
|
||||
"chrome://global/content/protection/moz/filesystem.js",
|
||||
"chrome://global/content/protection/moz/debug.js", // req js/lang.js moz/prefs.js moz/filesystem.js
|
||||
|
||||
"chrome://global/content/protection/map.js",
|
||||
"chrome://global/content/protection/moz/base64.js",
|
||||
"chrome://global/content/protection/moz/cryptohasher.js",
|
||||
"chrome://global/content/protection/enchash-decrypter.js",
|
||||
];
|
||||
|
||||
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
|
||||
dump('*** loading subscript ' + libFile + '\n');
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript(libFile);
|
||||
}
|
||||
|
||||
function ProtectionTableEnchash() {
|
||||
G_Map.call(this);
|
||||
this.debugZone = "trtable-enchash";
|
||||
this.enchashDecrypter_ = new PROT_EnchashDecrypter();
|
||||
}
|
||||
|
||||
ProtectionTableEnchash.inherits(G_Map);
|
||||
|
||||
/**
|
||||
* Look up a URL in an enchashDB
|
||||
*
|
||||
* @returns Boolean indicating whether the URL matches the regular
|
||||
* expression contained in the table value
|
||||
*/
|
||||
ProtectionTableEnchash.prototype.find = function(url) {
|
||||
var host = this.enchashDecrypter_.getCanonicalHost(url);
|
||||
|
||||
for (var i = 0; i < PROT_EnchashDecrypter.MAX_DOTS + 1; i++) {
|
||||
var key = this.enchashDecrypter_.getLookupKey(host);
|
||||
|
||||
var encrypted = this.find_(key);
|
||||
if (encrypted) {
|
||||
G_Debug(this, "Enchash DB has host " + host);
|
||||
|
||||
// We have encrypted regular expressions for this host. Let's
|
||||
// decrypt them and see if we have a match.
|
||||
var decrypted = this.enchashDecrypter_.decryptData(encrypted, host);
|
||||
var res = this.enchashDecrypter_.parseRegExps(decrypted);
|
||||
for (var j = 0; j < res.length; j++) {
|
||||
if (res[j].test(url))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var index = host.indexOf(".");
|
||||
if (index == -1)
|
||||
break;
|
||||
host = host.substring(index + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Module object
|
||||
function ProtectionTableEnchashMod() {
|
||||
this.firstTime = true;
|
||||
this.cid = Components.ID("{04f15d1d-2db8-4b8e-91d7-82f30308b434}");
|
||||
this.progid = "@mozilla.org/protection/protectiontable;1?type=enchash";
|
||||
}
|
||||
|
||||
ProtectionTableEnchashMod.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,
|
||||
"Protection Table Enchash Module",
|
||||
this.progid,
|
||||
fileSpec,
|
||||
loc,
|
||||
type);
|
||||
};
|
||||
|
||||
ProtectionTableEnchashMod.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;
|
||||
}
|
||||
|
||||
ProtectionTableEnchashMod.prototype.canUnload = function(compMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtectionTableEnchashMod.prototype.factory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return (new ProtectionTableEnchash()).QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
var EnchashModInst = new ProtectionTableEnchashMod();
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return EnchashModInst;
|
||||
}
|
||||
118
mozilla/toolkit/components/protection/src/protectionTableSite.js
Normal file
118
mozilla/toolkit/components/protection/src/protectionTableSite.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const G_GDEBUG = false;
|
||||
|
||||
// Use subscript loader to load files. The files in ../content get mapped
|
||||
// to chrome://global/content/protection/. Order matters if one file depends
|
||||
// on another file during initialization.
|
||||
const LIB_FILES = [
|
||||
"chrome://global/content/protection/js/lang.js",
|
||||
|
||||
"chrome://global/content/protection/moz/preferences.js",
|
||||
"chrome://global/content/protection/moz/filesystem.js",
|
||||
"chrome://global/content/protection/moz/debug.js", // req js/lang.js moz/prefs.js moz/filesystem.js
|
||||
|
||||
"chrome://global/content/protection/map.js",
|
||||
];
|
||||
|
||||
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
|
||||
dump('*** loading subscript ' + libFile + '\n');
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript(libFile);
|
||||
}
|
||||
|
||||
function ProtectionTableSite() {
|
||||
G_Map.call(this);
|
||||
this.debugZone = "trtable-site";
|
||||
this.ioService_ = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
}
|
||||
|
||||
ProtectionTableSite.inherits(G_Map);
|
||||
|
||||
/**
|
||||
* Look up a URL in a site DB
|
||||
*
|
||||
* @returns Boolean true if URL matches in table, we normalize based on
|
||||
* part of the path
|
||||
*/
|
||||
ProtectionTableSite.prototype.find = function(url) {
|
||||
var nsIURI = this.ioService_.newURI(url, null, null);
|
||||
var host = nsIURI.asciiHost;
|
||||
var hostComponents = host.split(".");
|
||||
|
||||
var path = nsIURI.path;
|
||||
var pathComponents = path.split("/");
|
||||
|
||||
// We don't have a good way to convert a fully specified URL into a
|
||||
// site. We try host name components with or without the first
|
||||
// path component.
|
||||
for (var i = 0; i < hostComponents.length - 1; i++) {
|
||||
host = hostComponents.slice(i).join(".") + "/";
|
||||
var val = this.find_(host);
|
||||
G_Debug(this, "Checking: " + host + " : " + val);
|
||||
if (val)
|
||||
return true;
|
||||
|
||||
// The path starts with a "/", so we are interested in the second path
|
||||
// component if it is available
|
||||
if (pathComponents.length >= 2) {
|
||||
host = host + pathComponents[1] + "/";
|
||||
var val = this.find_(host);
|
||||
G_Debug(this, "Checking: " + host + " : " + val);
|
||||
if (val)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Module object
|
||||
function ProtectionTableSiteMod() {
|
||||
this.firstTime = true;
|
||||
this.cid = Components.ID("{1e48f217-34e4-44a3-9b4a-9b66ed0a1201}");
|
||||
this.progid = "@mozilla.org/protection/protectiontable;1?type=site";
|
||||
}
|
||||
|
||||
ProtectionTableSiteMod.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,
|
||||
"Protection Table Site Module",
|
||||
this.progid,
|
||||
fileSpec,
|
||||
loc,
|
||||
type);
|
||||
};
|
||||
|
||||
ProtectionTableSiteMod.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;
|
||||
}
|
||||
|
||||
ProtectionTableSiteMod.prototype.canUnload = function(compMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtectionTableSiteMod.prototype.factory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return (new ProtectionTableSite()).QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
var SiteModInst = new ProtectionTableSiteMod();
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return SiteModInst;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const G_GDEBUG = false;
|
||||
|
||||
// Use subscript loader to load files. The files in ../content get mapped
|
||||
// to chrome://global/content/protection/. Order matters if one file depends
|
||||
// on another file during initialization.
|
||||
const LIB_FILES = [
|
||||
"chrome://global/content/protection/js/lang.js",
|
||||
|
||||
"chrome://global/content/protection/moz/preferences.js",
|
||||
"chrome://global/content/protection/moz/filesystem.js",
|
||||
"chrome://global/content/protection/moz/debug.js", // req js/lang.js moz/prefs.js moz/filesystem.js
|
||||
|
||||
"chrome://global/content/protection/map.js",
|
||||
"chrome://global/content/protection/url-canonicalizer.js"
|
||||
];
|
||||
|
||||
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
|
||||
dump('*** loading subscript ' + libFile + '\n');
|
||||
Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader)
|
||||
.loadSubScript(libFile);
|
||||
}
|
||||
|
||||
function ProtectionTableUrl() {
|
||||
G_Map.call(this);
|
||||
this.debugZone = "trtable-url";
|
||||
}
|
||||
|
||||
ProtectionTableUrl.inherits(G_Map);
|
||||
|
||||
/**
|
||||
* Look up a URL in a URL table
|
||||
*
|
||||
* @returns Boolean true if the canonicalized url is in the table
|
||||
*/
|
||||
ProtectionTableUrl.prototype.find = function(url) {
|
||||
var canonicalized = PROT_URLCanonicalizer.canonicalizeURL_(url);
|
||||
// Uncomment for debugging
|
||||
G_Debug(this, "Looking up: " + url + " (" + canonicalized + ")");
|
||||
return this.find_(canonicalized);
|
||||
}
|
||||
|
||||
|
||||
// Module object
|
||||
function ProtectionTableUrlMod() {
|
||||
this.firstTime = true;
|
||||
this.cid = Components.ID("{43399ee0-da0b-46a8-9541-08721265981c}");
|
||||
this.progid = "@mozilla.org/protection/protectiontable;1?type=url";
|
||||
}
|
||||
|
||||
ProtectionTableUrlMod.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,
|
||||
"Protection Table Url Module",
|
||||
this.progid,
|
||||
fileSpec,
|
||||
loc,
|
||||
type);
|
||||
};
|
||||
|
||||
ProtectionTableUrlMod.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;
|
||||
}
|
||||
|
||||
ProtectionTableUrlMod.prototype.canUnload = function(compMgr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ProtectionTableUrlMod.prototype.factory = {
|
||||
createInstance: function(outer, iid) {
|
||||
if (outer != null)
|
||||
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
||||
return (new ProtectionTableUrl()).QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
var UrlModInst = new ProtectionTableUrlMod();
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return UrlModInst;
|
||||
}
|
||||
47
mozilla/toolkit/components/protection/tests/Makefile.in
Normal file
47
mozilla/toolkit/components/protection/tests/Makefile.in
Normal file
@@ -0,0 +1,47 @@
|
||||
#
|
||||
# ***** 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 mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Netscape Communications Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 1998
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
#
|
||||
# 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 *****
|
||||
|
||||
|
||||
DEPTH = ../../../..
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
2
mozilla/toolkit/components/protection/tests/jar.mn
Normal file
2
mozilla/toolkit/components/protection/tests/jar.mn
Normal file
@@ -0,0 +1,2 @@
|
||||
toolkit.jar:
|
||||
+ content/global/protection/unittests.xul (unittests.xul)
|
||||
178
mozilla/toolkit/components/protection/tests/unittests.xul
Normal file
178
mozilla/toolkit/components/protection/tests/unittests.xul
Normal file
@@ -0,0 +1,178 @@
|
||||
<?xml version="1.0"?>
|
||||
<window id="PROT_unittest"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="onProtUnittestLoad();"
|
||||
title="prot unittests">
|
||||
|
||||
<script><![CDATA[
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
function G_Debug(zone, s) {
|
||||
var label = document.createElement('label');
|
||||
var txt = "[" + zone + "] " + s;
|
||||
label.appendChild(document.createTextNode(txt));
|
||||
|
||||
document.documentElement.appendChild(label);
|
||||
}
|
||||
|
||||
function G_Assert(zone, cond, msg) {
|
||||
if (!cond) {
|
||||
G_Debug(zone, msg);
|
||||
throw msg;
|
||||
}
|
||||
}
|
||||
|
||||
function ProtectionTableTests() {
|
||||
var z = "trtable UNITTEST";
|
||||
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
var url = "http://www.yahoo.com?foo=bar";
|
||||
var url2 = "http://168.188.99.26/.secure/www.ebay.com/";
|
||||
var urlTable = Cc['@mozilla.org/protection/protectiontable;1?type=url']
|
||||
.createInstance(Ci.nsIProtectionTable);
|
||||
urlTable.insert(url, "1");
|
||||
urlTable.insert(url2, "1");
|
||||
G_Assert(z, urlTable.find(url), "URL lookups broken");
|
||||
G_Assert(z, !urlTable.find("about:config"), "about:config breaks domlook");
|
||||
G_Assert(z, urlTable.find(url2), "URL lookups broken");
|
||||
G_Assert(z, urlTable.find("http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/%77%77%77%2E%65%62%61%79%2E%63%6F%6D/") == true,
|
||||
"URL Canonicalization broken");
|
||||
G_Assert(z, urlTable.size == 2, 'urlTable: wrong size');
|
||||
|
||||
var dom1 = "bar.com";
|
||||
var dom2 = "amazon.co.uk";
|
||||
var dom3 = "127.0.0.1";
|
||||
var domainTable = Cc['@mozilla.org/protection/protectiontable;1?type=domain']
|
||||
.createInstance(Ci.nsIProtectionTable);
|
||||
domainTable.insert(dom1, "1");
|
||||
domainTable.insert(dom2, "1");
|
||||
domainTable.insert(dom3, "1");
|
||||
G_Assert(z, domainTable.find("http://www.bar.com/?zaz=asdf#url"),
|
||||
"Domain lookups broken (single dot)");
|
||||
G_Assert(z, domainTable.find("http://www.amazon.co.uk/?z=af#url"),
|
||||
"Domain lookups broken (two dots)");
|
||||
G_Assert(z, domainTable.find("http://127.0.0.1/?z=af#url"),
|
||||
"Domain lookups broken (IP)");
|
||||
G_Assert(z, domainTable.size == 3, 'domainTable: wrong size');
|
||||
|
||||
var site1 = "google.com/safebrowsing/";
|
||||
var site2 = "www.foo.bar/";
|
||||
var site3 = "127.0.0.1/";
|
||||
var siteTable = Cc['@mozilla.org/protection/protectiontable;1?type=site']
|
||||
.createInstance(Ci.nsIProtectionTable);
|
||||
siteTable.insert(site1, "1");
|
||||
siteTable.insert(site2, "1");
|
||||
siteTable.insert(site3, "1");
|
||||
G_Assert(z, siteTable.find("http://www.google.com/safebrowsing/1.php"),
|
||||
"Site lookups broken - reducing");
|
||||
G_Assert(z, siteTable.find("http://www.foo.bar/some/random/path"),
|
||||
"Site lookups broken - fqdn");
|
||||
G_Assert(z, siteTable.find("http://127.0.0.1/something?hello=1"),
|
||||
"Site lookups broken - IP");
|
||||
G_Assert(z, !siteTable.find("http://www.google.com/search/"),
|
||||
"Site lookups broken - overreaching");
|
||||
G_Assert(z, siteTable.size == 3, 'siteTable: wrong size');
|
||||
|
||||
var url1 = "http://poseidon.marinet.gr/~eleni/eBay/index.php";
|
||||
var domainHash = "01844755C8143C4579BB28DD59C23747";
|
||||
var enchashTable = Cc['@mozilla.org/protection/protectiontable;1?type=enchash']
|
||||
.createInstance(Ci.nsIProtectionTable);
|
||||
enchashTable.insert(domainHash, "bGtEQWJuMl9FA3Kl5RiXMpgFU8nDJl9J0hXjUck9+"
|
||||
+ "mMUQwAN6llf0gJeY5DIPPc2f+a8MSBFJN17ANGJ"
|
||||
+ "Zl5oZVsQfSW4i12rlScsx4tweZAE");
|
||||
G_Assert(z, enchashTable.find(url1), 'enchash lookup failed');
|
||||
G_Assert(z, !enchashTable.find(url1 + '/foo'),
|
||||
"enchash lookup broken - overreaching");
|
||||
G_Assert(z, enchashTable.size == 1, 'enchashTable: wrong size');
|
||||
|
||||
// TODO: test replace
|
||||
G_Debug(z, "PASSED");
|
||||
}
|
||||
|
||||
function ProtectionListManagerTests() {
|
||||
var z = "listmanager UNITTEST";
|
||||
G_Debug(z, "Starting");
|
||||
|
||||
//var tempDir = G_File.createUniqueTempDir();
|
||||
var baseName = (new Date().getTime()) + ".tmp";
|
||||
var tempDir = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties)
|
||||
.get("TmpD", Ci.nsILocalFile);
|
||||
tempDir.append(baseName);
|
||||
tempDir.createUnique(tempDir.DIRECTORY_TYPE, 0744);
|
||||
|
||||
var listManager = Cc["@mozilla.org/protection/protectionlistmanager;1"]
|
||||
.createInstance(Ci.nsIProtectionListManager);
|
||||
listManager.setAppDir(tempDir);
|
||||
|
||||
var data = "";
|
||||
|
||||
var set1Name = "test1-foo-domain";
|
||||
data += "[" + set1Name + " 1.2]\n";
|
||||
var set1 = {};
|
||||
for (var i = 0; i < 10; i++) {
|
||||
set1["http://" + i + ".com"] = 1;
|
||||
data += "+" + i + ".com\t1\n";
|
||||
}
|
||||
|
||||
data += "\n";
|
||||
var set2Name = "test2-foo-domain";
|
||||
// TODO must have blank line
|
||||
data += "\n[" + set2Name + " 1.7]\n";
|
||||
var set2 = {};
|
||||
for (var i = 0; i < 5; i++) {
|
||||
set2["http://" + i + ".com"] = 1;
|
||||
data += "+" + i + ".com\t1\n";
|
||||
}
|
||||
|
||||
function deserialized(tablesKnown, tablesData) {
|
||||
listManager.wrappedJSObject.dataReady(tablesKnown, tablesData);
|
||||
|
||||
var file = tempDir.clone();
|
||||
file.append(set1Name + ".sst");
|
||||
G_Assert(z, file.exists() && file.isFile() && file.isReadable(),
|
||||
"Failed to write out: " + file.path);
|
||||
|
||||
file = tempDir.clone();
|
||||
file.append(set2Name + ".sst");
|
||||
G_Assert(z, file.exists() && file.isFile() && file.isReadable(),
|
||||
"Failed to write out: " + file.path);
|
||||
|
||||
// now try to read them back from disk
|
||||
listManager = Cc["@mozilla.org/protection/protectionlistmanager;1"]
|
||||
.createInstance(Ci.nsIProtectionListManager);
|
||||
listManager.setAppDir(tempDir);
|
||||
var tables = [ set1Name, set2Name ];
|
||||
listManager.wrappedJSObject.enableUpdateTables(tables);
|
||||
listManager.wrappedJSObject.readDataFiles();
|
||||
|
||||
// assert that the values match
|
||||
for (var prop in set1) {
|
||||
G_Assert(z,
|
||||
listManager.wrappedJSObject.tablesData[set1Name].find(prop),
|
||||
"Couldn't find member " + prop + "of set1 from disk.");
|
||||
}
|
||||
|
||||
for (var prop in set2) {
|
||||
G_Assert(z,
|
||||
listManager.wrappedJSObject.tablesData[set2Name].find(prop),
|
||||
"Couldn't find member " + prop + "of set2 from disk.");
|
||||
}
|
||||
|
||||
tempDir.remove(true);
|
||||
|
||||
G_Debug(z, "PASSED");
|
||||
};
|
||||
|
||||
// Use the unwrapped object for the unittest
|
||||
listManager.wrappedJSObject.deserialize_(data, deserialized);
|
||||
}
|
||||
|
||||
function onProtUnittestLoad() {
|
||||
ProtectionTableTests();
|
||||
ProtectionListManagerTests();
|
||||
}
|
||||
]]></script>
|
||||
</window>
|
||||
Reference in New Issue
Block a user