Skip to content

Commit

Permalink
compose: Accept mailto URI at To: prompt.
Browse files Browse the repository at this point in the history
The mailto URI can provide values to prefill header fields and the
message body.
  • Loading branch information
wangp committed May 22, 2015
1 parent a56e357 commit 3a8863f
Show file tree
Hide file tree
Showing 8 changed files with 619 additions and 103 deletions.
117 changes: 96 additions & 21 deletions src/compose.m
Expand Up @@ -47,6 +47,7 @@

:- implementation.

:- import_module assoc_list.
:- import_module char.
:- import_module dir.
:- import_module float.
Expand Down Expand Up @@ -74,7 +75,10 @@
:- import_module prog_config.
:- import_module quote_arg.
:- import_module rfc2045.
:- import_module rfc2047.
:- import_module rfc2047.decoder.
:- import_module rfc5322.writer.
:- import_module rfc6068.
:- import_module scrollable.
:- import_module send_util.
:- import_module string_util.
Expand Down Expand Up @@ -136,41 +140,112 @@
%-----------------------------------------------------------------------------%

start_compose(Config, Screen, Transition, !ToHistory, !SubjectHistory, !IO) :-
get_from_address(Config, FromAddress, !IO),
text_entry_initial(Screen, "To: ", !.ToHistory, "",
complete_config_key(Config, addressbook_section), MaybeTo, !IO),
complete_config_key(Config, addressbook_section), MaybeInput, !IO),
(
MaybeTo = yes(To),
add_history_nodup(To, !ToHistory),
MaybeInput = yes(Input),
add_history_nodup(Input, !ToHistory),
( is_mailto_uri(Input) ->
( extract_mailto(Input, Headers, Body) ->
start_compose_2(Config, Screen, Headers, Body, Transition,
!SubjectHistory, !IO)
;
Message = set_warning("Could not parse mailto URI."),
Transition = screen_transition(not_sent, Message)
)
;
expand_aliases(Config, backslash_quote_meta_chars, Input, To, !IO),
Headers0 = init_headers,
Headers = Headers0 ^ h_to := header_value(To),
Body = "",
start_compose_2(Config, Screen, Headers, Body, Transition,
!SubjectHistory, !IO)
)
;
MaybeInput = no,
Transition = screen_transition(not_sent, no_change)
).

:- pred start_compose_2(prog_config::in, screen::in, headers::in, string::in,
screen_transition(sent)::out, history::in, history::out, io::di, io::uo)
is det.

start_compose_2(Config, Screen, !.Headers, Body, Transition,
!SubjectHistory, !IO) :-
Subject0 = header_value_string(!.Headers ^ h_subject),
( Subject0 = "" ->
text_entry_initial(Screen, "Subject: ", !.SubjectHistory, "",
complete_none, MaybeSubject, !IO),
(
MaybeSubject = yes(Subject),
add_history_nodup(Subject, !SubjectHistory),
address_to_string(no_encoding, FromAddress, From, _FromValid),
expand_aliases(Config, backslash_quote_meta_chars, To, ExpandTo,
!IO),
some [!Headers] (
!:Headers = init_headers,
!Headers ^ h_from := header_value(From),
!Headers ^ h_to := header_value(ExpandTo),
!Headers ^ h_subject := decoded_unstructured(Subject),
Headers = !.Headers
),
Text = "",
Attachments = [],
MaybeOldDraft = no,
create_edit_stage(Config, Screen, Headers, Text, Attachments,
MaybeOldDraft, Transition, !IO)
!Headers ^ h_subject := decoded_unstructured(Subject),
start_compose_3(Config, Screen, !.Headers, Body, Transition, !IO)
;
MaybeSubject = no,
Transition = screen_transition(not_sent, no_change)
)
;
MaybeTo = no,
Transition = screen_transition(not_sent, no_change)
start_compose_3(Config, Screen, !.Headers, Body, Transition, !IO)
).

:- pred start_compose_3(prog_config::in, screen::in, headers::in, string::in,
screen_transition(sent)::out, io::di, io::uo) is det.

start_compose_3(Config, Screen, !.Headers, Body, Transition, !IO) :-
get_from_address(Config, FromAddress, !IO),
address_to_string(no_encoding, FromAddress, From, _FromValid),
!Headers ^ h_from := header_value(From),
Attachments = [],
MaybeOldDraft = no,
create_edit_stage(Config, Screen, !.Headers, Body, Attachments,
MaybeOldDraft, Transition, !IO).

%-----------------------------------------------------------------------------%

:- pred extract_mailto(string::in, headers::out, string::out) is semidet.

extract_mailto(Input, !:Headers, Body) :-
parse_mailto_uri(Input, Params),
require_det
(
lookup_header_field(Params, "To", To),
lookup_header_field(Params, "Cc", Cc),
lookup_header_field(Params, "Bcc", Bcc),
lookup_header_field(Params, "Subject", Subject0),
lookup_header_field(Params, "Reply-To", ReplyTo),
lookup_header_field(Params, "In-Reply-To", InReplyTo),
lookup_header_field(Params, "body", Body0),
rfc2047.decoder.decode_unstructured(Subject0, Subject),
replace_crlf(Body0, Body),

!:Headers = init_headers,
!Headers ^ h_to := header_value(To),
!Headers ^ h_cc := header_value(Cc),
!Headers ^ h_bcc := header_value(Bcc),
!Headers ^ h_subject := decoded_unstructured(Subject),
!Headers ^ h_replyto := header_value(ReplyTo),
!Headers ^ h_inreplyto := header_value(InReplyTo)
).

:- pred lookup_header_field(assoc_list(hfname, hfvalue)::in, string::in,
string::out) is det.

lookup_header_field([], _, "").
lookup_header_field([K0 - V0 | T], K, V) :-
( strcase_equal(K0, K) ->
V = V0
;
lookup_header_field(T, K, V)
).

:- pred replace_crlf(string::in, string::out) is det.

replace_crlf(S0, S) :-
string.replace_all(S0, "\r\n", "\n", S).

%-----------------------------------------------------------------------------%

:- pred expand_aliases(prog_config::in, quote_opt::in, string::in, string::out,
io::di, io::uo) is det.

Expand Down
139 changes: 73 additions & 66 deletions src/rfc3986.m
Expand Up @@ -21,6 +21,8 @@

:- pred valid_uri_char(char::in) is semidet.

:- pred unreserved_char(char::in) is semidet.

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%

Expand Down Expand Up @@ -169,73 +171,78 @@
valid_uri_char('#'). % gen-delims
valid_uri_char('['). % gen-delims
valid_uri_char(']'). % gen-delims
valid_uri_char('A'). % unreserved
valid_uri_char('B').
valid_uri_char('C').
valid_uri_char('D').
valid_uri_char('E').
valid_uri_char('F').
valid_uri_char('G').
valid_uri_char('H').
valid_uri_char('I').
valid_uri_char('J').
valid_uri_char('K').
valid_uri_char('L').
valid_uri_char('M').
valid_uri_char('N').
valid_uri_char('O').
valid_uri_char('P').
valid_uri_char('Q').
valid_uri_char('R').
valid_uri_char('S').
valid_uri_char('T').
valid_uri_char('U').
valid_uri_char('V').
valid_uri_char('W').
valid_uri_char('X').
valid_uri_char('Y').
valid_uri_char('Z').
valid_uri_char('a').
valid_uri_char('b').
valid_uri_char('c').
valid_uri_char('d').
valid_uri_char('e').
valid_uri_char('f').
valid_uri_char('g').
valid_uri_char('h').
valid_uri_char('i').
valid_uri_char('j').
valid_uri_char('k').
valid_uri_char('l').
valid_uri_char('m').
valid_uri_char('n').
valid_uri_char('o').
valid_uri_char('p').
valid_uri_char('q').
valid_uri_char('r').
valid_uri_char('s').
valid_uri_char('t').
valid_uri_char('u').
valid_uri_char('v').
valid_uri_char('w').
valid_uri_char('x').
valid_uri_char('y').
valid_uri_char('z').
valid_uri_char('0').
valid_uri_char('1').
valid_uri_char('2').
valid_uri_char('3').
valid_uri_char('4').
valid_uri_char('5').
valid_uri_char('6').
valid_uri_char('7').
valid_uri_char('8').
valid_uri_char('9').
valid_uri_char('-'). % unreserved
valid_uri_char('_'). % unreserved
valid_uri_char('.'). % unreserved
valid_uri_char('~'). % unreserved
valid_uri_char('%'). % percent-encoding
valid_uri_char(C) :-
unreserved_char(C).

%-----------------------------------------------------------------------------%

unreserved_char('A').
unreserved_char('B').
unreserved_char('C').
unreserved_char('D').
unreserved_char('E').
unreserved_char('F').
unreserved_char('G').
unreserved_char('H').
unreserved_char('I').
unreserved_char('J').
unreserved_char('K').
unreserved_char('L').
unreserved_char('M').
unreserved_char('N').
unreserved_char('O').
unreserved_char('P').
unreserved_char('Q').
unreserved_char('R').
unreserved_char('S').
unreserved_char('T').
unreserved_char('U').
unreserved_char('V').
unreserved_char('W').
unreserved_char('X').
unreserved_char('Y').
unreserved_char('Z').
unreserved_char('a').
unreserved_char('b').
unreserved_char('c').
unreserved_char('d').
unreserved_char('e').
unreserved_char('f').
unreserved_char('g').
unreserved_char('h').
unreserved_char('i').
unreserved_char('j').
unreserved_char('k').
unreserved_char('l').
unreserved_char('m').
unreserved_char('n').
unreserved_char('o').
unreserved_char('p').
unreserved_char('q').
unreserved_char('r').
unreserved_char('s').
unreserved_char('t').
unreserved_char('u').
unreserved_char('v').
unreserved_char('w').
unreserved_char('x').
unreserved_char('y').
unreserved_char('z').
unreserved_char('0').
unreserved_char('1').
unreserved_char('2').
unreserved_char('3').
unreserved_char('4').
unreserved_char('5').
unreserved_char('6').
unreserved_char('7').
unreserved_char('8').
unreserved_char('9').
unreserved_char('-').
unreserved_char('.').
unreserved_char('_').
unreserved_char('~').

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sts=4 sw=4 et
20 changes: 19 additions & 1 deletion src/rfc5322.m
Expand Up @@ -64,7 +64,7 @@

%-----------------------------------------------------------------------------%

% Exports for rfc2047, rfc2231.
% Exports for rfc2047, rfc2231, rfc6068.

:- pred ascii(char::in) is semidet.

Expand All @@ -76,6 +76,10 @@

:- pred atext_or_nonascii(char::in, bool::in, bool::out) is semidet.

:- pred dtext_no_obs(char::in) is semidet.

:- pred qtext(char::in) is semidet.

:- func make_quoted_string(string) = quoted_string.

:- func word_to_string(word) = string.
Expand Down Expand Up @@ -138,6 +142,20 @@
!:AllAscii = no
).

dtext_no_obs(C) :-
char.to_int(C, I),
( 33 =< I, I =< 90
; 94 =< I, I =< 126
).

qtext(C) :-
char.to_int(C, I),
( I = 33
; 35 =< I, I =< 91
; 93 =< I, I =< 126
).
% or obs-qtext

make_quoted_string(String) = quoted_string(Wrap) :-
( string.all_match(ascii, String) ->
Wrap = ascii(String)
Expand Down
17 changes: 2 additions & 15 deletions src/rfc5322.parser.m
Expand Up @@ -351,12 +351,7 @@
( nonascii(C) ->
!:AllAscii = no
;
char.to_int(C, I),
( I = 33
; 35 =< I, I =< 91
; 93 =< I, I =< 126
)
% or obs-qtext
qtext(C)
).

%-----------------------------------------------------------------------------%
Expand Down Expand Up @@ -568,22 +563,14 @@
:- pred dtext_or_nonascii(char::in, bool::in, bool::out) is semidet.

dtext_or_nonascii(C, !AllAscii) :-
( dtext(C) ->
( dtext_no_obs(C) ->
true
;
% Just for consistency...
nonascii(C),
!:AllAscii = no
).

:- pred dtext(char::in) is semidet.

dtext(C) :-
char.to_int(C, I),
( 33 =< I, I =< 90
; 94 =< I, I =< 126
% or obs-dtext (but update writer then)
).

%-----------------------------------------------------------------------------%

Expand Down

0 comments on commit 3a8863f

Please sign in to comment.