1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.io;
17
18 import java.io.BufferedReader;
19 import java.io.File;
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.UnsupportedEncodingException;
25 import java.net.MalformedURLException;
26 import java.net.URL;
27 import java.net.URLEncoder;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.FileSystemException;
30 import java.nio.file.InvalidPathException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 import java.util.jar.JarEntry;
35 import java.util.jar.JarInputStream;
36
37 import org.apache.ibatis.logging.Log;
38 import org.apache.ibatis.logging.LogFactory;
39
40
41
42
43
44
45 public class DefaultVFS extends VFS {
46 private static final Log log = LogFactory.getLog(DefaultVFS.class);
47
48
49 private static final byte[] JAR_MAGIC = { 'P', 'K', 3, 4 };
50
51 @Override
52 public boolean isValid() {
53 return true;
54 }
55
56 @Override
57 public List<String> list(URL url, String path) throws IOException {
58 InputStream is = null;
59 try {
60 List<String> resources = new ArrayList<>();
61
62
63
64 URL jarUrl = findJarForResource(url);
65 if (jarUrl != null) {
66 is = jarUrl.openStream();
67 if (log.isDebugEnabled()) {
68 log.debug("Listing " + url);
69 }
70 resources = listResources(new JarInputStream(is), path);
71 } else {
72 List<String> children = new ArrayList<>();
73 try {
74 if (isJar(url)) {
75
76
77 is = url.openStream();
78 try (JarInputStream jarInput = new JarInputStream(is)) {
79 if (log.isDebugEnabled()) {
80 log.debug("Listing " + url);
81 }
82 File destinationDir = new File(path);
83 for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) {
84 if (log.isDebugEnabled()) {
85 log.debug("Jar entry: " + entry.getName());
86 }
87 File entryFile = new File(destinationDir, entry.getName()).getCanonicalFile();
88 if (!entryFile.getPath().startsWith(destinationDir.getCanonicalPath())) {
89 throw new IOException("Bad zip entry: " + entry.getName());
90 }
91 children.add(entry.getName());
92 }
93 }
94 } else {
95
96
97
98
99
100
101
102 is = url.openStream();
103 List<String> lines = new ArrayList<>();
104 try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
105 for (String line; (line = reader.readLine()) != null;) {
106 if (log.isDebugEnabled()) {
107 log.debug("Reader entry: " + line);
108 }
109 lines.add(line);
110 if (getResources(path + "/" + line).isEmpty()) {
111 lines.clear();
112 break;
113 }
114 }
115 } catch (InvalidPathException | FileSystemException e) {
116
117 lines.clear();
118 }
119 if (!lines.isEmpty()) {
120 if (log.isDebugEnabled()) {
121 log.debug("Listing " + url);
122 }
123 children.addAll(lines);
124 }
125 }
126 } catch (FileNotFoundException e) {
127
128
129
130
131 if (!"file".equals(url.getProtocol())) {
132
133 throw e;
134 }
135 File file = new File(url.getFile());
136 if (log.isDebugEnabled()) {
137 log.debug("Listing directory " + file.getAbsolutePath());
138 }
139 if (file.isDirectory()) {
140 if (log.isDebugEnabled()) {
141 log.debug("Listing " + url);
142 }
143 children = Arrays.asList(file.list());
144 }
145 }
146
147
148 String prefix = url.toExternalForm();
149 if (!prefix.endsWith("/")) {
150 prefix = prefix + "/";
151 }
152
153
154 for (String child : children) {
155 String resourcePath = path + "/" + child;
156 resources.add(resourcePath);
157 URL childUrl = new URL(prefix + child);
158 resources.addAll(list(childUrl, resourcePath));
159 }
160 }
161
162 return resources;
163 } finally {
164 if (is != null) {
165 try {
166 is.close();
167 } catch (Exception e) {
168
169 }
170 }
171 }
172 }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 protected List<String> listResources(JarInputStream jar, String path) throws IOException {
189
190 if (!path.startsWith("/")) {
191 path = "/" + path;
192 }
193 if (!path.endsWith("/")) {
194 path = path + "/";
195 }
196
197
198 List<String> resources = new ArrayList<>();
199 for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) {
200 if (!entry.isDirectory()) {
201
202 StringBuilder name = new StringBuilder(entry.getName());
203 if (name.charAt(0) != '/') {
204 name.insert(0, '/');
205 }
206
207
208 if (name.indexOf(path) == 0) {
209 if (log.isDebugEnabled()) {
210 log.debug("Found resource: " + name);
211 }
212
213 resources.add(name.substring(1));
214 }
215 }
216 }
217 return resources;
218 }
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233 protected URL findJarForResource(URL url) throws MalformedURLException {
234 if (log.isDebugEnabled()) {
235 log.debug("Find JAR URL: " + url);
236 }
237
238
239 boolean continueLoop = true;
240 while (continueLoop) {
241 try {
242 url = new URL(url.getFile());
243 if (log.isDebugEnabled()) {
244 log.debug("Inner URL: " + url);
245 }
246 } catch (MalformedURLException e) {
247
248 continueLoop = false;
249 }
250 }
251
252
253 StringBuilder jarUrl = new StringBuilder(url.toExternalForm());
254 int index = jarUrl.lastIndexOf(".jar");
255 if (index < 0) {
256 if (log.isDebugEnabled()) {
257 log.debug("Not a JAR: " + jarUrl);
258 }
259 return null;
260 }
261 jarUrl.setLength(index + 4);
262 if (log.isDebugEnabled()) {
263 log.debug("Extracted JAR URL: " + jarUrl);
264 }
265
266
267 try {
268 URL testUrl = new URL(jarUrl.toString());
269 if (isJar(testUrl)) {
270 return testUrl;
271 }
272
273 if (log.isDebugEnabled()) {
274 log.debug("Not a JAR: " + jarUrl);
275 }
276 jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
277 File file = new File(jarUrl.toString());
278
279
280 if (!file.exists()) {
281 try {
282 file = new File(URLEncoder.encode(jarUrl.toString(), StandardCharsets.UTF_8.name()));
283 } catch (UnsupportedEncodingException e) {
284 throw new RuntimeException("Unsupported encoding? UTF-8? That's impossible.");
285 }
286 }
287
288 if (file.exists()) {
289 if (log.isDebugEnabled()) {
290 log.debug("Trying real file: " + file.getAbsolutePath());
291 }
292 testUrl = file.toURI().toURL();
293 if (isJar(testUrl)) {
294 return testUrl;
295 }
296 }
297 } catch (MalformedURLException e) {
298 log.warn("Invalid JAR URL: " + jarUrl);
299 }
300
301 if (log.isDebugEnabled()) {
302 log.debug("Not a JAR: " + jarUrl);
303 }
304 return null;
305 }
306
307
308
309
310
311
312
313
314
315
316 protected String getPackagePath(String packageName) {
317 return packageName == null ? null : packageName.replace('.', '/');
318 }
319
320
321
322
323
324
325
326
327
328 protected boolean isJar(URL url) {
329 return isJar(url, new byte[JAR_MAGIC.length]);
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343 protected boolean isJar(URL url, byte[] buffer) {
344 try (InputStream is = url.openStream()) {
345 is.read(buffer, 0, JAR_MAGIC.length);
346 if (Arrays.equals(buffer, JAR_MAGIC)) {
347 if (log.isDebugEnabled()) {
348 log.debug("Found JAR: " + url);
349 }
350 return true;
351 }
352 } catch (Exception e) {
353
354 }
355
356 return false;
357 }
358 }