nsAboutCache.cpp (14879B)
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 #include "mozilla/Components.h" 25 26 using namespace mozilla::net; 27 28 NS_IMPL_ISUPPORTS(nsAboutCache, nsIAboutModule) 29 NS_IMPL_ISUPPORTS(nsAboutCache::Channel, nsIChannel, nsIRequest, 30 nsICacheStorageVisitor) 31 32 NS_IMETHODIMP 33 nsAboutCache::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, 34 nsIChannel** result) { 35 nsresult rv; 36 37 NS_ENSURE_ARG_POINTER(aURI); 38 39 RefPtr<Channel> channel = new Channel(); 40 rv = channel->Init(aURI, aLoadInfo); 41 if (NS_FAILED(rv)) return rv; 42 43 channel.forget(result); 44 45 return NS_OK; 46 } 47 48 nsresult nsAboutCache::Channel::Init(nsIURI* aURI, nsILoadInfo* aLoadInfo) { 49 nsresult rv; 50 51 mCancel = false; 52 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 58 ); 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("memory"_ns); 68 mStorageList.AppendElement("disk"_ns); 69 } else { 70 // ...and visit just the specified storage, entries will output too 71 mStorageList.AppendElement(storageName); 72 } 73 74 // The entries header is added on encounter of the first entry 75 mEntriesHeaderAdded = false; 76 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; 81 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"); 98 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"); 106 } 107 108 rv = FlushBuffer(); 109 if (NS_FAILED(rv)) { 110 NS_WARNING("Failed to flush buffer"); 111 } 112 113 return NS_OK; 114 } 115 116 NS_IMETHODIMP nsAboutCache::Channel::AsyncOpen(nsIStreamListener* aListener) { 117 nsresult rv; 118 119 if (!mChannel) { 120 return NS_ERROR_UNEXPECTED; 121 } 122 123 // Kick the walk loop. 124 rv = VisitNextStorage(); 125 if (NS_FAILED(rv)) return rv; 126 127 rv = mChannel->AsyncOpen(aListener); 128 if (NS_FAILED(rv)) return rv; 129 130 return NS_OK; 131 } 132 133 NS_IMETHODIMP nsAboutCache::Channel::Open(nsIInputStream** _retval) { 134 return NS_ERROR_NOT_IMPLEMENTED; 135 } 136 137 nsresult nsAboutCache::Channel::ParseURI(nsIURI* uri, nsACString& storage) { 138 // 139 // about:cache[?storage=<storage-name>[&context=<context-key>]] 140 // 141 nsresult rv; 142 143 nsAutoCString path; 144 rv = uri->GetPathQueryRef(path); 145 if (NS_FAILED(rv)) return rv; 146 147 storage.Truncate(); 148 149 nsACString::const_iterator start, valueStart, end; 150 path.BeginReading(start); 151 path.EndReading(end); 152 153 valueStart = end; 154 if (!FindInReadable("?storage="_ns, start, valueStart)) { 155 return NS_OK; 156 } 157 158 storage.Assign(Substring(valueStart, end)); 159 160 return NS_OK; 161 } 162 163 nsresult nsAboutCache::Channel::VisitNextStorage() { 164 if (!mStorageList.Length()) return NS_ERROR_NOT_AVAILABLE; 165 166 mStorageName = mStorageList[0]; 167 mStorageList.RemoveElementAt(0); 168 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)); 176 } 177 178 void nsAboutCache::Channel::FireVisitStorage() { 179 nsresult rv; 180 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())); 188 189 rv = FlushBuffer(); 190 if (NS_FAILED(rv)) { 191 NS_WARNING("Failed to flush buffer"); 192 } 193 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(); 197 } 198 } 199 200 nsresult nsAboutCache::Channel::VisitStorage(nsACString const& storageName) { 201 nsresult rv; 202 203 rv = GetStorage(storageName, nullptr, getter_AddRefs(mStorage)); 204 if (NS_FAILED(rv)) return rv; 205 206 rv = mStorage->AsyncVisitStorage(this, !mOverview); 207 if (NS_FAILED(rv)) return rv; 208 209 return NS_OK; 210 } 211 212 // static 213 nsresult nsAboutCache::GetStorage(nsACString const& storageName, 214 nsILoadContextInfo* loadInfo, 215 nsICacheStorage** storage) { 216 nsresult rv; 217 218 nsCOMPtr<nsICacheStorageService> cacheService; 219 cacheService = mozilla::components::CacheStorage::Service(&rv); 220 if (NS_FAILED(rv)) return rv; 221 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; 230 } 231 if (NS_FAILED(rv)) return rv; 232 233 cacheStorage.forget(storage); 234 return NS_OK; 235 } 236 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; 245 } 246 247 mBuffer.AssignLiteral("<h2>"); 248 nsAppendEscapedHTML(mStorageName, mBuffer); 249 mBuffer.AppendLiteral( 250 "</h2>\n" 251 "<table id=\""); 252 mBuffer.AppendLiteral("\">\n"); 253 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"); 264 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"); 274 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"); 284 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"); 296 } 297 mBuffer.AppendLiteral( 298 " </td>\n" 299 " </tr>\n"); 300 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"); 310 } 311 } 312 313 mBuffer.AppendLiteral("</table>\n"); 314 315 // The entries header is added on encounter of the first entry 316 mEntriesHeaderAdded = false; 317 318 nsresult rv = FlushBuffer(); 319 if (NS_FAILED(rv)) { 320 NS_WARNING("Failed to flush buffer"); 321 } 322 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(); 328 } 329 330 return NS_OK; 331 } 332 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; 342 } 343 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; 369 } 370 371 // Generate a about:cache-entry URL for this entry... 372 373 nsAutoCString url; 374 url.AssignLiteral("about:cache-entry?storage="); 375 nsAppendEscapedHTML(mStorageName, url); 376 377 nsAutoCString context; 378 CacheFileUtils::AppendKeyPrefix(aInfo, context); 379 url.AppendLiteral("&context="); 380 nsAppendEscapedHTML(context, url); 381 382 url.AppendLiteral("&eid="); 383 nsAppendEscapedHTML(aIdEnhance, url); 384 385 nsAutoCString cacheUriSpec; 386 aURI->GetAsciiSpec(cacheUriSpec); 387 nsAutoCString escapedCacheURI; 388 nsAppendEscapedHTML(cacheUriSpec, escapedCacheURI); 389 url.AppendLiteral("&uri="); 390 url += escapedCacheURI; 391 392 // Entry start... 393 mBuffer.AppendLiteral(" <tr>\n"); 394 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(':'); 402 } 403 mBuffer.Append(escapedCacheURI); 404 mBuffer.AppendLiteral("</a>"); 405 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>"); 412 } 413 414 mBuffer.AppendLiteral("</td>\n"); 415 416 // Content length 417 mBuffer.AppendLiteral(" <td>"); 418 mBuffer.AppendInt(aDataSize); 419 mBuffer.AppendLiteral(" bytes</td>\n"); 420 421 // Length of alternative content 422 mBuffer.AppendLiteral(" <td>"); 423 mBuffer.AppendInt(aAltDataSize); 424 mBuffer.AppendLiteral(" bytes</td>\n"); 425 426 // Number of accesses 427 mBuffer.AppendLiteral(" <td>"); 428 mBuffer.AppendInt(aFetchCount); 429 mBuffer.AppendLiteral("</td>\n"); 430 431 // vars for reporting time 432 char buf[255]; 433 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"); 441 } 442 mBuffer.AppendLiteral("</td>\n"); 443 444 // Expires time 445 mBuffer.AppendLiteral(" <td>"); 446 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"); 457 } 458 mBuffer.AppendLiteral("</td>\n"); 459 460 // Pinning 461 mBuffer.AppendLiteral(" <td>"); 462 if (aPinned) { 463 mBuffer.AppendLiteral("Pinned"); 464 } else { 465 mBuffer.AppendLiteral(" "); 466 } 467 mBuffer.AppendLiteral("</td>\n"); 468 469 // Entry is done... 470 mBuffer.AppendLiteral(" </tr>\n"); 471 472 return FlushBuffer(); 473 } 474 475 NS_IMETHODIMP 476 nsAboutCache::Channel::OnCacheEntryVisitCompleted() { 477 if (!mStream) { 478 return NS_ERROR_FAILURE; 479 } 480 481 if (mEntriesHeaderAdded) { 482 mBuffer.AppendLiteral("</table>\n"); 483 } 484 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; 491 } 492 } 493 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"); 501 } 502 mStream->Close(); 503 504 return NS_OK; 505 } 506 507 nsresult nsAboutCache::Channel::FlushBuffer() { 508 nsresult rv; 509 510 uint32_t bytesWritten; 511 rv = mStream->Write(mBuffer.get(), mBuffer.Length(), &bytesWritten); 512 mBuffer.Truncate(); 513 514 if (NS_FAILED(rv)) { 515 mCancel = true; 516 } 517 518 return rv; 519 } 520 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; 526 } 527 528 // static 529 nsresult nsAboutCache::Create(REFNSIID aIID, void** aResult) { 530 RefPtr<nsAboutCache> about = new nsAboutCache(); 531 return about->QueryInterface(aIID, aResult); 532 } 533 534 NS_IMETHODIMP 535 nsAboutCache::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { 536 return NS_ERROR_ILLEGAL_VALUE; 537 } 538 539 ////////////////////////////////////////////////////////////////////////////////