diff --git a/tests.py b/tests.py index 81d2678..7b05835 100644 --- a/tests.py +++ b/tests.py @@ -155,6 +155,50 @@ def test_unicode_multiline(): # json tries to encode as utf-8 and it would break if some chars could not be encoded json.dumps(cal.serialize()) + def test_missing_object_terminator(self): + """ + Test parsing of vObject without line terminator on final line. + """ + # Proper CRLF. + raw = "BEGIN:VCARD\r\n" \ + + "END:VCARD" + card = base.readOne(raw) + self.assertIsNotNone(card) + + # Check with folded line too. + raw = "BEGIN:VCARD\r\n" \ + + "END:\r\n" \ + + " VCARD" + card = base.readOne(raw) + self.assertIsNotNone(card) + + # LF-only (Unix-style). + raw = "BEGIN:VCARD\n" \ + + "END:VCARD" + card = base.readOne(raw) + self.assertIsNotNone(card) + + # CR-only (old MacOS-style). + raw = "BEGIN:VCARD\r" \ + + "END:VCARD" + card = base.readOne(raw) + self.assertIsNotNone(card) + + def test_parsing_error_line_number(self): + """ + Check that the line number reported for a parsing error is correct. + """ + # Mismatched item names, with folded line. + raw = "BEGIN:\r\n" \ + + " AAA\r\n" \ + + "END:BBB" + with self.assertRaises(base.ParseError) as context: + card = base.readOne(raw) + + # Check line number of parsing error. + e = context.exception + self.assertEqual(e.args[1], 3) + @staticmethod def test_ical_to_hcal(): """ diff --git a/vobject/base.py b/vobject/base.py index 77010ac..1d03736 100644 --- a/vobject/base.py +++ b/vobject/base.py @@ -831,12 +831,12 @@ def parseLine(line, lineNumber=None): # logical line regular expressions -patterns['lineend'] = r'(?:\r\n|\r|\n|$)' +patterns['lineend'] = r'(?:\r\n|\r|\n)' patterns['wrap'] = r'{lineend!s} [\t ]'.format(**patterns) patterns['logicallines'] = r""" ( (?: [^\r\n] | {wrap!s} )* - {lineend!s} + (?: {lineend!s} | $ ) ) """.format(**patterns) @@ -883,7 +883,13 @@ def getLogicalLines(fp, allowQP=True): lineNumber = 1 for match in logical_lines_re.finditer(val): - line, n = wrap_re.subn('', match.group()) + log_line = match.group() + # It's possible that the final line of the vobject doesn't + # have a line ending. For ease of parsing, just add it. + # This should really be in an 'if not strict' branch. + if log_line[-1:] not in "\r\n": + log_line += "\r\n" + line, n = wrap_re.subn('', log_line) if line != '': yield line, lineNumber lineNumber += n