2
# ***** BEGIN LICENSE BLOCK *****
3
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
5
# The contents of this file are subject to the Mozilla Public License Version
6
# 1.1 (the "License"); you may not use this file except in compliance with
7
# the License. You may obtain a copy of the License at
8
# http://www.mozilla.org/MPL/
10
# Software distributed under the License is distributed on an "AS IS" basis,
11
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12
# for the specific language governing rights and limitations under the
15
# The Original Code is the Extension Manager.
17
# The Initial Developer of the Original Code is
18
# the Mozilla Foundation.
19
# Portions created by the Initial Developer are Copyright (C) 2009
20
# the Initial Developer. All Rights Reserved.
23
# Dave Townsend <dtownsend@oxymoronical.com>
24
# Chris Coulson <chris.coulson@canonical.com>
26
# Alternatively, the contents of this file may be used under the terms of
27
# either the GNU General Public License Version 2 or later (the "GPL"), or
28
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29
# in which case the provisions of the GPL or the LGPL are applicable instead
30
# of those above. If you wish to allow use of your version of this file only
31
# under the terms of either the GPL or the LGPL, and not to allow others to
32
# use your version of this file under the terms of the MPL, indicate your
33
# decision by deleting the provisions above and replace them with the notice
34
# and other provisions required by the GPL or the LGPL. If you do not delete
35
# the provisions above, a recipient may use your version of this file under
36
# the terms of any one of the MPL, the GPL or the LGPL.
38
# ***** END LICENSE BLOCK *****
41
const Cc = Components.classes;
42
const Ci = Components.interfaces;
43
const Cu = Components.utils;
44
const Cr = Components.results;
46
const nsILocalFile = Ci.nsILocalFile;
47
const nsIFile = Ci.nsIFile;
48
const nsIRDFService = Ci.nsIRDFService;
49
const nsIRDFLiteral = Ci.nsIRDFLiteral;
50
const nsIRDFResource = Ci.nsIRDFResource;
51
const nsIRDFInt = Ci.nsIRDFInt;
52
const nsIRDFXMLParser = Ci.nsIRDFXMLParser;
53
const nsIRDFDataSource = Ci.nsIRDFDataSource;
54
const nsIInputStreamChannel = Ci.nsIInputStreamChannel;
55
const nsIChannel = Ci.nsIChannel;
56
const nsIFileInputStream = Ci.nsIFileInputStream;
57
const nsIBufferedInputStream = Ci.nsIBufferedInputStream;
58
const nsIZipReader = Ci.nsIZipReader;
60
Cu.import("resource://gre/modules/Services.jsm");
61
Cu.import("resource://gre/modules/AddonManager.jsm");
62
Cu.import("resource://gre/modules/NetUtil.jsm");
64
const PREF_AI_INSTALL_CACHE = "extensions.ubufox@ubuntu.com.installCache";
65
const PREF_AI_INSTALL_DIRS = "extensions.ubufox@ubuntu.com.installDirs";
67
const FILE_INSTALL_MANIFEST = "install.rdf";
69
const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
70
const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
72
const REGEXP_XPI_FILE = /^.+\.xpi$/;
73
const REGEXP_VALID_ID = /^(\{[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/;
75
const PROP_METADATA = [ "id", "version" ];
77
var EXPORTED_SYMBOLS = [];
80
* A helpful wrapper around the prefs service that allows for default values
81
* when requested values aren't set.
84
getCharPref: function(aName, aDefaultValue) {
86
return Services.prefs.getCharPref(aName);
93
getBoolPref: function(aName, aDefaultValue) {
95
return Services.prefs.getBoolPref(aName);
103
function AddonInternal() {
106
this.__defineGetter__("gRDF", function() {
108
return this.gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
109
getService(nsIRDFService);
112
function EM_R(aProperty) {
113
return gRDF.GetResource(PREFIX_NS_EM + aProperty);
116
function getRDFValue(aLiteral) {
117
if (aLiteral instanceof nsIRDFLiteral)
118
return aLiteral.Value;
119
if (aLiteral instanceof nsIRDFResource)
120
return aLiteral.Value;
121
if (aLiteral instanceof nsIRDFInt)
122
return aLiteral.Value;
126
function getRDFProperty(aDs, aResource, aProperty) {
127
return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
130
function loadManifestFromRDF(aUri, aStream) {
131
let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
132
createInstance(nsIRDFXMLParser)
133
let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
134
createInstance(nsIRDFDataSource);
135
let listener = rdfParser.parseAsync(ds, aUri);
136
let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
137
createInstance(nsIInputStreamChannel);
138
channel.setURI(aUri);
139
channel.contentStream = aStream;
140
channel.QueryInterface(nsIChannel);
141
channel.contentType = "text/xml";
143
listener.onStartRequest(channel, null);
147
let count = aStream.available();
149
listener.onDataAvailable(channel, null, aStream, pos, count);
151
count = aStream.available();
153
listener.onStopRequest(channel, null, Cr.NS_OK);
156
listener.onStopRequest(channel, null, e.result);
160
let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
161
let addon = new AddonInternal();
162
PROP_METADATA.forEach(function(aProp) {
163
addon[aProp] = getRDFProperty(ds, root, aProp);
169
function loadManifestFromDir(aDir) {
170
let file = aDir.clone();
171
file.append(FILE_INSTALL_MANIFEST);
172
if (!file.exists() || !file.isFile())
173
throw new Error("Directory " + aDir.path + " does not contain a valid " +
176
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
177
createInstance(nsIFileInputStream);
178
fis.init(file, -1, -1, false);
179
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
180
createInstance(nsIBufferedInputStream);
184
let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
193
function loadManifestFromZipReader(aZipReader) {
194
let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
195
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
196
createInstance(nsIBufferedInputStream);
200
let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST);
201
let addon = loadManifestFromRDF(uri, bis);
211
function loadManifestFromZipFile(aXPIFile) {
212
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
213
createInstance(nsIZipReader);
215
zipReader.open(aXPIFile);
217
return loadManifestFromZipReader(zipReader);
224
function loadManifestFromFile(aFile) {
226
return loadManifestFromZipFile(aFile);
228
return loadManifestFromDir(aFile);
231
function buildJarURI(aJarfile, aPath) {
232
let uri = Services.io.newFileURI(aJarfile);
233
uri = "jar:" + uri.spec + "!/" + aPath;
234
return NetUtil.newURI(uri);
237
function DirectoryEntry(file, dirCache) {
239
this.dirCache = dirCache;
242
var uAddonInstaller = {
245
startup: function() {
247
this.installCache = JSON.parse(Prefs.getCharPref(PREF_AI_INSTALL_CACHE,
250
Cu.reportError("installCache failed to load: " + e);
254
if (this.installCache == null)
255
this.installCache = [];
258
let dirs = Prefs.getCharPref(PREF_AI_INSTALL_DIRS,
259
"/usr/share/ubufox/extensions").split(",");
260
dirs.forEach(function(aDir) {
262
let dirFile = Cc["@mozilla.org/file/local;1"].createInstance(nsILocalFile);
263
dirFile.initWithPath(aDir);
266
uAddonInstaller.installCache.forEach(function(aCache) {
268
if (aCache.location == aDir)
271
Cu.reportError("Entry in installCache missing location");
272
uAddonInstaller.installCache.splice(uAddonInstaller.installCache.indexOf(aCache), 1);
276
if (cache && !("mtime" in cache && "addons" in cache)) {
277
Cu.reportError("Entry in installCache for " + aDir + " is missing mtime or addons array");
278
uAddonInstaller.installCache.splice(uAddonInstaller.installCache.indexOf(cache), 1);
282
if (dirFile.exists() && dirFile.isDirectory() &&
283
(!cache || dirFile.lastModifiedTime != cache.mtime)) {
285
cache = JSON.parse("{\"location\": \"" + aDir + "\", \"addons\": []}");
286
uAddonInstaller.installCache.push(cache);
288
cache.mtime = dirFile.lastModifiedTime;
289
uAddonInstaller.processDir(dirFile, cache);
292
Cu.reportError("Failed to process directory " + aDir);
300
Services.prefs.setCharPref(PREF_AI_INSTALL_CACHE,
301
JSON.stringify(this.installCache));
304
processDir: function(aDir, aCache) {
305
let entries = aDir.directoryEntries;
306
while (entries.hasMoreElements())
307
this.processFile(entries.getNext().QueryInterface(nsIFile), aCache);
310
processFile: function(aFile, aCache) {
311
var id = aFile.leafName;
313
if (aFile.isFile()) {
314
if (REGEXP_XPI_FILE.test(id))
315
id = id.substring(0, (id.length - 4));
318
} else if (!aFile.isDirectory()) {
322
if (!REGEXP_VALID_ID.test(id))
325
var cachedAddon = null;
326
var seenBefore = false;
327
aCache.addons.forEach(function(aAddon) {
330
cachedAddon = aAddon;
332
Cu.reportError("Addon in cache without ID");
333
aCache.addons.splice(aCache.addons.indexOf(aAddon), 1);
340
if (cachedAddon && !("mtime" in cachedAddon)) {
341
Cu.reportError("Addon entry for " + id + " in installCache is missing mtime");
342
aCache.addons.splice(aCache.addons.indexOf(cachedAddon), 1);
346
if (cachedAddon && aFile.lastModifiedTime == cachedAddon.mtime)
350
cachedAddon = JSON.parse("{\"id\": \"" + id + "\"}");
351
aCache.addons.push(cachedAddon);
354
cachedAddon.mtime = aFile.lastModifiedTime;
358
addon = loadManifestFromFile(aFile);
360
Cu.reportError("Failed to load extension manifest: " + e);
367
AddonManager.getAddonByID(id, function(aAddon) {
368
let shouldInstall = false;
370
if ((Services.vc.compare(aAddon.version, addon.version) < 0) &&
371
(aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)) {
372
shouldInstall = true;
374
} else if (!seenBefore) {
375
shouldInstall = true;
381
AddonManager.getInstallForFile(aFile, function(aInstall) {
383
aInstall.addListener(uAddonInstaller);
390
onInstallEnded: function(aInstall, aAddon) {
391
aInstall.removeListener(this);
393
onUpdateAvailable: function(aAddon, aInstall) {
397
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
400
onInstallCancelled: function(aInstall) {
401
aInstall.removeListener(this);
404
onInstallFailed: function(aInstall) {
405
aInstall.removeListener(this);
409
uAddonInstaller.startup();