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