diff --git a/HISTORY.rst b/HISTORY.rst index a1e0d9c15a..b476a09ab4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -91,6 +91,7 @@ DOCUMENTS_RECENT_COUNT has been renamed to DOCUMENTS_RECENT_ACCESS_COUNT. New setting DOCUMENTS_RECENT_ADDED_COUNT added. +- Use platform independant hashing for transformations. 3.0.3 (2018-08-17) ================== diff --git a/mayan/apps/converter/tests/literals.py b/mayan/apps/converter/tests/literals.py index 4cb3d9704e..335c47f76c 100644 --- a/mayan/apps/converter/tests/literals.py +++ b/mayan/apps/converter/tests/literals.py @@ -2,3 +2,14 @@ from __future__ import unicode_literals TEST_TRANSFORMATION_NAME = 'rotate' TEST_TRANSFORMATION_ARGUMENT = 'degrees: 180' +TEST_TRANSFORMATION_COMBINED_CACHE_HASH = '384bf78014d2aed7255d9e548a0694c70af0b22545653214bcceb1ac6286b5f7' +TEST_TRANSFORMATION_RESIZE_CACHE_HASH = '4aa319f5a6950985a19380a1f279a66769d04138bd1583844270fe8c269260fc' +TEST_TRANSFORMATION_RESIZE_CACHE_HASH_2 = 'cc8d220d40e810b995181c0c69b44b7a61c3bb039c0be96a5465fcaf698ca99a' +TEST_TRANSFORMATION_RESIZE_HEIGHT = 528 +TEST_TRANSFORMATION_RESIZE_HEIGHT_2 = 529 +TEST_TRANSFORMATION_RESIZE_WIDTH = 123 +TEST_TRANSFORMATION_RESIZE_WIDTH_2 = 124 +TEST_TRANSFORMATION_ROTATE_CACHE_HASH = 'df6a5854fccdd3a56145fcd7f7bf52bdf95fe940d6611d435e80dfcaca3b61ac' +TEST_TRANSFORMATION_ROTATE_DEGRESS = 34 +TEST_TRANSFORMATION_ZOOM_CACHE_HASH = 'ac7a864de6a95889d5892301e142f8cdc5808f55010c0b820ed056902fc25a73' +TEST_TRANSFORMATION_ZOOM_PERCENT = 49 diff --git a/mayan/apps/converter/tests/test_transformations.py b/mayan/apps/converter/tests/test_transformations.py index ad613dfaa8..cba100fc8c 100644 --- a/mayan/apps/converter/tests/test_transformations.py +++ b/mayan/apps/converter/tests/test_transformations.py @@ -10,17 +10,19 @@ from ..transformations import ( TransformationRotate, TransformationZoom ) -TRANSFORMATION_RESIZE_WIDTH = 123 -TRANSFORMATION_RESIZE_HEIGHT = 528 -TRANSFORMATION_RESIZE_CACHE_HASH = '348d60cb7c028c95' -TRANSFORMATION_RESIZE_WIDTH_2 = 124 -TRANSFORMATION_RESIZE_HEIGHT_2 = 529 -TRANSFORMATION_RESIZE_CACHE_HASH_2 = '348d78cb5709c1bf' -TRANSFORMATION_ROTATE_DEGRESS = 34 -TRANSFORMATION_ROTATE_CACHE_HASH = '45148f480ad5f8bd' -TRANSFORMATION_COMBINED_CACHE_HASH = '1267dbe78a1759da' -TRANSFORMATION_ZOOM_PERCENT = 49 -TRANSFORMATION_ZOOM_CACHE_HASH = '1b333603674469e0' +from .literals import ( + TEST_TRANSFORMATION_COMBINED_CACHE_HASH, + TEST_TRANSFORMATION_RESIZE_CACHE_HASH, + TEST_TRANSFORMATION_RESIZE_CACHE_HASH_2, + TEST_TRANSFORMATION_RESIZE_HEIGHT, + TEST_TRANSFORMATION_RESIZE_HEIGHT_2, + TEST_TRANSFORMATION_RESIZE_WIDTH, + TEST_TRANSFORMATION_RESIZE_WIDTH_2, + TEST_TRANSFORMATION_ROTATE_CACHE_HASH, + TEST_TRANSFORMATION_ROTATE_DEGRESS, + TEST_TRANSFORMATION_ZOOM_CACHE_HASH, + TEST_TRANSFORMATION_ZOOM_PERCENT, +) class TransformationBaseTestCase(TestCase): @@ -45,64 +47,65 @@ class TransformationBaseTestCase(TestCase): def test_resize_cache_hashing(self): # Test if the hash is being generated correctly transformation = TransformationResize( - width=TRANSFORMATION_RESIZE_WIDTH, - height=TRANSFORMATION_RESIZE_HEIGHT + width=TEST_TRANSFORMATION_RESIZE_WIDTH, + height=TEST_TRANSFORMATION_RESIZE_HEIGHT ) self.assertEqual( - transformation.cache_hash(), TRANSFORMATION_RESIZE_CACHE_HASH + transformation.cache_hash(), TEST_TRANSFORMATION_RESIZE_CACHE_HASH ) # Test if the hash is being alternated correctly transformation = TransformationResize( - width=TRANSFORMATION_RESIZE_WIDTH_2, - height=TRANSFORMATION_RESIZE_HEIGHT_2 + width=TEST_TRANSFORMATION_RESIZE_WIDTH_2, + height=TEST_TRANSFORMATION_RESIZE_HEIGHT_2 ) self.assertEqual( - transformation.cache_hash(), TRANSFORMATION_RESIZE_CACHE_HASH_2 + transformation.cache_hash(), + TEST_TRANSFORMATION_RESIZE_CACHE_HASH_2 ) def test_rotate_cache_hashing(self): # Test if the hash is being generated correctly transformation = TransformationRotate( - degrees=TRANSFORMATION_ROTATE_DEGRESS + degrees=TEST_TRANSFORMATION_ROTATE_DEGRESS ) self.assertEqual( - transformation.cache_hash(), TRANSFORMATION_ROTATE_CACHE_HASH + transformation.cache_hash(), TEST_TRANSFORMATION_ROTATE_CACHE_HASH ) def test_rotate_zoom_hashing(self): # Test if the hash is being generated correctly transformation = TransformationZoom( - percent=TRANSFORMATION_ZOOM_PERCENT + percent=TEST_TRANSFORMATION_ZOOM_PERCENT ) self.assertEqual( - transformation.cache_hash(), TRANSFORMATION_ZOOM_CACHE_HASH + transformation.cache_hash(), TEST_TRANSFORMATION_ZOOM_CACHE_HASH ) def test_cache_hash_combining(self): # Test magic method and hash combining transformation_resize = TransformationResize( - width=TRANSFORMATION_RESIZE_WIDTH, - height=TRANSFORMATION_RESIZE_HEIGHT + width=TEST_TRANSFORMATION_RESIZE_WIDTH, + height=TEST_TRANSFORMATION_RESIZE_HEIGHT ) transformation_rotate = TransformationRotate( - degrees=TRANSFORMATION_ROTATE_DEGRESS + degrees=TEST_TRANSFORMATION_ROTATE_DEGRESS ) transformation_zoom = TransformationZoom( - percent=TRANSFORMATION_ZOOM_PERCENT + percent=TEST_TRANSFORMATION_ZOOM_PERCENT ) self.assertEqual( BaseTransformation.combine( (transformation_rotate, transformation_resize, transformation_zoom) - ), TRANSFORMATION_COMBINED_CACHE_HASH + ), TEST_TRANSFORMATION_COMBINED_CACHE_HASH ) diff --git a/mayan/apps/converter/transformations.py b/mayan/apps/converter/transformations.py index 140f64a987..48a44069ab 100644 --- a/mayan/apps/converter/transformations.py +++ b/mayan/apps/converter/transformations.py @@ -1,11 +1,12 @@ from __future__ import unicode_literals +import hashlib import logging from PIL import Image, ImageColor, ImageFilter -from django.utils.six import text_type from django.utils.translation import string_concat, ugettext_lazy as _ +from django.utils.encoding import force_text logger = logging.getLogger(__name__) @@ -19,25 +20,17 @@ class BaseTransformation(object): name = 'base_transformation' _registry = {} - @staticmethod - def encode_hash(decoded_value): - return hex(abs(decoded_value))[2:] - - @staticmethod - def decode_hash(encoded_value): - return int(encoded_value, 16) - @staticmethod def combine(transformations): result = None - for index, transformation in enumerate(transformations): + for transformation in transformations: if not result: - result = hash((BaseTransformation.decode_hash(transformation.cache_hash()), index)) + result = hashlib.sha256(transformation.cache_hash()) else: - result ^= hash((BaseTransformation.decode_hash(transformation.cache_hash()), index)) + result.update(transformation.cache_hash()) - return BaseTransformation.encode_hash(result) + return result.hexdigest() @classmethod def register(cls, transformation): @@ -69,11 +62,14 @@ class BaseTransformation(object): self.kwargs[argument_name] = kwargs.get(argument_name) def cache_hash(self): - result = text_type.__hash__(self.name) - for index, (key, value) in enumerate(self.kwargs.items()): - result ^= hash((key, index)) ^ hash((value, index)) + result = hashlib.sha256(self.name) - return BaseTransformation.encode_hash(result) + # Sort arguments for guaranteed repeatability + for key, value in sorted(self.kwargs.items()): + result.update(force_text(key)) + result.update(force_text(value)) + + return result.hexdigest() def execute_on(self, image): self.image = image