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.view.facelets.util;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.JarURLConnection;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.net.URLDecoder;
28  import java.util.Arrays;
29  import java.util.Enumeration;
30  import java.util.HashSet;
31  import java.util.LinkedHashSet;
32  import java.util.Set;
33  import java.util.jar.JarEntry;
34  import java.util.jar.JarFile;
35  import java.util.zip.ZipEntry;
36  import java.util.zip.ZipInputStream;
37  
38  import org.apache.myfaces.shared.util.ClassUtils;
39  import java.nio.ByteBuffer;
40  import java.nio.charset.Charset;
41  import java.security.AccessController;
42  import java.security.PrivilegedAction;
43  /**
44   * @author Jacob Hookom
45   * @author Roland Huss
46   * @author Ales Justin (ales.justin@jboss.org)
47   * @version $Id$
48   */
49  public final class Classpath
50  {
51      private static final Charset UTF8 = Charset.forName("UTF-8");
52      private static final Set<String> EXCLUDED_PREFIX_SET = new HashSet<String>(Arrays.asList("rar:", "sar:"));
53      private static final Set<String> EXCLUDED_SUFFIX_SET = new HashSet<String>(Arrays.asList(".rar", ".sar"));
54  
55      private Classpath()
56      {
57      }
58  
59      public static URL[] search(String prefix, String suffix) throws IOException
60      {
61          return search(ClassUtils.getContextClassLoader(), prefix, suffix);
62      }
63  
64      public static URL[] search(ClassLoader loader, String prefix, String suffix) throws IOException
65      {
66          Set<URL> all = new LinkedHashSet<URL>();
67  
68          _searchResource(all, loader, prefix, prefix, suffix);
69          _searchResource(all, loader, prefix + "MANIFEST.MF", prefix, suffix);
70  
71          URL[] urlArray = (URL[]) all.toArray(new URL[all.size()]);
72  
73          return urlArray;
74      }
75  
76      private static void _searchResource(Set<URL> result, ClassLoader loader, String resource, String prefix,
77                                          String suffix) throws IOException
78      {
79          for (Enumeration<URL> urls = loader.getResources(resource); urls.hasMoreElements();)
80          {
81              URL url = urls.nextElement();
82              URLConnection conn = url.openConnection();
83              conn.setUseCaches(false);
84              conn.setDefaultUseCaches(false);
85  
86              try (JarFile jar = (conn instanceof JarURLConnection) ? 
87                  ((JarURLConnection) conn).getJarFile() : _getAlternativeJarFile(url))
88              {
89                  if (jar != null)
90                  {
91                      _searchJar(loader, result, jar, prefix, suffix);
92                  }
93                  else
94                  {
95                      if (!_searchDir(result, new File(URLDecoder.decode(url.getFile(), "UTF-8")), suffix))
96                      {
97                          _searchFromURL(result, prefix, suffix, url);
98                      }
99                  }
100             }
101             catch (Throwable e) 
102             {
103                 // This can happen if the classloader provided us a URL that it thinks exists
104                 // but really doesn't.  In particular, if a JAR contains META-INF/MANIFEST.MF
105                 // but not META-INF/, some classloaders may incorrectly report that META-INF/
106                 // exists and we'll end up here.  Just ignore this case.
107                 continue;
108             }
109         }
110     }
111 
112     private static boolean _searchDir(Set<URL> result, File dir, String suffix) throws IOException
113     {
114         boolean dirExists = false;
115         if (System.getSecurityManager() != null)
116         {
117             final File finalDir = dir;
118             dirExists = (Boolean) AccessController.doPrivileged(new PrivilegedAction()
119             {
120                 public Object run() 
121                 {
122                     return finalDir.exists();
123                 }
124             });
125         }  
126         else
127         {
128             dirExists = dir.exists();
129         }
130         if (dirExists && dir.isDirectory())
131         {
132             File[] dirFiles = dir.listFiles();
133             if (dirFiles != null) 
134             {
135                 for (File file : dirFiles)
136                 {
137                     String path = file.getAbsolutePath();
138                     if (file.isDirectory())
139                     {
140                         _searchDir(result, file, suffix);
141                     }
142                     else if (path.endsWith(suffix))
143                     {
144                         result.add(file.toURI().toURL());
145                     }
146                 }
147                 return true;
148             }
149         }
150 
151         return false;
152     }
153 
154     /**
155      * Search from URL. Fall back on prefix tokens if not able to read from original url param.
156      * 
157      * @param result
158      *            the result urls
159      * @param prefix
160      *            the current prefix
161      * @param suffix
162      *            the suffix to match
163      * @param url
164      *            the current url to start search
165      * @throws IOException
166      *             for any error
167      */
168     private static void _searchFromURL(Set<URL> result, String prefix, String suffix, URL url) throws IOException
169     {
170         boolean done = false;
171 
172         InputStream is = _getInputStream(url);
173         if (is != null)
174         {
175             try
176             {
177                 ZipInputStream zis;
178                 if (is instanceof ZipInputStream)
179                 {
180                     zis = (ZipInputStream) is;
181                 }
182                 else
183                 {
184                     zis = new ZipInputStream(is);
185                 }
186 
187                 try
188                 {
189                     ZipEntry entry = zis.getNextEntry();
190                     // initial entry should not be null
191                     // if we assume this is some inner jar
192                     done = entry != null;
193 
194                     while (entry != null)
195                     {
196                         String entryName = entry.getName();
197                         if (entryName.endsWith(suffix))
198                         {
199                             result.add(new URL(url.toExternalForm() + entryName));
200                         }
201 
202                         entry = zis.getNextEntry();
203                     }
204                 }
205                 finally
206                 {
207                     zis.close();
208                 }
209             }
210             catch (Exception ignore)
211             {
212             }
213         }
214 
215         if (!done && prefix.length() > 0)
216         {
217             // we add '/' at the end since join adds it as well
218             String urlString = url.toExternalForm() + "/";
219 
220             String[] split = prefix.split("/");
221 
222             prefix = _join(split, true);
223 
224             String end = _join(split, false);
225             urlString = urlString.substring(0, urlString.lastIndexOf(end));
226             if (isExcludedPrefix(urlString))
227             {
228                 // excluded URL found, ignore it
229                 return;
230             }
231             url = new URL(urlString);
232 
233             _searchFromURL(result, prefix, suffix, url);
234         }
235     }
236 
237     /**
238      * Join tokens, exlude last if param equals true.
239      * 
240      * @param tokens
241      *            the tokens
242      * @param excludeLast
243      *            do we exclude last token
244      * @return joined tokens
245      */
246     private static String _join(String[] tokens, boolean excludeLast)
247     {
248         StringBuilder join = new StringBuilder();
249         int length = tokens.length - (excludeLast ? 1 : 0);
250         for (int i = 0; i < length; i++)
251         {
252             join.append(tokens[i]).append("/");
253         }
254 
255         return join.toString();
256     }
257 
258     /**
259      * Open input stream from url. Ignore any errors.
260      * 
261      * @param url
262      *            the url to open
263      * @return input stream or null if not possible
264      */
265     private static InputStream _getInputStream(URL url)
266     {
267         try
268         {
269             return url.openStream();
270         }
271         catch (Throwable t)
272         {
273             return null;
274         }
275     }
276 
277     /**
278      * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
279      * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
280      * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
281      */
282     private static JarFile _getAlternativeJarFile(URL url) throws IOException
283     {
284         String urlFile = url.getFile();
285 
286         // Find suffix prefixed by "!/" on Weblogic
287         int wlIndex = urlFile.indexOf("!/");
288         // Find suffix prefixed by '!' on OC4J
289         int oc4jIndex = urlFile.indexOf('!');
290         // Take the first found suffix
291         int separatorIndex = wlIndex == -1 && oc4jIndex == -1 ? -1 : wlIndex < oc4jIndex ? wlIndex : oc4jIndex;
292 
293         if (separatorIndex != -1)
294         {
295             String jarFileUrl = urlFile.substring(0, separatorIndex);
296             // And trim off any "file:" prefix.
297             if (jarFileUrl.startsWith("file:"))
298             {
299                 jarFileUrl = jarFileUrl.substring("file:".length());
300             }
301             // make sure this is a valid file system path by removing escaping of white-space characters, etc. 
302             jarFileUrl = decodeFilesystemUrl(jarFileUrl);
303             if (isExcludedPrefix(jarFileUrl) || isExcludedSuffix(jarFileUrl))
304             {
305                 // excluded URL found, ignore it
306                 return null;
307             }
308             return new JarFile(jarFileUrl);
309         }
310 
311         return null;
312     }
313 
314     private static boolean isExcludedPrefix(String url)
315     {
316         return EXCLUDED_PREFIX_SET.contains(url.substring(0, 4));
317     }
318 
319     private static boolean isExcludedSuffix(String url)
320     {
321         int length = url.length();
322         return EXCLUDED_SUFFIX_SET.contains(url.substring(length - 4, length));
323     }
324 
325     private static void _searchJar(ClassLoader loader, Set<URL> result, JarFile file, String prefix, String suffix)
326             throws IOException
327     {
328         Enumeration<JarEntry> e = file.entries();
329         while (e.hasMoreElements())
330         {
331             try
332             {
333                 String name = e.nextElement().getName();
334                 if (name.startsWith(prefix) && name.endsWith(suffix))
335                 {
336                     Enumeration<URL> e2 = loader.getResources(name);
337                     while (e2.hasMoreElements())
338                     {
339                         result.add(e2.nextElement());
340                     }
341                 }
342             }
343             catch (Throwable t)
344             {
345                 // shallow
346             }
347         }
348     }
349 
350     private static String decodeFilesystemUrl(String url)
351     {
352         //borrowed from commons-io FileUtils.
353         String decoded = url;
354         if (url != null && url.indexOf('%') >= 0)
355         {
356             int n = url.length();
357             StringBuffer buffer = new StringBuffer();
358             ByteBuffer bytes = ByteBuffer.allocate(n);
359             for (int i = 0; i < n; )
360             {
361                 if (url.charAt(i) == '%')
362                 {
363                     try
364                     {
365                         do
366                         {
367                             byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16);
368                             bytes.put(octet);
369                             i += 3;
370                         } while (i < n && url.charAt(i) == '%');
371                         continue;
372                     }
373                     catch (RuntimeException e)
374                     {
375                         // malformed percent-encoded octet, fall through and
376                         // append characters literally
377                     }
378                     finally
379                     {
380                         if (bytes.position() > 0)
381                         {
382                             bytes.flip();
383                             buffer.append(UTF8.decode(bytes).toString());
384                             bytes.clear();
385                         }
386                     }
387                 }
388                 buffer.append(url.charAt(i++));
389             }
390             decoded = buffer.toString();
391         }
392         return decoded;
393     }
394 
395 }