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