| 1: | // TarInputStream.cs | |
| 2: | // Copyright (C) 2001 Mike Krueger | |
| 3: | // | |
| 4: | // This program is free software; you can redistribute it and/or | |
| 5: | // modify it under the terms of the GNU General Public License | |
| 6: | // as published by the Free Software Foundation; either version 2 | |
| 7: | // of the License, or (at your option) any later version. | |
| 8: | // | |
| 9: | // This program is distributed in the hope that it will be useful, | |
| 10: | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11: | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12: | // GNU General Public License for more details. | |
| 13: | // | |
| 14: | // You should have received a copy of the GNU General Public License | |
| 15: | // along with this program; if not, write to the Free Software | |
| 16: | // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| 17: | // | |
| 18: | // Linking this library statically or dynamically with other modules is | |
| 19: | // making a combined work based on this library. Thus, the terms and | |
| 20: | // conditions of the GNU General Public License cover the whole | |
| 21: | // combination. | |
| 22: | // | |
| 23: | // As a special exception, the copyright holders of this library give you | |
| 24: | // permission to link this library with independent modules to produce an | |
| 25: | // executable, regardless of the license terms of these independent | |
| 26: | // modules, and to copy and distribute the resulting executable under | |
| 27: | // terms of your choice, provided that you also meet, for each linked | |
| 28: | // independent module, the terms and conditions of the license of that | |
| 29: | // module. An independent module is a module which is not derived from | |
| 30: | // or based on this library. If you modify this library, you may extend | |
| 31: | // this exception to your version of the library, but you are not | |
| 32: | // obligated to do so. If you do not wish to do so, delete this | |
| 33: | // exception statement from your version. | |
| 34: | ||
| 35: | using System; | |
| 36: | using System.IO; | |
| 37: | using System.Text; | |
| 38: | ||
| 39: | namespace ICSharpCode.SharpZipLib.Tar { | |
| 40: | ||
| 41: | public delegate void ProgressMessageHandler(TarArchive archive, string message); | |
| 42: | ||
| 43: | /// <summary> | |
| 44: | /// The TarArchive class implements the concept of a | |
| 45: | /// tar archive. A tar archive is a series of entries, each of | |
| 46: | /// which represents a file system object. Each entry in | |
| 47: | /// the archive consists of a header record. Directory entries | |
| 48: | /// consist only of the header record, and are followed by entries | |
| 49: | /// for the directory's contents. File entries consist of a | |
| 50: | /// header record followed by the number of records needed to | |
| 51: | /// contain the file's contents. All entries are written on | |
| 52: | /// record boundaries. Records are 512 bytes long. | |
| 53: | /// | |
| 54: | /// TarArchives are instantiated in either read or write mode, | |
| 55: | /// based upon whether they are instantiated with an InputStream | |
| 56: | /// or an OutputStream. Once instantiated TarArchives read/write | |
| 57: | /// mode can not be changed. | |
| 58: | /// | |
| 59: | /// There is currently no support for random access to tar archives. | |
| 60: | /// However, it seems that subclassing TarArchive, and using the | |
| 61: | /// TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum() | |
| 62: | /// methods, this would be rather trvial. | |
| 63: | /// </summary> | |
| 64: | public class TarArchive | |
| 65: | { | |
| 66: | protected bool verbose; | |
| 67: | protected bool debug; | |
| 68: | protected bool keepOldFiles; | |
| 69: | protected bool asciiTranslate; | |
| 70: | ||
| 71: | protected int userId; | |
| 72: | protected string userName; | |
| 73: | protected int groupId; | |
| 74: | protected string groupName; | |
| 75: | ||
| 76: | protected string rootPath; | |
| 77: | protected string pathPrefix; | |
| 78: | ||
| 79: | protected int recordSize; | |
| 80: | protected byte[] recordBuf; | |
| 81: | ||
| 82: | protected TarInputStream tarIn; | |
| 83: | protected TarOutputStream tarOut; | |
| 84: | ||
| 85: | public event ProgressMessageHandler ProgressMessageEvent; | |
| 86: | ||
| 87: | protected virtual void OnProgressMessageEvent(string message) | |
| 88: | { | |
| 89: | if (ProgressMessageEvent != null) { | |
| 90: | ProgressMessageEvent(this, message); | |
| 91: | } | |
| 92: | } | |
| 93: | ||
| 94: | protected TarArchive() | |
| 95: | { | |
| 96: | } | |
| 97: | ||
| 98: | /// <summary> | |
| 99: | /// The InputStream based constructors create a TarArchive for the | |
| 100: | /// purposes of e'x'tracting or lis't'ing a tar archive. Thus, use | |
| 101: | /// these constructors when you wish to extract files from or list | |
| 102: | /// the contents of an existing tar archive. | |
| 103: | /// </summary> | |
| 104: | public static TarArchive CreateInputTarArchive(Stream inputStream) | |
| 105: | { | |
| 106: | return CreateInputTarArchive(inputStream, TarBuffer.DEFAULT_BLKSIZE); | |
| 107: | } | |
| 108: | ||
| 109: | public static TarArchive CreateInputTarArchive(Stream inputStream, int blockSize) | |
| 110: | { | |
| 111: | return CreateInputTarArchive(inputStream, blockSize, TarBuffer.DEFAULT_RCDSIZE); | |
| 112: | } | |
| 113: | ||
| 114: | public static TarArchive CreateInputTarArchive(Stream inputStream, int blockSize, int recordSize) | |
| 115: | { | |
| 116: | TarArchive archive = new TarArchive(); | |
| 117: | archive.tarIn = new TarInputStream(inputStream, blockSize, recordSize); | |
| 118: | archive.Initialize(recordSize); | |
| 119: | return archive; | |
| 120: | } | |
| 121: | ||
| 122: | /// <summary> | |
| 123: | /// The OutputStream based constructors create a TarArchive for the | |
| 124: | /// purposes of 'c'reating a tar archive. Thus, use these constructors | |
| 125: | /// when you wish to create a new tar archive and write files into it. | |
| 126: | /// </summary> | |
| 127: | public static TarArchive CreateOutputTarArchive(Stream outputStream) | |
| 128: | { | |
| 129: | return CreateOutputTarArchive(outputStream, TarBuffer.DEFAULT_BLKSIZE); | |
| 130: | } | |
| 131: | ||
| 132: | public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockSize) | |
| 133: | { | |
| 134: | return CreateOutputTarArchive(outputStream, blockSize, TarBuffer.DEFAULT_RCDSIZE); | |
| 135: | } | |
| 136: | ||
| 137: | public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockSize, int recordSize) | |
| 138: | { | |
| 139: | TarArchive archive = new TarArchive(); | |
| 140: | archive.tarOut = new TarOutputStream(outputStream, blockSize, recordSize); | |
| 141: | archive.Initialize(recordSize); | |
| 142: | return archive; | |
| 143: | } | |
| 144: | ||
| 145: | ||
| 146: | /// <summary> | |
| 147: | /// Common constructor initialization code. | |
| 148: | /// </summary> | |
| 149: | void Initialize(int recordSize) | |
| 150: | { | |
| 151: | this.rootPath = null; | |
| 152: | this.pathPrefix = null; | |
| 153: | ||
| 154: | // this.tempPath = System.getProperty( "user.dir" ); | |
| 155: | ||
| 156: | this.userId = 0; | |
| 157: | this.userName = String.Empty; | |
| 158: | this.groupId = 0; | |
| 159: | this.groupName = String.Empty; | |
| 160: | ||
| 161: | this.debug = false; | |
| 162: | this.verbose = false; | |
| 163: | this.keepOldFiles = false; | |
| 164: | ||
| 165: | this.recordBuf = new byte[RecordSize]; | |
| 166: | } | |
| 167: | ||
| 168: | /** | |
| 169: | * Set the debugging flag. | |
| 170: | * | |
| 171: | * @param debugF The new debug setting. | |
| 172: | */ | |
| 173: | public void SetDebug(bool debugF) | |
| 174: | { | |
| 175: | this.debug = debugF; | |
| 176: | if (this.tarIn != null) { | |
| 177: | this.tarIn.SetDebug(debugF); | |
| 178: | } | |
| 179: | if (this.tarOut != null) { | |
| 180: | this.tarOut.SetDebug(debugF); | |
| 181: | } | |
| 182: | } | |
| 183: | ||
| 184: | /// <summary> | |
| 185: | /// Get/Set the verbosity setting. | |
| 186: | /// </summary> | |
| 187: | public bool IsVerbose { | |
| 188: | get { | |
| 189: | return verbose; | |
| 190: | } | |
| 191: | set { | |
| 192: | verbose = value; | |
| 193: | } | |
| 194: | } | |
| 195: | ||
| 196: | /// <summary> | |
| 197: | /// Set the flag that determines whether existing files are | |
| 198: | /// kept, or overwritten during extraction. | |
| 199: | /// </summary> | |
| 200: | /// <param name="keepOldFiles"> | |
| 201: | /// If true, do not overwrite existing files. | |
| 202: | /// </param> | |
| 203: | public void SetKeepOldFiles(bool keepOldFiles) | |
| 204: | { | |
| 205: | this.keepOldFiles = keepOldFiles; | |
| 206: | } | |
| 207: | ||
| 208: | /// <summary> | |
| 209: | /// Set the ascii file translation flag. If ascii file translatio | |
| 210: | /// is true, then the MIME file type will be consulted to determine | |
| 211: | /// if the file is of type 'text/*'. If the MIME type is not found, | |
| 212: | /// then the TransFileTyper is consulted if it is not null. If | |
| 213: | /// either of these two checks indicates the file is an ascii text | |
| 214: | /// file, it will be translated. The translation converts the local | |
| 215: | /// operating system's concept of line ends into the UNIX line end, | |
| 216: | /// '\n', which is the defacto standard for a TAR archive. This makes | |
| 217: | /// text files compatible with UNIX, and since most tar implementations | |
| 218: | /// text files compatible with UNIX, and since most tar implementations | |
| 219: | /// </summary> | |
| 220: | /// <param name= "asciiTranslate"> | |
| 221: | /// If true, translate ascii text files. | |
| 222: | /// </param> | |
| 223: | public void SetAsciiTranslation(bool asciiTranslate) | |
| 224: | { | |
| 225: | this.asciiTranslate = asciiTranslate; | |
| 226: | } | |
| 227: | ||
| 228: | /* | |
| 229: | /// <summary> | |
| 230: | /// Set the object that will determine if a file is of type | |
| 231: | /// ascii text for translation purposes. | |
| 232: | /// </summary> | |
| 233: | /// <param name="transTyper"> | |
| 234: | /// The new TransFileTyper object. | |
| 235: | /// </param> | |
| 236: | public void SetTransFileTyper(TarTransFileTyper transTyper) | |
| 237: | { | |
| 238: | this.transTyper = transTyper; | |
| 239: | }*/ | |
| 240: | ||
| 241: | /// <summary> | |
| 242: | /// Set user and group information that will be used to fill in the | |
| 243: | /// tar archive's entry headers. Since Java currently provides no means | |
| 244: | /// of determining a user name, user id, group name, or group id for | |
| 245: | /// a given File, TarArchive allows the programmer to specify values | |
| 246: | /// to be used in their place. | |
| 247: | /// </summary> | |
| 248: | /// <param name="userId"> | |
| 249: | /// The user Id to use in the headers. | |
| 250: | /// </param> | |
| 251: | /// <param name="userName"> | |
| 252: | /// The user name to use in the headers. | |
| 253: | /// </param> | |
| 254: | /// <param name="groupId"> | |
| 255: | /// The group id to use in the headers. | |
| 256: | /// </param> | |
| 257: | /// <param name="groupName"> | |
| 258: | /// The group name to use in the headers. | |
| 259: | /// </param> | |
| 260: | public void SetUserInfo(int userId, string userName, int groupId, string groupName) | |
| 261: | { | |
| 262: | this.userId = userId; | |
| 263: | this.userName = userName; | |
| 264: | this.groupId = groupId; | |
| 265: | this.groupName = groupName; | |
| 266: | } | |
| 267: | ||
| 268: | /// <summary> | |
| 269: | /// Get the user id being used for archive entry headers. | |
| 270: | /// </summary> | |
| 271: | /// <returns> | |
| 272: | /// The current user id. | |
| 273: | /// </returns> | |
| 274: | public int UserId { | |
| 275: | get { | |
| 276: | return this.userId; | |
| 277: | } | |
| 278: | } | |
| 279: | ||
| 280: | /// <summary> | |
| 281: | /// Get the user name being used for archive entry headers. | |
| 282: | /// </summary> | |
| 283: | /// <returns> | |
| 284: | /// The current user name. | |
| 285: | /// </returns> | |
| 286: | public string UserName { | |
| 287: | get { | |
| 288: | return this.userName; | |
| 289: | } | |
| 290: | } | |
| 291: | ||
| 292: | /// <summary> | |
| 293: | /// Get the group id being used for archive entry headers. | |
| 294: | /// </summary> | |
| 295: | /// <returns> | |
| 296: | /// The current group id. | |
| 297: | /// </returns> | |
| 298: | public int GroupId { | |
| 299: | get { | |
| 300: | return this.groupId; | |
| 301: | } | |
| 302: | } | |
| 303: | ||
| 304: | /// <summary> | |
| 305: | /// Get the group name being used for archive entry headers. | |
| 306: | /// </summary> | |
| 307: | /// <returns> | |
| 308: | /// The current group name. | |
| 309: | /// </returns> | |
| 310: | public string GroupName { | |
| 311: | get { | |
| 312: | return this.groupName; | |
| 313: | } | |
| 314: | } | |
| 315: | ||
| 316: | /// <summary> | |
| 317: | /// Get the archive's record size. Because of its history, tar | |
| 318: | /// supports the concept of buffered IO consisting of BLOCKS of | |
| 319: | /// RECORDS. This allowed tar to match the IO characteristics of | |
| 320: | /// the physical device being used. Of course, in the Java world, | |
| 321: | /// this makes no sense, WITH ONE EXCEPTION - archives are expected | |
| 322: | /// to be propertly "blocked". Thus, all of the horrible TarBuffer | |
| 323: | /// support boils down to simply getting the "boundaries" correct. | |
| 324: | /// </summary> | |
| 325: | /// <returns> | |
| 326: | /// The record size this archive is using. | |
| 327: | /// </returns> | |
| 328: | public int RecordSize { | |
| 329: | get { | |
| 330: | if (this.tarIn != null) { | |
| 331: | return this.tarIn.GetRecordSize(); | |
| 332: | } else if (this.tarOut != null) { | |
| 333: | return this.tarOut.GetRecordSize(); | |
| 334: | } | |
| 335: | return TarBuffer.DEFAULT_RCDSIZE; | |
| 336: | } | |
| 337: | } | |
| 338: | ||
| 339: | /// <summary> | |
| 340: | /// Close the archive. This simply calls the underlying | |
| 341: | /// tar stream's close() method. | |
| 342: | /// </summary> | |
| 343: | public void CloseArchive() | |
| 344: | { | |
| 345: | if (this.tarIn != null) { | |
| 346: | this.tarIn.Close(); | |
| 347: | } else if (this.tarOut != null) { | |
| 348: | this.tarOut.Flush(); | |
| 349: | this.tarOut.Close(); | |
| 350: | } | |
| 351: | } | |
| 352: | ||
| 353: | /// <summary> | |
| 354: | /// Perform the "list" command and list the contents of the archive. | |
| 355: | /// | |
| 356: | /// NOTE That this method uses the progress display to actually list | |
| 357: | /// the conents. If the progress display is not set, nothing will be | |
| 358: | /// listed! | |
| 359: | /// </summary> | |
| 360: | public void ListContents() | |
| 361: | { | |
| 362: | while (true) { | |
| 363: | TarEntry entry = this.tarIn.GetNextEntry(); | |
| 364: | ||
| 365: | if (entry == null) { | |
| 366: | if (this.debug) { | |
| 367: | Console.Error.WriteLine("READ EOF RECORD"); | |
| 368: | } | |
| 369: | break; | |
| 370: | } | |
| 371: | OnProgressMessageEvent(entry.Name); | |
| 372: | } | |
| 373: | } | |
| 374: | ||
| 375: | /// <summary> | |
| 376: | /// Perform the "extract" command and extract the contents of the archive. | |
| 377: | /// </summary> | |
| 378: | /// <param name="destDir"> | |
| 379: | /// The destination directory into which to extract. | |
| 380: | /// </param> | |
| 381: | public void ExtractContents(string destDir) | |
| 382: | { | |
| 383: | while (true) { | |
| 384: | TarEntry entry = this.tarIn.GetNextEntry(); | |
| 385: | ||
| 386: | if (entry == null) { | |
| 387: | if (this.debug) { | |
| 388: | Console.Error.WriteLine("READ EOF RECORD"); | |
| 389: | } | |
| 390: | break; | |
| 391: | } | |
| 392: | ||
| 393: | this.ExtractEntry(destDir, entry); | |
| 394: | } | |
| 395: | } | |
| 396: | ||
| 397: | void EnsureDirectoryExists(string directoryName) | |
| 398: | { | |
| 399: | if (!Directory.Exists(directoryName)) { | |
| 400: | try { | |
| 401: | Directory.CreateDirectory(directoryName); | |
| 402: | } catch (Exception e) { | |
| 403: | throw new IOException("error making directory path '" + directoryName + "', " + e.Message); | |
| 404: | } | |
| 405: | } | |
| 406: | } | |
| 407: | ||
| 408: | ||
| 409: | bool IsBinary(string filename) | |
| 410: | { | |
| 411: | FileStream fs = File.OpenRead(filename); | |
| 412: | &nb |