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(getInputStream(false))
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
134
135
136 @Override
137 public void copyTo(File directory)
138 throws IOException
139 {
140 if (!exists())
141 return;
142
143 if(LOG.isDebugEnabled())
144 LOG.debug("Extract "+this+" to "+directory);
145
146 String urlString = this.getURL().toExternalForm().trim();
147 int endOfJarUrl = urlString.indexOf("!/");
148 int startOfJarUrl = (endOfJarUrl >= 0?4:0);
149
150 if (endOfJarUrl < 0)
151 throw new IOException("Not a valid jar url: "+urlString);
152
153 URL jarFileURL = new URL(urlString.substring(startOfJarUrl, endOfJarUrl));
154 String subEntryName = (endOfJarUrl+2 < urlString.length() ? urlString.substring(endOfJarUrl + 2) : null);
155 boolean subEntryIsDir = (subEntryName != null && subEntryName.endsWith("/")?true:false);
156
157 if (LOG.isDebugEnabled())
158 LOG.debug("Extracting entry = "+subEntryName+" from jar "+jarFileURL);
159
160 try (InputStream is = jarFileURL.openConnection().getInputStream();
161 JarInputStream jin = new JarInputStream(is))
162 {
163 JarEntry entry;
164 boolean shouldExtract;
165 while((entry=jin.getNextJarEntry())!=null)
166 {
167 String entryName = entry.getName();
168 if ((subEntryName != null) && (entryName.startsWith(subEntryName)))
169 {
170
171 if (!subEntryIsDir && subEntryName.length()+1==entryName.length() && entryName.endsWith("/"))
172 subEntryIsDir=true;
173
174
175
176 if (subEntryIsDir)
177 {
178
179
180
181
182 entryName = entryName.substring(subEntryName.length());
183 if (!entryName.equals(""))
184 {
185
186 shouldExtract = true;
187 }
188 else
189 shouldExtract = false;
190 }
191 else
192 shouldExtract = true;
193 }
194 else if ((subEntryName != null) && (!entryName.startsWith(subEntryName)))
195 {
196
197
198 shouldExtract = false;
199 }
200 else
201 {
202
203 shouldExtract = true;
204 }
205
206 if (!shouldExtract)
207 {
208 if (LOG.isDebugEnabled())
209 LOG.debug("Skipping entry: "+entryName);
210 continue;
211 }
212
213 String dotCheck = entryName.replace('\\', '/');
214 dotCheck = URIUtil.canonicalPath(dotCheck);
215 if (dotCheck == null)
216 {
217 if (LOG.isDebugEnabled())
218 LOG.debug("Invalid entry: "+entryName);
219 continue;
220 }
221
222 File file=new File(directory,entryName);
223
224 if (entry.isDirectory())
225 {
226
227 if (!file.exists())
228 file.mkdirs();
229 }
230 else
231 {
232
233 File dir = new File(file.getParent());
234 if (!dir.exists())
235 dir.mkdirs();
236
237
238 try (OutputStream fout = new FileOutputStream(file))
239 {
240 IO.copy(jin,fout);
241 }
242
243
244 if (entry.getTime()>=0)
245 file.setLastModified(entry.getTime());
246 }
247 }
248
249 if ((subEntryName == null) || (subEntryName != null && subEntryName.equalsIgnoreCase("META-INF/MANIFEST.MF")))
250 {
251 Manifest manifest = jin.getManifest();
252 if (manifest != null)
253 {
254 File metaInf = new File (directory, "META-INF");
255 metaInf.mkdir();
256 File f = new File(metaInf, "MANIFEST.MF");
257 try (OutputStream fout = new FileOutputStream(f))
258 {
259 manifest.write(fout);
260 }
261 }
262 }
263 }
264 }
265
266 public static Resource newJarResource(Resource resource) throws IOException
267 {
268 if (resource instanceof JarResource)
269 return resource;
270 return Resource.newResource("jar:" + resource + "!/");
271 }
272 }