1. /*
  2. * @(#)ZipInputStream.java 1.28 00/02/02
  3. *
  4. * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package java.util.zip;
  11. import java.io.InputStream;
  12. import java.io.IOException;
  13. import java.io.EOFException;
  14. import java.io.PushbackInputStream;
  15. /**
  16. * This class implements an input stream filter for reading files in the
  17. * ZIP file format. Includes support for both compressed and uncompressed
  18. * entries.
  19. *
  20. * @author David Connelly
  21. * @version 1.28, 02/02/00
  22. */
  23. public
  24. class ZipInputStream extends InflaterInputStream implements ZipConstants {
  25. private ZipEntry entry;
  26. private CRC32 crc = new CRC32();
  27. private long remaining;
  28. private byte[] tmpbuf = new byte[512];
  29. private static final int STORED = ZipEntry.STORED;
  30. private static final int DEFLATED = ZipEntry.DEFLATED;
  31. private boolean closed = false;
  32. // this flag is set to true after EOF has reached for
  33. // one entry
  34. private boolean entryEOF = false;
  35. /**
  36. * Check to make sure that this stream has not been closed
  37. */
  38. private void ensureOpen() throws IOException {
  39. if (closed) {
  40. throw new IOException("Stream closed");
  41. }
  42. }
  43. /**
  44. * Creates a new ZIP input stream.
  45. * @param in the actual input stream
  46. */
  47. public ZipInputStream(InputStream in) {
  48. super(new PushbackInputStream(in, 512), new Inflater(true), 512);
  49. if(in == null) {
  50. throw new NullPointerException("in is null");
  51. }
  52. }
  53. /**
  54. * Reads the next ZIP file entry and positions stream at the beginning
  55. * of the entry data.
  56. * @return the ZipEntry just read
  57. * @exception ZipException if a ZIP file error has occurred
  58. * @exception IOException if an I/O error has occurred
  59. */
  60. public ZipEntry getNextEntry() throws IOException {
  61. ensureOpen();
  62. if (entry != null) {
  63. closeEntry();
  64. }
  65. crc.reset();
  66. inf.reset();
  67. if ((entry = readLOC()) == null) {
  68. return null;
  69. }
  70. if (entry.method == STORED) {
  71. remaining = entry.size;
  72. }
  73. entryEOF = false;
  74. return entry;
  75. }
  76. /**
  77. * Closes the current ZIP entry and positions the stream for reading the
  78. * next entry.
  79. * @exception ZipException if a ZIP file error has occurred
  80. * @exception IOException if an I/O error has occurred
  81. */
  82. public void closeEntry() throws IOException {
  83. ensureOpen();
  84. while (read(tmpbuf, 0, tmpbuf.length) != -1) ;
  85. entryEOF = true;
  86. }
  87. /**
  88. * Returns 0 after EOF has reached for the current entry data,
  89. * otherwise always return 1.
  90. * <p>
  91. * Programs should not count on this method to return the actual number
  92. * of bytes that could be read without blocking.
  93. *
  94. * @return 1 before EOF and 0 after EOF has reached for current entry.
  95. * @exception IOException if an I/O error occurs.
  96. *
  97. */
  98. public int available() throws IOException {
  99. ensureOpen();
  100. if (entryEOF) {
  101. return 0;
  102. } else {
  103. return 1;
  104. }
  105. }
  106. /**
  107. * Reads from the current ZIP entry into an array of bytes. Blocks until
  108. * some input is available.
  109. * @param b the buffer into which the data is read
  110. * @param off the start offset of the data
  111. * @param len the maximum number of bytes read
  112. * @return the actual number of bytes read, or -1 if the end of the
  113. * entry is reached
  114. * @exception ZipException if a ZIP file error has occurred
  115. * @exception IOException if an I/O error has occurred
  116. */
  117. public int read(byte[] b, int off, int len) throws IOException {
  118. ensureOpen();
  119. if ((off < 0) || (off > b.length) || (len < 0) ||
  120. ((off + len) > b.length) || ((off + len) < 0)) {
  121. throw new IndexOutOfBoundsException();
  122. } else if (len == 0) {
  123. return 0;
  124. }
  125. if (entry == null) {
  126. return -1;
  127. }
  128. switch (entry.method) {
  129. case DEFLATED:
  130. len = super.read(b, off, len);
  131. if (len == -1) {
  132. readEnd(entry);
  133. entryEOF = true;
  134. entry = null;
  135. } else {
  136. crc.update(b, off, len);
  137. }
  138. return len;
  139. case STORED:
  140. if (remaining <= 0) {
  141. entryEOF = true;
  142. entry = null;
  143. return -1;
  144. }
  145. if (len > remaining) {
  146. len = (int)remaining;
  147. }
  148. len = in.read(b, off, len);
  149. if (len == -1) {
  150. throw new ZipException("unexpected EOF");
  151. }
  152. crc.update(b, off, len);
  153. remaining -= len;
  154. return len;
  155. default:
  156. throw new InternalError("invalid compression method");
  157. }
  158. }
  159. /**
  160. * Skips specified number of bytes in the current ZIP entry.
  161. * @param n the number of bytes to skip
  162. * @return the actual number of bytes skipped
  163. * @exception ZipException if a ZIP file error has occurred
  164. * @exception IOException if an I/O error has occurred
  165. * @exception IllegalArgumentException if n < 0
  166. */
  167. public long skip(long n) throws IOException {
  168. if (n < 0) {
  169. throw new IllegalArgumentException("negative skip length");
  170. }
  171. ensureOpen();
  172. int max = (int)Math.min(n, Integer.MAX_VALUE);
  173. int total = 0;
  174. while (total < max) {
  175. int len = max - total;
  176. if (len > tmpbuf.length) {
  177. len = tmpbuf.length;
  178. }
  179. len = read(tmpbuf, 0, len);
  180. if (len == -1) {
  181. entryEOF = true;
  182. break;
  183. }
  184. total += len;
  185. }
  186. return total;
  187. }
  188. /**
  189. * Closes the ZIP input stream.
  190. * @exception IOException if an I/O error has occurred
  191. */
  192. public void close() throws IOException {
  193. super.close();
  194. closed = true;
  195. }
  196. /*
  197. * Reads local file (LOC) header for next entry.
  198. */
  199. private ZipEntry readLOC() throws IOException {
  200. try {
  201. readFully(tmpbuf, 0, LOCHDR);
  202. } catch (EOFException e) {
  203. return null;
  204. }
  205. if (get32(tmpbuf, 0) != LOCSIG) {
  206. return null;
  207. }
  208. // get the entry name and create the ZipEntry first
  209. int len = get16(tmpbuf, LOCNAM);
  210. if (len == 0) {
  211. throw new ZipException("missing entry name");
  212. }
  213. byte[] b = new byte[len];
  214. readFully(b, 0, len);
  215. ZipEntry e = createZipEntry(getUTF8String(b, 0, len));
  216. // now get the remaining fields for the entry
  217. e.version = get16(tmpbuf, LOCVER);
  218. e.flag = get16(tmpbuf, LOCFLG);
  219. if ((e.flag & 1) == 1) {
  220. throw new ZipException("encrypted ZIP entry not supported");
  221. }
  222. e.method = get16(tmpbuf, LOCHOW);
  223. e.time = get32(tmpbuf, LOCTIM);
  224. if ((e.flag & 8) == 8) {
  225. /* EXT descriptor present */
  226. if (e.method != DEFLATED) {
  227. throw new ZipException(
  228. "only DEFLATED entries can have EXT descriptor");
  229. }
  230. } else {
  231. e.crc = get32(tmpbuf, LOCCRC);
  232. e.csize = get32(tmpbuf, LOCSIZ);
  233. e.size = get32(tmpbuf, LOCLEN);
  234. }
  235. len = get16(tmpbuf, LOCEXT);
  236. if (len > 0) {
  237. b = new byte[len];
  238. readFully(b, 0, len);
  239. e.extra = b;
  240. }
  241. return e;
  242. }
  243. /*
  244. * Fetches a UTF8-encoded String from the specified byte array.
  245. */
  246. private static String getUTF8String(byte[] b, int off, int len) {
  247. // First, count the number of characters in the sequence
  248. int count = 0;
  249. int max = off + len;
  250. int i = off;
  251. while (i < max) {
  252. int c = b[i++] & 0xff;
  253. switch (c >> 4) {
  254. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
  255. // 0xxxxxxx
  256. count++;
  257. break;
  258. case 12: case 13:
  259. // 110xxxxx 10xxxxxx
  260. if ((int)(b[i++] & 0xc0) != 0x80) {
  261. throw new IllegalArgumentException();
  262. }
  263. count++;
  264. break;
  265. case 14:
  266. // 1110xxxx 10xxxxxx 10xxxxxx
  267. if (((int)(b[i++] & 0xc0) != 0x80) ||
  268. ((int)(b[i++] & 0xc0) != 0x80)) {
  269. throw new IllegalArgumentException();
  270. }
  271. count++;
  272. break;
  273. default:
  274. // 10xxxxxx, 1111xxxx
  275. throw new IllegalArgumentException();
  276. }
  277. }
  278. if (i != max) {
  279. throw new IllegalArgumentException();
  280. }
  281. // Now decode the characters...
  282. char[] cs = new char[count];
  283. i = 0;
  284. while (off < max) {
  285. int c = b[off++] & 0xff;
  286. switch (c >> 4) {
  287. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
  288. // 0xxxxxxx
  289. cs[i++] = (char)c;
  290. break;
  291. case 12: case 13:
  292. // 110xxxxx 10xxxxxx
  293. cs[i++] = (char)(((c & 0x1f) << 6) | (b[off++] & 0x3f));
  294. break;
  295. case 14:
  296. // 1110xxxx 10xxxxxx 10xxxxxx
  297. int t = (b[off++] & 0x3f) << 6;
  298. cs[i++] = (char)(((c & 0x0f) << 12) | t | (b[off++] & 0x3f));
  299. break;
  300. default:
  301. // 10xxxxxx, 1111xxxx
  302. throw new IllegalArgumentException();
  303. }
  304. }
  305. return new String(cs, 0, count);
  306. }
  307. /**
  308. * Creates a new <code>ZipEntry</code> object for the specified
  309. * entry name.
  310. *
  311. * @param name the ZIP file entry name
  312. * @return the ZipEntry just created
  313. */
  314. protected ZipEntry createZipEntry(String name) {
  315. return new ZipEntry(name);
  316. }
  317. /*
  318. * Reads end of deflated entry as well as EXT descriptor if present.
  319. */
  320. private void readEnd(ZipEntry e) throws IOException {
  321. int n = inf.getRemaining();
  322. if (n > 0) {
  323. ((PushbackInputStream)in).unread(buf, len - n, n);
  324. }
  325. if ((e.flag & 8) == 8) {
  326. /* EXT descriptor present */
  327. readFully(tmpbuf, 0, EXTHDR);
  328. long sig = get32(tmpbuf, 0);
  329. if (sig != EXTSIG) {
  330. throw new ZipException("invalid EXT descriptor signature");
  331. }
  332. e.crc = get32(tmpbuf, EXTCRC);
  333. e.csize = get32(tmpbuf, EXTSIZ);
  334. e.size = get32(tmpbuf, EXTLEN);
  335. }
  336. if (e.size != inf.getTotalOut()) {
  337. throw new ZipException(
  338. "invalid entry size (expected " + e.size + " but got " +
  339. inf.getTotalOut() + " bytes)");
  340. }
  341. if (e.csize != inf.getTotalIn()) {
  342. throw new ZipException(
  343. "invalid entry compressed size (expected " + e.csize +
  344. " but got " + inf.getTotalIn() + " bytes)");
  345. }
  346. if (e.crc != crc.getValue()) {
  347. throw new ZipException(
  348. "invalid entry CRC (expected 0x" + Long.toHexString(e.crc) +
  349. " but got 0x" + Long.toHexString(crc.getValue()) + ")");
  350. }
  351. }
  352. /*
  353. * Reads bytes, blocking until all bytes are read.
  354. */
  355. private void readFully(byte[] b, int off, int len) throws IOException {
  356. while (len > 0) {
  357. int n = in.read(b, off, len);
  358. if (n == -1) {
  359. throw new EOFException();
  360. }
  361. off += n;
  362. len -= n;
  363. }
  364. }
  365. /*
  366. * Fetches unsigned 16-bit value from byte array at specified offset.
  367. * The bytes are assumed to be in Intel (little-endian) byte order.
  368. */
  369. private static final int get16(byte b[], int off) {
  370. return (b[off] & 0xff) | ((b[off+1] & 0xff) << 8);
  371. }
  372. /*
  373. * Fetches unsigned 32-bit value from byte array at specified offset.
  374. * The bytes are assumed to be in Intel (little-endian) byte order.
  375. */
  376. private static final long get32(byte b[], int off) {
  377. return get16(b, off) | ((long)get16(b, off+2) << 16);
  378. }
  379. }