Backed out 3 changesets (bug 1844563, bug 1906826) for crashes a=backout
[gecko.git] / netwerk / protocol / about / nsAboutCache.cpp
blob9dde979e9f46eea14ebf57b3edef7bbb5a4355ed
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"
8 #include "nsIURI.h"
9 #include "nsCOMPtr.h"
10 #include "nsNetUtil.h"
11 #include "nsIPipe.h"
12 #include "nsContentUtils.h"
13 #include "nsEscape.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)
32 NS_IMETHODIMP
33 nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
34 nsIChannel** result) {
35 nsresult rv;
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);
45 return NS_OK;
48 nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
49 nsresult rv;
51 mCancel = false;
53 nsCOMPtr<nsIInputStream> inputStream;
54 NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
55 (uint32_t)-1,
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();
65 if (mOverview) {
66 // ...and visit all we can
67 mStorageList.AppendElement("memory"_ns);
68 mStorageList.AppendElement("disk"_ns);
69 } else {
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(
83 "<!DOCTYPE html>\n"
84 "<html>\n"
85 "<head>\n"
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"
95 "</head>\n"
96 "<body class=\"aboutPageWideContainer\">\n"
97 "<h1>Information about the Network Cache Storage Service</h1>\n");
99 if (!mOverview) {
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");
108 rv = FlushBuffer();
109 if (NS_FAILED(rv)) {
110 NS_WARNING("Failed to flush buffer");
113 return NS_OK;
116 NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) {
117 nsresult rv;
119 if (!mChannel) {
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;
130 return NS_OK;
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>]]
141 nsresult rv;
143 nsAutoCString path;
144 rv = uri->GetPathQueryRef(path);
145 if (NS_FAILED(rv)) return rv;
147 storage.Truncate();
149 nsACString::const_iterator start, valueStart, end;
150 path.BeginReading(start);
151 path.EndReading(end);
153 valueStart = end;
154 if (!FindInReadable("?storage="_ns, start, valueStart)) {
155 return NS_OK;
158 storage.Assign(Substring(valueStart, end));
160 return NS_OK;
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
172 // directly.
173 return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
174 "nsAboutCache::Channel::FireVisitStorage", this,
175 &nsAboutCache::Channel::FireVisitStorage));
178 void nsAboutCache::Channel::FireVisitStorage() {
179 nsresult rv;
181 rv = VisitStorage(mStorageName);
182 if (NS_FAILED(rv)) {
183 nsAutoCString escaped;
184 nsAppendEscapedHTML(mStorageName, escaped);
185 mBuffer.Append(nsPrintfCString(
186 "<p>Unrecognized storage name '%s' in about:cache URL</p>",
187 escaped.get()));
189 rv = FlushBuffer();
190 if (NS_FAILED(rv)) {
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) {
201 nsresult rv;
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;
209 return NS_OK;
212 // static
213 nsresult nsAboutCache::GetStorage(nsACString const& storageName,
214 nsILoadContextInfo* loadInfo,
215 nsICacheStorage** storage) {
216 nsresult rv;
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));
228 } else {
229 rv = NS_ERROR_UNEXPECTED;
231 if (NS_FAILED(rv)) return rv;
233 cacheStorage.forget(storage);
234 return NS_OK;
237 NS_IMETHODIMP
238 nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount,
239 uint64_t aConsumption,
240 uint64_t aCapacity,
241 nsIFile* aDirectory) {
242 // We need mStream for this
243 if (!mStream) {
244 return NS_ERROR_FAILURE;
247 mBuffer.AssignLiteral("<h2>");
248 nsAppendEscapedHTML(mStorageName, mBuffer);
249 mBuffer.AppendLiteral(
250 "</h2>\n"
251 "<table id=\"");
252 mBuffer.AppendLiteral("\">\n");
254 // Write out cache info
255 // Number of entries
256 mBuffer.AppendLiteral(
257 " <tr>\n"
258 " <th>Number of entries:</th>\n"
259 " <td>");
260 mBuffer.AppendInt(aEntryCount);
261 mBuffer.AppendLiteral(
262 "</td>\n"
263 " </tr>\n");
265 // Maximum storage size
266 mBuffer.AppendLiteral(
267 " <tr>\n"
268 " <th>Maximum storage size:</th>\n"
269 " <td>");
270 mBuffer.AppendInt(aCapacity / 1024);
271 mBuffer.AppendLiteral(
272 " KiB</td>\n"
273 " </tr>\n");
275 // Storage in use
276 mBuffer.AppendLiteral(
277 " <tr>\n"
278 " <th>Storage in use:</th>\n"
279 " <td>");
280 mBuffer.AppendInt(aConsumption / 1024);
281 mBuffer.AppendLiteral(
282 " KiB</td>\n"
283 " </tr>\n");
285 // Storage disk location
286 mBuffer.AppendLiteral(
287 " <tr>\n"
288 " <th>Storage disk location:</th>\n"
289 " <td>");
290 if (aDirectory) {
291 nsAutoString path;
292 aDirectory->GetPath(path);
293 mBuffer.Append(NS_ConvertUTF16toUTF8(path));
294 } else {
295 mBuffer.AppendLiteral("none, only stored in memory");
297 mBuffer.AppendLiteral(
298 " </td>\n"
299 " </tr>\n");
301 if (mOverview) { // The about:cache case
302 if (aEntryCount != 0) { // Add the "List Cache Entries" link
303 mBuffer.AppendLiteral(
304 " <tr>\n"
305 " <td colspan=\"2\"><a href=\"about:cache?storage=");
306 nsAppendEscapedHTML(mStorageName, mBuffer);
307 mBuffer.AppendLiteral(
308 "\">List Cache Entries</a></td>\n"
309 " </tr>\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();
319 if (NS_FAILED(rv)) {
320 NS_WARNING("Failed to flush buffer");
323 if (mOverview) {
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();
330 return NS_OK;
333 NS_IMETHODIMP
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(
346 "<hr/>\n"
347 "<table id=\"entries\">\n"
348 " <colgroup>\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"
356 " </colgroup>\n"
357 " <thead>\n"
358 " <tr>\n"
359 " <th>Key</th>\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"
366 " </tr>\n"
367 " </thead>\n");
368 mEntriesHeaderAdded = true;
371 // Generate a about:cache-entry URL for this entry...
373 nsAutoCString url;
374 url.AssignLiteral("about:cache-entry?storage=");
375 nsAppendEscapedHTML(mStorageName, url);
377 nsAutoCString context;
378 CacheFileUtils::AppendKeyPrefix(aInfo, context);
379 url.AppendLiteral("&amp;context=");
380 nsAppendEscapedHTML(context, url);
382 url.AppendLiteral("&amp;eid=");
383 nsAppendEscapedHTML(aIdEnhance, url);
385 nsAutoCString cacheUriSpec;
386 aURI->GetAsciiSpec(cacheUriSpec);
387 nsAutoCString escapedCacheURI;
388 nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
389 url.AppendLiteral("&amp;uri=");
390 url += escapedCacheURI;
392 // Entry start...
393 mBuffer.AppendLiteral(" <tr>\n");
395 // URI
396 mBuffer.AppendLiteral(" <td><a href=\"");
397 mBuffer.Append(url);
398 mBuffer.AppendLiteral("\">");
399 if (!aIdEnhance.IsEmpty()) {
400 nsAppendEscapedHTML(aIdEnhance, mBuffer);
401 mBuffer.Append(':');
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");
416 // Content length
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
432 char buf[255];
434 // Last modified time
435 mBuffer.AppendLiteral(" <td>");
436 if (aLastModified) {
437 PrintTimeString(buf, sizeof(buf), aLastModified);
438 mBuffer.Append(buf);
439 } else {
440 mBuffer.AppendLiteral("No last modified time");
442 mBuffer.AppendLiteral("</td>\n");
444 // Expires time
445 mBuffer.AppendLiteral(" <td>");
447 // Bug - 633747.
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);
454 mBuffer.Append(buf);
455 } else {
456 mBuffer.AppendLiteral("No expiration time");
458 mBuffer.AppendLiteral("</td>\n");
460 // Pinning
461 mBuffer.AppendLiteral(" <td>");
462 if (aPinned) {
463 mBuffer.AppendLiteral("Pinned");
464 } else {
465 mBuffer.AppendLiteral("&nbsp;");
467 mBuffer.AppendLiteral("</td>\n");
469 // Entry is done...
470 mBuffer.AppendLiteral(" </tr>\n");
472 return FlushBuffer();
475 NS_IMETHODIMP
476 nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
477 if (!mStream) {
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.
490 return NS_OK;
494 // We are done!
495 mBuffer.AppendLiteral(
496 "</body>\n"
497 "</html>\n");
498 nsresult rv = FlushBuffer();
499 if (NS_FAILED(rv)) {
500 NS_WARNING("Failed to flush buffer");
502 mStream->Close();
504 return NS_OK;
507 nsresult nsAboutCache::Channel::FlushBuffer() {
508 nsresult rv;
510 uint32_t bytesWritten;
511 rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
512 mBuffer.Truncate();
514 if (NS_FAILED(rv)) {
515 mCancel = true;
518 return rv;
521 NS_IMETHODIMP
522 nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) {
523 *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
524 nsIAboutModule::IS_SECURE_CHROME_UI;
525 return NS_OK;
528 // static
529 nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) {
530 RefPtr<nsAboutCache> about = new nsAboutCache();
531 return about->QueryInterface(aIID, aResult);
534 NS_IMETHODIMP
535 nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
536 return NS_ERROR_ILLEGAL_VALUE;
539 ////////////////////////////////////////////////////////////////////////////////