View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.util.resource;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FilterInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.net.JarURLConnection;
28  import java.net.URL;
29  import java.net.URLConnection;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarInputStream;
32  import java.util.jar.Manifest;
33  
34  import org.eclipse.jetty.util.IO;
35  import org.eclipse.jetty.util.URIUtil;
36  import org.eclipse.jetty.util.log.Log;
37  import org.eclipse.jetty.util.log.Logger;
38  
39  
40  /* ------------------------------------------------------------ */
41  public class JarResource extends URLResource
42  {
43      private static final Logger LOG = Log.getLogger(JarResource.class);
44      protected JarURLConnection _jarConnection;
45      
46      /* -------------------------------------------------------- */
47      protected JarResource(URL url)
48      {
49          super(url,null);
50      }
51  
52      /* ------------------------------------------------------------ */
53      protected JarResource(URL url, boolean useCaches)
54      {
55          super(url, null, useCaches);
56      }
57      
58      /* ------------------------------------------------------------ */
59      @Override
60      public synchronized void close()
61      {
62          _jarConnection=null;
63          super.close();
64      }
65      
66      /* ------------------------------------------------------------ */
67      @Override
68      protected synchronized boolean checkConnection()
69      {
70          super.checkConnection();
71          try
72          {
73              if (_jarConnection!=_connection)
74                  newConnection();
75          }
76          catch(IOException e)
77          {
78              LOG.ignore(e);
79              _jarConnection=null;
80          }
81          
82          return _jarConnection!=null;
83      }
84  
85      /* ------------------------------------------------------------ */
86      /**
87       * @throws IOException Sub-classes of <code>JarResource</code> may throw an IOException (or subclass) 
88       */
89      protected void newConnection() throws IOException
90      {
91          _jarConnection=(JarURLConnection)_connection;
92      }
93      
94      /* ------------------------------------------------------------ */
95      /**
96       * Returns true if the represented resource exists.
97       */
98      @Override
99      public boolean exists()
100     {
101         if (_urlString.endsWith("!/"))
102             return checkConnection();
103         else
104             return super.exists();
105     }    
106 
107     /* ------------------------------------------------------------ */
108     @Override
109     public File getFile()
110         throws IOException
111     {
112         return null;
113     }
114     
115     /* ------------------------------------------------------------ */
116     @Override
117     public InputStream getInputStream()
118         throws java.io.IOException
119     {     
120         checkConnection();
121         if (!_urlString.endsWith("!/"))
122             return new FilterInputStream(getInputStream(false)) 
123             {
124                 @Override
125                 public void close() throws IOException {this.in=IO.getClosedStream();}
126             };
127 
128         URL url = new URL(_urlString.substring(4,_urlString.length()-2));      
129         InputStream is = url.openStream();
130         return is;
131     }
132     
133  
134     
135     
136     /* ------------------------------------------------------------ */
137     @Override
138     public void copyTo(File directory)
139         throws IOException
140     {
141         if (!exists())
142             return;
143         
144         if(LOG.isDebugEnabled())
145             LOG.debug("Extract "+this+" to "+directory);
146         
147         String urlString = this.getURL().toExternalForm().trim();
148         int endOfJarUrl = urlString.indexOf("!/");
149         int startOfJarUrl = (endOfJarUrl >= 0?4:0);
150         
151         if (endOfJarUrl < 0)
152             throw new IOException("Not a valid jar url: "+urlString);
153         
154         URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
155         String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
156         boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
157       
158         if (LOG.isDebugEnabled()) 
159             LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
160         URLConnection c = jarFileURL.openConnection();
161         c.setUseCaches(false);
162         try (InputStream is = c.getInputStream();
163              JarInputStream jin = new JarInputStream(is))
164         {
165             JarEntry entry;
166             boolean shouldExtract;
167             while((entry=jin.getNextJarEntry())!=null)
168             {
169                 String entryName = entry.getName();
170                 if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
171                 {
172                     // is the subentry really a dir?
173                     if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
174                             subEntryIsDir=true;
175 
176                     //if there is a particular subEntry that we are looking for, only
177                     //extract it.
178                     if (subEntryIsDir)
179                     {
180                         //if it is a subdirectory we are looking for, then we
181                         //are looking to extract its contents into the target
182                         //directory. Remove the name of the subdirectory so
183                         //that we don't wind up creating it too.
184                         entryName = entryName.substring(subEntryName.length());
185                         if (!entryName.equals(""))
186                         {
187                             //the entry is
188                             shouldExtract = true;
189                         }
190                         else
191                             shouldExtract = false;
192                     }
193                     else
194                         shouldExtract = true;
195                 }
196                 else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
197                 {
198                     //there is a particular entry we are looking for, and this one
199                     //isn't it
200                     shouldExtract = false;
201                 }
202                 else
203                 {
204                     //we are extracting everything
205                     shouldExtract =  true;
206                 }
207 
208                 if (!shouldExtract)
209                 {
210                     if (LOG.isDebugEnabled())
211                         LOG.debug("Skipping entry: "+entryName);
212                     continue;
213                 }
214 
215                 String dotCheck = entryName.replace('\\', '/');
216                 dotCheck = URIUtil.canonicalPath(dotCheck);
217                 if (dotCheck == null)
218                 {
219                     if (LOG.isDebugEnabled())
220                         LOG.debug("Invalid entry: "+entryName);
221                     continue;
222                 }
223 
224                 File file=new File(directory,entryName);
225 
226                 if (entry.isDirectory())
227                 {
228                     // Make directory
229                     if (!file.exists())
230                         file.mkdirs();
231                 }
232                 else
233                 {
234                     // make directory (some jars don't list dirs)
235                     File dir = new File(file.getParent());
236                     if (!dir.exists())
237                         dir.mkdirs();
238 
239                     // Make file
240                     try (OutputStream fout = new FileOutputStream(file))
241                     {
242                         IO.copy(jin,fout);
243                     }
244 
245                     // touch the file.
246                     if (entry.getTime()>=0)
247                         file.setLastModified(entry.getTime());
248                 }
249             }
250 
251             if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
252             {
253                 Manifest manifest = jin.getManifest();
254                 if (manifest != null)
255                 {
256                     File metaInf = new File (directory, "META-INF");
257                     metaInf.mkdir();
258                     File f = new File(metaInf, "MANIFEST.MF");
259                     try (OutputStream fout = new FileOutputStream(f))
260                     {
261                         manifest.write(fout);
262                     }
263                 }
264             }
265         }
266     }   
267     
268     public static Resource newJarResource(Resource resource) throws IOException
269     {
270         if (resource instanceof JarResource)
271             return resource;
272         return Resource.newResource("jar:" + resource + "!/");
273     }
274 }