Skip to content

Commit 4e690b9

Browse files
committed
Implement quoting rules in procedural ODBC
Right now, it just blithely duplicates the functions backing it, and the logic has been adapted to fit the procedural ODBC one. Putting this in `main/` or something would be a good idea to avoid having to keep this function in multiple places. I see some potential for more sharing and alteration of the rules in both though.
1 parent da680cd commit 4e690b9

File tree

1 file changed

+143
-2
lines changed

1 file changed

+143
-2
lines changed

ext/odbc/php_odbc.c

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,97 @@
4848

4949
#endif
5050

51+
/**
52+
* Determines if a string matches the ODBC quoting rules.
53+
*
54+
* A valid quoted string begins with a '{', ends with a '}', and has no '}'
55+
* inside of the string that aren't repeated (as to be escaped).
56+
*
57+
* These rules are what .NET also follows.
58+
*/
59+
static bool is_odbc_quoted(const char *str)
60+
{
61+
if (!str) {
62+
return false;
63+
}
64+
/* ODBC quotes are curly braces */
65+
if (str[0] != '{') {
66+
return false;
67+
}
68+
/* Check for } that aren't doubled up or at the end of the string */
69+
size_t length = strlen(str);
70+
for (size_t i = 0; i < length; i++) {
71+
if (str[i] == '}' && str[i + 1] == '}') {
72+
/* Skip over so we don't count it again */
73+
i++;
74+
} else if (str[i] == '}' && str[i + 1] != '\0') {
75+
/* If not at the end, not quoted */
76+
return false;
77+
}
78+
}
79+
return true;
80+
}
81+
82+
/**
83+
* Determines if a value for a connection string should be quoted.
84+
*
85+
* The ODBC specification mentions:
86+
* "Because of connection string and initialization file grammar, keywords and
87+
* and attribute values that contain the characters []{}(),;?*=!@ not enclosed
88+
* with braces should be avoided."
89+
*
90+
* Note that it assumes that the string is *not* already quoted. You should
91+
* check beforehand.
92+
*/
93+
static bool should_odbc_quote(const char *str)
94+
{
95+
return strpbrk(str, "[]{}(),;?*=!@") != NULL;
96+
}
97+
98+
/**
99+
* Estimates the worst-case scenario for a quoted version of a string's size.
100+
*/
101+
static size_t estimate_odbc_quote_length(const char *in_str)
102+
{
103+
/* Assume all '}'. Include '{,' '}', and the null terminator too */
104+
return (strlen(in_str) * 2) + 3;
105+
}
106+
107+
/**
108+
* Quotes a string with ODBC rules.
109+
*
110+
* Some characters (curly braces, semicolons) are special and must be quoted.
111+
* In the case of '}' in a quoted string, they must be escaped SQL style; that
112+
* is, repeated.
113+
*/
114+
static size_t odbc_quote(char *out_str, const char *in_str, size_t out_str_size)
115+
{
116+
*out_str++ = '{';
117+
out_str_size--;
118+
while (out_str_size > 2) {
119+
if (*in_str == '\0') {
120+
break;
121+
} else if (*in_str == '}' && out_str_size - 1 > 2) {
122+
/* enough room to append */
123+
*out_str++ = '}';
124+
*out_str++ = *in_str++;
125+
out_str_size -= 2;
126+
} else if (*in_str == '}') {
127+
/* not enough, truncate here */
128+
break;
129+
} else {
130+
*out_str++ = *in_str++;
131+
out_str_size--;
132+
}
133+
}
134+
/* append termination */
135+
*out_str++ = '}';
136+
*out_str++ = '\0';
137+
out_str_size -= 2;
138+
/* return how many characters were left */
139+
return strlen(in_str);
140+
}
141+
51142
/*
52143
* not defined elsewhere
53144
*/
@@ -2169,8 +2260,58 @@ int odbc_sqlconnect(odbc_connection **conn, char *db, char *uid, char *pwd, int
21692260

21702261
if (strstr((char*)db, ";")) {
21712262
direct = 1;
2172-
if (uid && !strstr ((char*)db, "uid") && !strstr((char*)db, "UID")) {
2173-
spprintf(&ldb, 0, "%s;UID=%s;PWD=%s", db, uid, pwd);
2263+
/* Force UID and PWD to be set in the DSN */
2264+
bool is_uid_set = uid && *uid
2265+
&& !strstr(db, "uid=")
2266+
&& !strstr(db, "UID=");
2267+
bool is_pwd_set = pwd && *pwd
2268+
&& !strstr(db, "pwd=")
2269+
&& !strstr(db, "PWD=");
2270+
if (is_uid_set && is_pwd_set) {
2271+
char *uid_quoted = NULL, *pwd_quoted = NULL;
2272+
bool should_quote_uid = !is_odbc_quoted(uid) && should_odbc_quote(uid);
2273+
bool should_quote_pwd = !is_odbc_quoted(pwd) && should_odbc_quote(pwd);
2274+
bool connection_string_fail = false;
2275+
if (should_quote_uid) {
2276+
size_t estimated_length = estimate_odbc_quote_length(uid);
2277+
uid_quoted = emalloc(estimated_length);
2278+
if (!uid_quoted) {
2279+
connection_string_fail = true;
2280+
goto connection_string_fail;
2281+
}
2282+
odbc_quote(uid_quoted, uid, estimated_length);
2283+
} else {
2284+
uid_quoted = uid;
2285+
}
2286+
if (should_quote_pwd) {
2287+
size_t estimated_length = estimate_odbc_quote_length(pwd);
2288+
pwd_quoted = emalloc(estimated_length);
2289+
if (!pwd_quoted) {
2290+
connection_string_fail = true;
2291+
goto connection_string_fail;
2292+
}
2293+
odbc_quote(pwd_quoted, pwd, estimated_length);
2294+
} else {
2295+
pwd_quoted = pwd;
2296+
}
2297+
spprintf(&ldb, 0, "%s;UID=%s;PWD=%s", db, uid_quoted, pwd_quoted);
2298+
connection_string_fail:
2299+
if (uid_quoted && should_quote_uid) {
2300+
efree(uid_quoted);
2301+
}
2302+
if (pwd_quoted && should_quote_pwd) {
2303+
efree(pwd_quoted);
2304+
}
2305+
/*
2306+
* In the PDO version, we fail, but we don't
2307+
* really have the facility for that, so fall
2308+
* back to the old unquoted case. Note that
2309+
* the success case hasn't been allocated, so
2310+
* it should be safe.
2311+
*/
2312+
if (connection_string_fail) {
2313+
spprintf(&ldb, 0, "%s;UID=%s;PWD=%s", db, uid, pwd);
2314+
}
21742315
} else {
21752316
ldb_len = strlen(db)+1;
21762317
ldb = (char*) emalloc(ldb_len);

0 commit comments

Comments
 (0)