|
|
@@ -1,377 +0,0 @@ |
|
|
|
/* |
|
|
|
* 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.apps.io; |
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
import java.io.File; |
|
|
|
import java.io.FileNotFoundException; |
|
|
|
import java.io.IOException; |
|
|
|
import java.net.MalformedURLException; |
|
|
|
import java.net.URI; |
|
|
|
import java.net.URISyntaxException; |
|
|
|
import java.net.URL; |
|
|
|
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; |
|
|
|
|
|
|
|
import org.apache.commons.io.FileUtils; |
|
|
|
import org.apache.commons.logging.Log; |
|
|
|
import org.apache.commons.logging.LogFactory; |
|
|
|
import org.apache.fop.apps.FOUserAgent; |
|
|
|
|
|
|
|
import org.apache.xmlgraphics.util.io.Base64EncodeStream; |
|
|
|
import org.apache.xmlgraphics.util.uri.CommonURIResolver; |
|
|
|
|
|
|
|
/** |
|
|
|
* Provides FOP specific URI resolution. This is the default URIResolver |
|
|
|
* {@link FOUserAgent} will use unless overridden. |
|
|
|
* |
|
|
|
* @see javax.xml.transform.URIResolver |
|
|
|
*/ |
|
|
|
public class FOURIResolver implements javax.xml.transform.URIResolver { |
|
|
|
|
|
|
|
// log |
|
|
|
private Log log = LogFactory.getLog("FOP"); |
|
|
|
|
|
|
|
/** Common URIResolver */ |
|
|
|
private CommonURIResolver commonURIResolver = new CommonURIResolver(); |
|
|
|
|
|
|
|
/** 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; |
|
|
|
|
|
|
|
/** |
|
|
|
* Checks if the given base URL is acceptable. It also normalizes the URL. |
|
|
|
* @param base the base URL to check |
|
|
|
* @return the normalized URL |
|
|
|
* @throws MalformedURLException if there's a problem with a file URL |
|
|
|
*/ |
|
|
|
public String checkBaseURL(String base) throws MalformedURLException { |
|
|
|
// replace back slash with forward slash to ensure windows file:/// URLS are supported |
|
|
|
base = base.replace('\\', '/'); |
|
|
|
if (!base.endsWith("/")) { |
|
|
|
// The behavior described by RFC 3986 regarding resolution of relative |
|
|
|
// references may be misleading for normal users: |
|
|
|
// file://path/to/resources + myResource.res -> file://path/to/myResource.res |
|
|
|
// file://path/to/resources/ + myResource.res -> file://path/to/resources/myResource.res |
|
|
|
// We assume that even when the ending slash is missing, users have the second |
|
|
|
// example in mind |
|
|
|
base += "/"; |
|
|
|
} |
|
|
|
File dir = new File(base); |
|
|
|
if (dir.isDirectory()) { |
|
|
|
return dir.toURI().toASCIIString(); |
|
|
|
} else { |
|
|
|
URI baseURI; |
|
|
|
try { |
|
|
|
baseURI = new URI(base); |
|
|
|
String scheme = baseURI.getScheme(); |
|
|
|
boolean directoryExists = true; |
|
|
|
if ("file".equals(scheme)) { |
|
|
|
dir = FileUtils.toFile(baseURI.toURL()); |
|
|
|
directoryExists = dir.isDirectory(); |
|
|
|
} |
|
|
|
if (scheme == null || !directoryExists) { |
|
|
|
String message = "base " + base + " is not a valid directory"; |
|
|
|
if (throwExceptions) { |
|
|
|
throw new MalformedURLException(message); |
|
|
|
} |
|
|
|
log.error(message); |
|
|
|
} |
|
|
|
return baseURI.toASCIIString(); |
|
|
|
} catch (URISyntaxException e) { |
|
|
|
//TODO not ideal: our base URLs are actually base URIs. |
|
|
|
throw new MalformedURLException(e.getMessage()); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Default constructor |
|
|
|
*/ |
|
|
|
public FOURIResolver() { |
|
|
|
this(false); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Additional constructor |
|
|
|
* |
|
|
|
* @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 e |
|
|
|
* the exception |
|
|
|
* @param errorStr |
|
|
|
* error string |
|
|
|
* @param strict |
|
|
|
* strict user config |
|
|
|
*/ |
|
|
|
private void handleException(Exception e, String errorStr, boolean strict) |
|
|
|
throws TransformerException { |
|
|
|
if (strict) { |
|
|
|
throw new TransformerException(errorStr, e); |
|
|
|
} |
|
|
|
log.error(e.getMessage()); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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 |
|
|
|
* 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. |
|
|
|
* @see javax.xml.transform.URIResolver#resolve(String, String) |
|
|
|
*/ |
|
|
|
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 = commonURIResolver.resolve(href, base); |
|
|
|
|
|
|
|
// Custom uri resolution |
|
|
|
if (source == null && uriResolver != null) { |
|
|
|
source = uriResolver.resolve(href, base); |
|
|
|
} |
|
|
|
|
|
|
|
// Fallback to default resolution mechanism |
|
|
|
if (source == null) { |
|
|
|
URL absoluteURL = null; |
|
|
|
int hashPos = href.indexOf('#'); |
|
|
|
String fileURL; |
|
|
|
String fragment; |
|
|
|
if (hashPos >= 0) { |
|
|
|
fileURL = href.substring(0, hashPos); |
|
|
|
fragment = href.substring(hashPos); |
|
|
|
} else { |
|
|
|
fileURL = href; |
|
|
|
fragment = null; |
|
|
|
} |
|
|
|
File file = new File(fileURL); |
|
|
|
if (file.canRead() && file.isFile()) { |
|
|
|
try { |
|
|
|
if (fragment != null) { |
|
|
|
absoluteURL = new URL(file.toURI().toURL().toExternalForm() + fragment); |
|
|
|
} else { |
|
|
|
absoluteURL = file.toURI().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 { |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
/* |
|
|
|
* 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. |
|
|
|
*/ |
|
|
|
assert (baseURL != null); |
|
|
|
String scheme = baseURL.getProtocol() + ":"; |
|
|
|
if (href.startsWith(scheme) && "file:".equals(scheme)) { |
|
|
|
href = href.substring(scheme.length()); |
|
|
|
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 { |
|
|
|
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 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 |
|
|
|
*/ |
|
|
|
protected void updateURLConnection(URLConnection connection, String href) { |
|
|
|
// 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 |
|
|
|
*/ |
|
|
|
protected void applyHttpBasicAuthentication(URLConnection connection, |
|
|
|
String username, String password) { |
|
|
|
String combined = username + ":" + password; |
|
|
|
try { |
|
|
|
ByteArrayOutputStream baout = new ByteArrayOutputStream(combined |
|
|
|
.length() * 2); |
|
|
|
Base64EncodeStream base64 = new Base64EncodeStream(baout); |
|
|
|
// 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")); |
|
|
|
} catch (IOException e) { |
|
|
|
// won't happen. We're operating in-memory. |
|
|
|
throw new RuntimeException( |
|
|
|
"Error during base64 encodation of username/password"); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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 |
|
|
|
*/ |
|
|
|
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; |
|
|
|
} |
|
|
|
} |