From 9818b3a998b5b3cade20dddf24ee95c98de8ba15 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 2 Nov 2024 21:38:05 +0200 Subject: [PATCH] implement 30,31 status codes --- README.md | 6 +- src/app/browser/window/tab/item/page.rs | 166 +++++++++++------- src/app/browser/window/tab/item/page/meta.rs | 44 ++--- .../window/tab/item/page/meta/redirect.rs | 37 ++-- 4 files changed, 138 insertions(+), 115 deletions(-) diff --git a/README.md b/README.md index 519c3942..a6e1a949 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ GTK 4 / Libadwaita client written in Rust * [x] Input * [x] `10` Input * [x] `11` Sensitive input - * [ ] Redirection - * [ ] `30` Temporary (partial) - * [ ] `31` Permanent (partial) + * [x] Redirection + * [x] `30` Temporary + * [x] `31` Permanent * [ ] Temporary failure * [ ] `40` Unspecified condition * [ ] `41` Server unavailable diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 5f0722ab..81f722c2 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -155,20 +155,62 @@ impl Page { } } + // @TODO rename to `load` 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 self.input.unset(); - // Init shared objects to not spawn a lot - let request_text = self.navigation.request_text(); + // Create shared variant value 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 self.meta.set_status(Status::Reload).set_title(&"Loading.."); self.action_update.activate(Some(&id)); // Route by request - match Uri::parse(&request_text, UriFlags::NONE) { + match Uri::parse(&request, UriFlags::NONE) { Ok(uri) => { // Route by scheme match uri.scheme().as_str() { @@ -199,17 +241,17 @@ impl Page { // Try interpret URI manually if Regex::match_simple( r"^[^\/\s]+\.[\w]{2,}", - request_text.clone(), + request.clone(), RegexCompileFlags::DEFAULT, RegexMatchFlags::DEFAULT, ) { // 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 - match Uri::parse(&request_text, UriFlags::NONE) { + match Uri::parse(&request, UriFlags::NONE) { Ok(_) => { // Update - self.navigation.set_request_text(&request_text); + self.navigation.set_request_text(&request); // Reload page self.action_page_reload.activate(None); @@ -220,13 +262,13 @@ impl Page { } } else { // Plain text given, make search request to default provider - let request_text = gformat!( + let request = gformat!( "gemini://tlgs.one/search?{}", - Uri::escape_string(&request_text, None, false) + Uri::escape_string(&request, None, false) ); // Update - self.navigation.set_request_text(&request_text); + self.navigation.set_request_text(&request); // Reload page self.action_page_reload.activate(None); @@ -366,44 +408,8 @@ impl Page { let id = self.id.to_variant(); let input = self.input.clone(); let meta = self.meta.clone(); - let navigation = self.navigation.clone(); 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 let client = SocketClient::new(); @@ -681,8 +687,8 @@ impl Page { // Extract redirection URL from response data match response.data() { Some(unresolved_url) => { - // New URL from server MAY to be relative (according to the protocol), - // resolve to absolute URI using current request value as the base for parser + // New URL from server MAY to be relative (according to the protocol specification), + // resolve to absolute URI gobject using current request as the base for parser: // https://docs.gtk.org/glib/type_func.Uri.resolve_relative.html match Uri::resolve_relative( Some(&uri.to_string()), @@ -690,60 +696,84 @@ impl Page { UriFlags::NONE, ) { 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) { Ok(resolved_uri) => { - // Client MUST prevent external redirects + // Client MUST prevent external redirects (by protocol specification) if is_external_uri(&resolved_uri, &uri) { // Update meta meta.set_status(Status::Failure) .set_title(&"Oops"); - // Show placeholder with confirmation request to continue + // Show placeholder with manual confirmation to continue @TODO status page? content.to_text_gemini( &uri, - &gformat!( // @TODO status page? + &gformat!( "# Redirect issue\n\nExternal redirects not allowed by protocol\n\nContinue:\n\n=> {}", 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 + 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( - match meta.redirect_count() { - Some(count) => count + 1, - None => 0 - }, + // Skip query and fragment by protocol requirements + // @TODO review fragment specification + resolved_uri.to_string_partial( + UriHideFlags::FRAGMENT | UriHideFlags::QUERY + ), + // Set follow policy based on status code match response.status() { gemini::client::response::meta::Status::PermanentRedirect => true, _ => 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_title(&"Redirect"); // @TODO is really wanted here? + .set_status(Status::Redirect) // @TODO is this status really wanted? + .set_title(&"Redirect"); - // Reload page to apply redirect + // Reload page to apply redirection action_page_reload.activate(None); } }, Err(reason) => { - meta.set_status(Status::Failure); + let status = Status::Failure; + let title = &"Oops"; + + meta.set_status(status) + .set_title(title); + content .to_status_failure() + .set_title(title) .set_description(Some(reason.message())); } } } Err(reason) => { - meta.set_status(Status::Failure); + let status = Status::Failure; + let title = &"Oops"; + + meta.set_status(status) + .set_title(title); + content .to_status_failure() + .set_title(title) .set_description(Some(reason.message())); }, } diff --git a/src/app/browser/window/tab/item/page/meta.rs b/src/app/browser/window/tab/item/page/meta.rs index 10d5e694..d00e34ac 100644 --- a/src/app/browser/window/tab/item/page/meta.rs +++ b/src/app/browser/window/tab/item/page/meta.rs @@ -1,7 +1,7 @@ mod redirect; use redirect::Redirect; -use gtk::glib::{GString, Uri}; +use gtk::glib::GString; use std::{cell::RefCell, sync::Arc}; #[derive(Debug, Clone)] @@ -27,6 +27,7 @@ pub struct Meta { status: RefCell, title: RefCell, redirect: RefCell>, + redirect_count: RefCell, } impl Meta { @@ -37,6 +38,7 @@ impl Meta { status: RefCell::new(status), title: RefCell::new(title), redirect: RefCell::new(None), + redirect_count: RefCell::new(0), }) } @@ -52,16 +54,22 @@ impl Meta { 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 - .replace(Some(Redirect::new(count, is_follow, target))); + .replace(Some(Redirect::new(request, is_foreground))); 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 { self.redirect.replace(None); self - } + } */ // Getters @@ -73,28 +81,14 @@ impl Meta { self.title.borrow().clone() } - pub fn is_redirect(&self) -> bool { - self.redirect.borrow().is_some() + pub fn redirect_count(&self) -> i8 { + self.redirect_count.borrow().clone() } - pub fn redirect_count(&self) -> Option { - match *self.redirect.borrow() { - Some(ref redirect) => Some(redirect.count().clone()), - None => None, - } - } - - pub fn redirect_target(&self) -> Option { - match *self.redirect.borrow() { - Some(ref redirect) => Some(redirect.target().clone()), - None => None, - } - } - - pub fn redirect_is_follow(&self) -> Option { - match *self.redirect.borrow() { - Some(ref redirect) => Some(redirect.is_follow().clone()), - None => None, - } + /// WARNING! + /// + /// This function **take** the `Redirect` without clone semantics + pub fn take_redirect(&self) -> Option { + self.redirect.take() } } diff --git a/src/app/browser/window/tab/item/page/meta/redirect.rs b/src/app/browser/window/tab/item/page/meta/redirect.rs index 7e8afa9a..2bd7b9c8 100644 --- a/src/app/browser/window/tab/item/page/meta/redirect.rs +++ b/src/app/browser/window/tab/item/page/meta/redirect.rs @@ -1,4 +1,4 @@ -use gtk::glib::Uri; +use gtk::glib::GString; /// # Redirection data holder /// @@ -8,33 +8,32 @@ use gtk::glib::Uri; /// /// ## Members /// -/// * `count` - to limit redirect attempts -/// * `is_follow` - indicates how to process this redirect exactly -/// * `target` - destination address +/// * `is_foreground` - indicates how to process this redirect +/// * `request` - destination +/// * 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 { - count: i8, - is_follow: bool, - target: Uri, + is_foreground: bool, + request: GString, } impl Redirect { - pub fn new(count: i8, is_follow: bool, target: Uri) -> Self { + // Constructors + + pub fn new(request: GString, is_foreground: bool) -> Self { Self { - count, - is_follow, - target, + is_foreground, + request, } } - pub fn count(&self) -> &i8 { - &self.count + // Getters + + pub fn request(&self) -> GString { + self.request.clone() } - pub fn is_follow(&self) -> &bool { - &self.is_follow - } - - pub fn target(&self) -> &Uri { - &self.target + pub fn is_foreground(&self) -> bool { + self.is_foreground } }