diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /netwerk/protocol/about | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/about/moz.build | 32 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutBlank.cpp | 52 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutBlank.h | 32 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutCache.cpp | 533 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutCache.h | 199 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutCacheEntry.cpp | 559 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutCacheEntry.h | 86 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutProtocolHandler.cpp | 414 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutProtocolHandler.h | 139 | ||||
-rw-r--r-- | netwerk/protocol/about/nsAboutProtocolUtils.h | 64 | ||||
-rw-r--r-- | netwerk/protocol/about/nsIAboutModule.idl | 119 |
11 files changed, 2229 insertions, 0 deletions
diff --git a/netwerk/protocol/about/moz.build b/netwerk/protocol/about/moz.build new file mode 100644 index 0000000000..f130d9b07b --- /dev/null +++ b/netwerk/protocol/about/moz.build @@ -0,0 +1,32 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + "nsIAboutModule.idl", +] + +XPIDL_MODULE = "necko_about" + +EXPORTS += [ + "nsAboutProtocolHandler.h", + "nsAboutProtocolUtils.h", +] + +UNIFIED_SOURCES += [ + "nsAboutBlank.cpp", + "nsAboutCache.cpp", + "nsAboutCacheEntry.cpp", + "nsAboutProtocolHandler.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/netwerk/base", + "/netwerk/cache2", +] diff --git a/netwerk/protocol/about/nsAboutBlank.cpp b/netwerk/protocol/about/nsAboutBlank.cpp new file mode 100644 index 0000000000..88db0d2de3 --- /dev/null +++ b/netwerk/protocol/about/nsAboutBlank.cpp @@ -0,0 +1,52 @@ +/* -*- 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 "nsAboutBlank.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" + +NS_IMPL_ISUPPORTS(nsAboutBlank, nsIAboutModule) + +NS_IMETHODIMP +nsAboutBlank::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIInputStream> in; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(in), ""_ns); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, + in.forget(), "text/html"_ns, "utf-8"_ns, + aLoadInfo); + if (NS_FAILED(rv)) return rv; + + channel.forget(result); + return rv; +} + +NS_IMETHODIMP +nsAboutBlank::GetURIFlags(nsIURI* aURI, uint32_t* result) { + *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::MAKE_LINKABLE | + nsIAboutModule::HIDE_FROM_ABOUTABOUT; + return NS_OK; +} + +NS_IMETHODIMP +nsAboutBlank::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { + return NS_ERROR_ILLEGAL_VALUE; +} + +nsresult nsAboutBlank::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsAboutBlank> about = new nsAboutBlank(); + return about->QueryInterface(aIID, aResult); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/protocol/about/nsAboutBlank.h b/netwerk/protocol/about/nsAboutBlank.h new file mode 100644 index 0000000000..6cb43507f5 --- /dev/null +++ b/netwerk/protocol/about/nsAboutBlank.h @@ -0,0 +1,32 @@ +/* -*- 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 nsAboutBlank_h__ +#define nsAboutBlank_h__ + +#include "nsIAboutModule.h" + +class nsAboutBlank : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIABOUTMODULE + + nsAboutBlank() = default; + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + virtual ~nsAboutBlank() = default; +}; + +#define NS_ABOUT_BLANK_MODULE_CID \ + { /* 3decd6c8-30ef-11d3-8cd0-0060b0fc14a3 */ \ + 0x3decd6c8, 0x30ef, 0x11d3, { \ + 0x8c, 0xd0, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 \ + } \ + } + +#endif // nsAboutBlank_h__ diff --git a/netwerk/protocol/about/nsAboutCache.cpp b/netwerk/protocol/about/nsAboutCache.cpp new file mode 100644 index 0000000000..1873277a7a --- /dev/null +++ b/netwerk/protocol/about/nsAboutCache.cpp @@ -0,0 +1,533 @@ +/* -*- 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 "nsAboutCache.h" +#include "nsIInputStream.h" +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsNetUtil.h" +#include "nsIPipe.h" +#include "nsContentUtils.h" +#include "nsEscape.h" +#include "nsAboutProtocolUtils.h" +#include "nsPrintfCString.h" + +#include "nsICacheStorageService.h" +#include "nsICacheStorage.h" +#include "CacheFileUtils.h" +#include "CacheObserver.h" + +#include "nsThreadUtils.h" + +using namespace mozilla::net; + +NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule) +NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest, + nsICacheStorageVisitor) + +NS_IMETHODIMP +nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + nsresult rv; + + NS_ENSURE_ARG_POINTER(aURI); + + RefPtr<Channel> channel = new Channel(); + rv = channel->Init(aURI, aLoadInfo); + if (NS_FAILED(rv)) return rv; + + channel.forget(result); + + return NS_OK; +} + +nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { + nsresult rv; + + mCancel = false; + + nsCOMPtr<nsIInputStream> inputStream; + NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384, + (uint32_t)-1, + true, // non-blocking input + false // blocking output + ); + + nsAutoCString storageName; + rv = ParseURI(aURI, storageName); + if (NS_FAILED(rv)) return rv; + + mOverview = storageName.IsEmpty(); + if (mOverview) { + // ...and visit all we can + mStorageList.AppendElement("memory"_ns); + mStorageList.AppendElement("disk"_ns); + } else { + // ...and visit just the specified storage, entries will output too + mStorageList.AppendElement(storageName); + } + + // The entries header is added on encounter of the first entry + mEntriesHeaderAdded = false; + + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), aURI, + inputStream.forget(), "text/html"_ns, + "utf-8"_ns, aLoadInfo); + if (NS_FAILED(rv)) return rv; + + mBuffer.AssignLiteral( + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <title>Network Cache Storage Information</title>\n" + " <meta charset=\"utf-8\">\n" + " <meta name=\"color-scheme\" content=\"light dark\">\n" + " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src " + "chrome:; object-src 'none'\"/>\n" + " <link rel=\"stylesheet\" " + "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n" + " <link rel=\"stylesheet\" " + "href=\"chrome://global/skin/aboutCache.css\"/>\n" + "</head>\n" + "<body class=\"aboutPageWideContainer\">\n" + "<h1>Information about the Network Cache Storage Service</h1>\n"); + + if (!mOverview) { + mBuffer.AppendLiteral( + "<a href=\"about:cache?storage=\">Back to overview</a>"); + } + + rv = FlushBuffer(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to flush buffer"); + } + + return NS_OK; +} + +NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) { + nsresult rv; + + if (!mChannel) { + return NS_ERROR_UNEXPECTED; + } + + // Kick the walk loop. + rv = VisitNextStorage(); + if (NS_FAILED(rv)) return rv; + + rv = mChannel->AsyncOpen(aListener); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) { + // + // about:cache[?storage=<storage-name>[&context=<context-key>]] + // + nsresult rv; + + nsAutoCString path; + rv = uri->GetPathQueryRef(path); + if (NS_FAILED(rv)) return rv; + + storage.Truncate(); + + nsACString::const_iterator start, valueStart, end; + path.BeginReading(start); + path.EndReading(end); + + valueStart = end; + if (!FindInReadable("?storage="_ns, start, valueStart)) { + return NS_OK; + } + + storage.Assign(Substring(valueStart, end)); + + return NS_OK; +} + +nsresult nsAboutCache::Channel::VisitNextStorage() { + if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE; + + mStorageName = mStorageList[0]; + mStorageList.RemoveElementAt(0); + + // Must re-dispatch since we cannot start another visit cycle + // from visitor callback. The cache v1 service doesn't like it. + // TODO - mayhemer, bug 913828, remove this dispatch and call + // directly. + return NS_DispatchToMainThread(mozilla::NewRunnableMethod( + "nsAboutCache::Channel::FireVisitStorage", this, + &nsAboutCache::Channel::FireVisitStorage)); +} + +void nsAboutCache::Channel::FireVisitStorage() { + nsresult rv; + + rv = VisitStorage(mStorageName); + if (NS_FAILED(rv)) { + nsAutoCString escaped; + nsAppendEscapedHTML(mStorageName, escaped); + mBuffer.Append(nsPrintfCString( + "<p>Unrecognized storage name '%s' in about:cache URL</p>", + escaped.get())); + + rv = FlushBuffer(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to flush buffer"); + } + + // Simulate finish of a visit cycle, this tries the next storage + // or closes the output stream (i.e. the UI loader will stop spinning) + OnCacheEntryVisitCompleted(); + } +} + +nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) { + nsresult rv; + + rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage)); + if (NS_FAILED(rv)) return rv; + + rv = mStorage->AsyncVisitStorage(this, !mOverview); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +// static +nsresult nsAboutCache::GetStorage(nsACString const& storageName, + nsILoadContextInfo* loadInfo, + nsICacheStorage** storage) { + nsresult rv; + + nsCOMPtr<nsICacheStorageService> cacheService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsICacheStorage> cacheStorage; + if (storageName == "disk") { + rv = cacheService->DiskCacheStorage(loadInfo, getter_AddRefs(cacheStorage)); + } else if (storageName == "memory") { + rv = cacheService->MemoryCacheStorage(loadInfo, + getter_AddRefs(cacheStorage)); + } else { + rv = NS_ERROR_UNEXPECTED; + } + if (NS_FAILED(rv)) return rv; + + cacheStorage.forget(storage); + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount, + uint64_t aConsumption, + uint64_t aCapacity, + nsIFile* aDirectory) { + // We need mStream for this + if (!mStream) { + return NS_ERROR_FAILURE; + } + + mBuffer.AssignLiteral("<h2>"); + nsAppendEscapedHTML(mStorageName, mBuffer); + mBuffer.AppendLiteral( + "</h2>\n" + "<table id=\""); + mBuffer.AppendLiteral("\">\n"); + + // Write out cache info + // Number of entries + mBuffer.AppendLiteral( + " <tr>\n" + " <th>Number of entries:</th>\n" + " <td>"); + mBuffer.AppendInt(aEntryCount); + mBuffer.AppendLiteral( + "</td>\n" + " </tr>\n"); + + // Maximum storage size + mBuffer.AppendLiteral( + " <tr>\n" + " <th>Maximum storage size:</th>\n" + " <td>"); + mBuffer.AppendInt(aCapacity / 1024); + mBuffer.AppendLiteral( + " KiB</td>\n" + " </tr>\n"); + + // Storage in use + mBuffer.AppendLiteral( + " <tr>\n" + " <th>Storage in use:</th>\n" + " <td>"); + mBuffer.AppendInt(aConsumption / 1024); + mBuffer.AppendLiteral( + " KiB</td>\n" + " </tr>\n"); + + // Storage disk location + mBuffer.AppendLiteral( + " <tr>\n" + " <th>Storage disk location:</th>\n" + " <td>"); + if (aDirectory) { + nsAutoString path; + aDirectory->GetPath(path); + mBuffer.Append(NS_ConvertUTF16toUTF8(path)); + } else { + mBuffer.AppendLiteral("none, only stored in memory"); + } + mBuffer.AppendLiteral( + " </td>\n" + " </tr>\n"); + + if (mOverview) { // The about:cache case + if (aEntryCount != 0) { // Add the "List Cache Entries" link + mBuffer.AppendLiteral( + " <tr>\n" + " <td colspan=\"2\"><a href=\"about:cache?storage="); + nsAppendEscapedHTML(mStorageName, mBuffer); + mBuffer.AppendLiteral( + "\">List Cache Entries</a></td>\n" + " </tr>\n"); + } + } + + mBuffer.AppendLiteral("</table>\n"); + + // The entries header is added on encounter of the first entry + mEntriesHeaderAdded = false; + + nsresult rv = FlushBuffer(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to flush buffer"); + } + + if (mOverview) { + // OnCacheEntryVisitCompleted() is not called when we do not iterate + // cache entries. Since this moves forward to the next storage in + // the list we want to visit, artificially call it here. + OnCacheEntryVisitCompleted(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCache::Channel::OnCacheEntryInfo( + nsIURI* aURI, const nsACString& aIdEnhance, int64_t aDataSize, + int64_t aAltDataSize, uint32_t aFetchCount, uint32_t aLastModified, + uint32_t aExpirationTime, bool aPinned, nsILoadContextInfo* aInfo) { + // We need mStream for this + if (!mStream || mCancel) { + // Returning a failure from this callback stops the iteration + return NS_ERROR_FAILURE; + } + + if (!mEntriesHeaderAdded) { + mBuffer.AppendLiteral( + "<hr/>\n" + "<table id=\"entries\">\n" + " <colgroup>\n" + " <col id=\"col-key\">\n" + " <col id=\"col-dataSize\">\n" + " <col id=\"col-altDataSize\">\n" + " <col id=\"col-fetchCount\">\n" + " <col id=\"col-lastModified\">\n" + " <col id=\"col-expires\">\n" + " <col id=\"col-pinned\">\n" + " </colgroup>\n" + " <thead>\n" + " <tr>\n" + " <th>Key</th>\n" + " <th>Data size</th>\n" + " <th>Alternative Data size</th>\n" + " <th>Fetch count</th>\n" + " <th>Last Modifed</th>\n" + " <th>Expires</th>\n" + " <th>Pinning</th>\n" + " </tr>\n" + " </thead>\n"); + mEntriesHeaderAdded = true; + } + + // Generate a about:cache-entry URL for this entry... + + nsAutoCString url; + url.AssignLiteral("about:cache-entry?storage="); + nsAppendEscapedHTML(mStorageName, url); + + nsAutoCString context; + CacheFileUtils::AppendKeyPrefix(aInfo, context); + url.AppendLiteral("&context="); + nsAppendEscapedHTML(context, url); + + url.AppendLiteral("&eid="); + nsAppendEscapedHTML(aIdEnhance, url); + + nsAutoCString cacheUriSpec; + aURI->GetAsciiSpec(cacheUriSpec); + nsAutoCString escapedCacheURI; + nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI); + url.AppendLiteral("&uri="); + url += escapedCacheURI; + + // Entry start... + mBuffer.AppendLiteral(" <tr>\n"); + + // URI + mBuffer.AppendLiteral(" <td><a href=\""); + mBuffer.Append(url); + mBuffer.AppendLiteral("\">"); + if (!aIdEnhance.IsEmpty()) { + nsAppendEscapedHTML(aIdEnhance, mBuffer); + mBuffer.Append(':'); + } + mBuffer.Append(escapedCacheURI); + mBuffer.AppendLiteral("</a>"); + + if (!context.IsEmpty()) { + mBuffer.AppendLiteral("<br><span title=\"Context separation key\">"); + nsAutoCString escapedContext; + nsAppendEscapedHTML(context, escapedContext); + mBuffer.Append(escapedContext); + mBuffer.AppendLiteral("</span>"); + } + + mBuffer.AppendLiteral("</td>\n"); + + // Content length + mBuffer.AppendLiteral(" <td>"); + mBuffer.AppendInt(aDataSize); + mBuffer.AppendLiteral(" bytes</td>\n"); + + // Length of alternative content + mBuffer.AppendLiteral(" <td>"); + mBuffer.AppendInt(aAltDataSize); + mBuffer.AppendLiteral(" bytes</td>\n"); + + // Number of accesses + mBuffer.AppendLiteral(" <td>"); + mBuffer.AppendInt(aFetchCount); + mBuffer.AppendLiteral("</td>\n"); + + // vars for reporting time + char buf[255]; + + // Last modified time + mBuffer.AppendLiteral(" <td>"); + if (aLastModified) { + PrintTimeString(buf, sizeof(buf), aLastModified); + mBuffer.Append(buf); + } else { + mBuffer.AppendLiteral("No last modified time"); + } + mBuffer.AppendLiteral("</td>\n"); + + // Expires time + mBuffer.AppendLiteral(" <td>"); + + // Bug - 633747. + // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing. + // So we check if time is 0, then we show a message, "Expired Immediately" + if (aExpirationTime == 0) { + mBuffer.AppendLiteral("Expired Immediately"); + } else if (aExpirationTime < 0xFFFFFFFF) { + PrintTimeString(buf, sizeof(buf), aExpirationTime); + mBuffer.Append(buf); + } else { + mBuffer.AppendLiteral("No expiration time"); + } + mBuffer.AppendLiteral("</td>\n"); + + // Pinning + mBuffer.AppendLiteral(" <td>"); + if (aPinned) { + mBuffer.AppendLiteral("Pinned"); + } else { + mBuffer.AppendLiteral(" "); + } + mBuffer.AppendLiteral("</td>\n"); + + // Entry is done... + mBuffer.AppendLiteral(" </tr>\n"); + + return FlushBuffer(); +} + +NS_IMETHODIMP +nsAboutCache::Channel::OnCacheEntryVisitCompleted() { + if (!mStream) { + return NS_ERROR_FAILURE; + } + + if (mEntriesHeaderAdded) { + mBuffer.AppendLiteral("</table>\n"); + } + + // Kick another storage visiting (from a storage that allows us.) + while (mStorageList.Length()) { + nsresult rv = VisitNextStorage(); + if (NS_SUCCEEDED(rv)) { + // Expecting new round of OnCache* calls. + return NS_OK; + } + } + + // We are done! + mBuffer.AppendLiteral( + "</body>\n" + "</html>\n"); + nsresult rv = FlushBuffer(); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to flush buffer"); + } + mStream->Close(); + + return NS_OK; +} + +nsresult nsAboutCache::Channel::FlushBuffer() { + nsresult rv; + + uint32_t bytesWritten; + rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten); + mBuffer.Truncate(); + + if (NS_FAILED(rv)) { + mCancel = true; + } + + return rv; +} + +NS_IMETHODIMP +nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) { + *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::IS_SECURE_CHROME_UI; + return NS_OK; +} + +// static +nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) { + RefPtr<nsAboutCache> about = new nsAboutCache(); + return about->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { + return NS_ERROR_ILLEGAL_VALUE; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/protocol/about/nsAboutCache.h b/netwerk/protocol/about/nsAboutCache.h new file mode 100644 index 0000000000..65292b9aae --- /dev/null +++ b/netwerk/protocol/about/nsAboutCache.h @@ -0,0 +1,199 @@ +/* -*- 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 nsAboutCache_h__ +#define nsAboutCache_h__ + +#include "nsIAboutModule.h" +#include "nsICacheStorageVisitor.h" +#include "nsICacheStorage.h" + +#include "nsString.h" +#include "nsIChannel.h" +#include "nsIOutputStream.h" +#include "nsILoadContextInfo.h" + +#include "nsCOMPtr.h" +#include "nsTArray.h" + +#define NS_FORWARD_SAFE_NSICHANNEL_SUBSET(_to) \ + NS_IMETHOD GetOriginalURI(nsIURI** aOriginalURI) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetOriginalURI(aOriginalURI); \ + } \ + NS_IMETHOD SetOriginalURI(nsIURI* aOriginalURI) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetOriginalURI(aOriginalURI); \ + } \ + NS_IMETHOD GetURI(nsIURI** aURI) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetURI(aURI); \ + } \ + NS_IMETHOD GetOwner(nsISupports** aOwner) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetOwner(aOwner); \ + } \ + NS_IMETHOD SetOwner(nsISupports* aOwner) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetOwner(aOwner); \ + } \ + NS_IMETHOD GetNotificationCallbacks( \ + nsIInterfaceRequestor** aNotificationCallbacks) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetNotificationCallbacks(aNotificationCallbacks); \ + } \ + NS_IMETHOD SetNotificationCallbacks( \ + nsIInterfaceRequestor* aNotificationCallbacks) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetNotificationCallbacks(aNotificationCallbacks); \ + } \ + NS_IMETHOD GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) \ + override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetSecurityInfo(aSecurityInfo); \ + } \ + NS_IMETHOD GetContentType(nsACString& aContentType) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentType(aContentType); \ + } \ + NS_IMETHOD SetContentType(const nsACString& aContentType) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetContentType(aContentType); \ + } \ + NS_IMETHOD GetContentCharset(nsACString& aContentCharset) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentCharset(aContentCharset); \ + } \ + NS_IMETHOD SetContentCharset(const nsACString& aContentCharset) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetContentCharset(aContentCharset); \ + } \ + NS_IMETHOD GetContentLength(int64_t* aContentLength) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentLength(aContentLength); \ + } \ + NS_IMETHOD SetContentLength(int64_t aContentLength) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetContentLength(aContentLength); \ + } \ + NS_IMETHOD GetContentDisposition(uint32_t* aContentDisposition) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentDisposition(aContentDisposition); \ + } \ + NS_IMETHOD SetContentDisposition(uint32_t aContentDisposition) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetContentDisposition(aContentDisposition); \ + } \ + NS_IMETHOD GetContentDispositionFilename( \ + nsAString& aContentDispositionFilename) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentDispositionFilename( \ + aContentDispositionFilename); \ + } \ + NS_IMETHOD SetContentDispositionFilename( \ + const nsAString& aContentDispositionFilename) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->SetContentDispositionFilename( \ + aContentDispositionFilename); \ + } \ + NS_IMETHOD GetContentDispositionHeader( \ + nsACString& aContentDispositionHeader) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER \ + : (_to)->GetContentDispositionHeader( \ + aContentDispositionHeader); \ + } \ + NS_IMETHOD GetLoadInfo(nsILoadInfo** aLoadInfo) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetLoadInfo(aLoadInfo); \ + } \ + NS_IMETHOD SetLoadInfo(nsILoadInfo* aLoadInfo) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->SetLoadInfo(aLoadInfo); \ + } \ + NS_IMETHOD GetIsDocument(bool* aIsDocument) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetIsDocument(aIsDocument); \ + } \ + NS_IMETHOD GetCanceled(bool* aCanceled) override { \ + return !(_to) ? NS_ERROR_NULL_POINTER : (_to)->GetCanceled(aCanceled); \ + }; + +class nsAboutCache final : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABOUTMODULE + + nsAboutCache() = default; + + [[nodiscard]] static nsresult Create(REFNSIID aIID, void** aResult); + + [[nodiscard]] static nsresult GetStorage(nsACString const& storageName, + nsILoadContextInfo* loadInfo, + nsICacheStorage** storage); + + protected: + virtual ~nsAboutCache() = default; + + class Channel final : public nsIChannel, public nsICacheStorageVisitor { + NS_DECL_ISUPPORTS + NS_DECL_NSICACHESTORAGEVISITOR + NS_FORWARD_SAFE_NSIREQUEST(mChannel) + NS_FORWARD_SAFE_NSICHANNEL_SUBSET(mChannel) + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + NS_IMETHOD Open(nsIInputStream** _retval) override; + + private: + virtual ~Channel() = default; + + public: + [[nodiscard]] nsresult Init(nsIURI* aURI, nsILoadInfo* aLoadInfo); + [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storage); + + // Finds a next storage we wish to visit (we use this method + // even there is a specified storage name, which is the only + // one in the list then.) Posts FireVisitStorage() when found. + [[nodiscard]] nsresult VisitNextStorage(); + // Helper method that calls VisitStorage() for the current storage. + // When it fails, OnCacheEntryVisitCompleted is simulated to close + // the output stream and thus the about:cache channel. + void FireVisitStorage(); + // Kiks the visit cycle for the given storage, names can be: + // "disk", "memory", "appcache" + // Note: any newly added storage type has to be manually handled here. + [[nodiscard]] nsresult VisitStorage(nsACString const& storageName); + + // Writes content of mBuffer to mStream and truncates + // the buffer. It may fail when the input stream is closed by canceling + // the input stream channel. It can be used to stop the cache iteration + // process. + [[nodiscard]] nsresult FlushBuffer(); + + // Whether we are showing overview status of all available + // storages. + bool mOverview = false; + + // Flag initially false, that indicates the entries header has + // been added to the output HTML. + bool mEntriesHeaderAdded = false; + + // Cancelation flag + bool mCancel = false; + + // The list of all storage names we want to visit + nsTArray<nsCString> mStorageList; + nsCString mStorageName; + nsCOMPtr<nsICacheStorage> mStorage; + + // Output data buffering and streaming output + nsCString mBuffer; + nsCOMPtr<nsIOutputStream> mStream; + + // The input stream channel, the one that actually does the job + nsCOMPtr<nsIChannel> mChannel; + }; +}; + +#define NS_ABOUT_CACHE_MODULE_CID \ + { /* 9158c470-86e4-11d4-9be2-00e09872a416 */ \ + 0x9158c470, 0x86e4, 0x11d4, { \ + 0x9b, 0xe2, 0x00, 0xe0, 0x98, 0x72, 0xa4, 0x16 \ + } \ + } + +#endif // nsAboutCache_h__ diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp new file mode 100644 index 0000000000..08dfcf6ca0 --- /dev/null +++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp @@ -0,0 +1,559 @@ +/* -*- Mode: C++; tab-width: 4; 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 <algorithm> + +#include "nsAboutCacheEntry.h" + +#include "CacheFileUtils.h" +#include "CacheObserver.h" +#include "mozilla/Sprintf.h" +#include "nsAboutCache.h" +#include "nsAboutProtocolUtils.h" +#include "nsContentUtils.h" +#include "nsEscape.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsICacheStorage.h" +#include "nsIPipe.h" +#include "nsITransportSecurityInfo.h" +#include "nsInputStreamPump.h" +#include "nsNetUtil.h" + +using namespace mozilla::net; + +#define HEXDUMP_MAX_ROWS 16 + +static void HexDump(uint32_t* state, const char* buf, int32_t n, + nsCString& result) { + char temp[16]; + + const unsigned char* p; + while (n) { + SprintfLiteral(temp, "%08x: ", *state); + result.Append(temp); + *state += HEXDUMP_MAX_ROWS; + + p = (const unsigned char*)buf; + + int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n); + + // print hex codes: + for (i = 0; i < row_max; ++i) { + SprintfLiteral(temp, "%02x ", *p++); + result.Append(temp); + } + for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) { + result.AppendLiteral(" "); + } + + // print ASCII glyphs if possible: + p = (const unsigned char*)buf; + for (i = 0; i < row_max; ++i, ++p) { + switch (*p) { + case '<': + result.AppendLiteral("<"); + break; + case '>': + result.AppendLiteral(">"); + break; + case '&': + result.AppendLiteral("&"); + break; + default: + if (*p < 0x7F && *p > 0x1F) { + result.Append(*p); + } else { + result.Append('.'); + } + } + } + + result.Append('\n'); + + buf += row_max; + n -= row_max; + } +} + +//----------------------------------------------------------------------------- +// nsAboutCacheEntry::nsISupports + +NS_IMPL_ISUPPORTS(nsAboutCacheEntry, nsIAboutModule) +NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel, nsICacheEntryOpenCallback, + nsICacheEntryMetaDataVisitor, nsIStreamListener, nsIRequest, + nsIChannel) + +//----------------------------------------------------------------------------- +// nsAboutCacheEntry::nsIAboutModule + +NS_IMETHODIMP +nsAboutCacheEntry::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + NS_ENSURE_ARG_POINTER(uri); + nsresult rv; + + RefPtr<Channel> channel = new Channel(); + rv = channel->Init(uri, aLoadInfo); + if (NS_FAILED(rv)) return rv; + + channel.forget(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCacheEntry::GetURIFlags(nsIURI* aURI, uint32_t* result) { + *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT; + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCacheEntry::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { + return NS_ERROR_ILLEGAL_VALUE; +} + +//----------------------------------------------------------------------------- +// nsAboutCacheEntry::Channel + +nsresult nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) { + nsresult rv; + + nsCOMPtr<nsIInputStream> stream; + rv = GetContentStream(uri, getter_AddRefs(stream)); + if (NS_FAILED(rv)) return rv; + + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), uri, + stream.forget(), "text/html"_ns, + "utf-8"_ns, aLoadInfo); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +nsresult nsAboutCacheEntry::Channel::GetContentStream(nsIURI* uri, + nsIInputStream** result) { + nsresult rv; + + // Init: (block size, maximum length) + nsCOMPtr<nsIAsyncInputStream> inputStream; + NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(mOutputStream), true, + false, 256, UINT32_MAX); + + constexpr auto buffer = + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src " + "chrome:; object-src 'none'\" />\n" + " <meta name=\"color-scheme\" content=\"light dark\" />\n" + " <title>Cache entry information</title>\n" + " <link rel=\"stylesheet\" " + "href=\"chrome://global/skin/in-content/info-pages.css\" " + "type=\"text/css\"/>\n" + " <link rel=\"stylesheet\" " + "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n" + "</head>\n" + "<body>\n" + "<h1>Cache entry information</h1>\n"_ns; + uint32_t n; + rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n); + if (NS_FAILED(rv)) return rv; + if (n != buffer.Length()) return NS_ERROR_UNEXPECTED; + + rv = OpenCacheEntry(uri); + if (NS_FAILED(rv)) return rv; + + inputStream.forget(result); + return NS_OK; +} + +nsresult nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI* uri) { + nsresult rv; + + rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo), mEnhanceId, + getter_AddRefs(mCacheURI)); + if (NS_FAILED(rv)) return rv; + + return OpenCacheEntry(); +} + +nsresult nsAboutCacheEntry::Channel::OpenCacheEntry() { + nsresult rv; + + nsCOMPtr<nsICacheStorage> storage; + rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, + getter_AddRefs(storage)); + if (NS_FAILED(rv)) return rv; + + // Invokes OnCacheEntryAvailable() + rv = storage->AsyncOpenURI( + mCacheURI, mEnhanceId, + nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +nsresult nsAboutCacheEntry::Channel::ParseURI(nsIURI* uri, + nsACString& storageName, + nsILoadContextInfo** loadInfo, + nsCString& enahnceID, + nsIURI** cacheUri) { + // + // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string] + // + nsresult rv; + + nsAutoCString path; + rv = uri->GetPathQueryRef(path); + if (NS_FAILED(rv)) return rv; + + nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end; + path.BeginReading(begin); + path.EndReading(end); + + keyBegin = begin; + keyEnd = end; + if (!FindInReadable("?storage="_ns, keyBegin, keyEnd)) { + return NS_ERROR_FAILURE; + } + + valBegin = keyEnd; // the value of the storage key starts after the key + + keyBegin = keyEnd; + keyEnd = end; + if (!FindInReadable("&context="_ns, keyBegin, keyEnd)) { + return NS_ERROR_FAILURE; + } + + storageName.Assign(Substring(valBegin, keyBegin)); + valBegin = keyEnd; // the value of the context key starts after the key + + keyBegin = keyEnd; + keyEnd = end; + if (!FindInReadable("&eid="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE; + + nsAutoCString contextKey(Substring(valBegin, keyBegin)); + valBegin = keyEnd; // the value of the eid key starts after the key + + keyBegin = keyEnd; + keyEnd = end; + if (!FindInReadable("&uri="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE; + + enahnceID.Assign(Substring(valBegin, keyBegin)); + + valBegin = keyEnd; // the value of the uri key starts after the key + nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one + + // Uf... parsing done, now get some objects from it... + + nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(contextKey); + if (!info) return NS_ERROR_FAILURE; + info.forget(loadInfo); + + rv = NS_NewURI(cacheUri, uriSpec); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsICacheEntryOpenCallback implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry* aEntry, + uint32_t* result) { + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry* entry, + bool isNew, nsresult status) { + nsresult rv; + + mWaitingForData = false; + if (entry) { + rv = WriteCacheEntryDescription(entry); + } else { + rv = WriteCacheEntryUnavailable(); + } + if (NS_FAILED(rv)) return rv; + + if (!mWaitingForData) { + // Data is not expected, close the output of content now. + CloseContent(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// Print-out helper methods +//----------------------------------------------------------------------------- + +#define APPEND_ROW(label, value) \ + PR_BEGIN_MACRO \ + buffer.AppendLiteral( \ + " <tr>\n" \ + " <th>"); \ + buffer.AppendLiteral(label); \ + buffer.AppendLiteral( \ + ":</th>\n" \ + " <td>"); \ + buffer.Append(value); \ + buffer.AppendLiteral( \ + "</td>\n" \ + " </tr>\n"); \ + PR_END_MACRO + +nsresult nsAboutCacheEntry::Channel::WriteCacheEntryDescription( + nsICacheEntry* entry) { + nsresult rv; + // This method appears to run in a situation where the run-time stack + // should have plenty of space, so allocating a large string on the + // stack is OK. + nsAutoCStringN<4097> buffer; + uint32_t n; + + nsAutoCString str; + + rv = entry->GetKey(str); + if (NS_FAILED(rv)) return rv; + + buffer.AssignLiteral( + "<table>\n" + " <tr>\n" + " <th>key:</th>\n" + " <td id=\"td-key\">"); + + // Test if the key is actually a URI + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), str); + + nsAutoCString escapedStr; + nsAppendEscapedHTML(str, escapedStr); + + // javascript: and data: URLs should not be linkified + // since clicking them can cause scripts to run - bug 162584 + if (NS_SUCCEEDED(rv) && + !(uri->SchemeIs("javascript") || uri->SchemeIs("data"))) { + buffer.AppendLiteral("<a href=\""); + buffer.Append(escapedStr); + buffer.AppendLiteral("\">"); + buffer.Append(escapedStr); + buffer.AppendLiteral("</a>"); + uri = nullptr; + } else { + buffer.Append(escapedStr); + } + buffer.AppendLiteral( + "</td>\n" + " </tr>\n"); + + // temp vars for reporting + char timeBuf[255]; + uint32_t u = 0; + nsAutoCString s; + + // Fetch Count + s.Truncate(); + entry->GetFetchCount(&u); + s.AppendInt(u); + APPEND_ROW("fetch count", s); + + // Last Fetched + entry->GetLastFetched(&u); + if (u) { + PrintTimeString(timeBuf, sizeof(timeBuf), u); + APPEND_ROW("last fetched", timeBuf); + } else { + APPEND_ROW("last fetched", "No last fetch time (bug 1000338)"); + } + + // Last Modified + entry->GetLastModified(&u); + if (u) { + PrintTimeString(timeBuf, sizeof(timeBuf), u); + APPEND_ROW("last modified", timeBuf); + } else { + APPEND_ROW("last modified", "No last modified time (bug 1000338)"); + } + + // Expiration Time + entry->GetExpirationTime(&u); + + // Bug - 633747. + // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing. + // So we check if time is 0, then we show a message, "Expired Immediately" + if (u == 0) { + APPEND_ROW("expires", "Expired Immediately"); + } else if (u < 0xFFFFFFFF) { + PrintTimeString(timeBuf, sizeof(timeBuf), u); + APPEND_ROW("expires", timeBuf); + } else { + APPEND_ROW("expires", "No expiration time"); + } + + // Data Size + s.Truncate(); + uint32_t dataSize; + if (NS_FAILED(entry->GetStorageDataSize(&dataSize))) dataSize = 0; + s.AppendInt( + (int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed. + s.AppendLiteral(" B"); + APPEND_ROW("Data size", s); + + // TODO - mayhemer + // Here used to be a link to the disk file (in the old cache for entries that + // did not fit any of the block files, in the new cache every time). + // I'd rather have a small set of buttons here to action on the entry: + // 1. save the content + // 2. save as a complete HTTP response (response head, headers, content) + // 3. doom the entry + // A new bug(s) should be filed here. + + // Security Info + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + entry->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) { + APPEND_ROW("Security", "This is a secure document."); + } else { + APPEND_ROW( + "Security", + "This document does not have any security info associated with it."); + } + + buffer.AppendLiteral( + "</table>\n" + "<hr/>\n" + "<table>\n"); + + mBuffer = &buffer; // make it available for OnMetaDataElement(). + entry->VisitMetaData(this); + mBuffer = nullptr; + + buffer.AppendLiteral("</table>\n"); + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + buffer.Truncate(); + + // Provide a hexdump of the data + if (!dataSize) { + return NS_OK; + } + + nsCOMPtr<nsIInputStream> stream; + entry->OpenInputStream(0, getter_AddRefs(stream)); + if (!stream) { + return NS_OK; + } + + RefPtr<nsInputStreamPump> pump; + rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream); + if (NS_FAILED(rv)) { + return NS_OK; // just ignore + } + + rv = pump->AsyncRead(this); + if (NS_FAILED(rv)) { + return NS_OK; // just ignore + } + + mWaitingForData = true; + return NS_OK; +} + +nsresult nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable() { + uint32_t n; + constexpr auto buffer = "The cache entry you selected is not available."_ns; + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsICacheEntryMetaDataVisitor implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnMetaDataElement(char const* key, + char const* value) { + mBuffer->AppendLiteral( + " <tr>\n" + " <th>"); + mBuffer->Append(key); + mBuffer->AppendLiteral( + ":</th>\n" + " <td>"); + nsAppendEscapedHTML(nsDependentCString(value), *mBuffer); + mBuffer->AppendLiteral( + "</td>\n" + " </tr>\n"); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIStreamListener implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest* request) { + mHexDumpState = 0; + + constexpr auto buffer = "<hr/>\n<pre>"_ns; + uint32_t n; + return mOutputStream->Write(buffer.get(), buffer.Length(), &n); +} + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest* request, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + uint32_t n; + return aInputStream->ReadSegments(&nsAboutCacheEntry::Channel::PrintCacheData, + this, aCount, &n); +} + +/* static */ +nsresult nsAboutCacheEntry::Channel::PrintCacheData( + nsIInputStream* aInStream, void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) { + nsAboutCacheEntry::Channel* a = + static_cast<nsAboutCacheEntry::Channel*>(aClosure); + + nsCString buffer; + HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer); + + uint32_t n; + a->mOutputStream->Write(buffer.get(), buffer.Length(), &n); + + *aWriteCount = aCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest* request, + nsresult result) { + constexpr auto buffer = "</pre>\n"_ns; + uint32_t n; + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + + CloseContent(); + + return NS_OK; +} + +void nsAboutCacheEntry::Channel::CloseContent() { + constexpr auto buffer = "</body>\n</html>\n"_ns; + uint32_t n; + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + + mOutputStream->Close(); + mOutputStream = nullptr; +} diff --git a/netwerk/protocol/about/nsAboutCacheEntry.h b/netwerk/protocol/about/nsAboutCacheEntry.h new file mode 100644 index 0000000000..76f8649061 --- /dev/null +++ b/netwerk/protocol/about/nsAboutCacheEntry.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; 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 nsAboutCacheEntry_h__ +#define nsAboutCacheEntry_h__ + +#include "nsIAboutModule.h" +#include "nsICacheEntryOpenCallback.h" +#include "nsICacheEntry.h" +#include "nsIStreamListener.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" + +class nsIAsyncOutputStream; +class nsIInputStream; +class nsILoadContextInfo; +class nsIURI; + +class nsAboutCacheEntry final : public nsIAboutModule { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABOUTMODULE + + private: + virtual ~nsAboutCacheEntry() = default; + + class Channel final : public nsICacheEntryOpenCallback, + public nsICacheEntryMetaDataVisitor, + public nsIStreamListener, + public nsIChannel { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYOPENCALLBACK + NS_DECL_NSICACHEENTRYMETADATAVISITOR + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_FORWARD_SAFE_NSICHANNEL(mChannel) + NS_FORWARD_SAFE_NSIREQUEST(mChannel) + + Channel() = default; + + private: + virtual ~Channel() = default; + + public: + [[nodiscard]] nsresult Init(nsIURI* uri, nsILoadInfo* aLoadInfo); + + [[nodiscard]] nsresult GetContentStream(nsIURI*, nsIInputStream**); + [[nodiscard]] nsresult OpenCacheEntry(nsIURI*); + [[nodiscard]] nsresult OpenCacheEntry(); + [[nodiscard]] nsresult WriteCacheEntryDescription(nsICacheEntry*); + [[nodiscard]] nsresult WriteCacheEntryUnavailable(); + [[nodiscard]] nsresult ParseURI(nsIURI* uri, nsACString& storageName, + nsILoadContextInfo** loadInfo, + nsCString& enahnceID, nsIURI** cacheUri); + void CloseContent(); + + [[nodiscard]] static nsresult PrintCacheData( + nsIInputStream* aInStream, void* aClosure, const char* aFromSegment, + uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount); + + private: + nsCString mStorageName, mEnhanceId; + nsCOMPtr<nsILoadContextInfo> mLoadInfo; + nsCOMPtr<nsIURI> mCacheURI; + + nsCString* mBuffer{nullptr}; + nsCOMPtr<nsIAsyncOutputStream> mOutputStream; + bool mWaitingForData{false}; + uint32_t mHexDumpState{0}; + + nsCOMPtr<nsIChannel> mChannel; + }; +}; + +#define NS_ABOUT_CACHE_ENTRY_MODULE_CID \ + { /* 7fa5237d-b0eb-438f-9e50-ca0166e63788 */ \ + 0x7fa5237d, 0xb0eb, 0x438f, { \ + 0x9e, 0x50, 0xca, 0x01, 0x66, 0xe6, 0x37, 0x88 \ + } \ + } + +#endif // nsAboutCacheEntry_h__ diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.cpp b/netwerk/protocol/about/nsAboutProtocolHandler.cpp new file mode 100644 index 0000000000..fe69c7ec02 --- /dev/null +++ b/netwerk/protocol/about/nsAboutProtocolHandler.cpp @@ -0,0 +1,414 @@ +/* -*- 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 "base/basictypes.h" +#include "mozilla/ArrayUtils.h" + +#include "nsAboutProtocolHandler.h" +#include "nsIURI.h" +#include "nsIAboutModule.h" +#include "nsContentUtils.h" +#include "nsString.h" +#include "nsNetCID.h" +#include "nsAboutProtocolUtils.h" +#include "nsError.h" +#include "nsNetUtil.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIChannel.h" +#include "nsIScriptError.h" +#include "nsIClassInfoImpl.h" + +#include "mozilla/ipc/URIUtils.h" + +namespace mozilla { +namespace net { + +static NS_DEFINE_CID(kNestedAboutURICID, NS_NESTEDABOUTURI_CID); + +static bool IsSafeForUntrustedContent(nsIAboutModule* aModule, nsIURI* aURI) { + uint32_t flags; + nsresult rv = aModule->GetURIFlags(aURI, &flags); + NS_ENSURE_SUCCESS(rv, false); + + return (flags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) != 0; +} + +static bool IsSafeToLinkForUntrustedContent(nsIURI* aURI) { + nsAutoCString path; + aURI->GetPathQueryRef(path); + + int32_t f = path.FindChar('#'); + if (f >= 0) { + path.SetLength(f); + } + + f = path.FindChar('?'); + if (f >= 0) { + path.SetLength(f); + } + + ToLowerCase(path); + + // The about modules for these URL types have the + // URI_SAFE_FOR_UNTRUSTED_CONTENT and MAKE_LINKABLE flags set. + return path.EqualsLiteral("blank") || path.EqualsLiteral("logo") || + path.EqualsLiteral("srcdoc"); +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsAboutProtocolHandler, nsIProtocolHandler, + nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference) + +//////////////////////////////////////////////////////////////////////////////// +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsAboutProtocolHandler::GetScheme(nsACString& result) { + result.AssignLiteral("about"); + return NS_OK; +} + +NS_IMETHODIMP +nsAboutProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags) { + // First use the default (which is "unsafe for content"): + *aFlags = URI_NORELATIVE | URI_NOAUTH | URI_DANGEROUS_TO_LOAD | + URI_SCHEME_NOT_SELF_LINKABLE; + + // Now try to see if this URI overrides the default: + nsCOMPtr<nsIAboutModule> aboutMod; + nsresult rv = NS_GetAboutModule(aURI, getter_AddRefs(aboutMod)); + if (NS_FAILED(rv)) { + // Swallow this and just tell the consumer the default: + return NS_OK; + } + uint32_t aboutModuleFlags = 0; + rv = aboutMod->GetURIFlags(aURI, &aboutModuleFlags); + // This should never happen, so pass back the error: + NS_ENSURE_SUCCESS(rv, rv); + + // Secure (https) pages can load safe about pages without becoming + // mixed content. + if (aboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT) { + *aFlags |= URI_IS_POTENTIALLY_TRUSTWORTHY; + // about: pages can only be loaded by unprivileged principals + // if they are marked as LINKABLE + if (aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { + // Replace URI_DANGEROUS_TO_LOAD with URI_LOADABLE_BY_ANYONE. + *aFlags &= ~URI_DANGEROUS_TO_LOAD; + *aFlags |= URI_LOADABLE_BY_ANYONE; + } + } + return NS_OK; +} + +// static +nsresult nsAboutProtocolHandler::CreateNewURI(const nsACString& aSpec, + const char* aCharset, + nsIURI* aBaseURI, + nsIURI** aResult) { + *aResult = nullptr; + nsresult rv; + + // Use a simple URI to parse out some stuff first + nsCOMPtr<nsIURI> url; + rv = NS_MutateURI(new nsSimpleURI::Mutator()).SetSpec(aSpec).Finalize(url); + + if (NS_FAILED(rv)) { + return rv; + } + + if (IsSafeToLinkForUntrustedContent(url)) { + // We need to indicate that this baby is safe. Use an inner URI that + // no one but the security manager will see. Make sure to preserve our + // path, in case someone decides to hardcode checks for particular + // about: URIs somewhere. + nsAutoCString spec; + rv = url->GetPathQueryRef(spec); + NS_ENSURE_SUCCESS(rv, rv); + + spec.InsertLiteral("moz-safe-about:", 0); + + nsCOMPtr<nsIURI> inner; + rv = NS_NewURI(getter_AddRefs(inner), spec); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_MutateURI(new nsNestedAboutURI::Mutator()) + .Apply(&nsINestedAboutURIMutator::InitWithBase, inner, aBaseURI) + .SetSpec(aSpec) + .Finalize(url); + NS_ENSURE_SUCCESS(rv, rv); + } + + url.swap(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + NS_ENSURE_ARG_POINTER(uri); + + // about:what you ask? + nsCOMPtr<nsIAboutModule> aboutMod; + nsresult rv = NS_GetAboutModule(uri, getter_AddRefs(aboutMod)); + + nsAutoCString path; + nsresult rv2 = NS_GetAboutModuleName(uri, path); + if (NS_SUCCEEDED(rv2) && path.EqualsLiteral("srcdoc")) { + // about:srcdoc is meant to be unresolvable, yet is included in the + // about lookup tables so that it can pass security checks when used in + // a srcdoc iframe. To ensure that it stays unresolvable, we pretend + // that it doesn't exist. + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (NS_SUCCEEDED(rv)) { + // The standard return case: + rv = aboutMod->NewChannel(uri, aLoadInfo, result); + if (NS_SUCCEEDED(rv)) { + // Not all implementations of nsIAboutModule::NewChannel() + // set the LoadInfo on the newly created channel yet, as + // an interim solution we set the LoadInfo here if not + // available on the channel. Bug 1087720 + nsCOMPtr<nsILoadInfo> loadInfo = (*result)->LoadInfo(); + if (aLoadInfo != loadInfo) { + NS_ASSERTION(false, + "nsIAboutModule->newChannel(aURI, aLoadInfo) needs to " + "set LoadInfo"); + AutoTArray<nsString, 2> params = { + u"nsIAboutModule->newChannel(aURI)"_ns, + u"nsIAboutModule->newChannel(aURI, aLoadInfo)"_ns}; + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, "Security by Default"_ns, + nullptr, // aDocument + nsContentUtils::eNECKO_PROPERTIES, "APIDeprecationWarning", params); + (*result)->SetLoadInfo(aLoadInfo); + } + + // If this URI is safe for untrusted content, enforce that its + // principal be based on the channel's originalURI by setting the + // owner to null. + // Note: this relies on aboutMod's newChannel implementation + // having set the proper originalURI, which probably isn't ideal. + if (IsSafeForUntrustedContent(aboutMod, uri)) { + (*result)->SetOwner(nullptr); + } + + RefPtr<nsNestedAboutURI> aboutURI; + nsresult rv2 = + uri->QueryInterface(kNestedAboutURICID, getter_AddRefs(aboutURI)); + if (NS_SUCCEEDED(rv2) && aboutURI->GetBaseURI()) { + nsCOMPtr<nsIWritablePropertyBag2> writableBag = + do_QueryInterface(*result); + if (writableBag) { + writableBag->SetPropertyAsInterface(u"baseURI"_ns, + aboutURI->GetBaseURI()); + } + } + } + return rv; + } + + // mumble... + + if (rv == NS_ERROR_FACTORY_NOT_REGISTERED) { + // This looks like an about: we don't know about. Convert + // this to an invalid URI error. + rv = NS_ERROR_MALFORMED_URI; + } + + return rv; +} + +NS_IMETHODIMP +nsAboutProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// Safe about protocol handler impl + +NS_IMPL_ISUPPORTS(nsSafeAboutProtocolHandler, nsIProtocolHandler, + nsISupportsWeakReference) + +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsSafeAboutProtocolHandler::GetScheme(nsACString& result) { + result.AssignLiteral("moz-safe-about"); + return NS_OK; +} + +NS_IMETHODIMP +nsSafeAboutProtocolHandler::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + *result = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsSafeAboutProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +//////////////////////////////////////////////////////////// +// nsNestedAboutURI implementation + +NS_IMPL_CLASSINFO(nsNestedAboutURI, nullptr, nsIClassInfo::THREADSAFE, + NS_NESTEDABOUTURI_CID); +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(nsNestedAboutURI) + +NS_INTERFACE_MAP_BEGIN(nsNestedAboutURI) + if (aIID.Equals(kNestedAboutURICID)) { + foundInterface = static_cast<nsIURI*>(this); + } else + NS_IMPL_QUERY_CLASSINFO(nsNestedAboutURI) +NS_INTERFACE_MAP_END_INHERITING(nsSimpleNestedURI) + +// nsISerializable + +NS_IMETHODIMP +nsNestedAboutURI::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult nsNestedAboutURI::ReadPrivate(nsIObjectInputStream* aStream) { + nsresult rv = nsSimpleNestedURI::ReadPrivate(aStream); + if (NS_FAILED(rv)) return rv; + + bool haveBase; + rv = aStream->ReadBoolean(&haveBase); + if (NS_FAILED(rv)) return rv; + + if (haveBase) { + nsCOMPtr<nsISupports> supports; + rv = aStream->ReadObject(true, getter_AddRefs(supports)); + if (NS_FAILED(rv)) return rv; + + mBaseURI = do_QueryInterface(supports, &rv); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsNestedAboutURI::Write(nsIObjectOutputStream* aStream) { + nsresult rv = nsSimpleNestedURI::Write(aStream); + if (NS_FAILED(rv)) return rv; + + rv = aStream->WriteBoolean(mBaseURI != nullptr); + if (NS_FAILED(rv)) return rv; + + if (mBaseURI) { + // A previous iteration of this code wrote out mBaseURI as nsISupports + // and then read it in as nsIURI, which is non-kosher when mBaseURI + // implements more than just a single line of interfaces and the + // canonical nsISupports* isn't the one a static_cast<> of mBaseURI + // would produce. For backwards compatibility with existing + // serializations we continue to write mBaseURI as nsISupports but + // switch to reading it as nsISupports, with a post-read QI to get to + // nsIURI. + rv = aStream->WriteCompoundObject(mBaseURI, NS_GET_IID(nsISupports), true); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP_(void) +nsNestedAboutURI::Serialize(mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + NestedAboutURIParams params; + URIParams nestedParams; + + nsSimpleNestedURI::Serialize(nestedParams); + params.nestedParams() = nestedParams; + + if (mBaseURI) { + SerializeURI(mBaseURI, params.baseURI()); + } + + aParams = params; +} + +bool nsNestedAboutURI::Deserialize(const mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + if (aParams.type() != URIParams::TNestedAboutURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const NestedAboutURIParams& params = aParams.get_NestedAboutURIParams(); + if (!nsSimpleNestedURI::Deserialize(params.nestedParams())) { + return false; + } + + mBaseURI = nullptr; + if (params.baseURI()) { + mBaseURI = DeserializeURI(*params.baseURI()); + } + return true; +} + +// nsSimpleURI +/* virtual */ nsSimpleURI* nsNestedAboutURI::StartClone( + nsSimpleURI::RefHandlingEnum aRefHandlingMode, const nsACString& aNewRef) { + // Sadly, we can't make use of nsSimpleNestedURI::StartClone here. + // However, this function is expected to exactly match that function, + // aside from the "new ns***URI()" call. + NS_ENSURE_TRUE(mInnerURI, nullptr); + + nsCOMPtr<nsIURI> innerClone; + nsresult rv = NS_OK; + if (aRefHandlingMode == eHonorRef) { + innerClone = mInnerURI; + } else if (aRefHandlingMode == eReplaceRef) { + rv = NS_GetURIWithNewRef(mInnerURI, aNewRef, getter_AddRefs(innerClone)); + } else { + rv = NS_GetURIWithoutRef(mInnerURI, getter_AddRefs(innerClone)); + } + + if (NS_FAILED(rv)) { + return nullptr; + } + + nsNestedAboutURI* url = new nsNestedAboutURI(innerClone, mBaseURI); + SetRefOnClone(url, aRefHandlingMode, aNewRef); + + return url; +} + +// Queries this list of interfaces. If none match, it queries mURI. +NS_IMPL_NSIURIMUTATOR_ISUPPORTS(nsNestedAboutURI::Mutator, nsIURISetters, + nsIURIMutator, nsISerializable, + nsINestedAboutURIMutator) + +NS_IMETHODIMP +nsNestedAboutURI::Mutate(nsIURIMutator** aMutator) { + RefPtr<nsNestedAboutURI::Mutator> mutator = new nsNestedAboutURI::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/protocol/about/nsAboutProtocolHandler.h b/netwerk/protocol/about/nsAboutProtocolHandler.h new file mode 100644 index 0000000000..1a15400361 --- /dev/null +++ b/netwerk/protocol/about/nsAboutProtocolHandler.h @@ -0,0 +1,139 @@ +/* -*- 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 nsAboutProtocolHandler_h___ +#define nsAboutProtocolHandler_h___ + +#include "nsIProtocolHandler.h" +#include "nsSimpleNestedURI.h" +#include "nsWeakReference.h" +#include "mozilla/Attributes.h" +#include "nsIURIMutator.h" + +class nsIURI; + +namespace mozilla { +namespace net { + +class nsAboutProtocolHandler : public nsIProtocolHandlerWithDynamicFlags, + public nsIProtocolHandler, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + // nsIProtocolHandler methods: + NS_DECL_NSIPROTOCOLHANDLER + NS_DECL_NSIPROTOCOLHANDLERWITHDYNAMICFLAGS + + // nsAboutProtocolHandler methods: + nsAboutProtocolHandler() = default; + + static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, nsIURI** result); + + private: + virtual ~nsAboutProtocolHandler() = default; +}; + +class nsSafeAboutProtocolHandler final : public nsIProtocolHandler, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + + // nsIProtocolHandler methods: + NS_DECL_NSIPROTOCOLHANDLER + + // nsSafeAboutProtocolHandler methods: + nsSafeAboutProtocolHandler() = default; + + private: + ~nsSafeAboutProtocolHandler() = default; +}; + +// Class to allow us to propagate the base URI to about:blank correctly +class nsNestedAboutURI final : public nsSimpleNestedURI { + private: + nsNestedAboutURI(nsIURI* aInnerURI, nsIURI* aBaseURI) + : nsSimpleNestedURI(aInnerURI), mBaseURI(aBaseURI) {} + nsNestedAboutURI() : nsSimpleNestedURI() {} + virtual ~nsNestedAboutURI() = default; + + public: + // Override QI so we can QI to our CID as needed + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + + // Override StartClone(), the nsISerializable methods, and + virtual nsSimpleURI* StartClone(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef) override; + NS_IMETHOD Mutate(nsIURIMutator** _retval) override; + NS_IMETHOD_(void) Serialize(ipc::URIParams& aParams) override; + + // nsISerializable + NS_IMETHOD Read(nsIObjectInputStream* aStream) override; + NS_IMETHOD Write(nsIObjectOutputStream* aStream) override; + + nsIURI* GetBaseURI() const { return mBaseURI; } + + protected: + nsCOMPtr<nsIURI> mBaseURI; + bool Deserialize(const mozilla::ipc::URIParams&); + nsresult ReadPrivate(nsIObjectInputStream* stream); + + public: + class Mutator final : public nsIURIMutator, + public BaseURIMutator<nsNestedAboutURI>, + public nsISerializable, + public nsINestedAboutURIMutator { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + + explicit Mutator() = default; + + private: + virtual ~Mutator() = default; + + [[nodiscard]] NS_IMETHOD Deserialize( + const mozilla::ipc::URIParams& aParams) override { + return InitFromIPCParams(aParams); + } + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override { + return InitFromInputStream(aStream); + } + + [[nodiscard]] NS_IMETHOD Finalize(nsIURI** aURI) override { + mURI.forget(aURI); + return NS_OK; + } + + [[nodiscard]] NS_IMETHOD SetSpec(const nsACString& aSpec, + nsIURIMutator** aMutator) override { + if (aMutator) { + NS_ADDREF(*aMutator = this); + } + return InitFromSpec(aSpec); + } + + [[nodiscard]] NS_IMETHOD InitWithBase(nsIURI* aInnerURI, + nsIURI* aBaseURI) override { + mURI = new nsNestedAboutURI(aInnerURI, aBaseURI); + return NS_OK; + } + + friend class nsNestedAboutURI; + }; + + friend BaseURIMutator<nsNestedAboutURI>; +}; + +} // namespace net +} // namespace mozilla + +#endif /* nsAboutProtocolHandler_h___ */ diff --git a/netwerk/protocol/about/nsAboutProtocolUtils.h b/netwerk/protocol/about/nsAboutProtocolUtils.h new file mode 100644 index 0000000000..68d1f4d54b --- /dev/null +++ b/netwerk/protocol/about/nsAboutProtocolUtils.h @@ -0,0 +1,64 @@ +/* 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 nsAboutProtocolUtils_h +#define nsAboutProtocolUtils_h + +#include "nsIURI.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsIAboutModule.h" +#include "nsServiceManagerUtils.h" +#include "prtime.h" + +[[nodiscard]] inline nsresult NS_GetAboutModuleName(nsIURI* aAboutURI, + nsCString& aModule) { + NS_ASSERTION(aAboutURI->SchemeIs("about"), + "should be used only on about: URIs"); + + MOZ_TRY(aAboutURI->GetPathQueryRef(aModule)); + + int32_t f = aModule.FindCharInSet("#?"_ns); + if (f != kNotFound) { + aModule.Truncate(f); + } + + // convert to lowercase, as all about: modules are lowercase + ToLowerCase(aModule); + return NS_OK; +} + +[[nodiscard]] inline bool NS_IsContentAccessibleAboutURI(nsIURI* aURI) { + MOZ_ASSERT(aURI->SchemeIs("about"), "Should be used only on about: URIs"); + nsAutoCString name; + if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, name)))) { + return true; + } + return name.EqualsLiteral("blank") || name.EqualsLiteral("srcdoc"); +} + +inline nsresult NS_GetAboutModule(nsIURI* aAboutURI, nsIAboutModule** aModule) { + MOZ_ASSERT(aAboutURI, "Must have URI"); + + nsAutoCString contractID; + MOZ_TRY(NS_GetAboutModuleName(aAboutURI, contractID)); + + // look up a handler to deal with "what" + contractID.InsertLiteral(NS_ABOUT_MODULE_CONTRACTID_PREFIX, 0); + + return CallGetService(contractID.get(), aModule); +} + +inline PRTime SecondsToPRTime(uint32_t t_sec) { + return (PRTime)t_sec * PR_USEC_PER_SEC; +} + +inline void PrintTimeString(char* buf, uint32_t bufsize, uint32_t t_sec) { + PRExplodedTime et; + PRTime t_usec = SecondsToPRTime(t_sec); + PR_ExplodeTime(t_usec, PR_LocalTimeParameters, &et); + PR_FormatTime(buf, bufsize, "%Y-%m-%d %H:%M:%S", &et); +} + +#endif diff --git a/netwerk/protocol/about/nsIAboutModule.idl b/netwerk/protocol/about/nsIAboutModule.idl new file mode 100644 index 0000000000..db82dad11d --- /dev/null +++ b/netwerk/protocol/about/nsIAboutModule.idl @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsISupports.idl" + +interface nsIURI; +interface nsIChannel; +interface nsILoadInfo; + +[scriptable, uuid(c0c19db9-1b5a-4ac5-b656-ed6f8149fa48)] +interface nsIAboutModule : nsISupports +{ + + /** + * Constructs a new channel for the about protocol module. + * + * @param aURI the uri of the new channel + * @param aLoadInfo the loadinfo of the new channel + */ + nsIChannel newChannel(in nsIURI aURI, + in nsILoadInfo aLoadInfo); + + /** + * A flag that indicates whether a URI should be run with content + * privileges. If it is, the about: protocol handler will enforce that + * the principal of channels created for it be based on their + * originalURI or URI (depending on the channel flags), by setting + * their "owner" to null. + * If content needs to be able to link to this URI, specify + * URI_CONTENT_LINKABLE as well. + */ + const unsigned long URI_SAFE_FOR_UNTRUSTED_CONTENT = (1 << 0); + + /** + * A flag that indicates whether script should be enabled for the + * given about: URI even if it's disabled in general. + */ + const unsigned long ALLOW_SCRIPT = (1 << 1); + + /** + * A flag that indicates whether this about: URI doesn't want to be listed + * in about:about, especially if it's not useful without a query string. + */ + const unsigned long HIDE_FROM_ABOUTABOUT = (1 << 2); + + /** + * A flag that indicates whether this about: URI wants Indexed DB enabled. + */ + const unsigned long ENABLE_INDEXED_DB = (1 << 3); + + /** + * A flag that indicates that this URI can be loaded in a child process + */ + const unsigned long URI_CAN_LOAD_IN_CHILD = (1 << 4); + + /** + * A flag that indicates that this URI must be loaded in a child process + */ + const unsigned long URI_MUST_LOAD_IN_CHILD = (1 << 5); + + /** + * Obsolete. This flag no longer has any effect and will be removed in future. + */ + const unsigned long MAKE_UNLINKABLE = (1 << 6); + + /** + * A flag that indicates that this URI should be linkable from content. + * Ignored unless URI_SAFE_FOR_UNTRUSTED_CONTENT is also specified. + * + * When adding a new about module with this flag make sure to also update + * IsSafeToLinkForUntrustedContent() in nsAboutProtocolHandler.cpp + */ + const unsigned long MAKE_LINKABLE = (1 << 7); + + /** + * A flag that indicates that this URI can be loaded in the privileged + * activity stream content process if said process is enabled. Ignored unless + * URI_MUST_LOAD_IN_CHILD is also specified. + */ + const unsigned long URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS = (1 << 8); + + /** + * A flag that indicates that this URI must be loaded in an extension process (if available). + */ + const unsigned long URI_MUST_LOAD_IN_EXTENSION_PROCESS = (1 << 9); + + /** + * A flag that indicates that this about: URI needs to allow unsanitized content. + * Only to be used by about:home and about:newtab. + */ + const unsigned long ALLOW_UNSANITIZED_CONTENT = (1 << 10); + + /** + * A flag that indicates that this about: URI is a secure chrome UI + */ + const unsigned long IS_SECURE_CHROME_UI = (1 << 11); + + /** + * A method to get the flags that apply to a given about: URI. The URI + * passed in is guaranteed to be one of the URIs that this module + * registered to deal with. + */ + unsigned long getURIFlags(in nsIURI aURI); + + /** + * A method to get the chrome URI that corresponds to a given about URI. + */ + nsIURI getChromeURI(in nsIURI aURI); +}; + +%{C++ + +#define NS_ABOUT_MODULE_CONTRACTID "@mozilla.org/network/protocol/about;1" +#define NS_ABOUT_MODULE_CONTRACTID_PREFIX NS_ABOUT_MODULE_CONTRACTID "?what=" +#define NS_ABOUT_MODULE_CONTRACTID_LENGTH 49 // strlen(NS_ABOUT_MODULE_CONTRACTID_PREFIX) + +%} |