using System; using System.IO; using System.Text; using ICSharpCode.SharpZipLib.Core; namespace ICSharpCode.SharpZipLib.Zip { /// /// WindowsNameTransform transforms names to windows compatible ones. /// public class WindowsNameTransform : INameTransform { /// /// The maximum windows path name permitted. /// /// This may not valid for all windows systems - CE?, etc but I cant find the equivalent in the CLR. const int MaxPath = 260; string _baseDirectory; bool _trimIncomingPaths; char _replacementChar = '_'; /// /// In this case we need Windows' invalid path characters. /// Path.GetInvalidPathChars() only returns a subset invalid on all platforms. /// static readonly char[] InvalidEntryChars = new char[] { '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f', // extra characters for masks, etc. '*', '?', ':' }; /// /// Initialises a new instance of /// /// public WindowsNameTransform(string baseDirectory) { if (baseDirectory == null) { throw new ArgumentNullException("nameof(baseDirectory)", "Directory name is invalid"); } BaseDirectory = baseDirectory; } /// /// Initialise a default instance of /// public WindowsNameTransform() { // Do nothing. } /// /// Gets or sets a value containing the target directory to prefix values with. /// public string BaseDirectory { get { return _baseDirectory; } set { if (value == null) { throw new ArgumentNullException("nameof(value)"); } _baseDirectory = Path.GetFullPath(value); } } /// /// Gets or sets a value indicating wether paths on incoming values should be removed. /// public bool TrimIncomingPaths { get { return _trimIncomingPaths; } set { _trimIncomingPaths = value; } } /// /// Transform a Zip directory name to a windows directory name. /// /// The directory name to transform. /// The transformed name. public string TransformDirectory(string name) { name = TransformFile(name); if (name.Length > 0) { while (name.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { name = name.Remove(name.Length - 1, 1); } } else { throw new ZipException("Cannot have an empty directory name"); } return name; } /// /// Transform a Zip format file name to a windows style one. /// /// The file name to transform. /// The transformed name. public string TransformFile(string name) { if (name != null) { name = MakeValidName(name, _replacementChar); if (_trimIncomingPaths) { name = Path.GetFileName(name); } // This may exceed windows length restrictions. // Combine will throw a PathTooLongException in that case. if (_baseDirectory != null) { name = Path.Combine(_baseDirectory, name); } } else { name = string.Empty; } return name; } /// /// Test a name to see if it is a valid name for a windows filename as extracted from a Zip archive. /// /// The name to test. /// Returns true if the name is a valid zip name; false otherwise. /// The filename isnt a true windows path in some fundamental ways like no absolute paths, no rooted paths etc. public static bool IsValidName(string name) { bool result = (name != null) && (name.Length <= MaxPath) && (string.Compare(name, MakeValidName(name, '_'), StringComparison.Ordinal) == 0) ; return result; } /// /// Force a name to be valid by replacing invalid characters with a fixed value /// /// The name to make valid /// The replacement character to use for any invalid characters. /// Returns a valid name public static string MakeValidName(string name, char replacement) { if (name == null) { throw new ArgumentNullException("nameof(name)"); } name = WindowsPathUtils.DropPathRoot(name.Replace("/", Path.DirectorySeparatorChar.ToString())); // Drop any leading slashes. while ((name.Length > 0) && (name[0] == Path.DirectorySeparatorChar)) { name = name.Remove(0, 1); } // Drop any trailing slashes. while ((name.Length > 0) && (name[name.Length - 1] == Path.DirectorySeparatorChar)) { name = name.Remove(name.Length - 1, 1); } // Convert consecutive \\ characters to \ int index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); while (index >= 0) { name = name.Remove(index, 1); index = name.IndexOf(string.Format("{0}{0}", Path.DirectorySeparatorChar), StringComparison.Ordinal); } // Convert any invalid characters using the replacement one. 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(); } // Check for names greater than MaxPath characters. // TODO: Were is CLR version of MaxPath defined? Can't find it in Environment. if (name.Length > MaxPath) { throw new PathTooLongException(); } return name; } /// /// Gets or set the character to replace invalid characters during transformations. /// public char Replacement { get { return _replacementChar; } set { for (int i = 0; i < InvalidEntryChars.Length; ++i) { if (InvalidEntryChars[i] == value) { throw new ArgumentException("invalid path character"); } } if ((value == Path.DirectorySeparatorChar) || (value == Path.AltDirectorySeparatorChar)) { throw new ArgumentException("invalid replacement character"); } _replacementChar = value; } } } }