From 2d06be62a5797fa6ace21753d8585643d4332e5b Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Wed, 19 Jan 2022 18:33:55 +0300 Subject: [PATCH 01/11] Tag parse refactor --- piexif/_exif.py | 22 +++++++ piexif/_load.py | 148 +++++++++++++++--------------------------------- tests/s_test.py | 5 -- 3 files changed, 69 insertions(+), 106 deletions(-) diff --git a/piexif/_exif.py b/piexif/_exif.py index c68d5ab..a34cb86 100644 --- a/piexif/_exif.py +++ b/piexif/_exif.py @@ -1,3 +1,6 @@ +from struct import calcsize + + class TYPES: Byte = 1 Ascii = 2 @@ -13,6 +16,25 @@ class TYPES: DFloat = 12 +TYPE_FORMAT = { + TYPES.Byte: 'B', + TYPES.Ascii: None, + TYPES.Short: 'H', + TYPES.Long: 'L', + TYPES.Rational: 'LL', + TYPES.SByte: 'b', + TYPES.Undefined: None, + TYPES.SShort: 'h', + TYPES.SLong: 'l', + TYPES.SRational: 'll', + TYPES.Float: 'f', + TYPES.DFloat: 'd', +} + + +TYPE_LENGTH = {t: calcsize('=' + f) for t, f in TYPE_FORMAT.items() if f} + + SIMPLE_NUMERICS = [ TYPES.Byte, TYPES.Short, diff --git a/piexif/_load.py b/piexif/_load.py index da91426..3412d14 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -103,6 +103,42 @@ def __init__(self, data): else: raise InvalidImageDataError("Given file is neither JPEG nor TIFF.") + def _read_tag(self, pointer): + tag, value_type, value_num = unpack_from( + self.endian_mark + "HHL", self.tiftag, pointer + ) + # Treat unknown types as `Undefined` + value_length = TYPE_LENGTH.get(value_type, 1) + value_length_total = value_length * value_num + if value_length_total > 4: + data_pointer = unpack_from( + self.endian_mark + "L", self.tiftag, pointer + 8 + )[0] + else: + data_pointer = pointer + 8 + + format = TYPE_FORMAT.get(value_type, None) + + if format is None: + # Ascii, Undefined and unknown types + if value_type == TYPES.Ascii: + # Crop ending zero + value_length_total = max(0, value_length_total - 1) + raw_value = self.tiftag[data_pointer:data_pointer+value_length_total] + values = (raw_value, ) + else: + # Unpacked types + values = unpack_from( + self.endian_mark + format * value_num, self.tiftag, data_pointer + ) + # Collate rationals + if len(format) > 1: + stride = len(format) + values = tuple( + values[i*stride:(i+1)*stride] for i in range(value_num) + ) + return tag, value_type, tuple(values) + def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} tag_count = unpack_from(self.endian_mark + "H", @@ -114,24 +150,24 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): t = ifd_name for x in range(tag_count): pointer = offset + 12 * x - tag, value_type, value_num = unpack_from( - self.endian_mark + "HHL", self.tiftag, pointer) - value = self.tiftag[pointer+8: pointer+12] - v_set = (value_type, value_num, value, tag) + tag, value_type, values = self._read_tag(pointer) if tag in TAGS[t]: - converted = self.convert_value(v_set) expected_value_type = TAGS[t][tag]['type'] if value_type != expected_value_type: try: - converted = coerce(converted, value_type, expected_value_type) + values = coerce(values, value_type, expected_value_type) except ValueError: # Skip if coercion failed continue - if isinstance(converted, tuple) and (len(converted) == 1): - converted = converted[0] - ifd_dict[tag] = converted + if len(values) == 1: + values = values[0] + ifd_dict[tag] = values elif read_unknown: - ifd_dict[tag] = (v_set[0], v_set[1], v_set[2], self.tiftag) + value_num, = unpack_from( + self.endian_mark + "L", self.tiftag, pointer + 4 + ) + pointer_or_value = self.tiftag[pointer + 8: pointer + 12] + ifd_dict[tag] = value_type, value_num, pointer_or_value, self.tiftag else: pass @@ -140,96 +176,6 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict["first_ifd_pointer"] = self.tiftag[pointer:pointer + 4] return ifd_dict - def convert_value(self, val): - data = None - t = val[0] - length = val[1] - value = val[2] - - if t == TYPES.Byte: # BYTE - if length > 4: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from("B" * length, self.tiftag, pointer) - else: - data = unpack_from("B" * length, value) - elif t == TYPES.Ascii: # ASCII - if length > 4: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = self.tiftag[pointer: pointer+length - 1] - else: - data = value[0: length - 1] - elif t == TYPES.Short: # SHORT - if length > 2: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "H" * length, - self.tiftag, pointer) - else: - data = unpack_from(self.endian_mark + "H" * length, value) - elif t == TYPES.Long: # LONG - if length > 1: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "L" * length, - self.tiftag, pointer) - else: - data = unpack_from(self.endian_mark + "L" * length, value) - elif t == TYPES.Rational: # RATIONAL - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = tuple( - unpack_from(self.endian_mark + "LL", - self.tiftag, pointer + x * 8) - for x in range(length) - ) - elif t == TYPES.SByte: # SIGNED BYTES - if length > 4: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from("b" * length, self.tiftag, pointer) - else: - data = unpack_from("b" * length, value) - elif t == TYPES.Undefined: # UNDEFINED BYTES - if length > 4: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = self.tiftag[pointer: pointer+length] - else: - data = value[0: length] - elif t == TYPES.SShort: # SIGNED SHORT - if length > 2: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "h" * length, - self.tiftag, pointer) - else: - data = unpack_from(self.endian_mark + "h" * length, value) - elif t == TYPES.SLong: # SLONG - if length > 1: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "l" * length, - self.tiftag, pointer) - else: - data = unpack_from(self.endian_mark + "l" * length, value) - elif t == TYPES.SRational: # SRATIONAL - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = tuple( - unpack_from(self.endian_mark + "ll", - self.tiftag, pointer + x * 8) - for x in range(length) - ) - elif t == TYPES.Float: # FLOAT - if length > 1: - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "f" * length, - self.tiftag, pointer) - else: - data = unpack_from(self.endian_mark + "f" * length, value) - elif t == TYPES.DFloat: # DOUBLE - pointer = unpack_from(self.endian_mark + "L", value)[0] - data = unpack_from(self.endian_mark + "d" * length, - self.tiftag, pointer) - else: - raise ValueError("Exif might be wrong. Got incorrect value " + - "type to decode.\n" + - "tag: " + str(val[3]) + "\ntype: " + str(t)) - - return data - def _get_key_name_dict(exif_dict): new_dict = { @@ -246,7 +192,7 @@ def coerce(value, type, target): if target == TYPES.Undefined: if type == TYPES.Byte: # Interpret numbers as byte values, to fit Pillow behaviour - return b''.join(min(x, 255).to_bytes(1, 'big') for x in value) + return ( b''.join(min(x, 255).to_bytes(1, 'big') for x in value), ) elif target in SIMPLE_NUMERICS: if type in SIMPLE_NUMERICS: return value diff --git a/tests/s_test.py b/tests/s_test.py index 5d105de..5a6b7ba 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -655,11 +655,6 @@ def test_ExifReader_return_unknown(self): self.assertEqual(ifd[65535][1], 0) self.assertEqual(ifd[65535][2], b"\x00\x00") - def test_ExifReader_convert_value_fail(self): - er = piexif._load._ExifReader(I1) - with self.assertRaises(ValueError): - er.convert_value((None, None, None, None)) - def test_split_into_segments_fail1(self): with self.assertRaises(InvalidImageDataError): _common.split_into_segments(b"I'm not JPEG") From aa354d5af15fcbcf03f2fb82a550bfa31bc25d8f Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Thu, 20 Jan 2022 14:13:49 +0300 Subject: [PATCH 02/11] `unpack_from` cleanup --- piexif/_load.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 3412d14..750d5e2 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -103,17 +103,16 @@ def __init__(self, data): else: raise InvalidImageDataError("Given file is neither JPEG nor TIFF.") + def _unpack_from(self, format, pointer): + return unpack_from(self.endian_mark + format, self.tiftag, pointer) + def _read_tag(self, pointer): - tag, value_type, value_num = unpack_from( - self.endian_mark + "HHL", self.tiftag, pointer - ) + tag, value_type, value_num = self._unpack_from("HHL", pointer) # Treat unknown types as `Undefined` value_length = TYPE_LENGTH.get(value_type, 1) value_length_total = value_length * value_num if value_length_total > 4: - data_pointer = unpack_from( - self.endian_mark + "L", self.tiftag, pointer + 8 - )[0] + data_pointer = self._unpack_from("L", pointer + 8)[0] else: data_pointer = pointer + 8 @@ -128,9 +127,7 @@ def _read_tag(self, pointer): values = (raw_value, ) else: # Unpacked types - values = unpack_from( - self.endian_mark + format * value_num, self.tiftag, data_pointer - ) + values = self._unpack_from(format * value_num, data_pointer) # Collate rationals if len(format) > 1: stride = len(format) @@ -141,8 +138,7 @@ def _read_tag(self, pointer): def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} - tag_count = unpack_from(self.endian_mark + "H", - self.tiftag, pointer)[0] + tag_count = self._unpack_from("H", pointer)[0] offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" @@ -163,9 +159,7 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): values = values[0] ifd_dict[tag] = values elif read_unknown: - value_num, = unpack_from( - self.endian_mark + "L", self.tiftag, pointer + 4 - ) + value_num, = self._unpack_from("L", pointer + 4) pointer_or_value = self.tiftag[pointer + 8: pointer + 12] ifd_dict[tag] = value_type, value_num, pointer_or_value, self.tiftag else: From af71e85dd879d0c070a984152e8cee766485f09c Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Thu, 20 Jan 2022 14:15:57 +0300 Subject: [PATCH 03/11] Review fixes --- piexif/_exif.py | 6 +++++- piexif/_load.py | 9 ++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/piexif/_exif.py b/piexif/_exif.py index a34cb86..fc0200d 100644 --- a/piexif/_exif.py +++ b/piexif/_exif.py @@ -32,7 +32,11 @@ class TYPES: } -TYPE_LENGTH = {t: calcsize('=' + f) for t, f in TYPE_FORMAT.items() if f} +TYPE_LENGTH = { + t: calcsize('=' + f) + for t, f in TYPE_FORMAT.items() + if f +} SIMPLE_NUMERICS = [ diff --git a/piexif/_load.py b/piexif/_load.py index 750d5e2..47b0561 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -109,9 +109,8 @@ def _unpack_from(self, format, pointer): def _read_tag(self, pointer): tag, value_type, value_num = self._unpack_from("HHL", pointer) # Treat unknown types as `Undefined` - value_length = TYPE_LENGTH.get(value_type, 1) - value_length_total = value_length * value_num - if value_length_total > 4: + value_length = TYPE_LENGTH.get(value_type, 1) * value_num + if value_length > 4: data_pointer = self._unpack_from("L", pointer + 8)[0] else: data_pointer = pointer + 8 @@ -122,8 +121,8 @@ def _read_tag(self, pointer): # Ascii, Undefined and unknown types if value_type == TYPES.Ascii: # Crop ending zero - value_length_total = max(0, value_length_total - 1) - raw_value = self.tiftag[data_pointer:data_pointer+value_length_total] + value_length = max(0, value_length - 1) + raw_value = self.tiftag[data_pointer:data_pointer+value_length] values = (raw_value, ) else: # Unpacked types From cb228214163b212415628bb07e40715211409db6 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Thu, 20 Jan 2022 14:38:25 +0300 Subject: [PATCH 04/11] Skip individual broken tags --- piexif/_load.py | 11 ++++++++++- tests/s_test.py | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 47b0561..effe718 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -137,7 +137,12 @@ def _read_tag(self, pointer): def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} + if pointer > len(self.tiftag) - 2: + return {} tag_count = self._unpack_from("H", pointer)[0] + ifd_length = 2 + 12 * tag_count + if pointer > len(self.tiftag) - ifd_length: + return {} offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" @@ -145,7 +150,11 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): t = ifd_name for x in range(tag_count): pointer = offset + 12 * x - tag, value_type, values = self._read_tag(pointer) + try: + tag, value_type, values = self._read_tag(pointer) + except struct.error: + # Skip broken tags + continue if tag in TAGS[t]: expected_value_type = TAGS[t][tag]['type'] if value_type != expected_value_type: diff --git a/tests/s_test.py b/tests/s_test.py index 5a6b7ba..ecfb63c 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -644,7 +644,7 @@ def _compare_piexifDict_PILDict(self, piexifDict, pilDict, p=True): class UTests(unittest.TestCase): def test_ExifReader_return_unknown(self): b1 = b"MM\x00\x2a\x00\x00\x00\x08" - b2 = b"\x00\x01" + b"\xff\xff\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + b2 = b"\x00\x01" + b"\xff\xff\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00" er = piexif._load._ExifReader(b1 + b2) if er.tiftag[0:2] == b"II": er.endian_mark = "<" @@ -653,7 +653,7 @@ def test_ExifReader_return_unknown(self): ifd = er.get_ifd_dict(8, "0th", True) self.assertEqual(ifd[65535][0], 0) self.assertEqual(ifd[65535][1], 0) - self.assertEqual(ifd[65535][2], b"\x00\x00") + self.assertEqual(ifd[65535][2], b"\x00\x00\x00\x00") def test_split_into_segments_fail1(self): with self.assertRaises(InvalidImageDataError): From 3c23775b8e29e5eaa1e3a6205d77fdf3d28c3d5a Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Thu, 20 Jan 2022 14:47:24 +0300 Subject: [PATCH 05/11] Read truncated IFDs --- piexif/_load.py | 3 ++- tests/s_test.py | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/piexif/_load.py b/piexif/_load.py index effe718..cb9d817 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -142,7 +142,8 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): tag_count = self._unpack_from("H", pointer)[0] ifd_length = 2 + 12 * tag_count if pointer > len(self.tiftag) - ifd_length: - return {} + # Truncate IFD + tag_count = (len(self.tiftag) - 2) // 12 offset = pointer + 2 if ifd_name in ["0th", "1st"]: t = "Image" diff --git a/tests/s_test.py b/tests/s_test.py index ecfb63c..1767fd1 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -655,6 +655,14 @@ def test_ExifReader_return_unknown(self): self.assertEqual(ifd[65535][1], 0) self.assertEqual(ifd[65535][2], b"\x00\x00\x00\x00") + def test_truncated_ifd(self): + b1 = b"MM\x00\x2a\x00\x00\x00\x08" + b2 = b"\xff\xff" + b"\x00\x0b\x00\x02\x00\x00\x00\x04" + b"FOO\x00" + er = piexif._load._ExifReader(b1 + b2) + er.endian_mark = ">" + ifd = er.get_ifd_dict(8, "0th", True) + self.assertEqual(ifd[ImageIFD.ProcessingSoftware], b"FOO") + def test_split_into_segments_fail1(self): with self.assertRaises(InvalidImageDataError): _common.split_into_segments(b"I'm not JPEG") From d7a9ff49d99ac2bea36be38294c24a95962c7dd5 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Thu, 20 Jan 2022 14:52:39 +0300 Subject: [PATCH 06/11] NUL terminates Ascii value see. https://github.com/hMatoba/Piexif/pull/127 --- piexif/_load.py | 4 ++-- tests/s_test.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index cb9d817..4e2552c 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -118,11 +118,11 @@ def _read_tag(self, pointer): format = TYPE_FORMAT.get(value_type, None) if format is None: + raw_value = self.tiftag[data_pointer:data_pointer+value_length] # Ascii, Undefined and unknown types if value_type == TYPES.Ascii: # Crop ending zero - value_length = max(0, value_length - 1) - raw_value = self.tiftag[data_pointer:data_pointer+value_length] + raw_value = raw_value.split(b'\0')[0] values = (raw_value, ) else: # Unpacked types diff --git a/tests/s_test.py b/tests/s_test.py index 1767fd1..fe03add 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -663,6 +663,14 @@ def test_truncated_ifd(self): ifd = er.get_ifd_dict(8, "0th", True) self.assertEqual(ifd[ImageIFD.ProcessingSoftware], b"FOO") + def test_ascii_zero(self): + b1 = b"MM\x00\x2a\x00\x00\x00\x08" + b2 = b"\x00\x01" + b"\x00\x0b\x00\x02\x00\x00\x00\x04" + b"F\x00OO" + er = piexif._load._ExifReader(b1 + b2) + er.endian_mark = ">" + ifd = er.get_ifd_dict(8, "0th", True) + self.assertEqual(ifd[ImageIFD.ProcessingSoftware], b"F") + def test_split_into_segments_fail1(self): with self.assertRaises(InvalidImageDataError): _common.split_into_segments(b"I'm not JPEG") From cefa77bba7bf011b25ef1992e79493a667b4cf27 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Fri, 21 Jan 2022 16:10:00 +0300 Subject: [PATCH 07/11] Skip tags with unknown type --- piexif/_load.py | 11 +++++++---- tests/s_test.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 4e2552c..12d1a47 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -108,12 +108,15 @@ def _unpack_from(self, format, pointer): def _read_tag(self, pointer): tag, value_type, value_num = self._unpack_from("HHL", pointer) - # Treat unknown types as `Undefined` + if value_type not in TYPE_FORMAT: + return None value_length = TYPE_LENGTH.get(value_type, 1) * value_num if value_length > 4: data_pointer = self._unpack_from("L", pointer + 8)[0] else: data_pointer = pointer + 8 + if data_pointer + value_length > len(self.tiftag): + return None format = TYPE_FORMAT.get(value_type, None) @@ -151,11 +154,11 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): t = ifd_name for x in range(tag_count): pointer = offset + 12 * x - try: - tag, value_type, values = self._read_tag(pointer) - except struct.error: + read_result = self._read_tag(pointer) + if not read_result: # Skip broken tags continue + tag, value_type, values = read_result if tag in TAGS[t]: expected_value_type = TAGS[t][tag]['type'] if value_type != expected_value_type: diff --git a/tests/s_test.py b/tests/s_test.py index fe03add..84a2156 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -644,15 +644,15 @@ def _compare_piexifDict_PILDict(self, piexifDict, pilDict, p=True): class UTests(unittest.TestCase): def test_ExifReader_return_unknown(self): b1 = b"MM\x00\x2a\x00\x00\x00\x08" - b2 = b"\x00\x01" + b"\xff\xff\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00" + b2 = b"\x00\x01" + b"\xff\xff\x00\x01\x00\x00\x00\x01" + b"\x00\x00\x00\x00" er = piexif._load._ExifReader(b1 + b2) if er.tiftag[0:2] == b"II": er.endian_mark = "<" else: er.endian_mark = ">" ifd = er.get_ifd_dict(8, "0th", True) - self.assertEqual(ifd[65535][0], 0) - self.assertEqual(ifd[65535][1], 0) + self.assertEqual(ifd[65535][0], 1) + self.assertEqual(ifd[65535][1], 1) self.assertEqual(ifd[65535][2], b"\x00\x00\x00\x00") def test_truncated_ifd(self): From ac540827dc90591163f3596ef87bda06e30f1a9b Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Fri, 21 Jan 2022 16:10:41 +0300 Subject: [PATCH 08/11] Review fixes --- piexif/_load.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 12d1a47..853f2b5 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -34,8 +34,8 @@ def load(input_data, key_is_name=False): else: exifReader.endian_mark = ">" - pointer = unpack_from(exifReader.endian_mark + "L", - exifReader.tiftag, 4)[0] + pointer, = unpack_from(exifReader.endian_mark + "L", + exifReader.tiftag, 4) exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th") first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer") if ImageIFD.ExifTag in exif_dict["0th"]: @@ -48,8 +48,8 @@ def load(input_data, key_is_name=False): pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag] exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop") if first_ifd_pointer != b"\x00\x00\x00\x00": - pointer = unpack_from(exifReader.endian_mark + "L", - first_ifd_pointer)[0] + pointer, = unpack_from(exifReader.endian_mark + "L", + first_ifd_pointer) exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st") if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]): @@ -112,7 +112,7 @@ def _read_tag(self, pointer): return None value_length = TYPE_LENGTH.get(value_type, 1) * value_num if value_length > 4: - data_pointer = self._unpack_from("L", pointer + 8)[0] + data_pointer, = self._unpack_from("L", pointer + 8) else: data_pointer = pointer + 8 if data_pointer + value_length > len(self.tiftag): @@ -142,12 +142,9 @@ def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): ifd_dict = {} if pointer > len(self.tiftag) - 2: return {} - tag_count = self._unpack_from("H", pointer)[0] - ifd_length = 2 + 12 * tag_count - if pointer > len(self.tiftag) - ifd_length: - # Truncate IFD - tag_count = (len(self.tiftag) - 2) // 12 + tag_count, = self._unpack_from("H", pointer) offset = pointer + 2 + tag_count = min(tag_count, (len(self.tiftag) - offset) // 12) if ifd_name in ["0th", "1st"]: t = "Image" else: @@ -198,7 +195,7 @@ def coerce(value, type, target): if target == TYPES.Undefined: if type == TYPES.Byte: # Interpret numbers as byte values, to fit Pillow behaviour - return ( b''.join(min(x, 255).to_bytes(1, 'big') for x in value), ) + return ( bytes(value), ) elif target in SIMPLE_NUMERICS: if type in SIMPLE_NUMERICS: return value From 3060d82526cc554bb39807d65d32579c751afd62 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Fri, 21 Jan 2022 16:10:52 +0300 Subject: [PATCH 09/11] Ascii zero-cutting test fix --- tests/s_test.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/s_test.py b/tests/s_test.py index 84a2156..1d1b619 100644 --- a/tests/s_test.py +++ b/tests/s_test.py @@ -589,14 +589,12 @@ def _compare_value(self, v1, v2): self.assertEqual(struct.pack("B", v2), v1) elif isinstance(v1, bytes) and isinstance(v2, str): try: - self.assertEqual(v1, v2.encode("latin1")) + # PIL does not crop at zero byte, do it here + self.assertEqual(v1, zero_crop(v2.encode("latin1"))) except: self.assertEqual(v1, v2) else: - try: - self.assertEqual(v1, v2.encode("latin1")) - except: - self.assertEqual(v1, v2) + assert False else: self.assertEqual(v1, v2) @@ -1063,5 +1061,8 @@ def suite(): return suite +def zero_crop(x): + return x.split(b'\0')[0] + if __name__ == '__main__': unittest.main() From d24d5178a5318be323503f7d065eb6c599d2a916 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Fri, 21 Jan 2022 16:12:59 +0300 Subject: [PATCH 10/11] Collation fix --- piexif/_load.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 853f2b5..948cbd6 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -132,10 +132,7 @@ def _read_tag(self, pointer): values = self._unpack_from(format * value_num, data_pointer) # Collate rationals if len(format) > 1: - stride = len(format) - values = tuple( - values[i*stride:(i+1)*stride] for i in range(value_num) - ) + values = zip(*[iter(values)] * len(format)) return tag, value_type, tuple(values) def get_ifd_dict(self, pointer, ifd_name, read_unknown=False): From 70b06ebc58736a25c481f931f237137f35731f73 Mon Sep 17 00:00:00 2001 From: Mikhail Iurkov Date: Fri, 21 Jan 2022 16:28:40 +0300 Subject: [PATCH 11/11] Indent fix --- piexif/_load.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/piexif/_load.py b/piexif/_load.py index 948cbd6..497ad51 100644 --- a/piexif/_load.py +++ b/piexif/_load.py @@ -34,8 +34,7 @@ def load(input_data, key_is_name=False): else: exifReader.endian_mark = ">" - pointer, = unpack_from(exifReader.endian_mark + "L", - exifReader.tiftag, 4) + pointer, = unpack_from(exifReader.endian_mark + "L", exifReader.tiftag, 4) exif_dict["0th"] = exifReader.get_ifd_dict(pointer, "0th") first_ifd_pointer = exif_dict["0th"].pop("first_ifd_pointer") if ImageIFD.ExifTag in exif_dict["0th"]: @@ -48,8 +47,7 @@ def load(input_data, key_is_name=False): pointer = exif_dict["Exif"][ExifIFD.InteroperabilityTag] exif_dict["Interop"] = exifReader.get_ifd_dict(pointer, "Interop") if first_ifd_pointer != b"\x00\x00\x00\x00": - pointer, = unpack_from(exifReader.endian_mark + "L", - first_ifd_pointer) + pointer, = unpack_from(exifReader.endian_mark + "L", first_ifd_pointer) exif_dict["1st"] = exifReader.get_ifd_dict(pointer, "1st") if (ImageIFD.JPEGInterchangeFormat in exif_dict["1st"] and ImageIFD.JPEGInterchangeFormatLength in exif_dict["1st"]):