Compare commits
5 commits
053f042e00
...
1395bb04ff
Author | SHA1 | Date | |
---|---|---|---|
1395bb04ff | |||
39c31d9651 | |||
4f52475f2c | |||
dbbcf322c3 | |||
6bd101504d |
6 changed files with 110 additions and 31 deletions
|
@ -20,6 +20,10 @@ 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 = "simple"
|
||||||
|
path = "examples/simple.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "main"
|
name = "main"
|
||||||
path = "examples/main.rs"
|
path = "examples/main.rs"
|
||||||
|
|
54
examples/simple.rs
Normal file
54
examples/simple.rs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
use tokio_gemini::{
|
||||||
|
certs::{
|
||||||
|
fingerprint::{generate_fingerprint, Algorithm},
|
||||||
|
verifier::SelfsignedCertVerifier,
|
||||||
|
},
|
||||||
|
Client, LibError,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Much simpler than examples/main.rs
|
||||||
|
// Hardcoded URL, no cert check, always write to stdout
|
||||||
|
//
|
||||||
|
// cargo add tokio-gemini
|
||||||
|
// cargo add tokio -F macros,rt-multi-thread
|
||||||
|
//
|
||||||
|
|
||||||
|
const URL: &str = "gemini://geminiprotocol.net/docs/protocol-specification.gmi";
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), LibError> {
|
||||||
|
let client = Client::builder()
|
||||||
|
.with_selfsigned_cert_verifier(CertVerifier)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
match client.request(URL).await?.ensure_ok() {
|
||||||
|
Ok(mut resp) => {
|
||||||
|
println!("{}", resp.text().await?);
|
||||||
|
}
|
||||||
|
Err(resp) => {
|
||||||
|
println!("{} {}", resp.status().num(), resp.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CertVerifier;
|
||||||
|
|
||||||
|
impl SelfsignedCertVerifier for CertVerifier {
|
||||||
|
fn verify(
|
||||||
|
&self,
|
||||||
|
cert: &tokio_gemini::certs::verifier::CertificateDer,
|
||||||
|
host: &str,
|
||||||
|
_now: tokio_gemini::certs::verifier::UnixTime,
|
||||||
|
) -> Result<bool, tokio_rustls::rustls::Error> {
|
||||||
|
// For real verification example with known_hosts file
|
||||||
|
// see examples/main.rs
|
||||||
|
eprintln!(
|
||||||
|
"Host = {}\nFingerprint = {}",
|
||||||
|
host,
|
||||||
|
generate_fingerprint(cert, Algorithm::Sha512),
|
||||||
|
);
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,11 @@ pub struct AllowAllCertVerifier(std::sync::Arc<CryptoProvider>);
|
||||||
|
|
||||||
impl AllowAllCertVerifier {
|
impl AllowAllCertVerifier {
|
||||||
pub fn yes_i_know_what_i_am_doing() -> Self {
|
pub fn yes_i_know_what_i_am_doing() -> Self {
|
||||||
AllowAllCertVerifier(CryptoProvider::get_default().unwrap().clone())
|
AllowAllCertVerifier(
|
||||||
|
CryptoProvider::get_default()
|
||||||
|
.map(|c| c.clone())
|
||||||
|
.unwrap_or_else(|| std::sync::Arc::new(rustls::crypto::ring::default_provider())),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,21 +85,25 @@ impl Client {
|
||||||
stream.write_all(url_str.as_bytes()).await?;
|
stream.write_all(url_str.as_bytes()).await?;
|
||||||
stream.write_all(b"\r\n").await?;
|
stream.write_all(b"\r\n").await?;
|
||||||
|
|
||||||
|
let status = {
|
||||||
let mut buf: [u8; 3] = [0, 0, 0]; // 2 digits, space
|
let mut buf: [u8; 3] = [0, 0, 0]; // 2 digits, space
|
||||||
stream.read_exact(&mut buf).await?;
|
stream.read_exact(&mut buf).await?;
|
||||||
let status = Status::parse_status(&buf)?;
|
Status::parse_status(&buf)?
|
||||||
|
};
|
||||||
|
|
||||||
let mut message: Vec<u8> = Vec::new();
|
let mut stream = tokio::io::BufReader::new(stream);
|
||||||
let mut buf_reader = tokio::io::BufReader::new(&mut stream);
|
|
||||||
let mut buf: [u8; 1] = [0]; // buffer for LF (\n)
|
let message = {
|
||||||
|
let mut result: Vec<u8> = Vec::new();
|
||||||
|
let mut buf = [0u8]; // buffer for LF (\n)
|
||||||
|
|
||||||
// reading message after status code
|
// reading message after status code
|
||||||
// until CRLF (\r\n)
|
// until CRLF (\r\n)
|
||||||
loop {
|
loop {
|
||||||
// until CR
|
// until CR
|
||||||
buf_reader.read_until(b'\r', &mut message).await?;
|
stream.read_until(b'\r', &mut result).await?;
|
||||||
// now read next char...
|
// now read next char...
|
||||||
buf_reader.read_exact(&mut buf).await?;
|
stream.read_exact(&mut buf).await?;
|
||||||
if buf[0] == b'\n' {
|
if buf[0] == b'\n' {
|
||||||
// ...and check if it's LF
|
// ...and check if it's LF
|
||||||
break;
|
break;
|
||||||
|
@ -107,17 +111,18 @@ impl Client {
|
||||||
// ...otherwise, CR is a part of message, not a CRLF terminator,
|
// ...otherwise, CR is a part of message, not a CRLF terminator,
|
||||||
// so append that one byte that's supposed to be LF (but not LF)
|
// so append that one byte that's supposed to be LF (but not LF)
|
||||||
// to the message buffer
|
// to the message buffer
|
||||||
message.push(buf[0].into());
|
result.push(buf[0].into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trim last CR
|
// trim last CR
|
||||||
if message.last().is_some_and(|c| c == &b'\r') {
|
if result.last().is_some_and(|c| c == &b'\r') {
|
||||||
message.pop();
|
result.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vec<u8> -> ASCII or UTF-8 String
|
// Vec<u8> -> ASCII or UTF-8 String
|
||||||
let message = String::from_utf8(message)?;
|
String::from_utf8(result)?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Response::new(status, message, stream))
|
Ok(Response::new(status, message, stream))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::{status::Status, LibError};
|
use crate::{status::Status, LibError, ReplyType};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
type BodyStream = tokio_rustls::client::TlsStream<tokio::net::TcpStream>;
|
type BodyStream = tokio::io::BufReader<tokio_rustls::client::TlsStream<tokio::net::TcpStream>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
|
@ -21,6 +21,14 @@ impl Response {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ensure_ok(self: Self) -> Result<Self, Self> {
|
||||||
|
if self.status.reply_type() == ReplyType::Success {
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
Err(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn status(self: &Self) -> Status {
|
pub fn status(self: &Self) -> Status {
|
||||||
self.status
|
self.status
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,10 @@ impl Status {
|
||||||
self.status_code
|
self.status_code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn num(self: &Self) -> u8 {
|
||||||
|
self.status_code.into()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reply_type(self: &Self) -> ReplyType {
|
pub fn reply_type(self: &Self) -> ReplyType {
|
||||||
self.reply_type
|
self.reply_type
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue