public inbox for development@lists.ipfire.org
 help / color / mirror / Atom feed
* [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function
@ 2025-04-18 10:54 Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 1/7] http-client-functions.pl: Introduce " Stefan Schantl
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This reworked patchset contains a libwwwperl based flexible downloader
function which easily can be used to download files or grab and display any
web content.

The function now has been moved into an own perl library file
(http-client-functions.pl) in order to cleanup the general functions
library file. This should help to speed up the WUI by avoid to load to
much and big perl modules.

The FetchPublicIp function has been reworked to use the new downloader
and therefore moved to the http-client-functions file. As a result of
this, the GetDyndnsRedIP function, which is using that function also
has to be moved to the new library file and some existing CGI files needed
to be adjusted to work properly again.

The following changes from the feedback of the previously sent patchset
are also part of this patchset:

* Move the downloader into a seperate file.
* Die in case no URL has been passed to the downloader.
* Allow to specify the timeout time (default to 60sec if not set) insted of hardcode it.

-Stefan

Stefan Schantl (7):
  http-client-functions.pl: Introduce LWP-based flexible downloader
    function.
  http-client-functions.pl: Add FetchPublicIP function.
  general-functions.pl: Drop FetchPublicIp function.
  Move GetDyndnsRedIP from general-functions.pl to
    http-client-functions.pl
  ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl
    file
  ids-functions.pl: Use new downloader function from
    http-client-functions.pl
  http-client-functions.pl: Allow to user define the timeout value.

 config/cfgroot/general-functions.pl     |  46 ----
 config/cfgroot/http-client-functions.pl | 345 ++++++++++++++++++++++++
 config/cfgroot/ids-functions.pl         | 206 ++------------
 config/rootfiles/common/configroot      |   1 +
 html/cgi-bin/ddns.cgi                   |   3 +-
 html/cgi-bin/wio.cgi                    |   3 +-
 lfs/configroot                          |   1 +
 7 files changed, 369 insertions(+), 236 deletions(-)
 create mode 100644 config/cfgroot/http-client-functions.pl

-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 1/7] http-client-functions.pl: Introduce LWP-based flexible downloader function.
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 2/7] http-client-functions.pl: Add FetchPublicIP function Stefan Schantl
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This perl library contains a function which can be used to grab content
and/or store it into files.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/http-client-functions.pl | 290 ++++++++++++++++++++++++
 config/rootfiles/common/configroot      |   1 +
 lfs/configroot                          |   1 +
 3 files changed, 292 insertions(+)
 create mode 100644 config/cfgroot/http-client-functions.pl

diff --git a/config/cfgroot/http-client-functions.pl b/config/cfgroot/http-client-functions.pl
new file mode 100644
index 000000000..26ead6908
--- /dev/null
+++ b/config/cfgroot/http-client-functions.pl
@@ -0,0 +1,290 @@
+#!/usr/bin/perl -w
+############################################################################
+#                                                                          #
+# This file is part of the IPFire Firewall.                                #
+#                                                                          #
+# IPFire is free software; you can redistribute it and/or modify           #
+# it under the terms of the GNU General Public License as published by     #
+# the Free Software Foundation; either version 2 of the License, or        #
+# (at your option) any later version.                                      #
+#                                                                          #
+# IPFire is distributed in the hope that it will be useful,                #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of           #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            #
+# GNU General Public License for more details.                             #
+#                                                                          #
+# You should have received a copy of the GNU General Public License        #
+# along with IPFire; if not, write to the Free Software                    #
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA #
+#                                                                          #
+# Copyright (C) 2025 IPFire Team <info@ipfire.org>                         #
+#                                                                          #
+############################################################################
+
+package HTTPClient;
+
+require '/var/ipfire/general-functions.pl';
+
+use strict;
+
+# Load module to move files.
+use File::Copy;
+
+# Load module to get file stats.
+use File::stat;
+
+# Load module to deal with temporary files.
+use File::Temp;
+
+# Load module to deal with the date formats used by the HTTP protocol.
+use HTTP::Date;
+
+# Load the libwwwperl User Agent module.
+use LWP::UserAgent;
+
+# Function to grab a given URL content or to download and store it on disk.
+#
+# The function requires a configuration hash to be passed.
+#
+# The following options (hash keys) are supported:
+#
+# URL -> The URL to the content or file. REQUIRED!
+# FILE -> The filename as fullpath where the content/file should be stored on disk.
+# ETAGSFILE -> A filename again as fullpath where Etags should be stored and read.
+# ETAGPREFIX -> In case a custom etag name should be used, otherwise it defaults to the given URL.
+# MAXSIZE -> In bytes until the downloader will abort downloading. (example: 10_485_760 for 10MB)
+#
+# If a file is given an If-Modified-Since header will be generated from the last modified timestamp
+# of an already stored file. In case an Etag file is specified an If-None-Match header will be added to
+# the request - Both can be used at the same time.
+#
+# In case no FILE option has been passed to the function, the content of the requested URL will be returned.
+#
+# Return codes (if FILE is used):
+#
+# nothing - On success
+# no url - If no URL has been specified.
+# not_modified - In case the servers responds with "Not modified" (304)
+# dl_error - If the requested URL cannot be accessed.
+# incomplete download - In case the size of the local file does not match the remote content_lenght.
+#
+sub downloader (%) {
+	my (%args) = @_;
+
+	# Remap args hash and convert all keys into upper case format.
+	%args = map { uc $_ => $args{$_} } keys %args;
+
+	# The amount of download attempts before giving up and
+	# logging an error.
+	my $max_dl_attempts = 3;
+
+	# Temporary directory to download the files.
+	my $tmp_dl_directory = "/var/tmp";
+
+	# Assign hash values.
+	my $url = $args{"URL"} if (exists($args{"URL"}));
+	my $file = $args{"FILE"} if (exists($args{"FILE"}));
+	my $etags_file = $args{"ETAGSFILE"} if (exists($args{"ETAGSFILE"}));
+	my $etagprefix = $url;
+	$etagprefix = $args{"ETAGPREFIX"} if (exists($args{"ETAGPREFIX"}));
+	my $max_size = $args{"MAXSIZE"} if (exists($args{"MAXSIZE"}));
+
+	# Abort with error "no url", if no URL has been given.
+	die "downloader: No URL has been given." unless ($url);
+
+	my %etags = ();
+	my $tmpfile;
+
+	# Read-in proxysettings.
+	my %proxysettings=();
+	&General::readhash("${General::swroot}/proxy/settings", \%proxysettings);
+
+	# Create a user agent instance.
+	#
+	# Request SSL hostname verification and specify path
+	# to the CA file.
+	my $ua = LWP::UserAgent->new(
+		ssl_opts => {
+			SSL_ca_file     => '/etc/ssl/cert.pem',
+			verify_hostname => 1,
+		},
+	);
+
+	# Set timeout to 10 seconds.
+	$ua->timeout(10);
+
+	# Assign maximum download size if set.
+	$ua->max_size($max_size) if ($max_size);
+
+	# Generate UserAgent.
+	my $agent = &General::MakeUserAgent();
+
+	# Set the generated UserAgent.
+	$ua->agent($agent);
+
+	# Check if an upstream proxy is configured.
+	if ($proxysettings{'UPSTREAM_PROXY'}) {
+		# Start generating proxy url.
+		my $proxy_url = "http://";
+
+		# Check if the proxy requires authentication.
+		if (($proxysettings{'UPSTREAM_USER'}) && ($proxysettings{'UPSTREAM_PASSWORD'})) {
+			# Add proxy auth details.
+			$proxy_url .= "$proxysettings{'UPSTREAM_USER'}\:$proxysettings{'UPSTREAM_PASSWORD'}\@";
+		}
+
+		# Add proxy server address and port.
+		$proxy_url .= $proxysettings{'UPSTREAM_PROXY'};
+
+		# Append proxy settings.
+		$ua->proxy(['http', 'https'], $proxy_url);
+	}
+
+	# Create a HTTP request element and pass the given URL to it.
+	my $request = HTTP::Request->new(GET => $url);
+
+	# Check if a file to store the output has been provided.
+	if ($file) {
+		# Check if the given file already exits, because it has been downloaded in the past.
+		#
+		# In this case we are requesting the server if the remote file has been changed or not.
+		# This will be done by sending the modification time in a special HTTP header.
+		if (-f $file) {
+			# Call stat on the file.
+			my $stat = stat($file);
+
+			# Omit the mtime of the existing file.
+			my $mtime = $stat->mtime;
+
+			# Convert the timestamp into right format.
+			my $http_date = time2str($mtime);
+
+			# Add the If-Modified-Since header to the request to ask the server if the
+			# file has been modified.
+			$request->header( 'If-Modified-Since' => "$http_date" );
+		}
+
+		# Generate a temporary file name, located in the tempoary download directory and with a suffix of ".tmp".
+		# The downloaded file will be stored there until some sanity checks are performed.
+		my $tmp = File::Temp->new( SUFFIX => ".tmp", DIR => "$tmp_dl_directory/", UNLINK => 0 );
+		$tmpfile = $tmp->filename();
+	}
+
+	# Check if an file for etags has been given.
+	if ($etags_file) {
+		# Read-in Etags file for known Etags if the file is present.
+		&readhash("$etags_file", \%etags) if (-f $etags_file);
+
+		# Check if an Etag for the requested file is stored.
+		if ($etags{$etagprefix}) {
+			# Grab the stored tag.
+			my $etag = $etags{$etagprefix};
+
+			# Add an "If-None-Match header to the request to ask the server if the
+			# file has been modified.
+			$request->header( 'If-None-Match' => $etag );
+		}
+	}
+
+	my $dl_attempt = 1;
+	my $response;
+
+	# Download and retry on failure.
+	while ($dl_attempt <= $max_dl_attempts) {
+		# Perform the request and save the output into the tmpfile if requested.
+		$response = $ua->request($request, $tmpfile);
+
+		# Check if the download was successfull.
+		if($response->is_success) {
+			# Break loop.
+			last;
+
+		# Check if the server responds with 304 (Not Modified).
+		} elsif ($response->code == 304) {
+			# Remove temporary file, if one exists.
+			unlink("$tmpfile") if (-e "$tmpfile");
+
+			# Return "not modified".
+			return "not modified";
+
+		# Check if we ran out of download re-tries.
+		} elsif ($dl_attempt eq $max_dl_attempts) {
+			# Obtain error.
+			my $error = $response->content;
+
+			# Remove temporary file, if one exists.
+			unlink("$tmpfile") if (-e "$tmpfile");
+
+			# Return the error message from response..
+			return "$error";
+		}
+
+		# Remove temporary file, if one exists.
+		unlink("$tmpfile") if (-e "$tmpfile");
+
+		# Increase download attempt counter.
+		$dl_attempt++;
+	}
+
+	# Obtain the connection headers.
+	my $headers = $response->headers;
+
+	# Check if an Etag file has been provided.
+	if ($etags_file) {
+		# Grab the Etag from the response if the server provides one.
+		if ($response->header('Etag')) {
+			# Add the provided Etag to the hash of tags.
+			$etags{$etagprefix} = $response->header('Etag');
+
+			# Write the etags file.
+			&General::writehash($etags_file, \%etags);
+		}
+	}
+
+	# Check if the response should be stored on disk.
+	if ($file) {
+		# Get the remote size of the content.
+		my $remote_size = $response->header('Content-Length');
+
+		# Perform a stat on the temporary file.
+		my $stat = stat($tmpfile);
+
+		# Grab the size of the stored temporary file.
+		my $local_size = $stat->size;
+
+		# Check if both sizes are equal.
+		if(($remote_size) && ($remote_size ne $local_size)) {
+			# Delete the temporary file.
+			unlink("$tmpfile");
+
+			# Abort and return "incomplete download" as error.
+			return "incomplete download";
+		}
+
+		# Move the temporaray file to the desired file by overwriting a may
+		# existing one.
+		move("$tmpfile", "$file");
+
+		# Omit the timestamp from response header, when the file has been modified the
+		# last time.
+		my $last_modified = $headers->last_modified;
+
+		# Check if we got a last-modified value from the server.
+		if ($last_modified) {
+			# Assign the last-modified timestamp as mtime to the
+			# stored file.
+			utime(time(), "$last_modified", "$file");
+		}
+
+		# Delete temporary file.
+		unlink("$tmpfile");
+
+		# If we got here, everything worked fine. Return nothing.
+		return;
+	} else {
+		# Decode the response content and return it.
+		return $response->decoded_content;
+	}
+}
+
+1;
diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
index 9839eee45..51472e7c5 100644
--- a/config/rootfiles/common/configroot
+++ b/config/rootfiles/common/configroot
@@ -79,6 +79,7 @@ var/ipfire/fwlogs
 var/ipfire/general-functions.pl
 var/ipfire/graphs.pl
 var/ipfire/header.pl
+var/ipfire/http-client-functions.pl
 var/ipfire/location-functions.pl
 var/ipfire/ids-functions.pl
 var/ipfire/ipblocklist-functions.pl
diff --git a/lfs/configroot b/lfs/configroot
index 9f6c1ff8c..1f752ddb6 100644
--- a/lfs/configroot
+++ b/lfs/configroot
@@ -76,6 +76,7 @@ $(TARGET) :
 
 	# Copy initial configfiles
 	cp $(DIR_SRC)/config/cfgroot/header.pl			$(CONFIG_ROOT)/
+	cp $(DIR_SRC)/config/cfgroot/http-client-functions.pl   $(CONFIG_ROOT)/
 	cp $(DIR_SRC)/config/cfgroot/general-functions.pl	$(CONFIG_ROOT)/
 	cp $(DIR_SRC)/config/cfgroot/network-functions.pl	$(CONFIG_ROOT)/
 	cp $(DIR_SRC)/config/cfgroot/location-functions.pl	$(CONFIG_ROOT)/
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 2/7] http-client-functions.pl: Add FetchPublicIP function.
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 1/7] http-client-functions.pl: Introduce " Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 3/7] general-functions.pl: Drop FetchPublicIp function Stefan Schantl
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This functions uses the newly introduced downloader to fetch
the pulic IPv4 address on red and will replace the current used one
from the general-functions.pl library.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/http-client-functions.pl | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/config/cfgroot/http-client-functions.pl b/config/cfgroot/http-client-functions.pl
index 26ead6908..bfb9fdd20 100644
--- a/config/cfgroot/http-client-functions.pl
+++ b/config/cfgroot/http-client-functions.pl
@@ -287,4 +287,24 @@ sub downloader (%) {
 	}
 }
 
+#
+# Tiny function to grab the public red IPv4 address using LWL.
+#
+sub FetchPublicIp {
+	# URL to grab the public IP.
+	my $url = "https://checkip4.dns.lightningwirelabs.com";
+
+	# Call downloader to fetch the public IP.
+	my $response = &downloader("URL" => $url);
+
+	# Omit the address from the resonse message.
+	if ($response =~ /Your IP address is: (\d+.\d+.\d+.\d+)/) {
+		# Return the address.
+		return $1;
+	}
+
+	# Unable to grab the address - Return nothing.
+	return;
+}
+
 1;
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 3/7] general-functions.pl: Drop FetchPublicIp function.
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 1/7] http-client-functions.pl: Introduce " Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 2/7] http-client-functions.pl: Add FetchPublicIP function Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 4/7] Move GetDyndnsRedIP from general-functions.pl to http-client-functions.pl Stefan Schantl
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This function has been reworked and moved into the
http-client-functions library.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/general-functions.pl | 21 ---------------------
 1 file changed, 21 deletions(-)

diff --git a/config/cfgroot/general-functions.pl b/config/cfgroot/general-functions.pl
index 8ba6e3f79..861f95dec 100644
--- a/config/cfgroot/general-functions.pl
+++ b/config/cfgroot/general-functions.pl
@@ -17,7 +17,6 @@ package General;
 use strict;
 use Socket;
 use IO::Socket;
-use Net::SSLeay;
 use Net::IPv4Addr qw(:all);
 
 $General::version = 'VERSION';
@@ -961,26 +960,6 @@ sub findhasharraykey {
     }
 }
 
-sub FetchPublicIp {
-    my %proxysettings;
-    &General::readhash("${General::swroot}/proxy/settings", \%proxysettings);
-    if ($_=$proxysettings{'UPSTREAM_PROXY'}) {
-        my ($peer, $peerport) = (/^(?:[a-zA-Z ]+\:\/\/)?(?:[A-Za-z0-9\_\.\-]*?(?:\:[A-Za-z0-9\_\.\-]*?)?\@)?([a-zA-Z0-9\.\_\-]*?)(?:\:([0-9]{1,5}))?(?:\/.*?)?$/);
-        Net::SSLeay::set_proxy($peer,$peerport,$proxysettings{'UPSTREAM_USER'},$proxysettings{'UPSTREAM_PASSWORD'} );
-    }
-    my $user_agent = &MakeUserAgent();
-    my ($out, $response) = Net::SSLeay::get_http(  'checkip4.dns.lightningwirelabs.com',
-                				    80,
-        					    "/",
-						    Net::SSLeay::make_headers('User-Agent' => $user_agent )
-						);
-    if ($response =~ m%HTTP/1\.. 200 OK%) {
-	$out =~ /Your IP address is: (\d+.\d+.\d+.\d+)/;
-	return $1;
-    }
-    return '';
-}
-
 #
 # Check if hostname.domain provided have IP provided
 # use gethostbyname to verify that
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 4/7] Move GetDyndnsRedIP from general-functions.pl to http-client-functions.pl
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
                   ` (2 preceding siblings ...)
  2025-04-18 10:54 ` [PATCHv2 3/7] general-functions.pl: Drop FetchPublicIp function Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 5/7] ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl file Stefan Schantl
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This function depends on the previously FetchPublicIp function
and so also has to be moved.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/general-functions.pl     | 25 ---------------------
 config/cfgroot/http-client-functions.pl | 30 +++++++++++++++++++++++++
 2 files changed, 30 insertions(+), 25 deletions(-)

diff --git a/config/cfgroot/general-functions.pl b/config/cfgroot/general-functions.pl
index 861f95dec..bbd0f9839 100644
--- a/config/cfgroot/general-functions.pl
+++ b/config/cfgroot/general-functions.pl
@@ -998,31 +998,6 @@ sub DyndnsServiceSync ($;$;$) {
     }
     return 0;
 }
-#
-# This sub returns the red IP used to compare in DyndnsServiceSync
-#
-sub GetDyndnsRedIP {
-    my %settings;
-    &General::readhash("${General::swroot}/ddns/settings", \%settings);
-
-    open(IP, "${General::swroot}/red/local-ipaddress") or return 'unavailable';
-    my $ip = <IP>;
-    close(IP);
-    chomp $ip;
-
-    # 100.64.0.0/10 is reserved for dual-stack lite (http://tools.ietf.org/html/rfc6598).
-    if (&General::IpInSubnet ($ip,'10.0.0.0','255.0.0.0') ||
-        &General::IpInSubnet ($ip,'172.16.0.0.','255.240.0.0') ||
-        &General::IpInSubnet ($ip,'192.168.0.0','255.255.0.0') ||
-        &General::IpInSubnet ($ip,'100.64.0.0', '255.192.0.0'))
-    {
-	if ($settings{'BEHINDROUTER'} eq 'FETCH_IP') {
-    	    my $RealIP = &General::FetchPublicIp;
-	    $ip = (&General::validip ($RealIP) ?  $RealIP : 'unavailable');
-	}
-    }
-    return $ip;
-}
 
 # Translate ICMP code to text
 # ref: http://www.iana.org/assignments/icmp-parameters
diff --git a/config/cfgroot/http-client-functions.pl b/config/cfgroot/http-client-functions.pl
index bfb9fdd20..f1f7de309 100644
--- a/config/cfgroot/http-client-functions.pl
+++ b/config/cfgroot/http-client-functions.pl
@@ -307,4 +307,34 @@ sub FetchPublicIp {
 	return;
 }
 
+#
+# This sub returns the red IP used to compare in DyndnsServiceSync
+#
+sub GetDyndnsRedIP {
+	my %settings;
+
+	# Read-in ddns settings.
+	&General::readhash("${General::swroot}/ddns/settings", \%settings);
+
+	# Try to grab the address from the local-ipaddress file.
+	my $ip = &General::grab_address_from_file("${General::swroot}/red/local-ipaddress") or return 'unavailable';
+
+	# 100.64.0.0/10 is reserved for dual-stack lite (http://tools.ietf.org/html/rfc6598).
+	if (&General::IpInSubnet ($ip,'10.0.0.0','255.0.0.0') ||
+		&General::IpInSubnet ($ip,'172.16.0.0.','255.240.0.0') ||
+		&General::IpInSubnet ($ip,'192.168.0.0','255.255.0.0') ||
+		&General::IpInSubnet ($ip,'100.64.0.0', '255.192.0.0'))
+	{
+		if ($settings{'BEHINDROUTER'} eq 'FETCH_IP') {
+			# Call function to omit the real address.
+			my $RealIP = &FetchPublicIp;
+
+			# Check if the grabbed address is valid.
+			$ip = (&General::validip ($RealIP) ?  $RealIP : 'unavailable');
+		}
+	}
+
+	return $ip;
+}
+
 1;
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 5/7] ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl file
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
                   ` (3 preceding siblings ...)
  2025-04-18 10:54 ` [PATCHv2 4/7] Move GetDyndnsRedIP from general-functions.pl to http-client-functions.pl Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 6/7] ids-functions.pl: Use new downloader function from http-client-functions.pl Stefan Schantl
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 html/cgi-bin/ddns.cgi | 3 ++-
 html/cgi-bin/wio.cgi  | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/html/cgi-bin/ddns.cgi b/html/cgi-bin/ddns.cgi
index 0e3ccbe45..34475b75c 100644
--- a/html/cgi-bin/ddns.cgi
+++ b/html/cgi-bin/ddns.cgi
@@ -29,6 +29,7 @@ use experimental 'smartmatch';
 require '/var/ipfire/general-functions.pl';
 require "${General::swroot}/lang.pl";
 require "${General::swroot}/header.pl";
+require "${General::swroot}/http-client-functions.pl";
 
 #workaround to suppress a warning when a variable is used only once
 my @dummy = ( ${Header::table2colour}, ${Header::colouryellow} );
@@ -559,7 +560,7 @@ open(FILE, $datafile) or die "Unable to open $datafile.";
 close(FILE);
 
 # Get IP address of the red interface.
-my $ip = &General::GetDyndnsRedIP();
+my $ip = &HTTPClient::GetDyndnsRedIP();
 my $id = 0;
 my $toggle_enabled;
 
diff --git a/html/cgi-bin/wio.cgi b/html/cgi-bin/wio.cgi
index f31f5d565..30a51104c 100644
--- a/html/cgi-bin/wio.cgi
+++ b/html/cgi-bin/wio.cgi
@@ -50,6 +50,7 @@ require '/var/ipfire/general-functions.pl';
 require '/var/ipfire/network-functions.pl';
 require '/var/ipfire/lang.pl';
 require '/var/ipfire/header.pl';
+require '/var/ipfire/http-client-functions.pl';
 require '/usr/lib/wio/wio-lib.pl';
 require '/usr/lib/wio/wio-graphs.pl';
 
@@ -1163,7 +1164,7 @@ close (FILE);
 		@temp = split (/\,/, $_);
 
 		if ( $temp[7] eq "on" ) {
-			$bgcolor = ( &General::DyndnsServiceSync (&General::GetDyndnsRedIP,$temp[1],$temp[2]) ? "$Header::colourgreen" : "$Header::colourred" );
+			$bgcolor = ( &General::DyndnsServiceSync (&HTTPClient::GetDyndnsRedIP,$temp[1],$temp[2]) ? "$Header::colourgreen" : "$Header::colourred" );
 		}
 		else {
 			$bgcolor = "blue";
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 6/7] ids-functions.pl: Use new downloader function from http-client-functions.pl
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
                   ` (4 preceding siblings ...)
  2025-04-18 10:54 ` [PATCHv2 5/7] ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl file Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-18 10:54 ` [PATCHv2 7/7] http-client-functions.pl: Allow to user define the timeout value Stefan Schantl
  2025-04-20 11:25 ` [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Michael Tremer
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/ids-functions.pl | 206 +++-----------------------------
 1 file changed, 18 insertions(+), 188 deletions(-)

diff --git a/config/cfgroot/ids-functions.pl b/config/cfgroot/ids-functions.pl
index 399f5cbf8..1a72e4c3e 100644
--- a/config/cfgroot/ids-functions.pl
+++ b/config/cfgroot/ids-functions.pl
@@ -27,6 +27,7 @@ package IDS;
 
 require '/var/ipfire/general-functions.pl';
 require "${General::swroot}/network-functions.pl";
+require "${General::swroot}/http-client-functions.pl";
 require "${General::swroot}/suricata/ruleset-sources";
 
 # Load perl module to deal with Archives.
@@ -44,15 +45,6 @@ use File::Path qw(rmtree);
 # Load module to get file stats.
 use File::stat;
 
-# Load module to deal with temporary files.
-use File::Temp;
-
-# Load module to deal with the date formats used by the HTTP protocol.
-use HTTP::Date;
-
-# Load the libwwwperl User Agent module.
-use LWP::UserAgent;
-
 # Load function from posix module to format time strings.
 use POSIX qw (strftime);
 
@@ -130,9 +122,6 @@ my $suricatactrl = "/usr/local/bin/suricatactrl";
 # Prefix for each downloaded ruleset.
 my $dl_rulesfile_prefix = "idsrules";
 
-# Temporary directory to download the rules files.
-my $tmp_dl_directory = "/var/tmp";
-
 # Temporary directory where the rulesets will be extracted.
 my $tmp_directory = "/tmp/ids_tmp";
 
@@ -299,61 +288,13 @@ sub checkdiskspace () {
 #
 ## This function is responsible for downloading the ruleset for a given provider.
 ##
-## * At first it initialize the downloader and sets an upstream proxy if configured.
-## * The next step will be to generate the final download url, by obtaining the URL for the desired
-##   ruleset and add the settings for the upstream proxy.
-## * Finally the function will grab the rule file or tarball from the server.
-##   It tries to reduce the amount of download by using the "If-Modified-Since" HTTP header.
-#
-## Return codes:
-##
-## * "no url" - If no download URL could be gathered for the provider.
-## * "not modified" - In case the already stored rules file is up to date.
-## * "incomplete download" - When the remote file size differs from the downloaded file size.
-## * "$error" - The error message generated from the LWP::User Agent module.
+## It uses the LWP-based downloader function from the general-functions.pl to
+## download the ruleset for a requested provider.
 #
 sub downloadruleset ($) {
 	my ($provider) = @_;
 
-	# The amount of download attempts before giving up and
-	# logging an error.
-	my $max_dl_attempts = 3;
-
-	# Read proxysettings.
-	my %proxysettings=();
-	&General::readhash("${General::swroot}/proxy/settings", \%proxysettings);
-
-	# Init the download module.
-	#
-	# Request SSL hostname verification and specify path
-	# to the CA file.
-	my $downloader = LWP::UserAgent->new(
-		ssl_opts => {
-			SSL_ca_file     => '/etc/ssl/cert.pem',
-			verify_hostname => 1,
-		}
-	);
-
-	# Set timeout to 10 seconds.
-	$downloader->timeout(10);
-
-	# Check if an upstream proxy is configured.
-	if ($proxysettings{'UPSTREAM_PROXY'}) {
-		my $proxy_url;
-
-		$proxy_url = "http://";
-
-		# Check if the proxy requires authentication.
-		if (($proxysettings{'UPSTREAM_USER'}) && ($proxysettings{'UPSTREAM_PASSWORD'})) {
-			$proxy_url .= "$proxysettings{'UPSTREAM_USER'}\:$proxysettings{'UPSTREAM_PASSWORD'}\@";
-		}
-
-		# Add proxy server address and port.
-		$proxy_url .= $proxysettings{'UPSTREAM_PROXY'};
-
-		# Setup proxy settings.
-		$downloader->proxy(['http', 'https'], $proxy_url);
-	}
+	my %settings = ();
 
 	# Grab the download url for the provider.
 	my $url = $IDS::Ruleset::Providers{$provider}{'dl_url'};
@@ -371,141 +312,30 @@ sub downloadruleset ($) {
 	# Abort and return "no url", if no url could be determined for the provider.
 	return "no url" unless ($url);
 
-	# Pass the requested URL to the downloader.
-	my $request = HTTP::Request->new(GET => $url);
-
-	# Generate temporary file name, located in the tempoary download directory and with a suffix of ".tmp".
-	# The downloaded file will be stored there until some sanity checks are performed.
-	my $tmp = File::Temp->new( SUFFIX => ".tmp", DIR => "$tmp_dl_directory/", UNLINK => 0 );
-	my $tmpfile = $tmp->filename();
+	# Pass the requested URL to the settings hash.
+	$settings{'URL'} = $url;
 
 	# Call function to get the final path and filename for the downloaded file.
 	my $dl_rulesfile = &_get_dl_rulesfile($provider);
 
-	# Check if the rulesfile already exits, because it has been downloaded in the past.
-	#
-	# In this case we are requesting the server if the remote file has been changed or not.
-	# This will be done by sending the modification time in a special HTTP header.
-	if (-f $dl_rulesfile) {
-		# Call stat on the file.
-		my $stat = stat($dl_rulesfile);
-
-		# Omit the mtime of the existing file.
-		my $mtime = $stat->mtime;
-
-		# Convert the timestamp into right format.
-		my $http_date = time2str($mtime);
-
-		# Add the If-Modified-Since header to the request to ask the server if the
-		# file has been modified.
-		$request->header( 'If-Modified-Since' => "$http_date" );
-	}
-
-	# Read-in Etags file for known Etags if the file is present.
-	my %etags = ();
-	&General::readhash("$etags_file", \%etags) if (-f $etags_file);
-
-	# Check if an Etag for the current provider is stored.
-	if ($etags{$provider}) {
-		# Grab the stored tag.
-		my $etag = $etags{$provider};
-
-		# Add an "If-None-Match header to the request to ask the server if the
-		# file has been modified.
-		$request->header( 'If-None-Match' => $etag );
-	}
-
-	my $dl_attempt = 1;
-	my $response;
-
-	# Download and retry on failure.
-	while ($dl_attempt <= $max_dl_attempts) {
-		# Perform the request and save the output into the tmpfile.
-		$response = $downloader->request($request, $tmpfile);
-
-		# Check if the download was successfull.
-		if($response->is_success) {
-			# Break loop.
-			last;
-
-		# Check if the server responds with 304 (Not Modified).
-		} elsif ($response->code == 304) {
-			# Remove temporary file, if one exists.
-			unlink("$tmpfile") if (-e "$tmpfile");
-
-			# Return "not modified".
-			return "not modified";
-
-		# Check if we ran out of download re-tries.
-		} elsif ($dl_attempt eq $max_dl_attempts) {
-			# Obtain error.
-			my $error = $response->content;
-
-			# Remove temporary file, if one exists.
-			unlink("$tmpfile") if (-e "$tmpfile");
-
-			# Return the error message from response..
-			return "$error";
-		}
-
-		# Remove temporary file, if one exists.
-		unlink("$tmpfile") if (-e "$tmpfile");
+	# Add the file information to the settings hash.
+	$settings{'FILE'} = $dl_rulesfile;
 
-		# Increase download attempt counter.
-		$dl_attempt++;
-	}
-
-	# Obtain the connection headers.
-	my $headers = $response->headers;
-
-	# Get the timestamp from header, when the file has been modified the
-	# last time.
-	my $last_modified = $headers->last_modified;
-
-	# Get the remote size of the downloaded file.
-	my $remote_filesize = $headers->content_length;
+	# Add Etag details to the settings hash.
+	$settings{'ETAGSFILE'} = $etags_file;
+	$settings{'ETAGPREFIX'} = $provider;
 
-	# Grab the Etag from response it the server provides one.
-	if ($response->header('Etag')) {
-		# Add the Etag to the etags hash.
-		$etags{$provider} = $response->header('Etag');
+	# Call the downloader and pass the settings hash.
+	my $response = &HTTPClient::downloader(%settings);
 
-		# Write the etags file.
-		&General::writehash($etags_file, \%etags);
+	# Return the response message if the downloader provided one.
+	if ($response) {
+		return $response;
 	}
 
-	# Perform stat on the tmpfile.
-	my $stat = stat($tmpfile);
-
-	# Grab the local filesize of the downloaded tarball.
-	my $local_filesize = $stat->size;
-
-	# Check if both file sizes match.
-	if (($remote_filesize) && ($remote_filesize ne $local_filesize)) {
-		# Delete temporary file.
-		unlink("$tmpfile");
-
-		# Return "1" - false.
-		return "incomplete download";
-	}
-
-	# Overwrite the may existing rulefile or tarball with the downloaded one.
-	move("$tmpfile", "$dl_rulesfile");
-
-	# Check if we got a last-modified value from the server.
-	if ($last_modified) {
-		# Assign the last-modified timestamp as mtime to the
-		# rules file.
-		utime(time(), "$last_modified", "$dl_rulesfile");
-	}
-
-	# Delete temporary file.
-	unlink("$tmpfile");
-
-	# Set correct ownership for the tarball.
-	set_ownership("$dl_rulesfile");
+	# Set correct ownership for the downloaded rules file.
+	&set_ownership("$dl_rulesfile");
 
-	# If we got here, everything worked fine. Return nothing.
 	return;
 }
 
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCHv2 7/7] http-client-functions.pl: Allow to user define the timeout value.
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
                   ` (5 preceding siblings ...)
  2025-04-18 10:54 ` [PATCHv2 6/7] ids-functions.pl: Use new downloader function from http-client-functions.pl Stefan Schantl
@ 2025-04-18 10:54 ` Stefan Schantl
  2025-04-20 11:25 ` [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Michael Tremer
  7 siblings, 0 replies; 9+ messages in thread
From: Stefan Schantl @ 2025-04-18 10:54 UTC (permalink / raw)
  To: development; +Cc: Stefan Schantl

This allows to specify the the timeout value.
Defaults to to 60 seconds if not set.

Signed-off-by: Stefan Schantl <stefan.schantl@ipfire.org>
---
 config/cfgroot/http-client-functions.pl | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/config/cfgroot/http-client-functions.pl b/config/cfgroot/http-client-functions.pl
index f1f7de309..c9484c575 100644
--- a/config/cfgroot/http-client-functions.pl
+++ b/config/cfgroot/http-client-functions.pl
@@ -89,6 +89,10 @@ sub downloader (%) {
 	$etagprefix = $args{"ETAGPREFIX"} if (exists($args{"ETAGPREFIX"}));
 	my $max_size = $args{"MAXSIZE"} if (exists($args{"MAXSIZE"}));
 
+	# Timeout defaults to 60 Seconds if not set.
+	my $timeout = 60;
+	$timeout = $args{"TIMEOUT"} if (exists($args{"TIMEOUT"}));
+
 	# Abort with error "no url", if no URL has been given.
 	die "downloader: No URL has been given." unless ($url);
 
@@ -110,8 +114,9 @@ sub downloader (%) {
 		},
 	);
 
-	# Set timeout to 10 seconds.
-	$ua->timeout(10);
+	# Set the timeout to the configured value.
+	# Defaults to 60 seconds if not set.
+	$ua->timeout($timeout);
 
 	# Assign maximum download size if set.
 	$ua->max_size($max_size) if ($max_size);
-- 
2.47.2



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function
  2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
                   ` (6 preceding siblings ...)
  2025-04-18 10:54 ` [PATCHv2 7/7] http-client-functions.pl: Allow to user define the timeout value Stefan Schantl
@ 2025-04-20 11:25 ` Michael Tremer
  7 siblings, 0 replies; 9+ messages in thread
From: Michael Tremer @ 2025-04-20 11:25 UTC (permalink / raw)
  To: Stefan Schantl; +Cc: development

Hello Stefan,

Thank you for working on this. I like this a lot.

This makes it all very independent from the other code and we can pull it all in wherever it is needed.

I didn’t test this, but the changes look good to me!

-Michael

> On 18 Apr 2025, at 11:54, Stefan Schantl <stefan.schantl@ipfire.org> wrote:
> 
> This reworked patchset contains a libwwwperl based flexible downloader
> function which easily can be used to download files or grab and display any
> web content.
> 
> The function now has been moved into an own perl library file
> (http-client-functions.pl) in order to cleanup the general functions
> library file. This should help to speed up the WUI by avoid to load to
> much and big perl modules.
> 
> The FetchPublicIp function has been reworked to use the new downloader
> and therefore moved to the http-client-functions file. As a result of
> this, the GetDyndnsRedIP function, which is using that function also
> has to be moved to the new library file and some existing CGI files needed
> to be adjusted to work properly again.
> 
> The following changes from the feedback of the previously sent patchset
> are also part of this patchset:
> 
> * Move the downloader into a seperate file.
> * Die in case no URL has been passed to the downloader.
> * Allow to specify the timeout time (default to 60sec if not set) insted of hardcode it.
> 
> -Stefan
> 
> Stefan Schantl (7):
>  http-client-functions.pl: Introduce LWP-based flexible downloader
>    function.
>  http-client-functions.pl: Add FetchPublicIP function.
>  general-functions.pl: Drop FetchPublicIp function.
>  Move GetDyndnsRedIP from general-functions.pl to
>    http-client-functions.pl
>  ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl
>    file
>  ids-functions.pl: Use new downloader function from
>    http-client-functions.pl
>  http-client-functions.pl: Allow to user define the timeout value.
> 
> config/cfgroot/general-functions.pl     |  46 ----
> config/cfgroot/http-client-functions.pl | 345 ++++++++++++++++++++++++
> config/cfgroot/ids-functions.pl         | 206 ++------------
> config/rootfiles/common/configroot      |   1 +
> html/cgi-bin/ddns.cgi                   |   3 +-
> html/cgi-bin/wio.cgi                    |   3 +-
> lfs/configroot                          |   1 +
> 7 files changed, 369 insertions(+), 236 deletions(-)
> create mode 100644 config/cfgroot/http-client-functions.pl
> 
> -- 
> 2.47.2
> 
> 



^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2025-04-20 11:25 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-04-18 10:54 [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 1/7] http-client-functions.pl: Introduce " Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 2/7] http-client-functions.pl: Add FetchPublicIP function Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 3/7] general-functions.pl: Drop FetchPublicIp function Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 4/7] Move GetDyndnsRedIP from general-functions.pl to http-client-functions.pl Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 5/7] ddns.cgi, wio.cgi: Use GetDyndnsRedIP from http-client-functions.pl file Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 6/7] ids-functions.pl: Use new downloader function from http-client-functions.pl Stefan Schantl
2025-04-18 10:54 ` [PATCHv2 7/7] http-client-functions.pl: Allow to user define the timeout value Stefan Schantl
2025-04-20 11:25 ` [PATCHv2 0/7] Introduce perl LWP-based flexible downloader function Michael Tremer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox