mod ado_net;
mod jdbc;
use std::collections::HashMap;
use std::path::PathBuf;
use super::AuthMethod;
use crate::EncryptionLevel;
use ado_net::*;
use jdbc::*;
#[derive(Clone, Debug)]
pub struct Config {
pub(crate) host: Option<String>,
pub(crate) port: Option<u16>,
pub(crate) database: Option<String>,
pub(crate) instance_name: Option<String>,
pub(crate) application_name: Option<String>,
pub(crate) encryption: EncryptionLevel,
pub(crate) trust: TrustConfig,
pub(crate) auth: AuthMethod,
pub(crate) readonly: bool,
}
#[derive(Clone, Debug)]
pub(crate) enum TrustConfig {
#[allow(dead_code)]
CaCertificateLocation(PathBuf),
TrustAll,
Default,
}
impl Default for Config {
fn default() -> Self {
Self {
host: None,
port: None,
database: None,
instance_name: None,
application_name: None,
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
encryption: EncryptionLevel::Required,
#[cfg(not(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))]
encryption: EncryptionLevel::NotSupported,
trust: TrustConfig::Default,
auth: AuthMethod::None,
readonly: false,
}
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn host(&mut self, host: impl ToString) {
self.host = Some(host.to_string());
}
pub fn port(&mut self, port: u16) {
self.port = Some(port);
}
pub fn database(&mut self, database: impl ToString) {
self.database = Some(database.to_string())
}
pub fn instance_name(&mut self, name: impl ToString) {
self.instance_name = Some(name.to_string());
}
pub fn application_name(&mut self, name: impl ToString) {
self.application_name = Some(name.to_string());
}
pub fn encryption(&mut self, encryption: EncryptionLevel) {
self.encryption = encryption;
}
pub fn trust_cert(&mut self) {
if let TrustConfig::CaCertificateLocation(_) = &self.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
}
self.trust = TrustConfig::TrustAll;
}
pub fn trust_cert_ca(&mut self, path: impl ToString) {
if let TrustConfig::TrustAll = &self.trust {
panic!("'trust_cert' and 'trust_cert_ca' are mutual exclusive! Only use one.")
} else {
self.trust = TrustConfig::CaCertificateLocation(PathBuf::from(path.to_string()))
}
}
pub fn authentication(&mut self, auth: AuthMethod) {
self.auth = auth;
}
pub fn readonly(&mut self, readnoly: bool) {
self.readonly = readnoly;
}
pub(crate) fn get_host(&self) -> &str {
self.host
.as_deref()
.filter(|v| v != &".")
.unwrap_or("localhost")
}
pub(crate) fn get_port(&self) -> u16 {
match (self.port, self.instance_name.as_ref()) {
(Some(port), _) => port,
(None, Some(_)) => 1434,
(None, None) => 1433,
}
}
pub fn get_addr(&self) -> String {
format!("{}:{}", self.get_host(), self.get_port())
}
pub fn from_ado_string(s: &str) -> crate::Result<Self> {
let ado: AdoNetConfig = s.parse()?;
Self::from_config_string(ado)
}
pub fn from_jdbc_string(s: &str) -> crate::Result<Self> {
let jdbc: JdbcConfig = s.parse()?;
Self::from_config_string(jdbc)
}
fn from_config_string(s: impl ConfigString) -> crate::Result<Self> {
let mut builder = Self::new();
let server = s.server()?;
if let Some(host) = server.host {
builder.host(host);
}
if let Some(port) = server.port {
builder.port(port);
}
if let Some(instance) = server.instance {
builder.instance_name(instance);
}
builder.authentication(s.authentication()?);
if let Some(database) = s.database() {
builder.database(database);
}
if let Some(name) = s.application_name() {
builder.application_name(name);
}
if s.trust_cert()? {
builder.trust_cert();
}
if let Some(ca) = s.trust_cert_ca() {
builder.trust_cert_ca(ca);
}
builder.encryption(s.encrypt()?);
builder.readonly(s.readonly());
Ok(builder)
}
}
pub(crate) struct ServerDefinition {
host: Option<String>,
port: Option<u16>,
instance: Option<String>,
}
pub(crate) trait ConfigString {
fn dict(&self) -> &HashMap<String, String>;
fn server(&self) -> crate::Result<ServerDefinition>;
fn authentication(&self) -> crate::Result<AuthMethod> {
let user = self
.dict()
.get("uid")
.or_else(|| self.dict().get("username"))
.or_else(|| self.dict().get("user"))
.or_else(|| self.dict().get("user id"))
.map(|s| s.as_str());
let pw = self
.dict()
.get("password")
.or_else(|| self.dict().get("pwd"))
.map(|s| s.as_str());
match self
.dict()
.get("integratedsecurity")
.or_else(|| self.dict().get("integrated security"))
{
#[cfg(all(windows, feature = "winauth"))]
Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => match (user, pw)
{
(None, None) => Ok(AuthMethod::Integrated),
_ => Ok(AuthMethod::windows(user.unwrap_or(""), pw.unwrap_or(""))),
},
#[cfg(feature = "integrated-auth-gssapi")]
Some(val) if val.to_lowercase() == "sspi" || Self::parse_bool(val)? => {
Ok(AuthMethod::Integrated)
}
_ => Ok(AuthMethod::sql_server(user.unwrap_or(""), pw.unwrap_or(""))),
}
}
fn database(&self) -> Option<String> {
self.dict()
.get("database")
.or_else(|| self.dict().get("initial catalog"))
.or_else(|| self.dict().get("databasename"))
.map(|db| db.to_string())
}
fn application_name(&self) -> Option<String> {
self.dict()
.get("application name")
.or_else(|| self.dict().get("applicationname"))
.map(|name| name.to_string())
}
fn trust_cert(&self) -> crate::Result<bool> {
self.dict()
.get("trustservercertificate")
.map(Self::parse_bool)
.unwrap_or(Ok(false))
}
fn trust_cert_ca(&self) -> Option<String> {
self.dict()
.get("trustservercertificateca")
.map(|ca| ca.to_string())
}
#[cfg(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
))]
fn encrypt(&self) -> crate::Result<EncryptionLevel> {
self.dict()
.get("encrypt")
.map(|val| match Self::parse_bool(val) {
Ok(true) => Ok(EncryptionLevel::Required),
Ok(false) => Ok(EncryptionLevel::Off),
Err(_) if val == "DANGER_PLAINTEXT" => Ok(EncryptionLevel::NotSupported),
Err(e) => Err(e),
})
.unwrap_or(Ok(EncryptionLevel::Off))
}
#[cfg(not(any(
feature = "rustls",
feature = "native-tls",
feature = "vendored-openssl"
)))]
fn encrypt(&self) -> crate::Result<EncryptionLevel> {
Ok(EncryptionLevel::NotSupported)
}
fn parse_bool<T: AsRef<str>>(v: T) -> crate::Result<bool> {
match v.as_ref().trim().to_lowercase().as_str() {
"true" | "yes" => Ok(true),
"false" | "no" => Ok(false),
_ => Err(crate::Error::Conversion(
"Connection string: Not a valid boolean".into(),
)),
}
}
fn readonly(&self) -> bool {
self.dict()
.get("applicationintent")
.filter(|val| *val == "ReadOnly")
.is_some()
}
}