Skip to content

Commit

Permalink
Merge pull request #4926 from cyrusimap/cyrus_eai
Browse files Browse the repository at this point in the history
Improve handling of 8bit message content and `message/global`
  • Loading branch information
rsto committed Jul 26, 2024
2 parents 8e5a485 + 65e9338 commit 59fef69
Show file tree
Hide file tree
Showing 48 changed files with 3,558 additions and 1,402 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -642,8 +642,8 @@ cunit_TESTS = \
cunit/mboxname.testc \
cunit/md5.testc \
cunit/message.testc \
cunit/message_iter_msgid.testc \
cunit/message_guid.testc \
cunit/msgid.testc \
cunit/parseaddr.testc \
cunit/parse.testc \
cunit/proc.testc \
Expand Down
2 changes: 1 addition & 1 deletion cassandane/Cassandane/Cyrus/SearchFuzzy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ sub get_snippets
sub run_delve {
my ($self, $dir, @args) = @_;
my $basedir = $self->{instance}->{basedir};
my @myargs = ('delve');
my @myargs = ('xapian-delve');
push(@myargs, @args);
push(@myargs, $dir);
$self->{instance}->run_command({redirects => {stdout => "$basedir/delve.out"}}, @myargs);
Expand Down
8 changes: 4 additions & 4 deletions cassandane/Cassandane/Instance.pm
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,10 @@ sub _find_binary

my $base = $self->{cyrus_destdir} . $self->{cyrus_prefix};

if ($name eq 'delve') {
if ($name =~ m/xapian-.*$/) {
my $lib = `ldd $base/libexec/imapd` || die "can't ldd imapd";
$lib =~ m{(/\S+)/lib/libxapian-([0-9.]+)\.so};
return "$1/bin/xapian-delve-$2";
return "$1/bin/$name-$2";
}

foreach (qw( bin sbin libexec libexec/cyrus-imapd lib cyrus/bin ))
Expand Down Expand Up @@ -571,7 +571,7 @@ sub _binary
my $cassini = Cassandane::Cassini->instance();

if ($cassini->bool_val('valgrind', 'enabled') &&
!($name =~ m/delve$/) &&
!($name =~ m/xapian.*$/) &&
!($name =~ m/\.pl$/) &&
!($name =~ m/^\//))
{
Expand Down Expand Up @@ -1958,7 +1958,7 @@ sub _fork_command
{
push(@cmd, $self->_binary($binary), '-C', $self->_imapd_conf());
}
elsif ($binary eq 'delve') {
elsif ($binary =~ m/xapian.*$/) {
push(@cmd, $self->_binary($binary));
}
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ EOF
attachments => [{
blobId => '#img',
name => "logo.gif",
type => 'image/gif',
}],
keywords => { '$draft' => JSON::true },
} } }, 'R1'],
Expand Down
147 changes: 147 additions & 0 deletions cassandane/tiny-tests/JMAPEmail/email_get_encoded_message
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!perl
use Cassandane::Tiny;
use MIME::Base64 qw(encode_base64);
use MIME::QuotedPrint qw(encode_qp);

sub encode_qp_strip_crlf
{
# encode_qp encodes CR in CRLF, so remove CR before encoding
my ($string, $eol) = @_;
(my $stripped_string = $string) =~ s/\r\n/\n/g;
return encode_qp($stripped_string, $eol);
}

sub test_email_get_encoded_message
:needs_component_jmap :NoMunge8Bit :RFC2047_UTF8
{
my ($self) = @_;
my $imap = $self->{store}->get_client();
my $jmap = $self->{jmap};
$jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail');

# Define some MIME message. Its content doesn't matter,
# but let's mix an 8bit character in the headers to
# make the content transfer encoding actually matter.
my $mime8Bit = <<'EOF';
From: hello@example.com
To: world@example.com
Subject: töst
Date: Thu, 20 May 2004 14:28:51 +0200
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8
täst
EOF
$mime8Bit =~ s/\r?\n/\r\n/gs;
my $mime8BitSize = bytes::length($mime8Bit);

# Define test cases.
my @tests = ({
type => 'message/rfc822',
encoding => '8bit',
body => $mime8Bit,
}, {
type => 'message/global',
encoding => '8bit',
body => $mime8Bit,
}, {
type => 'message/rfc822',
encoding => 'base64',
body => encode_base64($mime8Bit, "\015\012"),
}, {
type => 'message/global',
encoding => 'base64',
body => encode_base64($mime8Bit, "\015\012"),
}, {
type => 'message/rfc822',
encoding => 'quoted-printable',
body => encode_qp_strip_crlf($mime8Bit, "\015\012"),
}, {
type => 'message/global',
encoding => 'quoted-printable',
body => encode_qp_strip_crlf($mime8Bit, "\015\012"),
});

my $res = $jmap->CallMethods([
['Email/get', { ids => [] }, 'R1']
]);
my $state = $res->[0][1]{state};
$self->assert_not_null($state);

# Run the tests.
while (my ($i, $tc) = each @tests) {
my $imapUid = $i + 1;
my $testId = "test$imapUid";

xlog $self, "Testing content-type $tc->{type} and encoding $tc->{encoding}";

xlog $self, "Delivering test message";
my $mime = <<"EOF";
From: from\@local
To: to\@local
Subject: $testId
Date: Thu, 20 May 2004 14:28:51 +0200
Content-Type: multipart/mixed; boundary=8c438cf1-a6ac-4d99-b388-b1dfd9550725=_
Mime-Version: 1.0
--8c438cf1-a6ac-4d99-b388-b1dfd9550725=_
Content-Type: text/plain; charset=utf-8
test
--8c438cf1-a6ac-4d99-b388-b1dfd9550725=_
Content-Type: $tc->{type}
Content-Transfer-Encoding: $tc->{encoding}
$tc->{body}
--8c438cf1-a6ac-4d99-b388-b1dfd9550725=_--
EOF
$mime =~ s/\r?\n/\r\n/gs;

my $msg = Cassandane::Message->new();
$msg->set_lines(split /\n/, $mime);
$self->{instance}->deliver($msg);

$res = $jmap->CallMethods([
['Email/changes', {
sinceState => $state
}, 'R1'],
['Email/get', {
'#ids' => {
resultOf => 'R1',
name => 'Email/changes',
path => '/created',
},
properties => ['subject', 'bodyStructure'],
bodyProperties => [
'blobId',
'header:content-transfer-encoding:asText',
'partId',
'size',
'type',
],
}, 'R2'],
]);
$self->assert_str_equals($testId, $res->[1][1]{list}[0]{subject});
# Update Email state.
$state = $res->[0][1]{newState};

xlog $self, "Assert Email/get returns expected message";
my $bodyPart = $res->[1][1]{list}[0]{bodyStructure}{subParts}[1];
$self->assert_str_equals("2", $bodyPart->{partId});
$self->assert_str_equals($tc->{type}, $bodyPart->{type});
$self->assert_str_equals($tc->{encoding},
$bodyPart->{'header:content-transfer-encoding:asText'});
$self->assert_num_equals($mime8BitSize, $bodyPart->{size});

xlog $self, "Assert Blob download";
$res = $self->download('cassandane', $bodyPart->{blobId});
$self->assert_str_equals($mime8Bit, $res->{content});

# While we are at it let's check IMAP, too.
xlog $self, "Assert IMAP FETCH BINARY";
$imap->select('INBOX');
$res = $imap->fetch($imapUid, '(BINARY[2])');
$self->assert_str_equals($mime8Bit, $res->{$imapUid}{binary});
}
}
68 changes: 68 additions & 0 deletions cassandane/tiny-tests/JMAPEmail/email_get_header_nfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!perl
use Cassandane::Tiny;
use MIME::Base64 qw(encode_base64);
use MIME::QuotedPrint qw(encode_qp);

sub test_email_get_header_nfc
:needs_component_jmap :NoMunge8Bit :RFC2047_UTF8
{
my ($self) = @_;
my $imap = $self->{store}->get_client();
my $jmap = $self->{jmap};
$jmap->AddUsing('https://cyrusimap.org/ns/jmap/mail');

my $nonNfcEmailAddress =
"\N{U+1F71}\N{U+1F73}\N{U+1F75}" . '@' .
"\N{U+1F77}\N{U+1F79}\N{U+1F7B}.local";
my $normalizedEmailAddress =
"\N{U+03AC}\N{U+03AD}\N{U+03AE}" . '@' .
"\N{U+03AF}\N{U+03CC}\N{U+03CD}.local";
my $normalizedEmailAddressEncoded =
"\N{U+03AC}\N{U+03AD}\N{U+03AE}" . '@' .
"xn--kxa2dd.local";

my $nonNfcXHeaderValue = "0.5\N{U+212B}";
my $normalizedXHeaderValue = "0.5\N{U+00C5}";

my $mime = <<"EOF";
From: from\@local
To: $nonNfcEmailAddress
Subject: test
Date: Mon, 27 May 2024 02:31:37 -0400
Content-Type: text/plain;charset=utf-8
X-My-Header: $nonNfcXHeaderValue
Mime-Version: 1.0
test
EOF
$mime =~ s/\r?\n/\r\n/gs;

my $msg = Cassandane::Message->new();
$msg->set_lines(split /\n/, $mime);
$self->{instance}->deliver($msg);

$res = $jmap->CallMethods([
['Email/query', { }, 'R1'],
['Email/get', {
'#ids' => {
resultOf => 'R1',
name => 'Email/query',
path => '/ids',
},
properties => [
'to',
'header:to',
'header:x-my-header:asText',
'header:x-my-header',
],
}, 'R2'],
]);
$self->assert_str_equals($normalizedEmailAddressEncoded,
$res->[1][1]{list}[0]{to}[0]{email});
$self->assert_str_equals(" $nonNfcEmailAddress",
$res->[1][1]{list}[0]{'header:to'});
$self->assert_str_equals($normalizedXHeaderValue,
$res->[1][1]{list}[0]{'header:x-my-header:asText'});
$self->assert_str_equals(" $nonNfcXHeaderValue",
$res->[1][1]{list}[0]{'header:x-my-header'});
}
Loading

0 comments on commit 59fef69

Please sign in to comment.