diff options
Diffstat (limited to 'netwerk/protocol/about/nsAboutCacheEntry.cpp')
-rw-r--r-- | netwerk/protocol/about/nsAboutCacheEntry.cpp | 603 |
1 files changed, 603 insertions, 0 deletions
diff --git a/netwerk/protocol/about/nsAboutCacheEntry.cpp b/netwerk/protocol/about/nsAboutCacheEntry.cpp new file mode 100644 index 000000000..183395976 --- /dev/null +++ b/netwerk/protocol/about/nsAboutCacheEntry.cpp @@ -0,0 +1,603 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAboutCacheEntry.h" + +#include "mozilla/Sprintf.h" + +#include "nsAboutCache.h" +#include "nsICacheStorage.h" +#include "CacheObserver.h" +#include "nsNetUtil.h" +#include "nsEscape.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsAboutProtocolUtils.h" +#include "nsContentUtils.h" +#include "nsInputStreamPump.h" +#include "CacheFileUtils.h" +#include <algorithm> +#include "nsIPipe.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, + 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; +} + +//----------------------------------------------------------------------------- +// 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, + NS_LITERAL_CSTRING("text/html"), + NS_LITERAL_CSTRING("utf-8"), + 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; + rv = NS_NewPipe2(getter_AddRefs(inputStream), + getter_AddRefs(mOutputStream), + true, false, + 256, UINT32_MAX); + if (NS_FAILED(rv)) return rv; + + NS_NAMED_LITERAL_CSTRING( + buffer, + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + " <title>Cache entry information</title>\n" + " <link rel=\"stylesheet\" " + "href=\"chrome://global/skin/about.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"); + 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; + + if (!CacheObserver::UseNewCache() && + mLoadInfo->IsPrivate() && + mStorageName.EqualsLiteral("disk")) { + // The cache v1 is storing all private entries in the memory-only + // cache, so it would not be found in the v1 disk cache. + mStorageName = NS_LITERAL_CSTRING("memory"); + } + + 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->GetPath(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(NS_LITERAL_CSTRING("?storage="), keyBegin, keyEnd)) + return NS_ERROR_FAILURE; + + valBegin = keyEnd; // the value of the storage key starts after the key + + keyBegin = keyEnd; keyEnd = end; + if (!FindInReadable(NS_LITERAL_CSTRING("&context="), 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(NS_LITERAL_CSTRING("&eid="), 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(NS_LITERAL_CSTRING("&uri="), 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, + nsIApplicationCache *aApplicationCache, + uint32_t *result) +{ + *result = nsICacheEntryOpenCallback::ENTRY_WANTED; + return NS_OK; +} + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry *entry, + bool isNew, + nsIApplicationCache *aApplicationCache, + nsresult status) +{ + nsresult rv; + + mWaitingForData = false; + if (entry) { + rv = WriteCacheEntryDescription(entry); + } else if (!CacheObserver::UseNewCache() && + !mLoadInfo->IsPrivate() && + mStorageName.EqualsLiteral("memory")) { + // If we were not able to find the entry in the memory storage + // try again in the disk storage. + // This is a workaround for cache v1: when an originally disk + // cache entry is recreated as memory-only, it's clientID doesn't + // change and we cannot find it in "HTTP-memory-only" session. + // "Disk" cache storage looks at "HTTP". + mStorageName = NS_LITERAL_CSTRING("disk"); + rv = OpenCacheEntry(); + if (NS_SUCCEEDED(rv)) { + return NS_OK; + } + } 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; + nsCString buffer; + uint32_t n; + + nsAutoCString str; + + rv = entry->GetKey(str); + if (NS_FAILED(rv)) return rv; + + buffer.SetCapacity(4096); + 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; + bool isJS = false; + bool isData = false; + + rv = NS_NewURI(getter_AddRefs(uri), str); + // 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", &isJS); + uri->SchemeIs("data", &isData); + } + char* escapedStr = nsEscapeHTML(str.get()); + if (NS_SUCCEEDED(rv) && !(isJS || isData)) { + buffer.AppendLiteral("<a href=\""); + buffer.Append(escapedStr); + buffer.AppendLiteral("\">"); + buffer.Append(escapedStr); + buffer.AppendLiteral("</a>"); + uri = nullptr; + } else { + buffer.Append(escapedStr); + } + free(escapedStr); + buffer.AppendLiteral("</td>\n" + " </tr>\n"); + + // temp vars for reporting + char timeBuf[255]; + uint32_t u = 0; + int32_t i = 0; + nsAutoCString s; + + // Fetch Count + s.Truncate(); + entry->GetFetchCount(&i); + s.AppendInt(i); + 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); + 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<nsISupports> 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, nullptr); + if (NS_FAILED(rv)) { + return NS_OK; // just ignore + } + + mWaitingForData = true; + return NS_OK; +} + +nsresult +nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable() +{ + uint32_t n; + NS_NAMED_LITERAL_CSTRING(buffer, + "The cache entry you selected is not available."); + 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>"); + char* escapedValue = nsEscapeHTML(value); + mBuffer->Append(escapedValue); + free(escapedValue); + mBuffer->AppendLiteral("</td>\n" + " </tr>\n"); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIStreamListener implementation +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest *request, nsISupports *ctx) +{ + mHexDumpState = 0; + + NS_NAMED_LITERAL_CSTRING(buffer, "<hr/>\n<pre>"); + uint32_t n; + return mOutputStream->Write(buffer.get(), buffer.Length(), &n); +} + +NS_IMETHODIMP +nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest *request, nsISupports *ctx, + 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, nsISupports *ctx, + nsresult result) +{ + NS_NAMED_LITERAL_CSTRING(buffer, "</pre>\n"); + uint32_t n; + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + + CloseContent(); + + return NS_OK; +} + +void +nsAboutCacheEntry::Channel::CloseContent() +{ + NS_NAMED_LITERAL_CSTRING(buffer, "</body>\n</html>\n"); + uint32_t n; + mOutputStream->Write(buffer.get(), buffer.Length(), &n); + + mOutputStream->Close(); + mOutputStream = nullptr; +} |