#include "ConnectionConfig.h" #include "util.h" #include #include #include #include namespace { struct { SslMode mode; const char* string; } SslModeStringTable[] = { { SslMode::disable, "disable" }, { SslMode::allow, "allow" }, { SslMode::prefer, "prefer" }, { SslMode::require, "require" }, { SslMode::verify_ca, "verify-ca" }, { SslMode::verify_full, "verify-full" } }; inline const char *valuePtr(const std::string &v) { return v.empty() ? nullptr : v.c_str(); } struct { const char * host = "host"; const char * hostaddr = "hostaddr"; const char * port = "port"; const char * dbname = "dbname"; const char * user = "user"; const char * password = "password"; const char * sslmode = "sslmode"; const char * sslcert = "sslcert"; const char * sslkey = "sslkey"; const char * sslrootcert = "sslrootcert"; const char * sslcrl = "sslcrl"; } keywords; } // end unnamed namespace QString SslModeToString(SslMode sm) { for (auto e : SslModeStringTable) if (e.mode == sm) return QString::fromUtf8(e.string); return {}; } SslMode StringToSslMode(QString s) { SslMode result = SslMode::allow; for (auto e : SslModeStringTable) if (e.string == s) result = e.mode; return {}; } ConnectionConfig::ConnectionConfig() : m_parameters{{"application_name", QCoreApplication::applicationName()}} {} const ConnectionGroup *ConnectionConfig::parent() const { return m_group; } void ConnectionConfig::setParent(ConnectionGroup *grp) { m_group = grp; } void ConnectionConfig::setUuid(const QUuid &uuid) { if (uuid != m_uuid) { m_dirty = true; m_uuid = uuid; } } const QUuid &ConnectionConfig::uuid() const { return m_uuid; } void ConnectionConfig::setName(const QString& desc) { if (m_name != desc) { m_dirty = true; m_name = std::move(desc); } } const QString& ConnectionConfig::name() const { return m_name; } void ConnectionConfig::setHost(const QString& host) { setParameter(keywords.host, host); } QString ConnectionConfig::host() const { return getParameter(keywords.host); } void ConnectionConfig::setHostAddr(const QString &v) { setParameter(keywords.hostaddr, v); } QString ConnectionConfig::hostAddr() const { return getParameter(keywords.hostaddr); } void ConnectionConfig::setPort(unsigned short port) { setParameter(keywords.port, QString::number(port)); } unsigned short ConnectionConfig::port() const { QString s = getParameter(keywords.port); if (s.isEmpty()) return 5432; unsigned short port = static_cast(s.toInt()); return port; } void ConnectionConfig::setUser(const QString& v) { setParameter(keywords.user, v); } QString ConnectionConfig::user() const { return getParameter(keywords.user); } void ConnectionConfig::setPassword(const QString& v) { setParameter(keywords.password, v); } QString ConnectionConfig::password() const { return getParameter(keywords.password); } void ConnectionConfig::setDbname(const QString& v) { setParameter(keywords.dbname, v); } QString ConnectionConfig::dbname() const { return getParameter(keywords.dbname); } void ConnectionConfig::setSslMode(SslMode m) { setParameter(keywords.sslmode, SslModeToString(m)); } SslMode ConnectionConfig::sslMode() const { QString s = getParameter(keywords.sslmode); return StringToSslMode(s); } void ConnectionConfig::setSslCert(const QString& v) { setParameter(keywords.sslcert, v); } QString ConnectionConfig::sslCert() const { return getParameter(keywords.sslcert); } void ConnectionConfig::setSslKey(const QString& v) { setParameter(keywords.sslkey, v); } QString ConnectionConfig::sslKey() const { return getParameter(keywords.sslkey); } void ConnectionConfig::setSslRootCert(const QString& v) { setParameter(keywords.sslrootcert, v); } QString ConnectionConfig::sslRootCert() const { return getParameter(keywords.sslrootcert); } void ConnectionConfig::setSslCrl(const QString& v) { setParameter(keywords.sslcrl, v); } QString ConnectionConfig::sslCrl() const { return getParameter(keywords.sslcrl); } // bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const // { // return host() == rhs.host() // // && m_hostaddr == rhs.m_hostaddr // // && m_port == rhs.m_port // // && m_user == rhs.m_user // // && m_password == rhs.m_password // // && m_dbname == rhs.m_dbname // ; // } bool ConnectionConfig::dirty() const { return m_dirty; } void ConnectionConfig::clean() { m_dirty = false; } QString ConnectionConfig::makeLongDescription() const { QString result; result = name() % " (" % user() % "@" % host() % ":" % QString::number(port()) % "/" % dbname() % ")"; return result; } const QByteArray& ConnectionConfig::encodedPassword() const { return m_encodedPassword; } void ConnectionConfig::setEncodedPassword(const QByteArray &encodedPassword) { m_dirty = true; m_encodedPassword = encodedPassword; } QString ConnectionConfig::escapeConnectionStringValue(const QString &value) { bool contains_spaces = false; int escapes = 0; for (auto&& c : value) if (c == ' ') contains_spaces = true; else if (c == '\'' || c == '\\') ++escapes; if (contains_spaces || escapes > 0 || value.length() == 0) { QString result; result.reserve(2 + value.length() + escapes); result += '\''; for (auto&& c : value) { if (c == '\'' || c == '\\') result += '\\'; result += c; } result += '\''; return result; } else return value; } QString ConnectionConfig::connectionString() const { QString s; for (auto && param : m_parameters) { // maybe we should prevent empty parameters from staying in the map? if (!param.second.isEmpty()) { s += param.first % "=" % escapeConnectionStringValue(param.second); } } // s += "host=" // % escapeConnectionStringValue(m_host) // % " port=" // % QString::number(m_port) // % " user=" // % escapeConnectionStringValue(m_user); // s += " password="; // s += escapeConnectionStringValue(m_password); // s += " dbname="; // s += escapeConnectionStringValue(m_dbname); // s += " sslmode="; // s += SslModeToString(m_sslMode); // if (!m_sslCert.isEmpty()) // { // s += " sslcert="; // s += escapeConnectionStringValue(m_sslCert); // } // if (!m_sslKey.isEmpty()) // { // s += " sslkey="; // s += escapeConnectionStringValue(m_sslKey); // } // if (!m_sslRootCert.isEmpty()) // { // s += " sslrootcrt="; // s += escapeConnectionStringValue(m_sslRootCert); // } // if (!m_sslCrl.isEmpty()) // { // s += " sslCrl="; // s += escapeConnectionStringValue(m_sslCrl); // } // s += " client_encoding=utf8"; // s += " application_name="; // s += escapeConnectionStringValue(m_applicationName); return s; } void ConnectionConfig::setParameter(const QString &name, const QString &value) { if (value.isEmpty()) { if (m_parameters.erase(name) > 0) { m_dirty = true; } } else { auto findResult = m_parameters.find(name); if (findResult == m_parameters.end()) { m_parameters.insert({name, value}); m_dirty = true; } else if (findResult->second != value) { findResult->second = value; m_dirty = true; } } //if (name == "sslMode") // m_sslMode = StringToSslMode(value); // Would it better to store everything in the map or keep the specific // fields for common keywords? // Map over fields // + can use foreach // - not strongly typed, but we should be carefull not to restrict ourselves // the specific fields are more something that helps in the UI } QString ConnectionConfig::getParameter(const QString &name) const { auto findResult = m_parameters.find(name); if (findResult == m_parameters.end()) return {}; return findResult->second; } void ConnectionConfig::writeToEnvironment(QProcessEnvironment &env) const { strToEnv(env, "PGHOST", getParameter(keywords.host)); strToEnv(env, "PGHOSTADDR", getParameter(keywords.hostaddr)); strToEnv(env, "PGPORT", getParameter(keywords.port)); strToEnv(env, "PGDATABASE", getParameter(keywords.dbname)); strToEnv(env, "PGUSER", getParameter(keywords.user)); strToEnv(env, "PGPASSWORD", getParameter(keywords.password)); strToEnv(env, "PGSSLMODE", getParameter(keywords.sslmode)); // strToEnv(env, "PGSSLCERT", m_sslCert); // strToEnv(env, "PGSSLKEY", m_sslKey); // strToEnv(env, "PGSSLROOTCERT", m_sslRootCert); // strToEnv(env, "PGSSLCRL", m_sslCrl); strToEnv(env, "PGSSLCOMPRESSION", "0"); strToEnv(env, "PGCONNECT_TIMEOUT", "10"); env.insert("PGCLIENTENCODING", "utf8"); env.remove("PGREQUIRESSL"); } void ConnectionConfig::strToEnv(QProcessEnvironment &env, const QString &var, const QString &val) { if (val.isEmpty()) env.remove(var); else env.insert(var, val); } void ConnectionGroup::erase(int idx, int count) { m_connections.remove(idx, count); } int ConnectionGroup::add(std::shared_ptr cc) { cc->setParent(this); m_connections.push_back(cc); return m_connections.size() - 1; } void ConnectionGroup::update(int idx, const ConnectionConfig &cc) { auto node = m_connections[idx]; *node = cc; node->setParent(this); }