implement gemini redirection handler

This commit is contained in:
yggverse 2025-01-19 02:33:58 +02:00
parent 983655e934
commit 4f7df8ea44
2 changed files with 56 additions and 56 deletions

View File

@ -14,17 +14,20 @@ use gtk::{
pub enum Request { pub enum Request {
Gemini { Gemini {
feature: Feature, feature: Feature,
referrer: Vec<Self>, referrer: Option<Box<Self>>,
uri: Uri,
},
Titan {
referrer: Option<Box<Self>>,
uri: Uri, uri: Uri,
}, },
Titan(Uri),
} }
impl Request { impl Request {
// Constructors // Constructors
/// Create new `Self` from featured string /// Create new `Self` from featured string
pub fn parse(query: &str, referrer: Option<Vec<Self>>) -> Result<Self, Error> { pub fn parse(query: &str, referrer: Option<Box<Self>>) -> Result<Self, Error> {
let (feature, request) = Feature::parse(query); let (feature, request) = Feature::parse(query);
match Uri::parse(request, UriFlags::NONE) { match Uri::parse(request, UriFlags::NONE) {
@ -37,15 +40,15 @@ impl Request {
pub fn from_uri( pub fn from_uri(
uri: Uri, uri: Uri,
feature: Option<Feature>, feature: Option<Feature>,
referrer: Option<Vec<Self>>, referrer: Option<Box<Self>>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
match uri.scheme().as_str() { match uri.scheme().as_str() {
"gemini" => Ok(Self::Gemini { "gemini" => Ok(Self::Gemini {
feature: feature.unwrap_or_default(), feature: feature.unwrap_or_default(),
referrer: referrer.unwrap_or_default(), referrer,
uri, uri,
}), }),
"titan" => Ok(Self::Titan(uri)), "titan" => Ok(Self::Titan { referrer, uri }),
_ => Err(Error::Unsupported), _ => Err(Error::Unsupported),
} }
} }
@ -65,7 +68,10 @@ impl Request {
referrer, referrer,
uri, uri,
} => gemini::send(client, feature, uri, referrer, cancellable, callback), } => gemini::send(client, feature, uri, referrer, cancellable, callback),
Self::Titan(_) => todo!(), Self::Titan {
referrer: _,
uri: _,
} => todo!(),
} }
} }
@ -79,7 +85,19 @@ impl Request {
referrer: _, referrer: _,
uri, uri,
} }
| Self::Titan(uri) => uri, | Self::Titan { referrer: _, uri } => uri,
} }
} }
/// Recursively count referrers of `Self`
/// * useful to apply redirection rules by protocol driver selected
pub fn referrers(&self) -> usize {
let count = match self {
Request::Gemini { referrer, .. } => referrer,
Request::Titan { referrer, .. } => referrer,
}
.as_ref()
.map_or(0, |request| request.referrers());
1 + count
}
} }

View File

@ -9,7 +9,7 @@ pub fn send(
client: &Client, client: &Client,
feature: Feature, feature: Feature,
uri: Uri, uri: Uri,
referrer: Vec<Request>, referrer: Option<Box<Request>>,
cancellable: Cancellable, cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static, callback: impl FnOnce(Response) + 'static,
) { ) {
@ -56,7 +56,7 @@ fn request(
fn handle( fn handle(
response: ggemini::client::connection::Response, response: ggemini::client::connection::Response,
base: Uri, base: Uri,
referrer: Vec<Request>, referrer: Option<Box<Request>>,
feature: Feature, feature: Feature,
cancellable: Cancellable, cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static, callback: impl FnOnce(Response) + 'static,
@ -117,23 +117,9 @@ fn handle(
})), })),
}, },
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-30-temporary-redirection // https://geminiprotocol.net/docs/protocol-specification.gmi#status-30-temporary-redirection
Status::Redirect => callback(redirect( Status::Redirect => callback(redirect(response, feature, base, referrer, false)),
response,
base,
referrer,
cancellable,
Priority::DEFAULT,
false,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection // https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
Status::PermanentRedirect => callback(redirect( Status::PermanentRedirect => callback(redirect(response, feature, base, referrer, true)),
response,
base,
referrer,
cancellable,
Priority::DEFAULT,
true,
)),
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60 // https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
Status::CertificateRequest => callback(Response::Certificate(Certificate::Request { Status::CertificateRequest => callback(Response::Certificate(Certificate::Request {
title: match response.meta.data { title: match response.meta.data {
@ -161,26 +147,24 @@ fn handle(
} }
} }
/// Shared redirection `Response` builder /// `Response::Redirect` builder
/// * [Redirect specification](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection)
fn redirect( fn redirect(
// Subject to parse
response: ggemini::client::connection::Response, response: ggemini::client::connection::Response,
// Wanted to process relative links feature: Feature,
base: Uri, base: Uri, // relative links conversion
// List of previous requests to handle redirection rules referrer: Option<Box<Request>>, // handles redirection rules
referrer: Vec<Request>, is_permanent: bool,
cancellable: Cancellable,
priority: Priority,
is_foreground: bool,
) -> Response { ) -> Response {
// Validate redirection attempt // Validate redirection count
// [Gemini protocol specifications](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection) if let Some(ref referrer) = referrer {
if referrer.len() > 5 { if referrer.referrers() > 5 {
return Response::Failure(Failure::Error { return Response::Failure(Failure::Error {
message: "Max redirection count reached".to_string(), message: "Max redirection count reached".to_string(),
}); });
}
} }
// Target URL expected from client response meta data // Target URL expected from response meta data
match response.meta.data { match response.meta.data {
Some(target) => match Uri::parse_relative(&base, target.as_str(), UriFlags::NONE) { Some(target) => match Uri::parse_relative(&base, target.as_str(), UriFlags::NONE) {
Ok(target) => { Ok(target) => {
@ -194,22 +178,20 @@ fn redirect(
.to_string(), .to_string(),
}); // @TODO placeholder page with optional link open button }); // @TODO placeholder page with optional link open button
} }
// Build new request
// Build new `Request` for redirection `Response` match Request::from_uri(target, Some(feature), referrer) {
// * make sure that `referrer` already contain current `Request` Ok(request) => Response::Redirect(if is_permanent {
// (to validate redirection count in chain) Redirect::Foreground(request)
todo!() } else {
/*let request = Redirect::Background(request)
Request::build(&target.to_string(), Some(referrer), cancellable, priority); }),
Err(e) => Response::Failure(Failure::Error {
Response::Redirect(if is_foreground { message: e.to_string(),
Redirect::Foreground(request) }),
} else { }
Redirect::Background(request)
})*/
} }
Err(e) => Response::Failure(Failure::Error { Err(e) => Response::Failure(Failure::Error {
message: format!("Could not parse target address: {e}"), message: e.to_string(),
}), }),
}, },
None => Response::Failure(Failure::Error { None => Response::Failure(Failure::Error {