/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.platform.svg.internal; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringReader; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import javax.servlet.http.HttpServletResponse; import org.apache.batik.transcoder.SVGAbstractTranscoder; import org.apache.batik.transcoder.TranscoderException; import org.apache.batik.transcoder.TranscoderInput; import org.apache.batik.transcoder.TranscoderOutput; import org.apache.batik.transcoder.image.PNGTranscoder; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.container.Container; import org.xwiki.container.servlet.ServletResponse; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.platform.svg.SVGRasterizer; import org.xwiki.resource.temporary.TemporaryResourceReference; import org.xwiki.resource.temporary.TemporaryResourceStore; /** * The straight-forward implementation of the {@link SVGRasterizer} role using Batik. * * @version $Id: a9c633386274128f20755bc440b321cbe70092ad $ * @since 8.0M1 */ @Component @Singleton public class BatikSVGRasterizer implements SVGRasterizer { private static final String TEMP_DIR_NAME = "svg"; private static final String RASTER_FILE_EXTENSION = ".png"; @Inject private Logger logger; @Inject private TemporaryResourceStore temporaryResourceStore; @Inject @Named("current") private DocumentReferenceResolver<String> currentDocumentResolver; @Inject private Container container; @Override public File rasterizeToTemporaryFile(String content, int width, int height) throws IOException { String fileName = getTemporaryFileName(content, width, height); TemporaryResourceReference reference = new TemporaryResourceReference(TEMP_DIR_NAME, fileName, null); File out = this.temporaryResourceStore.getTemporaryFile(reference); if (rasterizeToFile(content, out, width, height)) { return out; } return null; } @Override public TemporaryResourceReference rasterizeToTemporaryResource(String content, int width, int height) throws IOException { return rasterizeToTemporaryResource(content, width, height, getCurrentDocument()); } @Override public TemporaryResourceReference rasterizeToTemporaryResource(String content, int width, int height, DocumentReference targetContext) throws IOException { String fileName = getTemporaryFileName(content, width, height); TemporaryResourceReference reference = new TemporaryResourceReference(TEMP_DIR_NAME, fileName, targetContext); File out = this.temporaryResourceStore.getTemporaryFile(reference); if (rasterizeToFile(content, out, width, height)) { return reference; } return null; } @Override public void rasterizeToResponse(String content, int width, int height) throws IOException { if (!(this.container.getResponse() instanceof ServletResponse)) { return; } HttpServletResponse response = ((ServletResponse) this.container.getResponse()).getHttpServletResponse(); File result = rasterizeToTemporaryFile(content, width, height); if (result == null) { return; } response.setContentLength((int) result.length()); response.setContentType("image/png"); OutputStream os = response.getOutputStream(); FileUtils.copyFile(result, os); os.flush(); } private boolean rasterizeToFile(String content, File out, int width, int height) throws IOException { if (!out.getParentFile().exists() && !out.getParentFile().mkdirs()) { this.logger.debug("Failed to create temporary folder [{}].", out.getParentFile().getAbsolutePath()); return false; } else if (out.exists() && out.isFile()) { this.logger.debug("Reusing existing temporary raster image: {}", out.getAbsolutePath()); return true; } else { try (OutputStream fout = new FileOutputStream(out)) { this.logger.debug("Rasterizing to temp file: {}", out.getAbsolutePath()); TranscoderInput input = new TranscoderInput(new StringReader(content)); TranscoderOutput output = new TranscoderOutput(fout); boolean success = rasterize(input, output, width, height); if (!success) { out.delete(); } return success; } } } private boolean rasterize(TranscoderInput input, TranscoderOutput output, int width, int height) { PNGTranscoder transcoder = new PNGTranscoder(); if (width > 0) { transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_WIDTH, Float.valueOf(width)); } if (height > 0) { transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_HEIGHT, Float.valueOf(height)); } // Set maximum width and height to 8k to avoid DoS attacks transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_WIDTH, Float.valueOf(8192)); transcoder.addTranscodingHint(SVGAbstractTranscoder.KEY_MAX_HEIGHT, Float.valueOf(8192)); try { transcoder.transcode(input, output); return true; } catch (TranscoderException ex) { this.logger.warn("Failed to rasterize SVG image: {}", ExceptionUtils.getRootCauseMessage(ex)); } return false; } private String getTemporaryFileName(String content, int width, int height) { return Math.abs(content.hashCode()) + RASTER_FILE_EXTENSION; } private DocumentReference getCurrentDocument() { return this.currentDocumentResolver.resolve(""); } }