diff --git a/lib/matplotlib/_type1font.py b/lib/matplotlib/_type1font.py index 0b5403a3720a..e91b1391b2c1 100644 --- a/lib/matplotlib/_type1font.py +++ b/lib/matplotlib/_type1font.py @@ -523,12 +523,18 @@ def _parse(self): # Some values need special parsing if key in ('Subrs', 'CharStrings', 'Encoding', 'OtherSubrs'): - prop[key], endpos = { + parser = { 'Subrs': self._parse_subrs, 'CharStrings': self._parse_charstrings, 'Encoding': self._parse_encoding, 'OtherSubrs': self._parse_othersubrs - }[key](source, data) + }[key] + try: + prop[key], endpos = parser(source, data) + except StopIteration: + raise RuntimeError( + f"Malformed Type1 font file: Incomplete /{key}" + ) from None pos.setdefault(key, []).append((keypos, endpos)) continue @@ -618,33 +624,28 @@ def _parse_subrs(self, tokens, _data): # front lets a malformed font declare a huge count in a few bytes and # force a large allocation before it is rejected. entries = {} - try: - for _ in range(count): - next(t for t in tokens if t.is_keyword('dup')) - index_token = next(tokens) - if not index_token.is_number(): - raise RuntimeError( - "Token following dup in Subrs definition must be a " - f"number, was {index_token}" - ) - nbytes_token = next(tokens) - if not nbytes_token.is_number(): - raise RuntimeError( - "Second token following dup in Subrs definition must " - f"be a number, was {nbytes_token}" - ) - token = next(tokens) - if not token.is_keyword(self._abbr['RD']): - raise RuntimeError( - f"Token preceding subr must be {self._abbr['RD']}, " - f"was {token}" - ) - binary_token = tokens.send(1+nbytes_token.value()) - entries[index_token.value()] = binary_token.value() - except StopIteration: - raise RuntimeError( - "Malformed Type1 font file: Incomplete /Subrs" - ) from None + for _ in range(count): + next(t for t in tokens if t.is_keyword('dup')) + index_token = next(tokens) + if not index_token.is_number(): + raise RuntimeError( + "Token following dup in Subrs definition must be a " + f"number, was {index_token}" + ) + nbytes_token = next(tokens) + if not nbytes_token.is_number(): + raise RuntimeError( + "Second token following dup in Subrs definition must " + f"be a number, was {nbytes_token}" + ) + token = next(tokens) + if not token.is_keyword(self._abbr['RD']): + raise RuntimeError( + f"Token preceding subr must be {self._abbr['RD']}, " + f"was {token}" + ) + binary_token = tokens.send(1+nbytes_token.value()) + entries[index_token.value()] = binary_token.value() # The indices must cover 0 to count-1 exactly. if (len(entries) != count diff --git a/lib/matplotlib/tests/test_type1font.py b/lib/matplotlib/tests/test_type1font.py index 067e9640e409..067ac27cd104 100644 --- a/lib/matplotlib/tests/test_type1font.py +++ b/lib/matplotlib/tests/test_type1font.py @@ -210,3 +210,19 @@ def test_Subrs_bad_indices(tmp_path, indices): # past the end nor a duplicate may reach the returned array. with pytest.raises(RuntimeError, match='indices do not cover'): t1f.Type1Font(_write_subrs_pfa(tmp_path / 'x.pfa', indices)) + + +@pytest.mark.parametrize('private, section', [ + (b'/CharStrings 1 begin\n/.notdef 5 RD \x00\x01\x02\x03\x04 ND\n', + 'CharStrings'), + (b'/CharStrings 1\n', 'CharStrings'), + (b'/CharStrings 1 begin\n', 'CharStrings'), + (b'/Encoding 1 array\ndup 0 /.notdef put\n', 'Encoding'), + (b'/OtherSubrs [ {} {} \n', 'OtherSubrs'), +]) +def test_incomplete_sections(tmp_path, private, section): + # A font that ends in the middle of a section used to raise a bare + # StopIteration out of the parser, which says nothing about the file. + path = _write_pfa(tmp_path / 'x.pfa', private) + with pytest.raises(RuntimeError, match=f'Incomplete /{section}'): + t1f.Type1Font(path)