Bug 50056 - ArrayIndexOutOfBounds in AbstractWMFPainter with 1-bit color images
Summary: ArrayIndexOutOfBounds in AbstractWMFPainter with 1-bit color images
Status: NEW
Alias: None
Product: Batik - Now in Jira
Classification: Unclassified
Component: Utilities (show other bugs)
Version: 1.8
Hardware: All All
: P2 normal
Target Milestone: ---
Assignee: Batik Developer's Mailing list
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-10-07 07:38 UTC by Viliam Anirud
Modified: 2010-10-08 11:06 UTC (History)
0 users



Attachments
The WMF file that failed (236.84 KB, application/octet-stream)
2010-10-07 07:38 UTC, Viliam Anirud
Details
A patch (1.52 KB, patch)
2010-10-08 11:03 UTC, Viliam Anirud
Details | Diff
The WMF file that failed (214.14 KB, application/octet-stream)
2010-10-08 11:06 UTC, Viliam Anirud
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Viliam Anirud 2010-10-07 07:38:09 UTC
Created attachment 26136 [details]
The WMF file that failed

I use WMFTranscoder to convert WMF files to SVG. I found a image, that fails with following exception:

java.lang.ArrayIndexOutOfBoundsException: 108248
	at org.apache.batik.transcoder.wmf.tosvg.AbstractWMFPainter.getImage(AbstractWMFPainter.java:194)
	at org.apache.batik.transcoder.wmf.tosvg.AbstractWMFPainter.getImage(AbstractWMFPainter.java:74)
	at org.apache.batik.transcoder.wmf.tosvg.WMFPainter.paint(WMFPainter.java:859)
	at org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder.transcode(WMFTranscoder.java:187)
	...

The length of the array that caused the exception was 108248, so 1 byte after the end was attempted to read.

The problem was on line 194 in AbstractWMFPainter, where next byte for the next iteration, which could be after the end, if there is no padding.

Here is fixed code for the getImage(byte[]) method:

/** Return the image associated with a bitmap in a Metafile.
     *  24 bits and 8 bits bitmaps are handled.
     *  @param bit the bitmap byte array
     *  @return the Image associated with the bitmap (null if the dimensions detected in the
     *     header are not consistent with the assumed dimensions)
     */
    protected BufferedImage getImage(byte[] bit) {
        // get the header of the bitmap, first the width and height
        int _width = (((int)bit[7] & 0x00ff) << 24) | (((int)bit[6] & 0x00ff) << 16)
                    | (((int)bit[5] & 0x00ff) << 8) | (int)bit[4] & 0x00ff;
        int _height = (((int)bit[11] & 0x00ff) << 24) | (((int)bit[10] & 0x00ff) << 16)
                    | (((int)bit[9] & 0x00ff) << 8) | (int)bit[8] & 0x00ff;

        // OK, we can safely create the data array now
        int[] bitI = new int[_width * _height];
        BufferedImage img = new BufferedImage(_width, _height, BufferedImage.TYPE_INT_RGB);
        WritableRaster raster = img.getRaster();

        // retrieve useful informations in bitmap header
        // size of header
        int _headerSize = (((int)bit[3] & 0x00ff) << 24) | (((int)bit[2] & 0x00ff)<<16)
                            | (((int)bit[1] & 0x00ff) << 8) | (int)bit[0] & 0x00ff;
        // number of planes
        int _planes = (((int)bit[13] & 0x00ff) << 8) | (int)bit[12] & 0x00ff;
        // number of bits per pixel
        int _nbit = (((int)bit[15] & 0x00ff) << 8) | (int)bit[14] & 0x00ff;
        // compression factor : unused
        // size of the image
        int _size = (((int)bit[23] & 0x00ff) << 24) | (((int)bit[22] & 0x00ff) << 16)
                        | (((int)bit[21] & 0x00ff) << 8) | (int)bit[20] & 0x00ff;
        // infer the size of image if it is not given in the file
        if (_size == 0) _size = ((((_width * _nbit) + 31) & ~31 ) >> 3) * _height;

        // number of used colors
        int _clrused = (((int)bit[35] & 0x00ff) << 24) | (((int)bit[34]&0x00ff) << 16)
                        | (((int)bit[33] & 0x00ff) << 8) | (int)bit[32]&0x00ff;

        // 24 bit image
        if (_nbit == 24) {
            // read the scan lines
            int pad = (_size / _height) - _width * 3;
            int offset = _headerSize; // begin to read data after header
            // populate the int array
            for (int j = 0; j < _height; j++) {
                for (int i = 0; i < _width; i++) {
                    bitI[_width * (_height - j - 1) + i] =
                        (255 & 0x00ff) << 24 | (((int)bit[offset+2] & 0x00ff) << 16)
                        | (((int)bit[offset+1] & 0x00ff) << 8) | (int)bit[offset] & 0x00ff;
                    offset += 3;
                }
                offset += pad;
            }
        // 8 bit image
        } else if (_nbit == 8) {
            // Determine the number of colors
            int nbColors = 0;
            if (_clrused > 0) nbColors = _clrused;
            else nbColors = (1 & 0x00ff) << 8;
            // Read the palette colors.
            int offset = _headerSize;
            int[]  palette = new int[nbColors];
            for (int i = 0; i < nbColors; i++) {
                palette[i] = (255 & 0x00ff) << 24 | (((int)bit[offset+2] & 0x00ff) << 16)
                            | (((int)bit[offset+1] & 0x00ff) << 8)
                            | (int)bit[offset] & 0x00ff;
                offset += 4;
            }

            // populate the int array
            /* need to recalculate size because the offset used for palette must be substracted
             * to overall size, else we will go after the end of the byte array...
             */
            _size = bit.length - offset;
            int pad = (_size / _height) - _width;
            for (int j = 0; j < _height; j++) {
                for (int i = 0; i < _width; i++) {
                    bitI[_width*(_height-j-1)+i] = palette [((int)bit[offset] & 0x00ff)];
                    offset++;
                }
                offset += pad;
            }
        // black and white image
        } else if (_nbit == 1) {
            // 2 colors only (black and white image)
            int nbColors = 2;
            // Read the palette colors.
            int offset = _headerSize;
            int[]  palette = new int[nbColors];
            for (int i = 0; i < nbColors; i++) {
                palette[i] = (255 & 0x00ff) << 24 | (((int)bit[offset+2] & 0x00ff) << 16)
                            | (((int)bit[offset+1] & 0x00ff) << 8)
                            | (int)bit[offset] & 0x00ff;
                offset += 4;
            }

            // populate the int array : each pixel corresponds to a bit in the byte array
            int pos = 7;
            byte currentByte = bit[offset];
            // padded to long words
            int pad = (_size / _height) - _width/8;
            for (int j = 0; j < _height; j++) {
                for (int i = 0; i < _width; i++) {
                    if ((currentByte & (1 << pos)) != 0) bitI[_width*(_height-j-1)+i] = palette[1];
                    else bitI[_width*(_height-j-1)+i] = palette[0];
                    pos--;
                    if (pos == -1) {
                        pos = 7;
                        offset++;
                        if (offset < bit.length)
                        	currentByte = bit[offset];
                    }
                }
                offset +=pad;
                pos = 7;
                if (offset < bit.length) currentByte = bit[offset];
            }
        }
        raster.setDataElements(0, 0, _width, _height, bitI);
        return img;
    }
Comment 1 Helder Magalhães 2010-10-07 07:54:31 UTC
Thanks for reporting! :-)


(In reply to comment #0)
> Created an attachment (id=26136) [details]
> The WMF file that failed

It's good to have the causer attached. ;-)


> I use WMFTranscoder to convert WMF files to SVG.

Could you provide the command-line used, if any? (If the conversion is triggered by Java source, I was mostly interested of any hints/parameters were passed to the transcoder).


> The length of the array that caused the exception was 108248, so 1 byte after
> the end was attempted to read.
> 
> The problem was on line 194 in AbstractWMFPainter, where next byte for the next
> iteration, which could be after the end, if there is no padding.
> 
> Here is fixed code for the getImage(byte[]) method:

I haven't yet checked for the solution, but the problem seems well evaluated. Could you turn your fix proposal into a patch? It's far better for reviewing and for allowing local testing (among other reasons). If you are unable to do that, please just state so. ;-)


Finally, please provide some details on your current environment: at a minimum, I'd say operating system, Java version and Batik version/SVN source code revision.
Comment 2 Viliam Anirud 2010-10-08 11:03:29 UTC
Created attachment 26141 [details]
A patch

The problem reproduces in the current trunk version, it is platform and java version idependent, but I use Windows XP and java 1.6. Problem was caused from command line. Here is a test case, you could merge it somewhere:

package test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;

import junit.framework.TestCase;

public class TestBug50056 extends TestCase {

	public void test() throws Exception {
		File f = new File("D:/tmp/failingfile.wmf");
		InputStream is = new FileInputStream(f);
		
		TranscoderInput wmfInput = new TranscoderInput(new FileInputStream(f));
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		TranscoderOutput svgOutput = new TranscoderOutput(baos);
        WMFTranscoder wmfTranscoder = new WMFTranscoder();
        wmfTranscoder.addTranscodingHint(WMFTranscoder.KEY_WIDTH, new Float(100));
        wmfTranscoder.addTranscodingHint(WMFTranscoder.KEY_HEIGHT, new Float(100));
        
        // this fails with an ArrayIndexOutOfBoundsException if bug 50056 reproduces
        wmfTranscoder.transcode(wmfInput, svgOutput);
	}
	
}
Comment 3 Viliam Anirud 2010-10-08 11:06:18 UTC
Created attachment 26142 [details]
The WMF file that failed

I mistakenly uploaded a working file, here is non-working one.