Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / toolkit / mozapps / update / src / nsPostUpdateWin.js
blobd68c181fbfbb6792a11edec49394b00eae632317
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
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/
9  *
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
13  * License.
14  *
15  * The Original Code is the Update Service.
16  *
17  * The Initial Developer of the Original Code is Google Inc.
18  * Portions created by the Initial Developer are Copyright (C) 2005
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *  Darin Fisher <darin@meer.net> (original author)
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
38 /**
39  * This file contains an implementation of nsIRunnable, which may be invoked
40  * to perform post-update modifications to the windows registry and uninstall
41  * logs required to complete an update of the application.  This code is very
42  * specific to the xpinstall wizard for windows.
43  */
45 const URI_BRAND_PROPERTIES     = "chrome://branding/locale/brand.properties";
47 const KEY_APPDIR          = "XCurProcD";
48 const KEY_TMPDIR          = "TmpD";
49 const KEY_UPDROOT         = "UpdRootD";
50 const KEY_UAPPDATA        = "UAppData";
52 // see prio.h
53 const PR_RDONLY      = 0x01;
54 const PR_WRONLY      = 0x02;
55 const PR_APPEND      = 0x10;
57 const PERMS_FILE     = 0644;
58 const PERMS_DIR      = 0700;
60 const nsIWindowsRegKey = Components.interfaces.nsIWindowsRegKey;
62 var gConsole = null;
63 var gAppUpdateLogPostUpdate = false;
65 //-----------------------------------------------------------------------------
67 /**
68  * Console logging support
69  */
70 function LOG(s) {
71   if (gAppUpdateLogPostUpdate) {
72     dump("*** PostUpdateWin: " + s + "\n");
73     gConsole.logStringMessage(s);
74   }
77 /**
78  * This function queries the XPCOM directory service.
79  */
80 function getFile(key) {
81   var dirSvc =
82       Components.classes["@mozilla.org/file/directory_service;1"].
83       getService(Components.interfaces.nsIProperties);
84   return dirSvc.get(key, Components.interfaces.nsIFile);
87 /**
88  * Creates a new file object given a native file path.
89  * @param   path
90  *          The native file path.
91  * @return  nsILocalFile object for the given native file path.
92  */
93 function newFile(path) {
94   var file = Components.classes["@mozilla.org/file/local;1"]
95                        .createInstance(Components.interfaces.nsILocalFile);
96   file.initWithPath(path);
97   return file;
101  * This function returns a file input stream.
102  */
103 function openFileInputStream(file) {
104   var stream =
105       Components.classes["@mozilla.org/network/file-input-stream;1"].
106       createInstance(Components.interfaces.nsIFileInputStream);
107   stream.init(file, PR_RDONLY, 0, 0);
108   return stream;
112  * This function returns a file output stream.
113  */
114 function openFileOutputStream(file, flags) {
115   var stream =
116       Components.classes["@mozilla.org/network/file-output-stream;1"].
117       createInstance(Components.interfaces.nsIFileOutputStream);
118   stream.init(file, flags, 0644, 0);
119   return stream;
122 //-----------------------------------------------------------------------------
124 const PREFIX_FILE = "File: ";
126 function InstallLogWriter() {
128 InstallLogWriter.prototype = {
129   _outputStream: null,  // nsIOutputStream to the install wizard log file
131   /**
132    * Write a single line to the output stream.
133    */
134   _writeLine: function(s) {
135     s = s + "\r\n";
136     this._outputStream.write(s, s.length);
137   },
139   /**
140    * This function creates an empty uninstall update log file if it doesn't
141    * exist and returns a reference to the resulting nsIFile.
142    */
143   _getUninstallLogFile: function() {
144     var file = getFile(KEY_APPDIR); 
145     file.append("uninstall");
146     if (!file.exists())
147       return null;
149     file.append("uninstall.log");
150     if (!file.exists())
151       file.create(Components.interfaces.nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
153     return file;
154   },
156   /**
157    * Return the update.log file.  Use last-update.log file in case the
158    * updates/0 directory has already been cleaned out (see bug 311302).
159    */
160   _getUpdateLogFile: function() {
161     function appendUpdateLogPath(root) {
162       var file = root.clone();
163       file.append("updates");
164       file.append("0");
165       file.append("update.log");
166       if (file.exists())
167         return file;
169       file = root; 
170       file.append("updates");
171       file.append("last-update.log");
172       if (file.exists())
173         return file;
175       return null;
176     }
178     // See the local appdata first if app dir is under Program Files.
179     var file = null;
180     var updRoot;
181     try {
182       updRoot = getFile(KEY_UPDROOT);
183     } catch (e) {
184     }
185     if (updRoot) {
186       file = appendUpdateLogPath(updRoot);
188       // When updating from Fx 2.0.0.1 to 2.0.0.3 (or later) on Vista,
189       // we will have to see also user app data (see bug 351949).
190       if (!file)
191         file = appendUpdateLogPath(getFile(KEY_UAPPDATA));
192     }
194     // See the app dir if not found or app dir is out of Program Files.
195     if (!file)
196       file = appendUpdateLogPath(getFile(KEY_APPDIR));
198     return file;
199   },
201   /**
202    * Read update.log to extract information about files that were
203    * newly added for this update.
204    */
205   _readUpdateLog: function(logFile, entries) {
206     var stream;
207     try {
208       stream = openFileInputStream(logFile).
209           QueryInterface(Components.interfaces.nsILineInputStream);
211       var line = {};
212       while (stream.readLine(line)) {
213         var data = line.value.split(" ");
214         if (data[0] == "EXECUTE" && data[1] == "ADD") {
215           // The uninstaller requires the path separator to be "\" and
216           // relative paths to start with a "\".
217           var relPath = "\\" + data[2].replace(/\//g, "\\");
218           entries[relPath] = null;
219         }
220       }
221     } finally {
222       if (stream)
223         stream.close();
224     }
225   },
227   /**
228    * Read install_wizard log files to extract information about files that were
229    * previously added by the xpinstall installer and software update.
230    */
231   _readXPInstallLog: function(logFile, entries) {
232     var stream;
233     try {
234       stream = openFileInputStream(logFile).
235           QueryInterface(Components.interfaces.nsILineInputStream);
237       function fixPath(path, offset) {
238         return path.substr(offset).replace(appDirPath, "");
239       }
241       var appDir = getFile(KEY_APPDIR);
242       var appDirPath = appDir.path;
243       var line = {};
244       while (stream.readLine(line)) {
245         var entry = line.value;
246         // This works with both the entries from xpinstall (e.g. Installing: )
247         // and from update (e.g. installing: )
248         var searchStr = "nstalling: ";
249         var index = entry.indexOf(searchStr);
250         if (index != -1) {
251           entries[fixPath(entry, index + searchStr.length)] = null;
252           continue;
253         }
255         searchStr = "Replacing: ";
256         index = entry.indexOf(searchStr);
257         if (index != -1) {
258           entries[fixPath(entry, index + searchStr.length)] = null;
259           continue;
260         }
262         searchStr = "Windows Shortcut: ";
263         index = entry.indexOf(searchStr);
264         if (index != -1) {
265           entries[fixPath(entry + ".lnk", index + searchStr.length)] = null;
266           continue;
267         }
268       }
269     } finally {
270       if (stream)
271         stream.close();
272     }
273   },
275   _readUninstallLog: function(logFile, entries) {
276     var stream;
277     try {
278       stream = openFileInputStream(logFile).
279           QueryInterface(Components.interfaces.nsILineInputStream);
281       var line = {};
282       var searchStr = "File: ";
283       while (stream.readLine(line)) {
284         var index = line.value.indexOf(searchStr);
285         if (index != -1) {
286           var str = line.value.substr(index + searchStr.length);
287           entries.push(str);
288         }
289       }
290     } finally {
291       if (stream)
292         stream.close();
293     }
294   },
296   /**
297    * This function initializes the log writer and is responsible for
298    * translating 'update.log' and the 'install_wizard' logs to the NSIS format.
299    */
300   begin: function() {
301     var updateLog = this._getUpdateLogFile();
302     if (!updateLog)
303       return;
305     var newEntries = { };
306     this._readUpdateLog(updateLog, newEntries);
308     try {
309       const nsIDirectoryEnumerator = Components.interfaces.nsIDirectoryEnumerator;
310       const nsILocalFile = Components.interfaces.nsILocalFile;
311       var prefixWizLog = "install_wizard";
312       var uninstallDir = getFile(KEY_APPDIR); 
313       uninstallDir.append("uninstall");
314       var entries = uninstallDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
315       while (true) {
316         var wizLog = entries.nextFile;
317         if (!wizLog)
318           break;
319         if (wizLog instanceof nsILocalFile && !wizLog.isDirectory() &&
320             wizLog.leafName.indexOf(prefixWizLog) == 0) {
321           this._readXPInstallLog(wizLog, newEntries);
322           wizLog.remove(false);
323         }
324       }
325     }
326     catch (e) {}
327     if (entries)
328       entries.close();
330     var uninstallLog = this._getUninstallLogFile();
331     var oldEntries = [];
332     this._readUninstallLog(uninstallLog, oldEntries);
334     // Prevent writing duplicate entries in the log file
335     for (var relPath in newEntries) {
336       if (oldEntries.indexOf(relPath) != -1)
337         delete newEntries[relPath];
338     }
340     if (newEntries.length == 0)
341       return;
343     // since we are not running with elevated privs, we can't write out
344     // the log file (at least, not on Vista).  So, write the output to
345     // temp, and then later, we'll pass the file (gCopiedLog) to
346     // the post update clean up process, which can copy it to
347     // the desired location (because it will have elevated privs)
348     gCopiedLog = getFile(KEY_TMPDIR);
349     gCopiedLog.append("uninstall");
350     gCopiedLog.createUnique(gCopiedLog.DIRECTORY_TYPE, PERMS_DIR);
351     if (uninstallLog)
352       uninstallLog.copyTo(gCopiedLog, "uninstall.log");
353     gCopiedLog.append("uninstall.log");
354     
355     LOG("uninstallLog = " + uninstallLog.path);
356     LOG("copiedLog = " + gCopiedLog.path);
357     
358     if (!gCopiedLog.exists())
359       gCopiedLog.create(Components.interfaces.nsILocalFile.NORMAL_FILE_TYPE, 
360                         PERMS_FILE);
361       
362     this._outputStream =
363         openFileOutputStream(gCopiedLog, PR_WRONLY | PR_APPEND);
365     // The NSIS uninstaller deletes all directories where the installer has
366     // added a file if the directory is empty after the files have been removed
367     // so there is no need to log directories.
368     for (var relPath in newEntries)
369       this._writeLine(PREFIX_FILE + relPath);
370   },
372   end: function() {
373     if (!this._outputStream)
374       return;
375     this._outputStream.close();
376     this._outputStream = null;
377   }
380 var installLogWriter;
381 var gCopiedLog;
383 //-----------------------------------------------------------------------------
386  * A thin wrapper around nsIWindowsRegKey
387  * note, only the "read" methods are exposed.  If you want to write
388  * to the registry on Vista, you need to be a priveleged app.
389  * We've moved that code into the uninstaller.
390  */
391 function RegKey() {
392   // Internally, we may pass parameters to this constructor.
393   if (arguments.length == 3) {
394     this._key = arguments[0];
395     this._root = arguments[1];
396     this._path = arguments[2];
397   } else {
398     this._key =
399         Components.classes["@mozilla.org/windows-registry-key;1"].
400         createInstance(nsIWindowsRegKey);
401   }
403 RegKey.prototype = {
404   _key: null,
405   _root: null,
406   _path: null,
408   ACCESS_READ:  nsIWindowsRegKey.ACCESS_READ,
410   ROOT_KEY_CURRENT_USER: nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
411   ROOT_KEY_LOCAL_MACHINE: nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
412   ROOT_KEY_CLASSES_ROOT: nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT,
413   
414   close: function() {
415     this._key.close();
416     this._root = null;
417     this._path = null;
418   },
420   open: function(rootKey, path, mode) {
421     this._key.open(rootKey, path, mode);
422     this._root = rootKey;
423     this._path = path;
424   },
426   openChild: function(path, mode) {
427     var child = this._key.openChild(path, mode);
428     return new RegKey(child, this._root, this._path + "\\" + path);
429   },
431   readStringValue: function(name) {
432     return this._key.readStringValue(name);
433   },
435   hasValue: function(name) {
436     return this._key.hasValue(name);
437   },
439   hasChild: function(name) {
440     return this._key.hasChild(name);
441   },
443   get childCount() {
444     return this._key.childCount;
445   },
447   getChildName: function(index) {
448     return this._key.getChildName(index);
449   },
451   toString: function() {
452     var root;
453     switch (this._root) {
454     case this.ROOT_KEY_CLASSES_ROOT:
455       root = "HKEY_KEY_CLASSES_ROOT";
456       break;
457     case this.ROOT_KEY_LOCAL_MACHINE:
458       root = "HKEY_LOCAL_MACHINE";
459       break;
460     case this.ROOT_KEY_CURRENT_USER:
461       root = "HKEY_CURRENT_USER";
462       break;
463     default:
464       LOG("unknown root key");
465       return "";
466     }
467     return root + "\\" + this._path;
468   }
472  * This method walks the registry looking for the registry keys of
473  * the previous version of the application.
474  */
475 function haveOldInstall(key, brandFullName, version) {
476   var ourInstallDir = getFile(KEY_APPDIR);
477   var result = false;
478   var childKey, productKey, mainKey;
479   try {
480     for (var i = 0; i < key.childCount; ++i) {
481       var childName = key.getChildName(i);
482       childKey = key.openChild(childName, key.ACCESS_READ);
483       if (childKey.hasValue("CurrentVersion")) {
484         for (var j = 0; j < childKey.childCount; ++j) {
485           var productVer = childKey.getChildName(j); 
486           productKey = childKey.openChild(productVer, key.ACCESS_READ);
487           if (productKey.hasChild("Main")) {
488             mainKey = productKey.openChild("Main", key.ACCESS_READ);
489             var installDir = mainKey.readStringValue("Install Directory");
490             mainKey.close();
491             LOG("old install? " + installDir + " vs " + ourInstallDir.path);
492             LOG("old install? " + childName + " vs " + brandFullName);
493             LOG("old install? " + productVer.split(" ")[0] + " vs " + version);
494             if (newFile(installDir).equals(ourInstallDir) &&
495                 (childName != brandFullName ||
496                 productVer.split(" ")[0] != version)) {
497               result = true;
498             }
499           }
500           productKey.close();
501           if (result)
502             break;
503         }
504       }
505       childKey.close();
506       if (result)
507         break;
508     }
509   } catch (e) {
510     result = false;
511     if (childKey)
512       childKey.close();
513     if (productKey)
514       productKey.close();
515     if (mainKey)
516       mainKey.close();
517   }
518   return result;
521 function checkRegistry()
523   LOG("checkRegistry");
525   var result = false;
526   
527   // Firefox is the only toolkit app that needs to do this. 
528   // return false for other applications.
529   var app = Components.classes["@mozilla.org/xre/app-info;1"].
530             getService(Components.interfaces.nsIXULAppInfo);
531   if (app.name == "Firefox") {          
532     try {
533       var key = new RegKey();
534       key.open(RegKey.prototype.ROOT_KEY_CLASSES_ROOT, "FirefoxHTML\\shell\\open\\command", key.ACCESS_READ);
535       var commandKey = key.readStringValue("");
536       LOG("commandKey = " + commandKey);
537       // if "-requestPending" is not found, we need to do the cleanup
538       result = (commandKey.indexOf("-requestPending") == -1);
539     } catch (e) {
540       LOG("failed to open command key for FirefoxHTML: " + e);
541     }
542     key.close();
543   }
544   return result;
547 function checkOldInstall(rootKey, vendorShortName, brandFullName, version)
549   var key = new RegKey();
550   var result = false;
552   try {
553     key.open(rootKey, "SOFTWARE\\" + vendorShortName, key.ACCESS_READ);
554     LOG("checkOldInstall: " + key + " " + brandFullName + " " + version);
555     result = haveOldInstall(key, brandFullName, version);
556   } catch (e) {
557     LOG("failed trying to find old install: " + e);
558   }
559   key.close();
560   return result;
563 //-----------------------------------------------------------------------------
565 function nsPostUpdateWin() {
566   gConsole = Components.classes["@mozilla.org/consoleservice;1"]
567                        .getService(Components.interfaces.nsIConsoleService);
568   var prefs = Components.classes["@mozilla.org/preferences-service;1"].
569               getService(Components.interfaces.nsIPrefBranch);
570   try {
571     gAppUpdateLogPostUpdate = prefs.getBoolPref("app.update.log.all");
572   }
573   catch (ex) {
574   }
575   try {
576     if (!gAppUpdateLogPostUpdate) 
577       gAppUpdateLogPostUpdate = prefs.getBoolPref("app.update.log.PostUpdate");
578   }
579   catch (ex) {
580   }
583 nsPostUpdateWin.prototype = {
584   QueryInterface: function(iid) {
585     if (iid.equals(Components.interfaces.nsIRunnable) ||
586         iid.equals(Components.interfaces.nsISupports))
587       return this;
588     throw Components.results.NS_ERROR_NO_INTERFACE;
589   },
591   run: function() {
592     // When uninstall/uninstall.update exists the uninstaller has already
593     // updated the uninstall.log with the files added by software update.
594     var updateUninstallFile = getFile(KEY_APPDIR); 
595     updateUninstallFile.append("uninstall");
596     updateUninstallFile.append("uninstall.update");
597     if (updateUninstallFile.exists()) {
598       LOG("nothing to do, uninstall.log has already been updated"); 
599       return;
600     }
602     try {
603       installLogWriter = new InstallLogWriter();
604       try {
605         installLogWriter.begin();
606       } finally {
607         installLogWriter.end();
608         installLogWriter = null;
609       }
610     } catch (e) {
611       LOG(e);
612     } 
613     
614     var app =
615       Components.classes["@mozilla.org/xre/app-info;1"].
616         getService(Components.interfaces.nsIXULAppInfo).
617         QueryInterface(Components.interfaces.nsIXULRuntime);
619     var sbs =
620       Components.classes["@mozilla.org/intl/stringbundle;1"].
621       getService(Components.interfaces.nsIStringBundleService);
622     var brandBundle = sbs.createBundle(URI_BRAND_PROPERTIES);
624     var vendorShortName = brandBundle.GetStringFromName("vendorShortName");
625     var brandFullName = brandBundle.GetStringFromName("brandFullName");
627     if (!gCopiedLog && 
628         !checkRegistry() &&
629         !checkOldInstall(RegKey.prototype.ROOT_KEY_LOCAL_MACHINE, 
630                          vendorShortName, brandFullName, app.version) &&
631         !checkOldInstall(RegKey.prototype.ROOT_KEY_CURRENT_USER, 
632                          vendorShortName, brandFullName, app.version)) {
633       LOG("nothing to do, so don't launch the helper");
634       return;
635     }
637     try {
638       var winAppHelper = 
639         app.QueryInterface(Components.interfaces.nsIWinAppHelper);
641       // note, gCopiedLog could be null
642       if (gCopiedLog)
643         LOG("calling postUpdate with: " + gCopiedLog.path);
644       else
645         LOG("calling postUpdate without a log");
647       winAppHelper.postUpdate(gCopiedLog);
648     } catch (e) {
649       LOG("failed to launch the helper to do the post update cleanup: " + e); 
650     }
651   }
654 //-----------------------------------------------------------------------------
656 var gModule = {
657   registerSelf: function(compMgr, fileSpec, location, type) {
658     compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
659     
660     for (var key in this._objects) {
661       var obj = this._objects[key];
662       compMgr.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
663                                       fileSpec, location, type);
664     }
665   },
666   
667   getClassObject: function(compMgr, cid, iid) {
668     if (!iid.equals(Components.interfaces.nsIFactory))
669       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
671     for (var key in this._objects) {
672       if (cid.equals(this._objects[key].CID))
673         return this._objects[key].factory;
674     }
675     
676     throw Components.results.NS_ERROR_NO_INTERFACE;
677   },
678   
679   _makeFactory: #1= function(ctor) {
680     function ci(outer, iid) {
681       if (outer != null)
682         throw Components.results.NS_ERROR_NO_AGGREGATION;
683       return (new ctor()).QueryInterface(iid);
684     } 
685     return { createInstance: ci };
686   },
687   
688   _objects: {
689     manager: { CID        : Components.ID("{d15b970b-5472-40df-97e8-eb03a04baa82}"),
690                contractID : "@mozilla.org/updates/post-update;1",
691                className  : "nsPostUpdateWin",
692                factory    : #1#(nsPostUpdateWin)
693              },
694   },
695   
696   canUnload: function(compMgr) {
697     return true;
698   }
701 function NSGetModule(compMgr, fileSpec) {
702   return gModule;