@@ -290,14 +290,31 @@ protected void extractFile(
290290 }
291291 }
292292
293- // Hmm. Symlinks re-evaluate back to the original file here. Unsure if this is a good thing...
294- final File targetFileName = FileUtils .resolveFile (dir , entryName );
293+ // For symlinks, we need to get the file path without following symlinks.
294+ // FileUtils.resolveFile calls getCanonicalFile() which follows symlinks,
295+ // causing the symlink to resolve to its target instead of the symlink itself.
296+ final File targetFileName ;
297+ if (!StringUtils .isEmpty (symlinkDestination )) {
298+ // For symlinks, use simple path resolution without canonicalization
299+ targetFileName = resolveFileWithoutFollowingSymlinks (dir , entryName );
300+ } else {
301+ // For regular files and directories, use the existing logic
302+ targetFileName = FileUtils .resolveFile (dir , entryName );
303+ }
295304
296305 // Make sure that the resolved path of the extracted file doesn't escape the destination directory
297306 // getCanonicalFile().toPath() is used instead of getCanonicalPath() (returns String),
298307 // because "/opt/directory".startsWith("/opt/dir") would return false negative.
299308 Path canonicalDirPath = dir .getCanonicalFile ().toPath ();
300- Path canonicalDestPath = targetFileName .getCanonicalFile ().toPath ();
309+
310+ // For symlinks, we need to check the symlink path itself, not the target it points to
311+ Path canonicalDestPath ;
312+ if (!StringUtils .isEmpty (symlinkDestination )) {
313+ // For symlinks, normalize without following the link
314+ canonicalDestPath = targetFileName .toPath ().toAbsolutePath ().normalize ();
315+ } else {
316+ canonicalDestPath = targetFileName .getCanonicalFile ().toPath ();
317+ }
301318
302319 if (!canonicalDestPath .startsWith (canonicalDirPath )) {
303320 throw new ArchiverException ("Entry is outside of the target directory (" + entryName + ")" );
@@ -396,4 +413,33 @@ protected boolean shouldExtractEntry(File targetDirectory, File targetFileName,
396413 private String normalizedFileSeparator (String pathOrEntry ) {
397414 return pathOrEntry .replace ("/" , File .separator );
398415 }
416+
417+ /**
418+ * Resolves a file path relative to a base directory without following symlinks.
419+ * This is similar to FileUtils.resolveFile but doesn't call getCanonicalFile(),
420+ * which would follow symlinks and resolve them to their targets.
421+ *
422+ * @param baseDir the base directory
423+ * @param filename the filename to resolve
424+ * @return the resolved file
425+ */
426+ private File resolveFileWithoutFollowingSymlinks (File baseDir , String filename ) {
427+ String filenm = filename ;
428+ if ('/' != File .separatorChar ) {
429+ filenm = filename .replace ('/' , File .separatorChar );
430+ }
431+
432+ if ('\\' != File .separatorChar ) {
433+ filenm = filenm .replace ('\\' , File .separatorChar );
434+ }
435+
436+ // For absolute paths, just return a File object without canonicalization
437+ if (filenm .startsWith (File .separator )) {
438+ return new File (filenm );
439+ }
440+
441+ // For relative paths, combine with base directory and get absolute path
442+ // but don't call getCanonicalFile() which would follow symlinks
443+ return new File (baseDir , filenm ).getAbsoluteFile ();
444+ }
399445}
0 commit comments