/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; this.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); const MSG_JAR_FLUSH = "AddonJarFlush"; /** * Sends local and remote notifications to flush a JAR file cache entry * * @param aJarFile * The ZIP/XPI/JAR file as a nsIFile */ function flushJarCache(aJarFile) { Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); } /** * Parses chrome manifest files. */ this.ChromeManifestParser = { /** * Reads and parses a chrome manifest file located at a specified URI, and all * secondary manifests it references. * * @param aURI * A nsIURI pointing to a chrome manifest. * Typically a file: or jar: URI. * @return Array of objects describing each manifest instruction, in the form: * { type: instruction-type, baseURI: string-uri, args: [arguments] } **/ parseSync: function(aURI) { function parseLine(aLine) { let line = aLine.trim(); if (line.length == 0 || line.charAt(0) == '#') return; let tokens = line.split(/\s+/); let type = tokens.shift(); if (type == "manifest") { let uri = NetUtil.newURI(tokens.shift(), null, aURI); data = data.concat(this.parseSync(uri)); } else { data.push({type: type, baseURI: baseURI, args: tokens}); } } let contents = ""; try { if (aURI.scheme == "jar") contents = this._readFromJar(aURI); else contents = this._readFromFile(aURI); } catch (e) { // Silently fail. } if (!contents) return []; let baseURI = NetUtil.newURI(".", null, aURI).spec; let data = []; let lines = contents.split("\n"); lines.forEach(parseLine.bind(this)); return data; }, _readFromJar: function(aURI) { let data = ""; let entries = []; let readers = []; try { // Deconstrict URI, which can be nested jar: URIs. let uri = aURI.clone(); while (uri instanceof Ci.nsIJARURI) { entries.push(uri.JAREntry); uri = uri.JARFile; } // Open the base jar. let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); reader.open(uri.QueryInterface(Ci.nsIFileURL).file); readers.push(reader); // Open the nested jars. for (let i = entries.length - 1; i > 0; i--) { let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. createInstance(Ci.nsIZipReader); innerReader.openInner(reader, entries[i]); readers.push(innerReader); reader = innerReader; } // First entry is the actual file we want to read. let zis = reader.getInputStream(entries[0]); data = NetUtil.readInputStreamToString(zis, zis.available()); } finally { // Close readers in reverse order. for (let i = readers.length - 1; i >= 0; i--) { readers[i].close(); flushJarCache(readers[i].file); } } return data; }, _readFromFile: function(aURI) { let file = aURI.QueryInterface(Ci.nsIFileURL).file; if (!file.exists() || !file.isFile()) return ""; let data = ""; let fis = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Ci.nsIFileInputStream); try { fis.init(file, -1, -1, false); data = NetUtil.readInputStreamToString(fis, fis.available()); } finally { fis.close(); } return data; }, /** * Detects if there were any instructions of a specified type in a given * chrome manifest. * * @param aManifest * Manifest data, as returned by ChromeManifestParser.parseSync(). * @param aType * Instruction type to filter by. * @return True if any matching instructions were found in the manifest. */ hasType: function(aManifest, aType) { return aManifest.some(entry => entry.type == aType); } };