View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs.provider.tar;
18  
19  import java.io.File;
20  import java.util.Date;
21  import java.util.Locale;
22  
23  /***
24   * This class represents an entry in a Tar archive. It consists of the entry's
25   * header, as well as the entry's File. Entries can be instantiated in one of
26   * three ways, depending on how they are to be used. <p>
27   *
28   * TarEntries that are created from the header bytes read from an archive are
29   * instantiated with the TarEntry( byte[] ) constructor. These entries will be
30   * used when extracting from or listing the contents of an archive. These
31   * entries have their header filled in using the header bytes. They also set the
32   * File to null, since they reference an archive entry not a file. <p>
33   *
34   * TarEntries that are created from Files that are to be written into an archive
35   * are instantiated with the TarEntry( File ) constructor. These entries have
36   * their header filled in using the File's information. They also keep a
37   * reference to the File for convenience when writing entries. <p>
38   *
39   * Finally, TarEntries can be constructed from nothing but a name. This allows
40   * the programmer to construct the entry by hand, for instance when only an
41   * InputStream is available for writing to the archive, and the header
42   * information is constructed from other information. In this case the header
43   * fields are set to defaults and the File is set to null. <p>
44   *
45   * The C structure for a Tar Entry's header is: <pre>
46   * struct header {
47   * char name[NAMSIZ];
48   * char mode[8];
49   * char uid[8];
50   * char gid[8];
51   * char size[12];
52   * char mtime[12];
53   * char chksum[8];
54   * char linkflag;
55   * char linkname[NAMSIZ];
56   * char magic[8];
57   * char uname[TUNMLEN];
58   * char gname[TGNMLEN];
59   * char devmajor[8];
60   * char devminor[8];
61   * } header;
62   * </pre>
63   *
64   * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
65   * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
66   * @author <a href="mailto:peter@apache.org">Peter Donald</a>
67   * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
68   * @see TarInputStream
69   * @see TarOutputStream
70   */
71  class TarEntry
72  {
73      /***
74       * The length of the name field in a header buffer.
75       */
76      public static final int NAMELEN = 100;
77  
78      /***
79       * The entry's modification time.
80       */
81      private int m_checkSum;
82  
83      /***
84       * The entry's group name.
85       */
86      private int m_devMajor;
87  
88      /***
89       * The entry's major device number.
90       */
91      private int m_devMinor;
92  
93      /***
94       * The entry's minor device number.
95       */
96      private File m_file;
97  
98      /***
99       * The entry's user id.
100      */
101     private int m_groupID;
102 
103     /***
104      * The entry's user name.
105      */
106     private StringBuffer m_groupName;
107 
108     /***
109      * The entry's checksum.
110      */
111     private byte m_linkFlag;
112 
113     /***
114      * The entry's link flag.
115      */
116     private StringBuffer m_linkName;
117 
118     /***
119      * The entry's link name.
120      */
121     private StringBuffer m_magic;
122 
123     /***
124      * The entry's size.
125      */
126     private long m_modTime;
127 
128     /***
129      * The entry's name.
130      */
131     private int m_mode;
132 
133     private StringBuffer m_name;
134 
135     /***
136      * The entry's group id.
137      */
138     private long m_size;
139 
140     /***
141      * The entry's permission mode.
142      */
143     private int m_userID;
144 
145     /***
146      * The entry's magic tag.
147      */
148     private StringBuffer m_userName;
149 
150     /***
151      * Construct an entry with only a name. This allows the programmer to
152      * construct the entry's header "by hand". File is set to null.
153      *
154      * @param name the name of the entry
155      */
156     TarEntry( final String name )
157     {
158         this();
159 
160         final boolean isDir = name.endsWith( "/" );
161 
162         m_name = new StringBuffer( name );
163         m_mode = isDir ? 040755 : 0100644;
164         m_linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
165         m_modTime = ( new Date() ).getTime() / 1000;
166         m_linkName = new StringBuffer( "" );
167         m_userName = new StringBuffer( "" );
168         m_groupName = new StringBuffer( "" );
169     }
170 
171     /***
172      * Construct an entry with a name an a link flag.
173      *
174      * @param name Description of Parameter
175      * @param linkFlag Description of Parameter
176      */
177     TarEntry( final String name, final byte linkFlag )
178     {
179         this( name );
180         m_linkFlag = linkFlag;
181     }
182 
183     /***
184      * Construct an entry for a file. File is set to file, and the header is
185      * constructed from information from the file.
186      *
187      * @param file The file that the entry represents.
188      */
189     TarEntry( final File file )
190     {
191         this();
192 
193         m_file = file;
194 
195         String name = file.getPath();
196 
197         // Strip off drive letters!
198         final String osName =
199             System.getProperty( "os.name" ).toLowerCase( Locale.US );
200         if( -1 != osName.indexOf( "netware" ) )
201         {
202             if( name.length() > 2 )
203             {
204                 final char ch1 = name.charAt( 0 );
205                 final char ch2 = name.charAt( 1 );
206 
207                 if( ch2 == ':' &&
208                     ( ( ch1 >= 'a' && ch1 <= 'z' ) ||
209                     ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
210                 {
211                     name = name.substring( 2 );
212                 }
213             }
214         }
215         else if( -1 != osName.indexOf( "netware" ) )
216         {
217             final int colon = name.indexOf( ':' );
218             if( colon != -1 )
219             {
220                 name = name.substring( colon + 1 );
221             }
222         }
223 
224         name = name.replace( File.separatorChar, '/' );
225 
226         // No absolute pathnames
227         // Windows (and Posix?) paths can start with "//NetworkDrive\",
228         // so we loop on starting /'s.
229         while( name.startsWith( "/" ) )
230         {
231             name = name.substring( 1 );
232         }
233 
234         m_linkName = new StringBuffer( "" );
235         m_name = new StringBuffer( name );
236 
237         if( file.isDirectory() )
238         {
239             m_mode = 040755;
240             m_linkFlag = TarConstants.LF_DIR;
241 
242             if( m_name.charAt( m_name.length() - 1 ) != '/' )
243             {
244                 m_name.append( "/" );
245             }
246         }
247         else
248         {
249             m_mode = 0100644;
250             m_linkFlag = TarConstants.LF_NORMAL;
251         }
252 
253         m_size = file.length();
254         m_modTime = file.lastModified() / 1000;
255         m_checkSum = 0;
256         m_devMajor = 0;
257         m_devMinor = 0;
258     }
259 
260     /***
261      * Construct an entry from an archive's header bytes. File is set to null.
262      *
263      * @param header The header bytes from a tar archive entry.
264      */
265     TarEntry( final byte[] header )
266     {
267         this();
268         parseTarHeader( header );
269     }
270 
271     /***
272      * Construct an empty entry and prepares the header values.
273      */
274     private TarEntry()
275     {
276         m_magic = new StringBuffer( TarConstants.TMAGIC );
277         m_name = new StringBuffer();
278         m_linkName = new StringBuffer();
279 
280         String user = System.getProperty( "user.name", "" );
281         if( user.length() > 31 )
282         {
283             user = user.substring( 0, 31 );
284         }
285 
286         m_userName = new StringBuffer( user );
287         m_groupName = new StringBuffer( "" );
288     }
289 
290     /***
291      * Set this entry's group id.
292      *
293      * @param groupId This entry's new group id.
294      */
295     public void setGroupID( final int groupId )
296     {
297         m_groupID = groupId;
298     }
299 
300     /***
301      * Set this entry's group id.
302      *
303      * @param groupId This entry's new group id.
304      * @deprecated Use setGroupID() instead
305      * @see #setGroupID(int)
306      */
307     public void setGroupId( final int groupId )
308     {
309         m_groupID = groupId;
310     }
311 
312     /***
313      * Set this entry's group name.
314      *
315      * @param groupName This entry's new group name.
316      */
317     public void setGroupName( final String groupName )
318     {
319         m_groupName = new StringBuffer( groupName );
320     }
321 
322     /***
323      * Set this entry's modification time. The parameter passed to this method
324      * is in "Java time".
325      *
326      * @param time This entry's new modification time.
327      */
328     public void setModTime( final long time )
329     {
330         m_modTime = time / 1000;
331     }
332 
333     /***
334      * Set this entry's modification time.
335      *
336      * @param time This entry's new modification time.
337      */
338     public void setModTime( final Date time )
339     {
340         m_modTime = time.getTime() / 1000;
341     }
342 
343     /***
344      * Set the mode for this entry
345      *
346      * @param mode The new Mode value
347      */
348     public void setMode( final int mode )
349     {
350         m_mode = mode;
351     }
352 
353     /***
354      * Set this entry's name.
355      *
356      * @param name This entry's new name.
357      */
358     public void setName( final String name )
359     {
360         m_name = new StringBuffer( name );
361     }
362 
363     /***
364      * Set this entry's file size.
365      *
366      * @param size This entry's new file size.
367      */
368     public void setSize( final long size )
369     {
370         m_size = size;
371     }
372 
373     /***
374      * Set this entry's user id.
375      *
376      * @param userId This entry's new user id.
377      */
378     public void setUserID( final int userId )
379     {
380         m_userID = userId;
381     }
382 
383     /***
384      * Set this entry's user id.
385      *
386      * @param userId This entry's new user id.
387      * @deprecated Use setUserID() instead
388      * @see #setUserID(int)
389      */
390     public void setUserId( final int userId )
391     {
392         m_userID = userId;
393     }
394 
395     /***
396      * Set this entry's user name.
397      *
398      * @param userName This entry's new user name.
399      */
400     public void setUserName( final String userName )
401     {
402         m_userName = new StringBuffer( userName );
403     }
404 
405     /***
406      * If this entry represents a file, and the file is a directory, return an
407      * array of TarEntries for this entry's children.
408      *
409      * @return An array of TarEntry's for this entry's children.
410      */
411     public TarEntry[] getDirectoryEntries()
412     {
413         if( null == m_file || !m_file.isDirectory() )
414         {
415             return new TarEntry[ 0 ];
416         }
417 
418         final String[] list = m_file.list();
419         final TarEntry[] result = new TarEntry[ list.length ];
420 
421         for( int i = 0; i < list.length; ++i )
422         {
423             result[ i ] = new TarEntry( new File( m_file, list[ i ] ) );
424         }
425 
426         return result;
427     }
428 
429     /***
430      * Get this entry's file.
431      *
432      * @return This entry's file.
433      */
434     public File getFile()
435     {
436         return m_file;
437     }
438 
439     /***
440      * Get this entry's group id.
441      *
442      * @return This entry's group id.
443      * @deprecated Use getGroupID() instead
444      * @see #getGroupID()
445      */
446     public int getGroupId()
447     {
448         return m_groupID;
449     }
450 
451     /***
452      * Get this entry's group id.
453      *
454      * @return This entry's group id.
455      */
456     public int getGroupID()
457     {
458         return m_groupID;
459     }
460 
461     /***
462      * Get this entry's group name.
463      *
464      * @return This entry's group name.
465      */
466     public String getGroupName()
467     {
468         return m_groupName.toString();
469     }
470 
471     /***
472      * Set this entry's modification time.
473      *
474      * @return The ModTime value
475      */
476     public Date getModTime()
477     {
478         return new Date( m_modTime * 1000 );
479     }
480 
481     /***
482      * Get this entry's mode.
483      *
484      * @return This entry's mode.
485      */
486     public int getMode()
487     {
488         return m_mode;
489     }
490 
491     /***
492      * Get this entry's name.
493      *
494      * @return This entry's name.
495      */
496     public String getName()
497     {
498         return m_name.toString();
499     }
500 
501     /***
502      * Get this entry's file size.
503      *
504      * @return This entry's file size.
505      */
506     public long getSize()
507     {
508         return m_size;
509     }
510 
511     /***
512      * Get this entry's checksum.
513      *
514      * @return This entry's checksum.
515      */
516     public int getCheckSum()
517     {
518         return m_checkSum;
519     }
520 
521     /***
522      * Get this entry's user id.
523      *
524      * @return This entry's user id.
525      * @deprecated Use getUserID() instead
526      * @see #getUserID()
527      */
528     public int getUserId()
529     {
530         return m_userID;
531     }
532 
533     /***
534      * Get this entry's user id.
535      *
536      * @return This entry's user id.
537      */
538     public int getUserID()
539     {
540         return m_userID;
541     }
542 
543     /***
544      * Get this entry's user name.
545      *
546      * @return This entry's user name.
547      */
548     public String getUserName()
549     {
550         return m_userName.toString();
551     }
552 
553     /***
554      * Determine if the given entry is a descendant of this entry. Descendancy
555      * is determined by the name of the descendant starting with this entry's
556      * name.
557      *
558      * @param desc Entry to be checked as a descendent of
559      * @return True if entry is a descendant of
560      */
561     public boolean isDescendent( final TarEntry desc )
562     {
563         return desc.getName().startsWith( getName() );
564     }
565 
566     /***
567      * Return whether or not this entry represents a directory.
568      *
569      * @return True if this entry is a directory.
570      */
571     public boolean isDirectory()
572     {
573         if( m_file != null )
574         {
575             return m_file.isDirectory();
576         }
577 
578         if( m_linkFlag == TarConstants.LF_DIR )
579         {
580             return true;
581         }
582 
583         if( getName().endsWith( "/" ) )
584         {
585             return true;
586         }
587 
588         return false;
589     }
590 
591     /***
592      * Indicate if this entry is a GNU long name block
593      *
594      * @return true if this is a long name extension provided by GNU tar
595      */
596     public boolean isGNULongNameEntry()
597     {
598         return m_linkFlag == TarConstants.LF_GNUTYPE_LONGNAME &&
599             m_name.toString().equals( TarConstants.GNU_LONGLINK );
600     }
601 
602     /***
603      * Determine if the two entries are equal. Equality is determined by the
604      * header names being equal.
605      *
606      * @param other Entry to be checked for equality.
607      * @return True if the entries are equal.
608      */
609     public boolean equals( final TarEntry other )
610     {
611         return getName().equals( other.getName() );
612     }
613 
614     /***
615      * Parse an entry's header information from a header buffer.
616      *
617      * @param header The tar entry header buffer to get information from.
618      */
619     private void parseTarHeader( final byte[] header )
620     {
621         int offset = 0;
622 
623         m_name = TarUtils.parseName( header, offset, NAMELEN );
624         offset += NAMELEN;
625         m_mode = (int)TarUtils.parseOctal( header, offset, TarConstants.MODELEN );
626         offset += TarConstants.MODELEN;
627         m_userID = (int)TarUtils.parseOctal( header, offset, TarConstants.UIDLEN );
628         offset += TarConstants.UIDLEN;
629         m_groupID = (int)TarUtils.parseOctal( header, offset, TarConstants.GIDLEN );
630         offset += TarConstants.GIDLEN;
631         m_size = TarUtils.parseOctal( header, offset, TarConstants.SIZELEN );
632         offset += TarConstants.SIZELEN;
633         m_modTime = TarUtils.parseOctal( header, offset, TarConstants.MODTIMELEN );
634         offset += TarConstants.MODTIMELEN;
635         m_checkSum = (int)TarUtils.parseOctal( header, offset, TarConstants.CHKSUMLEN );
636         offset += TarConstants.CHKSUMLEN;
637         m_linkFlag = header[ offset++ ];
638         m_linkName = TarUtils.parseName( header, offset, NAMELEN );
639         offset += NAMELEN;
640         m_magic = TarUtils.parseName( header, offset, TarConstants.MAGICLEN );
641         offset += TarConstants.MAGICLEN;
642         m_userName = TarUtils.parseName( header, offset, TarConstants.UNAMELEN );
643         offset += TarConstants.UNAMELEN;
644         m_groupName = TarUtils.parseName( header, offset, TarConstants.GNAMELEN );
645         offset += TarConstants.GNAMELEN;
646         m_devMajor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
647         offset += TarConstants.DEVLEN;
648         m_devMinor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
649     }
650 
651     /***
652      * Write an entry's header information to a header buffer.
653      *
654      * @param buffer The tar entry header buffer to fill in.
655      */
656     public void writeEntryHeader( final byte[] buffer )
657     {
658         int offset = 0;
659 
660         offset = TarUtils.getNameBytes( m_name, buffer, offset, NAMELEN );
661         offset = TarUtils.getOctalBytes( m_mode, buffer, offset, TarConstants.MODELEN );
662         offset = TarUtils.getOctalBytes( m_userID, buffer, offset, TarConstants.UIDLEN );
663         offset = TarUtils.getOctalBytes( m_groupID, buffer, offset, TarConstants.GIDLEN );
664         offset = TarUtils.getLongOctalBytes( m_size, buffer, offset, TarConstants.SIZELEN );
665         offset = TarUtils.getLongOctalBytes( m_modTime, buffer, offset, TarConstants.MODTIMELEN );
666 
667         final int checkSumOffset = offset;
668         for( int i = 0; i < TarConstants.CHKSUMLEN; ++i )
669         {
670             buffer[ offset++ ] = (byte)' ';
671         }
672 
673         buffer[ offset++ ] = m_linkFlag;
674         offset = TarUtils.getNameBytes( m_linkName, buffer, offset, NAMELEN );
675         offset = TarUtils.getNameBytes( m_magic, buffer, offset, TarConstants.MAGICLEN );
676         offset = TarUtils.getNameBytes( m_userName, buffer, offset, TarConstants.UNAMELEN );
677         offset = TarUtils.getNameBytes( m_groupName, buffer, offset, TarConstants.GNAMELEN );
678         offset = TarUtils.getOctalBytes( m_devMajor, buffer, offset, TarConstants.DEVLEN );
679         offset = TarUtils.getOctalBytes( m_devMinor, buffer, offset, TarConstants.DEVLEN );
680 
681         while( offset < buffer.length )
682         {
683             buffer[ offset++ ] = 0;
684         }
685 
686         final long checkSum = TarUtils.computeCheckSum( buffer );
687         TarUtils.getCheckSumOctalBytes( checkSum, buffer, checkSumOffset, TarConstants.CHKSUMLEN );
688     }
689 }