root/trunk/Updater/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs @ 597

Wersja 597, 20.5 KB (wprowadzona przez marek, 17 years temu)

re #165

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