| 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: | /// <summary> | |
| 42: | /// The TarInputStream reads a UNIX tar archive as an InputStream. | |
| 43: | /// methods are provided to position at each successive entry in | |
| 44: | /// the archive, and the read each entry as a normal input stream | |
| 45: | /// using read(). | |
| 46: | /// </summary> | |
| 47: | public class TarInputStream : Stream | |
| 48: | { | |
| 49: | protected bool debug; | |
| 50: | protected bool hasHitEOF; | |
| 51: | ||
| 52: | protected int entrySize; | |
| 53: | protected int entryOffset; | |
| 54: | ||
| 55: | protected byte[] readBuf; | |
| 56: | ||
| 57: | protected TarBuffer buffer; | |
| 58: | protected TarEntry currEntry; | |
| 59: | protected IEntryFactory eFactory; | |
| 60: | ||
| 61: | Stream inputStream; | |
| 62: | /// <summary> | |
| 63: | /// I needed to implement the abstract member. | |
| 64: | /// </summary> | |
| 65: | public override bool CanRead { | |
| 66: | get { | |
| 67: | return inputStream.CanRead; | |
| 68: | } | |
| 69: | } | |
| 70: | ||
| 71: | /// <summary> | |
| 72: | /// I needed to implement the abstract member. | |
| 73: | /// </summary> | |
| 74: | public override bool CanSeek { | |
| 75: | get { | |
| 76: | return inputStream.CanSeek; | |
| 77: | } | |
| 78: | } | |
| 79: | ||
| 80: | /// <summary> | |
| 81: | /// I needed to implement the abstract member. | |
| 82: | /// </summary> | |
| 83: | public override bool CanWrite { | |
| 84: | get { | |
| 85: | return inputStream.CanWrite; | |
| 86: | } | |
| 87: | } | |
| 88: | ||
| 89: | /// <summary> | |
| 90: | /// I needed to implement the abstract member. | |
| 91: | /// </summary> | |
| 92: | public override long Length { | |
| 93: | get { | |
| 94: | return inputStream.Length; | |
| 95: | } | |
| 96: | } | |
| 97: | ||
| 98: | /// <summary> | |
| 99: | /// I needed to implement the abstract member. | |
| 100: | /// </summary> | |
| 101: | public override long Position { | |
| 102: | get { | |
| 103: | return inputStream.Position; | |
| 104: | } | |
| 105: | set { | |
| 106: | inputStream.Position = value; | |
| 107: | } | |
| 108: | } | |
| 109: | ||
| 110: | /// <summary> | |
| 111: | /// Flushes the baseInputStream | |
| 112: | /// </summary> | |
| 113: | public override void Flush() | |
| 114: | { | |
| 115: | inputStream.Flush(); | |
| 116: | } | |
| 117: | ||
| 118: | /// <summary> | |
| 119: | /// I needed to implement the abstract member. | |
| 120: | /// </summary> | |
| 121: | public override long Seek(long offset, SeekOrigin origin) | |
| 122: | { | |
| 123: | return inputStream.Seek(offset, origin); | |
| 124: | } | |
| 125: | ||
| 126: | /// <summary> | |
| 127: | /// I needed to implement the abstract member. | |
| 128: | /// </summary> | |
| 129: | public override void SetLength(long val) | |
| 130: | { | |
| 131: | inputStream.SetLength(val); | |
| 132: | } | |
| 133: | ||
| 134: | /// <summary> | |
| 135: | /// I needed to implement the abstract member. | |
| 136: | /// </summary> | |
| 137: | public override void Write(byte[] array, int offset, int count) | |
| 138: | { | |
| 139: | inputStream.Write(array, offset, count); | |
| 140: | } | |
| 141: | ||
| 142: | /// <summary> | |
| 143: | /// I needed to implement the abstract member. | |
| 144: | /// </summary> | |
| 145: | public override void WriteByte(byte val) | |
| 146: | { | |
| 147: | inputStream.WriteByte(val); | |
| 148: | } | |
| 149: | ||
| 150: | ||
| 151: | public TarInputStream(Stream inputStream) : this(inputStream, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE) | |
| 152: | { | |
| 153: | } | |
| 154: | ||
| 155: | public TarInputStream(Stream inputStream, int blockSize) : this(inputStream, blockSize, TarBuffer.DEFAULT_RCDSIZE) | |
| 156: | { | |
| 157: | } | |
| 158: | ||
| 159: | public TarInputStream(Stream inputStream, int blockSize, int recordSize) | |
| 160: | { | |
| 161: | this.inputStream = inputStream; | |
| 162: | this.buffer = TarBuffer.CreateInputTarBuffer(inputStream, blockSize, recordSize); | |
| 163: | ||
| 164: | this.readBuf = null; | |
| 165: | this.debug = false; | |
| 166: | this.hasHitEOF = false; | |
| 167: | this.eFactory = null; | |
| 168: | } | |
| 169: | ||
| 170: | public void SetDebug(bool debugF) | |
| 171: | { | |
| 172: | this.debug = debugF; | |
| 173: | SetBufferDebug(debugF); | |
| 174: | } | |
| 175: | public void SetBufferDebug(bool debug) | |
| 176: | { | |
| 177: | this.buffer.SetDebug(debug); | |
| 178: | } | |
| 179: | ||
| 180: | ||
| 181: | ||
| 182: | public void SetEntryFactory(IEntryFactory factory) | |
| 183: | { | |
| 184: | this.eFactory = factory; | |
| 185: | } | |
| 186: | ||
| 187: | /// <summary> | |
| 188: | /// Closes this stream. Calls the TarBuffer's close() method. | |
| 189: | /// The underlying stream is closed by the TarBuffer. | |
| 190: | /// </summary> | |
| 191: | public override void Close() | |
| 192: | { | |
| 193: | this.buffer.Close(); | |
| 194: | } | |
| 195: | ||
| 196: | /// <summary> | |
| 197: | /// Get the record size being used by this stream's TarBuffer. | |
| 198: | /// </summary> | |
| 199: | /// <returns> | |
| 200: | /// TarBuffer record size. | |
| 201: | /// </returns> | |
| 202: | public int GetRecordSize() | |
| 203: | { | |
| 204: | return this.buffer.GetRecordSize(); | |
| 205: | } | |
| 206: | ||
| 207: | /// <summary> | |
| 208: | /// Get the available data that can be read from the current | |
| 209: | /// entry in the archive. This does not indicate how much data | |
| 210: | /// is left in the entire archive, only in the current entry. | |
| 211: | /// This value is determined from the entry's size header field | |
| 212: | /// and the amount of data already read from the current entry. | |
| 213: | /// </summary> | |
| 214: | /// <returns> | |
| 215: | /// The number of available bytes for the current entry. | |
| 216: | /// </returns> | |
| 217: | public int Available { | |
| 218: | get { | |
| 219: | return this.entrySize - this.entryOffset; | |
| 220: | } | |
| 221: | } | |
| 222: | ||
| 223: | /// <summary> | |
| 224: | /// Skip bytes in the input buffer. This skips bytes in the | |
| 225: | /// current entry's data, not the entire archive, and will | |
| 226: | /// stop at the end of the current entry's data if the number | |
| 227: | /// to skip extends beyond that point. | |
| 228: | /// </summary> | |
| 229: | /// <param name="numToSkip"> | |
| 230: | /// The number of bytes to skip. | |
| 231: | /// </param> | |
| 232: | public void Skip(int numToSkip) | |
| 233: | { | |
| 234: | // REVIEW | |
| 235: | // This is horribly inefficient, but it ensures that we | |
| 236: | // properly skip over bytes via the TarBuffer... | |
| 237: | // | |
| 238: | byte[] skipBuf = new byte[8 * 1024]; | |
| 239: | ||
| 240: | for (int num = numToSkip; num > 0;){ | |
| 241: | int numRead = this.Read(skipBuf, 0, (num > skipBuf.Length ? skipBuf.Length : num)); | |
| 242: | ||
| 243: | if (numRead == -1) { | |
| 244: | break; | |
| 245: | } | |
| 246: | ||
| 247: | num -= numRead; | |
| 248: | } | |
| 249: | } | |
| 250: | ||
| 251: | /// <summary> | |
| 252: | /// Since we do not support marking just yet, we return false. | |
| 253: | /// </summary> | |
| 254: | public bool IsMarkSupported { | |
| 255: | get { | |
| 256: | return false; | |
| 257: | } | |
| 258: | } | |
| 259: | ||
| 260: | /// <summary> | |
| 261: | /// Since we do not support marking just yet, we do nothing. | |
| 262: | /// </summary> | |
| 263: | /// <param name ="markLimit"> | |
| 264: | /// The limit to mark. | |
| 265: | /// </param> | |
| 266: | public void Mark(int markLimit) | |
| 267: | { | |
| 268: | } | |
| 269: | ||
| 270: | /// <summary> | |
| 271: | /// Since we do not support marking just yet, we do nothing. | |
| 272: | /// </summary> | |
| 273: | public void Reset() | |
| 274: | { | |
| 275: | } | |
| 276: | ||
| 277: | /// <summary> | |
| 278: | /// Get the next entry in this tar archive. This will skip | |
| 279: | /// over any remaining data in the current entry, if there | |
| 280: | /// is one, and place the input stream at the header of the | |
| 281: | /// next entry, and read the header and instantiate a new | |
| 282: | /// TarEntry from the header bytes and return that entry. | |
| 283: | /// If there are no more entries in the archive, null will | |
| 284: | /// be returned to indicate that the end of the archive has | |
| 285: | /// been reached. | |
| 286: | /// </summary> | |
| 287: | /// <returns> | |
| 288: | /// The next TarEntry in the archive, or null. | |
| 289: | /// </returns> | |
| 290: | public TarEntry GetNextEntry() | |
| 291: | { | |
| 292: | if (this.hasHitEOF) { | |
| 293: | return null; | |
| 294: | } | |
| 295: | ||
| 296: | if (this.currEntry != null) { | |
| 297: | int numToSkip = this.entrySize - this.entryOffset; | |
| 298: | ||
| 299: | if (this.debug) { | |
| 300: | Console.Error.WriteLine("TarInputStream: SKIP currENTRY '" + this.currEntry.Name + "' SZ " + this.entrySize + " OFF " + this.entryOffset + " skipping " + numToSkip + " bytes"); | |
| 301: | } | |
| 302: | ||
| 303: | if (numToSkip > 0) { | |
| 304: | this.Skip(numToSkip); | |
| 305: | } | |
| 306: | ||
| 307: | this.readBuf = null; | |
| 308: | } | |
| 309: | ||
| 310: | byte[] headerBuf = this.buffer.ReadRecord(); | |
| 311: | ||
| 312: | if (headerBuf == null) { | |
| 313: | if (this.debug) { | |
| 314: | Console.Error.WriteLine("READ NULL RECORD"); | |
| 315: | } | |
| 316: | ||
| 317: | this.hasHitEOF = true; | |
| 318: | } else if (this.buffer.IsEOFRecord(headerBuf)) { | |
| 319: | if (this.debug) { | |
| 320: | Console.Error.WriteLine( "READ EOF RECORD" ); | |
| 321: | } | |
| 322: | ||
| 323: | this.hasHitEOF = true; | |
| 324: | } | |
| 325: | ||
| 326: | if (this.hasHitEOF) { | |
| 327: | this.currEntry = null; | |
| 328: | } else { | |
| 329: | try { | |
| 330: | if (this.eFactory == null) { | |
| 331: | this.currEntry = new TarEntry(headerBuf); | |
| 332: | } else { | |
| 333: | this.currEntry = this.eFactory.CreateEntry(headerBuf); | |
| 334: | } | |
| 335: | ||
| 336: | if (!(headerBuf[257] == 'u' && headerBuf[258] == 's' && headerBuf[259] == 't' && headerBuf[260] == 'a' && headerBuf[261] == 'r')) { | |
| 337: | throw new InvalidHeaderException("header magic is not 'ustar', but '" + headerBuf[257] + headerBuf[258] + headerBuf[259] + headerBuf[260] + headerBuf[261] + | |
| 338: | "', or (dec) " + ((int)headerBuf[257]) + ", " + ((int)headerBuf[258]) + ", " + ((int)headerBuf[259]) + ", " + ((int)headerBuf[260]) + ", " + ((int)headerBuf[261])); | |
| 339: | } | |
| 340: | ||
| 341: | if (this.debug) { | |
| 342: | Console.Error.WriteLine("TarInputStream: SET CURRENTRY '" + this.currEntry.Name + "' size = " + this.currEntry.Size); | |
| 343: | } | |
| 344: | ||
| 345: | this.entryOffset = 0; | |
| 346: | ||
| 347: | // REVIEW How do we resolve this discrepancy?! | |
| 348: | this.entrySize = (int) this.currEntry.Size; | |
| 349: | } catch (InvalidHeaderException ex) { | |
| 350: | this.entrySize = 0; | |
| 351: | this.entryOffset = 0; | |
| 352: | this.currEntry = null; | |
| 353: | throw new InvalidHeaderException("bad header in block " + this.buffer.GetCurrentBlockNum() + " record " + this.buffer.GetCurrentRecordNum() + ", " + ex.Message); | |
| 354: | } | |
| 355: | } | |
| 356: | return this.currEntry; | |
| 357: | } | |
| 358: | ||
| 359: | /// <summary> | |
| 360: | /// Reads a byte from the current tar archive entry. | |
| 361: | /// This method simply calls read(byte[], int, int). | |
| 362: | /// </summary> | |
| 363: | public override int ReadByte() | |
| 364: | { | |
| 365: | byte[] oneByteBuffer = new byte[1]; | |
| 366: | int num = this.Read(oneByteBuffer, 0, 1); | |
| 367: | if (num <= 0) { // return -1 to indicate that no byte was read. | |
| 368: | return -1; | |
| 369: | } | |
| 370: | return (int)oneByteBuffer[0]; | |
| 371: | } | |
| 372: | ||
| 373: | /// <summary> | |
| 374: | /// Reads bytes from the current tar archive entry. | |
| 375: | /// | |
| 376: | /// This method is aware of the boundaries of the current | |
| 377: | /// entry in the archive and will deal with them as if they | |
| 378: | /// entry in the archive and will deal with them as if they | |
| 379: | /// </summary> | |
| 380: | /// <param name="buf"> | |
| 381: | /// The buffer into which to place bytes read. | |
| 382: | /// </param> | |
| 383: | /// <param name="offset"> | |
| 384: | /// The offset at which to place bytes read. | |
| 385: | /// </param> | |
| 386: | /// <param name="numToRead"> | |
| 387: | /// The number of bytes to read. | |
| 388: | /// </param> | |
| 389: | /// <returns> | |
| 390: | /// The number of bytes read, or -1 at EOF. | |