129 lines
4.5 KiB
Rust
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))
|
|
}
|
|
}
|