diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/extend/SVGDrawer.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/extend/SVGDrawer.java
index 187f7e2b8..db4da5f6d 100644
--- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/extend/SVGDrawer.java
+++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/extend/SVGDrawer.java
@@ -12,18 +12,20 @@
import com.openhtmltopdf.render.RenderingContext;
public interface SVGDrawer extends Closeable {
- public void importFontFaceRules(List fontFaces,
+ void importFontFaceRules(List fontFaces,
SharedContext shared);
- public SVGImage buildSVGImage(Element svgElement, Box box, CssContext cssContext, double cssWidth,
+ SVGImage buildSVGImage(Element svgElement, Box box, CssContext cssContext, double cssWidth,
double cssHeight, double dotsPerPixel);
- public static interface SVGImage {
- public int getIntrinsicWidth();
+ default void withUserAgent(UserAgentCallback userAgentCallback) {}
- public int getIntrinsicHeight();
+ interface SVGImage {
+ int getIntrinsicWidth();
- public void drawSVG(OutputDevice outputDevice, RenderingContext ctx,
+ int getIntrinsicHeight();
+
+ void drawSVG(OutputDevice outputDevice, RenderingContext ctx,
double x, double y);
}
}
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-custom-protocol.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-custom-protocol.pdf
new file mode 100644
index 000000000..142ad2798
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-custom-protocol.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-load-blocked.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-load-blocked.pdf
new file mode 100644
index 000000000..0b3d6edd9
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-load-blocked.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-whitelist-file-protocol.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-whitelist-file-protocol.pdf
new file mode 100644
index 000000000..364f59930
Binary files /dev/null and b/openhtmltopdf-examples/src/main/resources/visualtest/expected/svg-external-file-whitelist-file-protocol.pdf differ
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/solid.svg b/openhtmltopdf-examples/src/main/resources/visualtest/html/solid.svg
new file mode 100644
index 000000000..4749541bb
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/solid.svg
@@ -0,0 +1,3 @@
+
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-custom-protocol.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-custom-protocol.html
new file mode 100644
index 000000000..15a0df61b
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-custom-protocol.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-load-blocked.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-load-blocked.html
new file mode 100644
index 000000000..4800bf78e
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-load-blocked.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-whitelist-file-protocol.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-whitelist-file-protocol.html
new file mode 100644
index 000000000..4800bf78e
--- /dev/null
+++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/svg-external-file-whitelist-file-protocol.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
index 65c04c110..1bbc7745e 100644
--- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
+++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/VisualRegressionTest.java
@@ -1,8 +1,13 @@
package com.openhtmltopdf.visualregressiontests;
-import java.io.File;
+import java.io.*;
+
import static org.junit.Assert.assertTrue;
-import java.io.IOException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+
+import com.openhtmltopdf.extend.FSStream;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -1118,6 +1123,37 @@ public void testIssue484ImgSrcDataImageSvgBase64() throws IOException {
assertTrue(vt.runTest("issue-484-data-image-svg-xml-base64", TestSupport.WITH_SVG));
}
+
+ @Test
+ public void testSVGLoadBlocked() throws IOException {
+ assertTrue(vt.runTest("svg-external-file-load-blocked", TestSupport.WITH_SVG));
+ }
+
+ @Test
+ public void testSVGLoadWhiteListFileProtocol() throws IOException {
+ assertTrue(vt.runTest("svg-external-file-whitelist-file-protocol",
+ (builder) -> builder.useSVGDrawer(new BatikSVGDrawer(SvgScriptMode.SECURE, Collections.singleton("file")))));
+ }
+
+ @Test
+ public void testSVGLoadCustomProtocol() throws IOException {
+ assertTrue(vt.runTest("svg-custom-protocol", (builder -> {
+ builder.useProtocolsStreamImplementation(url -> new FSStream() {
+
+ @Override
+ public InputStream getStream() {
+ return new ByteArrayInputStream("".getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public Reader getReader() {
+ return new InputStreamReader(getStream(), StandardCharsets.UTF_8);
+ }
+ }, "custom");
+ builder.useSVGDrawer(new BatikSVGDrawer(SvgScriptMode.SECURE, Collections.singleton("custom")));
+ })));
+ }
+
// TODO:
// + Elements that appear just on generated overflow pages.
// + content property (page counters, etc)
diff --git a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
index 57c798811..631d51a49 100644
--- a/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
+++ b/openhtmltopdf-pdfbox/src/main/java/com/openhtmltopdf/pdfboxout/PdfBoxRenderer.java
@@ -163,6 +163,10 @@ public class PdfBoxRenderer implements Closeable, PageSupplier {
PdfBoxUserAgent userAgent = new PdfBoxUserAgent(_outputDevice);
+ if (_svgImpl != null) {
+ _svgImpl.withUserAgent(userAgent);
+ }
+
userAgent.setProtocolsStreamFactory(state._streamFactoryMap);
if (state._resolver != null) {
diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java
index 14e67c0bc..9fe79f3f8 100644
--- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java
+++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGDrawer.java
@@ -1,8 +1,11 @@
package com.openhtmltopdf.svgsupport;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import com.openhtmltopdf.extend.UserAgentCallback;
import org.w3c.dom.Element;
import com.openhtmltopdf.css.sheet.FontFaceRule;
@@ -14,16 +17,18 @@
import com.openhtmltopdf.svgsupport.PDFTranscoder.OpenHtmlFontResolver;
public class BatikSVGDrawer implements SVGDrawer {
+ private final Set allowedProtocols;
public OpenHtmlFontResolver fontResolver;
private final boolean allowScripts;
private final boolean allowExternalResources;
+ private UserAgentCallback userAgentCallback;
- public static enum SvgScriptMode {
+ public enum SvgScriptMode {
SECURE,
INSECURE_ALLOW_SCRIPTS;
}
- public static enum SvgExternalResourceMode {
+ public enum SvgExternalResourceMode {
SECURE,
INSECURE_ALLOW_EXTERNAL_RESOURCE_REQUESTS;
}
@@ -40,6 +45,20 @@ public static enum SvgExternalResourceMode {
public BatikSVGDrawer(SvgScriptMode scriptMode, SvgExternalResourceMode externalResourceMode) {
this.allowScripts = scriptMode == SvgScriptMode.INSECURE_ALLOW_SCRIPTS;
this.allowExternalResources = externalResourceMode == SvgExternalResourceMode.INSECURE_ALLOW_EXTERNAL_RESOURCE_REQUESTS;
+ this.allowedProtocols = null;
+ }
+
+ /**
+ * Creates a SVGDrawer
that can allow arbitary scripts to run and allow the loading of
+ * external resources with the specified protocols.
+ *
+ * @param scriptMode
+ * @param allowedProtocols
+ */
+ public BatikSVGDrawer(SvgScriptMode scriptMode, Set allowedProtocols) {
+ this.allowScripts = scriptMode == SvgScriptMode.INSECURE_ALLOW_SCRIPTS;
+ this.allowExternalResources = false;
+ this.allowedProtocols = Collections.unmodifiableSet(allowedProtocols);
}
/**
@@ -59,6 +78,11 @@ public void importFontFaceRules(List fontFaces,
this.fontResolver.importFontFaces(fontFaces, shared);
}
+ @Override
+ public void withUserAgent(UserAgentCallback userAgentCallback) {
+ this.userAgentCallback = userAgentCallback;
+ }
+
@Override
public SVGImage buildSVGImage(Element svgElement, Box box, CssContext c,
double cssWidth, double cssHeight, double dotsPerPixel) {
@@ -69,7 +93,8 @@ public SVGImage buildSVGImage(Element svgElement, Box box, CssContext c,
BatikSVGImage img = new BatikSVGImage(svgElement, box, cssWidth, cssHeight,
cssMaxWidth, cssMaxHeight, dotsPerPixel);
img.setFontResolver(fontResolver);
- img.setSecurityOptions(allowScripts, allowExternalResources);
+ img.setUserAgentCallback(userAgentCallback);
+ img.setSecurityOptions(allowScripts, allowExternalResources, allowedProtocols);
return img;
}
diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java
index cab3cf8cb..e2b7a215f 100644
--- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java
+++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/BatikSVGImage.java
@@ -1,8 +1,10 @@
package com.openhtmltopdf.svgsupport;
import java.awt.Point;
+import java.util.Set;
import java.util.logging.Level;
+import com.openhtmltopdf.extend.UserAgentCallback;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.SVGAbstractTranscoder;
import org.apache.batik.transcoder.TranscoderException;
@@ -28,6 +30,7 @@ public class BatikSVGImage implements SVGImage {
private final double dotsPerPixel;
private OpenHtmlFontResolver fontResolver;
private final PDFTranscoder pdfTranscoder;
+ private UserAgentCallback userAgentCallback;
public BatikSVGImage(Element svgElement, Box box, double cssWidth, double cssHeight,
double cssMaxWidth, double cssMaxHeight, double dotsPerPixel) {
@@ -98,10 +101,14 @@ public void setFontResolver(OpenHtmlFontResolver fontResolver) {
this.fontResolver = fontResolver;
}
- public void setSecurityOptions(boolean allowScripts, boolean allowExternalResources) {
- this.pdfTranscoder.setSecurityOptions(allowScripts, allowExternalResources);
+ public void setSecurityOptions(boolean allowScripts, boolean allowExternalResources, Set allowedProtocols) {
+ this.pdfTranscoder.setSecurityOptions(allowScripts, allowExternalResources, allowedProtocols);
this.pdfTranscoder.addTranscodingHint(SVGAbstractTranscoder.KEY_EXECUTE_ONLOAD, allowScripts);
}
+
+ public void setUserAgentCallback(UserAgentCallback userAgentCallback) {
+ this.userAgentCallback = userAgentCallback;
+ }
public Integer parseLength(String attrValue) {
// TODO read length with units and convert to dots.
@@ -159,7 +166,7 @@ public void drawSVG(OutputDevice outputDevice, RenderingContext ctx,
}
pdfTranscoder.setRenderingParameters(outputDevice, ctx, x, y,
- fontResolver);
+ fontResolver, userAgentCallback);
try {
DOMImplementation impl = SVGDOMImplementation
diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlDocumentLoader.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlDocumentLoader.java
new file mode 100644
index 000000000..2381582f5
--- /dev/null
+++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlDocumentLoader.java
@@ -0,0 +1,40 @@
+package com.openhtmltopdf.svgsupport;
+
+import com.openhtmltopdf.extend.UserAgentCallback;
+import com.openhtmltopdf.util.XRLog;
+import org.apache.batik.bridge.DocumentLoader;
+import org.apache.batik.bridge.UserAgent;
+import org.w3c.dom.Document;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class OpenHtmlDocumentLoader extends DocumentLoader {
+
+ private final UserAgentCallback userAgentCallback;
+
+ public OpenHtmlDocumentLoader(UserAgent userAgent, UserAgentCallback userAgentCallback) {
+ super(userAgent);
+ this.userAgentCallback = userAgentCallback;
+ }
+
+
+ @Override
+ public Document loadDocument(String uri) throws IOException {
+ try {
+ // special handling of relative uri in case of file protocol, we receive something like "file:file.svg"
+ // The path will be null, but the scheme specific part will be not null
+ URI parsedURI = new URI(uri);
+ if ("file".equals(parsedURI.getScheme()) && parsedURI.getPath() == null && parsedURI.getSchemeSpecificPart() != null) {
+ uri = userAgentCallback.resolveURI(parsedURI.getSchemeSpecificPart());
+ } else if (!parsedURI.isAbsolute()) {
+ uri = userAgentCallback.resolveURI(uri);
+ }
+ } catch (URISyntaxException uriSyntaxException) {
+ XRLog.exception("URI syntax exception while loading external svg resource: " + uri, uriSyntaxException);
+ }
+ return super.loadDocument(uri, new ByteArrayInputStream(userAgentCallback.getBinaryResource(uri)));
+ }
+}
diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlUserAgent.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlUserAgent.java
index ee619c6a5..fe78470a3 100644
--- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlUserAgent.java
+++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/OpenHtmlUserAgent.java
@@ -7,16 +7,20 @@
import com.openhtmltopdf.svgsupport.PDFTranscoder.OpenHtmlFontResolver;
import com.openhtmltopdf.util.XRLog;
+import java.util.Set;
+
public class OpenHtmlUserAgent extends UserAgentAdapter {
private final OpenHtmlFontResolver resolver;
private final boolean allowScripts;
private final boolean allowExternalResources;
+ private final Set allowedProtocols;
- public OpenHtmlUserAgent(OpenHtmlFontResolver resolver, boolean allowScripts, boolean allowExternalResources) {
+ public OpenHtmlUserAgent(OpenHtmlFontResolver resolver, boolean allowScripts, boolean allowExternalResources, Set allowedProtocols) {
this.resolver = resolver;
this.allowScripts = allowScripts;
this.allowExternalResources = allowExternalResources;
+ this.allowedProtocols = allowedProtocols;
}
@Override
@@ -34,7 +38,7 @@ public void checkLoadScript(String scriptType, ParsedURL scriptURL, ParsedURL do
@Override
public void checkLoadExternalResource(ParsedURL resourceURL, ParsedURL docURL) throws SecurityException {
- if (!this.allowExternalResources) {
+ if (!this.allowExternalResources && (allowedProtocols == null || !allowedProtocols.contains(resourceURL.getProtocol()))) {
XRLog.exception("Tried to fetch external resource from SVG. Refusing. Details: " + resourceURL + ", " + docURL);
throw new SecurityException("Tried to fetch external resource (" + resourceURL + ") from SVG. Refused!");
}
diff --git a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java
index 3a8ef5939..c83d54c50 100644
--- a/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java
+++ b/openhtmltopdf-svg-support/src/main/java/com/openhtmltopdf/svgsupport/PDFTranscoder.java
@@ -7,13 +7,16 @@
import com.openhtmltopdf.css.style.FSDerivedValue;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer;
+import com.openhtmltopdf.extend.UserAgentCallback;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.render.Box;
import com.openhtmltopdf.render.RenderingContext;
import com.openhtmltopdf.simple.extend.ReplacedElementScaleHelper;
import com.openhtmltopdf.util.XRLog;
+import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.FontFace;
import org.apache.batik.bridge.FontFamilyResolver;
+import org.apache.batik.bridge.svg12.SVG12BridgeContext;
import org.apache.batik.gvt.font.GVTFontFamily;
import org.apache.batik.transcoder.ErrorHandler;
import org.apache.batik.transcoder.SVGAbstractTranscoder;
@@ -28,6 +31,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class PDFTranscoder extends SVGAbstractTranscoder {
private OpenHtmlFontResolver fontResolver;
@@ -39,6 +43,8 @@ public class PDFTranscoder extends SVGAbstractTranscoder {
private final double dotsPerPixel;
private boolean allowScripts = false;
private boolean allowExternalResources = false;
+ private UserAgentCallback userAgentCallback;
+ private Set allowedProtocols;
public PDFTranscoder(Box box, double dotsPerPixel, double width, double height) {
this.box = box;
@@ -47,12 +53,13 @@ public PDFTranscoder(Box box, double dotsPerPixel, double width, double height)
this.dotsPerPixel = dotsPerPixel;
}
- public void setRenderingParameters(OutputDevice od, RenderingContext ctx, double x, double y, OpenHtmlFontResolver fontResolver) {
+ public void setRenderingParameters(OutputDevice od, RenderingContext ctx, double x, double y, OpenHtmlFontResolver fontResolver, UserAgentCallback userAgentCallback) {
this.x = x;
this.y = y;
this.outputDevice = od;
this.ctx = ctx;
this.fontResolver = fontResolver;
+ this.userAgentCallback = userAgentCallback;
}
@Override
@@ -201,18 +208,28 @@ public void importFontFaces(List fontFaces, SharedContext ctx) {
}
}
}
-
- public void setSecurityOptions(boolean allowScripts, boolean allowExternalResources) {
+
+ public void setSecurityOptions(boolean allowScripts, boolean allowExternalResources, Set allowedProtocols) {
this.allowScripts = allowScripts;
this.allowExternalResources = allowExternalResources;
+ this.allowedProtocols = allowedProtocols;
}
-
+
+ @Override
+ protected BridgeContext createBridgeContext(String svgVersion) {
+ if ("1.2".equals(svgVersion)) {
+ return new SVG12BridgeContext(userAgent, new OpenHtmlDocumentLoader(userAgent, userAgentCallback));
+ } else {
+ return new BridgeContext(userAgent, new OpenHtmlDocumentLoader(userAgent, userAgentCallback));
+ }
+ }
+
@Override
protected void transcode(Document svg, String uri, TranscoderOutput out) throws TranscoderException {
// Note: We have to initialize user agent here and not in ::createUserAgent() as method
// is called before our constructor is called in the super constructor.
- this.userAgent = new OpenHtmlUserAgent(this.fontResolver, this.allowScripts, this.allowExternalResources);
+ this.userAgent = new OpenHtmlUserAgent(this.fontResolver, this.allowScripts, this.allowExternalResources, this.allowedProtocols);
super.transcode(svg, uri, out);
Rectangle contentBounds = box.getContentAreaEdge(box.getAbsX(), box.getAbsY(), ctx);