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 }