1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. /*
  18. * This package is based on the work done by Timothy Gerard Endres
  19. * (time@ice.com) to whom the Ant project is very grateful for his great code.
  20. */
  21. package org.apache.tools.tar;
  22. import java.io.File;
  23. import java.util.Date;
  24. import java.util.Locale;
  25. /**
  26. * This class represents an entry in a Tar archive. It consists
  27. * of the entry's header, as well as the entry's File. Entries
  28. * can be instantiated in one of three ways, depending on how
  29. * they are to be used.
  30. * <p>
  31. * TarEntries that are created from the header bytes read from
  32. * an archive are instantiated with the TarEntry( byte[] )
  33. * constructor. These entries will be used when extracting from
  34. * or listing the contents of an archive. These entries have their
  35. * header filled in using the header bytes. They also set the File
  36. * to null, since they reference an archive entry not a file.
  37. * <p>
  38. * TarEntries that are created from Files that are to be written
  39. * into an archive are instantiated with the TarEntry( File )
  40. * constructor. These entries have their header filled in using
  41. * the File's information. They also keep a reference to the File
  42. * for convenience when writing entries.
  43. * <p>
  44. * Finally, TarEntries can be constructed from nothing but a name.
  45. * This allows the programmer to construct the entry by hand, for
  46. * instance when only an InputStream is available for writing to
  47. * the archive, and the header information is constructed from
  48. * other information. In this case the header fields are set to
  49. * defaults and the File is set to null.
  50. *
  51. * <p>
  52. * The C structure for a Tar Entry's header is:
  53. * <pre>
  54. * struct header {
  55. * char name[NAMSIZ];
  56. * char mode[8];
  57. * char uid[8];
  58. * char gid[8];
  59. * char size[12];
  60. * char mtime[12];
  61. * char chksum[8];
  62. * char linkflag;
  63. * char linkname[NAMSIZ];
  64. * char magic[8];
  65. * char uname[TUNMLEN];
  66. * char gname[TGNMLEN];
  67. * char devmajor[8];
  68. * char devminor[8];
  69. * } header;
  70. * </pre>
  71. *
  72. */
  73. public class TarEntry implements TarConstants {
  74. /** The entry's name. */
  75. private StringBuffer name;
  76. /** The entry's permission mode. */
  77. private int mode;
  78. /** The entry's user id. */
  79. private int userId;
  80. /** The entry's group id. */
  81. private int groupId;
  82. /** The entry's size. */
  83. private long size;
  84. /** The entry's modification time. */
  85. private long modTime;
  86. /** The entry's checksum. */
  87. private int checkSum;
  88. /** The entry's link flag. */
  89. private byte linkFlag;
  90. /** The entry's link name. */
  91. private StringBuffer linkName;
  92. /** The entry's magic tag. */
  93. private StringBuffer magic;
  94. /** The entry's user name. */
  95. private StringBuffer userName;
  96. /** The entry's group name. */
  97. private StringBuffer groupName;
  98. /** The entry's major device number. */
  99. private int devMajor;
  100. /** The entry's minor device number. */
  101. private int devMinor;
  102. /** The entry's file reference */
  103. private File file;
  104. /** Maximum length of a user's name in the tar file */
  105. public static final int MAX_NAMELEN = 31;
  106. /** Default permissions bits for directories */
  107. public static final int DEFAULT_DIR_MODE = 040755;
  108. /** Default permissions bits for files */
  109. public static final int DEFAULT_FILE_MODE = 0100644;
  110. /** Convert millis to seconds */
  111. public static final int MILLIS_PER_SECOND = 1000;
  112. /**
  113. * Construct an empty entry and prepares the header values.
  114. */
  115. private TarEntry () {
  116. this.magic = new StringBuffer(TMAGIC);
  117. this.name = new StringBuffer();
  118. this.linkName = new StringBuffer();
  119. String user = System.getProperty("user.name", "");
  120. if (user.length() > MAX_NAMELEN) {
  121. user = user.substring(0, MAX_NAMELEN);
  122. }
  123. this.userId = 0;
  124. this.groupId = 0;
  125. this.userName = new StringBuffer(user);
  126. this.groupName = new StringBuffer("");
  127. this.file = null;
  128. }
  129. /**
  130. * Construct an entry with only a name. This allows the programmer
  131. * to construct the entry's header "by hand". File is set to null.
  132. *
  133. * @param name the entry name
  134. */
  135. public TarEntry(String name) {
  136. this();
  137. boolean isDir = name.endsWith("/");
  138. this.devMajor = 0;
  139. this.devMinor = 0;
  140. this.name = new StringBuffer(name);
  141. this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
  142. this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
  143. this.userId = 0;
  144. this.groupId = 0;
  145. this.size = 0;
  146. this.checkSum = 0;
  147. this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
  148. this.linkName = new StringBuffer("");
  149. this.userName = new StringBuffer("");
  150. this.groupName = new StringBuffer("");
  151. this.devMajor = 0;
  152. this.devMinor = 0;
  153. }
  154. /**
  155. * Construct an entry with a name an a link flag.
  156. *
  157. * @param name the entry name
  158. * @param linkFlag the entry link flag.
  159. */
  160. public TarEntry(String name, byte linkFlag) {
  161. this(name);
  162. this.linkFlag = linkFlag;
  163. }
  164. /**
  165. * Construct an entry for a file. File is set to file, and the
  166. * header is constructed from information from the file.
  167. *
  168. * @param file The file that the entry represents.
  169. */
  170. public TarEntry(File file) {
  171. this();
  172. this.file = file;
  173. String name = file.getPath();
  174. String osname = System.getProperty("os.name").toLowerCase(Locale.US);
  175. if (osname != null) {
  176. // Strip off drive letters!
  177. // REVIEW Would a better check be "(File.separator == '\')"?
  178. if (osname.startsWith("windows")) {
  179. if (name.length() > 2) {
  180. char ch1 = name.charAt(0);
  181. char ch2 = name.charAt(1);
  182. if (ch2 == ':'
  183. && ((ch1 >= 'a' && ch1 <= 'z')
  184. || (ch1 >= 'A' && ch1 <= 'Z'))) {
  185. name = name.substring(2);
  186. }
  187. }
  188. } else if (osname.indexOf("netware") > -1) {
  189. int colon = name.indexOf(':');
  190. if (colon != -1) {
  191. name = name.substring(colon + 1);
  192. }
  193. }
  194. }
  195. name = name.replace(File.separatorChar, '/');
  196. // No absolute pathnames
  197. // Windows (and Posix?) paths can start with "\\NetworkDrive\",
  198. // so we loop on starting /'s.
  199. while (name.startsWith("/")) {
  200. name = name.substring(1);
  201. }
  202. this.linkName = new StringBuffer("");
  203. this.name = new StringBuffer(name);
  204. if (file.isDirectory()) {
  205. this.mode = DEFAULT_DIR_MODE;
  206. this.linkFlag = LF_DIR;
  207. if (this.name.charAt(this.name.length() - 1) != '/') {
  208. this.name.append("/");
  209. }
  210. } else {
  211. this.mode = DEFAULT_FILE_MODE;
  212. this.linkFlag = LF_NORMAL;
  213. }
  214. this.size = file.length();
  215. this.modTime = file.lastModified() / MILLIS_PER_SECOND;
  216. this.checkSum = 0;
  217. this.devMajor = 0;
  218. this.devMinor = 0;
  219. }
  220. /**
  221. * Construct an entry from an archive's header bytes. File is set
  222. * to null.
  223. *
  224. * @param headerBuf The header bytes from a tar archive entry.
  225. */
  226. public TarEntry(byte[] headerBuf) {
  227. this();
  228. this.parseTarHeader(headerBuf);
  229. }
  230. /**
  231. * Determine if the two entries are equal. Equality is determined
  232. * by the header names being equal.
  233. *
  234. * @param it Entry to be checked for equality.
  235. * @return True if the entries are equal.
  236. */
  237. public boolean equals(TarEntry it) {
  238. return this.getName().equals(it.getName());
  239. }
  240. /**
  241. * Determine if the two entries are equal. Equality is determined
  242. * by the header names being equal.
  243. *
  244. * @param it Entry to be checked for equality.
  245. * @return True if the entries are equal.
  246. */
  247. public boolean equals(Object it) {
  248. if (it == null || getClass() != it.getClass()) {
  249. return false;
  250. }
  251. return equals((TarEntry) it);
  252. }
  253. /**
  254. * Hashcodes are based on entry names.
  255. *
  256. * @return the entry hashcode
  257. */
  258. public int hashCode() {
  259. return getName().hashCode();
  260. }
  261. /**
  262. * Determine if the given entry is a descendant of this entry.
  263. * Descendancy is determined by the name of the descendant
  264. * starting with this entry's name.
  265. *
  266. * @param desc Entry to be checked as a descendent of this.
  267. * @return True if entry is a descendant of this.
  268. */
  269. public boolean isDescendent(TarEntry desc) {
  270. return desc.getName().startsWith(this.getName());
  271. }
  272. /**
  273. * Get this entry's name.
  274. *
  275. * @return This entry's name.
  276. */
  277. public String getName() {
  278. return this.name.toString();
  279. }
  280. /**
  281. * Set this entry's name.
  282. *
  283. * @param name This entry's new name.
  284. */
  285. public void setName(String name) {
  286. this.name = new StringBuffer(name);
  287. }
  288. /**
  289. * Set the mode for this entry
  290. *
  291. * @param mode the mode for this entry
  292. */
  293. public void setMode(int mode) {
  294. this.mode = mode;
  295. }
  296. /**
  297. * Get this entry's link name.
  298. *
  299. * @return This entry's link name.
  300. */
  301. public String getLinkName() {
  302. return this.linkName.toString();
  303. }
  304. /**
  305. * Get this entry's user id.
  306. *
  307. * @return This entry's user id.
  308. */
  309. public int getUserId() {
  310. return this.userId;
  311. }
  312. /**
  313. * Set this entry's user id.
  314. *
  315. * @param userId This entry's new user id.
  316. */
  317. public void setUserId(int userId) {
  318. this.userId = userId;
  319. }
  320. /**
  321. * Get this entry's group id.
  322. *
  323. * @return This entry's group id.
  324. */
  325. public int getGroupId() {
  326. return this.groupId;
  327. }
  328. /**
  329. * Set this entry's group id.
  330. *
  331. * @param groupId This entry's new group id.
  332. */
  333. public void setGroupId(int groupId) {
  334. this.groupId = groupId;
  335. }
  336. /**
  337. * Get this entry's user name.
  338. *
  339. * @return This entry's user name.
  340. */
  341. public String getUserName() {
  342. return this.userName.toString();
  343. }
  344. /**
  345. * Set this entry's user name.
  346. *
  347. * @param userName This entry's new user name.
  348. */
  349. public void setUserName(String userName) {
  350. this.userName = new StringBuffer(userName);
  351. }
  352. /**
  353. * Get this entry's group name.
  354. *
  355. * @return This entry's group name.
  356. */
  357. public String getGroupName() {
  358. return this.groupName.toString();
  359. }
  360. /**
  361. * Set this entry's group name.
  362. *
  363. * @param groupName This entry's new group name.
  364. */
  365. public void setGroupName(String groupName) {
  366. this.groupName = new StringBuffer(groupName);
  367. }
  368. /**
  369. * Convenience method to set this entry's group and user ids.
  370. *
  371. * @param userId This entry's new user id.
  372. * @param groupId This entry's new group id.
  373. */
  374. public void setIds(int userId, int groupId) {
  375. this.setUserId(userId);
  376. this.setGroupId(groupId);
  377. }
  378. /**
  379. * Convenience method to set this entry's group and user names.
  380. *
  381. * @param userName This entry's new user name.
  382. * @param groupName This entry's new group name.
  383. */
  384. public void setNames(String userName, String groupName) {
  385. this.setUserName(userName);
  386. this.setGroupName(groupName);
  387. }
  388. /**
  389. * Set this entry's modification time. The parameter passed
  390. * to this method is in "Java time".
  391. *
  392. * @param time This entry's new modification time.
  393. */
  394. public void setModTime(long time) {
  395. this.modTime = time / MILLIS_PER_SECOND;
  396. }
  397. /**
  398. * Set this entry's modification time.
  399. *
  400. * @param time This entry's new modification time.
  401. */
  402. public void setModTime(Date time) {
  403. this.modTime = time.getTime() / MILLIS_PER_SECOND;
  404. }
  405. /**
  406. * Set this entry's modification time.
  407. *
  408. * @return time This entry's new modification time.
  409. */
  410. public Date getModTime() {
  411. return new Date(this.modTime * MILLIS_PER_SECOND);
  412. }
  413. /**
  414. * Get this entry's file.
  415. *
  416. * @return This entry's file.
  417. */
  418. public File getFile() {
  419. return this.file;
  420. }
  421. /**
  422. * Get this entry's mode.
  423. *
  424. * @return This entry's mode.
  425. */
  426. public int getMode() {
  427. return this.mode;
  428. }
  429. /**
  430. * Get this entry's file size.
  431. *
  432. * @return This entry's file size.
  433. */
  434. public long getSize() {
  435. return this.size;
  436. }
  437. /**
  438. * Set this entry's file size.
  439. *
  440. * @param size This entry's new file size.
  441. */
  442. public void setSize(long size) {
  443. this.size = size;
  444. }
  445. /**
  446. * Indicate if this entry is a GNU long name block
  447. *
  448. * @return true if this is a long name extension provided by GNU tar
  449. */
  450. public boolean isGNULongNameEntry() {
  451. return linkFlag == LF_GNUTYPE_LONGNAME
  452. && name.toString().equals(GNU_LONGLINK);
  453. }
  454. /**
  455. * Return whether or not this entry represents a directory.
  456. *
  457. * @return True if this entry is a directory.
  458. */
  459. public boolean isDirectory() {
  460. if (this.file != null) {
  461. return this.file.isDirectory();
  462. }
  463. if (this.linkFlag == LF_DIR) {
  464. return true;
  465. }
  466. if (this.getName().endsWith("/")) {
  467. return true;
  468. }
  469. return false;
  470. }
  471. /**
  472. * If this entry represents a file, and the file is a directory, return
  473. * an array of TarEntries for this entry's children.
  474. *
  475. * @return An array of TarEntry's for this entry's children.
  476. */
  477. public TarEntry[] getDirectoryEntries() {
  478. if (this.file == null || !this.file.isDirectory()) {
  479. return new TarEntry[0];
  480. }
  481. String[] list = this.file.list();
  482. TarEntry[] result = new TarEntry[list.length];
  483. for (int i = 0; i < list.length; ++i) {
  484. result[i] = new TarEntry(new File(this.file, list[i]));
  485. }
  486. return result;
  487. }
  488. /**
  489. * Write an entry's header information to a header buffer.
  490. *
  491. * @param outbuf The tar entry header buffer to fill in.
  492. */
  493. public void writeEntryHeader(byte[] outbuf) {
  494. int offset = 0;
  495. offset = TarUtils.getNameBytes(this.name, outbuf, offset, NAMELEN);
  496. offset = TarUtils.getOctalBytes(this.mode, outbuf, offset, MODELEN);
  497. offset = TarUtils.getOctalBytes(this.userId, outbuf, offset, UIDLEN);
  498. offset = TarUtils.getOctalBytes(this.groupId, outbuf, offset, GIDLEN);
  499. offset = TarUtils.getLongOctalBytes(this.size, outbuf, offset, SIZELEN);
  500. offset = TarUtils.getLongOctalBytes(this.modTime, outbuf, offset, MODTIMELEN);
  501. int csOffset = offset;
  502. for (int c = 0; c < CHKSUMLEN; ++c) {
  503. outbuf[offset++] = (byte) ' ';
  504. }
  505. outbuf[offset++] = this.linkFlag;
  506. offset = TarUtils.getNameBytes(this.linkName, outbuf, offset, NAMELEN);
  507. offset = TarUtils.getNameBytes(this.magic, outbuf, offset, MAGICLEN);
  508. offset = TarUtils.getNameBytes(this.userName, outbuf, offset, UNAMELEN);
  509. offset = TarUtils.getNameBytes(this.groupName, outbuf, offset, GNAMELEN);
  510. offset = TarUtils.getOctalBytes(this.devMajor, outbuf, offset, DEVLEN);
  511. offset = TarUtils.getOctalBytes(this.devMinor, outbuf, offset, DEVLEN);
  512. while (offset < outbuf.length) {
  513. outbuf[offset++] = 0;
  514. }
  515. long checkSum = TarUtils.computeCheckSum(outbuf);
  516. TarUtils.getCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN);
  517. }
  518. /**
  519. * Parse an entry's header information from a header buffer.
  520. *
  521. * @param header The tar entry header buffer to get information from.
  522. */
  523. public void parseTarHeader(byte[] header) {
  524. int offset = 0;
  525. this.name = TarUtils.parseName(header, offset, NAMELEN);
  526. offset += NAMELEN;
  527. this.mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
  528. offset += MODELEN;
  529. this.userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
  530. offset += UIDLEN;
  531. this.groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
  532. offset += GIDLEN;
  533. this.size = TarUtils.parseOctal(header, offset, SIZELEN);
  534. offset += SIZELEN;
  535. this.modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
  536. offset += MODTIMELEN;
  537. this.checkSum = (int) TarUtils.parseOctal(header, offset, CHKSUMLEN);
  538. offset += CHKSUMLEN;
  539. this.linkFlag = header[offset++];
  540. this.linkName = TarUtils.parseName(header, offset, NAMELEN);
  541. offset += NAMELEN;
  542. this.magic = TarUtils.parseName(header, offset, MAGICLEN);
  543. offset += MAGICLEN;
  544. this.userName = TarUtils.parseName(header, offset, UNAMELEN);
  545. offset += UNAMELEN;
  546. this.groupName = TarUtils.parseName(header, offset, GNAMELEN);
  547. offset += GNAMELEN;
  548. this.devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
  549. offset += DEVLEN;
  550. this.devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
  551. }
  552. }