View Javadoc

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
7    * "License"); you may not use this file except in compliance
8    * with the License.  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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.resource;
20  
21  import java.io.IOException;
22  
23  import java.net.URL;
24  
25  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
26  
27  
28  /**
29   * A resource loader implementation which loads resources
30   * using the context class loader.  The returned resource URL will be null
31   * for paths that attempt to access paths outside the root directory by having
32   * ".." in the path.
33   *
34   */
35  public class ClassLoaderResourceLoader extends ResourceLoader
36  {
37    /**
38     * Constructs a new root ClassLoaderResourceLoader.
39     */
40    public ClassLoaderResourceLoader()
41    {
42      this((String)null);
43    }
44  
45    /**
46     * Constructs a new ClassLoaderResourceLoader with specified parent.
47     *
48     * @param parent  the parent resource loader
49     */
50    public ClassLoaderResourceLoader(
51      ResourceLoader parent)
52    {
53      this(null, parent);
54    }
55  
56    /**
57     * Constructs a new root ClassLoaderResourceLoader with specified top
58     * level resource package.
59     *
60     * @param rootPackage  the top level package used to interpret resource paths
61     * For example, it could be "META-INF".
62     */
63    public ClassLoaderResourceLoader(
64      String rootPackage)
65    {
66      _resourcePrefix = _getResourcePrefix(rootPackage);
67    }
68  
69    /**
70     * Constructs a new root ClassLoaderResourceLoader with specified top
71     * level resource package and parent resource loader.
72     *
73     * @param rootPackage  the top level package used to interpret resource paths
74     * @param parent  the parent resource loader
75     */
76    public ClassLoaderResourceLoader(
77      String rootPackage,
78      ResourceLoader parent)
79    {
80      super(parent);
81      _resourcePrefix = _getResourcePrefix(rootPackage);
82    }
83  
84    @Override
85    protected URL findResource(
86      String path) throws IOException
87    {
88      // Strip off leading slash, since this can
89      // trip up ClassLoader.getResource().
90      if (path.charAt(0) == '/')
91        path = path.substring(1);
92  
93      if (_resourcePrefix != null)
94      {
95        if (_resourcePrefix.endsWith("/"))
96        {
97          path = _resourcePrefix + path;
98        }
99        else
100       {
101         path = _resourcePrefix + "/" + path;
102       }
103     }
104 
105     if (!_isPathWithinRoot(path))
106     {
107       _LOG.severe("RESOURCE_PATH_OUTSIDE_ROOT", path);
108       return null;
109     }
110     
111     return getClassLoader().getResource(path);
112   }
113 
114   /**
115    * Returns the ClassLoader to use when looking up resources under the top
116    * level package.  By default, this is the context class loader.
117    *
118    * @return the ClassLoader used to lookup resources
119    */
120   protected ClassLoader getClassLoader()
121   {
122     return Thread.currentThread().getContextClassLoader();
123   }
124 
125   /**
126    * Converts root package into a resource prefix.  For example, converts
127    * the package "org.example" into resource prefix "org/example".
128    *
129    * @param rootPackage  the root package
130    *
131    * @return the resource prefix
132    */
133   static private String _getResourcePrefix(
134     String rootPackage)
135   {
136     if (rootPackage == null ||
137         rootPackage.length() == 0)
138     {
139       return null;
140     }
141 
142     return rootPackage.replace('.', '/');
143   }
144   
145   /**
146    * Checks to see if the path when ".." are resolved out is within the root.
147    * Returns true if the path is within the root, otherwise returns false.
148    * e.g., /afr/../../foo.gif is out of root. It gets resolved to /../foo.gif.
149    * /afr/../tmp/../xyz/foo.gif is within the root. It gets resolved to
150    * /xyz/foo.gif
151    * 
152    * @param path -String the path
153    * @return boolean true if the path does not get resolved outside the root
154    */
155   private static boolean _isPathWithinRoot(String path)
156   {
157     if (path.indexOf("..") == -1)
158       return true;
159       
160     // It would be strange if the path has ".." in it because
161     // the browsers (IE7 and Firefox 1.5) resolve ".." out of the path before
162     // requesting the resource.
163     // Therefore we warn if we find a path with "..".
164     // Then, we try to resolve it ourselves to find out if it is outside
165     // the root.
166     _LOG.warning("RESOURCE_PATH_DOTS", path);
167     
168     while (path != null && path.length() > 0 )
169     {
170       // as soon as it starts with .. or /.., we know we are out of bounds
171       if (path.startsWith(".."))
172         return false;
173       int index = path.indexOf("/../");
174       if (index == 0)
175         return false;
176     
177       // couldn't find "/../" in the path, so we are within the root
178       if (index == -1)
179         return true;
180     
181       // resolve out the first match of "/../" by taking it out as well
182       // as the /foo/ before it. 
183       String beginning = path.substring(0, index);
184       String end = path.substring(index+4);
185 
186       int lastIndex = beginning.lastIndexOf("/");
187       if (lastIndex == -1)
188       {
189         //could not find / in the beginning string, so strip the entire thing
190         path = end;
191       }
192       else
193       {
194         path = beginning.substring(0, lastIndex+1) +  end;
195       }
196     } 
197     
198     return true;
199   }
200   
201 
202   private final String _resourcePrefix;
203   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
204     ClassLoaderResourceLoader.class);
205 }