use current Request as referrer

This commit is contained in:
yggverse 2025-01-19 03:15:03 +02:00
parent 4698d7dce1
commit dd171dabe9
2 changed files with 65 additions and 66 deletions

View File

@ -27,7 +27,7 @@ impl Request {
// Constructors // Constructors
/// Create new `Self` from featured string /// Create new `Self` from featured string
pub fn parse(query: &str, referrer: Option<Box<Self>>) -> Result<Self, Error> { pub fn parse(query: &str, referrer: Option<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) {
@ -40,15 +40,18 @@ impl Request {
pub fn from_uri( pub fn from_uri(
uri: Uri, uri: Uri,
feature: Option<Feature>, feature: Option<Feature>,
referrer: Option<Box<Self>>, referrer: Option<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: referrer.map(Box::new),
uri,
}),
"titan" => Ok(Self::Titan {
referrer: referrer.map(Box::new),
uri, uri,
}), }),
"titan" => Ok(Self::Titan { referrer, uri }),
_ => Err(Error::Unsupported), _ => Err(Error::Unsupported),
} }
} }
@ -62,16 +65,9 @@ impl Request {
cancellable: Cancellable, cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static, callback: impl FnOnce(Response) + 'static,
) { ) {
match self { match &self {
Self::Gemini { Self::Gemini { .. } => gemini::request(client, self, cancellable, callback),
feature, Self::Titan { .. } => todo!(),
referrer,
uri,
} => gemini::request(client, feature, uri, referrer, cancellable, callback),
Self::Titan {
referrer: _,
uri: _,
} => todo!(),
} }
} }
@ -89,6 +85,14 @@ impl Request {
} }
} }
/// Get `Feature` reference for `Self`
pub fn feature(&self) -> &Feature {
match self {
Request::Gemini { feature, .. } => feature,
Request::Titan { .. } => &Feature::Default,
}
}
/// Recursively count referrers of `Self` /// Recursively count referrers of `Self`
/// * useful to apply redirection rules by protocol driver selected /// * useful to apply redirection rules by protocol driver selected
pub fn referrers(&self) -> usize { pub fn referrers(&self) -> usize {

View File

@ -7,18 +7,16 @@ use gtk::{
pub fn request( pub fn request(
client: &Client, client: &Client,
feature: Feature, request: Request,
uri: Uri,
referrer: Option<Box<Request>>,
cancellable: Cancellable, cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static, callback: impl FnOnce(Response) + 'static,
) { ) {
send( send(
client, client,
uri.clone(), request.as_uri().clone(),
cancellable.clone(), cancellable.clone(),
move |result| match result { move |result| match result {
Ok(response) => handle(response, uri, referrer, feature, cancellable, callback), Ok(response) => handle(request, response, cancellable, callback),
Err(e) => callback(Response::Failure(Failure::Error { Err(e) => callback(Response::Failure(Failure::Error {
message: e.to_string(), message: e.to_string(),
})), })),
@ -54,10 +52,8 @@ fn send(
/// Shared handler for Gemini `Result` /// Shared handler for Gemini `Result`
/// * same implementation for Gemini and Titan protocols response /// * same implementation for Gemini and Titan protocols response
fn handle( fn handle(
request: Request,
response: ggemini::client::connection::Response, response: ggemini::client::connection::Response,
base: Uri,
referrer: Option<Box<Request>>,
feature: Feature,
cancellable: Cancellable, cancellable: Cancellable,
callback: impl FnOnce(Response) + 'static, callback: impl FnOnce(Response) + 'static,
) { ) {
@ -65,14 +61,14 @@ fn handle(
match response.meta.status { match response.meta.status {
// https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected // https://geminiprotocol.net/docs/protocol-specification.gmi#input-expected
Status::Input => callback(Response::Input(Input::Response { Status::Input => callback(Response::Input(Input::Response {
base, base: request.as_uri().clone(),
title: match response.meta.data { title: match response.meta.data {
Some(data) => data.to_gstring(), Some(data) => data.to_gstring(),
None => "Input expected".into(), None => "Input expected".into(),
}, },
})), })),
Status::SensitiveInput => callback(Response::Input(Input::Sensitive { Status::SensitiveInput => callback(Response::Input(Input::Sensitive {
base, base: request.as_uri().clone(),
title: match response.meta.data { title: match response.meta.data {
Some(data) => data.to_gstring(), Some(data) => data.to_gstring(),
None => "Input expected".into(), None => "Input expected".into(),
@ -87,12 +83,12 @@ fn handle(
cancellable.clone(), cancellable.clone(),
move |result| match result { move |result| match result {
Ok(text) => callback(Response::TextGemini { Ok(text) => callback(Response::TextGemini {
base, base: request.as_uri().clone(),
source: text.data, source: text.data,
is_source_request: matches!(feature, Feature::Source), is_source_request: matches!(request.feature(), Feature::Source), // @TODO return `Feature`?
}), }),
Err(e) => callback(Response::Failure(Failure::Mime { Err(e) => callback(Response::Failure(Failure::Mime {
base, base: request.as_uri().clone(),
mime: mime.to_string(), mime: mime.to_string(),
message: e.to_string(), message: e.to_string(),
})), })),
@ -100,14 +96,14 @@ fn handle(
), ),
"image/png" | "image/gif" | "image/jpeg" | "image/webp" => { "image/png" | "image/gif" | "image/jpeg" | "image/webp" => {
callback(Response::Stream { callback(Response::Stream {
base, base: request.as_uri().clone(),
mime: mime.to_string(), mime: mime.to_string(),
stream: response.connection.stream(), stream: response.connection.stream(),
cancellable, cancellable,
}) })
} }
mime => callback(Response::Failure(Failure::Mime { mime => callback(Response::Failure(Failure::Mime {
base, base: request.as_uri().clone(),
mime: mime.to_string(), mime: mime.to_string(),
message: format!("Content type `{mime}` yet not supported"), message: format!("Content type `{mime}` yet not supported"),
})), })),
@ -117,9 +113,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(response, feature, base, referrer, false)), Status::Redirect => callback(redirect(request, response, 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(response, feature, base, referrer, true)), Status::PermanentRedirect => callback(redirect(request, response, 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 {
@ -150,50 +146,49 @@ fn handle(
/// `Response::Redirect` builder /// `Response::Redirect` builder
/// * [Redirect specification](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection) /// * [Redirect specification](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection)
fn redirect( fn redirect(
request: Request,
response: ggemini::client::connection::Response, response: ggemini::client::connection::Response,
feature: Feature,
base: Uri, // relative links conversion
referrer: Option<Box<Request>>, // handles redirection rules
is_permanent: bool, is_permanent: bool,
) -> Response { ) -> Response {
// Validate redirection count // Validate redirection count
if let Some(ref referrer) = referrer { if request.referrers() > 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 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) => {
Ok(target) => { match Uri::parse_relative(request.as_uri(), target.as_str(), UriFlags::NONE) {
// Disallow external redirection Ok(target) => {
if base.scheme() != target.scheme() // Disallow external redirection
|| base.port() != target.port() if request.as_uri().scheme() != target.scheme()
|| base.host() != target.host() || request.as_uri().port() != target.port()
{ || request.as_uri().host() != target.host()
return Response::Failure(Failure::Error { {
message: "External redirects not allowed by protocol specification" return Response::Failure(Failure::Error {
.to_string(), message: "External redirects not allowed by protocol specification"
}); // @TODO placeholder page with optional link open button .to_string(),
} }); // @TODO placeholder page with optional link open button
// Build new request }
match Request::from_uri(target, Some(feature), referrer) { // Build new request
Ok(request) => Response::Redirect(if is_permanent { match Request::from_uri(target, None, Some(request)) {
Redirect::Foreground(request) Ok(request) => Response::Redirect(if is_permanent {
} else { Redirect::Foreground(request)
Redirect::Background(request) } else {
}), Redirect::Background(request)
Err(e) => Response::Failure(Failure::Error { }),
message: e.to_string(), Err(e) => Response::Failure(Failure::Error {
}), message: e.to_string(),
}),
}
} }
Err(e) => Response::Failure(Failure::Error {
message: e.to_string(),
}),
} }
Err(e) => Response::Failure(Failure::Error { }
message: e.to_string(),
}),
},
None => Response::Failure(Failure::Error { None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(), message: "Target address not found".to_string(),
}), }),