diff --git a/mssql_python/connection_string_builder.py b/mssql_python/connection_string_builder.py index 69277f52..257cf9f1 100644 --- a/mssql_python/connection_string_builder.py +++ b/mssql_python/connection_string_builder.py @@ -77,10 +77,9 @@ def _escape_value(self, value: str) -> str: """ Escape a parameter value if it contains special characters. - Per MS-ODBCSTR specification: - Values containing ';', '{', '}', '=', or spaces should be braced for safety - '}' inside braced values is escaped as '}}' - - '{' inside braced values is escaped as '{{' + - '{' does not need to be escaped Args: value: Parameter value to escape @@ -95,7 +94,7 @@ def _escape_value(self, value: str) -> str: >>> builder._escape_value("local;host") '{local;host}' >>> builder._escape_value("p}w{d") - '{p}}w{{d}' + '{p}}w{d}' >>> builder._escape_value("ODBC Driver 18 for SQL Server") '{ODBC Driver 18 for SQL Server}' """ @@ -107,8 +106,9 @@ def _escape_value(self, value: str) -> str: needs_braces = any(ch in value for ch in ";{}= ") if needs_braces: - # Escape existing braces by doubling them - escaped = value.replace("}", "}}").replace("{", "{{") + # Escape closing braces by doubling them (ODBC requirement) + # Opening braces do not need to be escaped + escaped = value.replace("}", "}}") return f"{{{escaped}}}" else: return value diff --git a/mssql_python/connection_string_parser.py b/mssql_python/connection_string_parser.py index 46125cdd..9dd88db2 100644 --- a/mssql_python/connection_string_parser.py +++ b/mssql_python/connection_string_parser.py @@ -7,7 +7,7 @@ Handles ODBC-specific syntax per MS-ODBCSTR specification: - Semicolon-separated key=value pairs - Braced values: {value} -- Escaped braces: }} → }, {{ → { +- Escaped braces: }} → } (only closing braces need escaping) Parser behavior: - Validates all key=value pairs @@ -331,7 +331,7 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str, Braced values: - Start with '{' and end with '}' - '}' inside the value is escaped as '}}' - - '{' inside the value is escaped as '{{' + - '{' inside the value does not need escaping - Can contain semicolons and other special characters Args: @@ -366,18 +366,8 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str, # Single '}' means end of braced value start_pos += 1 return "".join(value), start_pos - elif ch == "{": - # Check if it's an escaped left brace - if start_pos + 1 < str_len and connection_str[start_pos + 1] == "{": - # Escaped left brace: '{{' → '{' - value.append("{") - start_pos += 2 - else: - # Single '{' inside braced value - keep it as is - value.append(ch) - start_pos += 1 else: - # Regular character + # Regular character (including '{' which doesn't need escaping per ODBC spec) value.append(ch) start_pos += 1 diff --git a/tests/test_010_connection_string_parser.py b/tests/test_010_connection_string_parser.py index de21ceb0..af55004d 100644 --- a/tests/test_010_connection_string_parser.py +++ b/tests/test_010_connection_string_parser.py @@ -61,13 +61,13 @@ def test_parse_braced_value_with_escaped_right_brace(self): """Test parsing braced values with escaped }}.""" parser = _ConnectionStringParser() result = parser._parse("PWD={p}}w{{d}") - assert result == {"pwd": "p}w{d"} + assert result == {"pwd": "p}w{{d"} def test_parse_braced_value_with_all_escapes(self): - """Test parsing braced values with both {{ and }} escapes.""" + """Test parsing braced values with }} escape ({{ not an escape sequence).""" parser = _ConnectionStringParser() result = parser._parse("Value={test}}{{escape}") - assert result == {"value": "test}{escape"} + assert result == {"value": "test}{{escape"} def test_parse_empty_value(self): """Test that empty value raises error.""" @@ -146,10 +146,10 @@ def test_parse_braced_value_with_left_brace(self): assert result == {"value": "test{value"} def test_parse_braced_value_double_left_brace(self): - """Test parsing braced value with escaped {{ (left brace).""" + """Test parsing braced value with {{ (not an escape sequence).""" parser = _ConnectionStringParser() result = parser._parse("Value={test{{value}") - assert result == {"value": "test{value"} + assert result == {"value": "test{{value"} def test_parse_unicode_characters(self): """Test parsing values with unicode characters.""" @@ -197,7 +197,7 @@ def test_parse_special_characters_in_braced_values(self): # Multiple special chars including braces result = parser._parse("Token={Bearer: abc123; Expires={{2024-01-01}}}") - assert result == {"token": "Bearer: abc123; Expires={2024-01-01}"} + assert result == {"token": "Bearer: abc123; Expires={{2024-01-01}"} def test_parse_numbers_and_symbols_in_passwords(self): """Test parsing passwords with various numbers and symbols."""