ZipNameTransform.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using ICSharpCode.SharpZipLib.Core;
  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
  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. if (!name.EndsWith("/", StringComparison.Ordinal)) {
  60. name += "/";
  61. }
  62. } else {
  63. throw new ZipException("Cannot have an empty directory name");
  64. }
  65. return name;
  66. }
  67. /// <summary>
  68. /// Transform a windows file name according to the Zip file naming conventions.
  69. /// </summary>
  70. /// <param name="name">The file name to transform.</param>
  71. /// <returns>The transformed name.</returns>
  72. public string TransformFile(string name)
  73. {
  74. if (name != null) {
  75. string lowerName = name.ToLower();
  76. if ((trimPrefix_ != null) && (lowerName.IndexOf(trimPrefix_, StringComparison.Ordinal) == 0)) {
  77. name = name.Substring(trimPrefix_.Length);
  78. }
  79. name = name.Replace(@"\", "/");
  80. name = WindowsPathUtils.DropPathRoot(name);
  81. // Drop any leading slashes.
  82. while ((name.Length > 0) && (name[0] == '/')) {
  83. name = name.Remove(0, 1);
  84. }
  85. // Drop any trailing slashes.
  86. while ((name.Length > 0) && (name[name.Length - 1] == '/')) {
  87. name = name.Remove(name.Length - 1, 1);
  88. }
  89. // Convert consecutive // characters to /
  90. int index = name.IndexOf("//", StringComparison.Ordinal);
  91. while (index >= 0) {
  92. name = name.Remove(index, 1);
  93. index = name.IndexOf("//", StringComparison.Ordinal);
  94. }
  95. name = MakeValidName(name, '_');
  96. } else {
  97. name = string.Empty;
  98. }
  99. return name;
  100. }
  101. /// <summary>
  102. /// Get/set the path prefix to be trimmed from paths if present.
  103. /// </summary>
  104. /// <remarks>The prefix is trimmed before any conversion from
  105. /// a windows path is done.</remarks>
  106. public string TrimPrefix {
  107. get { return trimPrefix_; }
  108. set {
  109. trimPrefix_ = value;
  110. if (trimPrefix_ != null) {
  111. trimPrefix_ = trimPrefix_.ToLower();
  112. }
  113. }
  114. }
  115. /// <summary>
  116. /// Force a name to be valid by replacing invalid characters with a fixed value
  117. /// </summary>
  118. /// <param name="name">The name to force valid</param>
  119. /// <param name="replacement">The replacement character to use.</param>
  120. /// <returns>Returns a valid name</returns>
  121. static string MakeValidName(string name, char replacement)
  122. {
  123. int index = name.IndexOfAny(InvalidEntryChars);
  124. if (index >= 0) {
  125. var builder = new StringBuilder(name);
  126. while (index >= 0) {
  127. builder[index] = replacement;
  128. if (index >= name.Length) {
  129. index = -1;
  130. } else {
  131. index = name.IndexOfAny(InvalidEntryChars, index + 1);
  132. }
  133. }
  134. name = builder.ToString();
  135. }
  136. if (name.Length > 0xffff) {
  137. throw new PathTooLongException();
  138. }
  139. return name;
  140. }
  141. /// <summary>
  142. /// Test a name to see if it is a valid name for a zip entry.
  143. /// </summary>
  144. /// <param name="name">The name to test.</param>
  145. /// <param name="relaxed">If true checking is relaxed about windows file names and absolute paths.</param>
  146. /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
  147. /// <remarks>Zip path names are actually in Unix format, and should only contain relative paths.
  148. /// This means that any path stored should not contain a drive or
  149. /// device letter, or a leading slash. All slashes should forward slashes '/'.
  150. /// An empty name is valid for a file where the input comes from standard input.
  151. /// A null name is not considered valid.
  152. /// </remarks>
  153. public static bool IsValidName(string name, bool relaxed)
  154. {
  155. bool result = (name != null);
  156. if (result) {
  157. if (relaxed) {
  158. result = name.IndexOfAny(InvalidEntryCharsRelaxed) < 0;
  159. } else {
  160. result =
  161. (name.IndexOfAny(InvalidEntryChars) < 0) &&
  162. (name.IndexOf('/') != 0);
  163. }
  164. }
  165. return result;
  166. }
  167. /// <summary>
  168. /// Test a name to see if it is a valid name for a zip entry.
  169. /// </summary>
  170. /// <param name="name">The name to test.</param>
  171. /// <returns>Returns true if the name is a valid zip name; false otherwise.</returns>
  172. /// <remarks>Zip path names are actually in unix format,
  173. /// and should only contain relative paths if a path is present.
  174. /// This means that the path stored should not contain a drive or
  175. /// device letter, or a leading slash. All slashes should forward slashes '/'.
  176. /// An empty name is valid where the input comes from standard input.
  177. /// A null name is not considered valid.
  178. /// </remarks>
  179. public static bool IsValidName(string name)
  180. {
  181. bool result =
  182. (name != null) &&
  183. (name.IndexOfAny(InvalidEntryChars) < 0) &&
  184. (name.IndexOf('/') != 0)
  185. ;
  186. return result;
  187. }
  188. #region Instance Fields
  189. string trimPrefix_;
  190. #endregion
  191. #region Class Fields
  192. static readonly char[] InvalidEntryChars;
  193. static readonly char[] InvalidEntryCharsRelaxed;
  194. #endregion
  195. }
  196. }