#include #include #include #include #include #include #include #include "validateODBCConnectionString.h" namespace DB { namespace ErrorCodes { extern const int BAD_ODBC_CONNECTION_STRING; } std::string validateODBCConnectionString(const std::string & connection_string) { /// Connection string is a list of name, value pairs. /// name and value are separated by '='. /// names are case insensitive. /// name=value pairs are separated by ';'. /// ASCII whitespace characters are skipped before and after delimiters. /// value may be optionally enclosed by {} /// in enclosed value, } is escaped as }}. /// /// Example: PWD={a}}b} means that password is a}b /// /// https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqldriverconnect-function?view=sql-server-2017#comments /// unixODBC has fixed size buffers on stack and has buffer overflow bugs. /// We will limit string sizes to small values. static constexpr size_t MAX_ELEMENT_SIZE = 100; static constexpr size_t MAX_CONNECTION_STRING_SIZE = 1000; if (connection_string.empty()) throw Exception("ODBC connection string cannot be empty", ErrorCodes::BAD_ODBC_CONNECTION_STRING); if (connection_string.size() >= MAX_CONNECTION_STRING_SIZE) throw Exception("ODBC connection string is too long", ErrorCodes::BAD_ODBC_CONNECTION_STRING); const char * pos = connection_string.data(); const char * end = pos + connection_string.size(); auto skip_whitespaces = [&] { while (pos < end && isWhitespaceASCII(*pos)) { if (*pos != ' ') throw Exception("ODBC connection string parameter contains unusual whitespace character", ErrorCodes::BAD_ODBC_CONNECTION_STRING); ++pos; } }; auto read_name = [&] { const char * begin = pos; if (pos < end && isValidIdentifierBegin(*pos)) ++pos; else throw Exception("ODBC connection string parameter name doesn't begin with valid identifier character", ErrorCodes::BAD_ODBC_CONNECTION_STRING); while (pos < end && isWordCharASCII(*pos)) ++pos; return std::string(begin, pos); }; auto read_plain_value = [&] { const char * begin = pos; while (pos < end && *pos != ';' && !isWhitespaceASCII(*pos)) { signed char c = *pos; if (c < 32 || strchr("[]{}(),;?*=!@'\"", c) != nullptr) throw Exception("ODBC connection string parameter value is unescaped and contains illegal character", ErrorCodes::BAD_ODBC_CONNECTION_STRING); ++pos; } return std::string(begin, pos); }; auto read_escaped_value = [&] { std::string res; if (pos < end && *pos == '{') ++pos; else throw Exception("ODBC connection string parameter value doesn't begin with opening curly brace", ErrorCodes::BAD_ODBC_CONNECTION_STRING); while (pos < end) { if (*pos == '}') { ++pos; if (pos >= end || *pos != '}') return res; } if (*pos == 0) throw Exception("ODBC connection string parameter value contains ASCII NUL character", ErrorCodes::BAD_ODBC_CONNECTION_STRING); res += *pos; ++pos; } throw Exception("ODBC connection string parameter is escaped but there is no closing curly brace", ErrorCodes::BAD_ODBC_CONNECTION_STRING); }; auto read_value = [&] { if (pos >= end) return std::string{}; if (*pos == '{') return read_escaped_value(); else return read_plain_value(); }; std::map parameters; while (pos < end) { skip_whitespaces(); std::string name = read_name(); skip_whitespaces(); Poco::toUpperInPlace(name); if (name == "FILEDSN" || name == "SAVEFILE" || name == "DRIVER") throw Exception("ODBC connection string has forbidden parameter", ErrorCodes::BAD_ODBC_CONNECTION_STRING); if (pos >= end) throw Exception("ODBC connection string parameter doesn't have value", ErrorCodes::BAD_ODBC_CONNECTION_STRING); if (*pos == '=') ++pos; else throw Exception("ODBC connection string parameter doesn't have value", ErrorCodes::BAD_ODBC_CONNECTION_STRING); skip_whitespaces(); std::string value = read_value(); skip_whitespaces(); if (name.size() > MAX_ELEMENT_SIZE || value.size() > MAX_ELEMENT_SIZE) throw Exception("ODBC connection string has too long keyword or value", ErrorCodes::BAD_ODBC_CONNECTION_STRING); if (!parameters.emplace(name, value).second) throw Exception("Duplicate parameter found in ODBC connection string", ErrorCodes::BAD_ODBC_CONNECTION_STRING); if (pos >= end) break; if (*pos == ';') ++pos; else throw Exception("Unexpected character found after parameter value in ODBC connection string", ErrorCodes::BAD_ODBC_CONNECTION_STRING); } /// Reconstruct the connection string. auto it = parameters.find("DSN"); if (parameters.end() == it) throw Exception("DSN parameter is mandatory for ODBC connection string", ErrorCodes::BAD_ODBC_CONNECTION_STRING); std::string dsn = it->second; if (dsn.empty()) throw Exception("DSN parameter cannot be empty in ODBC connection string", ErrorCodes::BAD_ODBC_CONNECTION_STRING); parameters.erase(it); std::string reconstructed_connection_string; auto write_plain_value = [&](const std::string & value) { reconstructed_connection_string += value; }; auto write_escaped_value = [&](const std::string & value) { reconstructed_connection_string += '{'; const char * value_pos = value.data(); const char * value_end = value_pos + value.size(); while (true) { const char * next_pos = find_first_symbols<'}'>(value_pos, value_end); if (next_pos == value_end) { reconstructed_connection_string.append(value_pos, next_pos - value_pos); break; } else { reconstructed_connection_string.append(value_pos, next_pos - value_pos); reconstructed_connection_string.append("}}"); value_pos = next_pos + 1; } } reconstructed_connection_string += '}'; }; auto write_value = [&](const std::string & value) { if (std::all_of(value.begin(), value.end(), isWordCharASCII)) write_plain_value(value); else write_escaped_value(value); }; auto write_element = [&](const std::string & name, const std::string & value) { reconstructed_connection_string.append(name); reconstructed_connection_string += '='; write_value(value); reconstructed_connection_string += ';'; }; /// Place DSN first because that's more safe. write_element("DSN", dsn); for (const auto & elem : parameters) write_element(elem.first, elem.second); if (reconstructed_connection_string.size() >= MAX_CONNECTION_STRING_SIZE) throw Exception("ODBC connection string is too long", ErrorCodes::BAD_ODBC_CONNECTION_STRING); return reconstructed_connection_string; } }