001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /* $Id: FOURIResolver.java 551874 2007-06-29 12:46:14Z jeremias $ */
019:
020: package org.apache.fop.apps;
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.File;
024: import java.io.FileNotFoundException;
025: import java.io.IOException;
026: import java.net.MalformedURLException;
027: import java.net.URL;
028: import java.net.URLConnection;
029:
030: import javax.xml.transform.Source;
031: import javax.xml.transform.TransformerException;
032: import javax.xml.transform.URIResolver;
033: import javax.xml.transform.stream.StreamSource;
034:
035: // commons logging
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.apache.fop.util.DataURIResolver;
039:
040: import org.apache.xmlgraphics.util.io.Base64EncodeStream;
041:
042: /**
043: * Provides FOP specific URI resolution. This is the default URIResolver
044: * {@link FOUserAgent} will use unless overidden.
045: *
046: * @see javax.xml.transform.URIResolver
047: */
048: public class FOURIResolver implements javax.xml.transform.URIResolver {
049:
050: // log
051: private Log log = LogFactory.getLog("FOP");
052:
053: /** URIResolver for RFC 2397 data URLs */
054: private URIResolver dataURIResolver = new DataURIResolver();
055:
056: /** A user settable URI Resolver */
057: private URIResolver uriResolver = null;
058:
059: /** true if exceptions are to be thrown if the URIs cannot be resolved. */
060: private boolean throwExceptions = false;
061:
062: /**
063: * Default constructor
064: */
065: public FOURIResolver() {
066: this (false);
067: }
068:
069: /**
070: * Additional constructor
071: *
072: * @param throwExceptions
073: * true if exceptions are to be thrown if the URIs cannot be
074: * resolved.
075: */
076: public FOURIResolver(boolean throwExceptions) {
077: this .throwExceptions = throwExceptions;
078: }
079:
080: /**
081: * Handles resolve exceptions appropriately.
082: *
083: * @param errorStr
084: * error string
085: * @param strict
086: * strict user config
087: */
088: private void handleException(Exception e, String errorStr,
089: boolean strict) throws TransformerException {
090: if (strict) {
091: throw new TransformerException(errorStr, e);
092: }
093: log.error(e.getMessage());
094: }
095:
096: /**
097: * Called by the processor through {@link FOUserAgent} when it encounters an
098: * uri in an external-graphic element. (see also
099: * {@link javax.xml.transform.URIResolver#resolve(String, String)} This
100: * resolver will allow URLs without a scheme, i.e. it assumes 'file:' as the
101: * default scheme. It also allows relative URLs with scheme, e.g.
102: * file:../../abc.jpg which is not strictly RFC compliant as long as the
103: * scheme is the same as the scheme of the base URL. If the base URL is null
104: * a 'file:' URL referencing the current directory is used as the base URL.
105: * If the method is successful it will return a Source of type
106: * {@link javax.xml.transform.stream.StreamSource} with its SystemID set to
107: * the resolved URL used to open the underlying InputStream.
108: *
109: * @param href
110: * An href attribute, which may be relative or absolute.
111: * @param base
112: * The base URI against which the first argument will be made
113: * absolute if the absolute URI is required.
114: * @return A {@link javax.xml.transform.Source} object, or null if the href
115: * cannot be resolved.
116: * @throws javax.xml.transform.TransformerException
117: * Never thrown by this implementation.
118: * @see javax.xml.transform.URIResolver#resolve(String, String)
119: */
120: public Source resolve(String href, String base)
121: throws TransformerException {
122: Source source = null;
123:
124: // data URLs can be quite long so evaluate early and don't try to build a File
125: // (can lead to problems)
126: source = dataURIResolver.resolve(href, base);
127:
128: // Custom uri resolution
129: if (source == null && uriResolver != null) {
130: source = uriResolver.resolve(href, base);
131: }
132:
133: // Fallback to default resolution mechanism
134: if (source == null) {
135: URL absoluteURL = null;
136: File file = new File(href);
137: if (file.canRead() && file.isFile()) {
138: try {
139: absoluteURL = file.toURL();
140: } catch (MalformedURLException mfue) {
141: handleException(mfue,
142: "Could not convert filename '" + href
143: + "' to URL", throwExceptions);
144: }
145: } else {
146: // no base provided
147: if (base == null) {
148: // We don't have a valid file protocol based URL
149: try {
150: absoluteURL = new URL(href);
151: } catch (MalformedURLException mue) {
152: try {
153: // the above failed, we give it another go in case
154: // the href contains only a path then file: is
155: // assumed
156: absoluteURL = new URL("file:" + href);
157: } catch (MalformedURLException mfue) {
158: handleException(mfue, "Error with URL '"
159: + href + "'", throwExceptions);
160: }
161: }
162:
163: // try and resolve from context of base
164: } else {
165: URL baseURL = null;
166: try {
167: baseURL = new URL(base);
168: } catch (MalformedURLException mfue) {
169: handleException(mfue, "Error with base URL '"
170: + base + "'", throwExceptions);
171: }
172:
173: /*
174: * This piece of code is based on the following statement in
175: * RFC2396 section 5.2:
176: *
177: * 3) If the scheme component is defined, indicating that
178: * the reference starts with a scheme name, then the
179: * reference is interpreted as an absolute URI and we are
180: * done. Otherwise, the reference URI's scheme is inherited
181: * from the base URI's scheme component.
182: *
183: * Due to a loophole in prior specifications [RFC1630], some
184: * parsers allow the scheme name to be present in a relative
185: * URI if it is the same as the base URI scheme.
186: * Unfortunately, this can conflict with the correct parsing
187: * of non-hierarchical URI. For backwards compatibility, an
188: * implementation may work around such references by
189: * removing the scheme if it matches that of the base URI
190: * and the scheme is known to always use the <hier_part>
191: * syntax.
192: *
193: * The URL class does not implement this work around, so we
194: * do.
195: */
196: String scheme = baseURL.getProtocol() + ":";
197: if (href.startsWith(scheme)) {
198: href = href.substring(scheme.length());
199: if ("file:".equals(scheme)) {
200: int colonPos = href.indexOf(':');
201: int slashPos = href.indexOf('/');
202: if (slashPos >= 0 && colonPos >= 0
203: && colonPos < slashPos) {
204: href = "/" + href; // Absolute file URL doesn't
205: // have a leading slash
206: }
207: }
208: }
209: try {
210: absoluteURL = new URL(baseURL, href);
211: } catch (MalformedURLException mfue) {
212: handleException(mfue, "Error with URL; base '"
213: + base + "' " + "href '" + href + "'",
214: throwExceptions);
215: }
216: }
217: }
218:
219: if (absoluteURL != null) {
220: String effURL = absoluteURL.toExternalForm();
221: try {
222: URLConnection connection = absoluteURL
223: .openConnection();
224: connection.setAllowUserInteraction(false);
225: connection.setDoInput(true);
226: updateURLConnection(connection, href);
227: connection.connect();
228: return new StreamSource(
229: connection.getInputStream(), effURL);
230: } catch (FileNotFoundException fnfe) {
231: // Note: This is on "debug" level since the caller is
232: // supposed to handle this
233: log.debug("File not found: " + effURL);
234: } catch (java.io.IOException ioe) {
235: log.error("Error with opening URL '" + effURL
236: + "': " + ioe.getMessage());
237: }
238: }
239: }
240: return source;
241: }
242:
243: /**
244: * This method allows you to set special values on a URLConnection just
245: * before the connect() method is called. Subclass FOURIResolver and
246: * override this method to do things like adding the user name and password
247: * for HTTP basic authentication.
248: *
249: * @param connection
250: * the URLConnection instance
251: * @param href
252: * the original URI
253: */
254: protected void updateURLConnection(URLConnection connection,
255: String href) {
256: // nop
257: }
258:
259: /**
260: * This is a convenience method for users who want to override
261: * updateURLConnection for HTTP basic authentication. Simply call it using
262: * the right username and password.
263: *
264: * @param connection
265: * the URLConnection to set up for HTTP basic authentication
266: * @param username
267: * the username
268: * @param password
269: * the password
270: */
271: protected void applyHttpBasicAuthentication(
272: URLConnection connection, String username, String password) {
273: String combined = username + ":" + password;
274: try {
275: ByteArrayOutputStream baout = new ByteArrayOutputStream(
276: combined.length() * 2);
277: Base64EncodeStream base64 = new Base64EncodeStream(baout);
278: // TODO Not sure what charset/encoding can be used with basic
279: // authentication
280: base64.write(combined.getBytes("UTF-8"));
281: base64.close();
282: connection.setRequestProperty("Authorization", "Basic "
283: + new String(baout.toByteArray(), "UTF-8"));
284: } catch (IOException e) {
285: // won't happen. We're operating in-memory.
286: throw new RuntimeException(
287: "Error during base64 encodation of username/password");
288: }
289: }
290:
291: /**
292: * Sets the custom URI Resolver. It is used for resolving factory-level URIs like
293: * hyphenation patterns and as backup for URI resolution performed during a
294: * rendering run.
295: *
296: * @param resolver
297: * the new URI resolver
298: */
299: public void setCustomURIResolver(URIResolver resolver) {
300: this .uriResolver = resolver;
301: }
302:
303: /**
304: * Returns the custom URI Resolver.
305: *
306: * @return the URI Resolver or null, if none is set
307: */
308: public URIResolver getCustomURIResolver() {
309: return this .uriResolver;
310: }
311:
312: /**
313: * @param throwExceptions
314: * Whether or not to throw exceptions on resolution error
315: */
316: public void setThrowExceptions(boolean throwExceptions) {
317: this.throwExceptions = throwExceptions;
318: }
319: }
|