1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.util.jar.JarEntry;
30 import java.util.jar.JarInputStream;
31 import java.util.jar.Manifest;
32
33 import org.eclipse.jetty.util.IO;
34 import org.eclipse.jetty.util.URIUtil;
35 import org.eclipse.jetty.util.log.Log;
36 import org.eclipse.jetty.util.log.Logger;
37
38
39
40 public class JarResource extends URLResource
41 {
42 private static final Logger LOG = Log.getLogger(JarResource.class);
43 protected JarURLConnection _jarConnection;
44
45
46 protected JarResource(URL url)
47 {
48 super(url,null);
49 }
50
51
52 protected JarResource(URL url, boolean useCaches)
53 {
54 super(url, null, useCaches);
55 }
56
57
58 @Override
59 public synchronized void close()
60 {
61 _jarConnection=null;
62 super.close();
63 }
64
65
66 @Override
67 protected synchronized boolean checkConnection()
68 {
69 super.checkConnection();
70 try
71 {
72 if (_jarConnection!=_connection)
73 newConnection();
74 }
75 catch(IOException e)
76 {
77 LOG.ignore(e);
78 _jarConnection=null;
79 }
80
81 return _jarConnection!=null;
82 }
83
84
85
86
87
88 protected void newConnection() throws IOException
89 {
90 _jarConnection=(JarURLConnection)_connection;
91 }
92
93
94
95
96
97 @Override
98 public boolean exists()
99 {
100 if (_urlString.endsWith("!/"))
101 return checkConnection();
102 else
103 return super.exists();
104 }
105
106
107 @Override
108 public File getFile()
109 throws IOException
110 {
111 return null;
112 }
113
114
115 @Override
116 public InputStream getInputStream()
117 throws java.io.IOException
118 {
119 checkConnection();
120 if (!_urlString.endsWith("!/"))
121 return new FilterInputStream(super.getInputStream())
122 {
123 @Override
124 public void close() throws IOException {this.in=IO.getClosedStream();}
125 };
126
127 URL url = new URL(_urlString.substring(4,_urlString.length()-2));
128 InputStream is = url.openStream();
129 return is;
130 }
131
132
133 @Override
134 public void copyTo(File directory)
135 throws IOException
136 {
137 if (!exists())
138 return;
139
140 if(LOG.isDebugEnabled())
141 LOG.debug("Extract "+this+" to "+directory);
142
143 String urlString = this.getURL().toExternalForm().trim();
144 int endOfJarUrl = urlString.indexOf("!/");
145 int startOfJarUrl = (endOfJarUrl >= 0?4:0);
146
147 if (endOfJarUrl < 0)
148 throw new IOException("Not a valid jar url: "+urlString);
149
150 URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
151 String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
152 boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
153
154 if (LOG.isDebugEnabled())
155 LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
156
157 try (InputStream is = jarFileURL.openConnection().getInputStream();
158 JarInputStream jin = new JarInputStream(is))
159 {
160 JarEntry entry;
161 boolean shouldExtract;
162 while((entry=jin.getNextJarEntry())!=null)
163 {
164 String entryName = entry.getName();
165 if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
166 {
167
168 if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
169 subEntryIsDir=true;
170
171
172
173 if (subEntryIsDir)
174 {
175
176
177
178
179 entryName = entryName.substring(subEntryName.length());
180 if (!entryName.equals(""))
181 {
182
183 shouldExtract = true;
184 }
185 else
186 shouldExtract = false;
187 }
188 else
189 shouldExtract = true;
190 }
191 else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
192 {
193
194
195 shouldExtract = false;
196 }
197 else
198 {
199
200 shouldExtract = true;
201 }
202
203 if (!shouldExtract)
204 {
205 if (LOG.isDebugEnabled())
206 LOG.debug("Skipping entry: "+entryName);
207 continue;
208 }
209
210 String dotCheck = entryName.replace('\\', '/');
211 dotCheck = URIUtil.canonicalPath(dotCheck);
212 if (dotCheck == null)
213 {
214 if (LOG.isDebugEnabled())
215 LOG.debug("Invalid entry: "+entryName);
216 continue;
217 }
218
219 File file=new File(directory,entryName);
220
221 if (entry.isDirectory())
222 {
223
224 if (!file.exists())
225 file.mkdirs();
226 }
227 else
228 {
229
230 File dir = new File(file.getParent());
231 if (!dir.exists())
232 dir.mkdirs();
233
234
235 try (OutputStream fout = new FileOutputStream(file))
236 {
237 IO.copy(jin,fout);
238 }
239
240
241 if (entry.getTime()>=0)
242 file.setLastModified(entry.getTime());
243 }
244 }
245
246 if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
247 {
248 Manifest manifest = jin.getManifest();
249 if (manifest != null)
250 {
251 File metaInf = new File (directory, "META-INF");
252 metaInf.mkdir();
253 File f = new File(metaInf, "MANIFEST.MF");
254 try (OutputStream fout = new FileOutputStream(f))
255 {
256 manifest.write(fout);
257 }
258 }
259 }
260 }
261 }
262
263 public static Resource newJarResource(Resource resource) throws IOException
264 {
265 if (resource instanceof JarResource)
266 return resource;
267 return Resource.newResource("jar:" + resource + "!/");
268 }
269 }