/***************************************************************************** * * 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. * ****************************************************************************/ package org.apache.padaf.preflight.contentstream; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_ARRAY_TOO_LONG; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_CONTENT_STREAM_INVALID_ARGUMENT; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_LITERAL_TOO_LONG; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_NAME_TOO_LONG; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_NUMERIC_RANGE; import static org.apache.padaf.preflight.ValidationConstants.ERROR_SYNTAX_TOO_MANY_ENTRIES; import static org.apache.padaf.preflight.ValidationConstants.MAX_ARRAY_ELEMENTS; import static org.apache.padaf.preflight.ValidationConstants.MAX_DICT_ENTRIES; import static org.apache.padaf.preflight.ValidationConstants.MAX_NAME_SIZE; import static org.apache.padaf.preflight.ValidationConstants.MAX_NEGATIVE_FLOAT; import static org.apache.padaf.preflight.ValidationConstants.MAX_POSITIVE_FLOAT; import static org.apache.padaf.preflight.ValidationConstants.MAX_STRING_LENGTH; import java.io.IOException; import java.util.List; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSFloat; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.util.PDFOperator; import org.apache.pdfbox.util.operator.OperatorProcessor; /** * This implementation of OperatorProcessor allow the operator validation * according PDF/A rules without compute the operator actions. */ public class StubOperator extends OperatorProcessor { /* * (non-Javadoc) * * @see * org.apache.pdfbox.util.operator.OperatorProcessor#process(org.apache.pdfbox * .util.PDFOperator, java.util.List) */ @Override public void process(PDFOperator operator, List<COSBase> arguments) throws IOException { String op = operator.getOperation(); if ("S".equals(op)) { checkNoOperands(arguments); } else if ("B".equals(op)) { checkNoOperands(arguments); } else if ("f".equals(op)) { checkNoOperands(arguments); } else if ("F".equals(op)) { checkNoOperands(arguments); } else if ("f*".equals(op)) { checkNoOperands(arguments); } else if ("b".equals(op)) { checkNoOperands(arguments); } else if ("B*".equals(op)) { checkNoOperands(arguments); } else if ("b*".equals(op)) { checkNoOperands(arguments); } else if ("s".equals(op)) { checkNoOperands(arguments); } else if ("EMC".equals(op)) { checkNoOperands(arguments); } else if ("BMC".equals(op)) { checkStringOperands(arguments, 1); } else if ("BDC".equals(op)) { checkTagAndPropertyOperands(arguments); } else if ("DP".equals(op)) { checkTagAndPropertyOperands(arguments); } else if ("c".equals(op)) { checkNumberOperands(arguments, 6); } else if ("v".equals(op)) { checkNumberOperands(arguments, 4); } else if ("y".equals(op)) { checkNumberOperands(arguments, 4); } else if ("d0".equals(op)) { checkNumberOperands(arguments, 2); } else if ("d1".equals(op)) { checkNumberOperands(arguments, 6); } else if ("g".equals(op)) { checkNumberOperands(arguments, 1); } else if ("G".equals(op)) { checkNumberOperands(arguments, 1); } else if ("gs".equals(op)) { checkStringOperands(arguments, 1); } else if ("h".equals(op)) { checkNoOperands(arguments); } else if ("i".equals(op)) { checkNumberOperands(arguments, 1); } else if ("l".equals(op)) { checkNumberOperands(arguments, 2); } else if ("m".equals(op)) { checkNumberOperands(arguments, 2); } else if ("M".equals(op)) { checkNumberOperands(arguments, 1); } else if ("MP".equals(op)) { checkStringOperands(arguments, 1); } else if ("n".equals(op)) { checkNoOperands(arguments); } else if ("re".equals(op)) { checkNumberOperands(arguments, 4); } else if ("ri".equals(op)) { checkStringOperands(arguments, 1); } else if ("s".equals(op)) { checkNoOperands(arguments); } else if ("S".equals(op)) { checkNoOperands(arguments); } else if ("sh".equals(op)) { checkStringOperands(arguments, 1); } else if ("'".equals(op)) { checkStringOperands(arguments, 1); } else if ("Tj".equals(op)) { checkStringOperands(arguments, 1); } else if ("TJ".equals(op)) { checkArrayOperands(arguments, 1); } else if ("W".equals(op)) { checkNoOperands(arguments); } else if ("W*".equals(op)) { checkNoOperands(arguments); } else if ("\"".equals(op)) { checkNumberOperands(arguments.subList(0, 2), 2); checkStringOperands(arguments.subList(2, arguments.size()), 1); } // else // ---- Some operators are processed by PDFBox Objects. // ---- Other operators are authorized but it isn't used. } /** * If the arguments list of Operator isn't empty, this method throws a * ContentStreamException. * * @param arguments * @throws ContentStreamException */ private void checkNoOperands(List<COSBase> arguments) throws ContentStreamException { if (arguments != null && !arguments.isEmpty()) { throw createInvalidArgumentsError(); } } /** * If the arguments list of Operator doesn't have String parameter, this * method throws a ContentStreamException. * * @param arguments * @param length * @throws ContentStreamException */ private void checkStringOperands(List<COSBase> arguments, int length) throws ContentStreamException { if (arguments == null || arguments.isEmpty() || arguments.size() != length) { throw createInvalidArgumentsError(); } for (int i = 0; i < length; ++i) { COSBase arg = arguments.get(i); if (!(arg instanceof COSName) && !(arg instanceof COSString)) { throw createInvalidArgumentsError(); } if (arg instanceof COSName && ((COSName) arg).getName().length() > MAX_NAME_SIZE) { throw createLimitError(ERROR_SYNTAX_NAME_TOO_LONG, "A Name operand is too long"); } if (arg instanceof COSString && ((COSString) arg).getString().getBytes().length > MAX_STRING_LENGTH) { throw createLimitError(ERROR_SYNTAX_LITERAL_TOO_LONG, "A String operand is too long"); } } } /** * If the arguments list of Operator doesn't have Array parameter, this method * throws a ContentStreamException. * * @param arguments * @param length * @throws ContentStreamException */ private void checkArrayOperands(List<COSBase> arguments, int length) throws ContentStreamException { if (arguments == null || arguments.isEmpty() || arguments.size() != length) { throw createInvalidArgumentsError(); } for (int i = 0; i < length; ++i) { COSBase arg = arguments.get(i); if (!(arg instanceof COSArray)) { throw createInvalidArgumentsError(); } if (((COSArray) arg).size() > MAX_ARRAY_ELEMENTS) { throw createLimitError(ERROR_SYNTAX_ARRAY_TOO_LONG, "Array has " + ((COSArray) arg).size() + " elements"); } } } /** * If the arguments list of Operator doesn't have Number parameters (Int, * float...), this method throws a ContentStreamException. * * @param arguments * the arguments list to check * @param length * the expected size of the list * @throws ContentStreamException */ private void checkNumberOperands(List<COSBase> arguments, int length) throws ContentStreamException { if (arguments == null || arguments.isEmpty() || arguments.size() != length) { throw createInvalidArgumentsError(); } for (int i = 0; i < length; ++i) { COSBase arg = arguments.get(i); if (!(arg instanceof COSFloat) && !(arg instanceof COSInteger)) { throw createInvalidArgumentsError(); } if (arg instanceof COSInteger && (((COSInteger) arg).longValue() > Integer.MAX_VALUE || ((COSInteger) arg) .longValue() < Integer.MIN_VALUE)) { throw createLimitError(ERROR_SYNTAX_NUMERIC_RANGE, "Invalid integer range in a Number operands"); } if (arg instanceof COSFloat && (((COSFloat) arg).doubleValue() > MAX_POSITIVE_FLOAT || ((COSFloat) arg) .doubleValue() < MAX_NEGATIVE_FLOAT)) { throw createLimitError(ERROR_SYNTAX_NUMERIC_RANGE, "Invalid float range in a Number operands"); } } } /** * The given arguments list is valid only if the first argument is a Tag (A * String) and if the second argument is a String or a Dictionary * * @param arguments * @throws ContentStreamException */ private void checkTagAndPropertyOperands(List<COSBase> arguments) throws ContentStreamException { if (arguments == null || arguments.isEmpty() || arguments.size() != 2) { throw createInvalidArgumentsError(); } COSBase arg = arguments.get(0); if (!(arg instanceof COSName) && !(arg instanceof COSString)) { throw createInvalidArgumentsError(); } if (arg instanceof COSName && ((COSName) arg).getName().length() > MAX_NAME_SIZE) { throw createLimitError(ERROR_SYNTAX_NAME_TOO_LONG,"A Name operand is too long"); } if (arg instanceof COSString && ((COSString) arg).getString().getBytes().length > MAX_STRING_LENGTH) { throw createLimitError(ERROR_SYNTAX_LITERAL_TOO_LONG,"A String operand is too long"); } COSBase arg2 = arguments.get(1); if (!(arg2 instanceof COSName) && !(arg2 instanceof COSString) && !(arg2 instanceof COSDictionary)) { throw createInvalidArgumentsError(); } if (arg2 instanceof COSName && ((COSName) arg2).getName().length() > MAX_NAME_SIZE) { throw createLimitError(ERROR_SYNTAX_NAME_TOO_LONG,"A Name operand is too long"); } if (arg2 instanceof COSString && ((COSString) arg2).getString().getBytes().length > MAX_STRING_LENGTH) { throw createLimitError(ERROR_SYNTAX_LITERAL_TOO_LONG,"A String operand is too long"); } if (arg2 instanceof COSDictionary && ((COSDictionary) arg2).size() > MAX_DICT_ENTRIES) { throw createLimitError(ERROR_SYNTAX_TOO_MANY_ENTRIES, "Dictionary has " + ((COSDictionary) arg2).size() + " entries"); } } /** * Create a ContentStreamException with * ERROR_SYNTAX_CONTENT_STREAM_INVALID_ARGUMENT. * * @return */ private ContentStreamException createInvalidArgumentsError() { ContentStreamException ex = new ContentStreamException(); ex.setValidationError(ERROR_SYNTAX_CONTENT_STREAM_INVALID_ARGUMENT); return ex; } /** * Create a ContentStreamException with * ERROR_SYNTAX_CONTENT_STREAM_INVALID_ARGUMENT. * * @return */ private ContentStreamException createLimitError(String errorCode, String details) { ContentStreamException ex = new ContentStreamException(details); ex.setValidationError(errorCode); return ex; } }