diff --git a/mozilla/modules/libjar/nsIZipReader.idl b/mozilla/modules/libjar/nsIZipReader.idl index 998454f8f5d..afb4c5949ef 100644 --- a/mozilla/modules/libjar/nsIZipReader.idl +++ b/mozilla/modules/libjar/nsIZipReader.idl @@ -26,6 +26,7 @@ * Don Bragg * Samir Gehani * Mitch Stoltz + * Jeff Walden * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -47,14 +48,50 @@ interface nsISimpleEnumerator; interface nsIInputStream; interface nsIFile; -[scriptable, uuid(6ca5e43e-9632-11d3-8cd9-0060b0fc14a3)] +[scriptable, uuid(ea950fff-aa64-49f9-879c-a830034786f8)] interface nsIZipEntry : nsISupports { + /** + * The name (including path) of the entry. + */ readonly attribute string name; + /** + * The type of compression used for the item. The possible values and + * their meanings are defined in the zip file specification at + * http://www.pkware.com/business_and_developers/developer/appnote/ + */ readonly attribute unsigned short compression; + /** + * The compressed size of the data in the item. + */ readonly attribute unsigned long size; + /** + * The uncompressed size of the data in the item. + */ readonly attribute unsigned long realSize; + /** + * The CRC-32 hash of the file in the entry. + */ readonly attribute unsigned long CRC32; + /** + * True if the name of the entry ends with '/' and false otherwise. + */ + readonly attribute boolean isDirectory; + /** + * The time at which this item was last modified. + */ + readonly attribute PRTime lastModifiedTime; + /** + * Use this attribute to determine whether this item is an actual zip entry + * or is one synthesized for part of a real entry's path. A synthesized + * entry represents a directory within the zip file which has no + * corresponding entry within the zip file. For example, the entry for the + * directory foo/ in a zip containing exactly one entry for foo/bar.txt + * is synthetic. If the zip file contains an actual entry for a directory, + * this attribute will be false for the nsIZipEntry for that directory. + * It is impossible for a file to be synthetic. + */ + readonly attribute boolean isSynthetic; }; [scriptable, uuid(6ff6a966-9632-11d3-8cd9-0060b0fc14a3)] @@ -65,6 +102,10 @@ interface nsIZipReader : nsISupports */ void init(in nsIFile zipFile); + /** + * The file that represents the zip with which this zip reader was + * initialized. + */ readonly attribute nsIFile file; /** @@ -82,12 +123,16 @@ interface nsIZipReader : nsISupports * Tests the integrity of the archive by performing a CRC check * on each item expanded into memory. If an entry is specified * the integrity of only that item is tested. If NULL is passed - * in the inetgrity of all items in the archive are tested. + * in the integrity of all items in the archive are tested. */ void test(in string aEntryName); /** * Extracts a zip entry into a local file specified by outFile. + * The entry must be stored in the zip in either uncompressed or + * DEFLATE-compressed format for the extraction to be successful. + * If the entry is a directory, the directory will be extracted + * non-recursively. */ void extract(in string zipEntry, in nsIFile outFile); @@ -98,11 +143,51 @@ interface nsIZipReader : nsISupports /** * Returns a simple enumerator whose elements are of type nsIZipEntry. + * + * @param aPattern + * A regular expression used to find matching entries in the zip file. + * Set this parameter to null to get all entries; otherwise, use the + * following syntax: + * + * o * matches anything + * o ? matches one character + * o $ matches the end of the string + * o [abc] matches one occurrence of a, b, or c. The only character that + * must be escaped inside the brackets is ]. ^ and - must never + * appear in the first and second positions within the brackets, + * respectively. (In the former case, the behavior specified for + * '[^az]' will happen.) + * o [a-z] matches any character between a and z. The characters a and z + * must either both be letters or both be numbers, with the + * character represented by 'a' having a lower ASCII value than + * the character represented by 'z'. + * o [^az] matches any character except a or z. If ] is to appear inside + * the brackets as a character to not match, it must be escaped. + * o pat~pat2 returns matches to the pattern 'pat' which do not also match + * the pattern 'pat2'. This may be used to perform filtering + * upon the results of one pattern to remove all matches which + * also match another pattern. For example, because '*' + * matches any string and '*z*' matches any string containing a + * 'z', '*~*z*' will match all strings except those containing + * a 'z'. Note that a pattern may not use '~' multiple times, + * so a string such as '*~*z*~*y*' is not a valid pattern. + * o (foo|bar) will match either the pattern foo or the pattern bar. + * Neither of the patterns foo or bar may use the 'pat~pat2' + * syntax described immediately above. + * o \ will escape a special character. Escaping is required for all + * special characters unless otherwise specified. + * o All other characters match case-sensitively. + * + * An aPattern not conforming to this syntax has undefined behavior. + * + * @throws NS_ERROR_ILLEGAL_VALUE on many but not all invalid aPattern + * values. */ nsISimpleEnumerator/**/ findEntries(in string aPattern); /** - * Returns an input stream containing the contents of the specified zip entry. + * Returns an input stream containing the contents of the specified zip + * entry. */ nsIInputStream getInputStream(in string zipEntry); }; diff --git a/mozilla/modules/libjar/nsJAR.cpp b/mozilla/modules/libjar/nsJAR.cpp index aaf6ddd57bf..353a82c5b6a 100644 --- a/mozilla/modules/libjar/nsJAR.cpp +++ b/mozilla/modules/libjar/nsJAR.cpp @@ -25,6 +25,7 @@ * Samir Gehani * Mitch Stoltz * Pierre Phaneuf + * Jeff Walden * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -58,23 +59,6 @@ #include #endif -//---------------------------------------------- -// Errors and other utility definitions -//---------------------------------------------- -#ifndef __gen_nsIFile_h__ -#define NS_ERROR_FILE_UNRECOGNIZED_PATH NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 1) -#define NS_ERROR_FILE_UNRESOLVABLE_SYMLINK NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 2) -#define NS_ERROR_FILE_EXECUTION_FAILED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 3) -#define NS_ERROR_FILE_UNKNOWN_TYPE NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 4) -#define NS_ERROR_FILE_DESTINATION_NOT_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 5) -#define NS_ERROR_FILE_TARGET_DOES_NOT_EXIST NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 6) -#define NS_ERROR_FILE_COPY_OR_MOVE_FAILED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 7) -#define NS_ERROR_FILE_ALREADY_EXISTS NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 8) -#define NS_ERROR_FILE_INVALID_PATH NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 9) -#define NS_ERROR_FILE_DISK_FULL NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 10) -#define NS_ERROR_FILE_CORRUPTED NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_FILES, 11) -#endif - static nsresult ziperr2nsresult(PRInt32 ziperr) { @@ -280,15 +264,37 @@ nsJAR::Extract(const char *zipEntry, nsIFile* outFile) if (err != ZIP_OK) return ziperr2nsresult(err); - // Remove existing file so we set permissions correctly. - localFile->Remove(PR_FALSE); + // Remove existing file or directory so we set permissions correctly. + // If it's a directory that already exists and contains files, throw + // an exception and return. - PRFileDesc* fd; - rv = localFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->mode, &fd); - if (NS_FAILED(rv)) return NS_ERROR_FILE_ACCESS_DENIED; + //XXX Bug 332139: + //XXX If we guarantee that rv in the case of a non-empty directory + //XXX is always FILE_DIR_NOT_EMPTY, we can remove + //XXX |rv == NS_ERROR_FAILURE| - bug 322183 needs to be completely + //XXX fixed before that can happen + rv = localFile->Remove(PR_FALSE); + if (rv == NS_ERROR_FILE_DIR_NOT_EMPTY || + rv == NS_ERROR_FAILURE) + return rv; - err = mZip.ExtractItemToFileDesc(item, fd, mFd); - PR_Close(fd); + if (item->isDirectory) + { + rv = localFile->Create(nsIFile::DIRECTORY_TYPE, item->mode); + if (NS_FAILED(rv)) return rv; + //XXX Do this in nsZipArchive? It would be nice to keep extraction + //XXX code completely there, but that would require a way to get a + //XXX PRDir from localFile. + } + else + { + PRFileDesc* fd; + rv = localFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, item->mode, &fd); + if (NS_FAILED(rv)) return rv; + + err = mZip.ExtractItemToFileDesc(item, fd, mFd); + PR_Close(fd); + } if (err != ZIP_OK) outFile->Remove(PR_FALSE); @@ -340,10 +346,11 @@ nsJAR::FindEntries(const char *aPattern, nsISimpleEnumerator **result) { if (!result) return NS_ERROR_INVALID_POINTER; - - nsZipFind *find = mZip.FindInit(aPattern); - if (!find) - return NS_ERROR_OUT_OF_MEMORY; + + nsZipFind *find; + PRInt32 rv = mZip.FindInit(aPattern, &find); + if (rv != ZIP_OK) + return ziperr2nsresult(rv); nsISimpleEnumerator *zipEnum = new nsJAREnumerator(find); if (!zipEnum) @@ -363,18 +370,16 @@ nsJAR::GetInputStream(const char* aFilename, nsIInputStream** result) NS_ENSURE_ARG_POINTER(result); nsJARInputStream* jis = new nsJARInputStream(); - if (!jis) return NS_ERROR_FAILURE; + if (!jis) return NS_ERROR_OUT_OF_MEMORY; - // addref now so we can delete if the Init() fails - *result = NS_STATIC_CAST(nsIInputStream*,jis); + // addref now so we can call Init() + *result = jis; NS_ADDREF(*result); - nsresult rv; - rv = jis->Init(this, aFilename); - + nsresult rv = jis->Init(this, aFilename); if (NS_FAILED(rv)) { NS_RELEASE(*result); - return NS_ERROR_FAILURE; + return rv; } return NS_OK; @@ -1055,7 +1060,7 @@ nsJARItem::~nsJARItem() { } -NS_IMPL_ISUPPORTS1(nsJARItem, nsIZipEntry) +NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARItem, nsIZipEntry) void nsJARItem::Init(nsZipItem* aZipItem) { @@ -1086,13 +1091,11 @@ nsJARItem::GetName(char * *aName) //------------------------------------------ // nsJARItem::GetCompression //------------------------------------------ -NS_IMETHODIMP +NS_IMETHODIMP nsJARItem::GetCompression(PRUint16 *aCompression) { if (!aCompression) return NS_ERROR_NULL_POINTER; - if (!mZipItem->compression) - return NS_ERROR_FAILURE; *aCompression = mZipItem->compression; return NS_OK; @@ -1101,13 +1104,11 @@ nsJARItem::GetCompression(PRUint16 *aCompression) //------------------------------------------ // nsJARItem::GetSize //------------------------------------------ -NS_IMETHODIMP +NS_IMETHODIMP nsJARItem::GetSize(PRUint32 *aSize) { if (!aSize) return NS_ERROR_NULL_POINTER; - if (!mZipItem->size) - return NS_ERROR_FAILURE; *aSize = mZipItem->size; return NS_OK; @@ -1116,13 +1117,11 @@ nsJARItem::GetSize(PRUint32 *aSize) //------------------------------------------ // nsJARItem::GetRealSize //------------------------------------------ -NS_IMETHODIMP +NS_IMETHODIMP nsJARItem::GetRealSize(PRUint32 *aRealsize) { if (!aRealsize) return NS_ERROR_NULL_POINTER; - if (!mZipItem->realsize) - return NS_ERROR_FAILURE; *aRealsize = mZipItem->realsize; return NS_OK; @@ -1131,18 +1130,55 @@ nsJARItem::GetRealSize(PRUint32 *aRealsize) //------------------------------------------ // nsJARItem::GetCrc32 //------------------------------------------ -NS_IMETHODIMP +NS_IMETHODIMP nsJARItem::GetCRC32(PRUint32 *aCrc32) { if (!aCrc32) return NS_ERROR_NULL_POINTER; - if (!mZipItem->crc32) - return NS_ERROR_FAILURE; *aCrc32 = mZipItem->crc32; return NS_OK; } +//------------------------------------------ +// nsJARItem::GetIsDirectory +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetIsDirectory(PRBool *aIsDirectory) +{ + if (!aIsDirectory) + return NS_ERROR_NULL_POINTER; + + *aIsDirectory = mZipItem->isDirectory; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetIsSynthetic +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetIsSynthetic(PRBool *aIsSynthetic) +{ + if (!aIsSynthetic) + return NS_ERROR_NULL_POINTER; + + *aIsSynthetic = mZipItem->isSynthetic; + return NS_OK; +} + +//------------------------------------------ +// nsJARItem::GetLastModifiedTime +//------------------------------------------ +NS_IMETHODIMP +nsJARItem::GetLastModifiedTime(PRTime* aLastModTime) +{ + if (!aLastModTime) + return NS_ERROR_NULL_POINTER; + + *aLastModTime = mZipItem->GetModTime(); + return NS_OK; +} + //////////////////////////////////////////////////////////////////////////////// // nsIZipReaderCache @@ -1289,15 +1325,15 @@ nsZipReaderCache::ReleaseZip(nsJAR* zip) // case is where one thread Releases the zip and discovers that the ref // count has gone to one. Before it can call this ReleaseZip method // another thread calls our GetZip method. The ref count goes to two. That - // second thread then Releases the zip and the ref coutn goes to one. It - // Then tries to enter this ReleaseZip method and blocks while the first + // second thread then Releases the zip and the ref count goes to one. It + // then tries to enter this ReleaseZip method and blocks while the first // thread is still here. The first thread continues and remove the zip from // the cache and calls its Release method sending the ref count to 0 and // deleting the zip. However, the second thread is still blocked at the // start of ReleaseZip, but the 'zip' param now hold a reference to a // deleted zip! // - // So, we are going to try safegaurding here by searching our hashtable while + // So, we are going to try safeguarding here by searching our hashtable while // locked here for the zip. We return fast if it is not found. ZipFindData find_data = {zip, PR_FALSE}; diff --git a/mozilla/modules/libjar/nsJAR.h b/mozilla/modules/libjar/nsJAR.h index 253ec33e427..734e1af326c 100644 --- a/mozilla/modules/libjar/nsJAR.h +++ b/mozilla/modules/libjar/nsJAR.h @@ -118,6 +118,7 @@ class nsJAR : public nsIZipReader, public nsIJAR } PRFileDesc* OpenFile(); + protected: //-- Private data members nsCOMPtr mZipFile; // The zip/jar file on disk @@ -230,4 +231,3 @@ protected: //////////////////////////////////////////////////////////////////////////////// #endif /* nsJAR_h__ */ - diff --git a/mozilla/modules/libjar/nsJARChannel.cpp b/mozilla/modules/libjar/nsJARChannel.cpp index 7bba2b99092..9f9ccf2f130 100644 --- a/mozilla/modules/libjar/nsJARChannel.cpp +++ b/mozilla/modules/libjar/nsJARChannel.cpp @@ -21,6 +21,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Jeff Walden * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -36,8 +37,10 @@ * * ***** END LICENSE BLOCK ***** */ +#include "nsJAR.h" #include "nsJARChannel.h" #include "nsJARProtocolHandler.h" +#include "nsJARDirectoryInputStream.h" #include "nsMimeTypes.h" #include "nsNetUtil.h" #include "nsInt64.h" @@ -50,6 +53,11 @@ static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); +// the entry for a directory will either be empty (in the case of the +// top-level directory) or will end with a slash +#define ENTRY_IS_DIRECTORY(_entry) \ + ((_entry).IsEmpty() || '/' == (_entry).Last()) + //----------------------------------------------------------------------------- #if defined(PR_LOGGING) @@ -74,10 +82,13 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIINPUTSTREAM - nsJARInputThunk(nsIFile *jarFile, const nsACString &jarEntry, + nsJARInputThunk(nsIFile *jarFile, + nsIURI* fullJarURI, + const nsACString &jarEntry, nsIZipReaderCache *jarCache) : mJarCache(jarCache) , mJarFile(jarFile) + , mFullJarURI(fullJarURI) , mJarEntry(jarEntry) , mContentLength(-1) { @@ -107,6 +118,7 @@ private: nsCOMPtr mJarCache; nsCOMPtr mJarReader; nsCOMPtr mJarFile; + nsCOMPtr mFullJarURI; nsCOMPtr mJarStream; nsCString mJarEntry; PRInt32 mContentLength; @@ -135,8 +147,25 @@ nsJARInputThunk::EnsureJarStream() } if (NS_FAILED(rv)) return rv; - rv = mJarReader->GetInputStream(mJarEntry.get(), - getter_AddRefs(mJarStream)); + if (ENTRY_IS_DIRECTORY(mJarEntry)) { + // This isn't simply part of nsJAR::GetInputStream because it shouldn't + // be possible to get an input stream for a directory in a zip via that + // path, just as it isn't possible to get a directory stream via an + // nsIFileInputStream + + nsCAutoString jarDirSpec; + rv = mFullJarURI->GetAsciiSpec(jarDirSpec); + if (NS_FAILED(rv)) return rv; + + rv = nsJARDirectoryInputStream::Create(mJarReader, + jarDirSpec, + mJarEntry.get(), + getter_AddRefs(mJarStream)); + } + else { + rv = mJarReader->GetInputStream(mJarEntry.get(), + getter_AddRefs(mJarStream)); + } if (NS_FAILED(rv)) return rv; // ask the JarStream for the content length @@ -260,7 +289,7 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache) nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile)); if (NS_FAILED(rv)) return rv; - mJarInput = new nsJARInputThunk(clonedFile, mJarEntry, jarCache); + mJarInput = new nsJARInputThunk(clonedFile, mJarURI, mJarEntry, jarCache); if (!mJarInput) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(mJarInput); @@ -281,7 +310,7 @@ nsJARChannel::EnsureJarInput(PRBool blocking) rv = mJarURI->GetJAREntry(mJarEntry); if (NS_FAILED(rv)) return rv; - // The name of the JAR entry must not contains URL escaped characters: + // The name of the JAR entry must not contain URL-escaped characters: // we're moving from URL domain to a filename domain here. nsStandardURL // does basic escaping by default, which breaks reading zipped files which // have e.g. spaces in their filenames. @@ -532,26 +561,31 @@ nsJARChannel::GetContentType(nsACString &result) // // generate content type and set it // - if (mJarEntry.IsEmpty()) { - LOG(("mJarEntry is empty!\n")); - return NS_ERROR_NOT_AVAILABLE; - } - const char *ext = nsnull, *fileName = mJarEntry.get(); PRInt32 len = mJarEntry.Length(); - for (PRInt32 i = len-1; i >= 0; i--) { - if (fileName[i] == '.') { - ext = &fileName[i + 1]; - break; + + // check if we're displaying a directory + // mJarEntry will be empty if we're trying to display + // the topmost directory in a zip, e.g. jar:foo.zip!/ + if (ENTRY_IS_DIRECTORY(mJarEntry)) { + mContentType.AssignLiteral(APPLICATION_HTTP_INDEX_FORMAT); + } + else { + // not a directory, take a guess by its extension + for (PRInt32 i = len-1; i >= 0; i--) { + if (fileName[i] == '.') { + ext = &fileName[i + 1]; + break; + } } + if (ext) { + nsIMIMEService *mimeServ = gJarHandler->MimeService(); + if (mimeServ) + mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType); + } + if (mContentType.IsEmpty()) + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); } - if (ext) { - nsIMIMEService *mimeServ = gJarHandler->MimeService(); - if (mimeServ) - mimeServ->GetTypeFromExtension(nsDependentCString(ext), mContentType); - } - if (mContentType.IsEmpty()) - mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); } result = mContentType; return NS_OK; diff --git a/mozilla/modules/libjar/nsJARDirectoryInputStream.cpp b/mozilla/modules/libjar/nsJARDirectoryInputStream.cpp new file mode 100644 index 00000000000..5475bc5dbe9 --- /dev/null +++ b/mozilla/modules/libjar/nsJARDirectoryInputStream.cpp @@ -0,0 +1,327 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* nsJARDirectoryInputStream.cpp + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla libjar code. + * + * The Initial Developer of the Original Code is + * Jeff Walden . + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsJARDirectoryInputStream.h" +#include "nsNetUtil.h" +#include "nsEscape.h" +#include "nsIFile.h" + +/** + * Sorting function which sorts nsIZipEntry items into alphabetical order. + */ +static int PR_CALLBACK compare(nsIZipEntry* aElement1, + nsIZipEntry* aElement2, + void* aData) +{ + //XXX not i18n, but names in zips have no defined charset, so we can't win + nsXPIDLCString name1, name2; + aElement1->GetName(getter_Copies(name1)); + aElement2->GetName(getter_Copies(name2)); + + return Compare(name1, name2); +} + +/*--------------------------------------------- + * nsISupports implementation + *--------------------------------------------*/ + +NS_IMPL_THREADSAFE_ISUPPORTS1(nsJARDirectoryInputStream, nsIInputStream) + +/*---------------------------------------------------------- + * nsJARDirectoryInputStream implementation + *--------------------------------------------------------*/ + +NS_IMETHODIMP +nsJARDirectoryInputStream::Available(PRUint32 *_retval) +{ + if (NS_FAILED(mStatus)) + return mStatus; + + *_retval = mBuffer.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsJARDirectoryInputStream::Read(char* buf, PRUint32 count, PRUint32 *bytesRead) +{ + if (mStatus == NS_BASE_STREAM_CLOSED) { + *bytesRead = 0; + return NS_OK; + } + + if (NS_FAILED(mStatus)) + return mStatus; + + nsresult rv; + + // If the buffer contains data, copy what's there up to the desired amount + PRUint32 numRead = CopyDataToBuffer(buf, count); + + if (count > 0) { + // empty the buffer and start writing directory entry lines to it + mBuffer.Truncate(); + mBufPos = 0; + PRUint32 arrayLen = mArray.Count(); + while (count > mBuffer.Length()) { + // have we consumed all the directory contents? + if (arrayLen <= mArrPos) + break; + + // Don't addref, for speed -- the object was addrefed when + // it was added to the array, so it won't become stale until + // the array dies + nsIZipEntry* ze = mArray.ObjectAt(mArrPos++); + + // Type + PRBool isDir; + rv = ze->GetIsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + const char* itemType; + if (isDir) { + itemType = "DIRECTORY\n"; + } else { + itemType = "FILE\n"; + } + + // Size (real, not compressed) + PRUint32 itemRealSize = 0; + rv = ze->GetRealSize(&itemRealSize); + if (NS_FAILED(rv)) return rv; + + // Name (escaped, relative) + nsXPIDLCString entryName; + rv = ze->GetName(getter_Copies(entryName)); + if (NS_FAILED(rv)) return rv; + + // names must be relative, so use the pre-calculated length + // of the directory name as the offset into the string + nsCAutoString itemName; + entryName.Cut(0, mDirNameLen); + NS_EscapeURL(entryName, esc_Minimal | esc_AlwaysCopy, itemName); + + // Last Modified Time + PRTime lmt = LL_Zero(); + rv = ze->GetLastModifiedTime(&lmt); + if (NS_FAILED(rv)) return rv; + + PRExplodedTime tm; + PR_ExplodeTime(lmt, PR_GMTParameters, &tm); + char itemLastModTime[65]; + PR_FormatTimeUSEnglish(itemLastModTime, + sizeof(itemLastModTime), + " %a,%%20%d%%20%b%%20%Y%%20%H:%M:%S%%20GMT ", + &tm); + + // write a 201: line to the buffer for this item + // 200: filename content-length last-modified file-type + mBuffer.AppendLiteral("201: "); + mBuffer.Append(itemName); + mBuffer.AppendLiteral(" "); + mBuffer.AppendInt(itemRealSize, 10); + mBuffer.Append(itemLastModTime); // starts/ends with ' ' + mBuffer.Append(itemType); // '\n'-terminated + } + + // Copy up to the desired amount of data to buffer + numRead += CopyDataToBuffer(buf, count); + } + + *bytesRead = numRead; + return NS_OK; +} + +NS_IMETHODIMP +nsJARDirectoryInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 count, PRUint32 *_retval) +{ + // XXX write me! + NS_NOTREACHED("Consumers should be using Read()!"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsJARDirectoryInputStream::IsNonBlocking(PRBool *aNonBlocking) +{ + *aNonBlocking = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsJARDirectoryInputStream::Close() +{ + mStatus = NS_BASE_STREAM_CLOSED; + return NS_OK; +} + +/* static */ nsresult +nsJARDirectoryInputStream::Create(nsIZipReader* aZip, + const nsACString& aJarDirSpec, + const char* aDir, + nsIInputStream** result) +{ + NS_ENSURE_ARG_POINTER(aZip); + NS_ENSURE_ARG_POINTER(aDir); + NS_ENSURE_ARG_POINTER(result); + + nsJARDirectoryInputStream* jdis = new nsJARDirectoryInputStream(); + if (!jdis) return NS_ERROR_OUT_OF_MEMORY; + + // addref now so we can call Init() + *result = jdis; + NS_ADDREF(*result); + + nsresult rv = jdis->Init(aZip, aJarDirSpec, aDir); + if (NS_FAILED(rv)) NS_RELEASE(*result); + + return rv; +} + +nsresult +nsJARDirectoryInputStream::Init(nsIZipReader* aZip, + const nsACString& aJarDirSpec, + const char* aDir) +{ + // Ensure that aDir is really a directory and that it exists. + // Watch out for the jar:foo.zip!/ (aDir is empty) top-level + // special case! + nsresult rv; + if (*aDir) { + nsCOMPtr ze; + rv = aZip->GetEntry(aDir, getter_AddRefs(ze)); + if (NS_FAILED(rv)) return rv; + + PRBool isDir; + rv = ze->GetIsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + + if (!isDir) + return NS_ERROR_ILLEGAL_VALUE; + } + + // We can get aDir's contents as nsIZipEntry items via FindEntries + // with the following pattern (see nsIZipReader.findEntries docs) + // assuming dirName is properly escaped: + // + // dirName + "?*~" + dirName + "?*/?*" + nsDependentCString dirName(aDir); + mDirNameLen = dirName.Length(); + + // iterate through dirName and copy it to escDirName, escaping chars + // which are special at the "top" level of the regexp so FindEntries + // works correctly + nsCAutoString escDirName; + const char* curr = dirName.BeginReading(); + const char* end = dirName.EndReading(); + while (curr != end) { + switch (*curr) { + case '*': + case '?': + case '$': + case '[': + case ']': + case '^': + case '~': + case '(': + case ')': + case '\\': + escDirName.Append('\\'); + // fall through + default: + escDirName.Append(*curr); + } + ++curr; + } + + nsCAutoString pattern = escDirName + NS_LITERAL_CSTRING("?*~") + + escDirName + NS_LITERAL_CSTRING("?*/?*"); + + nsCOMPtr dirEnum; + rv = aZip->FindEntries(pattern.get(), getter_AddRefs(dirEnum)); + if (NS_FAILED(rv)) return rv; + + PRBool more; + nsCOMPtr item; + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { + rv = dirEnum->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr ze = do_QueryInterface(item); + if (ze) + mArray.AppendObject(ze); // addrefs + } + } + + // Sort it + mArray.Sort(compare, nsnull); + + mBuffer.AppendLiteral("300: "); + mBuffer.Append(aJarDirSpec); + mBuffer.AppendLiteral("\n200: filename content-length last-modified file-type\n"); + + return NS_OK; +} + +PRUint32 +nsJARDirectoryInputStream::CopyDataToBuffer(char* &aBuffer, PRUint32 &aCount) +{ + PRUint32 writeLength = PR_MIN(aCount, mBuffer.Length() - mBufPos); + + if (writeLength > 0) { + memcpy(aBuffer, mBuffer.get() + mBufPos, writeLength); + mBufPos += writeLength; + aCount -= writeLength; + aBuffer += writeLength; + } + + // return number of bytes copied to the buffer so the + // Read method can return the number of bytes copied + return writeLength; +} + +//---------------------------------------------- +// nsJARDirectoryInputStream constructor and destructor +//---------------------------------------------- + +nsJARDirectoryInputStream::nsJARDirectoryInputStream() + : mStatus(NS_OK), mArrPos(0), mBufPos(0) +{ +} + +nsJARDirectoryInputStream::~nsJARDirectoryInputStream() +{ + Close(); +} diff --git a/mozilla/modules/libjar/nsJARDirectoryInputStream.h b/mozilla/modules/libjar/nsJARDirectoryInputStream.h new file mode 100644 index 00000000000..dd01d4e8b1f --- /dev/null +++ b/mozilla/modules/libjar/nsJARDirectoryInputStream.h @@ -0,0 +1,81 @@ +/* nsJARDirectoryInputStream.h + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla libjar code. + * + * The Initial Developer of the Original Code is + * Jeff Walden . + * Portions created by the Initial Developer are Copyright (C) 2006 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsJARDIRECTORYINPUTSTREAM_h__ +#define nsJARDIRECTORYINPUTSTREAM_h__ + +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsIInputStream.h" +#include "nsJAR.h" + +/*------------------------------------------------------------------------- + * Class nsJARDirectoryInputStream declaration. This class represents a + * stream whose contents are an application/http-index-format listing for + * a directory in a zip file. + *------------------------------------------------------------------------*/ +class nsJARDirectoryInputStream : public nsIInputStream +{ + public: + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + static nsresult Create(nsIZipReader* aZip, + const nsACString& aJarDirSpec, + const char* aDir, + nsIInputStream** result); + private: + nsJARDirectoryInputStream(); + virtual ~nsJARDirectoryInputStream(); + + nsresult + Init(nsIZipReader* aZip, const nsACString& aJarDirSpec, const char* aDir); + + PRUint32 CopyDataToBuffer(char* &aBuffer, PRUint32 &aCount); + + protected: + nsresult mStatus; // current status of the stream + PRUint32 mDirNameLen; // length of dirname + nsCAutoString mBuffer; // storage for generated text of stream + PRUint32 mArrPos; // current position within mArray + PRUint32 mBufPos; // current position within mBuffer + nsCOMArray mArray; // array of nsIZipEntrys in directory +}; + +#endif /* nsJARDIRECTORYINPUTSTREAM_h__ */ diff --git a/mozilla/modules/libjar/nsJARInputStream.cpp b/mozilla/modules/libjar/nsJARInputStream.cpp index 435fd29ecc8..5925635d97a 100644 --- a/mozilla/modules/libjar/nsJARInputStream.cpp +++ b/mozilla/modules/libjar/nsJARInputStream.cpp @@ -93,26 +93,26 @@ nsJARInputStream::IsNonBlocking(PRBool *aNonBlocking) NS_IMETHODIMP nsJARInputStream::Close() { - NS_IF_RELEASE(mJAR); + mJAR = nsnull; return NS_OK; } nsresult nsJARInputStream::Init(nsJAR* aJAR, const char* aFilename) { - if (!aFilename) - return NS_ERROR_NULL_POINTER; + NS_ENSURE_ARG_POINTER(aJAR); + NS_ENSURE_ARG_POINTER(aFilename); + mJAR = aJAR; - NS_ADDREF(mJAR); - PRInt32 result; + // Don't assert if !fd, because it's possible that aJAR comes + // from a cache and aJAR's file has been deled PRFileDesc* fd = aJAR->OpenFile(); - NS_ASSERTION(fd, "Couldn't open JAR!"); - if (!fd) return NS_ERROR_UNEXPECTED; - - result = aJAR->mZip.ReadInit(aFilename, &mReadInfo, fd); + + // mReadInfo takes ownership of fd here + PRInt32 result = aJAR->mZip.ReadInit(aFilename, &mReadInfo, fd); if (result != ZIP_OK) return NS_ERROR_FAILURE; return NS_OK; @@ -123,7 +123,6 @@ nsJARInputStream::Init(nsJAR* aJAR, const char* aFilename) //---------------------------------------------- nsJARInputStream::nsJARInputStream() - : mJAR(nsnull) { } @@ -131,4 +130,3 @@ nsJARInputStream::~nsJARInputStream() { Close(); } - diff --git a/mozilla/modules/libjar/nsJARInputStream.h b/mozilla/modules/libjar/nsJARInputStream.h index b85335261b1..d90443ff24f 100644 --- a/mozilla/modules/libjar/nsJARInputStream.h +++ b/mozilla/modules/libjar/nsJARInputStream.h @@ -1,4 +1,3 @@ - /* nsJARInputStream.h * * ***** BEGIN LICENSE BLOCK ***** @@ -46,13 +45,14 @@ {0xa756724a, 0x1dd1, 0x11b2, \ {0x90, 0xd8, 0x9c, 0x98, 0xfc, 0x2b, 0x7a, 0xc0}} +#include "nsAutoPtr.h" #include "nsIInputStream.h" #include "nsJAR.h" /*------------------------------------------------------------------------- - * Class nsJARInputStream declaration. This class defines objects returned - * by calls to nsJAR::GetInputStream(filename) for the purpose of reading - * a file item out of a JAR file. + * Class nsJARInputStream declaration. This class defines the type of the + * object returned by calls to nsJAR::GetInputStream(filename) for the + * purpose of reading a file item out of a JAR file. *------------------------------------------------------------------------*/ class nsJARInputStream : public nsIInputStream { @@ -71,9 +71,7 @@ class nsJARInputStream : public nsIInputStream protected: nsZipReadState mReadInfo; - - nsJAR* mJAR; + nsRefPtr mJAR; }; -#endif /* nsJAR_h__ */ - +#endif /* nsJARINPUTSTREAM_h__ */ diff --git a/mozilla/modules/libjar/nsWildCard.cpp b/mozilla/modules/libjar/nsWildCard.cpp index ca73d556805..535c9e77025 100644 --- a/mozilla/modules/libjar/nsWildCard.cpp +++ b/mozilla/modules/libjar/nsWildCard.cpp @@ -38,9 +38,10 @@ /* * * * - * shexp.c: shell-like wildcard match routines + * nsWildCard.cpp: shell-like wildcard match routines * - * See shexp.h for public documentation. + * See nsIZipReader.findEntries documentation in nsIZipReader.idl for + * a description of the syntax supported by the routines in this file. * * Rob McCool * @@ -50,7 +51,7 @@ #include "plstr.h" #include "prmem.h" -/* ----------------------------- shexp_valid ------------------------------ */ +/* ----------------------------- _valid_subexp ------------------------------ */ static int @@ -76,7 +77,7 @@ _valid_subexp(char *expr, char stop) ++nsc; if((!expr[++x]) || (expr[x] == ']')) return INVALID_SXP; - for(++x;expr[x] && (expr[x] != ']');++x) + for(;expr[x] && (expr[x] != ']');++x) if(expr[x] == '\\') if(!expr[++x]) return INVALID_SXP; @@ -133,7 +134,7 @@ NS_WildCardValid(char *expr) } -/* ----------------------------- shexp_match ----------------------------- */ +/* ----------------------------- _shexp_match ----------------------------- */ #define MATCH 0 @@ -231,8 +232,16 @@ _shexp_match(char *str, char *expr, PRBool case_insensitive) else { int matched; - for (matched=0;expr[y] != ']';y++) + for (matched=0;expr[y] != ']';y++) { + /* match an escaped ']' character */ + if('\\' == expr[y] && ']' == expr[y+1]) { + if(']' == str[x]) + matched |= 1; + y++; /* move an extra char to compensate for '\\' */ + continue; + } matched |= (str[x] == expr[y]); + } if (neg ^ (!matched)) ret = NOMATCH; } diff --git a/mozilla/modules/libjar/nsWildCard.h b/mozilla/modules/libjar/nsWildCard.h index 9cefdd8c4d0..79fcb14b67b 100644 --- a/mozilla/modules/libjar/nsWildCard.h +++ b/mozilla/modules/libjar/nsWildCard.h @@ -36,29 +36,17 @@ * ***** END LICENSE BLOCK ***** */ /* - * shexp.h: Defines and prototypes for shell exp. match routines + * nsWildCard.h: Defines and prototypes for shell exp. match routines * + * See nsIZipReader.findEntries docs in nsIZipReader.idl for a description of + * the supported expression syntax. + * + * Note that the syntax documentation explicitly says the results of certain + * expressions are undefined. This is intentional to require less robustness + * in the code. Regular expression parsing is hard; the smaller the set of + * features and interactions this code must support, the easier it is to + * ensure it works. * - * This routine will match a string with a shell expression. The expressions - * accepted are based loosely on the expressions accepted by zsh. - * - * o * matches anything - * o ? matches one character - * o \ will escape a special character - * o $ matches the end of the string - * o [abc] matches one occurence of a, b, or c. The only character that needs - * to be escaped in this is ], all others are not special. - * o [a-z] matches any character between a and z - * o [^az] matches any character except a or z - * o ~ followed by another shell expression will remove any pattern - * matching the shell expression from the match list - * o (foo|bar) will match either the substring foo, or the substring bar. - * These can be shell expressions as well. - * - * The public interface to these routines is documented below. - * - * Rob McCool - * */ #ifndef nsWildCard_h__ diff --git a/mozilla/modules/libjar/nsZipArchive.cpp b/mozilla/modules/libjar/nsZipArchive.cpp index 66e6fdc8609..d535f1d877c 100644 --- a/mozilla/modules/libjar/nsZipArchive.cpp +++ b/mozilla/modules/libjar/nsZipArchive.cpp @@ -25,6 +25,7 @@ * Samir Gehani * Mitch Stoltz * Jeroen Dobbelaere + * Jeff Walden * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -115,7 +116,7 @@ char * strdup(const char *src) #include #endif -#ifndef XP_UNIX /* we need to have some constant defined in limits.h and unistd.h */ +#ifndef XP_UNIX /* we need some constants defined in limits.h and unistd.h */ # ifndef S_IFMT # define S_IFMT 0170000 # endif @@ -275,7 +276,12 @@ PR_PUBLIC_API(void*) ZIP_FindInit(void* hZip, const char * pattern) return 0; /* whatever it is isn't one of ours! */ /*--- initialize the pattern search ---*/ - return zip->FindInit(pattern); + nsZipFind* find; + PRInt32 rv = zip->FindInit(pattern, &find); + if (rv != ZIP_OK) + find = NULL; + + return find; } @@ -519,9 +525,10 @@ PRInt32 nsZipArchive::Test(const char *aEntryName, PRFileDesc* aFd) } else // test all items in archive { - nsZipFind *iterator = FindInit(NULL); - if (!iterator) - return ZIP_ERR_GENERAL; + nsZipFind *iterator; + rv = FindInit(NULL, &iterator); + if (rv != ZIP_OK) + return rv; // iterate over items in list while (ZIP_OK == FindNext(iterator, &currItem)) @@ -548,7 +555,6 @@ PRInt32 nsZipArchive::Test(const char *aEntryName, PRFileDesc* aFd) //--------------------------------------------- PRInt32 nsZipArchive::CloseArchive() { - #ifndef STANDALONE PL_FinishArenaPool(&mArena); @@ -664,6 +670,10 @@ PRInt32 nsZipArchive::ExtractFile(const char* zipEntry, const char* aOutname, if (!item) return ZIP_ERR_FNF; + // Directory extraction is handled in nsJAR::Extract, + // so the item to be extracted should never be a directory + PR_ASSERT(!item->isDirectory); + // delete any existing file so that we overwrite the file permissions PR_Delete(aOutname); @@ -702,7 +712,7 @@ nsZipArchive::ExtractItemToFileDesc(nsZipItem* item, PRFileDesc* outFD, PRFileDesc* aFd) { //-- sanity check arguments - if (item == 0 || outFD == 0) + if (item == 0 || outFD == 0 || item->isDirectory) return ZIP_ERR_PARAM; PRInt32 status; @@ -729,8 +739,15 @@ nsZipArchive::ExtractItemToFileDesc(nsZipItem* item, PRFileDesc* outFD, //--------------------------------------------- // nsZipArchive::FindInit //--------------------------------------------- -nsZipFind* nsZipArchive::FindInit(const char * aPattern) +PRInt32 +nsZipArchive::FindInit(const char * aPattern, nsZipFind **aFind) { + if (!aFind) + return ZIP_ERR_PARAM; + + // null out param in case an error happens + *aFind = NULL; + PRBool regExp = PR_FALSE; char* pattern = 0; @@ -740,7 +757,7 @@ nsZipFind* nsZipArchive::FindInit(const char * aPattern) switch (NS_WildCardValid((char*)aPattern)) { case INVALID_SXP: - return 0; + return ZIP_ERR_PARAM; case NON_SXP: regExp = PR_FALSE; @@ -753,15 +770,19 @@ nsZipFind* nsZipArchive::FindInit(const char * aPattern) default: // undocumented return value from RegExpValid! PR_ASSERT(PR_FALSE); - return 0; + return ZIP_ERR_PARAM; } pattern = PL_strdup(aPattern); if (!pattern) - return 0; + return ZIP_ERR_PARAM; } - return new nsZipFind(this, pattern, regExp); + *aFind = new nsZipFind(this, pattern, regExp); + if (!*aFind) + return ZIP_ERR_MEMORY; + + return ZIP_OK; } @@ -803,14 +824,44 @@ PRInt32 nsZipArchive::FindNext(nsZipFind* aFind, nsZipItem** aResult) #else found = (PL_strcmp(item->name, aFind->mPattern) == 0); #endif + + // The way that the actual zip entry for a directory overrides a synthetic + // entry created earlier means that a properly-constructed zip could return + // the "same" entry twice during enumeration. For example, adding foo/bar + // and then foo/ to a new zip with Info-ZIP will return two entries for foo/ + // during any enumeration that finds foo/. Here's how we solve the problem: + // + // * non-synthetic items are always matches + // * a synthetic item is a match if for every prior item in + // the current chain, either of the following hold: + // * the prior item is not a directory (synthetic implies directory) + // * the prior item's name is different from the current item's name + // + // We test whether a prior item is a directory before comparing names + // because name comparison involves function call overhead and because + // the typical zip contains more files than directories. + if (found && item->isSynthetic) + { + for (nsZipItem* curr = mFiles[slot]; curr != item; curr = curr->next) + { + if (!curr->isDirectory) + continue; + if (0 == strcmp(item->name, curr->name)) + { + // we already found the real item with this name -- skip this item + found = PR_FALSE; + break; + } + } + } } if (found) { - *aResult = item; - aFind->mSlot = slot; - aFind->mItem = item; - status = ZIP_OK; + *aResult = item; + aFind->mSlot = slot; + aFind->mItem = item; + status = ZIP_OK; } else status = ZIP_ERR_FNF; @@ -873,6 +924,26 @@ PRInt32 nsZipArchive::ResolveSymlink(const char *path, nsZipItem *item) #define BR_BUF_SIZE 1024 /* backward read buffer size */ +//--------------------------------------------- +// nsZipArchive::CreateZipItem +//--------------------------------------------- +nsZipItem* nsZipArchive::CreateZipItem() +{ + nsZipItem* item; + +#ifndef STANDALONE + // Arena allocate the nsZipItem + void *mem; + PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem)); + // Use placement new to arena allocate the nsZipItem + item = mem ? new (mem) nsZipItem() : nsnull; +#else + item = new nsZipItem(); +#endif + + return item; +} + //--------------------------------------------- // nsZipArchive::BuildFileList //--------------------------------------------- @@ -945,7 +1016,7 @@ PRInt32 nsZipArchive::BuildFileList(PRFileDesc* aFd) if(pos <= 0) //-- We're at the beginning of the file, and still no sign - //-- of the end signiture. File must be corrupted! + //-- of the end signature. File must be corrupted! status = ZIP_ERR_CORRUPT; //-- backward read must overlap ZipEnd length @@ -991,40 +1062,32 @@ PRInt32 nsZipArchive::BuildFileList(PRFileDesc* aFd) PRUint32 namelen = xtoint(central->filename_len); PRUint32 extralen = xtoint(central->extrafield_len); PRUint32 commentlen = xtoint(central->commentfield_len); -#ifndef STANDALONE - // Arena allocate the nsZipItem - void *mem; - PL_ARENA_ALLOCATE(mem, &mArena, sizeof(nsZipItem)); - // Use placement new to arena allcoate the nsZipItem - nsZipItem* item = mem ? new (mem) nsZipItem() : nsnull; -#else - nsZipItem* item = new nsZipItem(); -#endif + + nsZipItem* item = CreateZipItem(); if (!item) { status = ZIP_ERR_MEMORY; break; } - item->offset = xtolong(central->localhdr_offset); + item->offset = xtolong(central->localhdr_offset); + item->size = xtolong(central->size); + item->realsize = xtolong(central->orglen); + item->crc32 = xtolong(central->crc32); + item->time = xtoint(central->time); + item->date = xtoint(central->date); + item->isSynthetic = PR_FALSE; item->compression = (PRUint8)xtoint(central->method); #if defined(DEBUG) - /* - * Make sure our space optimization is non lossy. - */ + /* Make sure our space optimization is non lossy. */ PR_ASSERT(xtoint(central->method) == (PRUint16)item->compression); #endif - item->size = xtolong(central->size); - item->realsize = xtolong(central->orglen); - item->crc32 = xtolong(central->crc32); PRUint32 external_attributes = xtolong(central->external_attributes); item->mode = ExtractMode(external_attributes); if (IsSymlink(external_attributes)) { item->flags |= ZIFLAG_SYMLINK; } - item->time = xtoint(central->time); - item->date = xtoint(central->date); pos += ZIPCENTRAL_SIZE; @@ -1032,6 +1095,7 @@ PRInt32 nsZipArchive::BuildFileList(PRFileDesc* aFd) // get the item name //------------------------------------------------------- #ifndef STANDALONE + void* mem; PL_ARENA_ALLOCATE(mem, &mArena, (namelen + 1)); item->name = (char *) mem; if (!item->name) @@ -1043,13 +1107,13 @@ PRInt32 nsZipArchive::BuildFileList(PRFileDesc* aFd) } #else item->name = new char[namelen + 1]; -#endif if (!item->name) { status = ZIP_ERR_MEMORY; delete item; break; } +#endif PRUint32 leftover = (PRUint32)(bufsize - pos); if (leftover < namelen) @@ -1070,13 +1134,118 @@ PRInt32 nsZipArchive::BuildFileList(PRFileDesc* aFd) memcpy(item->name, buf+pos, namelen); item->name[namelen] = 0; + //-- an item whose name ends with '/' is a directory + item->isDirectory = ('/' == item->name[namelen - 1]); + //-- add item to file table + //-- note that an explicit entry for a directory will override + //-- a fake entry created for that directory (as in the case + //-- of processing foo/bar.txt and then foo/) -- this will + //-- preserve an explicit directory's metadata at the cost of + //-- an extra nsZipItem (and that only happens if we process a + //-- file inside that directory before processing the directory + //-- entry itself) PRUint32 hash = HashName(item->name); item->next = mFiles[hash]; mFiles[hash] = item; pos += namelen; + //-- add entries for directories in the current item's path + //-- go from end to beginning, because then we can stop trying + //-- to create diritems if we find that the diritem we want to + //-- create already exists + //-- start just before the last char so as to not add the item + //-- twice if it's a directory + for (char* p = item->name + namelen - 2; p >= item->name; p--) + { + if ('/' != *p) + continue; + + PRUint32 dirnamelen = p + 1 - item->name; + + // See whether we need to create any more implicit directories, + // because if we don't we can avoid a lot of work. + // We can even avoid (de)allocating space for a bogus dirname with + // a little trickery -- save the char at item->name[dirnamelen], + // set it to 0, compare the strings, and restore the saved + // char when done + char savedChar = item->name[dirnamelen]; + item->name[dirnamelen] = 0; + + // Is the directory in the file table? + PRUint32 hash = HashName(item->name); + PRBool done = PR_FALSE; + for (nsZipItem* zi = mFiles[hash]; zi != NULL; zi = zi->next) + { + if (0 == strcmp(item->name, zi->name)) + { + // we've already added this dir and all its parents + done = PR_TRUE; + break; + } + } + + // restore the char immediately + item->name[dirnamelen] = savedChar; + + // if the directory was found, break out of the directory + // creation loop now that we know all implicit directories + // are there -- otherwise, start creating the zip item + if (done) + break; + + nsZipItem* diritem = CreateZipItem(); + if (!diritem) + { + status = ZIP_ERR_MEMORY; + break; + } + +#ifndef STANDALONE + void* mem; + PL_ARENA_ALLOCATE(mem, &mArena, (dirnamelen + 1)); + char* dirname = (char *) mem; + if (!dirname) + { + status = ZIP_ERR_MEMORY; + // No need to delete name. It gets deleted only when the entire arena + // goes away. + break; + } +#else + char* dirname = new char[dirnamelen + 1]; + if (!dirname) + { + status = ZIP_ERR_MEMORY; + delete diritem; + break; + } +#endif + + memcpy(dirname, item->name, dirnamelen); + dirname[dirnamelen] = 0; + diritem->name = dirname; + + diritem->isDirectory = PR_TRUE; + diritem->isSynthetic = PR_TRUE; + diritem->compression = STORED; + diritem->size = diritem->realsize = 0; + diritem->crc32 = 0; + diritem->mode = 0755; + + // Set an obviously wrong last-modified date/time, because + // finding something more accurate like the most recent + // last-modified date/time of the dir's contents is a lot + // of effort. The date/time corresponds to 1980-01-01 00:00. + diritem->time = 0; + diritem->date = 1 + (1 << 5) + (0 << 9); + + // add diritem to the file table + diritem->next = mFiles[hash]; + mFiles[hash] = diritem; + } /* end processing of dirs in item's name */ + //------------------------------------------------------- // set up to process the next item at the top of loop //------------------------------------------------------- @@ -1254,7 +1423,7 @@ nsZipArchive::CopyItemToDisk(const nsZipItem* aItem, #ifndef STANDALONE //------------------------------------------ -// nsZipArchive::Read +// nsZipReadState::Read //------------------------------------------ PRInt32 nsZipReadState::Read(char* aBuffer, PRUint32 aCount, @@ -1281,7 +1450,7 @@ nsZipReadState::Read(char* aBuffer, PRUint32 aCount, result = ZIP_ERR_UNSUPPORTED; } - // be agressive about closing! + // be aggressive about closing! // note that sometimes, we will close mFd before we've finished // deflating - this is because zlib buffers the input if (mCurPos >= mItem->size && mFd) { @@ -1578,6 +1747,14 @@ PRInt32 nsZipArchive::TestItem(const nsZipItem* aItem, PRFileDesc* aFd) if (aItem->compression != STORED && aItem->compression != DEFLATED) return ZIP_ERR_UNSUPPORTED; + //-- don't test synthetic items -- I think for a normal zip + //-- you actually *can* get away with testing a synthetic + //-- with no ill results except some unnecessary work, but + //-- for a zip which doesn't start with a zip entry (e.g., + //-- a self-extracting zip) it'll fail + if (aItem->isSynthetic) + return ZIP_OK; + //-- move to the start of file's data if (SeekToItem(aItem, aFd) != ZIP_OK) return ZIP_ERR_CORRUPT; diff --git a/mozilla/modules/libjar/nsZipArchive.h b/mozilla/modules/libjar/nsZipArchive.h index d77b497c5ad..7386f7a8ab2 100644 --- a/mozilla/modules/libjar/nsZipArchive.h +++ b/mozilla/modules/libjar/nsZipArchive.h @@ -116,10 +116,15 @@ class nsZipItem public: char* name; /* '\0' terminated */ - PRUint32 offset; - PRUint32 size; /* size in original file */ - PRUint32 realsize; /* inflated size */ - PRUint32 crc32; + PRUint32 offset; + PRUint32 size; /* size in original file */ + PRUint32 realsize; /* inflated size */ + PRUint32 crc32; + PRPackedBool isDirectory; + PRPackedBool isSynthetic; /* whether item is an actual zip entry or was + generated as part of a real entry's path, + e.g. foo/ in a zip containing only foo/a.txt + and no foo/ entry is synthetic */ nsZipItem* next; @@ -259,9 +264,12 @@ public: * * @param aPattern a string or RegExp pattern to search for * (may be NULL to find all files in archive) - * @return a structure used in FindNext. NULL indicates error + * @param aFind a pointer to a pointer to a structure used + * in FindNext. In the case of an error this + * will be set to NULL. + * @return status code */ - nsZipFind* FindInit(const char * aPattern); + PRInt32 FindInit(const char * aPattern, nsZipFind** aFind); /** * FindNext @@ -298,6 +306,7 @@ private: nsZipArchive& operator=(const nsZipArchive& rhs); // prevent assignments nsZipArchive(const nsZipArchive& rhs); // prevent copies + nsZipItem* CreateZipItem(); PRInt32 BuildFileList(PRFileDesc* aFd); nsZipItem* GetFileItem(const char * zipEntry); PRUint32 HashName(const char* aName); diff --git a/mozilla/modules/libjar/objs.mk b/mozilla/modules/libjar/objs.mk index d87cda66de8..5c32b638fa8 100644 --- a/mozilla/modules/libjar/objs.mk +++ b/mozilla/modules/libjar/objs.mk @@ -43,6 +43,7 @@ MODULES_STANDALONE_LCPPSRCS = \ MODULES_LIBJAR_LCPPSRCS = \ $(MODULES_STANDALONE_LCPPSRCS) \ nsJARInputStream.cpp \ + nsJARDirectoryInputStream.cpp \ nsJAR.cpp \ nsJARFactory.cpp \ nsXPTZipLoader.cpp \ diff --git a/mozilla/netwerk/streamconv/converters/nsIndexedToHTML.cpp b/mozilla/netwerk/streamconv/converters/nsIndexedToHTML.cpp index dcbe17b01c1..6232ec790a1 100644 --- a/mozilla/netwerk/streamconv/converters/nsIndexedToHTML.cpp +++ b/mozilla/netwerk/streamconv/converters/nsIndexedToHTML.cpp @@ -237,6 +237,21 @@ nsIndexedToHTML::OnStartRequest(nsIRequest* request, nsISupports *aContext) { } else if (NS_SUCCEEDED(uri->SchemeIs("gopher", &isScheme)) && isScheme) { mExpectAbsLoc = PR_TRUE; + } else if (NS_SUCCEEDED(uri->SchemeIs("jar", &isScheme)) && isScheme) { + nsCAutoString path; + rv = uri->GetPath(path); + if (NS_FAILED(rv)) return rv; + + // a top-level jar directory URL is of the form jar:foo.zip!/ + // path will be of the form foo.zip!/, and its last two characters + // will be "!/" + //XXX this won't work correctly when the name of the directory being + //XXX displayed ends with "!", but then again, jar: URIs don't deal + //XXX particularly well with such directories anyway + if (!StringEndsWith(path, NS_LITERAL_CSTRING("!/"))) { + rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr); + if (NS_FAILED(rv)) return rv; + } } else { // default behavior for other protocols is to assume the channel's @@ -513,12 +528,16 @@ nsIndexedToHTML::OnIndexAvailable(nsIRequest *aRequest, } else { // escape as relative - escFlags = esc_Forced | esc_OnlyASCII | esc_AlwaysCopy | esc_FileBaseName | esc_Colon; + // esc_Directory is needed for protocols which allow the same name for + // both a directory and a file and distinguish between the two by a + // trailing '/' -- without it, the trailing '/' will be escaped, and + // links from within that directory will be incorrect + escFlags = esc_Forced | esc_OnlyASCII | esc_AlwaysCopy | esc_FileBaseName | esc_Colon | esc_Directory; } NS_EscapeURL(utf8UnEscapeSpec.get(), utf8UnEscapeSpec.Length(), escFlags, escapeBuf); - + AppendUTF8toUTF16(escapeBuf, pushBuffer); - + pushBuffer.AppendLiteral("\"> entries; - rv = hZip->FindEntries("*", getter_AddRefs(entries)); + rv = hZip->FindEntries(nsnull, getter_AddRefs(entries)); if (NS_FAILED(rv)) return rv; @@ -138,7 +138,19 @@ nsresult VerifySigning(nsIZipReader* hZip, nsIPrincipal* aPrincipal) if ( PL_strncasecmp("META-INF/", name.get(), 9) == 0) continue; - // we only count the entries not in the meta-inf directory + // libjar creates fake entries for directories which are + // not in the zip but do exist as part of the path of some + // entry within the zip, e.g. foo/ in a zip containing + // only foo/bar.txt -- skip those, because they shouldn't + // be in the manifest + PRBool isSynthetic; + rv = file->GetIsSynthetic(&isSynthetic); + if (NS_FAILED(rv)) return rv; + if (isSynthetic) + continue; + + // we only count the entries which are not in the meta-inf + // directory and which are explicitly listed in the zip entryCount++; // Each entry must be signed