001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: SystemIDResolver.java 468655 2006-10-28 07:12:06Z minchau $
020     */
021    package org.apache.xml.utils;
022    
023    import java.io.File;
024    
025    import javax.xml.transform.TransformerException;
026    
027    import org.apache.xml.utils.URI.MalformedURIException;
028    
029    /**
030     * This class is used to resolve relative URIs and SystemID 
031     * strings into absolute URIs.
032     *
033     * <p>This is a generic utility for resolving URIs, other than the 
034     * fact that it's declared to throw TransformerException.  Please 
035     * see code comments for details on how resolution is performed.</p>
036     * @xsl.usage internal
037     */
038    public class SystemIDResolver
039    {
040    
041      /**
042       * Get an absolute URI from a given relative URI (local path). 
043       * 
044       * <p>The relative URI is a local filesystem path. The path can be
045       * absolute or relative. If it is a relative path, it is resolved relative 
046       * to the system property "user.dir" if it is available; if not (i.e. in an 
047       * Applet perhaps which throws SecurityException) then we just return the
048       * relative path. The space and backslash characters are also replaced to
049       * generate a good absolute URI.</p>
050       *
051       * @param localPath The relative URI to resolve
052       *
053       * @return Resolved absolute URI
054       */
055      public static String getAbsoluteURIFromRelative(String localPath)
056      {
057        if (localPath == null || localPath.length() == 0)
058          return "";
059          
060        // If the local path is a relative path, then it is resolved against
061        // the "user.dir" system property.
062        String absolutePath = localPath;
063        if (!isAbsolutePath(localPath))
064        {
065          try 
066          {
067            absolutePath = getAbsolutePathFromRelativePath(localPath);
068          }
069          // user.dir not accessible from applet
070          catch (SecurityException se) 
071          {
072            return "file:" + localPath;
073          }
074        }
075    
076        String urlString;
077        if (null != absolutePath)
078        {
079          if (absolutePath.startsWith(File.separator))
080            urlString = "file://" + absolutePath;
081          else
082            urlString = "file:///" + absolutePath;        
083        }
084        else
085          urlString = "file:" + localPath;
086        
087        return replaceChars(urlString);
088      }
089      
090      /**
091       * Return an absolute path from a relative path.
092       *
093       * @param relativePath A relative path
094       * @return The absolute path
095       */
096      private static String getAbsolutePathFromRelativePath(String relativePath)
097      {
098        return new File(relativePath).getAbsolutePath();
099      }
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    }