tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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("&lt;");
     59          break;
     60        case '>':
     61          result.AppendLiteral("&gt;");
     62          break;
     63        case '&':
     64          result.AppendLiteral("&amp;");
     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 }