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/. */
5 
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"
16 
17 #include "nsICacheStorageService.h"
18 #include "nsICacheStorage.h"
19 #include "CacheFileUtils.h"
20 #include "CacheObserver.h"
21 
22 #include "nsThreadUtils.h"
23 
24 using namespace mozilla::net;
25 
NS_IMPL_ISUPPORTS(nsAboutCache,nsIAboutModule)26 NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule)
27 NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest,
28                   nsICacheStorageVisitor)
29 
30 NS_IMETHODIMP
31 nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
32                          nsIChannel** result) {
33   nsresult rv;
34 
35   NS_ENSURE_ARG_POINTER(aURI);
36 
37   RefPtr<Channel> channel = new Channel();
38   rv = channel->Init(aURI, aLoadInfo);
39   if (NS_FAILED(rv)) return rv;
40 
41   channel.forget(result);
42 
43   return NS_OK;
44 }
45 
Init(nsIURI * aURI,nsILoadInfo * aLoadInfo)46 nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
47   nsresult rv;
48 
49   mCancel = false;
50 
51   nsCOMPtr<nsIInputStream> inputStream;
52   rv = NS_NewPipe(getter_AddRefs(inputStream), getter_AddRefs(mStream), 16384,
53                   (uint32_t)-1,
54                   true,  // non-blocking input
55                   false  // blocking output
56   );
57   if (NS_FAILED(rv)) return rv;
58 
59   nsAutoCString storageName;
60   rv = ParseURI(aURI, storageName);
61   if (NS_FAILED(rv)) return rv;
62 
63   mOverview = storageName.IsEmpty();
64   if (mOverview) {
65     // ...and visit all we can
66     mStorageList.AppendElement("memory"_ns);
67     mStorageList.AppendElement("disk"_ns);
68   } else {
69     // ...and visit just the specified storage, entries will output too
70     mStorageList.AppendElement(storageName);
71   }
72 
73   // The entries header is added on encounter of the first entry
74   mEntriesHeaderAdded = false;
75 
76   rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), aURI,
77                                         inputStream.forget(), "text/html"_ns,
78                                         "utf-8"_ns, aLoadInfo);
79   if (NS_FAILED(rv)) return rv;
80 
81   mBuffer.AssignLiteral(
82       "<!DOCTYPE html>\n"
83       "<html>\n"
84       "<head>\n"
85       "  <title>Network Cache Storage Information</title>\n"
86       "  <meta charset=\"utf-8\">\n"
87       "  <meta http-equiv=\"Content-Security-Policy\" content=\"default-src "
88       "chrome:; object-src 'none'\"/>\n"
89       "  <link rel=\"stylesheet\" href=\"chrome://global/skin/about.css\"/>\n"
90       "  <link rel=\"stylesheet\" "
91       "href=\"chrome://global/skin/aboutCache.css\"/>\n"
92       "</head>\n"
93       "<body class=\"aboutPageWideContainer\">\n"
94       "<h1>Information about the Network Cache Storage Service</h1>\n");
95 
96   if (!mOverview) {
97     mBuffer.AppendLiteral(
98         "<a href=\"about:cache?storage=\">Back to overview</a>");
99   }
100 
101   rv = FlushBuffer();
102   if (NS_FAILED(rv)) {
103     NS_WARNING("Failed to flush buffer");
104   }
105 
106   return NS_OK;
107 }
108 
AsyncOpen(nsIStreamListener * aListener)109 NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) {
110   nsresult rv;
111 
112   if (!mChannel) {
113     return NS_ERROR_UNEXPECTED;
114   }
115 
116   // Kick the walk loop.
117   rv = VisitNextStorage();
118   if (NS_FAILED(rv)) return rv;
119 
120   rv = NS_MaybeOpenChannelUsingAsyncOpen(mChannel, aListener);
121   if (NS_FAILED(rv)) return rv;
122 
123   return NS_OK;
124 }
125 
Open(nsIInputStream ** _retval)126 NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) {
127   return NS_ERROR_NOT_IMPLEMENTED;
128 }
129 
ParseURI(nsIURI * uri,nsACString & storage)130 nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) {
131   //
132   // about:cache[?storage=<storage-name>[&context=<context-key>]]
133   //
134   nsresult rv;
135 
136   nsAutoCString path;
137   rv = uri->GetPathQueryRef(path);
138   if (NS_FAILED(rv)) return rv;
139 
140   storage.Truncate();
141 
142   nsACString::const_iterator start, valueStart, end;
143   path.BeginReading(start);
144   path.EndReading(end);
145 
146   valueStart = end;
147   if (!FindInReadable("?storage="_ns, start, valueStart)) {
148     return NS_OK;
149   }
150 
151   storage.Assign(Substring(valueStart, end));
152 
153   return NS_OK;
154 }
155 
VisitNextStorage()156 nsresult nsAboutCache::Channel::VisitNextStorage() {
157   if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE;
158 
159   mStorageName = mStorageList[0];
160   mStorageList.RemoveElementAt(0);
161 
162   // Must re-dispatch since we cannot start another visit cycle
163   // from visitor callback.  The cache v1 service doesn't like it.
164   // TODO - mayhemer, bug 913828, remove this dispatch and call
165   // directly.
166   return NS_DispatchToMainThread(mozilla::NewRunnableMethod(
167       "nsAboutCache::Channel::FireVisitStorage", this,
168       &nsAboutCache::Channel::FireVisitStorage));
169 }
170 
FireVisitStorage()171 void nsAboutCache::Channel::FireVisitStorage() {
172   nsresult rv;
173 
174   rv = VisitStorage(mStorageName);
175   if (NS_FAILED(rv)) {
176     nsAutoCString escaped;
177     nsAppendEscapedHTML(mStorageName, escaped);
178     mBuffer.Append(nsPrintfCString(
179         "<p>Unrecognized storage name '%s' in about:cache URL</p>",
180         escaped.get()));
181 
182     rv = FlushBuffer();
183     if (NS_FAILED(rv)) {
184       NS_WARNING("Failed to flush buffer");
185     }
186 
187     // Simulate finish of a visit cycle, this tries the next storage
188     // or closes the output stream (i.e. the UI loader will stop spinning)
189     OnCacheEntryVisitCompleted();
190   }
191 }
192 
VisitStorage(nsACString const & storageName)193 nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) {
194   nsresult rv;
195 
196   rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage));
197   if (NS_FAILED(rv)) return rv;
198 
199   rv = mStorage->AsyncVisitStorage(this, !mOverview);
200   if (NS_FAILED(rv)) return rv;
201 
202   return NS_OK;
203 }
204 
205 // static
GetStorage(nsACString const & storageName,nsILoadContextInfo * loadInfo,nsICacheStorage ** storage)206 nsresult nsAboutCache::GetStorage(nsACString const& storageName,
207                                   nsILoadContextInfo* loadInfo,
208                                   nsICacheStorage** storage) {
209   nsresult rv;
210 
211   nsCOMPtr<nsICacheStorageService> cacheService =
212       do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
213   if (NS_FAILED(rv)) return rv;
214 
215   nsCOMPtr<nsICacheStorage> cacheStorage;
216   if (storageName == "disk") {
217     rv = cacheService->DiskCacheStorage(loadInfo, getter_AddRefs(cacheStorage));
218   } else if (storageName == "memory") {
219     rv = cacheService->MemoryCacheStorage(loadInfo,
220                                           getter_AddRefs(cacheStorage));
221   } else {
222     rv = NS_ERROR_UNEXPECTED;
223   }
224   if (NS_FAILED(rv)) return rv;
225 
226   cacheStorage.forget(storage);
227   return NS_OK;
228 }
229 
230 NS_IMETHODIMP
OnCacheStorageInfo(uint32_t aEntryCount,uint64_t aConsumption,uint64_t aCapacity,nsIFile * aDirectory)231 nsAboutCache::Channel::OnCacheStorageInfo(uint32_t aEntryCount,
232                                           uint64_t aConsumption,
233                                           uint64_t aCapacity,
234                                           nsIFile* aDirectory) {
235   // We need mStream for this
236   if (!mStream) {
237     return NS_ERROR_FAILURE;
238   }
239 
240   mBuffer.AssignLiteral("<h2>");
241   nsAppendEscapedHTML(mStorageName, mBuffer);
242   mBuffer.AppendLiteral(
243       "</h2>\n"
244       "<table id=\"");
245   mBuffer.AppendLiteral("\">\n");
246 
247   // Write out cache info
248   // Number of entries
249   mBuffer.AppendLiteral(
250       "  <tr>\n"
251       "    <th>Number of entries:</th>\n"
252       "    <td>");
253   mBuffer.AppendInt(aEntryCount);
254   mBuffer.AppendLiteral(
255       "</td>\n"
256       "  </tr>\n");
257 
258   // Maximum storage size
259   mBuffer.AppendLiteral(
260       "  <tr>\n"
261       "    <th>Maximum storage size:</th>\n"
262       "    <td>");
263   mBuffer.AppendInt(aCapacity / 1024);
264   mBuffer.AppendLiteral(
265       " KiB</td>\n"
266       "  </tr>\n");
267 
268   // Storage in use
269   mBuffer.AppendLiteral(
270       "  <tr>\n"
271       "    <th>Storage in use:</th>\n"
272       "    <td>");
273   mBuffer.AppendInt(aConsumption / 1024);
274   mBuffer.AppendLiteral(
275       " KiB</td>\n"
276       "  </tr>\n");
277 
278   // Storage disk location
279   mBuffer.AppendLiteral(
280       "  <tr>\n"
281       "    <th>Storage disk location:</th>\n"
282       "    <td>");
283   if (aDirectory) {
284     nsAutoString path;
285     aDirectory->GetPath(path);
286     mBuffer.Append(NS_ConvertUTF16toUTF8(path));
287   } else {
288     mBuffer.AppendLiteral("none, only stored in memory");
289   }
290   mBuffer.AppendLiteral(
291       "    </td>\n"
292       "  </tr>\n");
293 
294   if (mOverview) {           // The about:cache case
295     if (aEntryCount != 0) {  // Add the "List Cache Entries" link
296       mBuffer.AppendLiteral(
297           "  <tr>\n"
298           "    <th><a href=\"about:cache?storage=");
299       nsAppendEscapedHTML(mStorageName, mBuffer);
300       mBuffer.AppendLiteral(
301           "\">List Cache Entries</a></th>\n"
302           "  </tr>\n");
303     }
304   }
305 
306   mBuffer.AppendLiteral("</table>\n");
307 
308   // The entries header is added on encounter of the first entry
309   mEntriesHeaderAdded = false;
310 
311   nsresult rv = FlushBuffer();
312   if (NS_FAILED(rv)) {
313     NS_WARNING("Failed to flush buffer");
314   }
315 
316   if (mOverview) {
317     // OnCacheEntryVisitCompleted() is not called when we do not iterate
318     // cache entries.  Since this moves forward to the next storage in
319     // the list we want to visit, artificially call it here.
320     OnCacheEntryVisitCompleted();
321   }
322 
323   return NS_OK;
324 }
325 
326 NS_IMETHODIMP
OnCacheEntryInfo(nsIURI * aURI,const nsACString & aIdEnhance,int64_t aDataSize,int32_t aFetchCount,uint32_t aLastModified,uint32_t aExpirationTime,bool aPinned,nsILoadContextInfo * aInfo)327 nsAboutCache::Channel::OnCacheEntryInfo(nsIURI* aURI,
328                                         const nsACString& aIdEnhance,
329                                         int64_t aDataSize, int32_t aFetchCount,
330                                         uint32_t aLastModified,
331                                         uint32_t aExpirationTime, bool aPinned,
332                                         nsILoadContextInfo* aInfo) {
333   // We need mStream for this
334   if (!mStream || mCancel) {
335     // Returning a failure from this callback stops the iteration
336     return NS_ERROR_FAILURE;
337   }
338 
339   if (!mEntriesHeaderAdded) {
340     mBuffer.AppendLiteral(
341         "<hr/>\n"
342         "<table id=\"entries\">\n"
343         "  <colgroup>\n"
344         "   <col id=\"col-key\">\n"
345         "   <col id=\"col-dataSize\">\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"
350         "  </colgroup>\n"
351         "  <thead>\n"
352         "    <tr>\n"
353         "      <th>Key</th>\n"
354         "      <th>Data size</th>\n"
355         "      <th>Fetch count</th>\n"
356         "      <th>Last Modifed</th>\n"
357         "      <th>Expires</th>\n"
358         "      <th>Pinning</th>\n"
359         "    </tr>\n"
360         "  </thead>\n");
361     mEntriesHeaderAdded = true;
362   }
363 
364   // Generate a about:cache-entry URL for this entry...
365 
366   nsAutoCString url;
367   url.AssignLiteral("about:cache-entry?storage=");
368   nsAppendEscapedHTML(mStorageName, url);
369 
370   nsAutoCString context;
371   CacheFileUtils::AppendKeyPrefix(aInfo, context);
372   url.AppendLiteral("&amp;context=");
373   nsAppendEscapedHTML(context, url);
374 
375   url.AppendLiteral("&amp;eid=");
376   nsAppendEscapedHTML(aIdEnhance, url);
377 
378   nsAutoCString cacheUriSpec;
379   aURI->GetAsciiSpec(cacheUriSpec);
380   nsAutoCString escapedCacheURI;
381   nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
382   url.AppendLiteral("&amp;uri=");
383   url += escapedCacheURI;
384 
385   // Entry start...
386   mBuffer.AppendLiteral("  <tr>\n");
387 
388   // URI
389   mBuffer.AppendLiteral("    <td><a href=\"");
390   mBuffer.Append(url);
391   mBuffer.AppendLiteral("\">");
392   if (!aIdEnhance.IsEmpty()) {
393     nsAppendEscapedHTML(aIdEnhance, mBuffer);
394     mBuffer.Append(':');
395   }
396   mBuffer.Append(escapedCacheURI);
397   mBuffer.AppendLiteral("</a>");
398 
399   if (!context.IsEmpty()) {
400     mBuffer.AppendLiteral("<br><span title=\"Context separation key\">");
401     nsAutoCString escapedContext;
402     nsAppendEscapedHTML(context, escapedContext);
403     mBuffer.Append(escapedContext);
404     mBuffer.AppendLiteral("</span>");
405   }
406 
407   mBuffer.AppendLiteral("</td>\n");
408 
409   // Content length
410   mBuffer.AppendLiteral("    <td>");
411   mBuffer.AppendInt(aDataSize);
412   mBuffer.AppendLiteral(" bytes</td>\n");
413 
414   // Number of accesses
415   mBuffer.AppendLiteral("    <td>");
416   mBuffer.AppendInt(aFetchCount);
417   mBuffer.AppendLiteral("</td>\n");
418 
419   // vars for reporting time
420   char buf[255];
421 
422   // Last modified time
423   mBuffer.AppendLiteral("    <td>");
424   if (aLastModified) {
425     PrintTimeString(buf, sizeof(buf), aLastModified);
426     mBuffer.Append(buf);
427   } else {
428     mBuffer.AppendLiteral("No last modified time");
429   }
430   mBuffer.AppendLiteral("</td>\n");
431 
432   // Expires time
433   mBuffer.AppendLiteral("    <td>");
434 
435   // Bug - 633747.
436   // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing.
437   // So we check if time is 0, then we show a message, "Expired Immediately"
438   if (aExpirationTime == 0) {
439     mBuffer.AppendLiteral("Expired Immediately");
440   } else if (aExpirationTime < 0xFFFFFFFF) {
441     PrintTimeString(buf, sizeof(buf), aExpirationTime);
442     mBuffer.Append(buf);
443   } else {
444     mBuffer.AppendLiteral("No expiration time");
445   }
446   mBuffer.AppendLiteral("</td>\n");
447 
448   // Pinning
449   mBuffer.AppendLiteral("    <td>");
450   if (aPinned) {
451     mBuffer.AppendLiteral("Pinned");
452   } else {
453     mBuffer.AppendLiteral("&nbsp;");
454   }
455   mBuffer.AppendLiteral("</td>\n");
456 
457   // Entry is done...
458   mBuffer.AppendLiteral("  </tr>\n");
459 
460   return FlushBuffer();
461 }
462 
463 NS_IMETHODIMP
OnCacheEntryVisitCompleted()464 nsAboutCache::Channel::OnCacheEntryVisitCompleted() {
465   if (!mStream) {
466     return NS_ERROR_FAILURE;
467   }
468 
469   if (mEntriesHeaderAdded) {
470     mBuffer.AppendLiteral("</table>\n");
471   }
472 
473   // Kick another storage visiting (from a storage that allows us.)
474   while (mStorageList.Length()) {
475     nsresult rv = VisitNextStorage();
476     if (NS_SUCCEEDED(rv)) {
477       // Expecting new round of OnCache* calls.
478       return NS_OK;
479     }
480   }
481 
482   // We are done!
483   mBuffer.AppendLiteral(
484       "</body>\n"
485       "</html>\n");
486   nsresult rv = FlushBuffer();
487   if (NS_FAILED(rv)) {
488     NS_WARNING("Failed to flush buffer");
489   }
490   mStream->Close();
491 
492   return NS_OK;
493 }
494 
FlushBuffer()495 nsresult nsAboutCache::Channel::FlushBuffer() {
496   nsresult rv;
497 
498   uint32_t bytesWritten;
499   rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten);
500   mBuffer.Truncate();
501 
502   if (NS_FAILED(rv)) {
503     mCancel = true;
504   }
505 
506   return rv;
507 }
508 
509 NS_IMETHODIMP
GetURIFlags(nsIURI * aURI,uint32_t * result)510 nsAboutCache::GetURIFlags(nsIURI* aURI, uint32_t* result) {
511   *result = nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT;
512   return NS_OK;
513 }
514 
515 // static
Create(nsISupports * aOuter,REFNSIID aIID,void ** aResult)516 nsresult nsAboutCache::Create(nsISupports* aOuter, REFNSIID aIID,
517                               void** aResult) {
518   RefPtr<nsAboutCache> about = new nsAboutCache();
519   return about->QueryInterface(aIID, aResult);
520 }
521 
522 NS_IMETHODIMP
GetChromeURI(nsIURI * aURI,nsIURI ** chromeURI)523 nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
524   return NS_ERROR_ILLEGAL_VALUE;
525 }
526 
527 ////////////////////////////////////////////////////////////////////////////////
528