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.

ZipFile.java 26 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  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. package org.apache.tools.zip;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.RandomAccessFile;
  23. import java.util.Calendar;
  24. import java.util.Collections;
  25. import java.util.Date;
  26. import java.util.Enumeration;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29. import java.util.zip.CRC32;
  30. import java.util.zip.Inflater;
  31. import java.util.zip.InflaterInputStream;
  32. import java.util.zip.ZipException;
  33. /**
  34. * Replacement for <code>java.util.ZipFile</code>.
  35. *
  36. * <p>This class adds support for file name encodings other than UTF-8
  37. * (which is required to work on ZIP files created by native zip tools
  38. * and is able to skip a preamble like the one found in self
  39. * extracting archives. Furthermore it returns instances of
  40. * <code>org.apache.tools.zip.ZipEntry</code> instead of
  41. * <code>java.util.zip.ZipEntry</code>.</p>
  42. *
  43. * <p>It doesn't extend <code>java.util.zip.ZipFile</code> as it would
  44. * have to reimplement all methods anyway. Like
  45. * <code>java.util.ZipFile</code>, it uses RandomAccessFile under the
  46. * covers and supports compressed and uncompressed entries.</p>
  47. *
  48. * <p>The method signatures mimic the ones of
  49. * <code>java.util.zip.ZipFile</code>, with a couple of exceptions:
  50. *
  51. * <ul>
  52. * <li>There is no getName method.</li>
  53. * <li>entries has been renamed to getEntries.</li>
  54. * <li>getEntries and getEntry return
  55. * <code>org.apache.tools.zip.ZipEntry</code> instances.</li>
  56. * <li>close is allowed to throw IOException.</li>
  57. * </ul>
  58. *
  59. */
  60. public class ZipFile {
  61. private static final int HASH_SIZE = 509;
  62. private static final int SHORT = 2;
  63. private static final int WORD = 4;
  64. private static final int NIBLET_MASK = 0x0f;
  65. private static final int BYTE_SHIFT = 8;
  66. private static final int POS_0 = 0;
  67. private static final int POS_1 = 1;
  68. private static final int POS_2 = 2;
  69. private static final int POS_3 = 3;
  70. /**
  71. * Maps ZipEntrys to Longs, recording the offsets of the local
  72. * file headers.
  73. */
  74. private final Map entries = new HashMap(HASH_SIZE);
  75. /**
  76. * Maps String to ZipEntrys, name -> actual entry.
  77. */
  78. private final Map nameMap = new HashMap(HASH_SIZE);
  79. private static final class OffsetEntry {
  80. private long headerOffset = -1;
  81. private long dataOffset = -1;
  82. }
  83. /**
  84. * The encoding to use for filenames and the file comment.
  85. *
  86. * <p>For a list of possible values see <a
  87. * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
  88. * Defaults to the platform's default character encoding.</p>
  89. */
  90. private String encoding = null;
  91. /**
  92. * The zip encoding to use for filenames and the file comment.
  93. */
  94. private final ZipEncoding zipEncoding;
  95. /**
  96. * The actual data source.
  97. */
  98. private RandomAccessFile archive;
  99. /**
  100. * Whether to look for and use Unicode extra fields.
  101. */
  102. private final boolean useUnicodeExtraFields;
  103. /**
  104. * Opens the given file for reading, assuming the platform's
  105. * native encoding for file names.
  106. *
  107. * @param f the archive.
  108. *
  109. * @throws IOException if an error occurs while reading the file.
  110. */
  111. public ZipFile(File f) throws IOException {
  112. this(f, null);
  113. }
  114. /**
  115. * Opens the given file for reading, assuming the platform's
  116. * native encoding for file names.
  117. *
  118. * @param name name of the archive.
  119. *
  120. * @throws IOException if an error occurs while reading the file.
  121. */
  122. public ZipFile(String name) throws IOException {
  123. this(new File(name), null);
  124. }
  125. /**
  126. * Opens the given file for reading, assuming the specified
  127. * encoding for file names, scanning unicode extra fields.
  128. *
  129. * @param name name of the archive.
  130. * @param encoding the encoding to use for file names
  131. *
  132. * @throws IOException if an error occurs while reading the file.
  133. */
  134. public ZipFile(String name, String encoding) throws IOException {
  135. this(new File(name), encoding, true);
  136. }
  137. /**
  138. * Opens the given file for reading, assuming the specified
  139. * encoding for file names and scanning for unicode extra fields.
  140. *
  141. * @param f the archive.
  142. * @param encoding the encoding to use for file names, use null
  143. * for the platform's default encoding
  144. *
  145. * @throws IOException if an error occurs while reading the file.
  146. */
  147. public ZipFile(File f, String encoding) throws IOException {
  148. this(f, encoding, true);
  149. }
  150. /**
  151. * Opens the given file for reading, assuming the specified
  152. * encoding for file names.
  153. *
  154. * @param f the archive.
  155. * @param encoding the encoding to use for file names, use null
  156. * for the platform's default encoding
  157. * @param useUnicodeExtraFields whether to use InfoZIP Unicode
  158. * Extra Fields (if present) to set the file names.
  159. *
  160. * @throws IOException if an error occurs while reading the file.
  161. */
  162. public ZipFile(File f, String encoding, boolean useUnicodeExtraFields)
  163. throws IOException {
  164. this.encoding = encoding;
  165. this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
  166. this.useUnicodeExtraFields = useUnicodeExtraFields;
  167. archive = new RandomAccessFile(f, "r");
  168. boolean success = false;
  169. try {
  170. Map entriesWithoutUTF8Flag = populateFromCentralDirectory();
  171. resolveLocalFileHeaderData(entriesWithoutUTF8Flag);
  172. success = true;
  173. } finally {
  174. if (!success) {
  175. try {
  176. archive.close();
  177. } catch (IOException e2) {
  178. // swallow, throw the original exception instead
  179. }
  180. }
  181. }
  182. }
  183. /**
  184. * The encoding to use for filenames and the file comment.
  185. *
  186. * @return null if using the platform's default character encoding.
  187. */
  188. public String getEncoding() {
  189. return encoding;
  190. }
  191. /**
  192. * Closes the archive.
  193. * @throws IOException if an error occurs closing the archive.
  194. */
  195. public void close() throws IOException {
  196. archive.close();
  197. }
  198. /**
  199. * close a zipfile quietly; throw no io fault, do nothing
  200. * on a null parameter
  201. * @param zipfile file to close, can be null
  202. */
  203. public static void closeQuietly(ZipFile zipfile) {
  204. if (zipfile != null) {
  205. try {
  206. zipfile.close();
  207. } catch (IOException e) {
  208. //ignore
  209. }
  210. }
  211. }
  212. /**
  213. * Returns all entries.
  214. * @return all entries as {@link ZipEntry} instances
  215. */
  216. public Enumeration getEntries() {
  217. return Collections.enumeration(entries.keySet());
  218. }
  219. /**
  220. * Returns a named entry - or <code>null</code> if no entry by
  221. * that name exists.
  222. * @param name name of the entry.
  223. * @return the ZipEntry corresponding to the given name - or
  224. * <code>null</code> if not present.
  225. */
  226. public ZipEntry getEntry(String name) {
  227. return (ZipEntry) nameMap.get(name);
  228. }
  229. /**
  230. * Returns an InputStream for reading the contents of the given entry.
  231. * @param ze the entry to get the stream for.
  232. * @return a stream to read the entry from.
  233. * @throws IOException if unable to create an input stream from the zipenty
  234. * @throws ZipException if the zipentry has an unsupported
  235. * compression method
  236. */
  237. public InputStream getInputStream(ZipEntry ze)
  238. throws IOException, ZipException {
  239. OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
  240. if (offsetEntry == null) {
  241. return null;
  242. }
  243. long start = offsetEntry.dataOffset;
  244. BoundedInputStream bis =
  245. new BoundedInputStream(start, ze.getCompressedSize());
  246. switch (ze.getMethod()) {
  247. case ZipEntry.STORED:
  248. return bis;
  249. case ZipEntry.DEFLATED:
  250. bis.addDummy();
  251. final Inflater inflater = new Inflater(true);
  252. return new InflaterInputStream(bis, inflater) {
  253. public void close() throws IOException {
  254. super.close();
  255. inflater.end();
  256. }
  257. };
  258. default:
  259. throw new ZipException("Found unsupported compression method "
  260. + ze.getMethod());
  261. }
  262. }
  263. private static final int CFH_LEN =
  264. /* version made by */ SHORT
  265. /* version needed to extract */ + SHORT
  266. /* general purpose bit flag */ + SHORT
  267. /* compression method */ + SHORT
  268. /* last mod file time */ + SHORT
  269. /* last mod file date */ + SHORT
  270. /* crc-32 */ + WORD
  271. /* compressed size */ + WORD
  272. /* uncompressed size */ + WORD
  273. /* filename length */ + SHORT
  274. /* extra field length */ + SHORT
  275. /* file comment length */ + SHORT
  276. /* disk number start */ + SHORT
  277. /* internal file attributes */ + SHORT
  278. /* external file attributes */ + WORD
  279. /* relative offset of local header */ + WORD;
  280. /**
  281. * Reads the central directory of the given archive and populates
  282. * the internal tables with ZipEntry instances.
  283. *
  284. * <p>The ZipEntrys will know all data that can be obtained from
  285. * the central directory alone, but not the data that requires the
  286. * local file header or additional data to be read.</p>
  287. *
  288. * @return a Map&lt;ZipEntry, NameAndComment>&gt; of
  289. * zipentries that didn't have the language encoding flag set when
  290. * read.
  291. */
  292. private Map populateFromCentralDirectory()
  293. throws IOException {
  294. HashMap noUTF8Flag = new HashMap();
  295. positionAtCentralDirectory();
  296. byte[] cfh = new byte[CFH_LEN];
  297. byte[] signatureBytes = new byte[WORD];
  298. archive.readFully(signatureBytes);
  299. long sig = ZipLong.getValue(signatureBytes);
  300. final long cfhSig = ZipLong.getValue(ZipOutputStream.CFH_SIG);
  301. if (sig != cfhSig && startsWithLocalFileHeader()) {
  302. throw new IOException("central directory is empty, can't expand"
  303. + " corrupt archive.");
  304. }
  305. while (sig == cfhSig) {
  306. archive.readFully(cfh);
  307. int off = 0;
  308. ZipEntry ze = new ZipEntry();
  309. int versionMadeBy = ZipShort.getValue(cfh, off);
  310. off += SHORT;
  311. ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK);
  312. off += SHORT; // skip version info
  313. final int generalPurposeFlag = ZipShort.getValue(cfh, off);
  314. final boolean hasUTF8Flag =
  315. (generalPurposeFlag & ZipOutputStream.UFT8_NAMES_FLAG) != 0;
  316. final ZipEncoding entryEncoding =
  317. hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
  318. off += SHORT;
  319. ze.setMethod(ZipShort.getValue(cfh, off));
  320. off += SHORT;
  321. // FIXME this is actually not very cpu cycles friendly as we are converting from
  322. // dos to java while the underlying Sun implementation will convert
  323. // from java to dos time for internal storage...
  324. long time = dosToJavaTime(ZipLong.getValue(cfh, off));
  325. ze.setTime(time);
  326. off += WORD;
  327. ze.setCrc(ZipLong.getValue(cfh, off));
  328. off += WORD;
  329. ze.setCompressedSize(ZipLong.getValue(cfh, off));
  330. off += WORD;
  331. ze.setSize(ZipLong.getValue(cfh, off));
  332. off += WORD;
  333. int fileNameLen = ZipShort.getValue(cfh, off);
  334. off += SHORT;
  335. int extraLen = ZipShort.getValue(cfh, off);
  336. off += SHORT;
  337. int commentLen = ZipShort.getValue(cfh, off);
  338. off += SHORT;
  339. off += SHORT; // disk number
  340. ze.setInternalAttributes(ZipShort.getValue(cfh, off));
  341. off += SHORT;
  342. ze.setExternalAttributes(ZipLong.getValue(cfh, off));
  343. off += WORD;
  344. byte[] fileName = new byte[fileNameLen];
  345. archive.readFully(fileName);
  346. ze.setName(entryEncoding.decode(fileName));
  347. // LFH offset,
  348. OffsetEntry offset = new OffsetEntry();
  349. offset.headerOffset = ZipLong.getValue(cfh, off);
  350. // data offset will be filled later
  351. entries.put(ze, offset);
  352. nameMap.put(ze.getName(), ze);
  353. byte[] cdExtraData = new byte[extraLen];
  354. archive.readFully(cdExtraData);
  355. ze.setCentralDirectoryExtra(cdExtraData);
  356. byte[] comment = new byte[commentLen];
  357. archive.readFully(comment);
  358. ze.setComment(entryEncoding.decode(comment));
  359. archive.readFully(signatureBytes);
  360. sig = ZipLong.getValue(signatureBytes);
  361. if (!hasUTF8Flag && useUnicodeExtraFields) {
  362. noUTF8Flag.put(ze, new NameAndComment(fileName, comment));
  363. }
  364. }
  365. return noUTF8Flag;
  366. }
  367. private static final int MIN_EOCD_SIZE =
  368. /* end of central dir signature */ WORD
  369. /* number of this disk */ + SHORT
  370. /* number of the disk with the */
  371. /* start of the central directory */ + SHORT
  372. /* total number of entries in */
  373. /* the central dir on this disk */ + SHORT
  374. /* total number of entries in */
  375. /* the central dir */ + SHORT
  376. /* size of the central directory */ + WORD
  377. /* offset of start of central */
  378. /* directory with respect to */
  379. /* the starting disk number */ + WORD
  380. /* zipfile comment length */ + SHORT;
  381. private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE
  382. /* maximum length of zipfile comment */ + 0xFFFF;
  383. private static final int CFD_LOCATOR_OFFSET =
  384. /* end of central dir signature */ WORD
  385. /* number of this disk */ + SHORT
  386. /* number of the disk with the */
  387. /* start of the central directory */ + SHORT
  388. /* total number of entries in */
  389. /* the central dir on this disk */ + SHORT
  390. /* total number of entries in */
  391. /* the central dir */ + SHORT
  392. /* size of the central directory */ + WORD;
  393. /**
  394. * Searches for the &quot;End of central dir record&quot;, parses
  395. * it and positions the stream at the first central directory
  396. * record.
  397. */
  398. private void positionAtCentralDirectory()
  399. throws IOException {
  400. boolean found = false;
  401. long off = archive.length() - MIN_EOCD_SIZE;
  402. long stopSearching = Math.max(0L, archive.length() - MAX_EOCD_SIZE);
  403. if (off >= 0) {
  404. archive.seek(off);
  405. byte[] sig = ZipOutputStream.EOCD_SIG;
  406. int curr = archive.read();
  407. while (off >= stopSearching && curr != -1) {
  408. if (curr == sig[POS_0]) {
  409. curr = archive.read();
  410. if (curr == sig[POS_1]) {
  411. curr = archive.read();
  412. if (curr == sig[POS_2]) {
  413. curr = archive.read();
  414. if (curr == sig[POS_3]) {
  415. found = true;
  416. break;
  417. }
  418. }
  419. }
  420. }
  421. archive.seek(--off);
  422. curr = archive.read();
  423. }
  424. }
  425. if (!found) {
  426. throw new ZipException("archive is not a ZIP archive");
  427. }
  428. archive.seek(off + CFD_LOCATOR_OFFSET);
  429. byte[] cfdOffset = new byte[WORD];
  430. archive.readFully(cfdOffset);
  431. archive.seek(ZipLong.getValue(cfdOffset));
  432. }
  433. /**
  434. * Number of bytes in local file header up to the &quot;length of
  435. * filename&quot; entry.
  436. */
  437. private static final long LFH_OFFSET_FOR_FILENAME_LENGTH =
  438. /* local file header signature */ WORD
  439. /* version needed to extract */ + SHORT
  440. /* general purpose bit flag */ + SHORT
  441. /* compression method */ + SHORT
  442. /* last mod file time */ + SHORT
  443. /* last mod file date */ + SHORT
  444. /* crc-32 */ + WORD
  445. /* compressed size */ + WORD
  446. /* uncompressed size */ + WORD;
  447. /**
  448. * Walks through all recorded entries and adds the data available
  449. * from the local file header.
  450. *
  451. * <p>Also records the offsets for the data to read from the
  452. * entries.</p>
  453. */
  454. private void resolveLocalFileHeaderData(Map entriesWithoutUTF8Flag)
  455. throws IOException {
  456. Enumeration e = getEntries();
  457. while (e.hasMoreElements()) {
  458. ZipEntry ze = (ZipEntry) e.nextElement();
  459. OffsetEntry offsetEntry = (OffsetEntry) entries.get(ze);
  460. long offset = offsetEntry.headerOffset;
  461. archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH);
  462. byte[] b = new byte[SHORT];
  463. archive.readFully(b);
  464. int fileNameLen = ZipShort.getValue(b);
  465. archive.readFully(b);
  466. int extraFieldLen = ZipShort.getValue(b);
  467. int lenToSkip = fileNameLen;
  468. while (lenToSkip > 0) {
  469. int skipped = archive.skipBytes(lenToSkip);
  470. if (skipped <= 0) {
  471. throw new RuntimeException("failed to skip file name in"
  472. + " local file header");
  473. }
  474. lenToSkip -= skipped;
  475. }
  476. byte[] localExtraData = new byte[extraFieldLen];
  477. archive.readFully(localExtraData);
  478. ze.setExtra(localExtraData);
  479. /*dataOffsets.put(ze,
  480. new Long(offset + LFH_OFFSET_FOR_FILENAME_LENGTH
  481. + SHORT + SHORT + fileNameLen + extraFieldLen));
  482. */
  483. offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH
  484. + SHORT + SHORT + fileNameLen + extraFieldLen;
  485. if (entriesWithoutUTF8Flag.containsKey(ze)) {
  486. setNameAndCommentFromExtraFields(ze,
  487. (NameAndComment)
  488. entriesWithoutUTF8Flag.get(ze));
  489. }
  490. }
  491. }
  492. /**
  493. * Convert a DOS date/time field to a Date object.
  494. *
  495. * @param zipDosTime contains the stored DOS time.
  496. * @return a Date instance corresponding to the given time.
  497. */
  498. protected static Date fromDosTime(ZipLong zipDosTime) {
  499. long dosTime = zipDosTime.getValue();
  500. return new Date(dosToJavaTime(dosTime));
  501. }
  502. /*
  503. * Converts DOS time to Java time (number of milliseconds since epoch).
  504. */
  505. private static long dosToJavaTime(long dosTime) {
  506. Calendar cal = Calendar.getInstance();
  507. // CheckStyle:MagicNumberCheck OFF - no point
  508. cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
  509. cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
  510. cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
  511. cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
  512. cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
  513. cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
  514. // CheckStyle:MagicNumberCheck ON
  515. return cal.getTime().getTime();
  516. }
  517. /**
  518. * Retrieve a String from the given bytes using the encoding set
  519. * for this ZipFile.
  520. *
  521. * @param bytes the byte array to transform
  522. * @return String obtained by using the given encoding
  523. * @throws ZipException if the encoding cannot be recognized.
  524. */
  525. protected String getString(byte[] bytes) throws ZipException {
  526. try {
  527. return ZipEncodingHelper.getZipEncoding(encoding).decode(bytes);
  528. } catch (IOException ex) {
  529. throw new ZipException("Failed to decode name: " + ex.getMessage());
  530. }
  531. }
  532. /**
  533. * Checks whether the archive starts with a LFH. If it doesn't,
  534. * it may be an empty archive.
  535. */
  536. private boolean startsWithLocalFileHeader() throws IOException {
  537. archive.seek(0);
  538. final byte[] start = new byte[WORD];
  539. archive.readFully(start);
  540. for (int i = 0; i < start.length; i++) {
  541. if (start[i] != ZipOutputStream.LFH_SIG[i]) {
  542. return false;
  543. }
  544. }
  545. return true;
  546. }
  547. /**
  548. * If the entry has Unicode*ExtraFields and the CRCs of the
  549. * names/comments match those of the extra fields, transfer the
  550. * known Unicode values from the extra field.
  551. */
  552. private void setNameAndCommentFromExtraFields(ZipEntry ze,
  553. NameAndComment nc) {
  554. UnicodePathExtraField name = (UnicodePathExtraField)
  555. ze.getExtraField(UnicodePathExtraField.UPATH_ID);
  556. String originalName = ze.getName();
  557. String newName = getUnicodeStringIfOriginalMatches(name, nc.name);
  558. if (newName != null && !originalName.equals(newName)) {
  559. ze.setName(newName);
  560. nameMap.remove(originalName);
  561. nameMap.put(newName, ze);
  562. }
  563. if (nc.comment != null && nc.comment.length > 0) {
  564. UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
  565. ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
  566. String newComment =
  567. getUnicodeStringIfOriginalMatches(cmt, nc.comment);
  568. if (newComment != null) {
  569. ze.setComment(newComment);
  570. }
  571. }
  572. }
  573. /**
  574. * If the stored CRC matches the one of the given name, return the
  575. * Unicode name of the given field.
  576. *
  577. * <p>If the field is null or the CRCs don't match, return null
  578. * instead.</p>
  579. */
  580. private String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
  581. byte[] orig) {
  582. if (f != null) {
  583. CRC32 crc32 = new CRC32();
  584. crc32.update(orig);
  585. long origCRC32 = crc32.getValue();
  586. if (origCRC32 == f.getNameCRC32()) {
  587. try {
  588. return ZipEncodingHelper
  589. .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
  590. } catch (IOException ex) {
  591. // UTF-8 unsupported? should be impossible the
  592. // Unicode*ExtraField must contain some bad bytes
  593. // TODO log this anywhere?
  594. return null;
  595. }
  596. }
  597. }
  598. return null;
  599. }
  600. /**
  601. * InputStream that delegates requests to the underlying
  602. * RandomAccessFile, making sure that only bytes from a certain
  603. * range can be read.
  604. */
  605. private class BoundedInputStream extends InputStream {
  606. private long remaining;
  607. private long loc;
  608. private boolean addDummyByte = false;
  609. BoundedInputStream(long start, long remaining) {
  610. this.remaining = remaining;
  611. loc = start;
  612. }
  613. public int read() throws IOException {
  614. if (remaining-- <= 0) {
  615. if (addDummyByte) {
  616. addDummyByte = false;
  617. return 0;
  618. }
  619. return -1;
  620. }
  621. synchronized (archive) {
  622. archive.seek(loc++);
  623. return archive.read();
  624. }
  625. }
  626. public int read(byte[] b, int off, int len) throws IOException {
  627. if (remaining <= 0) {
  628. if (addDummyByte) {
  629. addDummyByte = false;
  630. b[off] = 0;
  631. return 1;
  632. }
  633. return -1;
  634. }
  635. if (len <= 0) {
  636. return 0;
  637. }
  638. if (len > remaining) {
  639. len = (int) remaining;
  640. }
  641. int ret = -1;
  642. synchronized (archive) {
  643. archive.seek(loc);
  644. ret = archive.read(b, off, len);
  645. }
  646. if (ret > 0) {
  647. loc += ret;
  648. remaining -= ret;
  649. }
  650. return ret;
  651. }
  652. /**
  653. * Inflater needs an extra dummy byte for nowrap - see
  654. * Inflater's javadocs.
  655. */
  656. void addDummy() {
  657. addDummyByte = true;
  658. }
  659. }
  660. private static final class NameAndComment {
  661. private final byte[] name;
  662. private final byte[] comment;
  663. private NameAndComment(byte[] name, byte[] comment) {
  664. this.name = name;
  665. this.comment = comment;
  666. }
  667. }
  668. }