diff options
-rw-r--r-- | README.md | 55 | ||||
-rw-r--r-- | lib/JWebmail/Controller/Webmail.pm | 8 | ||||
-rw-r--r-- | lib/JWebmail/Model/Driver/MockJSON.pm | 5 | ||||
-rwxr-xr-x | lib/JWebmail/Model/Driver/QMailAuthuser/Extract.pm | 76 | ||||
-rw-r--r-- | lib/JWebmail/Model/Driver/QMailAuthuser/schema.json | 54 | ||||
-rw-r--r-- | templates/headers/_display_headers.html.ep | 6 | ||||
-rw-r--r-- | templates/webmail/readmail.html.ep | 22 |
7 files changed, 126 insertions, 100 deletions
@@ -7,12 +7,12 @@ oMail has not seen much progress in the last two decades so my <jannis@fehcom.de> goal is to bring it up to date. OMail was tied to qmail. JWebmail is not so tightly bound. -## This includes: +This includes: - Using a perl web framework and leave the deprecated CGI behind. You can still use it in a cgi setup if you like but you now have the option of plack/psgi and fcgi as well as the build in server hypnotoad. -- Set up a MVC architecture instead of spaghetti. +- Set up a MVC architecture instead of a single file. - Improve security by only running a small part of the model with elevated privileges. - Make sure it works well with sqmail and its authuser @@ -21,6 +21,7 @@ OMail was tied to qmail. JWebmail is not so tightly bound. Maybe I even add a POP or IMAP based backends instead of reading them from disk. + ## License JWebmail is available under the GNU General Public License version 3 or later. @@ -39,6 +40,7 @@ an external web server. Posts ----- + * Complain about IPC::Open2 ignoring 'open' pragma * Complain about undef references causing errors, and non-fine granular switch no strict 'refs' * Thank for perldoc.org @@ -46,6 +48,7 @@ Posts I18N patch url_for ------------------ + I have taken the monkey patching approach that was taken by Mojolicious::Plugin::I18N. I used `can` to get the old method for looser coupling and used the Mojo::Util::monkey_patch instead of manually doing it. This is probably overkill. @@ -62,6 +65,7 @@ One issue when taking the Match approach is that it needs knowledge of the stash values which can cause cyclic references. I thought of three approaches injecting the modified Match instance into the class: + 1. Extending the Mojolicious::Controller and overriding the new method. This has the issue that inheritance is static but one can use Roles that are dynamically consumed. @@ -79,27 +83,28 @@ I am now using a global route containing the language after the changes to matching have been published. No need to monkey_patch and it works well enough. -Concepts --------- -- Router -- Configuration -- Middleware (auth) -- Controller/Handler -- Templates -- Template helpers -- i18n (url rewriting) -- Sessions (server side) -- Flash, maybe -- Pagination -- Validation -- Logging -- Debug printing -- Development server -- MIME handling +## Concepts + + Router Mojolicious build-in + Configuration INI via Config::Tiny + Middleware (auth) Mojo under + Controller/Handler Mojolicious::Controller + Templates Mojo format ep + Template helpers Mojolicious->helper + i18n (url rewriting) see 'I18N patch url_for' + Sessions (server side) self developed plug-in + Flash Mojo + Pagination self developed + Validation Mojo + Logging Mojo::Log + Debug printing Data::Dumper + Development server Mojo + MIME handling Dependencies ------------ + - M & V - Mojolicious - Config::Tiny @@ -107,3 +112,15 @@ Dependencies - C - Mail::Box::Manager - Email::MIME + + +## Architecture + + Webserver <--> Application + Server + | + Application <--> Extractor + +The Webserver acts as a proxy to the Application Server. + +The Extractor is a stateless process that reads mails from a source. diff --git a/lib/JWebmail/Controller/Webmail.pm b/lib/JWebmail/Controller/Webmail.pm index f0f2efa..cd7b5c7 100644 --- a/lib/JWebmail/Controller/Webmail.pm +++ b/lib/JWebmail/Controller/Webmail.pm @@ -298,11 +298,11 @@ sub raw { } if (my $type = $self->param('body')) { - if ($mail->{content_type} =~ '^multipart/') { - my ($content) = grep {$_->{type} =~ $type} @{ $mail->{body} }; - $self->render(text => $content->{val}); + if ($mail->{head}{content_type} =~ '^multipart/') { + my ($content) = grep {$_->{head}{content_type} =~ $type} @{ $mail->{body} }; + $self->render(text => $content->{body}); } - elsif ($mail->{content_type} =~ $type) { + elsif ($mail->{head}{content_type} =~ $type) { $self->render(text => $mail->{body}) ; } else { diff --git a/lib/JWebmail/Model/Driver/MockJSON.pm b/lib/JWebmail/Model/Driver/MockJSON.pm index aafc74d..258246d 100644 --- a/lib/JWebmail/Model/Driver/MockJSON.pm +++ b/lib/JWebmail/Model/Driver/MockJSON.pm @@ -62,9 +62,8 @@ sub communicate { my $s = sub { my $sort_by = $args{args}->[LIST_SORT]; my $rev = $sort_by !~ m/^![[:lower:]]+/ ? 1 : -1; - $sort_by =~ s/!//; - $sort_by = "date_received" if $sort_by eq "date"; - return ($a->{$sort_by} cmp $b->{$sort_by}) * $rev; + $sort_by =~ s/^!//; + return (($a->{$sort_by}||$a->{head}{$sort_by}) cmp ($b->{$sort_by}||$b->{head}{$sort_by})) * $rev; }; return ([sort { &$s } @{ $self->list_reply }[$args{args}->[LIST_START]..$args{args}->[LIST_END]]], 0); } diff --git a/lib/JWebmail/Model/Driver/QMailAuthuser/Extract.pm b/lib/JWebmail/Model/Driver/QMailAuthuser/Extract.pm index 6639ad9..a59e265 100755 --- a/lib/JWebmail/Model/Driver/QMailAuthuser/Extract.pm +++ b/lib/JWebmail/Model/Driver/QMailAuthuser/Extract.pm @@ -12,7 +12,7 @@ use Carp; use List::Util 'min'; use Encode v2.88 'decode'; -use open IO => ':encoding(UTF-8)', ':std'; +#use open IO => ':encoding(UTF-8)', ':std'; no warnings 'experimental::smartmatch'; use Mail::Box::Manager; @@ -22,9 +22,9 @@ use constant ROOT_MAILDIR => '.'; sub main { my ($maildir) = shift(@ARGV) =~ m/(.*)/; - my ($su) = shift(@ARGV) =~ m/(.*)/; - my ($user) = shift(@ARGV) =~ m/([[:alpha:]]+)/; - my $mode = shift @ARGV; _ok($mode =~ m/([[:alpha:]-]{1,20})/); + my ($su) = shift(@ARGV) =~ m/(.*)/; + my ($user) = shift(@ARGV) =~ m/([[:alpha:]]+)/; + my $mode = shift @ARGV; _ok($mode =~ m/([[:alpha:]-]{1,20})/); my @args = @ARGV; delete $ENV{PATH}; @@ -57,7 +57,7 @@ sub main { }; $folder->close; - print encode_json $reply; + print(encode_json $reply); if (ref $reply eq 'HASH' && $reply->{error}) { exit 3; } @@ -115,16 +115,18 @@ sub list { for my $msg (@msgs) { my $msg2 = { - subject => decode('MIME-Header', $msg->subject), - from => _addresses($msg->from), - to => _addresses($msg->to), - cc => _addresses($msg->cc), - bcc => _addresses($msg->bcc), - date_received => _iso8601_utc($msg->timestamp), - size => $msg->size, - content_type => ''. $msg->contentType, - mid => $msg->messageId, - new => $msg->label('seen'), + mid => $msg->messageId, + size => $msg->size, + new => $msg->label('seen'), + head => { + subject => decode('MIME-Header', $msg->subject), + from => _addresses($msg->from), + to => _addresses($msg->to), + cc => _addresses($msg->cc), + bcc => _addresses($msg->bcc), + date => _iso8601_utc($msg->timestamp), + content_type => ''.$msg->contentType, + }, }; push @msgs2, $msg2; } @@ -165,19 +167,21 @@ sub _addresses { sub read_mail { my ($folder, $mid) = @_; - + my $msg = $folder->find($mid); return {error => 'no such message', mid => $mid} unless $msg; return { - subject => decode('MIME-Header', $msg->subject), - from => _addresses($msg->from), - to => _addresses($msg->to), - cc => _addresses($msg->cc), - bcc => _addresses($msg->bcc), - date_received => _iso8601_utc($msg->timestamp), - size => $msg->size, - content_type => ''. $msg->contentType, - body => do { + size => $msg->size, + head => { + subject => decode('MIME-Header', $msg->subject), + from => _addresses($msg->from), + to => _addresses($msg->to), + cc => _addresses($msg->cc), + bcc => _addresses($msg->bcc), + date => _iso8601_utc($msg->timestamp), + content_type => ''. $msg->contentType, + }, + body => do { if ($msg->isMultipart) { [map {{type => ''. $_->contentType, val => '' . $_->decoded}} $msg->body->parts] } @@ -200,21 +204,23 @@ sub search { return scalar(grep { $_->decoded =~ /$search_pattern/ || (decode('MIME-Header', $_->subject)) =~ /$search_pattern/ } $m->body->parts) if $m->isMultipart; - $m->body->decoded =~ /$search_pattern/ ||(decode('MIME-Header', $m->subject)) =~ /$search_pattern/; + $m->body->decoded =~ /$search_pattern/ || (decode('MIME-Header', $m->subject)) =~ /$search_pattern/; }); my @msgs2; for my $msg (@msgs) { my $msg2 = { - subject => decode('MIME-Header', $msg->subject), - from => _addresses($msg->from), - to => _addresses($msg->to), - cc => _addresses($msg->cc), - bcc => _addresses($msg->bcc), - date_received => _iso8601_utc($msg->timestamp), - size => $msg->size, - content_type => ''. $msg->contentType, - mid => $msg->messageId, + size => $msg->size, + mid => $msg->messageId, + head => { + subject => decode('MIME-Header', $msg->subject), + from => _addresses($msg->from), + to => _addresses($msg->to), + cc => _addresses($msg->cc), + bcc => _addresses($msg->bcc), + date => _iso8601_utc($msg->timestamp), + content_type => ''. $msg->contentType, + }, }; push @msgs2, $msg2; } diff --git a/lib/JWebmail/Model/Driver/QMailAuthuser/schema.json b/lib/JWebmail/Model/Driver/QMailAuthuser/schema.json index 5d5247a..b63a5eb 100644 --- a/lib/JWebmail/Model/Driver/QMailAuthuser/schema.json +++ b/lib/JWebmail/Model/Driver/QMailAuthuser/schema.json @@ -33,47 +33,51 @@ "mail_head": { "type": "object", "properties": { - "new": {"type": "boolean"}, - "mid": {"type": "string"}, "content_type": {"type": "string"}, - "size": {"type": "integer", "minimum": 0}, - "date_send": {"type": "string"}, - "date_received": {"type": "string"}, + "date": {"type": "string"}, "cc": {"$ref": "#/definitions/mail_addrs"}, "bcc": {"$ref": "#/definitions/mail_addrs"}, "to": {"$ref": "#/definitions/mail_addrs"}, "from": {"$ref": "#/definitions/mail_addrs"}, "subject": {"type": "string"} }, - "required": ["mid"] + "required": ["date", "from"] }, - "list": { + "head_list": { "type": "array", "items": { "$ref": "#/definitions/mail_head" } }, - "mail": { - "$ref": "#/definitions/mail_head", - "properties": { - "body": { - "anyOf": [ - {"type": "string"}, - { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "properties": { - "val": {"type": "string"}, - "type": {"type": "string"} - } - } + "mail_body": { + "anyOf": [ + {"type": "string"}, + { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "head": {"$ref": "#/definitions/mail_head"}, + "body": {"$ref": "#/definitions/mail_body"} } - ] + } + }, + { + "ref": "#/definitions/mail" } + ] + }, + "mail": { + "type": "object", + "properties": { + "new": {"type": "boolean"}, + "mid": {"type": "string"}, + "size": {"type": "integer", "minimum": 0}, + "head": {"$ref": "#/definitions/mail_head"}, + "body": {"$ref": "#/definitions/mail_body"} }, - "required": ["body"] + "required": ["mid"] } } } diff --git a/templates/headers/_display_headers.html.ep b/templates/headers/_display_headers.html.ep index da04873..d3f9457 100644 --- a/templates/headers/_display_headers.html.ep +++ b/templates/headers/_display_headers.html.ep @@ -71,14 +71,14 @@ %= ucfirst($msg->{is_multipart} ? l('yes') : l('no')); </td> <td> - % my $date = parse_iso_date $msg->{date_received}; + % my $date = parse_iso_date $msg->{head}{date}; %= join('/', $date->{mday}, $date->{month}, $date->{year}) . " $date->{hour}:$date->{min}"; </td> <td> - %= $msg->{from}->[0]->{name} || $msg->{from}->[0]->{email}; + %= $msg->{head}{from}[0]{name} || $msg->{head}{from}[0]{email}; </td> <td> - <a href="<%= url_for('read', id => $msg->{mid}) %>"> <%= $msg->{subject} || '_' %> </a> + <a href="<%= url_for('read', id => $msg->{mid}) %>"> <%= $msg->{head}{subject} || '_' %> </a> </td> <td> %= print_sizes10 $msg->{size}; diff --git a/templates/webmail/readmail.html.ep b/templates/webmail/readmail.html.ep index 8c2432a..0a9fde0 100644 --- a/templates/webmail/readmail.html.ep +++ b/templates/webmail/readmail.html.ep @@ -12,25 +12,25 @@ <dl id=show-head> <dt> <%= uc l 'subject' %> </dt> - <dd> <%= $msg->{subject} %> </dd> -%= $mail_fmt->('from', $msg->{from}); -%= $mail_fmt->('to', $msg->{to}); -%= $mail_fmt->('cc', $msg->{cc}) if !ref $msg->{cc} || @{ $msg->{cc} }; -%= $mail_fmt->('bcc', $msg->{bcc}) if !ref $msg->{bcc} || @{ $msg->{cc} }; + <dd> <%= $msg->{head}{subject} %> </dd> +%= $mail_fmt->('from', $msg->{head}{from}); +%= $mail_fmt->('to', $msg->{head}{to}); +%= $mail_fmt->('cc', $msg->{head}{cc}) if !ref $msg->{head}{cc} || @{ $msg->{head}{cc} }; +%= $mail_fmt->('bcc', $msg->{head}{bcc}) if !ref $msg->{head}{bcc} || @{ $msg->{head}{bcc} }; <dt> <%= uc l 'date' %> </dt> - <dd> <%= $msg->{date_received} %> </dd> + <dd> <%= $msg->{head}{date} %> </dd> <dt> <%= uc l 'size' %> </dt> <dd> <%= print_sizes10 $msg->{size} %> </dd> <dt> <%= uc l 'content-type' %> </dt> - <dd> <%= $msg->{content_type} %> </dd> + <dd> <%= $msg->{head}{content_type} %> </dd> </dl> % my $body = $msg->{body}; -% if ($msg->{content_type} eq 'multipart/alternative') { +% if ($msg->{head}{content_type} eq 'multipart/alternative') { % for (reverse @$body) { <div class=show-body> -% my $x = mime_render($_->{type}, $_->{val}); +% my $x = mime_render($_->{head}{content_type}, $_->{body}); %== $x; </div> % last if $x; @@ -39,13 +39,13 @@ % elsif (ref $body eq 'HASH') { % for (%$body) { <div class=show-body> -%== mime_render($_->{type}, $_->{val}); +%== mime_render($_->{head}{content_type}, $_->{body}); </div> % } % } % else { <div class=show-body> -%== mime_render($msg->{content_type}, $body); +%== mime_render($msg->{head}{content_type}, $body); </div> % } |