qlparse/src/ulp.rs

129 lines
4.5 KiB
Rust

use std::str::FromStr;
use serde::Serialize;
use thiserror::Error;
#[derive(Serialize, Debug)]
pub struct Ulp(Option<String>, Option<String>, String);
#[derive(Error, Debug)]
pub enum ParseUlpError {
#[error("incomplete ULP string")]
Incomplete,
}
enum State {
Url,
Username(char),
Password,
}
impl FromStr for Ulp {
type Err = ParseUlpError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// intermediary
let mut state = State::Url;
let mut buf = String::new();
let mut port_buf = String::new();
// final
let mut saved_url = None;
let mut saved_username = None;
let saved_password;
let mut it = s.chars().peekable();
loop {
let c = it.next();
match state {
State::Url => match c {
Some('|') => {
state = State::Username('|');
saved_url = Some(buf.split_off(0));
}
Some(':') if !matches!(buf.as_str(), "http" | "https") => {
// at this point, we expect either a port
// or the username after a colon
// --
// we try to determine which it is
// take all chars that could be
// part of a port
while matches!(it.peek(), Some('0'..='9')) {
port_buf.push(it.next().unwrap());
}
// peek next char for following checks
let Some(nc) = it.peek() else {
// done + change form:
// * buf is username, port_buf is pw
saved_password = port_buf;
saved_username = Some(buf);
break;
};
// if it is surely a port, clear port buf and
// continue on
if matches!(nc, '/' | '|') {
buf.push_str(&port_buf);
port_buf.clear();
continue;
}
// if it could be either a port or a username,
// keep for later and move on
if matches!(nc, ':') && matches!(port_buf.as_str(), "443" | "80") {
continue;
}
// if we didn't match anything yet,
// we assume it is part of a username
state = State::Username(':');
saved_url = Some(buf.split_off(0));
}
Some(c) => buf.push(c),
None => return Err(ParseUlpError::Incomplete),
},
State::Username(sep) => match c {
Some(c) if sep != c => buf.push(c),
Some(_) => {
state = State::Password;
saved_username = Some(buf.split_off(0));
}
None => {
// done + change form:
// * port_buf||url_buf is username, buf is password
saved_password = buf;
if port_buf.is_empty() {
saved_username = saved_url;
saved_url = None;
} else {
saved_username = Some(port_buf);
}
break;
}
},
State::Password => {
if let Some(c) = c {
buf.push(c);
} else {
// done:
// * we can be sure port_buf is part of url
if !port_buf.is_empty() {
let saved_url = saved_url.as_mut().unwrap();
saved_url.push(':');
saved_url.push_str(&port_buf);
}
saved_password = buf;
break;
}
}
}
}
Ok(Ulp(saved_url, saved_username, saved_password))
}
}