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