Compare commits

..

7 commits

Author SHA1 Message Date
95362fd9e8
feat: add example and dev deps
Note: current version of main.rs example just disables TLS cert verification if `-k` arg provided.
Instead, it must use custom storage for self-signed certs.

tokio with macros & fs features in dev deps is needed for main.rs example.
2024-08-01 12:05:39 +04:00
16fc740397
fix: trim \r from message
mime type couldn't be parsed because of this
2024-08-01 11:59:32 +04:00
2d3a19c60d
feat: add &str utf8 conv error to LibError enum 2024-08-01 11:59:05 +04:00
9b80902390
fix: return mutable ref to BodyStream 2024-08-01 11:58:34 +04:00
6efe51bb15
feat: add impl Debug 2024-08-01 10:32:55 +04:00
e92f0c62a2
feat: return LibError from Response::mime(&Self) 2024-08-01 10:29:34 +04:00
a6040176fb
feat: expose all modules 2024-08-01 10:28:45 +04:00
7 changed files with 164 additions and 12 deletions

12
Cargo.lock generated
View file

@ -340,6 +340,7 @@ dependencies = [
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio-macros",
"windows-sys", "windows-sys",
] ]
@ -355,6 +356,17 @@ dependencies = [
"webpki-roots", "webpki-roots",
] ]
[[package]]
name = "tokio-macros"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.0" version = "0.26.0"

View file

@ -10,3 +10,10 @@ tokio = { version = "1.39.2", features = ["io-util", "net"] }
tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] } tokio-rustls = { version = "0.26.0", default-features = false, features = ["ring"] }
url = "2.5.2" url = "2.5.2"
webpki-roots = "0.26.3" webpki-roots = "0.26.3"
[[example]]
name = "main"
path = "examples/main.rs"
[dev-dependencies]
tokio = { version = "1.39.2", features = ["macros", "rt-multi-thread", "io-util", "fs"] }

111
examples/main.rs Normal file
View file

@ -0,0 +1,111 @@
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_rustls::rustls::{
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
ClientConfig, SignatureScheme,
};
#[tokio::main]
async fn main() -> Result<(), tokio_gemini::LibError> {
let mut args = std::env::args();
let mut insecure = false;
let mut url = "gemini://geminiprotocol.net/".to_owned();
if let Some(arg) = args.nth(1) {
if arg == "-k" {
insecure = true;
if let Some(arg) = args.nth(2) {
url = arg;
}
} else {
url = arg;
}
}
let client = if insecure {
tokio_gemini::Client::from(get_insecure_config())
} else {
tokio_gemini::Client::default()
};
let mut resp = client.request(&url).await?;
{
let status_code = resp.status().status_code();
let status_num: u8 = status_code.into();
eprintln!("{} {:?}", status_num, status_code);
}
if resp.status().reply_type() == tokio_gemini::ReplyType::Success {
let mime = resp.mime()?;
eprintln!("Mime: {}", mime);
let mut buf = [0u8, 128];
let body = resp.body();
if mime.type_() == mime::TEXT {
loop {
let n = body.read(&mut buf).await?;
if n == 0 {
break;
}
print!("{}", std::str::from_utf8(&buf[..n])?);
}
} else {
eprintln!("Downloading into content.bin");
let mut f = tokio::fs::File::create("content.bin").await?;
loop {
let n = body.read(&mut buf).await?;
if n == 0 {
break;
}
f.write_all(&buf[..n]).await?;
}
}
} else {
eprintln!("Message: {}", resp.message());
}
Ok(())
}
fn get_insecure_config() -> ClientConfig {
ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(std::sync::Arc::new(NoCertVerification {}))
.with_no_client_auth()
}
#[derive(Debug)]
struct NoCertVerification;
impl ServerCertVerifier for NoCertVerification {
fn verify_server_cert(
&self,
end_entity: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
intermediates: &[tokio_rustls::rustls::pki_types::CertificateDer<'_>],
server_name: &tokio_rustls::rustls::pki_types::ServerName<'_>,
ocsp_response: &[u8],
now: tokio_rustls::rustls::pki_types::UnixTime,
) -> Result<ServerCertVerified, tokio_rustls::rustls::Error> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
dss: &tokio_rustls::rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &tokio_rustls::rustls::pki_types::CertificateDer<'_>,
dss: &tokio_rustls::rustls::DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, tokio_rustls::rustls::Error> {
Ok(HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<tokio_rustls::rustls::SignatureScheme> {
vec![
SignatureScheme::ECDSA_NISTP256_SHA256,
SignatureScheme::ECDSA_NISTP384_SHA384,
SignatureScheme::ECDSA_NISTP521_SHA512,
]
}
}

View file

@ -1,8 +1,11 @@
#[derive(Debug)]
pub enum LibError { pub enum LibError {
IoError(std::io::Error), IoError(std::io::Error),
InvalidUrlError(InvalidUrl), InvalidUrlError(InvalidUrl),
StatusOutOfRange(u8), StatusOutOfRange(u8),
MessageNotUtf8(std::string::FromUtf8Error), MessageNotUtf8(std::string::FromUtf8Error),
BodyNotUtf8(std::str::Utf8Error),
InvalidMime(mime::FromStrError),
} }
impl From<std::io::Error> for LibError { impl From<std::io::Error> for LibError {
@ -40,6 +43,21 @@ impl From<std::string::FromUtf8Error> for LibError {
} }
} }
impl From<std::str::Utf8Error> for LibError {
#[inline]
fn from(err: std::str::Utf8Error) -> Self {
Self::BodyNotUtf8(err)
}
}
impl From<mime::FromStrError> for LibError {
#[inline]
fn from(err: mime::FromStrError) -> Self {
Self::InvalidMime(err)
}
}
#[derive(Debug)]
pub enum InvalidUrl { pub enum InvalidUrl {
ParseError(url::ParseError), ParseError(url::ParseError),
SchemeNotGemini, SchemeNotGemini,

View file

@ -1,10 +1,10 @@
mod error; pub mod error;
mod response; pub mod response;
mod status; pub mod status;
use error::*; pub use error::*;
use response::Response; pub use response::Response;
use status::Status; pub use status::*;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::sync::Arc; use std::sync::Arc;
@ -79,6 +79,9 @@ impl Client {
message.push(buf[0].into()); message.push(buf[0].into());
} }
} }
if message.last().is_some_and(|c| c == &b'\r') {
message.pop();
}
let message = String::from_utf8(message)?; let message = String::from_utf8(message)?;
Ok(Response::new(status, message, stream)) Ok(Response::new(status, message, stream))
} }

View file

@ -1,7 +1,8 @@
use crate::status::Status; use crate::{status::Status, LibError};
type BodyStream = tokio_rustls::client::TlsStream<tokio::net::TcpStream>; type BodyStream = tokio_rustls::client::TlsStream<tokio::net::TcpStream>;
#[derive(Debug)]
pub struct Response { pub struct Response {
status: Status, status: Status,
message: String, message: String,
@ -25,11 +26,11 @@ impl Response {
&self.message &self.message
} }
pub fn mime(self: &Self) -> Result<mime::Mime, mime::FromStrError> { pub fn mime(self: &Self) -> Result<mime::Mime, LibError> {
self.message.parse() self.message.parse().map_err(|e| LibError::InvalidMime(e))
} }
pub fn body(self: &Self) -> &BodyStream { pub fn body(self: &mut Self) -> &mut BodyStream {
&self.body &mut self.body
} }
} }

View file

@ -2,7 +2,7 @@ use crate::error::LibError;
use num_enum::{IntoPrimitive, TryFromPrimitive}; use num_enum::{IntoPrimitive, TryFromPrimitive};
#[derive(Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Status { pub struct Status {
status_code: StatusCode, status_code: StatusCode,
reply_type: ReplyType, reply_type: ReplyType,