From 27068592a0748690a0fd88cf095965e97bd8b767 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 17 Feb 2013 23:12:45 +0100 Subject: [PATCH] Fix connection parsing in SOCI Firebird backend to handle spaces in values. Really parse the connection strings instead of just breaking them on spaces. This allows to detect errors such as not using "key=value" syntax at all but most importantly allows to use spaces inside the values, as can be necessary for the "service" parameter value when using embedded Firebird as the file names can contain spaces. Signed-off-by: Vadim Zeitlin --- src/backends/firebird/session.cpp | 159 +++++++++++++++++++++++++++--- 1 file changed, 144 insertions(+), 15 deletions(-) diff --git a/src/backends/firebird/session.cpp b/src/backends/firebird/session.cpp index dd18fe2b3..d8d163a37 100644 --- a/src/backends/firebird/session.cpp +++ b/src/backends/firebird/session.cpp @@ -9,6 +9,7 @@ #include "soci-firebird.h" #include "error-firebird.h" #include "session.h" +#include #include #include #include @@ -19,32 +20,160 @@ using namespace soci::details::firebird; namespace { -// retrieves parameters from the uniform connect string -void explodeISCConnectString(std::string const &connectString, - std::map ¶meters) +// Helpers of explodeISCConnectString() for reading words from a string. "Word" +// here is defined very loosely as just a sequence of non-space characters. +// +// All these helper functions update the input iterator to point to the first +// character not consumed by them. + +// Advance the input iterator until the first non-space character or end of the +// string. +void skipWhiteSpace(std::string::const_iterator& i, std::string::const_iterator const &end) { - std::string tmp; - for (std::string::const_iterator i = connectString.begin(), - end = connectString.end(); i != end; ++i) + std::locale const loc; + for (; i != end; ++i) { - if (*i == '=') + if (!std::isspace(*i, loc)) + break; + } +} + +// Return the string of all characters until the first space or the specified +// delimiter. +// +// Throws if the first non-space character after the end of the word is not the +// delimiter. However just returns en empty string, without throwing, if +// nothing is left at all in the string except for white space. +std::string +getWordUntil(std::string const &s, std::string::const_iterator &i, char delim) +{ + std::string::const_iterator const end = s.end(); + skipWhiteSpace(i, end); + + // We need to handle this case specially because it's not an error if + // nothing at all remains in the string. But if anything does remain, then + // we must have the delimiter. + if (i == end) + return std::string(); + + // Simply put anything until the delimiter into the word, stopping at the + // first white space character. + std::string word; + std::locale const loc; + for (; i != end; ++i) + { + if (*i == delim) + break; + + if (std::isspace(*i, loc)) { - tmp += ' '; + skipWhiteSpace(i, end); + if (i == end || *i != delim) + { + std::ostringstream os; + os << "Expected '" << delim << "' at position " + << (i - s.begin() + 1) + << " in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + break; } - else + + word += *i; + } + + if (i == end) + { + std::ostringstream os; + os << "Expected '" << delim + << "' not found before the end of the string " + << "in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + ++i; // Skip the delimiter itself. + + return word; +} + +// Return a possibly quoted word, i.e. either just a sequence of non-space +// characters or everything inside a double-quoted string. +// +// Throws if the word is quoted and the closing quote is not found. However +// doesn't throw, just returns an empty string if there is nothing left. +std::string +getPossiblyQuotedWord(std::string const &s, std::string::const_iterator &i) +{ + std::string::const_iterator const end = s.end(); + skipWhiteSpace(i, end); + + std::string word; + + if (i != end && *i == '"') + { + for (;;) { - tmp += *i; + if (++i == end) + { + std::ostringstream os; + os << "Expected '\"' not found before the end of the string " + "in Firebird connection string \"" + << s << "\"."; + + throw soci_error(os.str()); + } + + if (*i == '"') + { + ++i; + break; + } + + word += *i; } } + else // Not quoted. + { + std::locale const loc; + for (; i != end; ++i) + { + if (std::isspace(*i, loc)) + break; - parameters.clear(); + word += *i; + } + } + + return word; +} + +// retrieves parameters from the uniform connect string which is supposed to be +// in the form "key=value[ key2=value2 ...]" and the values may be quoted to +// allow including spaces into them. Notice that currently there is no way to +// include both a space and a double quote in a value. +std::map +explodeISCConnectString(std::string const &connectString) +{ + std::map parameters; - std::istringstream iss(tmp); std::string key, value; - while (iss >> key >> value) + for (std::string::const_iterator i = connectString.begin(); ; ) { + key = getWordUntil(connectString, i, '='); + if (key.empty()) + break; + + value = getPossiblyQuotedWord(connectString, i); + parameters.insert(std::pair(key, value)); } + + return parameters; } // extracts given parameter from map previusly build with explodeISCConnectString @@ -74,8 +203,8 @@ firebird_session_backend::firebird_session_backend( , decimals_as_strings_(false) { // extract connection parameters - std::map params; - explodeISCConnectString(connectString, params); + std::map + params(explodeISCConnectString(connectString)); ISC_STATUS stat[stat_size]; std::string param;