diff options
author | Just van Rossum <just@letterror.com> | 2002-12-30 22:08:05 (GMT) |
---|---|---|
committer | Just van Rossum <just@letterror.com> | 2002-12-30 22:08:05 (GMT) |
commit | 52e14d640be3a7fa2c17f5a2a6bc9626d622aa40 (patch) | |
tree | 417c361ba0bae8b22b262570769933ccdd5ad5e0 /Modules | |
parent | 60087fb45092d9c199cea162e58d9193c7c1558c (diff) | |
download | cpython-52e14d640be3a7fa2c17f5a2a6bc9626d622aa40.zip cpython-52e14d640be3a7fa2c17f5a2a6bc9626d622aa40.tar.gz cpython-52e14d640be3a7fa2c17f5a2a6bc9626d622aa40.tar.bz2 |
PEP 302 + zipimport:
- new import hooks in import.c, exposed in the sys module
- new module called 'zipimport'
- various changes to allow bootstrapping from zip files
I hope I didn't break the Windows build (or anything else for that
matter), but then again, it's been sitting on sf long enough...
Regarding the latest discussions on python-dev: zipimport sets
pkg.__path__ as specified in PEP 273, and likewise, sys.path item such as
/path/to/Archive.zip/subdir/ are supported again.
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/Setup.dist | 4 | ||||
-rw-r--r-- | Modules/getpath.c | 18 | ||||
-rw-r--r-- | Modules/zipimport.c | 1187 |
3 files changed, 1209 insertions, 0 deletions
diff --git a/Modules/Setup.dist b/Modules/Setup.dist index 6df361d..5388086 100644 --- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -113,6 +113,10 @@ errno errnomodule.c # posix (UNIX) errno values _sre _sre.c # Fredrik Lundh's new regular expressions _codecs _codecsmodule.c # access to the builtin codecs and codec registry +# The zipimport module is always imported at startup. Having it as a +# builtin module avoids some bootstrapping problems and reduces overhead. +zipimport zipimport.c + # The rest of the modules listed in this file are all commented out by # default. Usually they can be detected and built as dynamically # loaded modules by the new setup.py script added in Python 2.1. If diff --git a/Modules/getpath.c b/Modules/getpath.c index 54b57c7..03646a5 100644 --- a/Modules/getpath.c +++ b/Modules/getpath.c @@ -365,6 +365,7 @@ calculate_path(void) char *path = getenv("PATH"); char *prog = Py_GetProgramName(); char argv0_path[MAXPATHLEN+1]; + char zip_path[MAXPATHLEN+1]; int pfound, efound; /* 1 if found; -1 if found build directory */ char *buf; size_t bufsz; @@ -483,6 +484,18 @@ calculate_path(void) else reduce(prefix); + strncpy(zip_path, prefix, MAXPATHLEN); + if (pfound > 0) { /* Use the reduced prefix returned by Py_GetPrefix() */ + reduce(zip_path); + reduce(zip_path); + } + else + strncpy(zip_path, PREFIX, MAXPATHLEN); + joinpath(zip_path, "lib/python00.zip"); + bufsz = strlen(zip_path); /* Replace "00" with version */ + zip_path[bufsz - 6] = VERSION[0]; + zip_path[bufsz - 5] = VERSION[2]; + if (!(efound = search_for_exec_prefix(argv0_path, home))) { if (!Py_FrozenFlag) fprintf(stderr, @@ -521,6 +534,7 @@ calculate_path(void) defpath = delim + 1; } + bufsz += strlen(zip_path) + 1; bufsz += strlen(exec_prefix) + 1; /* This is the only malloc call in this file */ @@ -541,6 +555,10 @@ calculate_path(void) else buf[0] = '\0'; + /* Next is the default zip path */ + strcat(buf, zip_path); + strcat(buf, delimiter); + /* Next goes merge of compile-time $PYTHONPATH with * dynamically located prefix. */ diff --git a/Modules/zipimport.c b/Modules/zipimport.c new file mode 100644 index 0000000..593c6e0 --- /dev/null +++ b/Modules/zipimport.c @@ -0,0 +1,1187 @@ +#include "Python.h" +#include "structmember.h" +#include "osdefs.h" +#include "marshal.h" +#include "compile.h" +#include <time.h> + + +#define IS_SOURCE 0x0 +#define IS_BYTECODE 0x1 +#define IS_PACKAGE 0x2 + +struct st_zip_searchorder { + char suffix[14]; + int type; +}; + +/* zip_searchorder defines how we search for a module in the Zip + archive: we first search for a package __init__, then for + non-package .pyc, .pyo and .py entries. The .pyc and .pyo entries + are swapped by initzipimport() if we run in optimized mode. Also, + '/' is replaced by SEP there. */ +struct st_zip_searchorder zip_searchorder[] = { + {"/__init__.pyc", IS_PACKAGE | IS_BYTECODE}, + {"/__init__.pyo", IS_PACKAGE | IS_BYTECODE}, + {"/__init__.py", IS_PACKAGE | IS_SOURCE}, + {".pyc", IS_BYTECODE}, + {".pyo", IS_BYTECODE}, + {".py", IS_SOURCE}, + {"", 0} +}; + +/* zipimporter object definition and support */ + +typedef struct _zipimporter ZipImporter; + +struct _zipimporter { + PyObject_HEAD + PyObject *archive; /* pathname of the Zip archive */ + PyObject *prefix; /* file prefix: "a/sub/directory/" */ + PyObject *files; /* dict with file info {path: toc_entry} */ +}; + +static PyTypeObject ZipImporter_Type; +static PyObject *ZipImportError; +static PyObject *zip_directory_cache = NULL; + +/* forward decls */ +static PyObject *read_directory(char *archive); +static PyObject *get_data(char *archive, PyObject *toc_entry); +static PyObject *get_module_code(ZipImporter *self, char *fullname, + int *p_ispackage, char **p_modpath); + + +#define ZipImporter_Check(op) PyObject_TypeCheck(op, &ZipImporter_Type) + + +/* zipimporter.__init__ + Split the "subdirectory" from the Zip archive path, lookup a matching + entry in sys.path_importer_cache, fetch the file directory from there + if found, or else read it from the archive. */ +static int +zipimporter_init(ZipImporter *self, PyObject *args, PyObject *kwds) +{ + char *path, *p, *prefix, buf[MAXPATHLEN+2]; + int len; + + if (!PyArg_ParseTuple(args, "s:zipimporter", + &path)) + return -1; + + len = strlen(path); + if (len == 0) { + PyErr_SetString(ZipImportError, "archive path is empty"); + return -1; + } + if (len >= MAXPATHLEN) { + PyErr_SetString(ZipImportError, + "archive path too long"); + return -1; + } + strcpy(buf, path); + +#ifdef ALTSEP + for (p = buf; *p; p++) { + if (*p == ALTSEP) + *p = SEP; + } +#endif + + path = NULL; + prefix = NULL; + for (;;) { + struct stat statbuf; + int rv; + + rv = stat(buf, &statbuf); + if (rv == 0) { + /* it exists */ + if (S_ISREG(statbuf.st_mode)) + /* it's a file */ + path = buf; + break; + } + /* back up one path element */ + p = strchr(buf, SEP); + if (prefix != NULL) + *prefix = SEP; + if (p == NULL) + break; + *p = '\0'; + prefix = p; + } + if (path != NULL) { + PyObject *files; + files = PyDict_GetItemString(zip_directory_cache, path); + if (files == NULL) { + files = read_directory(buf); + if (files == NULL) + return -1; + if (PyDict_SetItemString(zip_directory_cache, path, + files) != 0) + return -1; + } + else + Py_INCREF(files); + self->files = files; + } + else { + PyErr_SetString(ZipImportError, "not a Zip file"); + return -1; + } + + if (prefix == NULL) + prefix = ""; + else { + prefix++; + len = strlen(prefix); + if (prefix[len-1] != SEP) { + /* add trailing SEP */ + prefix[len] = SEP; + prefix[len + 1] = '\0'; + } + } + + self->archive = PyString_FromString(buf); + if (self->archive == NULL) + return -1; + + self->prefix = PyString_FromString(prefix); + if (self->prefix == NULL) + return -1; + + return 0; +} + +/* GC support. */ +static int +zipimporter_traverse(PyObject *obj, visitproc visit, void *arg) +{ + ZipImporter *self = (ZipImporter *)obj; + int err; + + if (self->files != NULL) { + err = visit(self->files, arg); + if (err) + return err; + } + return 0; +} + +static void +zipimporter_dealloc(ZipImporter *self) +{ + PyObject_GC_UnTrack(self); + Py_XDECREF(self->archive); + Py_XDECREF(self->files); + self->ob_type->tp_free((PyObject *)self); +} + +static PyObject * +zipimporter_repr(ZipImporter *self) +{ + char buf[500]; + char *archive = "???"; + char *prefix = ""; + + if (self->archive != NULL && PyString_Check(self->archive)) + archive = PyString_AsString(self->archive); + if (self->prefix != NULL && PyString_Check(self->prefix)) + prefix = PyString_AsString(self->prefix); + if (prefix != NULL && *prefix) + PyOS_snprintf(buf, sizeof(buf), + "<zipimporter object \"%.300s%c%.150s\">", + archive, SEP, prefix); + else + PyOS_snprintf(buf, sizeof(buf), + "<zipimporter object \"%.300s\">", + archive); + return PyString_FromString(buf); +} + +/* return fullname.split(".")[-1] */ +static char * +get_subname(char *fullname) +{ + char *subname = strrchr(fullname, '.'); + if (subname == NULL) + subname = fullname; + else + subname++; + return subname; +} + +/* Given a (sub)modulename, write the potential file path in the + archive (without extension) to the path buffer. Return the + length of the resulting string. */ +static int +make_filename(char *prefix, char *name, char *path) +{ + int len; + char *p; + + len = strlen(prefix); + + /* self.prefix + name [+ SEP + "__init__"] + ".py[co]" */ + if (len + strlen(name) + 13 >= MAXPATHLEN) { + PyErr_SetString(ZipImportError, "path too long"); + return -1; + } + + strcpy(path, prefix); + strcpy(path + len, name); + for (p = path + len; *p; p++) { + if (*p == '.') + *p = SEP; + } + len += strlen(name); + return len; +} + +enum module_info { + MI_ERROR, + MI_NOT_FOUND, + MI_MODULE, + MI_PACKAGE +}; + +/* Return some information about a module. */ +static enum module_info +get_module_info(ZipImporter *self, char *fullname) +{ + char *subname, path[MAXPATHLEN + 1]; + int len; + struct st_zip_searchorder *zso; + + subname = get_subname(fullname); + + len = make_filename(PyString_AsString(self->prefix), subname, path); + if (len < 0) + return MI_ERROR; + + for (zso = zip_searchorder; *zso->suffix; zso++) { + strcpy(path + len, zso->suffix); + if (PyDict_GetItemString(self->files, path) != NULL) { + if (zso->type & IS_PACKAGE) + return MI_PACKAGE; + else + return MI_MODULE; + } + } + return MI_NOT_FOUND; +} + +/* Check whether we can satisfy the import of the module named by + 'fullname'. Return self if we can, None if we can't. */ +static PyObject * +zipimporter_find_module(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + PyObject *path = NULL; + char *fullname; + enum module_info mi; + + if (!PyArg_ParseTuple(args, "s|O:zipimporter.find_module", + &fullname, &path)) + return NULL; + + mi = get_module_info(self, fullname); + if (mi == MI_ERROR) + return NULL; + if (mi == MI_NOT_FOUND) { + Py_INCREF(Py_None); + return Py_None; + } + Py_INCREF(self); + return (PyObject *)self; +} + +/* Load and return the module named by 'fullname'. */ +static PyObject * +zipimporter_load_module(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + PyObject *code, *mod, *dict; + char *fullname, *modpath; + int ispackage; + + if (!PyArg_ParseTuple(args, "s:zipimporter.load_module", + &fullname)) + return NULL; + + code = get_module_code(self, fullname, &ispackage, &modpath); + if (code == NULL) + return NULL; + + mod = PyImport_AddModule(fullname); + if (mod == NULL) { + Py_DECREF(code); + return NULL; + } + dict = PyModule_GetDict(mod); + + /* mod.__loader__ = self */ + if (PyDict_SetItemString(dict, "__loader__", (PyObject *)self) != 0) + goto error; + + if (ispackage) { + /* add __path__ to the module *before* the code gets + executed */ + PyObject *pkgpath, *fullpath; + char *prefix = PyString_AsString(self->prefix); + char *subname = get_subname(fullname); + int err; + + fullpath = PyString_FromFormat("%s%c%s%s", + PyString_AsString(self->archive), + SEP, + *prefix ? prefix : "", + subname); + if (fullpath == NULL) + goto error; + + pkgpath = Py_BuildValue("[O]", fullpath); + Py_DECREF(fullpath); + if (pkgpath == NULL) + goto error; + err = PyDict_SetItemString(dict, "__path__", pkgpath); + Py_DECREF(pkgpath); + if (err != 0) + goto error; + } + mod = PyImport_ExecCodeModuleEx(fullname, code, modpath); + Py_DECREF(code); + if (Py_VerboseFlag) + PySys_WriteStderr("import %s # loaded from Zip %s\n", + fullname, modpath); + return mod; +error: + Py_DECREF(code); + Py_DECREF(mod); + return NULL; +} + +/* Return a bool signifying whether the module is a package or not. */ +static PyObject * +zipimporter_is_package(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + char *fullname; + enum module_info mi; + + if (!PyArg_ParseTuple(args, "s:zipimporter.find_module", + &fullname)) + return NULL; + + mi = get_module_info(self, fullname); + if (mi == MI_ERROR) + return NULL; + if (mi == MI_NOT_FOUND) { + PyErr_Format(ZipImportError, "can't find module '%.200s'", + fullname); + return NULL; + } + return PyBool_FromLong(mi == MI_PACKAGE); +} + +static PyObject * +zipimporter_get_data(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + char *path; +#ifdef ALTSEP + char *p, buf[MAXPATHLEN + 1];; +#endif + PyObject *toc_entry; + int len; + + if (!PyArg_ParseTuple(args, "s:zipimporter.get_data", &path)) + return NULL; + +#ifdef ALTSEP + if (strlen(path) >= MAXPATHLEN) { + PyErr_SetString(ZipImportError, "path too long"); + return NULL; + } + strcpy(buf, path); + for (p = buf; *p; p++) { + if (*p == ALTSEP) + *p = SEP; + } + path = buf; +#endif + len = PyString_Size(self->archive); + if (len < strlen(path) && + strncmp(path, PyString_AsString(self->archive), len) == 0 && + path[len] == SEP) { + path = path + len + 1; + } + + toc_entry = PyDict_GetItemString(self->files, path); + if (toc_entry == NULL) { + PyErr_Format(PyExc_IOError, "file not found [%.200s]", + path); + return NULL; + } + return get_data(PyString_AsString(self->archive), toc_entry); +} + +static PyObject * +zipimporter_get_code(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + char *fullname; + + if (!PyArg_ParseTuple(args, "s:zipimporter.get_code", &fullname)) + return NULL; + + return get_module_code(self, fullname, NULL, NULL); +} + +static PyObject * +zipimporter_get_source(PyObject *obj, PyObject *args) +{ + ZipImporter *self = (ZipImporter *)obj; + PyObject *toc_entry; + char *fullname, *subname, path[MAXPATHLEN+1]; + int len; + enum module_info mi; + + if (!PyArg_ParseTuple(args, "s:zipimporter.get_source", &fullname)) + return NULL; + + mi = get_module_info(self, fullname); + if (mi == MI_ERROR) + return NULL; + if (mi == MI_NOT_FOUND) { + PyErr_Format(ZipImportError, "can't find module '%.200s'", + fullname); + return NULL; + } + subname = get_subname(fullname); + + len = make_filename(PyString_AsString(self->prefix), subname, path); + if (len < 0) + return NULL; + + if (mi == MI_PACKAGE) { + path[len] = SEP; + strcpy(path + len + 1, "__init__.py"); + } + else + strcpy(path + len, ".py"); + + toc_entry = PyDict_GetItemString(self->files, path); + if (toc_entry != NULL) + return get_data(PyString_AsString(self->archive), toc_entry); + + /* we have the module, but no source */ + Py_INCREF(Py_None); + return Py_None; +} + +PyDoc_STRVAR(doc_find_module, +"find_module(fullname, path=None) -> self or None.\n\ +\n\ +Search for a module specified by 'fullname'. 'fullname' must be the\n\ +fully qualified (dotted) module name. It returns the zipimporter\n\ +instance itself if the module was found, or None if it wasn't.\n\ +The optional 'path' argument is ignored -- it's there for compatibility\n\ +with the importer protocol."); + +PyDoc_STRVAR(doc_load_module, +"load_module(fullname) -> module.\n\ +\n\ +Load the module specified by 'fullname'. 'fullname' must be the\n\ +fully qualified (dotted) module name. It returns the imported\n\ +module, or raises ZipImportError if it wasn't found."); + +PyDoc_STRVAR(doc_get_data, +"get_data(pathname) -> string with file data.\n\ +\n\ +Return the data associated with 'pathname'. Raise IOError if\n\ +the file wasn't found."); + +PyDoc_STRVAR(doc_is_package, +"is_package(fullname) -> bool.\n\ +\n\ +Return True if the module specified by fullname is a package.\n\ +Raise ZipImportError is the module couldn't be found."); + +PyDoc_STRVAR(doc_get_code, +"get_code(fullname) -> code object.\n\ +\n\ +Return the code object for the specified module. Raise ZipImportError\n\ +is the module couldn't be found."); + +PyDoc_STRVAR(doc_get_source, +"get_source(fullname) -> source string.\n\ +\n\ +Return the source code for the specified module. Raise ZipImportError\n\ +is the module couldn't be found, return None if the archive does\n\ +contain the module, but has no source for it."); + +static PyMethodDef zipimporter_methods[] = { + {"find_module", zipimporter_find_module, METH_VARARGS, + doc_find_module}, + {"load_module", zipimporter_load_module, METH_VARARGS, + doc_load_module}, + {"get_data", zipimporter_get_data, METH_VARARGS, + doc_get_data}, + {"get_code", zipimporter_get_code, METH_VARARGS, + doc_get_code}, + {"get_source", zipimporter_get_source, METH_VARARGS, + doc_get_source}, + {"is_package", zipimporter_is_package, METH_VARARGS, + doc_is_package}, + {NULL, NULL} /* sentinel */ +}; + +static PyMemberDef zipimporter_members[] = { + {"archive", T_OBJECT, offsetof(ZipImporter, archive), READONLY}, + {"prefix", T_OBJECT, offsetof(ZipImporter, prefix), READONLY}, + {"_files", T_OBJECT, offsetof(ZipImporter, files), READONLY}, + {NULL} +}; + +PyDoc_STRVAR(zipimporter_doc, +"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."); + +#define DEFERRED_ADDRESS(ADDR) 0 + +static PyTypeObject ZipImporter_Type = { + PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type)) + 0, + "zipimport.zipimporter", + sizeof(ZipImporter), + 0, /* tp_itemsize */ + (destructor)zipimporter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)zipimporter_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC, /* tp_flags */ + zipimporter_doc, /* tp_doc */ + zipimporter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + zipimporter_methods, /* tp_methods */ + zipimporter_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)zipimporter_init, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + + +/* implementation */ + +/* Given a buffer, return the short that is represented by the first + 2 bytes, encoded as little endian. This partially reimplements + marshal.c:r_short(). */ +static int +get_short(unsigned char *buf) +{ + short x; + x = buf[0]; + x |= buf[1] << 8; + /* Sign-extension, in case short greater than 16 bits */ + x |= -(x & 0x8000); + return x; +} + +/* Given a buffer, return the long that is represented by the first + 4 bytes, encoded as little endian. This partially reimplements + marshal.c:r_long() */ +static long +get_long(unsigned char *buf) { + long x; + x = buf[0]; + x |= (long)buf[1] << 8; + x |= (long)buf[2] << 16; + x |= (long)buf[3] << 24; +#if SIZEOF_LONG > 4 + /* Sign extension for 64-bit machines */ + x |= -(x & 0x80000000L); +#endif + return x; +} + +/* + read_directory(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. + + A toc_entry is a tuple: + + (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 + 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. +*/ +static PyObject * +read_directory(char *archive) +{ + PyObject *files = NULL; + FILE *fp; + long compress, crc, data_size, file_size, file_offset, date, time; + long header_offset, name_size, header_size, header_end; + long i, l, length, count; + char path[MAXPATHLEN + 5]; + char name[MAXPATHLEN + 5]; + char *p, endof_central_dir[22]; + + if (strlen(archive) > MAXPATHLEN) { + PyErr_SetString(PyExc_OverflowError, + "Zip path name is too long"); + return NULL; + } + strcpy(path, archive); + + fp = fopen(archive, "rb"); + if (fp == NULL) { + PyErr_Format(ZipImportError, "can't open Zip file: " + "'%.200s'", archive); + return NULL; + } + fseek(fp, -22, 2); /* Seek from end of file */ + header_end = ftell(fp); + if (fread(endof_central_dir, 1, 22, fp) != 22) { + fclose(fp); + PyErr_Format(ZipImportError, "can't read Zip file: " + "'%.200s'", archive); + return NULL; + } + if (get_long(endof_central_dir) != 0x06054B50) { + /* Bad: End of Central Dir signature */ + fclose(fp); + PyErr_Format(ZipImportError, "not a Zip file: " + "'%.200s'", archive); + return NULL; + } + + header_offset = get_long(endof_central_dir + 16); + + files = PyDict_New(); + if (files == NULL) + goto error; + + length = (long)strlen(path); + path[length] = SEP; + + /* Start of Central Directory */ + count = 0; + for (;;) { + PyObject *t; + int err; + + fseek(fp, header_offset, 0); /* Start of file header */ + l = PyMarshal_ReadLongFromFile(fp); + if (l != 0x02014B50) + break; /* Bad: Central Dir File Header */ + fseek(fp, header_offset + 10, 0); + compress = PyMarshal_ReadShortFromFile(fp); + time = PyMarshal_ReadShortFromFile(fp); + date = PyMarshal_ReadShortFromFile(fp); + crc = PyMarshal_ReadLongFromFile(fp); + data_size = PyMarshal_ReadLongFromFile(fp); + file_size = PyMarshal_ReadLongFromFile(fp); + name_size = PyMarshal_ReadShortFromFile(fp); + header_size = 46 + name_size + + PyMarshal_ReadShortFromFile(fp) + + PyMarshal_ReadShortFromFile(fp); + fseek(fp, header_offset + 42, 0); + file_offset = PyMarshal_ReadLongFromFile(fp); + if (name_size > MAXPATHLEN) + name_size = MAXPATHLEN; + + p = name; + for (i = 0; i < name_size; i++) { + *p = (char)getc(fp); + if (*p == '/') + *p = SEP; + p++; + } + *p = 0; /* Add terminating null byte */ + header_offset += header_size; + + strncpy(path + length + 1, name, MAXPATHLEN - length - 1); + + t = Py_BuildValue("siiiiiii", path, compress, data_size, + file_size, file_offset, time, date, crc); + if (t == NULL) + goto error; + err = PyDict_SetItemString(files, name, t); + Py_DECREF(t); + if (err != 0) + goto error; + count++; + } + fclose(fp); + if (Py_VerboseFlag) + PySys_WriteStderr("# zipimport: found %ld names in %s\n", + count, archive); + return files; +error: + fclose(fp); + Py_XDECREF(files); + return NULL; +} + +/* Return the zlib.decompress function object, or NULL if zlib couldn't + be imported. The function is cached when found, so subsequent calls + don't import zlib again. Returns a *borrowed* reference. + XXX This makes zlib.decompress immortal. */ +static PyObject * +get_decompress_func(void) +{ + static PyObject *decompress = NULL; + + if (decompress == NULL) { + PyObject *zlib; + static int importing_zlib = 0; + + if (importing_zlib != 0) + /* Someone has a zlib.py[co] in their Zip file; + let's avoid a stack overflow. */ + return NULL; + importing_zlib = 1; + zlib = PyImport_ImportModule("zlib"); /* import zlib */ + importing_zlib = 0; + if (zlib != NULL) { + decompress = PyObject_GetAttrString(zlib, + "decompress"); + Py_DECREF(zlib); + } + else + PyErr_Clear(); + if (Py_VerboseFlag) + PySys_WriteStderr("# zipimport: zlib %s\n", + zlib != NULL ? "available": "UNAVAILABLE"); + } + return decompress; +} + +/* Given a path to a Zip file and a toc_entry, return the (uncompressed) + data as a new reference. */ +static PyObject * +get_data(char *archive, PyObject *toc_entry) +{ + PyObject *raw_data, *data = NULL, *decompress; + char *buf; + FILE *fp; + int err, bytes_read = 0; + long l; + char *datapath; + long compress, data_size, file_size, file_offset; + long time, date, crc; + + if (!PyArg_ParseTuple(toc_entry, "siiiiiii", &datapath, &compress, + &data_size, &file_size, &file_offset, &time, + &date, &crc)) { + return NULL; + } + + fp = fopen(archive, "rb"); + if (!fp) { + PyErr_Format(PyExc_IOError, + "zipimport: can not open file %s", archive); + return NULL; + } + + /* Check to make sure the local file header is correct */ + fseek(fp, file_offset, 0); + l = PyMarshal_ReadLongFromFile(fp); + if (l != 0x04034B50) { + /* Bad: Local File Header */ + PyErr_Format(ZipImportError, + "bad local file header in %s", + archive); + fclose(fp); + return NULL; + } + fseek(fp, file_offset + 26, 0); + l = 30 + PyMarshal_ReadShortFromFile(fp) + + PyMarshal_ReadShortFromFile(fp); /* local header size */ + file_offset += l; /* Start of file data */ + + raw_data = PyString_FromStringAndSize((char *)NULL, compress == 0 ? + data_size : data_size + 1); + if (raw_data == NULL) { + fclose(fp); + return NULL; + } + buf = PyString_AsString(raw_data); + + err = fseek(fp, file_offset, 0); + if (err == 0) + bytes_read = fread(buf, 1, data_size, fp); + fclose(fp); + if (err || bytes_read != data_size) { + PyErr_SetString(PyExc_IOError, + "zipimport: can't read data"); + Py_DECREF(raw_data); + return NULL; + } + + if (compress != 0) { + buf[data_size] = 'Z'; /* saw this in zipfile.py */ + data_size++; + } + buf[data_size] = '\0'; + + if (compress == 0) /* data is not compressed */ + return raw_data; + + /* Decompress with zlib */ + decompress = get_decompress_func(); + if (decompress == NULL) { + PyErr_SetString(ZipImportError, + "can't decompress data; " + "zlib not available"); + goto error; + } + data = PyObject_CallFunction(decompress, "Ol", raw_data, -15); +error: + Py_DECREF(raw_data); + return data; +} + +/* Lenient date/time comparison function. The precision of the mtime + in the archive is lower than the mtime stored in a .pyc: we + must allow a difference of at most one second. */ +static int +eq_mtime(time_t t1, time_t t2) +{ + time_t d = t1 - t2; + if (d < 0) + d = -d; + /* dostime only stores even seconds, so be lenient */ + return d <= 1; +} + +/* Given the contents of a .py[co] file in a buffer, unmarshal the data + and return the code object. Return None if it the magic word doesn't + match (we do this instead of raising an exception as we fall back + to .py if available and we don't want to mask other errors). + Returns a new reference. */ +static PyObject * +unmarshal_code(char *pathname, PyObject *data, time_t mtime) +{ + PyObject *code; + char *buf = PyString_AsString(data); + int size = PyString_Size(data); + + if (size <= 9) { + PyErr_SetString(ZipImportError, + "bad pyc data"); + return NULL; + } + + if (get_long(buf) != PyImport_GetMagicNumber()) { + if (Py_VerboseFlag) + PySys_WriteStderr("# %s has bad magic\n", + pathname); + Py_INCREF(Py_None); + return Py_None; /* signal caller to try alternative */ + } + + if (mtime != 0 && !eq_mtime(get_long(buf + 4), mtime)) { + if (Py_VerboseFlag) + PySys_WriteStderr("# %s has bad mtime\n", + pathname); + Py_INCREF(Py_None); + return Py_None; /* signal caller to try alternative */ + } + + code = PyMarshal_ReadObjectFromString(buf + 8, size - 8); + if (code == NULL) + return NULL; + if (!PyCode_Check(code)) { + Py_DECREF(code); + PyErr_Format(PyExc_TypeError, + "compiled module %.200s is not a code object", + pathname); + return NULL; + } + return code; +} + +/* Replace any occurances of "\r\n?" in the input string with "\n". + This converts DOS and Mac line endings to Unix line endings. + Also append a trailing "\n" to be compatible with + PyParser_SimpleParseFile(). Returns a new reference. */ +static PyObject * +normalize_line_endings(PyObject *source) +{ + char *q, *p = PyString_AsString(source); + int length = PyString_Size(source) + 1; + PyObject *fixed_source; + + fixed_source = PyString_FromStringAndSize(p, length); + if (fixed_source == NULL) + return NULL; + + q = PyString_AsString(fixed_source); + /* replace "\r\n?" by "\n" */ + for (;;) { + if (*p == '\r') { + *q++ = '\n'; + if (*(p + 1) == '\n') { + p++; + length--; + } + } + else + *q++ = *p; + if (*p == '\0') + break; + p++; + } + *q++ = '\n'; /* add trailing \n */ + *q = '\0'; + _PyString_Resize(&fixed_source, length); + return fixed_source; +} + +/* Given a string buffer containing Python source code, compile it + return and return a code object as a new reference. */ +static PyObject * +compile_source(char *pathname, PyObject *source) +{ + PyObject *code, *fixed_source; + + fixed_source = normalize_line_endings(source); + if (fixed_source == NULL) + return NULL; + + code = Py_CompileString(PyString_AsString(fixed_source), pathname, + Py_file_input); + Py_DECREF(fixed_source); + return code; +} + +/* Convert the date/time values found in the Zip archive to a value + that's compatible with the time stamp stored in .pyc files. */ +time_t parse_dostime(int dostime, int dosdate) +{ + struct tm stm; + + stm.tm_sec = (dostime & 0x1f) * 2; + stm.tm_min = (dostime >> 5) & 0x3f; + stm.tm_hour = (dostime >> 11) & 0x1f; + stm.tm_mday = dosdate & 0x1f; + stm.tm_mon = ((dosdate >> 5) & 0x0f) - 1; + stm.tm_year = ((dosdate >> 9) & 0x7f) + 80; + stm.tm_isdst = 0; /* wday/yday is ignored */ + + return mktime(&stm); +} + +/* Given a path to a .pyc or .pyo file in the archive, return the + modifictaion time of the matching .py file, or 0 if no source + is available. */ +static time_t +get_mtime_of_source(ZipImporter *self, char *path) +{ + PyObject *toc_entry; + time_t mtime = 0; + int lastchar = strlen(path) - 1; + char savechar = path[lastchar]; + path[lastchar] = '\0'; /* strip 'c' or 'o' from *.py[co] */ + toc_entry = PyDict_GetItemString(self->files, path); + if (toc_entry != NULL && PyTuple_Check(toc_entry) && + PyTuple_Size(toc_entry) == 8) { + /* fetch the time stamp of the .py file for comparison + with an embedded pyc time stamp */ + int time, date; + time = PyInt_AsLong(PyTuple_GetItem(toc_entry, 5)); + date = PyInt_AsLong(PyTuple_GetItem(toc_entry, 6)); + mtime = parse_dostime(time, date); + } + path[lastchar] = savechar; + return mtime; +} + +/* Return the code object for the module named by 'fullname' from the + Zip archive as a new reference. */ +static PyObject * +get_code_from_data(ZipImporter *self, int ispackage, int isbytecode, + time_t mtime, PyObject *toc_entry) +{ + PyObject *data, *code; + char *modpath; + char *archive = PyString_AsString(self->archive); + + if (archive == NULL) + return NULL; + + data = get_data(archive, toc_entry); + if (data == NULL) + return NULL; + + modpath = PyString_AsString(PyTuple_GetItem(toc_entry, 0)); + + if (isbytecode) { + code = unmarshal_code(modpath, data, mtime); + } + else { + code = compile_source(modpath, data); + } + Py_DECREF(data); + return code; +} + +/* Get the code object assoiciated with the module specified by + 'fullname'. */ +static PyObject * +get_module_code(ZipImporter *self, char *fullname, + int *p_ispackage, char **p_modpath) +{ + PyObject *toc_entry; + char *subname, path[MAXPATHLEN + 1]; + int len; + struct st_zip_searchorder *zso; + + subname = get_subname(fullname); + + len = make_filename(PyString_AsString(self->prefix), subname, path); + if (len < 0) + return NULL; + + for (zso = zip_searchorder; *zso->suffix; zso++) { + PyObject *code = NULL; + + strcpy(path + len, zso->suffix); + if (Py_VerboseFlag > 1) + PySys_WriteStderr("# trying %s%c%s\n", + PyString_AsString(self->archive), + SEP, path); + toc_entry = PyDict_GetItemString(self->files, path); + if (toc_entry != NULL) { + time_t mtime = 0; + int ispackage = zso->type & IS_PACKAGE; + int isbytecode = zso->type & IS_BYTECODE; + + if (isbytecode) + mtime = get_mtime_of_source(self, path); + if (p_ispackage != NULL) + *p_ispackage = ispackage; + code = get_code_from_data(self, ispackage, + isbytecode, mtime, + toc_entry); + if (code == Py_None) { + /* bad magic number or non-matching mtime + in byte code, try next */ + Py_DECREF(code); + continue; + } + if (code != NULL && p_modpath != NULL) + *p_modpath = PyString_AsString( + PyTuple_GetItem(toc_entry, 0)); + return code; + } + } + PyErr_Format(ZipImportError, "can't find module '%.200s'", fullname); + return NULL; +} + + +/* Module init */ + +PyDoc_STRVAR(zipimport_doc, +"zipimport provides support for importing Python modules from Zip archives.\n\ +\n\ +This module exports three objects:\n\ +- zipimporter: a class; its constructor takes a path to a Zip archive.\n\ +- ZipImporterError: exception raised by zipimporter objects. It's a\n\ + subclass of ImportError, so it can be caught as ImportError, too.\n\ +- _zip_directory_cache: a dict, mapping archive paths to zip directory\n\ + info dicts, as used in zipimporter._files.\n\ +\n\ +It is usually not needed to use the zipimport module explicitly; it is\n\ +used by the builtin import mechanism for sys.path items that are paths\n\ +to Zip archives."); + +PyMODINIT_FUNC +initzipimport(void) +{ + PyObject *mod; + + if (PyType_Ready(&ZipImporter_Type) < 0) + return; + + /* Correct directory separator */ + zip_searchorder[0].suffix[0] = SEP; + zip_searchorder[1].suffix[0] = SEP; + zip_searchorder[2].suffix[0] = SEP; + if (Py_OptimizeFlag) { + /* Reverse *.pyc and *.pyo */ + struct st_zip_searchorder tmp; + tmp = zip_searchorder[0]; + zip_searchorder[0] = zip_searchorder[1]; + zip_searchorder[1] = tmp; + tmp = zip_searchorder[3]; + zip_searchorder[3] = zip_searchorder[4]; + zip_searchorder[4] = tmp; + } + + mod = Py_InitModule4("zipimport", NULL, zipimport_doc, + NULL, PYTHON_API_VERSION); + + ZipImportError = PyErr_NewException("zipimport.ZipImportError", + PyExc_ImportError, NULL); + if (ZipImportError == NULL) + return; + + Py_INCREF(ZipImportError); + if (PyModule_AddObject(mod, "ZipImportError", + ZipImportError) < 0) + return; + + Py_INCREF(&ZipImporter_Type); + if (PyModule_AddObject(mod, "zipimporter", + (PyObject *)&ZipImporter_Type) < 0) + return; + + zip_directory_cache = PyDict_New(); + if (zip_directory_cache == NULL) + return; + Py_INCREF(zip_directory_cache); + if (PyModule_AddObject(mod, "_zip_directory_cache", + zip_directory_cache) < 0) + return; +} |