Added hammock project to debug streaming issues
[pithos-ms-client] / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Tar / TarInputStream.cs
1 // TarInputStream.cs
2 //
3 // Copyright (C) 2001 Mike Krueger
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 //
19 // Linking this library statically or dynamically with other modules is
20 // making a combined work based on this library.  Thus, the terms and
21 // conditions of the GNU General Public License cover the whole
22 // combination.
23 //
24 // As a special exception, the copyright holders of this library give you
25 // permission to link this library with independent modules to produce an
26 // executable, regardless of the license terms of these independent
27 // modules, and to copy and distribute the resulting executable under
28 // terms of your choice, provided that you also meet, for each linked
29 // independent module, the terms and conditions of the license of that
30 // module.  An independent module is a module which is not derived from
31 // or based on this library.  If you modify this library, you may extend
32 // this exception to your version of the library, but you are not
33 // obligated to do so.  If you do not wish to do so, delete this
34 // exception statement from your version.
35
36 using System;
37 using System.IO;
38 using System.Text;
39
40 namespace ICSharpCode.SharpZipLib.Silverlight.Tar
41 {
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
49     {
50         #region Constructors
51
52         /// <summary>
53         /// Construct a TarInputStream with default block factor
54         /// </summary>
55         /// <param name="inputStream">stream to source data from</param>
56         public TarInputStream(Stream inputStream)
57             : this(inputStream, TarBuffer.DefaultBlockFactor)
58         {
59         }
60
61         /// <summary>
62         /// Construct a TarInputStream with user specified block factor
63         /// </summary>
64         /// <param name="inputStream">stream to source data from</param>
65         /// <param name="blockFactor">block factor to apply to archive</param>
66         public TarInputStream(Stream inputStream, int blockFactor)
67         {
68             this.inputStream = inputStream;
69             _buffer = TarBuffer.CreateInputTarBuffer(inputStream, blockFactor);
70         }
71
72         #endregion
73
74         #region Stream Overrides
75
76         /// <summary>
77         /// Gets a value indicating whether the current stream supports reading
78         /// </summary>
79         public override bool CanRead
80         {
81             get { return inputStream.CanRead; }
82         }
83
84         /// <summary>
85         /// Gets a value indicating whether the current stream supports seeking
86         /// This property always returns false.
87         /// </summary>
88         public override bool CanSeek
89         {
90             get { return false; }
91         }
92
93         /// <summary>
94         /// Gets a value indicating if the stream supports writing.
95         /// This property always returns false.
96         /// </summary>
97         public override bool CanWrite
98         {
99             get { return false; }
100         }
101
102         /// <summary>
103         /// The length in bytes of the stream
104         /// </summary>
105         public override long Length
106         {
107             get { return inputStream.Length; }
108         }
109
110         /// <summary>
111         /// Gets or sets the position within the stream. 
112         /// Setting the Position is not supported and throws a NotSupportedExceptionNotSupportedException
113         /// </summary>
114         /// <exception cref="NotSupportedException">Any attempt to set position</exception>
115         public override long Position
116         {
117             get { return inputStream.Position; }
118             set { throw new NotSupportedException("TarInputStream Seek not supported"); }
119         }
120
121         /// <summary>
122         /// Flushes the baseInputStream
123         /// </summary>
124         public override void Flush()
125         {
126             inputStream.Flush();
127         }
128
129         /// <summary>
130         /// Set the streams position.  This operation is not supported and will throw a NotSupportedException
131         /// </summary>
132         /// <param name="offset">The offset relative to the origin to seek to.</param>
133         /// <param name="origin">The <see cref="SeekOrigin"/> to start seeking from.</param>
134         /// <returns>The new position in the stream.</returns>
135         /// <exception cref="NotSupportedException">Any access</exception>
136         public override long Seek(long offset, SeekOrigin origin)
137         {
138             throw new NotSupportedException("TarInputStream Seek not supported");
139         }
140
141         /// <summary>
142         /// Sets the length of the stream
143         /// This operation is not supported and will throw a NotSupportedException
144         /// </summary>
145         /// <param name="value">The new stream length.</param>
146         /// <exception cref="NotSupportedException">Any access</exception>
147         public override void SetLength(long value)
148         {
149             throw new NotSupportedException("TarInputStream SetLength not supported");
150         }
151
152         /// <summary>
153         /// Writes a block of bytes to this stream using data from a buffer.
154         /// This operation is not supported and will throw a NotSupportedException
155         /// </summary>
156         /// <param name="buffer">The buffer containing bytes to write.</param>
157         /// <param name="offset">The offset in the buffer of the frist byte to write.</param>
158         /// <param name="count">The number of bytes to write.</param>
159         /// <exception cref="NotSupportedException">Any access</exception>
160         public override void Write(byte[] buffer, int offset, int count)
161         {
162             throw new NotSupportedException("TarInputStream Write not supported");
163         }
164
165         /// <summary>
166         /// Writes a byte to the current position in the file stream.
167         /// This operation is not supported and will throw a NotSupportedException
168         /// </summary>
169         /// <param name="value">The byte value to write.</param>
170         /// <exception cref="NotSupportedException">Any access</exception>
171         public override void WriteByte(byte value)
172         {
173             throw new NotSupportedException("TarInputStream WriteByte not supported");
174         }
175
176         /// <summary>
177         /// Reads a byte from the current tar archive entry.
178         /// </summary>
179         /// <returns>A byte cast to an int; -1 if the at the end of the stream.</returns>
180         public override int ReadByte()
181         {
182             var oneByteBuffer = new byte[1];
183             var num = Read(oneByteBuffer, 0, 1);
184             if (num <= 0)
185             {
186                 // return -1 to indicate that no byte was read.
187                 return -1;
188             }
189             return oneByteBuffer[0];
190         }
191
192         /// <summary>
193         /// Reads bytes from the current tar archive entry.
194         /// 
195         /// This method is aware of the boundaries of the current
196         /// entry in the archive and will deal with them appropriately
197         /// </summary>
198         /// <param name="buffer">
199         /// The buffer into which to place bytes read.
200         /// </param>
201         /// <param name="offset">
202         /// The offset at which to place bytes read.
203         /// </param>
204         /// <param name="count">
205         /// The number of bytes to read.
206         /// </param>
207         /// <returns>
208         /// The number of bytes read, or 0 at end of stream/EOF.
209         /// </returns>
210         public override int Read(byte[] buffer, int offset, int count)
211         {
212             if (buffer == null)
213             {
214                 throw new ArgumentNullException("buffer");
215             }
216
217             var totalRead = 0;
218
219             if (entryOffset >= entrySize)
220             {
221                 return 0;
222             }
223
224             long numToRead = count;
225
226             if ((numToRead + entryOffset) > entrySize)
227             {
228                 numToRead = entrySize - entryOffset;
229             }
230
231             if (readBuffer != null)
232             {
233                 var sz = (numToRead > readBuffer.Length) ? readBuffer.Length : (int) numToRead;
234
235                 Array.Copy(readBuffer, 0, buffer, offset, sz);
236
237                 if (sz >= readBuffer.Length)
238                 {
239                     readBuffer = null;
240                 }
241                 else
242                 {
243                     var newLen = readBuffer.Length - sz;
244                     var newBuf = new byte[newLen];
245                     Array.Copy(readBuffer, sz, newBuf, 0, newLen);
246                     readBuffer = newBuf;
247                 }
248
249                 totalRead += sz;
250                 numToRead -= sz;
251                 offset += sz;
252             }
253
254             while (numToRead > 0)
255             {
256                 var rec = _buffer.ReadBlock();
257                 if (rec == null)
258                 {
259                     // Unexpected EOF!
260                     throw new TarException("unexpected EOF with " + numToRead + " bytes unread");
261                 }
262
263                 var sz = (int) numToRead;
264                 var recLen = rec.Length;
265
266                 if (recLen > sz)
267                 {
268                     Array.Copy(rec, 0, buffer, offset, sz);
269                     readBuffer = new byte[recLen - sz];
270                     Array.Copy(rec, sz, readBuffer, 0, recLen - sz);
271                 }
272                 else
273                 {
274                     sz = recLen;
275                     Array.Copy(rec, 0, buffer, offset, recLen);
276                 }
277
278                 totalRead += sz;
279                 numToRead -= sz;
280                 offset += sz;
281             }
282
283             entryOffset += totalRead;
284
285             return totalRead;
286         }
287
288         /// <summary>
289         /// Closes this stream. Calls the TarBuffer's close() method.
290         /// The underlying stream is closed by the TarBuffer.
291         /// </summary>
292         public override void Close()
293         {
294             _buffer.Close();
295         }
296
297         #endregion
298
299         /// <summary>
300         /// Get the record size being used by this stream's TarBuffer.
301         /// </summary>
302         public int RecordSize
303         {
304             get { return _buffer.RecordSize; }
305         }
306
307         /// <summary>
308         /// Get the available data that can be read from the current
309         /// entry in the archive. This does not indicate how much data
310         /// is left in the entire archive, only in the current entry.
311         /// This value is determined from the entry's size header field
312         /// and the amount of data already read from the current entry.
313         /// </summary>
314         /// <returns>
315         /// The number of available bytes for the current entry.
316         /// </returns>
317         public long Available
318         {
319             get { return entrySize - entryOffset; }
320         }
321
322         /// <summary>
323         /// Return a value of true if marking is supported; false otherwise.
324         /// </summary>
325         /// <remarks>Currently marking is not supported, the return value is always false.</remarks>
326         public bool IsMarkSupported
327         {
328             get { return false; }
329         }
330
331         /// <summary>
332         /// Set the entry factory for this instance.
333         /// </summary>
334         /// <param name="factory">The factory for creating new entries</param>
335         public void SetEntryFactory(IEntryFactory factory)
336         {
337             entryFactory = factory;
338         }
339
340         /// <summary>
341         /// Get the record size being used by this stream's TarBuffer.
342         /// </summary>
343         /// <returns>
344         /// TarBuffer record size.
345         /// </returns>
346         [Obsolete("Use RecordSize property instead")]
347         public int GetRecordSize()
348         {
349             return _buffer.RecordSize;
350         }
351
352         /// <summary>
353         /// Skip bytes in the input buffer. This skips bytes in the
354         /// current entry's data, not the entire archive, and will
355         /// stop at the end of the current entry's data if the number
356         /// to skip extends beyond that point.
357         /// </summary>
358         /// <param name="skipCount">
359         /// The number of bytes to skip.
360         /// </param>
361         public void Skip(long skipCount)
362         {
363             // TODO: REVIEW efficiency of TarInputStream.Skip
364             // This is horribly inefficient, but it ensures that we
365             // properly skip over bytes via the TarBuffer...
366             //
367             var skipBuf = new byte[8*1024];
368
369             for (var num = skipCount; num > 0;)
370             {
371                 var toRead = num > skipBuf.Length ? skipBuf.Length : (int) num;
372                 var numRead = Read(skipBuf, 0, toRead);
373
374                 if (numRead == -1)
375                 {
376                     break;
377                 }
378
379                 num -= numRead;
380             }
381         }
382
383         /// <summary>
384         /// Since we do not support marking just yet, we do nothing.
385         /// </summary>
386         /// <param name ="markLimit">
387         /// The limit to mark.
388         /// </param>
389         public void Mark(int markLimit)
390         {
391         }
392
393         /// <summary>
394         /// Since we do not support marking just yet, we do nothing.
395         /// </summary>
396         public void Reset()
397         {
398         }
399
400         /// <summary>
401         /// Get the next entry in this tar archive. This will skip
402         /// over any remaining data in the current entry, if there
403         /// is one, and place the input stream at the header of the
404         /// next entry, and read the header and instantiate a new
405         /// TarEntry from the header bytes and return that entry.
406         /// If there are no more entries in the archive, null will
407         /// be returned to indicate that the end of the archive has
408         /// been reached.
409         /// </summary>
410         /// <returns>
411         /// The next TarEntry in the archive, or null.
412         /// </returns>
413         public TarEntry GetNextEntry()
414         {
415             if (hasHitEOF)
416             {
417                 return null;
418             }
419
420             if (currentEntry != null)
421             {
422                 SkipToNextEntry();
423             }
424
425             var headerBuf = _buffer.ReadBlock();
426
427             if (headerBuf == null)
428             {
429                 hasHitEOF = true;
430             }
431             else if (_buffer.IsEOFBlock(headerBuf))
432             {
433                 hasHitEOF = true;
434             }
435
436             if (hasHitEOF)
437             {
438                 currentEntry = null;
439             }
440             else
441             {
442                 try
443                 {
444                     var header = new TarHeader();
445                     header.ParseBuffer(headerBuf);
446                     if (!header.IsChecksumValid)
447                     {
448                         throw new TarException("Header checksum is invalid");
449                     }
450                     entryOffset = 0;
451                     entrySize = header.Size;
452
453                     StringBuilder longName = null;
454
455                     if (header.TypeFlag == TarHeader.LF_GNU_LONGNAME)
456                     {
457                         var nameBuffer = new byte[TarBuffer.BlockSize];
458                         var numToRead = entrySize;
459
460                         longName = new StringBuilder();
461
462                         while (numToRead > 0)
463                         {
464                             var numRead = Read(nameBuffer, 0,
465                                                (numToRead > nameBuffer.Length ? nameBuffer.Length : (int) numToRead));
466
467                             if (numRead == -1)
468                             {
469                                 throw new InvalidHeaderException("Failed to read long name entry");
470                             }
471
472                             longName.Append(TarHeader.ParseName(nameBuffer, 0, numRead).ToString());
473                             numToRead -= numRead;
474                         }
475
476                         SkipToNextEntry();
477                         headerBuf = _buffer.ReadBlock();
478                     }
479                     else if (header.TypeFlag == TarHeader.LF_GHDR)
480                     {
481                         // POSIX global extended header 
482                         // Ignore things we dont understand completely for now
483                         SkipToNextEntry();
484                         headerBuf = _buffer.ReadBlock();
485                     }
486                     else if (header.TypeFlag == TarHeader.LF_XHDR)
487                     {
488                         // POSIX extended header
489                         // Ignore things we dont understand completely for now
490                         SkipToNextEntry();
491                         headerBuf = _buffer.ReadBlock();
492                     }
493                     else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR)
494                     {
495                         // TODO: could show volume name when verbose
496                         SkipToNextEntry();
497                         headerBuf = _buffer.ReadBlock();
498                     }
499                     else if (header.TypeFlag != TarHeader.LF_NORMAL &&
500                              header.TypeFlag != TarHeader.LF_OLDNORM &&
501                              header.TypeFlag != TarHeader.LF_DIR)
502                     {
503                         // Ignore things we dont understand completely for now
504                         SkipToNextEntry();
505                         headerBuf = _buffer.ReadBlock();
506                     }
507
508                     if (entryFactory == null)
509                     {
510                         currentEntry = new TarEntry(headerBuf);
511                         if (longName != null)
512                         {
513                             currentEntry.Name = longName.ToString();
514                         }
515                     }
516                     else
517                     {
518                         currentEntry = entryFactory.CreateEntry(headerBuf);
519                     }
520
521                     // Magic was checked here for 'ustar' but there are multiple valid possibilities
522                     // so this is not done anymore.
523
524                     entryOffset = 0;
525
526                     // TODO: Review How do we resolve this discrepancy?!
527                     entrySize = currentEntry.Size;
528                 }
529                 catch (InvalidHeaderException ex)
530                 {
531                     entrySize = 0;
532                     entryOffset = 0;
533                     currentEntry = null;
534                     var errorText = string.Format("Bad header in record {0} block {1} {2}",
535                                                   _buffer.CurrentRecord, _buffer.CurrentBlock, ex.Message);
536                     throw new InvalidHeaderException(errorText);
537                 }
538             }
539             return currentEntry;
540         }
541
542         /// <summary>
543         /// Copies the contents of the current tar archive entry directly into
544         /// an output stream.
545         /// </summary>
546         /// <param name="outputStream">
547         /// The OutputStream into which to write the entry's data.
548         /// </param>
549         public void CopyEntryContents(Stream outputStream)
550         {
551             var tempBuffer = new byte[32*1024];
552
553             while (true)
554             {
555                 var numRead = Read(tempBuffer, 0, tempBuffer.Length);
556                 if (numRead <= 0)
557                 {
558                     break;
559                 }
560                 outputStream.Write(tempBuffer, 0, numRead);
561             }
562         }
563
564         private void SkipToNextEntry()
565         {
566             var numToSkip = entrySize - entryOffset;
567
568             if (numToSkip > 0)
569             {
570                 Skip(numToSkip);
571             }
572
573             readBuffer = null;
574         }
575
576         #region Nested type: EntryFactoryAdapter
577
578         /// <summary>
579         /// Standard entry factory class creating instances of the class TarEntry
580         /// </summary>
581         public class EntryFactoryAdapter : IEntryFactory
582         {
583             #region IEntryFactory Members
584
585             /// <summary>
586             /// Create a <see cref="TarEntry"/> based on named
587             /// </summary>
588             /// <param name="name">The name to use for the entry</param>
589             /// <returns>A new <see cref="TarEntry"/></returns>
590             public TarEntry CreateEntry(string name)
591             {
592                 return TarEntry.CreateTarEntry(name);
593             }
594
595             /// <summary>
596             /// Create a tar entry with details obtained from <paramref name="fileName">file</paramref>
597             /// </summary>
598             /// <param name="fileName">The name of the file to retrieve details from.</param>
599             /// <returns>A new <see cref="TarEntry"/></returns>
600             public TarEntry CreateEntryFromFile(string fileName)
601             {
602                 return TarEntry.CreateEntryFromFile(fileName);
603             }
604
605             /// <summary>
606             /// Create an entry based on details in <paramref name="headerBuffer">header</paramref>
607             /// </summary>                      
608             /// <param name="headerBuffer">The buffer containing entry details.</param>
609             /// <returns>A new <see cref="TarEntry"/></returns>
610             public TarEntry CreateEntry(byte[] headerBuffer)
611             {
612                 return new TarEntry(headerBuffer);
613             }
614
615             #endregion
616         }
617
618         #endregion
619
620         #region Instance Fields
621
622         /// <summary>
623         /// Stream used as the source of input data.
624         /// </summary>
625         private readonly Stream inputStream;
626
627         /// <summary>
628         /// Working buffer
629         /// </summary>
630         protected TarBuffer _buffer;
631
632         /// <summary>
633         /// Current entry being read
634         /// </summary>
635         private TarEntry currentEntry;
636
637         /// <summary>
638         /// Factory used to create TarEntry or descendant class instance
639         /// </summary>
640         protected IEntryFactory entryFactory;
641
642         /// <summary>
643         /// Number of bytes read for this entry so far
644         /// </summary>
645         protected long entryOffset;
646
647         /// <summary>
648         /// Size of this entry as recorded in header
649         /// </summary>
650         protected long entrySize;
651
652         /// <summary>
653         /// Flag set when last block has been read
654         /// </summary>
655         protected bool hasHitEOF;
656
657         /// <summary>
658         /// Buffer used with calls to <code>Read()</code>
659         /// </summary>          
660         protected byte[] readBuffer;
661
662         #endregion
663
664         #region Nested type: IEntryFactory
665
666         /// <summary>
667         /// This interface is provided, along with the method <see cref="SetEntryFactory"/>, to allow
668         /// the programmer to have their own <see cref="TarEntry"/> subclass instantiated for the
669         /// entries return from <see cref="GetNextEntry"/>.
670         /// </summary>
671         public interface IEntryFactory
672         {
673             /// <summary>
674             /// Create an entry based on name alone
675             /// </summary>
676             /// <param name="name">
677             /// Name of the new EntryPointNotFoundException to create
678             /// </param>
679             /// <returns>created TarEntry or descendant class</returns>
680             TarEntry CreateEntry(string name);
681
682             /// <summary>
683             /// Create an instance based on an actual file
684             /// </summary>
685             /// <param name="fileName">
686             /// Name of file to represent in the entry
687             /// </param>
688             /// <returns>
689             /// Created TarEntry or descendant class
690             /// </returns>
691             TarEntry CreateEntryFromFile(string fileName);
692
693             /// <summary>
694             /// Create a tar entry based on the header information passed
695             /// </summary>
696             /// <param name="headerBuf">
697             /// Buffer containing header information to base entry on
698             /// </param>
699             /// <returns>
700             /// Created TarEntry or descendant class
701             /// </returns>
702             TarEntry CreateEntry(byte[] headerBuf);
703         }
704
705         #endregion
706     }
707 }
708
709 /* The original Java file had this header:
710         ** Authored by Timothy Gerard Endres
711         ** <mailto:time@gjt.org>  <http://www.trustice.com>
712         **
713         ** This work has been placed into the public domain.
714         ** You may use this work in any way and for any purpose you wish.
715         **
716         ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
717         ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
718         ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
719         ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
720         ** REDISTRIBUTION OF THIS SOFTWARE.
721         **
722         */