diff --git a/caps/src/nsScriptSecurityManager.cpp b/caps/src/nsScriptSecurityManager.cpp index b16afdc9e641..02879a4e4708 100644 --- a/caps/src/nsScriptSecurityManager.cpp +++ b/caps/src/nsScriptSecurityManager.cpp @@ -316,118 +316,7 @@ PRBool nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI, nsIURI* aTargetURI) { - // Note that this is not an Equals() test on purpose -- for URIs that don't - // support host/port, we want equality to basically be object identity, for - // security purposes. Otherwise, for example, two javascript: URIs that - // are otherwise unrelated could end up "same origin", which would be - // unfortunate. - if (aSourceURI && aSourceURI == aTargetURI) - { - return PR_TRUE; - } - - if (!aTargetURI || !aSourceURI) - { - return PR_FALSE; - } - - // If either URI is a nested URI, get the base URI - nsCOMPtr sourceBaseURI = NS_GetInnermostURI(aSourceURI); - nsCOMPtr targetBaseURI = NS_GetInnermostURI(aTargetURI); - - if (!sourceBaseURI || !targetBaseURI) - return PR_FALSE; - - // Compare schemes - nsCAutoString targetScheme; - PRBool sameScheme = PR_FALSE; - if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) || - NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) || - !sameScheme) - { - // Not same-origin if schemes differ - return PR_FALSE; - } - - // special handling for file: URIs - if (targetScheme.EqualsLiteral("file")) - { - // in traditional unsafe behavior all files are the same origin - if (!sStrictFileOriginPolicy) - return PR_TRUE; - - nsCOMPtr sourceFileURL(do_QueryInterface(sourceBaseURI)); - nsCOMPtr targetFileURL(do_QueryInterface(targetBaseURI)); - - if (!sourceFileURL || !targetFileURL) - return PR_FALSE; - - nsCOMPtr sourceFile, targetFile; - - sourceFileURL->GetFile(getter_AddRefs(sourceFile)); - targetFileURL->GetFile(getter_AddRefs(targetFile)); - - if (!sourceFile || !targetFile) - return PR_FALSE; - - // Otherwise they had better match - PRBool filesAreEqual = PR_FALSE; - nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual); - return NS_SUCCEEDED(rv) && filesAreEqual; - } - - // Special handling for mailnews schemes - if (targetScheme.EqualsLiteral("imap") || - targetScheme.EqualsLiteral("mailbox") || - targetScheme.EqualsLiteral("news")) - { - // Each message is a distinct trust domain; use the - // whole spec for comparison - nsCAutoString targetSpec; - nsCAutoString sourceSpec; - return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) && - NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) && - targetSpec.Equals(sourceSpec) ); - } - - // Compare hosts - nsCAutoString targetHost; - nsCAutoString sourceHost; - if (NS_FAILED( targetBaseURI->GetHost(targetHost) ) || - NS_FAILED( sourceBaseURI->GetHost(sourceHost) ) || - !targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator())) - { - // Not same-origin if hosts differ - return PR_FALSE; - } - - // Compare ports - PRInt32 targetPort; - nsresult rv = targetBaseURI->GetPort(&targetPort); - PRInt32 sourcePort; - if (NS_SUCCEEDED(rv)) - rv = sourceBaseURI->GetPort(&sourcePort); - PRBool result = NS_SUCCEEDED(rv) && targetPort == sourcePort; - // If the port comparison failed, see if either URL has a - // port of -1. If so, replace -1 with the default port - // for that scheme. - if (NS_SUCCEEDED(rv) && !result && - (sourcePort == -1 || targetPort == -1)) - { - NS_ENSURE_TRUE(sIOService, PR_FALSE); - - PRInt32 defaultPort = NS_GetDefaultPort(targetScheme.get()); - if (defaultPort == -1) - return PR_FALSE; // No default port for this scheme - - if (sourcePort == -1) - sourcePort = defaultPort; - else if (targetPort == -1) - targetPort = defaultPort; - result = targetPort == sourcePort; - } - - return result; + return NS_SecurityCompareURIs(aSourceURI, aTargetURI, sStrictFileOriginPolicy); } NS_IMETHODIMP diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 26a9f66d87ec..5ad1ca9a7ce7 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1250,6 +1250,11 @@ public: */ static PRBool OfflineAppAllowed(nsIURI *aURI); + /** + * Check whether an application should be allowed to use offline APIs. + */ + static PRBool OfflineAppAllowed(nsIPrincipal *aPrincipal); + /** * Increases the count of blockers preventing scripts from running. * NOTE: You might want to use nsAutoScriptBlocker rather than calling diff --git a/content/base/src/Makefile.in b/content/base/src/Makefile.in index 68f24a71a6dc..907b53cded3b 100644 --- a/content/base/src/Makefile.in +++ b/content/base/src/Makefile.in @@ -59,6 +59,7 @@ REQUIRES = xpcom \ js \ webshell \ necko \ + nkcache \ mimetype \ exthandler \ chardet \ diff --git a/content/base/src/nsContentSink.cpp b/content/base/src/nsContentSink.cpp index 9f4a1bbe0edb..442b52dac80b 100644 --- a/content/base/src/nsContentSink.cpp +++ b/content/base/src/nsContentSink.cpp @@ -73,6 +73,7 @@ #include "nsIOfflineCacheUpdate.h" #include "nsIApplicationCache.h" #include "nsIApplicationCacheContainer.h" +#include "nsIApplicationCacheService.h" #include "nsIScriptSecurityManager.h" #include "nsIDOMLoadStatus.h" #include "nsICookieService.h" @@ -96,6 +97,10 @@ #include "nsPresShellIterator.h" #include "nsPIDOMWindow.h" #include "mozAutoDocUpdate.h" +#include "nsIWebNavigation.h" +#include "nsIDocumentLoader.h" +#include "nsICachingChannel.h" +#include "nsICacheEntryDescriptor.h" PRLogModuleInfo* gContentSinkLogModuleInfo; @@ -847,79 +852,304 @@ nsContentSink::PrefetchHref(const nsAString &aHref, } } +nsresult +nsContentSink::GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey) +{ + aCacheKey.Truncate(); + + nsresult rv; + nsCOMPtr cachingChannel = do_QueryInterface(aChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr token; + rv = cachingChannel->GetCacheToken(getter_AddRefs(token)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr descriptor = do_QueryInterface(token, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = descriptor->GetKey(aCacheKey); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsContentSink::SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache, + nsIURI *aManifestURI, + PRBool aIsTopDocument, + PRBool aFetchedWithHTTPGetOrEquiv, + CacheSelectionAction *aAction) +{ + *aAction = CACHE_SELECTION_NONE; + + nsCOMPtr applicationCacheDocument = + do_QueryInterface(mDocument); + NS_ASSERTION(applicationCacheDocument, + "mDocument must implement nsIApplicationCacheContainer."); + + nsresult rv; + + // We might decide on a new application cache... + nsCOMPtr applicationCache = aLoadApplicationCache; + + if (applicationCache) { + nsCAutoString groupID; + rv = applicationCache->GetGroupID(groupID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr groupURI; + rv = NS_NewURI(getter_AddRefs(groupURI), groupID); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool equal = PR_FALSE; + rv = groupURI->Equals(aManifestURI, &equal); + NS_ENSURE_SUCCESS(rv, rv); + + if (!equal) { + // This is a foreign entry, mark it as such. If this is a + // toplevel load, force a reload to avoid loading the foreign + // entry. The next attempt will not choose this cache entry + // (because it has been marked foreign). + + nsCAutoString cachekey; + rv = GetChannelCacheKey(mDocument->GetChannel(), cachekey); + NS_ENSURE_SUCCESS(rv, rv); + + rv = applicationCache->MarkEntry(cachekey, + nsIApplicationCache::ITEM_FOREIGN); + NS_ENSURE_SUCCESS(rv, rv); + + if (aIsTopDocument) { + *aAction = CACHE_SELECTION_RELOAD; + } + + return NS_OK; + } + + if (aIsTopDocument) { + // This is a top level document and the http manifest attribute + // URI is equal to the manifest URI of the cache the document + // was loaded from - associate the document with that cache and + // invoke the cache update process. + rv = applicationCacheDocument->SetApplicationCache(applicationCache); + NS_ENSURE_SUCCESS(rv, rv); + + *aAction = CACHE_SELECTION_UPDATE; + } + } + else { + // The document was not loaded from an application cache + // Here we know the manifest has the same origin as the + // document. There is call to CheckMayLoad() on it above. + + if (!aFetchedWithHTTPGetOrEquiv) { + // The document was not loaded using HTTP GET or equivalent + // method. The spec says to run the cache selection algorithm w/o + // the manifest specified but we can just do return NS_OK here. + + return NS_OK; + } + + // If there is an existing application cache for this manifest, + // associate it with the document. + nsCAutoString manifestURISpec; + rv = aManifestURI->GetAsciiSpec(manifestURISpec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr appCacheService = + do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID); + if (!appCacheService) { + // No application cache service, nothing to do here. + return NS_OK; + } + + rv = appCacheService->GetActiveCache(manifestURISpec, + getter_AddRefs(applicationCache)); + NS_ENSURE_SUCCESS(rv, rv); + + if (applicationCache) { + rv = applicationCacheDocument->SetApplicationCache(applicationCache); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // XXX bug 443023: if there is already a scheduled update or + // update in progress we have to add this document as + // an implicit entry. + } + + // Always do an update in this case + *aAction = CACHE_SELECTION_UPDATE; + } + + if (applicationCache) { + // We are now associated with an application cache. This item + // should be marked as an implicit entry. + nsCAutoString cachekey; + rv = GetChannelCacheKey(mDocument->GetChannel(), cachekey); + if (NS_SUCCEEDED(rv)) { + rv = applicationCache->MarkEntry(cachekey, + nsIApplicationCache::ITEM_IMPLICIT); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +nsContentSink::SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache, + PRBool aIsTopDocument, + nsIURI **aManifestURI, + CacheSelectionAction *aAction) +{ + *aManifestURI = nsnull; + *aAction = CACHE_SELECTION_NONE; + + if (!aIsTopDocument || !aLoadApplicationCache) { + return NS_OK; + } + + nsresult rv; + + // The document was loaded from an application cache, use that + // application cache as the document's application cache. + nsCOMPtr applicationCacheDocument = + do_QueryInterface(mDocument); + NS_ASSERTION(applicationCacheDocument, + "mDocument must implement nsIApplicationCacheContainer."); + + rv = applicationCacheDocument->SetApplicationCache(aLoadApplicationCache); + NS_ENSURE_SUCCESS(rv, rv); + + // Return the uri and invoke the update process for the selected + // application cache. + nsCAutoString groupID; + rv = aLoadApplicationCache->GetGroupID(groupID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(aManifestURI, groupID); + NS_ENSURE_SUCCESS(rv, rv); + + *aAction = CACHE_SELECTION_UPDATE; + + return NS_OK; +} + void nsContentSink::ProcessOfflineManifest(nsIContent *aElement) { + // Only check the manifest for root document nodes. + if (aElement != mDocument->GetRootContent()) { + return; + } + + nsresult rv; + // Check for a manifest= attribute. nsAutoString manifestSpec; aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::manifest, manifestSpec); - if (manifestSpec.IsEmpty() || - manifestSpec.FindChar('#') != kNotFound) { + // Grab the application cache the document was loaded from, if any. + nsCOMPtr applicationCache; + + nsCOMPtr applicationCacheChannel = + do_QueryInterface(mDocument->GetChannel()); + if (applicationCacheChannel) { + rv = applicationCacheChannel->GetApplicationCache( + getter_AddRefs(applicationCache)); + if (NS_FAILED(rv)) { + return; + } + } + + if (manifestSpec.IsEmpty() && !applicationCache) { + // Not loaded from an application cache, and no manifest + // attribute. Nothing to do here. return; } - // We only care about manifests in toplevel windows. - nsCOMPtr pwindow = - do_QueryInterface(mDocument->GetScriptGlobalObject()); - if (!pwindow) { + // The manifest attribute is handled differently if the document is + // not toplevel. + nsCOMPtr window = mDocument->GetWindow(); + if (!window) return; - } - - nsCOMPtr window = - do_QueryInterface(pwindow->GetOuterWindow()); - if (!window) { - return; - } - nsCOMPtr parent; window->GetParent(getter_AddRefs(parent)); - if (parent.get() != window.get()) { - return; - } - - // Only update if the document has permission to use offline APIs. - if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) { - return; - } - - // XXX: at this point in the spec there is an algorithm for - // confirming whether the cache that was selected at load time was - // the proper application cache for this document. This will - // be implemented in a separate patch; For now just assume that we - // chose an acceptable application cache. - - nsCOMPtr channelContainer = - do_QueryInterface(mDocument->GetChannel()); - - nsCOMPtr docContainer = - do_QueryInterface(mDocument); - - if (channelContainer && docContainer) { - nsCOMPtr appCache; - channelContainer->GetApplicationCache(getter_AddRefs(appCache)); - docContainer->SetApplicationCache(appCache); - } + PRBool isTop = (parent == window); + CacheSelectionAction action = CACHE_SELECTION_NONE; nsCOMPtr manifestURI; - nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI), - manifestSpec, mDocument, - mDocumentURI); - if (!manifestURI) { - return; + + if (manifestSpec.IsEmpty()) { + rv = SelectDocAppCacheNoManifest(applicationCache, + isTop, + getter_AddRefs(manifestURI), + &action); + if (NS_FAILED(rv)) { + return; + } + } + else { + nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(manifestURI), + manifestSpec, mDocument, + mDocumentURI); + if (!manifestURI) { + return; + } + + // Documents must list a manifest from the same origin + rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, PR_TRUE); + if (NS_FAILED(rv)) { + return; + } + + // Only continue if the document has permission to use offline APIs. + if (!nsContentUtils::OfflineAppAllowed(mDocument->NodePrincipal())) { + return; + } + + PRBool fetchedWithHTTPGetOrEquiv = PR_FALSE; + nsCOMPtr httpChannel(do_QueryInterface(mDocument->GetChannel())); + if (httpChannel) { + nsCAutoString method; + rv = httpChannel->GetRequestMethod(method); + if (NS_SUCCEEDED(rv)) + fetchedWithHTTPGetOrEquiv = method.Equals("GET"); + } + + rv = SelectDocAppCache(applicationCache, manifestURI, isTop, + fetchedWithHTTPGetOrEquiv, &action); + if (NS_FAILED(rv)) { + return; + } } - // Documents must list a manifest from the same origin - nsresult rv = mDocument->NodePrincipal()->CheckMayLoad(manifestURI, PR_TRUE); - if (NS_FAILED(rv)) { + switch (action) + { + case CACHE_SELECTION_NONE: return; - } + case CACHE_SELECTION_UPDATE: { + nsCOMPtr updateService = + do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); - // Start the update - nsCOMPtr domdoc = do_QueryInterface(mDocument); - nsCOMPtr updateService = - do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID); - updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc); + if (updateService) { + nsCOMPtr domdoc = do_QueryInterface(mDocument); + updateService->ScheduleOnDocumentStop(manifestURI, mDocumentURI, domdoc); + } + break; + } + case CACHE_SELECTION_RELOAD: { + // This situation occurs only for toplevel documents, see bottom + // of SelectDocAppCache method. + NS_ASSERTION(isTop, "Should only reload toplevel documents!"); + nsCOMPtr webNav = do_QueryInterface(mDocShell); + + webNav->Stop(nsIWebNavigation::STOP_ALL); + webNav->Reload(nsIWebNavigation::LOAD_FLAGS_NONE); + break; + } + } } void diff --git a/content/base/src/nsContentSink.h b/content/base/src/nsContentSink.h index a7e29c085562..3a61d3236ea8 100644 --- a/content/base/src/nsContentSink.h +++ b/content/base/src/nsContentSink.h @@ -76,6 +76,7 @@ class nsIContent; class nsIViewManager; class nsNodeInfoManager; class nsScriptLoader; +class nsIApplicationCache; #ifdef NS_DEBUG @@ -150,6 +151,25 @@ protected: nsContentSink(); virtual ~nsContentSink(); + enum CacheSelectionAction { + // There is no offline cache manifest specified by the document, + // or the document was loaded from a cache other than the one it + // specifies via its manifest attribute and IS NOT a top-level + // document, or an error occurred during the cache selection + // algorithm. + CACHE_SELECTION_NONE = 0, + + // The offline cache manifest must be updated. + CACHE_SELECTION_UPDATE = 1, + + // The document was loaded from a cache other than the one it + // specifies via its manifest attribute and IS a top-level + // document. In this case, the document is marked as foreign in + // the cache it was loaded from and must be reloaded from the + // correct cache (the one it specifies). + CACHE_SELECTION_RELOAD = 2 + }; + nsresult Init(nsIDocument* aDoc, nsIURI* aURI, nsISupports* aContainer, nsIChannel* aChannel); @@ -171,6 +191,60 @@ protected: void PrefetchHref(const nsAString &aHref, nsIContent *aSource, PRBool aExplicit); + + // Gets the cache key (used to identify items in a cache) of the channel. + nsresult GetChannelCacheKey(nsIChannel* aChannel, nsACString& aCacheKey); + + // There is an offline cache manifest attribute specified and the + // document is allowed to use the offline cache. Process the cache + // selection algorithm for this document and the manifest. Result is + // an action that must be taken on the manifest, see + // CacheSelectionAction enum above. + // + // @param aLoadApplicationCache + // The application cache from which the load originated, if + // any. + // @param aManifestURI + // The manifest URI listed in the document. + // @param aIsTopDocument + // TRUE if this is a toplevel document. + // @param aFetchedWithHTTPGetOrEquiv + // TRUE if this was fetched using the HTTP GET method. + // @param aAction + // Out parameter, returns the action that should be performed + // by the calling function. + nsresult SelectDocAppCache(nsIApplicationCache *aLoadApplicationCache, + nsIURI *aManifestURI, + PRBool aIsTopDocument, + PRBool aFetchedWithHTTPGetOrEquiv, + CacheSelectionAction *aAction); + + // There is no offline cache manifest attribute specified. Process + // the cache selection algorithm w/o the manifest. Result is an + // action that must be taken, see CacheSelectionAction enum + // above. In case the offline cache manifest has to be updated the + // manifest URI is returned in aManifestURI. + // + // @param aLoadApplicationCache + // The application cache from which the load originated, if + // any. + // @param aIsTopDocument + // TRUE if this is a toplevel document. + // @param aManifestURI + // Out parameter, returns the manifest URI of the cache that + // was selected. + // @param aAction + // Out parameter, returns the action that should be performed + // by the calling function. + nsresult SelectDocAppCacheNoManifest(nsIApplicationCache *aLoadApplicationCache, + PRBool aIsTopDocument, + nsIURI **aManifestURI, + CacheSelectionAction *aAction); + + // Searches for the offline cache manifest attribute and calls one + // of the above defined methods to select the document's application + // cache, let it be associated with the document and eventually + // schedule the cache update process. void ProcessOfflineManifest(nsIContent *aElement); // Tries to scroll to the URI's named anchor. Once we've successfully diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 0365daa79326..ac41ae933e4a 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -811,6 +811,16 @@ nsContentUtils::OfflineAppAllowed(nsIURI *aURI) return NS_OfflineAppAllowed(aURI, sPrefBranch); } +/* static */ +PRBool +nsContentUtils::OfflineAppAllowed(nsIPrincipal *aPrincipal) +{ + nsCOMPtr codebaseURI; + aPrincipal->GetURI(getter_AddRefs(codebaseURI)); + + return OfflineAppAllowed(codebaseURI); +} + // static void nsContentUtils::Shutdown() diff --git a/dom/src/offline/nsDOMOfflineResourceList.cpp b/dom/src/offline/nsDOMOfflineResourceList.cpp index c1ddf24fcc43..b4468cbd23de 100644 --- a/dom/src/offline/nsDOMOfflineResourceList.cpp +++ b/dom/src/offline/nsDOMOfflineResourceList.cpp @@ -338,7 +338,11 @@ nsDOMOfflineResourceList::Add(const nsAString& aURI) do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); - rv = update->Init(PR_TRUE, mManifestURI, mDocumentURI); + nsCAutoString clientID; + rv = appCache->GetClientID(clientID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = update->InitPartial(mManifestURI, clientID, mDocumentURI); NS_ENSURE_SUCCESS(rv, rv); rv = update->AddDynamicURI(requestedURI); diff --git a/netwerk/base/public/nsNetUtil.h b/netwerk/base/public/nsNetUtil.h index e3d75404fd98..ad69da89d6d2 100644 --- a/netwerk/base/public/nsNetUtil.h +++ b/netwerk/base/public/nsNetUtil.h @@ -74,6 +74,7 @@ #include "nsIStringStream.h" #include "nsILocalFile.h" #include "nsIFileStreams.h" +#include "nsIFileURL.h" #include "nsIProtocolProxyService.h" #include "nsIProxyInfo.h" #include "nsIFileStreams.h" @@ -1465,4 +1466,128 @@ NS_OfflineAppAllowed(nsIURI *aURI, nsIPrefBranch *aPrefBranch = nsnull) return allowed; } +inline PRBool +NS_SecurityCompareURIs(nsIURI* aSourceURI, + nsIURI* aTargetURI, + PRBool aStrictFileOriginPolicy) +{ + // Note that this is not an Equals() test on purpose -- for URIs that don't + // support host/port, we want equality to basically be object identity, for + // security purposes. Otherwise, for example, two javascript: URIs that + // are otherwise unrelated could end up "same origin", which would be + // unfortunate. + if (aSourceURI && aSourceURI == aTargetURI) + { + return PR_TRUE; + } + + if (!aTargetURI || !aSourceURI) + { + return PR_FALSE; + } + + // If either URI is a nested URI, get the base URI + nsCOMPtr sourceBaseURI = NS_GetInnermostURI(aSourceURI); + nsCOMPtr targetBaseURI = NS_GetInnermostURI(aTargetURI); + + if (!sourceBaseURI || !targetBaseURI) + return PR_FALSE; + + // Compare schemes + nsCAutoString targetScheme; + PRBool sameScheme = PR_FALSE; + if (NS_FAILED( targetBaseURI->GetScheme(targetScheme) ) || + NS_FAILED( sourceBaseURI->SchemeIs(targetScheme.get(), &sameScheme) ) || + !sameScheme) + { + // Not same-origin if schemes differ + return PR_FALSE; + } + + // special handling for file: URIs + if (targetScheme.EqualsLiteral("file")) + { + // in traditional unsafe behavior all files are the same origin + if (!aStrictFileOriginPolicy) + return PR_TRUE; + + nsCOMPtr sourceFileURL(do_QueryInterface(sourceBaseURI)); + nsCOMPtr targetFileURL(do_QueryInterface(targetBaseURI)); + + if (!sourceFileURL || !targetFileURL) + return PR_FALSE; + + nsCOMPtr sourceFile, targetFile; + + sourceFileURL->GetFile(getter_AddRefs(sourceFile)); + targetFileURL->GetFile(getter_AddRefs(targetFile)); + + if (!sourceFile || !targetFile) + return PR_FALSE; + + // Otherwise they had better match + PRBool filesAreEqual = PR_FALSE; + nsresult rv = sourceFile->Equals(targetFile, &filesAreEqual); + return NS_SUCCEEDED(rv) && filesAreEqual; + } + + // Special handling for mailnews schemes + if (targetScheme.EqualsLiteral("imap") || + targetScheme.EqualsLiteral("mailbox") || + targetScheme.EqualsLiteral("news")) + { + // Each message is a distinct trust domain; use the + // whole spec for comparison + nsCAutoString targetSpec; + nsCAutoString sourceSpec; + return ( NS_SUCCEEDED( targetBaseURI->GetSpec(targetSpec) ) && + NS_SUCCEEDED( sourceBaseURI->GetSpec(sourceSpec) ) && + targetSpec.Equals(sourceSpec) ); + } + + // Compare hosts + nsCAutoString targetHost; + nsCAutoString sourceHost; + if (NS_FAILED( targetBaseURI->GetAsciiHost(targetHost) ) || + NS_FAILED( sourceBaseURI->GetAsciiHost(sourceHost) )) + { + return PR_FALSE; + } + +#ifdef MOZILLA_INTERNAL_API + if (!targetHost.Equals(sourceHost, nsCaseInsensitiveCStringComparator() )) +#else + if (!targetHost.Equals(sourceHost, CaseInsensitiveCompare)) +#endif + { + return PR_FALSE; + } + + // Compare ports + PRInt32 targetPort; + nsresult rv = targetBaseURI->GetPort(&targetPort); + PRInt32 sourcePort; + if (NS_SUCCEEDED(rv)) + rv = sourceBaseURI->GetPort(&sourcePort); + PRBool result = NS_SUCCEEDED(rv) && targetPort == sourcePort; + // If the port comparison failed, see if either URL has a + // port of -1. If so, replace -1 with the default port + // for that scheme. + if (NS_SUCCEEDED(rv) && !result && + (sourcePort == -1 || targetPort == -1)) + { + PRInt32 defaultPort = NS_GetDefaultPort(targetScheme.get()); + if (defaultPort == -1) + return PR_FALSE; // No default port for this scheme + + if (sourcePort == -1) + sourcePort = defaultPort; + else if (targetPort == -1) + targetPort = defaultPort; + result = targetPort == sourcePort; + } + + return result; +} + #endif // !nsNetUtil_h__ diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp index beb1c716ea22..d53bed6a28fd 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp @@ -44,6 +44,8 @@ #include "nsNetUtil.h" #include "nsAutoPtr.h" #include "nsEscape.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" #include "nsString.h" #include "nsPrintfCString.h" #include "nsCRT.h" @@ -692,6 +694,20 @@ nsOfflineCacheDevice::~nsOfflineCacheDevice() Shutdown(); } +/* static */ +PRBool +nsOfflineCacheDevice::GetStrictFileOriginPolicy() +{ + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + + PRBool retval; + if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval))) + return retval; + + // As default value use true (be more strict) + return PR_TRUE; +} + PRUint32 nsOfflineCacheDevice::CacheSize() { @@ -1745,13 +1761,40 @@ nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key, rv = statement->ExecuteStep(&hasRows); NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr keyURI; + rv = NS_NewURI(getter_AddRefs(keyURI), key); + NS_ENSURE_SUCCESS(rv, rv); + while (hasRows) { - nsCString clientID; - rv = statement->GetUTF8String(0, clientID); + PRInt32 itemType; + rv = statement->GetInt32(1, &itemType); NS_ENSURE_SUCCESS(rv, rv); - if (mActiveCaches.Contains(clientID)) - return GetApplicationCache(clientID, out); + if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) { + nsCAutoString clientID; + rv = statement->GetUTF8String(0, clientID); + NS_ENSURE_SUCCESS(rv, rv); + + if (mActiveCaches.Contains(clientID)) { + nsCAutoString groupID; + rv = GetGroupForCache(clientID, groupID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr groupURI; + rv = NS_NewURI(getter_AddRefs(groupURI), groupID); + if (NS_SUCCEEDED(rv)) { + // When we are choosing an initial cache to load the top + // level document from, the URL of that document must have + // the same origin as the manifest, according to the spec. + // The following check is here because explicit, fallback + // and dynamic entries might have origin different from the + // manifest origin. XXX: dynamic shouldn't? + if (NS_SecurityCompareURIs(keyURI, groupURI, + GetStrictFileOriginPolicy())) + return GetApplicationCache(clientID, out); + } + } + } rv = statement->ExecuteStep(&hasRows); NS_ENSURE_SUCCESS(rv, rv); diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.h b/netwerk/cache/src/nsDiskCacheDeviceSQL.h index af10e516e87c..ffd569976fe9 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h @@ -184,6 +184,8 @@ private: nsIWeakReference *weakRef, void *ctx); + static PRBool GetStrictFileOriginPolicy(); + PRBool Initialized() { return mDB != nsnull; } nsresult InitActiveCaches(); diff --git a/uriloader/prefetch/nsIOfflineCacheUpdate.idl b/uriloader/prefetch/nsIOfflineCacheUpdate.idl index ba1797b99a6a..a32dd54fb930 100644 --- a/uriloader/prefetch/nsIOfflineCacheUpdate.idl +++ b/uriloader/prefetch/nsIOfflineCacheUpdate.idl @@ -115,7 +115,7 @@ interface nsIOfflineCacheUpdateObserver : nsISupports { * load its items one by one, sending itemCompleted() to any registered * observers. */ -[scriptable, uuid(4b206247-82ee-46cf-a8b7-f7284e753bc2)] +[scriptable, uuid(877261bb-b952-4d27-847e-859bdd47c0ec)] interface nsIOfflineCacheUpdate : nsISupports { /** * Fetch the status of the running update. This will return a value @@ -154,21 +154,30 @@ interface nsIOfflineCacheUpdate : nsISupports { /** * Initialize the update. * - * @param aPartialUpdate - * TRUE if the update should just update the URIs given to it, - * FALSE if all URLs for the owner domain should be added. * @param aManifestURI - * The manifest URI to be checked, or for partial updates the - * manifest that should own resources that are added. + * The manifest URI to be checked. * @param aDocumentURI * The page that is requesting the update. */ - void init(in boolean aPartialUpdate, - in nsIURI aManifestURI, - in nsIURI aDocumentURI); + void init(in nsIURI aManifestURI, in nsIURI aDocumentURI); /** - * Add a URI to the offline cache as part of the update. + * Initialize the update for partial processing. + * + * @param aManifestURI + * The manifest URI of the related cache. + * @param aClientID + * Client ID of the cache to store resource to. This ClientID + * must be ID of cache in the cache group identified by + * the manifest URI passed in the first parameter. + * @param aDocumentURI + * The page that is requesting the update. May be null + * when this information is unknown. + */ + void initPartial(in nsIURI aManifestURI, in ACString aClientID, in nsIURI aDocumentURI); + + /** + * Add a dynamic URI to the offline cache as part of the update. * * @param aURI * The URI to add. diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp index ae81301325b0..0e79c9ea64d5 100644 --- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp +++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp @@ -46,6 +46,7 @@ #include "nsICacheService.h" #include "nsICacheSession.h" #include "nsICachingChannel.h" +#include "nsIDocumentLoader.h" #include "nsIDOMWindow.h" #include "nsIDOMOfflineResourceList.h" #include "nsIObserverService.h" @@ -57,6 +58,7 @@ #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsStreamUtils.h" +#include "nsStringEnumerator.h" #include "nsThreadUtils.h" #include "prlog.h" @@ -577,6 +579,7 @@ nsOfflineManifestItem::HandleManifestLine(const nsCString::const_iterator &aBegi // this should have been dealt with earlier return NS_ERROR_FAILURE; } + case PARSE_CACHE_ENTRIES: { nsCOMPtr uri; rv = NS_NewURI(getter_AddRefs(uri), line, nsnull, mURI); @@ -810,8 +813,7 @@ nsOfflineCacheUpdate::GetCacheKey(nsIURI *aURI, nsACString &aKey) } nsresult -nsOfflineCacheUpdate::Init(PRBool aPartialUpdate, - nsIURI *aManifestURI, +nsOfflineCacheUpdate::Init(nsIURI *aManifestURI, nsIURI *aDocumentURI) { nsresult rv; @@ -822,9 +824,9 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate, if (!service) return NS_ERROR_FAILURE; - LOG(("nsOfflineCacheUpdate::Init [%p, %d]", this, aPartialUpdate)); + LOG(("nsOfflineCacheUpdate::Init [%p]", this)); - mPartialUpdate = aPartialUpdate; + mPartialUpdate = PR_FALSE; // Only http and https applications are supported. PRBool match; @@ -858,20 +860,66 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate, getter_AddRefs(mPreviousApplicationCache)); NS_ENSURE_SUCCESS(rv, rv); - // Partial updates to existing application caches don't need a new cache. - if (aPartialUpdate && mPreviousApplicationCache) { - mApplicationCache = mPreviousApplicationCache; - } else { - rv = cacheService->CreateApplicationCache(manifestSpec, - getter_AddRefs(mApplicationCache)); - NS_ENSURE_SUCCESS(rv, rv); - } + rv = cacheService->CreateApplicationCache(manifestSpec, + getter_AddRefs(mApplicationCache)); + NS_ENSURE_SUCCESS(rv, rv); rv = mApplicationCache->GetClientID(mClientID); NS_ENSURE_SUCCESS(rv, rv); mState = STATE_INITIALIZED; + return NS_OK; +} +nsresult +nsOfflineCacheUpdate::InitPartial(nsIURI *aManifestURI, + const nsACString& clientID, + nsIURI *aDocumentURI) +{ + nsresult rv; + + // Make sure the service has been initialized + nsOfflineCacheUpdateService* service = + nsOfflineCacheUpdateService::EnsureService(); + if (!service) + return NS_ERROR_FAILURE; + + LOG(("nsOfflineCacheUpdate::InitPartial [%p]", this)); + + mPartialUpdate = PR_TRUE; + mClientID = clientID; + mDocumentURI = aDocumentURI; + + mManifestURI = aManifestURI; + rv = mManifestURI->GetAsciiHost(mUpdateDomain); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr cacheService = + do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = cacheService->GetApplicationCache(mClientID, + getter_AddRefs(mApplicationCache)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mApplicationCache) { + nsCAutoString manifestSpec; + rv = GetCacheKey(mManifestURI, manifestSpec); + NS_ENSURE_SUCCESS(rv, rv); + + rv = cacheService->CreateApplicationCache + (manifestSpec, getter_AddRefs(mApplicationCache)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCAutoString groupID; + rv = mApplicationCache->GetGroupID(groupID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewURI(getter_AddRefs(mManifestURI), groupID); + NS_ENSURE_SUCCESS(rv, rv); + + mState = STATE_INITIALIZED; return NS_OK; } @@ -1610,6 +1658,7 @@ nsOfflineCacheUpdateService::ScheduleOnDocumentStop(nsIURI *aManifestURI, LOG(("nsOfflineCacheUpdateService::ScheduleOnDocumentStop [%p, manifestURI=%p, documentURI=%p doc=%p]", this, aManifestURI, aDocumentURI, aDocument)); + // Proceed with cache update PendingUpdate *update = new PendingUpdate(); update->mManifestURI = aManifestURI; update->mDocumentURI = aDocumentURI; @@ -1737,7 +1786,7 @@ nsOfflineCacheUpdateService::ScheduleUpdate(nsIURI *aManifestURI, if (!update) return NS_ERROR_OUT_OF_MEMORY; - rv = update->Init(PR_FALSE, aManifestURI, aDocumentURI); + rv = update->Init(aManifestURI, aDocumentURI); NS_ENSURE_SUCCESS(rv, rv); rv = update->Schedule();