diff options
author | Marius Volkhart <mariusvolkhart@apache.org> | 2021-02-28 23:16:14 +0000 |
---|---|---|
committer | Marius Volkhart <mariusvolkhart@apache.org> | 2021-02-28 23:16:14 +0000 |
commit | d1c9a07860e365db4e335d24ad67e87544bdcceb (patch) | |
tree | 76c8d1fa79182df3aba38df998a41ab3e10a9c34 /src/scratchpad/testcases/org | |
parent | 77ec895ee01e8213d0b9f36f4c05dc839648294f (diff) | |
download | poi-d1c9a07860e365db4e335d24ad67e87544bdcceb.tar.gz poi-d1c9a07860e365db4e335d24ad67e87544bdcceb.zip |
Add the ability to edit HSLFPictureData contents
Pictures can now be edited by calling HSLFPictureData#setData(byte[]). The byte[] should contain the image data as an image viewer might read it.
To enable this functionality, a tighter coupling between the EscherBSERecords of the slideshow and the HSLFPictureData was required. This ensures that changes in image data size are accurately recorded in the records.
In the course of coupling the records and the HSLFPictureData, various scenarios arose where a mapping of records to pictures was non-trivial. Accordingly, the HSLFSlideShowImpl#matchPicturesAndRecords(...) function was added to perform a more sophisticated matching pass. This function is heavily exercised by org.apache.poi.hslf.usermodel.TestBugs.testFile[5] and PPTX2PNG.render[2], as well as the new TestPictures#testSlideshowWithIncorrectOffsets().
Closes #225
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1887017 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/scratchpad/testcases/org')
3 files changed, 185 insertions, 7 deletions
diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/dev/BaseTestPPTIterating.java b/src/scratchpad/testcases/org/apache/poi/hslf/dev/BaseTestPPTIterating.java index 869b96e4b8..6722f00f41 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/dev/BaseTestPPTIterating.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/dev/BaseTestPPTIterating.java @@ -55,6 +55,7 @@ public abstract class BaseTestPPTIterating { ENCRYPTED_FILES.add("Password_Protected-np-hello.ppt"); ENCRYPTED_FILES.add("Password_Protected-56-hello.ppt"); ENCRYPTED_FILES.add("Password_Protected-hello.ppt"); + ENCRYPTED_FILES.add("ppt_with_png_encrypted.ppt"); } protected static final Map<String,Class<? extends Throwable>> EXCLUDED = diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java index e514f78835..55a50be321 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java @@ -35,6 +35,7 @@ import javax.imageio.ImageIO; import org.apache.poi.POIDataSamples; import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -87,16 +88,19 @@ public final class TestPicture { } /** - * Picture#getEscherBSERecord threw NullPointerException if EscherContainerRecord.BSTORE_CONTAINER - * was not found. The correct behaviour is to return null. + * {@link HSLFPictureShape#getEscherBSERecord()} threw {@link NullPointerException} if + * {@link EscherContainerRecord#BSTORE_CONTAINER} was not found. The correct behaviour is to return null. */ @Test void bug46122() throws IOException { + HSLFPictureData detachedData; + try (HSLFSlideShow ppt = new HSLFSlideShow()) { + detachedData = ppt.addPicture(new byte[0], PictureType.PNG); + } try (HSLFSlideShow ppt = new HSLFSlideShow()) { HSLFSlide slide = ppt.createSlide(); - HSLFPictureData pd = HSLFPictureData.create(PictureType.PNG); - HSLFPictureShape pict = new HSLFPictureShape(pd); //index to non-existing picture data + HSLFPictureShape pict = new HSLFPictureShape(detachedData); //index to non-existing picture data pict.setAnchor(new Rectangle2D.Double(50, 50, 100, 100)); pict.setSheet(slide); HSLFPictureData data = pict.getPictureData(); diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java index fce2e1804d..40076c758f 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPictures.java @@ -27,9 +27,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.Random; import org.apache.poi.POIDataSamples; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; import org.apache.poi.hslf.HSLFTestDataSamples; import org.apache.poi.hslf.blip.DIB; import org.apache.poi.hslf.blip.EMF; @@ -37,6 +42,7 @@ import org.apache.poi.hslf.blip.JPEG; import org.apache.poi.hslf.blip.PICT; import org.apache.poi.hslf.blip.PNG; import org.apache.poi.hslf.blip.WMF; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.sl.image.ImageHeaderEMF; import org.apache.poi.sl.image.ImageHeaderPICT; import org.apache.poi.sl.image.ImageHeaderWMF; @@ -512,9 +518,8 @@ public final class TestPictures { int streamSize = out.size(); - HSLFPictureData data = HSLFPictureData.create(PictureType.JPEG); - data.setData(new byte[100]); - int offset = hslf.addPicture(data); + HSLFPictureData data = ppt.addPicture(new byte[100], PictureType.JPEG); + int offset = data.getOffset(); assertEquals(streamSize, offset); assertEquals(3, ppt.getPictureData().size()); @@ -560,4 +565,172 @@ public final class TestPictures { assertEquals(1, picture.getIndex()); } } + + /** + * Verify that it is possible for a user to change the contents of a {@link HSLFPictureData} using + * {@link HSLFPictureData#setData(byte[])}, and that the changes are saved to the slideshow. + */ + @Test + void testEditPictureData() throws IOException { + byte[] newImage = slTests.readFile("tomcat.png"); + ByteArrayOutputStream modifiedSlideShow = new ByteArrayOutputStream(); + + // Load an existing slideshow and modify the image + try (HSLFSlideShow ppt = HSLFTestDataSamples.getSlideShow("ppt_with_png.ppt")) { + HSLFPictureData picture = ppt.getPictureData().get(0); + picture.setData(newImage); + ppt.write(modifiedSlideShow); + } + + // Load the modified slideshow and verify the image content + try (HSLFSlideShow ppt = new HSLFSlideShow(new ByteArrayInputStream(modifiedSlideShow.toByteArray()))) { + HSLFPictureData picture = ppt.getPictureData().get(0); + byte[] modifiedImageData = picture.getData(); + assertArrayEquals(newImage, modifiedImageData); + } + } + + /** + * Verify that it is possible for a user to change the contents of an encrypted {@link HSLFPictureData} using + * {@link HSLFPictureData#setData(byte[])}, and that the changes are saved to the slideshow. + */ + @Test + void testEditPictureDataEncrypted() throws IOException { + byte[] newImage = slTests.readFile("tomcat.png"); + ByteArrayOutputStream modifiedSlideShow = new ByteArrayOutputStream(); + + Biff8EncryptionKey.setCurrentUserPassword("password"); + try { + // Load an existing slideshow and modify the image + try (HSLFSlideShow ppt = HSLFTestDataSamples.getSlideShow("ppt_with_png_encrypted.ppt")) { + HSLFPictureData picture = ppt.getPictureData().get(0); + picture.setData(newImage); + ppt.write(modifiedSlideShow); + } + + // Load the modified slideshow and verify the image content + try (HSLFSlideShow ppt = new HSLFSlideShow(new ByteArrayInputStream(modifiedSlideShow.toByteArray()))) { + HSLFPictureData picture = ppt.getPictureData().get(0); + byte[] modifiedImageData = picture.getData(); + assertArrayEquals(newImage, modifiedImageData); + } + } finally { + Biff8EncryptionKey.setCurrentUserPassword(null); + } + } + + /** + * Verify that the {@link EscherBSERecord#getOffset()} values are modified for all images after the image being + * changed. + */ + @Test + void testEditPictureDataRecordOffsetsAreShifted() throws IOException { + int[] originalOffsets = {0, 12013, 15081, 34162, 59563}; + int[] modifiedOffsets = {0, 35, 3103, 22184, 47585}; + + ByteArrayOutputStream inMemory = new ByteArrayOutputStream(); + try (HSLFSlideShow ppt = HSLFTestDataSamples.getSlideShow("pictures.ppt")) { + int[] offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + assertArrayEquals(originalOffsets, offsets); + + HSLFPictureData imageBeingChanged = ppt.getPictureData().get(0); + // It doesn't matter that this isn't a valid image. We are just testing offsets here. + imageBeingChanged.setData(new byte[10]); + + // Verify that the in-memory representations have all been updated + offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + assertArrayEquals(modifiedOffsets, offsets); + + ppt.write(inMemory); + } + + try (HSLFSlideShow ppt = new HSLFSlideShow(new ByteArrayInputStream(inMemory.toByteArray()))) { + + // Verify that the persisted representations have all been updated + int[] offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + assertArrayEquals(modifiedOffsets, offsets); + } + } + + /** + * Verify that the {@link EscherBSERecord#getOffset()} values are modified for all images after the image being + * changed, but assuming that the records are not stored in a sorted-by-offset fashion. + * + * We have not encountered a file that has meaningful data that is not sorted. However, we have encountered files + * that have records with an offset of 0 interspersed between meaningful records. See {@code 53446.ppt} and + * {@code alterman_security.ppt} for examples. + */ + @Test + void testEditPictureDataOutOfOrderRecords() throws IOException { + int[] modifiedOffsets = {0, 35, 3103, 22184, 47585}; + + ByteArrayOutputStream inMemory = new ByteArrayOutputStream(); + try (HSLFSlideShow ppt = HSLFTestDataSamples.getSlideShow("pictures.ppt")) { + + // For this test we're going to intentionally manipulate the records into a shuffled order. + EscherContainerRecord container = ppt.getPictureData().get(0).bStore; + List<EscherRecord> children = container.getChildRecords(); + for (EscherRecord child : children) { + container.removeChildRecord(child); + } + Collections.shuffle(children); + for (EscherRecord child : children) { + container.addChildRecord(child); + } + + HSLFPictureData imageBeingChanged = ppt.getPictureData().get(0); + // It doesn't matter that this isn't a valid image. We are just testing offsets here. + imageBeingChanged.setData(new byte[10]); + + // Verify that the in-memory representations have all been updated + int[] offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + Arrays.sort(offsets); + assertArrayEquals(modifiedOffsets, offsets); + + ppt.write(inMemory); + } + + try (HSLFSlideShow ppt = new HSLFSlideShow(new ByteArrayInputStream(inMemory.toByteArray()))) { + + // Verify that the persisted representations have all been updated + int[] offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + Arrays.sort(offsets); + assertArrayEquals(modifiedOffsets, offsets); + } + } + + /** + * Verify that a slideshow with records that have offsets not matching those of the pictures in the stream still + * correctly pairs the records and pictures. + */ + @Test + void testSlideshowWithIncorrectOffsets() throws IOException { + int[] originalOffsets; + int originalNumberOfRecords; + + // Create a presentation that has records with unmatched offsets, but with matched UIDs. + ByteArrayOutputStream inMemory = new ByteArrayOutputStream(); + try (HSLFSlideShow ppt = HSLFTestDataSamples.getSlideShow("pictures.ppt")) { + originalOffsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + originalNumberOfRecords = ppt.getPictureData().get(0).bStore.getChildRecords().size(); + + Random random = new Random(); + for (HSLFPictureData picture : ppt.getPictureData()) { + // Bound is arbitrary and irrelevant to the test. + picture.bse.setOffset(random.nextInt(500_000)); + } + ppt.write(inMemory); + } + + try (HSLFSlideShow ppt = new HSLFSlideShow(new ByteArrayInputStream(inMemory.toByteArray()))) { + + // Verify that the offsets all got fixed. + int[] offsets = ppt.getPictureData().stream().mapToInt(HSLFPictureData::getOffset).toArray(); + assertArrayEquals(originalOffsets, offsets); + + // Verify that there are the same number of records as in the original slideshow. + int numberOfRecords = ppt.getPictureData().get(0).bStore.getChildRecords().size(); + assertEquals(originalNumberOfRecords, numberOfRecords); + } + } } |