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