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