1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/UniquePtr.h"
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <prprf.h>
12 #include <prtime.h>
13 
14 #ifdef XP_WIN
15 #  include <windows.h>
16 #  include <shlobj.h>
17 #  include "mozilla/PolicyChecks.h"
18 #endif
19 #ifdef XP_UNIX
20 #  include <unistd.h>
21 #endif
22 
23 #include "nsToolkitProfileService.h"
24 #include "CmdLineAndEnvUtils.h"
25 #include "nsIFile.h"
26 
27 #ifdef XP_MACOSX
28 #  include <CoreFoundation/CoreFoundation.h>
29 #  include "nsILocalFileMac.h"
30 #endif
31 
32 #include "nsAppDirectoryServiceDefs.h"
33 #include "nsDirectoryServiceDefs.h"
34 #include "nsNetCID.h"
35 #include "nsXULAppAPI.h"
36 #include "nsThreadUtils.h"
37 
38 #include "nsIRunnable.h"
39 #include "nsXREDirProvider.h"
40 #include "nsAppRunner.h"
41 #include "nsString.h"
42 #include "nsReadableUtils.h"
43 #include "nsNativeCharsetUtils.h"
44 #include "mozilla/Attributes.h"
45 #include "mozilla/Sprintf.h"
46 #include "nsPrintfCString.h"
47 #include "mozilla/UniquePtr.h"
48 #include "nsIToolkitShellService.h"
49 #include "mozilla/Telemetry.h"
50 #include "nsProxyRelease.h"
51 
52 using namespace mozilla;
53 
54 #define DEV_EDITION_NAME "dev-edition-default"
55 #define DEFAULT_NAME "default"
56 #define COMPAT_FILE NS_LITERAL_STRING("compatibility.ini")
57 #define PROFILE_DB_VERSION "2"
58 #define INSTALL_PREFIX "Install"
59 #define INSTALL_PREFIX_LENGTH 7
60 
61 struct KeyValue {
KeyValueKeyValue62   KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
63 
64   nsCString key;
65   nsCString value;
66 };
67 
GetStrings(const char * aString,const char * aValue,void * aClosure)68 static bool GetStrings(const char* aString, const char* aValue,
69                        void* aClosure) {
70   nsTArray<UniquePtr<KeyValue>>* array =
71       static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
72   array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
73 
74   return true;
75 }
76 
77 /**
78  * Returns an array of the strings inside a section of an ini file.
79  */
GetSectionStrings(nsINIParser * aParser,const char * aSection)80 nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
81                                                 const char* aSection) {
82   nsTArray<UniquePtr<KeyValue>> result;
83   aParser->GetStrings(aSection, &GetStrings, &result);
84   return result;
85 }
86 
RemoveProfileFiles(nsIToolkitProfile * aProfile,bool aInBackground)87 void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
88   nsCOMPtr<nsIFile> rootDir;
89   aProfile->GetRootDir(getter_AddRefs(rootDir));
90   nsCOMPtr<nsIFile> localDir;
91   aProfile->GetLocalDir(getter_AddRefs(localDir));
92 
93   // Just lock the directories, don't mark the profile as locked or the lock
94   // will attempt to release its reference to the profile on the background
95   // thread which will assert.
96   nsCOMPtr<nsIProfileLock> lock;
97   nsresult rv =
98       NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock));
99   NS_ENSURE_SUCCESS_VOID(rv);
100 
101   nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
102       "nsToolkitProfile::RemoveProfileFiles",
103       [rootDir, localDir, lock]() mutable {
104         bool equals;
105         nsresult rv = rootDir->Equals(localDir, &equals);
106         // The root dir might contain the temp dir, so remove
107         // the temp dir first.
108         if (NS_SUCCEEDED(rv) && !equals) {
109           localDir->Remove(true);
110         }
111 
112         // Ideally we'd unlock after deleting but since the lock is a file
113         // in the profile we must unlock before removing.
114         lock->Unlock();
115         // nsIProfileLock is not threadsafe so release our reference to it on
116         // the main thread.
117         NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
118                                lock.forget());
119 
120         rv = rootDir->Remove(true);
121         NS_ENSURE_SUCCESS_VOID(rv);
122       });
123 
124   if (aInBackground) {
125     nsCOMPtr<nsIEventTarget> target =
126         do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
127     target->Dispatch(runnable, NS_DISPATCH_NORMAL);
128   } else {
129     runnable->Run();
130   }
131 }
132 
nsToolkitProfile(const nsACString & aName,nsIFile * aRootDir,nsIFile * aLocalDir,bool aFromDB)133 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
134                                    nsIFile* aLocalDir, bool aFromDB)
135     : mName(aName),
136       mRootDir(aRootDir),
137       mLocalDir(aLocalDir),
138       mLock(nullptr),
139       mIndex(0),
140       mSection("Profile") {
141   NS_ASSERTION(aRootDir, "No file!");
142 
143   RefPtr<nsToolkitProfile> prev =
144       nsToolkitProfileService::gService->mProfiles.getLast();
145   if (prev) {
146     mIndex = prev->mIndex + 1;
147   }
148   mSection.AppendInt(mIndex);
149 
150   nsToolkitProfileService::gService->mProfiles.insertBack(this);
151 
152   // If this profile isn't in the database already add it.
153   if (!aFromDB) {
154     nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
155     db->SetString(mSection.get(), "Name", mName.get());
156 
157     bool isRelative = false;
158     nsCString descriptor;
159     nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
160                                                             &isRelative);
161 
162     db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
163     db->SetString(mSection.get(), "Path", descriptor.get());
164   }
165 }
166 
NS_IMPL_ISUPPORTS(nsToolkitProfile,nsIToolkitProfile)167 NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
168 
169 NS_IMETHODIMP
170 nsToolkitProfile::GetRootDir(nsIFile** aResult) {
171   NS_ADDREF(*aResult = mRootDir);
172   return NS_OK;
173 }
174 
175 NS_IMETHODIMP
GetLocalDir(nsIFile ** aResult)176 nsToolkitProfile::GetLocalDir(nsIFile** aResult) {
177   NS_ADDREF(*aResult = mLocalDir);
178   return NS_OK;
179 }
180 
181 NS_IMETHODIMP
GetName(nsACString & aResult)182 nsToolkitProfile::GetName(nsACString& aResult) {
183   aResult = mName;
184   return NS_OK;
185 }
186 
187 NS_IMETHODIMP
SetName(const nsACString & aName)188 nsToolkitProfile::SetName(const nsACString& aName) {
189   NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
190 
191   if (mName.Equals(aName)) {
192     return NS_OK;
193   }
194 
195   // Changing the name from the dev-edition default profile name makes this
196   // profile no longer the dev-edition default.
197   if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
198       nsToolkitProfileService::gService->mDevEditionDefault == this) {
199     nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
200   }
201 
202   mName = aName;
203 
204   nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
205       mSection.get(), "Name", mName.get());
206   NS_ENSURE_SUCCESS(rv, rv);
207 
208   // Setting the name to the dev-edition default profile name will cause this
209   // profile to become the dev-edition default.
210   if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
211       !nsToolkitProfileService::gService->mDevEditionDefault) {
212     nsToolkitProfileService::gService->mDevEditionDefault = this;
213   }
214 
215   return NS_OK;
216 }
217 
RemoveInternal(bool aRemoveFiles,bool aInBackground)218 nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
219                                           bool aInBackground) {
220   NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
221 
222   if (mLock) return NS_ERROR_FILE_IS_LOCKED;
223 
224   if (!isInList()) {
225     return NS_ERROR_NOT_INITIALIZED;
226   }
227 
228   if (aRemoveFiles) {
229     RemoveProfileFiles(this, aInBackground);
230   }
231 
232   nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
233   db->DeleteSection(mSection.get());
234 
235   // We make some assumptions that the profile's index in the database is based
236   // on its position in the linked list. Removing a profile means we have to fix
237   // the index of later profiles in the list. The easiest way to do that is just
238   // to move the last profile into the profile's position and just update its
239   // index.
240   RefPtr<nsToolkitProfile> last =
241       nsToolkitProfileService::gService->mProfiles.getLast();
242   if (last != this) {
243     // Update the section in the db.
244     last->mIndex = mIndex;
245     db->RenameSection(last->mSection.get(), mSection.get());
246     last->mSection = mSection;
247 
248     if (last != getNext()) {
249       last->remove();
250       setNext(last);
251     }
252   }
253 
254   remove();
255 
256   if (nsToolkitProfileService::gService->mNormalDefault == this) {
257     nsToolkitProfileService::gService->mNormalDefault = nullptr;
258   }
259   if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
260     nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
261   }
262   if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
263     nsToolkitProfileService::gService->SetDefaultProfile(nullptr);
264   }
265 
266   return NS_OK;
267 }
268 
269 NS_IMETHODIMP
Remove(bool removeFiles)270 nsToolkitProfile::Remove(bool removeFiles) {
271   return RemoveInternal(removeFiles, false /* in background */);
272 }
273 
274 NS_IMETHODIMP
RemoveInBackground(bool removeFiles)275 nsToolkitProfile::RemoveInBackground(bool removeFiles) {
276   return RemoveInternal(removeFiles, true /* in background */);
277 }
278 
279 NS_IMETHODIMP
Lock(nsIProfileUnlocker ** aUnlocker,nsIProfileLock ** aResult)280 nsToolkitProfile::Lock(nsIProfileUnlocker** aUnlocker,
281                        nsIProfileLock** aResult) {
282   if (mLock) {
283     NS_ADDREF(*aResult = mLock);
284     return NS_OK;
285   }
286 
287   RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
288   if (!lock) return NS_ERROR_OUT_OF_MEMORY;
289 
290   nsresult rv = lock->Init(this, aUnlocker);
291   if (NS_FAILED(rv)) return rv;
292 
293   NS_ADDREF(*aResult = lock);
294   return NS_OK;
295 }
296 
NS_IMPL_ISUPPORTS(nsToolkitProfileLock,nsIProfileLock)297 NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
298 
299 nsresult nsToolkitProfileLock::Init(nsToolkitProfile* aProfile,
300                                     nsIProfileUnlocker** aUnlocker) {
301   nsresult rv;
302   rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
303   if (NS_SUCCEEDED(rv)) mProfile = aProfile;
304 
305   return rv;
306 }
307 
Init(nsIFile * aDirectory,nsIFile * aLocalDirectory,nsIProfileUnlocker ** aUnlocker)308 nsresult nsToolkitProfileLock::Init(nsIFile* aDirectory,
309                                     nsIFile* aLocalDirectory,
310                                     nsIProfileUnlocker** aUnlocker) {
311   nsresult rv;
312 
313   rv = mLock.Lock(aDirectory, aUnlocker);
314 
315   if (NS_SUCCEEDED(rv)) {
316     mDirectory = aDirectory;
317     mLocalDirectory = aLocalDirectory;
318   }
319 
320   return rv;
321 }
322 
323 NS_IMETHODIMP
GetDirectory(nsIFile ** aResult)324 nsToolkitProfileLock::GetDirectory(nsIFile** aResult) {
325   if (!mDirectory) {
326     NS_ERROR("Not initialized, or unlocked!");
327     return NS_ERROR_NOT_INITIALIZED;
328   }
329 
330   NS_ADDREF(*aResult = mDirectory);
331   return NS_OK;
332 }
333 
334 NS_IMETHODIMP
GetLocalDirectory(nsIFile ** aResult)335 nsToolkitProfileLock::GetLocalDirectory(nsIFile** aResult) {
336   if (!mLocalDirectory) {
337     NS_ERROR("Not initialized, or unlocked!");
338     return NS_ERROR_NOT_INITIALIZED;
339   }
340 
341   NS_ADDREF(*aResult = mLocalDirectory);
342   return NS_OK;
343 }
344 
345 NS_IMETHODIMP
Unlock()346 nsToolkitProfileLock::Unlock() {
347   if (!mDirectory) {
348     NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
349     return NS_ERROR_UNEXPECTED;
350   }
351 
352   mLock.Unlock();
353 
354   if (mProfile) {
355     mProfile->mLock = nullptr;
356     mProfile = nullptr;
357   }
358   mDirectory = nullptr;
359   mLocalDirectory = nullptr;
360 
361   return NS_OK;
362 }
363 
364 NS_IMETHODIMP
GetReplacedLockTime(PRTime * aResult)365 nsToolkitProfileLock::GetReplacedLockTime(PRTime* aResult) {
366   mLock.GetReplacedLockTime(aResult);
367   return NS_OK;
368 }
369 
~nsToolkitProfileLock()370 nsToolkitProfileLock::~nsToolkitProfileLock() {
371   if (mDirectory) {
372     Unlock();
373   }
374 }
375 
376 nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
377 
NS_IMPL_ISUPPORTS(nsToolkitProfileService,nsIToolkitProfileService)378 NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
379 
380 nsToolkitProfileService::nsToolkitProfileService()
381     : mStartupProfileSelected(false),
382       mStartWithLast(true),
383       mIsFirstRun(true),
384       mUseDevEditionProfile(false),
385 #ifdef MOZ_DEDICATED_PROFILES
386       mUseDedicatedProfile(!IsSnapEnvironment() && !UseLegacyProfiles()),
387 #else
388       mUseDedicatedProfile(false),
389 #endif
390       mCreatedAlternateProfile(false),
391       mStartupReason(NS_LITERAL_STRING("unknown")),
392       mMaybeLockProfile(false),
393       mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
394       mProfileDBExists(false),
395       mProfileDBFileSize(0),
396       mProfileDBModifiedTime(0) {
397 #ifdef MOZ_DEV_EDITION
398   mUseDevEditionProfile = true;
399 #endif
400   gService = this;
401 }
402 
~nsToolkitProfileService()403 nsToolkitProfileService::~nsToolkitProfileService() {
404   gService = nullptr;
405   mProfiles.clear();
406 }
407 
CompleteStartup()408 void nsToolkitProfileService::CompleteStartup() {
409   if (!mStartupProfileSelected) {
410     return;
411   }
412 
413   ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
414             mStartupReason);
415 
416   if (mMaybeLockProfile) {
417     nsCOMPtr<nsIToolkitShellService> shell =
418         do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
419     if (!shell) {
420       return;
421     }
422 
423     bool isDefaultApp;
424     nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
425     NS_ENSURE_SUCCESS_VOID(rv);
426 
427     if (isDefaultApp) {
428       mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
429 
430       // There is a very small chance that this could fail if something else
431       // overwrote the profiles database since we started up, probably less than
432       // a second ago. There isn't really a sane response here, all the other
433       // profile changes are already flushed so whether we fail to flush here or
434       // force quit the app makes no difference.
435       NS_ENSURE_SUCCESS_VOID(Flush());
436     }
437   }
438 }
439 
440 // Tests whether the passed profile was last used by this install.
IsProfileForCurrentInstall(nsIToolkitProfile * aProfile)441 bool nsToolkitProfileService::IsProfileForCurrentInstall(
442     nsIToolkitProfile* aProfile) {
443   nsCOMPtr<nsIFile> profileDir;
444   nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
445   NS_ENSURE_SUCCESS(rv, false);
446 
447   nsCOMPtr<nsIFile> compatFile;
448   rv = profileDir->Clone(getter_AddRefs(compatFile));
449   NS_ENSURE_SUCCESS(rv, false);
450 
451   rv = compatFile->Append(COMPAT_FILE);
452   NS_ENSURE_SUCCESS(rv, false);
453 
454   nsINIParser compatData;
455   rv = compatData.Init(compatFile);
456   NS_ENSURE_SUCCESS(rv, false);
457 
458   /**
459    * In xpcshell gDirServiceProvider doesn't have all the correct directories
460    * set so using NS_GetSpecialDirectory works better there. But in a normal
461    * app launch the component registry isn't initialized so
462    * NS_GetSpecialDirectory doesn't work. So we have to use two different
463    * paths to support testing.
464    */
465   nsCOMPtr<nsIFile> currentGreDir;
466   rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(currentGreDir));
467   if (rv == NS_ERROR_NOT_INITIALIZED) {
468     currentGreDir = gDirServiceProvider->GetGREDir();
469     MOZ_ASSERT(currentGreDir, "No GRE dir found.");
470   } else if (NS_FAILED(rv)) {
471     return false;
472   }
473 
474   nsCString lastGreDirStr;
475   rv = compatData.GetString("Compatibility", "LastPlatformDir", lastGreDirStr);
476   // If this string is missing then this profile is from an ancient version.
477   // We'll opt to use it in this case.
478   if (NS_FAILED(rv)) {
479     return true;
480   }
481 
482   nsCOMPtr<nsIFile> lastGreDir;
483   rv = NS_NewNativeLocalFile(EmptyCString(), false, getter_AddRefs(lastGreDir));
484   NS_ENSURE_SUCCESS(rv, false);
485 
486   rv = lastGreDir->SetPersistentDescriptor(lastGreDirStr);
487   NS_ENSURE_SUCCESS(rv, false);
488 
489 #ifdef XP_WIN
490 #  if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
491   mozilla::PathString lastGreDirPath, currentGreDirPath;
492   lastGreDirPath = lastGreDir->NativePath();
493   currentGreDirPath = currentGreDir->NativePath();
494   if (lastGreDirPath.Equals(currentGreDirPath,
495                             nsCaseInsensitiveStringComparator)) {
496     return true;
497   }
498 
499   // Convert a 64-bit install path to what would have been the 32-bit install
500   // path to allow users to migrate their profiles from one to the other.
501   PWSTR pathX86 = nullptr;
502   HRESULT hres =
503       SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86);
504   if (SUCCEEDED(hres)) {
505     nsDependentString strPathX86(pathX86);
506     if (!StringBeginsWith(currentGreDirPath, strPathX86,
507                           nsCaseInsensitiveStringComparator)) {
508       PWSTR path = nullptr;
509       hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path);
510       if (SUCCEEDED(hres)) {
511         if (StringBeginsWith(currentGreDirPath, nsDependentString(path),
512                              nsCaseInsensitiveStringComparator)) {
513           currentGreDirPath.Replace(0, wcslen(path), strPathX86);
514         }
515       }
516       CoTaskMemFree(path);
517     }
518   }
519   CoTaskMemFree(pathX86);
520 
521   return lastGreDirPath.Equals(currentGreDirPath,
522                                nsCaseInsensitiveStringComparator);
523 #  endif
524 #endif
525 
526   bool equal;
527   rv = lastGreDir->Equals(currentGreDir, &equal);
528   NS_ENSURE_SUCCESS(rv, false);
529 
530   return equal;
531 }
532 
533 /**
534  * Used the first time an install with dedicated profile support runs. Decides
535  * whether to mark the passed profile as the default for this install.
536  *
537  * The goal is to reduce disruption but ideally end up with the OS default
538  * install using the old default profile.
539  *
540  * If the decision is to use the profile then it will be unassigned as the
541  * dedicated default for other installs.
542  *
543  * We won't attempt to use the profile if it was last used by a different
544  * install.
545  *
546  * If the profile is currently in use by an install that was either the OS
547  * default install or the profile has been explicitely chosen by some other
548  * means then we won't use it.
549  *
550  * aResult will be set to true if we chose to make the profile the new dedicated
551  * default.
552  */
MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile * aProfile,bool * aResult)553 nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
554     nsIToolkitProfile* aProfile, bool* aResult) {
555   nsresult rv;
556   *aResult = false;
557 
558   // If the profile was last used by a different install then we won't use it.
559   if (!IsProfileForCurrentInstall(aProfile)) {
560     return NS_OK;
561   }
562 
563   nsCString descriptor;
564   rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
565   NS_ENSURE_SUCCESS(rv, rv);
566 
567   // Get a list of all the installs.
568   nsTArray<nsCString> installs = GetKnownInstalls();
569 
570   // Cache the installs that use the profile.
571   nsTArray<nsCString> inUseInstalls;
572 
573   // See if the profile is already in use by an install that hasn't locked it.
574   for (uint32_t i = 0; i < installs.Length(); i++) {
575     const nsCString& install = installs[i];
576 
577     nsCString path;
578     rv = mProfileDB.GetString(install.get(), "Default", path);
579     if (NS_FAILED(rv)) {
580       continue;
581     }
582 
583     // Is this install using the profile we care about?
584     if (!descriptor.Equals(path)) {
585       continue;
586     }
587 
588     // Is this profile locked to this other install?
589     nsCString isLocked;
590     rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
591     if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
592       return NS_OK;
593     }
594 
595     inUseInstalls.AppendElement(install);
596   }
597 
598   // At this point we've decided to take the profile. Strip it from other
599   // installs.
600   for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
601     // Removing the default setting entirely will make the install go through
602     // the first run process again at startup and create itself a new profile.
603     mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
604   }
605 
606   // Set this as the default profile for this install.
607   SetDefaultProfile(aProfile);
608 
609   // SetDefaultProfile will have locked this profile to this install so no
610   // other installs will steal it, but this was auto-selected so we want to
611   // unlock it so that other installs can potentially take it.
612   mProfileDB.DeleteString(mInstallSection.get(), "Locked");
613 
614   // Persist the changes.
615   rv = Flush();
616   NS_ENSURE_SUCCESS(rv, rv);
617 
618   // Once XPCOM is available check if this is the default application and if so
619   // lock the profile again.
620   mMaybeLockProfile = true;
621   *aResult = true;
622 
623   return NS_OK;
624 }
625 
IsFileOutdated(nsIFile * aFile,bool aExists,PRTime aLastModified,int64_t aLastSize)626 bool IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
627                     int64_t aLastSize) {
628   nsCOMPtr<nsIFile> file;
629   nsresult rv = aFile->Clone(getter_AddRefs(file));
630   if (NS_FAILED(rv)) {
631     return false;
632   }
633 
634   bool exists;
635   rv = aFile->Exists(&exists);
636   if (NS_FAILED(rv) || exists != aExists) {
637     return true;
638   }
639 
640   if (!exists) {
641     return false;
642   }
643 
644   int64_t size;
645   rv = aFile->GetFileSize(&size);
646   if (NS_FAILED(rv) || size != aLastSize) {
647     return true;
648   }
649 
650   PRTime time;
651   rv = aFile->GetLastModifiedTime(&time);
652   if (NS_FAILED(rv) || time != aLastModified) {
653     return true;
654   }
655 
656   return false;
657 }
658 
UpdateFileStats(nsIFile * aFile,bool * aExists,PRTime * aLastModified,int64_t * aLastSize)659 nsresult UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
660                          int64_t* aLastSize) {
661   nsCOMPtr<nsIFile> file;
662   nsresult rv = aFile->Clone(getter_AddRefs(file));
663   NS_ENSURE_SUCCESS(rv, rv);
664 
665   rv = file->Exists(aExists);
666   NS_ENSURE_SUCCESS(rv, rv);
667 
668   if (!(*aExists)) {
669     *aLastModified = 0;
670     *aLastSize = 0;
671     return NS_OK;
672   }
673 
674   rv = file->GetFileSize(aLastSize);
675   NS_ENSURE_SUCCESS(rv, rv);
676 
677   rv = file->GetLastModifiedTime(aLastModified);
678   NS_ENSURE_SUCCESS(rv, rv);
679 
680   return NS_OK;
681 }
682 
683 NS_IMETHODIMP
GetIsListOutdated(bool * aResult)684 nsToolkitProfileService::GetIsListOutdated(bool* aResult) {
685   if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
686                      mProfileDBFileSize)) {
687     *aResult = true;
688     return NS_OK;
689   }
690 
691   *aResult = false;
692   return NS_OK;
693 }
694 
695 struct ImportInstallsClosure {
696   nsINIParser* backupData;
697   nsINIParser* profileDB;
698 };
699 
ImportInstalls(const char * aSection,void * aClosure)700 static bool ImportInstalls(const char* aSection, void* aClosure) {
701   ImportInstallsClosure* closure =
702       static_cast<ImportInstallsClosure*>(aClosure);
703 
704   nsTArray<UniquePtr<KeyValue>> strings =
705       GetSectionStrings(closure->backupData, aSection);
706   if (strings.IsEmpty()) {
707     return true;
708   }
709 
710   nsCString newSection(INSTALL_PREFIX);
711   newSection.Append(aSection);
712   nsCString buffer;
713 
714   for (uint32_t i = 0; i < strings.Length(); i++) {
715     closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
716                                   strings[i]->value.get());
717   }
718 
719   return true;
720 }
721 
Init()722 nsresult nsToolkitProfileService::Init() {
723   NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
724   nsresult rv;
725 
726   rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
727   NS_ENSURE_SUCCESS(rv, rv);
728 
729   rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
730   NS_ENSURE_SUCCESS(rv, rv);
731 
732   rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
733   NS_ENSURE_SUCCESS(rv, rv);
734 
735   rv = mProfileDBFile->AppendNative(NS_LITERAL_CSTRING("profiles.ini"));
736   NS_ENSURE_SUCCESS(rv, rv);
737 
738   rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
739   NS_ENSURE_SUCCESS(rv, rv);
740 
741   rv = mInstallDBFile->AppendNative(NS_LITERAL_CSTRING("installs.ini"));
742   NS_ENSURE_SUCCESS(rv, rv);
743 
744   nsAutoCString buffer;
745 
746   rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
747                        &mProfileDBModifiedTime, &mProfileDBFileSize);
748   if (NS_SUCCEEDED(rv) && mProfileDBExists) {
749     mProfileDBFile->GetFileSize(&mProfileDBFileSize);
750     mProfileDBFile->GetLastModifiedTime(&mProfileDBModifiedTime);
751 
752     rv = mProfileDB.Init(mProfileDBFile);
753     // Init does not fail on parsing errors, only on OOM/really unexpected
754     // conditions.
755     if (NS_FAILED(rv)) {
756       return rv;
757     }
758 
759     rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
760     if (NS_SUCCEEDED(rv)) {
761       mStartWithLast = !buffer.EqualsLiteral("0");
762     }
763 
764     rv = mProfileDB.GetString("General", "Version", buffer);
765     if (NS_FAILED(rv)) {
766       // This is a profiles.ini written by an older version. We must restore
767       // any install data from the backup.
768       nsINIParser installDB;
769 
770       if (NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
771         // There is install data to import.
772         ImportInstallsClosure closure = {&installDB, &mProfileDB};
773         installDB.GetSections(&ImportInstalls, &closure);
774       }
775 
776       rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
777       NS_ENSURE_SUCCESS(rv, rv);
778     }
779   } else {
780     rv = mProfileDB.SetString("General", "StartWithLastProfile",
781                               mStartWithLast ? "1" : "0");
782     NS_ENSURE_SUCCESS(rv, rv);
783     rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
784     NS_ENSURE_SUCCESS(rv, rv);
785   }
786 
787   nsCString installProfilePath;
788 
789   if (mUseDedicatedProfile) {
790     nsString installHash;
791     rv = gDirServiceProvider->GetInstallHash(installHash);
792     NS_ENSURE_SUCCESS(rv, rv);
793     CopyUTF16toUTF8(installHash, mInstallSection);
794     mInstallSection.Insert(INSTALL_PREFIX, 0);
795 
796     // Try to find the descriptor for the default profile for this install.
797     rv = mProfileDB.GetString(mInstallSection.get(), "Default",
798                               installProfilePath);
799 
800     // Not having a value means this install doesn't appear in installs.ini so
801     // this is the first run for this install.
802     if (NS_FAILED(rv)) {
803       mIsFirstRun = true;
804 
805       // Gets the install section that would have been created if the install
806       // path has incorrect casing (see bug 1555319). We use this later during
807       // profile selection.
808       rv = gDirServiceProvider->GetLegacyInstallHash(installHash);
809       NS_ENSURE_SUCCESS(rv, rv);
810       CopyUTF16toUTF8(installHash, mLegacyInstallSection);
811       mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
812     } else {
813       mIsFirstRun = false;
814     }
815   }
816 
817   nsToolkitProfile* currentProfile = nullptr;
818 
819 #ifdef MOZ_DEV_EDITION
820   nsCOMPtr<nsIFile> ignoreDevEditionProfile;
821   rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
822   if (NS_FAILED(rv)) {
823     return rv;
824   }
825 
826   rv = ignoreDevEditionProfile->AppendNative(
827       NS_LITERAL_CSTRING("ignore-dev-edition-profile"));
828   if (NS_FAILED(rv)) {
829     return rv;
830   }
831 
832   bool shouldIgnoreSeparateProfile;
833   rv = ignoreDevEditionProfile->Exists(&shouldIgnoreSeparateProfile);
834   if (NS_FAILED(rv)) return rv;
835 
836   mUseDevEditionProfile = !shouldIgnoreSeparateProfile;
837 #endif
838 
839   nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
840 
841   unsigned int nonDevEditionProfiles = 0;
842   unsigned int c = 0;
843   for (c = 0; true; ++c) {
844     nsAutoCString profileID("Profile");
845     profileID.AppendInt(c);
846 
847     rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
848     if (NS_FAILED(rv)) break;
849 
850     bool isRelative = buffer.EqualsLiteral("1");
851 
852     nsAutoCString filePath;
853 
854     rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
855     if (NS_FAILED(rv)) {
856       NS_ERROR("Malformed profiles.ini: Path= not found");
857       continue;
858     }
859 
860     nsAutoCString name;
861 
862     rv = mProfileDB.GetString(profileID.get(), "Name", name);
863     if (NS_FAILED(rv)) {
864       NS_ERROR("Malformed profiles.ini: Name= not found");
865       continue;
866     }
867 
868     nsCOMPtr<nsIFile> rootDir;
869     rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(rootDir));
870     NS_ENSURE_SUCCESS(rv, rv);
871 
872     if (isRelative) {
873       rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
874     } else {
875       rv = rootDir->SetPersistentDescriptor(filePath);
876     }
877     if (NS_FAILED(rv)) continue;
878 
879     nsCOMPtr<nsIFile> localDir;
880     if (isRelative) {
881       rv =
882           NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
883       NS_ENSURE_SUCCESS(rv, rv);
884 
885       rv = localDir->SetRelativeDescriptor(mTempData, filePath);
886     } else {
887       localDir = rootDir;
888     }
889 
890     currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
891     NS_ENSURE_TRUE(currentProfile, NS_ERROR_OUT_OF_MEMORY);
892 
893     // If a user has modified the ini file path it may make for a valid profile
894     // path but not match what we would have serialised and so may not match
895     // the path in the install section. Re-serialise it to get it in the
896     // expected form again.
897     bool nowRelative;
898     nsCString descriptor;
899     GetProfileDescriptor(currentProfile, descriptor, &nowRelative);
900 
901     if (isRelative != nowRelative || !descriptor.Equals(filePath)) {
902       mProfileDB.SetString(profileID.get(), "IsRelative",
903                            nowRelative ? "1" : "0");
904       mProfileDB.SetString(profileID.get(), "Path", descriptor.get());
905 
906       // Should we flush now? It costs some startup time and we will fix it on
907       // the next startup anyway. If something else causes a flush then it will
908       // be fixed in the ini file then.
909     }
910 
911     rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
912     if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
913       mNormalDefault = currentProfile;
914     }
915 
916     // Is this the default profile for this install?
917     if (mUseDedicatedProfile && !mDedicatedProfile &&
918         installProfilePath.Equals(descriptor)) {
919       // Found a profile for this install.
920       mDedicatedProfile = currentProfile;
921     }
922 
923     if (name.EqualsLiteral(DEV_EDITION_NAME)) {
924       mDevEditionDefault = currentProfile;
925     } else {
926       nonDevEditionProfiles++;
927       autoSelectProfile = currentProfile;
928     }
929   }
930 
931   // If there is only one non-dev-edition profile then mark it as the default.
932   if (!mNormalDefault && nonDevEditionProfiles == 1) {
933     SetNormalDefault(autoSelectProfile);
934   }
935 
936   if (!mUseDedicatedProfile) {
937     if (mUseDevEditionProfile) {
938       // When using the separate dev-edition profile not finding it means this
939       // is a first run.
940       mIsFirstRun = !mDevEditionDefault;
941     } else {
942       // If there are no normal profiles then this is a first run.
943       mIsFirstRun = nonDevEditionProfiles == 0;
944     }
945   }
946 
947   return NS_OK;
948 }
949 
950 NS_IMETHODIMP
SetStartWithLastProfile(bool aValue)951 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
952   if (mStartWithLast != aValue) {
953     nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
954                                        aValue ? "1" : "0");
955     NS_ENSURE_SUCCESS(rv, rv);
956     mStartWithLast = aValue;
957   }
958   return NS_OK;
959 }
960 
961 NS_IMETHODIMP
GetStartWithLastProfile(bool * aResult)962 nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
963   *aResult = mStartWithLast;
964   return NS_OK;
965 }
966 
967 NS_IMETHODIMP
GetProfiles(nsISimpleEnumerator ** aResult)968 nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
969   *aResult = new ProfileEnumerator(mProfiles.getFirst());
970   if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
971 
972   NS_ADDREF(*aResult);
973   return NS_OK;
974 }
975 
976 NS_IMETHODIMP
HasMoreElements(bool * aResult)977 nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
978   *aResult = mCurrent ? true : false;
979   return NS_OK;
980 }
981 
982 NS_IMETHODIMP
GetNext(nsISupports ** aResult)983 nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
984   if (!mCurrent) return NS_ERROR_FAILURE;
985 
986   NS_ADDREF(*aResult = mCurrent);
987 
988   mCurrent = mCurrent->getNext();
989   return NS_OK;
990 }
991 
992 NS_IMETHODIMP
GetCurrentProfile(nsIToolkitProfile ** aResult)993 nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
994   NS_IF_ADDREF(*aResult = mCurrent);
995   return NS_OK;
996 }
997 
998 NS_IMETHODIMP
GetDefaultProfile(nsIToolkitProfile ** aResult)999 nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
1000   if (mUseDedicatedProfile) {
1001     NS_IF_ADDREF(*aResult = mDedicatedProfile);
1002     return NS_OK;
1003   }
1004 
1005   if (mUseDevEditionProfile) {
1006     NS_IF_ADDREF(*aResult = mDevEditionDefault);
1007     return NS_OK;
1008   }
1009 
1010   NS_IF_ADDREF(*aResult = mNormalDefault);
1011   return NS_OK;
1012 }
1013 
SetNormalDefault(nsIToolkitProfile * aProfile)1014 void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
1015   if (mNormalDefault == aProfile) {
1016     return;
1017   }
1018 
1019   if (mNormalDefault) {
1020     nsToolkitProfile* profile =
1021         static_cast<nsToolkitProfile*>(mNormalDefault.get());
1022     mProfileDB.DeleteString(profile->mSection.get(), "Default");
1023   }
1024 
1025   mNormalDefault = aProfile;
1026 
1027   if (mNormalDefault) {
1028     nsToolkitProfile* profile =
1029         static_cast<nsToolkitProfile*>(mNormalDefault.get());
1030     mProfileDB.SetString(profile->mSection.get(), "Default", "1");
1031   }
1032 }
1033 
1034 NS_IMETHODIMP
SetDefaultProfile(nsIToolkitProfile * aProfile)1035 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
1036   if (mUseDedicatedProfile) {
1037     if (mDedicatedProfile != aProfile) {
1038       if (!aProfile) {
1039         // Setting this to the empty string means no profile will be found on
1040         // startup but we'll recognise that this install has been used
1041         // previously.
1042         mProfileDB.SetString(mInstallSection.get(), "Default", "");
1043       } else {
1044         nsCString profilePath;
1045         nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
1046         NS_ENSURE_SUCCESS(rv, rv);
1047 
1048         mProfileDB.SetString(mInstallSection.get(), "Default",
1049                              profilePath.get());
1050       }
1051       mDedicatedProfile = aProfile;
1052 
1053       // Some kind of choice has happened here, lock this profile to this
1054       // install.
1055       mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
1056     }
1057     return NS_OK;
1058   }
1059 
1060   if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
1061     // The separate profile is hardcoded.
1062     return NS_ERROR_FAILURE;
1063   }
1064 
1065   SetNormalDefault(aProfile);
1066 
1067   return NS_OK;
1068 }
1069 
1070 NS_IMETHODIMP
GetCreatedAlternateProfile(bool * aResult)1071 nsToolkitProfileService::GetCreatedAlternateProfile(bool* aResult) {
1072   *aResult = mCreatedAlternateProfile;
1073   return NS_OK;
1074 }
1075 
1076 // Gets the profile root directory descriptor for storing in profiles.ini or
1077 // installs.ini.
GetProfileDescriptor(nsIToolkitProfile * aProfile,nsACString & aDescriptor,bool * aIsRelative)1078 nsresult nsToolkitProfileService::GetProfileDescriptor(
1079     nsIToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
1080   nsCOMPtr<nsIFile> profileDir;
1081   nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
1082   NS_ENSURE_SUCCESS(rv, rv);
1083 
1084   // if the profile dir is relative to appdir...
1085   bool isRelative;
1086   rv = mAppData->Contains(profileDir, &isRelative);
1087 
1088   nsCString profilePath;
1089   if (NS_SUCCEEDED(rv) && isRelative) {
1090     // we use a relative descriptor
1091     rv = profileDir->GetRelativeDescriptor(mAppData, profilePath);
1092   } else {
1093     // otherwise, a persistent descriptor
1094     rv = profileDir->GetPersistentDescriptor(profilePath);
1095   }
1096   NS_ENSURE_SUCCESS(rv, rv);
1097 
1098   aDescriptor.Assign(profilePath);
1099   if (aIsRelative) {
1100     *aIsRelative = isRelative;
1101   }
1102 
1103   return NS_OK;
1104 }
1105 
CreateDefaultProfile(nsIToolkitProfile ** aResult)1106 nsresult nsToolkitProfileService::CreateDefaultProfile(
1107     nsIToolkitProfile** aResult) {
1108   // Create a new default profile
1109   nsAutoCString name;
1110   if (mUseDevEditionProfile) {
1111     name.AssignLiteral(DEV_EDITION_NAME);
1112   } else if (mUseDedicatedProfile) {
1113     name.AppendPrintf("default-%s", mUpdateChannel.get());
1114   } else {
1115     name.AssignLiteral(DEFAULT_NAME);
1116   }
1117 
1118   nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
1119   NS_ENSURE_SUCCESS(rv, rv);
1120 
1121   if (mUseDedicatedProfile) {
1122     SetDefaultProfile(mCurrent);
1123   } else if (mUseDevEditionProfile) {
1124     mDevEditionDefault = mCurrent;
1125   } else {
1126     SetNormalDefault(mCurrent);
1127   }
1128 
1129   return NS_OK;
1130 }
1131 
1132 /**
1133  * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
1134  * See nsIToolkitProfileService.idl.
1135  */
1136 NS_IMETHODIMP
SelectStartupProfile(const nsTArray<nsCString> & aArgv,bool aIsResetting,const nsACString & aUpdateChannel,const nsACString & aLegacyInstallHash,nsIFile ** aRootDir,nsIFile ** aLocalDir,nsIToolkitProfile ** aProfile,bool * aDidCreate)1137 nsToolkitProfileService::SelectStartupProfile(
1138     const nsTArray<nsCString>& aArgv, bool aIsResetting,
1139     const nsACString& aUpdateChannel, const nsACString& aLegacyInstallHash,
1140     nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
1141     bool* aDidCreate) {
1142   int argc = aArgv.Length();
1143   // Our command line handling expects argv to be null-terminated so construct
1144   // an appropriate array.
1145   auto argv = MakeUnique<char*[]>(argc + 1);
1146   // Also, our command line handling removes things from the array without
1147   // freeing them so keep track of what we've created separately.
1148   auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
1149 
1150   for (int i = 0; i < argc; i++) {
1151     allocated[i].reset(ToNewCString(aArgv[i]));
1152     argv[i] = allocated[i].get();
1153   }
1154   argv[argc] = nullptr;
1155 
1156   mUpdateChannel = aUpdateChannel;
1157   if (!aLegacyInstallHash.IsEmpty()) {
1158     mLegacyInstallSection.Assign(aLegacyInstallHash);
1159     mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
1160   }
1161 
1162   bool wasDefault;
1163   nsresult rv =
1164       SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
1165                            aProfile, aDidCreate, &wasDefault);
1166 
1167   // Since we were called outside of the normal startup path complete any
1168   // startup tasks.
1169   if (NS_SUCCEEDED(rv)) {
1170     CompleteStartup();
1171   }
1172 
1173   return rv;
1174 }
1175 
1176 /**
1177  * Selects or creates a profile to use based on the profiles database, any
1178  * environment variables and any command line arguments. Will not create
1179  * a profile if aIsResetting is true. The profile is selected based on this
1180  * order of preference:
1181  * * Environment variables (set when restarting the application).
1182  * * --profile command line argument.
1183  * * --createprofile command line argument (this also causes the app to exit).
1184  * * -p command line argument.
1185  * * A new profile created if this is the first run of the application.
1186  * * The default profile.
1187  * aRootDir and aLocalDir are set to the data and local directories for the
1188  * profile data. If a profile from the database was selected it will be
1189  * returned in aProfile.
1190  * aDidCreate will be set to true if a new profile was created.
1191  * This function should be called once at startup and will fail if called again.
1192  * aArgv should be an array of aArgc + 1 strings, the last element being null.
1193  * Both aArgv and aArgc will be mutated.
1194  */
SelectStartupProfile(int * aArgc,char * aArgv[],bool aIsResetting,nsIFile ** aRootDir,nsIFile ** aLocalDir,nsIToolkitProfile ** aProfile,bool * aDidCreate,bool * aWasDefaultSelection)1195 nsresult nsToolkitProfileService::SelectStartupProfile(
1196     int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
1197     nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
1198     bool* aWasDefaultSelection) {
1199   if (mStartupProfileSelected) {
1200     return NS_ERROR_ALREADY_INITIALIZED;
1201   }
1202 
1203   mStartupProfileSelected = true;
1204   *aDidCreate = false;
1205   *aWasDefaultSelection = false;
1206 
1207   nsresult rv;
1208   const char* arg;
1209 
1210   // Use the profile specified in the environment variables (generally from an
1211   // app initiated restart).
1212   nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
1213   if (lf) {
1214     nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
1215     if (!localDir) {
1216       localDir = lf;
1217     }
1218 
1219     // Clear out flags that we handled (or should have handled!) last startup.
1220     const char* dummy;
1221     CheckArg(*aArgc, aArgv, "p", &dummy);
1222     CheckArg(*aArgc, aArgv, "profile", &dummy);
1223     CheckArg(*aArgc, aArgv, "profilemanager");
1224 
1225     nsCOMPtr<nsIToolkitProfile> profile;
1226     GetProfileByDir(lf, localDir, getter_AddRefs(profile));
1227 
1228     if (profile && mIsFirstRun && mUseDedicatedProfile) {
1229       if (profile ==
1230           (mUseDevEditionProfile ? mDevEditionDefault : mNormalDefault)) {
1231         // This is the first run of a dedicated profile build where the selected
1232         // profile is the previous default so we should either make it the
1233         // default profile for this install or push the user to a new profile.
1234 
1235         bool result;
1236         rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1237         NS_ENSURE_SUCCESS(rv, rv);
1238         if (result) {
1239           mStartupReason = NS_LITERAL_STRING("restart-claimed-default");
1240 
1241           mCurrent = profile;
1242         } else {
1243           rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1244           if (NS_FAILED(rv)) {
1245             *aProfile = nullptr;
1246             return rv;
1247           }
1248 
1249           rv = Flush();
1250           NS_ENSURE_SUCCESS(rv, rv);
1251 
1252           mStartupReason = NS_LITERAL_STRING("restart-skipped-default");
1253           *aDidCreate = true;
1254           mCreatedAlternateProfile = true;
1255         }
1256 
1257         NS_IF_ADDREF(*aProfile = mCurrent);
1258         mCurrent->GetRootDir(aRootDir);
1259         mCurrent->GetLocalDir(aLocalDir);
1260 
1261         return NS_OK;
1262       }
1263     }
1264 
1265     if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
1266       mStartupReason = NS_LITERAL_STRING("profile-manager");
1267     } else if (aIsResetting) {
1268       mStartupReason = NS_LITERAL_STRING("profile-reset");
1269     } else {
1270       mStartupReason = NS_LITERAL_STRING("restart");
1271     }
1272 
1273     mCurrent = profile;
1274     lf.forget(aRootDir);
1275     localDir.forget(aLocalDir);
1276     NS_IF_ADDREF(*aProfile = profile);
1277     return NS_OK;
1278   }
1279 
1280   // Check the -profile command line argument. It accepts a single argument that
1281   // gives the path to use for the profile.
1282   ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg);
1283   if (ar == ARG_BAD) {
1284     PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
1285     return NS_ERROR_FAILURE;
1286   }
1287   if (ar) {
1288     nsCOMPtr<nsIFile> lf;
1289     rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
1290     NS_ENSURE_SUCCESS(rv, rv);
1291 
1292     // Make sure that the profile path exists and it's a directory.
1293     bool exists;
1294     rv = lf->Exists(&exists);
1295     NS_ENSURE_SUCCESS(rv, rv);
1296     if (!exists) {
1297       rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
1298       NS_ENSURE_SUCCESS(rv, rv);
1299     } else {
1300       bool isDir;
1301       rv = lf->IsDirectory(&isDir);
1302       NS_ENSURE_SUCCESS(rv, rv);
1303       if (!isDir) {
1304         PR_fprintf(
1305             PR_STDERR,
1306             "Error: argument --profile requires a path to a directory\n");
1307         return NS_ERROR_FAILURE;
1308       }
1309     }
1310 
1311     mStartupReason = NS_LITERAL_STRING("argument-profile");
1312 
1313     GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
1314     NS_ADDREF(*aRootDir = lf);
1315     // If the root dir matched a profile then use its local dir, otherwise use
1316     // the root dir as the local dir.
1317     if (mCurrent) {
1318       mCurrent->GetLocalDir(aLocalDir);
1319     } else {
1320       lf.forget(aLocalDir);
1321     }
1322 
1323     NS_IF_ADDREF(*aProfile = mCurrent);
1324     return NS_OK;
1325   }
1326 
1327   // Check the -createprofile command line argument. It accepts a single
1328   // argument that is either the name for the new profile or the name followed
1329   // by the path to use.
1330   ar = CheckArg(*aArgc, aArgv, "createprofile", &arg, CheckArgFlag::RemoveArg);
1331   if (ar == ARG_BAD) {
1332     PR_fprintf(PR_STDERR,
1333                "Error: argument --createprofile requires a profile name\n");
1334     return NS_ERROR_FAILURE;
1335   }
1336   if (ar) {
1337     const char* delim = strchr(arg, ' ');
1338     nsCOMPtr<nsIToolkitProfile> profile;
1339     if (delim) {
1340       nsCOMPtr<nsIFile> lf;
1341       rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
1342                                  getter_AddRefs(lf));
1343       if (NS_FAILED(rv)) {
1344         PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
1345         return rv;
1346       }
1347 
1348       // As with --profile, assume that the given path will be used for the
1349       // main profile directory.
1350       rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
1351                          getter_AddRefs(profile));
1352     } else {
1353       rv = CreateProfile(nullptr, nsDependentCString(arg),
1354                          getter_AddRefs(profile));
1355     }
1356     // Some pathological arguments can make it this far
1357     if (NS_FAILED(rv) || NS_FAILED(Flush())) {
1358       PR_fprintf(PR_STDERR, "Error creating profile.\n");
1359     }
1360     return NS_ERROR_ABORT;
1361   }
1362 
1363   // Check the -p command line argument. It either accepts a profile name and
1364   // uses that named profile or without a name it opens the profile manager.
1365   ar = CheckArg(*aArgc, aArgv, "p", &arg);
1366   if (ar == ARG_BAD) {
1367     ar = CheckArg(*aArgc, aArgv, "osint");
1368     if (ar == ARG_FOUND) {
1369       PR_fprintf(
1370           PR_STDERR,
1371           "Error: argument -p is invalid when argument --osint is specified\n");
1372       return NS_ERROR_FAILURE;
1373     }
1374 
1375     return NS_ERROR_SHOW_PROFILE_MANAGER;
1376   }
1377   if (ar) {
1378     ar = CheckArg(*aArgc, aArgv, "osint");
1379     if (ar == ARG_FOUND) {
1380       PR_fprintf(
1381           PR_STDERR,
1382           "Error: argument -p is invalid when argument --osint is specified\n");
1383       return NS_ERROR_FAILURE;
1384     }
1385 
1386     rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
1387     if (NS_SUCCEEDED(rv)) {
1388       mStartupReason = NS_LITERAL_STRING("argument-p");
1389 
1390       mCurrent->GetRootDir(aRootDir);
1391       mCurrent->GetLocalDir(aLocalDir);
1392 
1393       NS_ADDREF(*aProfile = mCurrent);
1394       return NS_OK;
1395     }
1396 
1397     return NS_ERROR_SHOW_PROFILE_MANAGER;
1398   }
1399 
1400   ar = CheckArg(*aArgc, aArgv, "profilemanager");
1401   if (ar == ARG_FOUND) {
1402     return NS_ERROR_SHOW_PROFILE_MANAGER;
1403   }
1404 
1405   if (mIsFirstRun && mUseDedicatedProfile &&
1406       !mInstallSection.Equals(mLegacyInstallSection)) {
1407     // The default profile could be assigned to a hash generated from an
1408     // incorrectly cased version of the installation directory (see bug
1409     // 1555319). Ideally we'd do all this while loading profiles.ini but we
1410     // can't override the legacy section value before that for tests.
1411     nsCString defaultDescriptor;
1412     rv = mProfileDB.GetString(mLegacyInstallSection.get(), "Default",
1413                               defaultDescriptor);
1414 
1415     if (NS_SUCCEEDED(rv)) {
1416       // There is a default here, need to see if it matches any profiles.
1417       bool isRelative;
1418       nsCString descriptor;
1419 
1420       for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1421         GetProfileDescriptor(profile, descriptor, &isRelative);
1422 
1423         if (descriptor.Equals(defaultDescriptor)) {
1424           // Found the default profile. Copy the install section over to
1425           // the correct location. We leave the old info in place for older
1426           // versions of Firefox to use.
1427           nsTArray<UniquePtr<KeyValue>> strings =
1428               GetSectionStrings(&mProfileDB, mLegacyInstallSection.get());
1429           for (const auto& kv : strings) {
1430             mProfileDB.SetString(mInstallSection.get(), kv->key.get(),
1431                                  kv->value.get());
1432           }
1433 
1434           // Flush now. This causes a small blip in startup but it should be
1435           // one time only whereas not flushing means we have to do this search
1436           // on every startup.
1437           Flush();
1438 
1439           // Now start up with the found profile.
1440           mDedicatedProfile = profile;
1441           mIsFirstRun = false;
1442           break;
1443         }
1444       }
1445     }
1446   }
1447 
1448   // If this is a first run then create a new profile.
1449   if (mIsFirstRun) {
1450     // If we're configured to always show the profile manager then don't create
1451     // a new profile to use.
1452     if (!mStartWithLast) {
1453       return NS_ERROR_SHOW_PROFILE_MANAGER;
1454     }
1455 
1456     if (mUseDedicatedProfile) {
1457       // This is the first run of a dedicated profile install. We have to decide
1458       // whether to use the default profile used by non-dedicated-profile
1459       // installs or to create a new profile.
1460 
1461       // Find what would have been the default profile for old installs.
1462       nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
1463       if (mUseDevEditionProfile) {
1464         profile = mDevEditionDefault;
1465       }
1466 
1467       if (profile) {
1468         nsCOMPtr<nsIFile> rootDir;
1469         profile->GetRootDir(getter_AddRefs(rootDir));
1470 
1471         nsCOMPtr<nsIFile> compat;
1472         rootDir->Clone(getter_AddRefs(compat));
1473         compat->Append(COMPAT_FILE);
1474 
1475         bool exists;
1476         rv = compat->Exists(&exists);
1477         NS_ENSURE_SUCCESS(rv, rv);
1478 
1479         // If the file is missing then either this is an empty profile (likely
1480         // generated by bug 1518591) or it is from an ancient version. We'll opt
1481         // to leave it for older versions in this case.
1482         if (exists) {
1483           bool result;
1484           rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1485           NS_ENSURE_SUCCESS(rv, rv);
1486           if (result) {
1487             mStartupReason = NS_LITERAL_STRING("firstrun-claimed-default");
1488 
1489             mCurrent = profile;
1490             rootDir.forget(aRootDir);
1491             profile->GetLocalDir(aLocalDir);
1492             profile.forget(aProfile);
1493             return NS_OK;
1494           }
1495 
1496           // We're going to create a new profile for this install. If there was
1497           // a potential previous default to use then the user may be confused
1498           // over why we're not using that anymore so set a flag for the front
1499           // end to use to notify the user about what has happened.
1500           mCreatedAlternateProfile = true;
1501         }
1502       }
1503     }
1504 
1505     rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1506     if (NS_SUCCEEDED(rv)) {
1507       // If there is only one profile and it isn't meant to be the profile that
1508       // older versions of Firefox use then we must create a default profile
1509       // for older versions of Firefox to avoid the existing profile being
1510       // auto-selected.
1511       if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
1512           mProfiles.getFirst() == mProfiles.getLast()) {
1513         nsCOMPtr<nsIToolkitProfile> newProfile;
1514         CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
1515                       getter_AddRefs(newProfile));
1516         SetNormalDefault(newProfile);
1517       }
1518 
1519       rv = Flush();
1520       NS_ENSURE_SUCCESS(rv, rv);
1521 
1522       if (mCreatedAlternateProfile) {
1523         mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default");
1524       } else {
1525         mStartupReason = NS_LITERAL_STRING("firstrun-created-default");
1526       }
1527 
1528       // Use the new profile.
1529       mCurrent->GetRootDir(aRootDir);
1530       mCurrent->GetLocalDir(aLocalDir);
1531       NS_ADDREF(*aProfile = mCurrent);
1532 
1533       *aDidCreate = true;
1534       return NS_OK;
1535     }
1536   }
1537 
1538   GetDefaultProfile(getter_AddRefs(mCurrent));
1539 
1540   // None of the profiles was marked as default (generally only happens if the
1541   // user modifies profiles.ini manually). Let the user choose.
1542   if (!mCurrent) {
1543     return NS_ERROR_SHOW_PROFILE_MANAGER;
1544   }
1545 
1546   // Let the caller know that the profile was selected by default.
1547   *aWasDefaultSelection = true;
1548   mStartupReason = NS_LITERAL_STRING("default");
1549 
1550   // Use the selected profile.
1551   mCurrent->GetRootDir(aRootDir);
1552   mCurrent->GetLocalDir(aLocalDir);
1553   NS_ADDREF(*aProfile = mCurrent);
1554 
1555   return NS_OK;
1556 }
1557 
1558 /**
1559  * Creates a new profile for reset and mark it as the current profile.
1560  */
CreateResetProfile(nsIToolkitProfile ** aNewProfile)1561 nsresult nsToolkitProfileService::CreateResetProfile(
1562     nsIToolkitProfile** aNewProfile) {
1563   nsAutoCString oldProfileName;
1564   mCurrent->GetName(oldProfileName);
1565 
1566   nsCOMPtr<nsIToolkitProfile> newProfile;
1567   // Make the new profile name the old profile (or "default-") + the time in
1568   // seconds since epoch for uniqueness.
1569   nsAutoCString newProfileName;
1570   if (!oldProfileName.IsEmpty()) {
1571     newProfileName.Assign(oldProfileName);
1572     newProfileName.Append("-");
1573   } else {
1574     newProfileName.AssignLiteral("default-");
1575   }
1576   newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
1577   nsresult rv = CreateProfile(nullptr,  // choose a default dir for us
1578                               newProfileName, getter_AddRefs(newProfile));
1579   if (NS_FAILED(rv)) return rv;
1580 
1581   mCurrent = newProfile;
1582   newProfile.forget(aNewProfile);
1583 
1584   // Don't flush the changes yet. That will happen once the migration
1585   // successfully completes.
1586   return NS_OK;
1587 }
1588 
1589 /**
1590  * This is responsible for deleting the old profile, copying its name to the
1591  * current profile and if the old profile was default making the new profile
1592  * default as well.
1593  */
ApplyResetProfile(nsIToolkitProfile * aOldProfile)1594 nsresult nsToolkitProfileService::ApplyResetProfile(
1595     nsIToolkitProfile* aOldProfile) {
1596   // If the old profile would have been the default for old installs then mark
1597   // the new profile as such.
1598   if (mNormalDefault == aOldProfile) {
1599     SetNormalDefault(mCurrent);
1600   }
1601 
1602   if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
1603     bool wasLocked = false;
1604     nsCString val;
1605     if (NS_SUCCEEDED(
1606             mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
1607       wasLocked = val.Equals("1");
1608     }
1609 
1610     SetDefaultProfile(mCurrent);
1611 
1612     // Make the locked state match if necessary.
1613     if (!wasLocked) {
1614       mProfileDB.DeleteString(mInstallSection.get(), "Locked");
1615     }
1616   }
1617 
1618   nsCString name;
1619   nsresult rv = aOldProfile->GetName(name);
1620   NS_ENSURE_SUCCESS(rv, rv);
1621 
1622   // Don't remove the old profile's files until after we've successfully flushed
1623   // the profile changes to disk.
1624   rv = aOldProfile->Remove(false);
1625   NS_ENSURE_SUCCESS(rv, rv);
1626 
1627   // Switching the name will make this the default for dev-edition if
1628   // appropriate.
1629   rv = mCurrent->SetName(name);
1630   NS_ENSURE_SUCCESS(rv, rv);
1631 
1632   rv = Flush();
1633   NS_ENSURE_SUCCESS(rv, rv);
1634 
1635   // Now that the profile changes are flushed, try to remove the old profile's
1636   // files. If we fail the worst that will happen is that an orphan directory is
1637   // left. Let this run in the background while we start up.
1638   RemoveProfileFiles(aOldProfile, true);
1639 
1640   return NS_OK;
1641 }
1642 
1643 NS_IMETHODIMP
GetProfileByName(const nsACString & aName,nsIToolkitProfile ** aResult)1644 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
1645                                           nsIToolkitProfile** aResult) {
1646   for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1647     if (profile->mName.Equals(aName)) {
1648       NS_ADDREF(*aResult = profile);
1649       return NS_OK;
1650     }
1651   }
1652 
1653   return NS_ERROR_FAILURE;
1654 }
1655 
1656 /**
1657  * Finds a profile from the database that uses the given root and local
1658  * directories.
1659  */
GetProfileByDir(nsIFile * aRootDir,nsIFile * aLocalDir,nsIToolkitProfile ** aResult)1660 void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
1661                                               nsIFile* aLocalDir,
1662                                               nsIToolkitProfile** aResult) {
1663   for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1664     bool equal;
1665     nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
1666     if (NS_SUCCEEDED(rv) && equal) {
1667       if (!aLocalDir) {
1668         // If no local directory was given then we will just use the normal
1669         // local directory for the profile.
1670         profile.forget(aResult);
1671         return;
1672       }
1673 
1674       rv = profile->mLocalDir->Equals(aLocalDir, &equal);
1675       if (NS_SUCCEEDED(rv) && equal) {
1676         profile.forget(aResult);
1677         return;
1678       }
1679     }
1680   }
1681 }
1682 
NS_LockProfilePath(nsIFile * aPath,nsIFile * aTempPath,nsIProfileUnlocker ** aUnlocker,nsIProfileLock ** aResult)1683 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
1684                             nsIProfileUnlocker** aUnlocker,
1685                             nsIProfileLock** aResult) {
1686   RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
1687   if (!lock) return NS_ERROR_OUT_OF_MEMORY;
1688 
1689   nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
1690   if (NS_FAILED(rv)) return rv;
1691 
1692   lock.forget(aResult);
1693   return NS_OK;
1694 }
1695 
SaltProfileName(nsACString & aName)1696 static void SaltProfileName(nsACString& aName) {
1697   char salt[9];
1698   NS_MakeRandomString(salt, 8);
1699   salt[8] = '.';
1700 
1701   aName.Insert(salt, 0, 9);
1702 }
1703 
1704 NS_IMETHODIMP
CreateUniqueProfile(nsIFile * aRootDir,const nsACString & aNamePrefix,nsIToolkitProfile ** aResult)1705 nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
1706                                              const nsACString& aNamePrefix,
1707                                              nsIToolkitProfile** aResult) {
1708   nsCOMPtr<nsIToolkitProfile> profile;
1709   nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
1710   if (NS_FAILED(rv)) {
1711     return CreateProfile(aRootDir, aNamePrefix, aResult);
1712   }
1713 
1714   uint32_t suffix = 1;
1715   while (true) {
1716     nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
1717                          suffix);
1718     rv = GetProfileByName(name, getter_AddRefs(profile));
1719     if (NS_FAILED(rv)) {
1720       return CreateProfile(aRootDir, name, aResult);
1721     }
1722     suffix++;
1723   }
1724 }
1725 
1726 NS_IMETHODIMP
CreateProfile(nsIFile * aRootDir,const nsACString & aName,nsIToolkitProfile ** aResult)1727 nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
1728                                        const nsACString& aName,
1729                                        nsIToolkitProfile** aResult) {
1730   nsresult rv = GetProfileByName(aName, aResult);
1731   if (NS_SUCCEEDED(rv)) {
1732     return rv;
1733   }
1734 
1735   nsCOMPtr<nsIFile> rootDir(aRootDir);
1736 
1737   nsAutoCString dirName;
1738   if (!rootDir) {
1739     rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
1740     NS_ENSURE_SUCCESS(rv, rv);
1741 
1742     dirName = aName;
1743     SaltProfileName(dirName);
1744 
1745     if (NS_IsNativeUTF8()) {
1746       rootDir->AppendNative(dirName);
1747     } else {
1748       rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
1749     }
1750   }
1751 
1752   nsCOMPtr<nsIFile> localDir;
1753 
1754   bool isRelative;
1755   rv = mAppData->Contains(rootDir, &isRelative);
1756   if (NS_SUCCEEDED(rv) && isRelative) {
1757     nsAutoCString path;
1758     rv = rootDir->GetRelativeDescriptor(mAppData, path);
1759     NS_ENSURE_SUCCESS(rv, rv);
1760 
1761     rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(localDir));
1762     NS_ENSURE_SUCCESS(rv, rv);
1763 
1764     rv = localDir->SetRelativeDescriptor(mTempData, path);
1765   } else {
1766     localDir = rootDir;
1767   }
1768 
1769   bool exists;
1770   rv = rootDir->Exists(&exists);
1771   NS_ENSURE_SUCCESS(rv, rv);
1772 
1773   if (exists) {
1774     rv = rootDir->IsDirectory(&exists);
1775     NS_ENSURE_SUCCESS(rv, rv);
1776 
1777     if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
1778   } else {
1779     nsCOMPtr<nsIFile> profileDirParent;
1780     nsAutoString profileDirName;
1781 
1782     rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
1783     NS_ENSURE_SUCCESS(rv, rv);
1784 
1785     rv = rootDir->GetLeafName(profileDirName);
1786     NS_ENSURE_SUCCESS(rv, rv);
1787 
1788     // let's ensure that the profile directory exists.
1789     rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1790     NS_ENSURE_SUCCESS(rv, rv);
1791     rv = rootDir->SetPermissions(0700);
1792 #ifndef ANDROID
1793     // If the profile is on the sdcard, this will fail but its non-fatal
1794     NS_ENSURE_SUCCESS(rv, rv);
1795 #endif
1796   }
1797 
1798   rv = localDir->Exists(&exists);
1799   NS_ENSURE_SUCCESS(rv, rv);
1800 
1801   if (!exists) {
1802     rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1803     NS_ENSURE_SUCCESS(rv, rv);
1804   }
1805 
1806   // We created a new profile dir. Let's store a creation timestamp.
1807   // Note that this code path does not apply if the profile dir was
1808   // created prior to launching.
1809   rv = CreateTimesInternal(rootDir);
1810   NS_ENSURE_SUCCESS(rv, rv);
1811 
1812   nsCOMPtr<nsIToolkitProfile> profile =
1813       new nsToolkitProfile(aName, rootDir, localDir, false);
1814   if (!profile) return NS_ERROR_OUT_OF_MEMORY;
1815 
1816   if (aName.Equals(DEV_EDITION_NAME)) {
1817     mDevEditionDefault = profile;
1818   }
1819 
1820   profile.forget(aResult);
1821   return NS_OK;
1822 }
1823 
1824 /**
1825  * Snaps (https://snapcraft.io/) use a different installation directory for
1826  * every version of an application. Since dedicated profiles uses the
1827  * installation directory to determine which profile to use this would lead
1828  * snap users getting a new profile on every application update.
1829  *
1830  * However the only way to have multiple installation of a snap is to install
1831  * a new snap instance. Different snap instances have different user data
1832  * directories and so already will not share profiles, in fact one instance
1833  * will not even be able to see the other instance's profiles since
1834  * profiles.ini will be stored in different places.
1835  *
1836  * So we can just disable dedicated profile support in this case and revert
1837  * back to the old method of just having a single default profile and still
1838  * get essentially the same benefits as dedicated profiles provides.
1839  */
IsSnapEnvironment()1840 bool nsToolkitProfileService::IsSnapEnvironment() {
1841   return !!PR_GetEnv("SNAP_NAME");
1842 }
1843 
1844 /**
1845  * In some situations dedicated profile support does not work well. This
1846  * includes a handful of linux distributions which always install different
1847  * application versions to different locations, some application sandboxing
1848  * systems as well as enterprise deployments. This environment variable provides
1849  * a way to opt out of dedicated profiles for these cases.
1850  *
1851  * For Windows, we provide a policy to accomplish the same thing.
1852  */
UseLegacyProfiles()1853 bool nsToolkitProfileService::UseLegacyProfiles() {
1854   bool legacyProfiles = !!PR_GetEnv("MOZ_LEGACY_PROFILES");
1855 #ifdef XP_WIN
1856   legacyProfiles |= PolicyCheckBoolean(L"LegacyProfiles");
1857 #endif
1858   return legacyProfiles;
1859 }
1860 
1861 struct FindInstallsClosure {
1862   nsINIParser* installData;
1863   nsTArray<nsCString>* installs;
1864 };
1865 
FindInstalls(const char * aSection,void * aClosure)1866 static bool FindInstalls(const char* aSection, void* aClosure) {
1867   FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
1868 
1869   // Check if the section starts with "Install"
1870   if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
1871     return true;
1872   }
1873 
1874   nsCString install(aSection);
1875   closure->installs->AppendElement(install);
1876 
1877   return true;
1878 }
1879 
GetKnownInstalls()1880 nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
1881   nsTArray<nsCString> result;
1882   FindInstallsClosure closure = {&mProfileDB, &result};
1883 
1884   mProfileDB.GetSections(&FindInstalls, &closure);
1885 
1886   return result;
1887 }
1888 
CreateTimesInternal(nsIFile * aProfileDir)1889 nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
1890   nsresult rv = NS_ERROR_FAILURE;
1891   nsCOMPtr<nsIFile> creationLog;
1892   rv = aProfileDir->Clone(getter_AddRefs(creationLog));
1893   NS_ENSURE_SUCCESS(rv, rv);
1894 
1895   rv = creationLog->AppendNative(NS_LITERAL_CSTRING("times.json"));
1896   NS_ENSURE_SUCCESS(rv, rv);
1897 
1898   bool exists = false;
1899   creationLog->Exists(&exists);
1900   if (exists) {
1901     return NS_OK;
1902   }
1903 
1904   rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
1905   NS_ENSURE_SUCCESS(rv, rv);
1906 
1907   // We don't care about microsecond resolution.
1908   int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
1909 
1910   // Write it out.
1911   PRFileDesc* writeFile;
1912   rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
1913   NS_ENSURE_SUCCESS(rv, rv);
1914 
1915   PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
1916   PR_Close(writeFile);
1917   return NS_OK;
1918 }
1919 
1920 NS_IMETHODIMP
GetProfileCount(uint32_t * aResult)1921 nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
1922   *aResult = 0;
1923   for (nsToolkitProfile* profile : mProfiles) {
1924     Unused << profile;
1925     (*aResult)++;
1926   }
1927 
1928   return NS_OK;
1929 }
1930 
1931 NS_IMETHODIMP
Flush()1932 nsToolkitProfileService::Flush() {
1933   if (GetIsListOutdated()) {
1934     return NS_ERROR_DATABASE_CHANGED;
1935   }
1936 
1937   nsresult rv;
1938 
1939   // If we aren't using dedicated profiles then nothing about the list of
1940   // installs can have changed, so no need to update the backup.
1941   if (mUseDedicatedProfile) {
1942     // Export the installs to the backup.
1943     nsTArray<nsCString> installs = GetKnownInstalls();
1944 
1945     if (!installs.IsEmpty()) {
1946       nsCString data;
1947       nsCString buffer;
1948 
1949       for (uint32_t i = 0; i < installs.Length(); i++) {
1950         nsTArray<UniquePtr<KeyValue>> strings =
1951             GetSectionStrings(&mProfileDB, installs[i].get());
1952         if (strings.IsEmpty()) {
1953           continue;
1954         }
1955 
1956         // Strip "Install" from the start.
1957         const nsDependentCSubstring& install =
1958             Substring(installs[i], INSTALL_PREFIX_LENGTH);
1959         data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
1960 
1961         for (uint32_t j = 0; j < strings.Length(); j++) {
1962           data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
1963                             strings[j]->value.get());
1964         }
1965 
1966         data.Append("\n");
1967       }
1968 
1969       FILE* writeFile;
1970       rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
1971       NS_ENSURE_SUCCESS(rv, rv);
1972 
1973       uint32_t length = data.Length();
1974       if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
1975         fclose(writeFile);
1976         return NS_ERROR_UNEXPECTED;
1977       }
1978 
1979       fclose(writeFile);
1980     } else {
1981       rv = mInstallDBFile->Remove(false);
1982       if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
1983           rv != NS_ERROR_FILE_NOT_FOUND) {
1984         return rv;
1985       }
1986     }
1987   }
1988 
1989   rv = mProfileDB.WriteToFile(mProfileDBFile);
1990   NS_ENSURE_SUCCESS(rv, rv);
1991 
1992   rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
1993                        &mProfileDBModifiedTime, &mProfileDBFileSize);
1994   NS_ENSURE_SUCCESS(rv, rv);
1995 
1996   return NS_OK;
1997 }
1998 
NS_IMPL_ISUPPORTS(nsToolkitProfileFactory,nsIFactory)1999 NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
2000 
2001 NS_IMETHODIMP
2002 nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
2003                                         void** aResult) {
2004   if (aOuter) return NS_ERROR_NO_AGGREGATION;
2005 
2006   RefPtr<nsToolkitProfileService> profileService =
2007       nsToolkitProfileService::gService;
2008   if (!profileService) {
2009     nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
2010     if (NS_FAILED(rv)) return rv;
2011   }
2012   return profileService->QueryInterface(aIID, aResult);
2013 }
2014 
2015 NS_IMETHODIMP
LockFactory(bool aVal)2016 nsToolkitProfileFactory::LockFactory(bool aVal) { return NS_OK; }
2017 
NS_NewToolkitProfileFactory(nsIFactory ** aResult)2018 nsresult NS_NewToolkitProfileFactory(nsIFactory** aResult) {
2019   *aResult = new nsToolkitProfileFactory();
2020   if (!*aResult) return NS_ERROR_OUT_OF_MEMORY;
2021 
2022   NS_ADDREF(*aResult);
2023   return NS_OK;
2024 }
2025 
NS_NewToolkitProfileService(nsToolkitProfileService ** aResult)2026 nsresult NS_NewToolkitProfileService(nsToolkitProfileService** aResult) {
2027   nsToolkitProfileService* profileService = new nsToolkitProfileService();
2028   if (!profileService) return NS_ERROR_OUT_OF_MEMORY;
2029   nsresult rv = profileService->Init();
2030   if (NS_FAILED(rv)) {
2031     NS_ERROR("nsToolkitProfileService::Init failed!");
2032     delete profileService;
2033     return rv;
2034   }
2035 
2036   NS_ADDREF(*aResult = profileService);
2037   return NS_OK;
2038 }
2039 
XRE_GetFileFromPath(const char * aPath,nsIFile ** aResult)2040 nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
2041 #if defined(XP_MACOSX)
2042   int32_t pathLen = strlen(aPath);
2043   if (pathLen > MAXPATHLEN) return NS_ERROR_INVALID_ARG;
2044 
2045   CFURLRef fullPath = CFURLCreateFromFileSystemRepresentation(
2046       nullptr, (const UInt8*)aPath, pathLen, true);
2047   if (!fullPath) return NS_ERROR_FAILURE;
2048 
2049   nsCOMPtr<nsIFile> lf;
2050   nsresult rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(lf));
2051   if (NS_SUCCEEDED(rv)) {
2052     nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
2053     if (NS_SUCCEEDED(rv)) {
2054       rv = lfMac->InitWithCFURL(fullPath);
2055       if (NS_SUCCEEDED(rv)) {
2056         lf.forget(aResult);
2057       }
2058     }
2059   }
2060   CFRelease(fullPath);
2061   return rv;
2062 
2063 #elif defined(XP_UNIX)
2064   char fullPath[MAXPATHLEN];
2065 
2066   if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
2067 
2068   return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
2069 #elif defined(XP_WIN)
2070   WCHAR fullPath[MAXPATHLEN];
2071 
2072   if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
2073     return NS_ERROR_FAILURE;
2074 
2075   return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
2076 
2077 #else
2078 #  error Platform-specific logic needed here.
2079 #endif
2080 }
2081