summaryrefslogtreecommitdiffstats
path: root/extensions/cookie
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/cookie')
-rw-r--r--extensions/cookie/moz.build30
-rw-r--r--extensions/cookie/nsCookieModule.cpp51
-rw-r--r--extensions/cookie/nsCookiePermission.cpp273
-rw-r--r--extensions/cookie/nsCookiePermission.h48
-rw-r--r--extensions/cookie/nsCookiePromptService.cpp101
-rw-r--r--extensions/cookie/nsCookiePromptService.h29
-rw-r--r--extensions/cookie/nsICookieAcceptDialog.idl21
-rw-r--r--extensions/cookie/nsICookiePromptService.idl45
-rw-r--r--extensions/cookie/nsPermission.cpp201
-rw-r--r--extensions/cookie/nsPermission.h43
-rw-r--r--extensions/cookie/nsPermissionManager.cpp2918
-rw-r--r--extensions/cookie/nsPermissionManager.h295
-rw-r--r--extensions/cookie/nsPopupWindowManager.cpp111
-rw-r--r--extensions/cookie/nsPopupWindowManager.h39
-rw-r--r--extensions/cookie/test/beltzner.jpgbin0 -> 9995 bytes
-rw-r--r--extensions/cookie/test/beltzner.jpg^headers^3
-rw-r--r--extensions/cookie/test/browser.ini3
-rw-r--r--extensions/cookie/test/browser_test_favicon.js28
-rw-r--r--extensions/cookie/test/damonbowling.jpgbin0 -> 44008 bytes
-rw-r--r--extensions/cookie/test/damonbowling.jpg^headers^2
-rw-r--r--extensions/cookie/test/file_chromecommon.js15
-rw-r--r--extensions/cookie/test/file_domain_hierarchy_inner.html14
-rw-r--r--extensions/cookie/test/file_domain_hierarchy_inner_inner.html14
-rw-r--r--extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html14
-rw-r--r--extensions/cookie/test/file_domain_inner.html14
-rw-r--r--extensions/cookie/test/file_domain_inner_inner.html14
-rw-r--r--extensions/cookie/test/file_image_inner.html15
-rw-r--r--extensions/cookie/test/file_image_inner_inner.html20
-rw-r--r--extensions/cookie/test/file_loadflags_inner.html17
-rw-r--r--extensions/cookie/test/file_localhost_inner.html14
-rw-r--r--extensions/cookie/test/file_loopback_inner.html14
-rw-r--r--extensions/cookie/test/file_subdomain_inner.html14
-rw-r--r--extensions/cookie/test/file_testcommon.js70
-rw-r--r--extensions/cookie/test/file_testloadflags.js104
-rw-r--r--extensions/cookie/test/file_testloadflags_chromescript.js112
-rw-r--r--extensions/cookie/test/image1.pngbin0 -> 821 bytes
-rw-r--r--extensions/cookie/test/image1.png^headers^3
-rw-r--r--extensions/cookie/test/image2.pngbin0 -> 821 bytes
-rw-r--r--extensions/cookie/test/image2.png^headers^3
-rw-r--r--extensions/cookie/test/mochitest.ini41
-rw-r--r--extensions/cookie/test/moz.build15
-rw-r--r--extensions/cookie/test/test1.css2
-rw-r--r--extensions/cookie/test/test1.css^headers^3
-rw-r--r--extensions/cookie/test/test2.css2
-rw-r--r--extensions/cookie/test/test2.css^headers^3
-rw-r--r--extensions/cookie/test/test_different_domain_in_hierarchy.html15
-rw-r--r--extensions/cookie/test/test_differentdomain.html15
-rw-r--r--extensions/cookie/test/test_image.html14
-rw-r--r--extensions/cookie/test/test_loadflags.html21
-rw-r--r--extensions/cookie/test/test_same_base_domain.html15
-rw-r--r--extensions/cookie/test/test_same_base_domain_2.html15
-rw-r--r--extensions/cookie/test/test_same_base_domain_3.html15
-rw-r--r--extensions/cookie/test/test_same_base_domain_4.html15
-rw-r--r--extensions/cookie/test/test_same_base_domain_5.html15
-rw-r--r--extensions/cookie/test/test_same_base_domain_6.html15
-rw-r--r--extensions/cookie/test/test_samedomain.html15
-rw-r--r--extensions/cookie/test/unit/cookieprompt.js22
-rw-r--r--extensions/cookie/test/unit/cookieprompt.manifest2
-rw-r--r--extensions/cookie/test/unit/head_cookies.js570
-rw-r--r--extensions/cookie/test/unit/test_bug526789.js248
-rw-r--r--extensions/cookie/test/unit/test_bug650522.js16
-rw-r--r--extensions/cookie/test/unit/test_bug667087.js16
-rw-r--r--extensions/cookie/test/unit/test_cookies_async_failure.js600
-rw-r--r--extensions/cookie/test/unit/test_cookies_persistence.js78
-rw-r--r--extensions/cookie/test/unit/test_cookies_privatebrowsing.js115
-rw-r--r--extensions/cookie/test/unit/test_cookies_profile_close.js92
-rw-r--r--extensions/cookie/test/unit/test_cookies_read.js122
-rw-r--r--extensions/cookie/test/unit/test_cookies_sync_failure.js286
-rw-r--r--extensions/cookie/test/unit/test_cookies_thirdparty.js147
-rw-r--r--extensions/cookie/test/unit/test_cookies_thirdparty_session.js69
-rw-r--r--extensions/cookie/test/unit/test_domain_eviction.js153
-rw-r--r--extensions/cookie/test/unit/test_eviction.js249
-rw-r--r--extensions/cookie/test/unit/test_permmanager_cleardata.js68
-rw-r--r--extensions/cookie/test/unit/test_permmanager_defaults.js295
-rw-r--r--extensions/cookie/test/unit/test_permmanager_expiration.js82
-rw-r--r--extensions/cookie/test/unit/test_permmanager_getAllForURI.js78
-rw-r--r--extensions/cookie/test/unit/test_permmanager_getPermissionObject.js95
-rw-r--r--extensions/cookie/test/unit/test_permmanager_idn.js49
-rw-r--r--extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js142
-rw-r--r--extensions/cookie/test/unit/test_permmanager_local_files.js43
-rw-r--r--extensions/cookie/test/unit/test_permmanager_matches.js183
-rw-r--r--extensions/cookie/test/unit/test_permmanager_matchesuri.js150
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_4-7.js207
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js226
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js284
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js168
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js284
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js162
-rw-r--r--extensions/cookie/test/unit/test_permmanager_migrate_7-8.js246
-rw-r--r--extensions/cookie/test/unit/test_permmanager_notifications.js140
-rw-r--r--extensions/cookie/test/unit/test_permmanager_removeall.js36
-rw-r--r--extensions/cookie/test/unit/test_permmanager_removeforapp.js99
-rw-r--r--extensions/cookie/test/unit/test_permmanager_removepermission.js67
-rw-r--r--extensions/cookie/test/unit/test_permmanager_removesince.js69
-rw-r--r--extensions/cookie/test/unit/test_permmanager_subdomains.js57
-rw-r--r--extensions/cookie/test/unit/test_schema_2_migration.js207
-rw-r--r--extensions/cookie/test/unit/test_schema_3_migration.js125
-rw-r--r--extensions/cookie/test/unit/xpcshell.ini48
-rw-r--r--extensions/cookie/test/unit_ipc/test_child.js59
-rw-r--r--extensions/cookie/test/unit_ipc/test_parent.js59
-rw-r--r--extensions/cookie/test/unit_ipc/xpcshell.ini7
101 files changed, 11468 insertions, 0 deletions
diff --git a/extensions/cookie/moz.build b/extensions/cookie/moz.build
new file mode 100644
index 000000000..36de41f95
--- /dev/null
+++ b/extensions/cookie/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['test']
+
+XPIDL_SOURCES += [
+ 'nsICookieAcceptDialog.idl',
+ 'nsICookiePromptService.idl',
+]
+
+XPIDL_MODULE = 'cookie'
+
+UNIFIED_SOURCES += [
+ 'nsCookieModule.cpp',
+ 'nsCookiePermission.cpp',
+ 'nsCookiePromptService.cpp',
+ 'nsPermission.cpp',
+ 'nsPermissionManager.cpp',
+ 'nsPopupWindowManager.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/extensions/cookie/nsCookieModule.cpp b/extensions/cookie/nsCookieModule.cpp
new file mode 100644
index 000000000..1157e0c5d
--- /dev/null
+++ b/extensions/cookie/nsCookieModule.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "mozilla/ModuleUtils.h"
+#include "nsIServiceManager.h"
+#include "nsPermissionManager.h"
+#include "nsPopupWindowManager.h"
+#include "nsICategoryManager.h"
+#include "nsCookiePromptService.h"
+#include "nsCookiePermission.h"
+#include "nsXPIDLString.h"
+
+// Define the constructor function for the objects
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPermissionManager,
+ nsPermissionManager::GetXPCOMSingleton)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPopupWindowManager, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCookiePermission)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsCookiePromptService)
+
+NS_DEFINE_NAMED_CID(NS_PERMISSIONMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_POPUPWINDOWMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_COOKIEPROMPTSERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_COOKIEPERMISSION_CID);
+
+
+static const mozilla::Module::CIDEntry kCookieCIDs[] = {
+ { &kNS_PERMISSIONMANAGER_CID, false, nullptr, nsIPermissionManagerConstructor },
+ { &kNS_POPUPWINDOWMANAGER_CID, false, nullptr, nsPopupWindowManagerConstructor },
+ { &kNS_COOKIEPROMPTSERVICE_CID, false, nullptr, nsCookiePromptServiceConstructor },
+ { &kNS_COOKIEPERMISSION_CID, false, nullptr, nsCookiePermissionConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kCookieContracts[] = {
+ { NS_PERMISSIONMANAGER_CONTRACTID, &kNS_PERMISSIONMANAGER_CID },
+ { NS_POPUPWINDOWMANAGER_CONTRACTID, &kNS_POPUPWINDOWMANAGER_CID },
+ { NS_COOKIEPROMPTSERVICE_CONTRACTID, &kNS_COOKIEPROMPTSERVICE_CID },
+ { NS_COOKIEPERMISSION_CONTRACTID, &kNS_COOKIEPERMISSION_CID },
+ { nullptr }
+};
+
+static const mozilla::Module kCookieModule = {
+ mozilla::Module::kVersion,
+ kCookieCIDs,
+ kCookieContracts
+};
+
+NSMODULE_DEFN(nsCookieModule) = &kCookieModule;
diff --git a/extensions/cookie/nsCookiePermission.cpp b/extensions/cookie/nsCookiePermission.cpp
new file mode 100644
index 000000000..7dfc943f0
--- /dev/null
+++ b/extensions/cookie/nsCookiePermission.cpp
@@ -0,0 +1,273 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCookiePermission.h"
+
+#include "mozIThirdPartyUtil.h"
+#include "nsICookie2.h"
+#include "nsIServiceManager.h"
+#include "nsICookiePromptService.h"
+#include "nsICookieManager2.h"
+#include "nsNetUtil.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIDOMWindow.h"
+#include "nsIPrincipal.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nsILoadContext.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsNetCID.h"
+#include "prtime.h"
+
+/****************************************************************
+ ************************ nsCookiePermission ********************
+ ****************************************************************/
+
+// values for mCookiesLifetimePolicy
+// 0 == accept normally
+// 1 == ask before accepting, no more supported, treated like ACCEPT_NORMALLY (Bug 606655).
+// 2 == downgrade to session
+// 3 == limit lifetime to N days
+static const uint32_t ACCEPT_NORMALLY = 0;
+static const uint32_t ASK_BEFORE_ACCEPT = 1;
+static const uint32_t ACCEPT_SESSION = 2;
+static const uint32_t ACCEPT_FOR_N_DAYS = 3;
+
+static const bool kDefaultPolicy = true;
+static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
+static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
+
+static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
+// obsolete pref names for migration
+static const char kCookiesLifetimeEnabled[] = "network.cookie.lifetime.enabled";
+static const char kCookiesLifetimeBehavior[] = "network.cookie.lifetime.behavior";
+
+static const char kPermissionType[] = "cookie";
+
+NS_IMPL_ISUPPORTS(nsCookiePermission,
+ nsICookiePermission,
+ nsIObserver)
+
+bool
+nsCookiePermission::Init()
+{
+ // Initialize nsIPermissionManager and fetch relevant prefs. This is only
+ // required for some methods on nsICookiePermission, so it should be done
+ // lazily.
+ nsresult rv;
+ mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+ mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return false;
+
+ // failure to access the pref service is non-fatal...
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch) {
+ prefBranch->AddObserver(kCookiesLifetimePolicy, this, false);
+ prefBranch->AddObserver(kCookiesLifetimeDays, this, false);
+ PrefChanged(prefBranch, nullptr);
+
+ // migration code for original cookie prefs
+ bool migrated;
+ rv = prefBranch->GetBoolPref(kCookiesPrefsMigrated, &migrated);
+ if (NS_FAILED(rv) || !migrated) {
+ bool lifetimeEnabled = false;
+ prefBranch->GetBoolPref(kCookiesLifetimeEnabled, &lifetimeEnabled);
+
+ // if they're limiting lifetime, use the appropriate limited lifetime pref
+ if (lifetimeEnabled) {
+ int32_t lifetimeBehavior;
+ prefBranch->GetIntPref(kCookiesLifetimeBehavior, &lifetimeBehavior);
+ if (lifetimeBehavior)
+ prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_FOR_N_DAYS);
+ else
+ prefBranch->SetIntPref(kCookiesLifetimePolicy, ACCEPT_SESSION);
+ }
+ prefBranch->SetBoolPref(kCookiesPrefsMigrated, true);
+ }
+ }
+
+ return true;
+}
+
+void
+nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
+ const char *aPref)
+{
+ int32_t val;
+
+#define PREF_CHANGED(_P) (!aPref || !strcmp(aPref, _P))
+
+ if (PREF_CHANGED(kCookiesLifetimePolicy) &&
+ NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val))) {
+ if (val != static_cast<int32_t>(ACCEPT_SESSION) && val != static_cast<int32_t>(ACCEPT_FOR_N_DAYS)) {
+ val = ACCEPT_NORMALLY;
+ }
+ mCookiesLifetimePolicy = val;
+ }
+
+ if (PREF_CHANGED(kCookiesLifetimeDays) &&
+ NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
+ // save cookie lifetime in seconds instead of days
+ mCookiesLifetimeSec = (int64_t)val * 24 * 60 * 60;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::SetAccess(nsIURI *aURI,
+ nsCookieAccess aAccess)
+{
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ //
+ // NOTE: nsCookieAccess values conveniently match up with
+ // the permission codes used by nsIPermissionManager.
+ // this is nice because it avoids conversion code.
+ //
+ return mPermMgr->Add(aURI, kPermissionType, aAccess,
+ nsIPermissionManager::EXPIRE_NEVER, 0);
+}
+
+NS_IMETHODIMP
+nsCookiePermission::CanAccess(nsIURI *aURI,
+ nsIChannel *aChannel,
+ nsCookieAccess *aResult)
+{
+ // Check this protocol doesn't allow cookies
+ bool hasFlags;
+ nsresult rv =
+ NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS,
+ &hasFlags);
+ if (NS_FAILED(rv) || hasFlags) {
+ *aResult = ACCESS_DENY;
+ return NS_OK;
+ }
+
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ // finally, check with permission manager...
+ rv = mPermMgr->TestPermission(aURI, kPermissionType, (uint32_t *) aResult);
+ if (NS_SUCCEEDED(rv)) {
+ if (*aResult == nsICookiePermission::ACCESS_SESSION) {
+ *aResult = nsICookiePermission::ACCESS_ALLOW;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::CanSetCookie(nsIURI *aURI,
+ nsIChannel *aChannel,
+ nsICookie2 *aCookie,
+ bool *aIsSession,
+ int64_t *aExpiry,
+ bool *aResult)
+{
+ NS_ASSERTION(aURI, "null uri");
+
+ *aResult = kDefaultPolicy;
+
+ // Lazily initialize ourselves
+ if (!EnsureInitialized())
+ return NS_ERROR_UNEXPECTED;
+
+ uint32_t perm;
+ mPermMgr->TestPermission(aURI, kPermissionType, &perm);
+ bool isThirdParty = false;
+ switch (perm) {
+ case nsICookiePermission::ACCESS_SESSION:
+ *aIsSession = true;
+ MOZ_FALLTHROUGH;
+
+ case nsICookiePermission::ACCESS_ALLOW:
+ *aResult = true;
+ break;
+
+ case nsICookiePermission::ACCESS_DENY:
+ *aResult = false;
+ break;
+
+ case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty);
+ // If it's third party, we can't set the cookie
+ if (isThirdParty)
+ *aResult = false;
+ break;
+
+ case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY:
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &isThirdParty);
+ // If it's third party, check whether cookies are already set
+ if (isThirdParty) {
+ nsresult rv;
+ nsCOMPtr<nsICookieManager2> cookieManager = do_GetService(NS_COOKIEMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ *aResult = false;
+ break;
+ }
+ uint32_t priorCookieCount = 0;
+ nsAutoCString hostFromURI;
+ aURI->GetHost(hostFromURI);
+ cookieManager->CountCookiesFromHost(hostFromURI, &priorCookieCount);
+ *aResult = priorCookieCount != 0;
+ }
+ break;
+
+ default:
+ // the permission manager has nothing to say about this cookie -
+ // so, we apply the default prefs to it.
+ NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
+
+ // now we need to figure out what type of accept policy we're dealing with
+ // if we accept cookies normally, just bail and return
+ if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ // declare this here since it'll be used in all of the remaining cases
+ int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
+ int64_t delta = *aExpiry - currentTime;
+
+ // We are accepting the cookie, but,
+ // if it's not a session cookie, we may have to limit its lifetime.
+ if (!*aIsSession && delta > 0) {
+ if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
+ // limit lifetime to session
+ *aIsSession = true;
+ } else if (delta > mCookiesLifetimeSec) {
+ // limit lifetime to specified time
+ *aExpiry = currentTime + mCookiesLifetimeSec;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCookiePermission::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
+ "unexpected topic - we only deal with pref changes!");
+
+ if (prefBranch)
+ PrefChanged(prefBranch, NS_LossyConvertUTF16toASCII(aData).get());
+ return NS_OK;
+}
diff --git a/extensions/cookie/nsCookiePermission.h b/extensions/cookie/nsCookiePermission.h
new file mode 100644
index 000000000..d683f206a
--- /dev/null
+++ b/extensions/cookie/nsCookiePermission.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCookiePermission_h__
+#define nsCookiePermission_h__
+
+#include "nsICookiePermission.h"
+#include "nsIPermissionManager.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "mozIThirdPartyUtil.h"
+
+class nsIPrefBranch;
+
+class nsCookiePermission : public nsICookiePermission
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOOKIEPERMISSION
+ NS_DECL_NSIOBSERVER
+
+ nsCookiePermission()
+ : mCookiesLifetimeSec(INT64_MAX)
+ , mCookiesLifetimePolicy(0) // ACCEPT_NORMALLY
+ {}
+
+ bool Init();
+ void PrefChanged(nsIPrefBranch *, const char *);
+
+private:
+ virtual ~nsCookiePermission() {}
+
+ bool EnsureInitialized() { return (mPermMgr != nullptr && mThirdPartyUtil != nullptr) || Init(); };
+
+ nsCOMPtr<nsIPermissionManager> mPermMgr;
+ nsCOMPtr<mozIThirdPartyUtil> mThirdPartyUtil;
+
+ int64_t mCookiesLifetimeSec; // lifetime limit specified in seconds
+ uint8_t mCookiesLifetimePolicy; // pref for how long cookies are stored
+};
+
+// {EF565D0A-AB9A-4A13-9160-0644CDFD859A}
+#define NS_COOKIEPERMISSION_CID \
+ {0xEF565D0A, 0xAB9A, 0x4A13, {0x91, 0x60, 0x06, 0x44, 0xcd, 0xfd, 0x85, 0x9a }}
+
+#endif
diff --git a/extensions/cookie/nsCookiePromptService.cpp b/extensions/cookie/nsCookiePromptService.cpp
new file mode 100644
index 000000000..258193ab8
--- /dev/null
+++ b/extensions/cookie/nsCookiePromptService.cpp
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "nsCookiePromptService.h"
+#include "nsICookie.h"
+#include "nsICookieAcceptDialog.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWindowWatcher.h"
+#include "nsIServiceManager.h"
+#include "nsString.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIMutableArray.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+/****************************************************************
+ ************************ nsCookiePromptService *****************
+ ****************************************************************/
+
+NS_IMPL_ISUPPORTS(nsCookiePromptService, nsICookiePromptService)
+
+nsCookiePromptService::nsCookiePromptService() {
+}
+
+nsCookiePromptService::~nsCookiePromptService() {
+}
+
+NS_IMETHODIMP
+nsCookiePromptService::CookieDialog(mozIDOMWindowProxy *aParent,
+ nsICookie *aCookie,
+ const nsACString &aHostname,
+ int32_t aCookiesFromHost,
+ bool aChangingCookie,
+ bool *aRememberDecision,
+ int32_t *aAccept)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIDialogParamBlock> block = do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID,&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetInt(nsICookieAcceptDialog::ACCEPT_COOKIE, 1);
+ block->SetString(nsICookieAcceptDialog::HOSTNAME, NS_ConvertUTF8toUTF16(aHostname).get());
+ block->SetInt(nsICookieAcceptDialog::COOKIESFROMHOST, aCookiesFromHost);
+ block->SetInt(nsICookieAcceptDialog::CHANGINGCOOKIE, aChangingCookie ? 1 : 0);
+
+ nsCOMPtr<nsIMutableArray> objects =
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = objects->AppendElement(aCookie, false);
+ if (NS_FAILED(rv)) return rv;
+
+ block->SetObjects(objects);
+
+ nsCOMPtr<nsIWindowWatcher> wwatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsISupports> arguments = do_QueryInterface(block);
+
+ nsCOMPtr<mozIDOMWindowProxy> parent(aParent);
+ if (!parent) // if no parent provided, consult the window watcher:
+ wwatcher->GetActiveWindow(getter_AddRefs(parent));
+
+ if (parent) {
+ auto* privateParent = nsPIDOMWindowOuter::From(parent);
+ if (privateParent)
+ privateParent = privateParent->GetPrivateRoot();
+ parent = privateParent;
+ }
+
+ // We're opening a chrome window and passing in a nsIDialogParamBlock. Setting
+ // the nsIDialogParamBlock as the .arguments property on the chrome window
+ // requires system principals on the stack, so we use an AutoNoJSAPI for that.
+ mozilla::dom::AutoNoJSAPI nojsapi;
+
+ // The cookie dialog will be modal for the root chrome window rather than the
+ // tab containing the permission-requesting page. This removes confusion
+ // about which monitor is displaying the dialog (see bug 470356), but also
+ // avoids unwanted tab switches (see bug 405239).
+ nsCOMPtr<mozIDOMWindowProxy> dialog;
+ rv = wwatcher->OpenWindow(parent, "chrome://cookie/content/cookieAcceptDialog.xul", "_blank",
+ "centerscreen,chrome,modal,titlebar", arguments,
+ getter_AddRefs(dialog));
+
+ if (NS_FAILED(rv)) return rv;
+
+ // get back output parameters
+ int32_t tempValue;
+ block->GetInt(nsICookieAcceptDialog::ACCEPT_COOKIE, &tempValue);
+ *aAccept = tempValue;
+
+ // GetInt returns a int32_t; we need to sanitize it into bool
+ block->GetInt(nsICookieAcceptDialog::REMEMBER_DECISION, &tempValue);
+ *aRememberDecision = (tempValue == 1);
+
+ return rv;
+}
+
diff --git a/extensions/cookie/nsCookiePromptService.h b/extensions/cookie/nsCookiePromptService.h
new file mode 100644
index 000000000..a6e10fce4
--- /dev/null
+++ b/extensions/cookie/nsCookiePromptService.h
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCookiePromptService_h__
+#define nsCookiePromptService_h__
+
+#include "nsICookiePromptService.h"
+
+class nsCookiePromptService : public nsICookiePromptService {
+
+ virtual ~nsCookiePromptService();
+
+public:
+
+ nsCookiePromptService();
+
+ NS_DECL_NSICOOKIEPROMPTSERVICE
+ NS_DECL_ISUPPORTS
+
+private:
+
+};
+
+// {CE002B28-92B7-4701-8621-CC925866FB87}
+#define NS_COOKIEPROMPTSERVICE_CID \
+ {0xCE002B28, 0x92B7, 0x4701, {0x86, 0x21, 0xCC, 0x92, 0x58, 0x66, 0xFB, 0x87}}
+
+#endif
diff --git a/extensions/cookie/nsICookieAcceptDialog.idl b/extensions/cookie/nsICookieAcceptDialog.idl
new file mode 100644
index 000000000..b02339780
--- /dev/null
+++ b/extensions/cookie/nsICookieAcceptDialog.idl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/*
+
+ This file contains some constants for the cookie accept dialog
+
+ */
+
+[scriptable, uuid(3F2F0D2C-BDEA-4B5A-AFC6-FCF18F66B97E)]
+interface nsICookieAcceptDialog: nsISupports {
+
+ const short ACCEPT_COOKIE=0;
+ const short REMEMBER_DECISION=1;
+ const short HOSTNAME=2;
+ const short COOKIESFROMHOST=3;
+ const short CHANGINGCOOKIE=4;
+};
diff --git a/extensions/cookie/nsICookiePromptService.idl b/extensions/cookie/nsICookiePromptService.idl
new file mode 100644
index 000000000..163da6a26
--- /dev/null
+++ b/extensions/cookie/nsICookiePromptService.idl
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An interface to open a dialog to ask to permission to accept the cookie.
+ */
+
+interface mozIDOMWindowProxy;
+interface nsICookie;
+
+[scriptable, uuid(65ca07c3-6241-4de1-bf41-3336470499db)]
+interface nsICookiePromptService : nsISupports
+{
+ const uint32_t DENY_COOKIE = 0;
+ const uint32_t ACCEPT_COOKIE = 1;
+ const uint32_t ACCEPT_SESSION_COOKIE = 2;
+
+ /* Open a dialog that asks for permission to accept a cookie
+ *
+ * @param parent
+ * @param cookie
+ * @param hostname the host that wants to set the cookie,
+ * not the domain: part of the cookie
+ * @param cookiesFromHost the number of cookies there are already for this host
+ * @param changingCookie are we changing this cookie?
+ * @param rememberDecision should we set the matching permission for this host?
+ * @returns 0 == deny cookie
+ * 1 == accept cookie
+ * 2 == accept cookie for current session
+ */
+
+ long cookieDialog(in mozIDOMWindowProxy parent,
+ in nsICookie cookie,
+ in ACString hostname,
+ in long cookiesFromHost,
+ in boolean changingCookie,
+ out boolean rememberDecision);
+};
+
+%{C++
+#define NS_COOKIEPROMPTSERVICE_CONTRACTID "@mozilla.org/embedcomp/cookieprompt-service;1"
+%}
diff --git a/extensions/cookie/nsPermission.cpp b/extensions/cookie/nsPermission.cpp
new file mode 100644
index 000000000..3d1eb140a
--- /dev/null
+++ b/extensions/cookie/nsPermission.cpp
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPermission.h"
+#include "nsContentUtils.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIEffectiveTLDService.h"
+#include "mozilla/BasePrincipal.h"
+
+// nsPermission Implementation
+
+NS_IMPL_CLASSINFO(nsPermission, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(nsPermission, nsIPermission)
+
+nsPermission::nsPermission(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+ : mPrincipal(aPrincipal)
+ , mType(aType)
+ , mCapability(aCapability)
+ , mExpireType(aExpireType)
+ , mExpireTime(aExpireTime)
+{
+}
+
+already_AddRefed<nsPermission>
+nsPermission::Create(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+{
+ NS_ENSURE_TRUE(aPrincipal, nullptr);
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::Cast(aPrincipal)->CloneStrippingUserContextIdAndFirstPartyDomain();
+
+ NS_ENSURE_TRUE(principal, nullptr);
+
+ RefPtr<nsPermission> permission =
+ new nsPermission(principal, aType, aCapability, aExpireType, aExpireTime);
+ return permission.forget();
+}
+
+NS_IMETHODIMP
+nsPermission::GetPrincipal(nsIPrincipal** aPrincipal)
+{
+ nsCOMPtr<nsIPrincipal> copy = mPrincipal;
+ copy.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetType(nsACString &aType)
+{
+ aType = mType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetCapability(uint32_t *aCapability)
+{
+ *aCapability = mCapability;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetExpireType(uint32_t *aExpireType)
+{
+ *aExpireType = mExpireType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetExpireTime(int64_t *aExpireTime)
+{
+ *aExpireTime = mExpireTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::Matches(nsIPrincipal* aPrincipal, bool aExactHost, bool* aMatches)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aMatches);
+
+ *aMatches = false;
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::Cast(aPrincipal)->CloneStrippingUserContextIdAndFirstPartyDomain();
+
+ if (!principal) {
+ *aMatches = false;
+ return NS_OK;
+ }
+
+ // If the principals are equal, then they match.
+ if (mPrincipal->Equals(principal)) {
+ *aMatches = true;
+ return NS_OK;
+ }
+
+ // If we are matching with an exact host, we're done now - the permissions don't match
+ // otherwise, we need to start comparing subdomains!
+ if (aExactHost) {
+ return NS_OK;
+ }
+
+ // Compare their OriginAttributes
+ const mozilla::PrincipalOriginAttributes& theirAttrs = mozilla::BasePrincipal::Cast(principal)->OriginAttributesRef();
+ const mozilla::PrincipalOriginAttributes& ourAttrs = mozilla::BasePrincipal::Cast(mPrincipal)->OriginAttributesRef();
+
+ if (theirAttrs != ourAttrs) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> theirURI;
+ nsresult rv = principal->GetURI(getter_AddRefs(theirURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> ourURI;
+ rv = mPrincipal->GetURI(getter_AddRefs(ourURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Compare schemes
+ nsAutoCString theirScheme;
+ rv = theirURI->GetScheme(theirScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString ourScheme;
+ rv = ourURI->GetScheme(ourScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (theirScheme != ourScheme) {
+ return NS_OK;
+ }
+
+ // Compare ports
+ int32_t theirPort;
+ rv = theirURI->GetPort(&theirPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t ourPort;
+ rv = ourURI->GetPort(&ourPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (theirPort != ourPort) {
+ return NS_OK;
+ }
+
+ // Check if the host or any subdomain of their host matches.
+ nsAutoCString theirHost;
+ rv = theirURI->GetHost(theirHost);
+ if (NS_FAILED(rv) || theirHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsAutoCString ourHost;
+ rv = ourURI->GetHost(ourHost);
+ if (NS_FAILED(rv) || ourHost.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return NS_ERROR_FAILURE;
+ }
+
+ // This loop will not loop forever, as GetNextSubDomain will eventually fail
+ // with NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS.
+ while (theirHost != ourHost) {
+ rv = tldService->GetNextSubDomain(theirHost, theirHost);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
+ return NS_OK;
+ } else {
+ return rv;
+ }
+ }
+ }
+
+ *aMatches = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::MatchesURI(nsIURI* aURI, bool aExactHost, bool* aMatches)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ mozilla::PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ return Matches(principal, aExactHost, aMatches);
+}
diff --git a/extensions/cookie/nsPermission.h b/extensions/cookie/nsPermission.h
new file mode 100644
index 000000000..6beb8aef4
--- /dev/null
+++ b/extensions/cookie/nsPermission.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPermission_h__
+#define nsPermission_h__
+
+#include "nsIPermission.h"
+#include "nsString.h"
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPermission : public nsIPermission
+{
+public:
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERMISSION
+
+ static already_AddRefed<nsPermission> Create(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime);
+
+protected:
+ nsPermission(nsIPrincipal* aPrincipal,
+ const nsACString &aType,
+ uint32_t aCapability,
+ uint32_t aExpireType,
+ int64_t aExpireTime);
+
+ virtual ~nsPermission() {};
+
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ nsCString mType;
+ uint32_t mCapability;
+ uint32_t mExpireType;
+ int64_t mExpireTime;
+};
+
+#endif // nsPermission_h__
diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp
new file mode 100644
index 000000000..d3696dd94
--- /dev/null
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -0,0 +1,2918 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsPermissionManager.h"
+#include "nsPermission.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include "nsILineInputStream.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "prprf.h"
+#include "mozilla/storage.h"
+#include "mozilla/Attributes.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsReadLine.h"
+#include "mozilla/Telemetry.h"
+#include "nsIConsoleService.h"
+#include "nsINavHistoryService.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIObserverService.h"
+
+static nsPermissionManager *gPermissionManager = nullptr;
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::ContentChild;
+using mozilla::Unused; // ha!
+
+static bool
+IsChildProcess()
+{
+ return XRE_IsContentProcess();
+}
+
+/**
+ * @returns The child process object, or if we are not in the child
+ * process, nullptr.
+ */
+static ContentChild*
+ChildProcess()
+{
+ if (IsChildProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ if (!cpc)
+ NS_RUNTIMEABORT("Content Process is nullptr!");
+ return cpc;
+ }
+
+ return nullptr;
+}
+
+static void
+LogToConsole(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+#define ENSURE_NOT_CHILD_PROCESS_(onError) \
+ PR_BEGIN_MACRO \
+ if (IsChildProcess()) { \
+ NS_ERROR("Cannot perform action in content process!"); \
+ onError \
+ } \
+ PR_END_MACRO
+
+#define ENSURE_NOT_CHILD_PROCESS \
+ ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
+
+#define ENSURE_NOT_CHILD_PROCESS_NORET \
+ ENSURE_NOT_CHILD_PROCESS_(;)
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+nsresult
+GetOriginFromPrincipal(nsIPrincipal* aPrincipal, nsACString& aOrigin)
+{
+ nsresult rv = aPrincipal->GetOriginNoSuffix(aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix;
+ rv = aPrincipal->GetOriginSuffix(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // mPrivateBrowsingId must be set to false because PermissionManager is not supposed to have
+ // any knowledge of private browsing. Allowing it to be true changes the suffix being hashed.
+ attrs.mPrivateBrowsingId = 0;
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ attrs.CreateSuffix(suffix);
+ aOrigin.Append(suffix);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
+{
+ nsAutoCString originNoSuffix;
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs(aAppId, aIsInIsolatedMozBrowserElement);
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsCString
+GetNextSubDomainForHost(const nsACString& aHost)
+{
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return EmptyCString();
+ }
+
+ nsCString subDomain;
+ nsresult rv = tldService->GetNextSubDomain(aHost, subDomain);
+ // We can fail if there is no more subdomain or if the host can't have a
+ // subdomain.
+ if (NS_FAILED(rv)) {
+ return EmptyCString();
+ }
+
+ return subDomain;
+}
+
+class ClearOriginDataObserver final : public nsIObserver {
+ ~ClearOriginDataObserver() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, "clear-origin-attributes-data"));
+
+ nsCOMPtr<nsIPermissionManager> permManager = do_GetService("@mozilla.org/permissionmanager;1");
+ return permManager->RemovePermissionsWithAttributes(nsDependentString(aData));
+ }
+};
+
+NS_IMPL_ISUPPORTS(ClearOriginDataObserver, nsIObserver)
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHelper {
+public:
+ virtual nsresult Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) = 0;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginDBMigration final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginDBMigration(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_hosts_new "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsresult rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHostfileImport final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginHostfileImport(nsPermissionManager* aPm,
+ nsPermissionManager::DBOperationType aOperation,
+ int64_t aID) : mPm(aPm)
+ , mOperation(aOperation)
+ , mID(aID)
+ {}
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mPm->AddInternal(principal, aType, aPermission, mID,
+ aExpireType, aExpireTime, aModificationTime,
+ nsPermissionManager::eDontNotify, mOperation);
+ }
+
+private:
+ RefPtr<nsPermissionManager> mPm;
+ nsPermissionManager::DBOperationType mOperation;
+ int64_t mID;
+};
+
+class MOZ_STACK_CLASS UpgradeIPHostToOriginDB final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeIPHostToOriginDB(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms"
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id FROM moz_perms WHERE origin = ?1 AND type = ?2"),
+ getter_AddRefs(mLookupStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ // Every time the migration code wants to insert an origin into
+ // the database we need to check to see if someone has already
+ // created a permissions entry for that permission. If they have,
+ // we don't want to insert a duplicate row.
+ //
+ // We can afford to do this lookup unconditionally and not perform
+ // caching, as a origin type pair should only be attempted to be
+ // inserted once.
+
+ nsresult rv = mLookupStmt->Reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(0, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(1, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we already have the row in the database, if we do, then
+ // we don't want to be inserting it again.
+ bool moreStmts = false;
+ if (NS_FAILED(mLookupStmt->ExecuteStep(&moreStmts)) || moreStmts) {
+ mLookupStmt->Reset();
+ NS_WARNING("A permissions entry was going to be re-migrated, "
+ "but was already found in the permissions database.");
+ return NS_OK;
+ }
+
+ // Actually insert the statement into the database.
+ rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageStatement> mLookupStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+
+nsresult
+UpgradeHostToOriginAndInsert(const nsACString& aHost, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement,
+ UpgradeHostToOriginHelper* aHelper)
+{
+ if (aHost.EqualsLiteral("<file>")) {
+ // We no longer support the magic host <file>
+ NS_WARNING("The magic host <file> is no longer supported. "
+ "It is being removed from the permissions database.");
+ return NS_OK;
+ }
+
+ // First, we check to see if the host is a valid URI. If it is, it can be imported directly
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aHost);
+ if (NS_SUCCEEDED(rv)) {
+ // It was previously possible to insert useless entries to your permissions database
+ // for URIs which have a null principal. This acts as a cleanup, getting rid of
+ // these useless database entries
+ bool nullpScheme = false;
+ if (NS_SUCCEEDED(uri->SchemeIs("moz-nullprincipal", &nullpScheme)) && nullpScheme) {
+ NS_WARNING("A moz-nullprincipal: permission is being discarded.");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ return NS_OK;
+ }
+
+ // The user may use this host at non-standard ports or protocols, we can use their history
+ // to guess what ports and protocols we want to add permissions for.
+ // We find every URI which they have visited with this host (or a subdomain of this host),
+ // and try to add it as a principal.
+ bool foundHistory = false;
+
+ nsCOMPtr<nsINavHistoryService> histSrv = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+ if (histSrv) {
+ nsCOMPtr<nsINavHistoryQuery> histQuery;
+ rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the eTLD+1 of the domain
+ nsAutoCString eTLD1;
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+ if (tldService) {
+ rv = tldService->GetBaseDomainFromHost(aHost, 0, eTLD1);
+ }
+
+ if (!tldService || NS_FAILED(rv)) {
+ // If the lookup on the tldService for the base domain for the host failed,
+ // that means that we just want to directly use the host as the host name
+ // for the lookup.
+ eTLD1 = aHost;
+ }
+
+ // We want to only find history items for this particular eTLD+1, and subdomains
+ rv = histQuery->SetDomain(eTLD1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQuery->SetDomainIsHost(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
+ rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to get the URIs for every item in the user's history with the given host
+ rv = histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only search history, because searching both bookmarks and history
+ // is not supported, and history tends to be more comprehensive.
+ rv = histQueryOpts->SetQueryType(nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We include hidden URIs (such as those visited via iFrames) as they may have permissions too
+ rv = histQueryOpts->SetIncludeHidden(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryResult> histResult;
+ rv = histSrv->ExecuteQuery(histQuery, histQueryOpts, getter_AddRefs(histResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
+ rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histResultContainer->SetContainerOpen(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t childCount = 0;
+ rv = histResultContainer->GetChildCount(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTHashtable<nsCStringHashKey> insertedOrigins;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ histResultContainer->GetChild(i, getter_AddRefs(child));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ uint32_t type;
+ rv = child->GetType(&type);
+ if (NS_WARN_IF(NS_FAILED(rv)) || type != nsINavHistoryResultNode::RESULT_TYPE_URI) {
+ NS_WARNING("Unexpected non-RESULT_TYPE_URI node in "
+ "UpgradeHostToOriginAndInsert()");
+ continue;
+ }
+
+ nsAutoCString uriSpec;
+ rv = child->GetUri(uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Use the provided host - this URI may be for a subdomain, rather than the host we care about.
+ rv = uri->SetHost(aHost);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // We now have a URI which we can make a nsIPrincipal out of
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Ensure that we don't insert the same origin repeatedly
+ if (insertedOrigins.Contains(origin)) {
+ continue;
+ }
+
+ foundHistory = true;
+ rv = aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
+ insertedOrigins.PutEntry(origin);
+ }
+
+ rv = histResultContainer->SetContainerOpen(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If we didn't find any origins for this host in the poermissions database,
+ // we can insert the default http:// and https:// permissions into the database.
+ // This has a relatively high liklihood of applying the permission to the correct
+ // origin.
+ if (!foundHistory) {
+ nsAutoCString hostSegment;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString origin;
+
+ // If this is an ipv6 URI, we need to surround it in '[', ']' before trying to
+ // parse it as a URI.
+ if (aHost.FindChar(':') != -1) {
+ hostSegment.Assign("[");
+ hostSegment.Append(aHost);
+ hostSegment.Append("]");
+ } else {
+ hostSegment.Assign(aHost);
+ }
+
+ // http:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+
+ // https:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsExpandedPrincipal(nsIPrincipal* aPrincipal)
+{
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ return !!ep;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsPermissionManager::PermissionKey::PermissionKey(nsIPrincipal* aPrincipal)
+{
+ MOZ_ALWAYS_SUCCEEDS(GetOriginFromPrincipal(aPrincipal, mOrigin));
+}
+
+/**
+ * Simple callback used by |AsyncClose| to trigger a treatment once
+ * the database is closed.
+ *
+ * Note: Beware that, if you hold onto a |CloseDatabaseListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class CloseDatabaseListener final : public mozIStorageCompletionCallback
+{
+ ~CloseDatabaseListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+ /**
+ * @param aManager The owning manager.
+ * @param aRebuildOnSuccess If |true|, reinitialize the database once
+ * it has been closed. Otherwise, do nothing such.
+ */
+ CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+ bool mRebuildOnSuccess;
+};
+
+NS_IMPL_ISUPPORTS(CloseDatabaseListener, mozIStorageCompletionCallback)
+
+CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess)
+ : mManager(aManager)
+ , mRebuildOnSuccess(aRebuildOnSuccess)
+{
+}
+
+NS_IMETHODIMP
+CloseDatabaseListener::Complete(nsresult, nsISupports*)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+ if (mRebuildOnSuccess && !manager->mIsShuttingDown) {
+ return manager->InitDB(true);
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Simple callback used by |RemoveAllInternal| to trigger closing
+ * the database and reinitializing it.
+ *
+ * Note: Beware that, if you hold onto a |DeleteFromMozHostListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class DeleteFromMozHostListener final : public mozIStorageStatementCallback
+{
+ ~DeleteFromMozHostListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+ /**
+ * @param aManager The owning manager.
+ */
+ explicit DeleteFromMozHostListener(nsPermissionManager* aManager);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+};
+
+NS_IMPL_ISUPPORTS(DeleteFromMozHostListener, mozIStorageStatementCallback)
+
+DeleteFromMozHostListener::
+DeleteFromMozHostListener(nsPermissionManager* aManager)
+ : mManager(aManager)
+{
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleResult(mozIStorageResultSet *)
+{
+ MOZ_CRASH("Should not get any results");
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleError(mozIStorageError *)
+{
+ // Errors are handled in |HandleCompletion|
+ return NS_OK;
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleCompletion(uint16_t aReason)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+
+ if (aReason == REASON_ERROR) {
+ manager->CloseDB(true);
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsPermissionManager::ClearOriginDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->AddObserver(new ClearOriginDataObserver(), "clear-origin-attributes-data", /* ownsWeak= */ false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPermissionManager Implementation
+
+#define PERMISSIONS_FILE_NAME "permissions.sqlite"
+#define HOSTS_SCHEMA_VERSION 9
+
+#define HOSTPERM_FILE_NAME "hostperm.1"
+
+// Default permissions are read from a URL - this is the preference we read
+// to find that URL. If not set, don't use any default permissions.
+static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
+
+static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
+
+NS_IMPL_ISUPPORTS(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
+
+nsPermissionManager::nsPermissionManager()
+ : mMemoryOnlyDB(false)
+ , mLargestID(0)
+ , mIsShuttingDown(false)
+{
+}
+
+nsPermissionManager::~nsPermissionManager()
+{
+ RemoveAllFromMemory();
+ gPermissionManager = nullptr;
+}
+
+// static
+nsIPermissionManager*
+nsPermissionManager::GetXPCOMSingleton()
+{
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ return gPermissionManager;
+ }
+
+ // Create a new singleton nsPermissionManager.
+ // We AddRef only once since XPCOM has rules about the ordering of module
+ // teardowns - by the time our module destructor is called, it's too late to
+ // Release our members, since GC cycles have already been completed and
+ // would result in serious leaks.
+ // See bug 209571.
+ gPermissionManager = new nsPermissionManager();
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ if (NS_FAILED(gPermissionManager->Init())) {
+ NS_RELEASE(gPermissionManager);
+ }
+ }
+
+ return gPermissionManager;
+}
+
+nsresult
+nsPermissionManager::Init()
+{
+ // If the 'permissions.memory_only' pref is set to true, then don't write any
+ // permission settings to disk, but keep them in a memory-only database.
+ mMemoryOnlyDB = mozilla::Preferences::GetBool("permissions.memory_only", false);
+
+ if (IsChildProcess()) {
+ // Stop here; we don't need the DB in the child process
+ return FetchPermissions();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, "profile-do-change", true);
+ }
+
+ // ignore failure here, since it's non-fatal (we can run fine without
+ // persistent storage - e.g. if there's no profile).
+ // XXX should we tell the user about this?
+ InitDB(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RefreshPermission() {
+ NS_ENSURE_TRUE(IsChildProcess(), NS_ERROR_FAILURE);
+
+ nsresult rv = RemoveAllFromMemory();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FetchPermissions();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::OpenDatabase(nsIFile* aPermissionsFile)
+{
+ nsresult rv;
+ nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ if (!storage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // cache a connection to the hosts database
+ if (mMemoryOnlyDB) {
+ rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(mDBConn));
+ } else {
+ rv = storage->OpenDatabase(aPermissionsFile, getter_AddRefs(mDBConn));
+ }
+ return rv;
+}
+
+nsresult
+nsPermissionManager::InitDB(bool aRemoveFile)
+{
+ nsCOMPtr<nsIFile> permissionsFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(PERMISSIONS_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aRemoveFile) {
+ bool exists = false;
+ rv = permissionsFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = OpenDatabase(permissionsFile);
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ LogToConsole(NS_LITERAL_STRING("permissions.sqlite is corrupted! Try again!"));
+
+ // Add telemetry probe
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERMISSIONS_SQL_CORRUPTED, 1);
+
+ // delete corrupted permissions.sqlite and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Corrupted permissions.sqlite has been removed."));
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool ready;
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready) {
+ LogToConsole(NS_LITERAL_STRING("Fail to get connection to permissions.sqlite! Try again!"));
+
+ // delete and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Defective permissions.sqlite has been removed."));
+
+ // Add telemetry probe
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::DEFECTIVE_PERMISSIONS_SQL_REMOVED, 1);
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready)
+ return NS_ERROR_UNEXPECTED;
+ }
+
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &tableExists);
+ if (!tableExists) {
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
+ }
+ if (!tableExists) {
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // table already exists; check the schema version before reading
+ int32_t dbSchemaVersion;
+ rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (dbSchemaVersion) {
+ // upgrading.
+ // every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one.
+ // fall through to current version
+
+ case 1:
+ {
+ // previous non-expiry version of database. Upgrade it by adding the
+ // expiration columns
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireType INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // TODO: we want to make default version as version 2 in order to fix bug 784875.
+ case 0:
+ case 2:
+ {
+ // Add appId/isInBrowserElement fields.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD appId INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->SetSchemaVersion(3);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // Version 3->4 is the creation of the modificationTime field.
+ case 3:
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We leave the modificationTime at zero for all existing records; using
+ // now() would mean, eg, that doing "remove all from the last hour"
+ // within the first hour after migration would remove all permissions.
+
+ rv = mDBConn->SetSchemaVersion(4);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // In version 5, host appId, and isInBrowserElement were merged into a
+ // single origin entry
+ //
+ // In version 6, the tables were renamed for backwards compatability reasons
+ // with version 4 and earlier.
+ //
+ // In version 7, a bug in the migration used for version 4->5 was discovered
+ // which could have triggered data-loss. Because of that, all users with a
+ // version 4, 5, or 6 database will be re-migrated from the backup database.
+ // (bug 1186034). This migration bug is not present after bug 1185340, and the
+ // re-migration ensures that all users have the fix.
+ case 5:
+ // This branch could also be reached via dbSchemaVersion == 3, in which case
+ // we want to fall through to the dbSchemaVersion == 4 case.
+ // The easiest way to do that is to perform this extra check here to make
+ // sure that we didn't get here via a fallthrough from v3
+ if (dbSchemaVersion == 5) {
+ // In version 5, the backup database is named moz_hosts_v4. We perform
+ // the version 5->6 migration to get the tables to have consistent
+ // naming conventions.
+
+ // Version 5->6 is the renaming of moz_hosts to moz_perms, and
+ // moz_hosts_v4 to moz_hosts (bug 1185343)
+ //
+ // In version 5, we performed the modifications to the permissions
+ // database in place, this meant that if you upgraded to a version which
+ // used V5, and then downgraded to a version which used v4 or earlier,
+ // the fallback path would drop the table, and your permissions data
+ // would be lost. This migration undoes that mistake, by restoring the
+ // old moz_hosts table (if it was present), and instead using the new
+ // table moz_perms for the new permissions schema.
+ //
+ // NOTE: If you downgrade, store new permissions, and then upgrade
+ // again, these new permissions won't be migrated or reflected in the
+ // updated database. This migration only occurs once, as if moz_perms
+ // exists, it will skip creating it. In addition, permissions added
+ // after the migration will not be visible in previous versions of
+ // firefox.
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (!permsTableExists) {
+ // Move the upgraded database to moz_perms
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("moz_hosts was not renamed to moz_perms, "
+ "as a moz_perms table already exists");
+
+ // In the situation where a moz_perms table already exists, but the
+ // schema is lower than 6, a migration has already previously occured
+ // to V6, but a downgrade has caused the moz_hosts table to be
+ // dropped. This should only occur in the case of a downgrade to a V5
+ // database, which was only present in a few day's nightlies. As that
+ // version was likely used only on a temporary basis, we assume that
+ // the database from the previous V6 has the permissions which the
+ // user actually wants to use. We have to get rid of moz_hosts such
+ // that moz_hosts_v4 can be moved into its place if it exists.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ // The moz_hosts table shouldn't exist anymore
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ MOZ_ASSERT(!hostsTableExists);
+#endif
+
+ // Rename moz_hosts_v4 back to it's original location, if it exists
+ bool v4TableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v4"), &v4TableExists);
+ if (v4TableExists) {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(6);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // At this point, the version 5 table has been migrated to a version 6 table
+ // We are guaranteed to have at least one of moz_hosts and moz_perms. If
+ // we have moz_hosts, we will migrate moz_hosts into moz_perms (even if
+ // we already have a moz_perms, as we need a re-migration due to bug 1186034).
+ //
+ // After this migration, we are guaranteed to have both a moz_hosts (for backwards
+ // compatability), and a moz_perms table. The moz_hosts table will have a v4 schema,
+ // and the moz_perms table will have a v6 schema.
+ case 4:
+ case 6:
+ {
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ if (hostsTableExists) {
+ bool migrationError = false;
+
+ // Both versions 4 and 6 have a version 4 formatted hosts table named
+ // moz_hosts. We can migrate this table to our version 7 table moz_perms.
+ // If moz_perms is present, then we can use it as a basis for comparison.
+
+ rv = mDBConn->BeginTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_new"), &tableExists);
+ if (tableExists) {
+ NS_WARNING("The temporary database moz_hosts_new already exists, dropping it.");
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_new"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_new ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id = 0;
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+ bool hasResult;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ migrationError = true;
+ continue;
+ }
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ migrationError = true;
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ migrationError = true;
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeHostToOriginDBMigration upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ migrationError = true;
+ }
+ }
+
+ // We don't drop the moz_hosts table such that it is avaliable for
+ // backwards-compatability and for future migrations in case of
+ // migration errors in the current code.
+ // Create a marker empty table which will indicate that the moz_hosts
+ // table is intended to act as a backup. If this table is not present,
+ // then the moz_hosts table was created as a random empty table.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_is_backup (dummy INTEGER PRIMARY KEY)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (permsTableExists) {
+ // The user already had a moz_perms table, and we are performing a
+ // re-migration. We count the rows in the old table for telemetry,
+ // and then back up their old database as moz_perms_v6
+
+ nsCOMPtr<mozIStorageStatement> countStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT COUNT(*) FROM moz_perms"),
+ getter_AddRefs(countStmt));
+ bool hasResult = false;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(countStmt->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ int32_t permsCount = countStmt->AsInt32(0);
+
+ // The id variable contains the number of rows inserted into the
+ // moz_hosts_new table (as one ID was used per entry)
+ uint32_t telemetryValue;
+ if (permsCount > id) {
+ telemetryValue = 3; // NEW > OLD
+ } else if (permsCount == id) {
+ telemetryValue = 2; // NEW == OLD
+ } else if (permsCount == 0) {
+ telemetryValue = 0; // NEW = 0
+ } else {
+ telemetryValue = 1; // NEW < OLD
+ }
+
+ // Report the telemetry value to telemetry
+ mozilla::Telemetry::Accumulate(
+ mozilla::Telemetry::PERMISSIONS_REMIGRATION_COMPARISON,
+ telemetryValue);
+ } else {
+ NS_WARNING("Could not count the rows in moz_perms");
+ }
+
+ // Back up the old moz_perms database as moz_perms_v6 before we
+ // move the new table into its position
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CommitTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERMISSIONS_MIGRATION_7_ERROR,
+ NS_WARN_IF(migrationError));
+ } else {
+ // We don't have a moz_hosts table, so we create one for downgrading purposes.
+ // This table is empty.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We are guaranteed to have a moz_perms table at this point.
+ }
+
+#ifdef DEBUG
+ {
+ // At this point, both the moz_hosts and moz_perms tables should exist
+ bool hostsTableExists = false;
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ MOZ_ASSERT(hostsTableExists && permsTableExists);
+ }
+#endif
+
+ rv = mDBConn->SetSchemaVersion(7);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 7-8 migration is the re-migration of localhost and ip-address
+ // entries due to errors in the previous version 7 migration which caused
+ // localhost and ip-address entries to be incorrectly discarded.
+ // The version 7 migration logic has been corrected, and thus this logic only
+ // needs to execute if the user is currently on version 7.
+ case 7:
+ {
+ // This migration will be relatively expensive as we need to perform
+ // database lookups for each origin which we want to insert. Fortunately,
+ // it shouldn't be too expensive as we only want to insert a small number
+ // of entries created for localhost or IP addresses.
+
+ // We only want to perform the re-migration if moz_hosts is a backup
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+
+ // Only perform this migration if the original schema version was 7, and
+ // the moz_hosts table is a backup.
+ if (dbSchemaVersion == 7 && hostsIsBackupExists) {
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> idStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT MAX(id) FROM moz_hosts"), getter_AddRefs(idStmt));
+ int64_t id = 0;
+ bool hasResult = false;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ id = idStmt->AsInt32(0) + 1;
+ }
+
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsAutoCString eTLD1;
+ rv = tldService->GetBaseDomainFromHost(host, 0, eTLD1);
+ if (NS_SUCCEEDED(rv)) {
+ // We only care about entries which the tldService can't handle
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeIPHostToOriginDB upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ }
+ }
+ }
+
+ // Even if we didn't perform the migration, we want to bump the schema
+ // version to 8.
+ rv = mDBConn->SetSchemaVersion(8);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 8-9 migration removes the unnecessary backup moz-hosts database contents.
+ // as the data no longer needs to be migrated
+ case 8:
+ {
+ // We only want to clear out the old table if it is a backup. If it isn't a backup,
+ // we don't need to touch it.
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+ if (hostsIsBackupExists) {
+ // Delete everything from the backup, we want to keep around the table so that
+ // you can still downgrade and not break things, but we don't need to keep the
+ // rows around.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The table is no longer a backup, so get rid of it.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_is_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(9);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // current version.
+ case HOSTS_SCHEMA_VERSION:
+ break;
+
+ // downgrading.
+ // if columns have been added to the table, we can still use the ones we
+ // understand safely. if columns have been deleted or altered, just
+ // blow away the table and start from scratch! if you change the way
+ // a column is interpreted, make sure you also change its name so this
+ // check will catch it.
+ default:
+ {
+ // check if all the expected columns exist
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT origin, type, permission, expireType, expireTime, "
+ "modificationTime FROM moz_perms"),
+ getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // our columns aren't there - drop the table!
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ }
+
+ // cache frequently used statements (for insertion, deletion, and updating)
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmtInsert));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms "
+ "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_perms "
+ "SET permission = ?2, expireType= ?3, expireTime = ?4, modificationTime = ?5 WHERE id = ?1"),
+ getter_AddRefs(mStmtUpdate));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Always import default permissions.
+ ImportDefaults();
+ // check whether to import or just read in the db
+ if (tableExists)
+ return Read();
+
+ return Import();
+}
+
+// sets the schema version and creates the moz_perms table.
+nsresult
+nsPermissionManager::CreateTable()
+{
+ // set the schema version, before creating the table
+ nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) return rv;
+
+ // create the table
+ // SQL also lives in automation.py.in. If you change this SQL change that
+ // one too
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_perms ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ // We also create a legacy V4 table, for backwards compatability,
+ // and to ensure that downgrades don't trigger a schema version change.
+ return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Add(nsIURI *aURI,
+ const char *aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddFromPrincipal(principal, aType, aPermission, aExpireType, aExpireTime);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+ NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_INVALID_ARG);
+
+ // Skip addition if the permission is already expired. Note that EXPIRE_SESSION only
+ // honors expireTime if it is nonzero.
+ if ((aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (aExpireType == nsIPermissionManager::EXPIRE_SESSION && aExpireTime != 0)) &&
+ aExpireTime <= (PR_Now() / 1000)) {
+ return NS_OK;
+ }
+
+ // We don't add the system principal because it actually has no URI and we
+ // always allow action for them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Null principals can't meaningfully have persisted permissions attached to
+ // them, so we don't allow adding permissions for them.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // A modificationTime of zero will cause AddInternal to use now().
+ int64_t modificationTime = 0;
+
+ return AddInternal(aPrincipal, nsDependentCString(aType), aPermission, 0,
+ aExpireType, aExpireTime, modificationTime, eNotify, eWriteToDB);
+}
+
+nsresult
+nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
+ const nsAFlatCString &aType,
+ uint32_t aPermission,
+ int64_t aID,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime,
+ NotifyOperationType aNotifyOperation,
+ DBOperationType aDBOperation,
+ const bool aIgnoreSessionPermissions)
+{
+ nsAutoCString origin;
+ nsresult rv = GetOriginFromPrincipal(aPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsChildProcess()) {
+ IPC::Permission permission(origin, aType, aPermission,
+ aExpireType, aExpireTime);
+
+ nsTArray<ContentParent*> cplist;
+ ContentParent::GetAll(cplist);
+ for (uint32_t i = 0; i < cplist.Length(); ++i) {
+ ContentParent* cp = cplist[i];
+ // On platforms where we use a preallocated template process we don't
+ // want to notify this process about session specific permissions so
+ // new tabs or apps created on it won't inherit the session permissions.
+ if (cp->IsPreallocated() &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION)
+ continue;
+ if (cp->NeedsPermissionsUpdate())
+ Unused << cp->SendAddPermission(permission);
+ }
+ }
+
+ // look up the type index
+ int32_t typeIndex = GetTypeIndex(aType.get(), true);
+ NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
+
+ // When an entry already exists, PutEntry will return that, instead
+ // of adding a new one
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ PermissionHashKey* entry = mPermissionTable.PutEntry(key);
+ if (!entry) return NS_ERROR_FAILURE;
+ if (!entry->GetKey()) {
+ mPermissionTable.RemoveEntry(entry);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // figure out the transaction type, and get any existing permission value
+ OperationType op;
+ int32_t index = entry->GetPermissionIndex(typeIndex);
+ if (index == -1) {
+ if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationNone;
+ else
+ op = eOperationAdding;
+
+ } else {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+
+ // remove the permission if the permission is UNKNOWN, update the
+ // permission if its value or expire type have changed OR if the time has
+ // changed and the expire type is time, otherwise, don't modify. There's
+ // no need to modify a permission that doesn't expire with time when the
+ // only thing changed is the expire time.
+ if (aPermission == oldPermissionEntry.mPermission &&
+ aExpireType == oldPermissionEntry.mExpireType &&
+ (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireTime == oldPermissionEntry.mExpireTime))
+ op = eOperationNone;
+ else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
+ // The existing permission is one added as a default and the new permission
+ // doesn't exactly match so we are replacing the default. This is true
+ // even if the new permission is UNKNOWN_ACTION (which means a "logical
+ // remove" of the default)
+ op = eOperationReplacingDefault;
+ else if (aID == cIDPermissionIsDefault)
+ // We are adding a default permission but a "real" permission already
+ // exists. This almost-certainly means we just did a removeAllSince and
+ // are re-importing defaults - so we can ignore this.
+ op = eOperationNone;
+ else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationRemoving;
+ else
+ op = eOperationChanging;
+ }
+
+ // child processes should *always* be passed a modificationTime of zero.
+ MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
+
+ // do the work for adding, deleting, or changing a permission:
+ // update the in-memory list, write to the db, and notify consumers.
+ int64_t id;
+ if (aModificationTime == 0) {
+ aModificationTime = PR_Now() / 1000;
+ }
+
+ switch (op) {
+ case eOperationNone:
+ {
+ // nothing to do
+ return NS_OK;
+ }
+
+ case eOperationAdding:
+ {
+ if (aDBOperation == eWriteToDB) {
+ // we'll be writing to the database - generate a known unique id
+ id = ++mLargestID;
+ } else {
+ // we're reading from the database - use the id already assigned
+ id = aID;
+ }
+
+#ifdef MOZ_B2G
+ // When we do the initial addition of the permissions we don't want to
+ // inherit session specific permissions from other tabs or apps
+ // so we ignore them and set the permission to PROMPT_ACTION if it was
+ // previously allowed or denied by the user.
+ if (aIgnoreSessionPermissions &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
+ aPermission = nsIPermissionManager::PROMPT_ACTION;
+ aExpireType = nsIPermissionManager::EXPIRE_NEVER;
+ }
+#endif // MOZ_B2G
+
+ entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission,
+ aExpireType, aExpireTime,
+ aModificationTime));
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ UpdateDB(op, mStmtInsert, id, origin, aType, aPermission, aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"added");
+ }
+
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+ id = oldPermissionEntry.mID;
+ entry->GetPermissions().RemoveElementAt(index);
+
+ if (aDBOperation == eWriteToDB)
+ // We care only about the id here so we pass dummy values for all other
+ // parameters.
+ UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ oldPermissionEntry.mPermission,
+ oldPermissionEntry.mExpireType,
+ oldPermissionEntry.mExpireTime,
+ u"deleted");
+ }
+
+ // If there are no more permissions stored for that entry, clear it.
+ if (entry->GetPermissions().IsEmpty()) {
+ mPermissionTable.RemoveEntry(entry);
+ }
+
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ id = entry->GetPermissions()[index].mID;
+
+ // If the new expireType is EXPIRE_SESSION, then we have to keep a
+ // copy of the previous permission/expireType values. This cached value will be
+ // used when restoring the permissions of an app.
+ if (entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = entry->GetPermissions()[index].mPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = entry->GetPermissions()[index].mExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = entry->GetPermissions()[index].mExpireTime;
+ } else if (aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = aPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
+ }
+
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
+ // We care only about the id, the permission and expireType/expireTime/modificationTime here.
+ // We pass dummy values for all other parameters.
+ UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(),
+ aPermission, aExpireType, aExpireTime, aModificationTime);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ break;
+ }
+ case eOperationReplacingDefault:
+ {
+ // this is handling the case when we have an existing permission
+ // entry that was created as a "default" (and thus isn't in the DB) with
+ // an explicit permission (that may include UNKNOWN_ACTION.)
+ // Note we will *not* get here if we are replacing an already replaced
+ // default value - that is handled as eOperationChanging.
+
+ // So this is a hybrid of eOperationAdding (as we are writing a new entry
+ // to the DB) and eOperationChanging (as we are replacing the in-memory
+ // repr and sending a "changed" notification).
+
+ // We want a new ID even if not writing to the DB, so the modified entry
+ // in memory doesn't have the magic cIDPermissionIsDefault value.
+ id = ++mLargestID;
+
+ // The default permission being replaced can't have session expiry.
+ NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_UNEXPECTED);
+ // We don't support the new entry having any expiry - supporting that would
+ // make things far more complex and none of the permissions we set as a
+ // default support that.
+ NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
+
+ // update the existing entry in memory.
+ entry->GetPermissions()[index].mID = id;
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ // If requested, create the entry in the DB.
+ if (aDBOperation == eWriteToDB) {
+ UpdateDB(eOperationAdding, mStmtInsert, id, origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Remove(nsIURI* aURI,
+ const char* aType)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveFromPrincipal(principal, aType);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ // System principals are never added to the database, no need to remove them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // AddInternal() handles removal, just let it do the work
+ return AddInternal(aPrincipal,
+ nsDependentCString(aType),
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ eNotify,
+ eWriteToDB);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermission(nsIPermission* aPerm)
+{
+ if (!aPerm) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = aPerm->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString type;
+ rv = aPerm->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Permissions are uniquely identified by their principal and type.
+ // We remove the permission using these two pieces of data.
+ return RemoveFromPrincipal(principal, type.get());
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAll()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllInternal(true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAllSince(int64_t aSince)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllModifiedSince(aSince);
+}
+
+void
+nsPermissionManager::CloseDB(bool aRebuildOnSuccess)
+{
+ // Null the statements, this will finalize them.
+ mStmtInsert = nullptr;
+ mStmtDelete = nullptr;
+ mStmtUpdate = nullptr;
+ if (mDBConn) {
+ mozIStorageCompletionCallback* cb = new CloseDatabaseListener(this,
+ aRebuildOnSuccess);
+ mozilla::DebugOnly<nsresult> rv = mDBConn->AsyncClose(cb);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mDBConn = nullptr; // Avoid race conditions
+ }
+}
+
+nsresult
+nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
+{
+ // Remove from memory and notify immediately. Since the in-memory
+ // database is authoritative, we do not need confirmation from the
+ // on-disk database to notify observers.
+ RemoveAllFromMemory();
+
+ // Re-import the defaults
+ ImportDefaults();
+
+ if (aNotifyObservers) {
+ NotifyObservers(nullptr, u"cleared");
+ }
+
+ // clear the db
+ if (mDBConn) {
+ nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
+ nsresult rv = mDBConn->
+ CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms"
+ ), getter_AddRefs(removeStmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!removeStmt) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ mozIStorageStatementCallback* cb = new DeleteFromMozHostListener(this);
+ rv = removeStmt->ExecuteAsync(cb, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestExactPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, false);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromWindow(mozIDOMWindow* aWindow,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ NS_ENSURE_ARG(aWindow);
+ nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(aWindow);
+
+ // Get the document for security check
+ nsCOMPtr<nsIDocument> document = window->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_NOINTERFACE);
+
+ nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, false, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ nsIPermission** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aResult = nullptr;
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Querying the permission object of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ // We don't call GetPermission(typeIndex) because that returns a fake
+ // UNKNOWN_ACTION entry if there is no match.
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ nsCOMPtr<nsIPermission> r = nsPermission::Create(principal,
+ mTypeArray.ElementAt(perm.mType),
+ perm.mPermission,
+ perm.mExpireType,
+ perm.mExpireTime);
+ if (NS_WARN_IF(!r)) {
+ return NS_ERROR_FAILURE;
+ }
+ r.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::CommonTestPermission(nsIPrincipal* aPrincipal,
+ const char *aType,
+ uint32_t *aPermission,
+ bool aExactHostMatch,
+ bool aIncludingSession)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ *aPermission = nsIPermissionManager::ALLOW_ACTION;
+ return NS_OK;
+ }
+
+ // Set the default.
+ *aPermission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ // For expanded principals, we want to iterate over the whitelist and see
+ // if the permission is granted for any of them.
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ if (ep) {
+ nsTArray<nsCOMPtr<nsIPrincipal>>* whitelist;
+ nsresult rv = ep->GetWhiteList(&whitelist);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < whitelist->Length(); ++i) {
+ uint32_t perm;
+ rv = CommonTestPermission(whitelist->ElementAt(i), aType, &perm, aExactHostMatch,
+ aIncludingSession);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (perm == nsIPermissionManager::ALLOW_ACTION) {
+ *aPermission = perm;
+ return NS_OK;
+ } else if (perm == nsIPermissionManager::PROMPT_ACTION) {
+ // Store it, but keep going to see if we can do better.
+ *aPermission = perm;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry ||
+ (!aIncludingSession &&
+ entry->GetPermission(typeIndex).mNonSessionExpireType ==
+ nsIPermissionManager::EXPIRE_SESSION)) {
+ return NS_OK;
+ }
+
+ *aPermission = aIncludingSession
+ ? entry->GetPermission(typeIndex).mPermission
+ : entry->GetPermission(typeIndex).mNonSessionPermission;
+
+ return NS_OK;
+}
+
+// Returns PermissionHashKey for a given { host, appId, isInBrowserElement } tuple.
+// This is not simply using PermissionKey because we will walk-up domains in
+// case of |host| contains sub-domains.
+// Returns null if nothing found.
+// Also accepts host on the format "<foo>". This will perform an exact match
+// lookup as the string doesn't contain any dots.
+nsPermissionManager::PermissionHashKey*
+nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
+ uint32_t aType,
+ bool aExactHostMatch)
+{
+ PermissionHashKey* entry = nullptr;
+
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ PermissionEntry permEntry = entry->GetPermission(aType);
+
+ // if the entry is expired, remove and keep looking for others.
+ // Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
+ if ((permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (permEntry.mExpireType == nsIPermissionManager::EXPIRE_SESSION &&
+ permEntry.mExpireTime != 0)) &&
+ permEntry.mExpireTime <= (PR_Now() / 1000)) {
+ entry = nullptr;
+ RemoveFromPrincipal(aPrincipal, mTypeArray[aType].get());
+ } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ entry = nullptr;
+ }
+ }
+
+ if (entry) {
+ return entry;
+ }
+
+ // If aExactHostMatch wasn't true, we can check if the base domain has a permission entry.
+ if (!aExactHostMatch) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCString domain = GetNextSubDomainForHost(host);
+ if (domain.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create a new principal which is identical to the current one, but with the new host
+ nsCOMPtr<nsIURI> newURI;
+ rv = uri->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = newURI->SetHost(domain);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // Copy the attributes over
+ mozilla::PrincipalOriginAttributes attrs =
+ mozilla::BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
+
+ return GetPermissionHashKey(principal, aType, aExactHostMatch);
+ }
+
+ // No entry, really...
+ return nullptr;
+}
+
+NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
+{
+ // roll an nsCOMArray of all our permissions, then hand out an enumerator
+ nsCOMArray<nsIPermission> array;
+
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Given how "default" permissions work and the possibility of them being
+ // overridden with UNKNOWN_ACTION, we might see this value here - but we
+ // do *not* want to return them via the enumerator.
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
+{
+ nsCOMArray<nsIPermission> array;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<PermissionKey> key = new PermissionKey(principal);
+ PermissionHashKey* entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Only return custom permissions
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ // The profile is about to change,
+ // or is going away because the application is shutting down.
+ mIsShuttingDown = true;
+ RemoveAllFromMemory();
+ CloseDB(false);
+ } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
+ // the profile has already changed; init the db from the new location
+ InitDB(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::RemoveAllModifiedSince(int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsCOMArray<nsIPermission> array;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ if (aModificationTime > permEntry.mModificationTime) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i<array.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ nsresult rv = array[i]->GetPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetPrincipal() failed!");
+ continue;
+ }
+
+ rv = array[i]->GetType(type);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetType() failed!");
+ continue;
+ }
+
+ // AddInternal handles removal, so let it do the work...
+ AddInternal(
+ principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+ // now re-import any defaults as they may now be required if we just deleted
+ // an override.
+ ImportDefaults();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ mozilla::OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return RemovePermissionsWithAttributes(pattern);
+}
+
+nsresult
+nsPermissionManager::RemovePermissionsWithAttributes(mozilla::OriginAttributesPattern& aPattern)
+{
+ nsCOMArray<nsIPermission> permissions;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(mozilla::BasePrincipal::Cast(principal)->OriginAttributesRef())) {
+ continue;
+ }
+
+ for (const auto& permEntry : entry->GetPermissions()) {
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ permissions.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i < permissions.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ permissions[i]->GetPrincipal(getter_AddRefs(principal));
+ permissions[i]->GetType(type);
+
+ AddInternal(principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPermissionManager private methods
+//*****************************************************************************
+
+nsresult
+nsPermissionManager::RemoveAllFromMemory()
+{
+ mLargestID = 0;
+ mTypeArray.Clear();
+ mPermissionTable.Clear();
+
+ return NS_OK;
+}
+
+// Returns -1 on failure
+int32_t
+nsPermissionManager::GetTypeIndex(const char *aType,
+ bool aAdd)
+{
+ for (uint32_t i = 0; i < mTypeArray.Length(); ++i)
+ if (mTypeArray[i].Equals(aType))
+ return i;
+
+ if (!aAdd) {
+ // Not found, but that is ok - we were just looking.
+ return -1;
+ }
+
+ // This type was not registered before.
+ // append it to the array, without copy-constructing the string
+ nsCString *elem = mTypeArray.AppendElement();
+ if (!elem)
+ return -1;
+
+ elem->Assign(aType);
+ return mTypeArray.Length() - 1;
+}
+
+// wrapper function for mangling (host,type,perm,expireType,expireTime)
+// set into an nsIPermission.
+void
+nsPermissionManager::NotifyObserversWithPermission(nsIPrincipal* aPrincipal,
+ const nsCString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(aPrincipal, aType, aPermission,
+ aExpireType, aExpireTime);
+ if (permission)
+ NotifyObservers(permission, aData);
+}
+
+// notify observers that the permission list changed. there are four possible
+// values for aData:
+// "deleted" means a permission was deleted. aPermission is the deleted permission.
+// "added" means a permission was added. aPermission is the added permission.
+// "changed" means a permission was altered. aPermission is the new permission.
+// "cleared" means the entire permission list was cleared. aPermission is null.
+void
+nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(aPermission,
+ kPermissionChangeNotification,
+ aData);
+}
+
+nsresult
+nsPermissionManager::Read()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+
+ // delete expired permissions before we read in the db
+ {
+ // this deletion has its own scope so the write lock is released when done.
+ nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms WHERE expireType = ?1 AND expireTime <= ?2"),
+ getter_AddRefs(stmtDeleteExpired));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt64ByIndex(1, PR_Now() / 1000);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmtDeleteExpired->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, origin, type, permission, expireType, expireTime, modificationTime "
+ "FROM moz_perms"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id;
+ nsAutoCString origin, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ bool hasResult;
+ bool readError = false;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // explicitly set our entry id counter for use in AddInternal(),
+ // and keep track of the largest id so we know where to pick up.
+ id = stmt->AsInt64(0);
+ if (id > mLargestID)
+ mLargestID = id;
+
+ rv = stmt->GetUTF8String(1, origin);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(2, type);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ permission = stmt->AsInt32(3);
+ expireType = stmt->AsInt32(4);
+
+ // convert into int64_t values (milliseconds)
+ expireTime = stmt->AsInt64(5);
+ modificationTime = stmt->AsInt64(6);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(origin, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = AddInternal(principal, type, permission, id, expireType, expireTime,
+ modificationTime, eDontNotify, eNoDBOperation);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+ }
+
+ if (readError) {
+ NS_ERROR("Error occured while reading the permissions database!");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static const char kMatchTypeHost[] = "host";
+static const char kMatchTypeOrigin[] = "origin";
+
+// Import() will read a file from the profile directory and add them to the
+// database before deleting the file - ie, this is a one-shot operation that
+// will not succeed on subsequent runs as the file imported from is removed.
+nsresult
+nsPermissionManager::Import()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> permissionsFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(HOSTPERM_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> fileInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
+ permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(fileInputStream, mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we successfully imported and wrote to the DB - delete the old file.
+ permissionsFile->Remove(false);
+ return NS_OK;
+}
+
+// ImportDefaults will read a URL with default permissions and add them to the
+// in-memory copy of permissions. The database is *not* written to.
+nsresult
+nsPermissionManager::ImportDefaults()
+{
+ nsCString defaultsURL = mozilla::Preferences::GetCString(kDefaultsUrlPrefName);
+ if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> defaultsURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ defaultsURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = channel->Open2(getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(inputStream, nullptr);
+ inputStream->Close();
+ return rv;
+}
+
+// _DoImport reads the specified stream and adds the parsed elements. If
+// |conn| is passed, the imported data will be written to the database, but if
+// |conn| is null the data will be added only to the in-memory copy of the
+// database.
+nsresult
+nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnection *conn)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+ // start a transaction on the storage db, to optimize insertions.
+ // transaction will automically commit on completion
+ // (note the transaction is a no-op if a null connection is passed)
+ mozStorageTransaction transaction(conn, true);
+
+ // The DB operation - we only try and write if a connection was passed.
+ DBOperationType operation = conn ? eWriteToDB : eNoDBOperation;
+ // and if no DB connection was passed we assume this is a "default" permission,
+ // so use the special ID which indicates this.
+ int64_t id = conn ? 0 : cIDPermissionIsDefault;
+
+ /* format is:
+ * matchtype \t type \t permission \t host
+ * Only "host" is supported for matchtype
+ * type is a string that identifies the type of permission (e.g. "cookie")
+ * permission is an integer between 1 and 15
+ */
+
+ // Ideally we'd do this with nsILineInputString, but this is called with an
+ // nsIInputStream that comes from a resource:// URI, which doesn't support
+ // that interface. So NS_ReadLine to the rescue...
+ nsLineBuffer<char> lineBuffer;
+ nsCString line;
+ bool isMore = true;
+ do {
+ rv = NS_ReadLine(inputStream, &lineBuffer, line, &isMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (line.IsEmpty() || line.First() == '#') {
+ continue;
+ }
+
+ nsTArray<nsCString> lineArray;
+
+ // Split the line at tabs
+ ParseString(line, '\t', lineArray);
+
+ if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ UpgradeHostToOriginHostfileImport upHelper(this, operation, id);
+ error = UpgradeHostToOriginAndInsert(lineArray[3], lineArray[1], permission,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime, nsIScriptSecurityManager::NO_APP_ID,
+ false, &upHelper);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing a host permission");
+ }
+ } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ error = GetPrincipalFromOrigin(lineArray[3], getter_AddRefs(principal));
+ if (NS_FAILED(error)) {
+ NS_WARNING("Couldn't import an origin permission - malformed origin");
+ continue;
+ }
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ error = AddInternal(principal, lineArray[1], permission, id,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime,
+ eDontNotify, operation);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing an origin permission");
+ }
+ }
+
+ } while (isMore);
+
+ return NS_OK;
+}
+
+void
+nsPermissionManager::UpdateDB(OperationType aOp,
+ mozIStorageAsyncStatement* aStmt,
+ int64_t aID,
+ const nsACString &aOrigin,
+ const nsACString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS_NORET;
+
+ nsresult rv;
+
+ // no statement is ok - just means we don't have a profile
+ if (!aStmt)
+ return;
+
+ switch (aOp) {
+ case eOperationAdding:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(1, aOrigin);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(2, aType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(3, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(4, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(5, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(6, aModificationTime);
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(1, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(2, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(3, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(4, aModificationTime);
+ break;
+ }
+
+ default:
+ {
+ NS_NOTREACHED("need a valid operation in UpdateDB()!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("db change failed!");
+ return;
+ }
+
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ rv = aStmt->ExecuteAsync(nullptr, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::UpdateExpireTime(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ uint64_t aSessionExpireTime,
+ uint64_t aPersistentExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ uint64_t nowms = PR_Now() / 1000;
+ if (aSessionExpireTime < nowms || aPersistentExpireTime < nowms) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Setting the expire time of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ if (perm.mExpireType == EXPIRE_TIME) {
+ perm.mExpireTime = aPersistentExpireTime;
+ } else if (perm.mExpireType == EXPIRE_SESSION && perm.mExpireTime != 0) {
+ perm.mExpireTime = aSessionExpireTime;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::FetchPermissions() {
+ MOZ_ASSERT(IsChildProcess(), "FetchPermissions can only be invoked in child process");
+ // Get the permissions from the parent process
+ InfallibleTArray<IPC::Permission> perms;
+ ChildProcess()->SendReadPermissions(&perms);
+
+ for (uint32_t i = 0; i < perms.Length(); i++) {
+ const IPC::Permission &perm = perms[i];
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(perm.origin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The child process doesn't care about modification times - it neither
+ // reads nor writes, nor removes them based on the date - so 0 (which
+ // will end up as now()) is fine.
+ uint64_t modificationTime = 0;
+ AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
+ perm.expireTime, modificationTime, eNotify, eNoDBOperation,
+ true /* ignoreSessionPermissions */);
+ }
+ return NS_OK;
+}
diff --git a/extensions/cookie/nsPermissionManager.h b/extensions/cookie/nsPermissionManager.h
new file mode 100644
index 000000000..4b21500b1
--- /dev/null
+++ b/extensions/cookie/nsPermissionManager.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPermissionManager_h__
+#define nsPermissionManager_h__
+
+#include "nsIPermissionManager.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+#include "nsTHashtable.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsPermission.h"
+#include "nsHashKeys.h"
+#include "nsCOMArray.h"
+#include "nsDataHashtable.h"
+
+namespace mozilla {
+class OriginAttributesPattern;
+}
+
+class nsIPermission;
+class mozIStorageConnection;
+class mozIStorageAsyncStatement;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class nsPermissionManager final : public nsIPermissionManager,
+ public nsIObserver,
+ public nsSupportsWeakReference
+{
+public:
+ class PermissionEntry
+ {
+ public:
+ PermissionEntry(int64_t aID, uint32_t aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime)
+ : mID(aID)
+ , mType(aType)
+ , mPermission(aPermission)
+ , mExpireType(aExpireType)
+ , mExpireTime(aExpireTime)
+ , mModificationTime(aModificationTime)
+ , mNonSessionPermission(aPermission)
+ , mNonSessionExpireType(aExpireType)
+ , mNonSessionExpireTime(aExpireTime)
+ {}
+
+ int64_t mID;
+ uint32_t mType;
+ uint32_t mPermission;
+ uint32_t mExpireType;
+ int64_t mExpireTime;
+ int64_t mModificationTime;
+ uint32_t mNonSessionPermission;
+ uint32_t mNonSessionExpireType;
+ uint32_t mNonSessionExpireTime;
+ };
+
+ /**
+ * PermissionKey is the key used by PermissionHashKey hash table.
+ *
+ * NOTE: It could be implementing nsIHashable but there is no reason to worry
+ * with XPCOM interfaces while we don't need to.
+ */
+ class PermissionKey
+ {
+ public:
+ explicit PermissionKey(nsIPrincipal* aPrincipal);
+ explicit PermissionKey(const nsACString& aOrigin)
+ : mOrigin(aOrigin)
+ {
+ }
+
+ bool operator==(const PermissionKey& aKey) const {
+ return mOrigin.Equals(aKey.mOrigin);
+ }
+
+ PLDHashNumber GetHashCode() const {
+ return mozilla::HashString(mOrigin);
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PermissionKey)
+
+ nsCString mOrigin;
+
+ private:
+ // Default ctor shouldn't be used.
+ PermissionKey() = delete;
+
+ // Dtor shouldn't be used outside of the class.
+ ~PermissionKey() {};
+ };
+
+ class PermissionHashKey : public nsRefPtrHashKey<PermissionKey>
+ {
+ public:
+ explicit PermissionHashKey(const PermissionKey* aPermissionKey)
+ : nsRefPtrHashKey<PermissionKey>(aPermissionKey)
+ {}
+
+ PermissionHashKey(const PermissionHashKey& toCopy)
+ : nsRefPtrHashKey<PermissionKey>(toCopy)
+ , mPermissions(toCopy.mPermissions)
+ {}
+
+ bool KeyEquals(const PermissionKey* aKey) const
+ {
+ return *aKey == *GetKey();
+ }
+
+ static PLDHashNumber HashKey(const PermissionKey* aKey)
+ {
+ return aKey->GetHashCode();
+ }
+
+ // Force the hashtable to use the copy constructor when shuffling entries
+ // around, otherwise the Auto part of our AutoTArray won't be happy!
+ enum { ALLOW_MEMMOVE = false };
+
+ inline nsTArray<PermissionEntry> & GetPermissions()
+ {
+ return mPermissions;
+ }
+
+ inline int32_t GetPermissionIndex(uint32_t aType) const
+ {
+ for (uint32_t i = 0; i < mPermissions.Length(); ++i)
+ if (mPermissions[i].mType == aType)
+ return i;
+
+ return -1;
+ }
+
+ inline PermissionEntry GetPermission(uint32_t aType) const
+ {
+ for (uint32_t i = 0; i < mPermissions.Length(); ++i)
+ if (mPermissions[i].mType == aType)
+ return mPermissions[i];
+
+ // unknown permission... return relevant data
+ return PermissionEntry(-1, aType, nsIPermissionManager::UNKNOWN_ACTION,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0);
+ }
+
+ private:
+ AutoTArray<PermissionEntry, 1> mPermissions;
+ };
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPERMISSIONMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsPermissionManager();
+ static nsIPermissionManager* GetXPCOMSingleton();
+ nsresult Init();
+
+ // enums for AddInternal()
+ enum OperationType {
+ eOperationNone,
+ eOperationAdding,
+ eOperationRemoving,
+ eOperationChanging,
+ eOperationReplacingDefault
+ };
+
+ enum DBOperationType {
+ eNoDBOperation,
+ eWriteToDB
+ };
+
+ enum NotifyOperationType {
+ eDontNotify,
+ eNotify
+ };
+
+ // A special value for a permission ID that indicates the ID was loaded as
+ // a default value. These will never be written to the database, but may
+ // be overridden with an explicit permission (including UNKNOWN_ACTION)
+ static const int64_t cIDPermissionIsDefault = -1;
+
+ nsresult AddInternal(nsIPrincipal* aPrincipal,
+ const nsAFlatCString &aType,
+ uint32_t aPermission,
+ int64_t aID,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime,
+ NotifyOperationType aNotifyOperation,
+ DBOperationType aDBOperation,
+ const bool aIgnoreSessionPermissions = false);
+
+ /**
+ * Initialize the "clear-origin-attributes-data" observing.
+ * Will create a nsPermissionManager instance if needed.
+ * That way, we can prevent have nsPermissionManager created at startup just
+ * to be able to clear data when an application is uninstalled.
+ */
+ static void ClearOriginDataObserverInit();
+
+ nsresult
+ RemovePermissionsWithAttributes(mozilla::OriginAttributesPattern& aAttrs);
+
+private:
+ virtual ~nsPermissionManager();
+
+ int32_t GetTypeIndex(const char *aTypeString,
+ bool aAdd);
+
+ PermissionHashKey* GetPermissionHashKey(nsIPrincipal* aPrincipal,
+ uint32_t aType,
+ bool aExactHostMatch);
+
+ nsresult CommonTestPermission(nsIPrincipal* aPrincipal,
+ const char *aType,
+ uint32_t *aPermission,
+ bool aExactHostMatch,
+ bool aIncludingSession);
+
+ nsresult OpenDatabase(nsIFile* permissionsFile);
+ nsresult InitDB(bool aRemoveFile);
+ nsresult CreateTable();
+ nsresult Import();
+ nsresult ImportDefaults();
+ nsresult _DoImport(nsIInputStream *inputStream, mozIStorageConnection *aConn);
+ nsresult Read();
+ void NotifyObserversWithPermission(nsIPrincipal* aPrincipal,
+ const nsCString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ const char16_t *aData);
+ void NotifyObservers(nsIPermission *aPermission, const char16_t *aData);
+
+ // Finalize all statements, close the DB and null it.
+ // if aRebuildOnSuccess, reinitialize database
+ void CloseDB(bool aRebuildOnSuccess = false);
+
+ nsresult RemoveAllInternal(bool aNotifyObservers);
+ nsresult RemoveAllFromMemory();
+ static void UpdateDB(OperationType aOp,
+ mozIStorageAsyncStatement* aStmt,
+ int64_t aID,
+ const nsACString& aOrigin,
+ const nsACString& aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime);
+
+ /**
+ * This method removes all permissions modified after the specified time.
+ */
+ nsresult
+ RemoveAllModifiedSince(int64_t aModificationTime);
+
+ /**
+ * Retrieve permissions from chrome process.
+ */
+ nsresult
+ FetchPermissions();
+
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtInsert;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtDelete;
+ nsCOMPtr<mozIStorageAsyncStatement> mStmtUpdate;
+
+ bool mMemoryOnlyDB;
+
+ nsTHashtable<PermissionHashKey> mPermissionTable;
+ // a unique, monotonically increasing id used to identify each database entry
+ int64_t mLargestID;
+
+ // An array to store the strings identifying the different types.
+ nsTArray<nsCString> mTypeArray;
+
+ // Initially, |false|. Set to |true| once shutdown has started, to avoid
+ // reopening the database.
+ bool mIsShuttingDown;
+
+ friend class DeleteFromMozHostListener;
+ friend class CloseDatabaseListener;
+};
+
+// {4F6B5E00-0C36-11d5-A535-0010A401EB10}
+#define NS_PERMISSIONMANAGER_CID \
+{ 0x4f6b5e00, 0xc36, 0x11d5, { 0xa5, 0x35, 0x0, 0x10, 0xa4, 0x1, 0xeb, 0x10 } }
+
+#endif /* nsPermissionManager_h__ */
diff --git a/extensions/cookie/nsPopupWindowManager.cpp b/extensions/cookie/nsPopupWindowManager.cpp
new file mode 100644
index 000000000..d95fb7440
--- /dev/null
+++ b/extensions/cookie/nsPopupWindowManager.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPopupWindowManager.h"
+
+#include "nsCRT.h"
+#include "nsIServiceManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "mozilla/Services.h"
+
+/**
+ * The Popup Window Manager maintains popup window permissions by website.
+ */
+
+static const char kPopupDisablePref[] = "dom.disable_open_during_load";
+
+//*****************************************************************************
+//*** nsPopupWindowManager object management and nsISupports
+//*****************************************************************************
+
+nsPopupWindowManager::nsPopupWindowManager() :
+ mPolicy(ALLOW_POPUP)
+{
+}
+
+nsPopupWindowManager::~nsPopupWindowManager()
+{
+}
+
+NS_IMPL_ISUPPORTS(nsPopupWindowManager,
+ nsIPopupWindowManager,
+ nsIObserver,
+ nsISupportsWeakReference)
+
+nsresult
+nsPopupWindowManager::Init()
+{
+ nsresult rv;
+ mPermissionManager = mozilla::services::GetPermissionManager();
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ bool permission;
+ rv = prefBranch->GetBoolPref(kPopupDisablePref, &permission);
+ if (NS_FAILED(rv)) {
+ permission = true;
+ }
+ mPolicy = permission ? (uint32_t) DENY_POPUP : (uint32_t) ALLOW_POPUP;
+
+ prefBranch->AddObserver(kPopupDisablePref, this, true);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPopupWindowManager::nsIPopupWindowManager
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsPopupWindowManager::TestPermission(nsIPrincipal* aPrincipal,
+ uint32_t *aPermission)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aPermission);
+
+ uint32_t permit;
+ *aPermission = mPolicy;
+
+ if (mPermissionManager) {
+ if (NS_SUCCEEDED(mPermissionManager->TestPermissionFromPrincipal(aPrincipal, "popup", &permit))) {
+ // Share some constants between interfaces?
+ if (permit == nsIPermissionManager::ALLOW_ACTION) {
+ *aPermission = ALLOW_POPUP;
+ } else if (permit == nsIPermissionManager::DENY_ACTION) {
+ *aPermission = DENY_POPUP;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPopupWindowManager::nsIObserver
+//*****************************************************************************
+NS_IMETHODIMP
+nsPopupWindowManager::Observe(nsISupports *aSubject,
+ const char *aTopic,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject);
+ NS_ASSERTION(!nsCRT::strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic),
+ "unexpected topic - we only deal with pref changes!");
+
+ if (prefBranch) {
+ // refresh our local copy of the "disable popups" pref
+ bool permission = true;
+ prefBranch->GetBoolPref(kPopupDisablePref, &permission);
+
+ mPolicy = permission ? (uint32_t) DENY_POPUP : (uint32_t) ALLOW_POPUP;
+ }
+
+ return NS_OK;
+}
diff --git a/extensions/cookie/nsPopupWindowManager.h b/extensions/cookie/nsPopupWindowManager.h
new file mode 100644
index 000000000..dd83b5abb
--- /dev/null
+++ b/extensions/cookie/nsPopupWindowManager.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPopupWindowManager_h__
+#define nsPopupWindowManager_h__
+
+#include "nsCOMPtr.h"
+
+#include "nsIObserver.h"
+#include "nsIPermissionManager.h"
+#include "nsIPopupWindowManager.h"
+#include "nsWeakReference.h"
+
+class nsPopupWindowManager : public nsIPopupWindowManager,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPOPUPWINDOWMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsPopupWindowManager();
+ nsresult Init();
+
+private:
+ virtual ~nsPopupWindowManager();
+
+ uint32_t mPolicy;
+ nsCOMPtr<nsIPermissionManager> mPermissionManager;
+};
+
+// {822bcd11-6432-48be-9e9d-36f7804b7747}
+#define NS_POPUPWINDOWMANAGER_CID \
+ {0x822bcd11, 0x6432, 0x48be, {0x9e, 0x9d, 0x36, 0xf7, 0x80, 0x4b, 0x77, 0x47}}
+
+#endif /* nsPopupWindowManager_h__ */
diff --git a/extensions/cookie/test/beltzner.jpg b/extensions/cookie/test/beltzner.jpg
new file mode 100644
index 000000000..75849bc40
--- /dev/null
+++ b/extensions/cookie/test/beltzner.jpg
Binary files differ
diff --git a/extensions/cookie/test/beltzner.jpg^headers^ b/extensions/cookie/test/beltzner.jpg^headers^
new file mode 100644
index 000000000..cb51f2701
--- /dev/null
+++ b/extensions/cookie/test/beltzner.jpg^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: mike=beltzer
+
diff --git a/extensions/cookie/test/browser.ini b/extensions/cookie/test/browser.ini
new file mode 100644
index 000000000..da7b1548f
--- /dev/null
+++ b/extensions/cookie/test/browser.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[browser_test_favicon.js]
diff --git a/extensions/cookie/test/browser_test_favicon.js b/extensions/cookie/test/browser_test_favicon.js
new file mode 100644
index 000000000..a89a70017
--- /dev/null
+++ b/extensions/cookie/test/browser_test_favicon.js
@@ -0,0 +1,28 @@
+// tests third party cookie blocking using a favicon load directly from chrome.
+// in this case, the docshell of the channel is chrome, not content; thus
+// the cookie should be considered third party.
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+
+ Services.obs.addObserver(function (theSubject, theTopic, theData) {
+ var uri = theSubject.QueryInterface(Components.interfaces.nsIURI);
+ var domain = uri.host;
+
+ if (domain == "example.org") {
+ ok(true, "foreign favicon cookie was blocked");
+
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ Services.obs.removeObserver(arguments.callee, "cookie-rejected");
+
+ finish();
+ }
+ }, "cookie-rejected", false);
+
+ // kick off a favicon load
+ gBrowser.setIcon(gBrowser.selectedTab, "http://example.org/tests/extensions/cookie/test/damonbowling.jpg",
+ Services.scriptSecurityManager.getSystemPrincipal());
+}
diff --git a/extensions/cookie/test/damonbowling.jpg b/extensions/cookie/test/damonbowling.jpg
new file mode 100644
index 000000000..8bdb2b604
--- /dev/null
+++ b/extensions/cookie/test/damonbowling.jpg
Binary files differ
diff --git a/extensions/cookie/test/damonbowling.jpg^headers^ b/extensions/cookie/test/damonbowling.jpg^headers^
new file mode 100644
index 000000000..77f4f4908
--- /dev/null
+++ b/extensions/cookie/test/damonbowling.jpg^headers^
@@ -0,0 +1,2 @@
+Cache-Control: no-store
+Set-Cookie: damon=bowling
diff --git a/extensions/cookie/test/file_chromecommon.js b/extensions/cookie/test/file_chromecommon.js
new file mode 100644
index 000000000..4383876b2
--- /dev/null
+++ b/extensions/cookie/test/file_chromecommon.js
@@ -0,0 +1,15 @@
+let { classes: Cc, utils: Cu, interfaces: Ci } = Components;
+
+let cs = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager2);
+
+addMessageListener("getCookieCountAndClear", () => {
+ let count = 0;
+ for (let list = cs.enumerator; list.hasMoreElements(); list.getNext())
+ ++count;
+ cs.removeAll();
+
+ sendAsyncMessage("getCookieCountAndClear:return", { count });
+});
+
+cs.removeAll();
diff --git a/extensions/cookie/test/file_domain_hierarchy_inner.html b/extensions/cookie/test/file_domain_hierarchy_inner.html
new file mode 100644
index 000000000..1a3734597
--- /dev/null
+++ b/extensions/cookie/test/file_domain_hierarchy_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://example.com/tests/extensions/cookie/test/file_domain_hierarchy_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_domain_hierarchy_inner_inner.html b/extensions/cookie/test/file_domain_hierarchy_inner_inner.html
new file mode 100644
index 000000000..3075b971a
--- /dev/null
+++ b/extensions/cookie/test/file_domain_hierarchy_inner_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta2=tag2">
+ <script type="text/javascript">
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://example.org/tests/extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html b/extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html
new file mode 100644
index 000000000..e8d09338b
--- /dev/null
+++ b/extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta3=tag3">
+ <script type="text/javascript">
+ document.cookie = "can3=has3";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_domain_inner.html b/extensions/cookie/test/file_domain_inner.html
new file mode 100644
index 000000000..1a05c45f8
--- /dev/null
+++ b/extensions/cookie/test/file_domain_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://example.org/tests/extensions/cookie/test/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_domain_inner_inner.html b/extensions/cookie/test/file_domain_inner_inner.html
new file mode 100644
index 000000000..7bc0e44a9
--- /dev/null
+++ b/extensions/cookie/test/file_domain_inner_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta2=tag2">
+ <script type="text/javascript">
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_image_inner.html b/extensions/cookie/test/file_image_inner.html
new file mode 100644
index 000000000..aada82d71
--- /dev/null
+++ b/extensions/cookie/test/file_image_inner.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+</head>
+<body>
+<iframe name="frame1" src="http://example.org/tests/extensions/cookie/test/file_image_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_image_inner_inner.html b/extensions/cookie/test/file_image_inner_inner.html
new file mode 100644
index 000000000..1989b53de
--- /dev/null
+++ b/extensions/cookie/test/file_image_inner_inner.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <link rel="stylesheet" type="text/css" media="all" href="http://example.org/tests/extensions/cookie/test/test1.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="http://example.com/tests/extensions/cookie/test/test2.css" />
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta2=tag2">
+ <script type="text/javascript">
+ function runTest() {
+ document.cookie = "can2=has2";
+
+ // send a message to our test document, to say we're done loading
+ window.parent.opener.postMessage("message", "http://mochi.test:8888");
+ }
+ </script>
+</head>
+<body>
+<img src="http://example.org/tests/extensions/cookie/test/image1.png" onload="runTest()" />
+<img src="http://example.com/tests/extensions/cookie/test/image2.png" onload="runTest()" />
+</body>
+</html>
diff --git a/extensions/cookie/test/file_loadflags_inner.html b/extensions/cookie/test/file_loadflags_inner.html
new file mode 100644
index 000000000..5a7a6ccc1
--- /dev/null
+++ b/extensions/cookie/test/file_loadflags_inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ function runTest() {
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("f_lf_i msg data img", "http://mochi.test:8888");
+ }
+ </script>
+</head>
+<body onload="window.opener.postMessage('f_lf_i msg data page', 'http://mochi.test:8888');">
+<img src="http://example.org/tests/extensions/cookie/test/beltzner.jpg" onload="runTest()" />
+</body>
+</html>
diff --git a/extensions/cookie/test/file_localhost_inner.html b/extensions/cookie/test/file_localhost_inner.html
new file mode 100644
index 000000000..db9917f00
--- /dev/null
+++ b/extensions/cookie/test/file_localhost_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://mochi.test:8888/tests/extensions/cookie/test/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_loopback_inner.html b/extensions/cookie/test/file_loopback_inner.html
new file mode 100644
index 000000000..e6c115c50
--- /dev/null
+++ b/extensions/cookie/test/file_loopback_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://127.0.0.1:8888/tests/extensions/cookie/test/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_subdomain_inner.html b/extensions/cookie/test/file_subdomain_inner.html
new file mode 100644
index 000000000..5f608b811
--- /dev/null
+++ b/extensions/cookie/test/file_subdomain_inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <META HTTP-EQUIV="Set-Cookie" CONTENT="meta=tag">
+ <script type="text/javascript">
+ document.cookie = "can=has";
+
+ // send a message to our test document, to say we're done loading
+ window.opener.postMessage("message", "http://mochi.test:8888");
+ </script>
+<body>
+<iframe name="frame1" src="http://test2.example.org/tests/extensions/cookie/test/file_domain_inner_inner.html"></iframe>
+</body>
+</html>
diff --git a/extensions/cookie/test/file_testcommon.js b/extensions/cookie/test/file_testcommon.js
new file mode 100644
index 000000000..3ee53c52c
--- /dev/null
+++ b/extensions/cookie/test/file_testcommon.js
@@ -0,0 +1,70 @@
+const SCRIPT_URL = SimpleTest.getTestFileURL("file_chromecommon.js");
+
+var gExpectedCookies;
+var gExpectedLoads;
+
+var gPopup;
+
+var gScript;
+
+var gLoads = 0;
+
+function setupTest(uri, cookies, loads) {
+ SimpleTest.waitForExplicitFinish();
+
+ var prefSet = new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({ set: [["network.cookie.cookieBehavior", 1]] }, resolve);
+ });
+
+ gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
+ gExpectedCookies = cookies;
+ gExpectedLoads = loads;
+
+ // Listen for MessageEvents.
+ window.addEventListener("message", messageReceiver, false);
+
+ prefSet.then(() => {
+ // load a window which contains an iframe; each will attempt to set
+ // cookies from their respective domains.
+ gPopup = window.open(uri, 'hai', 'width=100,height=100');
+ });
+}
+
+function finishTest() {
+ gScript.destroy();
+ SimpleTest.finish();
+}
+
+/** Receives MessageEvents to this window. */
+// Count and check loads.
+function messageReceiver(evt) {
+ is(evt.data, "message", "message data received from popup");
+ if (evt.data != "message") {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver, false);
+
+ finishTest();
+ return;
+ }
+
+ // only run the test when all our children are done loading & setting cookies
+ if (++gLoads == gExpectedLoads) {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver, false);
+
+ runTest();
+ }
+}
+
+// runTest() is run by messageReceiver().
+// Count and check cookies.
+function runTest() {
+ // set a cookie from a domain of "localhost"
+ document.cookie = "oh=hai";
+
+ gScript.addMessageListener("getCookieCountAndClear:return", ({ count }) => {
+ is(count, gExpectedCookies, "total number of cookies");
+ finishTest();
+ });
+ gScript.sendAsyncMessage("getCookieCountAndClear");
+}
diff --git a/extensions/cookie/test/file_testloadflags.js b/extensions/cookie/test/file_testloadflags.js
new file mode 100644
index 000000000..9cf4a15a6
--- /dev/null
+++ b/extensions/cookie/test/file_testloadflags.js
@@ -0,0 +1,104 @@
+const SCRIPT_URL = SimpleTest.getTestFileURL('file_testloadflags_chromescript.js');
+
+var gExpectedCookies;
+var gExpectedHeaders;
+var gExpectedLoads;
+
+var gObs;
+var gPopup;
+
+var gHeaders = 0;
+var gLoads = 0;
+
+// setupTest() is run from 'onload='.
+function setupTest(uri, domain, cookies, loads, headers) {
+ info("setupTest uri: " + uri + " domain: " + domain + " cookies: " + cookies +
+ " loads: " + loads + " headers: " + headers);
+
+ SimpleTest.waitForExplicitFinish();
+
+ var prefSet = new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({ set: [["network.cookie.cookieBehavior", 1]] }, resolve);
+ });
+
+ gExpectedCookies = cookies;
+ gExpectedLoads = loads;
+ gExpectedHeaders = headers;
+
+ gScript = SpecialPowers.loadChromeScript(SCRIPT_URL);
+ gScript.addMessageListener("info", ({ str }) => info(str));
+ gScript.addMessageListener("ok", ({ c, m }) => ok(c, m));
+ gScript.addMessageListener("observer:gotCookie", ({ cookie, uri }) => {
+ isnot(cookie.indexOf("oh=hai"), -1,
+ "cookie 'oh=hai' is in header for " + uri);
+ ++gHeaders;
+ });
+
+ var scriptReady = new Promise(resolve => {
+ gScript.addMessageListener("init:return", resolve);
+ gScript.sendAsyncMessage("init", { domain });
+ });
+
+ // Listen for MessageEvents.
+ window.addEventListener("message", messageReceiver, false);
+
+ Promise.all([ prefSet, scriptReady ]).then(() => {
+ // load a window which contains an iframe; each will attempt to set
+ // cookies from their respective domains.
+ gPopup = window.open(uri, 'hai', 'width=100,height=100');
+ });
+}
+
+function finishTest()
+{
+ gScript.addMessageListener("shutdown:return", () => {
+ gScript.destroy();
+ SimpleTest.finish();
+ });
+ gScript.sendAsyncMessage("shutdown");
+}
+
+/** Receives MessageEvents to this window. */
+// Count and check loads.
+function messageReceiver(evt)
+{
+ ok(evt.data == "f_lf_i msg data img" || evt.data == "f_lf_i msg data page",
+ "message data received from popup");
+ if (evt.data == "f_lf_i msg data img") {
+ info("message data received from popup for image");
+ }
+ if (evt.data == "f_lf_i msg data page") {
+ info("message data received from popup for page");
+ }
+ if (evt.data != "f_lf_i msg data img" && evt.data != "f_lf_i msg data page") {
+ info("got this message but don't know what it is " + evt.data);
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver, false);
+
+ finishTest();
+ return;
+ }
+
+ // only run the test when all our children are done loading & setting cookies
+ if (++gLoads == gExpectedLoads) {
+ gPopup.close();
+ window.removeEventListener("message", messageReceiver, false);
+
+ runTest();
+ }
+}
+
+// runTest() is run by messageReceiver().
+// Check headers, and count and check cookies.
+function runTest() {
+ // set a cookie from a domain of "localhost"
+ document.cookie = "o=noes";
+
+ is(gHeaders, gExpectedHeaders, "number of observed request headers");
+ gScript.addMessageListener("getCookieCount:return", ({ count }) => {
+ is(count, gExpectedCookies, "total number of cookies");
+ finishTest();
+ });
+
+ gScript.sendAsyncMessage("getCookieCount");
+}
diff --git a/extensions/cookie/test/file_testloadflags_chromescript.js b/extensions/cookie/test/file_testloadflags_chromescript.js
new file mode 100644
index 000000000..26eedacd9
--- /dev/null
+++ b/extensions/cookie/test/file_testloadflags_chromescript.js
@@ -0,0 +1,112 @@
+let { classes: Cc, interfaces: Ci } = Components;
+
+var gObs;
+
+function info(s) {
+ sendAsyncMessage("info", { str: String(s) });
+}
+
+function ok(c, m) {
+ sendAsyncMessage("ok", { c, m });
+}
+
+function is(a, b, m) {
+ ok(Object.is(a, b), m + " (" + a + " === " + b + ")");
+}
+
+// Count headers.
+function obs() {
+ info("adding observer");
+
+ this.os = Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService);
+ this.os.addObserver(this, "http-on-modify-request", false);
+}
+
+obs.prototype = {
+ observe(theSubject, theTopic, theData) {
+ info("theSubject " + theSubject);
+ info("theTopic " + theTopic);
+ info("theData " + theData);
+
+ var channel = theSubject.QueryInterface(Ci.nsIHttpChannel);
+ info("channel " + channel);
+ try {
+ info("channel.URI " + channel.URI);
+ info("channel.URI.spec " + channel.URI.spec);
+ channel.visitRequestHeaders({
+ visitHeader: function(aHeader, aValue) {
+ info(aHeader + ": " + aValue);
+ }});
+ } catch (err) {
+ ok(false, "catch error " + err);
+ }
+
+ // Ignore notifications we don't care about (like favicons)
+ if (channel.URI.spec.indexOf(
+ "http://example.org/tests/extensions/cookie/test/") == -1) {
+ info("ignoring this one");
+ return;
+ }
+
+ sendAsyncMessage("observer:gotCookie",
+ { cookie: channel.getRequestHeader("Cookie"),
+ uri: channel.URI.spec });
+ },
+
+ remove() {
+ info("removing observer");
+
+ this.os.removeObserver(this, "http-on-modify-request");
+ this.os = null;
+ }
+}
+
+function getCookieCount(cs) {
+ let count = 0;
+ let list = cs.enumerator;
+ while (list.hasMoreElements()) {
+ let cookie = list.getNext().QueryInterface(Ci.nsICookie);
+ info("cookie: " + cookie);
+ info("cookie host " + cookie.host + " path " + cookie.path + " name " + cookie.name +
+ " value " + cookie.value + " isSecure " + cookie.isSecure + " expires " + cookie.expires);
+ ++count;
+ }
+
+ return count;
+}
+
+addMessageListener("init", ({ domain }) => {
+ let cs = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager2);
+
+ info("we are going to remove these cookies");
+
+ let count = getCookieCount(cs);
+ info(count + " cookies");
+
+ cs.removeAll();
+ cs.add(domain, "", "oh", "hai", false, false, true, Math.pow(2, 62), {});
+ is(cs.countCookiesFromHost(domain), 1, "number of cookies for domain " + domain);
+
+ gObs = new obs();
+ sendAsyncMessage("init:return");
+});
+
+addMessageListener("getCookieCount", () => {
+ let cs = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ let count = getCookieCount(cs);
+
+ cs.removeAll();
+ sendAsyncMessage("getCookieCount:return", { count });
+});
+
+addMessageListener("shutdown", () => {
+ gObs.remove();
+
+ let cs = Cc["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager2);
+ cs.removeAll();
+ sendAsyncMessage("shutdown:return");
+});
diff --git a/extensions/cookie/test/image1.png b/extensions/cookie/test/image1.png
new file mode 100644
index 000000000..272d67c0c
--- /dev/null
+++ b/extensions/cookie/test/image1.png
Binary files differ
diff --git a/extensions/cookie/test/image1.png^headers^ b/extensions/cookie/test/image1.png^headers^
new file mode 100644
index 000000000..2390289e0
--- /dev/null
+++ b/extensions/cookie/test/image1.png^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: foo=bar
+
diff --git a/extensions/cookie/test/image2.png b/extensions/cookie/test/image2.png
new file mode 100644
index 000000000..272d67c0c
--- /dev/null
+++ b/extensions/cookie/test/image2.png
Binary files differ
diff --git a/extensions/cookie/test/image2.png^headers^ b/extensions/cookie/test/image2.png^headers^
new file mode 100644
index 000000000..6c0eea5ab
--- /dev/null
+++ b/extensions/cookie/test/image2.png^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-store
+Set-Cookie: foo2=bar2
+
diff --git a/extensions/cookie/test/mochitest.ini b/extensions/cookie/test/mochitest.ini
new file mode 100644
index 000000000..673fad7b8
--- /dev/null
+++ b/extensions/cookie/test/mochitest.ini
@@ -0,0 +1,41 @@
+[DEFAULT]
+support-files =
+ beltzner.jpg
+ beltzner.jpg^headers^
+ damonbowling.jpg
+ damonbowling.jpg^headers^
+ file_chromecommon.js
+ file_domain_hierarchy_inner.html
+ file_domain_hierarchy_inner_inner.html
+ file_domain_hierarchy_inner_inner_inner.html
+ file_domain_inner.html
+ file_domain_inner_inner.html
+ file_image_inner.html
+ file_image_inner_inner.html
+ file_loadflags_inner.html
+ file_localhost_inner.html
+ file_loopback_inner.html
+ file_subdomain_inner.html
+ file_testcommon.js
+ file_testloadflags.js
+ file_testloadflags_chromescript.js
+ image1.png
+ image1.png^headers^
+ image2.png
+ image2.png^headers^
+ test1.css
+ test1.css^headers^
+ test2.css
+ test2.css^headers^
+
+[test_different_domain_in_hierarchy.html]
+[test_differentdomain.html]
+[test_image.html]
+[test_loadflags.html]
+[test_same_base_domain.html]
+[test_same_base_domain_2.html]
+[test_same_base_domain_3.html]
+[test_same_base_domain_4.html]
+[test_same_base_domain_5.html]
+[test_same_base_domain_6.html]
+[test_samedomain.html]
diff --git a/extensions/cookie/test/moz.build b/extensions/cookie/test/moz.build
new file mode 100644
index 000000000..7474661d8
--- /dev/null
+++ b/extensions/cookie/test/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ 'unit/xpcshell.ini',
+ 'unit_ipc/xpcshell.ini',
+]
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
+
+BROWSER_CHROME_MANIFESTS += ['browser.ini']
+
diff --git a/extensions/cookie/test/test1.css b/extensions/cookie/test/test1.css
new file mode 100644
index 000000000..139597f9c
--- /dev/null
+++ b/extensions/cookie/test/test1.css
@@ -0,0 +1,2 @@
+
+
diff --git a/extensions/cookie/test/test1.css^headers^ b/extensions/cookie/test/test1.css^headers^
new file mode 100644
index 000000000..729babb5a
--- /dev/null
+++ b/extensions/cookie/test/test1.css^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-cache
+Set-Cookie: css=bar
+
diff --git a/extensions/cookie/test/test2.css b/extensions/cookie/test/test2.css
new file mode 100644
index 000000000..139597f9c
--- /dev/null
+++ b/extensions/cookie/test/test2.css
@@ -0,0 +1,2 @@
+
+
diff --git a/extensions/cookie/test/test2.css^headers^ b/extensions/cookie/test/test2.css^headers^
new file mode 100644
index 000000000..b12d32c72
--- /dev/null
+++ b/extensions/cookie/test/test2.css^headers^
@@ -0,0 +1,3 @@
+Cache-Control: no-cache
+Set-Cookie: css2=bar2
+
diff --git a/extensions/cookie/test/test_different_domain_in_hierarchy.html b/extensions/cookie/test/test_different_domain_in_hierarchy.html
new file mode 100644
index 000000000..61bc58539
--- /dev/null
+++ b/extensions/cookie/test/test_different_domain_in_hierarchy.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test cookie requests from within a window hierarchy of different base domains</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_domain_hierarchy_inner.html', 3, 3)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_differentdomain.html b/extensions/cookie/test/test_differentdomain.html
new file mode 100644
index 000000000..a74bceaf4
--- /dev/null
+++ b/extensions/cookie/test/test_differentdomain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.com/tests/extensions/cookie/test/file_domain_inner.html', 3, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_image.html b/extensions/cookie/test/test_image.html
new file mode 100644
index 000000000..f39a19894
--- /dev/null
+++ b/extensions/cookie/test/test_image.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_image_inner.html', 7, 3)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js"></script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_loadflags.html b/extensions/cookie/test/test_loadflags.html
new file mode 100644
index 000000000..3eea710f2
--- /dev/null
+++ b/extensions/cookie/test/test_loadflags.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<!--
+ *5 cookies: 1+1 from file_testloadflags.js, 2 from file_loadflags_inner.html + 1 from beltzner.jpg.
+ *1 load: file_loadflags_inner.html.
+ *2 headers: 1 for file_loadflags_inner.html + 1 for beltzner.jpg.
+ -->
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_loadflags_inner.html', 'example.org', 5, 2, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testloadflags.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain.html b/extensions/cookie/test/test_same_base_domain.html
new file mode 100644
index 000000000..9232b4e09
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://test1.example.org/tests/extensions/cookie/test/file_domain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain_2.html b/extensions/cookie/test/test_same_base_domain_2.html
new file mode 100644
index 000000000..d692524c5
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain_2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://test1.example.org/tests/extensions/cookie/test/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain_3.html b/extensions/cookie/test/test_same_base_domain_3.html
new file mode 100644
index 000000000..f0cd687f9
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain_3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain_4.html b/extensions/cookie/test/test_same_base_domain_4.html
new file mode 100644
index 000000000..0a4026fe2
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain_4.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://mochi.test:8888/tests/extensions/cookie/test/file_localhost_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain_5.html b/extensions/cookie/test/test_same_base_domain_5.html
new file mode 100644
index 000000000..49b2379b6
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain_5.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://sub1.test1.example.org/tests/extensions/cookie/test/file_subdomain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_same_base_domain_6.html b/extensions/cookie/test/test_same_base_domain_6.html
new file mode 100644
index 000000000..9056739a6
--- /dev/null
+++ b/extensions/cookie/test/test_same_base_domain_6.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://127.0.0.1:8888/tests/extensions/cookie/test/file_loopback_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/test_samedomain.html b/extensions/cookie/test/test_samedomain.html
new file mode 100644
index 000000000..9051d3b39
--- /dev/null
+++ b/extensions/cookie/test/test_samedomain.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Cross domain access to properties</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_domain_inner.html', 5, 2)">
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="text/javascript" src="file_testcommon.js">
+</script>
+</pre>
+</body>
+</html>
diff --git a/extensions/cookie/test/unit/cookieprompt.js b/extensions/cookie/test/unit/cookieprompt.js
new file mode 100644
index 000000000..c0a17e006
--- /dev/null
+++ b/extensions/cookie/test/unit/cookieprompt.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var Ci = Components.interfaces;
+
+function CookiePromptService() {
+}
+
+CookiePromptService.prototype = {
+ classID: Components.ID("{509b5540-c87c-11dd-ad8b-0800200c9a66}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICookiePromptService]),
+
+ cookieDialog: function(parent, cookie, hostname,
+ cookiesFromHost, changingCookie,
+ rememberDecision) {
+ return 0;
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CookiePromptService]);
diff --git a/extensions/cookie/test/unit/cookieprompt.manifest b/extensions/cookie/test/unit/cookieprompt.manifest
new file mode 100644
index 000000000..5a3bcad40
--- /dev/null
+++ b/extensions/cookie/test/unit/cookieprompt.manifest
@@ -0,0 +1,2 @@
+component {509b5540-c87c-11dd-ad8b-0800200c9a66} cookieprompt.js
+contract @mozilla.org/embedcomp/cookieprompt-service;1 {509b5540-c87c-11dd-ad8b-0800200c9a66}
diff --git a/extensions/cookie/test/unit/head_cookies.js b/extensions/cookie/test/unit/head_cookies.js
new file mode 100644
index 000000000..fe4c0a53d
--- /dev/null
+++ b/extensions/cookie/test/unit/head_cookies.js
@@ -0,0 +1,570 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+XPCOMUtils.defineLazyServiceGetter(Services, "cookies",
+ "@mozilla.org/cookieService;1",
+ "nsICookieService");
+XPCOMUtils.defineLazyServiceGetter(Services, "cookiemgr",
+ "@mozilla.org/cookiemanager;1",
+ "nsICookieManager2");
+
+XPCOMUtils.defineLazyServiceGetter(Services, "etld",
+ "@mozilla.org/network/effective-tld-service;1",
+ "nsIEffectiveTLDService");
+
+function do_check_throws(f, result, stack)
+{
+ if (!stack)
+ stack = Components.stack.caller;
+
+ try {
+ f();
+ } catch (exc) {
+ if (exc.result == result)
+ return;
+ do_throw("expected result " + result + ", caught " + exc, stack);
+ }
+ do_throw("expected result " + result + ", none thrown", stack);
+}
+
+// Helper to step a generator function and catch a StopIteration exception.
+function do_run_generator(generator)
+{
+ try {
+ generator.next();
+ } catch (e) {
+ if (e != StopIteration)
+ do_throw("caught exception " + e, Components.stack.caller);
+ }
+}
+
+// Helper to finish a generator function test.
+function do_finish_generator_test(generator)
+{
+ do_execute_soon(function() {
+ generator.close();
+ do_test_finished();
+ });
+}
+
+function _observer(generator, topic) {
+ Services.obs.addObserver(this, topic, false);
+
+ this.generator = generator;
+ this.topic = topic;
+}
+
+_observer.prototype = {
+ observe: function (subject, topic, data) {
+ do_check_eq(this.topic, topic);
+
+ Services.obs.removeObserver(this, this.topic);
+
+ // Continue executing the generator function.
+ if (this.generator)
+ do_run_generator(this.generator);
+
+ this.generator = null;
+ this.topic = null;
+ }
+}
+
+// Close the cookie database. If a generator is supplied, it will be invoked
+// once the close is complete.
+function do_close_profile(generator) {
+ // Register an observer for db close.
+ let obs = new _observer(generator, "cookie-db-closed");
+
+ // Close the db.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-before-change", "shutdown-persist");
+}
+
+// Load the cookie database. If a generator is supplied, it will be invoked
+// once the load is complete.
+function do_load_profile(generator) {
+ // Register an observer for read completion.
+ let obs = new _observer(generator, "cookie-db-read");
+
+ // Load the profile.
+ let service = Services.cookies.QueryInterface(Ci.nsIObserver);
+ service.observe(null, "profile-do-change", "");
+}
+
+// Set a single session cookie using http and test the cookie count
+// against 'expected'
+function do_set_single_http_cookie(uri, channel, expected) {
+ Services.cookies.setCookieStringFromHttp(uri, null, null, "foo=bar", null, channel);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected);
+}
+
+// Set four cookies; with & without channel, http and non-http; and test
+// the cookie count against 'expected' after each set.
+function do_set_cookies(uri, channel, session, expected) {
+ let suffix = session ? "" : "; max-age=1000";
+
+ // without channel
+ Services.cookies.setCookieString(uri, null, "oh=hai" + suffix, null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[0]);
+ // with channel
+ Services.cookies.setCookieString(uri, null, "can=has" + suffix, channel);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[1]);
+ // without channel, from http
+ Services.cookies.setCookieStringFromHttp(uri, null, null, "cheez=burger" + suffix, null, null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[2]);
+ // with channel, from http
+ Services.cookies.setCookieStringFromHttp(uri, null, null, "hot=dog" + suffix, null, channel);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[3]);
+}
+
+function do_count_enumerator(enumerator) {
+ let i = 0;
+ while (enumerator.hasMoreElements()) {
+ enumerator.getNext();
+ ++i;
+ }
+ return i;
+}
+
+function do_count_cookies() {
+ return do_count_enumerator(Services.cookiemgr.enumerator);
+}
+
+// Helper object to store cookie data.
+function Cookie(name,
+ value,
+ host,
+ path,
+ expiry,
+ lastAccessed,
+ creationTime,
+ isSession,
+ isSecure,
+ isHttpOnly)
+{
+ this.name = name;
+ this.value = value;
+ this.host = host;
+ this.path = path;
+ this.expiry = expiry;
+ this.lastAccessed = lastAccessed;
+ this.creationTime = creationTime;
+ this.isSession = isSession;
+ this.isSecure = isSecure;
+ this.isHttpOnly = isHttpOnly;
+
+ let strippedHost = host.charAt(0) == '.' ? host.slice(1) : host;
+
+ try {
+ this.baseDomain = Services.etld.getBaseDomainFromHost(strippedHost);
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
+ e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS)
+ this.baseDomain = strippedHost;
+ }
+}
+
+// Object representing a database connection and associated statements. The
+// implementation varies depending on schema version.
+function CookieDatabaseConnection(file, schema)
+{
+ // Manually generate a cookies.sqlite file with appropriate rows, columns,
+ // and schema version. If it already exists, just set up our statements.
+ let exists = file.exists();
+
+ this.db = Services.storage.openDatabase(file);
+ this.schema = schema;
+ if (!exists)
+ this.db.schemaVersion = schema;
+
+ switch (schema) {
+ case 1:
+ {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ id, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :isSecure, \
+ :isHttpOnly)");
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id");
+
+ break;
+ }
+
+ case 2:
+ {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT OR REPLACE INTO moz_cookies ( \
+ id, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :isSecure, \
+ :isHttpOnly)");
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id");
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id");
+
+ break;
+ }
+
+ case 3:
+ {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ baseDomain TEXT, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER)");
+
+ this.db.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ id, \
+ baseDomain, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :id, \
+ :baseDomain, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :isSecure, \
+ :isHttpOnly)");
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies WHERE id = :id");
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id");
+
+ break;
+ }
+
+ case 4:
+ {
+ if (!exists) {
+ this.db.executeSimpleSQL(
+ "CREATE TABLE moz_cookies ( \
+ id INTEGER PRIMARY KEY, \
+ baseDomain TEXT, \
+ name TEXT, \
+ value TEXT, \
+ host TEXT, \
+ path TEXT, \
+ expiry INTEGER, \
+ lastAccessed INTEGER, \
+ creationTime INTEGER, \
+ isSecure INTEGER, \
+ isHttpOnly INTEGER \
+ CONSTRAINT moz_uniqueid UNIQUE (name, host, path))");
+
+ this.db.executeSimpleSQL(
+ "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)");
+
+ this.db.executeSimpleSQL(
+ "PRAGMA journal_mode = WAL");
+ }
+
+ this.stmtInsert = this.db.createStatement(
+ "INSERT INTO moz_cookies ( \
+ baseDomain, \
+ name, \
+ value, \
+ host, \
+ path, \
+ expiry, \
+ lastAccessed, \
+ creationTime, \
+ isSecure, \
+ isHttpOnly) \
+ VALUES ( \
+ :baseDomain, \
+ :name, \
+ :value, \
+ :host, \
+ :path, \
+ :expiry, \
+ :lastAccessed, \
+ :creationTime, \
+ :isSecure, \
+ :isHttpOnly)");
+
+ this.stmtDelete = this.db.createStatement(
+ "DELETE FROM moz_cookies \
+ WHERE name = :name AND host = :host AND path = :path");
+
+ this.stmtUpdate = this.db.createStatement(
+ "UPDATE moz_cookies SET lastAccessed = :lastAccessed \
+ WHERE name = :name AND host = :host AND path = :path");
+
+ break;
+ }
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+}
+
+CookieDatabaseConnection.prototype =
+{
+ insertCookie: function(cookie)
+ {
+ if (!(cookie instanceof Cookie))
+ do_throw("not a cookie");
+
+ switch (this.schema)
+ {
+ case 1:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 2:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 3:
+ this.stmtInsert.bindByName("id", cookie.creationTime);
+ this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ case 4:
+ this.stmtInsert.bindByName("baseDomain", cookie.baseDomain);
+ this.stmtInsert.bindByName("name", cookie.name);
+ this.stmtInsert.bindByName("value", cookie.value);
+ this.stmtInsert.bindByName("host", cookie.host);
+ this.stmtInsert.bindByName("path", cookie.path);
+ this.stmtInsert.bindByName("expiry", cookie.expiry);
+ this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed);
+ this.stmtInsert.bindByName("creationTime", cookie.creationTime);
+ this.stmtInsert.bindByName("isSecure", cookie.isSecure);
+ this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly);
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtInsert);
+ },
+
+ deleteCookie: function(cookie)
+ {
+ if (!(cookie instanceof Cookie))
+ do_throw("not a cookie");
+
+ switch (this.db.schemaVersion)
+ {
+ case 1:
+ case 2:
+ case 3:
+ this.stmtDelete.bindByName("id", cookie.creationTime);
+ break;
+
+ case 4:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtDelete);
+ },
+
+ updateCookie: function(cookie)
+ {
+ if (!(cookie instanceof Cookie))
+ do_throw("not a cookie");
+
+ switch (this.db.schemaVersion)
+ {
+ case 1:
+ do_throw("can't update a schema 1 cookie!");
+
+ case 2:
+ case 3:
+ this.stmtUpdate.bindByName("id", cookie.creationTime);
+ this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+ break;
+
+ case 4:
+ this.stmtDelete.bindByName("name", cookie.name);
+ this.stmtDelete.bindByName("host", cookie.host);
+ this.stmtDelete.bindByName("path", cookie.path);
+ this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed);
+ break;
+
+ default:
+ do_throw("unrecognized schemaVersion!");
+ }
+
+ do_execute_stmt(this.stmtUpdate);
+ },
+
+ close: function()
+ {
+ this.stmtInsert.finalize();
+ this.stmtDelete.finalize();
+ if (this.stmtUpdate)
+ this.stmtUpdate.finalize();
+ this.db.close();
+
+ this.stmtInsert = null;
+ this.stmtDelete = null;
+ this.stmtUpdate = null;
+ this.db = null;
+ }
+}
+
+function do_get_cookie_file(profile)
+{
+ let file = profile.clone();
+ file.append("cookies.sqlite");
+ return file;
+}
+
+// Count the cookies from 'host' in a database. If 'host' is null, count all
+// cookies.
+function do_count_cookies_in_db(connection, host)
+{
+ let select = null;
+ if (host) {
+ select = connection.createStatement(
+ "SELECT COUNT(1) FROM moz_cookies WHERE host = :host");
+ select.bindByName("host", host);
+ } else {
+ select = connection.createStatement(
+ "SELECT COUNT(1) FROM moz_cookies");
+ }
+
+ select.executeStep();
+ let result = select.getInt32(0);
+ select.reset();
+ select.finalize();
+ return result;
+}
+
+// Execute 'stmt', ensuring that we reset it if it throws.
+function do_execute_stmt(stmt)
+{
+ try {
+ stmt.executeStep();
+ stmt.reset();
+ } catch (e) {
+ stmt.reset();
+ throw e;
+ }
+}
diff --git a/extensions/cookie/test/unit/test_bug526789.js b/extensions/cookie/test/unit/test_bug526789.js
new file mode 100644
index 000000000..0eac1d492
--- /dev/null
+++ b/extensions/cookie/test/unit/test_bug526789.js
@@ -0,0 +1,248 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ cm.removeAll();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // test that variants of 'baz.com' get normalized appropriately, but that
+ // malformed hosts are rejected
+ cm.add("baz.com", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com"), 1);
+ do_check_eq(cm.countCookiesFromHost("BAZ.com"), 1);
+ do_check_eq(cm.countCookiesFromHost(".baz.com"), 1);
+ do_check_eq(cm.countCookiesFromHost("baz.com."), 0);
+ do_check_eq(cm.countCookiesFromHost(".baz.com."), 0);
+ do_check_throws(function() {
+ cm.countCookiesFromHost("baz.com..");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost("baz..com");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost("..baz.com");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ cm.remove("BAZ.com.", "foo", "/", false, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com"), 1);
+ cm.remove("baz.com", "foo", "/", false, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com"), 0);
+
+ // Test that 'baz.com' and 'baz.com.' are treated differently
+ cm.add("baz.com.", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com"), 0);
+ do_check_eq(cm.countCookiesFromHost("BAZ.com"), 0);
+ do_check_eq(cm.countCookiesFromHost(".baz.com"), 0);
+ do_check_eq(cm.countCookiesFromHost("baz.com."), 1);
+ do_check_eq(cm.countCookiesFromHost(".baz.com."), 1);
+ cm.remove("baz.com", "foo", "/", false, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com."), 1);
+ cm.remove("baz.com.", "foo", "/", false, {});
+ do_check_eq(cm.countCookiesFromHost("baz.com."), 0);
+
+ // test that domain cookies are illegal for IP addresses, aliases such as
+ // 'localhost', and eTLD's such as 'co.uk'
+ cm.add("192.168.0.1", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("192.168.0.1"), 1);
+ do_check_eq(cm.countCookiesFromHost("192.168.0.1."), 0);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".192.168.0.1");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".192.168.0.1.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.add("localhost", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("localhost"), 1);
+ do_check_eq(cm.countCookiesFromHost("localhost."), 0);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".localhost");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".localhost.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.add("co.uk", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("co.uk"), 1);
+ do_check_eq(cm.countCookiesFromHost("co.uk."), 0);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".co.uk");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".co.uk.");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.removeAll();
+
+ // test that setting an empty or '.' http:// host results in a no-op
+ var uri = NetUtil.newURI("http://baz.com/");
+ var emptyuri = NetUtil.newURI("http:///");
+ var doturi = NetUtil.newURI("http://./");
+ do_check_eq(uri.asciiHost, "baz.com");
+ do_check_eq(emptyuri.asciiHost, "");
+ do_check_eq(doturi.asciiHost, ".");
+ cs.setCookieString(emptyuri, null, "foo2=bar", null);
+ do_check_eq(getCookieCount(), 0);
+ cs.setCookieString(doturi, null, "foo3=bar", null);
+ do_check_eq(getCookieCount(), 0);
+ cs.setCookieString(uri, null, "foo=bar", null);
+ do_check_eq(getCookieCount(), 1);
+
+ do_check_eq(cs.getCookieString(uri, null), "foo=bar");
+ do_check_eq(cs.getCookieString(emptyuri, null), null);
+ do_check_eq(cs.getCookieString(doturi, null), null);
+
+ do_check_eq(cm.countCookiesFromHost(""), 0);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.countCookiesFromHost("..");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ var e = cm.getCookiesFromHost("", {});
+ do_check_false(e.hasMoreElements());
+ do_check_throws(function() {
+ cm.getCookiesFromHost(".", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.getCookiesFromHost("..", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ e = cm.getCookiesFromHost("baz.com", {});
+ do_check_true(e.hasMoreElements());
+ do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).name, "foo");
+ do_check_false(e.hasMoreElements());
+ e = cm.getCookiesFromHost("", {});
+ do_check_false(e.hasMoreElements());
+ do_check_throws(function() {
+ cm.getCookiesFromHost(".", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_throws(function() {
+ cm.getCookiesFromHost("..", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.removeAll();
+
+ // test that an empty file:// host works
+ emptyuri = NetUtil.newURI("file:///");
+ do_check_eq(emptyuri.asciiHost, "");
+ do_check_eq(NetUtil.newURI("file://./").asciiHost, "");
+ do_check_eq(NetUtil.newURI("file://foo.bar/").asciiHost, "");
+ cs.setCookieString(emptyuri, null, "foo2=bar", null);
+ do_check_eq(getCookieCount(), 1);
+ cs.setCookieString(emptyuri, null, "foo3=bar; domain=", null);
+ do_check_eq(getCookieCount(), 2);
+ cs.setCookieString(emptyuri, null, "foo4=bar; domain=.", null);
+ do_check_eq(getCookieCount(), 2);
+ cs.setCookieString(emptyuri, null, "foo5=bar; domain=bar.com", null);
+ do_check_eq(getCookieCount(), 2);
+
+ do_check_eq(cs.getCookieString(emptyuri, null), "foo2=bar; foo3=bar");
+
+ do_check_eq(cm.countCookiesFromHost("baz.com"), 0);
+ do_check_eq(cm.countCookiesFromHost(""), 2);
+ do_check_throws(function() {
+ cm.countCookiesFromHost(".");
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ e = cm.getCookiesFromHost("baz.com", {});
+ do_check_false(e.hasMoreElements());
+ e = cm.getCookiesFromHost("", {});
+ do_check_true(e.hasMoreElements());
+ e.getNext();
+ do_check_true(e.hasMoreElements());
+ e.getNext();
+ do_check_false(e.hasMoreElements());
+ do_check_throws(function() {
+ cm.getCookiesFromHost(".", {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ cm.removeAll();
+
+ // test that an empty host to add() or remove() works,
+ // but a host of '.' doesn't
+ cm.add("", "/", "foo2", "bar", false, false, true, expiry, {});
+ do_check_eq(getCookieCount(), 1);
+ do_check_throws(function() {
+ cm.add(".", "/", "foo3", "bar", false, false, true, expiry, {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+ do_check_eq(getCookieCount(), 1);
+
+ cm.remove("", "foo2", "/", false, {});
+ do_check_eq(getCookieCount(), 0);
+ do_check_throws(function() {
+ cm.remove(".", "foo3", "/", false, {});
+ }, Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ // test that the 'domain' attribute accepts a leading dot for IP addresses,
+ // aliases such as 'localhost', and eTLD's such as 'co.uk'; but that the
+ // resulting cookie is for the exact host only.
+ testDomainCookie("http://192.168.0.1/", "192.168.0.1");
+ testDomainCookie("http://localhost/", "localhost");
+ testDomainCookie("http://co.uk/", "co.uk");
+
+ // Test that trailing dots are treated differently for purposes of the
+ // 'domain' attribute when using setCookieString.
+ testTrailingDotCookie("http://localhost", "localhost");
+ testTrailingDotCookie("http://foo.com", "foo.com");
+
+ cm.removeAll();
+}
+
+function getCookieCount() {
+ var count = 0;
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ var enumerator = cm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ if (!(enumerator.getNext() instanceof Ci.nsICookie2))
+ throw new Error("not a cookie");
+ ++count;
+ }
+ return count;
+}
+
+function testDomainCookie(uriString, domain) {
+ var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+
+ cm.removeAll();
+
+ var uri = NetUtil.newURI(uriString);
+ cs.setCookieString(uri, null, "foo=bar; domain=" + domain, null);
+ var e = cm.getCookiesFromHost(domain, {});
+ do_check_true(e.hasMoreElements());
+ do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).host, domain);
+ cm.removeAll();
+
+ cs.setCookieString(uri, null, "foo=bar; domain=." + domain, null);
+ e = cm.getCookiesFromHost(domain, {});
+ do_check_true(e.hasMoreElements());
+ do_check_eq(e.getNext().QueryInterface(Ci.nsICookie2).host, domain);
+ cm.removeAll();
+}
+
+function testTrailingDotCookie(uriString, domain) {
+ var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+
+ cm.removeAll();
+
+ var uri = NetUtil.newURI(uriString);
+ cs.setCookieString(uri, null, "foo=bar; domain=" + domain + ".", null);
+ do_check_eq(cm.countCookiesFromHost(domain), 0);
+ do_check_eq(cm.countCookiesFromHost(domain + "."), 0);
+ cm.removeAll();
+
+ uri = NetUtil.newURI(uriString + ".");
+ cs.setCookieString(uri, null, "foo=bar; domain=" + domain, null);
+ do_check_eq(cm.countCookiesFromHost(domain), 0);
+ do_check_eq(cm.countCookiesFromHost(domain + "."), 0);
+ cm.removeAll();
+}
+
diff --git a/extensions/cookie/test/unit/test_bug650522.js b/extensions/cookie/test/unit/test_bug650522.js
new file mode 100644
index 000000000..1d99cb233
--- /dev/null
+++ b/extensions/cookie/test/unit/test_bug650522.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character at the beginning
+ // followed by a dot.
+ cm.add("e.mail.com", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("e.mail.com"), 1);
+ do_check_eq(cs.getCookieString(NetUtil.newURI("http://e.mail.com"), null), "foo=bar");
+}
diff --git a/extensions/cookie/test/unit/test_bug667087.js b/extensions/cookie/test/unit/test_bug667087.js
new file mode 100644
index 000000000..2400dea51
--- /dev/null
+++ b/extensions/cookie/test/unit/test_bug667087.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+
+function run_test() {
+ var cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+ var cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ var expiry = (Date.now() + 1000) * 1000;
+
+ // Test our handling of host names with a single character consisting only
+ // of a single character
+ cm.add("a", "/", "foo", "bar", false, false, true, expiry, {});
+ do_check_eq(cm.countCookiesFromHost("a"), 1);
+ do_check_eq(cs.getCookieString(NetUtil.newURI("http://a"), null), "foo=bar");
+}
diff --git a/extensions/cookie/test/unit/test_cookies_async_failure.js b/extensions/cookie/test/unit/test_cookies_async_failure.js
new file mode 100644
index 000000000..628553a4e
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_async_failure.js
@@ -0,0 +1,600 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the various ways opening a cookie database can fail in an asynchronous
+// (i.e. after synchronous initialization) manner, and that the database is
+// renamed and recreated under each circumstance. These circumstances are, in no
+// particular order:
+//
+// 1) A write operation failing after the database has been read in.
+// 2) Asynchronous read failure due to a corrupt database.
+// 3) Synchronous read failure due to a corrupt database, when reading:
+// a) a single base domain;
+// b) the entire database.
+// 4) Asynchronous read failure, followed by another failure during INSERT but
+// before the database closes for rebuilding. (The additional error should be
+// ignored.)
+// 5) Asynchronous read failure, followed by an INSERT failure during rebuild.
+// This should result in an abort of the database rebuild; the partially-
+// built database should be moved to 'cookies.sqlite.bak-rebuild'.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ this.profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Get the cookie file and the backup file.
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Create a cookie object for testing.
+ this.now = Date.now() * 1000;
+ this.futureExpiry = Math.round(this.now / 1e6 + 1000);
+ this.cookie = new Cookie("oh", "hai", "bar.com", "/", this.futureExpiry,
+ this.now, this.now, false, false, false);
+
+ this.sub_generator = run_test_1(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_2(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_3(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_5(test_generator);
+ sub_generator.next();
+ yield;
+
+ finish_test();
+ return;
+}
+
+function do_get_backup_file(profile)
+{
+ let file = profile.clone();
+ file.append("cookies.sqlite.bak");
+ return file;
+}
+
+function do_get_rebuild_backup_file(profile)
+{
+ let file = profile.clone();
+ file.append("cookies.sqlite.bak-rebuild");
+ return file;
+}
+
+function do_corrupt_db(file)
+{
+ // Sanity check: the database size should be larger than 450k, since we've
+ // written about 460k of data. If it's not, let's make it obvious now.
+ let size = file.fileSize;
+ do_check_true(size > 450e3);
+
+ // Corrupt the database by writing bad data to the end of the file. We
+ // assume that the important metadata -- table structure etc -- is stored
+ // elsewhere, and that doing this will not cause synchronous failure when
+ // initializing the database connection. This is totally empirical --
+ // overwriting between 1k and 100k of live data seems to work. (Note that the
+ // database file will be larger than the actual content requires, since the
+ // cookie service uses a large growth increment. So we calculate the offset
+ // based on the expected size of the content, not just the file size.)
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, 2, -1, 0);
+ let sstream = ostream.QueryInterface(Ci.nsISeekableStream);
+ let n = size - 450e3 + 20e3;
+ sstream.seek(Ci.nsISeekableStream.NS_SEEK_SET, size - n);
+ for (let i = 0; i < n; ++i) {
+ ostream.write("a", 1);
+ }
+ ostream.flush();
+ ostream.close();
+
+ do_check_eq(file.clone().fileSize, size);
+ return size;
+}
+
+function run_test_1(generator)
+{
+ // Load the profile and populate it.
+ let uri = NetUtil.newURI("http://foo.com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Open a database connection now, before we load the profile and begin
+ // asynchronous write operations. In order to tell when the async delete
+ // statement has completed, we do something tricky: open a schema 2 connection
+ // and add a cookie with null baseDomain. We can then wait until we see it
+ // deleted in the new database.
+ let db2 = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ db2.db.executeSimpleSQL("INSERT INTO moz_cookies (baseDomain) VALUES (NULL)");
+ db2.close();
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
+ do_check_eq(do_count_cookies_in_db(db.db), 2);
+
+ // Load the profile, and wait for async read completion...
+ do_load_profile(sub_generator);
+ yield;
+
+ // ... and the DELETE statement to finish.
+ while (do_count_cookies_in_db(db.db) == 2) {
+ do_execute_soon(function() {
+ do_run_generator(sub_generator);
+ });
+ yield;
+ }
+ do_check_eq(do_count_cookies_in_db(db.db), 1);
+
+ // Insert a row.
+ db.insertCookie(cookie);
+ db.close();
+
+ // Attempt to insert a cookie with the same (name, host, path) triplet.
+ Services.cookiemgr.add(cookie.host, cookie.path, cookie.name, "hallo",
+ cookie.isSecure, cookie.isHttpOnly, cookie.isSession, cookie.expiry, {});
+
+ // Check that the cookie service accepted the new cookie.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
+
+ // Wait for the cookie service to rename the old database and rebuild.
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // At this point, the cookies should still be in memory.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(cookie.host), 1);
+ do_check_eq(do_count_cookies(), 2);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the original database was renamed, and that it contains the
+ // original cookie.
+ do_check_true(do_get_backup_file(profile).exists());
+ let backupdb = Services.storage.openDatabase(do_get_backup_file(profile));
+ do_check_eq(do_count_cookies_in_db(backupdb, "foo.com"), 1);
+ backupdb.close();
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 1);
+ let enumerator = Services.cookiemgr.getCookiesFromHost(cookie.host, {});
+ do_check_true(enumerator.hasMoreElements());
+ let dbcookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ do_check_eq(dbcookie.value, "hallo");
+ do_check_false(enumerator.hasMoreElements());
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
+function run_test_2(generator)
+{
+ // Load the profile and populate it.
+ do_load_profile();
+ for (let i = 0; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ }
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in the first cookie. This will cause it to go into the
+ // cookie table, whereupon it will be written out during database rebuild.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+
+ // Wait for the asynchronous read to choke, at which point the backup file
+ // will be created and the database rebuilt.
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // At this point, the cookies should still be in memory.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+ do_check_eq(do_count_cookies(), 1);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the original database was renamed.
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ do_check_eq(do_count_cookies_in_db(db, "0.com"), 1);
+ db.close();
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+ do_check_eq(do_count_cookies(), 1);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
+function run_test_3(generator)
+{
+ // Set the maximum cookies per base domain limit to a large value, so that
+ // corrupting the database is easier.
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 3000);
+
+ // Load the profile and populate it.
+ do_load_profile();
+ for (let i = 0; i < 10; ++i) {
+ let uri = NetUtil.newURI("http://hither.com/");
+ Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
+ null);
+ }
+ for (let i = 10; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://haithur.com/");
+ Services.cookies.setCookieString(uri, null, "oh" + i + "=hai; max-age=1000",
+ null);
+ }
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in the cookies for our two domains. The first should
+ // succeed, but the second should fail midway through, resulting in none of
+ // those cookies being present.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("hither.com"), 10);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("haithur.com"), 0);
+
+ // Wait for the backup file to be created and the database rebuilt.
+ do_check_false(do_get_backup_file(profile).exists());
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ do_check_eq(do_count_cookies_in_db(db, "hither.com"), 10);
+ do_check_eq(do_count_cookies_in_db(db), 10);
+ db.close();
+
+ // Check that the original database was renamed.
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+
+ // Rename it back, and try loading the entire database synchronously.
+ do_get_backup_file(profile).moveTo(null, "cookies.sqlite");
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in everything.
+ do_check_eq(do_count_cookies(), 0);
+
+ // Wait for the backup file to be created and the database rebuilt.
+ do_check_false(do_get_backup_file(profile).exists());
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+ db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ do_check_eq(do_count_cookies_in_db(db), 0);
+ db.close();
+
+ // Check that the original database was renamed.
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
+function run_test_4(generator)
+{
+ // Load the profile and populate it.
+ do_load_profile();
+ for (let i = 0; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ }
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in the first cookie. This will cause it to go into the
+ // cookie table, whereupon it will be written out during database rebuild.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+
+ // Queue up an INSERT for the same base domain. This should also go into
+ // memory and be written out during database rebuild.
+ let uri = NetUtil.newURI("http://0.com/");
+ Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
+
+ // Wait for the asynchronous read to choke and the insert to fail shortly
+ // thereafter, at which point the backup file will be created and the database
+ // rebuilt.
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the original database was renamed.
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
+ db.close();
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
+ do_check_eq(do_count_cookies(), 2);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
+function run_test_4(generator)
+{
+ // Load the profile and populate it.
+ do_load_profile();
+ for (let i = 0; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ }
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in the first cookie. This will cause it to go into the
+ // cookie table, whereupon it will be written out during database rebuild.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+
+ // Queue up an INSERT for the same base domain. This should also go into
+ // memory and be written out during database rebuild.
+ let uri = NetUtil.newURI("http://0.com/");
+ Services.cookies.setCookieString(uri, null, "oh2=hai; max-age=1000", null);
+
+ // Wait for the asynchronous read to choke and the insert to fail shortly
+ // thereafter, at which point the backup file will be created and the database
+ // rebuilt.
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+ do_execute_soon(function() { do_run_generator(sub_generator); });
+ yield;
+
+ // At this point, the cookies should still be in memory.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
+ do_check_eq(do_count_cookies(), 2);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the original database was renamed.
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+ let db = Services.storage.openDatabase(do_get_cookie_file(profile));
+ do_check_eq(do_count_cookies_in_db(db, "0.com"), 2);
+ db.close();
+
+ // Load the profile, and check that it contains the new cookie.
+ do_load_profile();
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 2);
+ do_check_eq(do_count_cookies(), 2);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ do_get_cookie_file(profile).remove(false);
+ do_get_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
+function run_test_5(generator)
+{
+ // Load the profile and populate it.
+ do_load_profile();
+ let uri = NetUtil.newURI("http://bar.com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; path=/; max-age=1000",
+ null);
+ for (let i = 0; i < 3000; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ }
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Corrupt the database file.
+ let size = do_corrupt_db(do_get_cookie_file(profile));
+
+ // Load the profile.
+ do_load_profile();
+
+ // At this point, the database connection should be open. Ensure that it
+ // succeeded.
+ do_check_false(do_get_backup_file(profile).exists());
+
+ // Synchronously read in the first two cookies. This will cause them to go
+ // into the cookie table, whereupon it will be written out during database
+ // rebuild.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+
+ // Wait for the asynchronous read to choke, at which point the backup file
+ // will be created and a new connection opened.
+ new _observer(sub_generator, "cookie-db-rebuilding");
+ yield;
+
+ // At this point, the cookies should still be in memory. (Note that these
+ // calls are re-entrant into the cookie service, but it's OK!)
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+ do_check_eq(do_count_cookies(), 2);
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+ do_check_false(do_get_rebuild_backup_file(profile).exists());
+
+ // Open a database connection, and write a row that will trigger a constraint
+ // violation.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
+ db.insertCookie(cookie);
+ do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
+ do_check_eq(do_count_cookies_in_db(db.db), 1);
+ db.close();
+
+ // Wait for the rebuild to bail and the database to be closed.
+ new _observer(sub_generator, "cookie-db-closed");
+ yield;
+
+ // Check that the original backup and the database itself are gone.
+ do_check_true(do_get_rebuild_backup_file(profile).exists());
+ do_check_true(do_get_backup_file(profile).exists());
+ do_check_eq(do_get_backup_file(profile).fileSize, size);
+ do_check_false(do_get_cookie_file(profile).exists());
+
+ // Check that the rebuild backup has the original bar.com cookie, and possibly
+ // a 0.com cookie depending on whether it got written out first or second.
+ db = new CookieDatabaseConnection(do_get_rebuild_backup_file(profile), 4);
+ do_check_eq(do_count_cookies_in_db(db.db, "bar.com"), 1);
+ let count = do_count_cookies_in_db(db.db);
+ do_check_true(count == 1 ||
+ count == 2 && do_count_cookies_in_db(db.db, "0.com") == 1);
+ db.close();
+
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("0.com"), 1);
+ do_check_eq(do_count_cookies(), 2);
+
+ // Close the profile. We do not need to wait for completion, because the
+ // database has already been closed.
+ do_close_profile();
+
+ // Clean up.
+ do_get_backup_file(profile).remove(false);
+ do_get_rebuild_backup_file(profile).remove(false);
+ do_check_false(do_get_cookie_file(profile).exists());
+ do_check_false(do_get_backup_file(profile).exists());
+ do_check_false(do_get_rebuild_backup_file(profile).exists());
+ do_run_generator(generator);
+}
+
diff --git a/extensions/cookie/test/unit/test_cookies_persistence.js b/extensions/cookie/test/unit/test_cookies_persistence.js
new file mode 100644
index 000000000..d6de81b72
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_persistence.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test for cookie persistence across sessions, for the cases:
+// 1) network.cookie.lifetimePolicy = 0 (expire naturally)
+// 2) network.cookie.lifetimePolicy = 2 (expire at end of session)
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ var spec1 = "http://foo.com/foo.html";
+ var spec2 = "http://bar.com/bar.html";
+ var uri1 = NetUtil.newURI(spec1);
+ var uri2 = NetUtil.newURI(spec2);
+ var channel1 = NetUtil.newChannel({uri: uri1, loadUsingSystemPrincipal: true});
+ var channel2 = NetUtil.newChannel({uri: uri2, loadUsingSystemPrincipal: true});
+
+ // Force the channel URI to be used when determining the originating URI of
+ // the channel.
+ var httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ var httpchannel2 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ httpchannel2.forceAllowThirdPartyCookie = true;
+
+ // test with cookies enabled, and third party cookies persistent.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref("network.cookie.thirdparty.sessionOnly", false);
+ do_set_cookies(uri1, channel1, false, [1, 2, 3, 4]);
+ do_set_cookies(uri2, channel2, true, [1, 2, 3, 4]);
+
+ // fake a profile change
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
+ do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // Again, but don't wait for the async close to complete. This should always
+ // work, since we blocked on close above and haven't kicked off any writes
+ // since then.
+ do_close_profile();
+ do_load_profile();
+ do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
+ do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // test with cookies set to session-only
+ Services.prefs.setIntPref("network.cookie.lifetimePolicy", 2);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel1, false, [1, 2, 3, 4]);
+ do_set_cookies(uri2, channel2, true, [1, 2, 3, 4]);
+
+ // fake a profile change
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
+ do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ finish_test();
+}
+
diff --git a/extensions/cookie/test/unit/test_cookies_privatebrowsing.js b/extensions/cookie/test/unit/test_cookies_privatebrowsing.js
new file mode 100644
index 000000000..ab35f8ef7
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_privatebrowsing.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test private browsing mode.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function make_channel(url) {
+ return NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
+ .QueryInterface(Ci.nsIHttpChannel);
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Test with cookies enabled.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Create URIs pointing to foo.com and bar.com.
+ let uri1 = NetUtil.newURI("http://foo.com/foo.html");
+ let uri2 = NetUtil.newURI("http://bar.com/bar.html");
+
+ // Set a cookie for host 1.
+ Services.cookies.setCookieString(uri1, null, "oh=hai; max-age=1000", null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+
+ // Enter private browsing mode, set a cookie for host 2, and check the counts.
+ var chan1 = make_channel(uri1.spec);
+ chan1.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ chan1.setPrivate(true);
+
+ var chan2 = make_channel(uri2.spec);
+ chan2.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ chan2.setPrivate(true);
+
+ Services.cookies.setCookieString(uri2, null, "oh=hai; max-age=1000", chan2);
+ do_check_eq(Services.cookiemgr.getCookieString(uri1, chan1), null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), "oh=hai");
+
+ // Remove cookies and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri1, chan1), null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), null);
+
+ Services.cookies.setCookieString(uri2, null, "oh=hai; max-age=1000", chan2);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), "oh=hai");
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ // Fake a profile change.
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+
+ // Check that the right cookie persisted.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ // Enter private browsing mode, set a cookie for host 2, and check the counts.
+ do_check_eq(Services.cookiemgr.getCookieString(uri1, chan1), null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), null);
+ Services.cookies.setCookieString(uri2, null, "oh=hai; max-age=1000", chan2);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), "oh=hai");
+
+ // Fake a profile change.
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+
+ // We're still in private browsing mode, but should have a new session.
+ // Check counts.
+ do_check_eq(Services.cookiemgr.getCookieString(uri1, chan1), null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), null);
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ // Enter private browsing mode.
+
+ // Fake a profile change, but wait for async read completion.
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile(test_generator);
+ yield;
+
+ // We're still in private browsing mode, but should have a new session.
+ // Check counts.
+ do_check_eq(Services.cookiemgr.getCookieString(uri1, chan1), null);
+ do_check_eq(Services.cookiemgr.getCookieString(uri2, chan2), null);
+
+ // Leave private browsing mode and check counts.
+ Services.obs.notifyObservers(null, "last-pb-context-exited", null);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri1.host), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(uri2.host), 0);
+
+ finish_test();
+}
diff --git a/extensions/cookie/test/unit/test_cookies_profile_close.js b/extensions/cookie/test/unit/test_cookies_profile_close.js
new file mode 100644
index 000000000..bae6e3a59
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_profile_close.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the cookie APIs behave sanely after 'profile-before-change'.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Start the cookieservice.
+ Services.cookies;
+
+ // Set a cookie.
+ let uri = NetUtil.newURI("http://foo.com");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ let enumerator = Services.cookiemgr.enumerator;
+ do_check_true(enumerator.hasMoreElements());
+ let cookie = enumerator.getNext();
+ do_check_false(enumerator.hasMoreElements());
+
+ // Fire 'profile-before-change'.
+ do_close_profile();
+
+ // Check that the APIs behave appropriately.
+ do_check_eq(Services.cookies.getCookieString(uri, null), null);
+ do_check_eq(Services.cookies.getCookieStringFromHttp(uri, null, null), null);
+ Services.cookies.setCookieString(uri, null, "oh2=hai", null);
+ Services.cookies.setCookieStringFromHttp(uri, null, null, "oh3=hai", null, null);
+ do_check_eq(Services.cookies.getCookieString(uri, null), null);
+
+ do_check_throws(function() {
+ Services.cookiemgr.removeAll();
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.enumerator;
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.add("foo.com", "", "oh4", "hai", false, false, false, 0, {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.remove("foo.com", "", "oh4", false, {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ let file = profile.clone();
+ file.append("cookies.txt");
+ Services.cookiemgr.importCookies(file);
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookiemgr.cookieExists(cookie);
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookies.countCookiesFromHost("foo.com");
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ do_check_throws(function() {
+ Services.cookies.getCookiesFromHost("foo.com", {});
+ }, Cr.NS_ERROR_NOT_AVAILABLE);
+
+ // Wait for the database to finish closing.
+ new _observer(test_generator, "cookie-db-closed");
+ yield;
+
+ // Load the profile and check that the API is available.
+ do_load_profile();
+ do_check_true(Services.cookiemgr.cookieExists(cookie));
+
+ finish_test();
+}
+
diff --git a/extensions/cookie/test/unit/test_cookies_read.js b/extensions/cookie/test/unit/test_cookies_read.js
new file mode 100644
index 000000000..b389ad8cc
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_read.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test cookie database asynchronous read operation.
+
+var test_generator = do_run_test();
+
+var CMAX = 1000; // # of cookies to create
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Start the cookieservice, to force creation of a database.
+ Services.cookies;
+
+ // Open a database connection now, after synchronous initialization has
+ // completed. We may not be able to open one later once asynchronous writing
+ // begins.
+ do_check_true(do_get_cookie_file(profile).exists());
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 4);
+
+ for (let i = 0; i < CMAX; ++i) {
+ let uri = NetUtil.newURI("http://" + i + ".com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+ }
+
+ do_check_eq(do_count_cookies(), CMAX);
+
+ // Wait until all CMAX cookies have been written out to the database.
+ while (do_count_cookies_in_db(db.db) < CMAX) {
+ do_execute_soon(function() {
+ do_run_generator(test_generator);
+ });
+ yield;
+ }
+
+ // Check the WAL file size. We set it to 16 pages of 32k, which means it
+ // should be around 500k.
+ let file = db.db.databaseFile;
+ do_check_true(file.exists());
+ do_check_true(file.fileSize < 1e6);
+ db.close();
+
+ // fake a profile change
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+
+ // test a few random cookies
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("999.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("abc.com"), 0);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("100.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("400.com"), 1);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("xyz.com"), 0);
+
+ // force synchronous load of everything
+ do_check_eq(do_count_cookies(), CMAX);
+
+ // check that everything's precisely correct
+ for (let i = 0; i < CMAX; ++i) {
+ let host = i.toString() + ".com";
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(host), 1);
+ }
+
+ // reload again, to make sure the additions were written correctly
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+
+ // remove some of the cookies, in both reverse and forward order
+ for (let i = 100; i-- > 0; ) {
+ let host = i.toString() + ".com";
+ Services.cookiemgr.remove(host, "oh", "/", false, {});
+ }
+ for (let i = CMAX - 100; i < CMAX; ++i) {
+ let host = i.toString() + ".com";
+ Services.cookiemgr.remove(host, "oh", "/", false, {});
+ }
+
+ // check the count
+ do_check_eq(do_count_cookies(), CMAX - 200);
+
+ // reload again, to make sure the removals were written correctly
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+
+ // check the count
+ do_check_eq(do_count_cookies(), CMAX - 200);
+
+ // reload again, but wait for async read completion
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile(test_generator);
+ yield;
+
+ // check that everything's precisely correct
+ do_check_eq(do_count_cookies(), CMAX - 200);
+ for (let i = 100; i < CMAX - 100; ++i) {
+ let host = i.toString() + ".com";
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(host), 1);
+ }
+
+ finish_test();
+}
+
diff --git a/extensions/cookie/test/unit/test_cookies_sync_failure.js b/extensions/cookie/test/unit/test_cookies_sync_failure.js
new file mode 100644
index 000000000..2de1de628
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_sync_failure.js
@@ -0,0 +1,286 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the various ways opening a cookie database can fail in a synchronous
+// (i.e. immediate) manner, and that the database is renamed and recreated
+// under each circumstance. These circumstances are, in no particular order:
+//
+// 1) A corrupt database, such that opening the connection fails.
+// 2) The 'moz_cookies' table doesn't exist.
+// 3) Not all of the expected columns exist, and statement creation fails when:
+// a) The schema version is larger than the current version.
+// b) The schema version is less than or equal to the current version.
+// 4) Migration fails. This will have different modes depending on the initial
+// version:
+// a) Schema 1: the 'lastAccessed' column already exists.
+// b) Schema 2: the 'baseDomain' column already exists; or 'baseDomain'
+// cannot be computed for a particular host.
+// c) Schema 3: the 'creationTime' column already exists; or the
+// 'moz_uniqueid' index already exists.
+
+var COOKIE_DATABASE_SCHEMA_CURRENT = 7;
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ this.profile = do_get_profile();
+
+ // Allow all cookies.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+
+ // Get the cookie file and the backup file.
+ this.cookieFile = profile.clone();
+ cookieFile.append("cookies.sqlite");
+ this.backupFile = profile.clone();
+ backupFile.append("cookies.sqlite.bak");
+ do_check_false(cookieFile.exists());
+ do_check_false(backupFile.exists());
+
+ // Create a cookie object for testing.
+ this.now = Date.now() * 1000;
+ this.futureExpiry = Math.round(this.now / 1e6 + 1000);
+ this.cookie = new Cookie("oh", "hai", "bar.com", "/", this.futureExpiry,
+ this.now, this.now, false, false, false);
+
+ this.sub_generator = run_test_1(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_2(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_3(test_generator, 99);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_3(test_generator, COOKIE_DATABASE_SCHEMA_CURRENT);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_3(test_generator, 4);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_3(test_generator, 3);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4_exists(test_generator, 1,
+ "ALTER TABLE moz_cookies ADD lastAccessed INTEGER");
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4_exists(test_generator, 2,
+ "ALTER TABLE moz_cookies ADD baseDomain TEXT");
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4_baseDomain(test_generator);
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4_exists(test_generator, 3,
+ "ALTER TABLE moz_cookies ADD creationTime INTEGER");
+ sub_generator.next();
+ yield;
+
+ this.sub_generator = run_test_4_exists(test_generator, 3,
+ "CREATE UNIQUE INDEX moz_uniqueid ON moz_cookies (name, host, path)");
+ sub_generator.next();
+ yield;
+
+ finish_test();
+ return;
+}
+
+const garbage = "hello thar!";
+
+function create_garbage_file(file)
+{
+ // Create an empty database file.
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, -1);
+ do_check_true(file.exists());
+ do_check_eq(file.fileSize, 0);
+
+ // Write some garbage to it.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, -1, 0);
+ ostream.write(garbage, garbage.length);
+ ostream.flush();
+ ostream.close();
+
+ file = file.clone(); // Windows maintains a stat cache. It's lame.
+ do_check_eq(file.fileSize, garbage.length);
+}
+
+function check_garbage_file(file)
+{
+ do_check_true(file.exists());
+ do_check_eq(file.fileSize, garbage.length);
+ file.remove(false);
+ do_check_false(file.exists());
+}
+
+function run_test_1(generator)
+{
+ // Create a garbage database file.
+ create_garbage_file(cookieFile);
+
+ // Load the profile and populate it.
+ let uri = NetUtil.newURI("http://foo.com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+
+ // Fake a profile change.
+ do_close_profile(sub_generator);
+ yield;
+ do_load_profile();
+
+ // Check that the new database contains the cookie, and the old file was
+ // renamed.
+ do_check_eq(do_count_cookies(), 1);
+ check_garbage_file(backupFile);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ cookieFile.remove(false);
+ do_check_false(cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function run_test_2(generator)
+{
+ // Load the profile and populate it.
+ do_load_profile();
+ let uri = NetUtil.newURI("http://foo.com/");
+ Services.cookies.setCookieString(uri, null, "oh=hai; max-age=1000", null);
+
+ // Fake a profile change.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Drop the table.
+ let db = Services.storage.openDatabase(cookieFile);
+ db.executeSimpleSQL("DROP TABLE moz_cookies");
+ db.close();
+
+ // Load the profile and check that the table is recreated in-place.
+ do_load_profile();
+ do_check_eq(do_count_cookies(), 0);
+ do_check_false(backupFile.exists());
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Clean up.
+ cookieFile.remove(false);
+ do_check_false(cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function run_test_3(generator, schema)
+{
+ // Manually create a schema 2 database, populate it, and set the schema
+ // version to the desired number.
+ let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ schema2db.insertCookie(cookie);
+ schema2db.db.schemaVersion = schema;
+ schema2db.close();
+
+ // Load the profile and check that the column existence test fails.
+ do_load_profile();
+ do_check_eq(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset.
+ let db = Services.storage.openDatabase(cookieFile);
+ do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+
+ // Clean up.
+ cookieFile.remove(false);
+ do_check_false(cookieFile.exists());
+ do_run_generator(generator);
+}
+
+function run_test_4_exists(generator, schema, stmt)
+{
+ // Manually create a database, populate it, and add the desired column.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), schema);
+ db.insertCookie(cookie);
+ db.db.executeSimpleSQL(stmt);
+ db.close();
+
+ // Load the profile and check that migration fails.
+ do_load_profile();
+ do_check_eq(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset and the backup file exists.
+ db = Services.storage.openDatabase(cookieFile);
+ do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+ do_check_true(backupFile.exists());
+
+ // Clean up.
+ cookieFile.remove(false);
+ backupFile.remove(false);
+ do_check_false(cookieFile.exists());
+ do_check_false(backupFile.exists());
+ do_run_generator(generator);
+}
+
+function run_test_4_baseDomain(generator)
+{
+ // Manually create a database and populate it with a bad host.
+ let db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ let badCookie = new Cookie("oh", "hai", ".", "/", this.futureExpiry, this.now,
+ this.now, false, false, false);
+ db.insertCookie(badCookie);
+ db.close();
+
+ // Load the profile and check that migration fails.
+ do_load_profile();
+ do_check_eq(do_count_cookies(), 0);
+
+ // Close the profile.
+ do_close_profile(sub_generator);
+ yield;
+
+ // Check that the schema version has been reset and the backup file exists.
+ db = Services.storage.openDatabase(cookieFile);
+ do_check_eq(db.schemaVersion, COOKIE_DATABASE_SCHEMA_CURRENT);
+ db.close();
+ do_check_true(backupFile.exists());
+
+ // Clean up.
+ cookieFile.remove(false);
+ backupFile.remove(false);
+ do_check_false(cookieFile.exists());
+ do_check_false(backupFile.exists());
+ do_run_generator(generator);
+}
diff --git a/extensions/cookie/test/unit/test_cookies_thirdparty.js b/extensions/cookie/test/unit/test_cookies_thirdparty.js
new file mode 100644
index 000000000..a39f807f2
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_thirdparty.js
@@ -0,0 +1,147 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test third party cookie blocking, for the cases:
+// 1) with null channel
+// 2) with channel, but with no docshell parent
+
+function run_test() {
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ var spec1 = "http://foo.com/foo.html";
+ var spec2 = "http://bar.com/bar.html";
+ var uri1 = NetUtil.newURI(spec1);
+ var uri2 = NetUtil.newURI(spec2);
+ var channel1 = NetUtil.newChannel({uri: uri1, loadUsingSystemPrincipal: true});
+ var channel2 = NetUtil.newChannel({uri: uri2, loadUsingSystemPrincipal: true});
+
+ // test with cookies enabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ do_set_cookies(uri1, channel1, true, [1, 2, 3, 4]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [1, 2, 3, 4]);
+ Services.cookies.removeAll();
+
+ // test with third party cookies blocked
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ do_set_cookies(uri1, channel1, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+
+ // Force the channel URI to be used when determining the originating URI of
+ // the channel.
+ var httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ var httpchannel2 = channel2.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ httpchannel2.forceAllowThirdPartyCookie = true;
+
+ // test with cookies enabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ do_set_cookies(uri1, channel1, true, [1, 2, 3, 4]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [1, 2, 3, 4]);
+ Services.cookies.removeAll();
+
+ // test with third party cookies blocked
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ do_set_cookies(uri1, channel1, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+
+ // test with third party cookies limited
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 3);
+ do_set_cookies(uri1, channel1, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ do_set_cookies(uri1, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie blocking with cookies enabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ var kPermissionType = "cookie";
+ var ALLOW_FIRST_PARTY_ONLY = 9;
+ // ALLOW_FIRST_PARTY_ONLY overrides
+ Services.perms.add(uri1, kPermissionType, ALLOW_FIRST_PARTY_ONLY);
+ do_set_cookies(uri1, channel1, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie blocking with 3rd party cookies disabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ do_set_cookies(uri1, channel1, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ // No preference has been set for uri2, but it should act as if
+ // ALLOW_FIRST_PARTY_ONLY has been set
+ do_set_cookies(uri2, channel2, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie blocking with 3rd party cookies limited
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 3);
+ do_set_cookies(uri1, channel1, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ // No preference has been set for uri2, but it should act as if
+ // LIMIT_THIRD_PARTY has been set
+ do_set_cookies(uri2, channel2, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri2, channel2, 1);
+ do_set_cookies(uri2, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ do_set_cookies(uri1, channel2, true, [1, 1, 1, 1]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie limiting with cookies enabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ var kPermissionType = "cookie";
+ var LIMIT_THIRD_PARTY = 10;
+ // LIMIT_THIRD_PARTY overrides
+ Services.perms.add(uri1, kPermissionType, LIMIT_THIRD_PARTY);
+ do_set_cookies(uri1, channel1, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ do_set_cookies(uri1, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie limiting with 3rd party cookies disabled
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 1);
+ do_set_cookies(uri1, channel1, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ // No preference has been set for uri2, but it should act as if
+ // ALLOW_FIRST_PARTY_ONLY has been set
+ do_set_cookies(uri2, channel2, true, [0, 1, 1, 2]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ do_set_cookies(uri1, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+
+ // Test per-site 3rd party cookie limiting with 3rd party cookies limited
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 3);
+ do_set_cookies(uri1, channel1, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ // No preference has been set for uri2, but it should act as if
+ // LIMIT_THIRD_PARTY has been set
+ do_set_cookies(uri2, channel2, true, [0, 1, 2, 3]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri2, channel2, 1);
+ do_set_cookies(uri2, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, true, [0, 0, 0, 0]);
+ Services.cookies.removeAll();
+ do_set_single_http_cookie(uri1, channel1, 1);
+ do_set_cookies(uri1, channel2, true, [2, 3, 4, 5]);
+ Services.cookies.removeAll();
+}
+
diff --git a/extensions/cookie/test/unit/test_cookies_thirdparty_session.js b/extensions/cookie/test/unit/test_cookies_thirdparty_session.js
new file mode 100644
index 000000000..36e5fb11a
--- /dev/null
+++ b/extensions/cookie/test/unit/test_cookies_thirdparty_session.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// test third party persistence across sessions, for the cases:
+// 1) network.cookie.thirdparty.sessionOnly = false
+// 2) network.cookie.thirdparty.sessionOnly = true
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Create URIs and channels pointing to foo.com and bar.com.
+ // We will use these to put foo.com into first and third party contexts.
+ var spec1 = "http://foo.com/foo.html";
+ var spec2 = "http://bar.com/bar.html";
+ var uri1 = NetUtil.newURI(spec1);
+ var uri2 = NetUtil.newURI(spec2);
+ var channel1 = NetUtil.newChannel({uri: uri1, loadUsingSystemPrincipal: true});
+ var channel2 = NetUtil.newChannel({uri: uri2, loadUsingSystemPrincipal: true});
+
+ // Force the channel URI to be used when determining the originating URI of
+ // the channel.
+ var httpchannel1 = channel1.QueryInterface(Ci.nsIHttpChannelInternal);
+ var httpchannel2 = channel2.QueryInterface(Ci.nsIHttpChannelInternal);
+ httpchannel1.forceAllowThirdPartyCookie = true;
+ httpchannel2.forceAllowThirdPartyCookie = true;
+
+ // test with cookies enabled, and third party cookies persistent.
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", 0);
+ Services.prefs.setBoolPref("network.cookie.thirdparty.sessionOnly", false);
+ do_set_cookies(uri1, channel2, false, [1, 2, 3, 4]);
+ do_set_cookies(uri2, channel1, true, [1, 2, 3, 4]);
+
+ // fake a profile change
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 4);
+ do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ // test with third party cookies for session only.
+ Services.prefs.setBoolPref("network.cookie.thirdparty.sessionOnly", true);
+ Services.cookies.removeAll();
+ do_set_cookies(uri1, channel2, false, [1, 2, 3, 4]);
+ do_set_cookies(uri2, channel1, true, [1, 2, 3, 4]);
+
+ // fake a profile change
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_eq(Services.cookies.countCookiesFromHost(uri1.host), 0);
+ do_check_eq(Services.cookies.countCookiesFromHost(uri2.host), 0);
+
+ finish_test();
+}
diff --git a/extensions/cookie/test/unit/test_domain_eviction.js b/extensions/cookie/test/unit/test_domain_eviction.js
new file mode 100644
index 000000000..349f6d77e
--- /dev/null
+++ b/extensions/cookie/test/unit/test_domain_eviction.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that domain eviction occurs when the cookies per base domain limit is
+// reached, and that expired cookies are evicted before live cookies.
+
+var test_generator = do_run_test();
+
+function run_test()
+{
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function continue_test()
+{
+ do_run_generator(test_generator);
+}
+
+function do_run_test()
+{
+ // Set the base domain limit to 50 so we have a known value.
+ Services.prefs.setIntPref("network.cookie.maxPerHost", 50);
+
+ let futureExpiry = Math.floor(Date.now() / 1000 + 1000);
+
+ // test eviction under the 50 cookies per base domain limit. this means
+ // that cookies for foo.com and bar.foo.com should count toward this limit,
+ // while cookies for baz.com should not. there are several tests we perform
+ // to make sure the base domain logic is working correctly.
+
+ // 1) simplest case: set 100 cookies for "foo.bar" and make sure 50 survive.
+ setCookies("foo.bar", 100, futureExpiry);
+ do_check_eq(countCookies("foo.bar", "foo.bar"), 50);
+
+ // 2) set cookies for different subdomains of "foo.baz", and an unrelated
+ // domain, and make sure all 50 within the "foo.baz" base domain are counted.
+ setCookies("foo.baz", 10, futureExpiry);
+ setCookies(".foo.baz", 10, futureExpiry);
+ setCookies("bar.foo.baz", 10, futureExpiry);
+ setCookies("baz.bar.foo.baz", 10, futureExpiry);
+ setCookies("unrelated.domain", 50, futureExpiry);
+ do_check_eq(countCookies("foo.baz", "baz.bar.foo.baz"), 40);
+ setCookies("foo.baz", 20, futureExpiry);
+ do_check_eq(countCookies("foo.baz", "baz.bar.foo.baz"), 50);
+
+ // 3) ensure cookies are evicted by order of lastAccessed time, if the
+ // limit on cookies per base domain is reached.
+ setCookies("horse.radish", 10, futureExpiry);
+
+ // Wait a while, to make sure the first batch of cookies is older than
+ // the second (timer resolution varies on different platforms).
+ do_timeout(100, continue_test);
+ yield;
+
+ setCookies("tasty.horse.radish", 50, futureExpiry);
+ do_check_eq(countCookies("horse.radish", "horse.radish"), 50);
+
+ let enumerator = Services.cookiemgr.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.host == "horse.radish")
+ do_throw("cookies not evicted by lastAccessed order");
+ }
+
+ // Test that expired cookies for a domain are evicted before live ones.
+ let shortExpiry = Math.floor(Date.now() / 1000 + 2);
+ setCookies("captchart.com", 49, futureExpiry);
+ Services.cookiemgr.add("captchart.com", "", "test100", "eviction",
+ false, false, false, shortExpiry, {});
+ do_timeout(2100, continue_test);
+ yield;
+
+ do_check_eq(countCookies("captchart.com", "captchart.com"), 50);
+ Services.cookiemgr.add("captchart.com", "", "test200", "eviction",
+ false, false, false, futureExpiry, {});
+ do_check_eq(countCookies("captchart.com", "captchart.com"), 50);
+
+ enumerator = Services.cookiemgr.getCookiesFromHost("captchart.com", {});
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ do_check_true(cookie.expiry == futureExpiry);
+ }
+
+ do_finish_generator_test(test_generator);
+}
+
+// set 'aNumber' cookies with host 'aHost', with distinct names.
+function
+setCookies(aHost, aNumber, aExpiry)
+{
+ for (let i = 0; i < aNumber; ++i)
+ Services.cookiemgr.add(aHost, "", "test" + i, "eviction",
+ false, false, false, aExpiry, {});
+}
+
+// count how many cookies are within domain 'aBaseDomain', using three
+// independent interface methods on nsICookieManager2:
+// 1) 'enumerator', an enumerator of all cookies;
+// 2) 'countCookiesFromHost', which returns the number of cookies within the
+// base domain of 'aHost',
+// 3) 'getCookiesFromHost', which returns an enumerator of 2).
+function
+countCookies(aBaseDomain, aHost)
+{
+ let enumerator = Services.cookiemgr.enumerator;
+
+ // count how many cookies are within domain 'aBaseDomain' using the cookie
+ // enumerator.
+ let cookies = [];
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.host.length >= aBaseDomain.length &&
+ cookie.host.slice(cookie.host.length - aBaseDomain.length) == aBaseDomain)
+ cookies.push(cookie);
+ }
+
+ // confirm the count using countCookiesFromHost and getCookiesFromHost.
+ let result = cookies.length;
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(aBaseDomain),
+ cookies.length);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost(aHost), cookies.length);
+
+ enumerator = Services.cookiemgr.getCookiesFromHost(aHost, {});
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.host.length >= aBaseDomain.length &&
+ cookie.host.slice(cookie.host.length - aBaseDomain.length) == aBaseDomain) {
+ let found = false;
+ for (let i = 0; i < cookies.length; ++i) {
+ if (cookies[i].host == cookie.host && cookies[i].name == cookie.name) {
+ found = true;
+ cookies.splice(i, 1);
+ break;
+ }
+ }
+
+ if (!found)
+ do_throw("cookie " + cookie.name + " not found in master enumerator");
+
+ } else {
+ do_throw("cookie host " + cookie.host + " not within domain " + aBaseDomain);
+ }
+ }
+
+ do_check_eq(cookies.length, 0);
+
+ return result;
+}
+
diff --git a/extensions/cookie/test/unit/test_eviction.js b/extensions/cookie/test/unit/test_eviction.js
new file mode 100644
index 000000000..7b1550208
--- /dev/null
+++ b/extensions/cookie/test/unit/test_eviction.js
@@ -0,0 +1,249 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var test_generator = do_run_test();
+
+function run_test()
+{
+ do_test_pending();
+ do_run_generator(test_generator);
+}
+
+function continue_test()
+{
+ do_run_generator(test_generator);
+}
+
+function repeat_test()
+{
+ // The test is probably going to fail because setting a batch of cookies took
+ // a significant fraction of 'gPurgeAge'. Compensate by rerunning the
+ // test with a larger purge age.
+ do_check_true(gPurgeAge < 64);
+ gPurgeAge *= 2;
+ gShortExpiry *= 2;
+
+ do_execute_soon(function() {
+ test_generator.close();
+ test_generator = do_run_test();
+ do_run_generator(test_generator);
+ });
+}
+
+// Purge threshold, in seconds.
+var gPurgeAge = 1;
+
+// Short expiry age, in seconds.
+var gShortExpiry = 2;
+
+// Required delay to ensure a purge occurs, in milliseconds. This must be at
+// least gPurgeAge + 10%, and includes a little fuzz to account for timer
+// resolution and possible differences between PR_Now() and Date.now().
+function get_purge_delay()
+{
+ return gPurgeAge * 1100 + 100;
+}
+
+// Required delay to ensure a cookie set with an expiry time 'gShortExpiry' into
+// the future will have expired.
+function get_expiry_delay()
+{
+ return gShortExpiry * 1000 + 100;
+}
+
+function do_run_test()
+{
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // twiddle prefs to convenient values for this test
+ Services.prefs.setIntPref("network.cookie.purgeAge", gPurgeAge);
+ Services.prefs.setIntPref("network.cookie.maxNumber", 100);
+
+ let expiry = Date.now() / 1000 + 1000;
+
+ // eviction is performed based on two limits: when the total number of cookies
+ // exceeds maxNumber + 10% (110), and when cookies are older than purgeAge
+ // (1 second). purging is done when both conditions are satisfied, and only
+ // those cookies are purged.
+
+ // we test the following cases of eviction:
+ // 1) excess and age are satisfied, but only some of the excess are old enough
+ // to be purged.
+ Services.cookiemgr.removeAll();
+ if (!set_cookies(0, 5, expiry)) {
+ repeat_test();
+ return;
+ }
+ // Sleep a while, to make sure the first batch of cookies is older than
+ // the second (timer resolution varies on different platforms).
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(5, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ // Fake a profile change, to ensure eviction affects the database correctly.
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(111, 5, 106));
+
+ // 2) excess and age are satisfied, and all of the excess are old enough
+ // to be purged.
+ Services.cookiemgr.removeAll();
+ if (!set_cookies(0, 10, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(10, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(111, 10, 101));
+
+ // 3) excess and age are satisfied, and more than the excess are old enough
+ // to be purged.
+ Services.cookiemgr.removeAll();
+ if (!set_cookies(0, 50, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(50, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(111, 50, 101));
+
+ // 4) excess but not age are satisfied.
+ Services.cookiemgr.removeAll();
+ if (!set_cookies(0, 120, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(120, 0, 120));
+
+ // 5) age but not excess are satisfied.
+ Services.cookiemgr.removeAll();
+ if (!set_cookies(0, 20, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(20, 110, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(110, 20, 110));
+
+ // 6) Excess and age are satisfied, but the cookie limit can be satisfied by
+ // purging expired cookies.
+ Services.cookiemgr.removeAll();
+ let shortExpiry = Math.floor(Date.now() / 1000) + gShortExpiry;
+ if (!set_cookies(0, 20, shortExpiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_expiry_delay(), continue_test);
+ yield;
+ if (!set_cookies(20, 110, expiry)) {
+ repeat_test();
+ return;
+ }
+ do_timeout(get_purge_delay(), continue_test);
+ yield;
+ if (!set_cookies(110, 111, expiry)) {
+ repeat_test();
+ return;
+ }
+
+ do_close_profile(test_generator);
+ yield;
+ do_load_profile();
+ do_check_true(check_remaining_cookies(111, 20, 91));
+
+ do_finish_generator_test(test_generator);
+}
+
+// Set 'end - begin' total cookies, with consecutively increasing hosts numbered
+// 'begin' to 'end'.
+function set_cookies(begin, end, expiry)
+{
+ do_check_true(begin != end);
+
+ let beginTime;
+ for (let i = begin; i < end; ++i) {
+ let host = "eviction." + i + ".tests";
+ Services.cookiemgr.add(host, "", "test", "eviction", false, false, false,
+ expiry, {});
+
+ if (i == begin)
+ beginTime = get_creationTime(i);
+ }
+
+ let endTime = get_creationTime(end - 1);
+ do_check_true(begin == end - 1 || endTime > beginTime);
+ if (endTime - beginTime > gPurgeAge * 1000000) {
+ // Setting cookies took an amount of time very close to the purge threshold.
+ // Retry the test with a larger threshold.
+ return false;
+ }
+
+ return true;
+}
+
+function get_creationTime(i)
+{
+ let host = "eviction." + i + ".tests";
+ let enumerator = Services.cookiemgr.getCookiesFromHost(host, {});
+ do_check_true(enumerator.hasMoreElements());
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ return cookie.creationTime;
+}
+
+// Test that 'aNumberToExpect' cookies remain after purging is complete, and
+// that the cookies that remain consist of the set expected given the number of
+// of older and newer cookies -- eviction should occur by order of lastAccessed
+// time, if both the limit on total cookies (maxNumber + 10%) and the purge age
+// + 10% are exceeded.
+function check_remaining_cookies(aNumberTotal, aNumberOld, aNumberToExpect) {
+ var enumerator = Services.cookiemgr.enumerator;
+
+ let i = 0;
+ while (enumerator.hasMoreElements()) {
+ var cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ ++i;
+
+ if (aNumberTotal != aNumberToExpect) {
+ // make sure the cookie is one of the batch we expect was purged.
+ var hostNumber = new Number(cookie.rawHost.split(".")[1]);
+ if (hostNumber < (aNumberOld - aNumberToExpect)) break;
+ }
+ }
+
+ return i == aNumberToExpect;
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_cleardata.js b/extensions/cookie/test/unit/test_permmanager_cleardata.js
new file mode 100644
index 000000000..faa2579f5
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_cleardata.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var pm;
+
+// Create a principal based on the { origin, originAttributes }.
+function createPrincipal(aOrigin, aOriginAttributes)
+{
+ return Services.scriptSecurityManager.createCodebasePrincipal(NetUtil.newURI(aOrigin), aOriginAttributes);
+}
+
+// Return the data required by 'clear-origin-attributes-data' notification.
+function getData(aPattern)
+{
+ return JSON.stringify(aPattern);
+}
+
+// Use aEntries to create principals, add permissions to them and check that they have them.
+// Then, it is notifying 'clear-origin-attributes-data' with the given aData and check if the permissions
+// of principals[i] matches the permission in aResults[i].
+function test(aEntries, aData, aResults)
+{
+ let principals = [];
+
+ for (entry of aEntries) {
+ principals.push(createPrincipal(entry.origin, entry.originAttributes));
+ }
+
+ for (principal of principals) {
+ do_check_eq(pm.testPermissionFromPrincipal(principal, "test/clear-origin"), pm.UNKNOWN_ACTION);
+ pm.addFromPrincipal(principal, "test/clear-origin", pm.ALLOW_ACTION, pm.EXPIRE_NEVER, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(principal, "test/clear-origin"), pm.ALLOW_ACTION);
+ }
+
+ Services.obs.notifyObservers(null, 'clear-origin-attributes-data', aData);
+
+ var length = aEntries.length;
+ for (let i=0; i<length; ++i) {
+ do_check_eq(pm.testPermissionFromPrincipal(principals[i], 'test/clear-origin'), aResults[i]);
+
+ // Remove allowed actions.
+ if (aResults[i] == pm.ALLOW_ACTION) {
+ pm.removeFromPrincipal(principals[i], 'test/clear-origin');
+ }
+ }
+}
+
+function run_test()
+{
+ do_get_profile();
+
+ pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ let entries = [
+ { origin: 'http://example.com', originAttributes: { appId: 1 } },
+ { origin: 'http://example.com', originAttributes: { appId: 1, inIsolatedMozBrowser: true } },
+ { origin: 'http://example.com', originAttributes: {} },
+ { origin: 'http://example.com', originAttributes: { appId: 2 } },
+ ];
+
+ // In that case, all permissions from app 1 should be removed but not the other ones.
+ test(entries, getData({appId: 1}), [ pm.UNKNOWN_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
+
+ // In that case, only the permissions of app 1 related to a browserElement should be removed.
+ // All the other permissions should stay.
+ test(entries, getData({appId: 1, inIsolatedMozBrowser: true}), [ pm.ALLOW_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_defaults.js b/extensions/cookie/test/unit/test_permmanager_defaults.js
new file mode 100644
index 000000000..604f2b4d9
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_defaults.js
@@ -0,0 +1,295 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The origin we use in most of the tests.
+const TEST_ORIGIN = NetUtil.newURI("http://example.org");
+const TEST_ORIGIN_HTTPS = NetUtil.newURI("https://example.org");
+const TEST_ORIGIN_2 = NetUtil.newURI("http://example.com");
+const TEST_ORIGIN_3 = NetUtil.newURI("https://example2.com:8080");
+const TEST_PERMISSION = "test-permission";
+Components.utils.import("resource://gre/modules/Promise.jsm");
+
+function promiseTimeout(delay) {
+ let deferred = Promise.defer();
+ do_timeout(delay, deferred.resolve);
+ return deferred.promise;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* do_test() {
+ // setup a profile.
+ do_get_profile();
+
+ // create a file in the temp directory with the defaults.
+ let file = do_get_tempdir();
+ file.append("test_default_permissions");
+
+ // write our test data to it.
+ let ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, -1, 0o666, 0);
+ let conv = Cc["@mozilla.org/intl/converter-output-stream;1"].
+ createInstance(Ci.nsIConverterOutputStream);
+ conv.init(ostream, "UTF-8", 0, 0);
+
+ conv.writeString("# this is a comment\n");
+ conv.writeString("\n"); // a blank line!
+ conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN.host + "\n");
+ conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN_2.host + "\n");
+ conv.writeString("origin\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN_3.spec + "\n");
+ conv.writeString("origin\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN.spec + "^appId=1000&inBrowser=1\n");
+ ostream.close();
+
+ // Set the preference used by the permission manager so the file is read.
+ Services.prefs.setCharPref("permissions.manager.defaultsUrl", "file://" + file.path);
+
+ // initialize the permission manager service - it will read that default.
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ // test the default permission was applied.
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, {});
+ let principalHttps = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_HTTPS, {});
+ let principal2 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_2, {});
+ let principal3 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_3, {});
+
+ let attrs = {appId: 1000, inIsolatedMozBrowser: true};
+ let principal4 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
+ let principal5 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_3, attrs);
+
+ attrs = {userContextId: 1};
+ let principal6 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
+ attrs = {firstPartyDomain: "cnn.com"};
+ let principal7 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
+ attrs = {userContextId: 1, firstPartyDomain: "cnn.com"};
+ let principal8 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
+
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principalHttps, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION));
+
+ // Didn't add
+ do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ pm.testPermissionFromPrincipal(principal5, TEST_PERMISSION));
+
+ // the permission should exist in the enumerator.
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum(TEST_ORIGIN));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum(TEST_ORIGIN_3));
+
+ // but should not have been written to the DB
+ yield checkCapabilityViaDB(null);
+
+ // remove all should not throw and the default should remain
+ pm.removeAll();
+
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal3, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal4, TEST_PERMISSION));
+ // make sure principals with userContextId or firstPartyDomain use the same permissions
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
+
+ // Asking for this permission to be removed should result in that permission
+ // having UNKNOWN_ACTION
+ pm.removeFromPrincipal(principal, TEST_PERMISSION);
+ do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ // make sure principals with userContextId or firstPartyDomain use the same permissions
+ do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
+ // and we should have this UNKNOWN_ACTION reflected in the DB
+ yield checkCapabilityViaDB(Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ // but the permission should *not* appear in the enumerator.
+ do_check_eq(null, findCapabilityViaEnum());
+
+ // and a subsequent RemoveAll should restore the default
+ pm.removeAll();
+
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ // make sure principals with userContextId or firstPartyDomain use the same permissions
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
+ // and allow it to again be seen in the enumerator.
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION, findCapabilityViaEnum());
+
+ // now explicitly add a permission - this too should override the default.
+ pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+
+ // it should be reflected in a permission check, in the enumerator and the DB
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ // make sure principals with userContextId or firstPartyDomain use the same permissions
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION, findCapabilityViaEnum());
+ yield checkCapabilityViaDB(Ci.nsIPermissionManager.DENY_ACTION);
+
+ // explicitly add a different permission - in this case we are no longer
+ // replacing the default, but instead replacing the replacement!
+ pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.PROMPT_ACTION);
+
+ // it should be reflected in a permission check, in the enumerator and the DB
+ do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ // make sure principals with userContextId or firstPartyDomain use the same permissions
+ do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+ pm.testPermissionFromPrincipal(principal6, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+ pm.testPermissionFromPrincipal(principal7, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION,
+ pm.testPermissionFromPrincipal(principal8, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.PROMPT_ACTION, findCapabilityViaEnum());
+ yield checkCapabilityViaDB(Ci.nsIPermissionManager.PROMPT_ACTION);
+
+ // --------------------------------------------------------------
+ // check default permissions and removeAllSince work as expected.
+ pm.removeAll(); // ensure only defaults are there.
+
+ // default for both principals is allow.
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+
+ // Add a default override for TEST_ORIGIN_2 - this one should *not* be
+ // restored in removeAllSince()
+ pm.addFromPrincipal(principal2, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+ yield promiseTimeout(20);
+
+ let since = Number(Date.now());
+ yield promiseTimeout(20);
+
+ // explicitly add a permission which overrides the default for the first
+ // principal - this one *should* be removed by removeAllSince.
+ pm.addFromPrincipal(principal, TEST_PERMISSION, Ci.nsIPermissionManager.DENY_ACTION);
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+ // do a removeAllSince.
+ pm.removeAllSince(since);
+
+ // the default for the first principal should re-appear as we modified it
+ // later then |since|
+ do_check_eq(Ci.nsIPermissionManager.ALLOW_ACTION,
+ pm.testPermissionFromPrincipal(principal, TEST_PERMISSION));
+
+ // but the permission for principal2 should remain as we added that before |since|.
+ do_check_eq(Ci.nsIPermissionManager.DENY_ACTION,
+ pm.testPermissionFromPrincipal(principal2, TEST_PERMISSION));
+
+ // remove the temp file we created.
+ file.remove(false);
+});
+
+// use an enumerator to find the requested permission. Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaEnum(origin = TEST_ORIGIN, type = TEST_PERMISSION) {
+ let result = undefined;
+ let e = Services.perms.enumerator;
+ while (e.hasMoreElements()) {
+ let perm = e.getNext().QueryInterface(Ci.nsIPermission);
+ if (perm.matchesURI(origin, true) &&
+ perm.type == type) {
+ if (result !== undefined) {
+ // we've already found one previously - that's bad!
+ do_throw("enumerator found multiple entries");
+ }
+ result = perm.capability;
+ }
+ }
+ return result || null;
+}
+
+// A function to check the DB has the specified capability. As the permission
+// manager uses async DB operations without a completion callback, the
+// distinct possibility exists that our checking of the DB will happen before
+// the permission manager update has completed - so we just retry a few times.
+// Returns a promise.
+function checkCapabilityViaDB(expected, origin = TEST_ORIGIN, type = TEST_PERMISSION) {
+ let deferred = Promise.defer();
+ let count = 0;
+ let max = 20;
+ let do_check = () => {
+ let got = findCapabilityViaDB(origin, type);
+ if (got == expected) {
+ // the do_check_eq() below will succeed - which is what we want.
+ do_check_eq(got, expected, "The database has the expected value");
+ deferred.resolve();
+ return;
+ }
+ // value isn't correct - see if we've retried enough
+ if (count++ == max) {
+ // the do_check_eq() below will fail - which is what we want.
+ do_check_eq(got, expected, "The database wasn't updated with the expected value");
+ deferred.resolve();
+ return;
+ }
+ // we can retry...
+ do_timeout(100, do_check);
+ }
+ do_check();
+ return deferred.promise;
+}
+
+// use the DB to find the requested permission. Returns the permission
+// value (ie, the "capability" in nsIPermission parlance) or null if it can't
+// be found.
+function findCapabilityViaDB(origin = TEST_ORIGIN, type = TEST_PERMISSION) {
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(origin, {});
+ let originStr = principal.origin;
+
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append("permissions.sqlite");
+
+ let storage = Cc["@mozilla.org/storage/service;1"]
+ .getService(Ci.mozIStorageService);
+
+ let connection = storage.openDatabase(file);
+
+ let query = connection.createStatement(
+ "SELECT permission FROM moz_perms WHERE origin = :origin AND type = :type");
+ query.bindByName("origin", originStr);
+ query.bindByName("type", type);
+
+ if (!query.executeStep()) {
+ // no row
+ return null;
+ }
+ let result = query.getInt32(0);
+ if (query.executeStep()) {
+ // this is bad - we never expect more than 1 row here.
+ do_throw("More than 1 row found!")
+ }
+ return result;
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_expiration.js b/extensions/cookie/test/unit/test_permmanager_expiration.js
new file mode 100644
index 000000000..7ab8e88ae
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_expiration.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that permissions with specific expiry times behave as expected.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function continue_test()
+{
+ do_run_generator(test_generator);
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ let pm = Services.perms;
+ let permURI = NetUtil.newURI("http://example.com");
+ let principal = Services.scriptSecurityManager.createCodebasePrincipal(permURI, {});
+
+ let now = Number(Date.now());
+
+ // add a permission with *now* expiration
+ pm.addFromPrincipal(principal, "test/expiration-perm-exp", 1, pm.EXPIRE_TIME, now);
+ pm.addFromPrincipal(principal, "test/expiration-session-exp", 1, pm.EXPIRE_SESSION, now);
+
+ // add a permission with future expiration (100 milliseconds)
+ pm.addFromPrincipal(principal, "test/expiration-perm-exp2", 1, pm.EXPIRE_TIME, now + 100);
+ pm.addFromPrincipal(principal, "test/expiration-session-exp2", 1, pm.EXPIRE_SESSION, now + 100);
+
+ // add a permission with future expiration (1000 seconds)
+ pm.addFromPrincipal(principal, "test/expiration-perm-exp3", 1, pm.EXPIRE_TIME, now + 1e6);
+ pm.addFromPrincipal(principal, "test/expiration-session-exp3", 1, pm.EXPIRE_SESSION, now + 1e6);
+
+ // add a permission without expiration
+ pm.addFromPrincipal(principal, "test/expiration-perm-nexp", 1, pm.EXPIRE_NEVER, 0);
+
+ // add a permission for renewal
+ pm.addFromPrincipal(principal, "test/expiration-perm-renewable", 1, pm.EXPIRE_TIME, now + 100);
+ pm.addFromPrincipal(principal, "test/expiration-session-renewable", 1, pm.EXPIRE_SESSION, now + 100);
+
+ // And immediately renew them with longer timeouts
+ pm.updateExpireTime(principal, "test/expiration-perm-renewable", true, now + 100, now + 1e6);
+ pm.updateExpireTime(principal, "test/expiration-session-renewable", true, now + 1e6, now + 100);
+
+ // check that the second two haven't expired yet
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-exp3"));
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-session-exp3"));
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-nexp"));
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-renewable"));
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-session-renewable"));
+
+ // ... and the first one has
+ do_timeout(10, continue_test);
+ yield;
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-exp"));
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal, "test/expiration-session-exp"));
+
+ // ... and that the short-term one will
+ do_timeout(200, continue_test);
+ yield;
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-exp2"));
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal, "test/expiration-session-exp2"));
+
+ // Check that .getPermission returns a matching result
+ do_check_null(pm.getPermissionObject(principal, "test/expiration-perm-exp", false));
+ do_check_null(pm.getPermissionObject(principal, "test/expiration-session-exp", false));
+ do_check_null(pm.getPermissionObject(principal, "test/expiration-perm-exp2", false));
+ do_check_null(pm.getPermissionObject(principal, "test/expiration-session-exp2", false));
+
+ // Check that the renewable permissions actually got renewed
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-perm-renewable"));
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal, "test/expiration-session-renewable"));
+
+ do_finish_generator_test(test_generator);
+}
+
diff --git a/extensions/cookie/test/unit/test_permmanager_getAllForURI.js b/extensions/cookie/test/unit/test_permmanager_getAllForURI.js
new file mode 100644
index 000000000..7fec41826
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_getAllForURI.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function check_enumerator(uri, permissions) {
+ let pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ let enumerator = pm.getAllForURI(uri);
+ for ([type, capability] of permissions) {
+ let perm = enumerator.getNext();
+ do_check_true(perm != null);
+ do_check_true(perm.principal.URI.equals(uri));
+ do_check_eq(perm.type, type);
+ do_check_eq(perm.capability, capability);
+ do_check_eq(perm.expireType, pm.EXPIRE_NEVER);
+ }
+ do_check_false(enumerator.hasMoreElements());
+}
+
+function run_test() {
+ let pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+
+ let uri = NetUtil.newURI("http://example.com");
+ let sub = NetUtil.newURI("http://sub.example.com");
+
+ check_enumerator(uri, [ ]);
+
+ pm.add(uri, "test/getallforuri", pm.ALLOW_ACTION);
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ]
+ ]);
+
+ // check that uris are matched exactly
+ check_enumerator(sub, [ ]);
+
+ pm.add(sub, "test/getallforuri", pm.PROMPT_ACTION);
+ pm.add(sub, "test/getallforuri2", pm.DENY_ACTION);
+
+ check_enumerator(sub, [
+ [ "test/getallforuri", pm.PROMPT_ACTION ],
+ [ "test/getallforuri2", pm.DENY_ACTION ]
+ ]);
+
+ // check that the original uri list has not changed
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ]
+ ]);
+
+ // check that UNKNOWN_ACTION permissions are ignored
+ pm.add(uri, "test/getallforuri2", pm.UNKNOWN_ACTION);
+ pm.add(uri, "test/getallforuri3", pm.DENY_ACTION);
+
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.ALLOW_ACTION ],
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ // check that permission updates are reflected
+ pm.add(uri, "test/getallforuri", pm.PROMPT_ACTION);
+
+ check_enumerator(uri, [
+ [ "test/getallforuri", pm.PROMPT_ACTION ],
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ // check that permission removals are reflected
+ pm.remove(uri, "test/getallforuri");
+
+ check_enumerator(uri, [
+ [ "test/getallforuri3", pm.DENY_ACTION ]
+ ]);
+
+ pm.removeAll();
+ check_enumerator(uri, [ ]);
+ check_enumerator(sub, [ ]);
+}
+
diff --git a/extensions/cookie/test/unit/test_permmanager_getPermissionObject.js b/extensions/cookie/test/unit/test_permmanager_getPermissionObject.js
new file mode 100644
index 000000000..d01b51923
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_getPermissionObject.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function getPrincipalFromURI(aURI) {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let uri = NetUtil.newURI(aURI);
+ return ssm.createCodebasePrincipal(uri, {});
+}
+
+function getSystemPrincipal() {
+ return Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager)
+ .getSystemPrincipal();
+}
+
+function run_test() {
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ do_check_null(pm.getPermissionObject(getSystemPrincipal(), "test/pobject", false));
+
+ let principal = getPrincipalFromURI("http://example.com");
+ let subPrincipal = getPrincipalFromURI("http://sub.example.com");
+ let subSubPrincipal = getPrincipalFromURI("http://sub.sub.example.com");
+
+ do_check_null(pm.getPermissionObject(principal, "test/pobject", false));
+ do_check_null(pm.getPermissionObject(principal, "test/pobject", true));
+
+ pm.addFromPrincipal(principal, "test/pobject", pm.ALLOW_ACTION);
+ var rootPerm = pm.getPermissionObject(principal, "test/pobject", false);
+ do_check_true(rootPerm != null);
+ do_check_eq(rootPerm.principal.origin, "http://example.com");
+ do_check_eq(rootPerm.type, "test/pobject");
+ do_check_eq(rootPerm.capability, pm.ALLOW_ACTION);
+ do_check_eq(rootPerm.expireType, pm.EXPIRE_NEVER);
+
+ var rootPerm2 = pm.getPermissionObject(principal, "test/pobject", true);
+ do_check_true(rootPerm != null);
+ do_check_eq(rootPerm.principal.origin, "http://example.com");
+
+ var subPerm = pm.getPermissionObject(subPrincipal, "test/pobject", true);
+ do_check_null(subPerm);
+ subPerm = pm.getPermissionObject(subPrincipal, "test/pobject", false);
+ do_check_true(subPerm != null);
+ do_check_eq(subPerm.principal.origin, "http://example.com");
+ do_check_eq(subPerm.type, "test/pobject");
+ do_check_eq(subPerm.capability, pm.ALLOW_ACTION);
+
+ subPerm = pm.getPermissionObject(subSubPrincipal, "test/pobject", true);
+ do_check_null(subPerm);
+ subPerm = pm.getPermissionObject(subSubPrincipal, "test/pobject", false);
+ do_check_true(subPerm != null);
+ do_check_eq(subPerm.principal.origin, "http://example.com");
+
+ pm.addFromPrincipal(principal, "test/pobject", pm.DENY_ACTION, pm.EXPIRE_SESSION);
+
+ // make sure permission objects are not dynamic
+ do_check_eq(rootPerm.capability, pm.ALLOW_ACTION);
+
+ // but do update on change
+ rootPerm = pm.getPermissionObject(principal, "test/pobject", true);
+ do_check_eq(rootPerm.capability, pm.DENY_ACTION);
+ do_check_eq(rootPerm.expireType, pm.EXPIRE_SESSION);
+
+ subPerm = pm.getPermissionObject(subPrincipal, "test/pobject", false);
+ do_check_eq(subPerm.principal.origin, "http://example.com");
+ do_check_eq(subPerm.capability, pm.DENY_ACTION);
+ do_check_eq(subPerm.expireType, pm.EXPIRE_SESSION);
+
+ pm.addFromPrincipal(subPrincipal, "test/pobject", pm.PROMPT_ACTION);
+ rootPerm = pm.getPermissionObject(principal, "test/pobject", true);
+ do_check_eq(rootPerm.principal.origin, "http://example.com");
+ do_check_eq(rootPerm.capability, pm.DENY_ACTION);
+
+ subPerm = pm.getPermissionObject(subPrincipal, "test/pobject", true);
+ do_check_eq(subPerm.principal.origin, "http://sub.example.com");
+ do_check_eq(subPerm.capability, pm.PROMPT_ACTION);
+
+ subPerm = pm.getPermissionObject(subPrincipal, "test/pobject", false);
+ do_check_eq(subPerm.principal.origin, "http://sub.example.com");
+ do_check_eq(subPerm.capability, pm.PROMPT_ACTION);
+
+ subPerm = pm.getPermissionObject(subSubPrincipal, "test/pobject", true);
+ do_check_null(subPerm);
+
+ subPerm = pm.getPermissionObject(subSubPrincipal, "test/pobject", false);
+ do_check_eq(subPerm.principal.origin, "http://sub.example.com");
+ do_check_eq(subPerm.capability, pm.PROMPT_ACTION);
+
+ pm.removeFromPrincipal(principal, "test/pobject");
+
+ rootPerm = pm.getPermissionObject(principal, "test/pobject", true);
+ do_check_null(rootPerm);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_idn.js b/extensions/cookie/test/unit/test_permmanager_idn.js
new file mode 100644
index 000000000..72fc06bba
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_idn.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function getPrincipalFromDomain(aDomain) {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let uri = NetUtil.newURI("http://" + aDomain);
+ return ssm.createCodebasePrincipal(uri, {});
+}
+
+function run_test() {
+ let profile = do_get_profile();
+ let pm = Services.perms;
+ let perm = 'test-idn';
+
+ // We create three principal linked to IDN.
+ // One with just a domain, one with a subdomain and one with the TLD
+ // containing a UTF-8 character.
+ let mainDomainPrincipal = getPrincipalFromDomain("fôû.com");
+ let subDomainPrincipal = getPrincipalFromDomain("fôô.bàr.com");
+ let tldPrincipal = getPrincipalFromDomain("fôû.bàr.côm");
+
+ // We add those to the permission manager.
+ pm.addFromPrincipal(mainDomainPrincipal, perm, pm.ALLOW_ACTION, 0, 0);
+ pm.addFromPrincipal(subDomainPrincipal, perm, pm.ALLOW_ACTION, 0, 0);
+ pm.addFromPrincipal(tldPrincipal, perm, pm.ALLOW_ACTION, 0, 0);
+
+ // They should obviously be there now..
+ do_check_eq(pm.testPermissionFromPrincipal(mainDomainPrincipal, perm), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(subDomainPrincipal, perm), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(tldPrincipal, perm), pm.ALLOW_ACTION);
+
+ // We do the same thing with the puny-encoded versions of the IDN.
+ let punyMainDomainPrincipal = getPrincipalFromDomain('xn--f-xgav.com');
+ let punySubDomainPrincipal = getPrincipalFromDomain('xn--f-xgaa.xn--br-jia.com');
+ let punyTldPrincipal = getPrincipalFromDomain('xn--f-xgav.xn--br-jia.xn--cm-8ja');
+
+ // Those principals should have the permission granted too.
+ do_check_eq(pm.testPermissionFromPrincipal(punyMainDomainPrincipal, perm), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(punySubDomainPrincipal, perm), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(punyTldPrincipal, perm), pm.ALLOW_ACTION);
+
+ // However, those two principals shouldn't be allowed because they are like
+ // the IDN but without the UT8-8 characters.
+ let witnessPrincipal = getPrincipalFromDomain("foo.com");
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, perm), pm.UNKNOWN_ACTION);
+ witnessPrincipal = getPrincipalFromDomain("foo.bar.com");
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, perm), pm.UNKNOWN_ACTION);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
new file mode 100644
index 000000000..8d36a9667
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var DEBUG_TEST = false;
+
+function run_test() {
+ // Setup a profile directory.
+ var dir = do_get_profile();
+ // Get the db file.
+ var file = dir.clone();
+ file.append("permissions.sqlite");
+
+ var storage = Cc["@mozilla.org/storage/service;1"]
+ .getService(Ci.mozIStorageService);
+
+ // Create database.
+ var connection = storage.openDatabase(file);
+ // The file should now exist.
+ do_check_true(file.exists());
+
+ connection.schemaVersion = 3;
+ connection.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ // Now we can inject garbadge in the database.
+ var garbadge = [
+ // Regular entry.
+ { host: '42', type: '0', permission: 1, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+
+ // Special values in host (some being invalid).
+ { host: 'scheme:file', type: '1', permission: 0, expireType: 0,
+ expireTime: 0, appId: 0, isInBrowserElement: 0 },
+ { host: '192.168.0.1', type: '2', permission: 0, expireType: 0,
+ expireTime: 0, appId: 0, isInBrowserElement: 0 },
+ { host: '2001:0db8:0000:0000:0000:ff00:0042:8329', type: '3', permission: 0,
+ expireType: 0, expireTime: 0, appId: 0, isInBrowserElement: 0 },
+ { host: '::1', type: '4', permission: 0, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+
+ // Permission is UNKNOWN_ACTION.
+ { host: '42', type: '5', permission: Ci.nsIPermissionManager.UNKNOWN_ACTION,
+ expireType: 0, expireTime: 0, appId: 0, isInBrowserElement: 0 },
+
+ // Permission is out of range.
+ { host: '42', type: '6', permission: 100, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+ { host: '42', type: '7', permission: -100, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+
+ // ExpireType is out of range.
+ { host: '42', type: '8', permission: 1, expireType: -100, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+ { host: '42', type: '9', permission: 1, expireType: 100, expireTime: 0,
+ appId: 0, isInBrowserElement: 0 },
+
+ // ExpireTime is at 0 with ExpireType = Time.
+ { host: '42', type: '10', permission: 1,
+ expireType: Ci.nsIPermissionManager.EXPIRE_TIME, expireTime: 0, appId: 0,
+ isInBrowserElement: 0 },
+
+ // ExpireTime has a value with ExpireType != Time
+ { host: '42', type: '11', permission: 1,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, expireTime: 1000,
+ appId: 0, isInBrowserElement: 0 },
+ { host: '42', type: '12', permission: 1,
+ expireType: Ci.nsIPermissionManager.EXPIRE_NEVER, expireTime: 1000,
+ appId: 0, isInBrowserElement: 0 },
+
+ // ExpireTime is negative.
+ { host: '42', type: '13', permission: 1,
+ expireType: Ci.nsIPermissionManager.EXPIRE_TIME, expireTime: -1,
+ appId: 0, isInBrowserElement: 0 },
+
+ // AppId is negative.
+ { host: '42', type: '14', permission: 1, expireType: 0, expireTime: 0,
+ appId: -1, isInBrowserElement: 0 },
+
+ // IsInBrowserElement is negative or higher than 1.
+ { host: '42', type: '15', permission: 1, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: -1 },
+ { host: '42', type: '16', permission: 1, expireType: 0, expireTime: 0,
+ appId: 0, isInBrowserElement: 10 },
+
+ // This insertion should be the last one. It is used to make sure we always
+ // load it regardless of the previous entries validities.
+ { host: 'example.org', type: 'test-load-invalid-entries',
+ permission: Ci.nsIPermissionManager.ALLOW_ACTION, expireType: 0,
+ expireTime: 0, appId: 0, isInBrowserElement: 0 },
+ ];
+
+ for (var i=0; i<garbadge.length; ++i) {
+ if (DEBUG_TEST) {
+ dump("\n value #" + i + "\n\n");
+ }
+ var data = garbadge[i];
+ connection.executeSimpleSQL(
+ "INSERT INTO moz_hosts " +
+ " (id, host, type, permission, expireType, expireTime, appId, isInBrowserElement) " +
+ "VALUES (" + i + ", '" + data.host + "', '" + data.type + "', "
+ + data.permission + ", " + data.expireType + ", "
+ + data.expireTime + ", " + data.appId + ", "
+ + data.isInBrowserElement + ")"
+ );
+ }
+
+ let earliestNow = Number(Date.now());
+ // Initialize the permission manager service
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ let latestNow = Number(Date.now());
+
+ // The schema should be upgraded to 9, and a 'modificationTime' column should
+ // exist with all records having a value of 0.
+ do_check_eq(connection.schemaVersion, 9);
+
+ let select = connection.createStatement("SELECT modificationTime FROM moz_perms")
+ let numMigrated = 0;
+ while (select.executeStep()) {
+ let thisModTime = select.getInt64(0);
+ do_check_true(thisModTime == 0, "new modifiedTime field is correct");
+ numMigrated += 1;
+ }
+ // check we found at least 1 record that was migrated.
+ do_check_true(numMigrated > 0, "we found at least 1 record that was migrated");
+
+ // This permission should always be there.
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let uri = NetUtil.newURI("http://example.org");
+ let principal = ssm.createCodebasePrincipal(uri, {});
+ do_check_eq(pm.testPermissionFromPrincipal(principal, 'test-load-invalid-entries'), Ci.nsIPermissionManager.ALLOW_ACTION);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_local_files.js b/extensions/cookie/test/unit/test_permmanager_local_files.js
new file mode 100644
index 000000000..b9c803f5c
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_local_files.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that permissions work for file:// URIs (aka local files).
+
+function getPrincipalFromURIString(uriStr)
+{
+ let uri = NetUtil.newURI(uriStr);
+ return Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
+}
+
+function run_test() {
+ let pm = Services.perms;
+
+ // If we add a permission to a file:// URI, the test should return true.
+ let principal = getPrincipalFromURIString("file:///foo/bar");
+ pm.addFromPrincipal(principal, "test/local-files", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(principal, "test/local-files"), pm.ALLOW_ACTION);
+
+ // Another file:// URI should have the same permission.
+ let witnessPrincipal = getPrincipalFromURIString("file:///bar/foo");
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+
+ // Giving "file:///" a permission shouldn't give it to all file:// URIs.
+ let rootPrincipal = getPrincipalFromURIString("file:///");
+ pm.addFromPrincipal(rootPrincipal, "test/local-files", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+
+ // Giving "file://" a permission shouldn't give it to all file:// URIs.
+ let schemeRootPrincipal = getPrincipalFromURIString("file://");
+ pm.addFromPrincipal(schemeRootPrincipal, "test/local-files", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+
+ // Giving 'node' a permission shouldn't give it to its 'children'.
+ let fileInDirPrincipal = getPrincipalFromURIString("file:///foo/bar/foobar.txt");
+ do_check_eq(pm.testPermissionFromPrincipal(fileInDirPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+
+ // Revert "file:///foo/bar" permission and check that it has been correctly taken into account.
+ pm.removeFromPrincipal(principal, "test/local-files");
+ do_check_eq(pm.testPermissionFromPrincipal(principal, "test/local-files"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(witnessPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(fileInDirPrincipal, "test/local-files"), pm.UNKNOWN_ACTION);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_matches.js b/extensions/cookie/test/unit/test_permmanager_matches.js
new file mode 100644
index 000000000..c15288890
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_matches.js
@@ -0,0 +1,183 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function matches_always(perm, principals) {
+ principals.forEach((principal) => {
+ do_check_true(perm.matches(principal, true), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ do_check_true(perm.matches(principal, false), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ });
+}
+
+function matches_weak(perm, principals) {
+ principals.forEach((principal) => {
+ do_check_false(perm.matches(principal, true), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ do_check_true(perm.matches(principal, false), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ });
+}
+
+function matches_never(perm, principals) {
+ principals.forEach((principal) => {
+ do_check_false(perm.matches(principal, true), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ do_check_false(perm.matches(principal, false), "perm: " + perm.principal.origin + ", princ: " + principal.origin);
+ });
+}
+
+function run_test() {
+ // initialize the permission manager service
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+
+ // Add some permissions
+ let uri0 = NetUtil.newURI("http://google.com/search?q=foo#hashtag", null, null);
+ let uri1 = NetUtil.newURI("http://hangouts.google.com/subdir", null, null);
+ let uri2 = NetUtil.newURI("http://google.org/", null, null);
+ let uri3 = NetUtil.newURI("https://google.com/some/random/subdirectory", null, null);
+ let uri4 = NetUtil.newURI("https://hangouts.google.com/#!/hangout", null, null);
+ let uri5 = NetUtil.newURI("http://google.com:8096/", null, null);
+
+ let uri0_n_n = secMan.createCodebasePrincipal(uri0, {});
+ let uri1_n_n = secMan.createCodebasePrincipal(uri1, {});
+ let uri2_n_n = secMan.createCodebasePrincipal(uri2, {});
+ let uri3_n_n = secMan.createCodebasePrincipal(uri3, {});
+ let uri4_n_n = secMan.createCodebasePrincipal(uri4, {});
+ let uri5_n_n = secMan.createCodebasePrincipal(uri5, {});
+
+ let attrs = {appId: 1000};
+ let uri0_1000_n = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_1000_n = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_1000_n = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_1000_n = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_1000_n = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_1000_n = secMan.createCodebasePrincipal(uri5, attrs);
+
+ attrs = {appId: 1000, inIsolatedMozBrowser: true};
+ let uri0_1000_y = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_1000_y = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_1000_y = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_1000_y = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_1000_y = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_1000_y = secMan.createCodebasePrincipal(uri5, attrs);
+
+ attrs = {appId: 2000};
+ let uri0_2000_n = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_2000_n = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_2000_n = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_2000_n = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_2000_n = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_2000_n = secMan.createCodebasePrincipal(uri5, attrs);
+
+ attrs = {appId: 2000, inIsolatedMozBrowser: true};
+ let uri0_2000_y = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_2000_y = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_2000_y = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_2000_y = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_2000_y = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_2000_y = secMan.createCodebasePrincipal(uri5, attrs);
+
+ attrs = {userContextId: 1};
+ let uri0_1 = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_1 = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_1 = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_1 = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_1 = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_1 = secMan.createCodebasePrincipal(uri5, attrs);
+
+ attrs = {firstPartyDomain: "cnn.com"};
+ let uri0_cnn = secMan.createCodebasePrincipal(uri0, attrs);
+ let uri1_cnn = secMan.createCodebasePrincipal(uri1, attrs);
+ let uri2_cnn = secMan.createCodebasePrincipal(uri2, attrs);
+ let uri3_cnn = secMan.createCodebasePrincipal(uri3, attrs);
+ let uri4_cnn = secMan.createCodebasePrincipal(uri4, attrs);
+ let uri5_cnn = secMan.createCodebasePrincipal(uri5, attrs);
+
+ pm.addFromPrincipal(uri0_n_n, "test/matches", pm.ALLOW_ACTION);
+ let perm_n_n = pm.getPermissionObject(uri0_n_n, "test/matches", true);
+ pm.addFromPrincipal(uri0_1000_n, "test/matches", pm.ALLOW_ACTION);
+ let perm_1000_n = pm.getPermissionObject(uri0_1000_n, "test/matches", true);
+ pm.addFromPrincipal(uri0_1000_y, "test/matches", pm.ALLOW_ACTION);
+ let perm_1000_y = pm.getPermissionObject(uri0_1000_y, "test/matches", true);
+ pm.addFromPrincipal(uri0_2000_n, "test/matches", pm.ALLOW_ACTION);
+ let perm_2000_n = pm.getPermissionObject(uri0_2000_n, "test/matches", true);
+ pm.addFromPrincipal(uri0_2000_y, "test/matches", pm.ALLOW_ACTION);
+ let perm_2000_y = pm.getPermissionObject(uri0_2000_y, "test/matches", true);
+ pm.addFromPrincipal(uri0_1, "test/matches", pm.ALLOW_ACTION);
+ let perm_1 = pm.getPermissionObject(uri0_n_n, "test/matches", true);
+ pm.addFromPrincipal(uri0_cnn, "test/matches", pm.ALLOW_ACTION);
+ let perm_cnn = pm.getPermissionObject(uri0_n_n, "test/matches", true);
+
+ matches_always(perm_n_n, [uri0_n_n, uri0_1, uri0_cnn]);
+ matches_weak(perm_n_n, [uri1_n_n, uri1_1, uri1_cnn]);
+ matches_never(perm_n_n, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri2_1, uri3_1, uri4_1, uri5_1,
+ uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_1000_n, [uri0_1000_n]);
+ matches_weak(perm_1000_n, [uri1_1000_n]);
+ matches_never(perm_1000_n, [uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+ uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_1000_y, [uri0_1000_y]);
+ matches_weak(perm_1000_y, [uri1_1000_y]);
+ matches_never(perm_1000_y, [uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+ uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_2000_n, [uri0_2000_n]);
+ matches_weak(perm_2000_n, [uri1_2000_n]);
+ matches_never(perm_2000_n, [uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+ uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_2000_y, [uri0_2000_y]);
+ matches_weak(perm_2000_y, [uri1_2000_y]);
+ matches_never(perm_2000_y, [uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+ uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_1, [uri0_n_n, uri0_1, uri0_cnn]);
+ matches_weak(perm_1, [uri1_n_n, uri1_1, uri1_cnn]);
+ matches_never(perm_1, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri2_1, uri3_1, uri4_1, uri5_1,
+ uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ matches_always(perm_cnn, [uri0_n_n, uri0_1, uri0_cnn]);
+ matches_weak(perm_cnn, [uri1_n_n, uri1_1, uri1_cnn]);
+ matches_never(perm_cnn, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+ uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+ uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+ uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+ uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+ uri2_1, uri3_1, uri4_1, uri5_1,
+ uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+ // Clean up!
+ pm.removeAll();
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_matchesuri.js b/extensions/cookie/test/unit/test_permmanager_matchesuri.js
new file mode 100644
index 000000000..88578166c
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_matchesuri.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function matches_always(perm, uris) {
+ uris.forEach((uri) => {
+ do_check_true(perm.matchesURI(uri, true), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ do_check_true(perm.matchesURI(uri, false), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ });
+}
+
+function matches_weak(perm, uris) {
+ uris.forEach((uri) => {
+ do_check_false(perm.matchesURI(uri, true), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ do_check_true(perm.matchesURI(uri, false), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ });
+}
+
+function matches_never(perm, uris) {
+ uris.forEach((uri) => {
+ do_check_false(perm.matchesURI(uri, true), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ do_check_false(perm.matchesURI(uri, false), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
+ });
+}
+
+function mk_permission(uri, isAppPermission = false) {
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+
+ // Get the permission from the principal!
+ let attrs = {appId: 1000};
+ let principal =
+ secMan.createCodebasePrincipal(uri, isAppPermission ? attrs : {});
+
+ pm.addFromPrincipal(principal, "test/matchesuri", pm.ALLOW_ACTION);
+ let permission = pm.getPermissionObject(principal, "test/matchesuri", true);
+
+ return permission;
+}
+
+function run_test() {
+ // initialize the permission manager service
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+
+ let fileprefix = "file:///";
+ if (Services.appinfo.OS == "WINNT") {
+ // Windows rejects files if they don't have a drive. See Bug 1180870
+ fileprefix += "c:/";
+ }
+
+ // Add some permissions
+ let uri0 = NetUtil.newURI("http://google.com:9091/just/a/path", null, null);
+ let uri1 = NetUtil.newURI("http://hangouts.google.com:9091/some/path", null, null);
+ let uri2 = NetUtil.newURI("http://google.com:9091/", null, null);
+ let uri3 = NetUtil.newURI("http://google.org:9091/", null, null);
+ let uri4 = NetUtil.newURI("http://deeper.hangouts.google.com:9091/", null, null);
+ let uri5 = NetUtil.newURI("https://google.com/just/a/path", null, null);
+ let uri6 = NetUtil.newURI("https://hangouts.google.com", null, null);
+ let uri7 = NetUtil.newURI("https://google.com/", null, null);
+
+ let fileuri1 = NetUtil.newURI(fileprefix + "a/file/path", null, null);
+ let fileuri2 = NetUtil.newURI(fileprefix + "a/file/path/deeper", null, null);
+ let fileuri3 = NetUtil.newURI(fileprefix + "a/file/otherpath", null, null);
+
+ {
+ let perm = mk_permission(uri0);
+ matches_always(perm, [uri0, uri2]);
+ matches_weak(perm, [uri1, uri4]);
+ matches_never(perm, [uri3, uri5, uri6, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri1);
+ matches_always(perm, [uri1]);
+ matches_weak(perm, [uri4]);
+ matches_never(perm, [uri0, uri2, uri3, uri5, uri6, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri2);
+ matches_always(perm, [uri0, uri2]);
+ matches_weak(perm, [uri1, uri4]);
+ matches_never(perm, [uri3, uri5, uri6, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri3);
+ matches_always(perm, [uri3]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri1, uri2, uri4, uri5, uri6, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri4);
+ matches_always(perm, [uri4]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri1, uri2, uri3, uri5, uri6, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri5);
+ matches_always(perm, [uri5, uri7]);
+ matches_weak(perm, [uri6]);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri6);
+ matches_always(perm, [uri6]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, uri5, uri7, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(uri7);
+ matches_always(perm, [uri5, uri7]);
+ matches_weak(perm, [uri6]);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, fileuri1, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(fileuri1);
+ matches_always(perm, [fileuri1]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, uri5, uri6, uri7, fileuri2, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(fileuri2);
+ matches_always(perm, [fileuri2]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, uri5, uri6, uri7, fileuri1, fileuri3]);
+ }
+
+ {
+ let perm = mk_permission(fileuri3);
+ matches_always(perm, [fileuri3]);
+ matches_weak(perm, []);
+ matches_never(perm, [uri0, uri1, uri2, uri3, uri4, uri5, uri6, uri7, fileuri1, fileuri2]);
+ }
+
+ // Clean up!
+ pm.removeAll();
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js b/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
new file mode 100644
index 000000000..4749422da
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
@@ -0,0 +1,207 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 4;
+
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ let stmtInsert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+ ") VALUES (" +
+ ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+ ")");
+
+ let id = 0;
+
+ function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+ let thisId = id++;
+
+ stmtInsert.bindByName("id", thisId);
+ stmtInsert.bindByName("host", host);
+ stmtInsert.bindByName("type", type);
+ stmtInsert.bindByName("permission", permission);
+ stmtInsert.bindByName("expireType", expireType);
+ stmtInsert.bindByName("expireTime", expireTime);
+ stmtInsert.bindByName("modificationTime", modificationTime);
+ stmtInsert.bindByName("appId", appId);
+ stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+ stmtInsert.execute();
+
+ return {
+ id: thisId,
+ host: host,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime,
+ appId: appId,
+ isInBrowserElement: isInBrowserElement
+ };
+ }
+
+ // Add some rows to the database
+ let created = [
+ insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+ insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+ insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+ insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+ insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+ insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+ ];
+
+ // CLose the db connection
+ stmtInsert.finalize();
+ db.close();
+ stmtInsert = null;
+ db = null;
+
+ let expected = [
+ // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
+ // and http://foo.com or a subdomain are never visited.
+ // ["http://foo.com", "A", 1, 0, 0],
+ // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+ // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ //
+ // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
+ // for subdomains of foo.com either
+ // ["http://sub.foo.com", "B", 1, 0, 0],
+ // ["http://subber.sub.foo.com", "B", 1, 0, 0],
+
+ ["https://foo.com", "A", 1, 0, 0],
+ ["https://foo.com", "C", 1, 0, 0],
+ ["https://foo.com^appId=1000", "A", 1, 0, 0],
+ ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ ["https://sub.foo.com", "B", 1, 0, 0],
+ ["https://subber.sub.foo.com", "B", 1, 0, 0],
+
+ // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
+ ["http://bar.ca", "B", 1, 0, 0],
+ ["https://bar.ca", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["file:///some/path/to/file.html", "A", 1, 0, 0],
+ ["file:///another/file.html", "A", 1, 0, 0],
+
+ // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
+ // also have these entries
+ ["ftp://foo.com:8000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000", "C", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
+
+ // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
+ // following entries
+ ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
+ ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+ // Make sure that we also support localhost, and IP addresses
+ ["http://localhost", "A", 1, 0, 0],
+ ["https://localhost", "A", 1, 0, 0],
+ ["http://127.0.0.1", "A", 1, 0, 0],
+ ["https://127.0.0.1", "A", 1, 0, 0],
+ ["http://192.0.2.235", "A", 1, 0, 0],
+ ["https://192.0.2.235", "A", 1, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Add some places to the places database
+ yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_false(db.tableExists("moz_perms_v6"));
+
+ // The moz_hosts table should still exist but be empty
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), 0);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js b/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
new file mode 100644
index 000000000..38646660a
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
@@ -0,0 +1,226 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+/*
+ * Prevent the nsINavHistoryService from being avaliable for the migration
+ */
+
+var CONTRACT_ID = "@mozilla.org/browser/nav-history-service;1";
+var factory = {
+ createInstance: function() {
+ throw new Error("There is no history service");
+ },
+ lockFactory: function() {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+var newClassID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+var oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+var oldFactory = Components.manager.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+registrar.unregisterFactory(oldClassID, oldFactory);
+registrar.registerFactory(newClassID, "", CONTRACT_ID, factory);
+
+function cleanupFactory() {
+ registrar.unregisterFactory(newClassID, factory);
+ registrar.registerFactory(oldClassID, "", CONTRACT_ID, oldFactory);
+}
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+/*
+ * Done nsINavHistoryService code
+ */
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ // Make sure that we can't resolve the nsINavHistoryService
+ try {
+ Cc['@mozilla.org/browser/nav-history-service;1'].getService(Ci.nsINavHistoryService);
+ do_check_true(false, "There shouldn't have been a nsINavHistoryService");
+ } catch (e) {
+ do_check_true(true, "There wasn't a nsINavHistoryService");
+ }
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 4;
+
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ let stmtInsert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+ ") VALUES (" +
+ ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+ ")");
+
+ let id = 0;
+
+ function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+ let thisId = id++;
+
+ stmtInsert.bindByName("id", thisId);
+ stmtInsert.bindByName("host", host);
+ stmtInsert.bindByName("type", type);
+ stmtInsert.bindByName("permission", permission);
+ stmtInsert.bindByName("expireType", expireType);
+ stmtInsert.bindByName("expireTime", expireTime);
+ stmtInsert.bindByName("modificationTime", modificationTime);
+ stmtInsert.bindByName("appId", appId);
+ stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+ stmtInsert.execute();
+
+ return {
+ id: thisId,
+ host: host,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime,
+ appId: appId,
+ isInBrowserElement: isInBrowserElement
+ };
+ }
+
+ // Add some rows to the database
+ let created = [
+ insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+ insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+ insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+ insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+ insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+ insertHost("263.123.555.676", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+ ];
+
+ // CLose the db connection
+ stmtInsert.finalize();
+ db.close();
+ stmtInsert = null;
+ db = null;
+
+ let expected = [
+ ["http://foo.com", "A", 1, 0, 0],
+ ["http://foo.com", "C", 1, 0, 0],
+ ["http://foo.com^appId=1000", "A", 1, 0, 0],
+ ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ ["http://sub.foo.com", "B", 1, 0, 0],
+ ["http://subber.sub.foo.com", "B", 1, 0, 0],
+
+ ["https://foo.com", "A", 1, 0, 0],
+ ["https://foo.com", "C", 1, 0, 0],
+ ["https://foo.com^appId=1000", "A", 1, 0, 0],
+ ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ ["https://sub.foo.com", "B", 1, 0, 0],
+ ["https://subber.sub.foo.com", "B", 1, 0, 0],
+
+ // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
+ ["http://bar.ca", "B", 1, 0, 0],
+ ["https://bar.ca", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["file:///some/path/to/file.html", "A", 1, 0, 0],
+ ["file:///another/file.html", "A", 1, 0, 0],
+
+ // Make sure that we also support localhost, and IP addresses
+ ["http://localhost", "A", 1, 0, 0],
+ ["https://localhost", "A", 1, 0, 0],
+ ["http://127.0.0.1", "A", 1, 0, 0],
+ ["https://127.0.0.1", "A", 1, 0, 0],
+ ["http://263.123.555.676", "A", 1, 0, 0],
+ ["https://263.123.555.676", "A", 1, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_false(db.tableExists("moz_perms_v6"));
+
+ // The moz_hosts table should still exist but be empty
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), 0);
+
+ db.close();
+ }
+
+ cleanupFactory();
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js b/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
new file mode 100644
index 000000000..4d38d72e8
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
@@ -0,0 +1,284 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 5;
+
+ /*
+ * V5 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",origin TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ")");
+
+ let stmt5Insert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, origin, type, permission, expireType, expireTime, modificationTime" +
+ ") VALUES (" +
+ ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+ ")");
+
+ /*
+ * V4 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts_v4 (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ let stmtInsert = db.createStatement(
+ "INSERT INTO moz_hosts_v4 (" +
+ "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+ ") VALUES (" +
+ ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+ ")");
+
+ let id = 0;
+
+ function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+ let thisId = id++;
+
+ stmt5Insert.bindByName("id", thisId);
+ stmt5Insert.bindByName("origin", origin);
+ stmt5Insert.bindByName("type", type);
+ stmt5Insert.bindByName("permission", permission);
+ stmt5Insert.bindByName("expireType", expireType);
+ stmt5Insert.bindByName("expireTime", expireTime);
+ stmt5Insert.bindByName("modificationTime", modificationTime);
+
+ stmt5Insert.execute();
+
+ return {
+ id: thisId,
+ origin: origin,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime
+ };
+ }
+
+ function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+ let thisId = id++;
+
+ stmtInsert.bindByName("id", thisId);
+ stmtInsert.bindByName("host", host);
+ stmtInsert.bindByName("type", type);
+ stmtInsert.bindByName("permission", permission);
+ stmtInsert.bindByName("expireType", expireType);
+ stmtInsert.bindByName("expireTime", expireTime);
+ stmtInsert.bindByName("modificationTime", modificationTime);
+ stmtInsert.bindByName("appId", appId);
+ stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+ stmtInsert.execute();
+
+ return {
+ id: thisId,
+ host: host,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime,
+ appId: appId,
+ isInBrowserElement: isInBrowserElement
+ };
+ }
+
+ let created5 = [
+ insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+ ];
+
+ // Add some rows to the database
+ let created = [
+ insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+ insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+ insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+ insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+ insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+ insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+ ];
+
+ // CLose the db connection
+ stmtInsert.finalize();
+ db.close();
+ stmtInsert = null;
+ db = null;
+
+ let expected = [
+ // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
+ // and http://foo.com or a subdomain are never visited.
+ // ["http://foo.com", "A", 1, 0, 0],
+ // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+ // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ //
+ // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
+ // for subdomains of foo.com either
+ // ["http://sub.foo.com", "B", 1, 0, 0],
+ // ["http://subber.sub.foo.com", "B", 1, 0, 0],
+
+ ["https://foo.com", "A", 1, 0, 0],
+ ["https://foo.com", "C", 1, 0, 0],
+ ["https://foo.com^appId=1000", "A", 1, 0, 0],
+ ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ ["https://sub.foo.com", "B", 1, 0, 0],
+ ["https://subber.sub.foo.com", "B", 1, 0, 0],
+
+ // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
+ ["http://bar.ca", "B", 1, 0, 0],
+ ["https://bar.ca", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["file:///some/path/to/file.html", "A", 1, 0, 0],
+ ["file:///another/file.html", "A", 1, 0, 0],
+
+ // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
+ // also have these entries
+ ["ftp://foo.com:8000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000", "C", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
+
+ // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
+ // following entries
+ ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
+ ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+ // Make sure that we also support localhost, and IP addresses
+ ["http://localhost", "A", 1, 0, 0],
+ ["https://localhost", "A", 1, 0, 0],
+ ["http://127.0.0.1", "A", 1, 0, 0],
+ ["https://127.0.0.1", "A", 1, 0, 0],
+ ["http://192.0.2.235", "A", 1, 0, 0],
+ ["https://192.0.2.235", "A", 1, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Add some places to the places database
+ yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_true(db.tableExists("moz_perms_v6"));
+
+ // The moz_hosts table should still exist but be empty
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), 0);
+
+ // Check that the moz_perms_v6 table contains the backup of the entry we created
+ let mozPermsV6Stmt = db.createStatement("SELECT " +
+ "origin, type, permission, expireType, expireTime, modificationTime " +
+ "FROM moz_perms_v6 WHERE id = :id");
+
+ // Check that the moz_hosts table still contains the correct values.
+ created5.forEach((it) => {
+ mozPermsV6Stmt.reset();
+ mozPermsV6Stmt.bindByName("id", it.id);
+ mozPermsV6Stmt.executeStep();
+ do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
+ do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
+ do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
+ do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
+ do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
+ do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
+ });
+
+ // Check that there are the right number of values
+ let mozPermsV6Count = db.createStatement("SELECT count(*) FROM moz_perms_v6");
+ mozPermsV6Count.executeStep();
+ do_check_eq(mozPermsV6Count.getInt64(0), created5.length);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js b/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
new file mode 100644
index 000000000..0e5820d2b
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
@@ -0,0 +1,168 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 5;
+
+ /*
+ * V5 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",origin TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ")");
+
+ let stmt5Insert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, origin, type, permission, expireType, expireTime, modificationTime" +
+ ") VALUES (" +
+ ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+ ")");
+
+ let id = 0;
+
+ function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+ let thisId = id++;
+
+ stmt5Insert.bindByName("id", thisId);
+ stmt5Insert.bindByName("origin", origin);
+ stmt5Insert.bindByName("type", type);
+ stmt5Insert.bindByName("permission", permission);
+ stmt5Insert.bindByName("expireType", expireType);
+ stmt5Insert.bindByName("expireTime", expireTime);
+ stmt5Insert.bindByName("modificationTime", modificationTime);
+
+ stmt5Insert.execute();
+
+ return {
+ id: thisId,
+ host: origin,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime
+ };
+ }
+
+ let created5 = [
+ insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+
+ insertOrigin("http://127.0.0.1", "B", 2, 0, 0, 0),
+ insertOrigin("http://localhost", "B", 2, 0, 0, 0),
+ ];
+
+ let created4 = []; // Didn't create any v4 entries, so the DB should be empty
+
+ // CLose the db connection
+ stmt5Insert.finalize();
+ db.close();
+ stmt5Insert = null;
+ db = null;
+
+ let expected = [
+ ["https://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
+
+ ["http://127.0.0.1", "B", 2, 0, 0, 0],
+ ["http://localhost", "B", 2, 0, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_false(db.tableExists("moz_perms_v6"));
+
+ let mozHostsStmt = db.createStatement("SELECT " +
+ "host, type, permission, expireType, expireTime, " +
+ "modificationTime, appId, isInBrowserElement " +
+ "FROM moz_hosts WHERE id = :id");
+
+ // Check that the moz_hosts table still contains the correct values.
+ created4.forEach((it) => {
+ mozHostsStmt.reset();
+ mozHostsStmt.bindByName("id", it.id);
+ mozHostsStmt.executeStep();
+ do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
+ do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
+ do_check_eq(mozHostsStmt.getInt64(2), it.permission);
+ do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
+ do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
+ do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
+ do_check_eq(mozHostsStmt.getInt64(6), it.appId);
+ do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
+ });
+
+ // Check that there are the right number of values
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), created4.length);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js b/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
new file mode 100644
index 000000000..aa634c7eb
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
@@ -0,0 +1,284 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 6;
+
+ /*
+ * V5 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_perms (" +
+ " id INTEGER PRIMARY KEY" +
+ ",origin TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ")");
+
+ let stmt6Insert = db.createStatement(
+ "INSERT INTO moz_perms (" +
+ "id, origin, type, permission, expireType, expireTime, modificationTime" +
+ ") VALUES (" +
+ ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+ ")");
+
+ /*
+ * V4 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ let stmtInsert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+ ") VALUES (" +
+ ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+ ")");
+
+ let id = 0;
+
+ function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+ let thisId = id++;
+
+ stmt6Insert.bindByName("id", thisId);
+ stmt6Insert.bindByName("origin", origin);
+ stmt6Insert.bindByName("type", type);
+ stmt6Insert.bindByName("permission", permission);
+ stmt6Insert.bindByName("expireType", expireType);
+ stmt6Insert.bindByName("expireTime", expireTime);
+ stmt6Insert.bindByName("modificationTime", modificationTime);
+
+ stmt6Insert.execute();
+
+ return {
+ id: thisId,
+ origin: origin,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime
+ };
+ }
+
+ function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+ let thisId = id++;
+
+ stmtInsert.bindByName("id", thisId);
+ stmtInsert.bindByName("host", host);
+ stmtInsert.bindByName("type", type);
+ stmtInsert.bindByName("permission", permission);
+ stmtInsert.bindByName("expireType", expireType);
+ stmtInsert.bindByName("expireTime", expireTime);
+ stmtInsert.bindByName("modificationTime", modificationTime);
+ stmtInsert.bindByName("appId", appId);
+ stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+ stmtInsert.execute();
+
+ return {
+ id: thisId,
+ host: host,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime,
+ appId: appId,
+ isInBrowserElement: isInBrowserElement
+ };
+ }
+
+ let created6 = [
+ insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+ ];
+
+ // Add some rows to the database
+ let created = [
+ insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+ insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+ insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+ insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+ insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+ insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+ ];
+
+ // CLose the db connection
+ stmtInsert.finalize();
+ db.close();
+ stmtInsert = null;
+ db = null;
+
+ let expected = [
+ // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
+ // and http://foo.com or a subdomain are never visited.
+ // ["http://foo.com", "A", 1, 0, 0],
+ // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+ // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ //
+ // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
+ // for subdomains of foo.com either
+ // ["http://sub.foo.com", "B", 1, 0, 0],
+ // ["http://subber.sub.foo.com", "B", 1, 0, 0],
+
+ ["https://foo.com", "A", 1, 0, 0],
+ ["https://foo.com", "C", 1, 0, 0],
+ ["https://foo.com^appId=1000", "A", 1, 0, 0],
+ ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
+ ["https://sub.foo.com", "B", 1, 0, 0],
+ ["https://subber.sub.foo.com", "B", 1, 0, 0],
+
+ // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
+ ["http://bar.ca", "B", 1, 0, 0],
+ ["https://bar.ca", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+ ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+ ["file:///some/path/to/file.html", "A", 1, 0, 0],
+ ["file:///another/file.html", "A", 1, 0, 0],
+
+ // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
+ // also have these entries
+ ["ftp://foo.com:8000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000", "C", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+ ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
+
+ // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
+ // following entries
+ ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
+ ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+ // Make sure that we also support localhost, and IP addresses
+ ["http://localhost", "A", 1, 0, 0],
+ ["https://localhost", "A", 1, 0, 0],
+ ["http://127.0.0.1", "A", 1, 0, 0],
+ ["https://127.0.0.1", "A", 1, 0, 0],
+ ["http://192.0.2.235", "A", 1, 0, 0],
+ ["https://192.0.2.235", "A", 1, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Add some places to the places database
+ yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_true(db.tableExists("moz_perms_v6"));
+
+ // The moz_hosts table should still exist but be empty
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), 0);
+
+ // Check that the moz_perms_v6 table contains the backup of the entry we created
+ let mozPermsV6Stmt = db.createStatement("SELECT " +
+ "origin, type, permission, expireType, expireTime, modificationTime " +
+ "FROM moz_perms_v6 WHERE id = :id");
+
+ // Check that the moz_hosts table still contains the correct values.
+ created6.forEach((it) => {
+ mozPermsV6Stmt.reset();
+ mozPermsV6Stmt.bindByName("id", it.id);
+ mozPermsV6Stmt.executeStep();
+ do_check_eq(mozPermsV6Stmt.getUTF8String(0), it.origin);
+ do_check_eq(mozPermsV6Stmt.getUTF8String(1), it.type);
+ do_check_eq(mozPermsV6Stmt.getInt64(2), it.permission);
+ do_check_eq(mozPermsV6Stmt.getInt64(3), it.expireType);
+ do_check_eq(mozPermsV6Stmt.getInt64(4), it.expireTime);
+ do_check_eq(mozPermsV6Stmt.getInt64(5), it.modificationTime);
+ });
+
+ // Check that there are the right number of values
+ let mozPermsV6Count = db.createStatement("SELECT count(*) FROM moz_perms_v6");
+ mozPermsV6Count.executeStep();
+ do_check_eq(mozPermsV6Count.getInt64(0), created6.length);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js b/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
new file mode 100644
index 000000000..7b138ff29
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 6;
+
+ /*
+ * V5 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_perms (" +
+ " id INTEGER PRIMARY KEY" +
+ ",origin TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ")");
+
+ let stmt6Insert = db.createStatement(
+ "INSERT INTO moz_perms (" +
+ "id, origin, type, permission, expireType, expireTime, modificationTime" +
+ ") VALUES (" +
+ ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+ ")");
+
+ let id = 0;
+
+ function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+ let thisId = id++;
+
+ stmt6Insert.bindByName("id", thisId);
+ stmt6Insert.bindByName("origin", origin);
+ stmt6Insert.bindByName("type", type);
+ stmt6Insert.bindByName("permission", permission);
+ stmt6Insert.bindByName("expireType", expireType);
+ stmt6Insert.bindByName("expireTime", expireTime);
+ stmt6Insert.bindByName("modificationTime", modificationTime);
+
+ stmt6Insert.execute();
+
+ return {
+ id: thisId,
+ host: origin,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime
+ };
+ }
+
+ let created6 = [
+ insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+ ];
+
+ let created4 = []; // Didn't create any v4 entries, so the DB should be empty
+
+ // CLose the db connection
+ stmt6Insert.finalize();
+ db.close();
+ stmt6Insert = null;
+ db = null;
+
+ let expected = [
+ ["https://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0]
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_false(db.tableExists("moz_perms_v6"));
+
+ let mozHostsStmt = db.createStatement("SELECT " +
+ "host, type, permission, expireType, expireTime, " +
+ "modificationTime, appId, isInBrowserElement " +
+ "FROM moz_hosts WHERE id = :id");
+
+ // Check that the moz_hosts table still contains the correct values.
+ created4.forEach((it) => {
+ mozHostsStmt.reset();
+ mozHostsStmt.bindByName("id", it.id);
+ mozHostsStmt.executeStep();
+ do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
+ do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
+ do_check_eq(mozHostsStmt.getInt64(2), it.permission);
+ do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
+ do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
+ do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
+ do_check_eq(mozHostsStmt.getInt64(6), it.appId);
+ do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
+ });
+
+ // Check that there are the right number of values
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), created4.length);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js b/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
new file mode 100644
index 000000000..56d5e2347
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
@@ -0,0 +1,246 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+var PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+ let file = profile.clone();
+ file.append(PERMISSIONS_FILE_NAME);
+ return file;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function test() {
+ /* Create and set up the permissions database */
+ let profile = do_get_profile();
+
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ db.schemaVersion = 7;
+
+ /*
+ * V5 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_perms (" +
+ " id INTEGER PRIMARY KEY" +
+ ",origin TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ")");
+
+ let stmt6Insert = db.createStatement(
+ "INSERT INTO moz_perms (" +
+ "id, origin, type, permission, expireType, expireTime, modificationTime" +
+ ") VALUES (" +
+ ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+ ")");
+
+ /*
+ * V4 table
+ */
+ db.executeSimpleSQL(
+ "CREATE TABLE moz_hosts (" +
+ " id INTEGER PRIMARY KEY" +
+ ",host TEXT" +
+ ",type TEXT" +
+ ",permission INTEGER" +
+ ",expireType INTEGER" +
+ ",expireTime INTEGER" +
+ ",modificationTime INTEGER" +
+ ",appId INTEGER" +
+ ",isInBrowserElement INTEGER" +
+ ")");
+
+ let stmtInsert = db.createStatement(
+ "INSERT INTO moz_hosts (" +
+ "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+ ") VALUES (" +
+ ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+ ")");
+
+ /*
+ * The v4 table is a backup
+ */
+ db.executeSimpleSQL("CREATE TABLE moz_hosts_is_backup (dummy INTEGER PRIMARY KEY)");
+
+ let id = 0;
+
+ function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+ let thisId = id++;
+
+ stmt6Insert.bindByName("id", thisId);
+ stmt6Insert.bindByName("origin", origin);
+ stmt6Insert.bindByName("type", type);
+ stmt6Insert.bindByName("permission", permission);
+ stmt6Insert.bindByName("expireType", expireType);
+ stmt6Insert.bindByName("expireTime", expireTime);
+ stmt6Insert.bindByName("modificationTime", modificationTime);
+
+ stmt6Insert.execute();
+
+ return {
+ id: thisId,
+ origin: origin,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime
+ };
+ }
+
+ function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+ let thisId = id++;
+
+ stmtInsert.bindByName("id", thisId);
+ stmtInsert.bindByName("host", host);
+ stmtInsert.bindByName("type", type);
+ stmtInsert.bindByName("permission", permission);
+ stmtInsert.bindByName("expireType", expireType);
+ stmtInsert.bindByName("expireTime", expireTime);
+ stmtInsert.bindByName("modificationTime", modificationTime);
+ stmtInsert.bindByName("appId", appId);
+ stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+ stmtInsert.execute();
+
+ return {
+ id: thisId,
+ host: host,
+ type: type,
+ permission: permission,
+ expireType: expireType,
+ expireTime: expireTime,
+ modificationTime: modificationTime,
+ appId: appId,
+ isInBrowserElement: isInBrowserElement
+ };
+ }
+
+ let created7 = [
+ insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+ insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+ insertOrigin("https://192.0.2.235", "A", 2, 0, 0),
+ ];
+
+ // Add some rows to the database
+ let created = [
+ insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+ insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+ insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+ insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+ insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+ insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+ insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+ insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
+ // Although ipv6 addresses are written with [] around the IP address,
+ // the .host property doesn't contain these []s, which means that we
+ // write it like this
+ insertHost("2001:db8::ff00:42:8329", "C", 1, 0, 0, 0, 0, false),
+ insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+ insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+ insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+ ];
+
+ // CLose the db connection
+ stmtInsert.finalize();
+ db.close();
+ stmtInsert = null;
+ db = null;
+
+ let expected = [
+ // We should have kept the previously migrated entries
+ ["https://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com", "A", 2, 0, 0, 0],
+ ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
+
+ // Make sure that we also support localhost, and IP addresses
+ ["https://localhost:8080", "A", 1, 0, 0],
+ ["ftp://127.0.0.1:8080", "A", 1, 0, 0],
+
+ ["http://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
+ ["https://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
+ ["http://192.0.2.235", "A", 1, 0, 0],
+
+ // There should only be one entry of this type in the database
+ ["https://192.0.2.235", "A", 2, 0, 0],
+ ];
+
+ let found = expected.map((it) => 0);
+
+ // Add some places to the places database
+ yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://127.0.0.1:8080", null, null));
+ yield PlacesTestUtils.addVisits(Services.io.newURI("https://localhost:8080", null, null));
+
+ // Force initialization of the nsPermissionManager
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ let isExpected = false;
+
+ expected.forEach((it, i) => {
+ if (permission.principal.origin == it[0] &&
+ permission.type == it[1] &&
+ permission.capability == it[2] &&
+ permission.expireType == it[3] &&
+ permission.expireTime == it[4]) {
+ isExpected = true;
+ found[i]++;
+ }
+ });
+
+ do_check_true(isExpected,
+ "Permission " + (isExpected ? "should" : "shouldn't") +
+ " be in permission database: " +
+ permission.principal.origin + ", " +
+ permission.type + ", " +
+ permission.capability + ", " +
+ permission.expireType + ", " +
+ permission.expireTime);
+ }
+
+ found.forEach((count, i) => {
+ do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+ });
+
+ // Check to make sure that all of the tables which we care about are present
+ {
+ let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+ do_check_true(db.tableExists("moz_perms"));
+ do_check_true(db.tableExists("moz_hosts"));
+ do_check_false(db.tableExists("moz_hosts_is_backup"));
+ do_check_false(db.tableExists("moz_perms_v6"));
+
+ // The moz_hosts table should still exist but be empty
+ let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+ mozHostsCount.executeStep();
+ do_check_eq(mozHostsCount.getInt64(0), 0);
+
+ // Check that there are the right number of values in the permissions database
+ let mozPermsCount = db.createStatement("SELECT count(*) FROM moz_perms");
+ mozPermsCount.executeStep();
+ do_check_eq(mozPermsCount.getInt64(0), expected.length);
+
+ db.close();
+ }
+});
diff --git a/extensions/cookie/test/unit/test_permmanager_notifications.js b/extensions/cookie/test/unit/test_permmanager_notifications.js
new file mode 100644
index 000000000..5708fe8ec
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_notifications.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the permissionmanager 'added', 'changed', 'deleted', and 'cleared'
+// notifications behave as expected.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function continue_test()
+{
+ do_run_generator(test_generator);
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ let pm = Services.perms;
+ let now = Number(Date.now());
+ let permType = "test/expiration-perm";
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let uri = NetUtil.newURI("http://example.com");
+ let principal = ssm.createCodebasePrincipal(uri, {});
+
+ let observer = new permission_observer(test_generator, now, permType);
+ Services.obs.addObserver(observer, "perm-changed", false);
+
+ // Add a permission, to test the 'add' notification. Note that we use
+ // do_execute_soon() so that we can use our generator to continue the test
+ // where we left off.
+ do_execute_soon(function() {
+ pm.addFromPrincipal(principal, permType, pm.ALLOW_ACTION, pm.EXPIRE_TIME, now + 100000);
+ });
+ yield;
+
+ // Alter a permission, to test the 'changed' notification.
+ do_execute_soon(function() {
+ pm.addFromPrincipal(principal, permType, pm.ALLOW_ACTION, pm.EXPIRE_TIME, now + 200000);
+ });
+ yield;
+
+ // Remove a permission, to test the 'deleted' notification.
+ do_execute_soon(function() {
+ pm.removeFromPrincipal(principal, permType);
+ });
+ yield;
+
+ // Clear permissions, to test the 'cleared' notification.
+ do_execute_soon(function() {
+ pm.removeAll();
+ });
+ yield;
+
+ Services.obs.removeObserver(observer, "perm-changed");
+ do_check_eq(observer.adds, 1);
+ do_check_eq(observer.changes, 1);
+ do_check_eq(observer.deletes, 1);
+ do_check_true(observer.cleared);
+
+ do_finish_generator_test(test_generator);
+}
+
+function permission_observer(generator, now, type) {
+ // Set up our observer object.
+ this.generator = generator;
+ this.pm = Services.perms;
+ this.now = now;
+ this.type = type;
+ this.adds = 0;
+ this.changes = 0;
+ this.deletes = 0;
+ this.cleared = false;
+}
+
+permission_observer.prototype = {
+ observe: function(subject, topic, data) {
+ do_check_eq(topic, "perm-changed");
+
+ // "deleted" means a permission was deleted. aPermission is the deleted permission.
+ // "added" means a permission was added. aPermission is the added permission.
+ // "changed" means a permission was altered. aPermission is the new permission.
+ // "cleared" means the entire permission list was cleared. aPermission is null.
+ if (data == "added") {
+ var perm = subject.QueryInterface(Ci.nsIPermission);
+ this.adds++;
+ switch (this.adds) {
+ case 1:
+ do_check_eq(this.type, perm.type);
+ do_check_eq(this.pm.EXPIRE_TIME, perm.expireType);
+ do_check_eq(this.now + 100000, perm.expireTime);
+ break;
+ default:
+ do_throw("too many add notifications posted.");
+ }
+
+ } else if (data == "changed") {
+ let perm = subject.QueryInterface(Ci.nsIPermission);
+ this.changes++;
+ switch (this.changes) {
+ case 1:
+ do_check_eq(this.type, perm.type);
+ do_check_eq(this.pm.EXPIRE_TIME, perm.expireType);
+ do_check_eq(this.now + 200000, perm.expireTime);
+ break;
+ default:
+ do_throw("too many change notifications posted.");
+ }
+
+ } else if (data == "deleted") {
+ var perm = subject.QueryInterface(Ci.nsIPermission);
+ this.deletes++;
+ switch (this.deletes) {
+ case 1:
+ do_check_eq(this.type, perm.type);
+ break;
+ default:
+ do_throw("too many delete notifications posted.");
+ }
+
+ } else if (data == "cleared") {
+ // only clear once: at the end
+ do_check_false(this.cleared);
+ do_check_eq(do_count_enumerator(Services.perms.enumerator), 0);
+ this.cleared = true;
+
+ } else {
+ do_throw("unexpected data '" + data + "'!");
+ }
+
+ // Continue the test.
+ do_run_generator(this.generator);
+ },
+};
+
diff --git a/extensions/cookie/test/unit/test_permmanager_removeall.js b/extensions/cookie/test/unit/test_permmanager_removeall.js
new file mode 100644
index 000000000..54294a915
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removeall.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ // setup a profile directory
+ var dir = do_get_profile();
+
+ // initialize the permission manager service
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ // get the db file
+ var file = dir.clone();
+ file.append("permissions.sqlite");
+ do_check_true(file.exists());
+
+ // corrupt the file
+ var ostream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ ostream.init(file, 0x02, 0o666, 0);
+ var conv = Cc["@mozilla.org/intl/converter-output-stream;1"].
+ createInstance(Ci.nsIConverterOutputStream);
+ conv.init(ostream, "UTF-8", 0, 0);
+ for (var i = 0; i < file.fileSize; ++i)
+ conv.writeString("a");
+ conv.close();
+
+ // prepare an empty hostperm.1 file so that it can be used for importing
+ var hostperm = dir.clone();
+ hostperm.append("hostperm.1");
+ ostream.init(hostperm, 0x02 | 0x08, 0o666, 0);
+ ostream.close();
+
+ // remove all should not throw
+ pm.removeAll();
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_removeforapp.js b/extensions/cookie/test/unit/test_permmanager_removeforapp.js
new file mode 100644
index 000000000..152409de2
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removeforapp.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ // initialize the permission manager service
+ let ssm = Services.scriptSecurityManager;
+ let pm = Services.perms;
+
+ function mkPrin(uri, appId, inIsolatedMozBrowser) {
+ return ssm.createCodebasePrincipal(Services.io.newURI(uri, null, null),
+ {appId: appId, inIsolatedMozBrowser: inIsolatedMozBrowser});
+ }
+
+ function checkPerms(perms) {
+ perms.forEach((perm) => {
+ // Look up the expected permission
+ do_check_eq(pm.getPermissionObject(mkPrin(perm[0], perm[1], perm[2]),
+ perm[3], true).capability,
+ perm[4], "Permission is expected in the permission database");
+ });
+
+ // Count the entries
+ let count = 0;
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) { enumerator.getNext(); count++; }
+
+ do_check_eq(count, perms.length, "There should be the right number of permissions in the DB");
+ }
+
+ checkPerms([]);
+
+ let permissions = [
+ ['http://google.com', 1001, false, 'a', 1],
+ ['http://google.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'a', 1],
+
+ ['http://google.com', 1001, true, 'a', 1],
+ ['http://google.com', 1001, true, 'b', 1],
+ ['http://mozilla.com', 1001, true, 'b', 1],
+ ['http://mozilla.com', 1001, true, 'a', 1],
+
+ ['http://google.com', 1011, false, 'a', 1],
+ ['http://google.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'a', 1],
+ ];
+
+ permissions.forEach((perm) => {
+ pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]);
+ });
+
+ checkPerms(permissions);
+
+ let remove_false_perms = [
+ ['http://google.com', 1011, false, 'a', 1],
+ ['http://google.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'a', 1],
+ ];
+
+ let attrs = { appId: 1001 };
+ pm.removePermissionsWithAttributes(JSON.stringify(attrs));
+ checkPerms(remove_false_perms);
+
+ let restore = [
+ ['http://google.com', 1001, false, 'a', 1],
+ ['http://google.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'a', 1],
+
+ ['http://google.com', 1001, true, 'a', 1],
+ ['http://google.com', 1001, true, 'b', 1],
+ ['http://mozilla.com', 1001, true, 'b', 1],
+ ['http://mozilla.com', 1001, true, 'a', 1],
+ ];
+
+ restore.forEach((perm) => {
+ pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]);
+ });
+ checkPerms(permissions);
+
+ let remove_true_perms = [
+ ['http://google.com', 1001, false, 'a', 1],
+ ['http://google.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'b', 1],
+ ['http://mozilla.com', 1001, false, 'a', 1],
+
+ ['http://google.com', 1011, false, 'a', 1],
+ ['http://google.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'b', 1],
+ ['http://mozilla.com', 1011, false, 'a', 1],
+ ];
+
+ attrs = { appId: 1001,
+ inIsolatedMozBrowser: true };
+ pm.removePermissionsWithAttributes(JSON.stringify(attrs));
+ checkPerms(remove_true_perms);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_removepermission.js b/extensions/cookie/test/unit/test_permmanager_removepermission.js
new file mode 100644
index 000000000..e8f5817e2
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removepermission.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+ // initialize the permission manager service
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ do_check_eq(perm_count(), 0);
+
+ // add some permissions
+ let uri = NetUtil.newURI("http://amazon.com:8080/foobarbaz", null, null);
+ let uri2 = NetUtil.newURI("http://google.com:2048/quxx", null, null);
+
+ pm.add(uri, "apple", 0);
+ pm.add(uri, "apple", 3);
+ pm.add(uri, "pear", 3);
+ pm.add(uri, "pear", 1);
+ pm.add(uri, "cucumber", 1);
+ pm.add(uri, "cucumber", 1);
+ pm.add(uri, "cucumber", 1);
+
+ pm.add(uri2, "apple", 2);
+ pm.add(uri2, "pear", 0);
+ pm.add(uri2, "pear", 2);
+
+ // Make sure that removePermission doesn't remove more than one permission each time
+ do_check_eq(perm_count(), 5);
+
+ remove_one_by_type("apple");
+ do_check_eq(perm_count(), 4);
+
+ remove_one_by_type("apple");
+ do_check_eq(perm_count(), 3);
+
+ remove_one_by_type("pear");
+ do_check_eq(perm_count(), 2);
+
+ remove_one_by_type("cucumber");
+ do_check_eq(perm_count(), 1);
+
+ remove_one_by_type("pear");
+ do_check_eq(perm_count(), 0);
+
+
+ function perm_count() {
+ let enumerator = pm.enumerator;
+ let count = 0;
+ while (enumerator.hasMoreElements()) {
+ count++;
+ enumerator.getNext();
+ }
+
+ return count;
+ }
+
+ function remove_one_by_type(type) {
+ let enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let it = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ if (it.type == type) {
+ pm.removePermission(it);
+ break;
+ }
+ }
+ }
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_removesince.js b/extensions/cookie/test/unit/test_permmanager_removesince.js
new file mode 100644
index 000000000..719f7f6f6
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_removesince.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that removing permissions since a specified time behaves as expected.
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function continue_test()
+{
+ do_run_generator(test_generator);
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ let pm = Services.perms;
+
+ // to help with testing edge-cases, we will arrange for .removeAllSince to
+ // remove *all* permissions from one principal and one permission from another.
+ let permURI1 = NetUtil.newURI("http://example.com");
+ let principal1 = Services.scriptSecurityManager.createCodebasePrincipal(permURI1, {});
+
+ let permURI2 = NetUtil.newURI("http://example.org");
+ let principal2 = Services.scriptSecurityManager.createCodebasePrincipal(permURI2, {});
+
+ // add a permission now - this isn't going to be removed.
+ pm.addFromPrincipal(principal1, "test/remove-since", 1);
+
+ // sleep briefly, then record the time - we'll remove all since then.
+ do_timeout(20, continue_test);
+ yield;
+
+ let since = Number(Date.now());
+
+ // *sob* - on Windows at least, the now recorded by nsPermissionManager.cpp
+ // might be a couple of ms *earlier* than what JS sees. So another sleep
+ // to ensure our |since| is greater than the time of the permissions we
+ // are now adding. Sadly this means we'll never be able to test when since
+ // exactly equals the modTime, but there you go...
+ do_timeout(20, continue_test);
+ yield;
+
+ // add another item - this second one should get nuked.
+ pm.addFromPrincipal(principal1, "test/remove-since-2", 1);
+
+ // add 2 items for the second principal - both will be removed.
+ pm.addFromPrincipal(principal2, "test/remove-since", 1);
+ pm.addFromPrincipal(principal2, "test/remove-since-2", 1);
+
+ // do the removal.
+ pm.removeAllSince(since);
+
+ // principal1 - the first one should remain.
+ do_check_eq(1, pm.testPermissionFromPrincipal(principal1, "test/remove-since"));
+ // but the second should have been removed.
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal1, "test/remove-since-2"));
+
+ // principal2 - both should have been removed.
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal2, "test/remove-since"));
+ do_check_eq(0, pm.testPermissionFromPrincipal(principal2, "test/remove-since-2"));
+
+ do_finish_generator_test(test_generator);
+}
diff --git a/extensions/cookie/test/unit/test_permmanager_subdomains.js b/extensions/cookie/test/unit/test_permmanager_subdomains.js
new file mode 100644
index 000000000..4e78e4d46
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_subdomains.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function getPrincipalFromURI(aURI) {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let uri = NetUtil.newURI(aURI);
+ return ssm.createCodebasePrincipal(uri, {});
+}
+
+function run_test() {
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+
+ // Adds a permission to a sub-domain. Checks if it is working.
+ let sub1Principal = getPrincipalFromURI("http://sub1.example.com");
+ pm.addFromPrincipal(sub1Principal, "test/subdomains", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(sub1Principal, "test/subdomains"), pm.ALLOW_ACTION);
+
+ // A sub-sub-domain should get the permission.
+ let subsubPrincipal = getPrincipalFromURI("http://sub.sub1.example.com");
+ do_check_eq(pm.testPermissionFromPrincipal(subsubPrincipal, "test/subdomains"), pm.ALLOW_ACTION);
+
+ // Another sub-domain shouldn't get the permission.
+ let sub2Principal = getPrincipalFromURI("http://sub2.example.com");
+ do_check_eq(pm.testPermissionFromPrincipal(sub2Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+
+ // Remove current permissions.
+ pm.removeFromPrincipal(sub1Principal, "test/subdomains");
+ do_check_eq(pm.testPermissionFromPrincipal(sub1Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+
+ // Adding the permission to the main domain. Checks if it is working.
+ let mainPrincipal = getPrincipalFromURI("http://example.com");
+ pm.addFromPrincipal(mainPrincipal, "test/subdomains", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(mainPrincipal, "test/subdomains"), pm.ALLOW_ACTION);
+
+ // All sub-domains should have the permission now.
+ do_check_eq(pm.testPermissionFromPrincipal(sub1Principal, "test/subdomains"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(sub2Principal, "test/subdomains"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(subsubPrincipal, "test/subdomains"), pm.ALLOW_ACTION);
+
+ // Remove current permissions.
+ pm.removeFromPrincipal(mainPrincipal, "test/subdomains");
+ do_check_eq(pm.testPermissionFromPrincipal(mainPrincipal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(sub1Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(sub2Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(subsubPrincipal, "test/subdomains"), pm.UNKNOWN_ACTION);
+
+ // A sanity check that the previous implementation wasn't passing...
+ let crazyPrincipal = getPrincipalFromURI("http://com");
+ pm.addFromPrincipal(crazyPrincipal, "test/subdomains", pm.ALLOW_ACTION, 0, 0);
+ do_check_eq(pm.testPermissionFromPrincipal(crazyPrincipal, "test/subdomains"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(mainPrincipal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(sub1Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(sub2Principal, "test/subdomains"), pm.UNKNOWN_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(subsubPrincipal, "test/subdomains"), pm.UNKNOWN_ACTION);
+}
diff --git a/extensions/cookie/test/unit/test_schema_2_migration.js b/extensions/cookie/test/unit/test_schema_2_migration.js
new file mode 100644
index 000000000..7dc5e823c
--- /dev/null
+++ b/extensions/cookie/test/unit/test_schema_2_migration.js
@@ -0,0 +1,207 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 2 (Gecko 1.9.3) to the current
+// version, presently 4 (Gecko 2.0).
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Create a schema 2 database.
+ let schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "foo.com", "/",
+ futureExpiry, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "bar.com", "/",
+ pastExpiry, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ futureExpiry + i, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ pastExpiry - i, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ futureExpiry - i, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ pastExpiry + i, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // Close it.
+ schema2db.close();
+ schema2db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ // 1) All unexpired, unique cookies exist.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ let enumerator = Services.cookiemgr.getCookiesFromHost("baz.com", {});
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ do_check_eq(cookie.expiry, futureExpiry + 44);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database so we can execute some more schema 2 statements on it.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+
+ // Populate it with more cookies.
+ for (let i = 60; i < 80; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "foo.com", "/",
+ futureExpiry, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+ for (let i = 80; i < 100; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "cat.com", "/",
+ futureExpiry, now, now + i, false, false, false);
+
+ schema2db.insertCookie(cookie);
+ }
+
+ // Attempt to add a cookie with the same (name, host, path) values as another
+ // cookie. This should succeed since we have a REPLACE clause for conflict on
+ // the unique index.
+ cookie = new Cookie("oh", "hai", "baz.com", "/",
+ futureExpiry, now, now + 100, false, false, false);
+
+ schema2db.insertCookie(cookie);
+
+ // Check that there is, indeed, a singular cookie for baz.com.
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "baz.com"), 1);
+
+ // Close it.
+ schema2db.close();
+ schema2db = null;
+
+ // Back up the database, so we can test both asynchronous and synchronous
+ // loading separately.
+ let file = do_get_cookie_file(profile);
+ let copy = profile.clone();
+ copy.append("cookies.sqlite.copy");
+ file.copyTo(null, copy.leafName);
+
+ // Load the database asynchronously, forcing a purge of the newly-added
+ // cookies. (Their baseDomain column will be NULL.)
+ do_load_profile(test_generator);
+ yield;
+
+ // Test the expected set of cookies.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database and prove that they were deleted.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ do_check_eq(do_count_cookies_in_db(schema2db.db), 40);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
+ schema2db.close();
+
+ // Copy the database back.
+ file.remove(false);
+ copy.copyTo(null, file.leafName);
+
+ // Load the database host-at-a-time.
+ do_load_profile();
+
+ // Test the expected set of cookies.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database and prove that they were deleted.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ do_check_eq(do_count_cookies_in_db(schema2db.db), 40);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
+ schema2db.close();
+
+ // Copy the database back.
+ file.remove(false);
+ copy.copyTo(null, file.leafName);
+
+ // Load the database synchronously, in its entirety.
+ do_load_profile();
+ do_check_eq(do_count_cookies(), 40);
+
+ // Test the expected set of cookies.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("baz.com"), 0);
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("cat.com"), 0);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database and prove that they were deleted.
+ schema2db = new CookieDatabaseConnection(do_get_cookie_file(profile), 2);
+ do_check_eq(do_count_cookies_in_db(schema2db.db), 40);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "foo.com"), 20);
+ do_check_eq(do_count_cookies_in_db(schema2db.db, "bar.com"), 20);
+ schema2db.close();
+
+ finish_test();
+}
+
diff --git a/extensions/cookie/test/unit/test_schema_3_migration.js b/extensions/cookie/test/unit/test_schema_3_migration.js
new file mode 100644
index 000000000..40a23f7e4
--- /dev/null
+++ b/extensions/cookie/test/unit/test_schema_3_migration.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test cookie database migration from version 3 (prerelease Gecko 2.0) to the
+// current version, presently 4 (Gecko 2.0).
+
+var test_generator = do_run_test();
+
+function run_test() {
+ do_test_pending();
+ test_generator.next();
+}
+
+function finish_test() {
+ do_execute_soon(function() {
+ test_generator.close();
+ do_test_finished();
+ });
+}
+
+function do_run_test() {
+ // Set up a profile.
+ let profile = do_get_profile();
+
+ // Create a schema 3 database.
+ let schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
+
+ let now = Date.now() * 1000;
+ let futureExpiry = Math.round(now / 1e6 + 1000);
+ let pastExpiry = Math.round(now / 1e6 - 1000);
+
+ // Populate it, with:
+ // 1) Unexpired, unique cookies.
+ for (let i = 0; i < 20; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "foo.com", "/",
+ futureExpiry, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // 2) Expired, unique cookies.
+ for (let i = 20; i < 40; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "bar.com", "/",
+ pastExpiry, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // 3) Many copies of the same cookie, some of which have expired and
+ // some of which have not.
+ for (let i = 40; i < 45; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ futureExpiry + i, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 45; i < 50; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ pastExpiry - i, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 50; i < 55; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ futureExpiry - i, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+ for (let i = 55; i < 60; ++i) {
+ let cookie = new Cookie("oh", "hai", "baz.com", "/",
+ pastExpiry + i, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // Close it.
+ schema3db.close();
+ schema3db = null;
+
+ // Load the database, forcing migration to the current schema version. Then
+ // test the expected set of cookies:
+ // 1) All unexpired, unique cookies exist.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("foo.com"), 20);
+
+ // 2) All expired, unique cookies exist.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("bar.com"), 20);
+
+ // 3) Only one cookie remains, and it's the one with the highest expiration
+ // time.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("baz.com"), 1);
+ let enumerator = Services.cookiemgr.getCookiesFromHost("baz.com", {});
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ do_check_eq(cookie.expiry, futureExpiry + 44);
+
+ do_close_profile(test_generator);
+ yield;
+
+ // Open the database so we can execute some more schema 3 statements on it.
+ schema3db = new CookieDatabaseConnection(do_get_cookie_file(profile), 3);
+
+ // Populate it with more cookies.
+ for (let i = 60; i < 80; ++i) {
+ let cookie = new Cookie("oh" + i, "hai", "cat.com", "/",
+ futureExpiry, now, now + i, false, false, false);
+
+ schema3db.insertCookie(cookie);
+ }
+
+ // Close it.
+ schema3db.close();
+ schema3db = null;
+
+ // Load the database. The cookies added immediately prior will have a NULL
+ // creationTime column.
+ do_load_profile();
+
+ // Test the expected set of cookies.
+ do_check_eq(Services.cookiemgr.countCookiesFromHost("cat.com"), 20);
+ enumerator = Services.cookiemgr.getCookiesFromHost("cat.com", {});
+ cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ do_check_eq(cookie.creationTime, 0);
+
+ finish_test();
+}
+
diff --git a/extensions/cookie/test/unit/xpcshell.ini b/extensions/cookie/test/unit/xpcshell.ini
new file mode 100644
index 000000000..e20d0142c
--- /dev/null
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -0,0 +1,48 @@
+[DEFAULT]
+head = head_cookies.js
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ cookieprompt.js
+ cookieprompt.manifest
+
+[test_bug526789.js]
+[test_bug650522.js]
+[test_bug667087.js]
+[test_cookies_async_failure.js]
+[test_cookies_persistence.js]
+skip-if = true # Bug 863738
+[test_cookies_privatebrowsing.js]
+[test_cookies_profile_close.js]
+[test_cookies_read.js]
+[test_cookies_sync_failure.js]
+[test_cookies_thirdparty.js]
+[test_cookies_thirdparty_session.js]
+[test_domain_eviction.js]
+[test_eviction.js]
+[test_permmanager_defaults.js]
+[test_permmanager_expiration.js]
+[test_permmanager_getAllForURI.js]
+[test_permmanager_getPermissionObject.js]
+[test_permmanager_notifications.js]
+[test_permmanager_removeall.js]
+[test_permmanager_removesince.js]
+[test_permmanager_removeforapp.js]
+[test_permmanager_load_invalid_entries.js]
+skip-if = debug == true
+[test_permmanager_idn.js]
+[test_permmanager_subdomains.js]
+[test_permmanager_local_files.js]
+[test_permmanager_cleardata.js]
+[test_schema_2_migration.js]
+[test_schema_3_migration.js]
+[test_permmanager_removepermission.js]
+[test_permmanager_matchesuri.js]
+[test_permmanager_matches.js]
+[test_permmanager_migrate_4-7.js]
+[test_permmanager_migrate_5-7a.js]
+[test_permmanager_migrate_5-7b.js]
+[test_permmanager_migrate_6-7a.js]
+[test_permmanager_migrate_6-7b.js]
+[test_permmanager_migrate_4-7_no_history.js]
+[test_permmanager_migrate_7-8.js]
diff --git a/extensions/cookie/test/unit_ipc/test_child.js b/extensions/cookie/test/unit_ipc/test_child.js
new file mode 100644
index 000000000..40a48d8b8
--- /dev/null
+++ b/extensions/cookie/test/unit_ipc/test_child.js
@@ -0,0 +1,59 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+function isParentProcess() {
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ return (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
+}
+
+function getPrincipalForURI(aURI) {
+ var uri = gIoService.newURI(aURI, null, null);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ return ssm.createCodebasePrincipal(uri, {});
+}
+
+function run_test() {
+ if (!isParentProcess()) {
+ const Ci = Components.interfaces;
+ const Cc = Components.classes;
+
+ var mM = Cc["@mozilla.org/childprocessmessagemanager;1"].
+ getService(Ci.nsISyncMessageSender);
+
+ var messageListener = {
+ receiveMessage: function(aMessage) {
+ switch(aMessage.name) {
+ case "TESTING:Stage2A":
+ // Permissions created after the child is present
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.org"), "cookie1"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.com"), "cookie2"), pm.DENY_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.net"), "cookie3"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://firefox.org"), "cookie1"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://firefox.com"), "cookie2"), pm.DENY_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://firefox.net"), "cookie3"), pm.ALLOW_ACTION);
+
+ mM.sendAsyncMessage("TESTING:Stage3");
+ break;
+
+ }
+ return true;
+ },
+ };
+
+ mM.addMessageListener("TESTING:Stage2A", messageListener);
+
+ // Permissions created before the child is present
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.org"), "cookie1"), pm.ALLOW_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.com"), "cookie2"), pm.DENY_ACTION);
+ do_check_eq(pm.testPermissionFromPrincipal(getPrincipalForURI("http://mozilla.net"), "cookie3"), pm.ALLOW_ACTION);
+
+ mM.sendAsyncMessage("TESTING:Stage2");
+ }
+}
+
diff --git a/extensions/cookie/test/unit_ipc/test_parent.js b/extensions/cookie/test/unit_ipc/test_parent.js
new file mode 100644
index 000000000..5423dd594
--- /dev/null
+++ b/extensions/cookie/test/unit_ipc/test_parent.js
@@ -0,0 +1,59 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+
+function isParentProcess() {
+ let appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ return (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT);
+}
+
+function getPrincipalForURI(aURI) {
+ var uri = gIoService.newURI(aURI, null, null);
+ var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ return ssm.createCodebasePrincipal(uri, {});
+}
+
+function run_test() {
+ if (isParentProcess()) {
+ var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+
+ // Permissions created before the child is present
+ pm.addFromPrincipal(getPrincipalForURI("http://mozilla.org"), "cookie1", pm.ALLOW_ACTION, pm.EXPIRE_NEVER, 0);
+ pm.addFromPrincipal(getPrincipalForURI("http://mozilla.com"), "cookie2", pm.DENY_ACTION, pm.EXPIRE_SESSION, 0);
+ pm.addFromPrincipal(getPrincipalForURI("http://mozilla.net"), "cookie3", pm.ALLOW_ACTION, pm.EXPIRE_TIME, Date.now() + 1000*60*60*24);
+
+ var mM = Cc["@mozilla.org/parentprocessmessagemanager;1"].
+ getService(Ci.nsIMessageBroadcaster);
+
+ var messageListener = {
+ receiveMessage: function(aMessage) {
+ switch(aMessage.name) {
+ case "TESTING:Stage2":
+ // Permissions created after the child is present
+ pm.addFromPrincipal(getPrincipalForURI("http://firefox.org"), "cookie1", pm.ALLOW_ACTION, pm.EXPIRE_NEVER, 0);
+ pm.addFromPrincipal(getPrincipalForURI("http://firefox.com"), "cookie2", pm.DENY_ACTION, pm.EXPIRE_SESSION, 0);
+ pm.addFromPrincipal(getPrincipalForURI("http://firefox.net"), "cookie3", pm.ALLOW_ACTION, pm.EXPIRE_TIME, Date.now() + 1000*60*60*24);
+ mM.broadcastAsyncMessage("TESTING:Stage2A");
+ break;
+
+ case "TESTING:Stage3":
+ do_test_finished();
+ break;
+ }
+ return true;
+ },
+ };
+
+ mM.addMessageListener("TESTING:Stage2", messageListener);
+ mM.addMessageListener("TESTING:Stage3", messageListener);
+
+ do_test_pending();
+ do_load_child_test_harness();
+ run_test_in_child("test_child.js");
+ }
+}
+
diff --git a/extensions/cookie/test/unit_ipc/xpcshell.ini b/extensions/cookie/test/unit_ipc/xpcshell.ini
new file mode 100644
index 000000000..5640ea0e3
--- /dev/null
+++ b/extensions/cookie/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+tail =
+skip-if = toolkit == 'android'
+
+[test_child.js]
+[test_parent.js]