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 #include "mozilla/Components.h"
26 using namespace mozilla::net
;
28 NS_IMPL_ISUPPORTS(nsAboutCache
, nsIAboutModule
)
29 NS_IMPL_ISUPPORTS(nsAboutCache::Channel
, nsIChannel
, nsIRequest
,
30 nsICacheStorageVisitor
)
33 nsAboutCache::NewChannel(nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
,
34 nsIChannel
** result
) {
37 NS_ENSURE_ARG_POINTER(aURI
);
39 RefPtr
<Channel
> channel
= new Channel();
40 rv
= channel
->Init(aURI
, aLoadInfo
);
41 if (NS_FAILED(rv
)) return rv
;
43 channel
.forget(result
);
48 nsresult
nsAboutCache::Channel::Init(nsIURI
* aURI
, nsILoadInfo
* aLoadInfo
) {
53 nsCOMPtr
<nsIInputStream
> inputStream
;
54 NS_NewPipe(getter_AddRefs(inputStream
), getter_AddRefs(mStream
), 16384,
56 true, // non-blocking input
57 false // blocking output
60 nsAutoCString storageName
;
61 rv
= ParseURI(aURI
, storageName
);
62 if (NS_FAILED(rv
)) return rv
;
64 mOverview
= storageName
.IsEmpty();
66 // ...and visit all we can
67 mStorageList
.AppendElement("memory"_ns
);
68 mStorageList
.AppendElement("disk"_ns
);
70 // ...and visit just the specified storage, entries will output too
71 mStorageList
.AppendElement(storageName
);
74 // The entries header is added on encounter of the first entry
75 mEntriesHeaderAdded
= false;
77 rv
= NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel
), aURI
,
78 inputStream
.forget(), "text/html"_ns
,
79 "utf-8"_ns
, aLoadInfo
);
80 if (NS_FAILED(rv
)) return rv
;
82 mBuffer
.AssignLiteral(
86 " <title>Network Cache Storage Information</title>\n"
87 " <meta charset=\"utf-8\">\n"
88 " <meta name=\"color-scheme\" content=\"light dark\">\n"
89 " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
90 "chrome:; object-src 'none'\"/>\n"
91 " <link rel=\"stylesheet\" "
92 "href=\"chrome://global/skin/in-content/info-pages.css\"/>\n"
93 " <link rel=\"stylesheet\" "
94 "href=\"chrome://global/skin/aboutCache.css\"/>\n"
96 "<body class=\"aboutPageWideContainer\">\n"
97 "<h1>Information about the Network Cache Storage Service</h1>\n");
100 mBuffer
.AppendLiteral(
101 "<a href=\"about:cache?storage=\">Back to overview</a>\n");
102 mBuffer
.AppendLiteral(
103 "<p id=\"explanation-dataSize\">Data sizes refer to the size of the "
104 "response body and do not reflect the amount of disk space that the "
105 "file occupies.</p>\n");
110 NS_WARNING("Failed to flush buffer");
116 NS_IMETHODIMP
nsAboutCache::Channel::AsyncOpen(nsIStreamListener
* aListener
) {
120 return NS_ERROR_UNEXPECTED
;
123 // Kick the walk loop.
124 rv
= VisitNextStorage();
125 if (NS_FAILED(rv
)) return rv
;
127 rv
= mChannel
->AsyncOpen(aListener
);
128 if (NS_FAILED(rv
)) return rv
;
133 NS_IMETHODIMP
nsAboutCache::Channel::Open(nsIInputStream
** _retval
) {
134 return NS_ERROR_NOT_IMPLEMENTED
;
137 nsresult
nsAboutCache::Channel::ParseURI(nsIURI
* uri
, nsACString
& storage
) {
139 // about:cache[?storage=<storage-name>[&context=<context-key>]]
144 rv
= uri
->GetPathQueryRef(path
);
145 if (NS_FAILED(rv
)) return rv
;
149 nsACString::const_iterator start
, valueStart
, end
;
150 path
.BeginReading(start
);
151 path
.EndReading(end
);
154 if (!FindInReadable("?storage="_ns
, start
, valueStart
)) {
158 storage
.Assign(Substring(valueStart
, end
));
163 nsresult
nsAboutCache::Channel::VisitNextStorage() {
164 if (!mStorageList
.Length()) return NS_ERROR_NOT_AVAILABLE
;
166 mStorageName
= mStorageList
[0];
167 mStorageList
.RemoveElementAt(0);
169 // Must re-dispatch since we cannot start another visit cycle
170 // from visitor callback. The cache v1 service doesn't like it.
171 // TODO - mayhemer, bug 913828, remove this dispatch and call
173 return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
174 "nsAboutCache::Channel::FireVisitStorage", this,
175 &nsAboutCache::Channel::FireVisitStorage
));
178 void nsAboutCache::Channel::FireVisitStorage() {
181 rv
= VisitStorage(mStorageName
);
183 nsAutoCString escaped
;
184 nsAppendEscapedHTML(mStorageName
, escaped
);
185 mBuffer
.Append(nsPrintfCString(
186 "<p>Unrecognized storage name '%s' in about:cache URL</p>",
191 NS_WARNING("Failed to flush buffer");
194 // Simulate finish of a visit cycle, this tries the next storage
195 // or closes the output stream (i.e. the UI loader will stop spinning)
196 OnCacheEntryVisitCompleted();
200 nsresult
nsAboutCache::Channel::VisitStorage(nsACString
const& storageName
) {
203 rv
= GetStorage(storageName
, nullptr, getter_AddRefs(mStorage
));
204 if (NS_FAILED(rv
)) return rv
;
206 rv
= mStorage
->AsyncVisitStorage(this, !mOverview
);
207 if (NS_FAILED(rv
)) return rv
;
213 nsresult
nsAboutCache::GetStorage(nsACString
const& storageName
,
214 nsILoadContextInfo
* loadInfo
,
215 nsICacheStorage
** storage
) {
218 nsCOMPtr
<nsICacheStorageService
> cacheService
;
219 cacheService
= mozilla::components::CacheStorage::Service(&rv
);
220 if (NS_FAILED(rv
)) return rv
;
222 nsCOMPtr
<nsICacheStorage
> cacheStorage
;
223 if (storageName
== "disk") {
224 rv
= cacheService
->DiskCacheStorage(loadInfo
, getter_AddRefs(cacheStorage
));
225 } else if (storageName
== "memory") {
226 rv
= cacheService
->MemoryCacheStorage(loadInfo
,
227 getter_AddRefs(cacheStorage
));
229 rv
= NS_ERROR_UNEXPECTED
;
231 if (NS_FAILED(rv
)) return rv
;
233 cacheStorage
.forget(storage
);
238 nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount
,
239 uint64_t aConsumption
,
241 nsIFile
* aDirectory
) {
242 // We need mStream for this
244 return NS_ERROR_FAILURE
;
247 mBuffer
.AssignLiteral("<h2>");
248 nsAppendEscapedHTML(mStorageName
, mBuffer
);
249 mBuffer
.AppendLiteral(
252 mBuffer
.AppendLiteral("\">\n");
254 // Write out cache info
256 mBuffer
.AppendLiteral(
258 " <th>Number of entries:</th>\n"
260 mBuffer
.AppendInt(aEntryCount
);
261 mBuffer
.AppendLiteral(
265 // Maximum storage size
266 mBuffer
.AppendLiteral(
268 " <th>Maximum storage size:</th>\n"
270 mBuffer
.AppendInt(aCapacity
/ 1024);
271 mBuffer
.AppendLiteral(
276 mBuffer
.AppendLiteral(
278 " <th>Storage in use:</th>\n"
280 mBuffer
.AppendInt(aConsumption
/ 1024);
281 mBuffer
.AppendLiteral(
285 // Storage disk location
286 mBuffer
.AppendLiteral(
288 " <th>Storage disk location:</th>\n"
292 aDirectory
->GetPath(path
);
293 mBuffer
.Append(NS_ConvertUTF16toUTF8(path
));
295 mBuffer
.AppendLiteral("none, only stored in memory");
297 mBuffer
.AppendLiteral(
301 if (mOverview
) { // The about:cache case
302 if (aEntryCount
!= 0) { // Add the "List Cache Entries" link
303 mBuffer
.AppendLiteral(
305 " <td colspan=\"2\"><a href=\"about:cache?storage=");
306 nsAppendEscapedHTML(mStorageName
, mBuffer
);
307 mBuffer
.AppendLiteral(
308 "\">List Cache Entries</a></td>\n"
313 mBuffer
.AppendLiteral("</table>\n");
315 // The entries header is added on encounter of the first entry
316 mEntriesHeaderAdded
= false;
318 nsresult rv
= FlushBuffer();
320 NS_WARNING("Failed to flush buffer");
324 // OnCacheEntryVisitCompleted() is not called when we do not iterate
325 // cache entries. Since this moves forward to the next storage in
326 // the list we want to visit, artificially call it here.
327 OnCacheEntryVisitCompleted();
334 nsAboutCache::Channel::OnCacheEntryInfo(
335 nsIURI
* aURI
, const nsACString
& aIdEnhance
, int64_t aDataSize
,
336 int64_t aAltDataSize
, uint32_t aFetchCount
, uint32_t aLastModified
,
337 uint32_t aExpirationTime
, bool aPinned
, nsILoadContextInfo
* aInfo
) {
338 // We need mStream for this
339 if (!mStream
|| mCancel
) {
340 // Returning a failure from this callback stops the iteration
341 return NS_ERROR_FAILURE
;
344 if (!mEntriesHeaderAdded
) {
345 mBuffer
.AppendLiteral(
347 "<table id=\"entries\">\n"
349 " <col id=\"col-key\">\n"
350 " <col id=\"col-dataSize\">\n"
351 " <col id=\"col-altDataSize\">\n"
352 " <col id=\"col-fetchCount\">\n"
353 " <col id=\"col-lastModified\">\n"
354 " <col id=\"col-expires\">\n"
355 " <col id=\"col-pinned\">\n"
360 " <th>Data size</th>\n"
361 " <th>Alternative Data size</th>\n"
362 " <th>Fetch count</th>\n"
363 " <th>Last Modifed</th>\n"
364 " <th>Expires</th>\n"
365 " <th>Pinning</th>\n"
368 mEntriesHeaderAdded
= true;
371 // Generate a about:cache-entry URL for this entry...
374 url
.AssignLiteral("about:cache-entry?storage=");
375 nsAppendEscapedHTML(mStorageName
, url
);
377 nsAutoCString context
;
378 CacheFileUtils::AppendKeyPrefix(aInfo
, context
);
379 url
.AppendLiteral("&context=");
380 nsAppendEscapedHTML(context
, url
);
382 url
.AppendLiteral("&eid=");
383 nsAppendEscapedHTML(aIdEnhance
, url
);
385 nsAutoCString cacheUriSpec
;
386 aURI
->GetAsciiSpec(cacheUriSpec
);
387 nsAutoCString escapedCacheURI
;
388 nsAppendEscapedHTML(cacheUriSpec
, escapedCacheURI
);
389 url
.AppendLiteral("&uri=");
390 url
+= escapedCacheURI
;
393 mBuffer
.AppendLiteral(" <tr>\n");
396 mBuffer
.AppendLiteral(" <td><a href=\"");
398 mBuffer
.AppendLiteral("\">");
399 if (!aIdEnhance
.IsEmpty()) {
400 nsAppendEscapedHTML(aIdEnhance
, mBuffer
);
403 mBuffer
.Append(escapedCacheURI
);
404 mBuffer
.AppendLiteral("</a>");
406 if (!context
.IsEmpty()) {
407 mBuffer
.AppendLiteral("<br><span title=\"Context separation key\">");
408 nsAutoCString escapedContext
;
409 nsAppendEscapedHTML(context
, escapedContext
);
410 mBuffer
.Append(escapedContext
);
411 mBuffer
.AppendLiteral("</span>");
414 mBuffer
.AppendLiteral("</td>\n");
417 mBuffer
.AppendLiteral(" <td>");
418 mBuffer
.AppendInt(aDataSize
);
419 mBuffer
.AppendLiteral(" bytes</td>\n");
421 // Length of alternative content
422 mBuffer
.AppendLiteral(" <td>");
423 mBuffer
.AppendInt(aAltDataSize
);
424 mBuffer
.AppendLiteral(" bytes</td>\n");
426 // Number of accesses
427 mBuffer
.AppendLiteral(" <td>");
428 mBuffer
.AppendInt(aFetchCount
);
429 mBuffer
.AppendLiteral("</td>\n");
431 // vars for reporting time
434 // Last modified time
435 mBuffer
.AppendLiteral(" <td>");
437 PrintTimeString(buf
, sizeof(buf
), aLastModified
);
440 mBuffer
.AppendLiteral("No last modified time");
442 mBuffer
.AppendLiteral("</td>\n");
445 mBuffer
.AppendLiteral(" <td>");
448 // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
449 // So we check if time is 0, then we show a message, "Expired Immediately"
450 if (aExpirationTime
== 0) {
451 mBuffer
.AppendLiteral("Expired Immediately");
452 } else if (aExpirationTime
< 0xFFFFFFFF) {
453 PrintTimeString(buf
, sizeof(buf
), aExpirationTime
);
456 mBuffer
.AppendLiteral("No expiration time");
458 mBuffer
.AppendLiteral("</td>\n");
461 mBuffer
.AppendLiteral(" <td>");
463 mBuffer
.AppendLiteral("Pinned");
465 mBuffer
.AppendLiteral(" ");
467 mBuffer
.AppendLiteral("</td>\n");
470 mBuffer
.AppendLiteral(" </tr>\n");
472 return FlushBuffer();
476 nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
478 return NS_ERROR_FAILURE
;
481 if (mEntriesHeaderAdded
) {
482 mBuffer
.AppendLiteral("</table>\n");
485 // Kick another storage visiting (from a storage that allows us.)
486 while (mStorageList
.Length()) {
487 nsresult rv
= VisitNextStorage();
488 if (NS_SUCCEEDED(rv
)) {
489 // Expecting new round of OnCache* calls.
495 mBuffer
.AppendLiteral(
498 nsresult rv
= FlushBuffer();
500 NS_WARNING("Failed to flush buffer");
507 nsresult
nsAboutCache::Channel::FlushBuffer() {
510 uint32_t bytesWritten
;
511 rv
= mStream
->Write(mBuffer
.get(), mBuffer
.Length(), &bytesWritten
);
522 nsAboutCache::GetURIFlags(nsIURI
* aURI
, uint32_t* result
) {
523 *result
= nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT
|
524 nsIAboutModule::IS_SECURE_CHROME_UI
;
529 nsresult
nsAboutCache::Create(REFNSIID aIID
, void** aResult
) {
530 RefPtr
<nsAboutCache
> about
= new nsAboutCache();
531 return about
->QueryInterface(aIID
, aResult
);
535 nsAboutCache::GetChromeURI(nsIURI
* aURI
, nsIURI
** chromeURI
) {
536 return NS_ERROR_ILLEGAL_VALUE
;
539 ////////////////////////////////////////////////////////////////////////////////