Add return code redirects to Nginx URL rewriting pages

This PR adds a "Redirect all requests" option to the server block and location URL Re-Writing pages in the Nginx Webserver module, backed by the Nginx return directive with a choice of 301, 302, 303, 307 or 308.

The existing rewrite table only offers the redirect and permanent flags, which map to 302 and 301. The other codes requested on the forum are not supported by rewrite at all, so they can only be provided through return.

The new option only manages return directives that are redirects. A bare URL form such as return https://example.com/; is shown as a 302, while non-redirect uses like return 404; are hidden from the page and never modified or removed.

Example of the resulting config for a server block:

return 308 "https://example.com$request_uri";

Fixes https://forum.virtualmin.com/t/137940
This commit is contained in:
Ilia Ross
2026-09-07 04:01:34 +02:00
parent 78e0f31899
commit 3e4bb23b21
7 changed files with 86 additions and 0 deletions

View File

@@ -5,6 +5,7 @@
* Add webserver logging controls to Usermin Configuration module
* Add option to rotate Webmin and Usermin webserver logs using `logrotate` instead of periodically clearing them [#2821](https://github.com/webmin/webmin/pull/2821)
* Add DNF 4 and 5 package hold management to the Software Package Updates module
* Add `return` based redirects with 301, 302, 303, 307 and 308 codes to the URL Re-Writing pages in the Nginx Webserver module [forum.virtualmin.com/t/137940](https://forum.virtualmin.com/t/137940)
* Fix DNF update confirmations by previewing packages and dependencies that will be installed or updated
* Fix PostgreSQL initialization on EL systems to use SCRAM-SHA-256 authentication by default
* Fix IPsec host key generation with modern Libreswan [#2132](https://github.com/webmin/webmin/issues/2132)

View File

@@ -23,6 +23,8 @@ print &ui_table_start($text{'rewrite_header'}, undef, 2);
print &nginx_rewrite_input("rewrite", $location);
print &nginx_return_input("return", $location);
print &nginx_onoff_input("rewrite_log", $location);
print &ui_table_end();

View File

@@ -19,6 +19,8 @@ print &ui_table_start($text{'rewrite_header'}, undef, 2);
print &nginx_rewrite_input("rewrite", $server);
print &nginx_return_input("return", $server);
print &nginx_onoff_input("rewrite_log", $server);
print &ui_table_end();

View File

@@ -324,6 +324,16 @@ rewrite_redirect=Return 302 redirect
rewrite_permanent=Return 301 redirect
rewrite_efrom=Source path $1 contains spaces
rewrite_eto=Destination URL $1 is missing or contains spaces
opt_return=Redirect all requests
rewrite_return_none=No redirect
rewrite_return_to=to URL
rewrite_return_301=301 (Moved permanently)
rewrite_return_302=302 (Found)
rewrite_return_303=303 (See other)
rewrite_return_307=307 (Temporary redirect)
rewrite_return_308=308 (Permanent redirect)
rewrite_ereturn=Redirect URL is missing or contains spaces
rewrite_ereturncode=Invalid redirect HTTP code
location_create=Create Location
location_edit=Edit Location

View File

@@ -1462,6 +1462,73 @@ for(my $i=0; defined(my $from = $in->{$name."_from_".$i}); $i++) {
&save_directive($parent, $name, \@obj);
}
# list_return_redirect_codes()
# Returns the HTTP codes that the return directive accepts with a URL
sub list_return_redirect_codes
{
return ( 301, 302, 303, 307, 308 );
}
# get_return_redirect(&object)
# Returns the code and URL of a return directive if it is a redirect, or an
# empty list for other uses such as return 404
sub get_return_redirect
{
my ($obj) = @_;
my @w = @{$obj->{'words'}};
if (@w == 2 && &indexof($w[0], &list_return_redirect_codes()) >= 0) {
return @w;
}
elsif (@w == 1 && $w[0] =~ /^(https?:\/\/|\$scheme)/) {
# Single URL form defaults to a 302 redirect
return ( 302, $w[0] );
}
return ( );
}
# nginx_return_input(name, &parent)
# Returns HTML for a redirect via the return directive
sub nginx_return_input
{
my ($name, $parent) = @_;
return undef if (!&supported_directive($name, $parent));
my $obj = &find($name, $parent);
my ($code, $url) = $obj ? &get_return_redirect($obj) : ( );
# Leave a return directive that is not a redirect alone
return undef if ($obj && !$code);
my $sel = &ui_select($name."_code", $code || 301,
[ map { [ $_, $text{'rewrite_return_'.$_} ] }
&list_return_redirect_codes() ]);
return &ui_table_row($text{'opt_'.$name},
&ui_opt_textbox($name, $url, 50, $text{'rewrite_return_none'},
$sel." ".$text{'rewrite_return_to'}." ", 0,
[ $name."_code" ]), 3);
}
# nginx_return_parse(name, &parent, &in)
# Updates the config with input from nginx_return_input
sub nginx_return_parse
{
my ($name, $parent, $in) = @_;
return undef if (!&supported_directive($name, $parent));
$in ||= \%in;
my $obj = &find($name, $parent);
# Never touch a return directive that was not shown in the form
return undef if ($obj && !&get_return_redirect($obj));
if ($in->{$name."_def"}) {
&save_directive($parent, $name, [ ]);
}
else {
my $code = $in->{$name."_code"};
&indexof($code, &list_return_redirect_codes()) >= 0 ||
&error($text{'rewrite_ereturncode'});
my $url = $in->{$name};
$url =~ /^\S+$/ || &error($text{'rewrite_ereturn'});
&save_directive($parent, $name, [ { 'name' => $name,
'words' => [ $code, $url ] } ]);
}
}
# list_log_formats([&server])
# Returns a list of all log format names
sub list_log_formats

View File

@@ -16,6 +16,8 @@ $location || &error($text{'location_egone'});
&nginx_rewrite_parse("rewrite", $location);
&nginx_return_parse("return", $location);
&nginx_onoff_parse("rewrite_log", $location);
&flush_config_file_lines();

View File

@@ -14,6 +14,8 @@ $server || &error($text{'server_egone'});
&nginx_rewrite_parse("rewrite", $server);
&nginx_return_parse("return", $server);
&nginx_onoff_parse("rewrite_log", $server);
&flush_config_file_lines();