ZipNameTransform.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. using ICSharpCode.SharpZipLib.Core;
  2. using System;
  3. using System.IO;
  4. using System.Text;
  5. namespace ICSharpCode.SharpZipLib.Zip
  6. {
  7. /// <summary>
  8. /// ZipNameTransform transforms names as per the Zip file naming convention.
  9. /// </summary>
  10. /// <remarks>The use of absolute names is supported although its use is not valid
  11. /// according to Zip naming conventions, and should not be used if maximum compatability is desired.</remarks>
  12. public class ZipNameTransform : INameTransform
  13. {
  14. #region Constructors
  15. /// <summary>
  16. /// Initialize a new instance of <see cref="ZipNameTransform"></see>
  17. /// </summary>
  18. public ZipNameTransform()
  19. {
  20. }
  21. /// <summary>
  22. /// Initialize a new instance of <see cref="ZipNameTransform"></see>
  23. /// </summary>
  24. /// <param name="trimPrefix">The string to trim from the front of paths if found.</param>
  25. public ZipNameTransform(string trimPrefix)
  26. {
  27. TrimPrefix = trimPrefix;
  28. }
  29. #endregion Constructors
  30. /// <summary>
  31. /// Static constructor.
  32. /// </summary>
  33. static ZipNameTransform()
  34. {
  35. char[] invalidPathChars;
  36. invalidPathChars = Path.GetInvalidPathChars();
  37. int howMany = invalidPathChars.Length + 2;
  38. InvalidEntryCharsRelaxed = new char[howMany];
  39. Array.Copy(invalidPathChars, 0, InvalidEntryCharsRelaxed, 0, invalidPathChars.Length);
  40. InvalidEntryCharsRelaxed[howMany - 1] = '*';
  41. InvalidEntryCharsRelaxed[howMany - 2] = '?';
  42. howMany = invalidPathChars.Length + 4;
  43. InvalidEntryChars = new char[howMany];
  44. Array.Copy(invalidPathChars, 0, InvalidEntryChars, 0, invalidPathChars.Length);
  45. InvalidEntryChars[howMany - 1] = ':';
  46. InvalidEntryChars[howMany - 2] = '\\';
  47. InvalidEntryChars[howMany - 3] = '*';
  48. InvalidEntryChars[howMany - 4] = '?';
  49. }
  50. /// <summary>
  51. /// Transform a windows directory name according to the Zip file naming conventions.
  52. /// </summary>
  53. /// <param name="name">The directory name to transform.</param>
  54. /// <returns>The transformed name.</returns>
  55. public string TransformDirectory(string name)
  56. {
  57. name = TransformFile(name);
  58. if (name.Length > 0)
  59. {
  60. if (!name.EndsWith("/", StringComparison.Ordinal))
  61. {
  62. name += "/";
  63. }
  64. }
  65. else
  66. {
  67. throw new ZipException("Cannot have an empty directory name");
  68. }
  69. return name;
  70. }
  71. /// <summary>
  72. /// Transform a windows file name according to the Zip file naming conventions.
  73. /// </summary>
  74. /// <param name="name">The file name to transform.</param>
  75. /// <returns>The transformed name.</returns>
  76. public string TransformFile(string name)
  77. {
  78. if (name != null)
  79. {
  80. string lowerName = name.ToLower();
  81. if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0))
  82. {
  83. name = name.Substring(trimPrefix_.Length);
  84. }
  85. name = name.Replace(@"\", "/");
  86. name = WindowsPathUtils.DropPathRoot(name);
  87. // Drop any leading slashes.
  88. while ((name.Length > 0) && (name[0] == '/'))
  89. {
  90. name = name.Remove(0, 1);
  91. }
  92. // Drop any trailing slashes.
  93. while ((name.Length > 0) && (name[name.Length - 1] == '/'))
  94. {
  95. name = name.Remove(name.Length - 1, 1);
  96. }
  97. // Convert consecutive // characters to /
  98. int index = name.IndexOf("//", StringComparison.Ordinal);
  99. while (index >= 0)
  100. {
  101. name = name.Remove(index, 1);
  102. index = name.IndexOf("//", StringComparison.Ordinal);
  103. }
  104. name = MakeValidName(name, '_');
  105. }
  106. else
  107. {
  108. name = string.Empty;
  109. }
  110. return name;
  111. }
  112. /// <summary>
  113. /// Get/set the path prefix to be trimmed from paths if present.
  114. /// </summary>
  115. /// <remarks>The prefix is trimmed before any conversion from
  116. /// a windows path is done.</remarks>
  117. public string TrimPrefix
  118. {
  119. get { return trimPrefix_; }
  120. set
  121. {
  122. trimPrefix_ = value;
  123. if (trimPrefix_ != null)
  124. {
  125. trimPrefix_ = trimPrefix_.ToLower();
  126. }
  127. }
  128. }
  129. /// <summary>
  130. /// Force a name to be valid by replacing invalid characters with a fixed value
  131. /// </summary>
  132. /// <param name="name">The name to force valid</param>
  133. /// <param name="replacement">The replacement character to use.</param>
  134. /// <returns>Returns a valid name</returns>
  135. private static string MakeValidName(string name, char replacement)
  136. {
  137. int index = name.IndexOfAny(InvalidEntryChars);
  138. if (index >= 0)
  139. {
  140. var builder = new StringBuilder(name);
  141. while (index >= 0)
  142. {
  143. builder[index] = replacement;
  144. if (index >= name.Length)
  145. {
  146. index = -1;
  147. }
  148. else
  149. {
  150. index = name.IndexOfAny(InvalidEntryChars, index + 1);
  151. }
  152. }
  153. name = builder.ToString();
  154. }
  155. if (name.Length > 0xffff)
  156. {
  157. throw new PathTooLongException();
  158. }
  159. return name;
  160. }
  161. /// <summary>
  162. /// Test a name to see if it is a valid name for a zip entry.
  163. /// </summary>
  164. /// <param name="name">The name to test.</param>
  165. /// <param name="relaxed">If true checking is relaxed about windows file names and absolute paths.</param>
  166. /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
  167. /// <remarks>Zip path names are actually in Unix format, and should only contain relative paths.
  168. /// This means that any path stored should not contain a drive or
  169. /// device letter, or a leading slash. All slashes should forward slashes '/'.
  170. /// An empty name is valid for a file where the input comes from standard input.
  171. /// A null name is not considered valid.
  172. /// </remarks>
  173. public static bool IsValidName(string name, bool relaxed)
  174. {
  175. bool result = (name != null);
  176. if (result)
  177. {
  178. if (relaxed)
  179. {
  180. result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0;
  181. }
  182. else
  183. {
  184. result =
  185. (name.IndexOfAny(InvalidEntryChars) < 0) &&
  186. (name.IndexOf('/') != 0);
  187. }
  188. }
  189. return result;
  190. }
  191. /// <summary>
  192. /// Test a name to see if it is a valid name for a zip entry.
  193. /// </summary>
  194. /// <param name="name">The name to test.</param>
  195. /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
  196. /// <remarks>Zip path names are actually in unix format,
  197. /// and should only contain relative paths if a path is present.
  198. /// This means that the path stored should not contain a drive or
  199. /// device letter, or a leading slash. All slashes should forward slashes '/'.
  200. /// An empty name is valid where the input comes from standard input.
  201. /// A null name is not considered valid.
  202. /// </remarks>
  203. public static bool IsValidName(string name)
  204. {
  205. bool result =
  206. (name != null) &&
  207. (name.IndexOfAny(InvalidEntryChars) < 0) &&
  208. (name.IndexOf('/') != 0)
  209. ;
  210. return result;
  211. }
  212. #region Instance Fields
  213. private string trimPrefix_;
  214. #endregion Instance Fields
  215. #region Class Fields
  216. private static readonly char[] InvalidEntryChars;
  217. private static readonly char[] InvalidEntryCharsRelaxed;
  218. #endregion Class Fields
  219. }
  220. }