001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: /*
020: * This package is based on the work done by Timothy Gerard Endres
021: * (time@ice.com) to whom the Ant project is very grateful for his great code.
022: */
023:
024: package org.apache.tools.tar;
025:
026: import java.io.File;
027: import java.util.Date;
028: import java.util.Locale;
029:
030: /**
031: * This class represents an entry in a Tar archive. It consists
032: * of the entry's header, as well as the entry's File. Entries
033: * can be instantiated in one of three ways, depending on how
034: * they are to be used.
035: * <p>
036: * TarEntries that are created from the header bytes read from
037: * an archive are instantiated with the TarEntry( byte[] )
038: * constructor. These entries will be used when extracting from
039: * or listing the contents of an archive. These entries have their
040: * header filled in using the header bytes. They also set the File
041: * to null, since they reference an archive entry not a file.
042: * <p>
043: * TarEntries that are created from Files that are to be written
044: * into an archive are instantiated with the TarEntry( File )
045: * constructor. These entries have their header filled in using
046: * the File's information. They also keep a reference to the File
047: * for convenience when writing entries.
048: * <p>
049: * Finally, TarEntries can be constructed from nothing but a name.
050: * This allows the programmer to construct the entry by hand, for
051: * instance when only an InputStream is available for writing to
052: * the archive, and the header information is constructed from
053: * other information. In this case the header fields are set to
054: * defaults and the File is set to null.
055: *
056: * <p>
057: * The C structure for a Tar Entry's header is:
058: * <pre>
059: * struct header {
060: * char name[NAMSIZ];
061: * char mode[8];
062: * char uid[8];
063: * char gid[8];
064: * char size[12];
065: * char mtime[12];
066: * char chksum[8];
067: * char linkflag;
068: * char linkname[NAMSIZ];
069: * char magic[8];
070: * char uname[TUNMLEN];
071: * char gname[TGNMLEN];
072: * char devmajor[8];
073: * char devminor[8];
074: * } header;
075: * </pre>
076: *
077: */
078:
079: public class TarEntry implements TarConstants {
080: /** The entry's name. */
081: private StringBuffer name;
082:
083: /** The entry's permission mode. */
084: private int mode;
085:
086: /** The entry's user id. */
087: private int userId;
088:
089: /** The entry's group id. */
090: private int groupId;
091:
092: /** The entry's size. */
093: private long size;
094:
095: /** The entry's modification time. */
096: private long modTime;
097:
098: /** The entry's link flag. */
099: private byte linkFlag;
100:
101: /** The entry's link name. */
102: private StringBuffer linkName;
103:
104: /** The entry's magic tag. */
105: private StringBuffer magic;
106:
107: /** The entry's user name. */
108: private StringBuffer userName;
109:
110: /** The entry's group name. */
111: private StringBuffer groupName;
112:
113: /** The entry's major device number. */
114: private int devMajor;
115:
116: /** The entry's minor device number. */
117: private int devMinor;
118:
119: /** The entry's file reference */
120: private File file;
121:
122: /** Maximum length of a user's name in the tar file */
123: public static final int MAX_NAMELEN = 31;
124:
125: /** Default permissions bits for directories */
126: public static final int DEFAULT_DIR_MODE = 040755;
127:
128: /** Default permissions bits for files */
129: public static final int DEFAULT_FILE_MODE = 0100644;
130:
131: /** Convert millis to seconds */
132: public static final int MILLIS_PER_SECOND = 1000;
133:
134: /**
135: * Construct an empty entry and prepares the header values.
136: */
137: private TarEntry() {
138: this .magic = new StringBuffer(TMAGIC);
139: this .name = new StringBuffer();
140: this .linkName = new StringBuffer();
141:
142: String user = System.getProperty("user.name", "");
143:
144: if (user.length() > MAX_NAMELEN) {
145: user = user.substring(0, MAX_NAMELEN);
146: }
147:
148: this .userId = 0;
149: this .groupId = 0;
150: this .userName = new StringBuffer(user);
151: this .groupName = new StringBuffer("");
152: this .file = null;
153: }
154:
155: /**
156: * Construct an entry with only a name. This allows the programmer
157: * to construct the entry's header "by hand". File is set to null.
158: *
159: * @param name the entry name
160: */
161: public TarEntry(String name) {
162: this ();
163:
164: boolean isDir = name.endsWith("/");
165:
166: this .devMajor = 0;
167: this .devMinor = 0;
168: this .name = new StringBuffer(name);
169: this .mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
170: this .linkFlag = isDir ? LF_DIR : LF_NORMAL;
171: this .userId = 0;
172: this .groupId = 0;
173: this .size = 0;
174: this .modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
175: this .linkName = new StringBuffer("");
176: this .userName = new StringBuffer("");
177: this .groupName = new StringBuffer("");
178: this .devMajor = 0;
179: this .devMinor = 0;
180:
181: }
182:
183: /**
184: * Construct an entry with a name an a link flag.
185: *
186: * @param name the entry name
187: * @param linkFlag the entry link flag.
188: */
189: public TarEntry(String name, byte linkFlag) {
190: this (name);
191: this .linkFlag = linkFlag;
192: }
193:
194: /**
195: * Construct an entry for a file. File is set to file, and the
196: * header is constructed from information from the file.
197: *
198: * @param file The file that the entry represents.
199: */
200: public TarEntry(File file) {
201: this ();
202:
203: this .file = file;
204:
205: String fileName = file.getPath();
206: String osname = System.getProperty("os.name").toLowerCase(
207: Locale.US);
208:
209: if (osname != null) {
210:
211: // Strip off drive letters!
212: // REVIEW Would a better check be "(File.separator == '\')"?
213:
214: if (osname.startsWith("windows")) {
215: if (fileName.length() > 2) {
216: char ch1 = fileName.charAt(0);
217: char ch2 = fileName.charAt(1);
218:
219: if (ch2 == ':'
220: && ((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z'))) {
221: fileName = fileName.substring(2);
222: }
223: }
224: } else if (osname.indexOf("netware") > -1) {
225: int colon = fileName.indexOf(':');
226: if (colon != -1) {
227: fileName = fileName.substring(colon + 1);
228: }
229: }
230: }
231:
232: fileName = fileName.replace(File.separatorChar, '/');
233:
234: // No absolute pathnames
235: // Windows (and Posix?) paths can start with "\\NetworkDrive\",
236: // so we loop on starting /'s.
237: while (fileName.startsWith("/")) {
238: fileName = fileName.substring(1);
239: }
240:
241: this .linkName = new StringBuffer("");
242: this .name = new StringBuffer(fileName);
243:
244: if (file.isDirectory()) {
245: this .mode = DEFAULT_DIR_MODE;
246: this .linkFlag = LF_DIR;
247:
248: if (this .name.charAt(this .name.length() - 1) != '/') {
249: this .name.append("/");
250: }
251: } else {
252: this .mode = DEFAULT_FILE_MODE;
253: this .linkFlag = LF_NORMAL;
254: }
255:
256: this .size = file.length();
257: this .modTime = file.lastModified() / MILLIS_PER_SECOND;
258: this .devMajor = 0;
259: this .devMinor = 0;
260: }
261:
262: /**
263: * Construct an entry from an archive's header bytes. File is set
264: * to null.
265: *
266: * @param headerBuf The header bytes from a tar archive entry.
267: */
268: public TarEntry(byte[] headerBuf) {
269: this ();
270: this .parseTarHeader(headerBuf);
271: }
272:
273: /**
274: * Determine if the two entries are equal. Equality is determined
275: * by the header names being equal.
276: *
277: * @param it Entry to be checked for equality.
278: * @return True if the entries are equal.
279: */
280: public boolean equals(TarEntry it) {
281: return this .getName().equals(it.getName());
282: }
283:
284: /**
285: * Determine if the two entries are equal. Equality is determined
286: * by the header names being equal.
287: *
288: * @param it Entry to be checked for equality.
289: * @return True if the entries are equal.
290: */
291: public boolean equals(Object it) {
292: if (it == null || getClass() != it.getClass()) {
293: return false;
294: }
295: return equals((TarEntry) it);
296: }
297:
298: /**
299: * Hashcodes are based on entry names.
300: *
301: * @return the entry hashcode
302: */
303: public int hashCode() {
304: return getName().hashCode();
305: }
306:
307: /**
308: * Determine if the given entry is a descendant of this entry.
309: * Descendancy is determined by the name of the descendant
310: * starting with this entry's name.
311: *
312: * @param desc Entry to be checked as a descendent of this.
313: * @return True if entry is a descendant of this.
314: */
315: public boolean isDescendent(TarEntry desc) {
316: return desc.getName().startsWith(this .getName());
317: }
318:
319: /**
320: * Get this entry's name.
321: *
322: * @return This entry's name.
323: */
324: public String getName() {
325: return this .name.toString();
326: }
327:
328: /**
329: * Set this entry's name.
330: *
331: * @param name This entry's new name.
332: */
333: public void setName(String name) {
334: this .name = new StringBuffer(name);
335: }
336:
337: /**
338: * Set the mode for this entry
339: *
340: * @param mode the mode for this entry
341: */
342: public void setMode(int mode) {
343: this .mode = mode;
344: }
345:
346: /**
347: * Get this entry's link name.
348: *
349: * @return This entry's link name.
350: */
351: public String getLinkName() {
352: return this .linkName.toString();
353: }
354:
355: /**
356: * Get this entry's user id.
357: *
358: * @return This entry's user id.
359: */
360: public int getUserId() {
361: return this .userId;
362: }
363:
364: /**
365: * Set this entry's user id.
366: *
367: * @param userId This entry's new user id.
368: */
369: public void setUserId(int userId) {
370: this .userId = userId;
371: }
372:
373: /**
374: * Get this entry's group id.
375: *
376: * @return This entry's group id.
377: */
378: public int getGroupId() {
379: return this .groupId;
380: }
381:
382: /**
383: * Set this entry's group id.
384: *
385: * @param groupId This entry's new group id.
386: */
387: public void setGroupId(int groupId) {
388: this .groupId = groupId;
389: }
390:
391: /**
392: * Get this entry's user name.
393: *
394: * @return This entry's user name.
395: */
396: public String getUserName() {
397: return this .userName.toString();
398: }
399:
400: /**
401: * Set this entry's user name.
402: *
403: * @param userName This entry's new user name.
404: */
405: public void setUserName(String userName) {
406: this .userName = new StringBuffer(userName);
407: }
408:
409: /**
410: * Get this entry's group name.
411: *
412: * @return This entry's group name.
413: */
414: public String getGroupName() {
415: return this .groupName.toString();
416: }
417:
418: /**
419: * Set this entry's group name.
420: *
421: * @param groupName This entry's new group name.
422: */
423: public void setGroupName(String groupName) {
424: this .groupName = new StringBuffer(groupName);
425: }
426:
427: /**
428: * Convenience method to set this entry's group and user ids.
429: *
430: * @param userId This entry's new user id.
431: * @param groupId This entry's new group id.
432: */
433: public void setIds(int userId, int groupId) {
434: this .setUserId(userId);
435: this .setGroupId(groupId);
436: }
437:
438: /**
439: * Convenience method to set this entry's group and user names.
440: *
441: * @param userName This entry's new user name.
442: * @param groupName This entry's new group name.
443: */
444: public void setNames(String userName, String groupName) {
445: this .setUserName(userName);
446: this .setGroupName(groupName);
447: }
448:
449: /**
450: * Set this entry's modification time. The parameter passed
451: * to this method is in "Java time".
452: *
453: * @param time This entry's new modification time.
454: */
455: public void setModTime(long time) {
456: this .modTime = time / MILLIS_PER_SECOND;
457: }
458:
459: /**
460: * Set this entry's modification time.
461: *
462: * @param time This entry's new modification time.
463: */
464: public void setModTime(Date time) {
465: this .modTime = time.getTime() / MILLIS_PER_SECOND;
466: }
467:
468: /**
469: * Set this entry's modification time.
470: *
471: * @return time This entry's new modification time.
472: */
473: public Date getModTime() {
474: return new Date(this .modTime * MILLIS_PER_SECOND);
475: }
476:
477: /**
478: * Get this entry's file.
479: *
480: * @return This entry's file.
481: */
482: public File getFile() {
483: return this .file;
484: }
485:
486: /**
487: * Get this entry's mode.
488: *
489: * @return This entry's mode.
490: */
491: public int getMode() {
492: return this .mode;
493: }
494:
495: /**
496: * Get this entry's file size.
497: *
498: * @return This entry's file size.
499: */
500: public long getSize() {
501: return this .size;
502: }
503:
504: /**
505: * Set this entry's file size.
506: *
507: * @param size This entry's new file size.
508: */
509: public void setSize(long size) {
510: this .size = size;
511: }
512:
513: /**
514: * Indicate if this entry is a GNU long name block
515: *
516: * @return true if this is a long name extension provided by GNU tar
517: */
518: public boolean isGNULongNameEntry() {
519: return linkFlag == LF_GNUTYPE_LONGNAME
520: && name.toString().equals(GNU_LONGLINK);
521: }
522:
523: /**
524: * Return whether or not this entry represents a directory.
525: *
526: * @return True if this entry is a directory.
527: */
528: public boolean isDirectory() {
529: if (this .file != null) {
530: return this .file.isDirectory();
531: }
532:
533: if (this .linkFlag == LF_DIR) {
534: return true;
535: }
536:
537: if (this .getName().endsWith("/")) {
538: return true;
539: }
540:
541: return false;
542: }
543:
544: /**
545: * If this entry represents a file, and the file is a directory, return
546: * an array of TarEntries for this entry's children.
547: *
548: * @return An array of TarEntry's for this entry's children.
549: */
550: public TarEntry[] getDirectoryEntries() {
551: if (this .file == null || !this .file.isDirectory()) {
552: return new TarEntry[0];
553: }
554:
555: String[] list = this .file.list();
556: TarEntry[] result = new TarEntry[list.length];
557:
558: for (int i = 0; i < list.length; ++i) {
559: result[i] = new TarEntry(new File(this .file, list[i]));
560: }
561:
562: return result;
563: }
564:
565: /**
566: * Write an entry's header information to a header buffer.
567: *
568: * @param outbuf The tar entry header buffer to fill in.
569: */
570: public void writeEntryHeader(byte[] outbuf) {
571: int offset = 0;
572:
573: offset = TarUtils.getNameBytes(this .name, outbuf, offset,
574: NAMELEN);
575: offset = TarUtils.getOctalBytes(this .mode, outbuf, offset,
576: MODELEN);
577: offset = TarUtils.getOctalBytes(this .userId, outbuf, offset,
578: UIDLEN);
579: offset = TarUtils.getOctalBytes(this .groupId, outbuf, offset,
580: GIDLEN);
581: offset = TarUtils.getLongOctalBytes(this .size, outbuf, offset,
582: SIZELEN);
583: offset = TarUtils.getLongOctalBytes(this .modTime, outbuf,
584: offset, MODTIMELEN);
585:
586: int csOffset = offset;
587:
588: for (int c = 0; c < CHKSUMLEN; ++c) {
589: outbuf[offset++] = (byte) ' ';
590: }
591:
592: outbuf[offset++] = this .linkFlag;
593: offset = TarUtils.getNameBytes(this .linkName, outbuf, offset,
594: NAMELEN);
595: offset = TarUtils.getNameBytes(this .magic, outbuf, offset,
596: MAGICLEN);
597: offset = TarUtils.getNameBytes(this .userName, outbuf, offset,
598: UNAMELEN);
599: offset = TarUtils.getNameBytes(this .groupName, outbuf, offset,
600: GNAMELEN);
601: offset = TarUtils.getOctalBytes(this .devMajor, outbuf, offset,
602: DEVLEN);
603: offset = TarUtils.getOctalBytes(this .devMinor, outbuf, offset,
604: DEVLEN);
605:
606: while (offset < outbuf.length) {
607: outbuf[offset++] = 0;
608: }
609:
610: long chk = TarUtils.computeCheckSum(outbuf);
611:
612: TarUtils
613: .getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
614: }
615:
616: /**
617: * Parse an entry's header information from a header buffer.
618: *
619: * @param header The tar entry header buffer to get information from.
620: */
621: public void parseTarHeader(byte[] header) {
622: int offset = 0;
623:
624: this .name = TarUtils.parseName(header, offset, NAMELEN);
625: offset += NAMELEN;
626: this .mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
627: offset += MODELEN;
628: this .userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
629: offset += UIDLEN;
630: this .groupId = (int) TarUtils
631: .parseOctal(header, offset, GIDLEN);
632: offset += GIDLEN;
633: this .size = TarUtils.parseOctal(header, offset, SIZELEN);
634: offset += SIZELEN;
635: this .modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
636: offset += MODTIMELEN;
637: offset += CHKSUMLEN;
638: this .linkFlag = header[offset++];
639: this .linkName = TarUtils.parseName(header, offset, NAMELEN);
640: offset += NAMELEN;
641: this .magic = TarUtils.parseName(header, offset, MAGICLEN);
642: offset += MAGICLEN;
643: this .userName = TarUtils.parseName(header, offset, UNAMELEN);
644: offset += UNAMELEN;
645: this .groupName = TarUtils.parseName(header, offset, GNAMELEN);
646: offset += GNAMELEN;
647: this .devMajor = (int) TarUtils.parseOctal(header, offset,
648: DEVLEN);
649: offset += DEVLEN;
650: this .devMinor = (int) TarUtils.parseOctal(header, offset,
651: DEVLEN);
652: }
653: }
|