implement shared redirection Response builder

This commit is contained in:
yggverse 2025-01-18 07:21:06 +02:00
parent 86a6ad058a
commit 86d191cc46
2 changed files with 69 additions and 44 deletions

View File

@ -2,6 +2,7 @@ use super::{
response::{Certificate, Failure, Input, Redirect}, response::{Certificate, Failure, Input, Redirect},
Profile, Request, Response, Profile, Request, Response,
}; };
use gtk::{ use gtk::{
gio::Cancellable, gio::Cancellable,
glib::{Priority, Uri, UriFlags}, glib::{Priority, Uri, UriFlags},
@ -99,39 +100,23 @@ pub 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(match response.meta.data { Status::Redirect => callback(redirect(
Some(data) => match Uri::parse_relative(&base, data.as_str(), UriFlags::NONE) { response.meta.data,
Ok(target) => Response::Redirect(Redirect::Foreground(Request::build( base,
&target.to_string(), referrer,
Some(referrer),
cancellable, cancellable,
priority, priority,
))), false,
Err(e) => Response::Failure(Failure::Error { )),
message: format!("Could not parse target address: {e}"),
}),
},
None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(),
}),
}), // @TODO validate redirect count
// 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(match response.meta.data { Status::PermanentRedirect => callback(redirect(
Some(data) => match Uri::parse_relative(&base, data.as_str(), UriFlags::NONE) { response.meta.data,
Ok(target) => Response::Redirect(Redirect::Background(Request::build( base,
&target.to_string(), referrer,
Some(referrer),
cancellable, cancellable,
priority, priority,
))), true,
Err(e) => Response::Failure(Failure::Error { )),
message: format!("Could not parse target address: {e}"),
}),
},
None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(),
}),
}), // @TODO validate redirect count
// 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 {
@ -164,3 +149,54 @@ pub fn handle(
})), })),
} }
} }
/// Shared redirection `Response` builder
fn redirect(
data: Option<ggemini::client::connection::response::meta::Data>,
base: Uri,
referrer: Vec<Request>,
cancellable: Cancellable,
priority: Priority,
is_foreground: bool,
) -> Response {
// Validate redirect according to
// [Gemini protocol specifications](https://geminiprotocol.net/docs/protocol-specification.gmi#redirection)
if referrer.len() > 5 {
return Response::Failure(Failure::Error {
message: format!("Max redirection count reached"),
});
}
match data {
Some(target) => match Uri::parse_relative(&base, target.as_str(), UriFlags::NONE) {
Ok(target) => {
// Disallow external redirection
if base.scheme() != target.scheme()
|| base.port() != target.port()
|| base.host() != target.host()
{
return Response::Failure(Failure::Error {
message: format!(
"External redirects not allowed by protocol specification"
),
}); // @TODO placeholder page with optional link open button
}
// Build request
let request =
Request::build(&target.to_string(), Some(referrer), cancellable, priority);
Response::Redirect(if is_foreground {
Redirect::Foreground(request)
} else {
Redirect::Background(request)
})
}
Err(e) => Response::Failure(Failure::Error {
message: format!("Could not parse target address: {e}"),
}),
},
None => Response::Failure(Failure::Error {
message: "Target address not found".to_string(),
}),
}
}

View File

@ -1,7 +1,7 @@
//! Some shared helpers collection //! Some shared helpers collection
// Global dependencies // Global dependencies
use gtk::glib::{DateTime, GString, Uri}; use gtk::glib::{DateTime, GString};
/// Format bytes to KB/MB/GB presentation /// Format bytes to KB/MB/GB presentation
pub fn format_bytes(value: usize) -> String { pub fn format_bytes(value: usize) -> String {
@ -34,14 +34,3 @@ pub fn format_time(t: &DateTime) -> GString {
pub fn now() -> DateTime { pub fn now() -> DateTime {
DateTime::now_local().unwrap() // @TODO handle? DateTime::now_local().unwrap() // @TODO handle?
} }
/// Compare `subject` with `base`
pub fn _is_external(subject: &Uri, base: &Uri) -> bool {
if subject.scheme() != base.scheme() {
return true;
}
if subject.port() != base.port() {
return true;
}
subject.host() != base.host()
} // @TODO not in use