Improve parsing. Add modules that were missed

CandyCorvid
Jul 13, 2023, 10:27 AM
3SPNKI46URBW4PD3JRMBV5YV5W56N3SFUTK25VQEF2FUZ64ZDQPAC

Dependencies

Change contents

  • file addition: server.rs (----------)
    [3.15]
    use std::{net::TcpStream, sync::Arc};
    use rustls::{client::ServerCertVerifier, ClientConnection, ServerName};
    use url::Url;
    pub(crate) fn connect<V: Default + ServerCertVerifier + 'static>(
    url: &Url,
    ) -> rustls::StreamOwned<ClientConnection, TcpStream> {
    let host = url.host().expect("");
    let sn = server(host);
    let stream = {
    let cfg = tls_cfg::<V>();
    let conn = {
    let client = ClientConnection::new(cfg, sn).expect("could not connect");
    client
    };
    let addrs = url
    .socket_addrs(|| Some(1965))
    .expect("could not get addresses for URL");
    let addr = addrs.get(0).expect("no addresses");
    let sock = TcpStream::connect(addr).expect("failed to connect");
    let stream = rustls::StreamOwned { conn, sock };
    stream
    };
    stream
    }
    fn server(host: url::Host<&str>) -> ServerName {
    let url::Host::Domain(s) = host
    else {unreachable!("the url is always a string")};
    let sn = s.try_into().expect("this should be a valid DNS name");
    sn
    }
    fn tls_cfg<V: Default + ServerCertVerifier + 'static>() -> Arc<rustls::ClientConfig> {
    let root_store = rustls::RootCertStore::empty();
    let cfg = {
    let mut cfg = rustls::ClientConfig::builder()
    .with_safe_defaults()
    .with_root_certificates(root_store)
    .with_no_client_auth();
    cfg.dangerous()
    .set_certificate_verifier(Arc::new(V::default()));
    Arc::new(cfg)
    };
    cfg
    }
  • file addition: response.rs (----------)
    [3.15]
    #[repr(u8)]
    #[derive(Debug, PartialEq, Eq)]
    pub enum Status {
    Input(header::Input) = 1,
    /// has a response body
    Success(header::Success) = 2,
    Redirect(header::Redirect) = 3,
    FailTemp(header::FailTemp) = 4,
    FailPerm(header::FailPerm) = 5,
    CertRequired(header::CertRequired) = 6,
    }
    pub mod raw {
    pub use super::Meta;
    pub type Status = [u8; 2];
    pub struct Header {
    pub meta: Meta,
    pub status: Status,
    }
    }
    pub mod header {
    use url::Url;
    use crate::response::Mime;
    use super::Meta;
    #[derive(Debug, PartialEq, Eq)]
    pub struct Input {
    pub prompt: String,
    pub sensitive: Option<bool>,
    }
    impl From<(u8, Meta)> for Input {
    fn from((code, meta): (u8, Meta)) -> Self {
    let prompt = super::Meta::try_from(meta).unwrap().0;
    let sensitive = match code {
    0 => Some(false),
    1 => Some(true),
    _ => None,
    };
    Self { prompt, sensitive }
    }
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct Success {
    pub mime: Mime,
    }
    impl From<(u8, Meta)> for Success {
    fn from((_, meta): (u8, Meta)) -> Self {
    let mime = Mime::try_from(Meta::try_from(meta).unwrap()).expect("failed to read mime");
    Self { mime }
    }
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct Redirect {
    pub url: Url,
    pub temporary: Option<bool>,
    }
    impl From<(u8, Meta)> for Redirect {
    fn from((code, meta): (u8, Meta)) -> Self {
    let temporary = match code {
    0 => Some(true),
    1 => Some(false),
    _ => None,
    };
    Self {
    url: meta.0.parse().expect("not a valid url"),
    temporary,
    }
    }
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct FailTemp {
    pub message: String,
    pub typ: Option<sub::FailTemp>,
    }
    impl From<(u8, Meta)> for FailTemp {
    fn from((code, meta): (u8, Meta)) -> Self {
    let typ = sub::FailTemp::try_from(code).ok();
    Self {
    message: meta.0,
    typ,
    }
    }
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct FailPerm {
    pub message: String,
    pub typ: Option<sub::FailPerm>,
    }
    impl From<(u8, Meta)> for FailPerm {
    fn from((code, meta): (u8, Meta)) -> Self {
    let typ = sub::FailPerm::try_from(code).ok();
    Self {
    message: meta.0,
    typ,
    }
    }
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct CertRequired {
    pub message: String,
    pub typ: Option<sub::CertRequired>,
    }
    impl From<(u8, Meta)> for CertRequired {
    fn from(value: (u8, Meta)) -> Self {
    let (code, meta) = value;
    let typ = sub::CertRequired::try_from(code).ok();
    Self {
    message: meta.0,
    typ,
    }
    }
    }
    mod sub {
    use num_enum::TryFromPrimitive;
    #[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
    #[repr(u8)]
    pub enum FailTemp {
    TemporaryFailure = 0,
    ServerUnavailable = 1,
    CgiError = 2,
    ProxyError = 3,
    SlowDown = 4,
    }
    #[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
    #[repr(u8)]
    pub enum FailPerm {
    PermanentFailure = 0,
    NotFound = 1,
    Gone = 2,
    ProxyRequestRefused = 3,
    BadRequest = 9,
    }
    #[derive(Debug, PartialEq, Eq, TryFromPrimitive)]
    #[repr(u8)]
    pub enum CertRequired {
    ClientCertificateRequired = 0,
    CertificateNotAuthorised = 1,
    CertificateNotValid = 2,
    }
    }
    }
    impl TryFrom<raw::Header> for Status {
    type Error = String;
    fn try_from(value: raw::Header) -> Result<Self, Self::Error> {
    let raw::Header { status, meta } = value;
    let [status, sub] = status;
    match match status {
    1 => Some(Status::Input((sub, meta).into())),
    2 => Some(Status::Success((sub, meta).into())),
    3 => Some(Status::Redirect((sub, meta).into())),
    4 => Some(Status::FailTemp((sub, meta).into())),
    5 => Some(Status::FailPerm((sub, meta).into())),
    6 => Some(Status::CertRequired((sub, meta).into())),
    _ => None,
    } {
    Some(s) => Ok(s),
    None => Err(format!("bad status code: {:?}", status)),
    }
    }
    }
    // TODO default is text/gemini
    #[derive(Debug, PartialEq, Eq)]
    pub struct Mime {
    typ: String,
    sub: String,
    }
    #[derive(Debug, PartialEq, Eq)]
    pub struct Meta(pub String);
    impl TryFrom<Meta> for Mime {
    type Error = String;
    fn try_from(meta: Meta) -> Result<Self, Self::Error> {
    let (typ, sub) = meta
    .0
    .split_once('/')
    .ok_or(format!("invalid MIME type: {}", meta.0))?;
    Ok(Self {
    typ: typ.to_owned(),
    sub: sub.to_owned(),
    })
    }
    }
  • edit in src/main.rs at line 4
    [3.3712]
    [2.20]
    use response::header::{CertRequired, FailPerm, FailTemp, Input, Redirect, Success};
  • replacement in src/main.rs at line 36
    [3.4124][3.4124:4208]()
    fn run_url(url: String) {
    let url: Url = url.parse().expect("Not a valid URL");
    [3.4124]
    [2.322]
    fn run_url(url: String) -> Option<Url> {
    let mut url: Url = url.parse().expect("Not a valid URL");
  • replacement in src/main.rs at line 40
    [3.4245][3.4245:4281]()
    send_request(&mut stream, url);
    [3.4245]
    [3.4281]
    send_request(&mut stream, &url).expect("request failed to send");
  • replacement in src/main.rs at line 45
    [3.4397][3.4397:4501]()
    let body = read_response_string(&mut stream).expect("failed to read body");
    println!("{body}");
    [3.4397]
    [3.4501]
    match head {
    response::Status::Input(s) => {
    let Input { prompt, sensitive } = s;
    println!("{prompt}");
    let input = match sensitive {
    Some(true) => rpassword::read_password(),
    _ => std::io::stdin().lines().next().expect("End of input"),
    }
    .expect("Failed to read input");
    let input = urlencoding::encode(&input);
    url.set_query(Some(&input));
    Some(url)
    }
    response::Status::Success(s) => {
    let Success { mime } = s;
    let body = read_response_string(&mut stream).expect("failed to read body");
    // TODO use mime for anything
    println!("page is encoded as {mime:?}");
    println!("{body}");
    None
    }
    response::Status::Redirect(s) => {
    let Redirect { url, temporary } = s;
    if let Some(false) = temporary {
    println!("Server has permanently moved. Redirecting to: {url}");
    println!("Please update your records!");
    } else {
    println!("Server has temporarily moved. Redirecting to: {url}");
    }
    Some(url)
    }
    response::Status::FailTemp(s) => {
    let FailTemp { message, typ } = s;
    println!("Temporary failure: {typ:?}! Please try again. Details:");
    println!("{message}");
    None
    }
    response::Status::FailPerm(s) => {
    let FailPerm { message, typ } = s;
    println!("Permanent failure: {typ:?}! Details:");
    println!("{message}");
    None
    }
    response::Status::CertRequired(s) => {
    let CertRequired { message, typ } = s;
    println!("Certificate required: {typ:?}! Details:");
    println!("{message}");
    None
    }
    }
  • edit in src/main.rs at line 97
    [3.4504][2.384:427](),[2.427][3.4547:4568](),[3.4547][3.4547:4568](),[3.4568][2.428:446](),[2.446][3.4586:4607](),[3.4586][3.4586:4607](),[3.4607][2.447:448]()
    // TODO parse from 2-digit response header
    type Code = [u8; 2];
    // TODO parse etc
    type Meta = Vec<u8>;
  • replacement in src/main.rs at line 103
    [3.4820][3.4820:4871]()
    panic!("separator b'{c}' was not a space")
    [3.4820]
    [3.4871]
    println!("Warning: illegal separator byte '0x{c:02X}' was not a space")
  • edit in src/main.rs at line 114
    [2.782][3.5032:5033](),[3.5032][3.5032:5033](),[3.5033][2.783:909]()
    let meta = meta.into_string().expect("Meta was not valid UTF8");
    let header = response::raw::Header { meta, status };
  • replacement in src/main.rs at line 115
    [3.5057][2.910:969]()
    Ok(header.try_into().expect("failed to parse header"))
    [3.5057]
    [2.969]
    let meta = response::Meta(meta.into_string().expect("Meta was not valid UTF8"));
    let status = response::raw::Header { meta, status }
    .try_into()
    .expect("failed to parse header");
    Ok(status)
  • replacement in src/main.rs at line 128
    [3.5636][2.1040:1093](),[2.1093][3.5727:5851](),[3.5727][3.5727:5851]()
    fn send_request(stream: &mut impl Write, url: Url) {
    stream.write(url.as_str().as_bytes()).expect("send failed");
    stream.write("\r\n".as_bytes()).expect("send failed");
    [3.5636]
    [3.5851]
    fn send_request(stream: &mut impl Write, url: &Url) -> ioResult<usize> {
    let mut sent = 0;
    sent += stream.write(url.as_str().as_bytes())?;
    sent += stream.write("\r\n".as_bytes())?;
    Ok(sent)
  • file addition: io.rs (----------)
    [3.15]
    use std::io::{Read, Result};
    pub(crate) fn read_byte(stream: &mut impl Read) -> Result<u8> {
    let [c] = read_n::<1>(stream)?;
    Ok(c)
    }
    pub(crate) fn read_n<const N: usize>(stream: &mut impl Read) -> Result<[u8; N]> {
    let mut buf = [0; N];
    stream.read_exact(&mut buf)?;
    Ok(buf)
    }
  • file addition: danger.rs (----------)
    [3.15]
    use std::time::SystemTime;
    use rustls::{client::ServerCertVerified, Certificate, Error, ServerName};
    // unconditionally trusts
    #[derive(Default)]
    pub struct Naive;
    // trust on first use
    pub struct Tofu {}
    impl rustls::client::ServerCertVerifier for Naive {
    fn verify_server_cert(
    &self,
    _: &Certificate,
    _: &[Certificate],
    _: &ServerName,
    _: &mut dyn Iterator<Item = &[u8]>,
    _: &[u8],
    _: SystemTime,
    ) -> Result<ServerCertVerified, Error> {
    Ok(ServerCertVerified::assertion())
    }
    }
  • edit in Cargo.toml at line 12
    [3.42190]
    [3.42190]
    num_enum = "0.6.0"
    rpassword = "7.2.0"
  • edit in Cargo.toml at line 16
    [3.42276]
    urlencoding = "2.1.2"
  • edit in Cargo.lock at line 52
    [3.43617]
    [3.43617]
    name = "autocfg"
    version = "1.1.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
    [[package]]
  • edit in Cargo.lock at line 187
    [3.46866]
    [3.46866]
    "num_enum",
    "rpassword",
  • edit in Cargo.lock at line 191
    [3.46885]
    [3.46885]
    "urlencoding",
  • edit in Cargo.lock at line 193
    [3.46887]
    [3.46887]
    [[package]]
    name = "hashbrown"
    version = "0.12.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
  • edit in Cargo.lock at line 220
    [3.47517]
    [3.47517]
    ]
    [[package]]
    name = "indexmap"
    version = "1.9.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
    dependencies = [
    "autocfg",
    "hashbrown",
  • edit in Cargo.lock at line 292
    [3.49082]
    [3.49082]
    name = "num_enum"
    version = "0.6.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "a0fa9d8a04aa0af7b5845b514a828f829ae3f0ec3f60d9842e1dfaeb49a0e68b"
    dependencies = [
    "num_enum_derive",
    ]
    [[package]]
    name = "num_enum_derive"
    version = "0.6.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "3e51dcc6bafb7f3ac88b65d2ad21f4b53d878e496712060e23011862ebd2d2d1"
    dependencies = [
    "proc-macro-crate",
    "proc-macro2",
    "quote",
    "syn 2.0.13",
    ]
    [[package]]
  • edit in Cargo.lock at line 325
    [3.49476]
    [3.49476]
    name = "proc-macro-crate"
    version = "1.3.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
    dependencies = [
    "once_cell",
    "toml_edit",
    ]
    [[package]]
  • edit in Cargo.lock at line 370
    [3.50392]
    [3.50392]
    "winapi",
    ]
    [[package]]
    name = "rpassword"
    version = "7.2.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
    dependencies = [
    "libc",
    "rtoolbox",
  • edit in Cargo.lock at line 385
    [3.50418]
    [3.50418]
    name = "rtoolbox"
    version = "0.0.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
    dependencies = [
    "libc",
    "winapi",
    ]
    [[package]]
  • edit in Cargo.lock at line 494
    [3.52921]
    [3.52921]
    [[package]]
    name = "toml_datetime"
    version = "0.6.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
    [[package]]
    name = "toml_edit"
    version = "0.19.8"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13"
    dependencies = [
    "indexmap",
    "toml_datetime",
    "winnow",
    ]
  • edit in Cargo.lock at line 551
    [3.54014]
    [3.54014]
    name = "urlencoding"
    version = "2.1.2"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
    [[package]]
  • edit in Cargo.lock at line 713
    [3.58544]
    [[package]]
    name = "winnow"
    version = "0.4.1"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28"
    dependencies = [
    "memchr",
    ]