/*
* Copyright 2016 Skynav, Inc. All rights reserved.
* Portions Copyright 2009 Extensible Formatting Systems, Inc (XFSI).
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.xfsi.xav.validation.images.jpeg;
import java.io.EOFException;
import java.io.IOException;
import com.xfsi.xav.validation.images.jpeg.JpegValidator.MsgCode;
import com.xfsi.xav.validation.util.AbstractLoggingValidator;
/**
* validates APP0 segment
*/
class SegmentParserFFE0 extends SegmentParser {
private final static byte jfifIdentifierLength = 5;
boolean validate(JpegInputStream jis, JpegState js, AbstractLoggingValidator mh) throws EOFException
{
try
{
int l = jis.readShort() & 0xffff;
mh.logAll(MsgCode.JPG01I005, 0xffe0, "APP0", l);
l -= 2; // already read segment length
js.incrementApp0SegmentCount();
if (js.getApp0SegmentCount() == 1)
{
if (js.getSegmentCount() != 2 || js.getLastCode() != 0xffd8)
mh.logResult(JpegValidator.MsgCode.JPG01E017, js.getSegmentCount());
else
mh.logResult(MsgCode.JPG01I015);
}
if (l > SegmentParserFFE0.jfifIdentifierLength)
{
if (isJfifSegment(jis, js, l, mh))
return true;
l -= SegmentParserFFE0.jfifIdentifierLength;
}
if (js.getApp0SegmentCount() == 1)
mh.logResult(JpegValidator.MsgCode.JPG01E019, js.getSegmentCount());
jis.skipBytes(l);
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return true;
}
private boolean isJfifSegment(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh)
{
try
{
byte[] identifier = new byte[SegmentParserFFE0.jfifIdentifierLength];
for (int i = 0; i < SegmentParserFFE0.jfifIdentifierLength; i++)
identifier[i] = jis.readByte();
segmentBytesLeft -= SegmentParserFFE0.jfifIdentifierLength;
if (isFirstJfifSegment(identifier))
return checkFirstJfifSegment(jis, js, segmentBytesLeft, mh);
if (isJfifExtensionSegment(identifier))
return checkJfifExtensionSegment(jis, js, segmentBytesLeft, mh);
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
private boolean isFirstJfifSegment(byte[] identifier)
{
for (int i = 0; i < jfifIdentifierLength; i++)
{
byte b = identifier[i];
if (i == 0) {
if (b != 0x4a)
return false;
} else if (i == 1) {
if (b != 0x46)
return false;
} else if (i == 2) {
if (b != 0x49)
return false;
} else if (i == 3) {
if (b != 0x46)
return false;
} else if (i == 4) {
if (b != 0x00)
return false;
} else
break;
}
return true;
}
private boolean isJfifExtensionSegment(byte[] identifier)
{
for (int i = 0; i < jfifIdentifierLength; i++)
{
byte b = identifier[i];
if (i == 0) {
if (b != 0x4a)
return false;
} else if (i == 1) {
if (b != 0x46)
return false;
} else if (i == 2) {
if (b != 0x58)
return false;
} else if (i == 3) {
if (b != 0x58)
return false;
} else if (i == 4) {
if (b != 0x00)
return false;
} else
break;
}
return true;
}
private boolean checkFirstJfifSegment(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh) throws EOFException
{
try
{
js.markLastJfifApp0SegmentIndex();
if (js.getSegmentCount() != 2)
mh.logResult(JpegValidator.MsgCode.JPG01E020, js.getSegmentCount());
if (js.getApp0SegmentCount() == 1)
mh.logResult(MsgCode.JPG01I016);
else
mh.logResult(JpegValidator.MsgCode.JPG01E021, js.getSegmentCount(), js.getApp0SegmentCount());
int expectedMajor = 1;
int expectedMinor = 2;
js.setJfifMajorVersion(jis.readByte() & 0xff);
segmentBytesLeft--;
js.setJfifMinorVersion(jis.readByte() & 0xff);
segmentBytesLeft--;
if (js.getJfifMajorVersion() != expectedMajor || js.getJfifMinorVersion() != expectedMinor)
mh.logResult(JpegValidator.MsgCode.JPG01W006, js.getJfifMajorVersion(), js.getJfifMinorVersion(), expectedMajor, expectedMinor);
int minUnits = 0;
int maxUnits = 2;
int units = jis.readByte() & 0xff;
segmentBytesLeft--;
if (units < minUnits || units > maxUnits)
mh.logResult(JpegValidator.MsgCode.JPG01W007, units, minUnits, maxUnits);
int xDensity = jis.readShort() & 0xffff;
segmentBytesLeft -= 2;
if (xDensity == 0)
mh.logResult(JpegValidator.MsgCode.JPG01W009);
int yDensity = jis.readShort() & 0xffff;
segmentBytesLeft -= 2;
if (yDensity == 0)
mh.logResult(JpegValidator.MsgCode.JPG01W010);
int xt = jis.readByte() & 0xff;
segmentBytesLeft--;
int xy = jis.readByte() & 0xff;
segmentBytesLeft --;
int rgb3n = 3 * xt * xy;
jis.skipBytes(rgb3n);
segmentBytesLeft -= rgb3n;
if (segmentBytesLeft != 0)
mh.logResult(JpegValidator.MsgCode.JPG01W013, segmentBytesLeft);
mh.logResult(JpegValidator.MsgCode.JPG01I017);
return true;
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
private boolean checkJfifExtensionSegment(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh) throws EOFException
{
try
{
if (js.getLastJfifApp0SegmentIndex() != (js.getSegmentCount() - 1))
mh.logResult(JpegValidator.MsgCode.JPG01E022, js.getSegmentCount(), js.getLastJfifApp0SegmentIndex());
js.markLastJfifApp0SegmentIndex();
if (js.getJfifMajorVersion() < 1 || (js.getJfifMajorVersion() == 1 && js.getJfifMinorVersion() < 2))
mh.logResult(JpegValidator.MsgCode.JPG01W011, js.getJfifMajorVersion(), js.getJfifMinorVersion());
int extensionCode = jis.readByte() & 0xff;
segmentBytesLeft -= 1;
switch (extensionCode)
{
case 0x10:
mh.logResult(JpegValidator.MsgCode.JPG01I018, extensionCode);
return checkThumbnailStoredAsJpeg(jis, js, segmentBytesLeft, mh);
case 0x11:
mh.logResult(JpegValidator.MsgCode.JPG01I018, extensionCode);
return checkThumbnailStored1BytePerPixel(jis, js, segmentBytesLeft, mh);
case 0x13:
mh.logResult(JpegValidator.MsgCode.JPG01I018, extensionCode);
return checkThumbnailStored3BytesPerPixel(jis, js, segmentBytesLeft, mh);
default:
mh.logResult(JpegValidator.MsgCode.JPG01W012, extensionCode);
}
jis.skipBytes(segmentBytesLeft);
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
private boolean checkThumbnailStoredAsJpeg(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh) throws EOFException
{
try
{
// TODO: validate thumbnail JPEG
jis.skipBytes(segmentBytesLeft);
return true;
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
private boolean checkThumbnailStored1BytePerPixel(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh) throws EOFException
{
try
{
int xThumbnail = jis.readByte() & 0xff;
segmentBytesLeft--;
int yThumbnail = jis.readByte() & 0xff;
segmentBytesLeft--;
jis.skipBytes(768);
segmentBytesLeft -= 768;
int thumbnailPixels = xThumbnail * yThumbnail;
jis.skipBytes(thumbnailPixels);
segmentBytesLeft -= thumbnailPixels;
if (segmentBytesLeft != 0)
mh.logResult(JpegValidator.MsgCode.JPG01W014, segmentBytesLeft);
return true;
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
private boolean checkThumbnailStored3BytesPerPixel(JpegInputStream jis, JpegState js, int segmentBytesLeft, AbstractLoggingValidator mh) throws EOFException
{
try
{
int xThumbnail = jis.readByte() & 0xff;
segmentBytesLeft--;
int yThumbnail = jis.readByte() & 0xff;
segmentBytesLeft--;
int thumbnailPixels = 3 * xThumbnail * yThumbnail;
jis.skipBytes(thumbnailPixels);
segmentBytesLeft -= thumbnailPixels;
if (segmentBytesLeft != 0)
mh.logResult(JpegValidator.MsgCode.JPG01W014, segmentBytesLeft);
return true;
}
catch (EOFException e)
{
mh.logResult(JpegValidator.MsgCode.JPG01F003, js.getCurrentCode(), js.getSegmentCount(), jis.getTotalBytesRead());
throw e;
}
catch (IOException e)
{
assert(false) : mh.msgFormatterNV(MsgCode.JPG01X003.toString(), Thread.currentThread().getStackTrace()[2].getMethodName(), e.getMessage());
}
return false;
}
}