Author: julius Date: Mon Jan 14 21:42:48 2013 New Revision: 1433151 URL: http://svn.apache.org/viewvc?rev=1433151&view=rev Log: COMPRESS-214 - better support for unix symlinks
Added: commons/proper/compress/trunk/src/test/resources/COMPRESS-214_unix_symlinks.zip (with props) Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/utils/IOUtils.java commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryTest.java Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java?rev=1433151&r1=1433150&r2=1433151&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntry.java Mon Jan 14 21:42:48 2013 @@ -17,14 +17,18 @@ */ package org.apache.commons.compress.archivers.zip; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.utils.IOUtils; + import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.zip.ZipException; -import org.apache.commons.compress.archivers.ArchiveEntry; /** * Extension that adds better handling of extra fields and provides @@ -267,6 +271,83 @@ public class ZipArchiveEntry extends jav } /** + * Returns true if this entry represents a unix symlink, + * in which case the entry's content contains the target path + * for the symlink. + * + * @return true if the entry represents a unix symlink, false otherwise. + */ + public boolean isUnixSymlink() { + return (getUnixMode() & UnixStat.LINK_FLAG) == UnixStat.LINK_FLAG; + } + + /** + * <p> + * Convenience method to return this entry's content as a String if isUnixSymlink() + * returns true, otherwise returns null. + * This method assumes the symlink's target path has been stored in the zip file using the + * UTF-8 character encoding. + * </p><p> + * Unfortunately, there is no reliable way to automatically obtain the target path's + * character encoding (within the zip file) should it differ from UTF-8. Zip files + * do not remember the character set used to encode zip contents. + * Entry names do not have this problem (thanks to various flags and extra fields), + * but symlink targets are not so lucky. If you happen to know the character set that was used + * to encode symlink paths in the zip file, feel free to use the overloaded version: + * <code>getUnixSymlink(ZipFile zf, String pathCharset)</code>. + * </p><p> + * This method can only be used in conjunction with a ZipFile object (since we need + * the entry's contents); users of ZipArchiveInputStream will need to create their + * own logic. + * </p> + * + * @param zf ZipFile object that contains this ZipArchiveEntry's contents. + * @return entry's content as a String (UTF-8 character set assumed). + * @throws IOException problem with content's input stream + */ + public String getUnixSymlink(ZipFile zf) throws IOException { + return getUnixSymlink(zf, "UTF-8"); + } + + /** + * <p> + * Convenience method to return this entry's content as a String if isUnixSymlink() + * returns true, otherwise returns null. + * </p><p> + * Unfortunately, there is no reliable way to automatically obtain the target path's + * character encoding (within the zip file) should it differ from UTF-8. Zip files + * do not remember the character set used to encode zip contents. + * Entry names do not have this problem (thanks to various flags and extra fields), + * but symlink targets are not so lucky. + * </p><p> + * This method can only be used in conjunction with a ZipFile object (since we need + * the entry's contents); users of ZipArchiveInputStream will need to create their + * own logic. + * </p> + * + * @param zf ZipFile object that contains this ZipArchiveEntry's contents. + * @param pathCharset the character set used to encode the symlink's target path. + * @return entry's content as a String (using the provided character set). + * @throws IOException problem with content's input stream + */ + public String getUnixSymlink(ZipFile zf, String pathCharset) throws IOException { + if (isUnixSymlink()) { + InputStream in = null; + try { + in = zf.getInputStream(this); + byte[] symlinkBytes = IOUtils.toByteArray(in); + return new String(symlinkBytes, pathCharset); + } finally { + if (in != null) { + in.close(); + } + } + } else { + return null; + } + } + + /** * Platform specification to put into the "version made * by" part of the central file header. * Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/utils/IOUtils.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/utils/IOUtils.java?rev=1433151&r1=1433150&r2=1433151&view=diff ============================================================================== --- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/utils/IOUtils.java (original) +++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/utils/IOUtils.java Mon Jan 14 21:42:48 2013 @@ -18,6 +18,7 @@ */ package org.apache.commons.compress.utils; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -69,4 +70,28 @@ public final class IOUtils { } return count; } + + + // toByteArray(InputStream) copied from: + // commons/proper/io/trunk/src/main/java/org/apache/commons/io/IOUtils.java?revision=1428941 + // January 8th, 2013 + // + // Assuming our copy() works just as well as theirs! :-) + + /** + * Gets the contents of an <code>InputStream</code> as a <code>byte[]</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input the <code>InputStream</code> to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws IOException if an I/O error occurs + */ + public static byte[] toByteArray(final InputStream input) throws IOException { + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } } Modified: commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryTest.java URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryTest.java?rev=1433151&r1=1433150&r2=1433151&view=diff ============================================================================== --- commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryTest.java (original) +++ commons/proper/compress/trunk/src/test/java/org/apache/commons/compress/archivers/zip/ZipArchiveEntryTest.java Mon Jan 14 21:42:48 2013 @@ -18,11 +18,16 @@ package org.apache.commons.compress.archivers.zip; +import junit.framework.TestCase; + import java.io.ByteArrayOutputStream; +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.util.Enumeration; +import java.util.TreeMap; import java.util.zip.ZipEntry; -import junit.framework.TestCase; - /** * JUnit 3 testcases for org.apache.commons.compress.archivers.zip.ZipEntry. * @@ -33,6 +38,54 @@ public class ZipArchiveEntryTest extends super(name); } + public void testUnixSymlinkSampleFile() throws Exception { + final String entryPrefix = "COMPRESS-214_unix_symlinks/"; + final TreeMap<String, String> expectedVals = new TreeMap<String, String>(); + + // I threw in some Japanese characters to keep things interesting. + expectedVals.put(entryPrefix + "link1", "../COMPRESS-214_unix_symlinks/./a/b/c/../../../\uF999"); + expectedVals.put(entryPrefix + "link2", "../COMPRESS-214_unix_symlinks/./a/b/c/../../../g"); + expectedVals.put(entryPrefix + "link3", "../COMPRESS-214_unix_symlinks/././a/b/c/../../../\u76F4\u6A39"); + expectedVals.put(entryPrefix + "link4", "\u82B1\u5B50/\u745B\u5B50"); + expectedVals.put(entryPrefix + "\uF999", "./\u82B1\u5B50/\u745B\u5B50/\u5897\u8C37/\uF999"); + expectedVals.put(entryPrefix + "g", "./a/b/c/d/e/f/g"); + expectedVals.put(entryPrefix + "\u76F4\u6A39", "./g"); + + // Notice how a directory link might contain a trailing slash, or it might not. + // Also note: symlinks are always stored as files, even if they link to directories. + expectedVals.put(entryPrefix + "link5", "../COMPRESS-214_unix_symlinks/././a/b"); + expectedVals.put(entryPrefix + "link6", "../COMPRESS-214_unix_symlinks/././a/b/"); + + // I looked into creating a test with hard links, but zip does not appear to + // support hard links, so nevermind. + + URL zip = getClass().getResource("/COMPRESS-214_unix_symlinks.zip"); + File archive = new File(new URI(zip.toString())); + ZipFile zf = null; + + try { + zf = new ZipFile(archive); + Enumeration<ZipArchiveEntry> en = zf.getEntries(); + while (en.hasMoreElements()) { + ZipArchiveEntry zae = en.nextElement(); + if (zae.isUnixSymlink()) { + String name = zae.getName(); + String link = zae.getUnixSymlink(zf); + String expected = expectedVals.get(name); + assertEquals(expected, link); + } else { + String link = zae.getUnixSymlink(zf); + // Should be null if it's not a symlink! + assertNull(link); + } + } + } finally { + if (zf != null) { + zf.close(); + } + } + } + /** * test handling of extra fields */ Added: commons/proper/compress/trunk/src/test/resources/COMPRESS-214_unix_symlinks.zip URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/test/resources/COMPRESS-214_unix_symlinks.zip?rev=1433151&view=auto ============================================================================== Binary file - no diff available. Propchange: commons/proper/compress/trunk/src/test/resources/COMPRESS-214_unix_symlinks.zip ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream