www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

profile.js (9924B)


      1 /*
      2     ***** BEGIN LICENSE BLOCK *****
      3     
      4     Copyright © 2016 Center for History and New Media
      5                      George Mason University, Fairfax, Virginia, USA
      6                      http://zotero.org
      7     
      8     This file is part of Zotero.
      9     
     10     Zotero is free software: you can redistribute it and/or modify
     11     it under the terms of the GNU Affero General Public License as published by
     12     the Free Software Foundation, either version 3 of the License, or
     13     (at your option) any later version.
     14     
     15     Zotero is distributed in the hope that it will be useful,
     16     but WITHOUT ANY WARRANTY; without even the implied warranty of
     17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     18     GNU Affero General Public License for more details.
     19     
     20     You should have received a copy of the GNU Affero General Public License
     21     along with Zotero.  If not, see <http://www.gnu.org/licenses/>.
     22     
     23     ***** END LICENSE BLOCK *****
     24 */
     25 
     26 "use strict";
     27 
     28 Components.utils.import("resource://gre/modules/osfile.jsm");
     29 
     30 Zotero.Profile = {
     31 	dir: OS.Constants.Path.profileDir,
     32 	
     33 	getDefaultInProfilesDir: Zotero.Promise.coroutine(function* (profilesDir) {
     34 		var profilesIni = OS.Path.join(profilesDir, "profiles.ini");
     35 		
     36 		try {
     37 			var iniContents = yield Zotero.File.getContentsAsync(profilesIni);
     38 		}
     39 		catch (e) {
     40 			if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
     41 				return false;
     42 			}
     43 			throw e;
     44 		}
     45 		
     46 		// cheap and dirty ini parser
     47 		var curSection = null;
     48 		var defaultSection = null;
     49 		var nSections = 0;
     50 		for (let line of iniContents.split(/(?:\r?\n|\r)/)) {
     51 			let tline = line.trim();
     52 			if(tline[0] == "[" && tline[tline.length-1] == "]") {
     53 				curSection = {};
     54 				if(tline != "[General]") nSections++;
     55 			} else if(curSection && tline != "") {
     56 				let equalsIndex = tline.indexOf("=");
     57 				let key = tline.substr(0, equalsIndex);
     58 				let val = tline.substr(equalsIndex+1);
     59 				curSection[key] = val;
     60 				if(key == "Default" && val == "1") {
     61 					defaultSection = curSection;
     62 				}
     63 			}
     64 		}
     65 		if (!defaultSection && curSection) defaultSection = curSection;
     66 		
     67 		if (!defaultSection || !defaultSection.Path) return false;
     68 		
     69 		var defaultProfile = defaultSection.IsRelative === "1"
     70 			? OS.Path.join(profilesDir, ...defaultSection.Path.split("/"))
     71 			: defaultSection.Path;
     72 		
     73 		try {
     74 			// Note: exists() returns false on no access, so use stat() instead
     75 			yield OS.File.stat(defaultProfile);
     76 		}
     77 		catch (e) {
     78 			if (e instanceof OS.File.Error) {
     79 				if (e.becauseNoSuchFile) {
     80 					return false;
     81 				}
     82 				throw e;
     83 			}
     84 		}
     85 		return [defaultProfile, nSections > 1];
     86 	}),
     87 	
     88 	
     89 	getProfilesDir: function () {
     90 		return OS.Path.dirname(this.dir);
     91 	},
     92 	
     93 	
     94 	/**
     95 	 * Get the path to the Profiles directory of the other app from this one (Firefox or Zotero),
     96 	 * which may or may not exist
     97 	 *
     98 	 * @return {String} - Path
     99 	 */
    100 	getOtherAppProfilesDir: function () {
    101 		var dir = OS.Path.dirname(OS.Path.dirname(OS.Path.dirname(this.dir)));
    102 		
    103 		if (Zotero.isStandalone) {
    104 			if (Zotero.isWin) {
    105 				dir = OS.Path.join(OS.Path.dirname(dir), "Mozilla", "Firefox");
    106 			}
    107 			else if (Zotero.isMac) {
    108 				dir = OS.Path.join(dir, "Firefox");
    109 			}
    110 			else {
    111 				dir = OS.Path.join(dir, ".mozilla", "firefox");
    112 			}
    113 		}
    114 		else {
    115 			if (Zotero.isWin) {
    116 				dir = OS.Path.join(OS.Path.dirname(dir), "Zotero", "Zotero");
    117 			}
    118 			else if (Zotero.isMac) {
    119 				dir = OS.Path.join(dir, "Zotero");
    120 			} else {
    121 				dir = OS.Path.join(dir, ".zotero", "zotero");
    122 			}
    123 		}
    124 		
    125 		return OS.Path.join(dir, "Profiles");
    126 	},
    127 	
    128 	
    129 	/**
    130 	 * Find other profile directories (for this app or the other app) using the given data directory
    131 	 *
    132 	 * @param {String} dataDir
    133 	 * @param {Boolean} [includeOtherApps=false] - Check Firefox profiles
    134 	 * @return {String[]}
    135 	 */
    136 	findOtherProfilesUsingDataDirectory: Zotero.Promise.coroutine(function* (dataDir, includeOtherApps = true) {
    137 		let otherAppProfiles = includeOtherApps ? (yield this._findOtherAppProfiles()) : [];
    138 		let otherProfiles = (yield this._findOtherProfiles()).concat(otherAppProfiles);
    139 		
    140 		// First get profiles pointing at this directory
    141 		otherProfiles = yield Zotero.Promise.filter(otherProfiles, Zotero.Promise.coroutine(function* (dir) {
    142 			let prefs = yield Zotero.File.getContentsAsync(OS.Path.join(dir, "prefs.js"));
    143 			prefs = prefs.trim().split(/(?:\r\n|\r|\n)/);
    144 			
    145 			return prefs.some(line => {
    146 				return line.includes("extensions.zotero.useDataDir") && line.includes("true");
    147 			}) && prefs.some(line => {
    148 				return line.match(/extensions\.zotero\.(lastD|d)ataDir/) && line.includes(dataDir)
    149 			});
    150 		}));
    151 		
    152 		// If the parent of the source directory is a profile directory from the other app, add that
    153 		// to the list, which addresses the situation where the source directory is a custom
    154 		// location for the current profile but is a default in the other app (meaning it wouldn't
    155 		// be added above).
    156 		let dataDirParent = OS.Path.dirname(dataDir);
    157 		if (otherAppProfiles.includes(dataDirParent) && !otherProfiles.includes(dataDirParent)) {
    158 			otherProfiles.push(dataDirParent);
    159 		}
    160 		
    161 		if (otherProfiles.length) {
    162 			Zotero.debug("Found other profiles pointing to " + dataDir);
    163 			Zotero.debug(otherProfiles);
    164 		}
    165 		else {
    166 			Zotero.debug("No other profiles point to " + dataDir);
    167 		}
    168 		
    169 		return otherProfiles;
    170 	}),
    171 	
    172 	
    173 	updateProfileDataDirectory: Zotero.Promise.coroutine(function* (profileDir, oldDir, newDir) {
    174 		let prefsFile = OS.Path.join(profileDir, "prefs.js");
    175 		let prefsFileTmp = OS.Path.join(profileDir, "prefs.js.tmp");
    176 		Zotero.debug("Updating " + prefsFile + " to point to new data directory");
    177 		let contents = yield Zotero.File.getContentsAsync(prefsFile);
    178 		contents = contents
    179 			.trim()
    180 			.split(/(?:\r\n|\r|\n)/)
    181 			// Remove existing lines
    182 			.filter(line => !line.match(/extensions\.zotero\.(useD|lastD|d)ataDir/));
    183 		// Shouldn't happen, but let's make sure we don't corrupt the prefs file
    184 		let safeVal = newDir.replace(/["]/g, "");
    185 		contents.push(
    186 			`user_pref("extensions.zotero.dataDir", "${safeVal}");`,
    187 			`user_pref("extensions.zotero.lastDataDir", "${safeVal}");`,
    188 			'user_pref("extensions.zotero.useDataDir", true);'
    189 		);
    190 		let lineSep = Zotero.isWin ? "\r\n" : "\n";
    191 		contents = contents.join(lineSep) + lineSep;
    192 		yield OS.File.writeAtomic(
    193 			prefsFile,
    194 			contents,
    195 			{
    196 				tmpPath: prefsFileTmp,
    197 				encoding: 'utf-8'
    198 			}
    199 		);
    200 	}),
    201 	
    202 	
    203 	/**
    204 	 * @return {Boolean} - True if accessible or skipped, false if not
    205 	 */
    206 	checkFirefoxProfileAccess: async function () {
    207 		try {
    208 			let profilesParent = OS.Path.dirname(Zotero.Profile.getOtherAppProfilesDir());
    209 			Zotero.debug("Looking for Firefox profile in " + profilesParent);
    210 			let defProfile = await this.getDefaultInProfilesDir(profilesParent);
    211 			if (defProfile) {
    212 				let profileDir = defProfile[0];
    213 				Zotero.debug("Found default profile at " + profileDir);
    214 				let prefsFile = OS.Path.join(profileDir, "prefs.js");
    215 				await Zotero.File.getContentsAsync(prefsFile);
    216 				let dir = OS.Path.join(profileDir, Zotero.DataDirectory.legacyDirName);
    217 				Zotero.debug("Checking for 'zotero' subdirectory");
    218 				if ((await OS.File.stat(dir)).isDir) {
    219 					let dbFilename = Zotero.DataDirectory.getDatabaseFilename();
    220 					let dbFile = OS.Path.join(dir, dbFilename);
    221 					Zotero.debug("Checking database access within 'zotero' subdirectory");
    222 					(await OS.File.stat(dbFile)).lastModificationDate;
    223 				}
    224 				else {
    225 					Zotero.debug("'zotero' is not a directory!");
    226 				}
    227 			}
    228 			else {
    229 				Zotero.debug("No default profile found");
    230 			}
    231 		}
    232 		catch (e) {
    233 			if (e instanceof OS.File.Error && e.becauseNoSuchFile) {
    234 				return true;
    235 			}
    236 			Zotero.debug(e, 2)
    237 			return false
    238 		}
    239 		return true;
    240 	},
    241 	
    242 	
    243 	readPrefsFromFile: async function (prefsFile) {
    244 		var sandbox = new Components.utils.Sandbox("http://www.example.com/");
    245 		Components.utils.evalInSandbox(
    246 			"var prefs = {};"+
    247 			"function user_pref(key, val) {"+
    248 				"prefs[key] = val;"+
    249 			"}"
    250 		, sandbox);
    251 		
    252 		(await Zotero.File.getContentsAsync(prefsFile))
    253 			.split(/\n/)
    254 			.filter((line) => {
    255 				// Strip comments
    256 				return !line.startsWith('#')
    257 					// Only process lines in our pref branch
    258 					&& line.includes(ZOTERO_CONFIG.PREF_BRANCH);
    259 			})
    260 			// Process each line individually
    261 			.forEach((line) => {
    262 				try {
    263 					Zotero.debug("Processing " + line);
    264 					Components.utils.evalInSandbox(line, sandbox);
    265 				}
    266 				catch (e) {
    267 					Zotero.logError("Error processing prefs line: " + line);
    268 				}
    269 			});
    270 		
    271 		return sandbox.prefs;
    272 	},
    273 	
    274 	
    275 	//
    276 	// Private methods
    277 	//
    278 	
    279 	/**
    280 	 * Get all profile directories within the given directory
    281 	 *
    282 	 * @return {String[]} - Array of paths
    283 	 */
    284 	_getProfilesInDir: Zotero.Promise.coroutine(function* (profilesDir) {
    285 		var dirs = [];
    286 		yield Zotero.File.iterateDirectory(profilesDir, function* (iterator) {
    287 			while (true) {
    288 				let entry = yield iterator.next();
    289 				// entry.isDir can be false for some reason on Travis, causing spurious test failures
    290 				if (Zotero.automatedTest && !entry.isDir && (yield OS.File.stat(entry.path)).isDir) {
    291 					Zotero.debug("Overriding isDir for " + entry.path);
    292 					entry.isDir = true;
    293 				}
    294 				if (entry.isDir && (yield OS.File.exists(OS.Path.join(entry.path, "prefs.js")))) {
    295 					dirs.push(entry.path);
    296 				}
    297 			}
    298 		});
    299 		return dirs;
    300 	}),
    301 	
    302 	
    303 	/**
    304 	 * Find other profile directories for this app (Firefox or Zotero)
    305 	 *
    306 	 * @return {String[]} - Array of paths
    307 	 */
    308 	_findOtherProfiles: Zotero.Promise.coroutine(function* () {
    309 		var profileDir = this.dir;
    310 		var profilesDir = this.getProfilesDir();
    311 		return this._getProfilesInDir(profilesDir).filter(dir => dir != profileDir);
    312 	}),
    313 	
    314 	
    315 	/**
    316 	 * Find profile directories for the other app (Firefox or Zotero)
    317 	 *
    318 	 * @return {String[]} - Array of paths
    319 	 */
    320 	_findOtherAppProfiles: Zotero.Promise.coroutine(function* () {
    321 		var dir = this.getOtherAppProfilesDir();
    322 		return (yield OS.File.exists(dir)) ? this._getProfilesInDir(dir) : [];
    323 	})
    324 };