1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsAboutCache.h"
7 #include "nsIInputStream.h"
10 #include "nsNetUtil.h"
12 #include "nsContentUtils.h"
14 #include "nsAboutProtocolUtils.h"
15 #include "nsPrintfCString.h"
17 #include "nsICacheStorageService.h"
18 #include "nsICacheStorage.h"
19 #include "CacheFileUtils.h"
20 #include "CacheObserver.h"
22 #include "nsThreadUtils.h"
24 using namespace mozilla::net
;
26 NS_IMPL_ISUPPORTS(nsAboutCache
, nsIAboutModule
)
27 NS_IMPL_ISUPPORTS(nsAboutCache::Channel
, nsIChannel
, nsIRequest
,
28 nsICacheStorageVisitor
)
31 nsAboutCache::NewChannel(nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
,
32 nsIChannel
** result
) {
35 NS_ENSURE_ARG_POINTER(aURI
);
37 RefPtr
<Channel
> channel
= new Channel();
38 rv
= channel
->Init(aURI
, aLoadInfo
);
39 if (NS_FAILED(rv
)) return rv
;
41 channel
.forget(result
);
46 nsresult
nsAboutCache::Channel::Init(nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
) {
51 nsCOMPtr
<nsIInputStream
> inputStream
;
52 NS_NewPipe(getter_AddRefs(inputStream
), getter_AddRefs(mStream
), 16384,
54 true, // non-blocking input
55 false // blocking output
58 nsAutoCString storageName
;
59 rv
= ParseURI(aURI
, storageName
);
60 if (NS_FAILED(rv
)) return rv
;
62 mOverview
= storageName
.IsEmpty();
64 // ...and visit all we can
65 mStorageList
.AppendElement("memory"_ns
);
66 mStorageList
.AppendElement("disk"_ns
);
68 // ...and visit just the specified storage, entries will output too
69 mStorageList
.AppendElement(storageName
);
72 // The entries header is added on encounter of the first entry
73 mEntriesHeaderAdded
= false;
75 rv
= NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel
), aURI
,
76 inputStream
.forget(), "text/html"_ns
,
77 "utf-8"_ns
, aLoadInfo
);
78 if (NS_FAILED(rv
)) return rv
;
80 mBuffer
.AssignLiteral(
84 " <title>Network Cache Storage Information</title>\n"
85 " <meta charset=\"utf-8\">\n"
86 " <meta name=\"color-scheme\" content=\"light dark\">\n"
87 " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
88 "chrome:; object-src 'none'\"/>\n"
89 " <link rel=\"stylesheet\" "
90 "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n"
91 " <link rel=\"stylesheet\" "
92 "href=\"chrome://global/skin/aboutCache.css\"/>\n"
94 "<body class=\"aboutPageWideContainer\">\n"
95 "<h1>Information about the Network Cache Storage Service</h1>\n");
98 mBuffer
.AppendLiteral(
99 "<a href=\"about:cache?storage=\">Back to overview</a>");
104 NS_WARNING("Failed to flush buffer");
110 NS_IMETHODIMP
nsAboutCache::Channel::AsyncOpen(nsIStreamListener
* aListener
) {
114 return NS_ERROR_UNEXPECTED
;
117 // Kick the walk loop.
118 rv
= VisitNextStorage();
119 if (NS_FAILED(rv
)) return rv
;
121 rv
= mChannel
->AsyncOpen(aListener
);
122 if (NS_FAILED(rv
)) return rv
;
127 NS_IMETHODIMP
nsAboutCache::Channel::Open(nsIInputStream
** _retval
) {
128 return NS_ERROR_NOT_IMPLEMENTED
;
131 nsresult
nsAboutCache::Channel::ParseURI(nsIURI
* uri
, nsACString
& storage
) {
133 // about:cache[?storage=<storage-name>[&context=<context-key>]]
138 rv
= uri
->GetPathQueryRef(path
);
139 if (NS_FAILED(rv
)) return rv
;
143 nsACString::const_iterator start
, valueStart
, end
;
144 path
.BeginReading(start
);
145 path
.EndReading(end
);
148 if (!FindInReadable("?storage="_ns
, start
, valueStart
)) {
152 storage
.Assign(Substring(valueStart
, end
));
157 nsresult
nsAboutCache::Channel::VisitNextStorage() {
158 if (!mStorageList
.Length()) return NS_ERROR_NOT_AVAILABLE
;
160 mStorageName
= mStorageList
[0];
161 mStorageList
.RemoveElementAt(0);
163 // Must re-dispatch since we cannot start another visit cycle
164 // from visitor callback. The cache v1 service doesn't like it.
165 // TODO - mayhemer, bug 913828, remove this dispatch and call
167 return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
168 "nsAboutCache::Channel::FireVisitStorage", this,
169 &nsAboutCache::Channel::FireVisitStorage
));
172 void nsAboutCache::Channel::FireVisitStorage() {
175 rv
= VisitStorage(mStorageName
);
177 nsAutoCString escaped
;
178 nsAppendEscapedHTML(mStorageName
, escaped
);
179 mBuffer
.Append(nsPrintfCString(
180 "<p>Unrecognized storage name '%s' in about:cache URL</p>",
185 NS_WARNING("Failed to flush buffer");
188 // Simulate finish of a visit cycle, this tries the next storage
189 // or closes the output stream (i.e. the UI loader will stop spinning)
190 OnCacheEntryVisitCompleted();
194 nsresult
nsAboutCache::Channel::VisitStorage(nsACString
const& storageName
) {
197 rv
= GetStorage(storageName
, nullptr, getter_AddRefs(mStorage
));
198 if (NS_FAILED(rv
)) return rv
;
200 rv
= mStorage
->AsyncVisitStorage(this, !mOverview
);
201 if (NS_FAILED(rv
)) return rv
;
207 nsresult
nsAboutCache::GetStorage(nsACString
const& storageName
,
208 nsILoadContextInfo
* loadInfo
,
209 nsICacheStorage
** storage
) {
212 nsCOMPtr
<nsICacheStorageService
> cacheService
=
213 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv
);
214 if (NS_FAILED(rv
)) return rv
;
216 nsCOMPtr
<nsICacheStorage
> cacheStorage
;
217 if (storageName
== "disk") {
218 rv
= cacheService
->DiskCacheStorage(loadInfo
, getter_AddRefs(cacheStorage
));
219 } else if (storageName
== "memory") {
220 rv
= cacheService
->MemoryCacheStorage(loadInfo
,
221 getter_AddRefs(cacheStorage
));
223 rv
= NS_ERROR_UNEXPECTED
;
225 if (NS_FAILED(rv
)) return rv
;
227 cacheStorage
.forget(storage
);
232 nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount
,
233 uint64_t aConsumption
,
235 nsIFile
* aDirectory
) {
236 // We need mStream for this
238 return NS_ERROR_FAILURE
;
241 mBuffer
.AssignLiteral("<h2>");
242 nsAppendEscapedHTML(mStorageName
, mBuffer
);
243 mBuffer
.AppendLiteral(
246 mBuffer
.AppendLiteral("\">\n");
248 // Write out cache info
250 mBuffer
.AppendLiteral(
252 " <th>Number of entries:</th>\n"
254 mBuffer
.AppendInt(aEntryCount
);
255 mBuffer
.AppendLiteral(
259 // Maximum storage size
260 mBuffer
.AppendLiteral(
262 " <th>Maximum storage size:</th>\n"
264 mBuffer
.AppendInt(aCapacity
/ 1024);
265 mBuffer
.AppendLiteral(
270 mBuffer
.AppendLiteral(
272 " <th>Storage in use:</th>\n"
274 mBuffer
.AppendInt(aConsumption
/ 1024);
275 mBuffer
.AppendLiteral(
279 // Storage disk location
280 mBuffer
.AppendLiteral(
282 " <th>Storage disk location:</th>\n"
286 aDirectory
->GetPath(path
);
287 mBuffer
.Append(NS_ConvertUTF16toUTF8(path
));
289 mBuffer
.AppendLiteral("none, only stored in memory");
291 mBuffer
.AppendLiteral(
295 if (mOverview
) { // The about:cache case
296 if (aEntryCount
!= 0) { // Add the "List Cache Entries" link
297 mBuffer
.AppendLiteral(
299 " <td colspan=\"2\"><a href=\"about:cache?storage=");
300 nsAppendEscapedHTML(mStorageName
, mBuffer
);
301 mBuffer
.AppendLiteral(
302 "\">List Cache Entries</a></td>\n"
307 mBuffer
.AppendLiteral("</table>\n");
309 // The entries header is added on encounter of the first entry
310 mEntriesHeaderAdded
= false;
312 nsresult rv
= FlushBuffer();
314 NS_WARNING("Failed to flush buffer");
318 // OnCacheEntryVisitCompleted() is not called when we do not iterate
319 // cache entries. Since this moves forward to the next storage in
320 // the list we want to visit, artificially call it here.
321 OnCacheEntryVisitCompleted();
328 nsAboutCache::Channel::OnCacheEntryInfo(
329 nsIURI
* aURI
, const nsACString
& aIdEnhance
, int64_t aDataSize
,
330 int64_t aAltDataSize
, uint32_t aFetchCount
, uint32_t aLastModified
,
331 uint32_t aExpirationTime
, bool aPinned
, nsILoadContextInfo
* aInfo
) {
332 // We need mStream for this
333 if (!mStream
|| mCancel
) {
334 // Returning a failure from this callback stops the iteration
335 return NS_ERROR_FAILURE
;
338 if (!mEntriesHeaderAdded
) {
339 mBuffer
.AppendLiteral(
341 "<table id=\"entries\">\n"
343 " <col id=\"col-key\">\n"
344 " <col id=\"col-dataSize\">\n"
345 " <col id=\"col-altDataSize\">\n"
346 " <col id=\"col-fetchCount\">\n"
347 " <col id=\"col-lastModified\">\n"
348 " <col id=\"col-expires\">\n"
349 " <col id=\"col-pinned\">\n"
354 " <th>Data size</th>\n"
355 " <th>Alternative Data size</th>\n"
356 " <th>Fetch count</th>\n"
357 " <th>Last Modifed</th>\n"
358 " <th>Expires</th>\n"
359 " <th>Pinning</th>\n"
362 mEntriesHeaderAdded
= true;
365 // Generate a about:cache-entry URL for this entry...
368 url
.AssignLiteral("about:cache-entry?storage=");
369 nsAppendEscapedHTML(mStorageName
, url
);
371 nsAutoCString context
;
372 CacheFileUtils::AppendKeyPrefix(aInfo
, context
);
373 url
.AppendLiteral("&context=");
374 nsAppendEscapedHTML(context
, url
);
376 url
.AppendLiteral("&eid=");
377 nsAppendEscapedHTML(aIdEnhance
, url
);
379 nsAutoCString cacheUriSpec
;
380 aURI
->GetAsciiSpec(cacheUriSpec
);
381 nsAutoCString escapedCacheURI
;
382 nsAppendEscapedHTML(cacheUriSpec
, escapedCacheURI
);
383 url
.AppendLiteral("&uri=");
384 url
+= escapedCacheURI
;
387 mBuffer
.AppendLiteral(" <tr>\n");
390 mBuffer
.AppendLiteral(" <td><a href=\"");
392 mBuffer
.AppendLiteral("\">");
393 if (!aIdEnhance
.IsEmpty()) {
394 nsAppendEscapedHTML(aIdEnhance
, mBuffer
);
397 mBuffer
.Append(escapedCacheURI
);
398 mBuffer
.AppendLiteral("</a>");
400 if (!context
.IsEmpty()) {
401 mBuffer
.AppendLiteral("<br><span title=\"Context separation key\">");
402 nsAutoCString escapedContext
;
403 nsAppendEscapedHTML(context
, escapedContext
);
404 mBuffer
.Append(escapedContext
);
405 mBuffer
.AppendLiteral("</span>");
408 mBuffer
.AppendLiteral("</td>\n");
411 mBuffer
.AppendLiteral(" <td>");
412 mBuffer
.AppendInt(aDataSize
);
413 mBuffer
.AppendLiteral(" bytes</td>\n");
415 // Length of alternative content
416 mBuffer
.AppendLiteral(" <td>");
417 mBuffer
.AppendInt(aAltDataSize
);
418 mBuffer
.AppendLiteral(" bytes</td>\n");
420 // Number of accesses
421 mBuffer
.AppendLiteral(" <td>");
422 mBuffer
.AppendInt(aFetchCount
);
423 mBuffer
.AppendLiteral("</td>\n");
425 // vars for reporting time
428 // Last modified time
429 mBuffer
.AppendLiteral(" <td>");
431 PrintTimeString(buf
, sizeof(buf
), aLastModified
);
434 mBuffer
.AppendLiteral("No last modified time");
436 mBuffer
.AppendLiteral("</td>\n");
439 mBuffer
.AppendLiteral(" <td>");
442 // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
443 // So we check if time is 0, then we show a message, "Expired Immediately"
444 if (aExpirationTime
== 0) {
445 mBuffer
.AppendLiteral("Expired Immediately");
446 } else if (aExpirationTime
< 0xFFFFFFFF) {
447 PrintTimeString(buf
, sizeof(buf
), aExpirationTime
);
450 mBuffer
.AppendLiteral("No expiration time");
452 mBuffer
.AppendLiteral("</td>\n");
455 mBuffer
.AppendLiteral(" <td>");
457 mBuffer
.AppendLiteral("Pinned");
459 mBuffer
.AppendLiteral(" ");
461 mBuffer
.AppendLiteral("</td>\n");
464 mBuffer
.AppendLiteral(" </tr>\n");
466 return FlushBuffer();
470 nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
472 return NS_ERROR_FAILURE
;
475 if (mEntriesHeaderAdded
) {
476 mBuffer
.AppendLiteral("</table>\n");
479 // Kick another storage visiting (from a storage that allows us.)
480 while (mStorageList
.Length()) {
481 nsresult rv
= VisitNextStorage();
482 if (NS_SUCCEEDED(rv
)) {
483 // Expecting new round of OnCache* calls.
489 mBuffer
.AppendLiteral(
492 nsresult rv
= FlushBuffer();
494 NS_WARNING("Failed to flush buffer");
501 nsresult
nsAboutCache::Channel::FlushBuffer() {
504 uint32_t bytesWritten
;
505 rv
= mStream
->Write(mBuffer
.get(), mBuffer
.Length(), &bytesWritten
);
516 nsAboutCache::GetURIFlags(nsIURI
* aURI
, uint32_t* result
) {
517 *result
= nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT
|
518 nsIAboutModule::IS_SECURE_CHROME_UI
;
523 nsresult
nsAboutCache::Create(REFNSIID aIID
, void** aResult
) {
524 RefPtr
<nsAboutCache
> about
= new nsAboutCache();
525 return about
->QueryInterface(aIID
, aResult
);
529 nsAboutCache::GetChromeURI(nsIURI
* aURI
, nsIURI
** chromeURI
) {
530 return NS_ERROR_ILLEGAL_VALUE
;
533 ////////////////////////////////////////////////////////////////////////////////