diff --git a/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java b/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java index c61327c5..9f0cf2c8 100644 --- a/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java +++ b/src/main/java/org/codehaus/plexus/archiver/AbstractUnArchiver.java @@ -324,6 +324,13 @@ protected void extractFile( } else if (isDirectory) { targetFileName.mkdirs(); } else { + // Delete existing file first to handle read-only files + // This matches the behavior of tar and unzip + if (targetFileName.exists()) { + // Make file writable before deleting (required on Windows for read-only files) + targetFileName.setWritable(true); + targetFileName.delete(); + } Files.copy(compressedInputStream, targetFileName.toPath(), REPLACE_EXISTING); } diff --git a/src/test/java/org/codehaus/plexus/archiver/AbstractUnArchiverTest.java b/src/test/java/org/codehaus/plexus/archiver/AbstractUnArchiverTest.java index a73ac715..3c57c541 100644 --- a/src/test/java/org/codehaus/plexus/archiver/AbstractUnArchiverTest.java +++ b/src/test/java/org/codehaus/plexus/archiver/AbstractUnArchiverTest.java @@ -188,4 +188,33 @@ void shouldExtractWhenCasingDifferOnlyInEntryNamePath(@TempDir File temporaryFol assertTrue(abstractUnArchiver.shouldExtractEntry(temporaryFolder, file, entryName, entryDate)); assertEquals(0, abstractUnArchiver.casingMessageEmitted.get()); } + + @Test + void shouldExtractReadOnlyFile(@TempDir File temporaryFolder) throws Exception { + // given + File readOnlyFile = new File(temporaryFolder, "readonly.txt"); + readOnlyFile.createNewFile(); + java.nio.file.Files.write(readOnlyFile.toPath(), "original content".getBytes()); + + // Make the file read-only (simulate -r-xr-xr-x permissions) + readOnlyFile.setWritable(false); + assertTrue(readOnlyFile.exists()); + assertFalse(readOnlyFile.canWrite()); + + // Create a mock input stream with new content + String newContent = "new content"; + java.io.InputStream inputStream = new java.io.ByteArrayInputStream(newContent.getBytes()); + + // when + abstractUnArchiver.setOverwrite(true); + abstractUnArchiver.extractFile( + null, temporaryFolder, inputStream, "readonly.txt", new Date(), false, null, null, null); + + // then + // The file should have been successfully overwritten + assertTrue(readOnlyFile.exists()); + byte[] actualBytes = java.nio.file.Files.readAllBytes(readOnlyFile.toPath()); + String actualContent = new String(actualBytes); + assertEquals(newContent, actualContent); + } }