From 3a12f0fddcef3b5b2b1ca615487bc41607985f10 Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Sun, 7 Dec 2025 07:19:49 -0800 Subject: [PATCH 1/3] FIX: Update escaping rules in connection string parser and builder --- mssql_python/connection_string_builder.py | 10 +++++----- mssql_python/connection_string_parser.py | 17 ++++++----------- tests/test_010_connection_string_parser.py | 14 +++++++------- 3 files changed, 18 insertions(+), 23 deletions(-) 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..bf58475d 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: @@ -367,15 +367,10 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str, 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 + # Opening braces do not need escaping per ODBC spec + # Keep them as literal characters + value.append(ch) + start_pos += 1 else: # Regular character value.append(ch) diff --git a/tests/test_010_connection_string_parser.py b/tests/test_010_connection_string_parser.py index de21ceb0..2ed07a6b 100644 --- a/tests/test_010_connection_string_parser.py +++ b/tests/test_010_connection_string_parser.py @@ -58,16 +58,16 @@ def test_parse_braced_value_with_semicolon(self): assert result == {"server": ";local;host", "database": "mydb"} def test_parse_braced_value_with_escaped_right_brace(self): - """Test parsing braced values with escaped }}.""" + """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 per ODBC spec).""" 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.""" From 4b7e35fd4377a218362d69f49b97d62455df1981 Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Sun, 7 Dec 2025 11:12:48 -0800 Subject: [PATCH 2/3] FIX: Remove unnecessary escaping for opening braces in connection string parser --- mssql_python/connection_string_parser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/mssql_python/connection_string_parser.py b/mssql_python/connection_string_parser.py index bf58475d..9dd88db2 100644 --- a/mssql_python/connection_string_parser.py +++ b/mssql_python/connection_string_parser.py @@ -366,13 +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 == "{": - # Opening braces do not need escaping per ODBC spec - # Keep them as literal characters - 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 From 14a49300ee569eb78df3728f264a8b77a0766bda Mon Sep 17 00:00:00 2001 From: Saurabh Singh <1623701+saurabh500@users.noreply.github.com> Date: Sun, 7 Dec 2025 11:14:30 -0800 Subject: [PATCH 3/3] FIX: Update docstrings for braced value parsing tests for clarity --- tests/test_010_connection_string_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_010_connection_string_parser.py b/tests/test_010_connection_string_parser.py index 2ed07a6b..af55004d 100644 --- a/tests/test_010_connection_string_parser.py +++ b/tests/test_010_connection_string_parser.py @@ -58,7 +58,7 @@ def test_parse_braced_value_with_semicolon(self): assert result == {"server": ";local;host", "database": "mydb"} def test_parse_braced_value_with_escaped_right_brace(self): - """Test parsing braced values with escaped }}.""" + """Test parsing braced values with escaped }}.""" parser = _ConnectionStringParser() result = parser._parse("PWD={p}}w{{d}") assert result == {"pwd": "p}w{{d"} @@ -146,7 +146,7 @@ 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 {{ (not an escape sequence per ODBC spec).""" + """Test parsing braced value with {{ (not an escape sequence).""" parser = _ConnectionStringParser() result = parser._parse("Value={test{{value}") assert result == {"value": "test{{value"}