fix: M3U EXTINF parsing for attributes ending in double equals

fixes: #1055
This commit is contained in:
Jonathan Caicedo 2026-03-06 08:10:30 -05:00
parent 11671e9c4f
commit a51eb37075
2 changed files with 45 additions and 2 deletions

View file

@ -482,8 +482,10 @@ def parse_extinf_line(line: str) -> dict:
attrs = {}
last_attr_end = 0
# Use a single regex that handles both quote types
for match in re.finditer(r'([^\s]+)=(["\'])([^\2]*?)\2', content):
# Use a single regex that handles both quote types.
# Keys must stop at '=' so values like base64-padded URLs ending with '=='
# don't get folded into the preceding attribute name.
for match in re.finditer(r'([^\s=]+)\s*=\s*(["\'])(.*?)\2', content):
key = match.group(1)
value = match.group(3)
attrs[key] = value

View file

@ -0,0 +1,41 @@
from django.test import SimpleTestCase
from apps.m3u.tasks import parse_extinf_line
class ParseExtinfLineTests(SimpleTestCase):
def test_preserves_equals_padding_in_tvg_logo(self):
line = (
'#EXTINF:-1 tvg-id="cp_891ee08a2cdfde210ec2c9137127103b" '
'tvg-chno="1001" '
'tvg-name="UK Sky Sports Premier League" '
'tvg-logo="https://e3.365dm.com/tvlogos/channels/1303-Logo.png?'
'U2t5IFNwb3J0cyBQcmVtaWVyIExlYWd1ZQ==" '
'group-title="Team Games",UK Sky Sports Premier League'
)
parsed = parse_extinf_line(line)
self.assertIsNotNone(parsed)
self.assertEqual(
parsed["attributes"]["tvg-logo"],
"https://e3.365dm.com/tvlogos/channels/1303-Logo.png?U2t5IFNwb3J0cyBQcmVtaWVyIExlYWd1ZQ==",
)
self.assertEqual(parsed["attributes"]["group-title"], "Team Games")
self.assertEqual(parsed["name"], "UK Sky Sports Premier League")
def test_supports_single_quoted_attributes(self):
line = (
"#EXTINF:-1 tvg-name='Channel One' tvg-logo='https://example.com/logo==.png' "
"group-title='Sports',Channel One"
)
parsed = parse_extinf_line(line)
self.assertIsNotNone(parsed)
self.assertEqual(
parsed["attributes"]["tvg-logo"],
"https://example.com/logo==.png",
)
self.assertEqual(parsed["attributes"]["group-title"], "Sports")
self.assertEqual(parsed["display_name"], "Channel One")