mirror of
https://github.com/YGGverse/Yoda.git
synced 2026-03-31 16:45:27 +00:00
implement 30,31 status codes
This commit is contained in:
parent
9f2d9ce110
commit
9818b3a998
4 changed files with 138 additions and 115 deletions
|
|
@ -41,9 +41,9 @@ GTK 4 / Libadwaita client written in Rust
|
||||||
* [x] Input
|
* [x] Input
|
||||||
* [x] `10` Input
|
* [x] `10` Input
|
||||||
* [x] `11` Sensitive input
|
* [x] `11` Sensitive input
|
||||||
* [ ] Redirection
|
* [x] Redirection
|
||||||
* [ ] `30` Temporary (partial)
|
* [x] `30` Temporary
|
||||||
* [ ] `31` Permanent (partial)
|
* [x] `31` Permanent
|
||||||
* [ ] Temporary failure
|
* [ ] Temporary failure
|
||||||
* [ ] `40` Unspecified condition
|
* [ ] `40` Unspecified condition
|
||||||
* [ ] `41` Server unavailable
|
* [ ] `41` Server unavailable
|
||||||
|
|
|
||||||
|
|
@ -155,20 +155,62 @@ impl Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO rename to `load`
|
||||||
pub fn navigation_reload(&self) {
|
pub fn navigation_reload(&self) {
|
||||||
|
/// Global limit to prevent infinitive redirects (ALL protocols)
|
||||||
|
/// * every protocol implementation has own value checker, according to specification
|
||||||
|
const DEFAULT_MAX_REDIRECT_COUNT: i8 = 10;
|
||||||
|
|
||||||
// Reset widgets
|
// Reset widgets
|
||||||
self.input.unset();
|
self.input.unset();
|
||||||
|
|
||||||
// Init shared objects to not spawn a lot
|
// Create shared variant value
|
||||||
let request_text = self.navigation.request_text();
|
|
||||||
let id = self.id.to_variant();
|
let id = self.id.to_variant();
|
||||||
|
|
||||||
|
// Try **take** request value from Redirect holder first
|
||||||
|
let request = if let Some(redirect) = self.meta.take_redirect() {
|
||||||
|
// Increase redirect counter
|
||||||
|
self.meta.set_redirect_count(self.meta.redirect_count() + 1);
|
||||||
|
|
||||||
|
// Prevent infinitive redirection by global settings
|
||||||
|
if self.meta.redirect_count() > DEFAULT_MAX_REDIRECT_COUNT {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update navigation on redirect `is_foreground`
|
||||||
|
if redirect.is_foreground() {
|
||||||
|
self.navigation
|
||||||
|
.set_request_text(redirect.request().as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return value from redirection holder
|
||||||
|
redirect.request()
|
||||||
|
} else {
|
||||||
|
// Add history record
|
||||||
|
let value = self.navigation.request_text();
|
||||||
|
|
||||||
|
match self.navigation.history_current() {
|
||||||
|
Some(current) => {
|
||||||
|
if current != value {
|
||||||
|
self.navigation.history_add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => self.navigation.history_add(value),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset redirect counter as value taken from user input
|
||||||
|
self.meta.set_redirect_count(0);
|
||||||
|
|
||||||
|
// Return value from navigation entry
|
||||||
|
self.navigation.request_text()
|
||||||
|
};
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
self.meta.set_status(Status::Reload).set_title(&"Loading..");
|
self.meta.set_status(Status::Reload).set_title(&"Loading..");
|
||||||
self.action_update.activate(Some(&id));
|
self.action_update.activate(Some(&id));
|
||||||
|
|
||||||
// Route by request
|
// Route by request
|
||||||
match Uri::parse(&request_text, UriFlags::NONE) {
|
match Uri::parse(&request, UriFlags::NONE) {
|
||||||
Ok(uri) => {
|
Ok(uri) => {
|
||||||
// Route by scheme
|
// Route by scheme
|
||||||
match uri.scheme().as_str() {
|
match uri.scheme().as_str() {
|
||||||
|
|
@ -199,17 +241,17 @@ impl Page {
|
||||||
// Try interpret URI manually
|
// Try interpret URI manually
|
||||||
if Regex::match_simple(
|
if Regex::match_simple(
|
||||||
r"^[^\/\s]+\.[\w]{2,}",
|
r"^[^\/\s]+\.[\w]{2,}",
|
||||||
request_text.clone(),
|
request.clone(),
|
||||||
RegexCompileFlags::DEFAULT,
|
RegexCompileFlags::DEFAULT,
|
||||||
RegexMatchFlags::DEFAULT,
|
RegexMatchFlags::DEFAULT,
|
||||||
) {
|
) {
|
||||||
// Seems request contain some host, try append default scheme
|
// Seems request contain some host, try append default scheme
|
||||||
let request_text = gformat!("gemini://{request_text}");
|
let request = gformat!("gemini://{request}");
|
||||||
// Make sure new request conversable to valid URI
|
// Make sure new request conversable to valid URI
|
||||||
match Uri::parse(&request_text, UriFlags::NONE) {
|
match Uri::parse(&request, UriFlags::NONE) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Update
|
// Update
|
||||||
self.navigation.set_request_text(&request_text);
|
self.navigation.set_request_text(&request);
|
||||||
|
|
||||||
// Reload page
|
// Reload page
|
||||||
self.action_page_reload.activate(None);
|
self.action_page_reload.activate(None);
|
||||||
|
|
@ -220,13 +262,13 @@ impl Page {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Plain text given, make search request to default provider
|
// Plain text given, make search request to default provider
|
||||||
let request_text = gformat!(
|
let request = gformat!(
|
||||||
"gemini://tlgs.one/search?{}",
|
"gemini://tlgs.one/search?{}",
|
||||||
Uri::escape_string(&request_text, None, false)
|
Uri::escape_string(&request, None, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
self.navigation.set_request_text(&request_text);
|
self.navigation.set_request_text(&request);
|
||||||
|
|
||||||
// Reload page
|
// Reload page
|
||||||
self.action_page_reload.activate(None);
|
self.action_page_reload.activate(None);
|
||||||
|
|
@ -366,44 +408,8 @@ impl Page {
|
||||||
let id = self.id.to_variant();
|
let id = self.id.to_variant();
|
||||||
let input = self.input.clone();
|
let input = self.input.clone();
|
||||||
let meta = self.meta.clone();
|
let meta = self.meta.clone();
|
||||||
let navigation = self.navigation.clone();
|
|
||||||
let url = uri.clone().to_str();
|
let url = uri.clone().to_str();
|
||||||
|
|
||||||
// Check for page redirect pending
|
|
||||||
if meta.is_redirect() {
|
|
||||||
// Check for protocol limits
|
|
||||||
if meta.redirect_count().unwrap() > 5 {
|
|
||||||
// Update meta
|
|
||||||
meta.set_status(Status::Failure).set_title(&"Oops");
|
|
||||||
// Show placeholder with confirmation request to continue
|
|
||||||
content.to_text_gemini(
|
|
||||||
&uri,
|
|
||||||
&gformat!(
|
|
||||||
// @TODO status page?
|
|
||||||
"# Redirect issue\n\nRedirection limit reached\n\nContinue:\n\n=> {}",
|
|
||||||
meta.redirect_target().unwrap().to_string()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return; // @TODO
|
|
||||||
} else {
|
|
||||||
action_page_open.activate(Some(
|
|
||||||
&meta.redirect_target().unwrap().to_string().to_variant(),
|
|
||||||
));
|
|
||||||
// @TODO is_follow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add history record
|
|
||||||
match navigation.history_current() {
|
|
||||||
Some(current) => {
|
|
||||||
if current != url {
|
|
||||||
navigation.history_add(url.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => navigation.history_add(url.clone()),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init socket
|
// Init socket
|
||||||
let client = SocketClient::new();
|
let client = SocketClient::new();
|
||||||
|
|
||||||
|
|
@ -681,8 +687,8 @@ impl Page {
|
||||||
// Extract redirection URL from response data
|
// Extract redirection URL from response data
|
||||||
match response.data() {
|
match response.data() {
|
||||||
Some(unresolved_url) => {
|
Some(unresolved_url) => {
|
||||||
// New URL from server MAY to be relative (according to the protocol),
|
// New URL from server MAY to be relative (according to the protocol specification),
|
||||||
// resolve to absolute URI using current request value as the base for parser
|
// resolve to absolute URI gobject using current request as the base for parser:
|
||||||
// https://docs.gtk.org/glib/type_func.Uri.resolve_relative.html
|
// https://docs.gtk.org/glib/type_func.Uri.resolve_relative.html
|
||||||
match Uri::resolve_relative(
|
match Uri::resolve_relative(
|
||||||
Some(&uri.to_string()),
|
Some(&uri.to_string()),
|
||||||
|
|
@ -690,60 +696,84 @@ impl Page {
|
||||||
UriFlags::NONE,
|
UriFlags::NONE,
|
||||||
) {
|
) {
|
||||||
Ok(resolved_url) => {
|
Ok(resolved_url) => {
|
||||||
// Build valid URI (this conversion wanted to process query and fragment later)
|
// Build valid URI from resolved URL string
|
||||||
|
// this conversion wanted to simply exclude `query` and `fragment` later (as restricted by protocol specification)
|
||||||
match Uri::parse(resolved_url.as_str(), UriFlags::NONE) {
|
match Uri::parse(resolved_url.as_str(), UriFlags::NONE) {
|
||||||
Ok(resolved_uri) => {
|
Ok(resolved_uri) => {
|
||||||
// Client MUST prevent external redirects
|
// Client MUST prevent external redirects (by protocol specification)
|
||||||
if is_external_uri(&resolved_uri, &uri) {
|
if is_external_uri(&resolved_uri, &uri) {
|
||||||
// Update meta
|
// Update meta
|
||||||
meta.set_status(Status::Failure)
|
meta.set_status(Status::Failure)
|
||||||
.set_title(&"Oops");
|
.set_title(&"Oops");
|
||||||
|
|
||||||
// Show placeholder with confirmation request to continue
|
// Show placeholder with manual confirmation to continue @TODO status page?
|
||||||
content.to_text_gemini(
|
content.to_text_gemini(
|
||||||
&uri,
|
&uri,
|
||||||
&gformat!( // @TODO status page?
|
&gformat!(
|
||||||
"# Redirect issue\n\nExternal redirects not allowed by protocol\n\nContinue:\n\n=> {}",
|
"# Redirect issue\n\nExternal redirects not allowed by protocol\n\nContinue:\n\n=> {}",
|
||||||
resolved_uri.to_string()
|
resolved_uri.to_string()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
// Client MUST limit the number of redirects they follow to 5 (by protocol specification)
|
||||||
|
} else if meta.redirect_count() >= 5 {
|
||||||
// Update meta
|
// Update meta
|
||||||
|
meta.set_status(Status::Failure)
|
||||||
|
.set_title(&"Oops");
|
||||||
|
|
||||||
|
// Show placeholder with manual confirmation to continue @TODO status page?
|
||||||
|
content.to_text_gemini(
|
||||||
|
&uri,
|
||||||
|
&gformat!(
|
||||||
|
"# Redirect issue\n\nLimit the number of redirects reached\n\nContinue:\n\n=> {}",
|
||||||
|
resolved_uri.to_string()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// Redirection value looks valid, create new redirect (stored in meta `Redirect` holder)
|
||||||
|
// then call page reload action to apply it by the parental controller
|
||||||
|
} else {
|
||||||
meta.set_redirect(
|
meta.set_redirect(
|
||||||
match meta.redirect_count() {
|
// Skip query and fragment by protocol requirements
|
||||||
Some(count) => count + 1,
|
// @TODO review fragment specification
|
||||||
None => 0
|
resolved_uri.to_string_partial(
|
||||||
},
|
UriHideFlags::FRAGMENT | UriHideFlags::QUERY
|
||||||
|
),
|
||||||
|
// Set follow policy based on status code
|
||||||
match response.status() {
|
match response.status() {
|
||||||
gemini::client::response::meta::Status::PermanentRedirect => true,
|
gemini::client::response::meta::Status::PermanentRedirect => true,
|
||||||
_ => false
|
_ => false
|
||||||
},
|
},
|
||||||
Uri::parse(
|
|
||||||
resolved_uri.to_string_partial(
|
|
||||||
UriHideFlags::FRAGMENT | UriHideFlags::QUERY // @TODO review fragment specification
|
|
||||||
).as_str(),
|
|
||||||
UriFlags::NONE
|
|
||||||
).unwrap()
|
|
||||||
)
|
)
|
||||||
.set_status(Status::Redirect)
|
.set_status(Status::Redirect) // @TODO is this status really wanted?
|
||||||
.set_title(&"Redirect"); // @TODO is really wanted here?
|
.set_title(&"Redirect");
|
||||||
|
|
||||||
// Reload page to apply redirect
|
// Reload page to apply redirection
|
||||||
action_page_reload.activate(None);
|
action_page_reload.activate(None);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
meta.set_status(Status::Failure);
|
let status = Status::Failure;
|
||||||
|
let title = &"Oops";
|
||||||
|
|
||||||
|
meta.set_status(status)
|
||||||
|
.set_title(title);
|
||||||
|
|
||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
|
.set_title(title)
|
||||||
.set_description(Some(reason.message()));
|
.set_description(Some(reason.message()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
meta.set_status(Status::Failure);
|
let status = Status::Failure;
|
||||||
|
let title = &"Oops";
|
||||||
|
|
||||||
|
meta.set_status(status)
|
||||||
|
.set_title(title);
|
||||||
|
|
||||||
content
|
content
|
||||||
.to_status_failure()
|
.to_status_failure()
|
||||||
|
.set_title(title)
|
||||||
.set_description(Some(reason.message()));
|
.set_description(Some(reason.message()));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
mod redirect;
|
mod redirect;
|
||||||
use redirect::Redirect;
|
use redirect::Redirect;
|
||||||
|
|
||||||
use gtk::glib::{GString, Uri};
|
use gtk::glib::GString;
|
||||||
use std::{cell::RefCell, sync::Arc};
|
use std::{cell::RefCell, sync::Arc};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -27,6 +27,7 @@ pub struct Meta {
|
||||||
status: RefCell<Status>,
|
status: RefCell<Status>,
|
||||||
title: RefCell<GString>,
|
title: RefCell<GString>,
|
||||||
redirect: RefCell<Option<Redirect>>,
|
redirect: RefCell<Option<Redirect>>,
|
||||||
|
redirect_count: RefCell<i8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Meta {
|
impl Meta {
|
||||||
|
|
@ -37,6 +38,7 @@ impl Meta {
|
||||||
status: RefCell::new(status),
|
status: RefCell::new(status),
|
||||||
title: RefCell::new(title),
|
title: RefCell::new(title),
|
||||||
redirect: RefCell::new(None),
|
redirect: RefCell::new(None),
|
||||||
|
redirect_count: RefCell::new(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,16 +54,22 @@ impl Meta {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_redirect(&self, count: i8, is_follow: bool, target: Uri) -> &Self {
|
pub fn set_redirect(&self, request: GString, is_foreground: bool) -> &Self {
|
||||||
self.redirect
|
self.redirect
|
||||||
.replace(Some(Redirect::new(count, is_follow, target)));
|
.replace(Some(Redirect::new(request, is_foreground)));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_redirect_count(&self, redirect_count: i8) -> &Self {
|
||||||
|
self.redirect_count.replace(redirect_count);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @TODO not in use
|
||||||
pub fn unset_redirect(&self) -> &Self {
|
pub fn unset_redirect(&self) -> &Self {
|
||||||
self.redirect.replace(None);
|
self.redirect.replace(None);
|
||||||
self
|
self
|
||||||
}
|
} */
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
|
|
@ -73,28 +81,14 @@ impl Meta {
|
||||||
self.title.borrow().clone()
|
self.title.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_redirect(&self) -> bool {
|
pub fn redirect_count(&self) -> i8 {
|
||||||
self.redirect.borrow().is_some()
|
self.redirect_count.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redirect_count(&self) -> Option<i8> {
|
/// WARNING!
|
||||||
match *self.redirect.borrow() {
|
///
|
||||||
Some(ref redirect) => Some(redirect.count().clone()),
|
/// This function **take** the `Redirect` without clone semantics
|
||||||
None => None,
|
pub fn take_redirect(&self) -> Option<Redirect> {
|
||||||
}
|
self.redirect.take()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redirect_target(&self) -> Option<Uri> {
|
|
||||||
match *self.redirect.borrow() {
|
|
||||||
Some(ref redirect) => Some(redirect.target().clone()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redirect_is_follow(&self) -> Option<bool> {
|
|
||||||
match *self.redirect.borrow() {
|
|
||||||
Some(ref redirect) => Some(redirect.is_follow().clone()),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use gtk::glib::Uri;
|
use gtk::glib::GString;
|
||||||
|
|
||||||
/// # Redirection data holder
|
/// # Redirection data holder
|
||||||
///
|
///
|
||||||
|
|
@ -8,33 +8,32 @@ use gtk::glib::Uri;
|
||||||
///
|
///
|
||||||
/// ## Members
|
/// ## Members
|
||||||
///
|
///
|
||||||
/// * `count` - to limit redirect attempts
|
/// * `is_foreground` - indicates how to process this redirect
|
||||||
/// * `is_follow` - indicates how to process this redirect exactly
|
/// * `request` - destination
|
||||||
/// * `target` - destination address
|
/// * currently, it's raw `GString` not [Uri](https://docs.gtk.org/glib/struct.Uri.html)
|
||||||
|
/// because of compatibility with request field as it could contain any other, not parsable values
|
||||||
pub struct Redirect {
|
pub struct Redirect {
|
||||||
count: i8,
|
is_foreground: bool,
|
||||||
is_follow: bool,
|
request: GString,
|
||||||
target: Uri,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Redirect {
|
impl Redirect {
|
||||||
pub fn new(count: i8, is_follow: bool, target: Uri) -> Self {
|
// Constructors
|
||||||
|
|
||||||
|
pub fn new(request: GString, is_foreground: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
count,
|
is_foreground,
|
||||||
is_follow,
|
request,
|
||||||
target,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count(&self) -> &i8 {
|
// Getters
|
||||||
&self.count
|
|
||||||
|
pub fn request(&self) -> GString {
|
||||||
|
self.request.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_follow(&self) -> &bool {
|
pub fn is_foreground(&self) -> bool {
|
||||||
&self.is_follow
|
self.is_foreground
|
||||||
}
|
|
||||||
|
|
||||||
pub fn target(&self) -> &Uri {
|
|
||||||
&self.target
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue