2 // Copyright (C) 2001 Mike Krueger
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.
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.
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.
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
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.
39 namespace ICSharpCode
.SharpZipLib
.Tar
43 /// The TarInputStream reads a UNIX tar archive as an InputStream.
44 /// methods are provided to position at each successive entry in
45 /// the archive, and the read each entry as a normal input stream
48 public class TarInputStream
: Stream
51 protected bool hasHitEOF
;
53 protected int entrySize
;
54 protected int entryOffset
;
56 protected byte[] readBuf
;
58 protected TarBuffer buffer
;
59 protected TarEntry currEntry
;
60 protected IEntryFactory eFactory
;
65 /// I needed to implement the abstract member.
67 public override bool CanRead
71 return inputStream
.CanRead
;
76 /// I needed to implement the abstract member.
78 public override bool CanSeek
80 // TODO is this valid? should it return false?
83 return inputStream
.CanSeek
;
88 /// I needed to implement the abstract member.
90 public override bool CanWrite
94 return inputStream
.CanWrite
;
99 /// I needed to implement the abstract member.
101 public override long Length
105 return inputStream
.Length
;
110 /// I needed to implement the abstract member.
112 public override long Position
116 return inputStream
.Position
;
120 inputStream
.Position
= value;
125 /// Flushes the baseInputStream
127 public override void Flush()
133 /// I needed to implement the abstract member.
135 public override long Seek(long offset
, SeekOrigin origin
)
138 return inputStream
.Seek(offset
, origin
);
142 /// I needed to implement the abstract member.
144 public override void SetLength(long val
)
146 inputStream
.SetLength(val
);
150 /// I needed to implement the abstract member.
152 public override void Write(byte[] array
, int offset
, int count
)
154 inputStream
.Write(array
, offset
, count
);
158 /// I needed to implement the abstract member.
160 public override void WriteByte(byte val
)
162 inputStream
.WriteByte(val
);
166 public TarInputStream(Stream inputStream
) : this(inputStream
, TarBuffer
.DefaultBlockFactor
)
170 public TarInputStream(Stream inputStream
, int blockFactor
)
172 this.inputStream
= inputStream
;
173 this.buffer
= TarBuffer
.CreateInputTarBuffer(inputStream
, blockFactor
);
177 this.hasHitEOF
= false;
178 this.eFactory
= null;
181 public void SetDebug(bool debugFlag
)
183 this.debug
= debugFlag
;
184 SetBufferDebug(debugFlag
);
187 public void SetBufferDebug(bool debug
)
189 this.buffer
.SetDebug(debug
);
194 public void SetEntryFactory(IEntryFactory factory
)
196 this.eFactory
= factory
;
200 /// Closes this stream. Calls the TarBuffer's close() method.
201 /// The underlying stream is closed by the TarBuffer.
203 public override void Close()
209 /// Get the record size being used by this stream's TarBuffer.
212 /// TarBuffer record size.
214 public int GetRecordSize()
216 return this.buffer
.GetRecordSize();
220 /// Get the available data that can be read from the current
221 /// entry in the archive. This does not indicate how much data
222 /// is left in the entire archive, only in the current entry.
223 /// This value is determined from the entry's size header field
224 /// and the amount of data already read from the current entry.
227 /// The number of available bytes for the current entry.
233 return this.entrySize
- this.entryOffset
;
238 /// Skip bytes in the input buffer. This skips bytes in the
239 /// current entry's data, not the entire archive, and will
240 /// stop at the end of the current entry's data if the number
241 /// to skip extends beyond that point.
243 /// <param name="numToSkip">
244 /// The number of bytes to skip.
246 public void Skip(int numToSkip
)
249 // This is horribly inefficient, but it ensures that we
250 // properly skip over bytes via the TarBuffer...
252 byte[] skipBuf
= new byte[8 * 1024];
254 for (int num
= numToSkip
; num
> 0;)
256 int numRead
= this.Read(skipBuf
, 0, (num
> skipBuf
.Length
? skipBuf
.Length
: num
));
268 /// Since we do not support marking just yet, we return false.
270 public bool IsMarkSupported
279 /// Since we do not support marking just yet, we do nothing.
281 /// <param name ="markLimit">
282 /// The limit to mark.
284 public void Mark(int markLimit
)
289 /// Since we do not support marking just yet, we do nothing.
295 void SkipToNextEntry()
297 int numToSkip
= this.entrySize
- this.entryOffset
;
301 //Console.WriteLine.WriteLine("TarInputStream: SKIP currENTRY '" + this.currEntry.Name + "' SZ " + this.entrySize + " OFF " + this.entryOffset + " skipping " + numToSkip + " bytes");
306 this.Skip(numToSkip
);
313 /// Get the next entry in this tar archive. This will skip
314 /// over any remaining data in the current entry, if there
315 /// is one, and place the input stream at the header of the
316 /// next entry, and read the header and instantiate a new
317 /// TarEntry from the header bytes and return that entry.
318 /// If there are no more entries in the archive, null will
319 /// be returned to indicate that the end of the archive has
323 /// The next TarEntry in the archive, or null.
325 public TarEntry
GetNextEntry()
332 if (this.currEntry
!= null)
337 byte[] headerBuf
= this.buffer
.ReadBlock();
339 if (headerBuf
== null)
343 //Console.WriteLine.WriteLine("READ NULL BLOCK");
346 this.hasHitEOF
= true;
348 else if (this.buffer
.IsEOFBlock(headerBuf
))
352 //Console.WriteLine.WriteLine( "READ EOF BLOCK" );
355 this.hasHitEOF
= true;
360 this.currEntry
= null;
366 TarHeader header
= new TarHeader();
367 header
.ParseBuffer(headerBuf
);
368 this.entryOffset
= 0;
369 this.entrySize
= (int)header
.size
;
371 StringBuilder longName
= null;
373 if (header
.typeFlag
== TarHeader
.LF_GNU_LONGNAME
)
375 Console
.WriteLine("TarInputStream: Long name found '" + header
.name
+ "' size = " + header
.size
); // DEBUG
377 byte[] nameBuffer
= new byte[TarBuffer
.BlockSize
];
379 int numToRead
= this.entrySize
;
381 longName
= new StringBuilder();
383 while (numToRead
> 0)
385 int numRead
= this.Read(nameBuffer
, 0, (numToRead
> nameBuffer
.Length
? nameBuffer
.Length
: numToRead
));
389 throw new InvalidHeaderException("Failed to read long name entry");
392 longName
.Append(TarHeader
.ParseName(nameBuffer
, 0, numRead
).ToString());
393 numToRead
-= numRead
;
396 Console
.WriteLine("TarInputStream: Long name is '" + longName
.ToString()); // DEBUG
399 headerBuf
= this.buffer
.ReadBlock();
401 else if (header
.typeFlag
== TarHeader
.LF_GHDR
) // POSIX global extended header
403 // Ignore things we dont understand completely for now
405 headerBuf
= this.buffer
.ReadBlock();
407 else if (header
.typeFlag
== TarHeader
.LF_XHDR
) // POSIX extended header
409 // Ignore things we dont understand completely for now
411 headerBuf
= this.buffer
.ReadBlock();
413 else if (header
.typeFlag
== TarHeader
.LF_GNU_VOLHDR
)
415 // TODO could show volume name when verbose?
417 headerBuf
= this.buffer
.ReadBlock();
419 else if (header
.typeFlag
!= TarHeader
.LF_NORMAL
420 && header
.typeFlag
!= TarHeader
.LF_OLDNORM
)
422 // Ignore things we dont understand completely for now
424 headerBuf
= this.buffer
.ReadBlock();
427 if (this.eFactory
== null)
429 this.currEntry
= new TarEntry(headerBuf
);
430 if (longName
!= null)
432 this.currEntry
.TarHeader
.name
.Length
= 0;
433 this.currEntry
.TarHeader
.name
.Append(longName
.ToString());
438 this.currEntry
= this.eFactory
.CreateEntry(headerBuf
);
441 // TODO -jr- ustar is not the only magic possible by any means
443 if (!(headerBuf
[257] == 'u' && headerBuf
[258] == 's' && headerBuf
[259] == 't' && headerBuf
[260] == 'a' && headerBuf
[261] == 'r'))
445 throw new InvalidHeaderException("header magic is not 'ustar', but '" + headerBuf
[257] + headerBuf
[258] + headerBuf
[259] + headerBuf
[260] + headerBuf
[261] +
446 "', or (dec) " + ((int)headerBuf
[257]) + ", " + ((int)headerBuf
[258]) + ", " + ((int)headerBuf
[259]) + ", " + ((int)headerBuf
[260]) + ", " + ((int)headerBuf
[261]));
451 //Console.WriteLine.WriteLine("TarInputStream: SET CURRENTRY '" + this.currEntry.Name + "' size = " + this.currEntry.Size);
454 this.entryOffset
= 0;
456 // TODO REVIEW How do we resolve this discrepancy?!
457 this.entrySize
= (int) this.currEntry
.Size
;
459 catch (InvalidHeaderException ex
)
462 this.entryOffset
= 0;
463 this.currEntry
= null;
464 throw new InvalidHeaderException("bad header in record " + this.buffer
.GetCurrentBlockNum() + " block " + this.buffer
.GetCurrentBlockNum() + ", " + ex
.Message
);
467 return this.currEntry
;
471 /// Reads a byte from the current tar archive entry.
472 /// This method simply calls read(byte[], int, int).
474 public override int ReadByte()
476 byte[] oneByteBuffer
= new byte[1];
477 int num
= this.Read(oneByteBuffer
, 0, 1);
478 if (num
<= 0) // return -1 to indicate that no byte was read.
482 return (int)oneByteBuffer
[0];
486 /// Reads bytes from the current tar archive entry.
488 /// This method is aware of the boundaries of the current
489 /// entry in the archive and will deal with them appropriately
491 /// <param name="outputBuffer">
492 /// The buffer into which to place bytes read.
494 /// <param name="offset">
495 /// The offset at which to place bytes read.
497 /// <param name="numToRead">
498 /// The number of bytes to read.
501 /// The number of bytes read, or 0 at end of stream/EOF.
503 public override int Read(byte[] outputBuffer
, int offset
, int numToRead
)
507 if (this.entryOffset
>= this.entrySize
)
512 if ((numToRead
+ this.entryOffset
) > this.entrySize
)
514 numToRead
= this.entrySize
- this.entryOffset
;
517 if (this.readBuf
!= null)
519 int sz
= (numToRead
> this.readBuf
.Length
) ? this.readBuf
.Length
: numToRead
;
521 Array
.Copy(this.readBuf
, 0, outputBuffer
, offset
, sz
);
523 if (sz
>= this.readBuf
.Length
)
529 int newLen
= this.readBuf
.Length
- sz
;
530 byte[] newBuf
= new byte[newLen
];
531 Array
.Copy(this.readBuf
, sz
, newBuf
, 0, newLen
);
532 this.readBuf
= newBuf
;
540 while (numToRead
> 0)
542 byte[] rec
= this.buffer
.ReadBlock();
546 throw new IOException("unexpected EOF with " + numToRead
+ " bytes unread");
550 int recLen
= rec
.Length
;
554 Array
.Copy(rec
, 0, outputBuffer
, offset
, sz
);
555 this.readBuf
= new byte[recLen
- sz
];
556 Array
.Copy(rec
, sz
, this.readBuf
, 0, recLen
- sz
);
561 Array
.Copy(rec
, 0, outputBuffer
, offset
, recLen
);
569 this.entryOffset
+= totalRead
;
575 /// Copies the contents of the current tar archive entry directly into
576 /// an output stream.
578 /// <param name="outputStream">
579 /// The OutputStream into which to write the entry's data.
581 public void CopyEntryContents(Stream outputStream
)
583 byte[] buf
= new byte[32 * 1024];
587 int numRead
= this.Read(buf
, 0, buf
.Length
);
592 outputStream
.Write(buf
, 0, numRead
);
597 /// This interface is provided, with the method setEntryFactory(), to allow
598 /// the programmer to have their own TarEntry subclass instantiated for the
599 /// entries return from getNextEntry().
601 public interface IEntryFactory
603 TarEntry
CreateEntry(string name
);
605 TarEntry
CreateEntryFromFile(string fileName
);
607 TarEntry
CreateEntry(byte[] headerBuf
);
610 public class EntryFactoryAdapter
: IEntryFactory
612 public TarEntry
CreateEntry(string name
)
614 return TarEntry
.CreateTarEntry(name
);
617 public TarEntry
CreateEntryFromFile(string fileName
)
619 return TarEntry
.CreateEntryFromFile(fileName
);
622 public TarEntry
CreateEntry(byte[] headerBuf
)
624 return new TarEntry(headerBuf
);
632 /* The original Java file had this header:
633 ** Authored by Timothy Gerard Endres
634 ** <mailto:time@gjt.org> <http://www.trustice.com>
636 ** This work has been placed into the public domain.
637 ** You may use this work in any way and for any purpose you wish.
639 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
640 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
641 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
642 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
643 ** REDISTRIBUTION OF THIS SOFTWARE.