summaryrefslogtreecommitdiff
path: root/lib/JWebmail/Controller
diff options
context:
space:
mode:
authorJannis M. Hoffmann <jannis.hoffmann@rwth-aachen.de>2020-10-29 12:13:04 +0100
committerJannis M. Hoffmann <jannis.hoffmann@rwth-aachen.de>2020-10-29 12:13:04 +0100
commitee43823179ee627ac16ea9da8168e5f1bf9619c0 (patch)
tree5e6c36d5629d2ce79f3cb1310998dc715a6f19c7 /lib/JWebmail/Controller
Initial commit; Stable version
Diffstat (limited to 'lib/JWebmail/Controller')
-rw-r--r--lib/JWebmail/Controller/Webmail.pm386
1 files changed, 386 insertions, 0 deletions
diff --git a/lib/JWebmail/Controller/Webmail.pm b/lib/JWebmail/Controller/Webmail.pm
new file mode 100644
index 0000000..3ec93f1
--- /dev/null
+++ b/lib/JWebmail/Controller/Webmail.pm
@@ -0,0 +1,386 @@
+package JWebmail::Controller::Webmail;
+
+use Mojo::Base 'Mojolicious::Controller';
+
+use File::Type;
+
+use constant {
+ S_USER => 'user', # Key for user name in active session
+};
+
+
+# no action has been taken, display login page
+sub noaction {
+ my $self = shift;
+
+ my $user = $self->session(S_USER);
+ if ($user) {
+ $self->res->code(307);
+ $self->redirect_to('home');
+ }
+}
+
+
+# middleware
+sub auth {
+ my $self = shift;
+
+ my $user = $self->session(S_USER);
+ my $pw = $self->session_passwd;
+
+ unless ($user && $pw) {
+ $self->flash(message => $self->l('no_session'));
+ $self->res->code(401);
+ $self->redirect_to('logout');
+ return 0;
+ }
+
+ return 1;
+}
+
+
+sub _time :prototype(&$$) {
+ my $code = shift;
+ my $self = shift;
+ my $name = shift;
+
+ $self->timing->begin($name);
+
+ my @res = $code->();
+
+ my $elapsed = $self->timing->elapsed($name);
+ $self->app->log->debug("$name took $elapsed seconds");
+
+ return wantarray ? @res : $res[-1];
+}
+
+
+sub login {
+ my $self = shift;
+
+ my $v = $self->validation;
+
+ my $user = $v->required('userid')->size(4, 50)->param;
+ my $passwd = $v->required('password')->size(4, 50)->like(qr/^.+$/)->param; # no new-lines
+
+ if ($v->has_error) {
+ $self->res->code(400);
+ return $self->render(action => 'noaction');
+ }
+
+ my $valid = _time { $self->users->verify_user($user, $passwd) } $self, 'verify user';
+
+ if ($valid) {
+ $self->session(S_USER() => $user);
+ $self->session_passwd($passwd);
+
+ $self->res->code(303);
+ $self->redirect_to('displayheaders');
+ }
+ else {
+ $self->res->code(401);
+ $self->render(action => 'noaction',
+ warning => $self->l('login') . ' ' . $self->l('failed') . '!',
+ );
+ }
+}
+
+
+sub logout {
+ my $self = shift;
+
+ delete $self->session->{S_USER()};
+ $self->session_passwd('');
+
+ # $self->session(expires => 1);
+
+ $self->res->code(303);
+ $self->redirect_to('noaction');
+}
+
+
+sub about {
+ my $self = shift;
+
+ $self->stash(
+ scriptadmin => $self->config->{defaults}{scriptadmin},
+ http_host => $self->tx->req->url->to_abs->host,
+ request_uri => $self->tx->req->url,
+ remote_addr => $self->tx->original_remote_address,
+ );
+}
+
+
+sub displayheaders {
+ no warnings 'experimental::smartmatch';
+ my $self = shift;
+
+ my $auth = AuthReadMails->new(
+ user => $self->session(S_USER),
+ password => $self->session_passwd,
+ challenge => $self->app->secrets->[0],
+ );
+
+ my $folders = _time { $self->users->folders($auth) } $self, 'user folders';
+ push @$folders, '';
+
+ unless ( $self->stash('folder') ~~ $folders ) {
+ $self->res->code(404);
+ $self->render(template => 'error',
+ error => $self->l('no_folder'),
+ links => [map { $self->url_for(folder => $_) } @$folders],
+ );
+ return;
+ }
+
+ my $v = $self->validation;
+ my $sort = $v->optional('sort')->like(qr'^!?(?:date|subject|sender|size)$')->param // '!date';
+ my $search = $v->optional('search')->param;
+
+ if ($v->has_error) {
+ $self->res->code(400);
+ $self->render(template => 'error', error => "errors in @{ $v->failed }");
+ return;
+ }
+
+ my ($total_byte_size, $cnt, $new) = _time { $self->users->count($auth, $self->stash('folder')) } $self, 'user count';
+
+ my ($start, $end) = $self->paginate($cnt);
+
+ $self->timing->begin('user_headers');
+ my $headers;
+ if ($search) {
+ $headers = $self->users->search(
+ $auth, $search, $self->stash('folder'),
+ );
+ }
+ else {
+ $headers = $self->users->read_headers_for(
+ auth => $auth,
+ folder => $self->stash('folder'),
+ start => $start,
+ end => $end,
+ sort => $sort,
+ );
+ }
+ my $elapsed = $self->timing->elapsed('user_headers');
+ $self->app->log->debug("Reading user headers took $elapsed seconds");
+
+ $self->stash(
+ msgs => $headers,
+ mail_folders => $folders,
+ total_size => $total_byte_size,
+ total_new_mails => $new,
+ );
+}
+
+
+sub readmail {
+ my $self = shift;
+
+ my $mid = $self->stash('id');
+
+ my $auth = AuthReadMails->new(
+ user => $self->session(S_USER),
+ password => $self->session_passwd,
+ challenge => $self->app->secrets->[0],
+ );
+
+ my $mail;
+ eval { $mail = $self->users->show($auth, $mid) };
+ if (my $err = $@) {
+ if ($err =~ m/unkown mail-id|no such message/) {
+ $self->reply->not_found;
+ return;
+ }
+ die $@;
+ }
+
+ $self->render(action => 'readmail',
+ msg => $mail,
+ );
+}
+
+
+sub writemail { }
+
+
+sub sendmail {
+ my $self = shift;
+
+ my %mail;
+ my $v = $self->validation;
+ $v->csrf_protect;
+
+ $mail{to} = $v->required('to', 'not_empty')->check('mail_line')->every_param;
+ $mail{message} = $v->required('body', 'not_empty')->param;
+ $mail{subject} = $v->required('subject', 'not_empty')->param;
+ $mail{cc} = $v->optional('cc', 'not_empty')->check('mail_line')->every_param;
+ $mail{bcc} = $v->optional('bcc', 'not_empty')->check('mail_line')->every_param;
+ $mail{reply} = $v->optional('back_to', 'not_empty')->check('mail_line')->param;
+ $mail{attach} = $v->optional('attach', 'non_empty_ul')->upload->param;
+ $mail{attach_type} = File::Type->new()->mime_type($mail{attach}->asset->get_chunk(0, 512)) if $mail{attach};
+ $mail{from} = $self->session(S_USER);
+
+ if ($v->has_error) {
+ $self->log->debug("mail send failed. Error in @{ $v->failed }");
+
+ $self->render(action => 'writemail',
+ warning => $self->l('error_send'),
+ );
+ return;
+ }
+
+ my $error = $self->send_mail(\%mail);
+
+ if ($error) {
+ $v->error('send'=> ['internal_error']); # make validation fail so that values are restored
+
+ $self->render(action => 'writemail',
+ warning => $self->l('error_send'),
+ );
+ return;
+ }
+
+ $self->flash(message => $self->l('succ_send'));
+ $self->res->code(303);
+ $self->redirect_to('displayheaders');
+}
+
+
+sub move {
+ my $self = shift;
+
+ my $v = $self->validation;
+ $v->csrf_protect;
+
+ if ($v->has_error) {
+ return;
+ }
+
+ my $auth = AuthReadMails->new(
+ user => $self->session(S_USER),
+ password => $self->session_passwd,
+ challenge => $self->app->secrets->[0],
+ );
+ my $folders = $self->users->folders($auth);
+
+ my $mm = $self->every_param('mail');
+ my $folder = $self->param('folder');
+
+ no warnings 'experimental::smartmatch';
+ die "$folder not valid" unless $folder ~~ $folders;
+
+ $self->users->move($auth, $_, $folder) for @$mm;
+
+ $self->flash(message => $self->l('succ_move'));
+ $self->res->code(303);
+ $self->redirect_to('displayheaders');
+}
+
+
+sub raw {
+ my $self = shift;
+
+ my $mid = $self->stash('id');
+
+ my $auth = AuthReadMails->new(
+ user => $self->session(S_USER),
+ password => $self->session_passwd,
+ challenge => $self->app->secrets->[0],
+ );
+
+ my $mail = $self->users->show($auth, $mid);
+
+ if ($self->param('body')//'' eq 'html') {
+ if ($mail->{content_type} eq 'text/html') {
+ $self->render(text => $mail->{body}) ;
+ }
+ elsif ($mail->{content_type} eq 'multipart/alternative') {
+ my ($content) = grep {$_->{type} eq 'text/html'} @{ $mail->{body} };
+ $self->render(text => $content->{val});
+ }
+ else {
+ $self->res->code(404);
+ }
+ }
+ else {
+ $self->res->headers->content_type('text/plain');
+ $self->render(text => $self->dumper($mail));
+ }
+}
+
+
+1
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+Webmail - All functions comprising the webmail application.
+
+=head1 SYNOPSIS
+
+ my $r = $app->routes;
+ $r->get('/about')->to('Webmail#about');
+ $r->post('/login')->to('Webmail#login');
+
+=head1 DESCRIPTION
+
+The controller of JWebmail.
+
+=head1 METHODS
+
+=head2 noaction
+
+The login page. This should be the root.
+
+=head2 auth
+
+ my $a = $r->under('/')->to('Webmail#auth');
+
+ An intermediate route that makes sure a user has a valid session.
+
+=head2 login
+
+Post route that checks login data.
+
+=head2 logout
+
+Route that clears session data.
+
+=head2 about
+
+Public route.
+
+=head2 displayheaders
+
+Provides an overview over messages.
+
+=head2 readmail
+
+Displays a single mail.
+
+=head2 writemail
+
+A mail editor.
+
+=head2 sendmail
+
+Sends a mail written in writemail.
+
+=head2 move
+
+Moves mails between mail forlders.
+
+=head2 raw
+
+Displays the mail raw, ready to be downloaded.
+
+=head1 DEPENCIES
+
+Mojolicious and File::Type
+
+=cut \ No newline at end of file