PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre4/installer/TarInputStream.java

#
Java | 519 lines | 310 code | 72 blank | 137 comment | 42 complexity | 52f3ffb1664130c20c241a314f6c5b54 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. ** Authored by Timothy Gerard Endres
  3. ** <mailto:time@gjt.org> <http://www.trustice.com>
  4. **
  5. ** This work has been placed into the public domain.
  6. ** You may use this work in any way and for any purpose you wish.
  7. **
  8. ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  9. ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
  10. ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
  11. ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
  12. ** REDISTRIBUTION OF THIS SOFTWARE.
  13. **
  14. */
  15. package installer;
  16. import java.io.*;
  17. /**
  18. * The TarInputStream reads a UNIX tar archive as an InputStream.
  19. * methods are provided to position at each successive entry in
  20. * the archive, and the read each entry as a normal input stream
  21. * using read().
  22. *
  23. * @version $Revision: 4480 $
  24. * @author Timothy Gerard Endres,
  25. * <a href="mailto:time@gjt.org">time@trustice.com</a>.
  26. * @see TarBuffer
  27. * @see TarHeader
  28. * @see TarEntry
  29. */
  30. public
  31. class TarInputStream
  32. extends FilterInputStream
  33. {
  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. protected EntryFactory eFactory;
  43. public
  44. TarInputStream( InputStream is )
  45. {
  46. this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
  47. }
  48. public
  49. TarInputStream( InputStream is, int blockSize )
  50. {
  51. this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
  52. }
  53. public
  54. TarInputStream( InputStream is, int blockSize, int recordSize )
  55. {
  56. super( is );
  57. this.buffer = new TarBuffer( is, blockSize, recordSize );
  58. this.readBuf = null;
  59. this.oneBuf = new byte[1];
  60. this.debug = false;
  61. this.hasHitEOF = false;
  62. this.eFactory = null;
  63. }
  64. /**
  65. * Sets the debugging flag.
  66. *
  67. * @param debugF True to turn on debugging.
  68. */
  69. public void
  70. setDebug( boolean debugF )
  71. {
  72. this.debug = debugF;
  73. }
  74. /**
  75. * Sets the debugging flag.
  76. *
  77. * @param debugF True to turn on debugging.
  78. */
  79. public void
  80. setEntryFactory( EntryFactory factory )
  81. {
  82. this.eFactory = factory;
  83. }
  84. /**
  85. * Sets the debugging flag in this stream's TarBuffer.
  86. *
  87. * @param debugF True to turn on debugging.
  88. */
  89. public void
  90. setBufferDebug( boolean debug )
  91. {
  92. this.buffer.setDebug( debug );
  93. }
  94. /**
  95. * Closes this stream. Calls the TarBuffer's close() method.
  96. */
  97. public void
  98. close()
  99. throws IOException
  100. {
  101. this.buffer.close();
  102. }
  103. /**
  104. * Get the record size being used by this stream's TarBuffer.
  105. *
  106. * @return The TarBuffer record size.
  107. */
  108. public int
  109. getRecordSize()
  110. {
  111. return this.buffer.getRecordSize();
  112. }
  113. /**
  114. * Get the available data that can be read from the current
  115. * entry in the archive. This does not indicate how much data
  116. * is left in the entire archive, only in the current entry.
  117. * This value is determined from the entry's size header field
  118. * and the amount of data already read from the current entry.
  119. *
  120. *
  121. * @return The number of available bytes for the current entry.
  122. */
  123. public int
  124. available()
  125. throws IOException
  126. {
  127. return this.entrySize - this.entryOffset;
  128. }
  129. /**
  130. * Skip bytes in the input buffer. This skips bytes in the
  131. * current entry's data, not the entire archive, and will
  132. * stop at the end of the current entry's data if the number
  133. * to skip extends beyond that point.
  134. *
  135. * @param numToSkip The number of bytes to skip.
  136. */
  137. public void
  138. skip( int numToSkip )
  139. throws IOException
  140. {
  141. // REVIEW
  142. // This is horribly inefficient, but it ensures that we
  143. // properly skip over bytes via the TarBuffer...
  144. //
  145. byte[] skipBuf = new byte[ 8 * 1024 ];
  146. for ( int num = numToSkip ; num > 0 ; )
  147. {
  148. int numRead =
  149. this.read( skipBuf, 0,
  150. ( num > skipBuf.length ? skipBuf.length : num ) );
  151. if ( numRead == -1 )
  152. break;
  153. num -= numRead;
  154. }
  155. }
  156. /**
  157. * Since we do not support marking just yet, we return false.
  158. *
  159. * @return False.
  160. */
  161. public boolean
  162. markSupported()
  163. {
  164. return false;
  165. }
  166. /**
  167. * Since we do not support marking just yet, we do nothing.
  168. *
  169. * @param markLimit The limit to mark.
  170. */
  171. public void
  172. mark( int markLimit )
  173. {
  174. }
  175. /**
  176. * Since we do not support marking just yet, we do nothing.
  177. */
  178. public void
  179. reset()
  180. {
  181. }
  182. /**
  183. * Get the next entry in this tar archive. This will skip
  184. * over any remaining data in the current entry, if there
  185. * is one, and place the input stream at the header of the
  186. * next entry, and read the header and instantiate a new
  187. * TarEntry from the header bytes and return that entry.
  188. * If there are no more entries in the archive, null will
  189. * be returned to indicate that the end of the archive has
  190. * been reached.
  191. *
  192. * @return The next TarEntry in the archive, or null.
  193. */
  194. public TarEntry
  195. getNextEntry()
  196. throws IOException
  197. {
  198. if ( this.hasHitEOF )
  199. return null;
  200. if ( this.currEntry != null )
  201. {
  202. int numToSkip = this.entrySize - this.entryOffset;
  203. if ( this.debug )
  204. System.err.println
  205. ( "TarInputStream: SKIP currENTRY '"
  206. + this.currEntry.getName() + "' SZ "
  207. + this.entrySize + " OFF " + this.entryOffset
  208. + " skipping " + numToSkip + " bytes" );
  209. if ( numToSkip > 0 )
  210. {
  211. this.skip( numToSkip );
  212. }
  213. this.readBuf = null;
  214. }
  215. byte[] headerBuf = this.buffer.readRecord();
  216. if ( headerBuf == null )
  217. {
  218. if ( this.debug )
  219. {
  220. System.err.println( "READ NULL RECORD" );
  221. }
  222. this.hasHitEOF = true;
  223. }
  224. else if ( this.buffer.isEOFRecord( headerBuf ) )
  225. {
  226. if ( this.debug )
  227. {
  228. System.err.println( "READ EOF RECORD" );
  229. }
  230. this.hasHitEOF = true;
  231. }
  232. if ( this.hasHitEOF )
  233. {
  234. this.currEntry = null;
  235. }
  236. else
  237. {
  238. try {
  239. if ( this.eFactory == null )
  240. {
  241. this.currEntry = new TarEntry( headerBuf );
  242. }
  243. else
  244. {
  245. this.currEntry =
  246. this.eFactory.createEntry( headerBuf );
  247. }
  248. if ( ! ( headerBuf[257] == 'u' && headerBuf[258] == 's'
  249. && headerBuf[259] == 't' && headerBuf[260] == 'a'
  250. && headerBuf[261] == 'r' ) )
  251. {
  252. throw new InvalidHeaderException
  253. ( "header magic is not 'ustar', but '"
  254. + headerBuf[257] + headerBuf[258] + headerBuf[259]
  255. + headerBuf[260] + headerBuf[261] + "', or (dec) "
  256. + ((int)headerBuf[257]) + ", "
  257. + ((int)headerBuf[258]) + ", "
  258. + ((int)headerBuf[259]) + ", "
  259. + ((int)headerBuf[260]) + ", "
  260. + ((int)headerBuf[261]) );
  261. }
  262. if ( this.debug )
  263. System.err.println
  264. ( "TarInputStream: SET CURRENTRY '"
  265. + this.currEntry.getName()
  266. + "' size = " + this.currEntry.getSize() );
  267. this.entryOffset = 0;
  268. // REVIEW How do we resolve this discrepancy?!
  269. this.entrySize = (int) this.currEntry.getSize();
  270. }
  271. catch ( InvalidHeaderException ex )
  272. {
  273. this.entrySize = 0;
  274. this.entryOffset = 0;
  275. this.currEntry = null;
  276. throw new InvalidHeaderException
  277. ( "bad header in block "
  278. + this.buffer.getCurrentBlockNum()
  279. + " record "
  280. + this.buffer.getCurrentRecordNum()
  281. + ", " + ex.getMessage() );
  282. }
  283. }
  284. return this.currEntry;
  285. }
  286. /**
  287. * Reads a byte from the current tar archive entry.
  288. *
  289. * This method simply calls read( byte[], int, int ).
  290. *
  291. * @return The byte read, or -1 at EOF.
  292. */
  293. public int
  294. read()
  295. throws IOException
  296. {
  297. int num = this.read( this.oneBuf, 0, 1 );
  298. if ( num == -1 )
  299. return num;
  300. else
  301. return (int) this.oneBuf[0];
  302. }
  303. /**
  304. * Reads bytes from the current tar archive entry.
  305. *
  306. * This method simply calls read( byte[], int, int ).
  307. *
  308. * @param buf The buffer into which to place bytes read.
  309. * @return The number of bytes read, or -1 at EOF.
  310. */
  311. public int
  312. read( byte[] buf )
  313. throws IOException
  314. {
  315. return this.read( buf, 0, buf.length );
  316. }
  317. /**
  318. * Reads bytes from the current tar archive entry.
  319. *
  320. * This method is aware of the boundaries of the current
  321. * entry in the archive and will deal with them as if they
  322. * were this stream's start and EOF.
  323. *
  324. * @param buf The buffer into which to place bytes read.
  325. * @param offset The offset at which to place bytes read.
  326. * @param numToRead The number of bytes to read.
  327. * @return The number of bytes read, or -1 at EOF.
  328. */
  329. public int
  330. read( byte[] buf, int offset, int numToRead )
  331. throws IOException
  332. {
  333. int totalRead = 0;
  334. if ( this.entryOffset >= this.entrySize )
  335. return -1;
  336. if ( (numToRead + this.entryOffset) > this.entrySize )
  337. {
  338. numToRead = (this.entrySize - this.entryOffset);
  339. }
  340. if ( this.readBuf != null )
  341. {
  342. int sz = ( numToRead > this.readBuf.length )
  343. ? this.readBuf.length : numToRead;
  344. System.arraycopy( this.readBuf, 0, buf, offset, sz );
  345. if ( sz >= this.readBuf.length )
  346. {
  347. this.readBuf = null;
  348. }
  349. else
  350. {
  351. int newLen = this.readBuf.length - sz;
  352. byte[] newBuf = new byte[ newLen ];
  353. System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
  354. this.readBuf = newBuf;
  355. }
  356. totalRead += sz;
  357. numToRead -= sz;
  358. offset += sz;
  359. }
  360. for ( ; numToRead > 0 ; )
  361. {
  362. byte[] rec = this.buffer.readRecord();
  363. if ( rec == null )
  364. {
  365. // Unexpected EOF!
  366. throw new IOException
  367. ( "unexpected EOF with " + numToRead + " bytes unread" );
  368. }
  369. int sz = numToRead;
  370. int recLen = rec.length;
  371. if ( recLen > sz )
  372. {
  373. System.arraycopy( rec, 0, buf, offset, sz );
  374. this.readBuf = new byte[ recLen - sz ];
  375. System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
  376. }
  377. else
  378. {
  379. sz = recLen;
  380. System.arraycopy( rec, 0, buf, offset, recLen );
  381. }
  382. totalRead += sz;
  383. numToRead -= sz;
  384. offset += sz;
  385. }
  386. this.entryOffset += totalRead;
  387. return totalRead;
  388. }
  389. /**
  390. * Copies the contents of the current tar archive entry directly into
  391. * an output stream.
  392. *
  393. * @param out The OutputStream into which to write the entry's data.
  394. */
  395. public void
  396. copyEntryContents( OutputStream out )
  397. throws IOException
  398. {
  399. byte[] buf = new byte[ 32 * 1024 ];
  400. for ( ; ; )
  401. {
  402. int numRead = this.read( buf, 0, buf.length );
  403. if ( numRead == -1 )
  404. break;
  405. out.write( buf, 0, numRead );
  406. }
  407. }
  408. /**
  409. * This interface is provided, with the method setEntryFactory(), to allow
  410. * the programmer to have their own TarEntry subclass instantiated for the
  411. * entries return from getNextEntry().
  412. */
  413. public
  414. interface EntryFactory
  415. {
  416. public TarEntry
  417. createEntry( String name );
  418. public TarEntry
  419. createEntry( File path )
  420. throws InvalidHeaderException;
  421. public TarEntry
  422. createEntry( byte[] headerBuf )
  423. throws InvalidHeaderException;
  424. }
  425. public
  426. class EntryAdapter
  427. implements EntryFactory
  428. {
  429. public TarEntry
  430. createEntry( String name )
  431. {
  432. return new TarEntry( name );
  433. }
  434. public TarEntry
  435. createEntry( File path )
  436. throws InvalidHeaderException
  437. {
  438. return new TarEntry( path );
  439. }
  440. public TarEntry
  441. createEntry( byte[] headerBuf )
  442. throws InvalidHeaderException
  443. {
  444. return new TarEntry( headerBuf );
  445. }
  446. }
  447. }