michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsAboutCacheEntry.h" michael@0: #include "nsICacheService.h" michael@0: #include "nsICacheSession.h" michael@0: #include "nsNetUtil.h" michael@0: #include "prprf.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIAsyncInputStream.h" michael@0: #include "nsIAsyncOutputStream.h" michael@0: #include "nsAboutProtocolUtils.h" michael@0: #include michael@0: michael@0: #define HEXDUMP_MAX_ROWS 16 michael@0: michael@0: static void michael@0: HexDump(uint32_t *state, const char *buf, int32_t n, nsCString &result) michael@0: { michael@0: char temp[16]; michael@0: michael@0: const unsigned char *p; michael@0: while (n) { michael@0: PR_snprintf(temp, sizeof(temp), "%08x: ", *state); michael@0: result.Append(temp); michael@0: *state += HEXDUMP_MAX_ROWS; michael@0: michael@0: p = (const unsigned char *) buf; michael@0: michael@0: int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n); michael@0: michael@0: // print hex codes: michael@0: for (i = 0; i < row_max; ++i) { michael@0: PR_snprintf(temp, sizeof(temp), "%02x ", *p++); michael@0: result.Append(temp); michael@0: } michael@0: for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) { michael@0: result.AppendLiteral(" "); michael@0: } michael@0: michael@0: // print ASCII glyphs if possible: michael@0: p = (const unsigned char *) buf; michael@0: for (i = 0; i < row_max; ++i, ++p) { michael@0: switch (*p) { michael@0: case '<': michael@0: result.AppendLiteral("<"); michael@0: break; michael@0: case '>': michael@0: result.AppendLiteral(">"); michael@0: break; michael@0: case '&': michael@0: result.AppendLiteral("&"); michael@0: break; michael@0: default: michael@0: if (*p < 0x7F && *p > 0x1F) { michael@0: result.Append(*p); michael@0: } else { michael@0: result.Append('.'); michael@0: } michael@0: } michael@0: } michael@0: michael@0: result.Append('\n'); michael@0: michael@0: buf += row_max; michael@0: n -= row_max; michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAboutCacheEntry::nsISupports michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAboutCacheEntry, michael@0: nsIAboutModule, michael@0: nsICacheMetaDataVisitor) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAboutCacheEntry::nsIAboutModule michael@0: michael@0: NS_IMETHODIMP michael@0: nsAboutCacheEntry::NewChannel(nsIURI *uri, nsIChannel **result) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(uri); michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr stream; michael@0: rv = GetContentStream(uri, getter_AddRefs(stream)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return NS_NewInputStreamChannel(result, uri, stream, michael@0: NS_LITERAL_CSTRING("text/html"), michael@0: NS_LITERAL_CSTRING("utf-8")); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAboutCacheEntry::GetURIFlags(nsIURI *aURI, uint32_t *result) michael@0: { michael@0: *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsAboutCacheEntry michael@0: michael@0: nsresult michael@0: nsAboutCacheEntry::GetContentStream(nsIURI *uri, nsIInputStream **result) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // Init: (block size, maximum length) michael@0: nsCOMPtr inputStream; michael@0: rv = NS_NewPipe2(getter_AddRefs(inputStream), michael@0: getter_AddRefs(mOutputStream), michael@0: true, false, michael@0: 256, UINT32_MAX); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_NAMED_LITERAL_CSTRING( michael@0: buffer, michael@0: "\n" michael@0: "\n" michael@0: "\n" michael@0: " Cache entry information\n" michael@0: " \n" michael@0: " \n" michael@0: "\n" michael@0: "\n" michael@0: "

Cache entry information

\n"); michael@0: uint32_t n; michael@0: rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (n != buffer.Length()) return NS_ERROR_UNEXPECTED; michael@0: michael@0: rv = OpenCacheEntry(uri); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: inputStream.forget(result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAboutCacheEntry::OpenCacheEntry(nsIURI *uri) michael@0: { michael@0: nsresult rv; michael@0: nsAutoCString clientID, key; michael@0: bool streamBased = true; michael@0: michael@0: rv = ParseURI(uri, clientID, streamBased, key); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr serv = michael@0: do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr session; michael@0: rv = serv->CreateSession(clientID.get(), michael@0: nsICache::STORE_ANYWHERE, michael@0: streamBased, michael@0: getter_AddRefs(session)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = session->SetDoomEntriesIfExpired(false); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return session->AsyncOpenCacheEntry(key, nsICache::ACCESS_READ, this, true); michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // helper methods michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #define APPEND_ROW(label, value) \ michael@0: PR_BEGIN_MACRO \ michael@0: buffer.AppendLiteral(" \n" \ michael@0: " "); \ michael@0: buffer.AppendLiteral(label); \ michael@0: buffer.AppendLiteral(":\n" \ michael@0: " "); \ michael@0: buffer.Append(value); \ michael@0: buffer.AppendLiteral("\n" \ michael@0: " \n"); \ michael@0: PR_END_MACRO michael@0: michael@0: nsresult michael@0: nsAboutCacheEntry::WriteCacheEntryDescription(nsICacheEntryDescriptor *descriptor) michael@0: { michael@0: nsresult rv; michael@0: nsCString buffer; michael@0: uint32_t n; michael@0: michael@0: nsAutoCString str; michael@0: michael@0: rv = descriptor->GetKey(str); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: buffer.SetCapacity(4096); michael@0: buffer.AssignLiteral("\n" michael@0: " \n" michael@0: " \n" michael@0: " \n" michael@0: " \n"); michael@0: michael@0: // temp vars for reporting michael@0: char timeBuf[255]; michael@0: uint32_t u = 0; michael@0: int32_t i = 0; michael@0: nsAutoCString s; michael@0: michael@0: // Fetch Count michael@0: s.Truncate(); michael@0: descriptor->GetFetchCount(&i); michael@0: s.AppendInt(i); michael@0: APPEND_ROW("fetch count", s); michael@0: michael@0: // Last Fetched michael@0: descriptor->GetLastFetched(&u); michael@0: if (u) { michael@0: PrintTimeString(timeBuf, sizeof(timeBuf), u); michael@0: APPEND_ROW("last fetched", timeBuf); michael@0: } else { michael@0: APPEND_ROW("last fetched", "No last fetch time"); michael@0: } michael@0: michael@0: // Last Modified michael@0: descriptor->GetLastModified(&u); michael@0: if (u) { michael@0: PrintTimeString(timeBuf, sizeof(timeBuf), u); michael@0: APPEND_ROW("last modified", timeBuf); michael@0: } else { michael@0: APPEND_ROW("last modified", "No last modified time"); michael@0: } michael@0: michael@0: // Expiration Time michael@0: descriptor->GetExpirationTime(&u); michael@0: if (u < 0xFFFFFFFF) { michael@0: PrintTimeString(timeBuf, sizeof(timeBuf), u); michael@0: APPEND_ROW("expires", timeBuf); michael@0: } else { michael@0: APPEND_ROW("expires", "No expiration time"); michael@0: } michael@0: michael@0: // Data Size michael@0: s.Truncate(); michael@0: uint32_t dataSize; michael@0: descriptor->GetStorageDataSize(&dataSize); michael@0: s.AppendInt((int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed. michael@0: APPEND_ROW("Data size", s); michael@0: michael@0: // Storage Policy michael@0: michael@0: // XXX Stream Based? michael@0: michael@0: // XXX Cache Device michael@0: // File on disk michael@0: nsCOMPtr cacheFile; michael@0: rv = descriptor->GetFile(getter_AddRefs(cacheFile)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsAutoString filePath; michael@0: cacheFile->GetPath(filePath); michael@0: APPEND_ROW("file on disk", NS_ConvertUTF16toUTF8(filePath)); michael@0: } michael@0: else michael@0: APPEND_ROW("file on disk", "none"); michael@0: michael@0: // Security Info michael@0: nsCOMPtr securityInfo; michael@0: descriptor->GetSecurityInfo(getter_AddRefs(securityInfo)); michael@0: if (securityInfo) { michael@0: APPEND_ROW("Security", "This is a secure document."); michael@0: } else { michael@0: APPEND_ROW("Security", michael@0: "This document does not have any security info associated with it."); michael@0: } michael@0: michael@0: buffer.AppendLiteral("
key:"); michael@0: michael@0: // Test if the key is actually a URI michael@0: nsCOMPtr uri; michael@0: bool isJS = false; michael@0: bool isData = false; michael@0: michael@0: rv = NS_NewURI(getter_AddRefs(uri), str); michael@0: // javascript: and data: URLs should not be linkified michael@0: // since clicking them can cause scripts to run - bug 162584 michael@0: if (NS_SUCCEEDED(rv)) { michael@0: uri->SchemeIs("javascript", &isJS); michael@0: uri->SchemeIs("data", &isData); michael@0: } michael@0: char* escapedStr = nsEscapeHTML(str.get()); michael@0: if (NS_SUCCEEDED(rv) && !(isJS || isData)) { michael@0: buffer.AppendLiteral(""); michael@0: buffer.Append(escapedStr); michael@0: buffer.AppendLiteral(""); michael@0: uri = 0; michael@0: } michael@0: else michael@0: buffer.Append(escapedStr); michael@0: nsMemory::Free(escapedStr); michael@0: buffer.AppendLiteral("
\n" michael@0: "
\n" michael@0: "\n"); michael@0: // Meta Data michael@0: // let's just look for some well known (HTTP) meta data tags, for now. michael@0: michael@0: // Client ID michael@0: nsXPIDLCString str2; michael@0: descriptor->GetClientID(getter_Copies(str2)); michael@0: if (!str2.IsEmpty()) APPEND_ROW("Client", str2); michael@0: michael@0: michael@0: mBuffer = &buffer; // make it available for VisitMetaDataElement(). michael@0: // nsCacheEntryDescriptor::VisitMetaData calls michael@0: // nsCacheEntry.h VisitMetaDataElements, which returns michael@0: // nsCacheMetaData::VisitElements, which calls michael@0: // nsAboutCacheEntry::VisitMetaDataElement (below) in a loop. michael@0: descriptor->VisitMetaData(this); michael@0: mBuffer = nullptr; michael@0: michael@0: buffer.AppendLiteral("
\n"); michael@0: mOutputStream->Write(buffer.get(), buffer.Length(), &n); michael@0: michael@0: buffer.Truncate(); michael@0: michael@0: // Provide a hexdump of the data michael@0: if (dataSize) { // don't draw an
if the Data Size is 0. michael@0: nsCOMPtr stream; michael@0: descriptor->OpenInputStream(0, getter_AddRefs(stream)); michael@0: if (stream) { michael@0: buffer.AssignLiteral("
\n" michael@0: "
");
michael@0:             uint32_t hexDumpState = 0;
michael@0:             char chunk[4096];
michael@0:             while(NS_SUCCEEDED(stream->Read(chunk, sizeof(chunk), &n)) && 
michael@0:                   n > 0) {
michael@0:                 HexDump(&hexDumpState, chunk, n, buffer);
michael@0:                 mOutputStream->Write(buffer.get(), buffer.Length(), &n);
michael@0:                 buffer.Truncate();
michael@0:             }
michael@0:             buffer.AssignLiteral("
\n"); michael@0: mOutputStream->Write(buffer.get(), buffer.Length(), &n); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAboutCacheEntry::WriteCacheEntryUnavailable() michael@0: { michael@0: uint32_t n; michael@0: NS_NAMED_LITERAL_CSTRING(buffer, michael@0: "The cache entry you selected is not available."); michael@0: mOutputStream->Write(buffer.get(), buffer.Length(), &n); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsAboutCacheEntry::ParseURI(nsIURI *uri, nsCString &clientID, michael@0: bool &streamBased, nsCString &key) michael@0: { michael@0: // michael@0: // about:cache-entry?client=[string]&sb=[boolean]&key=[string] michael@0: // michael@0: nsresult rv; michael@0: michael@0: nsAutoCString path; michael@0: rv = uri->GetPath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsACString::const_iterator i1, i2, i3, end; michael@0: path.BeginReading(i1); michael@0: path.EndReading(end); michael@0: michael@0: i2 = end; michael@0: if (!FindInReadable(NS_LITERAL_CSTRING("?client="), i1, i2)) michael@0: return NS_ERROR_FAILURE; michael@0: // i2 points to the start of clientID michael@0: michael@0: i1 = i2; michael@0: i3 = end; michael@0: if (!FindInReadable(NS_LITERAL_CSTRING("&sb="), i1, i3)) michael@0: return NS_ERROR_FAILURE; michael@0: // i1 points to the end of clientID michael@0: // i3 points to the start of isStreamBased michael@0: michael@0: clientID.Assign(Substring(i2, i1)); michael@0: michael@0: i1 = i3; michael@0: i2 = end; michael@0: if (!FindInReadable(NS_LITERAL_CSTRING("&key="), i1, i2)) michael@0: return NS_ERROR_FAILURE; michael@0: // i1 points to the end of isStreamBased michael@0: // i2 points to the start of key michael@0: michael@0: streamBased = FindCharInReadable('1', i3, i1); michael@0: key.Assign(Substring(i2, end)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsICacheMetaDataVisitor implementation michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsAboutCacheEntry::VisitMetaDataElement(const char * key, michael@0: const char * value, michael@0: bool * keepGoing) michael@0: { michael@0: mBuffer->AppendLiteral(" \n" michael@0: " "); michael@0: mBuffer->Append(key); michael@0: mBuffer->AppendLiteral(":\n" michael@0: " "); michael@0: char* escapedValue = nsEscapeHTML(value); michael@0: mBuffer->Append(escapedValue); michael@0: nsMemory::Free(escapedValue); michael@0: mBuffer->AppendLiteral("\n" michael@0: " \n"); michael@0: michael@0: *keepGoing = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsICacheListener implementation michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsAboutCacheEntry::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry, michael@0: nsCacheAccessMode access, michael@0: nsresult status) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (entry) michael@0: rv = WriteCacheEntryDescription(entry); michael@0: else michael@0: rv = WriteCacheEntryUnavailable(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t n; michael@0: NS_NAMED_LITERAL_CSTRING(buffer, "\n\n"); michael@0: mOutputStream->Write(buffer.get(), buffer.Length(), &n); michael@0: mOutputStream->Close(); michael@0: mOutputStream = nullptr; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAboutCacheEntry::OnCacheEntryDoomed(nsresult status) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: }