/**
* JHOVE2 - Next-generation architecture for format-aware characterization
* <p>
* Copyright (c) 2010 by The Regents of the University of California. All rights reserved.
* </p>
* <p>
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* </p>
* <ul>
* <li>Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.</li>
* <li>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.</li>
* <li>Neither the name of the University of California/California Digital
* Library, Ithaka Harbors/Portico, or Stanford University, nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.</li>
* </ul>
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT OWNER OR 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.
* </p>
*/
package org.jhove2.module.format.tiff;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.LinkedList;
import java.util.List;
import org.jhove2.annotation.ReportableProperty;
import org.jhove2.core.JHOVE2;
import org.jhove2.core.JHOVE2Exception;
import org.jhove2.core.Message;
import org.jhove2.core.Message.Context;
import org.jhove2.core.Message.Severity;
import org.jhove2.core.format.Format;
import org.jhove2.core.io.Input;
import org.jhove2.core.source.MeasurableSource;
import org.jhove2.core.source.Source;
import org.jhove2.module.format.BaseFormatModule;
import org.jhove2.module.format.Validator;
import org.jhove2.module.format.Validator.Validity;
import org.jhove2.persist.FormatModuleAccessor;
import com.sleepycat.persist.model.Persistent;
/**
* JHOVE2 TIFF module. This module parses a TIFF instance and captures selected
* characterization information
*
* @author mstrong
*
*/
@Persistent
public class TiffModule
extends BaseFormatModule
implements Validator
{
/** TIFF module version identifier. */
public static final String VERSION = "2.0.0";
/** TIFF module release date. */
public static final String RELEASE = "2010-09-10";
/** TIFF module rights statement. */
public static final String RIGHTS =
"Copyright 2010 by The Regents of the University of California. " +
"Available under the terms of the BSD license.";
/** TIFF module validation coverage. */
public static final Coverage COVERAGE = Coverage.Inclusive;
/** TIFF Module validity status. */
protected Validity validity;
/** TIFF IFH - Image File Header */
protected IFH ifh = new IFH();
/** Fail fast message. */
protected Message failFastMessage;
/** TIFF Invalid Field Message */
protected List<Message> invalidFieldMessage;
/** TIFF Invalid Field Message */
protected List<Message> invalidFirstTwoBytesMessage;
/** TIFF Invalid Field Message */
protected List<Message> prematureEOFMessage;
/** TIFF Invalid Field Message */
protected List<Message> invalidMagicNumberMessage;
/** TIFF Invalid Field Message */
protected List<Message> byteOffsetNotWordAlignedMessage;
/** TIFF version, defaults to 4. As features are recognized, update the version accordingly */
protected int version = 4;
/** List of IFDs */
List<IFD> ifdList = new LinkedList<IFD>();
/** Factory for mapper from tiff id to Format */
protected Tiff2FormatMapFactory tiff2FormatMapFactory;
/**
* Instantiate a new <code>TIFFModule</code>.
*
* @param format
* TIFF format
* @param formatModuleAccessor
* FormatModuleAccessor to manage access to Format Profiles
*/
public TiffModule(Format format,
FormatModuleAccessor formatModuleAccessor) {
super(VERSION, RELEASE, RIGHTS, format, formatModuleAccessor);
this.validity = Validity.Undetermined;
}
public TiffModule() {
this(null, null);
}
/**
* Parse a source unit.
*
* @param jhove2
* JHOVE2 framework
* @param source
* TIFF source unit
* @param input
* TIFF source input
* @return Number of bytes consumed
* @throws EOFException
* If End-of-File is reached reading the source unit
* @throws IOException
* If an I/O exception is raised reading the source unit
* @throws JHOVE2Exception
* @see org.jhove2.module.format.Parser#parse(org.jhove2.core.JHOVE2,
* org.jhove2.core.source.Source, org.jhove2.core.io.Input)
*/
@Override
public long parse(JHOVE2 jhove2, Source source, Input input)
throws EOFException, IOException, JHOVE2Exception
{
long consumed = 0L;
this.validity = Validity.True;
/* initialize the tiff tags */
TiffTag.getTiffTags(jhove2);
int numErrors = 0;
long start = ((MeasurableSource) source).getStartingOffset();
input.setPosition(start);
try {
// read the first two bytes to determine the endianess
byte[] b = new byte[2];
b[0] = input.readSignedByte();
b[1] = input.readSignedByte();
consumed +=2;
ByteOrder byteOrder = null;
/* validate first 2 bytes */
if ((b[0] != b[1]) &&
(b[0] == 0x49 || b[0] == 0x4D)) {
this.validity = Validity.False;
numErrors++;
Object[]messageArgs = new Object[]{0, input.getPosition(), b[0]};
this.invalidFirstTwoBytesMessage.add(new Message(Severity.ERROR,
Context.OBJECT,
"org.jhove2.module.format.tiff.TIFFModule.invalidFirstTwoBytesMessage",
messageArgs, jhove2.getConfigInfo()));
}
if (b[0] == 0x49) { // 'I'
byteOrder = ByteOrder.LITTLE_ENDIAN;
}
else if (b[0] ==0x4D) { // 'M'
byteOrder = ByteOrder.BIG_ENDIAN;
}
ifh.setByteOrdering(new String(b));
ifh.setByteOrder(byteOrder);
/* set the endianess so subsequent reads are the correct endianess */
input.setByteOrder(byteOrder);
int magic = input.readUnsignedShort();
consumed +=2;
if (magic != 43 && magic != 42) {
this.validity = Validity.False;
Object[]messageArgs = new Object[]{magic};
this.invalidMagicNumberMessage.add(new Message(Severity.ERROR,
Context.OBJECT,
"org.jhove2.module.format.tiff.TIFFModule.invalidMagicNumberMessage",
messageArgs, jhove2.getConfigInfo()));
}
else if(magic == 43) {
// we got a Big TIFF here
}
ifh.setMagicNumber(magic);
ifdList = parseIFDList(jhove2, source, input);
/* loop through IfdList and validate each one */
for (IFD ifd:ifdList){
if (ifd instanceof TiffIFD) {
((TiffIFD) ifd).postParse();
ifd.validate(jhove2, source);
Validity validity = ifd.isValid();
switch (validity){
case Undetermined:
if (this.validity==Validity.True){
this.validity = validity;
}
break;
case False: //False is stronger than Undetermined
this.validity = validity;
break;
case True: // if module already flagged as False or Undetermined, will not undo just because one IFD is good
break;
}
}
}
}
catch (EOFException e) {
this.validity = Validity.False;
this.prematureEOFMessage.add(new Message(Severity.ERROR,
Context.OBJECT,
"org.jhove2.module.format.tiff.TIFFModule.PrematureEOFMessage",
jhove2.getConfigInfo()));
return consumed;
}
return consumed;
}
/**
* parse the IFD(s) validating that there is at least one offset
* and that it is word-aligned. Following the offset
* of the first IFD, parse the linked list of IFDs
*
* @throws JHOVE2Exception
*
*/
private List<IFD> parseIFDList(JHOVE2 jhove2, Source source, Input input)
throws EOFException, IOException, JHOVE2Exception{
long offset = 0L;
try {
/* read the offset to the 0th IFD */
offset = input.readUnsignedInt();
ifh.setFirstIFD(offset);
/* must have at least 1 IFD */
if (offset == 0L) {
this.validity = Validity.False;
this.invalidFieldMessage.add(new Message(Severity.ERROR,
Context.OBJECT,
"org.jhove2.module.format.tiff.TIFFModule.NoIFDInTIFFFileMessage",
jhove2.getConfigInfo()));
}
}
catch (IOException e) {
throw new IOException ("TiffModule.parseIFDs(): IOException reading offset to first IFD",e);
}
/* Parse the list of IFDs */
List<IFD> list = new LinkedList<IFD>();
long nextIfdOffset = offset;
while (nextIfdOffset != 0L) {
/* offset must be word aligned (even number) */
if ((offset & 1) != 0) {
this.validity = Validity.False;
Object[]messageArgs = new Object[]{0, input.getPosition(), offset};
this.byteOffsetNotWordAlignedMessage.add(new Message(Severity.ERROR,
Context.OBJECT,
"org.jhove2.module.format.tiff.TIFFModule.ByteOffsetNotWordAlignedMessage",
messageArgs, jhove2.getConfigInfo()));
}
IFD ifd = parseIFD(nextIfdOffset, list, jhove2, source, input);
nextIfdOffset = ifd.getNextIFD();
}
return list;
}
/**
* following the offsets, process the IFD in the list of IFDs in the TIFF file
*
* @param offset
* @throws JHOVE2Exception
* */
private IFD parseIFD(long ifdOffset, List<IFD> list, JHOVE2 jhove2, Source source, Input input)
throws EOFException, IOException, JHOVE2Exception {
IFD ifd = new TiffIFD();
ifd.setOffset(ifdOffset);
/* parse for the appropriate IFD type */
ifd.parse(jhove2, source, input, this.getTiff2FormatMapFactory());
if (ifdList.size () == 0) {
ifd.setFirst (true);
}
else if (ifdList.size() == 1 ) {
// For some profiles, the second IFD is assumed to
// be the thumbnail. This may not be valid under
// all circumstances.
ifd.setThumbnail (true);
}
list.add(ifd);
int version = ifd.getVersion();
if (version > this.version) {
this.version = version;
}
// TODO: parse subIFDs chains here
// TODO: parse EXIF/GPS/InterOP/GlobalParms IFDChains here
return ifd;
}
/**
* Get module validation coverage.
*
* @return the coverage
*/
@Override
public Coverage getCoverage() {
return COVERAGE;
}
/**
* Validate a TIFF source unit.
*
* @param jhove2
* JHOVE2 framework
* @param source
* TIFF source unit
* @return UTF-8 validation status
* @see org.jhove2.module.format.Validator#validate(org.jhove2.core.JHOVE2,
* org.jhove2.core.source.Source)
*
*/
@Override
public Validity validate(JHOVE2 jhove2, Source source, Input input)
throws JHOVE2Exception
{
return this.validity;
}
/**
* Get TIFF source unit's validation status.
*
* @return the validity
*/
@Override
public Validity isValid() {
return this.validity;
}
/**
*
* @return IFH Image File Header
*/
@ReportableProperty(order = 1, value="IFH")
public IFH getIFH() {
return this.ifh;
}
/**
* returns the list of IFDs for this TIFF object
*
* @return List<IFD>
*/
@ReportableProperty(order = 2, value="IFDs.")
public List<IFD> getIFDs() {
return this.ifdList;
}
/**
* Get fail fast message.
*
* @return Fail fast message
*/
@ReportableProperty(order = 3, value = "Fail fast message.")
public Message getFailFast() {
return this.failFastMessage;
}
/**
* Get Invalid Field Message.
*
* @return InvalidFieldMessage
*/
@ReportableProperty(order = 4, value = "Invalid Field Message.")
public List<Message> getInvalidFieldMessage() {
return invalidFieldMessage;
}
/**
* Get Invalid First Two Bytes Message.
*
* @return InvalidFirstTwoBytesMessage
*/
@ReportableProperty(order = 5, value = "Invalid First Two Bytes Message.")
public List<Message> getInvalidFirstTwoBytesMessage() {
return invalidFirstTwoBytesMessage;
}
/**
* Get Premature EOF Message.
*
* @return PrematureEOFMessage
*/
@ReportableProperty(order = 6, value = "Premature EOF Message.")
public List<Message> getPrematureEOFMessage() {
return prematureEOFMessage;
}
/**
* Get Invalid Magic Number Message.
*
* @return InvalidMagicNumberMessage
*/
@ReportableProperty(order = 7, value = "Invalid Magic Number Message.")
public List<Message> getInvalidMagicNumberMessage() {
return invalidMagicNumberMessage;
}
/**
* Get Byte Offset Not Word Aligned Message.
*
* @return ByteOffsetNotWordAlignedMessage
*/
@ReportableProperty(order = 8, value = "Byte Offset Not Word Aligned Message.")
public List<Message> getByteOffsetNotWordAlignedMessage() {
return byteOffsetNotWordAlignedMessage;
}
/**
* @return the version
*/
@ReportableProperty(order = 9, value = "TIFF version.")
public int getTiffVersion() {
return this.version;
}
/**
* @return the tiff2FormatMapFactory
*/
public Tiff2FormatMapFactory getTiff2FormatMapFactory() {
return tiff2FormatMapFactory;
}
/**
* @param tiff2FormatMapFactory the tiff2FormatMapFactory to set
*/
public void setTiff2FormatMapFactory(Tiff2FormatMapFactory tiff2FormatMapFactory) {
this.tiff2FormatMapFactory = tiff2FormatMapFactory;
}
}