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