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