tor-browser

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

nsJARInputStream.cpp (11825B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* nsJARInputStream.cpp
      3 *
      4 * This Source Code Form is subject to the terms of the Mozilla Public
      5 * License, v. 2.0. If a copy of the MPL was not distributed with this
      6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      7 
      8 #include "nsJARInputStream.h"
      9 #include "zipstruct.h"  // defines ZIP compression codes
     10 #include "nsZipArchive.h"
     11 #include "mozilla/MmapFaultHandler.h"
     12 #include "mozilla/StaticPrefs_network.h"
     13 #include "mozilla/UniquePtr.h"
     14 
     15 #include "nsEscape.h"
     16 #include "nsDebug.h"
     17 #include <algorithm>
     18 #include <limits>
     19 #if defined(XP_WIN)
     20 #  include <windows.h>
     21 #endif
     22 
     23 /*---------------------------------------------
     24 *  nsISupports implementation
     25 *--------------------------------------------*/
     26 
     27 NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream)
     28 
     29 /*----------------------------------------------------------
     30 * nsJARInputStream implementation
     31 * Takes ownership of |fd|, even on failure
     32 *--------------------------------------------------------*/
     33 
     34 nsresult nsJARInputStream::InitFile(nsZipHandle* aFd, const uint8_t* aData,
     35                                    nsZipItem* aItem) {
     36  nsresult rv = NS_OK;
     37  MOZ_DIAGNOSTIC_ASSERT(aFd, "Argument may not be null");
     38  if (!aFd) {
     39    return NS_ERROR_INVALID_ARG;
     40  }
     41  MOZ_ASSERT(aItem, "Argument may not be null");
     42 
     43  // Mark it as closed, in case something fails in initialisation
     44  mMode = MODE_CLOSED;
     45  //-- prepare for the compression type
     46  switch (aItem->Compression()) {
     47    case STORED:
     48      mMode = MODE_COPY;
     49      break;
     50 
     51    case DEFLATED:
     52      rv = gZlibInit(&mZs);
     53      NS_ENSURE_SUCCESS(rv, rv);
     54 
     55      mMode = MODE_INFLATE;
     56      mInCrc = aItem->CRC32();
     57      mOutCrc = crc32(0L, Z_NULL, 0);
     58      break;
     59 
     60    default:
     61      mFd = aFd;
     62      return NS_ERROR_NOT_IMPLEMENTED;
     63  }
     64 
     65  // Must keep handle to filepointer and mmap structure as long as we need
     66  // access to the mmapped data
     67  mFd = aFd;
     68  mZs.next_in = (Bytef*)aData;
     69  if (!mZs.next_in) {
     70    return NS_ERROR_FILE_CORRUPTED;
     71  }
     72  mZs.avail_in = aItem->Size();
     73  mOutSize = aItem->RealSize();
     74  mZs.total_out = 0;
     75  return NS_OK;
     76 }
     77 
     78 nsresult nsJARInputStream::InitDirectory(nsJAR* aJar, const char* aDir) {
     79  MOZ_ASSERT(aJar, "Argument may not be null");
     80  MOZ_ASSERT(aDir, "Argument may not be null");
     81 
     82  // Mark it as closed, in case something fails in initialisation
     83  mMode = MODE_CLOSED;
     84 
     85  // Keep the zipReader for getting the actual zipItems
     86  mJar = aJar;
     87  mJar->mLock.AssertCurrentThreadIn();
     88  mozilla::UniquePtr<nsZipFind> find;
     89  nsresult rv;
     90  // We can get aDir's contents as strings via FindEntries
     91  // with the following pattern (see nsIZipReader.findEntries docs)
     92  // assuming dirName is properly escaped:
     93  //
     94  //   dirName + "?*~" + dirName + "?*/?*"
     95  nsDependentCString dirName(aDir);
     96  mNameLen = dirName.Length();
     97 
     98  // iterate through dirName and copy it to escDirName, escaping chars
     99  // which are special at the "top" level of the regexp so FindEntries
    100  // works correctly
    101  nsAutoCString escDirName;
    102  const char* curr = dirName.BeginReading();
    103  const char* end = dirName.EndReading();
    104  while (curr != end) {
    105    switch (*curr) {
    106      case '*':
    107      case '?':
    108      case '$':
    109      case '[':
    110      case ']':
    111      case '^':
    112      case '~':
    113      case '(':
    114      case ')':
    115      case '\\':
    116        escDirName.Append('\\');
    117        [[fallthrough]];
    118      default:
    119        escDirName.Append(*curr);
    120    }
    121    ++curr;
    122  }
    123  nsAutoCString pattern = escDirName + "?*~"_ns + escDirName + "?*/?*"_ns;
    124  rv = mJar->mZip->FindInit(pattern.get(), mozilla::getter_Transfers(find));
    125  if (NS_FAILED(rv)) return rv;
    126 
    127  const char* name;
    128  uint16_t nameLen;
    129  while ((rv = find->FindNext(&name, &nameLen)) == NS_OK) {
    130    // Must copy, to make it zero-terminated
    131    mArray.AppendElement(nsCString(name, nameLen));
    132  }
    133 
    134  if (rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
    135    return NS_ERROR_FAILURE;  // no error translation
    136  }
    137 
    138  // Sort it
    139  mArray.Sort();
    140 
    141  mBuffer.AppendLiteral(
    142      "200: filename content-length last-modified file-type\n");
    143 
    144  // Open for reading
    145  mMode = MODE_DIRECTORY;
    146  mZs.total_out = 0;
    147  mArrPos = 0;
    148  return NS_OK;
    149 }
    150 
    151 NS_IMETHODIMP
    152 nsJARInputStream::Available(uint64_t* _retval) {
    153  // A lot of callers don't check the error code.
    154  // They just use the _retval value.
    155  *_retval = 0;
    156 
    157  uint64_t maxAvailableSize = 0;
    158 
    159  switch (mMode) {
    160    case MODE_NOTINITED:
    161      break;
    162 
    163    case MODE_CLOSED:
    164      return NS_BASE_STREAM_CLOSED;
    165 
    166    case MODE_DIRECTORY:
    167      *_retval = mBuffer.Length();
    168      break;
    169 
    170    case MODE_INFLATE:
    171    case MODE_COPY:
    172      maxAvailableSize = mozilla::StaticPrefs::network_jar_max_available_size();
    173      if (!maxAvailableSize) {
    174        maxAvailableSize = std::numeric_limits<uint64_t>::max();
    175      }
    176      *_retval = std::min<uint64_t>(mOutSize - mZs.total_out, maxAvailableSize);
    177      break;
    178  }
    179 
    180  return NS_OK;
    181 }
    182 
    183 NS_IMETHODIMP
    184 nsJARInputStream::StreamStatus() {
    185  return mMode == MODE_CLOSED ? NS_BASE_STREAM_CLOSED : NS_OK;
    186 }
    187 
    188 NS_IMETHODIMP
    189 nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytesRead) {
    190  NS_ENSURE_ARG_POINTER(aBuffer);
    191  NS_ENSURE_ARG_POINTER(aBytesRead);
    192 
    193  *aBytesRead = 0;
    194 
    195  nsresult rv = NS_OK;
    196  MMAP_FAULT_HANDLER_BEGIN_HANDLE(mFd)
    197  switch (mMode) {
    198    case MODE_NOTINITED:
    199      return NS_OK;
    200 
    201    case MODE_CLOSED:
    202      return NS_BASE_STREAM_CLOSED;
    203 
    204    case MODE_DIRECTORY:
    205      return ReadDirectory(aBuffer, aCount, aBytesRead);
    206 
    207    case MODE_INFLATE:
    208      if (mZs.total_out < mOutSize) {
    209        rv = ContinueInflate(aBuffer, aCount, aBytesRead);
    210      }
    211      // be aggressive about releasing the file!
    212      // note that sometimes, we will release  mFd before we've finished
    213      // deflating - this is because zlib buffers the input
    214      if (mZs.avail_in == 0) {
    215        mFd = nullptr;
    216      }
    217      break;
    218 
    219    case MODE_COPY:
    220      if (mFd) {
    221        MOZ_DIAGNOSTIC_ASSERT(mOutSize >= mZs.total_out,
    222                              "Did we read more than expected?");
    223        uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out));
    224        if (count) {
    225          std::copy(mZs.next_in + mZs.total_out,
    226                    mZs.next_in + mZs.total_out + count, aBuffer);
    227          mZs.total_out += count;
    228        }
    229        *aBytesRead = count;
    230      }
    231      // be aggressive about releasing the file!
    232      // note that sometimes, we will release mFd before we've finished copying.
    233      if (mZs.total_out >= mOutSize) {
    234        mFd = nullptr;
    235      }
    236      break;
    237  }
    238  MMAP_FAULT_HANDLER_CATCH(NS_ERROR_FAILURE)
    239  return rv;
    240 }
    241 
    242 NS_IMETHODIMP
    243 nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void* closure,
    244                               uint32_t count, uint32_t* _retval) {
    245  // don't have a buffer to read from, so this better not be called!
    246  return NS_ERROR_NOT_IMPLEMENTED;
    247 }
    248 
    249 NS_IMETHODIMP
    250 nsJARInputStream::IsNonBlocking(bool* aNonBlocking) {
    251  *aNonBlocking = false;
    252  return NS_OK;
    253 }
    254 
    255 NS_IMETHODIMP
    256 nsJARInputStream::Close() {
    257  if (mMode == MODE_INFLATE) {
    258    inflateEnd(&mZs);
    259  }
    260  mMode = MODE_CLOSED;
    261  mFd = nullptr;
    262  return NS_OK;
    263 }
    264 
    265 nsresult nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount,
    266                                           uint32_t* aBytesRead) {
    267  bool finished = false;
    268 
    269  // No need to check the args, ::Read did that, but assert them at least
    270  NS_ASSERTION(aBuffer, "aBuffer parameter must not be null");
    271  NS_ASSERTION(aBytesRead, "aBytesRead parameter must not be null");
    272 
    273  // Keep old total_out count
    274  const uint32_t oldTotalOut = mZs.total_out;
    275 
    276  // make sure we aren't reading too much
    277  mZs.avail_out = std::min(aCount, (mOutSize - oldTotalOut));
    278  mZs.next_out = (unsigned char*)aBuffer;
    279 
    280  if (mMode == MODE_INFLATE) {
    281    // now inflate
    282    int zerr = inflate(&mZs, Z_SYNC_FLUSH);
    283    if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) {
    284      return NS_ERROR_FILE_CORRUPTED;
    285    }
    286    // If inflating did not read anything more, then the stream is finished.
    287    finished = (zerr == Z_STREAM_END) ||
    288               (mZs.avail_out && mZs.total_out == oldTotalOut);
    289  }
    290 
    291  *aBytesRead = (mZs.total_out - oldTotalOut);
    292 
    293  // Calculate the CRC on the output
    294  mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead);
    295 
    296  // be aggressive about ending the inflation
    297  // for some reason we don't always get Z_STREAM_END
    298  if (finished || mZs.total_out >= mOutSize) {
    299    if (mMode == MODE_INFLATE) {
    300      int zerr = inflateEnd(&mZs);
    301      if (zerr != Z_OK) {
    302        return NS_ERROR_FILE_CORRUPTED;
    303      }
    304 
    305      // Stream is finished but has a different size from what
    306      // we expected.
    307      if (mZs.total_out != mOutSize) {
    308        return NS_ERROR_FILE_CORRUPTED;
    309      }
    310    }
    311 
    312    // stop returning valid data as soon as we know we have a bad CRC
    313    if (mOutCrc != mInCrc) {
    314      return NS_ERROR_FILE_CORRUPTED;
    315    }
    316  }
    317 
    318  return NS_OK;
    319 }
    320 
    321 nsresult nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount,
    322                                         uint32_t* aBytesRead) {
    323  // No need to check the args, ::Read did that, but assert them at least
    324  NS_ASSERTION(aBuffer, "aBuffer parameter must not be null");
    325  NS_ASSERTION(aBytesRead, "aBytesRead parameter must not be null");
    326 
    327  // If the buffer contains data, copy what's there up to the desired amount
    328  uint32_t numRead = CopyDataToBuffer(aBuffer, aCount);
    329 
    330  if (aCount > 0) {
    331    mozilla::RecursiveMutexAutoLock lock(mJar->mLock);
    332    // empty the buffer and start writing directory entry lines to it
    333    mBuffer.Truncate();
    334    mCurPos = 0;
    335    const uint32_t arrayLen = mArray.Length();
    336 
    337    for (; aCount > mBuffer.Length(); mArrPos++) {
    338      // have we consumed all the directory contents?
    339      if (arrayLen <= mArrPos) break;
    340 
    341      const char* entryName = mArray[mArrPos].get();
    342      uint32_t entryNameLen = mArray[mArrPos].Length();
    343      nsZipItem* ze = mJar->mZip->GetItem(
    344          nsDependentCString(mArray[mArrPos].get(), mArray[mArrPos].Length()));
    345      NS_ENSURE_TRUE(ze, NS_ERROR_FILE_NOT_FOUND);
    346 
    347      // Last Modified Time
    348      PRExplodedTime tm;
    349      PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm);
    350      char itemLastModTime[65];
    351      PR_FormatTimeUSEnglish(itemLastModTime, sizeof(itemLastModTime),
    352                             " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", &tm);
    353 
    354      // write a 201: line to the buffer for this item
    355      // 200: filename content-length last-modified file-type
    356      mBuffer.AppendLiteral("201: ");
    357 
    358      // Names must be escaped and relative, so use the pre-calculated length
    359      // of the directory name as the offset into the string
    360      // NS_EscapeURL adds the escaped URL to the give string buffer
    361      NS_EscapeURL(entryName + mNameLen, entryNameLen - mNameLen,
    362                   esc_Minimal | esc_AlwaysCopy, mBuffer);
    363 
    364      mBuffer.Append(' ');
    365      mBuffer.AppendInt(ze->RealSize(), 10);
    366      mBuffer.Append(itemLastModTime);  // starts/ends with ' '
    367      if (ze->IsDirectory())
    368        mBuffer.AppendLiteral("DIRECTORY\n");
    369      else
    370        mBuffer.AppendLiteral("FILE\n");
    371    }
    372 
    373    // Copy up to the desired amount of data to buffer
    374    numRead += CopyDataToBuffer(aBuffer, aCount);
    375  }
    376 
    377  *aBytesRead = numRead;
    378  return NS_OK;
    379 }
    380 
    381 uint32_t nsJARInputStream::CopyDataToBuffer(char*& aBuffer, uint32_t& aCount) {
    382  const uint32_t writeLength =
    383      std::min<uint32_t>(aCount, mBuffer.Length() - mCurPos);
    384 
    385  if (writeLength > 0) {
    386    std::copy(mBuffer.get() + mCurPos, mBuffer.get() + mCurPos + writeLength,
    387              aBuffer);
    388    mCurPos += writeLength;
    389    aCount -= writeLength;
    390    aBuffer += writeLength;
    391  }
    392 
    393  // return number of bytes copied to the buffer so the
    394  // Read method can return the number of bytes copied
    395  return writeLength;
    396 }