[pmg-devel] [PATCH pmg-api] dkim: local mail: fix `Date` header formatting under different locales
Christoph Heiss
c.heiss at proxmox.com
Mon Sep 1 14:12:27 CEST 2025
This was reported in the community forum to cause problems if the
(shell) locale was set to something different than 'C' or 'en_US.UTF-8'
[0].
Tl;dr: A different locale would cause the `Date` header to be formatted
according to that locale, instead of following the RFC2822 format.
E.g. under the locale `de_DE.UTF-8`, the header would look like this:
Date: Mo, 25 Aug 2025 00:01:04 +0200
instead of the correct format of:
Date: Mon, 25 Aug 2025 00:01:04 +0200
`perldoc POSIX` documents the following under strftime:
> For example, the specifiers `aAbBcpZ` change according to the locale
> settings of the user, [..]
Tested this by running the following, before and after:
export LC_TIME=de_DE.UTF-8
unset LC_ALL # ensure LC_TIME is used
pmgreport
.. and verifying that the date header is now correct.
[0] https://forum.proxmox.com/threads/datumsprobleme-mit-den-pmg-reports.170245
Fixes: 77cfddd ("dkim: local mail: add Date header")
Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
src/PMG/RuleDB/Notify.pm | 3 +--
src/PMG/SMTP.pm | 3 +--
src/PMG/Utils.pm | 28 ++++++++++++++++++++--
src/tests/Makefile | 6 ++++-
src/tests/test_utils.pl | 52 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 85 insertions(+), 7 deletions(-)
create mode 100755 src/tests/test_utils.pl
diff --git a/src/PMG/RuleDB/Notify.pm b/src/PMG/RuleDB/Notify.pm
index caee64b..6adedb2 100644
--- a/src/PMG/RuleDB/Notify.pm
+++ b/src/PMG/RuleDB/Notify.pm
@@ -8,7 +8,6 @@ use MIME::Head;
use MIME::Entity;
use MIME::Words qw(encode_mimewords);
use Encode qw(decode encode);
-use POSIX qw(strftime);
use PVE::SafeSyslog;
@@ -249,7 +248,7 @@ sub execute {
From => $from_header,
To => $to,
Subject => encode_mimewords(encode('UTF-8', $subject), "Charset" => "UTF-8"),
- Date => strftime("%a, %d %b %Y %T %z", localtime()),
+ Date => PMG::Utils::format_date_header(localtime()),
Data => encode('UTF-8', $body),
);
diff --git a/src/PMG/SMTP.pm b/src/PMG/SMTP.pm
index 58c952d..dcb2795 100644
--- a/src/PMG/SMTP.pm
+++ b/src/PMG/SMTP.pm
@@ -5,7 +5,6 @@ use warnings;
use IO::Socket;
use Encode;
use MIME::Entity;
-use POSIX qw(strftime);
use PVE::SafeSyslog;
@@ -295,7 +294,7 @@ EOF
Type => 'multipart/report; report-type=delivery-status;',
To => $sender,
From => $from_header,
- Date => strftime("%a, %d %b %Y %T %z", localtime()),
+ Date => PMG::Utils::format_date_header(localtime()),
Subject => 'Undelivered Mail',
);
diff --git a/src/PMG/Utils.pm b/src/PMG/Utils.pm
index 890096f..0e47ad2 100644
--- a/src/PMG/Utils.pm
+++ b/src/PMG/Utils.pm
@@ -26,7 +26,7 @@ use MIME::Words;
use Net::Cmd;
use Net::IP;
use Net::SMTP;
-use POSIX qw(strftime);
+use POSIX qw(strftime setlocale);
use RRDs;
use Socket;
use Time::HiRes qw (gettimeofday);
@@ -1360,7 +1360,7 @@ sub finalize_report {
Type => ($html && $plaintext) ? 'multipart/alternative' : 'multipart/related',
To => $data->{pmail_raw},
From => $mailfrom,
- Date => strftime("%a, %d %b %Y %T %z", localtime()),
+ Date => format_date_header(localtime()),
Subject => bencode_header(decode_entities($title)),
);
@@ -1726,4 +1726,28 @@ sub test_regex {
return undef;
}
+=head3 format_date_header
+
+Returns a RFC2822-formatted timestamp for usage in the Date header.
+
+Takes the same parameters as C<POSIX::strftime(fmt, ..)>, i.e. C<$sec>,
+C<$min>, C<$hour>, C<$mday>, C<$mon>, C<$year>, C<$wday>, C<$yday>, C<$isdst>
+in that exact order - as e.g. returned by C<localtime()>.
+
+For example:
+
+ use PMG::Utils;
+ my $date = PMG::Utils::format_date_header(localtime());
+
+=cut
+
+sub format_date_header {
+ # ensure that we always use the right locale for formatting `Date` headers
+ my $old_locale = setlocale(POSIX::LC_TIME, 'C') // 'C';
+ my $date = strftime('%a, %d %b %Y %T %z', @_);
+ setlocale(POSIX::LC_TIME, 'C');
+
+ return $date;
+}
+
1;
diff --git a/src/tests/Makefile b/src/tests/Makefile
index dc35796..68f77e4 100644
--- a/src/tests/Makefile
+++ b/src/tests/Makefile
@@ -4,7 +4,8 @@ export PERLLIB = ..
all:
-check:
+.PHONY: check
+check: test-utils
./create_testdb.pl
./init_testdb.pl
./print_testdb.pl > testdb.txt.new
@@ -17,6 +18,9 @@ check:
# test_proxy.pl \
# test_unpack.pl
+.PHONY: test-utils
+test-utils:
+ ./test_utils.pl
clean:
rm -rf *~ proxytest_report.out test.cfg testdb.txt.new
diff --git a/src/tests/test_utils.pl b/src/tests/test_utils.pl
new file mode 100755
index 0000000..8abcc8b
--- /dev/null
+++ b/src/tests/test_utils.pl
@@ -0,0 +1,52 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use POSIX qw(setlocale strftime);
+
+use PMG::Utils;
+
+subtest 'format_date_header works' => sub {
+ cmp_ok(length(PMG::Utils::format_date_header(localtime())), '>=', 30);
+ is(
+ PMG::Utils::format_date_header(47, 55, 12, 1, 8, 125, 1, 243, 1),
+ 'Mon, 01 Sep 2025 12:55:47 +0200',
+ );
+ is(
+ PMG::Utils::format_date_header(59, 2, 8, 2, 0, 125, 4, 2, 0),
+ 'Thu, 02 Jan 2025 08:02:59 +0100',
+ );
+};
+
+subtest 'format_date_header works with other locales' => sub {
+ # also check correctness under some other locale
+ my $old_locale = setlocale(POSIX::LC_TIME);
+
+ if (!defined(setlocale(POSIX::LC_TIME, "de_DE.UTF-8"))) {
+ # if the locale is not available, setlocale() returns undef
+ # in that case, the tests below do not make sense
+ plan(skip_all => "due to 'de_DE.UTF-8' locale not available");
+ }
+
+ # first check if the other locale indeed produces another format
+ is(
+ strftime('%a, %d %b %Y %T %z', 47, 55, 12, 1, 8, 125, 1, 243, 1),
+ 'Mo, 01 Sep 2025 12:55:47 +0200',
+ );
+
+ cmp_ok(length(PMG::Utils::format_date_header(localtime())), '>=', 30);
+ is(
+ PMG::Utils::format_date_header(47, 55, 12, 1, 8, 125, 1, 243, 1),
+ 'Mon, 01 Sep 2025 12:55:47 +0200',
+ );
+ is(
+ PMG::Utils::format_date_header(59, 2, 8, 2, 0, 125, 4, 2, 0),
+ 'Thu, 02 Jan 2025 08:02:59 +0100',
+ );
+
+ setlocale(POSIX::LC_TIME, $old_locale);
+};
+
+done_testing();
--
2.50.1
More information about the pmg-devel
mailing list