/* This code is part of Freenet. It is distributed under the GNU General
* Public License, version 2 (or at your option any later version). See
* http://www.gnu.org/ for further details of the GPL. */
package freenet.client.filter;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import freenet.l10n.NodeL10n;
import freenet.support.io.FileUtil;
/**
* @author kurmiashish
* This class would verify whether the BMP header is valid or not
Reference:
http://www.fastgraph.com/help/bmp_header_format.html
http://en.wikipedia.org/wiki/BMP_file_format
*/
public class BMPFilter implements ContentDataFilter {
static final byte[] bmpHeaderwindows =
{ (byte)'B', (byte)'M'};
static final byte[] bmpHeaderos2bArray =
{ (byte)'B', (byte)'A'};
static final byte[] bmpHeaderos2cIcon =
{ (byte)'C', (byte)'I'};
static final byte[] bmpHeaderos2cPointer =
{ (byte)'C', (byte)'P'};
static final byte[] bmpHeaderos2Icon =
{ (byte)'I', (byte)'C'};
static final byte[] bmpHeaderos2Pointer =
{ (byte)'P', (byte)'T'};
private int unsignedByte(byte b)
{
if (b >= 0)
return b;
else
return 256+b;
}
public int readInt(DataInputStream dis) throws IOException
{
int result;
byte[] data = new byte[4];
result = dis.read(data);
if (result < 0) // end of file reached
throw new EOFException();
result = (unsignedByte(data[2]) << 16) | (unsignedByte(data[1]) << 8) | unsignedByte(data[0]);
result|=(unsignedByte(data[3]) << 24);
return result;
}
public int readShort(DataInputStream dis) throws IOException
{
int result = dis.read();
if (result < 0)// end of file reached
throw new EOFException();
int r2 = dis.read();
if (r2 < 0)// end of file reached
throw new EOFException();
return result | (r2*256);
}
@Override
public void readFilter(InputStream input, OutputStream output, String charset, HashMap<String, String> otherParams,
FilterCallback cb) throws DataFilterException, IOException {
DataInputStream dis = new DataInputStream(input);
dis.mark(54);
byte[] StartWord = new byte[2];
dis.readFully(StartWord);
if((!Arrays.equals(StartWord, bmpHeaderwindows)) && (!Arrays.equals(StartWord, bmpHeaderos2bArray)) && (!Arrays.equals(StartWord, bmpHeaderos2cIcon)) && (!Arrays.equals(StartWord, bmpHeaderos2cPointer)) && (!Arrays.equals(StartWord, bmpHeaderos2Icon)) && (!Arrays.equals(StartWord, bmpHeaderos2Pointer))) { //Checking the first word
throwHeaderError(l10n("InvalidStartWordT"), l10n("InvalidStartWordD"));
}
int fileSize = readInt(dis); // read total file size
byte[] skipbytes=new byte[4];
dis.readFully(skipbytes);
int headerSize = readInt(dis); // read file header size or pixel offset
if(headerSize<0) {
throwHeaderError(l10n("InvalidOffsetT"), l10n("InvalidOffsetD"));
}
int size_bitmapinfoheader=readInt(dis);
if(size_bitmapinfoheader!=40) {
throwHeaderError(l10n("InvalidBitMapInfoHeaderSizeT"), l10n("InvalidBitMapInfoHeaderSizeD"));
}
int imageWidth = readInt(dis); // read width
int imageHeight = readInt(dis); // read height
if(imageWidth<0 || imageHeight<0) {
throwHeaderError(l10n("InvalidDimensionT"), l10n("InvalidDimensionD"));
}
int no_plane=readShort(dis);
if(no_plane!=1) { // No of planes should be 1
throwHeaderError(l10n("InvalidNoOfPlanesT"), l10n("InvalidNoOfPlanesD"));
}
int bitDepth = readShort(dis);
// Bit depth should be 1,2,4,8,16 or 32.
if(bitDepth!=1 && bitDepth!=2 && bitDepth!=4 && bitDepth!=8 && bitDepth!=16 && bitDepth!=24 && bitDepth!=32) {
throwHeaderError(l10n("InvalidBitDepthT"), l10n("InvalidBitDepthD"));
}
int compression_type=readInt(dis);
if( !(compression_type>=0 && compression_type<=3) ) {
throwHeaderError(l10n("Invalid Compression type"), l10n("Compression type field is set to "+compression_type+" instead of 0-3"));
}
int imagedatasize=readInt(dis);
if(fileSize!=headerSize+imagedatasize) {
throwHeaderError(l10n("InvalidFileSizeT"), l10n("InvalidFileSizeD"));
}
int horizontal_resolution=readInt(dis);
int vertical_resolution=readInt(dis);
if(horizontal_resolution<0 || vertical_resolution<0) {
throwHeaderError(l10n("InvalidResolutionT"), l10n("InvalidResolutionD"));
}
if(compression_type==0) {
// Verifying the file size w.r.t. image dimensions(width and height), bitDepth with imagedatasize(including padding).
int bitsPerLine = imageWidth * bitDepth;
//Lines are padded to a 4 byte boundary
if(bitsPerLine % 32 != 0) {
bitsPerLine += 32 - bitsPerLine % 32;
}
int bytesPerLine = bitsPerLine / 8;
int calculatedsize = bytesPerLine * imageHeight;
if(calculatedsize!=imagedatasize) {
throwHeaderError(l10n("InvalidImageDataSizeT"), l10n("InvalidImageDataSizeD" ));
}
}
dis.reset();
FileUtil.copy(dis, output, -1);
output.flush();
}
private static String l10n(String key) {
return NodeL10n.getBase().getString("BMPFilter."+key);
}
private void throwHeaderError(String shortReason, String reason) throws DataFilterException {
// Throw an exception
String message = l10n("notBMP");
if(reason != null) message += ' ' + reason;
if(shortReason != null)
message += " - (" + shortReason + ')';
throw new DataFilterException(shortReason, shortReason, message);
}
@Override
public void writeFilter(InputStream input, OutputStream output, String charset, HashMap<String, String> otherParams,
FilterCallback cb) throws DataFilterException, IOException {
return;
}
}