We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SharedDataMiddleware fails with IsADirectory
Minimal example:

```python
from flask import Flask
from werkzeug.middleware.shared_data import SharedDataMiddleware

app = Flask(__name__)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/': ('flask', 'json')})
```

This only seems to happen when using `/` with the tuple syntax to reference a package. When using another mapping like `/test/` it works fine.

```
Traceback (most recent call last):
  File "/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/flask/app.py", line 2328, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/werkzeug/middleware/shared_data.py", line 231, in __call__
    f, mtime, file_size = file_loader()
  File "/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/werkzeug/middleware/shared_data.py", line 132, in <lambda>
    open(filename, "rb"),
IsADirectoryError: [Errno 21] Is a directory: '/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/flask/json/'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/werkzeug/middleware/shared_data.py`
Content:
```
1 """
2 Serve Shared Static Files
3 =========================
4 
5 .. autoclass:: SharedDataMiddleware
6     :members: is_allowed
7 
8 :copyright: 2007 Pallets
9 :license: BSD-3-Clause
10 """
11 import mimetypes
12 import os
13 import posixpath
14 from datetime import datetime
15 from io import BytesIO
16 from time import mktime
17 from time import time
18 from zlib import adler32
19 
20 from .._compat import PY2
21 from .._compat import string_types
22 from ..filesystem import get_filesystem_encoding
23 from ..http import http_date
24 from ..http import is_resource_modified
25 from ..security import safe_join
26 from ..wsgi import get_path_info
27 from ..wsgi import wrap_file
28 
29 
30 class SharedDataMiddleware(object):
31 
32     """A WSGI middleware that provides static content for development
33     environments or simple server setups. Usage is quite simple::
34 
35         import os
36         from werkzeug.wsgi import SharedDataMiddleware
37 
38         app = SharedDataMiddleware(app, {
39             '/static': os.path.join(os.path.dirname(__file__), 'static')
40         })
41 
42     The contents of the folder ``./shared`` will now be available on
43     ``http://example.com/shared/``.  This is pretty useful during development
44     because a standalone media server is not required.  One can also mount
45     files on the root folder and still continue to use the application because
46     the shared data middleware forwards all unhandled requests to the
47     application, even if the requests are below one of the shared folders.
48 
49     If `pkg_resources` is available you can also tell the middleware to serve
50     files from package data::
51 
52         app = SharedDataMiddleware(app, {
53             '/static': ('myapplication', 'static')
54         })
55 
56     This will then serve the ``static`` folder in the `myapplication`
57     Python package.
58 
59     The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch`
60     rules for files that are not accessible from the web.  If `cache` is set to
61     `False` no caching headers are sent.
62 
63     Currently the middleware does not support non ASCII filenames.  If the
64     encoding on the file system happens to be the encoding of the URI it may
65     work but this could also be by accident.  We strongly suggest using ASCII
66     only file names for static files.
67 
68     The middleware will guess the mimetype using the Python `mimetype`
69     module.  If it's unable to figure out the charset it will fall back
70     to `fallback_mimetype`.
71 
72     .. versionchanged:: 0.5
73        The cache timeout is configurable now.
74 
75     .. versionadded:: 0.6
76        The `fallback_mimetype` parameter was added.
77 
78     :param app: the application to wrap.  If you don't want to wrap an
79                 application you can pass it :exc:`NotFound`.
80     :param exports: a list or dict of exported files and folders.
81     :param disallow: a list of :func:`~fnmatch.fnmatch` rules.
82     :param fallback_mimetype: the fallback mimetype for unknown files.
83     :param cache: enable or disable caching headers.
84     :param cache_timeout: the cache timeout in seconds for the headers.
85     """
86 
87     def __init__(
88         self,
89         app,
90         exports,
91         disallow=None,
92         cache=True,
93         cache_timeout=60 * 60 * 12,
94         fallback_mimetype="text/plain",
95     ):
96         self.app = app
97         self.exports = []
98         self.cache = cache
99         self.cache_timeout = cache_timeout
100 
101         if hasattr(exports, "items"):
102             exports = exports.items()
103 
104         for key, value in exports:
105             if isinstance(value, tuple):
106                 loader = self.get_package_loader(*value)
107             elif isinstance(value, string_types):
108                 if os.path.isfile(value):
109                     loader = self.get_file_loader(value)
110                 else:
111                     loader = self.get_directory_loader(value)
112             else:
113                 raise TypeError("unknown def %r" % value)
114 
115             self.exports.append((key, loader))
116 
117         if disallow is not None:
118             from fnmatch import fnmatch
119 
120             self.is_allowed = lambda x: not fnmatch(x, disallow)
121 
122         self.fallback_mimetype = fallback_mimetype
123 
124     def is_allowed(self, filename):
125         """Subclasses can override this method to disallow the access to
126         certain files.  However by providing `disallow` in the constructor
127         this method is overwritten.
128         """
129         return True
130 
131     def _opener(self, filename):
132         return lambda: (
133             open(filename, "rb"),
134             datetime.utcfromtimestamp(os.path.getmtime(filename)),
135             int(os.path.getsize(filename)),
136         )
137 
138     def get_file_loader(self, filename):
139         return lambda x: (os.path.basename(filename), self._opener(filename))
140 
141     def get_package_loader(self, package, package_path):
142         from pkg_resources import DefaultProvider, ResourceManager, get_provider
143 
144         loadtime = datetime.utcnow()
145         provider = get_provider(package)
146         manager = ResourceManager()
147         filesystem_bound = isinstance(provider, DefaultProvider)
148 
149         def loader(path):
150             if path is None:
151                 return None, None
152 
153             path = safe_join(package_path, path)
154 
155             if not provider.has_resource(path):
156                 return None, None
157 
158             basename = posixpath.basename(path)
159 
160             if filesystem_bound:
161                 return (
162                     basename,
163                     self._opener(provider.get_resource_filename(manager, path)),
164                 )
165 
166             s = provider.get_resource_string(manager, path)
167             return basename, lambda: (BytesIO(s), loadtime, len(s))
168 
169         return loader
170 
171     def get_directory_loader(self, directory):
172         def loader(path):
173             if path is not None:
174                 path = safe_join(directory, path)
175             else:
176                 path = directory
177 
178             if os.path.isfile(path):
179                 return os.path.basename(path), self._opener(path)
180 
181             return None, None
182 
183         return loader
184 
185     def generate_etag(self, mtime, file_size, real_filename):
186         if not isinstance(real_filename, bytes):
187             real_filename = real_filename.encode(get_filesystem_encoding())
188 
189         return "wzsdm-%d-%s-%s" % (
190             mktime(mtime.timetuple()),
191             file_size,
192             adler32(real_filename) & 0xFFFFFFFF,
193         )
194 
195     def __call__(self, environ, start_response):
196         path = get_path_info(environ)
197 
198         if PY2:
199             path = path.encode(get_filesystem_encoding())
200 
201         file_loader = None
202 
203         for search_path, loader in self.exports:
204             if search_path == path:
205                 real_filename, file_loader = loader(None)
206 
207                 if file_loader is not None:
208                     break
209 
210             if not search_path.endswith("/"):
211                 search_path += "/"
212 
213             if path.startswith(search_path):
214                 real_filename, file_loader = loader(path[len(search_path) :])
215 
216                 if file_loader is not None:
217                     break
218 
219         if file_loader is None or not self.is_allowed(real_filename):
220             return self.app(environ, start_response)
221 
222         guessed_type = mimetypes.guess_type(real_filename)
223         mime_type = guessed_type[0] or self.fallback_mimetype
224         f, mtime, file_size = file_loader()
225 
226         headers = [("Date", http_date())]
227 
228         if self.cache:
229             timeout = self.cache_timeout
230             etag = self.generate_etag(mtime, file_size, real_filename)
231             headers += [
232                 ("Etag", '"%s"' % etag),
233                 ("Cache-Control", "max-age=%d, public" % timeout),
234             ]
235 
236             if not is_resource_modified(environ, etag, last_modified=mtime):
237                 f.close()
238                 start_response("304 Not Modified", headers)
239                 return []
240 
241             headers.append(("Expires", http_date(time() + timeout)))
242         else:
243             headers.append(("Cache-Control", "public"))
244 
245         headers.extend(
246             (
247                 ("Content-Type", mime_type),
248                 ("Content-Length", str(file_size)),
249                 ("Last-Modified", http_date(mtime)),
250             )
251         )
252         start_response("200 OK", headers)
253         return wrap_file(environ, f)
254
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/werkzeug/middleware/shared_data.py b/src/werkzeug/middleware/shared_data.py
--- a/src/werkzeug/middleware/shared_data.py
+++ b/src/werkzeug/middleware/shared_data.py
@@ -10,6 +10,7 @@
 """
 import mimetypes
 import os
+import pkgutil
 import posixpath
 from datetime import datetime
 from io import BytesIO
@@ -139,32 +140,65 @@
         return lambda x: (os.path.basename(filename), self._opener(filename))
 
     def get_package_loader(self, package, package_path):
-        from pkg_resources import DefaultProvider, ResourceManager, get_provider
-
         loadtime = datetime.utcnow()
-        provider = get_provider(package)
-        manager = ResourceManager()
-        filesystem_bound = isinstance(provider, DefaultProvider)
+        provider = pkgutil.get_loader(package)
 
-        def loader(path):
-            if path is None:
-                return None, None
+        if hasattr(provider, "get_resource_reader"):
+            # Python 3
+            reader = provider.get_resource_reader(package)
+
+            def loader(path):
+                if path is None:
+                    return None, None
 
-            path = safe_join(package_path, path)
+                path = safe_join(package_path, path)
+                basename = posixpath.basename(path)
 
-            if not provider.has_resource(path):
-                return None, None
+                try:
+                    resource = reader.open_resource(path)
+                except IOError:
+                    return None, None
 
-            basename = posixpath.basename(path)
+                if isinstance(resource, BytesIO):
+                    return (
+                        basename,
+                        lambda: (resource, loadtime, len(resource.getvalue())),
+                    )
 
-            if filesystem_bound:
                 return (
                     basename,
-                    self._opener(provider.get_resource_filename(manager, path)),
+                    lambda: (
+                        resource,
+                        datetime.utcfromtimestamp(os.path.getmtime(resource.name)),
+                        os.path.getsize(resource.name),
+                    ),
                 )
 
-            s = provider.get_resource_string(manager, path)
-            return basename, lambda: (BytesIO(s), loadtime, len(s))
+        else:
+            # Python 2
+            package_filename = provider.get_filename(package)
+            is_filesystem = os.path.exists(package_filename)
+            root = os.path.join(os.path.dirname(package_filename), package_path)
+
+            def loader(path):
+                if path is None:
+                    return None, None
+
+                path = safe_join(root, path)
+                basename = posixpath.basename(path)
+
+                if is_filesystem:
+                    if not os.path.isfile(path):
+                        return None, None
+
+                    return basename, self._opener(path)
+
+                try:
+                    data = provider.get_data(path)
+                except IOError:
+                    return None, None
+
+                return basename, lambda: (BytesIO(data), loadtime, len(data))
 
         return loader

{"golden_diff": "diff --git a/src/werkzeug/middleware/shared_data.py b/src/werkzeug/middleware/shared_data.py\n--- a/src/werkzeug/middleware/shared_data.py\n+++ b/src/werkzeug/middleware/shared_data.py\n@@ -10,6 +10,7 @@\n \"\"\"\n import mimetypes\n import os\n+import pkgutil\n import posixpath\n from datetime import datetime\n from io import BytesIO\n@@ -139,32 +140,65 @@\n         return lambda x: (os.path.basename(filename), self._opener(filename))\n \n     def get_package_loader(self, package, package_path):\n-        from pkg_resources import DefaultProvider, ResourceManager, get_provider\n-\n         loadtime = datetime.utcnow()\n-        provider = get_provider(package)\n-        manager = ResourceManager()\n-        filesystem_bound = isinstance(provider, DefaultProvider)\n+        provider = pkgutil.get_loader(package)\n \n-        def loader(path):\n-            if path is None:\n-                return None, None\n+        if hasattr(provider, \"get_resource_reader\"):\n+            # Python 3\n+            reader = provider.get_resource_reader(package)\n+\n+            def loader(path):\n+                if path is None:\n+                    return None, None\n \n-            path = safe_join(package_path, path)\n+                path = safe_join(package_path, path)\n+                basename = posixpath.basename(path)\n \n-            if not provider.has_resource(path):\n-                return None, None\n+                try:\n+                    resource = reader.open_resource(path)\n+                except IOError:\n+                    return None, None\n \n-            basename = posixpath.basename(path)\n+                if isinstance(resource, BytesIO):\n+                    return (\n+                        basename,\n+                        lambda: (resource, loadtime, len(resource.getvalue())),\n+                    )\n \n-            if filesystem_bound:\n                 return (\n                     basename,\n-                    self._opener(provider.get_resource_filename(manager, path)),\n+                    lambda: (\n+                        resource,\n+                        datetime.utcfromtimestamp(os.path.getmtime(resource.name)),\n+                        os.path.getsize(resource.name),\n+                    ),\n                 )\n \n-            s = provider.get_resource_string(manager, path)\n-            return basename, lambda: (BytesIO(s), loadtime, len(s))\n+        else:\n+            # Python 2\n+            package_filename = provider.get_filename(package)\n+            is_filesystem = os.path.exists(package_filename)\n+            root = os.path.join(os.path.dirname(package_filename), package_path)\n+\n+            def loader(path):\n+                if path is None:\n+                    return None, None\n+\n+                path = safe_join(root, path)\n+                basename = posixpath.basename(path)\n+\n+                if is_filesystem:\n+                    if not os.path.isfile(path):\n+                        return None, None\n+\n+                    return basename, self._opener(path)\n+\n+                try:\n+                    data = provider.get_data(path)\n+                except IOError:\n+                    return None, None\n+\n+                return basename, lambda: (BytesIO(data), loadtime, len(data))\n \n         return loader\n", "issue": "SharedDataMiddleware fails with IsADirectory\nMinimal example:\r\n\r\n```python\r\nfrom flask import Flask\r\nfrom werkzeug.middleware.shared_data import SharedDataMiddleware\r\n\r\napp = Flask(__name__)\r\napp.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/': ('flask', 'json')})\r\n```\r\n\r\nThis only seems to happen when using `/` with the tuple syntax to reference a package. When using another mapping like `/test/` it works fine.\r\n\r\n```\r\nTraceback (most recent call last):\r\n  File \"/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/flask/app.py\", line 2328, in __call__\r\n    return self.wsgi_app(environ, start_response)\r\n  File \"/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/werkzeug/middleware/shared_data.py\", line 231, in __call__\r\n    f, mtime, file_size = file_loader()\r\n  File \"/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/werkzeug/middleware/shared_data.py\", line 132, in <lambda>\r\n    open(filename, \"rb\"),\r\nIsADirectoryError: [Errno 21] Is a directory: '/home/adrian/dev/flask-react-example/.venv/lib/python3.7/site-packages/flask/json/'\r\n```\n", "before_files": [{"content": "\"\"\"\nServe Shared Static Files\n=========================\n\n.. autoclass:: SharedDataMiddleware\n    :members: is_allowed\n\n:copyright: 2007 Pallets\n:license: BSD-3-Clause\n\"\"\"\nimport mimetypes\nimport os\nimport posixpath\nfrom datetime import datetime\nfrom io import BytesIO\nfrom time import mktime\nfrom time import time\nfrom zlib import adler32\n\nfrom .._compat import PY2\nfrom .._compat import string_types\nfrom ..filesystem import get_filesystem_encoding\nfrom ..http import http_date\nfrom ..http import is_resource_modified\nfrom ..security import safe_join\nfrom ..wsgi import get_path_info\nfrom ..wsgi import wrap_file\n\n\nclass SharedDataMiddleware(object):\n\n    \"\"\"A WSGI middleware that provides static content for development\n    environments or simple server setups. Usage is quite simple::\n\n        import os\n        from werkzeug.wsgi import SharedDataMiddleware\n\n        app = SharedDataMiddleware(app, {\n            '/static': os.path.join(os.path.dirname(__file__), 'static')\n        })\n\n    The contents of the folder ``./shared`` will now be available on\n    ``http://example.com/shared/``.  This is pretty useful during development\n    because a standalone media server is not required.  One can also mount\n    files on the root folder and still continue to use the application because\n    the shared data middleware forwards all unhandled requests to the\n    application, even if the requests are below one of the shared folders.\n\n    If `pkg_resources` is available you can also tell the middleware to serve\n    files from package data::\n\n        app = SharedDataMiddleware(app, {\n            '/static': ('myapplication', 'static')\n        })\n\n    This will then serve the ``static`` folder in the `myapplication`\n    Python package.\n\n    The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch`\n    rules for files that are not accessible from the web.  If `cache` is set to\n    `False` no caching headers are sent.\n\n    Currently the middleware does not support non ASCII filenames.  If the\n    encoding on the file system happens to be the encoding of the URI it may\n    work but this could also be by accident.  We strongly suggest using ASCII\n    only file names for static files.\n\n    The middleware will guess the mimetype using the Python `mimetype`\n    module.  If it's unable to figure out the charset it will fall back\n    to `fallback_mimetype`.\n\n    .. versionchanged:: 0.5\n       The cache timeout is configurable now.\n\n    .. versionadded:: 0.6\n       The `fallback_mimetype` parameter was added.\n\n    :param app: the application to wrap.  If you don't want to wrap an\n                application you can pass it :exc:`NotFound`.\n    :param exports: a list or dict of exported files and folders.\n    :param disallow: a list of :func:`~fnmatch.fnmatch` rules.\n    :param fallback_mimetype: the fallback mimetype for unknown files.\n    :param cache: enable or disable caching headers.\n    :param cache_timeout: the cache timeout in seconds for the headers.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        exports,\n        disallow=None,\n        cache=True,\n        cache_timeout=60 * 60 * 12,\n        fallback_mimetype=\"text/plain\",\n    ):\n        self.app = app\n        self.exports = []\n        self.cache = cache\n        self.cache_timeout = cache_timeout\n\n        if hasattr(exports, \"items\"):\n            exports = exports.items()\n\n        for key, value in exports:\n            if isinstance(value, tuple):\n                loader = self.get_package_loader(*value)\n            elif isinstance(value, string_types):\n                if os.path.isfile(value):\n                    loader = self.get_file_loader(value)\n                else:\n                    loader = self.get_directory_loader(value)\n            else:\n                raise TypeError(\"unknown def %r\" % value)\n\n            self.exports.append((key, loader))\n\n        if disallow is not None:\n            from fnmatch import fnmatch\n\n            self.is_allowed = lambda x: not fnmatch(x, disallow)\n\n        self.fallback_mimetype = fallback_mimetype\n\n    def is_allowed(self, filename):\n        \"\"\"Subclasses can override this method to disallow the access to\n        certain files.  However by providing `disallow` in the constructor\n        this method is overwritten.\n        \"\"\"\n        return True\n\n    def _opener(self, filename):\n        return lambda: (\n            open(filename, \"rb\"),\n            datetime.utcfromtimestamp(os.path.getmtime(filename)),\n            int(os.path.getsize(filename)),\n        )\n\n    def get_file_loader(self, filename):\n        return lambda x: (os.path.basename(filename), self._opener(filename))\n\n    def get_package_loader(self, package, package_path):\n        from pkg_resources import DefaultProvider, ResourceManager, get_provider\n\n        loadtime = datetime.utcnow()\n        provider = get_provider(package)\n        manager = ResourceManager()\n        filesystem_bound = isinstance(provider, DefaultProvider)\n\n        def loader(path):\n            if path is None:\n                return None, None\n\n            path = safe_join(package_path, path)\n\n            if not provider.has_resource(path):\n                return None, None\n\n            basename = posixpath.basename(path)\n\n            if filesystem_bound:\n                return (\n                    basename,\n                    self._opener(provider.get_resource_filename(manager, path)),\n                )\n\n            s = provider.get_resource_string(manager, path)\n            return basename, lambda: (BytesIO(s), loadtime, len(s))\n\n        return loader\n\n    def get_directory_loader(self, directory):\n        def loader(path):\n            if path is not None:\n                path = safe_join(directory, path)\n            else:\n                path = directory\n\n            if os.path.isfile(path):\n                return os.path.basename(path), self._opener(path)\n\n            return None, None\n\n        return loader\n\n    def generate_etag(self, mtime, file_size, real_filename):\n        if not isinstance(real_filename, bytes):\n            real_filename = real_filename.encode(get_filesystem_encoding())\n\n        return \"wzsdm-%d-%s-%s\" % (\n            mktime(mtime.timetuple()),\n            file_size,\n            adler32(real_filename) & 0xFFFFFFFF,\n        )\n\n    def __call__(self, environ, start_response):\n        path = get_path_info(environ)\n\n        if PY2:\n            path = path.encode(get_filesystem_encoding())\n\n        file_loader = None\n\n        for search_path, loader in self.exports:\n            if search_path == path:\n                real_filename, file_loader = loader(None)\n\n                if file_loader is not None:\n                    break\n\n            if not search_path.endswith(\"/\"):\n                search_path += \"/\"\n\n            if path.startswith(search_path):\n                real_filename, file_loader = loader(path[len(search_path) :])\n\n                if file_loader is not None:\n                    break\n\n        if file_loader is None or not self.is_allowed(real_filename):\n            return self.app(environ, start_response)\n\n        guessed_type = mimetypes.guess_type(real_filename)\n        mime_type = guessed_type[0] or self.fallback_mimetype\n        f, mtime, file_size = file_loader()\n\n        headers = [(\"Date\", http_date())]\n\n        if self.cache:\n            timeout = self.cache_timeout\n            etag = self.generate_etag(mtime, file_size, real_filename)\n            headers += [\n                (\"Etag\", '\"%s\"' % etag),\n                (\"Cache-Control\", \"max-age=%d, public\" % timeout),\n            ]\n\n            if not is_resource_modified(environ, etag, last_modified=mtime):\n                f.close()\n                start_response(\"304 Not Modified\", headers)\n                return []\n\n            headers.append((\"Expires\", http_date(time() + timeout)))\n        else:\n            headers.append((\"Cache-Control\", \"public\"))\n\n        headers.extend(\n            (\n                (\"Content-Type\", mime_type),\n                (\"Content-Length\", str(file_size)),\n                (\"Last-Modified\", http_date(mtime)),\n            )\n        )\n        start_response(\"200 OK\", headers)\n        return wrap_file(environ, f)\n", "path": "src/werkzeug/middleware/shared_data.py"}], "after_files": [{"content": "\"\"\"\nServe Shared Static Files\n=========================\n\n.. autoclass:: SharedDataMiddleware\n    :members: is_allowed\n\n:copyright: 2007 Pallets\n:license: BSD-3-Clause\n\"\"\"\nimport mimetypes\nimport os\nimport pkgutil\nimport posixpath\nfrom datetime import datetime\nfrom io import BytesIO\nfrom time import mktime\nfrom time import time\nfrom zlib import adler32\n\nfrom .._compat import PY2\nfrom .._compat import string_types\nfrom ..filesystem import get_filesystem_encoding\nfrom ..http import http_date\nfrom ..http import is_resource_modified\nfrom ..security import safe_join\nfrom ..wsgi import get_path_info\nfrom ..wsgi import wrap_file\n\n\nclass SharedDataMiddleware(object):\n\n    \"\"\"A WSGI middleware that provides static content for development\n    environments or simple server setups. Usage is quite simple::\n\n        import os\n        from werkzeug.wsgi import SharedDataMiddleware\n\n        app = SharedDataMiddleware(app, {\n            '/static': os.path.join(os.path.dirname(__file__), 'static')\n        })\n\n    The contents of the folder ``./shared`` will now be available on\n    ``http://example.com/shared/``.  This is pretty useful during development\n    because a standalone media server is not required.  One can also mount\n    files on the root folder and still continue to use the application because\n    the shared data middleware forwards all unhandled requests to the\n    application, even if the requests are below one of the shared folders.\n\n    If `pkg_resources` is available you can also tell the middleware to serve\n    files from package data::\n\n        app = SharedDataMiddleware(app, {\n            '/static': ('myapplication', 'static')\n        })\n\n    This will then serve the ``static`` folder in the `myapplication`\n    Python package.\n\n    The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch`\n    rules for files that are not accessible from the web.  If `cache` is set to\n    `False` no caching headers are sent.\n\n    Currently the middleware does not support non ASCII filenames.  If the\n    encoding on the file system happens to be the encoding of the URI it may\n    work but this could also be by accident.  We strongly suggest using ASCII\n    only file names for static files.\n\n    The middleware will guess the mimetype using the Python `mimetype`\n    module.  If it's unable to figure out the charset it will fall back\n    to `fallback_mimetype`.\n\n    .. versionchanged:: 0.5\n       The cache timeout is configurable now.\n\n    .. versionadded:: 0.6\n       The `fallback_mimetype` parameter was added.\n\n    :param app: the application to wrap.  If you don't want to wrap an\n                application you can pass it :exc:`NotFound`.\n    :param exports: a list or dict of exported files and folders.\n    :param disallow: a list of :func:`~fnmatch.fnmatch` rules.\n    :param fallback_mimetype: the fallback mimetype for unknown files.\n    :param cache: enable or disable caching headers.\n    :param cache_timeout: the cache timeout in seconds for the headers.\n    \"\"\"\n\n    def __init__(\n        self,\n        app,\n        exports,\n        disallow=None,\n        cache=True,\n        cache_timeout=60 * 60 * 12,\n        fallback_mimetype=\"text/plain\",\n    ):\n        self.app = app\n        self.exports = []\n        self.cache = cache\n        self.cache_timeout = cache_timeout\n\n        if hasattr(exports, \"items\"):\n            exports = exports.items()\n\n        for key, value in exports:\n            if isinstance(value, tuple):\n                loader = self.get_package_loader(*value)\n            elif isinstance(value, string_types):\n                if os.path.isfile(value):\n                    loader = self.get_file_loader(value)\n                else:\n                    loader = self.get_directory_loader(value)\n            else:\n                raise TypeError(\"unknown def %r\" % value)\n\n            self.exports.append((key, loader))\n\n        if disallow is not None:\n            from fnmatch import fnmatch\n\n            self.is_allowed = lambda x: not fnmatch(x, disallow)\n\n        self.fallback_mimetype = fallback_mimetype\n\n    def is_allowed(self, filename):\n        \"\"\"Subclasses can override this method to disallow the access to\n        certain files.  However by providing `disallow` in the constructor\n        this method is overwritten.\n        \"\"\"\n        return True\n\n    def _opener(self, filename):\n        return lambda: (\n            open(filename, \"rb\"),\n            datetime.utcfromtimestamp(os.path.getmtime(filename)),\n            int(os.path.getsize(filename)),\n        )\n\n    def get_file_loader(self, filename):\n        return lambda x: (os.path.basename(filename), self._opener(filename))\n\n    def get_package_loader(self, package, package_path):\n        loadtime = datetime.utcnow()\n        provider = pkgutil.get_loader(package)\n\n        if hasattr(provider, \"get_resource_reader\"):\n            # Python 3\n            reader = provider.get_resource_reader(package)\n\n            def loader(path):\n                if path is None:\n                    return None, None\n\n                path = safe_join(package_path, path)\n                basename = posixpath.basename(path)\n\n                try:\n                    resource = reader.open_resource(path)\n                except IOError:\n                    return None, None\n\n                if isinstance(resource, BytesIO):\n                    return (\n                        basename,\n                        lambda: (resource, loadtime, len(resource.getvalue())),\n                    )\n\n                return (\n                    basename,\n                    lambda: (\n                        resource,\n                        datetime.utcfromtimestamp(os.path.getmtime(resource.name)),\n                        os.path.getsize(resource.name),\n                    ),\n                )\n\n        else:\n            # Python 2\n            package_filename = provider.get_filename(package)\n            is_filesystem = os.path.exists(package_filename)\n            root = os.path.join(os.path.dirname(package_filename), package_path)\n\n            def loader(path):\n                if path is None:\n                    return None, None\n\n                path = safe_join(root, path)\n                basename = posixpath.basename(path)\n\n                if is_filesystem:\n                    if not os.path.isfile(path):\n                        return None, None\n\n                    return basename, self._opener(path)\n\n                try:\n                    data = provider.get_data(path)\n                except IOError:\n                    return None, None\n\n                return basename, lambda: (BytesIO(data), loadtime, len(data))\n\n        return loader\n\n    def get_directory_loader(self, directory):\n        def loader(path):\n            if path is not None:\n                path = safe_join(directory, path)\n            else:\n                path = directory\n\n            if os.path.isfile(path):\n                return os.path.basename(path), self._opener(path)\n\n            return None, None\n\n        return loader\n\n    def generate_etag(self, mtime, file_size, real_filename):\n        if not isinstance(real_filename, bytes):\n            real_filename = real_filename.encode(get_filesystem_encoding())\n\n        return \"wzsdm-%d-%s-%s\" % (\n            mktime(mtime.timetuple()),\n            file_size,\n            adler32(real_filename) & 0xFFFFFFFF,\n        )\n\n    def __call__(self, environ, start_response):\n        path = get_path_info(environ)\n\n        if PY2:\n            path = path.encode(get_filesystem_encoding())\n\n        file_loader = None\n\n        for search_path, loader in self.exports:\n            if search_path == path:\n                real_filename, file_loader = loader(None)\n\n                if file_loader is not None:\n                    break\n\n            if not search_path.endswith(\"/\"):\n                search_path += \"/\"\n\n            if path.startswith(search_path):\n                real_filename, file_loader = loader(path[len(search_path) :])\n\n                if file_loader is not None:\n                    break\n\n        if file_loader is None or not self.is_allowed(real_filename):\n            return self.app(environ, start_response)\n\n        guessed_type = mimetypes.guess_type(real_filename)\n        mime_type = guessed_type[0] or self.fallback_mimetype\n        f, mtime, file_size = file_loader()\n\n        headers = [(\"Date\", http_date())]\n\n        if self.cache:\n            timeout = self.cache_timeout\n            etag = self.generate_etag(mtime, file_size, real_filename)\n            headers += [\n                (\"Etag\", '\"%s\"' % etag),\n                (\"Cache-Control\", \"max-age=%d, public\" % timeout),\n            ]\n\n            if not is_resource_modified(environ, etag, last_modified=mtime):\n                f.close()\n                start_response(\"304 Not Modified\", headers)\n                return []\n\n            headers.append((\"Expires\", http_date(time() + timeout)))\n        else:\n            headers.append((\"Cache-Control\", \"public\"))\n\n        headers.extend(\n            (\n                (\"Content-Type\", mime_type),\n                (\"Content-Length\", str(file_size)),\n                (\"Last-Modified\", http_date(mtime)),\n            )\n        )\n        start_response(\"200 OK\", headers)\n        return wrap_file(environ, f)\n", "path": "src/werkzeug/middleware/shared_data.py"}]}