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