Bug 60345 - Handle corrupt PICT streams

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1769226 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-11-10 23:04:25 +00:00
parent d5e4e462fc
commit cf010d88b2
6 changed files with 151 additions and 24 deletions

View File

@ -17,6 +17,7 @@
package org.apache.poi.sl.draw; package org.apache.poi.sl.draw;
import java.awt.AlphaComposite;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
@ -62,7 +63,7 @@ public class BitmapImageRenderer implements ImageRenderer {
/** /**
* Read the image data via ImageIO and optionally try to workaround metadata errors. * Read the image data via ImageIO and optionally try to workaround metadata errors.
* The resulting image is of image image type {@link BufferedImage#TYPE_INT_ARGB} * The resulting image is of image type {@link BufferedImage#TYPE_INT_ARGB}
* *
* @param data the data stream * @param data the data stream
* @param contentType the content type * @param contentType the content type
@ -72,6 +73,10 @@ public class BitmapImageRenderer implements ImageRenderer {
private static BufferedImage readImage(InputStream data, String contentType) throws IOException { private static BufferedImage readImage(InputStream data, String contentType) throws IOException {
IOException lastException = null; IOException lastException = null;
BufferedImage img = null; BufferedImage img = null;
if (data.markSupported()) {
data.mark(data.available());
}
// currently don't use FileCacheImageInputStream, // currently don't use FileCacheImageInputStream,
// because of the risk of filling the file handles (see #59166) // because of the risk of filling the file handles (see #59166)
ImageInputStream iis = new MemoryCacheImageInputStream(data); ImageInputStream iis = new MemoryCacheImageInputStream(data);
@ -84,31 +89,93 @@ public class BitmapImageRenderer implements ImageRenderer {
ImageReader reader = iter.next(); ImageReader reader = iter.next();
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
// 0:default mode, 1:fallback mode // 0:default mode, 1:fallback mode
for (int mode=0; img==null && mode<2; mode++) { for (int mode=0; img==null && mode<3; mode++) {
iis.reset(); lastException = null;
try {
iis.reset();
} catch (IOException e) {
if (data.markSupported()) {
data.reset();
data.mark(data.available());
iis.close();
iis = new MemoryCacheImageInputStream(data);
} else {
// can't restore the input stream, so we need to stop processing here
lastException = e;
break;
}
}
iis.mark(); iis.mark();
if (mode == 1) { try {
// fallback mode for invalid image band metadata
// see http://stackoverflow.com/questions/10416378 switch (mode) {
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0); case 0:
while (imageTypes.hasNext()) { reader.setInput(iis, false, true);
ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); img = reader.read(0, param);
int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); break;
if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) { case 1: {
param.setDestinationType(imageTypeSpecifier); // try to load picture in gray scale mode
// fallback mode for invalid image band metadata
// see http://stackoverflow.com/questions/10416378
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
while (imageTypes.hasNext()) {
ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
int bufferedImageType = imageTypeSpecifier.getBufferedImageType();
if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) {
param.setDestinationType(imageTypeSpecifier);
break;
}
}
reader.setInput(iis, false, true);
img = reader.read(0, param);
break;
}
case 2: {
// try to load truncated pictures by supplying a BufferedImage
// and use the processed data up till the point of error
reader.setInput(iis, false, true);
int height = reader.getHeight(0);
int width = reader.getWidth(0);
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0);
if (imageTypes.hasNext()) {
ImageTypeSpecifier imageTypeSpecifier = imageTypes.next();
img = imageTypeSpecifier.createBufferedImage(width, height);
param.setDestination(img);
} else {
lastException = new IOException("unable to load even a truncated version of the image.");
break;
}
try {
reader.read(0, param);
} finally {
if (img.getType() != BufferedImage.TYPE_INT_ARGB) {
int y = findTruncatedBlackBox(img, width, height);
if (y < height) {
BufferedImage argbImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = argbImg.createGraphics();
g.clipRect(0, 0, width, y);
g.drawImage(img, 0, 0, null);
g.dispose();
img.flush();
img = argbImg;
}
}
}
break; break;
} }
} }
}
try {
reader.setInput(iis, false, true);
img = reader.read(0, param);
} catch (IOException e) { } catch (IOException e) {
lastException = e; if (mode < 2) {
lastException = e;
}
} catch (RuntimeException e) { } catch (RuntimeException e) {
lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e); if (mode < 2) {
lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e);
}
} }
} }
reader.dispose(); reader.dispose();
@ -140,6 +207,21 @@ public class BitmapImageRenderer implements ImageRenderer {
return img; return img;
} }
private static int findTruncatedBlackBox(BufferedImage img, int width, int height) {
// scan through the image to find the black box after the truncated data
int h = height-1;
for (; h > 0; h--) {
for (int w = width-1; w > 0; w-=width/10) {
int p = img.getRGB(w, h);
if (p != 0xff000000) {
return h+1;
}
}
}
return 0;
}
@Override @Override
public BufferedImage getImage() { public BufferedImage getImage() {
return img; return img;

View File

@ -116,6 +116,10 @@ public abstract class Metafile extends HSLFPictureData {
public int getSize(){ public int getSize(){
return 34; return 34;
} }
public int getWmfSize() {
return wmfsize;
}
} }
protected static byte[] compress(byte[] bytes, int offset, int length) throws IOException { protected static byte[] compress(byte[] bytes, int offset, int length) throws IOException {

View File

@ -25,12 +25,16 @@ import java.io.IOException;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
import org.apache.poi.hslf.exceptions.HSLFException; 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; import org.apache.poi.util.Units;
/** /**
* Represents Macintosh PICT picture data. * Represents Macintosh PICT picture data.
*/ */
public final class PICT extends Metafile { public final class PICT extends Metafile {
private static POILogger LOG = POILogFactory.getLogger(PICT.class);
public static class NativeHeader { public static class NativeHeader {
/** /**
* skip the first 512 bytes - they are MAC specific crap * 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 { private byte[] read(byte[] data, int pos) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream bis = new ByteArrayInputStream(data); ByteArrayInputStream bis = new ByteArrayInputStream(data);
Header header = new Header(); Header header = new Header();
header.read(data, pos); header.read(data, pos);
bis.skip(pos + header.getSize()); bis.skip(pos + header.getSize());
InflaterInputStream inflater = new InflaterInputStream( bis );
byte[] chunk = new byte[4096]; byte[] chunk = new byte[4096];
int count; ByteArrayOutputStream out = new ByteArrayOutputStream(header.getWmfSize());
while ((count = inflater.read(chunk)) >=0 ) { InflaterInputStream inflater = new InflaterInputStream( bis );
out.write(chunk,0,count); 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(); 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"); 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);
}
}
} }

Binary file not shown.

Binary file not shown.