SystemIDResolver.java revision 9f8118474e9513f7a5b7d2a05e4a0fb15d1a6569
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the  "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *     http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18/*
19 * $Id: SystemIDResolver.java 468655 2006-10-28 07:12:06Z minchau $
20 */
21package org.apache.xml.utils;
22
23import java.io.File;
24
25import javax.xml.transform.TransformerException;
26
27import org.apache.xml.utils.URI.MalformedURIException;
28
29/**
30 * This class is used to resolve relative URIs and SystemID
31 * strings into absolute URIs.
32 *
33 * <p>This is a generic utility for resolving URIs, other than the
34 * fact that it's declared to throw TransformerException.  Please
35 * see code comments for details on how resolution is performed.</p>
36 * @xsl.usage internal
37 */
38public class SystemIDResolver
39{
40
41  /**
42   * Get an absolute URI from a given relative URI (local path).
43   *
44   * <p>The relative URI is a local filesystem path. The path can be
45   * absolute or relative. If it is a relative path, it is resolved relative
46   * to the system property "user.dir" if it is available; if not (i.e. in an
47   * Applet perhaps which throws SecurityException) then we just return the
48   * relative path. The space and backslash characters are also replaced to
49   * generate a good absolute URI.</p>
50   *
51   * @param localPath The relative URI to resolve
52   *
53   * @return Resolved absolute URI
54   */
55  public static String getAbsoluteURIFromRelative(String localPath)
56  {
57    if (localPath == null || localPath.length() == 0)
58      return "";
59
60    // If the local path is a relative path, then it is resolved against
61    // the "user.dir" system property.
62    String absolutePath = localPath;
63    if (!isAbsolutePath(localPath))
64    {
65      try
66      {
67        absolutePath = getAbsolutePathFromRelativePath(localPath);
68      }
69      // user.dir not accessible from applet
70      catch (SecurityException se)
71      {
72        return "file:" + localPath;
73      }
74    }
75
76    String urlString;
77    if (null != absolutePath)
78    {
79      if (absolutePath.startsWith(File.separator))
80        urlString = "file://" + absolutePath;
81      else
82        urlString = "file:///" + absolutePath;
83    }
84    else
85      urlString = "file:" + localPath;
86
87    return replaceChars(urlString);
88  }
89
90  /**
91   * Return an absolute path from a relative path.
92   *
93   * @param relativePath A relative path
94   * @return The absolute path
95   */
96  private static String getAbsolutePathFromRelativePath(String relativePath)
97  {
98    return new File(relativePath).getAbsolutePath();
99  }
100
101  /**
102   * Return true if the systemId denotes an absolute URI .
103   *
104   * @param systemId The systemId string
105   * @return true if the systemId is an an absolute URI
106   */
107  public static boolean isAbsoluteURI(String systemId)
108  {
109     /** http://www.ietf.org/rfc/rfc2396.txt
110      *   Authors should be aware that a path segment which contains a colon
111      * character cannot be used as the first segment of a relative URI path
112      * (e.g., "this:that"), because it would be mistaken for a scheme name.
113     **/
114     /**
115      * %REVIEW% Can we assume here that systemId is a valid URI?
116      * It looks like we cannot ( See discussion of this common problem in
117      * Bugzilla Bug 22777 ).
118     **/
119     //"fix" for Bugzilla Bug 22777
120    if(isWindowsAbsolutePath(systemId)){
121        return false;
122     }
123
124    final int fragmentIndex = systemId.indexOf('#');
125    final int queryIndex = systemId.indexOf('?');
126    final int slashIndex = systemId.indexOf('/');
127    final int colonIndex = systemId.indexOf(':');
128
129    //finding substring  before '#', '?', and '/'
130    int index = systemId.length() -1;
131    if(fragmentIndex > 0)
132        index = fragmentIndex;
133    if((queryIndex > 0) && (queryIndex <index))
134        index = queryIndex;
135    if((slashIndex > 0) && (slashIndex <index))
136        index = slashIndex;
137    // return true if there is ':' before '#', '?', and '/'
138    return ((colonIndex >0) && (colonIndex<index));
139
140  }
141
142  /**
143   * Return true if the local path is an absolute path.
144   *
145   * @param systemId The path string
146   * @return true if the path is absolute
147   */
148  public static boolean isAbsolutePath(String systemId)
149  {
150    if(systemId == null)
151        return false;
152    final File file = new File(systemId);
153    return file.isAbsolute();
154
155  }
156
157   /**
158   * Return true if the local path is a Windows absolute path.
159   *
160   * @param systemId The path string
161   * @return true if the path is a Windows absolute path
162   */
163    private static boolean isWindowsAbsolutePath(String systemId)
164  {
165    if(!isAbsolutePath(systemId))
166      return false;
167    // On Windows, an absolute path starts with "[drive_letter]:\".
168    if (systemId.length() > 2
169        && systemId.charAt(1) == ':'
170        && Character.isLetter(systemId.charAt(0))
171        && (systemId.charAt(2) == '\\' || systemId.charAt(2) == '/'))
172      return true;
173    else
174      return false;
175  }
176
177  /**
178   * Replace spaces with "%20" and backslashes with forward slashes in
179   * the input string to generate a well-formed URI string.
180   *
181   * @param str The input string
182   * @return The string after conversion
183   */
184  private static String replaceChars(String str)
185  {
186    StringBuffer buf = new StringBuffer(str);
187    int length = buf.length();
188    for (int i = 0; i < length; i++)
189    {
190      char currentChar = buf.charAt(i);
191      // Replace space with "%20"
192      if (currentChar == ' ')
193      {
194        buf.setCharAt(i, '%');
195        buf.insert(i+1, "20");
196        length = length + 2;
197        i = i + 2;
198      }
199      // Replace backslash with forward slash
200      else if (currentChar == '\\')
201      {
202        buf.setCharAt(i, '/');
203      }
204    }
205
206    return buf.toString();
207  }
208
209  /**
210   * Take a SystemID string and try to turn it into a good absolute URI.
211   *
212   * @param systemId A URI string, which may be absolute or relative.
213   *
214   * @return The resolved absolute URI
215   */
216  public static String getAbsoluteURI(String systemId)
217  {
218    String absoluteURI = systemId;
219    if (isAbsoluteURI(systemId))
220    {
221      // Only process the systemId if it starts with "file:".
222      if (systemId.startsWith("file:"))
223      {
224        String str = systemId.substring(5);
225
226        // Resolve the absolute path if the systemId starts with "file:///"
227        // or "file:/". Don't do anything if it only starts with "file://".
228        if (str != null && str.startsWith("/"))
229        {
230          if (str.startsWith("///") || !str.startsWith("//"))
231          {
232            // A Windows path containing a drive letter can be relative.
233            // A Unix path starting with "file:/" is always absolute.
234            int secondColonIndex = systemId.indexOf(':', 5);
235            if (secondColonIndex > 0)
236            {
237              String localPath = systemId.substring(secondColonIndex-1);
238              try {
239                if (!isAbsolutePath(localPath))
240                  absoluteURI = systemId.substring(0, secondColonIndex-1) +
241                                getAbsolutePathFromRelativePath(localPath);
242              }
243              catch (SecurityException se) {
244                return systemId;
245              }
246            }
247          }
248        }
249        else
250        {
251          return getAbsoluteURIFromRelative(systemId.substring(5));
252        }
253
254        return replaceChars(absoluteURI);
255      }
256      else
257        return systemId;
258    }
259    else
260      return getAbsoluteURIFromRelative(systemId);
261
262  }
263
264
265  /**
266   * Take a SystemID string and try to turn it into a good absolute URI.
267   *
268   * @param urlString SystemID string
269   * @param base The URI string used as the base for resolving the systemID
270   *
271   * @return The resolved absolute URI
272   * @throws TransformerException thrown if the string can't be turned into a URI.
273   */
274  public static String getAbsoluteURI(String urlString, String base)
275          throws TransformerException
276  {
277    if (base == null)
278      return getAbsoluteURI(urlString);
279
280    String absoluteBase = getAbsoluteURI(base);
281    URI uri = null;
282    try
283    {
284      URI baseURI = new URI(absoluteBase);
285      uri = new URI(baseURI, urlString);
286    }
287    catch (MalformedURIException mue)
288    {
289      throw new TransformerException(mue);
290    }
291
292    return replaceChars(uri.toString());
293  }
294
295}
296