lib/PublicInbox/Reply.pm | 9 ++++++---
t/plack.t | 1 +
diff --git a/lib/PublicInbox/Reply.pm b/lib/PublicInbox/Reply.pm
index d96fadfc8b3bfc8eec1c8400218a88fa4280f77b..592dfb624f7a7091af7cdf4bd935d09624488d74 100644
--- a/lib/PublicInbox/Reply.pm
+++ b/lib/PublicInbox/Reply.pm
@@ -1,10 +1,10 @@
-# Copyright (C) 2014-2021 all contributors
+# Copyright (C) all contributors
# License: AGPL-3.0+
# For reply instructions and address generation in WWW UI
package PublicInbox::Reply;
use strict;
-use warnings;
+use v5.10.1;
use URI::Escape qw/uri_escape_utf8/;
use PublicInbox::Hval qw(ascii_html obfuscate_addrs mid_href);
use PublicInbox::Address;
@@ -81,7 +81,6 @@ push @arg, "--to=$arg_to";
# no $subj for $href below
} else {
push @arg, "--to=$to";
- $to = uri_escape_utf8($to);
$subj = uri_escape_utf8($subj);
}
my @cc = sort values %$cc;
@@ -105,6 +104,10 @@ # I'm not sure if address obfuscation and mailto: links can
# be made compatible; and address obfuscation is misguided,
# anyways.
return (\@arg, '', $reply_to_all) if $obfs;
+
+ # keep `@' instead of using `%40' for RFC 6068
+ utf8::encode($to);
+ $to =~ s!([^A-Za-z0-9\-\._~\@])!$URI::Escape::escapes{$1}!ge;
# order matters, Subject is the least important header,
# so it is last in case it's lost/truncated in a copy+paste
diff --git a/t/plack.t b/t/plack.t
index e4dedce6a8444454c5d127d9feeddf11e66e2d75..a5fd54c9981c3c845b05262085e789ed6b867155 100644
--- a/t/plack.t
+++ b/t/plack.t
@@ -85,6 +85,7 @@ test_psgi($app, sub {
my ($cb) = @_;
my $res = $cb->(GET('http://example.com/test/crlf@example.com/'));
is($res->code, 200, 'retrieved CRLF as HTML');
+ like($res->content, qr/mailto:me\@example/, 'no %40, per RFC 6068');
unlike($res->content, qr/\r/, 'no CR in HTML');
$res = $cb->(GET('http://example.com/test/crlf@example.com/raw'));
is($res->code, 200, 'retrieved CRLF raw');