1:   // TarInputStream.cs
2:   // Copyright (C) 2001 Mike Krueger
3:   //
4:   // This program is free software; you can redistribute it and/or
5:   // modify it under the terms of the GNU General Public License
6:   // as published by the Free Software Foundation; either version 2
7:   // of the License, or (at your option) any later version.
8:   //
9:   // This program is distributed in the hope that it will be useful,
10:   // but WITHOUT ANY WARRANTY; without even the implied warranty of
11:   // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12:   // GNU General Public License for more details.
13:   //
14:   // You should have received a copy of the GNU General Public License
15:   // along with this program; if not, write to the Free Software
16:   // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17:   //
18:   // Linking this library statically or dynamically with other modules is
19:   // making a combined work based on this library.  Thus, the terms and
20:   // conditions of the GNU General Public License cover the whole
21:   // combination.
22:   //
23:   // As a special exception, the copyright holders of this library give you
24:   // permission to link this library with independent modules to produce an
25:   // executable, regardless of the license terms of these independent
26:   // modules, and to copy and distribute the resulting executable under
27:   // terms of your choice, provided that you also meet, for each linked
28:   // independent module, the terms and conditions of the license of that
29:   // module.  An independent module is a module which is not derived from
30:   // or based on this library.  If you modify this library, you may extend
31:   // this exception to your version of the library, but you are not
32:   // obligated to do so.  If you do not wish to do so, delete this
33:   // exception statement from your version.
34:  
35:   using System;
36:   using System.IO;
37:   using System.Text;
38:  
39:   namespace ICSharpCode.SharpZipLib.Tar {
40:       
41:       public delegate void ProgressMessageHandler(TarArchive archivestring message);
42:       
43:       /// <summary>
44:       /// The TarArchive class implements the concept of a
45:       /// tar archive. A tar archive is a series of entries, each of
46:       /// which represents a file system object. Each entry in
47:       /// the archive consists of a header record. Directory entries
48:       /// consist only of the header record, and are followed by entries
49:       /// for the directory's contents. File entries consist of a
50:       /// header record followed by the number of records needed to
51:       /// contain the file's contents. All entries are written on
52:       /// record boundaries. Records are 512 bytes long.
53:       /// 
54:       /// TarArchives are instantiated in either read or write mode,
55:       /// based upon whether they are instantiated with an InputStream
56:       /// or an OutputStream. Once instantiated TarArchives read/write
57:       /// mode can not be changed.
58:       /// 
59:       /// There is currently no support for random access to tar archives.
60:       /// However, it seems that subclassing TarArchive, and using the
61:       /// TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()
62:       /// methods, this would be rather trvial.
63:       /// </summary>
64:       public class TarArchive
65:       {
66:           protected bool verbose;
67:           protected bool debug;
68:           protected bool keepOldFiles;
69:           protected bool asciiTranslate;
70:           
71:           protected int    userId;
72:           protected string userName;
73:           protected int    groupId;
74:           protected string groupName;
75:           
76:           protected string rootPath;
77:           protected string pathPrefix;
78:           
79:           protected int    recordSize;
80:           protected byte[] recordBuf;
81:           
82:           protected TarInputStream  tarIn;
83:           protected TarOutputStream tarOut;
84:           
85:           public event ProgressMessageHandler ProgressMessageEvent;
86:           
87:           protected virtual void OnProgressMessageEvent(string message)
88:           {
89:               if (ProgressMessageEvent != null) {
90:                   ProgressMessageEvent(thismessage);
91:               }
92:           }
93:           
94:           protected TarArchive()
95:           {
96:           }
97:           
98:           /// <summary>
99:           /// The InputStream based constructors create a TarArchive for the
100:           /// purposes of e'x'tracting or lis't'ing a tar archive. Thus, use
101:           /// these constructors when you wish to extract files from or list
102:           /// the contents of an existing tar archive.
103:           /// </summary>
104:           public static TarArchive CreateInputTarArchive(Stream inputStream)
105:           {
106:               return CreateInputTarArchive(inputStreamTarBuffer.DEFAULT_BLKSIZE);
107:           }
108:           
109:           public static TarArchive CreateInputTarArchive(Stream inputStreamint blockSize)
110:           {
111:               return CreateInputTarArchive(inputStreamblockSizeTarBuffer.DEFAULT_RCDSIZE);
112:           }
113:           
114:           public static TarArchive CreateInputTarArchive(Stream inputStreamint blockSizeint recordSize)
115:           {
116:               TarArchive archive new TarArchive();
117:               archive.tarIn new TarInputStream(inputStreamblockSizerecordSize);
118:               archive.Initialize(recordSize);
119:               return archive;
120:           }
121:           
122:           /// <summary>
123:           /// The OutputStream based constructors create a TarArchive for the
124:           /// purposes of 'c'reating a tar archive. Thus, use these constructors
125:           /// when you wish to create a new tar archive and write files into it.
126:           /// </summary>
127:           public static TarArchive CreateOutputTarArchive(Stream outputStream)
128:           {
129:               return CreateOutputTarArchive(outputStreamTarBuffer.DEFAULT_BLKSIZE);
130:           }
131:           
132:           public static TarArchive CreateOutputTarArchive(Stream outputStreamint blockSize)
133:           {
134:               return CreateOutputTarArchive(outputStreamblockSizeTarBuffer.DEFAULT_RCDSIZE);
135:           }
136:           
137:           public static TarArchive CreateOutputTarArchive(Stream outputStreamint blockSizeint recordSize)
138:           {
139:               TarArchive archive new TarArchive();
140:               archive.tarOut new TarOutputStream(outputStreamblockSizerecordSize);
141:               archive.Initialize(recordSize);
142:               return archive;
143:           }
144:           
145:           
146:           /// <summary>
147:           /// Common constructor initialization code.
148:           /// </summary>
149:           void Initialize(int recordSize)
150:           {
151:               this.rootPath   null;
152:               this.pathPrefix null;
153:               
154:   //            this.tempPath   = System.getProperty( "user.dir" );
155:               
156:               this.userId    0;
157:               this.userName  String.Empty;
158:               this.groupId   0;
159:               this.groupName String.Empty;
160:               
161:               this.debug           false;
162:               this.verbose         false;
163:               this.keepOldFiles    false;
164:               
165:               this.recordBuf new byte[RecordSize];
166:           }
167:           
168:           /**
169:           * Set the debugging flag.
170:           *
171:           * @param debugF The new debug setting.
172:           */
173:           public void SetDebug(bool debugF)
174:           {
175:               this.debug debugF;
176:               if (this.tarIn != null) {
177:                   this.tarIn.SetDebug(debugF);
178:               
179:               if (this.tarOut != null) {
180:                   this.tarOut.SetDebug(debugF);
181:               }
182:           }
183:           
184:           /// <summary>
185:           /// Get/Set the verbosity setting.
186:           /// </summary>
187:           public bool IsVerbose {
188:               get {
189:                   return verbose;
190:               }
191:               set {
192:                   verbose value;
193:               }
194:           }
195:           
196:           /// <summary>
197:           /// Set the flag that determines whether existing files are
198:           /// kept, or overwritten during extraction.
199:           /// </summary>
200:           /// <param name="keepOldFiles">
201:           /// If true, do not overwrite existing files.
202:           /// </param>
203:           public void SetKeepOldFiles(bool keepOldFiles)
204:           {
205:               this.keepOldFiles keepOldFiles;
206:           }
207:           
208:           /// <summary>
209:           /// Set the ascii file translation flag. If ascii file translatio
210:           /// is true, then the MIME file type will be consulted to determine
211:           /// if the file is of type 'text/*'. If the MIME type is not found,
212:           /// then the TransFileTyper is consulted if it is not null. If
213:           /// either of these two checks indicates the file is an ascii text
214:           /// file, it will be translated. The translation converts the local
215:           /// operating system's concept of line ends into the UNIX line end,
216:           /// '\n', which is the defacto standard for a TAR archive. This makes
217:           /// text files compatible with UNIX, and since most tar implementations
218:           /// text files compatible with UNIX, and since most tar implementations
219:           /// </summary>
220:           /// <param name= "asciiTranslate">
221:           /// If true, translate ascii text files.
222:           /// </param>
223:           public void SetAsciiTranslation(bool asciiTranslate)
224:           {
225:               this.asciiTranslate asciiTranslate;
226:           }
227:           
228:           /*
229:           /// <summary>
230:           /// Set the object that will determine if a file is of type
231:           /// ascii text for translation purposes.
232:           /// </summary>
233:           /// <param name="transTyper">
234:           /// The new TransFileTyper object.
235:           /// </param>
236:           public void SetTransFileTyper(TarTransFileTyper transTyper)
237:           {
238:               this.transTyper = transTyper;
239:           }*/
240:           
241:           /// <summary>
242:           /// Set user and group information that will be used to fill in the
243:           /// tar archive's entry headers. Since Java currently provides no means
244:           /// of determining a user name, user id, group name, or group id for
245:           /// a given File, TarArchive allows the programmer to specify values
246:           /// to be used in their place.
247:           /// </summary>
248:           /// <param name="userId">
249:           /// The user Id to use in the headers.
250:           /// </param>
251:           /// <param name="userName">
252:           /// The user name to use in the headers.
253:           /// </param>
254:           /// <param name="groupId">
255:           /// The group id to use in the headers.
256:           /// </param>
257:           /// <param name="groupName">
258:           /// The group name to use in the headers.
259:           /// </param>
260:           public void SetUserInfo(int userIdstring userNameint groupIdstring groupName)
261:           {
262:               this.userId    userId;
263:               this.userName  userName;
264:               this.groupId   groupId;
265:               this.groupName groupName;
266:           }
267:           
268:           /// <summary>
269:           /// Get the user id being used for archive entry headers.
270:           /// </summary>
271:           /// <returns>
272:           /// The current user id.
273:           /// </returns>
274:           public int UserId {
275:               get {
276:                   return this.userId;
277:               }
278:           }
279:           
280:           /// <summary>
281:           /// Get the user name being used for archive entry headers.
282:           /// </summary>
283:           /// <returns>
284:           /// The current user name.
285:           /// </returns>
286:           public string UserName {
287:               get {
288:                   return this.userName;
289:               }
290:           }
291:           
292:           /// <summary>
293:           /// Get the group id being used for archive entry headers.
294:           /// </summary>
295:           /// <returns>
296:           /// The current group id.
297:           /// </returns>
298:           public int GroupId {
299:               get {
300:                   return this.groupId;
301:               }
302:           }
303:           
304:           /// <summary>
305:           /// Get the group name being used for archive entry headers.
306:           /// </summary>
307:           /// <returns>
308:           /// The current group name.
309:           /// </returns>
310:           public string GroupName {
311:               get {
312:                   return this.groupName;
313:               }
314:           }
315:           
316:           /// <summary>
317:           /// Get the archive's record size. Because of its history, tar
318:           /// supports the concept of buffered IO consisting of BLOCKS of
319:           /// RECORDS. This allowed tar to match the IO characteristics of
320:           /// the physical device being used. Of course, in the Java world,
321:           /// this makes no sense, WITH ONE EXCEPTION - archives are expected
322:           /// to be propertly "blocked". Thus, all of the horrible TarBuffer
323:           /// support boils down to simply getting the "boundaries" correct.
324:           /// </summary>
325:           /// <returns>
326:           /// The record size this archive is using.
327:           /// </returns>
328:           public int RecordSize {
329:               get {
330:                   if (this.tarIn != null) {
331:                       return this.tarIn.GetRecordSize();
332:                   else if (this.tarOut != null) {
333:                       return this.tarOut.GetRecordSize();
334:                   }
335:                   return TarBuffer.DEFAULT_RCDSIZE;
336:               }
337:           }
338:           
339:           /// <summary>
340:           /// Close the archive. This simply calls the underlying
341:           /// tar stream's close() method.
342:           /// </summary>
343:           public void CloseArchive()
344:           {
345:               if (this.tarIn != null) {
346:                   this.tarIn.Close();
347:               else if (this.tarOut != null) {
348:                   this.tarOut.Flush();
349:                   this.tarOut.Close();
350:               }
351:           }
352:           
353:           /// <summary>
354:           /// Perform the "list" command and list the contents of the archive.
355:           /// 
356:           /// NOTE That this method uses the progress display to actually list
357:           /// the conents. If the progress display is not set, nothing will be
358:           /// listed!
359:           /// </summary>
360:           public void ListContents()
361:           {
362:               while (true) {
363:                   TarEntry entry this.tarIn.GetNextEntry();
364:                   
365:                   if (entry == null) {
366:                       if (this.debug) {
367:                           Console.Error.WriteLine("READ EOF RECORD");
368:                       }
369:                       break;
370:                   }
371:                   OnProgressMessageEvent(entry.Name);
372:               }
373:           }
374:           
375:           /// <summary>
376:           /// Perform the "extract" command and extract the contents of the archive.
377:           /// </summary>
378:           /// <param name="destDir">
379:           /// The destination directory into which to extract.
380:           /// </param>
381:           public void ExtractContents(string destDir)
382:           {
383:               while (true) {
384:                   TarEntry entry this.tarIn.GetNextEntry();
385:                   
386:                   if (entry == null) {
387:                       if (this.debug) {
388:                           Console.Error.WriteLine("READ EOF RECORD");
389:                       }
390:                       break;
391:                   }
392:                   
393:                   this.ExtractEntry(destDirentry);
394:               }
395:           }
396:           
397:           void EnsureDirectoryExists(string directoryName)
398:           {
399:               if (!Directory.Exists(directoryName)) {
400:                   try {
401:                       Directory.CreateDirectory(directoryName);
402:                   catch (Exception e) {
403:                       throw new IOException("error making directory path '" directoryName "', " e.Message);
404:                   }
405:               }
406:           }
407:           
408:           
409:           bool IsBinary(string filename)
410:           {
411:               FileStream fs File.OpenRead(filename);
412:            &nb