Repository: falconry/falcon

Issue:
Replace the obsolete JS media type
[RFC-4329](https://www.rfc-editor.org/rfc/rfc4329) is obsolete.

According to [RFC-9239](https://www.rfc-editor.org/rfc/rfc9239),

- Changed the intended usage of the media type "text/javascript" from OBSOLETE to COMMON.
- Changed the intended usage for all other script media types to obsolete.


Discussion:
Thanks for heads up @euj1n0ng!

Falcon isn't heavily tied to Javascript, so I suppose this is just about updating [`constants.py::MEDIA_JS`](https://github.com/falconry/falcon/blob/master/falcon/constants.py#L115). Would you like to create a PR?

Resolving patch:
diff --git a/falcon/constants.py b/falcon/constants.py
--- a/falcon/constants.py
+++ b/falcon/constants.py
@@ -105,14 +105,10 @@
 # contrary to the RFCs.
 MEDIA_XML = 'application/xml'
 
-# NOTE(kgriffs): RFC 4329 recommends application/* over text/.
-# furthermore, parsers are required to respect the Unicode
-# encoding signature, if present in the document, and to default
-# to UTF-8 when not present. Note, however, that implementations
-# are not required to support anything besides UTF-8, so it is
-# unclear how much utility an encoding signature (or the charset
-# parameter for that matter) has in practice.
-MEDIA_JS = 'application/javascript'
+# NOTE(euj1n0ng): According to RFC 9239, Changed the intended usage of the
+#   media type "text/javascript" from OBSOLETE to COMMON. Changed
+#   the intended usage for all other script media types to obsolete.
+MEDIA_JS = 'text/javascript'
 
 # NOTE(kgriffs): According to RFC 6838, most text media types should
 # include the charset parameter.
@@ -141,6 +137,30 @@
     ]
 )
 
+# NOTE(vytas): We strip the preferred charsets from the default static file
+#   type mapping as it is hard to make any assumptions without knowing which
+#   files are going to be served. Moreover, the popular web servers (like
+#   Nginx) do not try to guess either.
+_DEFAULT_STATIC_MEDIA_TYPES = tuple(
+    (ext, media_type.split(';', 1)[0])
+    for ext, media_type in (
+        ('.bmp', MEDIA_BMP),
+        ('.gif', MEDIA_GIF),
+        ('.htm', MEDIA_HTML),
+        ('.html', MEDIA_HTML),
+        ('.jpeg', MEDIA_JPEG),
+        ('.jpg', MEDIA_JPEG),
+        ('.js', MEDIA_JS),
+        ('.json', MEDIA_JSON),
+        ('.mjs', MEDIA_JS),
+        ('.png', MEDIA_PNG),
+        ('.txt', MEDIA_TEXT),
+        ('.xml', MEDIA_XML),
+        ('.yaml', MEDIA_YAML),
+        ('.yml', MEDIA_YAML),
+    )
+)
+
 # NOTE(kgriffs): Special singleton to be used internally whenever using
 #   None would be ambiguous.
 _UNSET = object()
diff --git a/falcon/response.py b/falcon/response.py
--- a/falcon/response.py
+++ b/falcon/response.py
@@ -17,6 +17,7 @@
 import functools
 import mimetypes
 
+from falcon.constants import _DEFAULT_STATIC_MEDIA_TYPES
 from falcon.constants import _UNSET
 from falcon.constants import DEFAULT_MEDIA_TYPE
 from falcon.errors import HeaderNotSupported
@@ -1246,4 +1247,5 @@ def __init__(self):
 
         if not mimetypes.inited:
             mimetypes.init()
-        self.static_media_types = mimetypes.types_map
+        self.static_media_types = mimetypes.types_map.copy()
+        self.static_media_types.update(_DEFAULT_STATIC_MEDIA_TYPES)


Test patch:
diff --git a/tests/test_response.py b/tests/test_response.py
--- a/tests/test_response.py
+++ b/tests/test_response.py
@@ -65,13 +65,20 @@ def test_response_removed_stream_len(resp):
 def test_response_option_mimetype_init(monkeypatch):
     mock = MagicMock()
     mock.inited = False
+    mock.types_map = {'.js': 'application/javascript'}
     monkeypatch.setattr('falcon.response.mimetypes', mock)
 
     ro = ResponseOptions()
 
-    assert ro.static_media_types is mock.types_map
+    assert ro.static_media_types['.js'] == 'text/javascript'
+    assert ro.static_media_types['.json'] == 'application/json'
+    assert ro.static_media_types['.mjs'] == 'text/javascript'
+
     mock.reset_mock()
     mock.inited = True
     ro = ResponseOptions()
-    assert ro.static_media_types is mock.types_map
     mock.init.assert_not_called()
+
+    assert ro.static_media_types['.js'] == 'text/javascript'
+    assert ro.static_media_types['.json'] == 'application/json'
+    assert ro.static_media_types['.mjs'] == 'text/javascript'