using System; using System.IO; using System.Text; using ICSharpCode.SharpZipLib.Core; namespace ICSharpCode.SharpZipLib.Zip { /// /// ZipNameTransform transforms names as per the Zip file naming convention. /// /// The use of absolute names is supported although its use is not valid /// according to Zip naming conventions, and should not be used if maximum compatability is desired. public class ZipNameTransform : INameTransform { #region Constructors /// /// Initialize a new instance of /// public ZipNameTransform() { } /// /// Initialize a new instance of /// /// The string to trim from the front of paths if found. public ZipNameTransform(string trimPrefix) { TrimPrefix = trimPrefix; } #endregion /// /// Static constructor. /// static ZipNameTransform() { char[] invalidPathChars; invalidPathChars = Path.GetInvalidPathChars(); int howMany = invalidPathChars.Length + 2; InvalidEntryCharsRelaxed = new char[howMany]; Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length); InvalidEntryCharsRelaxed[howMany - 1] = '*'; InvalidEntryCharsRelaxed[howMany - 2] = '?'; howMany = invalidPathChars.Length + 4; InvalidEntryChars = new char[howMany]; Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length); InvalidEntryChars[howMany - 1] = ':'; InvalidEntryChars[howMany - 2] = '\\'; InvalidEntryChars[howMany - 3] = '*'; InvalidEntryChars[howMany - 4] = '?'; } /// /// 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) { string lowerName = name.ToLower(); if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) { name = name.Substring(trimPrefix_.Length); } name = name.Replace(@"\", "/"); name = WindowsPathUtils.DropPathRoot(name); // Drop any leading slashes. while ((name.Length > 0) && (name[0] == '/')) { name = name.Remove(0, 1); } // Drop any trailing slashes. while ((name.Length > 0) && (name[name.Length - 1] == '/')) { name = name.Remove(name.Length - 1, 1); } // Convert consecutive // characters to / int index = name.IndexOf("//", StringComparison.Ordinal); while (index >= 0) { name = name.Remove(index, 1); index = name.IndexOf("//", StringComparison.Ordinal); } name = MakeValidName(name, '_'); } else { name = string.Empty; } return name; } /// /// Get/set the path prefix to be trimmed from paths if present. /// /// The prefix is trimmed before any conversion from /// a windows path is done. public string TrimPrefix { get { return trimPrefix_; } set { trimPrefix_ = value; if (trimPrefix_ != null) { trimPrefix_ = trimPrefix_.ToLower(); } } } /// /// Force a name to be valid by replacing invalid characters with a fixed value /// /// The name to force valid /// The replacement character to use. /// Returns a valid name static string MakeValidName(string name, char replacement) { int index = name.IndexOfAny(InvalidEntryChars); if (index >= 0) { var builder = new StringBuilder(name); while (index >= 0) { builder[index] = replacement; if (index >= name.Length) { index = -1; } else { index = name.IndexOfAny(InvalidEntryChars, index + 1); } } name = builder.ToString(); } if (name.Length > 0xffff) { throw new PathTooLongException(); } return name; } /// /// Test a name to see if it is a valid name for a zip entry. /// /// The name to test. /// If true checking is relaxed about windows file names and absolute paths. /// Returns true if the name is a valid zip name; false otherwise. /// Zip path names are actually in Unix format, and should only contain relative paths. /// This means that any path stored should not contain a drive or /// device letter, or a leading slash. All slashes should forward slashes '/'. /// An empty name is valid for a file where the input comes from standard input. /// A null name is not considered valid. /// public static bool IsValidName(string name, bool relaxed) { bool result = (name != null); if (result) { if (relaxed) { result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0; } else { result = (name.IndexOfAny(InvalidEntryChars) < 0) && (name.IndexOf('/') != 0); } } return result; } /// /// Test a name to see if it is a valid name for a zip entry. /// /// The name to test. /// Returns true if the name is a valid zip name; false otherwise. /// Zip path names are actually in unix format, /// and should only contain relative paths if a path is present. /// This means that the path stored should not contain a drive or /// device letter, or a leading slash. All slashes should forward slashes '/'. /// An empty name is valid where the input comes from standard input. /// A null name is not considered valid. /// public static bool IsValidName(string name) { bool result = (name != null) && (name.IndexOfAny(InvalidEntryChars) < 0) && (name.IndexOf('/') != 0) ; return result; } #region Instance Fields string trimPrefix_; #endregion #region Class Fields static readonly char[] InvalidEntryChars; static readonly char[] InvalidEntryCharsRelaxed; #endregion } }