Updated patch, more tests, supersedes the previous two patches.
I've combined the two patches above into one as well as updated the regex
to be more RFC 2822 compliant with regards to whitespace ([ \t] are valid
not \s) and continuations containing _only_ whitespace.
From 9dbc62b96c3ed4cd260684fa8473b9e02f834e8e Mon Sep 17 00:00:00 2001
From: Thomas Sibley <trs@bestpractical.com>
Date: Wed, 3 Oct 2012 14:07:03 -0700
Subject: [PATCH] Reject and warn about bad header continuations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Newlines without a space or tab after them are invalid continuations and
may lead to hidden or invalid headers once the object is stringified.
Newlines with _only_ space after them before the next newline are also
invalid continuations per RFC 2822 § 3.2.3.
Refuse to insert headers with invalid continuations, but allow folding
(when Modify is true) to fix them up automatically.
---
lib/Mail/Header.pm | 10 ++++++++--
t/header.t | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 62 insertions(+), 3 deletions(-)
diff --git a/lib/Mail/Header.pm b/lib/Mail/Header.pm
index 644215d..b275dd2 100644
--- a/lib/Mail/Header.pm
+++ b/lib/Mail/Header.pm
@@ -180,8 +180,14 @@ sub _fmt_line
|| $HDR_LENGTHS{$tag}
|| $self->fold_length;
- _fold_line $line, $maxlen
- if $modify && defined $maxlen;
+ if ($modify && defined $maxlen) {
+ # folding will fix bad header continuations for us
+ _fold_line $line, $maxlen;
+ }
+ elsif ($line =~ /\r?\n([^ \t]|\s+$)/om) {
+ return _error "Bad RFC 2822 header continuation, skipping '$tag': ",
+ "No whitespace or only whitespace after newline in '$line'\n";
+ }
$line =~ s/\n*$/\n/so;
($tag, $line);
diff --git a/t/header.t b/t/header.t
index 4585f2a..2459ce4 100644
--- a/t/header.t
+++ b/t/header.t
@@ -1,6 +1,6 @@
require Mail::Header;
-print "1..25\n";
+print "1..55\n";
$h = new Mail::Header;
@@ -173,3 +173,56 @@ printf "ok %d\n",++$t;
print $h->as_string,"\n----\n",$headout,"\nnot "
unless $h->as_string eq $headout;
printf "ok %d\n",++$t;
+
+{
+ my @warnings;
+ local $SIG{__WARN__} = sub { push @warnings, "@_"; warn @_; };
+
+ my @bad_headers = (
+ "To: foo\@example.com\nBcc: evil\@example.com\n", # inserted header
+ "To: foo\n\n bar\n", # newline doesn't count as WSP
+ "To: foo\n \t\n", # continuation with only WSP
+ "To: foo\n \n bar\n", # middle continuation with only WSP
+ );
+
+ for my $header (@bad_headers) {
+ print "not "
+ unless $h = new Mail::Header [$header];
+ printf "ok %d\n",++$t;
+
+ print "not "
+ unless @warnings == 1 and shift(@warnings) =~ /bad rfc 2822 header continuation/i;
+ printf "ok %d\n",++$t;
+
+ print "not "
+ if $h->get("To") or $h->get("Bcc");
+ printf "ok %d\n",++$t;
+
+ print "not "
+ unless $h = new Mail::Header [$header], Modify => 1;
+ printf "ok %d\n",++$t;
+
+ print "not "
+ if @warnings;
+ printf "ok %d\n",++$t;
+
+ (my $to = $header) =~ s/(^To: |\n(?!\z))//g;
+ $to =~ s/\s*(?=\n\z)//;
+ print "not "
+ unless $h->get("To") eq $to;
+ printf "ok %d\n",++$t;
+
+ print "not "
+ if $h->get("Bcc");
+ printf "ok %d\n",++$t;
+ }
+}
+
+my $continued = "foo\@example.com,\n bar\@example.com\n";
+print "not "
+ unless $h = new Mail::Header ["To: $continued"];
+printf "ok %d\n",++$t;
+
+print "not "
+ unless $h->get("To") eq $continued;
+printf "ok %d\n",++$t;
--
1.7.11.3