-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathzipimporter.java
More file actions
582 lines (515 loc) · 18.7 KB
/
zipimporter.java
File metadata and controls
582 lines (515 loc) · 18.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
/* Copyright (c) 2007 Jython Developers */
package org.python.modules.zipimport;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.python.core.ArgParser;
import org.python.core.Py;
import org.python.core.PyDictionary;
import org.python.core.PyException;
import org.python.core.PyInteger;
import org.python.core.PyLong;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PySystemState;
import org.python.core.PyTuple;
import org.python.core.PyType;
import org.python.core.PyUnicode;
import org.python.core.Traverseproc;
import org.python.core.Visitproc;
import org.python.core.imp;
import org.python.core.util.FileUtil;
import org.python.core.util.StringUtil;
import org.python.core.util.importer;
import org.python.expose.ExposedGet;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
import org.python.expose.ExposedType;
/**
* Import Python modules and packages from ZIP-format archives.
*
* @author Philip Jenvey
*/
@ExposedType(name = "zipimport.zipimporter", base = PyObject.class)
public class zipimporter extends importer<PyObject> implements Traverseproc {
public static final PyType TYPE = PyType.fromClass(zipimporter.class);
@ExposedGet
public static final PyString __doc__ = new PyString(
"zipimporter(archivepath) -> zipimporter object\n" +
"\n" +
"Create a new zipimporter instance. 'archivepath' must be a path to\n" +
"a zipfile. ZipImportError is raised if 'archivepath' doesn't point to\n" +
"a valid Zip archive.");
private static Logger log = Logger.getLogger("org.python.import");
/** Path to the Zip archive */
public String archive;
/** Path to the Zip archive as FS-encoded <code>str</code>. */
@ExposedGet(name = "archive")
public PyString getArchive() {
return Py.fileSystemEncode(archive);
}
/** File prefix: "a/sub/directory/" */
@ExposedGet
public String prefix;
/** Dict with file info {path: tocEntry} */
@ExposedGet(name = "_files")
public PyObject files;
/** The PySystemState this zipimporter is associated with */
private PySystemState sys;
public zipimporter() {
super();
}
public zipimporter(PyType subType) {
super(subType);
}
public zipimporter(String path) {
super();
zipimporter___init__(path);
}
@ExposedNew
@ExposedMethod
final void zipimporter___init__(PyObject[] args, String[] kwds) {
ArgParser ap = new ArgParser("__init__", args, kwds, new String[] {"path"});
String path = imp.fileSystemDecode(ap.getPyObject(0));
zipimporter___init__(path);
}
private void zipimporter___init__(String path) {
if (path == null || path.length() == 0) {
throw zipimport.ZipImportError("archive path is empty");
}
File pathFile = new File(path);
sys = Py.getSystemState();
prefix = "";
while (true) {
File fullPathFile = new File(sys.getPath(pathFile.getPath()));
try {
if (fullPathFile.isFile()) {
archive = pathFile.getPath();
break;
}
} catch (SecurityException se) {
// continue
}
// back up one path element
File parentFile = pathFile.getParentFile();
if (parentFile == null) {
break;
}
prefix = pathFile.getName() + File.separator + prefix;
pathFile = parentFile;
}
if (archive != null) {
PyUnicode archivePath = Py.newUnicode(archive);
files = zipimport._zip_directory_cache.__finditem__(archivePath);
if (files == null) {
files = readDirectory(archive);
zipimport._zip_directory_cache.__setitem__(archivePath, files);
}
} else {
throw zipimport.ZipImportError("not a Zip file: " + path);
}
if (prefix != "" && !prefix.endsWith(File.separator)) {
prefix += File.separator;
}
}
public PyObject find_module(String fullname) {
return zipimporter_find_module(fullname, null);
}
/**
* Find the module for the fully qualified name.
*
* @param fullname the fully qualified name of the module
* @param path if not installed on the meta-path None or a module path
* @return a loader instance if this importer can load the module, None
* otherwise
*/
public PyObject find_module(String fullname, String path) {
return zipimporter_find_module(fullname, path);
}
@ExposedMethod(defaults = "null")
final PyObject zipimporter_find_module(String fullname, String path) {
return importer_find_module(fullname, path);
}
/**
* Load a module for the fully qualified name.
*
* @param fullname the fully qualified name of the module
* @return a loaded PyModule
*/
public PyObject load_module(String fullname) {
return zipimporter_load_module(fullname);
}
@ExposedMethod
final PyObject zipimporter_load_module(String fullname) {
return importer_load_module(fullname);
}
/**
* Return the uncompressed data for the file at the specified path
* as bytes.
*
* @param path a String path name within the archive
* @return a String of data in binary mode (no CRLF)
*/
@Override
public String get_data(String path) {
return zipimporter_get_data(Py.newUnicode(path));
}
@ExposedMethod
final String zipimporter_get_data(PyObject opath) {
String path = Py.fileSystemDecode(opath);
int len = archive.length();
if (len < path.length() && path.startsWith(archive + File.separator)) {
path = path.substring(len + 1);
}
PyObject tocEntry = files.__finditem__(path);
if (tocEntry == null) {
throw Py.IOError(path);
}
Bundle zipBundle = makeBundle(path, tocEntry);
byte[] data;
try {
data = FileUtil.readBytes(zipBundle.inputStream);
} catch (IOException ioe) {
throw Py.IOError(ioe);
} finally {
zipBundle.close();
}
return StringUtil.fromBytes(data);
}
/**
* Return a boolean signifying whether the module is a package or
* not.
*
* @param fullname the fully qualified name of the module
* @return a boolean describing if the module is a package
*/
public boolean is_package(String fullname) {
return zipimporter_is_package(fullname);
}
@ExposedMethod
final boolean zipimporter_is_package(String fullname) {
ModuleInfo moduleInfo = getModuleInfo(fullname);
if (moduleInfo == ModuleInfo.NOT_FOUND) {
throw zipimport.ZipImportError(String.format("can't find module '%s'", fullname));
}
return moduleInfo == ModuleInfo.PACKAGE;
}
/**
* Return the code object associated with the module.
*
* @param fullname the fully qualified name of the module
* @return the module's PyCode object or None
*/
public PyObject get_code(String fullname) {
return zipimporter_get_code(fullname);
}
@ExposedMethod
final PyObject zipimporter_get_code(String fullname) {
ModuleCodeData moduleCodeData = getModuleCode(fullname);
if (moduleCodeData != null) {
return moduleCodeData.code;
}
return Py.None;
}
public PyObject get_filename(String fullname) {
return zipimporter_get_filename(fullname);
}
@ExposedMethod
final PyObject zipimporter_get_filename(String fullname) {
ModuleCodeData moduleCodeData = getModuleCode(fullname);
if (moduleCodeData != null) {
// File names generally expected in the FS encoding at the Python level
return Py.fileSystemEncode(moduleCodeData.path);
}
return Py.None;
}
/**
* Return the source code for the module as a string (using
* newline characters for line endings)
*
* @param fullname the fully qualified name of the module
* @return a String of the module's source code or null
*/
public String get_source(String fullname) {
return zipimporter_get_source(fullname);
}
@ExposedMethod
final String zipimporter_get_source(String fullname) {
ModuleInfo moduleInfo = getModuleInfo(fullname);
if (moduleInfo == ModuleInfo.ERROR) {
return null;
}
if (moduleInfo == ModuleInfo.NOT_FOUND) {
throw zipimport.ZipImportError(String.format("can't find module '%s'", fullname));
}
String path = makeFilename(fullname);
if (moduleInfo == ModuleInfo.PACKAGE) {
path += File.separator + "__init__.py";
} else {
path += ".py";
}
PyObject tocEntry = files.__finditem__(path);
return (tocEntry == null) ? null : get_data(path);
}
/**
* Given a path to a compressed file in the archive, return the
* file's (uncompressed) data stream in a ZipBundle.
*
* @param datapath file's filename inside of the archive
* @return a ZipBundle with an InputStream to the file's
* uncompressed data
*/
@Override
public ZipBundle makeBundle(String datapath, PyObject entry) {
datapath = datapath.replace(File.separatorChar, '/');
ZipFile zipArchive;
try {
zipArchive = new ZipFile(new File(sys.getPath(archive)));
} catch (IOException ioe) {
throw zipimport.ZipImportError("zipimport: can not open file: " + archive);
}
ZipEntry dataEntry = zipArchive.getEntry(datapath);
try {
return new ZipBundle(zipArchive, zipArchive.getInputStream(dataEntry));
} catch (IOException ioe) {
log.log(Level.FINE, "zipimporter.getDataStream exception: {0}", ioe.toString());
throw zipimport.ZipImportError("zipimport: can not open file: " + archive);
}
}
@Override
protected long getSourceMtime(String path) {
String sourcePath = path.substring(0, path.length() - 9) + ".py";
PyObject sourceTocEntry = files.__finditem__(sourcePath);
if (sourceTocEntry == null) {
return -1;
}
int time;
int date;
try {
time = sourceTocEntry.__finditem__(5).asInt();
date = sourceTocEntry.__finditem__(6).asInt();
} catch (PyException pye) {
if (!pye.match(Py.TypeError)) {
throw pye;
}
time = -1;
date = -1;
}
return dosTimeToEpoch(time, date);
}
/**
* readDirectory(archive) -> files dict (new reference)
*
* Given a path to a Zip archive, build a dict, mapping file names
* (local to the archive, using SEP as a separator) to toc entries.
*
* @param archive PyString path to the archive
* @return a PyDictionary of tocEntrys
* @see #readZipFile(ZipFile, PyObject)
*/
private PyObject readDirectory(String archive) {
File file = new File(sys.getPath(archive));
if (!file.canRead()) {
throw zipimport.ZipImportError(String.format("can't open Zip file: '%s'", archive));
}
ZipFile zipFile;
try {
zipFile = new ZipFile(file);
} catch (IOException ioe) {
throw zipimport.ZipImportError(String.format("can't read Zip file: '%s'", archive));
}
PyObject files = new PyDictionary();
try {
readZipFile(zipFile, files);
} finally {
try {
zipFile.close();
} catch (IOException ioe) {
throw Py.IOError(ioe);
}
}
return files;
}
/**
* Read ZipFile metadata into a dict of toc entries.
*
* A tocEntry is a tuple:
*
* (__file__, # value to use for __file__, available for all files
* compress, # compression kind; 0 for uncompressed
* data_size, # size of compressed data on disk
* file_size, # size of decompressed data
* file_offset, # offset of file header from start of archive (or -1 in Jython)
* time, # mod time of file (in dos format)
* date, # mod data of file (in dos format)
* crc, # crc checksum of the data
* )
*
* Directories can be recognized by the trailing SEP in the name, data_size and
* file_offset are 0.
*
* @param zipFile ZipFile to read
* @param files a dict-like PyObject
*/
private void readZipFile(ZipFile zipFile, PyObject files) {
for (Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
zipEntries.hasMoreElements();) {
ZipEntry zipEntry = zipEntries.nextElement();
String name = zipEntry.getName().replace('/', File.separatorChar);
// File names generally expected in the FS encoding at the Python level
PyObject __file__ = Py.fileSystemEncode(archive + File.separator + name);
PyObject compress = Py.newInteger(zipEntry.getMethod());
PyObject data_size = new PyLong(zipEntry.getCompressedSize());
PyObject file_size = new PyLong(zipEntry.getSize());
// file_offset is a CPython optimization; it's used to seek directly to the
// file when reading it later. Jython doesn't do this nor is the offset
// available
PyObject file_offset = Py.newInteger(-1);
PyObject time = new PyInteger(epochToDosTime(zipEntry.getTime()));
PyObject date = new PyInteger(epochToDosDate(zipEntry.getTime()));
PyObject crc = new PyLong(zipEntry.getCrc());
PyTuple entry = new PyTuple(__file__, compress, data_size, file_size, file_offset,
time, date, crc);
files.__setitem__(Py.newStringOrUnicode(name), entry);
}
}
@Override
protected String getSeparator() {
return File.separator;
}
@Override
protected String makeFilename(String fullname) {
return prefix + getSubname(fullname).replace('.', File.separatorChar);
}
@Override
protected String makePackagePath(String fullname) {
return archive + File.separator + prefix + getSubname(fullname);
}
@Override
protected String makeFilePath(String fullname) {
return makePackagePath(fullname);
}
@Override
protected PyObject makeEntry(String fullFilename) {
return files.__finditem__(fullFilename);
}
/**
* Return fullname.split(".")[-1].
*
* @param fullname
* a String value
* @return a split(".")[-1] String value
*/
protected String getSubname(String fullname) {
int i = fullname.lastIndexOf(".");
if (i >= 0) {
return fullname.substring(i + 1);
}
return fullname;
}
/**
* Convert a time in milliseconds since epoch to DOS date format
*
* @param time in milliseconds, a long value
* @return an int, dos style date value
*/
@SuppressWarnings("deprecation")
private int epochToDosDate(long time) {
// This and the other conversion methods are cut and pasted from
// java.util.zip.ZipEntry: hence the use deprecated Date APIs
Date d = new Date(time);
int year = d.getYear() + 1900;
if (year < 1980) {
return (1 << 21) | (1 << 16);
}
return (year - 1980) << 9 | (d.getMonth() + 1) << 5 | d.getDate() << 0;
}
/**
* Convert a time in milliseconds since epoch to DOS time format
*
* @param time in milliseconds, a long value
* @return an int, dos style time value
*/
@SuppressWarnings("deprecation")
private int epochToDosTime(long time) {
Date d = new Date(time);
return d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1;
}
/**
* Convert the date/time values found in the Zip archive to a long
* time (in milliseconds) value.
*
* @param dostime a dos style timestamp (only time) integer
* @param dosdate a dos style date integer
* @return a long time (in milliseconds) value
*/
@SuppressWarnings("deprecation")
private long dosTimeToEpoch(int dosTime, int dosDate) {
Date d = new Date(((dosDate >> 9) & 0x7f) + 80,
((dosDate >> 5) & 0x0f) - 1,
dosDate & 0x1f,
(dosTime >> 11) & 0x1f,
(dosTime >> 5) & 0x3f,
(dosTime & 0x1f) * 2);
return d.getTime();
}
@Override
public String toString() {
return zipimporter_toString();
}
@ExposedMethod(names = "__repr__")
final String zipimporter_toString() {
// __repr__ has to return bytes not unicode
String bytesName = archive != null ? Py.fileSystemEncode(archive).getString() : "???";
if (prefix != null && !"".equals(prefix)) {
return String.format("<zipimporter object \"%.300s%c%.150s\">", bytesName,
File.separatorChar, prefix);
}
return String.format("<zipimporter object \"%.300s\">", bytesName);
}
/**
* ZipBundle is a ZipFile and one of its InputStreams, bundled together so the ZipFile can be
* closed when finished with its InputStream.
*/
private class ZipBundle extends Bundle {
ZipFile zipFile;
public ZipBundle(ZipFile zipFile, InputStream inputStream) {
super(inputStream);
this.zipFile = zipFile;
}
/**
* Close the ZipFile; implicitly closes all of its InputStreams.
*
* Raises an IOError if a problem occurred.
*/
@Override
public void close() {
try {
zipFile.close();
} catch (IOException ioe) {
throw Py.IOError(ioe);
}
}
}
/* Traverseproc implementation */
@Override
public int traverse(Visitproc visit, Object arg) {
if (files != null) {
int retVal = visit.visit(files, arg);
if (retVal != 0) {
return retVal;
}
}
return sys == null ? 0 : visit.visit(sys, arg);
}
@Override
public boolean refersDirectlyTo(PyObject ob) {
return ob != null && (ob == files || ob == sys);
}
}