pub mod error;
pub mod request;
pub mod response;
pub use error::Error;
pub use request::{Mode, Request};
pub use response::Response;
use gio::{
Cancellable, IOStream, NetworkAddress, SocketConnection, TlsCertificate, TlsClientConnection,
prelude::{IOStreamExt, OutputStreamExtManual, TlsCertificateExt, TlsConnectionExt},
};
use glib::{
Bytes, Priority,
object::{Cast, ObjectExt},
};
#[derive(Debug, Clone)]
pub struct Connection {
pub network_address: NetworkAddress,
pub socket_connection: SocketConnection,
pub tls_client_connection: TlsClientConnection,
}
impl Connection {
pub fn build(
socket_connection: SocketConnection,
network_address: NetworkAddress,
client_certificate: Option<TlsCertificate>,
server_certificates: Option<Vec<TlsCertificate>>,
is_session_resumption: bool,
) -> Result<Self, Error> {
Ok(Self {
tls_client_connection: match new_tls_client_connection(
&socket_connection,
Some(&network_address),
server_certificates,
is_session_resumption,
) {
Ok(tls_client_connection) => {
if let Some(ref c) = client_certificate {
tls_client_connection.set_certificate(c);
}
tls_client_connection
}
Err(e) => return Err(e),
},
network_address,
socket_connection,
})
}
pub fn request_async(
self,
request: Request,
priority: Priority,
cancellable: Cancellable,
callback: impl FnOnce(Result<(Response, Self), Error>) + 'static,
) {
let output_stream = self.stream().output_stream();
output_stream.clone().write_all_async(
Bytes::from_owned(request.header()),
priority,
Some(&cancellable.clone()),
move |result| match result {
Ok(_) => match request {
Request::Gemini { mode, .. } => match mode {
Mode::HeaderOnly => Response::header_from_connection_async(
self,
priority,
cancellable,
|result, connection| {
callback(match result {
Ok(response) => Ok((response, connection)),
Err(e) => Err(Error::Response(e)),
})
},
),
},
Request::Titan { data, mode, .. } => output_stream.write_all_async(
data,
priority,
Some(&cancellable.clone()),
move |result| match result {
Ok(_) => match mode {
Mode::HeaderOnly => Response::header_from_connection_async(
self,
priority,
cancellable,
|result, connection| {
callback(match result {
Ok(response) => Ok((response, connection)),
Err(e) => Err(Error::Response(e)),
})
},
),
},
Err((b, e)) => callback(Err(Error::Request(b, e))),
},
),
},
Err((b, e)) => callback(Err(Error::Request(b, e))),
},
)
}
pub fn stream(&self) -> IOStream {
self.tls_client_connection.clone().upcast::<IOStream>()
}
}
fn new_tls_client_connection(
socket_connection: &SocketConnection,
server_identity: Option<&NetworkAddress>,
server_certificates: Option<Vec<TlsCertificate>>,
is_session_resumption: bool,
) -> Result<TlsClientConnection, Error> {
match TlsClientConnection::new(socket_connection, server_identity) {
Ok(tls_client_connection) => {
tls_client_connection.set_property("session-resumption-enabled", is_session_resumption);
tls_client_connection.set_require_close_notify(true);
tls_client_connection.connect_accept_certificate(move |_, c, _| {
server_certificates
.as_ref()
.is_none_or(|server_certificates| {
for server_certificate in server_certificates {
if server_certificate.is_same(c) {
return true;
}
}
false
})
});
Ok(tls_client_connection)
}
Err(e) => Err(Error::TlsClientConnection(e)),
}
}