|
48 | 48 |
|
49 | 49 | #endif
|
50 | 50 |
|
| 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 | + |
51 | 142 | /*
|
52 | 143 | * not defined elsewhere
|
53 | 144 | */
|
@@ -2169,8 +2260,58 @@ int odbc_sqlconnect(odbc_connection **conn, char *db, char *uid, char *pwd, int
|
2169 | 2260 |
|
2170 | 2261 | if (strstr((char*)db, ";")) {
|
2171 | 2262 | 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 | + } |
2174 | 2315 | } else {
|
2175 | 2316 | ldb_len = strlen(db)+1;
|
2176 | 2317 | ldb = (char*) emalloc(ldb_len);
|
|
0 commit comments