diff --git a/django/core/signing.py b/django/core/signing.py index 0a566ff47d..5ee19a9336 100644 --- a/django/core/signing.py +++ b/django/core/signing.py @@ -40,13 +40,13 @@ import time import zlib from django.conf import settings -from django.utils import baseconv from django.utils.crypto import constant_time_compare, salted_hmac from django.utils.encoding import force_bytes from django.utils.module_loading import import_string from django.utils.regex_helper import _lazy_re_compile _SEP_UNSAFE = _lazy_re_compile(r'^[A-z0-9-_=]*$') +BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' class BadSignature(Exception): @@ -59,6 +59,31 @@ class SignatureExpired(BadSignature): pass +def b62_encode(s): + if s == 0: + return '0' + sign = '-' if s < 0 else '' + s = abs(s) + encoded = '' + while s > 0: + s, remainder = divmod(s, 62) + encoded = BASE62_ALPHABET[remainder] + encoded + return sign + encoded + + +def b62_decode(s): + if s == '0': + return 0 + sign = 1 + if s[0] == '-': + s = s[1:] + sign = -1 + decoded = 0 + for digit in s: + decoded = decoded * 62 + BASE62_ALPHABET.index(digit) + return sign * decoded + + def b64_encode(s): return base64.urlsafe_b64encode(s).strip(b'=') @@ -187,7 +212,7 @@ class Signer: class TimestampSigner(Signer): def timestamp(self): - return baseconv.base62.encode(int(time.time())) + return b62_encode(int(time.time())) def sign(self, value): value = '%s%s%s' % (value, self.sep, self.timestamp()) @@ -200,7 +225,7 @@ class TimestampSigner(Signer): """ result = super().unsign(value) value, timestamp = result.rsplit(self.sep, 1) - timestamp = baseconv.base62.decode(timestamp) + timestamp = b62_decode(timestamp) if max_age is not None: if isinstance(max_age, datetime.timedelta): max_age = max_age.total_seconds() diff --git a/django/utils/baseconv.py b/django/utils/baseconv.py index c43d9e5a9d..21f1fb3b91 100644 --- a/django/utils/baseconv.py +++ b/django/utils/baseconv.py @@ -1,3 +1,4 @@ +# RemovedInDjango50Warning # Copyright (c) 2010 Guilherme Gondim. All rights reserved. # Copyright (c) 2009 Simon Willison. All rights reserved. # Copyright (c) 2002 Drew Perttula. All rights reserved. @@ -36,6 +37,15 @@ Sample usage:: -1234 """ +import warnings + +from django.utils.deprecation import RemovedInDjango50Warning + +warnings.warn( + 'The django.utils.baseconv module is deprecated.', + category=RemovedInDjango50Warning, + stacklevel=2, +) BASE2_ALPHABET = '01' BASE16_ALPHABET = '0123456789ABCDEF' diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 861242dcfd..e3896cb136 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -17,6 +17,8 @@ details on these changes. * The ``SERIALIZE`` test setting will be removed. +* The undocumented ``django.utils.baseconv`` module will be removed. + .. _deprecation-removed-in-4.1: 4.1 diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index bf7e03fb00..ee3aa6cc70 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -432,6 +432,8 @@ Miscellaneous :attr:`~django.test.TestCase.databases` with the :ref:`serialized_rollback ` option enabled. +* The undocumented ``django.utils.baseconv`` module is deprecated. + Features removed in 4.0 ======================= diff --git a/tests/signing/tests.py b/tests/signing/tests.py index 4b9cec7d25..1bd7277bc6 100644 --- a/tests/signing/tests.py +++ b/tests/signing/tests.py @@ -195,3 +195,10 @@ class TestTimestampSigner(SimpleTestCase): self.assertEqual(signer.unsign(ts, max_age=datetime.timedelta(seconds=11)), value) with self.assertRaises(signing.SignatureExpired): signer.unsign(ts, max_age=10) + + +class TestBase62(SimpleTestCase): + def test_base62(self): + tests = [-10 ** 10, 10 ** 10, 1620378259, *range(-100, 100)] + for i in tests: + self.assertEqual(i, signing.b62_decode(signing.b62_encode(i))) diff --git a/tests/utils_tests/test_baseconv.py b/tests/utils_tests/test_baseconv.py index b6bfc5ef20..989e1eb0bf 100644 --- a/tests/utils_tests/test_baseconv.py +++ b/tests/utils_tests/test_baseconv.py @@ -1,10 +1,15 @@ from unittest import TestCase -from django.utils.baseconv import ( - BaseConverter, base2, base16, base36, base56, base62, base64, -) +from django.test import ignore_warnings +from django.utils.deprecation import RemovedInDjango50Warning + +with ignore_warnings(category=RemovedInDjango50Warning): + from django.utils.baseconv import ( + BaseConverter, base2, base16, base36, base56, base62, base64, + ) +# RemovedInDjango50Warning class TestBaseConv(TestCase): def test_baseconv(self):