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