/* * Copyright 2016 Skynav, Inc. All rights reserved. * * 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.skynav.ttv.verifier.ttml; import java.io.BufferedInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import org.xml.sax.Locator; import com.skynav.ttv.model.Model; import com.skynav.ttv.model.value.Image; import com.skynav.ttv.util.IOUtil; import com.skynav.ttv.util.Location; import com.skynav.ttv.util.Reporter; import com.skynav.ttv.verifier.AbstractVerifier; import com.skynav.ttv.verifier.ImageVerifier; import com.skynav.ttv.verifier.VerifierContext; import com.xfsi.xav.test.Test; import com.xfsi.xav.test.TestInfo; import com.xfsi.xav.test.TestManager; import com.xfsi.xav.util.Error; import com.xfsi.xav.util.MimeType; import com.xfsi.xav.util.Progress; import com.xfsi.xav.util.Result; import com.xfsi.xav.validation.images.jpeg.JpegValidator; import com.xfsi.xav.validation.images.png.PngValidator; import com.xfsi.xav.validation.util.AbstractTestInfo; import com.xfsi.xav.validation.util.AbstractTestManager; public class TTML2ImageVerifier extends AbstractVerifier implements ImageVerifier { public TTML2ImageVerifier(Model model) { super(model); } public boolean verify(Object content, Locator locator, VerifierContext context, ItemType type) { setState(content, context); if (type == ItemType.Other) return verifyOtherItem(content, locator, context); else throw new IllegalArgumentException(); } protected boolean verifyOtherItem(Object content, Locator locator, VerifierContext context) { boolean failed = false; if (content instanceof Image) failed = !verify((Image) content, locator, context); if (failed) { Reporter reporter = context.getReporter(); reporter.logError(reporter.message(locator, "*KEY*", "Invalid image item.")); } return !failed; } protected boolean verify(Image content, Locator locator, VerifierContext context) { boolean failed = false; Reporter reporter = context.getReporter(); Location location = new Location(content, null, null, locator); MimeType[] mimeType = new MimeType[1]; if (!sniffImage(content, mimeType, location, context)) { reporter.logError(reporter.message(locator, "*KEY*", "Unable to determine image type.")); failed = true; } else { MimeType mt = mimeType[0]; assert mt != null; if (!isSupportedMimeType(mt)) { reporter.logError(reporter.message(locator, "*KEY*", "Image type ''{0}'' is not supported.", mt.getType())); failed = true; } if (!failed && !verifyImage(content, mt, location, context)) failed = true; } return !failed; } private static final short[] pngSignature = new short[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; private static final MimeType pngType = new MimeType(MimeType.IMAGE_PNG_TYPE); private static final short[] jpgSignature = new short[] { 0xFF, 0xD8, 0xFF, 0xE0, -1, -1, 0x4A, 0x46, 0x49, 0x46, 0x00 }; private static final MimeType jpgType = new MimeType(MimeType.IMAGE_JPG_TYPE); private static final MimeType unknownType = new MimeType(); private static final Signature[] signatures = { new Signature(pngSignature, pngType.getType()), new Signature(jpgSignature, jpgType.getType()), }; private static final int signatureLengthMaximum; static { int saLenMax = 0; for (Signature s : signatures) { short[] sa = s.getSignature(); int saLen = sa.length; if (saLen > saLenMax) saLenMax = saLen; } signatureLengthMaximum = saLenMax; } private boolean sniffImage(Image image, MimeType[] outputType, Location location, VerifierContext context) { boolean failed = false; Reporter reporter = context.getReporter(); MimeType mt = unknownType; BufferedInputStream bis = null; try { bis = new BufferedInputStream(image.getURI().toURL().openStream()); byte[] buf = new byte[signatureLengthMaximum]; int nb = IOUtil.readCompletely(bis, buf); for (Signature s : signatures) { MimeType mtSniffed = sniffImage(buf, nb, s.getSignature(), s.getType()); if (mtSniffed != null) { mt = mtSniffed; break; } } } catch (MalformedURLException e) { reporter.logError(e); failed = true; } catch (IOException e) { reporter.logError(e); failed = true; } finally { IOUtil.closeSafely(bis); } if (mt != null) { if ((outputType != null) && (outputType.length > 0)) outputType[0] = mt; } return !failed; } private MimeType sniffImage(byte[] buf, int len, short[] signature, String type) { if (matchAtStart(signature, buf)) return new MimeType(type); else return null; } private boolean matchAtStart(short[] b1, byte[] b2) { if (b1 != null && b2 != null) { // Need to have sniffable array as long as signature if (b2.length >= b1.length) { for (int i = 0; i < b1.length; i++) { // Negative values matches all bytes if (b1[i] < 0) continue; else { short b2s = (short) (0xFF & b2[i]); if (b1[i] != b2s) return false; } } return true; } } return false; } private boolean isSupportedMimeType(MimeType mt) { String[] components = mt.getType().split(";"); String t = (components.length > 0) ? components[0] : null; String p = (components.length > 1) ? components[1] : null; if (t != null) t = t.trim(); if (p != null) p = p.trim(); return getModel().isSupportedResourceType(t, p); } private boolean verifyImage(Image image, MimeType mimeType, Location location, VerifierContext context) { Reporter reporter = context.getReporter(); Locator locator = location.getLocator(); Test t = getImageValidator(mimeType); if (t != null) { BufferedInputStream bis = null; try { TestInfo ti = new TestInfoAdapter(image, mimeType, location); TestManager tm = new TestManagerAdapter(context); URI uri = image.getURI(); bis = new BufferedInputStream(uri.toURL().openStream()); ti.setResourceStream(bis); reporter.logInfo(reporter.message("*KEY*", "Verifying image ''{0}'' as ''{1}''.", getImageName(uri), mimeType.toString())); Result r = t.run(tm, ti); if (r.isFailure()) return false; else { int w = (Integer) r.getState("width"); int h = (Integer) r.getState("height"); image.setExtent(w, h); return true; } } catch (RuntimeException e) { throw e; } catch (Exception e) { reporter.logError(e); return false; } finally { IOUtil.closeSafely(bis); } } else { reporter.logError(reporter.message(locator, "*KEY*", "No image validator for ''{0}''.", mimeType.toString())); return false; } } private String getImageName(URI uri) { String p = uri.getPath(); int i = p.lastIndexOf('/'); if (i >= 0) p = p.substring(i + 1); return p; } private Test getImageValidator(MimeType mimeType) { if (mimeType.equals(pngType)) return new PngValidator(); else if (mimeType.equals(jpgType)) return new JpegValidator(); else return null; } private static class Signature { private short[] signature; private String type; Signature(short[] signature, String type) { this.signature = signature; this.type = type; } short[] getSignature() { return signature; } String getType() { return type; } } private static class TestInfoAdapter extends AbstractTestInfo { private Image image; private MimeType mimeType; private Location location; TestInfoAdapter(Image image, MimeType mimeType, Location location) { this.image = image; this.mimeType = mimeType; this.location = location; } @SuppressWarnings("unused") public Image getImage() { return image; } public MimeType getMimeType() { return mimeType; } public Location getLocation() { return location; } } private static class TestManagerAdapter extends AbstractTestManager { private VerifierContext context; TestManagerAdapter(VerifierContext context) { this.context = context; } public void reportError(TestInfo ti, Error error) { Error.Severity s = error.getSeverity(); if (s.isSevereAs(Error.Severity.ERROR_SEVERITY)) { Reporter reporter = context.getReporter(); reporter.logError(reporter.message(getLocation(ti).getLocator(), "*KEY*", error.getMessage())); } } public void reportProgress(TestInfo ti, Progress progress) { } private Location getLocation(TestInfo ti) { if ((ti != null) && (ti instanceof TestInfoAdapter)) return ((TestInfoAdapter) ti).getLocation(); else return new Location(); } } }