use super::builders::{parse_bool, parse_duration, parse_uint};
use super::*;
const PROXY_HOST_KEY: &str = "\0https_proxy_host";
const PROXY_PORT_KEY: &str = "\0https_proxy_port";
fn is_common_param(name: &str) -> bool {
matches!(
name,
"expire_time"
| "failover"
| "https_proxy"
| "https_proxy_port"
| "load_balance"
| "pool_boundary"
| "pool_name"
| "pool_connection_class"
| "pool_purity"
| "retry_count"
| "retry_delay"
| "sdu"
| "source_route"
| "ssl_server_cert_dn"
| "ssl_server_dn_match"
| "transport_connect_timeout"
| "use_sni"
| "wallet_location"
)
}
fn is_extra_description_param(name: &str) -> bool {
matches!(name, "enable" | "recv_buf_size" | "send_buf_size")
}
fn is_host_or_service_char(ch: char) -> bool {
ch.is_alphanumeric() || matches!(ch, '-' | '_' | '.')
}
struct Ez<'a> {
chars: &'a [char],
pos: usize,
temp_pos: usize,
}
impl<'a> Ez<'a> {
fn current(&self) -> char {
self.chars[self.temp_pos]
}
fn skip_spaces(&mut self) {
while self.temp_pos < self.chars.len() && self.chars[self.temp_pos].is_whitespace() {
self.temp_pos += 1;
}
}
fn parse_keyword(&mut self) {
while self.temp_pos < self.chars.len() {
let ch = self.current();
if !ch.is_alphanumeric() && ch != '_' && ch != '.' {
break;
}
self.temp_pos += 1;
}
}
fn parse_protocol(&mut self) -> Option<String> {
let mut start_sep_pos = self.pos;
let mut num_sep_chars = 0i32;
let mut protocol: Option<String> = None;
self.temp_pos = self.pos;
while self.temp_pos < self.chars.len() {
let ch = self.current();
if ch == ':' {
protocol = Some(
self.chars[self.pos..self.temp_pos]
.iter()
.collect::<String>()
.to_ascii_lowercase(),
);
start_sep_pos = self.temp_pos + 1;
} else if ch == '/' && (self.temp_pos - start_sep_pos) as i32 == num_sep_chars {
num_sep_chars += 1;
if num_sep_chars == 2 {
self.temp_pos += 1;
self.pos = self.temp_pos;
break;
}
} else if !ch.is_alphabetic() && ch != '-' && ch != '_' {
break;
}
self.temp_pos += 1;
}
if protocol.is_some() && num_sep_chars == 2 {
protocol
} else {
None
}
}
fn parse_host(&mut self, address: &mut Address) {
let mut found_bracket = false;
let mut found_host = false;
let mut start_pos = self.temp_pos;
while self.temp_pos < self.chars.len() {
let ch = self.current();
if !found_bracket && !found_host && ch == '[' {
found_bracket = true;
start_pos = self.temp_pos + 1;
} else if found_bracket && ch == ']' {
address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
self.temp_pos += 1;
self.pos = self.temp_pos;
break;
} else if found_bracket || is_host_or_service_char(ch) {
self.temp_pos += 1;
found_host = true;
} else {
if found_host {
address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
self.pos = self.temp_pos;
}
break;
}
}
if found_host && self.temp_pos == self.chars.len() && address.host.is_none() {
address.host = Some(self.chars[start_pos..self.temp_pos].iter().collect());
self.pos = self.temp_pos;
}
}
fn parse_port(&mut self, address: &mut Address) {
let start = self.temp_pos;
let mut found = false;
while self.temp_pos < self.chars.len() && self.current().is_ascii_digit() {
found = true;
self.temp_pos += 1;
}
if found {
let digits: String = self.chars[start..self.temp_pos].iter().collect();
if let Ok(port) = digits.parse::<u16>() {
address.port = port;
}
}
}
}
#[allow(clippy::too_many_lines)]
pub(super) fn parse(chars: &[char], connect_string: &str) -> Result<Option<Descriptor>> {
let mut ez = Ez {
chars,
pos: 0,
temp_pos: 0,
};
let template_protocol = match ez.parse_protocol() {
Some(protocol) => Protocol::from_keyword(&protocol)?,
None => Protocol::Tcp,
};
let mut address_lists: Vec<Vec<Address>> = Vec::new();
let mut current_list: Vec<Address> = Vec::new();
ez.temp_pos = ez.pos;
let mut port_index = 0usize;
loop {
let mut address = Address {
protocol: template_protocol,
port: template_protocol.default_port(),
..Address::default()
};
ez.parse_host(&mut address);
if ez.temp_pos != ez.pos || ez.pos >= chars.len() {
if ez.pos >= chars.len() && address.host.is_some() {
current_list.push(address);
}
break;
}
ez.pos = ez.temp_pos;
current_list.push(address);
if ez.temp_pos >= chars.len() {
break;
}
let mut ch = ez.current();
if ch == ':' {
ez.temp_pos += 1;
if let Some(last) = current_list.last_mut() {
ez.parse_port(last);
let port = last.port;
ez.pos = ez.temp_pos;
if ez.pos >= chars.len() {
break;
}
let upper = current_list.len() - 1;
for addr in current_list.iter_mut().take(upper).skip(port_index) {
addr.port = port;
}
port_index = current_list.len();
}
ch = ez.current();
}
if ch == ';' {
address_lists.push(std::mem::take(&mut current_list));
port_index = 0;
} else if ch != ',' {
break;
}
ez.temp_pos += 1;
}
address_lists.push(current_list);
let mut description = Description::default();
let mut found_service_section = false;
parse_service_name(&mut ez, chars, &mut description, &mut found_service_section);
if found_service_section {
parse_instance_name(&mut ez, chars, &mut description);
}
if !found_service_section {
return Ok(None);
}
parse_parameters(&mut ez, chars, connect_string, &mut description)?;
if ez.pos != chars.len() {
if ez.pos > 0 {
return Err(err_cannot_parse(connect_string));
}
return Ok(None);
}
let mut lists: Vec<AddressList> = Vec::new();
for hosts in address_lists {
if hosts.is_empty() {
continue;
}
lists.push(AddressList {
addresses: hosts,
failover: true,
..AddressList::default()
});
}
if lists.is_empty() {
return Ok(None);
}
description.address_lists = lists;
let proxy_host = description
.extra
.iter()
.find(|(k, _)| k == PROXY_HOST_KEY)
.map(|(_, v)| v.clone());
let proxy_port = description
.extra
.iter()
.find(|(k, _)| k == PROXY_PORT_KEY)
.and_then(|(_, v)| v.parse::<u16>().ok());
description
.extra
.retain(|(k, _)| k != PROXY_HOST_KEY && k != PROXY_PORT_KEY);
if proxy_host.is_some() || proxy_port.is_some() {
for list in &mut description.address_lists {
for addr in &mut list.addresses {
if let Some(host) = &proxy_host {
addr.https_proxy = Some(host.clone());
}
if let Some(port) = proxy_port {
addr.https_proxy_port = port;
}
}
}
}
Ok(Some(Descriptor {
descriptions: vec![description],
load_balance: false,
failover: true,
source_route: false,
}))
}
fn parse_service_name(
ez: &mut Ez,
chars: &[char],
description: &mut Description,
found_slash_out: &mut bool,
) {
let mut found_service_name = false;
let mut found_server_type = false;
let mut found_slash = false;
let mut found_colon = false;
let mut service_name_end_pos = 0usize;
ez.temp_pos = ez.pos;
while ez.temp_pos < chars.len() {
let ch = ez.current();
if !found_slash && ch == '/' {
found_slash = true;
} else if found_service_name && !found_colon && ch == ':' {
found_colon = true;
} else if found_slash && !found_colon && is_host_or_service_char(ch) {
found_service_name = true;
service_name_end_pos = ez.temp_pos + 1;
} else if found_colon && ch.is_alphabetic() {
found_server_type = true;
} else {
break;
}
ez.temp_pos += 1;
}
if found_service_name {
description.connect_data.service_name =
Some(chars[ez.pos + 1..service_name_end_pos].iter().collect());
}
if found_slash {
ez.pos = ez.temp_pos;
*found_slash_out = true;
}
if found_server_type {
let value: String = chars[service_name_end_pos + 1..ez.temp_pos]
.iter()
.collect();
if let Ok(server_type) = ServerType::from_keyword(&value) {
description.connect_data.server_type = Some(server_type);
}
}
}
fn parse_instance_name(ez: &mut Ez, chars: &[char], description: &mut Description) {
let mut found_instance_name = false;
let mut found_slash = false;
let mut instance_name_end_pos = 0usize;
ez.temp_pos = ez.pos;
while ez.temp_pos < chars.len() {
let ch = ez.current();
if !found_slash && ch == '/' {
found_slash = true;
} else if found_slash && is_host_or_service_char(ch) {
found_instance_name = true;
instance_name_end_pos = ez.temp_pos + 1;
} else {
break;
}
ez.temp_pos += 1;
}
if found_instance_name {
description.connect_data.instance_name =
Some(chars[ez.pos + 1..instance_name_end_pos].iter().collect());
ez.pos = ez.temp_pos;
}
}
fn parse_parameters(
ez: &mut Ez,
chars: &[char],
connect_string: &str,
description: &mut Description,
) -> Result<()> {
let mut expected_sep = '?';
ez.temp_pos = ez.pos;
while ez.temp_pos < chars.len() {
let ch = ez.current();
if ch != expected_sep {
break;
}
expected_sep = '&';
ez.temp_pos += 1;
parse_one_parameter(ez, chars, connect_string, description)?;
}
Ok(())
}
fn parse_one_parameter(
ez: &mut Ez,
chars: &[char],
connect_string: &str,
description: &mut Description,
) -> Result<()> {
ez.skip_spaces();
let start = ez.temp_pos;
ez.parse_keyword();
if ez.temp_pos == start || ez.temp_pos >= chars.len() {
return Ok(());
}
let raw_name: String = chars[start..ez.temp_pos]
.iter()
.collect::<String>()
.to_ascii_lowercase();
let (name, keep) = if let Some(stripped) = raw_name.strip_prefix("pyo.") {
(stripped.to_string(), true)
} else {
let keep = is_common_param(&raw_name) || is_extra_description_param(&raw_name);
(canonical_param_name(&raw_name).to_string(), keep)
};
ez.skip_spaces();
if ez.temp_pos >= chars.len() {
return Ok(());
}
if ez.current() != '=' {
return Ok(());
}
ez.temp_pos += 1;
ez.skip_spaces();
let mut start_pos = ez.temp_pos;
let mut end_pos = ez.temp_pos;
while ez.temp_pos < chars.len() {
let ch = ez.current();
if ch == '"' {
if ez.temp_pos > start_pos {
return Ok(());
}
ez.temp_pos += 1;
start_pos = ez.temp_pos;
let mut closed = false;
while ez.temp_pos < chars.len() {
let qc = ez.current();
ez.temp_pos += 1;
if qc == '"' {
closed = true;
break;
}
}
if !closed {
return Err(err_descriptor(
connect_string,
ez.temp_pos,
"missing ending quote (\")",
));
}
end_pos = ez.temp_pos - 1;
break;
} else if ch == '&' {
end_pos = ez.temp_pos;
break;
}
ez.temp_pos += 1;
end_pos = ez.temp_pos;
}
if end_pos > start_pos && keep {
let value: String = chars[start_pos..end_pos].iter().collect();
apply_easy_param(connect_string, description, &name, &value)?;
}
ez.skip_spaces();
ez.pos = ez.temp_pos;
Ok(())
}
fn apply_easy_param(
connect_string: &str,
description: &mut Description,
name: &str,
value: &str,
) -> Result<()> {
match name {
"expire_time" => {
description.expire_time = parse_uint(connect_string, "EXPIRE_TIME", value)?
}
"retry_count" => {
description.retry_count = parse_uint(connect_string, "RETRY_COUNT", value)?
}
"retry_delay" => {
description.retry_delay = parse_uint(connect_string, "RETRY_DELAY", value)?
}
"sdu" => {
description.sdu = parse_uint(connect_string, "SDU", value)?.clamp(MIN_SDU, MAX_SDU);
}
"tcp_connect_timeout" => {
description.tcp_connect_timeout =
parse_duration(connect_string, "TRANSPORT_CONNECT_TIMEOUT", value)?;
}
"failover" => description.failover = parse_bool(value),
"load_balance" => description.load_balance = parse_bool(value),
"source_route" => description.source_route = parse_bool(value),
"use_sni" => description.use_sni = parse_bool(value),
"ssl_server_dn_match" => description.security.ssl_server_dn_match = parse_bool(value),
"ssl_server_cert_dn" => {
description.security.ssl_server_cert_dn = Some(value.to_string());
}
"wallet_location" => description.security.wallet_location = Some(value.to_string()),
"https_proxy" => description
.extra
.push((PROXY_HOST_KEY.to_string(), value.to_string())),
"https_proxy_port" => description
.extra
.push((PROXY_PORT_KEY.to_string(), value.to_string())),
"pool_boundary" => description.connect_data.pool_boundary = Some(value.to_string()),
"pool_name" => description.connect_data.pool_name = Some(value.to_string()),
"cclass" => {
if !value.is_empty() {
description.connect_data.cclass = Some(value.to_string());
}
}
"purity" => {
description.connect_data.purity = Some(Purity::from_keyword(value)?);
}
"enable" | "recv_buf_size" | "send_buf_size" => {
description
.extra
.push((name.to_ascii_uppercase(), value.to_string()));
}
_ => {}
}
Ok(())
}