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 468654 2006-10-28 07:09:23Z minchau $
020     */
021    package org.apache.xml.serializer.utils;
022    
023    import java.io.File;
024    
025    import javax.xml.transform.TransformerException;
026    
027    import org.apache.xml.serializer.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     * 
037     * This class is a copy of the one in org.apache.xml.utils. 
038     * It exists to cut the serializers dependancy on that package.
039     *
040     * This class is not a public API, it is only public because it is
041     * used in org.apache.xml.serializer. 
042     * 
043     * @xsl.usage internal
044     */
045    public final class SystemIDResolver
046    {
047    
048      /**
049       * Get an absolute URI from a given relative URI (local path). 
050       * 
051       * <p>The relative URI is a local filesystem path. The path can be
052       * absolute or relative. If it is a relative path, it is resolved relative 
053       * to the system property "user.dir" if it is available; if not (i.e. in an 
054       * Applet perhaps which throws SecurityException) then we just return the
055       * relative path. The space and backslash characters are also replaced to
056       * generate a good absolute URI.</p>
057       *
058       * @param localPath The relative URI to resolve
059       *
060       * @return Resolved absolute URI
061       */
062      public static String getAbsoluteURIFromRelative(String localPath)
063      {
064        if (localPath == null || localPath.length() == 0)
065          return "";
066          
067        // If the local path is a relative path, then it is resolved against
068        // the "user.dir" system property.
069        String absolutePath = localPath;
070        if (!isAbsolutePath(localPath))
071        {
072          try 
073          {
074            absolutePath = getAbsolutePathFromRelativePath(localPath);
075          }
076          // user.dir not accessible from applet
077          catch (SecurityException se) 
078          {
079            return "file:" + localPath;
080          }
081        }
082    
083        String urlString;
084        if (null != absolutePath)
085        {
086          if (absolutePath.startsWith(File.separator))
087            urlString = "file://" + absolutePath;
088          else
089            urlString = "file:///" + absolutePath;        
090        }
091        else
092          urlString = "file:" + localPath;
093        
094        return replaceChars(urlString);
095      }
096      
097      /**
098       * Return an absolute path from a relative path.
099       *
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    }