(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / ICSharpCode.SharpZipLib / ICSharpCode.SharpZipLib / Tar / TarInputStream.cs
blob4ba1de45ea2cba5a26389303f370e1cea40db600
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.
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
21 // combination.
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.
35 using System;
36 using System.IO;
37 using System.Text;
39 namespace ICSharpCode.SharpZipLib.Tar
42 /// <summary>
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
46 /// using read().
47 /// </summary>
48 public class TarInputStream : Stream
50 protected bool debug;
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;
62 Stream inputStream;
64 /// <summary>
65 /// I needed to implement the abstract member.
66 /// </summary>
67 public override bool CanRead
69 get
71 return inputStream.CanRead;
75 /// <summary>
76 /// I needed to implement the abstract member.
77 /// </summary>
78 public override bool CanSeek
80 // TODO is this valid? should it return false?
81 get
83 return inputStream.CanSeek;
87 /// <summary>
88 /// I needed to implement the abstract member.
89 /// </summary>
90 public override bool CanWrite
92 get
94 return inputStream.CanWrite;
98 /// <summary>
99 /// I needed to implement the abstract member.
100 /// </summary>
101 public override long Length
103 get
105 return inputStream.Length;
109 /// <summary>
110 /// I needed to implement the abstract member.
111 /// </summary>
112 public override long Position
114 get
116 return inputStream.Position;
118 set
120 inputStream.Position = value;
124 /// <summary>
125 /// Flushes the baseInputStream
126 /// </summary>
127 public override void Flush()
129 inputStream.Flush();
132 /// <summary>
133 /// I needed to implement the abstract member.
134 /// </summary>
135 public override long Seek(long offset, SeekOrigin origin)
137 // TODO allow this?
138 return inputStream.Seek(offset, origin);
141 /// <summary>
142 /// I needed to implement the abstract member.
143 /// </summary>
144 public override void SetLength(long val)
146 inputStream.SetLength(val);
149 /// <summary>
150 /// I needed to implement the abstract member.
151 /// </summary>
152 public override void Write(byte[] array, int offset, int count)
154 inputStream.Write(array, offset, count);
157 /// <summary>
158 /// I needed to implement the abstract member.
159 /// </summary>
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);
175 this.readBuf = null;
176 this.debug = false;
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;
199 /// <summary>
200 /// Closes this stream. Calls the TarBuffer's close() method.
201 /// The underlying stream is closed by the TarBuffer.
202 /// </summary>
203 public override void Close()
205 this.buffer.Close();
208 /// <summary>
209 /// Get the record size being used by this stream's TarBuffer.
210 /// </summary>
211 /// <returns>
212 /// TarBuffer record size.
213 /// </returns>
214 public int GetRecordSize()
216 return this.buffer.GetRecordSize();
219 /// <summary>
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.
225 /// </summary>
226 /// <returns>
227 /// The number of available bytes for the current entry.
228 /// </returns>
229 public int Available
231 get
233 return this.entrySize - this.entryOffset;
237 /// <summary>
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.
242 /// </summary>
243 /// <param name="numToSkip">
244 /// The number of bytes to skip.
245 /// </param>
246 public void Skip(int numToSkip)
248 // TODO REVIEW
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));
258 if (numRead == -1)
260 break;
263 num -= numRead;
267 /// <summary>
268 /// Since we do not support marking just yet, we return false.
269 /// </summary>
270 public bool IsMarkSupported
272 get
274 return false;
278 /// <summary>
279 /// Since we do not support marking just yet, we do nothing.
280 /// </summary>
281 /// <param name ="markLimit">
282 /// The limit to mark.
283 /// </param>
284 public void Mark(int markLimit)
288 /// <summary>
289 /// Since we do not support marking just yet, we do nothing.
290 /// </summary>
291 public void Reset()
295 void SkipToNextEntry()
297 int numToSkip = this.entrySize - this.entryOffset;
299 if (this.debug)
301 //Console.WriteLine.WriteLine("TarInputStream: SKIP currENTRY '" + this.currEntry.Name + "' SZ " + this.entrySize + " OFF " + this.entryOffset + " skipping " + numToSkip + " bytes");
304 if (numToSkip > 0)
306 this.Skip(numToSkip);
309 this.readBuf = null;
312 /// <summary>
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
320 /// been reached.
321 /// </summary>
322 /// <returns>
323 /// The next TarEntry in the archive, or null.
324 /// </returns>
325 public TarEntry GetNextEntry()
327 if (this.hasHitEOF)
329 return null;
332 if (this.currEntry != null)
334 SkipToNextEntry();
337 byte[] headerBuf = this.buffer.ReadBlock();
339 if (headerBuf == null)
341 if (this.debug)
343 //Console.WriteLine.WriteLine("READ NULL BLOCK");
346 this.hasHitEOF = true;
348 else if (this.buffer.IsEOFBlock(headerBuf))
350 if (this.debug)
352 //Console.WriteLine.WriteLine( "READ EOF BLOCK" );
355 this.hasHitEOF = true;
358 if (this.hasHitEOF)
360 this.currEntry = null;
362 else
364 try
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));
387 if (numRead == -1)
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
398 SkipToNextEntry();
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
404 SkipToNextEntry();
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
410 SkipToNextEntry();
411 headerBuf = this.buffer.ReadBlock();
413 else if (header.typeFlag == TarHeader.LF_GNU_VOLHDR)
415 // TODO could show volume name when verbose?
416 SkipToNextEntry();
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
423 SkipToNextEntry();
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());
436 else
438 this.currEntry = this.eFactory.CreateEntry(headerBuf);
441 // TODO -jr- ustar is not the only magic possible by any means
442 // tar, xtar, ...
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]));
449 if (this.debug)
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)
461 this.entrySize = 0;
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;
470 /// <summary>
471 /// Reads a byte from the current tar archive entry.
472 /// This method simply calls read(byte[], int, int).
473 /// </summary>
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.
480 return -1;
482 return (int)oneByteBuffer[0];
485 /// <summary>
486 /// Reads bytes from the current tar archive entry.
487 ///
488 /// This method is aware of the boundaries of the current
489 /// entry in the archive and will deal with them appropriately
490 /// </summary>
491 /// <param name="outputBuffer">
492 /// The buffer into which to place bytes read.
493 /// </param>
494 /// <param name="offset">
495 /// The offset at which to place bytes read.
496 /// </param>
497 /// <param name="numToRead">
498 /// The number of bytes to read.
499 /// </param>
500 /// <returns>
501 /// The number of bytes read, or 0 at end of stream/EOF.
502 /// </returns>
503 public override int Read(byte[] outputBuffer, int offset, int numToRead)
505 int totalRead = 0;
507 if (this.entryOffset >= this.entrySize)
509 return 0;
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)
525 this.readBuf = null;
527 else
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;
535 totalRead += sz;
536 numToRead -= sz;
537 offset += sz;
540 while (numToRead > 0)
542 byte[] rec = this.buffer.ReadBlock();
543 if (rec == null)
545 // Unexpected EOF!
546 throw new IOException("unexpected EOF with " + numToRead + " bytes unread");
549 int sz = numToRead;
550 int recLen = rec.Length;
552 if (recLen > sz)
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);
558 else
560 sz = recLen;
561 Array.Copy(rec, 0, outputBuffer, offset, recLen);
564 totalRead += sz;
565 numToRead -= sz;
566 offset += sz;
569 this.entryOffset += totalRead;
571 return totalRead;
574 /// <summary>
575 /// Copies the contents of the current tar archive entry directly into
576 /// an output stream.
577 /// </summary>
578 /// <param name="outputStream">
579 /// The OutputStream into which to write the entry's data.
580 /// </param>
581 public void CopyEntryContents(Stream outputStream)
583 byte[] buf = new byte[32 * 1024];
585 while (true)
587 int numRead = this.Read(buf, 0, buf.Length);
588 if (numRead <= 0)
590 break;
592 outputStream.Write(buf, 0, numRead);
596 /// <summary>
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().
600 /// </summary>
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.