diff --git a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs index 71a739600..0391b8e0e 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs @@ -368,6 +368,7 @@ public void CreateZip(Stream outputStream, string sourceDirectory, bool recurse, using (outputStream_ = new ZipOutputStream(outputStream)) { outputStream_.SetLevel((int)CompressionLevel); + outputStream_.NameTransform = null; // all required transforms handled by us if (password_ != null) { diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs index 1b5e01a68..9569018bd 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipNameTransform.cs @@ -247,4 +247,76 @@ public static bool IsValidName(string name) #endregion Class Fields } + + /// + /// An implementation of INameTransform that transforms entry paths as per the Zip file naming convention. + /// Strips path roots and puts directory separators in the correct format ('/') + /// + public class PathTransformer : INameTransform + { + /// + /// Initialize a new instance of + /// + public PathTransformer() + { + } + + /// + /// Transform a windows directory name according to the Zip file naming conventions. + /// + /// The directory name to transform. + /// The transformed name. + public string TransformDirectory(string name) + { + name = TransformFile(name); + + if (name.Length > 0) + { + if (!name.EndsWith("/", StringComparison.Ordinal)) + { + name += "/"; + } + } + else + { + throw new ZipException("Cannot have an empty directory name"); + } + + return name; + } + + /// + /// Transform a windows file name according to the Zip file naming conventions. + /// + /// The file name to transform. + /// The transformed name. + public string TransformFile(string name) + { + if (name != null) + { + // Put separators in the expected format. + name = name.Replace(@"\", "/"); + + // Remove the path root. + name = WindowsPathUtils.DropPathRoot(name); + + // Drop any leading and trailing slashes. + name = name.Trim('/'); + + // Convert consecutive // characters to / + int index = name.IndexOf("//", StringComparison.Ordinal); + while (index >= 0) + { + name = name.Remove(index, 1); + index = name.IndexOf("//", StringComparison.Ordinal); + } + } + else + { + name = string.Empty; + } + + return name; + } + } } diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs index bfd308daa..004985323 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs @@ -1,4 +1,5 @@ using ICSharpCode.SharpZipLib.Checksum; +using ICSharpCode.SharpZipLib.Core; using ICSharpCode.SharpZipLib.Zip.Compression; using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using System; @@ -146,6 +147,12 @@ public UseZip64 UseZip64 set { useZip64_ = value; } } + /// + /// Used for transforming the names of entries added by . + /// Defaults to , set to null to disable transforms and use names as supplied. + /// + public INameTransform NameTransform { get; set; } = new PathTransformer(); + /// /// Write an unsigned short in little endian byte order. /// @@ -182,6 +189,34 @@ private void WriteLeLong(long value) } } + // Apply any configured transforms/cleaning to the name of the supplied entry. + private string TransformEntryName(ZipEntry entry) + { + string transformedName = entry.Name; + + if (this.NameTransform != null) + { + if (entry.IsDirectory) + { + transformedName = this.NameTransform.TransformDirectory(entry.Name); + } + else + { + transformedName = this.NameTransform.TransformFile(entry.Name); + } + } + + return transformedName; + } + + // Convert an entry name to a byte array, and apply any transforms + private byte[] TransformEntryNameToArray(ZipEntry entry) + { + string transformedName = TransformEntryName(entry); + byte[] name = ZipStrings.ConvertToArray(entry.Flags, transformedName); + return name; + } + /// /// Starts a new Zip entry. It automatically closes the previous /// entry if present. @@ -367,7 +402,7 @@ public void PutNextEntry(ZipEntry entry) } } - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + byte[] name = TransformEntryNameToArray(entry); if (name.Length > 0xFFFF) { @@ -787,7 +822,7 @@ public override void Finish() WriteLeInt((int)entry.Size); } - byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); + byte[] name = TransformEntryNameToArray(entry); if (name.Length > 0xffff) {