From 77d2ab194a6f1d1b7d486c11b55bbff85a190f70 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 19:21:35 -0400 Subject: [PATCH 1/3] Checking for ICC profile that is None. #183 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/fixtures.py | 5 +++++ glymur/test/test_printing.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index a106ee4..c309a26 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -359,13 +359,16 @@ class ColourSpecificationBox(Jp2kBox): else: # 2.7 has trouble pretty-printing ordered dicts so we just have # to print as a regular dict in this case. - if sys.hexversion < 0x03000000: - icc_profile = dict(self.icc_profile) + if self.icc_profile is None: + msg += '\n ICC Profile: None' else: - icc_profile = self.icc_profile - dispvalue = pprint.pformat(icc_profile) - lines = [' ' * 8 + y for y in dispvalue.split('\n')] - msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) + if sys.hexversion < 0x03000000: + icc_profile = dict(self.icc_profile) + else: + icc_profile = self.icc_profile + dispvalue = pprint.pformat(icc_profile) + lines = [' ' * 8 + y for y in dispvalue.split('\n')] + msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) return msg diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 63f5837..b47f211 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -582,3 +582,8 @@ issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) Component 0 ==> palette column 0 Component 1 ==> palette column 0 Component 2 ==> 2""" + +issue_183_colr = """Colour Specification Box (colr) @ (62, 12) + Method: restricted ICC profile + Precedence: 0 + ICC Profile: None""" diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index 3dd58f5..6a819ac 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -920,6 +920,17 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() self.assertEqual(actual, fixtures.issue_182_cmap) + def test_issue183(self): + filename = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') + + with warnings.catch_warnings(): + # Ignore warning about bad pclr box. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[2].box[1]) + actual = fake_out.getvalue().strip() + self.assertEqual(actual, fixtures.issue_183_colr) @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") From 2ae848b6f0cf3d025c1877851e249cea03118738 Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:03:04 -0400 Subject: [PATCH 2/3] Checking for division by zero with bad tile dimensions. #181 --- glymur/codestream.py | 20 ++++++++++------- glymur/test/test_codestream.py | 39 +++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/glymur/codestream.py b/glymur/codestream.py index 4259788..cf21ec5 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -673,6 +673,18 @@ class Codestream(object): msg = msg.format(j, subsampling[0], subsampling[1]) warnings.warn(msg) + try: + num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0]) + num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1]) + except ZeroDivisionError as err: + warnings.warn("Invalid tile dimensions.") + else: + numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) + if numtiles > 65535: + msg = "Invalid number of tiles ({0}).".format(numtiles) + warnings.warn(msg) + + kwargs = {'rsiz': rsiz, 'xysiz': xysiz, 'xyosiz': xyosiz, @@ -1514,14 +1526,6 @@ class SIZsegment(Segment): lst.append(bitdepth - 1) self.ssiz = tuple(lst) - num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) - num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) - numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) - if numtiles > 65535: - msg = "Invalid number of tiles ({0}).".format(numtiles) - warnings.warn(msg) - - def __repr__(self): msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 5edd144..6e5cc43 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -13,17 +13,12 @@ import struct import sys import tempfile import unittest +import warnings from glymur import Jp2k import glymur -try: - DATA_ROOT = os.environ['OPJ_DATA_ROOT'] -except KeyError: - DATA_ROOT = None -except: - raise - +from .fixtures import opj_data_file, OPJ_DATA_ROOT class TestCodestream(unittest.TestCase): """Test suite for unusual codestream cases.""" @@ -34,7 +29,21 @@ class TestCodestream(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_tile_height_is_zero(self): + """Zero tile height should not cause an exception.""" + filename = opj_data_file('input/nonregression/2539.pdf.SIGFPE.706.1712.jp2') + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + Jp2k(filename) + else: + with self.assertWarns(UserWarning): + Jp2k(jfile) + + + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_reserved_marker_segment(self): @@ -45,7 +54,7 @@ class TestCodestream(unittest.TestCase): # # Let's inject a reserved marker segment into a file that # we know something about to make sure we can still parse it. - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with open(filename, 'rb') as ifile: # Everything up until the first QCD marker. @@ -67,7 +76,7 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") @unittest.skipIf(sys.hexversion < 0x03020000, "Uses features introduced in 3.2.") @@ -77,7 +86,7 @@ class TestCodestream(unittest.TestCase): # Let's inject a marker segment whose marker does not appear to # be valid. We still parse the file, but warn about the offending # marker. - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: with open(filename, 'rb') as ifile: # Everything up until the first QCD marker. @@ -100,11 +109,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[2].length, 3) self.assertEqual(codestream.segment[2].data, b'\x00') - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_psot_is_zero(self): """Psot=0 in SOT is perfectly legal. Issue #78.""" - filename = os.path.join(DATA_ROOT, + filename = os.path.join(OPJ_DATA_ROOT, 'input/nonregression/123.j2c') j = Jp2k(filename) codestream = j.get_codestream(header_only=False) @@ -125,11 +134,11 @@ class TestCodestream(unittest.TestCase): self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7)) - @unittest.skipIf(DATA_ROOT is None, + @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") def test_siz_segment_ssiz_signed(self): """ssiz attribute to be removed in future release""" - filename = os.path.join(DATA_ROOT, 'input/conformance/p0_03.j2k') + filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') j = Jp2k(filename) codestream = j.get_codestream() From 42e1b213561b3952e42321218ae87bc1108cdf4a Mon Sep 17 00:00:00 2001 From: John Evans Date: Wed, 12 Mar 2014 20:50:42 -0400 Subject: [PATCH 3/3] Should not raise an exception during parsing of XML box. #184 --- glymur/jp2box.py | 15 +++++++++------ glymur/test/test_codestream.py | 2 +- glymur/test/test_jp2box_xml.py | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index c309a26..5dba568 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -2631,19 +2631,22 @@ class XMLBox(Jp2kBox): read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') - except UnicodeDecodeError as ude: + except UnicodeDecodeError as err: # Possibly bad string of bytes to begin with. # Try to search for -1: - text = read_buffer[decl_start:].decode('utf-8') - else: - raise + if decl_start <= -1: + msg = 'A problem was encountered while parsing an XML box:' + msg += '\n\n\t"{0}"\n\nNo XML was retrieved.' + warnings.warn(msg.format(str(err))) + return XMLBox(xml=None, length=length, offset=offset) + + text = read_buffer[decl_start:].decode('utf-8') # Let the user know that the XML box was problematic. msg = 'A UnicodeDecodeError was encountered parsing an XML box at ' msg += 'byte position {0} ({1}), but the XML was still recovered.' - msg = msg.format(offset, ude.reason) + msg = msg.format(offset, err.reason) warnings.warn(msg, UserWarning) # Strip out any trailing nulls, as they can foul up XML parsing. diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py index 6e5cc43..ce03ef7 100644 --- a/glymur/test/test_codestream.py +++ b/glymur/test/test_codestream.py @@ -40,7 +40,7 @@ class TestCodestream(unittest.TestCase): Jp2k(filename) else: with self.assertWarns(UserWarning): - Jp2k(jfile) + Jp2k(filename) @unittest.skipIf(OPJ_DATA_ROOT is None, diff --git a/glymur/test/test_jp2box_xml.py b/glymur/test/test_jp2box_xml.py index facc4df..9194a49 100644 --- a/glymur/test/test_jp2box_xml.py +++ b/glymur/test/test_jp2box_xml.py @@ -39,6 +39,7 @@ from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox from glymur.jp2box import JPEG2000SignatureBox +from .fixtures import OPJ_DATA_ROOT, opj_data_file @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestXML(unittest.TestCase): @@ -93,6 +94,22 @@ class TestXML(unittest.TestCase): def tearDown(self): os.unlink(self.xmlfile) + def test_invalid_utf8(self): + """Bad byte sequence that cannot be parsed.""" + filename = opj_data_file(os.path.join('input', + 'nonregression', + '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2')) + if sys.hexversion < 0x03000000: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + else: + with self.assertWarns(UserWarning): + jp2 = Jp2k(filename) + + self.assertIsNone(jp2.box[3].box[1].box[1].xml) + + def test_negative_file_and_xml(self): """The XML should come from only one source.""" xml_object = ET.parse(self.xmlfile)