Skip to content

Commit e7694ff

Browse files
authored
feat: support alternate request charset (@W-14001583) (#131)
* add test for utf-16 request body encoding * handle other charsets for json request body * add logging test for mimer errors * log invalid mime translations * changelog + version bump * add request body to mimer error logs
1 parent 5498f2e commit e7694ff

File tree

7 files changed

+72
-4
lines changed

7 files changed

+72
-4
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.29.0
2+
current_version = 0.30.0
33

44
[bumpversion:file:pyproject.toml]
55

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
# [0.30.0] - 2023-08-23
8+
- [PR 131](https://github.com/salesforce/django-declarative-apis/pull/131) Support non-utf8 request body
9+
710
# [0.29.0] - 2023-08-16
811
- [PR 129](https://github.com/salesforce/django-declarative-apis/pull/129) Remove outdated HttpResponse wrapper
912

django_declarative_apis/resources/utils.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
# For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
#
77

8+
import logging
89
import warnings
910
from pydoc import locate
1011

1112
from decorator import decorator
1213
from django import get_version as django_version
14+
from django.conf import settings
1315
from django.http import HttpResponse
1416

1517

18+
logger = logging.getLogger(__name__)
19+
20+
1621
def format_error(error):
1722
return "Django Declarative APIs (Django %s) crash report:\n\n%s" % (
1823
django_version(),
@@ -189,15 +194,30 @@ def translate(self):
189194
if loadee:
190195
try:
191196
data = self.request.body
197+
charset = self.request.encoding or getattr(
198+
settings, "DEFAULT_CHARSET", "utf-8"
199+
)
192200
# PY3: Loaders usually don't work with bytes:
193-
data = data.decode("utf-8")
201+
data = data.decode(charset)
194202
self.request.data = loadee(data)
195203

196204
# Reset both POST and PUT from request, as its
197205
# misleading having their presence around.
198206
self.request.POST = self.request.PUT = dict()
199207
except (TypeError, ValueError):
200208
# This also catches if loadee is None.
209+
log_mimer_data_exception = getattr(
210+
settings, "DDA_LOG_MIMER_DATA_EXCEPTION", False
211+
)
212+
if log_mimer_data_exception:
213+
# using the exception logger should give a better hint of what exactly went wrong
214+
logger.exception(
215+
'ev=dda_mime_data_exception, content_type="%s", body="%s"',
216+
self.request.headers.get(
217+
"content-type", "missing content type header!"
218+
),
219+
self.request.body,
220+
)
201221
raise MimerDataException
202222
else:
203223
self.request.data = None

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
# built documents.
7575

7676
# The full version, including alpha/beta/rc tags.
77-
release = '0.29.0' # set by bumpversion
77+
release = '0.30.0' # set by bumpversion
7878

7979
# The short X.Y version.
8080
version = release.rsplit('.', 1)[0]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "django-declarative-apis"
7-
version = "0.29.0" # set by bumpversion
7+
version = "0.30.0" # set by bumpversion
88
description = "Simple, readable, declarative APIs for Django"
99
readme = "README.md"
1010
dependencies = [

tests/resources/test_resource.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import django.conf
1212
import django.core.exceptions
1313
import django.test
14+
from django.test.utils import override_settings
1415
from unittest import mock
1516

1617
from django_declarative_apis.authentication.oauthlib import oauth_errors
@@ -53,6 +54,49 @@ class Handler:
5354
resource_instance = res(req)
5455
self.assertEqual(resource_instance.content, b"Bad Request")
5556

57+
def test_call_alternate_charset(self):
58+
class Handler:
59+
allowed_methods = ("POST",)
60+
method_handlers = {
61+
"POST": lambda req, *args, **kwargs: (http.HTTPStatus.OK, "")
62+
}
63+
64+
body = {"foo": "bar"}
65+
req = self.create_request(
66+
method="POST",
67+
body=body,
68+
content_type="application/json; charset=utf-16",
69+
use_auth_header_signature=True,
70+
)
71+
72+
res = resource.Resource(lambda: Handler())
73+
resource_instance = res(req)
74+
self.assertEqual(200, resource_instance.status_code)
75+
76+
def test_call_invalid_charset(self):
77+
class Handler:
78+
allowed_methods = ("POST",)
79+
method_handlers = {
80+
"POST": lambda req, *args, **kwargs: (http.HTTPStatus.OK, "")
81+
}
82+
83+
body = {"foo": "bar"}
84+
req = self.create_request(
85+
method="POST",
86+
body=body,
87+
content_type="application/json; charset=utf-16",
88+
use_auth_header_signature=True,
89+
)
90+
req.encoding = "utf-8"
91+
res = resource.Resource(lambda: Handler())
92+
with override_settings(DDA_LOG_MIMER_DATA_EXCEPTION=True):
93+
with self.assertLogs("django_declarative_apis.resources.utils") as logs:
94+
resource_instance = res(req)
95+
self.assertTrue(
96+
any(["ev=dda_mime_data_exception" in o for o in logs.output])
97+
)
98+
self.assertEqual(400, resource_instance.status_code)
99+
56100
def test_call_put(self):
57101
class Handler:
58102
allowed_methods = ("PUT",)

tests/testutils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
_ENCODERS = {
2929
DEFAULT_CONTENT_TYPE: lambda data: urllib.parse.urlencode(data),
3030
"application/json": lambda data: json.dumps(data),
31+
"application/json; charset=utf-16": lambda data: json.dumps(data),
3132
}
3233

3334

0 commit comments

Comments
 (0)