nsAboutCacheEntry.cpp (16895B)
1 /* -*- Mode: C++; tab-width: 4; 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 <algorithm> 7 8 #include "nsAboutCacheEntry.h" 9 10 #include "CacheFileUtils.h" 11 #include "CacheObserver.h" 12 #include "Dictionary.h" 13 #include "mozilla/Sprintf.h" 14 #include "nsAboutCache.h" 15 #include "nsAboutProtocolUtils.h" 16 #include "nsContentUtils.h" 17 #include "nsEscape.h" 18 #include "nsIAsyncInputStream.h" 19 #include "nsIAsyncOutputStream.h" 20 #include "nsICacheStorage.h" 21 #include "nsIPipe.h" 22 #include "nsITransportSecurityInfo.h" 23 #include "nsInputStreamPump.h" 24 #include "nsNetUtil.h" 25 26 using namespace mozilla::net; 27 28 #define HEXDUMP_MAX_ROWS 16 29 30 static void HexDump(uint32_t* state, const char* buf, int32_t n, 31 nsCString& result) { 32 char temp[16]; 33 34 const unsigned char* p; 35 while (n) { 36 SprintfLiteral(temp, "%08x: ", *state); 37 result.Append(temp); 38 *state += HEXDUMP_MAX_ROWS; 39 40 p = (const unsigned char*)buf; 41 42 int32_t i, row_max = std::min(HEXDUMP_MAX_ROWS, n); 43 44 // print hex codes: 45 for (i = 0; i < row_max; ++i) { 46 SprintfLiteral(temp, "%02x ", *p++); 47 result.Append(temp); 48 } 49 for (i = row_max; i < HEXDUMP_MAX_ROWS; ++i) { 50 result.AppendLiteral(" "); 51 } 52 53 // print ASCII glyphs if possible: 54 p = (const unsigned char*)buf; 55 for (i = 0; i < row_max; ++i, ++p) { 56 switch (*p) { 57 case '<': 58 result.AppendLiteral("<"); 59 break; 60 case '>': 61 result.AppendLiteral(">"); 62 break; 63 case '&': 64 result.AppendLiteral("&"); 65 break; 66 default: 67 if (*p < 0x7F && *p > 0x1F) { 68 result.Append(*p); 69 } else { 70 result.Append('.'); 71 } 72 } 73 } 74 75 result.Append('\n'); 76 77 buf += row_max; 78 n -= row_max; 79 } 80 } 81 82 //----------------------------------------------------------------------------- 83 // nsAboutCacheEntry::nsISupports 84 85 NS_IMPL_ISUPPORTS(nsAboutCacheEntry, nsIAboutModule) 86 NS_IMPL_ISUPPORTS(nsAboutCacheEntry::Channel, nsICacheEntryOpenCallback, 87 nsICacheEntryMetaDataVisitor, nsIStreamListener, nsIRequest, 88 nsIChannel) 89 90 //----------------------------------------------------------------------------- 91 // nsAboutCacheEntry::nsIAboutModule 92 93 NS_IMETHODIMP 94 nsAboutCacheEntry::NewChannel(nsIURI* uri, nsILoadInfo* aLoadInfo, 95 nsIChannel** result) { 96 NS_ENSURE_ARG_POINTER(uri); 97 nsresult rv; 98 99 RefPtr<Channel> channel = new Channel(); 100 rv = channel->Init(uri, aLoadInfo); 101 if (NS_FAILED(rv)) return rv; 102 103 channel.forget(result); 104 105 return NS_OK; 106 } 107 108 NS_IMETHODIMP 109 nsAboutCacheEntry::GetURIFlags(nsIURI* aURI, uint32_t* result) { 110 *result = nsIAboutModule::HIDE_FROM_ABOUTABOUT | 111 nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT; 112 return NS_OK; 113 } 114 115 NS_IMETHODIMP 116 nsAboutCacheEntry::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) { 117 return NS_ERROR_ILLEGAL_VALUE; 118 } 119 120 //----------------------------------------------------------------------------- 121 // nsAboutCacheEntry::Channel 122 123 nsresult nsAboutCacheEntry::Channel::Init(nsIURI* uri, nsILoadInfo* aLoadInfo) { 124 nsresult rv; 125 126 nsCOMPtr<nsIInputStream> stream; 127 rv = GetContentStream(uri, getter_AddRefs(stream)); 128 if (NS_FAILED(rv)) return rv; 129 130 rv = NS_NewInputStreamChannelInternal(getter_AddRefs(mChannel), uri, 131 stream.forget(), "text/html"_ns, 132 "utf-8"_ns, aLoadInfo); 133 if (NS_FAILED(rv)) return rv; 134 135 return NS_OK; 136 } 137 138 nsresult nsAboutCacheEntry::Channel::GetContentStream(nsIURI* uri, 139 nsIInputStream** result) { 140 nsresult rv; 141 142 // Init: (block size, maximum length) 143 nsCOMPtr<nsIAsyncInputStream> inputStream; 144 NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(mOutputStream), true, 145 false, 256, UINT32_MAX); 146 147 constexpr auto buffer = 148 "<!DOCTYPE html>\n" 149 "<html>\n" 150 "<head>\n" 151 " <meta http-equiv=\"Content-Security-Policy\" content=\"default-src " 152 "chrome:; object-src 'none'\" />\n" 153 " <meta name=\"color-scheme\" content=\"light dark\" />\n" 154 " <title>Cache entry information</title>\n" 155 " <link rel=\"stylesheet\" " 156 "href=\"chrome://global/skin/in-content/info-pages.css\" " 157 "type=\"text/css\"/>\n" 158 " <link rel=\"stylesheet\" " 159 "href=\"chrome://global/skin/aboutCacheEntry.css\" type=\"text/css\"/>\n" 160 "</head>\n" 161 "<body>\n" 162 "<h1>Cache entry information</h1>\n"_ns; 163 uint32_t n; 164 rv = mOutputStream->Write(buffer.get(), buffer.Length(), &n); 165 if (NS_FAILED(rv)) return rv; 166 if (n != buffer.Length()) return NS_ERROR_UNEXPECTED; 167 168 rv = OpenCacheEntry(uri); 169 if (NS_FAILED(rv)) return rv; 170 171 inputStream.forget(result); 172 return NS_OK; 173 } 174 175 nsresult nsAboutCacheEntry::Channel::OpenCacheEntry(nsIURI* uri) { 176 nsresult rv; 177 178 rv = ParseURI(uri, mStorageName, getter_AddRefs(mLoadInfo), mEnhanceId, 179 getter_AddRefs(mCacheURI)); 180 if (NS_FAILED(rv)) return rv; 181 182 return OpenCacheEntry(); 183 } 184 185 nsresult nsAboutCacheEntry::Channel::OpenCacheEntry() { 186 nsresult rv; 187 188 nsCOMPtr<nsICacheStorage> storage; 189 rv = nsAboutCache::GetStorage(mStorageName, mLoadInfo, 190 getter_AddRefs(storage)); 191 if (NS_FAILED(rv)) return rv; 192 193 // Invokes OnCacheEntryAvailable() 194 rv = storage->AsyncOpenURI( 195 mCacheURI, mEnhanceId, 196 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this); 197 if (NS_FAILED(rv)) return rv; 198 199 return NS_OK; 200 } 201 202 nsresult nsAboutCacheEntry::Channel::ParseURI(nsIURI* uri, 203 nsACString& storageName, 204 nsILoadContextInfo** loadInfo, 205 nsCString& enahnceID, 206 nsIURI** cacheUri) { 207 // 208 // about:cache-entry?storage=[string]&contenxt=[string]&eid=[string]&uri=[string] 209 // 210 nsresult rv; 211 212 nsAutoCString path; 213 rv = uri->GetPathQueryRef(path); 214 if (NS_FAILED(rv)) return rv; 215 216 nsACString::const_iterator keyBegin, keyEnd, valBegin, begin, end; 217 path.BeginReading(begin); 218 path.EndReading(end); 219 220 keyBegin = begin; 221 keyEnd = end; 222 if (!FindInReadable("?storage="_ns, keyBegin, keyEnd)) { 223 return NS_ERROR_FAILURE; 224 } 225 226 valBegin = keyEnd; // the value of the storage key starts after the key 227 228 keyBegin = keyEnd; 229 keyEnd = end; 230 if (!FindInReadable("&context="_ns, keyBegin, keyEnd)) { 231 return NS_ERROR_FAILURE; 232 } 233 234 storageName.Assign(Substring(valBegin, keyBegin)); 235 valBegin = keyEnd; // the value of the context key starts after the key 236 237 keyBegin = keyEnd; 238 keyEnd = end; 239 if (!FindInReadable("&eid="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE; 240 241 nsAutoCString contextKey(Substring(valBegin, keyBegin)); 242 valBegin = keyEnd; // the value of the eid key starts after the key 243 244 keyBegin = keyEnd; 245 keyEnd = end; 246 if (!FindInReadable("&uri="_ns, keyBegin, keyEnd)) return NS_ERROR_FAILURE; 247 248 enahnceID.Assign(Substring(valBegin, keyBegin)); 249 250 valBegin = keyEnd; // the value of the uri key starts after the key 251 nsAutoCString uriSpec(Substring(valBegin, end)); // uri is the last one 252 253 // Uf... parsing done, now get some objects from it... 254 255 nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(contextKey); 256 if (!info) return NS_ERROR_FAILURE; 257 info.forget(loadInfo); 258 259 rv = NS_NewURI(cacheUri, uriSpec); 260 if (NS_FAILED(rv)) return rv; 261 262 return NS_OK; 263 } 264 265 //----------------------------------------------------------------------------- 266 // nsICacheEntryOpenCallback implementation 267 //----------------------------------------------------------------------------- 268 269 NS_IMETHODIMP 270 nsAboutCacheEntry::Channel::OnCacheEntryCheck(nsICacheEntry* aEntry, 271 uint32_t* result) { 272 *result = nsICacheEntryOpenCallback::ENTRY_WANTED; 273 return NS_OK; 274 } 275 276 NS_IMETHODIMP 277 nsAboutCacheEntry::Channel::OnCacheEntryAvailable(nsICacheEntry* entry, 278 bool isNew, nsresult status) { 279 nsresult rv; 280 281 mWaitingForData = false; 282 if (entry) { 283 rv = WriteCacheEntryDescription(entry); 284 } else { 285 rv = WriteCacheEntryUnavailable(); 286 } 287 if (NS_FAILED(rv)) return rv; 288 289 if (!mWaitingForData) { 290 // Data is not expected, close the output of content now. 291 CloseContent(); 292 } 293 294 return NS_OK; 295 } 296 297 //----------------------------------------------------------------------------- 298 // Print-out helper methods 299 //----------------------------------------------------------------------------- 300 301 #define APPEND_ROW(label, value) \ 302 PR_BEGIN_MACRO \ 303 buffer.AppendLiteral( \ 304 " <tr>\n" \ 305 " <th>"); \ 306 buffer.AppendLiteral(label); \ 307 buffer.AppendLiteral( \ 308 ":</th>\n" \ 309 " <td>"); \ 310 buffer.Append(value); \ 311 buffer.AppendLiteral( \ 312 "</td>\n" \ 313 " </tr>\n"); \ 314 PR_END_MACRO 315 316 nsresult nsAboutCacheEntry::Channel::WriteCacheEntryDescription( 317 nsICacheEntry* entry) { 318 nsresult rv; 319 // This method appears to run in a situation where the run-time stack 320 // should have plenty of space, so allocating a large string on the 321 // stack is OK. 322 nsAutoCStringN<4097> buffer; 323 uint32_t n; 324 325 nsAutoCString str; 326 327 rv = entry->GetKey(str); 328 if (NS_FAILED(rv)) return rv; 329 330 buffer.AssignLiteral( 331 "<table>\n" 332 " <tr>\n" 333 " <th>key:</th>\n" 334 " <td id=\"td-key\">"); 335 336 // Test if the key is actually a URI 337 nsCOMPtr<nsIURI> uri; 338 rv = NS_NewURI(getter_AddRefs(uri), str); 339 340 nsAutoCString escapedStr; 341 nsAppendEscapedHTML(str, escapedStr); 342 343 // javascript: and data: URLs should not be linkified 344 // since clicking them can cause scripts to run - bug 162584 345 if (NS_SUCCEEDED(rv) && 346 !(uri->SchemeIs("javascript") || uri->SchemeIs("data"))) { 347 buffer.AppendLiteral("<a href=\""); 348 buffer.Append(escapedStr); 349 buffer.AppendLiteral("\">"); 350 buffer.Append(escapedStr); 351 buffer.AppendLiteral("</a>"); 352 uri = nullptr; 353 } else { 354 buffer.Append(escapedStr); 355 } 356 buffer.AppendLiteral( 357 "</td>\n" 358 " </tr>\n"); 359 360 // temp vars for reporting 361 char timeBuf[255]; 362 uint32_t u = 0; 363 nsAutoCString s; 364 365 // Fetch Count 366 s.Truncate(); 367 entry->GetFetchCount(&u); 368 s.AppendInt(u); 369 APPEND_ROW("fetch count", s); 370 371 // Last Fetched 372 entry->GetLastFetched(&u); 373 if (u) { 374 PrintTimeString(timeBuf, sizeof(timeBuf), u); 375 APPEND_ROW("last fetched", timeBuf); 376 } else { 377 APPEND_ROW("last fetched", "No last fetch time (bug 1000338)"); 378 } 379 380 // Last Modified 381 entry->GetLastModified(&u); 382 if (u) { 383 PrintTimeString(timeBuf, sizeof(timeBuf), u); 384 APPEND_ROW("last modified", timeBuf); 385 } else { 386 APPEND_ROW("last modified", "No last modified time (bug 1000338)"); 387 } 388 389 // Expiration Time 390 entry->GetExpirationTime(&u); 391 392 // Bug - 633747. 393 // When expiration time is 0, we show 1970-01-01 01:00:00 which is confusing. 394 // So we check if time is 0, then we show a message, "Expired Immediately" 395 if (u == 0) { 396 APPEND_ROW("expires", "Expired Immediately"); 397 } else if (u < 0xFFFFFFFF) { 398 PrintTimeString(timeBuf, sizeof(timeBuf), u); 399 APPEND_ROW("expires", timeBuf); 400 } else { 401 APPEND_ROW("expires", "No expiration time"); 402 } 403 404 // Data Size 405 s.Truncate(); 406 uint32_t dataSize; 407 if (NS_FAILED(entry->GetStorageDataSize(&dataSize))) dataSize = 0; 408 s.AppendInt( 409 (int32_t)dataSize); // XXX nsICacheEntryInfo interfaces should be fixed. 410 s.AppendLiteral(" B"); 411 APPEND_ROW("Data size", s); 412 413 // TODO - mayhemer 414 // Here used to be a link to the disk file (in the old cache for entries that 415 // did not fit any of the block files, in the new cache every time). 416 // I'd rather have a small set of buttons here to action on the entry: 417 // 1. save the content 418 // 2. save as a complete HTTP response (response head, headers, content) 419 // 3. doom the entry 420 // A new bug(s) should be filed here. 421 422 // Security Info 423 nsCOMPtr<nsITransportSecurityInfo> securityInfo; 424 entry->GetSecurityInfo(getter_AddRefs(securityInfo)); 425 if (securityInfo) { 426 APPEND_ROW("Security", "This is a secure document."); 427 } else { 428 APPEND_ROW( 429 "Security", 430 "This document does not have any security info associated with it."); 431 } 432 433 buffer.AppendLiteral( 434 "</table>\n" 435 "<hr/>\n" 436 "<table>\n"); 437 438 mBuffer = &buffer; // make it available for OnMetaDataElement(). 439 entry->VisitMetaData(this); 440 mBuffer = nullptr; 441 442 buffer.AppendLiteral("</table>\n"); 443 mOutputStream->Write(buffer.get(), buffer.Length(), &n); 444 buffer.Truncate(); 445 446 // Provide a hexdump of the data 447 if (!dataSize) { 448 return NS_OK; 449 } 450 451 nsCOMPtr<nsIInputStream> stream; 452 entry->OpenInputStream(0, getter_AddRefs(stream)); 453 if (!stream) { 454 return NS_OK; 455 } 456 457 RefPtr<nsInputStreamPump> pump; 458 rv = nsInputStreamPump::Create(getter_AddRefs(pump), stream); 459 if (NS_FAILED(rv)) { 460 return NS_OK; // just ignore 461 } 462 463 rv = pump->AsyncRead(this); 464 if (NS_FAILED(rv)) { 465 return NS_OK; // just ignore 466 } 467 468 mWaitingForData = true; 469 return NS_OK; 470 } 471 472 nsresult nsAboutCacheEntry::Channel::WriteCacheEntryUnavailable() { 473 uint32_t n; 474 constexpr auto buffer = "The cache entry you selected is not available."_ns; 475 mOutputStream->Write(buffer.get(), buffer.Length(), &n); 476 return NS_OK; 477 } 478 479 //----------------------------------------------------------------------------- 480 // nsICacheEntryMetaDataVisitor implementation 481 //----------------------------------------------------------------------------- 482 483 NS_IMETHODIMP 484 nsAboutCacheEntry::Channel::OnMetaDataElement(char const* key, 485 char const* value) { 486 mBuffer->AppendLiteral( 487 " <tr>\n" 488 " <th>"); 489 mBuffer->Append(key); 490 mBuffer->AppendLiteral( 491 ":</th>\n" 492 " <td>"); 493 if (mEnhanceId.EqualsLiteral("dict:")) { 494 // We set the content ID to CONTENT_TYPE_DICTIONARY, ensure that's correct 495 if (strcmp(key, "ctid") == 0) { 496 MOZ_ASSERT(strcmp(value, "7") == 0); 497 } else { 498 RefPtr<DictionaryCacheEntry> dict = new DictionaryCacheEntry("temp"); 499 dict->ParseMetadata(value); 500 nsAppendEscapedHTML( 501 nsPrintfCString("Hash: %s\nPattern: %s\nId: %s\nMatch-Id: ", 502 dict->GetHash().get(), dict->GetPattern().get(), 503 dict->GetId().get()), 504 *mBuffer); 505 dict->AppendMatchDest(*mBuffer); 506 mBuffer->AppendLiteral("\n"); 507 } 508 } 509 nsAppendEscapedHTML(nsDependentCString(value), *mBuffer); 510 mBuffer->AppendLiteral( 511 "</td>\n" 512 " </tr>\n"); 513 514 return NS_OK; 515 } 516 517 //----------------------------------------------------------------------------- 518 // nsIStreamListener implementation 519 //----------------------------------------------------------------------------- 520 521 NS_IMETHODIMP 522 nsAboutCacheEntry::Channel::OnStartRequest(nsIRequest* request) { 523 mHexDumpState = 0; 524 525 constexpr auto buffer = "<hr/>\n<pre>"_ns; 526 uint32_t n; 527 return mOutputStream->Write(buffer.get(), buffer.Length(), &n); 528 } 529 530 NS_IMETHODIMP 531 nsAboutCacheEntry::Channel::OnDataAvailable(nsIRequest* request, 532 nsIInputStream* aInputStream, 533 uint64_t aOffset, uint32_t aCount) { 534 uint32_t n; 535 return aInputStream->ReadSegments(&nsAboutCacheEntry::Channel::PrintCacheData, 536 this, aCount, &n); 537 } 538 539 /* static */ 540 nsresult nsAboutCacheEntry::Channel::PrintCacheData( 541 nsIInputStream* aInStream, void* aClosure, const char* aFromSegment, 542 uint32_t aToOffset, uint32_t aCount, uint32_t* aWriteCount) { 543 nsAboutCacheEntry::Channel* a = 544 static_cast<nsAboutCacheEntry::Channel*>(aClosure); 545 546 nsCString buffer; 547 HexDump(&a->mHexDumpState, aFromSegment, aCount, buffer); 548 549 uint32_t n; 550 a->mOutputStream->Write(buffer.get(), buffer.Length(), &n); 551 552 *aWriteCount = aCount; 553 554 return NS_OK; 555 } 556 557 NS_IMETHODIMP 558 nsAboutCacheEntry::Channel::OnStopRequest(nsIRequest* request, 559 nsresult result) { 560 constexpr auto buffer = "</pre>\n"_ns; 561 uint32_t n; 562 mOutputStream->Write(buffer.get(), buffer.Length(), &n); 563 564 CloseContent(); 565 566 return NS_OK; 567 } 568 569 void nsAboutCacheEntry::Channel::CloseContent() { 570 constexpr auto buffer = "</body>\n</html>\n"_ns; 571 uint32_t n; 572 mOutputStream->Write(buffer.get(), buffer.Length(), &n); 573 574 mOutputStream->Close(); 575 mOutputStream = nullptr; 576 }