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.FilterInputStream;
  23. import java.io.IOException;
  24. import java.io.InputStream;
  25. import java.io.OutputStream;
  26. /**
  27. * The TarInputStream reads a UNIX tar archive as an InputStream.
  28. * methods are provided to position at each successive entry in
  29. * the archive, and the read each entry as a normal input stream
  30. * using read().
  31. *
  32. */
  33. public class TarInputStream extends FilterInputStream {
  34. protected boolean debug;
  35. protected boolean hasHitEOF;
  36. protected int entrySize;
  37. protected int entryOffset;
  38. protected byte[] oneBuf;
  39. protected byte[] readBuf;
  40. protected TarBuffer buffer;
  41. protected TarEntry currEntry;
  42. private boolean v7Format;
  43. public TarInputStream(InputStream is) {
  44. this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
  45. }
  46. public TarInputStream(InputStream is, int blockSize) {
  47. this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
  48. }
  49. public TarInputStream(InputStream is, int blockSize, int recordSize) {
  50. super(is);
  51. this.buffer = new TarBuffer(is, blockSize, recordSize);
  52. this.readBuf = null;
  53. this.oneBuf = new byte[1];
  54. this.debug = false;
  55. this.hasHitEOF = false;
  56. this.v7Format = false;
  57. }
  58. /**
  59. * Sets the debugging flag.
  60. *
  61. * @param debug True to turn on debugging.
  62. */
  63. public void setDebug(boolean debug) {
  64. this.debug = debug;
  65. this.buffer.setDebug(debug);
  66. }
  67. /**
  68. * Closes this stream. Calls the TarBuffer's close() method.
  69. */
  70. public void close() throws IOException {
  71. this.buffer.close();
  72. }
  73. /**
  74. * Get the record size being used by this stream's TarBuffer.
  75. *
  76. * @return The TarBuffer record size.
  77. */
  78. public int getRecordSize() {
  79. return this.buffer.getRecordSize();
  80. }
  81. /**
  82. * Get the available data that can be read from the current
  83. * entry in the archive. This does not indicate how much data
  84. * is left in the entire archive, only in the current entry.
  85. * This value is determined from the entry's size header field
  86. * and the amount of data already read from the current entry.
  87. *
  88. *
  89. * @return The number of available bytes for the current entry.
  90. */
  91. public int available() throws IOException {
  92. return this.entrySize - this.entryOffset;
  93. }
  94. /**
  95. * Skip bytes in the input buffer. This skips bytes in the
  96. * current entry's data, not the entire archive, and will
  97. * stop at the end of the current entry's data if the number
  98. * to skip extends beyond that point.
  99. *
  100. * @param numToSkip The number of bytes to skip.
  101. */
  102. public long skip(long numToSkip) throws IOException {
  103. // REVIEW
  104. // This is horribly inefficient, but it ensures that we
  105. // properly skip over bytes via the TarBuffer...
  106. //
  107. byte[] skipBuf = new byte[8 * 1024];
  108. long skip = numToSkip;
  109. while (skip > 0) {
  110. int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
  111. int numRead = this.read(skipBuf, 0, realSkip);
  112. if (numRead == -1) {
  113. break;
  114. }
  115. skip -= numRead;
  116. }
  117. return (numToSkip - skip);
  118. }
  119. /**
  120. * Since we do not support marking just yet, we return false.
  121. *
  122. * @return False.
  123. */
  124. public boolean markSupported() {
  125. return false;
  126. }
  127. /**
  128. * Since we do not support marking just yet, we do nothing.
  129. *
  130. * @param markLimit The limit to mark.
  131. */
  132. public void mark(int markLimit) {
  133. }
  134. /**
  135. * Since we do not support marking just yet, we do nothing.
  136. */
  137. public void reset() {
  138. }
  139. /**
  140. * Get the next entry in this tar archive. This will skip
  141. * over any remaining data in the current entry, if there
  142. * is one, and place the input stream at the header of the
  143. * next entry, and read the header and instantiate a new
  144. * TarEntry from the header bytes and return that entry.
  145. * If there are no more entries in the archive, null will
  146. * be returned to indicate that the end of the archive has
  147. * been reached.
  148. *
  149. * @return The next TarEntry in the archive, or null.
  150. */
  151. public TarEntry getNextEntry() throws IOException {
  152. if (this.hasHitEOF) {
  153. return null;
  154. }
  155. if (this.currEntry != null) {
  156. int numToSkip = this.entrySize - this.entryOffset;
  157. if (this.debug) {
  158. System.err.println("TarInputStream: SKIP currENTRY '"
  159. + this.currEntry.getName() + "' SZ "
  160. + this.entrySize + " OFF "
  161. + this.entryOffset + " skipping "
  162. + numToSkip + " bytes");
  163. }
  164. if (numToSkip > 0) {
  165. this.skip(numToSkip);
  166. }
  167. this.readBuf = null;
  168. }
  169. byte[] headerBuf = this.buffer.readRecord();
  170. if (headerBuf == null) {
  171. if (this.debug) {
  172. System.err.println("READ NULL RECORD");
  173. }
  174. this.hasHitEOF = true;
  175. } else if (this.buffer.isEOFRecord(headerBuf)) {
  176. if (this.debug) {
  177. System.err.println("READ EOF RECORD");
  178. }
  179. this.hasHitEOF = true;
  180. }
  181. if (this.hasHitEOF) {
  182. this.currEntry = null;
  183. } else {
  184. this.currEntry = new TarEntry(headerBuf);
  185. if (!(headerBuf[257] == 'u' && headerBuf[258] == 's'
  186. && headerBuf[259] == 't' && headerBuf[260] == 'a'
  187. && headerBuf[261] == 'r')) {
  188. this.v7Format = true;
  189. }
  190. if (this.debug) {
  191. System.err.println("TarInputStream: SET CURRENTRY '"
  192. + this.currEntry.getName()
  193. + "' size = "
  194. + this.currEntry.getSize());
  195. }
  196. this.entryOffset = 0;
  197. // REVIEW How do we resolve this discrepancy?!
  198. this.entrySize = (int) this.currEntry.getSize();
  199. }
  200. if (this.currEntry != null && this.currEntry.isGNULongNameEntry()) {
  201. // read in the name
  202. StringBuffer longName = new StringBuffer();
  203. byte[] buffer = new byte[256];
  204. int length = 0;
  205. while ((length = read(buffer)) >= 0) {
  206. longName.append(new String(buffer, 0, length));
  207. }
  208. getNextEntry();
  209. // remove trailing null terminator
  210. if (longName.length() > 0
  211. && longName.charAt(longName.length() - 1) == 0) {
  212. longName.deleteCharAt(longName.length() - 1);
  213. }
  214. this.currEntry.setName(longName.toString());
  215. }
  216. return this.currEntry;
  217. }
  218. /**
  219. * Reads a byte from the current tar archive entry.
  220. *
  221. * This method simply calls read( byte[], int, int ).
  222. *
  223. * @return The byte read, or -1 at EOF.
  224. */
  225. public int read() throws IOException {
  226. int num = this.read(this.oneBuf, 0, 1);
  227. if (num == -1) {
  228. return num;
  229. } else {
  230. return (int) this.oneBuf[0];
  231. }
  232. }
  233. /**
  234. * Reads bytes from the current tar archive entry.
  235. *
  236. * This method simply calls read( byte[], int, int ).
  237. *
  238. * @param buf The buffer into which to place bytes read.
  239. * @return The number of bytes read, or -1 at EOF.
  240. */
  241. public int read(byte[] buf) throws IOException {
  242. return this.read(buf, 0, buf.length);
  243. }
  244. /**
  245. * Reads bytes from the current tar archive entry.
  246. *
  247. * This method is aware of the boundaries of the current
  248. * entry in the archive and will deal with them as if they
  249. * were this stream's start and EOF.
  250. *
  251. * @param buf The buffer into which to place bytes read.
  252. * @param offset The offset at which to place bytes read.
  253. * @param numToRead The number of bytes to read.
  254. * @return The number of bytes read, or -1 at EOF.
  255. */
  256. public int read(byte[] buf, int offset, int numToRead) throws IOException {
  257. int totalRead = 0;
  258. if (this.entryOffset >= this.entrySize) {
  259. return -1;
  260. }
  261. if ((numToRead + this.entryOffset) > this.entrySize) {
  262. numToRead = (this.entrySize - this.entryOffset);
  263. }
  264. if (this.readBuf != null) {
  265. int sz = (numToRead > this.readBuf.length) ? this.readBuf.length
  266. : numToRead;
  267. System.arraycopy(this.readBuf, 0, buf, offset, sz);
  268. if (sz >= this.readBuf.length) {
  269. this.readBuf = null;
  270. } else {
  271. int newLen = this.readBuf.length - sz;
  272. byte[] newBuf = new byte[newLen];
  273. System.arraycopy(this.readBuf, sz, newBuf, 0, newLen);
  274. this.readBuf = newBuf;
  275. }
  276. totalRead += sz;
  277. numToRead -= sz;
  278. offset += sz;
  279. }
  280. while (numToRead > 0) {
  281. byte[] rec = this.buffer.readRecord();
  282. if (rec == null) {
  283. // Unexpected EOF!
  284. throw new IOException("unexpected EOF with " + numToRead
  285. + " bytes unread");
  286. }
  287. int sz = numToRead;
  288. int recLen = rec.length;
  289. if (recLen > sz) {
  290. System.arraycopy(rec, 0, buf, offset, sz);
  291. this.readBuf = new byte[recLen - sz];
  292. System.arraycopy(rec, sz, this.readBuf, 0, recLen - sz);
  293. } else {
  294. sz = recLen;
  295. System.arraycopy(rec, 0, buf, offset, recLen);
  296. }
  297. totalRead += sz;
  298. numToRead -= sz;
  299. offset += sz;
  300. }
  301. this.entryOffset += totalRead;
  302. return totalRead;
  303. }
  304. /**
  305. * Copies the contents of the current tar archive entry directly into
  306. * an output stream.
  307. *
  308. * @param out The OutputStream into which to write the entry's data.
  309. */
  310. public void copyEntryContents(OutputStream out) throws IOException {
  311. byte[] buf = new byte[32 * 1024];
  312. while (true) {
  313. int numRead = this.read(buf, 0, buf.length);
  314. if (numRead == -1) {
  315. break;
  316. }
  317. out.write(buf, 0, numRead);
  318. }
  319. }
  320. }