aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/java/org/apache/fop/apps/FOURIResolver.java358
-rw-r--r--src/java/org/apache/fop/apps/FOUserAgent.java15
-rw-r--r--src/java/org/apache/fop/apps/FopFactory.java129
-rw-r--r--src/java/org/apache/fop/util/ColorSpaceCache.java110
-rw-r--r--src/java/org/apache/fop/util/DataURIResolver.java78
-rw-r--r--src/java/org/apache/fop/util/DataURLUtil.java67
-rw-r--r--src/java/org/apache/fop/util/WriterOutputStream.java91
-rw-r--r--test/java/org/apache/fop/UtilityCodeTestSuite.java4
-rw-r--r--test/java/org/apache/fop/util/DataURIResolverTestCase.java116
9 files changed, 715 insertions, 253 deletions
diff --git a/src/java/org/apache/fop/apps/FOURIResolver.java b/src/java/org/apache/fop/apps/FOURIResolver.java
index 13baeaa56..d68905e4c 100644
--- a/src/java/org/apache/fop/apps/FOURIResolver.java
+++ b/src/java/org/apache/fop/apps/FOURIResolver.java
@@ -19,7 +19,6 @@
package org.apache.fop.apps;
-import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
@@ -30,28 +29,34 @@ import java.net.URLConnection;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
// commons logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.fop.util.DataURIResolver;
-// base64 support for "data" urls
-import org.apache.xmlgraphics.util.io.Base64DecodeStream;
import org.apache.xmlgraphics.util.io.Base64EncodeStream;
/**
- * Provides FOP specific URI resolution.
- * This is the default URIResolver {@link FOUserAgent} will use unless overidden.
+ * Provides FOP specific URI resolution. This is the default URIResolver
+ * {@link FOUserAgent} will use unless overidden.
+ *
* @see javax.xml.transform.URIResolver
*/
-public class FOURIResolver
- implements javax.xml.transform.URIResolver {
-
+public class FOURIResolver implements javax.xml.transform.URIResolver {
+
// log
private Log log = LogFactory.getLog("FOP");
- // true if exceptions are to be thrown if the URIs cannot be resolved.
+ /** URIResolver for RFC 2397 data URLs */
+ private URIResolver dataURIResolver = new DataURIResolver();
+
+ /** A user settable URI Resolver */
+ private URIResolver uriResolver = null;
+
+ /** true if exceptions are to be thrown if the URIs cannot be resolved. */
private boolean throwExceptions = false;
/**
@@ -60,23 +65,28 @@ public class FOURIResolver
public FOURIResolver() {
this(false);
}
-
+
/**
* Additional constructor
- * @param throwExceptions true if exceptions are to be thrown if the URIs cannot be
- * resolved.
+ *
+ * @param throwExceptions
+ * true if exceptions are to be thrown if the URIs cannot be
+ * resolved.
*/
public FOURIResolver(boolean throwExceptions) {
this.throwExceptions = throwExceptions;
}
-
+
/**
* Handles resolve exceptions appropriately.
- * @param errorStr error string
- * @param strict strict user config
+ *
+ * @param errorStr
+ * error string
+ * @param strict
+ * strict user config
*/
private void handleException(Exception e, String errorStr, boolean strict)
- throws TransformerException {
+ throws TransformerException {
if (strict) {
throw new TransformerException(errorStr, e);
}
@@ -84,182 +94,220 @@ public class FOURIResolver
}
/**
- * Called by the processor through {@link FOUserAgent} when it encounters an
- * uri in an external-graphic element.
- * (see also {@link javax.xml.transform.URIResolver#resolve(String, String)}
- * This resolver will allow URLs without a scheme, i.e. it assumes 'file:' as
- * the default scheme. It also allows relative URLs with scheme,
- * e.g. file:../../abc.jpg which is not strictly RFC compliant as long as the
- * scheme is the same as the scheme of the base URL. If the base URL is null
+ * Called by the processor through {@link FOUserAgent} when it encounters an
+ * uri in an external-graphic element. (see also
+ * {@link javax.xml.transform.URIResolver#resolve(String, String)} This
+ * resolver will allow URLs without a scheme, i.e. it assumes 'file:' as the
+ * default scheme. It also allows relative URLs with scheme, e.g.
+ * file:../../abc.jpg which is not strictly RFC compliant as long as the
+ * scheme is the same as the scheme of the base URL. If the base URL is null
* a 'file:' URL referencing the current directory is used as the base URL.
- * If the method is successful it will return a Source of type
- * {@link javax.xml.transform.stream.StreamSource} with its SystemID set to
+ * If the method is successful it will return a Source of type
+ * {@link javax.xml.transform.stream.StreamSource} with its SystemID set to
* the resolved URL used to open the underlying InputStream.
*
- * @param href An href attribute, which may be relative or absolute.
- * @param base The base URI against which the first argument will be made
- * absolute if the absolute URI is required.
- * @return A {@link javax.xml.transform.Source} object, or null if the href
- * cannot be resolved.
- * @throws javax.xml.transform.TransformerException Never thrown by this implementation.
+ * @param href
+ * An href attribute, which may be relative or absolute.
+ * @param base
+ * The base URI against which the first argument will be made
+ * absolute if the absolute URI is required.
+ * @return A {@link javax.xml.transform.Source} object, or null if the href
+ * cannot be resolved.
+ * @throws javax.xml.transform.TransformerException
+ * Never thrown by this implementation.
* @see javax.xml.transform.URIResolver#resolve(String, String)
*/
- public Source resolve(String href, String base) throws TransformerException {
- // data URLs can be quite long so don't try to build a File (can lead to problems)
- if (href.startsWith("data:")) {
- return parseDataURI(href);
+ public Source resolve(String href, String base) throws TransformerException {
+ Source source = null;
+
+ // data URLs can be quite long so evaluate early and don't try to build a File
+ // (can lead to problems)
+ source = dataURIResolver.resolve(href, base);
+
+ // Custom uri resolution
+ if (source == null && uriResolver != null) {
+ source = uriResolver.resolve(href, base);
}
- URL absoluteURL = null;
- File file = new File(href);
- if (file.canRead() && file.isFile()) {
- try {
- absoluteURL = file.toURL();
- } catch (MalformedURLException mfue) {
- handleException(mfue,
- "Could not convert filename '" + href + "' to URL", throwExceptions);
- }
- } else {
- // no base provided
- if (base == null) {
- // We don't have a valid file protocol based URL
+ // Fallback to default resolution mechanism
+ if (source == null) {
+ URL absoluteURL = null;
+ File file = new File(href);
+ if (file.canRead() && file.isFile()) {
try {
- absoluteURL = new URL(href);
- } catch (MalformedURLException mue) {
+ absoluteURL = file.toURL();
+ } catch (MalformedURLException mfue) {
+ handleException(mfue, "Could not convert filename '" + href
+ + "' to URL", throwExceptions);
+ }
+ } else {
+ // no base provided
+ if (base == null) {
+ // We don't have a valid file protocol based URL
try {
- // the above failed, we give it another go in case
- // the href contains only a path then file: is assumed
- absoluteURL = new URL("file:" + href);
- } catch (MalformedURLException mfue) {
- handleException(mfue,
- "Error with URL '" + href + "'", throwExceptions);
+ absoluteURL = new URL(href);
+ } catch (MalformedURLException mue) {
+ try {
+ // the above failed, we give it another go in case
+ // the href contains only a path then file: is
+ // assumed
+ absoluteURL = new URL("file:" + href);
+ } catch (MalformedURLException mfue) {
+ handleException(mfue, "Error with URL '" + href
+ + "'", throwExceptions);
+ }
}
- }
- // try and resolve from context of base
- } else {
- URL baseURL = null;
- try {
- baseURL = new URL(base);
- } catch (MalformedURLException mfue) {
- handleException(mfue, "Error with base URL '" + base + "'", throwExceptions);
- }
+ // try and resolve from context of base
+ } else {
+ URL baseURL = null;
+ try {
+ baseURL = new URL(base);
+ } catch (MalformedURLException mfue) {
+ handleException(mfue, "Error with base URL '" + base
+ + "'", throwExceptions);
+ }
- /*
- * This piece of code is based on the following statement in
- * RFC2396 section 5.2:
- *
- * 3) If the scheme component is defined, indicating that the
- * reference starts with a scheme name, then the reference is
- * interpreted as an absolute URI and we are done. Otherwise,
- * the reference URI's scheme is inherited from the base URI's
- * scheme component.
- *
- * Due to a loophole in prior specifications [RFC1630], some
- * parsers allow the scheme name to be present in a relative URI
- * if it is the same as the base URI scheme. Unfortunately, this
- * can conflict with the correct parsing of non-hierarchical
- * URI. For backwards compatibility, an implementation may work
- * around such references by removing the scheme if it matches
- * that of the base URI and the scheme is known to always use
- * the <hier_part> syntax.
- *
- * The URL class does not implement this work around, so we do.
- */
- String scheme = baseURL.getProtocol() + ":";
- if (href.startsWith(scheme)) {
- href = href.substring(scheme.length());
- if ("file:".equals(scheme)) {
- int colonPos = href.indexOf(':');
- int slashPos = href.indexOf('/');
- if (slashPos >= 0 && colonPos >= 0 && colonPos < slashPos) {
- href = "/" + href; // Absolute file URL doesn't
- // have a leading slash
+ /*
+ * This piece of code is based on the following statement in
+ * RFC2396 section 5.2:
+ *
+ * 3) If the scheme component is defined, indicating that
+ * the reference starts with a scheme name, then the
+ * reference is interpreted as an absolute URI and we are
+ * done. Otherwise, the reference URI's scheme is inherited
+ * from the base URI's scheme component.
+ *
+ * Due to a loophole in prior specifications [RFC1630], some
+ * parsers allow the scheme name to be present in a relative
+ * URI if it is the same as the base URI scheme.
+ * Unfortunately, this can conflict with the correct parsing
+ * of non-hierarchical URI. For backwards compatibility, an
+ * implementation may work around such references by
+ * removing the scheme if it matches that of the base URI
+ * and the scheme is known to always use the <hier_part>
+ * syntax.
+ *
+ * The URL class does not implement this work around, so we
+ * do.
+ */
+ String scheme = baseURL.getProtocol() + ":";
+ if (href.startsWith(scheme)) {
+ href = href.substring(scheme.length());
+ if ("file:".equals(scheme)) {
+ int colonPos = href.indexOf(':');
+ int slashPos = href.indexOf('/');
+ if (slashPos >= 0 && colonPos >= 0
+ && colonPos < slashPos) {
+ href = "/" + href; // Absolute file URL doesn't
+ // have a leading slash
+ }
}
}
+ try {
+ absoluteURL = new URL(baseURL, href);
+ } catch (MalformedURLException mfue) {
+ handleException(mfue, "Error with URL; base '" + base
+ + "' " + "href '" + href + "'", throwExceptions);
+ }
}
+ }
+
+ if (absoluteURL != null) {
+ String effURL = absoluteURL.toExternalForm();
try {
- absoluteURL = new URL(baseURL, href);
- } catch (MalformedURLException mfue) {
- handleException(mfue,
- "Error with URL; base '" + base + "' " + "href '" + href + "'",
- throwExceptions);
+ URLConnection connection = absoluteURL.openConnection();
+ connection.setAllowUserInteraction(false);
+ connection.setDoInput(true);
+ updateURLConnection(connection, href);
+ connection.connect();
+ return new StreamSource(connection.getInputStream(), effURL);
+ } catch (FileNotFoundException fnfe) {
+ // Note: This is on "debug" level since the caller is
+ // supposed to handle this
+ log.debug("File not found: " + effURL);
+ } catch (java.io.IOException ioe) {
+ log.error("Error with opening URL '" + effURL + "': "
+ + ioe.getMessage());
}
}
}
-
- if (absoluteURL != null) {
- String effURL = absoluteURL.toExternalForm();
- try {
- URLConnection connection = absoluteURL.openConnection();
- connection.setAllowUserInteraction(false);
- connection.setDoInput(true);
- updateURLConnection(connection, href);
- connection.connect();
- return new StreamSource(connection.getInputStream(), effURL);
- } catch (FileNotFoundException fnfe) {
- //Note: This is on "debug" level since the caller is supposed to handle this
- log.debug("File not found: " + effURL);
- } catch (java.io.IOException ioe) {
- log.error("Error with opening URL '" + effURL + "': " + ioe.getMessage());
- }
- }
- return null;
+ return source;
}
/**
- * This method allows you to set special values on a URLConnection just before the connect()
- * method is called. Subclass FOURIResolver and override this method to do things like
- * adding the user name and password for HTTP basic authentication.
- * @param connection the URLConnection instance
- * @param href the original URI
+ * This method allows you to set special values on a URLConnection just
+ * before the connect() method is called. Subclass FOURIResolver and
+ * override this method to do things like adding the user name and password
+ * for HTTP basic authentication.
+ *
+ * @param connection
+ * the URLConnection instance
+ * @param href
+ * the original URI
*/
protected void updateURLConnection(URLConnection connection, String href) {
- //nop
+ // nop
}
-
+
/**
- * This is a convenience method for users who want to override updateURLConnection for
- * HTTP basic authentication. Simply call it using the right username and password.
- * @param connection the URLConnection to set up for HTTP basic authentication
- * @param username the username
- * @param password the password
+ * This is a convenience method for users who want to override
+ * updateURLConnection for HTTP basic authentication. Simply call it using
+ * the right username and password.
+ *
+ * @param connection
+ * the URLConnection to set up for HTTP basic authentication
+ * @param username
+ * the username
+ * @param password
+ * the password
*/
- protected void applyHttpBasicAuthentication(URLConnection connection,
+ protected void applyHttpBasicAuthentication(URLConnection connection,
String username, String password) {
String combined = username + ":" + password;
try {
- ByteArrayOutputStream baout = new ByteArrayOutputStream(combined.length() * 2);
+ ByteArrayOutputStream baout = new ByteArrayOutputStream(combined
+ .length() * 2);
Base64EncodeStream base64 = new Base64EncodeStream(baout);
- //TODO Not sure what charset/encoding can be used with basic authentication
+ // TODO Not sure what charset/encoding can be used with basic
+ // authentication
base64.write(combined.getBytes("UTF-8"));
base64.close();
- connection.setRequestProperty("Authorization",
- "Basic " + new String(baout.toByteArray(), "UTF-8"));
+ connection.setRequestProperty("Authorization", "Basic "
+ + new String(baout.toByteArray(), "UTF-8"));
} catch (IOException e) {
- //won't happen. We're operating in-memory.
- throw new RuntimeException("Error during base64 encodation of username/password");
+ // won't happen. We're operating in-memory.
+ throw new RuntimeException(
+ "Error during base64 encodation of username/password");
}
}
-
+
/**
- * Parses inline data URIs as generated by MS Word's XML export and FO stylesheet.
- * @see <a href="http://www.ietf.org/rfc/rfc2397">RFC 2397</a>
+ * Sets the custom URI Resolver. It is used for resolving factory-level URIs like
+ * hyphenation patterns and as backup for URI resolution performed during a
+ * rendering run.
+ *
+ * @param resolver
+ * the new URI resolver
*/
- private Source parseDataURI(String href) {
- int commaPos = href.indexOf(',');
- // header is of the form data:[<mediatype>][;base64]
- String header = href.substring(0, commaPos);
- String data = href.substring(commaPos + 1);
- if (header.endsWith(";base64")) {
- byte[] bytes = data.getBytes();
- ByteArrayInputStream encodedStream = new ByteArrayInputStream(bytes);
- Base64DecodeStream decodedStream = new Base64DecodeStream(encodedStream);
- return new StreamSource(decodedStream);
- } else {
- //Note that this is not quite the full story here. But since we are only interested
- //in base64-encoded binary data, the next line will probably never be called.
- return new StreamSource(new java.io.StringReader(data));
- }
+ public void setCustomURIResolver(URIResolver resolver) {
+ this.uriResolver = resolver;
+ }
+
+ /**
+ * Returns the custom URI Resolver.
+ *
+ * @return the URI Resolver or null, if none is set
+ */
+ public URIResolver getCustomURIResolver() {
+ return this.uriResolver;
+ }
+
+ /**
+ * @param throwExceptions
+ * Whether or not to throw exceptions on resolution error
+ */
+ public void setThrowExceptions(boolean throwExceptions) {
+ this.throwExceptions = throwExceptions;
}
}
diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java
index 24df2f75b..33106ee12 100644
--- a/src/java/org/apache/fop/apps/FOUserAgent.java
+++ b/src/java/org/apache/fop/apps/FOUserAgent.java
@@ -361,26 +361,27 @@ public class FOUserAgent {
* Attempts to resolve the given URI.
* Will use the configured resolver and if not successful fall back
* to the default resolver.
- * @param uri URI to access
+ * @param href URI to access
* @param base the base URI to resolve against
* @return A {@link javax.xml.transform.Source} object, or null if the URI
* cannot be resolved.
* @see org.apache.fop.apps.FOURIResolver
*/
- public Source resolveURI(String uri, String base) {
+ public Source resolveURI(String href, String base) {
Source source = null;
- //RFC 2397 data URLs don't need to be resolved, just decode them.
- boolean bypassURIResolution = uri.startsWith("data:");
+ //RFC 2397 data URLs don't need to be resolved, just decode them through FOP's default
+ //URIResolver.
+ boolean bypassURIResolution = href.startsWith("data:");
if (!bypassURIResolution && uriResolver != null) {
try {
- source = uriResolver.resolve(uri, base);
+ source = uriResolver.resolve(href, base);
} catch (TransformerException te) {
- log.error("Attempt to resolve URI '" + uri + "' failed: ", te);
+ log.error("Attempt to resolve URI '" + href + "' failed: ", te);
}
}
if (source == null) {
// URI Resolver not configured or returned null, use default resolver from the factory
- source = getFactory().resolveURI(uri, base);
+ source = getFactory().resolveURI(href, base);
}
return source;
}
diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java
index 1e7ab2a62..99af1671a 100644
--- a/src/java/org/apache/fop/apps/FopFactory.java
+++ b/src/java/org/apache/fop/apps/FopFactory.java
@@ -15,13 +15,11 @@
* limitations under the License.
*/
-/* $Id$ */
+/* $Id: $ */
package org.apache.fop.apps;
import java.awt.color.ColorSpace;
-import java.awt.color.ICC_ColorSpace;
-import java.awt.color.ICC_Profile;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
@@ -29,13 +27,11 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
-import java.util.Map;
import java.util.Set;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
-import javax.xml.transform.stream.StreamSource;
import org.xml.sax.SAXException;
@@ -52,6 +48,7 @@ import org.apache.fop.image.ImageFactory;
import org.apache.fop.layoutmgr.LayoutManagerMaker;
import org.apache.fop.render.RendererFactory;
import org.apache.fop.render.XMLHandlerRegistry;
+import org.apache.fop.util.ColorSpaceCache;
import org.apache.fop.util.ContentHandlerFactoryRegistry;
/**
@@ -66,10 +63,10 @@ public class FopFactory {
private static Log log = LogFactory.getLog(FopFactory.class);
/** Factory for Renderers and FOEventHandlers */
- private RendererFactory rendererFactory = new RendererFactory();
+ private RendererFactory rendererFactory;
/** Registry for XML handlers */
- private XMLHandlerRegistry xmlHandlers = new XMLHandlerRegistry();
+ private XMLHandlerRegistry xmlHandlers;
/** The registry for ElementMapping instances */
private ElementMappingRegistry elementMappingRegistry;
@@ -78,17 +75,13 @@ public class FopFactory {
private ContentHandlerFactoryRegistry contentHandlerFactoryRegistry
= new ContentHandlerFactoryRegistry();
- /** Our default resolver if none is set */
- private URIResolver foURIResolver = null;
-
- /** A user settable URI Resolver */
- private URIResolver uriResolver = null;
-
/** The resolver for user-supplied hyphenation patterns */
- private HyphenationTreeResolver hyphResolver;
+ private HyphenationTreeResolver hyphResolver = null;
+
+ private ColorSpaceCache colorSpaceCache = null;
/** Image factory for creating fop image objects */
- private ImageFactory imageFactory = new ImageFactory();
+ private ImageFactory imageFactory;
/** Configuration layer used to configure fop */
private FopFactoryConfigurator config = null;
@@ -145,11 +138,10 @@ public class FopFactory {
/** Optional overriding LayoutManagerMaker */
private LayoutManagerMaker lmMakerOverride = null;
- private Set ignoredNamespaces = new java.util.HashSet();
+ private Set ignoredNamespaces;
+
+ private FOURIResolver foURIResolver;
- /** Map with cached ICC based ColorSpace objects. */
- private Map colorSpaceMap = null;
-
/**
* Main constructor.
*/
@@ -157,8 +149,11 @@ public class FopFactory {
this.config = new FopFactoryConfigurator(this);
this.elementMappingRegistry = new ElementMappingRegistry(this);
this.foURIResolver = new FOURIResolver(validateUserConfigStrictly());
- // Use a synchronized Map - I am not really sure this is needed, but better safe than sorry.
- this.colorSpaceMap = Collections.synchronizedMap(new java.util.HashMap());
+ this.colorSpaceCache = new ColorSpaceCache(foURIResolver);
+ this.imageFactory = new ImageFactory();
+ this.rendererFactory = new RendererFactory();
+ this.xmlHandlers = new XMLHandlerRegistry();
+ this.ignoredNamespaces = new java.util.HashSet();
setUseCache(FopFactoryConfigurator.DEFAULT_USE_CACHE);
}
@@ -397,11 +392,12 @@ public class FopFactory {
* */
public void setHyphenBaseURL(final String hyphenBase) throws MalformedURLException {
if (hyphenBase != null) {
- this.hyphResolver = new HyphenationTreeResolver() {
+ setHyphenationTreeResolver(
+ new HyphenationTreeResolver() {
public Source resolve(String href) {
return resolveURI(href, hyphenBase);
}
- };
+ });
}
this.hyphenBase = checkBaseURL(hyphenBase);
}
@@ -411,8 +407,8 @@ public class FopFactory {
* patterns and as backup for URI resolution performed during a rendering run.
* @param resolver the new URI resolver
*/
- public void setURIResolver(URIResolver resolver) {
- this.uriResolver = resolver;
+ public void setURIResolver(URIResolver uriResolver) {
+ foURIResolver.setCustomURIResolver(uriResolver);
}
/**
@@ -420,7 +416,7 @@ public class FopFactory {
* @return the URI Resolver
*/
public URIResolver getURIResolver() {
- return this.uriResolver;
+ return foURIResolver;
}
/** @return the HyphenationTreeResolver for resolving user-supplied hyphenation patterns. */
@@ -429,6 +425,14 @@ public class FopFactory {
}
/**
+ * sets the HyphenationTreeResolver
+ * @param hyphResolver
+ */
+ public void setHyphenationTreeResolver(HyphenationTreeResolver hyphResolver) {
+ this.hyphResolver = hyphResolver;
+ }
+
+ /**
* Activates strict XSL content model validation for FOP
* Default is false (FOP will continue processing where it can)
* @param validateStrictly true to turn on strict validation
@@ -669,6 +673,7 @@ public class FopFactory {
*/
public void setStrictUserConfigValidation(boolean strictUserConfigValidation) {
this.strictUserConfigValidation = strictUserConfigValidation;
+ this.foURIResolver.setThrowExceptions(strictUserConfigValidation);
}
/**
@@ -708,39 +713,22 @@ public class FopFactory {
return this.fontCache;
}
- //------------------------------------------- URI resolution
-
/**
* Attempts to resolve the given URI.
* Will use the configured resolver and if not successful fall back
* to the default resolver.
- * @param uri URI to access
+ * @param href URI to access
* @param baseUri the base URI to resolve against
* @return A {@link javax.xml.transform.Source} object, or null if the URI
* cannot be resolved.
* @see org.apache.fop.apps.FOURIResolver
*/
- public Source resolveURI(String uri, String baseUri) {
+ public Source resolveURI(String href, String baseUri) {
Source source = null;
- //RFC 2397 data URLs don't need to be resolved, just decode them.
- boolean bypassURIResolution = uri.startsWith("data:");
- if (!bypassURIResolution && uriResolver != null) {
- try {
- source = uriResolver.resolve(uri, baseUri);
- } catch (TransformerException te) {
- log.error("Attempt to resolve URI '" + uri + "' failed: ", te);
- if (validateUserConfigStrictly()) {
- return null;
- }
- }
- }
- if (source == null) {
- // URI Resolver not configured or returned null, use default resolver
- try {
- source = foURIResolver.resolve(uri, baseUri);
- } catch (TransformerException te) {
- log.error("Attempt to resolve URI '" + uri + "' failed: ", te);
- }
+ try {
+ source = foURIResolver.resolve(href, baseUri);
+ } catch (TransformerException e) {
+ log.error("Attempt to resolve URI '" + href + "' failed: ", e);
}
return source;
}
@@ -759,47 +747,6 @@ public class FopFactory {
* @return ICC ColorSpace object or null if ColorSpace could not be created
*/
public ColorSpace getColorSpace(String baseUri, String iccProfileSrc) {
- ColorSpace colorSpace = null;
- if (!this.colorSpaceMap.containsKey(baseUri + iccProfileSrc)) {
- try {
- ICC_Profile iccProfile = null;
- // First attempt to use the FOP URI resolver to locate the ICC
- // profile
- Source src = this.resolveURI(iccProfileSrc, baseUri);
- if (src != null && src instanceof StreamSource) {
- // FOP URI resolver found ICC profile - create ICC profile
- // from the Source
- iccProfile = ICC_Profile.getInstance(((StreamSource) src)
- .getInputStream());
- } else {
- // TODO - Would it make sense to fall back on VM ICC
- // resolution
- // Problem is the cache might be more difficult to maintain
- //
- // FOP URI resolver did not find ICC profile - perhaps the
- // Java VM can find it?
- // iccProfile = ICC_Profile.getInstance(iccProfileSrc);
- }
- if (iccProfile != null) {
- colorSpace = new ICC_ColorSpace(iccProfile);
- }
- } catch (IOException e) {
- // Ignore exception - will be logged a bit further down
- // (colorSpace == null case)
- }
-
- if (colorSpace != null) {
- // Put in cache (not when VM resolved it as we can't control
- this.colorSpaceMap.put(baseUri + iccProfileSrc, colorSpace);
- } else {
- // TODO To avoid an excessive amount of warnings perhaps
- // register a null ColorMap in the colorSpaceMap
- log.warn("Color profile '" + iccProfileSrc + "' not found.");
- }
- } else {
- colorSpace = (ColorSpace) this.colorSpaceMap.get(baseUri
- + iccProfileSrc);
- }
- return colorSpace;
+ return colorSpaceCache.get(baseUri, iccProfileSrc);
}
}
diff --git a/src/java/org/apache/fop/util/ColorSpaceCache.java b/src/java/org/apache/fop/util/ColorSpaceCache.java
new file mode 100644
index 000000000..92dcf8d55
--- /dev/null
+++ b/src/java/org/apache/fop/util/ColorSpaceCache.java
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Map with cached ICC based ColorSpace objects.
+ */
+public class ColorSpaceCache {
+ /** logger instance */
+ private static Log log = LogFactory.getLog(ColorSpaceCache.class);
+
+ private URIResolver resolver;
+ private Map colorSpaceMap = Collections.synchronizedMap(new java.util.HashMap());
+
+ /**
+ * Default constructor
+ * @param resolver uri resolver
+ */
+ public ColorSpaceCache(URIResolver resolver) {
+ this.resolver = resolver;
+ }
+
+ /**
+ * Create (if needed) and return an ICC ColorSpace instance.
+ *
+ * The ICC profile source is taken from the src attribute of the color-profile FO element.
+ * If the ICC ColorSpace is not yet in the cache a new one is created and stored in the cache.
+ *
+ * The FOP URI resolver is used to try and locate the ICC file.
+ * If that fails null is returned.
+ *
+ * @param base a base URI to resolve relative URIs
+ * @param iccProfileSrc ICC Profile source to return a ColorSpace for
+ * @return ICC ColorSpace object or null if ColorSpace could not be created
+ */
+ public ColorSpace get(String base, String iccProfileSrc) {
+ ColorSpace colorSpace = null;
+ if (!colorSpaceMap.containsKey(base + iccProfileSrc)) {
+ try {
+ ICC_Profile iccProfile = null;
+ // First attempt to use the FOP URI resolver to locate the ICC
+ // profile
+ Source src = resolver.resolve(iccProfileSrc, base);
+ if (src != null && src instanceof StreamSource) {
+ // FOP URI resolver found ICC profile - create ICC profile
+ // from the Source
+ iccProfile = ICC_Profile.getInstance(((StreamSource) src)
+ .getInputStream());
+ } else {
+ // TODO - Would it make sense to fall back on VM ICC
+ // resolution
+ // Problem is the cache might be more difficult to maintain
+ //
+ // FOP URI resolver did not find ICC profile - perhaps the
+ // Java VM can find it?
+ // iccProfile = ICC_Profile.getInstance(iccProfileSrc);
+ }
+ if (iccProfile != null) {
+ colorSpace = new ICC_ColorSpace(iccProfile);
+ }
+ } catch (Exception e) {
+ // Ignore exception - will be logged a bit further down
+ // (colorSpace == null case)
+ }
+
+ if (colorSpace != null) {
+ // Put in cache (not when VM resolved it as we can't control
+ colorSpaceMap.put(base + iccProfileSrc, colorSpace);
+ } else {
+ // TODO To avoid an excessive amount of warnings perhaps
+ // register a null ColorMap in the colorSpaceMap
+ log.warn("Color profile '" + iccProfileSrc + "' not found.");
+ }
+ } else {
+ colorSpace = (ColorSpace)colorSpaceMap.get(base
+ + iccProfileSrc);
+ }
+ return colorSpace;
+ }
+}
diff --git a/src/java/org/apache/fop/util/DataURIResolver.java b/src/java/org/apache/fop/util/DataURIResolver.java
new file mode 100644
index 000000000..4ae4be156
--- /dev/null
+++ b/src/java/org/apache/fop/util/DataURIResolver.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.ByteArrayInputStream;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+// base64 support for "data" urls
+import org.apache.xmlgraphics.util.io.Base64DecodeStream;
+
+/**
+ * Resolves data URLs (described in RFC 2397) returning its data as a StreamSource.
+ *
+ * @see javax.xml.transform.URIResolver
+ * @see <a href="http://www.ietf.org/rfc/rfc2397">RFC 2397</a>
+ */
+public class DataURIResolver implements URIResolver {
+
+ /**
+ * @see javax.xml.transform.URIResolver#resolve(java.lang.String, java.lang.String)
+ */
+ public Source resolve(String href, String base) throws TransformerException {
+ if (href.startsWith("data:")) {
+ return parseDataURI(href);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Parses inline data URIs as generated by MS Word's XML export and FO
+ * stylesheet.
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc2397">RFC 2397</a>
+ */
+ private Source parseDataURI(String href) {
+ int commaPos = href.indexOf(',');
+ // header is of the form data:[<mediatype>][;base64]
+ String header = href.substring(0, commaPos);
+ String data = href.substring(commaPos + 1);
+ if (header.endsWith(";base64")) {
+ byte[] bytes = data.getBytes();
+ ByteArrayInputStream encodedStream = new ByteArrayInputStream(bytes);
+ Base64DecodeStream decodedStream = new Base64DecodeStream(
+ encodedStream);
+ return new StreamSource(decodedStream);
+ } else {
+ // Note that this is not quite the full story here. But since we are
+ // only interested
+ // in base64-encoded binary data, the next line will probably never
+ // be called.
+ //TODO Handle un-escaping of special URL chars like %20
+ return new StreamSource(new java.io.StringReader(data));
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/util/DataURLUtil.java b/src/java/org/apache/fop/util/DataURLUtil.java
new file mode 100644
index 000000000..03236dd45
--- /dev/null
+++ b/src/java/org/apache/fop/util/DataURLUtil.java
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.xmlgraphics.util.io.Base64EncodeStream;
+
+/**
+ * Utility classes for generating RFC 2397 data URLs.
+ */
+public class DataURLUtil {
+
+ /**
+ * Creates a new data URL and returns it as a String.
+ * @param in the InputStream to read the data from
+ * @param mediatype the MIME type of the content, or null
+ * @return the newly created data URL
+ * @throws IOException if an I/O error occurs
+ */
+ public static String createDataURL(InputStream in, String mediatype) throws IOException {
+ StringWriter writer = new StringWriter();
+ writeDataURL(in, mediatype, writer);
+ return writer.toString();
+ }
+
+ /**
+ * Generates a data URL and writes it to a Writer.
+ * @param in the InputStream to read the data from
+ * @param mediatype the MIME type of the content, or null
+ * @param writer the Writer to write to
+ * @throws IOException if an I/O error occurs
+ */
+ public static void writeDataURL(InputStream in, String mediatype, Writer writer)
+ throws IOException {
+ writer.write("data:");
+ if (mediatype != null) {
+ writer.write(mediatype);
+ }
+ writer.write(";base64,");
+ Base64EncodeStream out = new Base64EncodeStream(
+ new WriterOutputStream(writer, "US-ASCII"));
+ IOUtils.copy(in, out);
+ out.flush();
+ }
+}
diff --git a/src/java/org/apache/fop/util/WriterOutputStream.java b/src/java/org/apache/fop/util/WriterOutputStream.java
new file mode 100644
index 000000000..d1908996a
--- /dev/null
+++ b/src/java/org/apache/fop/util/WriterOutputStream.java
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.Writer;
+
+/**
+ * An OutputStream wrapper for a Writer.
+ */
+public class WriterOutputStream extends OutputStream {
+
+ private Writer writer;
+ private String encoding;
+
+ /**
+ * Creates a new WriterOutputStream.
+ * @param writer the Writer to write to
+ */
+ public WriterOutputStream(Writer writer) {
+ this(writer, null);
+ }
+
+ /**
+ * Creates a new WriterOutputStream.
+ * @param writer the Writer to write to
+ * @param encoding the encoding to use, or null if the default encoding should be used
+ */
+ public WriterOutputStream(Writer writer, String encoding) {
+ this.writer = writer;
+ this.encoding = encoding;
+ }
+
+ /**
+ * @see java.io.OutputStream#close()
+ */
+ public void close() throws IOException {
+ writer.close();
+ }
+
+ /**
+ * @see java.io.OutputStream#flush()
+ */
+ public void flush() throws IOException {
+ writer.flush();
+ }
+
+ /**
+ * @see java.io.OutputStream#write(byte[], int, int)
+ */
+ public void write(byte[] buf, int offset, int length) throws IOException {
+ if (encoding != null) {
+ writer.write(new String(buf, offset, length, encoding));
+ } else {
+ writer.write(new String(buf, offset, length));
+ }
+ }
+
+ /**
+ * @see java.io.OutputStream#write(byte[])
+ */
+ public void write(byte[] buf) throws IOException {
+ write(buf, 0, buf.length);
+ }
+
+ /**
+ * @see java.io.OutputStream#write(int)
+ */
+ public void write(int b) throws IOException {
+ write(new byte[] {(byte)b});
+ }
+
+}
diff --git a/test/java/org/apache/fop/UtilityCodeTestSuite.java b/test/java/org/apache/fop/UtilityCodeTestSuite.java
index f84390518..679e16ce7 100644
--- a/test/java/org/apache/fop/UtilityCodeTestSuite.java
+++ b/test/java/org/apache/fop/UtilityCodeTestSuite.java
@@ -21,6 +21,8 @@ package org.apache.fop;
import org.apache.fop.traits.BorderPropsTestCase;
import org.apache.fop.traits.TraitColorTestCase;
+import org.apache.fop.util.DataURIResolverTestCase;
+import org.apache.fop.util.ElementListUtilsTestCase;
import org.apache.fop.util.PDFNumberTestCase;
import org.apache.fop.util.UnitConvTestCase;
@@ -44,6 +46,8 @@ public class UtilityCodeTestSuite {
suite.addTest(new TestSuite(UnitConvTestCase.class));
suite.addTest(new TestSuite(TraitColorTestCase.class));
suite.addTest(new TestSuite(BorderPropsTestCase.class));
+ suite.addTest(new TestSuite(ElementListUtilsTestCase.class));
+ suite.addTest(new TestSuite(DataURIResolverTestCase.class));
//$JUnit-END$
return suite;
}
diff --git a/test/java/org/apache/fop/util/DataURIResolverTestCase.java b/test/java/org/apache/fop/util/DataURIResolverTestCase.java
new file mode 100644
index 000000000..133d4fcd1
--- /dev/null
+++ b/test/java/org/apache/fop/util/DataURIResolverTestCase.java
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.util;
+
+import java.io.ByteArrayInputStream;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.io.IOUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for the RFC 2397 data URL/URI resolver.
+ */
+public class DataURIResolverTestCase extends TestCase {
+
+ private static final byte[] TESTDATA = new byte[] {0, 1, 2, 3, 4, 5};
+
+ /**
+ * Tests DataURLUtil.
+ * @throws Exception if an error occurs
+ */
+ public void testRFC2397Generator() throws Exception {
+ String url = DataURLUtil.createDataURL(new ByteArrayInputStream(TESTDATA), null);
+ assertEquals("Generated data URL is wrong", "data:;base64,AAECAwQF", url);
+
+ url = DataURLUtil.createDataURL(new ByteArrayInputStream(TESTDATA), "application/pdf");
+ assertEquals("Generated data URL is wrong", "data:application/pdf;base64,AAECAwQF", url);
+ }
+
+ /**
+ * Test the URIResolver contract if the protocol doesn't match. Resolver must return null
+ * in this case.
+ * @throws Exception if an error occurs
+ */
+ public void testNonMatchingContract() throws Exception {
+ URIResolver resolver = new DataURIResolver();
+ Source src;
+
+ src = resolver.resolve("http://xmlgraphics.apache.org/fop/index.html", null);
+ assertNull(src);
+
+ src = resolver.resolve("index.html", "http://xmlgraphics.apache.org/fop/");
+ assertNull(src);
+ }
+
+ private static boolean byteCmp(byte[] src, int srcOffset, byte[] cmp) {
+ for (int i = 0, c = cmp.length; i < c; i++) {
+ if (src[srcOffset + i] != cmp[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Test the DataURIResolver with correct values.
+ * @throws Exception if an error occurs
+ */
+ public void testDataURLHandling() throws Exception {
+ URIResolver resolver = new DataURIResolver();
+ Source src;
+
+ src = resolver.resolve("data:;base64,AAECAwQF", null);
+ assertNotNull(src);
+ StreamSource streamSource = (StreamSource)src;
+ byte[] data = IOUtils.toByteArray(streamSource.getInputStream());
+ assertTrue("Decoded data doesn't match the test data", byteCmp(TESTDATA, 0, data));
+
+ src = resolver.resolve(
+ "data:application/octet-stream;interpreter=fop;base64,AAECAwQF", null);
+ assertNotNull(src);
+ streamSource = (StreamSource)src;
+ assertNotNull(streamSource.getInputStream());
+ assertNull(streamSource.getReader());
+ data = IOUtils.toByteArray(streamSource.getInputStream());
+ assertTrue("Decoded data doesn't match the test data", byteCmp(TESTDATA, 0, data));
+
+ src = resolver.resolve("data:,FOP", null);
+ assertNotNull(src);
+ streamSource = (StreamSource)src;
+ assertNull(streamSource.getInputStream());
+ assertNotNull(streamSource.getReader());
+ String text = IOUtils.toString(streamSource.getReader());
+ assertEquals("FOP", text);
+
+ /* TODO Un-escaping of special URL chars like %20 hasn't been implemented, yet.
+ src = resolver.resolve("data:,A%20brief%20note", null);
+ assertNotNull(src);
+ streamSource = (StreamSource)src;
+ text = IOUtils.toString(streamSource.getReader());
+ assertEquals("A brief note", text);
+ */
+ }
+
+}