michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* nsJARInputStream.cpp michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsJARInputStream.h" michael@0: #include "zipstruct.h" // defines ZIP compression codes michael@0: #include "nsZipArchive.h" michael@0: michael@0: #include "nsNetUtil.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIFile.h" michael@0: #include "nsDebug.h" michael@0: #include michael@0: #if defined(XP_WIN) michael@0: #include michael@0: #endif michael@0: michael@0: /*--------------------------------------------- michael@0: * nsISupports implementation michael@0: *--------------------------------------------*/ michael@0: michael@0: NS_IMPL_ISUPPORTS(nsJARInputStream, nsIInputStream) michael@0: michael@0: /*---------------------------------------------------------- michael@0: * nsJARInputStream implementation michael@0: *--------------------------------------------------------*/ michael@0: michael@0: nsresult michael@0: nsJARInputStream::InitFile(nsJAR *aJar, nsZipItem *item) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); michael@0: NS_ABORT_IF_FALSE(item, "Argument may not be null"); michael@0: michael@0: // Mark it as closed, in case something fails in initialisation michael@0: mMode = MODE_CLOSED; michael@0: //-- prepare for the compression type michael@0: switch (item->Compression()) { michael@0: case STORED: michael@0: mMode = MODE_COPY; michael@0: break; michael@0: michael@0: case DEFLATED: michael@0: rv = gZlibInit(&mZs); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mMode = MODE_INFLATE; michael@0: mInCrc = item->CRC32(); michael@0: mOutCrc = crc32(0L, Z_NULL, 0); michael@0: break; michael@0: michael@0: default: michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Must keep handle to filepointer and mmap structure as long as we need access to the mmapped data michael@0: mFd = aJar->mZip->GetFD(); michael@0: mZs.next_in = (Bytef *)aJar->mZip->GetData(item); michael@0: if (!mZs.next_in) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: mZs.avail_in = item->Size(); michael@0: mOutSize = item->RealSize(); michael@0: mZs.total_out = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARInputStream::InitDirectory(nsJAR* aJar, michael@0: const nsACString& aJarDirSpec, michael@0: const char* aDir) michael@0: { michael@0: NS_ABORT_IF_FALSE(aJar, "Argument may not be null"); michael@0: NS_ABORT_IF_FALSE(aDir, "Argument may not be null"); michael@0: michael@0: // Mark it as closed, in case something fails in initialisation michael@0: mMode = MODE_CLOSED; michael@0: michael@0: // Keep the zipReader for getting the actual zipItems michael@0: mJar = aJar; michael@0: nsZipFind *find; michael@0: nsresult rv; michael@0: // We can get aDir's contents as strings via FindEntries michael@0: // with the following pattern (see nsIZipReader.findEntries docs) michael@0: // assuming dirName is properly escaped: michael@0: // michael@0: // dirName + "?*~" + dirName + "?*/?*" michael@0: nsDependentCString dirName(aDir); michael@0: mNameLen = dirName.Length(); michael@0: michael@0: // iterate through dirName and copy it to escDirName, escaping chars michael@0: // which are special at the "top" level of the regexp so FindEntries michael@0: // works correctly michael@0: nsAutoCString escDirName; michael@0: const char* curr = dirName.BeginReading(); michael@0: const char* end = dirName.EndReading(); michael@0: while (curr != end) { michael@0: switch (*curr) { michael@0: case '*': michael@0: case '?': michael@0: case '$': michael@0: case '[': michael@0: case ']': michael@0: case '^': michael@0: case '~': michael@0: case '(': michael@0: case ')': michael@0: case '\\': michael@0: escDirName.Append('\\'); michael@0: // fall through michael@0: default: michael@0: escDirName.Append(*curr); michael@0: } michael@0: ++curr; michael@0: } michael@0: nsAutoCString pattern = escDirName + NS_LITERAL_CSTRING("?*~") + michael@0: escDirName + NS_LITERAL_CSTRING("?*/?*"); michael@0: rv = mJar->mZip->FindInit(pattern.get(), &find); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: const char *name; michael@0: uint16_t nameLen; michael@0: while ((rv = find->FindNext( &name, &nameLen )) == NS_OK) { michael@0: // Must copy, to make it zero-terminated michael@0: mArray.AppendElement(nsCString(name,nameLen)); michael@0: } michael@0: delete find; michael@0: michael@0: if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST && NS_FAILED(rv)) { michael@0: return NS_ERROR_FAILURE; // no error translation michael@0: } michael@0: michael@0: // Sort it michael@0: mArray.Sort(); michael@0: michael@0: mBuffer.AssignLiteral("300: "); michael@0: mBuffer.Append(aJarDirSpec); michael@0: mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n"); michael@0: michael@0: // Open for reading michael@0: mMode = MODE_DIRECTORY; michael@0: mZs.total_out = 0; michael@0: mArrPos = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputStream::Available(uint64_t *_retval) michael@0: { michael@0: // A lot of callers don't check the error code. michael@0: // They just use the _retval value. michael@0: *_retval = 0; michael@0: michael@0: switch (mMode) { michael@0: case MODE_NOTINITED: michael@0: break; michael@0: michael@0: case MODE_CLOSED: michael@0: return NS_BASE_STREAM_CLOSED; michael@0: michael@0: case MODE_DIRECTORY: michael@0: *_retval = mBuffer.Length(); michael@0: break; michael@0: michael@0: case MODE_INFLATE: michael@0: case MODE_COPY: michael@0: *_retval = mOutSize - mZs.total_out; michael@0: break; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aBuffer); michael@0: NS_ENSURE_ARG_POINTER(aBytesRead); michael@0: michael@0: *aBytesRead = 0; michael@0: michael@0: nsresult rv = NS_OK; michael@0: MOZ_WIN_MEM_TRY_BEGIN michael@0: switch (mMode) { michael@0: case MODE_NOTINITED: michael@0: return NS_OK; michael@0: michael@0: case MODE_CLOSED: michael@0: return NS_BASE_STREAM_CLOSED; michael@0: michael@0: case MODE_DIRECTORY: michael@0: return ReadDirectory(aBuffer, aCount, aBytesRead); michael@0: michael@0: case MODE_INFLATE: michael@0: if (mFd) { michael@0: rv = ContinueInflate(aBuffer, aCount, aBytesRead); michael@0: } michael@0: // be aggressive about releasing the file! michael@0: // note that sometimes, we will release mFd before we've finished michael@0: // deflating - this is because zlib buffers the input michael@0: if (mZs.avail_in == 0) { michael@0: mFd = nullptr; michael@0: } michael@0: break; michael@0: michael@0: case MODE_COPY: michael@0: if (mFd) { michael@0: uint32_t count = std::min(aCount, mOutSize - uint32_t(mZs.total_out)); michael@0: if (count) { michael@0: memcpy(aBuffer, mZs.next_in + mZs.total_out, count); michael@0: mZs.total_out += count; michael@0: } michael@0: *aBytesRead = count; michael@0: } michael@0: // be aggressive about releasing the file! michael@0: // note that sometimes, we will release mFd before we've finished copying. michael@0: if (mZs.total_out >= mOutSize) { michael@0: mFd = nullptr; michael@0: } michael@0: break; michael@0: } michael@0: MOZ_WIN_MEM_TRY_CATCH(rv = NS_ERROR_FAILURE) michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t *_retval) michael@0: { michael@0: // don't have a buffer to read from, so this better not be called! michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputStream::IsNonBlocking(bool *aNonBlocking) michael@0: { michael@0: *aNonBlocking = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsJARInputStream::Close() michael@0: { michael@0: if (mMode == MODE_INFLATE) { michael@0: inflateEnd(&mZs); michael@0: } michael@0: mMode = MODE_CLOSED; michael@0: mFd = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARInputStream::ContinueInflate(char* aBuffer, uint32_t aCount, michael@0: uint32_t* aBytesRead) michael@0: { michael@0: // No need to check the args, ::Read did that, but assert them at least michael@0: NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); michael@0: NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); michael@0: michael@0: // Keep old total_out count michael@0: const uint32_t oldTotalOut = mZs.total_out; michael@0: michael@0: // make sure we aren't reading too much michael@0: mZs.avail_out = std::min(aCount, (mOutSize-oldTotalOut)); michael@0: mZs.next_out = (unsigned char*)aBuffer; michael@0: michael@0: // now inflate michael@0: int zerr = inflate(&mZs, Z_SYNC_FLUSH); michael@0: if ((zerr != Z_OK) && (zerr != Z_STREAM_END)) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: michael@0: *aBytesRead = (mZs.total_out - oldTotalOut); michael@0: michael@0: // Calculate the CRC on the output michael@0: mOutCrc = crc32(mOutCrc, (unsigned char*)aBuffer, *aBytesRead); michael@0: michael@0: // be aggressive about ending the inflation michael@0: // for some reason we don't always get Z_STREAM_END michael@0: if (zerr == Z_STREAM_END || mZs.total_out == mOutSize) { michael@0: inflateEnd(&mZs); michael@0: michael@0: // stop returning valid data as soon as we know we have a bad CRC michael@0: if (mOutCrc != mInCrc) michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsJARInputStream::ReadDirectory(char* aBuffer, uint32_t aCount, uint32_t *aBytesRead) michael@0: { michael@0: // No need to check the args, ::Read did that, but assert them at least michael@0: NS_ASSERTION(aBuffer,"aBuffer parameter must not be null"); michael@0: NS_ASSERTION(aBytesRead,"aBytesRead parameter must not be null"); michael@0: michael@0: // If the buffer contains data, copy what's there up to the desired amount michael@0: uint32_t numRead = CopyDataToBuffer(aBuffer, aCount); michael@0: michael@0: if (aCount > 0) { michael@0: // empty the buffer and start writing directory entry lines to it michael@0: mBuffer.Truncate(); michael@0: mCurPos = 0; michael@0: const uint32_t arrayLen = mArray.Length(); michael@0: michael@0: for ( ;aCount > mBuffer.Length(); mArrPos++) { michael@0: // have we consumed all the directory contents? michael@0: if (arrayLen <= mArrPos) michael@0: break; michael@0: michael@0: const char * entryName = mArray[mArrPos].get(); michael@0: uint32_t entryNameLen = mArray[mArrPos].Length(); michael@0: nsZipItem* ze = mJar->mZip->GetItem(entryName); michael@0: NS_ENSURE_TRUE(ze, NS_ERROR_FILE_TARGET_DOES_NOT_EXIST); michael@0: michael@0: // Last Modified Time michael@0: PRExplodedTime tm; michael@0: PR_ExplodeTime(ze->LastModTime(), PR_GMTParameters, &tm); michael@0: char itemLastModTime[65]; michael@0: PR_FormatTimeUSEnglish(itemLastModTime, michael@0: sizeof(itemLastModTime), michael@0: " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", michael@0: &tm); michael@0: michael@0: // write a 201: line to the buffer for this item michael@0: // 200: filename content-length last-modified file-type michael@0: mBuffer.AppendLiteral("201: "); michael@0: michael@0: // Names must be escaped and relative, so use the pre-calculated length michael@0: // of the directory name as the offset into the string michael@0: // NS_EscapeURL adds the escaped URL to the give string buffer michael@0: NS_EscapeURL(entryName + mNameLen, michael@0: entryNameLen - mNameLen, michael@0: esc_Minimal | esc_AlwaysCopy, michael@0: mBuffer); michael@0: michael@0: mBuffer.Append(' '); michael@0: mBuffer.AppendInt(ze->RealSize(), 10); michael@0: mBuffer.Append(itemLastModTime); // starts/ends with ' ' michael@0: if (ze->IsDirectory()) michael@0: mBuffer.AppendLiteral("DIRECTORY\n"); michael@0: else michael@0: mBuffer.AppendLiteral("FILE\n"); michael@0: } michael@0: michael@0: // Copy up to the desired amount of data to buffer michael@0: numRead += CopyDataToBuffer(aBuffer, aCount); michael@0: } michael@0: michael@0: *aBytesRead = numRead; michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint32_t michael@0: nsJARInputStream::CopyDataToBuffer(char* &aBuffer, uint32_t &aCount) michael@0: { michael@0: const uint32_t writeLength = std::min(aCount, mBuffer.Length() - mCurPos); michael@0: michael@0: if (writeLength > 0) { michael@0: memcpy(aBuffer, mBuffer.get() + mCurPos, writeLength); michael@0: mCurPos += writeLength; michael@0: aCount -= writeLength; michael@0: aBuffer += writeLength; michael@0: } michael@0: michael@0: // return number of bytes copied to the buffer so the michael@0: // Read method can return the number of bytes copied michael@0: return writeLength; michael@0: }