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:
beng%bengoodger.com
2006-04-17 21:55:45 +00:00
parent 0b800bdc50
commit 91d83ed736
38 changed files with 8920 additions and 0 deletions

View 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

View 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_;
}

View File

@@ -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");
}
}

View 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;
}

View 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;
}

View 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();
}

View 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);
}

View 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");
}
}

View 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;
}

View 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_;
});

View 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();
}
}

View 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");
}
}

View File

@@ -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");
}
}

View 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();
}

View 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");
}
}

View 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);
}

View File

@@ -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");
}
}

View 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");
}
}

View 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");
}
}

View 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");
}
}

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View 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");
}
}

View 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);
}
}

View 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;
}

View 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)

View 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

View File

@@ -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);
};

View File

@@ -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);
};

View 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

View File

@@ -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;
}

View File

@@ -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;
}

View 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/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;
}

View 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;
}

View File

@@ -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;
}

View 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

View File

@@ -0,0 +1,2 @@
toolkit.jar:
+ content/global/protection/unittests.xul (unittests.xul)

View 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>