modules/libjar/nsJARInputStream.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial