diff --git a/library/network_connections.py b/library/network_connections.py index 6b17fd25..03d994aa 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -301,7 +301,26 @@ def KeyValid(cls, name): @classmethod def ValueEscape(cls, value): - + """Quote a value for an ifcfg file, which is shell syntax. + + Two quoting styles are produced, matching the Bash Reference + Manual: + + ANSI-C quoting, $'...', used when the value contains a + character below 0x20 (Bash Reference Manual 3.1.2.4). + Backslash escapes are decoded per the ANSI C standard, so + \\nnn is the eight-bit character whose value is the *octal* + value nnn, one to three octal digits. Escapes are emitted at + a fixed three digits so they cannot absorb a following digit. + Backslash and single quote are escaped with a preceding + backslash. NUL cannot be represented; bash truncates the + string at it. + + Double quoting, "...", used otherwise (Bash Reference Manual + 3.1.2.3). Within double quotes the characters $, `, \\ and " + retain their special meaning and are escaped with a preceding + backslash. + """ r = getattr(cls, "_re_ValueEscape", None) if r is None: r = re.compile("^[a-zA-Z_0-9-.]*$") @@ -314,8 +333,8 @@ def ValueEscape(cls, value): # needs ansic escaping due to ANSI control characters (newline) s = "$'" for c in value: - if ord(c) < ord(c): - s += "\\" + str(ord(c)) + if ord(c) < ord(" "): + s += "\\%03o" % ord(c) elif c == "\\" or c == "'": s += "\\" + c else: diff --git a/tests/unit/test_network_connections.py b/tests/unit/test_network_connections.py index 12177f70..3f4ec489 100644 --- a/tests/unit/test_network_connections.py +++ b/tests/unit/test_network_connections.py @@ -5657,5 +5657,22 @@ def unstable_fetch(): self.assertEqual(fetch_mock.call_count, 51) +class TestIfcfgUtilValueEscape(unittest.TestCase): + def test_plain_value_is_not_quoted(self): + self.assertEqual(IfcfgUtil.ValueEscape("eth0"), "eth0") + + def test_control_char_escaped_as_octal(self): + self.assertEqual(IfcfgUtil.ValueEscape("line1\nline2"), "$'line1\\012line2'") + + def test_control_char_with_quote_and_backslash(self): + self.assertEqual(IfcfgUtil.ValueEscape("a\n'b\\c"), "$'a\\012\\'b\\\\c'") + + def test_double_quoting_path_is_unchanged(self): + self.assertEqual(IfcfgUtil.ValueEscape('a "b" $c'), '"a \\"b\\" \\$c"') + + def test_octal_escape_is_not_ambiguous_with_following_digit(self): + self.assertEqual(IfcfgUtil.ValueEscape("\x01" + "1"), "$'\\0011'") + + if __name__ == "__main__": unittest.main()