You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

TarEntry.java 18 kB

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