commit 4c5ec309ca6064e8c942e1e40491448a8c80c96d from: Jelmer Vernooij via: GitHub date: Fri Sep 02 10:40:40 2022 UTC Merge pull request #1020 from jelmer/config-closing-bracket Cope with closing bracket within quotes commit - a99f7ffc57dc4c3c349f8b430c9230c23a68bd8a commit + 4c5ec309ca6064e8c942e1e40491448a8c80c96d blob - cd475edb8c7a87f47dfce9825d2cfbefa4e97551 blob + 7f09ae57a41e4ec5ad9871016e300ad1652086b9 --- dulwich/config.py +++ dulwich/config.py @@ -483,6 +483,46 @@ def _strip_comments(line: bytes) -> bytes: return line +def _parse_section_header_line(line: bytes) -> Tuple[Section, bytes]: + # Parse section header ("[bla]") + line = _strip_comments(line).rstrip() + in_quotes = False + escaped = False + for i, c in enumerate(line): + if escaped: + escaped = False + continue + if c == ord(b'"'): + in_quotes = not in_quotes + if c == ord(b'\\'): + escaped = True + if c == ord(b']') and not in_quotes: + last = i + break + else: + raise ValueError("expected trailing ]") + pts = line[1:last].split(b" ", 1) + line = line[last + 1:] + section: Section + if len(pts) == 2: + if pts[1][:1] != b'"' or pts[1][-1:] != b'"': + raise ValueError("Invalid subsection %r" % pts[1]) + else: + pts[1] = pts[1][1:-1] + if not _check_section_name(pts[0]): + raise ValueError("invalid section name %r" % pts[0]) + section = (pts[0], pts[1]) + else: + if not _check_section_name(pts[0]): + raise ValueError("invalid section name %r" % pts[0]) + pts = pts[0].split(b".", 1) + if len(pts) == 2: + section = (pts[0], pts[1]) + else: + section = (pts[0],) + return section, line + + class ConfigFile(ConfigDict): """A Git configuration file, like .git/config or ~/.gitconfig.""" @@ -508,31 +548,8 @@ class ConfigFile(ConfigDict): line = line[3:] line = line.lstrip() if setting is None: - # Parse section header ("[bla]") if len(line) > 0 and line[:1] == b"[": - line = _strip_comments(line).rstrip() - try: - last = line.index(b"]") - except ValueError: - raise ValueError("expected trailing ]") - pts = line[1:last].split(b" ", 1) - line = line[last + 1 :] - if len(pts) == 2: - if pts[1][:1] != b'"' or pts[1][-1:] != b'"': - raise ValueError("Invalid subsection %r" % pts[1]) - else: - pts[1] = pts[1][1:-1] - if not _check_section_name(pts[0]): - raise ValueError("invalid section name %r" % pts[0]) - section = (pts[0], pts[1]) - else: - if not _check_section_name(pts[0]): - raise ValueError("invalid section name %r" % pts[0]) - pts = pts[0].split(b".", 1) - if len(pts) == 2: - section = (pts[0], pts[1]) - else: - section = (pts[0],) + section, line = _parse_section_header_line(line) ret._values.setdefault(section) if _strip_comments(line).strip() == b"": continue blob - 3078b6e638646abf6b8a5f739aa6d380b89f3cb2 blob + eea02e2c2e0e67ddfcd48c3e07731957da5f22fa --- dulwich/tests/test_config.py +++ dulwich/tests/test_config.py @@ -103,6 +103,10 @@ class ConfigFileTests(TestCase): def test_comment_character_within_section_string(self): cf = self.from_file(b'[branch "foo#bar"] # a comment\nbar= foo\n') self.assertEqual(ConfigFile({(b"branch", b"foo#bar"): {b"bar": b"foo"}}), cf) + + def test_closing_bracket_within_section_string(self): + cf = self.from_file(b'[branch "foo]bar"] # a comment\nbar= foo\n') + self.assertEqual(ConfigFile({(b"branch", b"foo]bar"): {b"bar": b"foo"}}), cf) def test_from_file_section(self): cf = self.from_file(b"[core]\nfoo = bar\n")