Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / toolkit / components / url-classifier / content / enchash-decrypter.js
blob2ff8f3b832bc1fa8fe52d376fc5d8014f0168853
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
14 # The Original Code is Google Safe Browsing.
16 # The Initial Developer of the Original Code is Google Inc.
17 # Portions created by the Initial Developer are Copyright (C) 2006
18 # the Initial Developer. All Rights Reserved.
20 # Contributor(s):
21 #   Fritz Schneider <fritz@google.com> (original author)
23 # Alternatively, the contents of this file may be used under the terms of
24 # either the GNU General Public License Version 2 or later (the "GPL"), or
25 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 # in which case the provisions of the GPL or the LGPL are applicable instead
27 # of those above. If you wish to allow use of your version of this file only
28 # under the terms of either the GPL or the LGPL, and not to allow others to
29 # use your version of this file under the terms of the MPL, indicate your
30 # decision by deleting the provisions above and replace them with the notice
31 # and other provisions required by the GPL or the LGPL. If you do not delete
32 # the provisions above, a recipient may use your version of this file under
33 # the terms of any one of the MPL, the GPL or the LGPL.
35 # ***** END LICENSE BLOCK *****
38 // This is the code used to interact with data encoded in the
39 // goog-black-enchash format. The format is basically a map from
40 // hashed hostnames to encrypted sequences of regular expressions
41 // where the encryption key is derived from the hashed
42 // hostname. Encoding lists like this raises the bar slightly on
43 // deriving complete table data from the db. This data format is NOT
44 // our idea; we would've raise the bar higher :)
46 // Anyway, this code is a port of the original C++ implementation by
47 // Garret. To ease verification, I mirrored that code as closely as
48 // possible.  As a result, you'll see some C++-style variable naming
49 // and roundabout (C++) ways of doing things. Additionally, I've
50 // omitted the comments.
52 // This code should not change, except to fix bugs.
54 // TODO: accommodate other kinds of perl-but-not-javascript qualifiers
56 /**
57  * This thing knows how to generate lookup keys and decrypt values found in
58  * a table of type enchash.
59  */
60 function PROT_EnchashDecrypter() {
61   this.debugZone = "enchashdecrypter";
62   this.REs_ = PROT_EnchashDecrypter.REs;
63   this.hasher_ = new G_CryptoHasher();
64   this.streamCipher_ = Cc["@mozilla.org/security/streamcipher;1"]
65                        .createInstance(Ci.nsIStreamCipher);
68 PROT_EnchashDecrypter.DATABASE_SALT = "oU3q.72p";
69 PROT_EnchashDecrypter.SALT_LENGTH = PROT_EnchashDecrypter.DATABASE_SALT.length;
71 PROT_EnchashDecrypter.MAX_DOTS = 5;
73 PROT_EnchashDecrypter.REs = {};
74 PROT_EnchashDecrypter.REs.FIND_DODGY_CHARS_GLOBAL =
75   new RegExp("[\x00-\x1f\x7f-\xff]+", "g");
76 PROT_EnchashDecrypter.REs.FIND_END_DOTS_GLOBAL =
77   new RegExp("^\\.+|\\.+$", "g");
78 PROT_EnchashDecrypter.REs.FIND_MULTIPLE_DOTS_GLOBAL =
79   new RegExp("\\.{2,}", "g");
80 PROT_EnchashDecrypter.REs.FIND_TRAILING_SPACE =
81   new RegExp("^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}) ");
82 PROT_EnchashDecrypter.REs.POSSIBLE_IP =
83   new RegExp("^((?:0x[0-9a-f]+|[0-9\\.])+)$", "i");
84 PROT_EnchashDecrypter.REs.FIND_BAD_OCTAL = new RegExp("(^|\\.)0\\d*[89]");
85 PROT_EnchashDecrypter.REs.IS_OCTAL = new RegExp("^0[0-7]*$");
86 PROT_EnchashDecrypter.REs.IS_DECIMAL = new RegExp("^[0-9]+$");
87 PROT_EnchashDecrypter.REs.IS_HEX = new RegExp("^0[xX]([0-9a-fA-F]+)$");
89 // Regexps are given in perl regexp format. Unfortunately, JavaScript's
90 // library isn't completely compatible. For example, you can't specify
91 // case-insensitive matching by using (?i) in the expression text :(
92 // So we manually set this bit with the help of this regular expression.
93 PROT_EnchashDecrypter.REs.CASE_INSENSITIVE = /\(\?i\)/g;
95 /**
96  * Helper function 
97  *
98  * @param str String to get chars from
99  * 
100  * @param n Number of characters to get
102  * @returns String made up of the last n characters of str
103  */ 
104 PROT_EnchashDecrypter.prototype.lastNChars_ = function(str, n) {
105   n = -n;
106   return str.substr(n);
110  * Translate a plaintext enchash value into regular expressions
112  * @param data String containing a decrypted enchash db entry
114  * @returns An array of RegExps
115  */
116 PROT_EnchashDecrypter.prototype.parseRegExps = function(data) {
117   var res = data.split("\t");
118   
119   G_Debug(this, "Got " + res.length + " regular rexpressions");
120   
121   for (var i = 0; i < res.length; i++) {
122     // Could have leading (?i); if so, set the flag and strip it
123     var flags = (this.REs_.CASE_INSENSITIVE.test(res[i])) ? "i" : "";
124     res[i] = res[i].replace(this.REs_.CASE_INSENSITIVE, "");
125     res[i] = new RegExp(res[i], flags);
126   }
128   return res;
132  * Get the canonical version of the given URL for lookup in a table of 
133  * type -url.
135  * @param url String to canonicalize
137  * @returns String containing the canonicalized url (maximally url-decoded
138  *          with hostname normalized, then specially url-encoded)
139  */
140 PROT_EnchashDecrypter.prototype.getCanonicalUrl = function(url) {
141   var urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
142                  .getService(Ci.nsIUrlClassifierUtils);
143   var escapedUrl = urlUtils.canonicalizeURL(url);
144   // Normalize the host
145   var host = this.getCanonicalHost(escapedUrl);
146   if (!host) {
147     // Probably an invalid url, return what we have so far.
148     return escapedUrl;
149   }
151   // Combine our normalized host with our escaped url.
152   var ioService = Cc["@mozilla.org/network/io-service;1"]
153                   .getService(Ci.nsIIOService);
154   var urlObj = ioService.newURI(escapedUrl, null, null);
155   urlObj.host = host;
156   return urlObj.asciiSpec;
160  * @param opt_maxDots Number maximum number of dots to include.
161  */
162 PROT_EnchashDecrypter.prototype.getCanonicalHost = function(str, opt_maxDots) {
163   var ioService = Cc["@mozilla.org/network/io-service;1"]
164                   .getService(Ci.nsIIOService);
165   try {
166     var urlObj = ioService.newURI(str, null, null);
167     var asciiHost = urlObj.asciiHost;
168   } catch (e) {
169     G_Debug(this, "Unable to get hostname: " + str);
170     return "";
171   }
173   var unescaped = unescape(asciiHost);
175   unescaped = unescaped.replace(this.REs_.FIND_DODGY_CHARS_GLOBAL, "")
176               .replace(this.REs_.FIND_END_DOTS_GLOBAL, "")
177               .replace(this.REs_.FIND_MULTIPLE_DOTS_GLOBAL, ".");
179   var temp = this.parseIPAddress_(unescaped);
180   if (temp)
181     unescaped = temp;
183   // Escape everything that's not alphanumeric, hyphen, or dot.
184   var urlUtils = Cc["@mozilla.org/url-classifier/utils;1"]
185                  .getService(Ci.nsIUrlClassifierUtils);
186   var escaped = urlUtils.escapeHostname(unescaped);
188   if (opt_maxDots) {
189     // Limit the number of dots
190     var k;
191     var index = escaped.length;
192     for (k = 0; k < opt_maxDots + 1; k++) {
193       temp = escaped.lastIndexOf(".", index - 1);
194       if (temp == -1) {
195         break;
196       } else {
197         index = temp;
198       }
199     }
200     
201     if (k == opt_maxDots + 1 && index != -1) {
202       escaped = escaped.substring(index + 1);
203     }
204   }
206   escaped = escaped.toLowerCase();
207   return escaped;
210 PROT_EnchashDecrypter.prototype.parseIPAddress_ = function(host) {
211   if (host.length <= 15) {
213     // The Windows resolver allows a 4-part dotted decimal IP address to
214     // have a space followed by any old rubbish, so long as the total length
215     // of the string doesn't get above 15 characters. So, "10.192.95.89 xy"
216     // is resolved to 10.192.95.89.
217     // If the string length is greater than 15 characters, e.g.
218     // "10.192.95.89 xy.wildcard.example.com", it will be resolved through
219     // DNS.
220     var match = this.REs_.FIND_TRAILING_SPACE.exec(host);
221     if (match) {
222       host = match[1];
223     }
224   }
225   
226   if (!this.REs_.POSSIBLE_IP.test(host))
227     return "";
229   var parts = host.split(".");
230   if (parts.length > 4)
231     return "";
233   var allowOctal = !this.REs_.FIND_BAD_OCTAL.test(host);
235   for (var k = 0; k < parts.length; k++) {
236     var canon;
237     if (k == parts.length - 1) {
238       canon = this.canonicalNum_(parts[k], 5 - parts.length, allowOctal);
239     } else {
240       canon = this.canonicalNum_(parts[k], 1, allowOctal);
241     }
242     if (canon != "") 
243       parts[k] = canon;
244     else
245       return "";
246   }
248   return parts.join(".");
251 PROT_EnchashDecrypter.prototype.canonicalNum_ = function(num, bytes, octal) {
252   if (bytes < 0) 
253     return "";
254   var temp_num;
256   if (octal && this.REs_.IS_OCTAL.test(num)) {
258     num = this.lastNChars_(num, 11);
260     temp_num = parseInt(num, 8);
261     if (isNaN(temp_num))
262       temp_num = -1;
264   } else if (this.REs_.IS_DECIMAL.test(num)) {
266     num = this.lastNChars_(num, 32);
268     temp_num = parseInt(num, 10);
269     if (isNaN(temp_num))
270       temp_num = -1;
272   } else if (this.REs_.IS_HEX.test(num)) {
273     var matches = this.REs_.IS_HEX.exec(num);
274     if (matches) {
275       num = matches[1];
276     }
278     temp_num = parseInt(num, 16);
279     if (isNaN(temp_num))
280       temp_num = -1;
282   } else {
283     return "";
284   }
286   if (temp_num == -1) 
287     return "";
289   // Since we mod the number, we're removing the least significant bits.  We
290   // Want to push them into the front of the array to preserve the order.
291   var parts = [];
292   while (bytes--) {
293     parts.unshift("" + (temp_num % 256));
294     temp_num -= temp_num % 256;
295     temp_num /= 256;
296   }
298   return parts.join(".");
301 PROT_EnchashDecrypter.prototype.getLookupKey = function(host) {
302   var dataKey = PROT_EnchashDecrypter.DATABASE_SALT + host;
303   dataKey = Array.map(dataKey, function(c) { return c.charCodeAt(0); });
305   this.hasher_.init(G_CryptoHasher.algorithms.MD5);
306   var lookupDigest = this.hasher_.updateFromArray(dataKey);
307   var lookupKey = this.hasher_.digestHex();
309   return lookupKey.toUpperCase();
312 PROT_EnchashDecrypter.prototype.decryptData = function(data, host) {
313   var ascii = atob(data);
315   var random_salt = ascii.slice(0, PROT_EnchashDecrypter.SALT_LENGTH);
316   var encrypted_data = ascii.slice(PROT_EnchashDecrypter.SALT_LENGTH);
317   var temp_decryption_key = PROT_EnchashDecrypter.DATABASE_SALT
318       + random_salt + host;
319   this.hasher_.init(G_CryptoHasher.algorithms.MD5);
320   this.hasher_.updateFromString(temp_decryption_key);
322   var keyFactory = Cc["@mozilla.org/security/keyobjectfactory;1"]
323                    .getService(Ci.nsIKeyObjectFactory);
324   var key = keyFactory.keyFromString(Ci.nsIKeyObject.RC4,
325                                      this.hasher_.digestRaw());
327   this.streamCipher_.init(key);
328   this.streamCipher_.updateFromString(encrypted_data);
330   return this.streamCipher_.finish(false /* no base64 */);