diff options
Diffstat (limited to 'src/java/org/apache/fop/apps/FOURIResolver.java')
-rw-r--r-- | src/java/org/apache/fop/apps/FOURIResolver.java | 358 |
1 files changed, 203 insertions, 155 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; } } |