/* ***** 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 Test Pilot. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Jono X * * 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 ***** */ function JarStore() { try { let baseDirName = "TestPilotExperimentFiles"; // this should go in pref? this._baseDir = null; this._localOverrides = {}; //override with code for debugging purposes this._index = {}; // tells us which jar file to look in for each module this._lastModified = {}; // tells us when each jar file was last modified this._init( baseDirName ); } catch (e) { console.warn("Error instantiating jar store: " + e); } } JarStore.prototype = { _init: function( baseDirectory ) { let prefs = require("preferences-service"); this._localOverrides = JSON.parse( prefs.get("extensions.testpilot.codeOverride", "{}")); let dir = Cc["@mozilla.org/file/directory_service;1"]. getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile); dir.append(baseDirectory); this._baseDir = dir; if( !this._baseDir.exists() || !this._baseDir.isDirectory() ) { // if jar storage directory doesn't exist, create it: console.info("Creating: " + this._baseDir.path + "\n"); this._baseDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0777); } else { // Process any jar files already on disk from previous runs: // Build lookup index of module->jar file and modified dates let jarFiles = this._baseDir.directoryEntries; while(jarFiles.hasMoreElements()) { let jarFile = jarFiles.getNext().QueryInterface(Ci.nsIFile); // Make sure this is actually a jar file: if (jarFile.leafName.indexOf(".jar") != jarFile.leafName.length - 4) { continue; } this._lastModified[jarFile.leafName] = jarFile.lastModifiedTime; this._indexJar(jarFile); } } }, _indexJar: function(jarFile) { let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] .createInstance(Ci.nsIZipReader); zipReader.open(jarFile); // must already be nsIFile let entries = zipReader.findEntries(null); while(entries.hasMore()) { // Find all .js files inside jar file: let entry = entries.getNext(); if (entry.indexOf(".js") == entry.length - 3) { // add entry to index let moduleName = entry.slice(0, entry.length - 3); this._index[moduleName] = jarFile.path; } } zipReader.close(); }, _verifyJar: function(jarFile, expectedHash) { // Compare the jar file's hash to the expected hash from the // index file. // from https://developer.mozilla.org/en/nsICryptoHash#Computing_the_Hash_of_a_File console.info("Attempting to verify jarfile vs hash = " + expectedHash); let istream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); // open for reading istream.init(jarFile, 0x01, 0444, 0); let ch = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); // Use SHA256, it's more secure than MD5: ch.init(ch.SHA256); // this tells updateFromStream to read the entire file const PR_UINT32_MAX = 0xffffffff; ch.updateFromStream(istream, PR_UINT32_MAX); // pass false here to get binary data back let hash = ch.finish(false); // return the two-digit hexadecimal code for a byte function toHexString(charCode) { return ("0" + charCode.toString(16)).slice(-2); } // convert the binary hash data to a hex string. let s = [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); // s now contains your hash in hex return (s == expectedHash); }, saveJarFile: function( filename, rawData, expectedHash ) { console.info("Saving a JAR file as " + filename + " hash = " + expectedHash); // rawData is a string of binary data representing a jar file let jarFile; try { jarFile = this._baseDir.clone(); // filename may have directories in it; use just the last part jarFile.append(filename.split("/").pop()); // If a file of that name already exists, remove it! if (jarFile.exists()) { jarFile.remove(false); } // From https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Getting_special_files jarFile.create( Ci.nsIFile.NORMAL_FILE_TYPE, 0600); let stream = Cc["@mozilla.org/network/safe-file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); stream.init(jarFile, 0x04 | 0x08 | 0x20, 0600, 0); // readwrite, create, truncate stream.write(rawData, rawData.length); if (stream instanceof Ci.nsISafeOutputStream) { stream.finish(); } else { stream.close(); } // Verify hash; if it's good, index and set last modified time. // If not good, remove it. if (this._verifyJar(jarFile, expectedHash)) { this._indexJar(jarFile); this._lastModified[jarFile.leafName] = jarFile.lastModifiedTime; } else { console.warn("Bad JAR file, doesn't match hash: " + expectedHash); jarFile.remove(false); } } catch(e) { console.warn("Error in saving jar file: " + e); // Remove any partially saved file if (jarFile.exists()) { jarFile.remove(false); } } }, resolveModule: function(root, path) { // Root will be null if require() was done by absolute path. if (root != null) { // TODO I don't think we need to do anything special here. } // drop ".js" off end of path to get module let module; if (path.indexOf(".js") == path.length - 3) { module = path.slice(0, path.length - 3); } else { module = path; } if (this._index[module]) { let resolvedPath = this._index[module] + "!" + module + ".js"; return resolvedPath; } return null; // must return a path... which gets passed to getFile. }, getFile: function(path) { // used externally by cuddlefish; takes the path and returns // {contents: data}. if (this._localOverrides[path]) { let code = this._localOverrides[path]; return {contents: code}; } let parts = path.split("!"); let filePath = parts[0]; let entryName = parts[1]; let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(filePath); return this._readEntryFromJarFile(file, entryName); }, _readEntryFromJarFile: function(jarFile, entryName) { // Reads out content of entry, without unzipping jar file to disk. // Open the jar file let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] .createInstance(Ci.nsIZipReader); zipReader.open(jarFile); // must already be nsIFile let rawStream = zipReader.getInputStream(entryName); let stream = Cc["@mozilla.org/scriptableinputstream;1"]. createInstance(Ci.nsIScriptableInputStream); stream.init(rawStream); try { let data = new String(); let chunk = {}; do { chunk = stream.read(-1); data += chunk; } while (chunk.length > 0); return {contents: data}; } catch(e) { console.warn("Error reading entry from jar file: " + e ); } return null; }, getFileModifiedDate: function(filename) { // used by remote experiment loader to know whether we have to redownload // a thing or not. filename = filename.split("/").pop(); if (this._lastModified[filename]) { return (this._lastModified[filename]); } else { return 0; } }, listAllFiles: function() { // used by remote experiment loader let x; let list = [x for (x in this._index)]; return list; }, setLocalOverride: function(path, code) { let prefs = require("preferences-service"); this._localOverrides[path] = code; prefs.set("extensions.testpilot.codeOverride", JSON.stringify(this._localOverrides)); } }; exports.JarStore = JarStore;