|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 this.EXPORTED_SYMBOLS = ["ChromeManifestParser"]; |
|
8 |
|
9 const Cc = Components.classes; |
|
10 const Ci = Components.interfaces; |
|
11 const Cr = Components.results; |
|
12 const Cu = Components.utils; |
|
13 |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 Cu.import("resource://gre/modules/NetUtil.jsm"); |
|
16 |
|
17 const MSG_JAR_FLUSH = "AddonJarFlush"; |
|
18 |
|
19 |
|
20 /** |
|
21 * Sends local and remote notifications to flush a JAR file cache entry |
|
22 * |
|
23 * @param aJarFile |
|
24 * The ZIP/XPI/JAR file as a nsIFile |
|
25 */ |
|
26 function flushJarCache(aJarFile) { |
|
27 Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null); |
|
28 Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster) |
|
29 .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path); |
|
30 } |
|
31 |
|
32 |
|
33 /** |
|
34 * Parses chrome manifest files. |
|
35 */ |
|
36 this.ChromeManifestParser = { |
|
37 |
|
38 /** |
|
39 * Reads and parses a chrome manifest file located at a specified URI, and all |
|
40 * secondary manifests it references. |
|
41 * |
|
42 * @param aURI |
|
43 * A nsIURI pointing to a chrome manifest. |
|
44 * Typically a file: or jar: URI. |
|
45 * @return Array of objects describing each manifest instruction, in the form: |
|
46 * { type: instruction-type, baseURI: string-uri, args: [arguments] } |
|
47 **/ |
|
48 parseSync: function CMP_parseSync(aURI) { |
|
49 function parseLine(aLine) { |
|
50 let line = aLine.trim(); |
|
51 if (line.length == 0 || line.charAt(0) == '#') |
|
52 return; |
|
53 let tokens = line.split(/\s+/); |
|
54 let type = tokens.shift(); |
|
55 if (type == "manifest") { |
|
56 let uri = NetUtil.newURI(tokens.shift(), null, aURI); |
|
57 data = data.concat(this.parseSync(uri)); |
|
58 } else { |
|
59 data.push({type: type, baseURI: baseURI, args: tokens}); |
|
60 } |
|
61 } |
|
62 |
|
63 let contents = ""; |
|
64 try { |
|
65 if (aURI.scheme == "jar") |
|
66 contents = this._readFromJar(aURI); |
|
67 else |
|
68 contents = this._readFromFile(aURI); |
|
69 } catch (e) { |
|
70 // Silently fail. |
|
71 } |
|
72 |
|
73 if (!contents) |
|
74 return []; |
|
75 |
|
76 let baseURI = NetUtil.newURI(".", null, aURI).spec; |
|
77 |
|
78 let data = []; |
|
79 let lines = contents.split("\n"); |
|
80 lines.forEach(parseLine.bind(this)); |
|
81 return data; |
|
82 }, |
|
83 |
|
84 _readFromJar: function CMP_readFromJar(aURI) { |
|
85 let data = ""; |
|
86 let entries = []; |
|
87 let readers = []; |
|
88 |
|
89 try { |
|
90 // Deconstrict URI, which can be nested jar: URIs. |
|
91 let uri = aURI.clone(); |
|
92 while (uri instanceof Ci.nsIJARURI) { |
|
93 entries.push(uri.JAREntry); |
|
94 uri = uri.JARFile; |
|
95 } |
|
96 |
|
97 // Open the base jar. |
|
98 let reader = Cc["@mozilla.org/libjar/zip-reader;1"]. |
|
99 createInstance(Ci.nsIZipReader); |
|
100 reader.open(uri.QueryInterface(Ci.nsIFileURL).file); |
|
101 readers.push(reader); |
|
102 |
|
103 // Open the nested jars. |
|
104 for (let i = entries.length - 1; i > 0; i--) { |
|
105 let innerReader = Cc["@mozilla.org/libjar/zip-reader;1"]. |
|
106 createInstance(Ci.nsIZipReader); |
|
107 innerReader.openInner(reader, entries[i]); |
|
108 readers.push(innerReader); |
|
109 reader = innerReader; |
|
110 } |
|
111 |
|
112 // First entry is the actual file we want to read. |
|
113 let zis = reader.getInputStream(entries[0]); |
|
114 data = NetUtil.readInputStreamToString(zis, zis.available()); |
|
115 } |
|
116 finally { |
|
117 // Close readers in reverse order. |
|
118 for (let i = readers.length - 1; i >= 0; i--) { |
|
119 readers[i].close(); |
|
120 flushJarCache(readers[i].file); |
|
121 } |
|
122 } |
|
123 |
|
124 return data; |
|
125 }, |
|
126 |
|
127 _readFromFile: function CMP_readFromFile(aURI) { |
|
128 let file = aURI.QueryInterface(Ci.nsIFileURL).file; |
|
129 if (!file.exists() || !file.isFile()) |
|
130 return ""; |
|
131 |
|
132 let data = ""; |
|
133 let fis = Cc["@mozilla.org/network/file-input-stream;1"]. |
|
134 createInstance(Ci.nsIFileInputStream); |
|
135 try { |
|
136 fis.init(file, -1, -1, false); |
|
137 data = NetUtil.readInputStreamToString(fis, fis.available()); |
|
138 } finally { |
|
139 fis.close(); |
|
140 } |
|
141 return data; |
|
142 }, |
|
143 |
|
144 /** |
|
145 * Detects if there were any instructions of a specified type in a given |
|
146 * chrome manifest. |
|
147 * |
|
148 * @param aManifest |
|
149 * Manifest data, as returned by ChromeManifestParser.parseSync(). |
|
150 * @param aType |
|
151 * Instruction type to filter by. |
|
152 * @return True if any matching instructions were found in the manifest. |
|
153 */ |
|
154 hasType: function CMP_hasType(aManifest, aType) { |
|
155 return aManifest.some(function hasType_matchEntryType(aEntry) { |
|
156 return aEntry.type == aType; |
|
157 }); |
|
158 } |
|
159 }; |