Compare commits
7 commits
4cf9278cad
...
95362fd9e8
Author | SHA1 | Date | |
---|---|---|---|
95362fd9e8 | |||
16fc740397 | |||
2d3a19c60d | |||
9b80902390 | |||
6efe51bb15 | |||
e92f0c62a2 | |||
a6040176fb |
7 changed files with 164 additions and 12 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
|
@ -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
111
examples/main.rs
Normal 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,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
18
src/error.rs
18
src/error.rs
|
@ -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,
|
||||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Reference in a new issue