]> source.dussan.org Git - poi.git/commitdiff
Bug 60345 - Handle corrupt PICT streams
authorAndreas Beeker <kiwiwings@apache.org>
Thu, 10 Nov 2016 23:04:25 +0000 (23:04 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Thu, 10 Nov 2016 23:04:25 +0000 (23:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1769226 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
src/scratchpad/src/org/apache/poi/hslf/blip/Metafile.java
src/scratchpad/src/org/apache/poi/hslf/blip/PICT.java
test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt [new file with mode: 0644]
test-data/slideshow/bug60345_paperfigures.ppt [new file with mode: 0644]
test-data/slideshow/bug60345_suba.ppt [new file with mode: 0644]

index c2df75f3b7df8836501717b7f80c6c32db8bd3a8..818b10b10dcaa7c2808a5b6a519ff8e6705512a0 100644 (file)
@@ -17,6 +17,7 @@
 \r
 package org.apache.poi.sl.draw;\r
 \r
+import java.awt.AlphaComposite;\r
 import java.awt.Dimension;\r
 import java.awt.Graphics;\r
 import java.awt.Graphics2D;\r
@@ -62,7 +63,7 @@ public class BitmapImageRenderer implements ImageRenderer {
     \r
     /**\r
      * Read the image data via ImageIO and optionally try to workaround metadata errors.\r
-     * The resulting image is of image image type {@link BufferedImage#TYPE_INT_ARGB}\r
+     * The resulting image is of image type {@link BufferedImage#TYPE_INT_ARGB}\r
      *\r
      * @param data the data stream\r
      * @param contentType the content type\r
@@ -72,6 +73,10 @@ public class BitmapImageRenderer implements ImageRenderer {
     private static BufferedImage readImage(InputStream data, String contentType) throws IOException {\r
         IOException lastException = null;\r
         BufferedImage img = null;\r
+        if (data.markSupported()) {\r
+            data.mark(data.available());\r
+        }\r
+        \r
         // currently don't use FileCacheImageInputStream,\r
         // because of the risk of filling the file handles (see #59166)\r
         ImageInputStream iis = new MemoryCacheImageInputStream(data);\r
@@ -84,31 +89,93 @@ public class BitmapImageRenderer implements ImageRenderer {
                 ImageReader reader = iter.next();\r
                 ImageReadParam param = reader.getDefaultReadParam();\r
                 // 0:default mode, 1:fallback mode\r
-                for (int mode=0; img==null && mode<2; mode++) {\r
-                    iis.reset();\r
+                for (int mode=0; img==null && mode<3; mode++) {\r
+                    lastException = null;\r
+                    try {\r
+                        iis.reset();\r
+                    } catch (IOException e) {\r
+                        if (data.markSupported()) {\r
+                            data.reset();\r
+                            data.mark(data.available());\r
+                            iis.close();\r
+                            iis = new MemoryCacheImageInputStream(data);\r
+                        } else {\r
+                            // can't restore the input stream, so we need to stop processing here\r
+                            lastException = e;\r
+                            break;\r
+                        }\r
+                    }\r
                     iis.mark();\r
 \r
-                    if (mode == 1) {\r
-                        // fallback mode for invalid image band metadata\r
-                        // see http://stackoverflow.com/questions/10416378\r
-                        Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
-                        while (imageTypes.hasNext()) {\r
-                            ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
-                            int bufferedImageType = imageTypeSpecifier.getBufferedImageType();\r
-                            if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {\r
-                                param.setDestinationType(imageTypeSpecifier);\r
+                    try {\r
+                    \r
+                        switch (mode) {\r
+                            case 0:\r
+                                reader.setInput(iis, false, true);\r
+                                img = reader.read(0, param);\r
+                                break;\r
+                            case 1: {\r
+                                // try to load picture in gray scale mode\r
+                                // fallback mode for invalid image band metadata\r
+                                // see http://stackoverflow.com/questions/10416378\r
+                                Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
+                                while (imageTypes.hasNext()) {\r
+                                    ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
+                                    int bufferedImageType = imageTypeSpecifier.getBufferedImageType();\r
+                                    if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {\r
+                                        param.setDestinationType(imageTypeSpecifier);\r
+                                        break;\r
+                                    }\r
+                                }\r
+                                reader.setInput(iis, false, true);\r
+                                img = reader.read(0, param);\r
+                                break;\r
+                            }\r
+                            case 2: {\r
+                                // try to load truncated pictures by supplying a BufferedImage\r
+                                // and use the processed data up till the point of error\r
+                                reader.setInput(iis, false, true);\r
+                                int height = reader.getHeight(0);\r
+                                int width = reader.getWidth(0);\r
+                                \r
+                                Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);\r
+                                if (imageTypes.hasNext()) {\r
+                                    ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();\r
+                                    img = imageTypeSpecifier.createBufferedImage(width, height);\r
+                                    param.setDestination(img);\r
+                                } else {\r
+                                    lastException = new IOException("unable to load even a truncated version of the image.");\r
+                                    break;\r
+                                }\r
+\r
+                                try {\r
+                                    reader.read(0, param);\r
+                                } finally {\r
+                                    if (img.getType() != BufferedImage.TYPE_INT_ARGB) {\r
+                                        int y = findTruncatedBlackBox(img, width, height);\r
+                                        if (y < height) {\r
+                                            BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);\r
+                                            Graphics2D g = argbImg.createGraphics();\r
+                                            g.clipRect(0, 0, width, y);\r
+                                            g.drawImage(img, 0, 0, null);\r
+                                            g.dispose();\r
+                                            img.flush();\r
+                                            img = argbImg;\r
+                                        }\r
+                                    }\r
+                                }                                \r
                                 break;\r
                             }\r
                         }\r
-                    }\r
                     \r
-                    try {\r
-                        reader.setInput(iis, false, true);\r
-                        img = reader.read(0, param);\r
                     } catch (IOException e) {\r
-                        lastException = e;\r
+                        if (mode < 2) {\r
+                            lastException = e;\r
+                        }\r
                     } catch (RuntimeException e) {\r
-                        lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);\r
+                        if (mode < 2) {\r
+                            lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);\r
+                        }\r
                     }\r
                 }\r
                 reader.dispose();\r
@@ -140,6 +207,21 @@ public class BitmapImageRenderer implements ImageRenderer {
         return img;\r
     }\r
 \r
+    private static int findTruncatedBlackBox(BufferedImage img, int width, int height) {\r
+        // scan through the image to find the black box after the truncated data\r
+        int h = height-1;\r
+        for (; h > 0; h--) {\r
+            for (int w = width-1; w > 0; w-=width/10) {\r
+                int p = img.getRGB(w, h);\r
+                if (p != 0xff000000) {\r
+                    return h+1;\r
+                }\r
+            }\r
+        }\r
+        return 0;\r
+    }\r
+    \r
+    \r
     @Override\r
     public BufferedImage getImage() {\r
         return img;\r
index a04ddc198631ca0638b59d8ae06998367ed5d270..4a6c3839a30c0355e2152345d5b2abf539b5eae2 100644 (file)
@@ -116,6 +116,10 @@ public abstract class Metafile extends HSLFPictureData {
         public int getSize(){
             return 34;
         }
+        
+        public int getWmfSize() {
+            return wmfsize;
+        }
     }
 
     protected static byte[] compress(byte[] bytes, int offset, int length) throws IOException {
index a5670a126d02fc84b835597a5ce99f4bde3b54d9..b0ce968e6b90b1e7ec62213d124caf902bc4ba1f 100644 (file)
@@ -25,12 +25,16 @@ import java.io.IOException;
 import java.util.zip.InflaterInputStream;
 
 import org.apache.poi.hslf.exceptions.HSLFException;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
 import org.apache.poi.util.Units;
 
 /**
  * Represents Macintosh PICT picture data.
  */
 public final class PICT extends Metafile {
+    private static POILogger LOG = POILogFactory.getLogger(PICT.class);
+    
     public static class NativeHeader {
         /**
          * skip the first 512 bytes - they are MAC specific crap
@@ -122,18 +126,37 @@ public final class PICT extends Metafile {
     }
 
     private byte[] read(byte[] data, int pos) throws IOException {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
         ByteArrayInputStream bis = new ByteArrayInputStream(data);
         Header header = new Header();
         header.read(data, pos);
         bis.skip(pos + header.getSize());
-        InflaterInputStream inflater = new InflaterInputStream( bis );
         byte[] chunk = new byte[4096];
-        int count;
-        while ((count = inflater.read(chunk)) >=0 ) {
-            out.write(chunk,0,count);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(header.getWmfSize());
+        InflaterInputStream inflater = new InflaterInputStream( bis );
+        try {
+            int count;
+            while ((count = inflater.read(chunk)) >=0 ) {
+                out.write(chunk,0,count);
+                // PICT zip-stream can be erroneous, so we clear the array to determine
+                // the maximum of read bytes, after the inflater crashed
+                bytefill(chunk, (byte)0);
+            }
+        } catch (Exception e) {
+            int lastLen;
+            for (lastLen=chunk.length-1; lastLen>=0 && chunk[lastLen] == 0; lastLen--);
+            if (++lastLen > 0) {
+                if (header.getWmfSize() > out.size()) {
+                    // sometimes the wmfsize is smaller than the amount of already successfully read bytes
+                    // in this case we take the lastLen as-is, otherwise we truncate it to the given size
+                    lastLen = Math.min(lastLen, header.getWmfSize() - out.size());
+                }
+                out.write(chunk,0,lastLen);
+            }
+            // End of picture marker for PICT is 0x00 0xFF
+            LOG.log(POILogger.ERROR, "PICT zip-stream is invalid, read as much as possible. Uncompressed length of header: "+header.getWmfSize()+" / Read bytes: "+out.size(), e);
+        } finally {
+            inflater.close();
         }
-        inflater.close();
         return out.toByteArray();
     }
 
@@ -192,4 +215,22 @@ public final class PICT extends Metafile {
                 throw new IllegalArgumentException(signature+" is not a valid instance/signature value for PICT");
         }        
     }
+    
+    
+    /*
+     * initialize a smaller piece of the array and use the System.arraycopy 
+     * call to fill in the rest of the array in an expanding binary fashion
+     */
+    private static void bytefill(byte[] array, byte value) {
+        // http://stackoverflow.com/questions/9128737/fastest-way-to-set-all-values-of-an-array
+        int len = array.length;
+
+        if (len > 0){
+            array[0] = value;
+        }
+
+        for (int i = 1; i < len; i += i) {
+            System.arraycopy(array, 0, array, i, ((len - i) < i) ? (len - i) : i);
+        }
+    }
 }
diff --git a/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt b/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt
new file mode 100644 (file)
index 0000000..5bb52fc
Binary files /dev/null and b/test-data/slideshow/bug60345_Jankovic_final_Retreat_2002.ppt differ
diff --git a/test-data/slideshow/bug60345_paperfigures.ppt b/test-data/slideshow/bug60345_paperfigures.ppt
new file mode 100644 (file)
index 0000000..6a04556
Binary files /dev/null and b/test-data/slideshow/bug60345_paperfigures.ppt differ
diff --git a/test-data/slideshow/bug60345_suba.ppt b/test-data/slideshow/bug60345_suba.ppt
new file mode 100644 (file)
index 0000000..9ffea08
Binary files /dev/null and b/test-data/slideshow/bug60345_suba.ppt differ