Repository: pallets/werkzeug

Issue:
set charset in shareddatamiddleware
# Description
UTF-8 Javascript files served by the SharedDataMiddleware do not contain a charset in the `Content-Type` response header. This leads to issues of javascript interpreted as US-ASCII by browsers (https://github.com/posativ/isso/issues/607).

**Current**
```
Content-Type: application/javascript
```
**Expected**
```
Content-Type: application/javascript; charset=UTF-8
```

The mime type is deducted by using python's mimetypes module and the filename:

https://github.com/pallets/werkzeug/blob/aa9676f5cd5ab7ee08b09f38df22ef3e3c564d87/src/werkzeug/middleware/shared_data.py#L256-L257

It is sent without appending an encoding:

https://github.com/pallets/werkzeug/blob/aa9676f5cd5ab7ee08b09f38df22ef3e3c564d87/src/werkzeug/middleware/shared_data.py#L281

Other usages seem to suggest that a call to `get_content_type` is necessary to append the charset, if applicable:

https://github.com/pallets/werkzeug/blob/e76aac8294626e24e1075e665cbf9657b88c4301/src/werkzeug/wrappers/common_descriptors.py#L146

# Possible solutions
I am not familiar with the codebase, but would it be possible to call `get_content_type`? I tried to modify it myself, but I don't know where to get the encoding from inside  `SharedDataMiddleware`.

My problem is solved when I hardcode 'utf-8' as charset:
```python
("Content-Type", get_content_type(mime_type, 'utf-8'))
```


Discussion:

Resolving patch:
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
@@ -24,6 +24,7 @@
 from ..http import http_date
 from ..http import is_resource_modified
 from ..security import safe_join
+from ..utils import get_content_type
 from ..wsgi import get_path_info
 from ..wsgi import wrap_file
 
@@ -70,19 +71,24 @@ class SharedDataMiddleware(object):
     module.  If it's unable to figure out the charset it will fall back
     to `fallback_mimetype`.
 
-    .. versionchanged:: 0.5
-       The cache timeout is configurable now.
-
-    .. versionadded:: 0.6
-       The `fallback_mimetype` parameter was added.
-
     :param app: the application to wrap.  If you don't want to wrap an
                 application you can pass it :exc:`NotFound`.
     :param exports: a list or dict of exported files and folders.
     :param disallow: a list of :func:`~fnmatch.fnmatch` rules.
-    :param fallback_mimetype: the fallback mimetype for unknown files.
     :param cache: enable or disable caching headers.
     :param cache_timeout: the cache timeout in seconds for the headers.
+    :param fallback_mimetype: The fallback mimetype for unknown files.
+
+    .. versionchanged:: 1.0
+        The default ``fallback_mimetype`` is
+        ``application/octet-stream``. If a filename looks like a text
+        mimetype, the ``utf-8`` charset is added to it.
+
+    .. versionadded:: 0.6
+        Added ``fallback_mimetype``.
+
+    .. versionchanged:: 0.5
+        Added ``cache_timeout``.
     """
 
     def __init__(
@@ -92,7 +98,7 @@ def __init__(
         disallow=None,
         cache=True,
         cache_timeout=60 * 60 * 12,
-        fallback_mimetype="text/plain",
+        fallback_mimetype="application/octet-stream",
     ):
         self.app = app
         self.exports = []
@@ -254,7 +260,7 @@ def __call__(self, environ, start_response):
             return self.app(environ, start_response)
 
         guessed_type = mimetypes.guess_type(real_filename)
-        mime_type = guessed_type[0] or self.fallback_mimetype
+        mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8")
         f, mtime, file_size = file_loader()
 
         headers = [("Date", http_date())]


Test patch:
diff --git a/tests/middleware/test_shared_data.py b/tests/middleware/test_shared_data.py
--- a/tests/middleware/test_shared_data.py
+++ b/tests/middleware/test_shared_data.py
@@ -47,6 +47,10 @@ def null_application(environ, start_response):
             app_iter, status, headers = run_wsgi_app(app, create_environ(p))
             assert status == "200 OK"
 
+            if p.endswith(".txt"):
+                content_type = next(v for k, v in headers if k == "Content-Type")
+                assert content_type == "text/plain; charset=utf-8"
+
             with closing(app_iter) as app_iter:
                 data = b"".join(app_iter).strip()