summaryrefslogtreecommitdiff
path: root/lib/JWebmail/Plugin
diff options
context:
space:
mode:
authorJannis M. Hoffmann <jannis@fehcom.de>2023-03-13 21:34:03 +0100
committerJannis M. Hoffmann <jannis@fehcom.de>2023-03-13 21:34:03 +0100
commit8ee2d7149baa58ea225cb40e0f95030ee21f1081 (patch)
tree11d2bd52f36d3d566f9abcb30654b9bd78e56422 /lib/JWebmail/Plugin
parent6441b5ad6657873fcd8f3695515fa6ef3bc4e6f5 (diff)
Split up Helper plugin and added Views instead
Diffstat (limited to 'lib/JWebmail/Plugin')
-rw-r--r--lib/JWebmail/Plugin/Helper.pm426
-rw-r--r--lib/JWebmail/Plugin/Paginate.pm145
-rw-r--r--lib/JWebmail/Plugin/RenderMail.pm182
3 files changed, 145 insertions, 608 deletions
diff --git a/lib/JWebmail/Plugin/Helper.pm b/lib/JWebmail/Plugin/Helper.pm
deleted file mode 100644
index b298a17..0000000
--- a/lib/JWebmail/Plugin/Helper.pm
+++ /dev/null
@@ -1,426 +0,0 @@
-package JWebmail::Plugin::Helper;
-
-use Mojo::Base Mojolicious::Plugin;
-
-use List::Util qw(all any min max);
-use Carp 'carp';
-use POSIX qw(floor round log ceil);
-
-use Mojo::Util qw(encode decode b64_encode b64_decode xml_escape);
-
-use constant TRUE_RANDOM => eval { require Crypt::URandom; Crypt::URandom->import('urandom'); 1 };
-
-
-### filter and checks for mojo validator
-
-sub mail_line {
- my ($v, $name, $value, @args) = @_;
-
- my $mail_addr = qr/\w+\@\w+\.\w+/;
- # my $unescaped_quote = qr/"(*nlb:\\)/; # greater perl version required
- my $unescaped_quote = qr/"(?<!:\\)/;
-
- return $value !~ /^(
- (
- (
- (
- $unescaped_quote.*?$unescaped_quote
- ) | (
- [\w\s]*
- )
- )
- \s*<$mail_addr>
- ) | (
- $mail_addr
- ))$
- /xn;
-}
-
-sub filter_empty_upload {
- my ($v, $name, $value) = @_;
-
- return $value->filename ? $value : undef;
-}
-
-
-### template formatting functions
-
-sub print_sizes10 {
- my $var = shift || return '0 Byte';
-
- my $i = floor(((log($var)/log(10))+1e-5) / 3);
- my $expo = $i * 3;
-
- my @PREFIX;
- $PREFIX[0] = 'Byte';
- $PREFIX[1] = 'kByte';
- $PREFIX[2] = 'MByte';
- $PREFIX[3] = 'GByte';
- $PREFIX[4] = 'TByte';
- $PREFIX[5] = 'PByte';
-
- return sprintf('%.0f %s', $var / (10**$expo), $PREFIX[$i]);
-}
-
-sub print_sizes2 {
- my $var = shift || return '0 Byte';
-
- my $i = floor(((log($var)/log(2))+1e-5) / 10);
- my $expo = $i * 10;
- my %PREFIX = (
- 0 => 'Byte',
- 1 => 'KiByte',
- 2 => 'MiByte',
- 3 => 'GiByte',
- 4 => 'TiByte',
- 5 => 'PiByte',
- );
- my $pref = $PREFIX{$i};
- return round($var / (2**$expo)) . " $pref";
-}
-
-my sub dgt { "([[:digit:]]{$_[0]})" }
-
-sub parse_iso_date {
- state $rx = do { my $re = dgt(4).'-'.dgt(2).'-'.dgt(2).'T'.dgt(2).':'.dgt(2).':'.dgt(2); qr/$re/a };
- my @d = shift =~ /$rx/;
- if (@d != 6) {
- # TODO
- warn "issue when parsing date";
- }
- return {
- year => $d[0],
- month => $d[1],
- mday => $d[2],
- hour => $d[3],
- min => $d[4],
- sec => $d[5],
- };
-}
-
-
-### session password handling
-
-use constant { S_PASSWD => 'pw', S_OTP_S3D_PW => 'otp_s3d_pw' };
-
-sub _rand_data {
- my $len = shift;
-
- if (TRUE_RANDOM) {
- #return makerandom_octet(Length => $len, Strength => 0); # was used for Crypt::Random
- return urandom($len);
- }
- else {
- my $res = '';
- for (0..$len-1) {
- vec($res, $_, 8) = int rand 256;
- }
-
- return $res;
- }
-}
-
-sub session_passwd {
- my ($c, $passwd, $challenge) = @_;
- my $secAlg = $c->config->{session}{secure};
-
- warn_crypt($c);
-
- if (defined $passwd) { # set
- if ($secAlg eq 'cram') {
- $c->session(S_PASSWD() => $passwd, challenge => $challenge);
- }
- elsif ($secAlg eq 's3d') {
- unless ($passwd) {
- $c->s3d(S_PASSWD, '');
- delete $c->session->{S_OTP_S3D_PW()};
- return;
- }
- die "'$passwd' contains invalid character \\n" if $passwd =~ /\n/;
- if (length $passwd < 20) {
- $passwd .= "\n" . ' ' x (20 - length($passwd) - 1);
- }
- my $passwd_utf8 = encode('UTF-8', $passwd);
- my $rand_bytes = _rand_data(length $passwd_utf8);
- $c->s3d(S_PASSWD, b64_encode($passwd_utf8 ^ $rand_bytes, ''));
- $c->session(S_OTP_S3D_PW, b64_encode($rand_bytes, ''));
- }
- else {
- $c->session(S_PASSWD() => $passwd);
- }
- }
- else { # get
- if ($secAlg eq 'cram') {
- wantarray or carp "you forgot the challenge";
- return ($c->session(S_PASSWD), $c->session('challenge'));
- }
- elsif ($secAlg eq 's3d') {
- my $pw = b64_decode($c->s3d(S_PASSWD) || '');
- my $otp = b64_decode($c->session(S_OTP_S3D_PW) || '');
- my ($res) = split "\n", decode('UTF-8', $pw ^ $otp), 2;
- return $res;
- }
- else {
- return $c->session(S_PASSWD);
- }
- }
-}
-
-sub warn_crypt {
- my $c = shift;
-
- state $once = 0;
-
- if ( !TRUE_RANDOM && !$once && lc $c->config->{session}{secure} eq 's3d' ) {
- $c->log->warn("Falling back to pseudo random generation. Please install Crypt::URandom");
- $once = 1;
- }
-}
-
-
-### pagination
-
-sub _clamp {
- my ($x, $y, $z) = @_;
-
- die '!($x <= $z)' unless $x <= $z;
-
- if ($x <= $y && $y <= $z) {
- return $y;
- }
-
- return $x if ($y < $x);
- return $z if ($z < $y);
-}
-
-sub _paginate {
- my %args = @_;
-
- my $first_item = $args{first_item};
- my $page_size = $args{page_size};
- my $total_items = $args{total_items};
-
- my $current_page = ceil($first_item/$page_size);
- my $total_pages = ceil($total_items/$page_size);
-
- my $page = sub {
- my $page_ = shift;
- return [0, 0] unless $total_items;
- $page_ = _clamp(0, $page_, $total_pages-1);
- [_clamp(0, $page_*$page_size, $total_items-1), _clamp(0, ($page_+1)*$page_size, $total_items)]
- };
-
- my $ret = {
- total_items => $total_items,
- page_size => $page_size,
-
- total_pages => $total_pages,
- current_page => $current_page,
-
- first_page => $page->(0),
- prev_page => $page->($current_page-1),
- this_page => $page->($current_page),
- next_page => $page->($current_page+1),
- last_page => $page->($total_pages-1),
- };
-
- if ($total_items) {
- $ret->{first_item} = $first_item;
- $ret->{last_item} = _clamp($first_item, $first_item+$page_size-1, $total_items-1);
- }
-
- return $ret;
-}
-
-sub paginate {
- my $c = shift;
- my ($count) = @_;
-
- my $v = $c->validation;
- my $start = $v->optional('start')->num(0, undef)->param // 0;
- my $psize = $v->optional('page_size')->num(1, undef)->param // 50;
-
- $start = _clamp(0, $start, max($count-1, 0));
- my $end = _clamp($start, $start+$psize, max($count, 0));
-
- $c->stash(pgn => _paginate(
- first_item => int($start/$psize)*$psize,
- page_size => $psize,
- total_items => $count,
- ));
-
- return $start, $end;
-}
-
-
-### registering
-
-sub register {
- my ($self, $app, $conf) = @_;
- $conf //= {};
-
- if (ref $conf->{import} eq 'ARRAY' and my @import = @{ $conf->{import} }) {
- my sub contains { any { $_[0] eq $_ } @import }
-
- # selective import
- $app->helper(print_sizes10 => sub { shift; print_sizes10(@_) })
- if contains 'print_sizes10';
- $app->helper(parse_iso_date => sub { shift; parse_iso_date(@_) })
- if contains 'parse_iso_date';
- $app->helper(print_sizes2 => sub { shift; print_sizes2(@_) })
- if contains 'print_sizes2';
- $app->helper(mime_render => \&mime_render)
- if contains 'mime_render';
- $app->helper(session_passwd => \&session_passwd)
- if contains 'session_passwd';
- $app->helper(paginate => \&paginate)
- if contains 'paginate';
- $app->validator->add_check(mail_line => \&mail_line)
- if contains 'mail_line';
- $app->validator->add_filter(non_empty_ul => \&filter_empty_upload)
- if contains 'non_empty_ul';
- }
- elsif (!$conf->{import}) { # default imports
- $app->helper(print_sizes10 => sub { shift; print_sizes10(@_) });
- $app->helper(parse_iso_date => sub { shift; parse_iso_date(@_) });
- $app->helper(mime_render => \&mime_render);
- $app->helper(session_passwd => \&session_passwd);
- $app->helper(paginate => \&paginate);
-
- $app->validator->add_check(mail_line => \&mail_line);
-
- $app->validator->add_filter(non_empty_ul => \&filter_empty_upload);
- }
- else {
- die 'unkown value for "import"'
- }
-}
-
-
-1
-
-__END__
-
-=encoding utf-8
-
-=head1 NAME
-
-Helper - Functions used as helpers in controller and templates and additional validator checks and filters
-
-=head1 SYNOPSIS
-
- use Mojo::Base 'Mojolicious';
-
- sub startup($self) {
- $app->plugin('Helper');
- }
-
-=head1 DESCRIPTION
-
-L<JWebmail::Helper> provides useful helper functions and validator cheks and filter for
-L<JWebmail::Controller::All> and various templates.
-
-=head1 HELPERS
-
-=head2 mail_line
-
-A check for validator used in mail headers for fields containing email addresses.
-
- $app->validator->add_check(mail_line => \&JWebmail::Plugin::Helper::mail_line);
-
- my $v = $c->validation;
- $v->required('to', 'not_empty')->check('mail_line');
-
-=head2 filter_empty_upload
-
-A filter for validator used to filter out empty uploads.
-
- $app->validator->add_filter(non_empty_ul => \&JWebmail::Plugin::Helper::filter_empty_upload);
-
- my $v = $c->validation;
- $v->required('file_upload', 'non_empty_ul');
-
-=head2 print_sizes10
-
-A helper for templates used to format byte sizes.
-
- $app->helper(print_sizes10 => sub { shift; JWebmail::Plugin::Helper::print_sizes10(@_) });
-
- %= print_sizes10 12345 # => 12 kB
-
-=head2 print_sizes2
-
-A helper for templates used to format byte sizes.
-
- %= print_sizes10 12345 # => 12 KiB
-
-This is not registered by default.
-
-=head2 paginate
-
-A helper for calculating page bounds.
-
-Takes the total number of items as argument.
-
-Reads in 'start' and 'page_size' query arguments.
-start is 0 based.
-
-Returns the calculated start and end points as 0 based inclusive range.
-
-Sets the stash values (all 1 based inclusive):
-
- first_item
- last_item
- total_items
- page_size
- total_pages
- current_page
- first_page
- prev_page
- next_page
- last_page
-
-=head2 session_passwd
-
-A helper used to set and get the session password. The behavior can be altered by
-setting the config variable C<< session => {secure => 's3d'} >>.
-
- $app->helper(session_passwd => \&JWebmail::Plugin::Helper::session_passwd);
-
- $c->session_passwd('s3cret');
-
-Currently the following modes are supported:
-
-=over 6
-
-=item none
-
-The password is plainly stored in session cookie.
-The cookie is stored on the client side and send with every request.
-
-=item cram
-
-A nonce is send to the client and the cram_md5 is generated there via js
-and crypto-js.
-This is vulnurable to replay attacks as the nonce is not invalidated ever.
-
-=item s3d
-
-The password is stored on the server. Additionally the password is encrypted
-by an one-time-pad that is stored in the users cookie.
-This is vulnurable to replay attacks during an active session.
-On log-in it is transfered plainly.
-
-=back
-
-=head1 DEPENDENCIES
-
-Mojolicious and recommended Crypt::URandom.
-
-=head1 SEE ALSO
-
-L<JWebmail>
-
-=head1 NOTICE
-
-This package is part of JWebmail.
diff --git a/lib/JWebmail/Plugin/Paginate.pm b/lib/JWebmail/Plugin/Paginate.pm
new file mode 100644
index 0000000..1a48ed3
--- /dev/null
+++ b/lib/JWebmail/Plugin/Paginate.pm
@@ -0,0 +1,145 @@
+package JWebmail::Plugin::Paginate;
+
+use Mojo::Base Mojolicious::Plugin;
+
+use List::Util qw(any max);
+use POSIX 'ceil';
+
+
+sub _clamp {
+ my ($x, $y, $z) = @_;
+
+ die '!($x <= $z)' unless $x <= $z;
+
+ if ($x <= $y && $y <= $z) {
+ return $y;
+ }
+
+ return $x if ($y < $x);
+ return $z if ($z < $y);
+}
+
+sub _paginate {
+ my %args = @_;
+
+ my $first_item = $args{first_item};
+ my $page_size = $args{page_size};
+ my $total_items = $args{total_items};
+
+ my $current_page = ceil($first_item/$page_size);
+ my $total_pages = ceil($total_items/$page_size);
+
+ my $page = sub {
+ my $page_ = shift;
+ return [0, 0] unless $total_items;
+ $page_ = _clamp(0, $page_, $total_pages-1);
+ [_clamp(0, $page_*$page_size, $total_items-1), _clamp(0, ($page_+1)*$page_size, $total_items)]
+ };
+
+ my $ret = {
+ total_items => $total_items,
+ page_size => $page_size,
+
+ total_pages => $total_pages,
+ current_page => $current_page,
+
+ first_page => $page->(0),
+ prev_page => $page->($current_page-1),
+ this_page => $page->($current_page),
+ next_page => $page->($current_page+1),
+ last_page => $page->($total_pages-1),
+ };
+
+ if ($total_items) {
+ $ret->{first_item} = $first_item;
+ $ret->{last_item} = _clamp($first_item, $first_item+$page_size-1, $total_items-1);
+ }
+
+ return $ret;
+}
+
+sub paginate {
+ my $c = shift;
+ my ($count) = @_;
+
+ my $v = $c->validation;
+ my $start = $v->optional('start')->num(0, undef)->param // 0;
+ my $psize = $v->optional('page_size')->num(1, undef)->param // 50;
+
+ $start = _clamp(0, $start, max($count-1, 0));
+ my $end = _clamp($start, $start+$psize, max($count, 0));
+
+ $c->stash(pgn => _paginate(
+ first_item => int($start/$psize)*$psize,
+ page_size => $psize,
+ total_items => $count,
+ ));
+
+ return $start, $end;
+}
+
+
+sub register {
+ my ($self, $app, $conf) = @_;
+ $conf //= {};
+
+ $app->helper(paginate => \&paginate);
+}
+
+
+1
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+JWebmail::Plugin::Paginate
+
+=head1 SYNOPSIS
+
+ sub my_route {
+ my ($c) = @_;
+
+ $c->stash($c->paginate(1234));
+ }
+
+=head1 DESCRIPTION
+
+L<JWebmail::Helper> provides useful helper functions and validator cheks and filter for
+L<JWebmail::Controller::All> and various templates.
+
+=head1 HELPERS
+
+=head2 paginate
+
+A helper for calculating page bounds.
+
+Takes the total number of items as argument.
+
+Reads in 'start' and 'page_size' query arguments.
+start is 0 based.
+
+Returns the calculated start and end points as 0 based inclusive range.
+
+Sets the stash values (all 1 based inclusive):
+
+ first_item
+ last_item
+ total_items
+ page_size
+ total_pages
+ current_page
+ first_page
+ prev_page
+ next_page
+ last_page
+
+=head1 SEE ALSO
+
+L<JWebmail>
+
+=head1 NOTICE
+
+This package is part of JWebmail.
diff --git a/lib/JWebmail/Plugin/RenderMail.pm b/lib/JWebmail/Plugin/RenderMail.pm
deleted file mode 100644
index 22edbbd..0000000
--- a/lib/JWebmail/Plugin/RenderMail.pm
+++ /dev/null
@@ -1,182 +0,0 @@
-package JWebmail::Plugin::RenderMail;
-
-use Mojo::Base 'Mojolicious::Plugin';
-
-use Mojo::ByteStream;
-use Mojo::Util 'xml_escape';
-
-
-sub render_text_plain {
- my ($_c, $_subtype, $content, $_path) = @_;
-
- $content = xml_escape($content);
- $content =~ s/\n/<br>/g;
-
- return qq'<div class="jwm-mail-body jwm-mail-body-text-plain">\n $content </div>\n';
-}
-
-sub render_text_html {
- my ($c, $_subtype, $_content, $path) = @_;
-
- my $url = $c->url_for('raw', id => $c->stash('id'));
- $url = $url->query(path => join('.', @$path)) if @$path;
-
- return qq'<iframe src="$url" class="jwm-mail-body-text-html" ></iframe>\n';
-}
-
-sub render_multipart_alternative {
- my ($c, $_subtype, $content, $path) = @_;
-
- my $parts = $content->{parts};
- my $R = qq'<div class="jwm-mail-body jwm-mail-body-multipart-alternative"\n>';
- my $i = 0;
- my $end;
-
- for (reverse @$parts) {
- if (!$end) {
- my $x = mime_render($c, to_mime_types($_->{head}), $_->{body}, [@$path, $#$parts-$i]);
- if ($x) {
- $R .= $x;
- $end = 1;
- }
- }
- else {
- $R .= '<details class="jwm-mail-body-multipart-alternative-extra" >';
- $R .= '<summary>';
- $R .= to_mime_type($_->{head});
- $R .= "</summary>\n";
- $R .= mime_render($c, to_mime_types($_->{head}), $_->{body}, [@$path, $#$parts-$i]);
- $R .= "</details>\n";
- }
- ++$i;
- }
- return $R . "</div>\n";
-}
-
-sub render_multipart {
- my ($c, $_subtype, $content, $path) = @_;
-
- my $parts = $content->{parts};
- my $R = qq'<div class="jwm-mail-body jwm-mail-body-multipart"\n>';
- my $i = 0;
-
- for (@$parts) {
- if ( !$_->{head}{content_disposition}
- || lc $_->{head}{content_disposition} eq 'none'
- || lc $_->{head}{content_disposition} eq 'inline') {
-
- $R .= mime_render($c, to_mime_types($_->{head}), $_->{body}, [@$path, $i]);
- }
- elsif (lc $_->{head}{content_disposition} eq 'attachment') {
- $R .= '<p>';
- $R .= $c->link_to($c->url_for(raw => id => $c->stash('id'))->query(path => join('.', @$path, $i)), (download => $_->{head}{filename}) => sub {
- 'Attachment ' . xml_escape($_->{head}{filename}) . ' of type ' . to_mime_type($_->{head});
- });
- $R .= "</p>\n";
- }
- else {
- warn "unknown Content-Disposition '$_->{head}{content_disposition}'";
- $R .= "<p>unknown Content-Disposition '$_->{head}{content_disposition}'</p>\n";
- }
- ++$i;
- }
- return $R . "</div>\n";
-}
-
-sub _format_header {
- my ($c, $category, $value) = @_;
-
- my $R = '';
-
- if (ref $value eq 'ARRAY' && $value->@*) {
- $R .= '<dt>' . xml_escape(uc $c->l($category)) . "</dt>\n";
- for ($value->@*) {
- $R .= '<dd>';
- $R .= xml_escape($_->{display_name} ? qq("$_->{display_name}" <$_->{address}>) : "$_->{address}");
- $R .= "<dd>\n";
- }
- }
- return $R;
-}
-
-sub render_message {
- my ($c, $subtype, $msg, $path) = @_;
-
- warn "unkown mime-subtype $subtype" unless $subtype eq 'rfc822';
-
- my $R .= '<div clas="jwm-mail">';
-
- $R .= '<dl class="jwm-mail-header">';
- $R .= '<dt>' . xml_escape(uc $c->l('subject')) . '</dt>';
- $R .= '<dd>' . xml_escape($msg->{head}{subject}) . "</dd>\n";
- $R .= _format_header($c, from => $msg->{head}{from});
- $R .= _format_header($c, to => $msg->{head}{to});
- $R .= _format_header($c, cc => $msg->{head}{cc});
- $R .= _format_header($c, bcc => $msg->{head}{bcc});
- $R .= '<dt>' . xml_escape(uc $c->l('date')) . '</dt>';
- $R .= '<dd>' . xml_escape($msg->{head}{date}) . "</dd>\n";
- $R .= '<dt>' . xml_escape(uc $c->l('content-type')) . '</dt>';
- $R .= '<dd>' . to_mime_type($msg->{head}{mime}) . "</dd>\n";
- $R .= "</dl>\n";
-
- #my $content = ref $msg->{body} && exists $msg->{body}{parts} ? $msg->{body}{parts} : $msg->{body};
-
- $R .= mime_render($c, to_mime_types($msg->{head}{mime}), $msg->{body}, [@$path, 0]);
-
- return $R . "</div>\n";
-}
-
-our %MIME_Render_Subs = (
- 'text/plain' => \&render_text_plain,
- 'text/html' => \&render_text_html,
- 'multipart/alternative' => \&render_multipart_alternative,
- 'multipart' => \&render_multipart,
- 'message' => \&render_message,
-);
-
-sub mime_render {
- my ($c, $maintype, $subtype, $content, $path) = @_;
-
- my $renderer = $MIME_Render_Subs{"$maintype/$subtype"} || $MIME_Render_Subs{$maintype};
-
- unless ($renderer) {
- return "<p>Unsupported MIME type of <code>$maintype/$subtype</code>.</p>\n";
- }
-
- return $renderer->($c, $subtype, $content, $path);
-}
-
-
-sub to_mime_type { lc xml_escape("$_[0]->{content_maintype}/$_[0]->{content_subtype}") }
-sub to_mime_types { return xml_escape($_[0]->{content_maintype}), xml_escape($_[0]->{content_subtype}) }
-
-
-sub register {
- my ($self, $app, $conf) = @_;
- $conf //= {};
-
- $app->helper('render_mail.format_mail' => sub { Mojo::ByteStream->new(mime_render($_[0], 'message', 'rfc822', $_[1], [])) });
- $app->helper(to_mime_type => sub { shift; to_mime_type(@_) });
-}
-
-1
-
-__END__
-
-=encoding utf-8
-
-=head1 NAME
-
-JWebmail::Plugin::RenderMail - Does the heavy lifting of converting an E-Mail to HTML
-
-=head1 HELPERS
-
-=head2 render_mail.format_mail
-
-Renders a mail to html recursively.
-
-=head2 to_mime_type
-
-Combines the content_maintype and content_subtype attributes into the regular MIME description.
-These attributes are found in a mail head mime section or as head for multipart messages.
-