issue 14660: Implement PEP 420, namespace packages.
diff --git a/Modules/zipimport.c b/Modules/zipimport.c
index f822f92..4bb2863 100644
--- a/Modules/zipimport.c
+++ b/Modules/zipimport.c
@@ -259,6 +259,29 @@
     MI_PACKAGE
 };
 
+/* Does this path represent a directory?
+   on error, return < 0
+   if not a dir, return 0
+   if a dir, return 1
+*/
+static int
+check_is_directory(ZipImporter *self, PyObject* prefix, PyObject *path)
+{
+    PyObject *dirpath;
+    PyObject *item;
+
+    /* See if this is a "directory". If so, it's eligible to be part
+       of a namespace package. We test by seeing if the name, with an
+       appended path separator, exists. */
+    dirpath = PyUnicode_FromFormat("%U%U%c", prefix, path, SEP);
+    if (dirpath == NULL)
+        return -1;
+    /* If dirpath is present in self->files, we have a directory. */
+    item = PyDict_GetItem(self->files, dirpath);
+    Py_DECREF(dirpath);
+    return item != NULL;
+}
+
 /* Return some information about a module. */
 static enum zi_module_info
 get_module_info(ZipImporter *self, PyObject *fullname)
@@ -296,6 +319,46 @@
     return MI_NOT_FOUND;
 }
 
+/* The guts of "find_loader" and "find_module". Return values:
+   -1: error
+    0: no loader or namespace portions found
+    1: module/package found
+    2: namespace portion found: *namespace_portion will point to the name
+*/
+static int
+find_loader(ZipImporter *self, PyObject *fullname, PyObject **namespace_portion)
+{
+    enum zi_module_info mi;
+
+    *namespace_portion = NULL;
+
+    mi = get_module_info(self, fullname);
+    if (mi == MI_ERROR)
+        return -1;
+    if (mi == MI_NOT_FOUND) {
+        /* Not a module or regular package. See if this is a directory, and
+           therefore possibly a portion of a namespace package. */
+        int is_dir = check_is_directory(self, self->prefix, fullname);
+        if (is_dir < 0)
+            return -1;
+        if (is_dir) {
+            /* This is possibly a portion of a namespace
+               package. Return the string representing its path,
+               without a trailing separator. */
+            *namespace_portion = PyUnicode_FromFormat("%U%c%U%U",
+                                                      self->archive, SEP,
+                                                      self->prefix, fullname);
+            if (*namespace_portion == NULL)
+                return -1;
+            return 2;
+        }
+        return 0;
+    }
+    /* This is a module or package. */
+    return 1;
+}
+
+
 /* 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 *
@@ -304,21 +367,78 @@
     ZipImporter *self = (ZipImporter *)obj;
     PyObject *path = NULL;
     PyObject *fullname;
-    enum zi_module_info mi;
+    PyObject* namespace_portion = NULL;
 
     if (!PyArg_ParseTuple(args, "U|O:zipimporter.find_module",
                           &fullname, &path))
-        return NULL;
+        goto error;
 
-    mi = get_module_info(self, fullname);
-    if (mi == MI_ERROR)
-        return NULL;
-    if (mi == MI_NOT_FOUND) {
+    switch (find_loader(self, fullname, &namespace_portion)) {
+    case -1:            /* Error */
+        goto error;
+    case 0:             /* Not found, return None */
+        Py_INCREF(Py_None);
+        return Py_None;
+    case 1:             /* Return self */
+        Py_INCREF(self);
+        return (PyObject *)self;
+    case 2:             /* A namespace portion, but not allowed via
+                           find_module, so return None */
+        Py_DECREF(namespace_portion);
         Py_INCREF(Py_None);
         return Py_None;
     }
-    Py_INCREF(self);
-    return (PyObject *)self;
+    /* Can't get here. */
+    assert(0);
+    return NULL;
+error:
+    Py_XDECREF(namespace_portion);
+    return NULL;
+}
+
+
+/* Check whether we can satisfy the import of the module named by
+   'fullname', or whether it could be a portion of a namespace
+   package. Return self if we can load it, a string containing the
+   full path if it's a possible namespace portion, None if we
+   can't load it. */
+static PyObject *
+zipimporter_find_loader(PyObject *obj, PyObject *args)
+{
+    ZipImporter *self = (ZipImporter *)obj;
+    PyObject *path = NULL;
+    PyObject *fullname;
+    PyObject *result = NULL;
+    PyObject *namespace_portion = NULL;
+
+    if (!PyArg_ParseTuple(args, "U|O:zipimporter.find_module",
+                          &fullname, &path))
+        goto error;
+
+    switch (find_loader(self, fullname, &namespace_portion)) {
+    case -1:            /* Error */
+        goto error;
+    case 0:             /* Not found, return (None, []) */
+        if (!(result = Py_BuildValue("O[]", Py_None)))
+            goto error;
+        return result;
+    case 1:             /* Return (self, []) */
+        if (!(result = Py_BuildValue("O[]", self)))
+            goto error;
+        return result;
+    case 2:             /* Return (None, [namespace_portion]) */
+        if (!(result = Py_BuildValue("O[O]", Py_None, namespace_portion)))
+            goto error;
+        return result;
+    }
+    /* Can't get here. */
+    assert(0);
+        return NULL;
+
+error:
+    Py_XDECREF(namespace_portion);
+    Py_XDECREF(result);
+        return NULL;
 }
 
 /* Load and return the module named by 'fullname'. */
@@ -558,6 +678,16 @@
 The optional 'path' argument is ignored -- it's there for compatibility\n\
 with the importer protocol.");
 
+PyDoc_STRVAR(doc_find_loader,
+"find_loader(fullname, path=None) -> self, str 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, a string containing the\n\
+full path name if it's possibly a portion of a namespace package,\n\
+or None otherwise. The optional 'path' argument is ignored -- it's\n\
+ there for compatibility with the importer protocol.");
+
 PyDoc_STRVAR(doc_load_module,
 "load_module(fullname) -> module.\n\
 \n\
@@ -599,6 +729,8 @@
 static PyMethodDef zipimporter_methods[] = {
     {"find_module", zipimporter_find_module, METH_VARARGS,
      doc_find_module},
+    {"find_loader", zipimporter_find_loader, METH_VARARGS,
+     doc_find_loader},
     {"load_module", zipimporter_load_module, METH_VARARGS,
      doc_load_module},
     {"get_data", zipimporter_get_data, METH_VARARGS,