From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- extensions/cookie/moz.build | 30 + extensions/cookie/nsCookieModule.cpp | 51 + extensions/cookie/nsCookiePermission.cpp | 273 ++ extensions/cookie/nsCookiePermission.h | 48 + extensions/cookie/nsCookiePromptService.cpp | 101 + extensions/cookie/nsCookiePromptService.h | 29 + extensions/cookie/nsICookieAcceptDialog.idl | 21 + extensions/cookie/nsICookiePromptService.idl | 45 + extensions/cookie/nsPermission.cpp | 201 ++ extensions/cookie/nsPermission.h | 43 + extensions/cookie/nsPermissionManager.cpp | 2918 ++++++++++++++++++++ extensions/cookie/nsPermissionManager.h | 295 ++ extensions/cookie/nsPopupWindowManager.cpp | 111 + extensions/cookie/nsPopupWindowManager.h | 39 + extensions/cookie/test/beltzner.jpg | Bin 0 -> 9995 bytes extensions/cookie/test/beltzner.jpg^headers^ | 3 + extensions/cookie/test/browser.ini | 3 + extensions/cookie/test/browser_test_favicon.js | 28 + extensions/cookie/test/damonbowling.jpg | Bin 0 -> 44008 bytes extensions/cookie/test/damonbowling.jpg^headers^ | 2 + extensions/cookie/test/file_chromecommon.js | 15 + .../cookie/test/file_domain_hierarchy_inner.html | 14 + .../test/file_domain_hierarchy_inner_inner.html | 14 + .../file_domain_hierarchy_inner_inner_inner.html | 14 + extensions/cookie/test/file_domain_inner.html | 14 + .../cookie/test/file_domain_inner_inner.html | 14 + extensions/cookie/test/file_image_inner.html | 15 + extensions/cookie/test/file_image_inner_inner.html | 20 + extensions/cookie/test/file_loadflags_inner.html | 17 + extensions/cookie/test/file_localhost_inner.html | 14 + extensions/cookie/test/file_loopback_inner.html | 14 + extensions/cookie/test/file_subdomain_inner.html | 14 + extensions/cookie/test/file_testcommon.js | 70 + extensions/cookie/test/file_testloadflags.js | 104 + .../cookie/test/file_testloadflags_chromescript.js | 112 + extensions/cookie/test/image1.png | Bin 0 -> 821 bytes extensions/cookie/test/image1.png^headers^ | 3 + extensions/cookie/test/image2.png | Bin 0 -> 821 bytes extensions/cookie/test/image2.png^headers^ | 3 + extensions/cookie/test/mochitest.ini | 41 + extensions/cookie/test/moz.build | 15 + extensions/cookie/test/test1.css | 2 + extensions/cookie/test/test1.css^headers^ | 3 + extensions/cookie/test/test2.css | 2 + extensions/cookie/test/test2.css^headers^ | 3 + .../test/test_different_domain_in_hierarchy.html | 15 + extensions/cookie/test/test_differentdomain.html | 15 + extensions/cookie/test/test_image.html | 14 + extensions/cookie/test/test_loadflags.html | 21 + extensions/cookie/test/test_same_base_domain.html | 15 + .../cookie/test/test_same_base_domain_2.html | 15 + .../cookie/test/test_same_base_domain_3.html | 15 + .../cookie/test/test_same_base_domain_4.html | 15 + .../cookie/test/test_same_base_domain_5.html | 15 + .../cookie/test/test_same_base_domain_6.html | 15 + extensions/cookie/test/test_samedomain.html | 15 + extensions/cookie/test/unit/cookieprompt.js | 22 + extensions/cookie/test/unit/cookieprompt.manifest | 2 + extensions/cookie/test/unit/head_cookies.js | 570 ++++ extensions/cookie/test/unit/test_bug526789.js | 248 ++ extensions/cookie/test/unit/test_bug650522.js | 16 + extensions/cookie/test/unit/test_bug667087.js | 16 + .../cookie/test/unit/test_cookies_async_failure.js | 600 ++++ .../cookie/test/unit/test_cookies_persistence.js | 78 + .../test/unit/test_cookies_privatebrowsing.js | 115 + .../cookie/test/unit/test_cookies_profile_close.js | 92 + extensions/cookie/test/unit/test_cookies_read.js | 122 + .../cookie/test/unit/test_cookies_sync_failure.js | 286 ++ .../cookie/test/unit/test_cookies_thirdparty.js | 147 + .../test/unit/test_cookies_thirdparty_session.js | 69 + .../cookie/test/unit/test_domain_eviction.js | 153 + extensions/cookie/test/unit/test_eviction.js | 249 ++ .../cookie/test/unit/test_permmanager_cleardata.js | 68 + .../cookie/test/unit/test_permmanager_defaults.js | 295 ++ .../test/unit/test_permmanager_expiration.js | 82 + .../test/unit/test_permmanager_getAllForURI.js | 78 + .../unit/test_permmanager_getPermissionObject.js | 95 + .../cookie/test/unit/test_permmanager_idn.js | 49 + .../unit/test_permmanager_load_invalid_entries.js | 142 + .../test/unit/test_permmanager_local_files.js | 43 + .../cookie/test/unit/test_permmanager_matches.js | 183 ++ .../test/unit/test_permmanager_matchesuri.js | 150 + .../test/unit/test_permmanager_migrate_4-7.js | 207 ++ .../test_permmanager_migrate_4-7_no_history.js | 226 ++ .../test/unit/test_permmanager_migrate_5-7a.js | 284 ++ .../test/unit/test_permmanager_migrate_5-7b.js | 168 ++ .../test/unit/test_permmanager_migrate_6-7a.js | 284 ++ .../test/unit/test_permmanager_migrate_6-7b.js | 162 ++ .../test/unit/test_permmanager_migrate_7-8.js | 246 ++ .../test/unit/test_permmanager_notifications.js | 140 + .../cookie/test/unit/test_permmanager_removeall.js | 36 + .../test/unit/test_permmanager_removeforapp.js | 99 + .../test/unit/test_permmanager_removepermission.js | 67 + .../test/unit/test_permmanager_removesince.js | 69 + .../test/unit/test_permmanager_subdomains.js | 57 + .../cookie/test/unit/test_schema_2_migration.js | 207 ++ .../cookie/test/unit/test_schema_3_migration.js | 125 + extensions/cookie/test/unit/xpcshell.ini | 48 + extensions/cookie/test/unit_ipc/test_child.js | 59 + extensions/cookie/test/unit_ipc/test_parent.js | 59 + extensions/cookie/test/unit_ipc/xpcshell.ini | 7 + 101 files changed, 11468 insertions(+) create mode 100644 extensions/cookie/moz.build create mode 100644 extensions/cookie/nsCookieModule.cpp create mode 100644 extensions/cookie/nsCookiePermission.cpp create mode 100644 extensions/cookie/nsCookiePermission.h create mode 100644 extensions/cookie/nsCookiePromptService.cpp create mode 100644 extensions/cookie/nsCookiePromptService.h create mode 100644 extensions/cookie/nsICookieAcceptDialog.idl create mode 100644 extensions/cookie/nsICookiePromptService.idl create mode 100644 extensions/cookie/nsPermission.cpp create mode 100644 extensions/cookie/nsPermission.h create mode 100644 extensions/cookie/nsPermissionManager.cpp create mode 100644 extensions/cookie/nsPermissionManager.h create mode 100644 extensions/cookie/nsPopupWindowManager.cpp create mode 100644 extensions/cookie/nsPopupWindowManager.h create mode 100644 extensions/cookie/test/beltzner.jpg create mode 100644 extensions/cookie/test/beltzner.jpg^headers^ create mode 100644 extensions/cookie/test/browser.ini create mode 100644 extensions/cookie/test/browser_test_favicon.js create mode 100644 extensions/cookie/test/damonbowling.jpg create mode 100644 extensions/cookie/test/damonbowling.jpg^headers^ create mode 100644 extensions/cookie/test/file_chromecommon.js create mode 100644 extensions/cookie/test/file_domain_hierarchy_inner.html create mode 100644 extensions/cookie/test/file_domain_hierarchy_inner_inner.html create mode 100644 extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html create mode 100644 extensions/cookie/test/file_domain_inner.html create mode 100644 extensions/cookie/test/file_domain_inner_inner.html create mode 100644 extensions/cookie/test/file_image_inner.html create mode 100644 extensions/cookie/test/file_image_inner_inner.html create mode 100644 extensions/cookie/test/file_loadflags_inner.html create mode 100644 extensions/cookie/test/file_localhost_inner.html create mode 100644 extensions/cookie/test/file_loopback_inner.html create mode 100644 extensions/cookie/test/file_subdomain_inner.html create mode 100644 extensions/cookie/test/file_testcommon.js create mode 100644 extensions/cookie/test/file_testloadflags.js create mode 100644 extensions/cookie/test/file_testloadflags_chromescript.js create mode 100644 extensions/cookie/test/image1.png create mode 100644 extensions/cookie/test/image1.png^headers^ create mode 100644 extensions/cookie/test/image2.png create mode 100644 extensions/cookie/test/image2.png^headers^ create mode 100644 extensions/cookie/test/mochitest.ini create mode 100644 extensions/cookie/test/moz.build create mode 100644 extensions/cookie/test/test1.css create mode 100644 extensions/cookie/test/test1.css^headers^ create mode 100644 extensions/cookie/test/test2.css create mode 100644 extensions/cookie/test/test2.css^headers^ create mode 100644 extensions/cookie/test/test_different_domain_in_hierarchy.html create mode 100644 extensions/cookie/test/test_differentdomain.html create mode 100644 extensions/cookie/test/test_image.html create mode 100644 extensions/cookie/test/test_loadflags.html create mode 100644 extensions/cookie/test/test_same_base_domain.html create mode 100644 extensions/cookie/test/test_same_base_domain_2.html create mode 100644 extensions/cookie/test/test_same_base_domain_3.html create mode 100644 extensions/cookie/test/test_same_base_domain_4.html create mode 100644 extensions/cookie/test/test_same_base_domain_5.html create mode 100644 extensions/cookie/test/test_same_base_domain_6.html create mode 100644 extensions/cookie/test/test_samedomain.html create mode 100644 extensions/cookie/test/unit/cookieprompt.js create mode 100644 extensions/cookie/test/unit/cookieprompt.manifest create mode 100644 extensions/cookie/test/unit/head_cookies.js create mode 100644 extensions/cookie/test/unit/test_bug526789.js create mode 100644 extensions/cookie/test/unit/test_bug650522.js create mode 100644 extensions/cookie/test/unit/test_bug667087.js create mode 100644 extensions/cookie/test/unit/test_cookies_async_failure.js create mode 100644 extensions/cookie/test/unit/test_cookies_persistence.js create mode 100644 extensions/cookie/test/unit/test_cookies_privatebrowsing.js create mode 100644 extensions/cookie/test/unit/test_cookies_profile_close.js create mode 100644 extensions/cookie/test/unit/test_cookies_read.js create mode 100644 extensions/cookie/test/unit/test_cookies_sync_failure.js create mode 100644 extensions/cookie/test/unit/test_cookies_thirdparty.js create mode 100644 extensions/cookie/test/unit/test_cookies_thirdparty_session.js create mode 100644 extensions/cookie/test/unit/test_domain_eviction.js create mode 100644 extensions/cookie/test/unit/test_eviction.js create mode 100644 extensions/cookie/test/unit/test_permmanager_cleardata.js create mode 100644 extensions/cookie/test/unit/test_permmanager_defaults.js create mode 100644 extensions/cookie/test/unit/test_permmanager_expiration.js create mode 100644 extensions/cookie/test/unit/test_permmanager_getAllForURI.js create mode 100644 extensions/cookie/test/unit/test_permmanager_getPermissionObject.js create mode 100644 extensions/cookie/test/unit/test_permmanager_idn.js create mode 100644 extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js create mode 100644 extensions/cookie/test/unit/test_permmanager_local_files.js create mode 100644 extensions/cookie/test/unit/test_permmanager_matches.js create mode 100644 extensions/cookie/test/unit/test_permmanager_matchesuri.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_4-7.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js create mode 100644 extensions/cookie/test/unit/test_permmanager_migrate_7-8.js create mode 100644 extensions/cookie/test/unit/test_permmanager_notifications.js create mode 100644 extensions/cookie/test/unit/test_permmanager_removeall.js create mode 100644 extensions/cookie/test/unit/test_permmanager_removeforapp.js create mode 100644 extensions/cookie/test/unit/test_permmanager_removepermission.js create mode 100644 extensions/cookie/test/unit/test_permmanager_removesince.js create mode 100644 extensions/cookie/test/unit/test_permmanager_subdomains.js create mode 100644 extensions/cookie/test/unit/test_schema_2_migration.js create mode 100644 extensions/cookie/test/unit/test_schema_3_migration.js create mode 100644 extensions/cookie/test/unit/xpcshell.ini create mode 100644 extensions/cookie/test/unit_ipc/test_child.js create mode 100644 extensions/cookie/test/unit_ipc/test_parent.js create mode 100644 extensions/cookie/test/unit_ipc/xpcshell.ini (limited to 'extensions/cookie') 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 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(ACCEPT_SESSION) && val != static_cast(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 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 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 mPermMgr; + nsCOMPtr 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 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 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 wwatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr arguments = do_QueryInterface(block); + + nsCOMPtr 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 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::Create(nsIPrincipal* aPrincipal, + const nsACString &aType, + uint32_t aCapability, + uint32_t aExpireType, + int64_t aExpireTime) +{ + NS_ENSURE_TRUE(aPrincipal, nullptr); + nsCOMPtr principal = + mozilla::BasePrincipal::Cast(aPrincipal)->CloneStrippingUserContextIdAndFirstPartyDomain(); + + NS_ENSURE_TRUE(principal, nullptr); + + RefPtr permission = + new nsPermission(principal, aType, aCapability, aExpireType, aExpireTime); + return permission.forget(); +} + +NS_IMETHODIMP +nsPermission::GetPrincipal(nsIPrincipal** aPrincipal) +{ + nsCOMPtr 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 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 theirURI; + nsresult rv = principal->GetURI(getter_AddRefs(theirURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 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 uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 mStmt; + nsCOMPtr 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 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 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 mStmt; + nsCOMPtr mLookupStmt; + nsCOMPtr 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("")) { + // We no longer support the magic host + NS_WARNING("The magic host 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 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 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 histSrv = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID); + + if (histSrv) { + nsCOMPtr histQuery; + rv = histSrv->GetNewQuery(getter_AddRefs(histQuery)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the eTLD+1 of the domain + nsAutoCString eTLD1; + nsCOMPtr 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 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 histResult; + rv = histSrv->ExecuteQuery(histQuery, histQueryOpts, getter_AddRefs(histResult)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 insertedOrigins; + for (uint32_t i = 0; i < childCount; i++) { + nsCOMPtr 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 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 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 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 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 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 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 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 manager = mManager.forget(); + + if (aReason == REASON_ERROR) { + manager->CloseDB(true); + } + + return NS_OK; +} + +/* static */ void +nsPermissionManager::ClearOriginDataObserverInit() +{ + nsCOMPtr 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 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 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 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 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(stmt->AsInt64(6)); + isInBrowserElement = static_cast(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 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 tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + MOZ_ASSERT(tldService); // We should always have a tldService + + nsCOMPtr 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 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(stmt->AsInt64(6)); + isInBrowserElement = static_cast(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 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 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 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 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 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 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 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 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 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 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 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 window = nsPIDOMWindowInner::From(aWindow); + + // Get the document for security check + nsCOMPtr document = window->GetExtantDoc(); + NS_ENSURE_TRUE(document, NS_NOINTERFACE); + + nsCOMPtr 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 principal; + nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + PermissionEntry& perm = entry->GetPermissions()[idx]; + nsCOMPtr 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 ep = do_QueryInterface(aPrincipal); + if (ep) { + nsTArray>* 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 "". 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 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 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 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 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 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 principal; + nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, + getter_AddRefs(principal)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr 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 array; + + nsCOMPtr principal; + nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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 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 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 principal; + nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, + getter_AddRefs(principal)); + if (NS_FAILED(rv)) { + continue; + } + + nsCOMPtr 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 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 permissions; + for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) { + PermissionHashKey* entry = iter.Get(); + + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 defaultsURI; + nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 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 perms; + ChildProcess()->SendReadPermissions(&perms); + + for (uint32_t i = 0; i < perms.Length(); i++) { + const IPC::Permission &perm = perms[i]; + + nsCOMPtr 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 + { + public: + explicit PermissionHashKey(const PermissionKey* aPermissionKey) + : nsRefPtrHashKey(aPermissionKey) + {} + + PermissionHashKey(const PermissionHashKey& toCopy) + : nsRefPtrHashKey(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 & 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 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 mDBConn; + nsCOMPtr mStmtInsert; + nsCOMPtr mStmtDelete; + nsCOMPtr mStmtUpdate; + + bool mMemoryOnlyDB; + + nsTHashtable 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 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 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 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 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 Binary files /dev/null and b/extensions/cookie/test/beltzner.jpg 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 Binary files /dev/null and b/extensions/cookie/test/damonbowling.jpg 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + + + + + 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + 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 Binary files /dev/null and b/extensions/cookie/test/image1.png 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 Binary files /dev/null and b/extensions/cookie/test/image2.png 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 @@ + + + + Test cookie requests from within a window hierarchy of different base domains + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 @@ + + + + Test for Cross domain access to properties + + + + +

+
+
+
+ + 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 { + 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 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("", "A", 1, 0, 0, 0, 0, false), + insertHost("", "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("", "A", 1, 0, 0, 0, 0, false), + insertHost("", "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("", "A", 1, 0, 0, 0, 0, false), + insertHost("", "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("", "A", 1, 0, 0, 0, 0, false), + insertHost("", "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("", "A", 1, 0, 0, 0, 0, false), + insertHost("", "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] -- cgit v1.2.3