Invalid content after updating empty password-protected archive.
Workaround: Instead of creating an empty archive, add a dummy file to it that can be removed later.
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)
Steps to reproduce:
1.Create a zip archive for a txt file with password protection
2. Try updating the protected file and insert a new text line for example.
As a result, an error occurs: System.ObjectDisposedException: 'Cannot access a closed Stream.'
Imports System.IO
Imports System.Text
Imports Telerik.Windows.Zip
Module Module1
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)
For Each entry As ZipArchiveEntry In oArchive.Entries
Using entryStream As Stream = entry.Open()
Dim reader As New StreamReader(entryStream)
Dim content As String = reader.ReadToEnd()
entryStream.Seek(0, SeekOrigin.End)
Dim writer As New StreamWriter(entryStream)
writer.WriteLine("Updated line.")
writer.Flush()
End Using
Next
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 = "MyPassword"
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 = "MyPassword"
End Sub
End Module
Steps to reproduce:
1.Create a zip archive for a txt file with password protection
2. Try updating the protected file and insert a new text line for example
The settings used for decrypting the file don't seem to be used encrypting again after the update operation. Here is a sample code snippet for reproducing the error message:
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)
For Each entry As ZipArchiveEntry In oArchive.Entries
Using entryStream As Stream = entry.Open()
Dim reader As New StreamReader(entryStream)
Dim content As String = reader.ReadToEnd()
entryStream.Seek(0, SeekOrigin.End)
Dim writer As New StreamWriter(entryStream)
writer.WriteLine("Updated line.")
writer.Flush()
End Using
Next
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 = "MyPassword"
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 = "MyPassword"
End Sub
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.
Add support for extraction of AES-encrypted archives.
There is a related feature request for the creation of such archives: ZipLibrary: Add support for creation of AES-encrypted archives.
I'm trying to implement the IXmlSerialization for class SerializableDictionary. When starts the serialization of class to xml, I'm catching the System.NotSupportedException: 'Can't flush final block twice'. The problem occurs when second serializer trying to call Serialize method on same XmlWriter.
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, false, null))
{
foreach (var item in array)
{
using (var entry = archive.CreateEntry($"{item.Name}.reg-ext"))
{
XmlInOut<RegulationItem>.SaveToStream(entry.Open(), item);
}
}
}
Could it be bug with the ArchiveEntry Stream?
I've placed a solution into a github repo. Also I tried to replace the stream of entry with the MemoryStream, and it helps. But I would to do it directly, if it's possible.
MemoryStream ms1 = new MemoryStream();
using (ZipArchive archive = new ZipArchive(ms1, ZipArchiveMode.Create, false, null))
{
using (PdfFileSource fileSource = new PdfFileSource(File.OpenRead(documentToSplit)))
{
string splitDocumentName = "1st page.pdf";
using (ZipArchiveEntry entry = archive.CreateEntry(splitDocumentName))
{
using (PdfStreamWriter writer = new PdfStreamWriter(entry.Open()))
{
PdfPageSource page = fileSource.Pages[0];
writer.WritePage(page);
}
}
}
}
File.WriteAllBytes("exported.zip", ms1.ToArray());
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);
}
}
}
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.