diff --git a/changes/908.fixed b/changes/908.fixed new file mode 100644 index 00000000..9ab25675 --- /dev/null +++ b/changes/908.fixed @@ -0,0 +1 @@ +Fixed an object name containing a bracket raising `ValueError: Input line is malformed.` in `FortinetConfigParser` and losing the whole config. The restore of a `config system replacemsg` placeholder was guarded by the presence of a bracket alone, so any bracketed name was mistaken for one. diff --git a/netutils/config/parser.py b/netutils/config/parser.py index 09c9f6e0..64b1f653 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -1119,10 +1119,13 @@ def _build_nested_config(self, line: str) -> t.Optional[str]: IndexError: When the number of parents does not match the expected deindent level. """ if "[" in line: - updated_line = self.uncommon_data.get(line.split('"')[1], None) - if not updated_line: - raise ValueError("Input line is malformed.") - line = updated_line + # Only a placeholder this parser put here, and nothing else. The + # guard used to be the bracket alone, so any object name containing + # one was taken for a placeholder and the lookup miss raised, losing + # the whole configuration. + parts = line.split('"') + if len(parts) > 2 and parts[1] in self.uncommon_data: + line = self.uncommon_data[parts[1]] self._update_config_lines(line) for line in self.generator_config: if not line[0].isspace(): diff --git a/tests/unit/test_parser.py b/tests/unit/test_parser.py index f98f9751..8988f2b4 100644 --- a/tests/unit/test_parser.py +++ b/tests/unit/test_parser.py @@ -70,6 +70,30 @@ def test_incorrect_banner_ios(): compliance.parser_map["cisco_ios"](banner_cfg).config_lines # pylint: disable=expression-not-assigned +def test_fortinet_bracket_in_object_name(): + """A bracket in an object name is not a replacemsg placeholder. + + _parse_out_offending replaces a `config system replacemsg` buffer with a + `[""]` placeholder and _build_nested_config restores it, but the guard + was `"[" in line` alone. Any object name containing a bracket was taken for a + placeholder, and the lookup miss raised ValueError out of __init__. + """ + cfg = 'config firewall address\n edit "obj[1]"\n set subnet 10.0.0.0 255.0.0.0\n next\nend\n' + config_lines = compliance.parser_map["fortinet_fortios"](cfg).config_lines + assert [line.config_line for line in config_lines] == [ + "config firewall address", + ' edit "obj[1]"', + " set subnet 10.0.0.0 255.0.0.0", + ] + + +def test_fortinet_replacemsg_buffer_still_restored(): + """The guard above must not stop a genuine placeholder being restored.""" + cfg = 'config system replacemsg webproxy "deny"\n set buffer "body"\nend\n' + config_lines = compliance.parser_map["fortinet_fortios"](cfg).config_lines + assert any("set buffer" in line.config_line for line in config_lines) + + def test_duplicate_line(): logging = ( "!\n"