From 98264493c4bf2806bb710586b2c4726369080245 Mon Sep 17 00:00:00 2001 From: Peter Hancock Date: Tue, 17 May 2011 11:07:06 +0000 Subject: [PATCH] Fixed io exception in MODCAParser caused by the improper use of mark() and reset() on the MODCA data input stream. See bugzilla 50909. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1104135 13f79535-47bb-0310-9956-ffa450edef68 --- .../fop/afp/apps/FontPatternExtractor.java | 5 +- .../apache/fop/afp/parser/MODCAParser.java | 72 +- .../afp/parser/UnparsedStructuredField.java | 648 +++++++++--------- .../apache/fop/afp/util/AFPResourceUtil.java | 2 +- status.xml | 3 + .../org/apache/fop/StandardTestSuite.java | 2 + .../fop/afp/parser/MODCAParserTestCase.java | 238 +++++++ 7 files changed, 616 insertions(+), 354 deletions(-) create mode 100644 test/java/org/apache/fop/afp/parser/MODCAParserTestCase.java diff --git a/src/java/org/apache/fop/afp/apps/FontPatternExtractor.java b/src/java/org/apache/fop/afp/apps/FontPatternExtractor.java index 0adffcd8b..7a2b8ed36 100644 --- a/src/java/org/apache/fop/afp/apps/FontPatternExtractor.java +++ b/src/java/org/apache/fop/afp/apps/FontPatternExtractor.java @@ -55,9 +55,10 @@ public class FontPatternExtractor { UnparsedStructuredField strucField; while ((strucField = parser.readNextStructuredField()) != null) { if (strucField.getSfTypeID() == 0xD3EE89) { + byte[] sfData = strucField.getData(); println(strucField.toString()); - HexDump.dump(strucField.getData(), 0, printStream, 0); - baout.write(strucField.getData()); + HexDump.dump(sfData, 0, printStream, 0); + baout.write(sfData); } } diff --git a/src/java/org/apache/fop/afp/parser/MODCAParser.java b/src/java/org/apache/fop/afp/parser/MODCAParser.java index 356d4f169..d0d0eced9 100644 --- a/src/java/org/apache/fop/afp/parser/MODCAParser.java +++ b/src/java/org/apache/fop/afp/parser/MODCAParser.java @@ -20,15 +20,23 @@ package org.apache.fop.afp.parser; import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fop.afp.parser.UnparsedStructuredField.Introducer; + /** * An simple MO:DCA/AFP parser. */ public class MODCAParser { + private static final Log LOG = LogFactory.getLog(MODCAParser.class); + + private static final int INTRODUCER_LENGTH = 8; + /** The carriage control character (0x5A) used to indicate the start of a structured field. */ public static final byte CARRIAGE_CONTROL_CHAR = (byte)(0x5A & 0xFF); @@ -39,19 +47,9 @@ public class MODCAParser { * @param in the {@link InputStream} to read the AFP file from. */ public MODCAParser(InputStream in) { - if (!in.markSupported()) { - in = new java.io.BufferedInputStream(in); - } this.din = new DataInputStream(in); } - /** - * Returns the {@link DataInputStream} used for parsing structured fields. - * @return the data input stream - */ - public DataInputStream getDataInputStream() { - return this.din; - } /** * Reads the next structured field from the input stream. @@ -61,20 +59,46 @@ public class MODCAParser { * @throws IOException if an I/O error occurs */ public UnparsedStructuredField readNextStructuredField() throws IOException { - try { - while (true) { - byte b = din.readByte(); //Skip 0x5A character if necessary (ex. AFP) - if (b == 0x0D || b == 0x0A) { - //CR and LF may be used as field delimiters - continue; - } else if (b == CARRIAGE_CONTROL_CHAR) { - break; //Signals the start of a new structured field - } + + //Find the SF delimiter + do { + //Exhausted streams and so no next SF + // - null return represents this case + // TODO should this happen? + if (din.available() == 0) { + return null; + } + } while (din.readByte() != CARRIAGE_CONTROL_CHAR); + + //Read introducer as byte array to preserve any data not parsed below + byte[] introducerData = new byte[INTRODUCER_LENGTH]; //Length of introducer + din.readFully(introducerData); + + Introducer introducer = new Introducer(introducerData); + + int dataLength = introducer.getLength() - INTRODUCER_LENGTH; + + //Handle optional extension + byte[] extData = null; + if (introducer.isExtensionPresent()) { + short extLength = 0; + extLength = (short)((din.readByte()) & 0xFF); + if (extLength > 0) { + extData = new byte[extLength - 1]; + din.readFully(extData); + dataLength -= extLength; } - } catch (EOFException eof) { - return null; } - return UnparsedStructuredField.readStructuredField(getDataInputStream()); - } + //Read payload + byte[] data = new byte[dataLength]; + din.readFully(data); + UnparsedStructuredField sf = new UnparsedStructuredField(introducer, data, extData); + + if (LOG.isTraceEnabled()) { + LOG.trace(sf); + } + + return sf; + } } diff --git a/src/java/org/apache/fop/afp/parser/UnparsedStructuredField.java b/src/java/org/apache/fop/afp/parser/UnparsedStructuredField.java index f775c05ee..4f782c5b1 100644 --- a/src/java/org/apache/fop/afp/parser/UnparsedStructuredField.java +++ b/src/java/org/apache/fop/afp/parser/UnparsedStructuredField.java @@ -19,337 +19,331 @@ package org.apache.fop.afp.parser; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; -import java.io.PrintStream; import java.text.DecimalFormat; -import org.apache.commons.io.HexDump; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** - * Represents an unparsed (generic) AFP structured field. - */ -public class UnparsedStructuredField { - - private static final Log LOG = LogFactory.getLog(UnparsedStructuredField.class); - - private static final int INTRODUCER_LENGTH = 8; - - private short sfLength; - private byte sfClassCode; - private byte sfTypeCode; - private byte sfCategoryCode; - private boolean sfiExtensionPresent; - private boolean sfiSegmentedData; - private boolean sfiPaddingPresent; - private short extLength; - private byte[] introducerData; - private byte[] extData; - private byte[] data; - - /** - * Default constructor. - */ - public UnparsedStructuredField() { - //nop - } - - /** - * Reads a structured field from a {@link DataInputStream}. The resulting object can be - * further interpreted be follow-up code. - * @param din the stream to read from - * @return the generic structured field - * @throws IOException if an I/O error occurs - */ - public static UnparsedStructuredField readStructuredField(DataInputStream din) - throws IOException { - UnparsedStructuredField sf = new UnparsedStructuredField(); - - //Read introducer as byte array to preserve any data not parsed below - din.mark(INTRODUCER_LENGTH); - sf.introducerData = new byte[INTRODUCER_LENGTH]; //Length of introducer - din.readFully(sf.introducerData); - din.reset(); - - //Parse the introducer - short len; - try { - len = din.readShort(); - } catch (EOFException eof) { - return null; - } - sf.sfLength = len; - sf.sfClassCode = din.readByte(); - sf.sfTypeCode = din.readByte(); - sf.sfCategoryCode = din.readByte(); - - //Flags - byte f = din.readByte(); - sf.sfiExtensionPresent = (f & 0x01) != 0; - sf.sfiSegmentedData = (f & 0x04) != 0; - sf.sfiPaddingPresent = (f & 0x10) != 0; - din.skip(2); //Reserved - - int dataLength = sf.sfLength - INTRODUCER_LENGTH; - - //Handle optional extension - if (sf.sfiExtensionPresent) { - sf.extLength = (short)(((short)din.readByte()) & 0xFF); - if (sf.extLength > 0) { - sf.extData = new byte[sf.extLength - 1]; - din.readFully(sf.extData); - dataLength -= sf.extLength; - } - } - - //Read payload - sf.data = new byte[dataLength]; - din.readFully(sf.data); - - if (LOG.isTraceEnabled()) { - LOG.trace(sf); - } - - return sf; - } - - /** {@inheritDoc} */ - public String toString() { - StringBuffer sb = new StringBuffer("Structured Field: "); - sb.append(Integer.toHexString(getSfTypeID()).toUpperCase()); - sb.append(", len="); - sb.append(new DecimalFormat("00000").format(getSfLength())); - sb.append(" ").append(getTypeCodeAsString()); - sb.append(" ").append(getCategoryCodeAsString()); - if (isSfiExtensionPresent()) { - sb.append(", SFI extension present"); - } - if (isSfiSegmentedData()) { - sb.append(", segmented data"); - } - if (isSfiPaddingPresent()) { - sb.append(", with padding"); - } - return sb.toString(); - } - - /** - * Dump the structured field as hex data to the given {@link PrintStream}. - * @param out the {@link PrintStream} to dump to - * @throws IOException if an I/O error occurs - */ - public void dump(PrintStream out) throws IOException { - out.println(toString()); - HexDump.dump(getData(), 0, out, 0); - } - - /** - * Dump the structured field as hex data to System.out. - * @throws IOException if an I/O error occurs - */ - public void dump() throws IOException { - dump(System.out); - } - - /** - * Returns type code function name for this field. - * @return the type code function name - */ - public String getTypeCodeAsString() { - switch ((int)getSfTypeCode() & 0xFF) { - case 0xA0: return "Attribute"; - case 0xA2: return "CopyCount"; - case 0xA6: return "Descriptor"; - case 0xA7: return "Control"; - case 0xA8: return "Begin"; - case 0xA9: return "End"; - case 0xAB: return "Map"; - case 0xAC: return "Position"; - case 0xAD: return "Process"; - case 0xAF: return "Include"; - case 0xB0: return "Table"; - case 0xB1: return "Migration"; - case 0xB2: return "Variable"; - case 0xB4: return "Link"; - case 0xEE: return "Data"; - default: return "Unknown:" + Integer.toHexString((int)getSfTypeCode()).toUpperCase(); - } - } - - /** - * Returns category code function name for this field. - * @return the category code function name - */ - public String getCategoryCodeAsString() { - switch ((int)getSfCategoryCode() & 0xFF) { - case 0x5F: return "Page Segment"; - case 0x6B: return "Object Area"; - case 0x77: return "Color Attribute Table"; - case 0x7B: return "IM Image"; - case 0x88: return "Medium"; - case 0x89: return "Font"; - case 0x8A: return "Coded Font"; - case 0x90: return "Process Element"; - case 0x92: return "Object Container"; - case 0x9B: return "Presentation Text"; - case 0xA7: return "Index"; - case 0xA8: return "Document"; - case 0xAD: return "Page Group"; - case 0xAF: return "Page"; - case 0xBB: return "Graphics"; - case 0xC3: return "Data Resource"; - case 0xC4: return "Document Environment Group (DEG)"; - case 0xC6: return "Resource Group"; - case 0xC7: return "Object Environment Group (OEG)"; - case 0xC9: return "Active Environment Group (AEG)"; - case 0xCC: return "Medium Map"; - case 0xCD: return "Form Map"; - case 0xCE: return "Name Resource"; - case 0xD8: return "Page Overlay"; - case 0xD9: return "Resource Environment Group (REG)"; - case 0xDF: return "Overlay"; - case 0xEA: return "Data Supression"; - case 0xEB: return "Bar Code"; - case 0xEE: return "No Operation"; - case 0xFB: return "Image"; - default: return "Unknown:" + Integer.toHexString((int)getSfTypeCode()).toUpperCase(); - } - } - - /** - * Returns the structured field's length. - * @return the field length - */ - public short getSfLength() { - return this.sfLength; - } - - /** - * Returns the structured field's identifier. - * @return the field identifier - */ - public int getSfTypeID() { - return ((getSfClassCode() & 0xFF) << 16) - | ((getSfTypeCode() & 0xFF) << 8) - | (getSfCategoryCode() & 0xFF); - } - - /** - * Returns the structured field's class code. - * @return the field class code - */ - public byte getSfClassCode() { - return this.sfClassCode; - } - - /** - * Returns the structured field's type code. - * @return the type code - */ - public byte getSfTypeCode() { - return this.sfTypeCode; - } - - /** - * Returns the structured field's category code. - * @return the sfCategoryCode - */ - public byte getSfCategoryCode() { - return this.sfCategoryCode; - } - - /** - * Indicates whether an field introducer extension is present. - * @return true if an field introducer extension is present - */ - public boolean isSfiExtensionPresent() { - return this.sfiExtensionPresent && (this.extData != null); - } - - /** - * Indicates whether segmented data is present. - * @return true if the data is segmented - */ - public boolean isSfiSegmentedData() { - return this.sfiSegmentedData; - } - - /** - * Indicates whether the data is padded. - * @return true if the data is padded - */ - public boolean isSfiPaddingPresent() { - return this.sfiPaddingPresent; - } - - /** - * Returns the length of the extension if present. - * @return the length of the extension (or 0 if no extension is present) - */ - public short getExtLength() { - return this.extLength; - } - - /** - * Returns the extension data if present. - * @return the extension data (or null if no extension is present) - */ - public byte[] getExtData() { - return this.extData; - } - - /** - * Returns the structured field's payload. - * @return the field's data - */ - public byte[] getData() { - return this.data; - } - - /** - * Returns the structured field's introducer data. - * @return the introducer data - */ - public byte[] getIntroducerData() { - return this.introducerData; - } - - /** - * Returns the complete structured field as a byte array. - * @return the complete field data - */ - public byte[] getCompleteFieldAsBytes() { - int len = INTRODUCER_LENGTH; - if (isSfiExtensionPresent()) { - len += getExtLength(); - } - len += getData().length; - byte[] bytes = new byte[len]; - int pos = 0; - System.arraycopy(getIntroducerData(), 0, bytes, pos, INTRODUCER_LENGTH); - pos += INTRODUCER_LENGTH; - if (isSfiExtensionPresent()) { - System.arraycopy(getExtData(), 0, bytes, pos, getExtLength()); - pos += getExtLength(); - } - System.arraycopy(getData(), 0, bytes, pos, getData().length); - return bytes; - } - - /** - * Writes this structured field to the given {@link OutputStream}. - * @param out the output stream - * @throws IOException if an I/O error occurs - */ - public void writeTo(OutputStream out) throws IOException { - out.write(this.introducerData); - if (isSfiExtensionPresent()) { - out.write(this.extData); - } - out.write(this.data); - } +* Represents an unparsed (generic) AFP structured field. +*/ +public final class UnparsedStructuredField { + + private final Introducer introducer; + private final byte[] extData; + private final byte[] data; + + /** + * + * @param Structured field introducer + * @param data Structured field data + * @param extData Structured field extension data + */ + UnparsedStructuredField(Introducer introducer, + byte[] data, byte[] extData) { + this.introducer = introducer; + this.data = data; + if (extData != null) { + this.extData = extData; + } else { + this.extData = null; + } + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer("Structured Field: "); + sb.append(Integer.toHexString(getSfTypeID()).toUpperCase()); + sb.append(", len="); + sb.append(new DecimalFormat("00000").format(getSfLength())); + sb.append(" ").append(getTypeCodeAsString()); + sb.append(" ").append(getCategoryCodeAsString()); + if (isSfiExtensionPresent()) { + sb.append(", SFI extension present"); + } + if (isSfiSegmentedData()) { + sb.append(", segmented data"); + } + if (isSfiPaddingPresent()) { + sb.append(", with padding"); + } + return sb.toString(); + } + + + /** + * Returns type code function name for this field. + * @return the type code function name + */ + private String getTypeCodeAsString() { + switch (getSfTypeCode() & 0xFF) { + case 0xA0: return "Attribute"; + case 0xA2: return "CopyCount"; + case 0xA6: return "Descriptor"; + case 0xA7: return "Control"; + case 0xA8: return "Begin"; + case 0xA9: return "End"; + case 0xAB: return "Map"; + case 0xAC: return "Position"; + case 0xAD: return "Process"; + case 0xAF: return "Include"; + case 0xB0: return "Table"; + case 0xB1: return "Migration"; + case 0xB2: return "Variable"; + case 0xB4: return "Link"; + case 0xEE: return "Data"; + default: return "Unknown:" + Integer.toHexString(getSfTypeCode()).toUpperCase(); + } + } + + /** + * Returns category code function name for this field. + * @return the category code function name + */ + private String getCategoryCodeAsString() { + switch (getSfCategoryCode() & 0xFF) { + case 0x5F: return "Page Segment"; + case 0x6B: return "Object Area"; + case 0x77: return "Color Attribute Table"; + case 0x7B: return "IM Image"; + case 0x88: return "Medium"; + case 0x89: return "Font"; + case 0x8A: return "Coded Font"; + case 0x90: return "Process Element"; + case 0x92: return "Object Container"; + case 0x9B: return "Presentation Text"; + case 0xA7: return "Index"; + case 0xA8: return "Document"; + case 0xAD: return "Page Group"; + case 0xAF: return "Page"; + case 0xBB: return "Graphics"; + case 0xC3: return "Data Resource"; + case 0xC4: return "Document Environment Group (DEG)"; + case 0xC6: return "Resource Group"; + case 0xC7: return "Object Environment Group (OEG)"; + case 0xC9: return "Active Environment Group (AEG)"; + case 0xCC: return "Medium Map"; + case 0xCD: return "Form Map"; + case 0xCE: return "Name Resource"; + case 0xD8: return "Page Overlay"; + case 0xD9: return "Resource Environment Group (REG)"; + case 0xDF: return "Overlay"; + case 0xEA: return "Data Supression"; + case 0xEB: return "Bar Code"; + case 0xEE: return "No Operation"; + case 0xFB: return "Image"; + default: return "Unknown:" + Integer.toHexString(getSfTypeCode()).toUpperCase(); + } + } + + /** + * Returns the structured field's length. + * @return the field length + */ + public short getSfLength() { + return introducer.length; + } + + /** + * Returns the structured field's identifier. + * @return the field identifier + */ + public int getSfTypeID() { + return ((getSfClassCode() & 0xFF) << 16) + | ((getSfTypeCode() & 0xFF) << 8) + | (getSfCategoryCode() & 0xFF); + } + + /** + * Returns the structured field's class code. + * @return the field class code + */ + public byte getSfClassCode() { + return introducer.classCode; + } + + /** + * Returns the structured field's type code. + * @return the type code + */ + public byte getSfTypeCode() { + return introducer.typeCode; + } + + /** + * Returns the structured field's category code. + * @return the sfCategoryCode + */ + public byte getSfCategoryCode() { + return introducer.categoryCode; + } + + /** + * Indicates whether an field introducer extension is present. + * @return true if an field introducer extension is present + */ + public boolean isSfiExtensionPresent() { + return introducer.extensionPresent && (this.extData != null); + } + + /** + * Indicates whether segmented data is present. + * @return true if the data is segmented + */ + public boolean isSfiSegmentedData() { + return introducer.segmentedData; + } + + /** + * Indicates whether the data is padded. + * @return true if the data is padded + */ + public boolean isSfiPaddingPresent() { + return introducer.paddingPresent; + } + + /** + * Returns the length of the extension if present. + * @return the length of the extension (or 0 if no extension is present) + */ + public short getExtLength() { + return (extData != null) ? (short)(extData.length + 1) : 0; + + } + + /** + * Returns the extension data if present. + * @return the extension data (or null if no extension is present) + */ + byte[] getExtData() { + if (this.extData == null) { + return null; + } + byte[] rtn = new byte[this.extData.length]; + System.arraycopy(this.extData, 0, rtn, 0, rtn.length); + return rtn; + } + + /** + * Returns the structured field's payload. + * @return the field's data + */ + public byte[] getData() { + if (this.data == null) { + return null; + } + byte[] rtn = new byte[this.data.length]; + System.arraycopy(this.data, 0, rtn, 0, rtn.length); + return rtn; + } + + //For unit testing + byte[] getIntroducerData() { + return introducer.getIntroducerData(); + } + + /** + * Returns the complete structured field as a byte array. + * @return the complete field data + */ + public byte[] getCompleteFieldAsBytes() { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(getSfLength()); + try { + writeTo(baos); + } catch (IOException ioe) { + //nop + } + return baos.toByteArray(); + + } + + /** + * Writes this structured field to the given {@link OutputStream}. + * @param out the output stream + * @throws IOException if an I/O error occurs + */ + public void writeTo(OutputStream out) throws IOException { + out.write(introducer.introducerData); + if (isSfiExtensionPresent()) { + out.write(this.extData.length + 1); + out.write(this.extData); + } + out.write(this.data); + } + + static final class Introducer { + + private final short length; + private final byte classCode; + private final byte typeCode; + private final byte categoryCode; + private final boolean extensionPresent; + private final boolean segmentedData; + private final boolean paddingPresent; + private final byte[] introducerData; + + Introducer(byte[] introducerData) throws IOException { + + this.introducerData = introducerData; + + // Parse the introducer; the 8 bytes have already been read from the stream just + // before, so we parse the introducer from the byte array + DataInputStream iis = new DataInputStream( + new ByteArrayInputStream(introducerData)); + + length = iis.readShort(); + + classCode = iis.readByte(); + typeCode = iis.readByte(); + categoryCode = iis.readByte(); + + //Flags + byte f = iis.readByte(); + + extensionPresent = (f & 0x01) != 0; + segmentedData = (f & 0x04) != 0; + paddingPresent = (f & 0x10) != 0; + + } + + + public short getLength() { + return length; + } + + public byte getClassCode() { + return classCode; + } + + public byte getTypeCode() { + return typeCode; + } + + public byte getCategoryCode() { + return categoryCode; + } + + public boolean isExtensionPresent() { + return extensionPresent; + } + + + public boolean isSegmentedData() { + return segmentedData; + } + + public boolean isPaddingPresent() { + return paddingPresent; + } + + public byte[] getIntroducerData() { + byte[] rtn = new byte[introducerData.length]; + System.arraycopy(introducerData, 0, rtn, 0, rtn.length); + return rtn; + } + } + } diff --git a/src/java/org/apache/fop/afp/util/AFPResourceUtil.java b/src/java/org/apache/fop/afp/util/AFPResourceUtil.java index ebb318046..979376b3e 100644 --- a/src/java/org/apache/fop/afp/util/AFPResourceUtil.java +++ b/src/java/org/apache/fop/afp/util/AFPResourceUtil.java @@ -29,8 +29,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.afp.AFPConstants; -import org.apache.fop.afp.modca.ResourceObject; import org.apache.fop.afp.modca.AbstractAFPObject.Category; +import org.apache.fop.afp.modca.ResourceObject; import org.apache.fop.afp.parser.MODCAParser; import org.apache.fop.afp.parser.UnparsedStructuredField; diff --git a/status.xml b/status.xml index 895ad3ee0..064cdf8e6 100644 --- a/status.xml +++ b/status.xml @@ -59,6 +59,9 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> + + Fixed io exception in MODCAParser caused by the improper use of mark() and reset() on the + MODCA data input stream. Added unit test. Removed invalid entries in ToUnicode table of CID subset fonts. diff --git a/test/java/org/apache/fop/StandardTestSuite.java b/test/java/org/apache/fop/StandardTestSuite.java index 480ab8bd0..d6a6f8367 100644 --- a/test/java/org/apache/fop/StandardTestSuite.java +++ b/test/java/org/apache/fop/StandardTestSuite.java @@ -23,6 +23,7 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.apache.fop.area.ViewportTestSuite; +import org.apache.fop.afp.parser.MODCAParserTestCase; import org.apache.fop.fonts.DejaVuLGCSerifTest; import org.apache.fop.image.loader.batik.ImageLoaderTestCase; import org.apache.fop.image.loader.batik.ImagePreloaderTestCase; @@ -57,6 +58,7 @@ public class StandardTestSuite { suite.addTest(new TestSuite(PDFCMapTestCase.class)); suite.addTest(new TestSuite(PDFsRGBSettingsTestCase.class)); suite.addTest(new TestSuite(DejaVuLGCSerifTest.class)); + suite.addTest(new TestSuite(MODCAParserTestCase.class)); suite.addTest(AFPTestSuite.suite()); suite.addTest(PSTestSuite.suite()); suite.addTest(RichTextFormatTestSuite.suite()); diff --git a/test/java/org/apache/fop/afp/parser/MODCAParserTestCase.java b/test/java/org/apache/fop/afp/parser/MODCAParserTestCase.java new file mode 100644 index 000000000..e079820b7 --- /dev/null +++ b/test/java/org/apache/fop/afp/parser/MODCAParserTestCase.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.afp.parser; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.util.Arrays; + +import junit.framework.TestCase; + +/** + * MODCAParser and MODCAParser.UnparsedStructuredField Unit tests + */ +public class MODCAParserTestCase extends TestCase { + + /** The carriage control character (0x5A) used to indicate the start of a structured field. */ + public static final byte CARRIAGE_CONTROL_CHAR = (byte)0x5A; + /**ASCII carriage return control character*/ + public static final byte CARRIAGE_RETURN = (byte)0x0A; + /**ASCII line feed control character */ + public static final byte LINE_FEED = (byte)0x0D; + /** 8 byte introducer describe the SF */ + private static final int INTRODUCER_LENGTH = 8; + + /** + * Test that the MODCA parser recognises carriage control (0x5A) as the Structured Field + * delimeter + * + * @throws Exception * + */ + public void testReadNextStructuredField1() throws Exception { + + // carriage control (0x5A) delimits structured fields, + // and control is handed to readStructuredField(DataInputStream) + byte[][] goodInputStream = new byte[][]{ + new byte[]{CARRIAGE_CONTROL_CHAR} + }; + + for (byte[] b : goodInputStream) { + try { + new MODCAParser(new ByteArrayInputStream(b)) + .readNextStructuredField(); + fail("BAD SF should throw EOF: " + byteArrayToString(b)); + } catch (EOFException eof) { + //passed + } + } + + // EOFException thrown when reading the input stream are caught and + // a null value is returned + byte[][] badInputStream = new byte[][]{ + new byte[]{}, + new byte[]{CARRIAGE_RETURN}, + new byte[]{LINE_FEED} + }; + + for (byte[] b : badInputStream) { + UnparsedStructuredField usf = new MODCAParser(new ByteArrayInputStream(b)) + .readNextStructuredField(); + assertNull(usf); + } + } + + + /** + * Test that the MODCA parser correctly constructs an UnparsedStructuredField + * from a well formed structured field + * + * @throws Exception * + */ + public void testReadNextStructuredField2() throws Exception { + + // no extension data + testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID + (byte)0, //flags excluding the bits for + //extension present, segmented data and padding present + false, false, + new byte[]{0, 0}, + new byte[]{1}, null); + + // with extension data + testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID + (byte)0, //flags excluding the bits for + //extension present, segmented data and padding present + false, false, + new byte[]{0, 0}, + new byte[]{1}, new byte[]{10}); + + // with ignored reserved bits + testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID + (byte)0, //flags excluding the bits for + //extension present, segmented data and padding present + false, false, + new byte[]{1, 2}, + new byte[]{1}, null); + + // with padding present and segmented data + testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID + (byte)(1 << 3), //flags excluding the bits for + //extension present, segmented data and padding present + true, true, + new byte[]{0, 0}, + new byte[]{1}, null); + + // with flags non zero + testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID + (byte)(1 << 3), //flags excluding the bits for + //extension present, segmented data and padding present + false, false, + new byte[]{0, 0}, + new byte[]{1}, null); + } + + + private void testSF(byte classCode, byte typeCode, byte categoryCode, + byte flags, boolean segmentedData, boolean paddingPresent, byte[] reserved, + byte[] data, byte[] extData) throws Exception { + + byte extDataLength = 0; + boolean extensionPresent = (extData != null); + + if (extensionPresent) { + flags = (byte)(flags | 0x01); + extDataLength = (byte)(extData.length + 1); //length includes length byte + } + + if (segmentedData) { + flags = (byte)(flags | 0x04); + } + + if (paddingPresent) { + flags = (byte)(flags | 0x10); + } + + short length = (short)(INTRODUCER_LENGTH + data.length + extDataLength); + byte[] lengthBytes = new byte[]{(byte)(length >> 8), (byte)(length & 0xFF)}; + + byte[] sfBytes = new byte[length]; + + //introducer bytes + sfBytes[0] = lengthBytes[0]; + sfBytes[1] = lengthBytes[1]; + sfBytes[2] = classCode; + sfBytes[3] = typeCode; + sfBytes[4] = categoryCode; + sfBytes[5] = flags; + sfBytes[6] = reserved[0]; + sfBytes[7] = reserved[1]; + + if (extDataLength > 0) { + sfBytes[8] = (byte)(extData.length + 1); + System.arraycopy(extData, 0, sfBytes, 9, extData.length); + } + + System.arraycopy(data, 0, sfBytes, length - data.length, data.length); + + + byte[] delimiteredSF = new byte[length + 1]; + + delimiteredSF[0] = (byte)0x5A; + + System.arraycopy(sfBytes, 0, delimiteredSF, 1, length); + + InputStream bis = new ByteArrayInputStream(delimiteredSF); + + UnparsedStructuredField actual = new MODCAParser(bis) + .readNextStructuredField(); + + //check introducer + assertEquals(length, actual.getSfLength()); + assertEquals(classCode, actual.getSfClassCode()); + assertEquals(typeCode, actual.getSfTypeCode()); + assertEquals(categoryCode, actual.getSfCategoryCode()); + assertEquals(extensionPresent, actual.isSfiExtensionPresent()); + assertEquals(segmentedData, actual.isSfiSegmentedData()); + assertEquals(paddingPresent, actual.isSfiPaddingPresent()); + + byte[] introducerData = new byte[]{(byte)(length >> 8), (byte)(length & 0xFF), + classCode, typeCode, categoryCode, flags, reserved[0], reserved[1]}; + + assertTrue(Arrays.equals(introducerData, actual.getIntroducerData())); + + //check data + assertTrue(Arrays.equals(data, actual.getData())); + + //check extension data + if (extData != null) { + assertTrue(Arrays.equals(extData, actual.getExtData())); + } + assertEquals( + (extData == null) ? 0 : extData.length + 1, // 1 byte for length byte + actual.getExtLength()); + + assertTrue(Arrays.equals(data, actual.getData())); + + int expectedSfTypeID = ((classCode & 0xFF) << 16) + | ((typeCode & 0xFF) << 8) + | (categoryCode & 0xFF); + + assertEquals(expectedSfTypeID, actual.getSfTypeID()); + + assertTrue(Arrays.equals(sfBytes, actual.getCompleteFieldAsBytes())); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + actual.writeTo(baos); + assertTrue(Arrays.equals(sfBytes, baos.toByteArray())); + + } + + + private static String byteArrayToString(byte[] byteArray) { + StringBuilder sb = new StringBuilder(); + for (byte b : byteArray) { + sb.append(Integer.toHexString(b & 0xFF)).append(" "); + } + return sb.toString(); + } + +} -- 2.39.5