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