Add support for the creation of AES-encrypted archives.
There is a related feature request for the extraction of such archives: ZipLibrary: Add support for extraction of AES-encrypted archives.
There should be API allowing to check if an archive is protected.
Reading a password protected archive by passing a wrong password doesn't notify the user that the password is wrong. Accessing one of the entries in the archive throws a generic "Invalid data" exception. Steps to reproduce: 1. Create a zip file with password. 2. Open the same archive with the API passing wrong password and then try to one of the entries. Observed: InvalidDataException is thrown when trying to read one of the entries in the archive. Expected: The exception should be more meaningful and if possible occur when reading the archive. There should be API allowing to check if the password is correct.
Add functionality for the ZipPackage to span across multiple files.
Provide a way for users to check whether a stream is a valid zip archive. Include a CRC check as well.
Currently, an exception is thrown when the file being unzipped already exists in the destination folder. Expose overload of the ExtractToDirectory() method allowing users to overwrite files.
Alternatively, the ZipArchiveEntry.ExtractToFile() method can be used:
using (ZipArchive source = ZipFile.Open(sourceFileName, ZipArchiveMode.Read, null))
{
foreach (ZipArchiveEntry entry in source.Entries)
{
string fullPath = Path.GetFullPath(Path.Combine(destinationDirectory, entry.FullName));
if (Path.GetFileName(fullPath).Length != 0)
{
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
// The boolean parameter determines whether an existing file that has the same name as the destination file should be overwritten
entry.ExtractToFile(fullPath, true);
}
}
}
According to the specification, the Enhanced Deflating algorithm is similar to Deflate but uses a sliding dictionary of up to 64K.
7zip and the Windows arvhiver report Data Error on all the files when the users try to extract an archive created with ZipLibrary and the default LzmaSettings.
The stream that is internally created by the CreateFromDirectory method remains opened and the user can't close it:
This is error message observed when using the below code snippet:
The process cannot access the file 'C:\Users\dyordano\OneDrive - Progress Software Corporation\MyProjects\1640071ZipFileCreateFromDirectory\myzip.zip' because it is being used by another process.
static void Main(string[] args)
{
string archiveFileName = @"..\..\myzip.zip";
string sourceDirectoryName = @"..\..\Folder";
File.Delete(archiveFileName);
Telerik.Windows.Zip.Extensions.ZipFile.CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);
using (Stream stream = File.Open(archiveFileName, FileMode.Open))
{
}
}
Workaround:
static void Main(string[] args)
{
string archiveFileName = @"..\..\myzip.zip";
string sourceDirectoryName = @"..\..\Folder";
File.Delete(archiveFileName);
// Telerik.Windows.Zip.Extensions.ZipFile.CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);
CreateFromDirectory(sourceDirectoryName, archiveFileName, Telerik.Windows.Zip.CompressionLevel.Optimal, true);
using (Stream stream = File.Open(archiveFileName, FileMode.Open))
{
}
}
private static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, Telerik.Windows.Zip.CompressionLevel compressionLevel, bool includeBaseDirectory)
{
Encoding entryNameEncoding = null;
DeflateSettings compressionSettings = new DeflateSettings()
{
CompressionLevel = compressionLevel,
HeaderType = CompressedStreamHeader.None
};
char[] directorySeparatorChar = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName);
FileStream fileStream = File.Open(destinationArchiveFileName, FileMode.CreateNew, FileAccess.Write, FileShare.None); ;
using (ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create,false, entryNameEncoding))
{
bool empty = true;
DirectoryInfo directoryInfo = new DirectoryInfo(sourceDirectoryName);
string fullName = directoryInfo.FullName;
if (includeBaseDirectory && directoryInfo.Parent != null)
{
fullName = directoryInfo.Parent.FullName;
}
foreach (FileSystemInfo fileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
{
empty = false;
int length = fileSystemInfo.FullName.Length - fullName.Length;
string entryName = fileSystemInfo.FullName.Substring(fullName.Length, length);
entryName = entryName.TrimStart(directorySeparatorChar);
DirectoryInfo subfolder = fileSystemInfo as DirectoryInfo;
if (subfolder != null)
{
if (IsDirectoryEmpty(subfolder))
{
zipArchive.CreateEntry(string.Concat(entryName, Path.DirectorySeparatorChar));
}
}
else
{
ZipFile.CreateEntryFromFile(zipArchive, fileSystemInfo.FullName, entryName, compressionSettings);
}
}
if (includeBaseDirectory && empty)
{
zipArchive.CreateEntry(string.Concat(directoryInfo.Name, Path.DirectorySeparatorChar));
}
}
}
private static bool IsDirectoryEmpty(DirectoryInfo directoryInfo)
{
bool empty = true;
using (IEnumerator<FileSystemInfo> enumerator = directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories).GetEnumerator())
{
if (enumerator.MoveNext())
{
empty = false;
}
}
return empty;
}
Note: We should ensure that all of the obsolete API that used to use the "leave open" flag don't keep the stream locked.
The attached gif file illustrates the inability to open the newly added file with the password:
Sub Main()
Dim sZipFilePath As String = "..\..\test.zip"
File.Delete(sZipFilePath)
CreateArchive(sZipFilePath)
Dim decryptionSettings As DecryptionSettings = EncryptionSettings.CreateDecryptionSettings()
AddHandler decryptionSettings.PasswordRequired, AddressOf DecryptionSettings_PasswordRequired
Dim compressionSettings As CompressionSettings = Nothing
Dim encoding As Encoding = Nothing
Using oFS As FileStream = File.Open(sZipFilePath, FileMode.OpenOrCreate)
Using oArchive As ZipArchive = ZipArchive.Update(oFS, encoding, compressionSettings, decryptionSettings)
Using entry As ZipArchiveEntry = oArchive.CreateEntry("newText.txt")
Dim writer As StreamWriter = New StreamWriter(entry.Open())
writer.WriteLine("Hello world!")
writer.Flush()
End Using
End Using
End Using
End Sub
Private Sub CreateArchive(sZipFilePath As String)
Using stream As Stream = File.Open(sZipFilePath, FileMode.Create)
Dim _encryptionSettings As PasswordEncryptionSettings = EncryptionSettings.CreatePkzipPasswordEncryptionSettings()
_encryptionSettings.Password = "telerik"
Dim compressionSettings As CompressionSettings = Nothing
Dim encoding As Encoding = Nothing
Using archive As ZipArchive = ZipArchive.Create(stream, encoding, compressionSettings, _encryptionSettings)
Using entry As ZipArchiveEntry = archive.CreateEntry("text.txt")
Dim writer As StreamWriter = New StreamWriter(entry.Open())
writer.WriteLine("Hello world!")
writer.Flush()
End Using
End Using
End Using
End Sub
Private Sub DecryptionSettings_PasswordRequired(ByVal sender As Object, ByVal e As PasswordRequiredEventArgs)
e.Password = "telerik"
End Sub
Workaround: Until this bug is fixed the obsolete API could be used instead:
ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, true, encoding, compressionSettings, encryptionSettings)
ZipArchive oArchive = new ZipArchive(stream, ZipArchiveMode.Update, true, encoding, compressionSettings, decryptionSettings)
When updating ZIP archive, there is no need for loading it fully in the memory. Each entry is represented as a separate stream; when the ZipArchive is disposed, each entry is written in the OutputStream which is MemoryStream. When an entry is written in the OutputStream it is read from the source file if it is not changed. The MemoryStream is used because the source file is needed to get the entries contents. A temporary file can be used instead and there will be no need of the memory stream, so that large files will be updated without using much memory (which causes OutOfMemoryException).
Provide a simple method to detect that a password for a ZIP file is not valid.
The current implemention usually throws an exception (which does not definitively identify the password as wrong), but not always. Sometimes it just returns corrupt data.
It would be nice to either have a typed exception consistently thrown, or a method to specifically check for an invalid password.
Thanks.