Message ID | 20220508120952.52-5-hofmann@leo-andres.de |
---|---|
State | Accepted |
Commit | 85d570843ef7b4b1a428dadf93e5a2a8410348ca |
Headers |
Return-Path: <development-bounces@lists.ipfire.org> Received: from mail01.ipfire.org (mail01.haj.ipfire.org [172.28.1.202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail01.haj.ipfire.org", Issuer "R3" (verified OK)) by web04.haj.ipfire.org (Postfix) with ESMTPS id 4Kx3673cn3z3x1v for <patchwork@web04.haj.ipfire.org>; Sun, 8 May 2022 12:10:23 +0000 (UTC) Received: from mail02.haj.ipfire.org (mail02.haj.ipfire.org [172.28.1.201]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail02.haj.ipfire.org", Issuer "R3" (verified OK)) by mail01.ipfire.org (Postfix) with ESMTPS id 4Kx3601C3Xznj; Sun, 8 May 2022 12:10:16 +0000 (UTC) Received: from mail02.haj.ipfire.org (localhost [127.0.0.1]) by mail02.haj.ipfire.org (Postfix) with ESMTP id 4Kx3600pD7z2yRl; Sun, 8 May 2022 12:10:16 +0000 (UTC) Received: from mail01.ipfire.org (mail01.haj.ipfire.org [172.28.1.202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail01.haj.ipfire.org", Issuer "R3" (verified OK)) by mail02.haj.ipfire.org (Postfix) with ESMTPS id 4Kx35x3Hq6z2xfn for <development@lists.ipfire.org>; Sun, 8 May 2022 12:10:13 +0000 (UTC) Received: from arche.uberspace.de (arche.uberspace.de [185.26.156.147]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mail01.ipfire.org (Postfix) with ESMTPS id 4Kx35q0BnSz5X5 for <development@lists.ipfire.org>; Sun, 8 May 2022 12:10:06 +0000 (UTC) Received: (qmail 30343 invoked by uid 990); 8 May 2022 12:10:04 -0000 From: Leo-Andres Hofmann <hofmann@leo-andres.de> To: development@lists.ipfire.org Subject: [PATCH 5/7] pakfire.cgi: Implement Post/Redirect/Get pattern Date: Sun, 8 May 2022 14:09:50 +0200 Message-Id: <20220508120952.52-5-hofmann@leo-andres.de> X-Mailer: git-send-email 2.27.0.windows.1 In-Reply-To: <20220508120952.52-1-hofmann@leo-andres.de> References: <20220508120952.52-1-hofmann@leo-andres.de> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Rspamd-Bar: - X-Rspamd-Report: R_MISSING_CHARSET(0.5) MIME_GOOD(-0.1) MID_CONTAINS_FROM(1) BAYES_HAM(-3) X-Rspamd-Score: -1.6 Received: from unknown (HELO unkown) (::1) by arche.uberspace.de (Haraka/2.8.28) with ESMTPSA; Sun, 08 May 2022 14:10:04 +0200 ARC-Seal: i=1; s=202003rsa; d=lists.ipfire.org; t=1652011807; a=rsa-sha256; cv=none; b=VY/QLraBiAdfjjiwGo/yshQStW2N6YnAjYj9ZWtymwxwk7kAmN2QemfG1Ak0TwqKfmakmH JatIO9OadQLtQXxElJybOExdpvdZcmPUNgMWI0h/DoGVkC4WSvUE8SCUhL33X0RzobQqzP Am7BP4DIJxBgT1tn9Oix7UvguBxe112tXt1SNuqKtVonpdLHDqteHje9dM9aEzVoh+E+vb es3bHDfR8FoNXkckrUKMcxV1llwoI3oov5vewW/08gvHRpmZ8Ogn2bEopQvU7shSQfTq3I frH+BDag5WLneFOmYL3jAbMGlOr06VmAgUELCfkerJuik4pMp8bprlTzZmUHAw== ARC-Authentication-Results: i=1; mail01.ipfire.org; dkim=none; spf=pass (mail01.ipfire.org: domain of hofmann@leo-andres.de designates 185.26.156.147 as permitted sender) smtp.mailfrom=hofmann@leo-andres.de; dmarc=pass (policy=quarantine) header.from=leo-andres.de ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=lists.ipfire.org; s=202003rsa; t=1652011807; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=zmtTYADReqyqVq6GAPupLiY3GVEAH0dITM5Cmw1KTyw=; b=vaRkO0dF30cuFRxXC9i/wu2FdNr8uKkX4RsyAxY35cQaTPSXlRK6mFptIVBFcoS8bWQDMA l/9zF//kemYUPyUErrGco6LAh14xQ6inoG5LRmWWy05yGqmCbdPPRNrcBJDck0y+D5Ohv0 2PEDKSepm7wnC78aN4UMDY5cRhYrM+RwkLHD1Ya8S/4pMsd1X8zP5GCYgoyQvDjjI1p/eR SbnQ0yDXfutix5XMONaduroMwyeghgBvi/ReL9hdhQtQbyeLv3F+MneYCBcBa5S5mezMvT Ghc/aLI3KXaSOlYchNpADhcTESW2urLZq1PsH5EVc0yVZDLFY6vlz4Zs0fCxRA== X-Rspamd-Queue-Id: 4Kx35q0BnSz5X5 X-Rspamd-Server: mail01.haj.ipfire.org X-Spamd-Result: default: False [-0.58 / 11.00]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM(-1.00)[-1.000]; SPF_REPUTATION_HAM(-0.77)[-0.77342682734463]; R_MISSING_CHARSET(0.50)[]; MX_INVALID(0.50)[]; DMARC_POLICY_ALLOW(-0.50)[leo-andres.de,quarantine]; R_SPF_ALLOW(-0.20)[+mx:c]; MIME_GOOD(-0.10)[text/plain]; IP_REPUTATION_HAM(-0.01)[asn: 205766(0.00), country: DE(-0.01), ip: 185.26.156.147(0.00)]; FROM_EQ_ENVFROM(0.00)[]; ARC_NA(0.00)[]; MIME_TRACE(0.00)[0:+]; R_DKIM_NA(0.00)[]; ASN(0.00)[asn:205766, ipnet:185.26.156.0/24, country:DE]; RCVD_IN_DNSWL_NONE(0.00)[185.26.156.147:from]; TO_MATCH_ENVRCPT_ALL(0.00)[]; RCPT_COUNT_ONE(0.00)[1]; RCVD_VIA_SMTP_AUTH(0.00)[]; RCVD_COUNT_TWO(0.00)[2]; FROM_HAS_DN(0.00)[]; ARC_SIGNED(0.00)[lists.ipfire.org:s=202003rsa:i=1]; TO_DN_NONE(0.00)[]; RCVD_TLS_ALL(0.00)[] Authentication-Results: mail01.ipfire.org; dkim=none; spf=pass (mail01.ipfire.org: domain of hofmann@leo-andres.de designates 185.26.156.147 as permitted sender) smtp.mailfrom=hofmann@leo-andres.de; dmarc=pass (policy=quarantine) header.from=leo-andres.de X-BeenThere: development@lists.ipfire.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: IPFire development talk <development.lists.ipfire.org> List-Unsubscribe: <https://lists.ipfire.org/mailman/options/development>, <mailto:development-request@lists.ipfire.org?subject=unsubscribe> List-Archive: <http://lists.ipfire.org/pipermail/development/> List-Post: <mailto:development@lists.ipfire.org> List-Help: <mailto:development-request@lists.ipfire.org?subject=help> List-Subscribe: <https://lists.ipfire.org/mailman/listinfo/development>, <mailto:development-request@lists.ipfire.org?subject=subscribe> Errors-To: development-bounces@lists.ipfire.org Sender: "Development" <development-bounces@lists.ipfire.org> |
Series |
[1/7] pakfire.cgi: Separate command processing and HTML generation
|
|
Commit Message
Leo-Andres Hofmann
May 8, 2022, 12:09 p.m. UTC
Refreshing the Pakfire page may cause a command to be
executed multiple times and induce odd errors.
This patch implements a HTTP 303 redirect after form processing,
which causes the browser to discard the POST form data.
Navigating backward or reloading the page now does not trigger
multiple executions anymore.
Fixes: #12781
Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
---
html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++-----
1 file changed, 50 insertions(+), 6 deletions(-)
Comments
Acked-by: Peter Müller <peter.muelle@ipfire.org> > Refreshing the Pakfire page may cause a command to be > executed multiple times and induce odd errors. > > This patch implements a HTTP 303 redirect after form processing, > which causes the browser to discard the POST form data. > Navigating backward or reloading the page now does not trigger > multiple executions anymore. > > Fixes: #12781 > > Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de> > --- > html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++----- > 1 file changed, 50 insertions(+), 6 deletions(-) > > diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi > index ec3ee2cc6..6fade81bd 100644 > --- a/html/cgi-bin/pakfire.cgi > +++ b/html/cgi-bin/pakfire.cgi > @@ -21,6 +21,7 @@ > > use strict; > use List::Util qw(any); > +use URI; > > # enable only the following on debugging purpose > #use warnings; > @@ -37,12 +38,17 @@ my %color = (); > my %pakfiresettings = (); > my %mainsettings = (); > > +# The page mode is used to explictly switch between user interface functions: > +my $PM_DEFAULT = 'default'; # Default user interface with command processing > +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) > +my $pagemode = $PM_DEFAULT; > + > # Load general settings > &General::readhash("${General::swroot}/main/settings", \%mainsettings); > &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); > &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); > > -# Get CGI request data > +# Get CGI POST request data > $cgiparams{'ACTION'} = ''; > $cgiparams{'FORCE'} = ''; > > @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; > > &Header::getcgihash(\%cgiparams); > > +# Get CGI GET request data (if available) > +if($ENV{'QUERY_STRING'}) { > + my $uri = URI->new($ENV{'REQUEST_URI'}); > + my %query = $uri->query_form; > + > + my $mode = lc($query{'mode'} // ''); > + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { > + $pagemode = $mode; # Limit to existing modes > + } > +} > + > ### Process AJAX/JSON request ### > if($cgiparams{'ACTION'} eq 'json-getstatus') { > # Send HTTP headers > @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { > } > > ### Process Pakfire install/update commands ### > -if($cgiparams{'ACTION'} ne '') { > +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { > if(&_is_pakfire_busy()) { > $errormessage = $Lang::tr{'pakfire already busy'}; > + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode > } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { > my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); > &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { > my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); > &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq 'update') { > &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq 'upgrade') { > &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { > $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; > > @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { > > # Update lists > &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } > } > } > @@ -221,8 +244,8 @@ if ($errormessage) { > &Header::closebox(); > } > > -# Show log output while Pakfire is running > -if(&_is_pakfire_busy()) { > +# Show only log output while Pakfire is running and stop afterwards > +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { > &Header::openbox("100%", "center", "Pakfire"); > > print <<END > @@ -253,7 +276,8 @@ END > } > > # Show Pakfire install/remove dependencies and confirm form > -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { > +# (_is_pakfire_busy status was checked before and can be omitted) > +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { > &Header::openbox("100%", "center", $Lang::tr{'request'}); > > my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); > @@ -291,7 +315,7 @@ END > &Header::closepage(); > exit; > > -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { > +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { > &Header::openbox("100%", "center", $Lang::tr{'request'}); > > my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); > @@ -476,3 +500,23 @@ sub _start_json_output { > print "Content-Type: application/json\n"; > print "\n"; # End of HTTP headers > } > + > +# Send HTTP 303 redirect headers to change page mode > +# GET is always used to display the redirected page, which will remove already processed POST form data. > +# Note: Custom headers must be sent before the HTML output is started by &Header::showhttpheaders(). > +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated immediately. > +sub _http_pagemode_redirect { > + my ($mode, $switch_mode) = @_; > + $mode //= $PM_DEFAULT; > + $switch_mode //= 0; > + > + # Send HTTP redirect with GET parameter > + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; > + print "Status: 303 See Other\n"; > + print "Location: $location\n"; > + > + # Change global page mode > + if($switch_mode) { > + $pagemode = $mode; > + } > +}
Hello, > On 8 May 2022, at 13:09, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote: > > Refreshing the Pakfire page may cause a command to be > executed multiple times and induce odd errors. > > This patch implements a HTTP 303 redirect after form processing, > which causes the browser to discard the POST form data. > Navigating backward or reloading the page now does not trigger > multiple executions anymore. > > Fixes: #12781 > > Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de> > --- > html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++----- > 1 file changed, 50 insertions(+), 6 deletions(-) > > diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi > index ec3ee2cc6..6fade81bd 100644 > --- a/html/cgi-bin/pakfire.cgi > +++ b/html/cgi-bin/pakfire.cgi > @@ -21,6 +21,7 @@ > > use strict; > use List::Util qw(any); > +use URI; > > # enable only the following on debugging purpose > #use warnings; > @@ -37,12 +38,17 @@ my %color = (); > my %pakfiresettings = (); > my %mainsettings = (); > > +# The page mode is used to explictly switch between user interface functions: > +my $PM_DEFAULT = 'default'; # Default user interface with command processing > +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) > +my $pagemode = $PM_DEFAULT; > + > # Load general settings > &General::readhash("${General::swroot}/main/settings", \%mainsettings); > &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); > &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); > > -# Get CGI request data > +# Get CGI POST request data > $cgiparams{'ACTION'} = ''; > $cgiparams{'FORCE'} = ''; > > @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; > > &Header::getcgihash(\%cgiparams); > > +# Get CGI GET request data (if available) > +if($ENV{'QUERY_STRING'}) { > + my $uri = URI->new($ENV{'REQUEST_URI'}); > + my %query = $uri->query_form; > + > + my $mode = lc($query{'mode'} // ''); > + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { > + $pagemode = $mode; # Limit to existing modes > + } > +} > + > ### Process AJAX/JSON request ### > if($cgiparams{'ACTION'} eq 'json-getstatus') { > # Send HTTP headers > @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { > } > > ### Process Pakfire install/update commands ### > -if($cgiparams{'ACTION'} ne '') { > +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { > if(&_is_pakfire_busy()) { > $errormessage = $Lang::tr{'pakfire already busy'}; > + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode > } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { > my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); > &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { > my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); > &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq 'update') { > &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq 'upgrade') { > &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { > $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; > > @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { > > # Update lists > &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); > + &_http_pagemode_redirect($PM_LOGREAD, 1); > } > } > } > @@ -221,8 +244,8 @@ if ($errormessage) { > &Header::closebox(); > } > > -# Show log output while Pakfire is running > -if(&_is_pakfire_busy()) { > +# Show only log output while Pakfire is running and stop afterwards > +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { > &Header::openbox("100%", "center", "Pakfire"); > > print <<END > @@ -253,7 +276,8 @@ END > } > > # Show Pakfire install/remove dependencies and confirm form > -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { > +# (_is_pakfire_busy status was checked before and can be omitted) > +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { > &Header::openbox("100%", "center", $Lang::tr{'request'}); > > my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); > @@ -291,7 +315,7 @@ END > &Header::closepage(); > exit; > > -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { > +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { > &Header::openbox("100%", "center", $Lang::tr{'request'}); > > my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); > @@ -476,3 +500,23 @@ sub _start_json_output { > print "Content-Type: application/json\n"; > print "\n"; # End of HTTP headers > } > + > +# Send HTTP 303 redirect headers to change page mode > +# GET is always used to display the redirected page, which will remove already processed POST form data. > +# Note: Custom headers must be sent before the HTML output is started by &Header::showhttpheaders(). > +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated immediately. > +sub _http_pagemode_redirect { > + my ($mode, $switch_mode) = @_; > + $mode //= $PM_DEFAULT; > + $switch_mode //= 0; > + > + # Send HTTP redirect with GET parameter > + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; > + print "Status: 303 See Other\n"; > + print "Location: $location\n"; I believe that technically you would want another newline at the end of the header. Would you also not want to call “exit(0)” here to finish processing the script? What else is there to do after you have redirected the user? -Michael > + > + # Change global page mode > + if($switch_mode) { > + $pagemode = $mode; > + } > +} > -- > 2.27.0.windows.1 >
Hi, Please excuse my delayed reply, I wanted to test core 168 first. 12. Mai 2022 11:30, "Michael Tremer" <michael.tremer@ipfire.org> schrieb: > Hello, > >> On 8 May 2022, at 13:09, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote: >> >> Refreshing the Pakfire page may cause a command to be >> executed multiple times and induce odd errors. >> >> This patch implements a HTTP 303 redirect after form processing, >> which causes the browser to discard the POST form data. >> Navigating backward or reloading the page now does not trigger >> multiple executions anymore. >> >> Fixes: #12781 >> >> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de> >> --- >> html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++----- >> 1 file changed, 50 insertions(+), 6 deletions(-) >> >> diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi >> index ec3ee2cc6..6fade81bd 100644 >> --- a/html/cgi-bin/pakfire.cgi >> +++ b/html/cgi-bin/pakfire.cgi >> @@ -21,6 +21,7 @@ >> >> use strict; >> use List::Util qw(any); >> +use URI; >> >> # enable only the following on debugging purpose >> #use warnings; >> @@ -37,12 +38,17 @@ my %color = (); >> my %pakfiresettings = (); >> my %mainsettings = (); >> >> +# The page mode is used to explictly switch between user interface functions: >> +my $PM_DEFAULT = 'default'; # Default user interface with command processing >> +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) >> +my $pagemode = $PM_DEFAULT; >> + >> # Load general settings >> &General::readhash("${General::swroot}/main/settings", \%mainsettings); >> &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); >> &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); >> >> -# Get CGI request data >> +# Get CGI POST request data >> $cgiparams{'ACTION'} = ''; >> $cgiparams{'FORCE'} = ''; >> >> @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; >> >> &Header::getcgihash(\%cgiparams); >> >> +# Get CGI GET request data (if available) >> +if($ENV{'QUERY_STRING'}) { >> + my $uri = URI->new($ENV{'REQUEST_URI'}); >> + my %query = $uri->query_form; >> + >> + my $mode = lc($query{'mode'} // ''); >> + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { >> + $pagemode = $mode; # Limit to existing modes >> + } >> +} >> + >> ### Process AJAX/JSON request ### >> if($cgiparams{'ACTION'} eq 'json-getstatus') { >> # Send HTTP headers >> @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { >> } >> >> ### Process Pakfire install/update commands ### >> -if($cgiparams{'ACTION'} ne '') { >> +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { >> if(&_is_pakfire_busy()) { >> $errormessage = $Lang::tr{'pakfire already busy'}; >> + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode >> } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { >> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >> &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", >> "--no-colors", @pkgs); >> + &_http_pagemode_redirect($PM_LOGREAD, 1); >> } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { >> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >> &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", >> @pkgs); >> + &_http_pagemode_redirect($PM_LOGREAD, 1); >> } elsif($cgiparams{'ACTION'} eq 'update') { >> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >> + &_http_pagemode_redirect($PM_LOGREAD, 1); >> } elsif($cgiparams{'ACTION'} eq 'upgrade') { >> &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); >> + &_http_pagemode_redirect($PM_LOGREAD, 1); >> } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { >> $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; >> >> @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { >> >> # Update lists >> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >> + &_http_pagemode_redirect($PM_LOGREAD, 1); >> } >> } >> } >> @@ -221,8 +244,8 @@ if ($errormessage) { >> &Header::closebox(); >> } >> >> -# Show log output while Pakfire is running >> -if(&_is_pakfire_busy()) { >> +# Show only log output while Pakfire is running and stop afterwards >> +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { >> &Header::openbox("100%", "center", "Pakfire"); >> >> print <<END >> @@ -253,7 +276,8 @@ END >> } >> >> # Show Pakfire install/remove dependencies and confirm form >> -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { >> +# (_is_pakfire_busy status was checked before and can be omitted) >> +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { >> &Header::openbox("100%", "center", $Lang::tr{'request'}); >> >> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >> @@ -291,7 +315,7 @@ END >> &Header::closepage(); >> exit; >> >> -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { >> +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { >> &Header::openbox("100%", "center", $Lang::tr{'request'}); >> >> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >> @@ -476,3 +500,23 @@ sub _start_json_output { >> print "Content-Type: application/json\n"; >> print "\n"; # End of HTTP headers >> } >> + >> +# Send HTTP 303 redirect headers to change page mode >> +# GET is always used to display the redirected page, which will remove already processed POST form >> data. >> +# Note: Custom headers must be sent before the HTML output is started by >> &Header::showhttpheaders(). >> +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated >> immediately. >> +sub _http_pagemode_redirect { >> + my ($mode, $switch_mode) = @_; >> + $mode //= $PM_DEFAULT; >> + $switch_mode //= 0; >> + >> + # Send HTTP redirect with GET parameter >> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; >> + print "Status: 303 See Other\n"; >> + print "Location: $location\n"; > > I believe that technically you would want another newline at the end of the header. Yes the second newline would terminate the header. I want Header::showhttpheaders() to be able to print it's headers later on, so I don't close the header yet. > Would you also not want to call “exit(0)” here to finish processing the script? What else is there > to do after you have redirected the user? I found that sometimes Perl did not close the connection for a long time, probably until the forked Pakfire process terminated. If this happened, the browser waited and did not immediately follow the redirect. However, it was able to start rendering the page received so far. That's why I decided to send a redirect header and additionally generate the log viewer before exiting. This way the user hopefully never gets to see a blank page. With core 168 installed on my test system I noticed that it happens much less often now. Personally I found this very difficult to reproduce and would like to leave my solution it as it is. Best regards Leo > -Michael > >> + >> + # Change global page mode >> + if($switch_mode) { >> + $pagemode = $mode; >> + } >> +} >> -- >> 2.27.0.windows.1
Hello, > On 20 May 2022, at 10:47, hofmann@leo-andres.de wrote: > > Hi, > Please excuse my delayed reply, I wanted to test core 168 first. > > 12. Mai 2022 11:30, "Michael Tremer" <michael.tremer@ipfire.org> schrieb: > >> Hello, >> >>> On 8 May 2022, at 13:09, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote: >>> >>> Refreshing the Pakfire page may cause a command to be >>> executed multiple times and induce odd errors. >>> >>> This patch implements a HTTP 303 redirect after form processing, >>> which causes the browser to discard the POST form data. >>> Navigating backward or reloading the page now does not trigger >>> multiple executions anymore. >>> >>> Fixes: #12781 >>> >>> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de> >>> --- >>> html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++----- >>> 1 file changed, 50 insertions(+), 6 deletions(-) >>> >>> diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi >>> index ec3ee2cc6..6fade81bd 100644 >>> --- a/html/cgi-bin/pakfire.cgi >>> +++ b/html/cgi-bin/pakfire.cgi >>> @@ -21,6 +21,7 @@ >>> >>> use strict; >>> use List::Util qw(any); >>> +use URI; >>> >>> # enable only the following on debugging purpose >>> #use warnings; >>> @@ -37,12 +38,17 @@ my %color = (); >>> my %pakfiresettings = (); >>> my %mainsettings = (); >>> >>> +# The page mode is used to explictly switch between user interface functions: >>> +my $PM_DEFAULT = 'default'; # Default user interface with command processing >>> +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) >>> +my $pagemode = $PM_DEFAULT; >>> + >>> # Load general settings >>> &General::readhash("${General::swroot}/main/settings", \%mainsettings); >>> &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); >>> &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); >>> >>> -# Get CGI request data >>> +# Get CGI POST request data >>> $cgiparams{'ACTION'} = ''; >>> $cgiparams{'FORCE'} = ''; >>> >>> @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; >>> >>> &Header::getcgihash(\%cgiparams); >>> >>> +# Get CGI GET request data (if available) >>> +if($ENV{'QUERY_STRING'}) { >>> + my $uri = URI->new($ENV{'REQUEST_URI'}); >>> + my %query = $uri->query_form; >>> + >>> + my $mode = lc($query{'mode'} // ''); >>> + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { >>> + $pagemode = $mode; # Limit to existing modes >>> + } >>> +} >>> + >>> ### Process AJAX/JSON request ### >>> if($cgiparams{'ACTION'} eq 'json-getstatus') { >>> # Send HTTP headers >>> @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { >>> } >>> >>> ### Process Pakfire install/update commands ### >>> -if($cgiparams{'ACTION'} ne '') { >>> +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { >>> if(&_is_pakfire_busy()) { >>> $errormessage = $Lang::tr{'pakfire already busy'}; >>> + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode >>> } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { >>> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >>> &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", >>> "--no-colors", @pkgs); >>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>> } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { >>> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >>> &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", >>> @pkgs); >>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>> } elsif($cgiparams{'ACTION'} eq 'update') { >>> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>> } elsif($cgiparams{'ACTION'} eq 'upgrade') { >>> &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); >>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>> } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { >>> $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; >>> >>> @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { >>> >>> # Update lists >>> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>> } >>> } >>> } >>> @@ -221,8 +244,8 @@ if ($errormessage) { >>> &Header::closebox(); >>> } >>> >>> -# Show log output while Pakfire is running >>> -if(&_is_pakfire_busy()) { >>> +# Show only log output while Pakfire is running and stop afterwards >>> +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { >>> &Header::openbox("100%", "center", "Pakfire"); >>> >>> print <<END >>> @@ -253,7 +276,8 @@ END >>> } >>> >>> # Show Pakfire install/remove dependencies and confirm form >>> -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { >>> +# (_is_pakfire_busy status was checked before and can be omitted) >>> +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { >>> &Header::openbox("100%", "center", $Lang::tr{'request'}); >>> >>> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >>> @@ -291,7 +315,7 @@ END >>> &Header::closepage(); >>> exit; >>> >>> -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { >>> +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { >>> &Header::openbox("100%", "center", $Lang::tr{'request'}); >>> >>> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >>> @@ -476,3 +500,23 @@ sub _start_json_output { >>> print "Content-Type: application/json\n"; >>> print "\n"; # End of HTTP headers >>> } >>> + >>> +# Send HTTP 303 redirect headers to change page mode >>> +# GET is always used to display the redirected page, which will remove already processed POST form >>> data. >>> +# Note: Custom headers must be sent before the HTML output is started by >>> &Header::showhttpheaders(). >>> +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated >>> immediately. >>> +sub _http_pagemode_redirect { >>> + my ($mode, $switch_mode) = @_; >>> + $mode //= $PM_DEFAULT; >>> + $switch_mode //= 0; >>> + >>> + # Send HTTP redirect with GET parameter >>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; >>> + print "Status: 303 See Other\n"; >>> + print "Location: $location\n"; >> >> I believe that technically you would want another newline at the end of the header. > > Yes the second newline would terminate the header. I want Header::showhttpheaders() to be able to > print it's headers later on, so I don't close the header yet. Oh, you intend to send a page? Why? That would never be shown when browsers follow the redirect. > >> Would you also not want to call “exit(0)” here to finish processing the script? What else is there >> to do after you have redirected the user? > > I found that sometimes Perl did not close the connection for a long time, probably until the forked > Pakfire process terminated. When the main CGI script terminates, the connection should be closed (unless you are using HTTP/1.1 with keep-alive). > If this happened, the browser waited and did not immediately follow the redirect. However, it was able to start rendering the page received so far. Yes, you are sending some content there. I don’t quite unterstand why. > That's why I decided to send a redirect header and additionally generate the log viewer before > exiting. This way the user hopefully never gets to see a blank page. I am not sure what the problem is, but I thought the exit(0) would fix it :) > With core 168 installed on my test system I noticed that it happens much less often now. > Personally I found this very difficult to reproduce and would like to leave my solution it as it is. > > Best regards > Leo > >> -Michael >> >>> + >>> + # Change global page mode >>> + if($switch_mode) { >>> + $pagemode = $mode; >>> + } >>> +} >>> -- >>> 2.27.0.windows.1
Hi Michael, Am 31.05.2022 um 14:10 schrieb Michael Tremer: > Hello, > >> On 20 May 2022, at 10:47, hofmann@leo-andres.de wrote: >> >> Hi, >> Please excuse my delayed reply, I wanted to test core 168 first. >> >> 12. Mai 2022 11:30, "Michael Tremer" <michael.tremer@ipfire.org> schrieb: >> >>> Hello, >>> >>>> On 8 May 2022, at 13:09, Leo-Andres Hofmann <hofmann@leo-andres.de> wrote: >>>> >>>> Refreshing the Pakfire page may cause a command to be >>>> executed multiple times and induce odd errors. >>>> >>>> This patch implements a HTTP 303 redirect after form processing, >>>> which causes the browser to discard the POST form data. >>>> Navigating backward or reloading the page now does not trigger >>>> multiple executions anymore. >>>> >>>> Fixes: #12781 >>>> >>>> Signed-off-by: Leo-Andres Hofmann <hofmann@leo-andres.de> >>>> --- >>>> html/cgi-bin/pakfire.cgi | 56 +++++++++++++++++++++++++++++++++++----- >>>> 1 file changed, 50 insertions(+), 6 deletions(-) >>>> >>>> diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi >>>> index ec3ee2cc6..6fade81bd 100644 >>>> --- a/html/cgi-bin/pakfire.cgi >>>> +++ b/html/cgi-bin/pakfire.cgi >>>> @@ -21,6 +21,7 @@ >>>> >>>> use strict; >>>> use List::Util qw(any); >>>> +use URI; >>>> >>>> # enable only the following on debugging purpose >>>> #use warnings; >>>> @@ -37,12 +38,17 @@ my %color = (); >>>> my %pakfiresettings = (); >>>> my %mainsettings = (); >>>> >>>> +# The page mode is used to explictly switch between user interface functions: >>>> +my $PM_DEFAULT = 'default'; # Default user interface with command processing >>>> +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) >>>> +my $pagemode = $PM_DEFAULT; >>>> + >>>> # Load general settings >>>> &General::readhash("${General::swroot}/main/settings", \%mainsettings); >>>> &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); >>>> &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); >>>> >>>> -# Get CGI request data >>>> +# Get CGI POST request data >>>> $cgiparams{'ACTION'} = ''; >>>> $cgiparams{'FORCE'} = ''; >>>> >>>> @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; >>>> >>>> &Header::getcgihash(\%cgiparams); >>>> >>>> +# Get CGI GET request data (if available) >>>> +if($ENV{'QUERY_STRING'}) { >>>> + my $uri = URI->new($ENV{'REQUEST_URI'}); >>>> + my %query = $uri->query_form; >>>> + >>>> + my $mode = lc($query{'mode'} // ''); >>>> + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { >>>> + $pagemode = $mode; # Limit to existing modes >>>> + } >>>> +} >>>> + >>>> ### Process AJAX/JSON request ### >>>> if($cgiparams{'ACTION'} eq 'json-getstatus') { >>>> # Send HTTP headers >>>> @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { >>>> } >>>> >>>> ### Process Pakfire install/update commands ### >>>> -if($cgiparams{'ACTION'} ne '') { >>>> +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { >>>> if(&_is_pakfire_busy()) { >>>> $errormessage = $Lang::tr{'pakfire already busy'}; >>>> + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode >>>> } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { >>>> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >>>> &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", >>>> "--no-colors", @pkgs); >>>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>>> } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { >>>> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >>>> &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", >>>> @pkgs); >>>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>>> } elsif($cgiparams{'ACTION'} eq 'update') { >>>> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >>>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>>> } elsif($cgiparams{'ACTION'} eq 'upgrade') { >>>> &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); >>>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>>> } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { >>>> $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; >>>> >>>> @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { >>>> >>>> # Update lists >>>> &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); >>>> + &_http_pagemode_redirect($PM_LOGREAD, 1); >>>> } >>>> } >>>> } >>>> @@ -221,8 +244,8 @@ if ($errormessage) { >>>> &Header::closebox(); >>>> } >>>> >>>> -# Show log output while Pakfire is running >>>> -if(&_is_pakfire_busy()) { >>>> +# Show only log output while Pakfire is running and stop afterwards >>>> +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { >>>> &Header::openbox("100%", "center", "Pakfire"); >>>> >>>> print <<END >>>> @@ -253,7 +276,8 @@ END >>>> } >>>> >>>> # Show Pakfire install/remove dependencies and confirm form >>>> -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { >>>> +# (_is_pakfire_busy status was checked before and can be omitted) >>>> +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { >>>> &Header::openbox("100%", "center", $Lang::tr{'request'}); >>>> >>>> my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); >>>> @@ -291,7 +315,7 @@ END >>>> &Header::closepage(); >>>> exit; >>>> >>>> -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { >>>> +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { >>>> &Header::openbox("100%", "center", $Lang::tr{'request'}); >>>> >>>> my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); >>>> @@ -476,3 +500,23 @@ sub _start_json_output { >>>> print "Content-Type: application/json\n"; >>>> print "\n"; # End of HTTP headers >>>> } >>>> + >>>> +# Send HTTP 303 redirect headers to change page mode >>>> +# GET is always used to display the redirected page, which will remove already processed POST form >>>> data. >>>> +# Note: Custom headers must be sent before the HTML output is started by >>>> &Header::showhttpheaders(). >>>> +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated >>>> immediately. >>>> +sub _http_pagemode_redirect { >>>> + my ($mode, $switch_mode) = @_; >>>> + $mode //= $PM_DEFAULT; >>>> + $switch_mode //= 0; >>>> + >>>> + # Send HTTP redirect with GET parameter >>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; >>>> + print "Status: 303 See Other\n"; >>>> + print "Location: $location\n"; >>> I believe that technically you would want another newline at the end of the header. >> Yes the second newline would terminate the header. I want Header::showhttpheaders() to be able to >> print it's headers later on, so I don't close the header yet. > Oh, you intend to send a page? > > Why? That would never be shown when browsers follow the redirect. I'll answer below, I think this is all related. > >>> Would you also not want to call “exit(0)” here to finish processing the script? What else is there >>> to do after you have redirected the user? >> I found that sometimes Perl did not close the connection for a long time, probably until the forked >> Pakfire process terminated. > When the main CGI script terminates, the connection should be closed (unless you are using HTTP/1.1 with keep-alive). > >> If this happened, the browser waited and did not immediately follow the redirect. However, it was able to start rendering the page received so far. > Yes, you are sending some content there. I don’t quite unterstand why. > >> That's why I decided to send a redirect header and additionally generate the log viewer before >> exiting. This way the user hopefully never gets to see a blank page. > I am not sure what the problem is, but I thought the exit(0) would fix it :) Strangely enough, this did not work for me. I used two test systems for this: Virtualbox on Windows and a VM on a Proxmox host. With both systems I had the problem that the page sometimes blocked the connection. I tried exit(0), closing STDIN/STDOUT, without success. I used htop to monitor the running processes: The connection remained open until the Pakfire process terminated. My browser did not follow the redirect while the connection was still open. To avoid having to wait on a blank page, I have chosen to send a page along with the redirect. You're right, if the redirect would always work immediately, all this would be unnecessary. But honestly it's beyond my knowledge to figure out what exactly is happening here. Do you know if this has been worked on? If this does not occur any more, I'd be happy to remove my workaround. Best Leo > >> With core 168 installed on my test system I noticed that it happens much less often now. >> Personally I found this very difficult to reproduce and would like to leave my solution it as it is. >> >> Best regards >> Leo >> >>> -Michael >>> >>>> + >>>> + # Change global page mode >>>> + if($switch_mode) { >>>> + $pagemode = $mode; >>>> + } >>>> +} >>>> -- >>>> 2.27.0.windows.1
diff --git a/html/cgi-bin/pakfire.cgi b/html/cgi-bin/pakfire.cgi index ec3ee2cc6..6fade81bd 100644 --- a/html/cgi-bin/pakfire.cgi +++ b/html/cgi-bin/pakfire.cgi @@ -21,6 +21,7 @@ use strict; use List::Util qw(any); +use URI; # enable only the following on debugging purpose #use warnings; @@ -37,12 +38,17 @@ my %color = (); my %pakfiresettings = (); my %mainsettings = (); +# The page mode is used to explictly switch between user interface functions: +my $PM_DEFAULT = 'default'; # Default user interface with command processing +my $PM_LOGREAD = 'logread'; # Log messages viewer (ignores all commands) +my $pagemode = $PM_DEFAULT; + # Load general settings &General::readhash("${General::swroot}/main/settings", \%mainsettings); &General::readhash("${General::swroot}/pakfire/settings", \%pakfiresettings); &General::readhash("/srv/web/ipfire/html/themes/ipfire/include/colors.txt", \%color); -# Get CGI request data +# Get CGI POST request data $cgiparams{'ACTION'} = ''; $cgiparams{'FORCE'} = ''; @@ -51,6 +57,17 @@ $cgiparams{'DELPAKS'} = ''; &Header::getcgihash(\%cgiparams); +# Get CGI GET request data (if available) +if($ENV{'QUERY_STRING'}) { + my $uri = URI->new($ENV{'REQUEST_URI'}); + my %query = $uri->query_form; + + my $mode = lc($query{'mode'} // ''); + if(($mode eq $PM_DEFAULT) || ($mode eq $PM_LOGREAD)) { + $pagemode = $mode; # Limit to existing modes + } +} + ### Process AJAX/JSON request ### if($cgiparams{'ACTION'} eq 'json-getstatus') { # Send HTTP headers @@ -96,19 +113,24 @@ if($cgiparams{'ACTION'} eq 'json-getstatus') { } ### Process Pakfire install/update commands ### -if($cgiparams{'ACTION'} ne '') { +if(($cgiparams{'ACTION'} ne '') && ($pagemode eq $PM_DEFAULT)) { if(&_is_pakfire_busy()) { $errormessage = $Lang::tr{'pakfire already busy'}; + $pagemode = $PM_LOGREAD; # Running Pakfire instance found, switch to log viewer mode } elsif(($cgiparams{'ACTION'} eq 'install') && ($cgiparams{'FORCE'} eq 'on')) { my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); &General::system_background("/usr/local/bin/pakfire", "install", "--non-interactive", "--no-colors", @pkgs); + &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif(($cgiparams{'ACTION'} eq 'remove') && ($cgiparams{'FORCE'} eq 'on')) { my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); &General::system_background("/usr/local/bin/pakfire", "remove", "--non-interactive", "--no-colors", @pkgs); + &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq 'update') { &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); + &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq 'upgrade') { &General::system_background("/usr/local/bin/pakfire", "upgrade", "-y", "--no-colors"); + &_http_pagemode_redirect($PM_LOGREAD, 1); } elsif($cgiparams{'ACTION'} eq $Lang::tr{'save'}) { $pakfiresettings{"TREE"} = $cgiparams{"TREE"}; @@ -122,6 +144,7 @@ if($cgiparams{'ACTION'} ne '') { # Update lists &General::system_background("/usr/local/bin/pakfire", "update", "--force", "--no-colors"); + &_http_pagemode_redirect($PM_LOGREAD, 1); } } } @@ -221,8 +244,8 @@ if ($errormessage) { &Header::closebox(); } -# Show log output while Pakfire is running -if(&_is_pakfire_busy()) { +# Show only log output while Pakfire is running and stop afterwards +if(($pagemode eq $PM_LOGREAD) || (&_is_pakfire_busy())) { &Header::openbox("100%", "center", "Pakfire"); print <<END @@ -253,7 +276,8 @@ END } # Show Pakfire install/remove dependencies and confirm form -if (($cgiparams{'ACTION'} eq 'install') && (! &_is_pakfire_busy())) { +# (_is_pakfire_busy status was checked before and can be omitted) +if (($cgiparams{'ACTION'} eq 'install') && ($pagemode eq $PM_DEFAULT)) { &Header::openbox("100%", "center", $Lang::tr{'request'}); my @pkgs = split(/\|/, $cgiparams{'INSPAKS'}); @@ -291,7 +315,7 @@ END &Header::closepage(); exit; -} elsif (($cgiparams{'ACTION'} eq 'remove') && (! &_is_pakfire_busy())) { +} elsif (($cgiparams{'ACTION'} eq 'remove') && ($pagemode eq $PM_DEFAULT)) { &Header::openbox("100%", "center", $Lang::tr{'request'}); my @pkgs = split(/\|/, $cgiparams{'DELPAKS'}); @@ -476,3 +500,23 @@ sub _start_json_output { print "Content-Type: application/json\n"; print "\n"; # End of HTTP headers } + +# Send HTTP 303 redirect headers to change page mode +# GET is always used to display the redirected page, which will remove already processed POST form data. +# Note: Custom headers must be sent before the HTML output is started by &Header::showhttpheaders(). +# If switch_mode is set to true, the global page mode variable ("$pagemode") is also updated immediately. +sub _http_pagemode_redirect { + my ($mode, $switch_mode) = @_; + $mode //= $PM_DEFAULT; + $switch_mode //= 0; + + # Send HTTP redirect with GET parameter + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}?mode=${mode}"; + print "Status: 303 See Other\n"; + print "Location: $location\n"; + + # Change global page mode + if($switch_mode) { + $pagemode = $mode; + } +}