/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2006 - 2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.modules.output.table.html.helper; import java.awt.Image; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.HashMap; import java.util.HashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.ImageContainer; import org.pentaho.reporting.engine.classic.core.LocalImageContainer; import org.pentaho.reporting.engine.classic.core.URLImageContainer; import org.pentaho.reporting.engine.classic.core.layout.output.RenderUtility; import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlContentGenerator; import org.pentaho.reporting.engine.classic.core.modules.output.table.html.URLRewriteException; import org.pentaho.reporting.libraries.base.encoder.UnsupportedEncoderException; import org.pentaho.reporting.libraries.base.util.IOUtils; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.repository.ContentCreationException; import org.pentaho.reporting.libraries.repository.ContentIOException; import org.pentaho.reporting.libraries.repository.ContentItem; import org.pentaho.reporting.libraries.repository.ContentLocation; import org.pentaho.reporting.libraries.repository.LibRepositoryBoot; import org.pentaho.reporting.libraries.repository.NameGenerator; import org.pentaho.reporting.libraries.resourceloader.Resource; import org.pentaho.reporting.libraries.resourceloader.ResourceData; import org.pentaho.reporting.libraries.resourceloader.ResourceException; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceLoadingException; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; public class DefaultHtmlContentGenerator implements HtmlContentGenerator { private static class ImageData { private byte[] imageData; private String mimeType; private String originalFileName; private ImageData( final byte[] imageData, final String mimeType, final String originalFileName ) { if ( imageData == null ) { throw new NullPointerException(); } if ( mimeType == null ) { throw new NullPointerException(); } if ( originalFileName == null ) { throw new NullPointerException(); } this.imageData = imageData; this.mimeType = mimeType; this.originalFileName = originalFileName; } public byte[] getImageData() { return imageData; } public String getMimeType() { return mimeType; } public String getOriginalFileName() { return originalFileName; } } private static final Log logger = LogFactory.getLog( DefaultHtmlContentGenerator.class ); private final HashSet<String> validRawTypes; private ResourceManager resourceManager; private HashMap<ResourceKey, String> knownResources; private HashMap<String, String> knownImages; private boolean copyExternalImages; private ContentLocation dataLocation; private NameGenerator dataNameGenerator; private ContentUrlReWriteService rewriterService; public DefaultHtmlContentGenerator( final ResourceManager resourceManager ) { this.knownImages = new HashMap<String, String>(); this.resourceManager = resourceManager; this.knownResources = new HashMap<ResourceKey, String>(); this.validRawTypes = new HashSet<String>(); this.validRawTypes.add( "image/gif" ); this.validRawTypes.add( "image/x-xbitmap" ); this.validRawTypes.add( "image/gi_" ); this.validRawTypes.add( "image/jpeg" ); this.validRawTypes.add( "image/jpg" ); this.validRawTypes.add( "image/jp_" ); this.validRawTypes.add( "application/jpg" ); this.validRawTypes.add( "application/x-jpg" ); this.validRawTypes.add( "image/pjpeg" ); this.validRawTypes.add( "image/pipeg" ); this.validRawTypes.add( "image/vnd.swiftview-jpeg" ); this.validRawTypes.add( "image/x-xbitmap" ); this.validRawTypes.add( "image/png" ); this.validRawTypes.add( "application/png" ); this.validRawTypes.add( "application/x-png" ); } public void setDataWriter( final ContentLocation dataLocation, final NameGenerator dataNameGenerator, final ContentUrlReWriteService rewriterService ) { this.dataNameGenerator = dataNameGenerator; this.dataLocation = dataLocation; this.rewriterService = rewriterService; } public void setCopyExternalImages( final boolean copyExternalImages ) { this.copyExternalImages = copyExternalImages; } public boolean isCopyExternalImages() { return copyExternalImages; } public ResourceManager getResourceManager() { return resourceManager; } public void registerFailure( final ResourceKey source ) { knownResources.put( source, null ); } public void registerContent( final ResourceKey source, final String name ) { knownResources.put( source, name ); } public boolean isRegistered( final ResourceKey source ) { return knownResources.containsKey( source ); } public String getRegisteredName( final ResourceKey source ) { final String o = knownResources.get( source ); if ( o != null ) { return o; } return null; } public String writeRaw( final ResourceKey source ) throws IOException { if ( source == null ) { throw new NullPointerException(); } if ( copyExternalImages == false ) { final Object identifier = source.getIdentifier(); if ( identifier instanceof URL ) { final URL url = (URL) identifier; final String protocol = url.getProtocol(); if ( "http".equalsIgnoreCase( protocol ) || "https".equalsIgnoreCase( protocol ) || "ftp".equalsIgnoreCase( protocol ) ) { return url.toExternalForm(); } } } if ( dataLocation == null ) { return null; } try { final ResourceData resourceData = resourceManager.load( source ); final String mimeType = queryMimeType( resourceData ); if ( isValidImage( mimeType ) ) { // lets do some voodo .. final ContentItem item = dataLocation.createItem( dataNameGenerator.generateName( extractFilename( resourceData ), mimeType ) ); if ( item.isWriteable() ) { item.setAttribute( LibRepositoryBoot.REPOSITORY_DOMAIN, LibRepositoryBoot.CONTENT_TYPE, mimeType ); // write it out .. final InputStream stream = new BufferedInputStream( resourceData.getResourceAsStream( resourceManager ) ); try { final OutputStream outputStream = new BufferedOutputStream( item.getOutputStream() ); try { IOUtils.getInstance().copyStreams( stream, outputStream ); } finally { outputStream.close(); } } finally { stream.close(); } return rewriterService.rewriteContentDataItem( item ); } } } catch ( ResourceLoadingException e ) { // Ok, loading the resource failed. Not a problem, so we will // recode the raw-object instead .. } catch ( ContentIOException e ) { // ignore it .. } catch ( URLRewriteException e ) { logger.warn( "Rewriting the URL failed.", e ); throw new RuntimeException( "Failed", e ); } return null; } public String writeImage( final ImageContainer image, final String encoderType, final float quality, final boolean alpha ) throws ContentIOException, IOException { if ( image == null ) { throw new NullPointerException(); } if ( dataLocation == null ) { return null; } final String cacheKey; if ( image instanceof URLImageContainer ) { final URLImageContainer uic = (URLImageContainer) image; cacheKey = uic.getSourceURLString(); final String retval = knownImages.get( cacheKey ); if ( retval != null ) { return retval; } final String sourceURLString = uic.getSourceURLString(); if ( uic.isLoadable() == false && sourceURLString != null ) { knownImages.put( cacheKey, sourceURLString ); return sourceURLString; } } else { cacheKey = null; } try { final ImageData data = getImageData( image, encoderType, quality, alpha ); if ( data == null ) { return null; } // write the encoded picture ... final String filename = IOUtils.getInstance().stripFileExtension( data.getOriginalFileName() ); final ContentItem dataFile = dataLocation.createItem( dataNameGenerator.generateName( filename, data.getMimeType() ) ); final String contentURL = rewriterService.rewriteContentDataItem( dataFile ); // a png encoder is included in JCommon ... final OutputStream out = new BufferedOutputStream( dataFile.getOutputStream() ); try { out.write( data.getImageData() ); out.flush(); } finally { out.close(); } if ( cacheKey != null ) { knownImages.put( cacheKey, contentURL ); } return contentURL; } catch ( ContentCreationException cce ) { // Can't create the content logger.warn( "Failed to create the content image: Reason given was: " + cce.getMessage() ); return null; } catch ( URLRewriteException re ) { // cannot handle this .. logger.warn( "Failed to write the URL: Reason given was: " + re.getMessage() ); return null; } catch ( UnsupportedEncoderException e ) { logger.warn( "Failed to write the URL: Reason given was: " + e.getMessage() ); return null; } } private String queryMimeType( final ResourceData resourceData ) throws ResourceLoadingException, IOException { final Object contentType = resourceData.getAttribute( ResourceData.CONTENT_TYPE ); if ( contentType instanceof String ) { return (String) contentType; } // now we are getting very primitive .. (Kids, dont do this at home) final byte[] data = new byte[12]; resourceData.getResource( resourceManager, data, 0, data.length ); return queryMimeType( data ); } private String queryMimeType( final byte[] data ) throws IOException { final ByteArrayInputStream stream = new ByteArrayInputStream( data ); if ( isGIF( stream ) ) { return "image/gif"; } stream.reset(); if ( isJPEG( stream ) ) { return "image/jpeg"; } stream.reset(); if ( isPNG( stream ) ) { return "image/png"; } return null; } private boolean isPNG( final ByteArrayInputStream data ) { final int[] PNF_FINGERPRINT = { 137, 80, 78, 71, 13, 10, 26, 10 }; for ( int i = 0; i < PNF_FINGERPRINT.length; i++ ) { if ( PNF_FINGERPRINT[i] != data.read() ) { return false; } } return true; } private boolean isJPEG( final InputStream data ) throws IOException { final int[] JPG_FINGERPRINT_1 = { 0xFF, 0xD8, 0xFF, 0xE0 }; for ( int i = 0; i < JPG_FINGERPRINT_1.length; i++ ) { if ( JPG_FINGERPRINT_1[i] != data.read() ) { return false; } } // then skip two bytes .. if ( data.read() == -1 ) { return false; } if ( data.read() == -1 ) { return false; } final int[] JPG_FINGERPRINT_2 = { 0x4A, 0x46, 0x49, 0x46, 0x00 }; for ( int i = 0; i < JPG_FINGERPRINT_2.length; i++ ) { if ( JPG_FINGERPRINT_2[i] != data.read() ) { return false; } } return true; } private boolean isGIF( final InputStream data ) throws IOException { final int[] GIF_FINGERPRINT = { 'G', 'I', 'F', '8' }; for ( int i = 0; i < GIF_FINGERPRINT.length; i++ ) { if ( GIF_FINGERPRINT[i] != data.read() ) { return false; } } return true; } private String extractFilename( final ResourceData resourceData ) { final String filename = (String) resourceData.getAttribute( ResourceData.FILENAME ); if ( filename == null ) { return "image"; } final String pureFileName = IOUtils.getInstance().getFileName( filename ); return IOUtils.getInstance().stripFileExtension( pureFileName ); } private boolean isValidImage( final String mimeType ) { return validRawTypes.contains( mimeType ); } private ImageData getImageData( final ImageContainer image, final String encoderType, final float quality, final boolean alpha ) throws IOException, UnsupportedEncoderException { ResourceManager resourceManager = getResourceManager(); ResourceKey url = null; // The image has an assigned URL ... if ( image instanceof URLImageContainer ) { final URLImageContainer urlImage = (URLImageContainer) image; url = urlImage.getResourceKey(); // if we have an source to load the image data from .. if ( url != null ) { if ( urlImage.isLoadable() && isSupportedImageFormat( url ) ) { try { final ResourceData data = resourceManager.load( url ); final byte[] imageData = data.getResource( resourceManager ); final String mimeType = queryMimeType( imageData ); final URL maybeRealURL = resourceManager.toURL( url ); if ( maybeRealURL != null ) { final String originalFileName = IOUtils.getInstance().getFileName( maybeRealURL ); return new ImageData( imageData, mimeType, originalFileName ); } else { return new ImageData( imageData, mimeType, "picture" ); } } catch ( ResourceException re ) { // ok, try as local ... logger.debug( "Failed to process image as raw-data, trying as processed data next", re ); } } } } if ( image instanceof LocalImageContainer ) { // Check, whether the imagereference contains an AWT image. // if so, then we can use that image instance for the recoding final LocalImageContainer li = (LocalImageContainer) image; Image awtImage = li.getImage(); if ( awtImage == null ) { if ( url != null ) { try { final Resource resource = resourceManager.createDirectly( url, Image.class ); awtImage = (Image) resource.getResource(); } catch ( ResourceException e ) { // ignore. } } } if ( awtImage != null ) { // now encode the image. We don't need to create digest data // for the image contents, as the image is perfectly identifyable // by its URL final byte[] imageData = RenderUtility.encodeImage( awtImage, encoderType, quality, alpha ); final String originalFileName; if ( url != null ) { final URL maybeRealURL = resourceManager.toURL( url ); if ( maybeRealURL != null ) { originalFileName = IOUtils.getInstance().getFileName( maybeRealURL ); } else { // we just need the picture part, the file-extension will be replaced by one that matches // the mime-type. originalFileName = "picture"; } } else { // we just need the picture part, the file-extension will be replaced by one that matches // the mime-type. originalFileName = "picture"; } return new ImageData( imageData, encoderType, originalFileName ); } } return null; } /** * Tests, whether the given URL points to a supported file format for common browsers. Returns true if the URL * references a JPEG, PNG or GIF image, false otherwise. * <p/> * The checked filetypes are the ones recommended by the W3C. * * @param key * the url that should be tested. * @return true, if the content type is supported by the browsers, false otherwise. */ protected boolean isSupportedImageFormat( final ResourceKey key ) { ResourceManager resourceManager = getResourceManager(); final URL url = resourceManager.toURL( key ); if ( url == null ) { return false; } final String file = url.getFile(); if ( StringUtils.endsWithIgnoreCase( file, ".jpg" ) ) { return true; } if ( StringUtils.endsWithIgnoreCase( file, ".jpeg" ) ) { return true; } if ( StringUtils.endsWithIgnoreCase( file, ".png" ) ) { return true; } if ( StringUtils.endsWithIgnoreCase( file, ".gif" ) ) { return true; } return false; } public ContentItem createItem( final String name, final String mimeType ) throws ContentIOException { return dataLocation.createItem( dataNameGenerator.generateName( name, mimeType ) ); } public boolean isExternalContentAvailable() { return dataLocation != null; } }