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=&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("&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("&context=");
428 nsAppendEscapedHTML(mContextString, url);
429
430 url.AppendLiteral("&eid=");
431 nsAppendEscapedHTML(aIdEnhance, url);
432
433 nsAutoCString cacheUriSpec;
434 aURI->GetAsciiSpec(cacheUriSpec);
435 nsAutoCString escapedCacheURI;
436 nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI);
437 url.AppendLiteral("&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(" ");
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