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.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
88
89 protected void newConnection() throws IOException
90 {
91 _jarConnection=(JarURLConnection)_connection;
92 }
93
94
95
96
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
173 if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
174 subEntryIsDir=true;
175
176
177
178 if (subEntryIsDir)
179 {
180
181
182
183
184 entryName = entryName.substring(subEntryName.length());
185 if (!entryName.equals(""))
186 {
187
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
199
200 shouldExtract = false;
201 }
202 else
203 {
204
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
229 if (!file.exists())
230 file.mkdirs();
231 }
232 else
233 {
234
235 File dir = new File(file.getParent());
236 if (!dir.exists())
237 dir.mkdirs();
238
239
240 try (OutputStream fout = new FileOutputStream(file))
241 {
242 IO.copy(jin,fout);
243 }
244
245
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 }