Skip to content

Commit db68b2a

Browse files
authored
FIX: Update escaping rules in connection string parser and builder (#364)
### Work Item / Issue Reference <!-- External contributors: GitHub Issue --> > GitHub Issue: #363 ------------------------------------------------------------------- ### Summary <!-- Insert your summary of changes below. Minimum 10 characters required. --> FIX: The escaping rules for the parser and the builder The parser was treating {{ as an escaped string and was parsing it as { and then the builder was escaping { to {{ However the ODBC driver doesn't require escaping of the opening brace, if its already wrapped in {}. Only closing brace values inside curlies require escaping. Hence a literal pw}d needs to be sent as {pw}}d} Fixed the parser and the builder to take care of this nuance. Fixes #363
1 parent cc3940c commit db68b2a

File tree

3 files changed

+14
-24
lines changed

3 files changed

+14
-24
lines changed

mssql_python/connection_string_builder.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,9 @@ def _escape_value(self, value: str) -> str:
7777
"""
7878
Escape a parameter value if it contains special characters.
7979
80-
Per MS-ODBCSTR specification:
8180
- Values containing ';', '{', '}', '=', or spaces should be braced for safety
8281
- '}' inside braced values is escaped as '}}'
83-
- '{' inside braced values is escaped as '{{'
82+
- '{' does not need to be escaped
8483
8584
Args:
8685
value: Parameter value to escape
@@ -95,7 +94,7 @@ def _escape_value(self, value: str) -> str:
9594
>>> builder._escape_value("local;host")
9695
'{local;host}'
9796
>>> builder._escape_value("p}w{d")
98-
'{p}}w{{d}'
97+
'{p}}w{d}'
9998
>>> builder._escape_value("ODBC Driver 18 for SQL Server")
10099
'{ODBC Driver 18 for SQL Server}'
101100
"""
@@ -107,8 +106,9 @@ def _escape_value(self, value: str) -> str:
107106
needs_braces = any(ch in value for ch in ";{}= ")
108107

109108
if needs_braces:
110-
# Escape existing braces by doubling them
111-
escaped = value.replace("}", "}}").replace("{", "{{")
109+
# Escape closing braces by doubling them (ODBC requirement)
110+
# Opening braces do not need to be escaped
111+
escaped = value.replace("}", "}}")
112112
return f"{{{escaped}}}"
113113
else:
114114
return value

mssql_python/connection_string_parser.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Handles ODBC-specific syntax per MS-ODBCSTR specification:
88
- Semicolon-separated key=value pairs
99
- Braced values: {value}
10-
- Escaped braces: }} → }, {{ → {
10+
- Escaped braces: }} → } (only closing braces need escaping)
1111
1212
Parser behavior:
1313
- Validates all key=value pairs
@@ -331,7 +331,7 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str,
331331
Braced values:
332332
- Start with '{' and end with '}'
333333
- '}' inside the value is escaped as '}}'
334-
- '{' inside the value is escaped as '{{'
334+
- '{' inside the value does not need escaping
335335
- Can contain semicolons and other special characters
336336
337337
Args:
@@ -366,18 +366,8 @@ def _parse_braced_value(self, connection_str: str, start_pos: int) -> Tuple[str,
366366
# Single '}' means end of braced value
367367
start_pos += 1
368368
return "".join(value), start_pos
369-
elif ch == "{":
370-
# Check if it's an escaped left brace
371-
if start_pos + 1 < str_len and connection_str[start_pos + 1] == "{":
372-
# Escaped left brace: '{{' → '{'
373-
value.append("{")
374-
start_pos += 2
375-
else:
376-
# Single '{' inside braced value - keep it as is
377-
value.append(ch)
378-
start_pos += 1
379369
else:
380-
# Regular character
370+
# Regular character (including '{' which doesn't need escaping per ODBC spec)
381371
value.append(ch)
382372
start_pos += 1
383373

tests/test_010_connection_string_parser.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,13 @@ def test_parse_braced_value_with_escaped_right_brace(self):
6161
"""Test parsing braced values with escaped }}."""
6262
parser = _ConnectionStringParser()
6363
result = parser._parse("PWD={p}}w{{d}")
64-
assert result == {"pwd": "p}w{d"}
64+
assert result == {"pwd": "p}w{{d"}
6565

6666
def test_parse_braced_value_with_all_escapes(self):
67-
"""Test parsing braced values with both {{ and }} escapes."""
67+
"""Test parsing braced values with }} escape ({{ not an escape sequence)."""
6868
parser = _ConnectionStringParser()
6969
result = parser._parse("Value={test}}{{escape}")
70-
assert result == {"value": "test}{escape"}
70+
assert result == {"value": "test}{{escape"}
7171

7272
def test_parse_empty_value(self):
7373
"""Test that empty value raises error."""
@@ -146,10 +146,10 @@ def test_parse_braced_value_with_left_brace(self):
146146
assert result == {"value": "test{value"}
147147

148148
def test_parse_braced_value_double_left_brace(self):
149-
"""Test parsing braced value with escaped {{ (left brace)."""
149+
"""Test parsing braced value with {{ (not an escape sequence)."""
150150
parser = _ConnectionStringParser()
151151
result = parser._parse("Value={test{{value}")
152-
assert result == {"value": "test{value"}
152+
assert result == {"value": "test{{value"}
153153

154154
def test_parse_unicode_characters(self):
155155
"""Test parsing values with unicode characters."""
@@ -197,7 +197,7 @@ def test_parse_special_characters_in_braced_values(self):
197197

198198
# Multiple special chars including braces
199199
result = parser._parse("Token={Bearer: abc123; Expires={{2024-01-01}}}")
200-
assert result == {"token": "Bearer: abc123; Expires={2024-01-01}"}
200+
assert result == {"token": "Bearer: abc123; Expires={{2024-01-01}"}
201201

202202
def test_parse_numbers_and_symbols_in_passwords(self):
203203
"""Test parsing passwords with various numbers and symbols."""

0 commit comments

Comments
 (0)