| @@ -1,6 +1,19 @@ | |||||
| Changes from Ant 1.9.11 TO Ant 1.9.12 | Changes from Ant 1.9.11 TO Ant 1.9.12 | ||||
| ===================================== | ===================================== | ||||
| Changes that could break older environments: | |||||
| ------------------------------------------- | |||||
| * <unzip>, <unjar> and <untar> will no longer extract entries whose | |||||
| names would make the created files be placed outside of the | |||||
| destination directory anymore by default. A new attribute | |||||
| allowFilesToEscapeDest can be used to override the behavior. | |||||
| Another special case is when stripAbsolutePathSpec is false (which | |||||
| still is the default) and the entry's name starts with a | |||||
| (back)slash and allowFilesToEscapeDest hasn't been specified | |||||
| explicitly, in this case the file may be created outside of the | |||||
| dest directory as well. | |||||
| Fixed bugs: | Fixed bugs: | ||||
| ----------- | ----------- | ||||
| @@ -69,6 +69,7 @@ public class Expand extends Task { | |||||
| private boolean failOnEmptyArchive = false; | private boolean failOnEmptyArchive = false; | ||||
| private boolean stripAbsolutePathSpec = false; | private boolean stripAbsolutePathSpec = false; | ||||
| private boolean scanForUnicodeExtraFields = true; | private boolean scanForUnicodeExtraFields = true; | ||||
| private Boolean allowFilesToEscapeDest = null; | |||||
| public static final String NATIVE_ENCODING = "native-encoding"; | public static final String NATIVE_ENCODING = "native-encoding"; | ||||
| @@ -259,14 +260,17 @@ public class Expand extends Task { | |||||
| boolean isDirectory, FileNameMapper mapper) | boolean isDirectory, FileNameMapper mapper) | ||||
| throws IOException { | throws IOException { | ||||
| if (stripAbsolutePathSpec && entryName.length() > 0 | |||||
| final boolean entryNameStartsWithPathSpec = entryName.length() > 0 | |||||
| && (entryName.charAt(0) == File.separatorChar | && (entryName.charAt(0) == File.separatorChar | ||||
| || entryName.charAt(0) == '/' | || entryName.charAt(0) == '/' | ||||
| || entryName.charAt(0) == '\\')) { | |||||
| || entryName.charAt(0) == '\\'); | |||||
| if (stripAbsolutePathSpec && entryNameStartsWithPathSpec) { | |||||
| log("stripped absolute path spec from " + entryName, | log("stripped absolute path spec from " + entryName, | ||||
| Project.MSG_VERBOSE); | Project.MSG_VERBOSE); | ||||
| entryName = entryName.substring(1); | entryName = entryName.substring(1); | ||||
| } | } | ||||
| boolean allowedOutsideOfDest = Boolean.TRUE == getAllowFilesToEscapeDest() | |||||
| || null == getAllowFilesToEscapeDest() && !stripAbsolutePathSpec && entryNameStartsWithPathSpec; | |||||
| if (patternsets != null && patternsets.size() > 0) { | if (patternsets != null && patternsets.size() > 0) { | ||||
| String name = entryName.replace('/', File.separatorChar) | String name = entryName.replace('/', File.separatorChar) | ||||
| @@ -332,6 +336,12 @@ public class Expand extends Task { | |||||
| mappedNames = new String[] {entryName}; | mappedNames = new String[] {entryName}; | ||||
| } | } | ||||
| File f = fileUtils.resolveFile(dir, mappedNames[0]); | File f = fileUtils.resolveFile(dir, mappedNames[0]); | ||||
| if (!allowedOutsideOfDest && !fileUtils.isLeadingPath(dir, f)) { | |||||
| log("skipping " + entryName + " as its target " + f + " is outside of " | |||||
| + dir + ".", Project.MSG_VERBOSE); | |||||
| return; | |||||
| } | |||||
| try { | try { | ||||
| if (!overwrite && f.exists() | if (!overwrite && f.exists() | ||||
| && f.lastModified() >= entryDate.getTime()) { | && f.lastModified() >= entryDate.getTime()) { | ||||
| @@ -533,4 +543,25 @@ public class Expand extends Task { | |||||
| return scanForUnicodeExtraFields; | return scanForUnicodeExtraFields; | ||||
| } | } | ||||
| /** | |||||
| * Whether to allow the extracted file or directory to be outside of the dest directory. | |||||
| * | |||||
| * @param b the flag | |||||
| * @since Ant 1.9.12 | |||||
| */ | |||||
| public void setAllowFilesToEscapeDest(boolean b) { | |||||
| allowFilesToEscapeDest = b; | |||||
| } | |||||
| /** | |||||
| * Whether to allow the extracted file or directory to be outside of the dest directory. | |||||
| * | |||||
| * @return {@code null} if the flag hasn't been set explicitly, | |||||
| * otherwise the value set by the user. | |||||
| * @since Ant 1.9.12 | |||||
| */ | |||||
| public Boolean getAllowFilesToEscapeDest() { | |||||
| return allowFilesToEscapeDest; | |||||
| } | |||||
| } | } | ||||
| @@ -24,6 +24,10 @@ | |||||
| <mkdir dir="${output}" /> | <mkdir dir="${output}" /> | ||||
| </target> | </target> | ||||
| <target name="tearDown" depends="antunit-base.tearDown"> | |||||
| <delete dir="/tmp/testdir"/> | |||||
| </target> | |||||
| <target name="testFailureOnBrokenCentralDirectoryStructure"> | <target name="testFailureOnBrokenCentralDirectoryStructure"> | ||||
| <au:expectfailure | <au:expectfailure | ||||
| expectedmessage="central directory is empty, can't expand corrupt archive."> | expectedmessage="central directory is empty, can't expand corrupt archive."> | ||||
| @@ -67,4 +71,46 @@ | |||||
| <!-- failed on Windows and other OSes with implicit file locking --> | <!-- failed on Windows and other OSes with implicit file locking --> | ||||
| <au:assertFileDoesntExist file="${input}/test.zip"/> | <au:assertFileDoesntExist file="${input}/test.zip"/> | ||||
| </target> | </target> | ||||
| <target name="testEntriesDontEscapeDestByDefault"> | |||||
| <mkdir dir="${input}/"/> | |||||
| <mkdir dir="${output}/"/> | |||||
| <unzip src="zip/direscape.zip" dest="${output}"/> | |||||
| <au:assertFileDoesntExist file="${input}/a"/> | |||||
| </target> | |||||
| <target name="testEntriesCanEscapeDestIfRequested"> | |||||
| <mkdir dir="${input}/"/> | |||||
| <mkdir dir="${output}/"/> | |||||
| <unzip src="zip/direscape.zip" dest="${output}" allowFilesToEscapeDest="true"/> | |||||
| <au:assertFileExists file="${input}/a"/> | |||||
| </target> | |||||
| <target name="-can-write-to-tmp?"> | |||||
| <mkdir dir="${input}"/> | |||||
| <echo file="${input}/A.java"><![CDATA[ | |||||
| public class A { | |||||
| public static void main(String[] args) { | |||||
| new java.io.File("/tmp/testdir/").mkdirs(); | |||||
| } | |||||
| } | |||||
| ]]></echo> | |||||
| <mkdir dir="${output}"/> | |||||
| <javac srcdir="${input}" destdir="${output}"/> | |||||
| <java classname="A" classpath="${output}"/> | |||||
| <available property="can-write-to-tmp!" file="/tmp/testdir/"/> | |||||
| </target> | |||||
| <target name="testEntriesCanEscapeDestViaAbsolutePathByDefault" | |||||
| depends="-can-write-to-tmp?" if="can-write-to-tmp!"> | |||||
| <unzip src="zip/direscape-absolute.zip" dest="${output}"/> | |||||
| <au:assertFileExists file="/tmp/testdir/a"/> | |||||
| </target> | |||||
| <target name="testEntriesDontEscapeDestViaAbsolutePathIfProhibited" | |||||
| depends="-can-write-to-tmp?" if="can-write-to-tmp!"> | |||||
| <unzip src="zip/direscape-absolute.zip" dest="${output}" | |||||
| allowFilesToEscapeDest="false"/> | |||||
| <au:assertFileDoesntExist file="/tmp/testdir/a"/> | |||||
| </target> | |||||
| </project> | </project> | ||||