Changes of Revision 3

erlang-eimap.spec Changed
x
 
1
@@ -5,30 +5,38 @@
2
 %global realname eimap
3
 %global debug_package %{nil}
4
 
5
-Name:      erlang-%{realname}
6
-Version:   0.1.5
7
-Release:   1%{?dist}
8
-Summary:   Erlang IMAP client
9
-Group:     Development/Libraries
10
-License:   BSD
11
-URL:       http://git.kolab.org/diffusion/EI/%{realname}.git
12
+Name:           erlang-%{realname}
13
+Version:        0.2.4
14
+Release:        0.20160111.git%{?dist}
15
+Summary:        Erlang IMAP client
16
+Group:          Development/Libraries
17
+License:        GPLv3+
18
+URL:            http://git.kolab.org/diffusion/EI/%{realname}.git
19
 %if 0%{?el7}%{?fedora}
20
-VCS:       scm:git:https://git.kolab.org/diffusion/EI/%{realname}.git
21
+VCS:            scm:git:https://git.kolab.org/diffusion/EI/%{realname}.git
22
 %endif
23
-Source0:   erlang-eimap-%{version}.tar.gz
24
+Source0:        erlang-eimap-0.2.4.tar.gz
25
 
26
-BuildRequires:  erlang-goldrush >= 0.1.6
27
-BuildRequires: erlang-lager >= 2.1.0
28
+Patch0001:      0001-there-is-only-ever-one-response.-be-strict-about-tha.patch
29
+Patch0002:      0002-the-explicit-capabilities-command-is-multiline-so-it.patch
30
+Patch0003:      0003-don-t-munge-the-incoming-response.-leave-that-to-the.patch
31
+
32
+BuildRequires: erlang-goldrush >= 0.1.7
33
+BuildRequires: erlang-lager >= 2.2.0
34
 BuildRequires: erlang-rebar >= 2.5.1
35
 
36
-Requires:  erlang-erts%{?_isa} >= R13B
37
-Requires:  erlang-stdlib%{?_isa} >= R13B
38
+Requires:       erlang-erts%{?_isa} >= R13B
39
+Requires:       erlang-stdlib%{?_isa} >= R13B
40
 
41
 %description
42
 IMAP client library for Erlang
43
 
44
 %prep
45
-%setup -q
46
+%setup -q -n eimap-%{version}
47
+
48
+%patch0001 -p1
49
+%patch0002 -p1
50
+%patch0003 -p1
51
 
52
 %build
53
 rebar compile -v
54
@@ -44,7 +52,6 @@
55
 
56
 install -D -m 644 ebin/%{realname}.app %{buildroot}%{_libdir}/erlang/lib/%{realname}-%{version}/ebin/%{realname}.app
57
 install -D -m 644 ebin/*.beam %{buildroot}%{_libdir}/erlang/lib/%{realname}-%{version}/ebin/.
58
-install -D -m 644 src/*.hrl %{buildroot}%{_libdir}/erlang/lib/%{realname}-%{version}/src/
59
 
60
 %files
61
 %doc README.md
62
@@ -53,11 +60,11 @@
63
 %dir %{_libdir}/erlang/lib/%{realname}-%{version}/src/
64
 %{_libdir}/erlang/lib/%{realname}-%{version}/ebin/%{realname}.app
65
 %{_libdir}/erlang/lib/%{realname}-%{version}/ebin/*.beam
66
-%{_libdir}/erlang/lib/%{realname}-%{version}/src/*.hrl
67
 
68
 %changelog
69
-* Mon Jan  4 2016 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 0.1.5-1
70
-- Release 0.1.5
71
-
72
+* Wed Jun 08 2016 Aaron Seigo <seigo@kolabsystems.com> - 0.2.4-1
73
+- Packaging of 0.2.4
74
+* Tue Jun 07 2016 Aaron Seigo <seigo@kolabsystems.com> - 0.2.2-1
75
+- Packaging of 0.2.2
76
 * Mon Dec 21 2015 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 0.1.2-1
77
 - First package
78
0001-there-is-only-ever-one-response.-be-strict-about-tha.patch Added
29
 
1
@@ -0,0 +1,27 @@
2
+From 297c7097833f10c70b4b3f928dbc6166282b947b Mon Sep 17 00:00:00 2001
3
+From: Aaron Seigo <aseigo@kde.org>
4
+Date: Mon, 20 Jun 2016 16:49:55 +0200
5
+Subject: [PATCH 1/3] there is only ever one response. be strict about that.
6
+
7
+---
8
+ src/commands/eimap_command_capability.erl | 4 ++--
9
+ 1 file changed, 2 insertions(+), 2 deletions(-)
10
+
11
+diff --git a/src/commands/eimap_command_capability.erl b/src/commands/eimap_command_capability.erl
12
+index 8f9d12e..a1b50c9 100644
13
+--- a/src/commands/eimap_command_capability.erl
14
++++ b/src/commands/eimap_command_capability.erl
15
+@@ -25,8 +25,8 @@
16
+ new_command(parse_serverid) -> { <<"CAPABILITY">>, single_line_response };
17
+ new_command(_Args) -> { <<"CAPABILITY">>, multiline_response }.
18
+ 
19
+-process_line(<<"* CAPABILITY ", Data/binary>>, Acc) ->
20
+-    [Data|Acc];
21
++process_line(<<"* CAPABILITY ", Data/binary>>, _Acc) ->
22
++    [Data];
23
+ process_line(_Data, Acc) -> Acc.
24
+ 
25
+ formulate_response(ok, Response) -> { fini, Response };
26
+-- 
27
+2.5.5
28
+
29
0002-the-explicit-capabilities-command-is-multiline-so-it.patch Added
28
 
1
@@ -0,0 +1,26 @@
2
+From ee6185b256058ee5884d01ac16faf30585480fbb Mon Sep 17 00:00:00 2001
3
+From: Aaron Seigo <aseigo@kde.org>
4
+Date: Mon, 20 Jun 2016 16:55:01 +0200
5
+Subject: [PATCH 2/3] the explicit capabilities command is multiline, so it
6
+ produces a list response
7
+
8
+---
9
+ src/eimap.erl | 2 +-
10
+ 1 file changed, 1 insertion(+), 1 deletion(-)
11
+
12
+diff --git a/src/eimap.erl b/src/eimap.erl
13
+index 76f02f2..ebf3a02 100644
14
+--- a/src/eimap.erl
15
++++ b/src/eimap.erl
16
+@@ -294,7 +294,7 @@ handle_info({ { connected, Receiver, ResponseToken }, { Capabilities, ServerID }
17
+             send_hello_string(Capabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver)
18
+     end,
19
+     { next_state, idle, State#state{ current_command = undefined, server_id = ServerID } };
20
+-handle_info({ { posttls_capabilities, Receiver, ResponseToken }, Capabilities }, _StateName, #state{ server_id = ServerID, passthrough = Passthrough, passthrough_recv = PassthroughReceiver } = State) ->
21
++handle_info({ { posttls_capabilities, Receiver, ResponseToken }, [Capabilities] }, _StateName, #state{ server_id = ServerID, passthrough = Passthrough, passthrough_recv = PassthroughReceiver } = State) ->
22
+     OurCapabilities =
23
+         case binary:match(Capabilities, <<"STARTTLS">>) of
24
+             nomatch -> <<Capabilities/binary, " STARTTLS">>;
25
+-- 
26
+2.5.5
27
+
28
0003-don-t-munge-the-incoming-response.-leave-that-to-the.patch Added
33
 
1
@@ -0,0 +1,31 @@
2
+From bc9d05a44c7ebbfb7bbbcb1006b23d2bb76d8101 Mon Sep 17 00:00:00 2001
3
+From: Aaron Seigo <aseigo@kde.org>
4
+Date: Mon, 20 Jun 2016 16:55:28 +0200
5
+Subject: [PATCH 3/3] don't munge the incoming response. leave that to the
6
+ application.
7
+
8
+---
9
+ src/eimap.erl | 7 +------
10
+ 1 file changed, 1 insertion(+), 6 deletions(-)
11
+
12
+diff --git a/src/eimap.erl b/src/eimap.erl
13
+index ebf3a02..b331d1d 100644
14
+--- a/src/eimap.erl
15
++++ b/src/eimap.erl
16
+@@ -295,12 +295,7 @@ handle_info({ { connected, Receiver, ResponseToken }, { Capabilities, ServerID }
17
+     end,
18
+     { next_state, idle, State#state{ current_command = undefined, server_id = ServerID } };
19
+ handle_info({ { posttls_capabilities, Receiver, ResponseToken }, [Capabilities] }, _StateName, #state{ server_id = ServerID, passthrough = Passthrough, passthrough_recv = PassthroughReceiver } = State) ->
20
+-    OurCapabilities =
21
+-        case binary:match(Capabilities, <<"STARTTLS">>) of
22
+-            nomatch -> <<Capabilities/binary, " STARTTLS">>;
23
+-            _ -> Capabilities
24
+-        end,
25
+-    send_hello_string(OurCapabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver),
26
++    send_hello_string(Capabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver),
27
+     { next_state, idle, State#state{ current_command = none } };
28
+ handle_info({ { selected, MBox }, ok }, StateName, State) ->
29
+     %%lager:info("~p Selected mbox ~p", [self(), MBox]),
30
+-- 
31
+2.5.5
32
+
33
debian.changelog Added
25
 
1
@@ -0,0 +1,23 @@
2
+erlang-eimap (0.2.4) unstable; urgency=medium
3
+
4
+  * Package the 0.2.4 release
5
+
6
+ -- Aaron Seigo <seigo@kolabsystems.com>  Wed, 08 Jun 2016 10:30:50 +0100
7
+
8
+erlang-eimap (0.2.2) unstable; urgency=medium
9
+
10
+  * Package the 0.2.2 release
11
+
12
+ -- Aaron Seigo <seigo@kolabsystems.com>  Tue, 07 Jun 2016 10:30:50 +0100
13
+
14
+erlang-eimap (0.2-1+git20160111~kolab1) unstable; urgency=medium
15
+
16
+  * Bump to new git version.
17
+
18
+ -- Christoph Erhardt <kolab@sicherha.de>  Thu, 11 Feb 2016 21:09:49 +0100
19
+
20
+erlang-eimap (0.1.5-1~kolab1) unstable; urgency=medium
21
+
22
+  * Initial release.
23
+
24
+ -- Christoph Erhardt <kolab@sicherha.de>  Sun, 07 Feb 2016 12:37:53 +0100
25
debian.control Added
22
 
1
@@ -0,0 +1,20 @@
2
+Source: erlang-eimap
3
+Maintainer: Christoph Erhardt <kolab@sicherha.de>
4
+Section: devel
5
+Priority: extra
6
+Standards-Version: 3.9.6
7
+Homepage: https://git.kolab.org/diffusion/EI/eimap.git
8
+Build-Depends: debhelper (>= 9), erlang-goldrush (>= 0.1.7), erlang-lager (>= 2.2.0), rebar (>= 2.5.1)
9
+
10
+Package: erlang-eimap
11
+Architecture: any
12
+Section: devel
13
+Priority: extra
14
+Depends: ${misc:Depends},
15
+   erlang-base | ${erlang-abi:Depends},
16
+   erlang-goldrush,
17
+    erlang-lager,
18
+   ${erlang:Depends}
19
+Enhances: erlang
20
+Description: Erlang IMAP client
21
+ IMAP client library for Erlang
22
debian.rules Added
21
 
1
@@ -0,0 +1,19 @@
2
+#!/usr/bin/make -f
3
+
4
+include /usr/share/dpkg/pkg-info.mk
5
+
6
+DESTDIR = $(CURDIR)/debian/erlang-eimap
7
+
8
+%:
9
+   dh $@
10
+
11
+override_dh_auto_build:
12
+   rebar compile -v
13
+
14
+override_dh_auto_install:
15
+   mkdir -p $(DESTDIR)/usr/lib/erlang/lib/eimap-$(DEB_VERSION_UPSTREAM)/ebin \
16
+         $(DESTDIR)/usr/lib/erlang/lib/eimap-$(DEB_VERSION_UPSTREAM)/src
17
+   install -D -m 644 ebin/eimap.app \
18
+           $(DESTDIR)/usr/lib/erlang/lib/eimap-$(DEB_VERSION_UPSTREAM)/ebin/eimap.app
19
+   install -D -m 644 ebin/*.beam \
20
+           $(DESTDIR)/usr/lib/erlang/lib/eimap-$(DEB_VERSION_UPSTREAM)/ebin/
21
debian.tar.gz Added
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_examine.erl Deleted
35
 
1
@@ -1,33 +0,0 @@
2
-%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
-%%
4
-%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
-%%
6
-%% This program is free software: you can redistribute it and/or modify
7
-%% it under the terms of the GNU General Public License as published by
8
-%% the Free Software Foundation, either version 3 of the License, or
9
-%% (at your option) any later version.
10
-%%
11
-%% This program is distributed in the hope that it will be useful,
12
-%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
-%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
-%% GNU General Public License for more details.
15
-%%
16
-%% You should have received a copy of the GNU General Public License
17
-%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
-
19
--module(eimap_command_examine).
20
--behavior(eimap_command).
21
--export([new/1, parse/2]).
22
-
23
-%% https://tools.ietf.org/html/rfc3501#section-6.3.2
24
-
25
-%% Public API
26
-new(MBox) when is_binary(MBox) -> <<"EXAMINE \"", MBox/binary, "\"">>.
27
-
28
-parse(Data, Tag) ->
29
-    case eimap_utils:check_response_for_failure(Data, Tag) of
30
-        ok -> { fini, ok };
31
-        { _, Reason } -> { error, Reason }
32
-    end.
33
-
34
-%% Private API
35
erlang-eimap-0.1.5.tar.gz/src/eimap.hrl Deleted
4
 
1
@@ -1,2 +0,0 @@
2
--record(eimap_server_config, { host :: string(), port = 143 :: integer(), tls = false :: boolean() }).
3
-
4
erlang-eimap-0.1.5.tar.gz/README.md -> erlang-eimap-0.2.4.tar.gz/README.md Changed
126
 
1
@@ -18,7 +18,7 @@
2
 To use eimap from your imap application add the following line to your rebar
3
 config:
4
 
5
-    { eimap, "*", {git, "git://git.kolab.org/diffusion/EI/eimap.git" }
6
+    { eimap, "*", {git, "git://git.kolab.org/diffusion/EI/eimap.git" } }
7
 
8
 There is no need to start the eimap application as it does not have any process
9
 or startup routines related to its usage. eimap does rely on lager being avilable,
10
@@ -28,7 +28,7 @@
11
 ============
12
 The eimap erlang module is the home of the primary API for the eimap library. It
13
 is a gen_fsm and should be started as a process in the normal Erlang/OTP manner for
14
-use. 
15
+use.
16
 
17
 An eimap instance represents a single IMAP connection to a single IMAP server
18
 and is stateful: commands that are started may change the selected folder, for
19
@@ -38,14 +38,14 @@
20
 Once started, an eimap process may be directed to connect to an imap server
21
 and then start with functions such as fetching path tokens:
22
 
23
-    ImapServerArgs = #eimap_server_config{ host = "acme.com" },
24
+    ImapServerArgs = [ { host, "imap.acme.com" }, { port, 143 }, { tls, starttls } ]
25
     { ok, Imap } = eimap:start_link(ImapServerArgs),
26
     eimap_imap:starttls(),
27
     eimap_imap:login(Imap, self(), undefined, "username", "password"),
28
     eimap_imap:connect(Imap),
29
     eimap_imap:get_path_tokens(Imap, self(), get_path_tokens)
30
 
31
-The eimap_server_config record is defined in eimap.hrl and allows one to set
32
+The Imap server args is a simple proplist which allows one to set
33
 host, port and TLS settings. For TLS, the following values are supported:
34
 
35
     * true: start a TLS session when opening the socket ("implicit TLS")
36
@@ -110,15 +110,53 @@
37
 The API for commands is defined in src/eimap_command.erl as a behavior. Commands
38
 are expected to provide at least two functions:
39
 
40
-    new(Args): create a command bitstring to be passed to the imap server
41
-               Args is specific to the command, and some commands ignore this
42
-               parameter
43
-    parse(Data, Tag): parses a response from the imap server; the Tag is the
44
-               IMAP command tag sent with the command and Data is the full
45
-               Data buffer being process (possibly including tagged lines).
46
-
47
-Data to be parsed may contain multiple lines, and in the case of commands with
48
-large responses may only contain part of the response.
49
+    new_command(Args) -> { Command, ResponseType }
50
+        create a command bitstring to be passed to the imap server and defines
51
+        the type of response for this command. Response types include
52
+        single_line_response, multiline_response, all_multiline_response
53
+        and blob_response.
54
+
55
+        Args is specific to the command, and some commands
56
+        ignore this parameter
57
+
58
+Single line response
59
+--------------------
60
+Commands which the IMAP server will respond to with a single line in return
61
+should use single_line_response and must implement formulate_response/2 which
62
+is passed the Data and the command Tag. This usually returns one of
63
+{ fini, Result } or { error, Reason }, though some special commands may return
64
+atoms which the eimap module responds to such as starttls.
65
+
66
+Multiline Response
67
+------------------
68
+This is the most common response type and is used when the IMAP server may
69
+respond with zero or more untagged responses and a final tagged response.
70
+
71
+Such commands must implement:
72
+
73
+    process_line/2 which is passed one line at a time (minus newlines)
74
+        and a list accumulator to add responses to
75
+
76
+    formulate_response/2 which is passed ok on success or an error tuple of 
77
+     { [no|bad], Reason }. This should return either { fini, Response } or
78
+     { error, Reason } in most cases; and for that there is
79
+     eimap_command:formulate_response. Which means that for most commands
80
+     formulate_response/2 is implemented as:
81
+
82
+        formulate_response(Result, Response) -> eimap_command:formulate(Result, Response).
83
+
84
+Commands which are all_multiline_responses also must implement process_tagged_line/2
85
+which behaves exactly like process_line but which accepts the tagged response line
86
+from the IMAP server. This is useful for commands where useful information is also
87
+passed in the tagged response line. An example of this is the select/exmine
88
+commands.
89
+
90
+Unstructured (blob) responses
91
+-----------------------------
92
+Responses that do not follow the usual untagged/tagged line response pattern
93
+may use the blog_response type and implement parse/2 which will be passed
94
+the data as it arrives. The command is responsible for all buffer stitching
95
+across network packets, etc.
96
 
97
 In the case of partial responses, parse/2 may return { more, fun/3, State }.
98
 The State object allows preserving the parsing state and will be passed back to
99
@@ -140,9 +178,7 @@
100
 =======
101
 Tests can be run with `make tests` or `rebar eunit`.
102
 
103
-All new commands must be accompanied by tests in the test/ directory. Currently
104
-testing is not remotely complete in coverage (a historical accident), and this
105
-needs to be rectified over time. New tests welcome.
106
+All new commands must be accompanied by tests in the test/ directory.
107
 
108
 Contributing
109
 ============
110
@@ -150,5 +186,15 @@
111
 Mailing list: devel@lists.kolab.com
112
 Project page: https://git.kolab.org/tag/eimap/
113
 
114
+This project uses the git flow workflow as described here:
115
+
116
+    http://nvie.com/posts/a-successful-git-branching-model/
117
+
118
+You can installed git flow from here:
119
+
120
+    https://github.com/nvie/gitflow
121
+
122
+Then initialize your local clone with `git flow init -d`.
123
+
124
 You can find the list of open tasks on the project page's workboard. Anything
125
 in the backlog is open to be worked on.
126
erlang-eimap-0.1.5.tar.gz/rebar.config -> erlang-eimap-0.2.4.tar.gz/rebar.config Changed
26
 
1
@@ -1,22 +1,17 @@
2
 %% -*- tab-width: 4;erlang-indent-level: 4;indent-tabs-mode: nil -*-
3
 %% ex: ft=erlang ts=4 sw=4 et
4
 
5
-{deps_dir, "deps"}.
6
 {deps, [
7
-    { lager, ".*", { git, "https://github.com/basho/lager.git", { tag, "2.1.0" } } }
8
+    { lager, ".*", { git, "git://github.com/basho/lager.git" } }
9
    ]}.
10
 
11
-{erl_opts, [ %%no_debug_info,
12
-            {parse_transform, lager_transform},
13
-            {platform_define, "(linux|solaris|freebsd|darwin)", 'HAVE_SENDFILE'}
14
-            ]}.
15
+{erl_opts, [warnings_as_errors, {parse_transform, lager_transform}]}.
16
 { erl_first_files, ["src/eimap_command.erl"] }.
17
 {sub_dirs, [
18
     "src",
19
     "rel",
20
     "tests"
21
  ]}.
22
-{erl_opts, [debug_info, fail_on_warning]}.
23
 {cover_enabled, true}.
24
 
25
 %%{require_otp_vsn, "17"}.
26
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_annotation.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_annotation.erl Changed
67
 
1
@@ -17,37 +17,23 @@
2
 
3
 -module(eimap_command_annotation).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 %% Public API
9
-new(Folder) when is_binary(Folder) ->
10
-    <<"GETANNOTATION \"", Folder/binary, "\" \"*\" \"value.shared\"">>.
11
+new_command(Folder) when is_list(Folder) -> new_command(list_to_binary(Folder));
12
+new_command(Folder) -> { <<"GETANNOTATION \"", Folder/binary, "\" \"*\" \"value.shared\"">>, multiline_response }.
13
 
14
-parse(Data, Tag) when is_binary(Data) ->
15
-    case eimap_utils:check_response_for_failure(Data, Tag) of
16
-        ok ->
17
-            Lines = binary:split(Data, <<"\r\n">>, [global]),
18
-            Rv = lists:foldl(fun(Line, Acc) -> parseLine(Line, Acc, Tag) end, [], Lines),
19
-            { fini, Rv };
20
-        { _, Reason } ->
21
-            { error, Reason }
22
-    end.
23
-
24
-
25
-%% Private API
26
-parseLine(<<" ", Data/binary>>, Acc, Tag) ->
27
-    parseLine(Data, Acc, Tag);
28
-parseLine(<<"* ANNOTATION ", Data/binary>>, Acc, _Tag) ->
29
+process_line(<<" ", Data/binary>>, Acc) ->
30
+    process_line(Data, Acc);
31
+process_line(<<"* ANNOTATION ", Data/binary>>, Acc) ->
32
     Pieces = binary:split(Data, <<"\"">>, [global]),
33
     process_pieces(Pieces, Acc);
34
-parseLine(<<>>, Acc, _Tag) ->
35
-    Acc;
36
-parseLine(Data, Acc, Tag) ->
37
-    case binary:match(Data, Tag) of
38
-        { 0, End } -> handle_possible_end(binary:part(Data, End + 1, byte_size(Data) - End - 1), Acc);
39
-        _ -> lager:warning("Unexpected response from imap server: ~p", [Data]), Acc
40
-    end.
41
+process_line(<<>>, Acc) ->
42
+    Acc.
43
+
44
+formulate_response(Result, Data) -> eimap_command:formulate_response(Result, Data).
45
 
46
+%% Private API
47
 process_pieces([MBox, Key, _, _, _, Value, _], Acc) when MBox =/= <<>> -> [ { Key, translate(Value) } | Acc ];
48
 process_pieces([_, _MBox, _, Key, _, _, _, Value, _], Acc) -> [ { Key, translate(Value) } | Acc ];
49
 process_pieces(_, Acc) -> Acc.
50
@@ -61,16 +47,3 @@
51
         _:_ -> Value
52
     end.
53
 
54
-handle_possible_end(<<"OK", _/binary>>, Acc) ->
55
-    Acc;
56
-handle_possible_end(<<"NO ", Reason/binary>>, Acc) ->
57
-    lager:warning("Annotation error from imap server: ~p", [Reason]),
58
-    Acc;
59
-handle_possible_end(<<"BAD ", Reason/binary>>, Acc) ->
60
-    lager:warning("Annotation error from imap server: ~p", [Reason]),
61
-    Acc;
62
-handle_possible_end([_Tag, Message], Acc) ->
63
-    handle_possible_end(Message, Acc);
64
-handle_possible_end(Message, Acc) ->
65
-    lager:warning("Unexpected response from imap server: ~p", [Message]),
66
-    Acc.
67
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_capability.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_capability.erl Changed
47
 
1
@@ -17,31 +17,34 @@
2
 
3
 -module(eimap_command_capability).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new(_Args) -> <<"CAPABILITY">>.
12
+new_command(parse_serverid) -> { <<"CAPABILITY">>, single_line_response };
13
+new_command(_Args) -> { <<"CAPABILITY">>, multiline_response }.
14
 
15
-parse(Data, Tag) -> formulate_reponse(eimap_utils:check_response_for_failure(Data, Tag),
16
-                                      eimap_utils:remove_tag_from_response(Data, Tag, check)).
17
+process_line(<<"* CAPABILITY ", Data/binary>>, Acc) ->
18
+    [Data|Acc];
19
+process_line(_Data, Acc) -> Acc.
20
 
21
+formulate_response(ok, Response) -> { fini, Response };
22
+formulate_response({ _, Reason }, _Data) -> { error, Reason };
23
+formulate_response(Data, Tag) -> parse_oneliner(eimap_utils:check_response_for_failure(Data, Tag),
24
+                                                eimap_utils:remove_tag_from_response(Data, Tag, check)).
25
 
26
 %% Private API
27
 % TODO: probably way too cyrus imap specific on the responses (capitalization, etc)
28
 % make generic with a nicer parser
29
-formulate_reponse(ok, <<"* OK [CAPABILITY ", Data/binary>>) ->
30
+parse_oneliner(ok, <<"* OK [CAPABILITY ", Data/binary>>) ->
31
     % this is a server response on connect
32
     { End, _ } = binary:match(Data, <<"]">>),
33
     { TextEnd, _ } = binary:match(Data, <<"\r\n">>),
34
     Capabilities = binary:part(Data, { 0, End }),
35
     ServerID = binary:part(Data, { End + 2, TextEnd - End  - 2}),
36
     { fini, { Capabilities, ServerID } };
37
-formulate_reponse(ok, <<"* CAPABILITY ", Data/binary>>) ->
38
-    { End, _ } = binary:match(Data, <<"\r\n">>),
39
-    Capabilities = binary:part(Data, { 0, End }),
40
-    { fini, Capabilities };
41
-formulate_reponse(ok, Data) -> Data;
42
-formulate_reponse({ _, Reason }, _Data) -> { error, Reason }.
43
+parse_oneliner({ _, Reason }, _Data) -> { error, Reason };
44
+parse_oneliner(_, Data) -> { fini, Data }.
45
+
46
 
47
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_compress.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_compress.erl Changed
26
 
1
@@ -17,17 +17,16 @@
2
 
3
 -module(eimap_command_compress).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
+-export([new_command/1, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new(_Args) -> <<"COMPRESS DEFLATE">>.
12
+new_command(_Args) -> { <<"COMPRESS DEFLATE">>, single_line_response }.
13
 
14
-parse(Data, Tag) -> formulate_reponse(eimap_utils:check_response_for_failure(Data, Tag)).
15
-
16
-
17
-%% Private API
18
-formulate_reponse(ok) -> compression_active;
19
-formulate_reponse({ _, Reason }) -> { error, Reason }.
20
+formulate_response(Data, Tag) ->
21
+    case eimap_utils:check_response_for_failure(Data, Tag) of
22
+        ok -> compression_active;
23
+        { _, Reason } -> { error, Reason }
24
+    end.
25
 
26
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_getmetadata.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_getmetadata.erl Changed
75
 
1
@@ -17,44 +17,44 @@
2
 
3
 -module(eimap_command_getmetadata).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2, continue_parse/3]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new({ Folder }) -> new({ Folder, [] });
12
-new({ Folder, Attributes }) when is_list(Folder) -> new({ list_to_binary(Folder), Attributes });
13
-new({ Folder, Attributes }) ->
14
-    AttributesList = format_attributes(Attributes, <<>>),
15
-    Command = <<"GETMETADATA (DEPTH infinity) \"", Folder/binary, "\"", AttributesList/binary>>,
16
-    Command.
17
-
18
-parse(Data, Tag) -> continue_parse(Data, Tag, { <<>>, [] }).
19
-
20
-continue_parse(Data, Tag, { LastPartialLine, Acc }) ->
21
-    FullBuffer = <<LastPartialLine/binary, Data/binary>>,
22
-    { FullLinesBuffer, NewLastPartialLine } = eimap_utils:only_full_lines(FullBuffer),
23
-    Lines = binary:split(FullLinesBuffer, <<"\r\n">>, [global]),
24
-    process_line(Tag, NewLastPartialLine, Lines, Acc).
25
+new_command({ Folder }) -> new_command({ Folder, [] });
26
+new_command({ Folder, Attributes }) -> new_command({ Folder, Attributes, infinity, nomax });
27
+
28
+%% Depth and MaxSize are both optional arguments, so we have a 4-tuple version of new_command to accomodate this
29
+%% "Depth, Maxsize" could be replaced with a proplist of the form [ { depth, Depth }, { maxsize, MaxSize } ] but
30
+%% as this is an internal (to eimap) API there is little benefit for the performance cost
31
+new_command({ Folder, Attributes, Depth, MaxSize }) when is_list(Folder) -> new_command({ list_to_binary(Folder), Attributes, Depth, MaxSize });
32
+new_command({ Folder, Attributes, Depth, MaxSize }) ->
33
+    AttributesString = format_attributes(Attributes, <<>>),
34
+    DepthString = depth_param(Depth),
35
+    MaxSizeString = maxsize_param(MaxSize),
36
+    Command = metadata_command(DepthString, MaxSizeString, Folder, AttributesString),
37
+    { Command, multiline_response }.
38
+
39
+process_line(<<"* METADATA ", Details/binary>>, Acc) ->
40
+    Results = parse_folder(Details),
41
+    [Results|Acc];
42
+process_line(_Line, Acc) -> Acc.
43
 
44
+formulate_response(Result, Data) -> eimap_command:formulate_response(Result, Data).
45
 
46
 %% Private API
47
-process_line(_Tag, LastPartialLine, [], Acc) -> { more, fun ?MODULE:continue_parse/3, { LastPartialLine, Acc } };
48
-process_line(Tag, LastPartialLine, [Line|MoreLines], Acc) ->
49
-    case eimap_utils:is_tagged_response(Line, Tag) of
50
-        true ->
51
-            formulate_response(eimap_utils:check_response_for_failure(Line, Tag), Acc);
52
-        false ->
53
-            process_line(Tag, LastPartialLine, MoreLines, process_metadata_response(Line, Acc))
54
-    end.
55
+depth_param(infinity) -> <<"DEPTH infinity">>;
56
+depth_param(Depth) when is_integer(Depth) -> Bin = integer_to_binary(Depth), <<"DEPTH ", Bin/binary>>;
57
+depth_param(_) -> <<>>.
58
 
59
-formulate_response(ok, Data) -> { fini, Data };
60
-formulate_response({ _, Reason }, _Data) -> { error, Reason }.
61
+maxsize_param(Size) when is_integer(Size) -> Bin = integer_to_binary(Size), <<"MAXSIZE ", Bin/binary>>;
62
+maxsize_param(_) -> <<>>.
63
 
64
-process_metadata_response(<<"* METADATA ", Details/binary>>, Acc) ->
65
-    Results = parse_folder(Details),
66
-    [Results|Acc];
67
-process_metadata_response(_Line, Acc) -> Acc.
68
+metadata_command(<<>>, <<>>, Folder, Attributes) -> <<"GETMETADATA \"", Folder/binary, "\"", Attributes/binary>>;
69
+metadata_command(Depth, <<>>, Folder, Attributes) -> <<"GETMETADATA (", Depth/binary, ") \"", Folder/binary, "\"", Attributes/binary>>;
70
+metadata_command(<<>>, MaxSize, Folder, Attributes) -> <<"GETMETADATA (", MaxSize/binary, ") \"", Folder/binary, "\"", Attributes/binary>>;
71
+metadata_command(Depth, MaxSize, Folder, Attributes) -> <<"GETMETADATA (", Depth/binary, " ", MaxSize/binary, ") \"", Folder/binary, "\"", Attributes/binary>>.
72
 
73
 format_attributes([], <<>>) -> <<>>;
74
 format_attributes([], String) -> <<" (", String/binary, ")">>;
75
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_login.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_login.erl Changed
27
 
1
@@ -17,19 +17,19 @@
2
 
3
 -module(eimap_command_login).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new({ User, Password }) ->
12
+new_command({ User, Password }) ->
13
     UserBin = eimap_utils:ensure_binary(User),
14
     PasswordBin = eimap_utils:ensure_binary(Password),
15
-    <<"LOGIN ", UserBin/binary, " ", PasswordBin/binary>>.
16
+    { <<"LOGIN ", UserBin/binary, " ", PasswordBin/binary>>, multiline_response }.
17
 
18
-parse(Data, Tag) -> formulate_reponse(eimap_utils:check_response_for_failure(Data, Tag)).
19
+process_line(_Data, Acc) -> Acc.
20
 
21
 %% Private API
22
-formulate_reponse(ok) -> { fini, authed };
23
-formulate_reponse({ _, Reason }) -> { error, Reason }.
24
+formulate_response(ok, _Acc) -> { fini, authed };
25
+formulate_response({ _, Reason }, _Acc) -> { error, Reason }.
26
 
27
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_logout.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_logout.erl Changed
24
 
1
@@ -17,17 +17,13 @@
2
 
3
 -module(eimap_command_logout).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
-
7
-%% http://tools.ietf.org/html/rfc2342
8
+-export([new_command/1, process_line/2, formulate_response/2]).
9
 
10
 %% Public API
11
-new(_Args) -> <<"LOGOUT">>.
12
-
13
-parse(Data, Tag) -> formulate_reponse(eimap_utils:check_response_for_failure(Data, Tag)).
14
+new_command(_Args) -> { <<"LOGOUT">>, multiline_response }.
15
 
16
+process_line(_Data, Acc) -> Acc.
17
 
18
-%% Private API
19
-formulate_reponse(ok) -> { close_socket, ok };
20
-formulate_reponse({ _, Reason }) -> { close_socket, { error, Reason } }.
21
+formulate_response(ok, _Data) -> { close_socket, ok };
22
+formulate_response({ _, Reason }, _Data) -> { close_socket, { error, Reason } }.
23
 
24
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_namespace.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_namespace.erl Changed
51
 
1
@@ -17,44 +17,27 @@
2
 
3
 -module(eimap_command_namespace).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2, continue_parse/3]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new(_Args) -> <<"NAMESPACE">>.
12
+new_command(_Args) -> { <<"NAMESPACE">>, multiline_response }.
13
 
14
-%TODO support multi-packet continuing parse
15
-parse(Data, Tag) -> continue_parse(Data, Tag, []).
16
+process_line(<<"* NAMESPACE ", Data/binary>>, Acc) -> [process_shared_prefix_parts(Data, 1)|Acc];
17
+process_line(_Line, Acc) -> Acc.
18
 
19
-continue_parse(Data, Tag, Acc) ->
20
-    Lines = binary:split(Data, <<"\r\n">>, [global]), %%TODO partial lines
21
-    process_line(Tag, Lines, Acc).
22
 
23
-
24
-%% Private API
25
-process_line(_Tag, [], Acc) -> { more, fun ?MODULE:continue_parse/3, Acc };
26
-process_line(Tag, [Line|MoreLines], Acc) ->
27
-    case eimap_utils:is_tagged_response(Line, Tag) of
28
-        true ->
29
-            formulate_response(eimap_utils:check_response_for_failure(Line, Tag), Acc);
30
-        false ->
31
-            process_line(Tag, MoreLines, [process_shared_prefix_parts(Line)|Acc])
32
-    end.
33
-
34
-%TODO: multiline!
35
 formulate_response(ok, [Acc]) -> { fini, relevant_shared_prefix_parts(Acc) };
36
 formulate_response({ _, Reason }, _Acc) -> { error, Reason }.
37
 
38
+% Private API
39
 relevant_shared_prefix_parts([]) -> { none, none};
40
 relevant_shared_prefix_parts([[], [], _]) -> { none, none };
41
 relevant_shared_prefix_parts([[], Delim]) -> { none, Delim };
42
 relevant_shared_prefix_parts([SharedPrefix, [], _]) -> { SharedPrefix, "/" };
43
 relevant_shared_prefix_parts([SharedPrefix, Delim]) -> { SharedPrefix, Delim }.
44
 
45
-process_shared_prefix_parts(<<"* NAMESPACE ", Data/binary>>) -> process_shared_prefix_parts(Data, 1);
46
-process_shared_prefix_parts(_) -> [].
47
-
48
 process_shared_prefix_parts(<<"NIL ", Data/binary>>, PartNumber) ->
49
     process_shared_prefix_parts(Data, PartNumber + 1);
50
 process_shared_prefix_parts(<<"NIL", Data/binary>>, PartNumber) ->
51
erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_noop.erl Added
32
 
1
@@ -0,0 +1,30 @@
2
+%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
+%%
4
+%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
+%%
6
+%% This program is free software: you can redistribute it and/or modify
7
+%% it under the terms of the GNU General Public License as published by
8
+%% the Free Software Foundation, either version 3 of the License, or
9
+%% (at your option) any later version.
10
+%%
11
+%% This program is distributed in the hope that it will be useful,
12
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+%% GNU General Public License for more details.
15
+%%
16
+%% You should have received a copy of the GNU General Public License
17
+%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+-module(eimap_command_noop).
20
+-behavior(eimap_command).
21
+-export([new_command/1, process_line/2, formulate_response/2]).
22
+
23
+%% https://tools.ietf.org/html/rfc3501#section-6.3.2
24
+
25
+%% Public API
26
+new_command(_) -> { <<"NOOP">>, multiline_response }.
27
+
28
+process_line(Data, Acc) -> eimap_command:process_status_line(Data, Acc).
29
+
30
+formulate_response(Response, Acc) -> eimap_command:formulate_response(Response, Acc).
31
+
32
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_peek_message.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_peek_message.erl Changed
20
 
1
@@ -17,15 +17,15 @@
2
 
3
 -module(eimap_command_peek_message).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2, continue_parse/3]).
6
+-export([new_command/1, parse/2, continue_parse/3]).
7
 -record(parse_state, { body_size, parts, data }).
8
 -record(parts, { headers = <<"">>, body = <<"">>, flags = <<"">> }).
9
 
10
 %% https://tools.ietf.org/html/rfc3501#section-6.4.5
11
 
12
 %% Public API
13
-new(MessageID) when is_integer(MessageID) -> new(integer_to_binary(MessageID));
14
-new(MessageID) when is_binary(MessageID) -> <<"UID FETCH ",  MessageID/binary, " (FLAGS BODY.PEEK[HEADER] BODY.PEEK[TEXT])">>.
15
+new_command(MessageID) when is_integer(MessageID) -> new_command(integer_to_binary(MessageID));
16
+new_command(MessageID) when is_binary(MessageID) -> { <<"UID FETCH ",  MessageID/binary, " (FLAGS BODY.PEEK[HEADER] BODY.PEEK[TEXT])">>, blob_response }.
17
 
18
 continue_parse(Data, _Tag, #parse_state{ body_size = Size, parts = Parts, data = PrevData }) ->
19
     try_body_parse(<<PrevData/binary, Data/binary>>, Size, Parts).
20
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_starttls.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_starttls.erl Changed
26
 
1
@@ -17,17 +17,16 @@
2
 
3
 -module(eimap_command_starttls).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2]).
6
+-export([new_command/1, formulate_response/2]).
7
 
8
 %% http://tools.ietf.org/html/rfc2342
9
 
10
 %% Public API
11
-new(_Args) -> <<"STARTTLS">>.
12
+new_command(_Args) -> { <<"STARTTLS">>, single_line_response }.
13
 
14
-parse(Data, Tag) -> formulate_reponse(eimap_utils:check_response_for_failure(Data, Tag)).
15
-
16
-
17
-%% Private API
18
-formulate_reponse(ok) -> starttls;
19
-formulate_reponse({ _, Reason }) -> { error, Reason }.
20
+formulate_response(Data, Tag) ->
21
+    case eimap_utils:check_response_for_failure(Data, Tag) of
22
+        ok -> starttls;
23
+        { _, Reason } -> { error, Reason }
24
+    end.
25
 
26
erlang-eimap-0.1.5.tar.gz/src/commands/eimap_command_status.erl -> erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_status.erl Changed
67
 
1
@@ -17,25 +17,26 @@
2
 
3
 -module(eimap_command_status).
4
 -behavior(eimap_command).
5
--export([new/1, parse/2, continue_parse/3]).
6
+-export([new_command/1, process_line/2, formulate_response/2]).
7
 
8
 % https://tools.ietf.org/html/rfc5464
9
 
10
 % supported attributes: messages, recent, uidnext, uidvalidity, unseen, annotate
11
 % Public API
12
-new({ Folder, Attributes }) when is_list(Folder) -> new({ list_to_binary(Folder), Attributes });
13
-new({ <<>>, Attributes}) -> new({ <<"INBOX">>, Attributes });
14
-new({ Folder, []}) -> new({ Folder, [messages] });
15
-new({ Folder, Attributes }) when is_list(Attributes) ->
16
-          AttributesString = attribute_string(Attributes, <<>>),
17
-          <<"STATUS ", Folder/binary, " (", AttributesString/binary, ")">>.
18
+new_command({ Folder, Attributes }) when is_list(Folder) -> new_command({ list_to_binary(Folder), Attributes });
19
+new_command({ <<>>, Attributes}) -> new_command({ <<"INBOX">>, Attributes });
20
+new_command({ Folder, []}) -> new_command({ Folder, [messages] });
21
+new_command({ Folder, Attributes }) when is_list(Attributes) ->
22
+            AttributesString = attribute_string(Attributes, <<>>),
23
+            { <<"STATUS ", Folder/binary, " (", AttributesString/binary, ")">>, multiline_response }.
24
 
25
-parse(Data, Tag) -> continue_parse(Data, Tag, []).
26
-
27
-continue_parse(Data, Tag, Acc) ->
28
-    Lines = binary:split(Data, <<"\r\n">>, [global]),
29
-    process_line(Tag, Lines, Acc).
30
+process_line(<<"* STATUS ", Data/binary>>, Acc) ->
31
+    process_status_items(binary:match(Data, <<"(">>),
32
+                         binary:match(Data, <<")">>),
33
+                         Data, Acc);
34
+process_line(_, Acc) -> Acc.
35
 
36
+formulate_response(Result, Data) -> eimap_command:formulate_response(Result, Data).
37
 
38
 %% Private API
39
 attribute_string([], <<>>) -> attribute_string(messages);
40
@@ -49,25 +50,7 @@
41
         Attr -> attribute_string(Attributes, <<String/binary, " ", Attr/binary>>)
42
     end.
43
 
44
-process_line(_Tag, [], Acc) -> { more, fun ?MODULE:continue_parse/3, Acc };
45
-process_line(Tag, [Line|MoreLines], Acc) ->
46
-    case eimap_utils:is_tagged_response(Line, Tag) of
47
-        true ->
48
-            formulate_response(eimap_utils:check_response_for_failure(Line, Tag), Acc);
49
-        false ->
50
-            process_line(Tag, MoreLines, process_status_items(Line, Acc))
51
-    end.
52
-
53
-%TODO: multiline, partial lines
54
-formulate_response(ok, Acc) -> { fini, Acc };
55
-formulate_response({ _, Reason }, _Acc) -> { error, Reason }.
56
-
57
-process_status_items(<<"* STATUS ", Data/binary>>, Acc) ->
58
-    process_status_items(binary:match(Data, <<"(">>),
59
-                         binary:match(Data, <<")">>),
60
-                         Data, Acc);
61
-process_status_items(_, Acc) -> Acc.
62
-
63
+% Private API
64
 process_status_items(nomatch, _, _Data, Acc) -> Acc;
65
 process_status_items(_, nomatch, _Data, Acc) -> Acc;
66
 process_status_items({ Start, _ }, { End, _ }, Data, Acc) ->
67
erlang-eimap-0.2.4.tar.gz/src/commands/eimap_command_switch_folder.erl Added
67
 
1
@@ -0,0 +1,65 @@
2
+%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
+%%
4
+%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
+%%
6
+%% This program is free software: you can redistribute it and/or modify
7
+%% it under the terms of the GNU General Public License as published by
8
+%% the Free Software Foundation, either version 3 of the License, or
9
+%% (at your option) any later version.
10
+%%
11
+%% This program is distributed in the hope that it will be useful,
12
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+%% GNU General Public License for more details.
15
+%%
16
+%% You should have received a copy of the GNU General Public License
17
+%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+-module(eimap_command_switch_folder).
20
+-behavior(eimap_command).
21
+-export([new_command/1, process_line/2, process_tagged_line/2, formulate_response/2]).
22
+
23
+%% https://tools.ietf.org/html/rfc3501#section-6.3.2
24
+
25
+%% Public API
26
+new_command({ MBox, Mechanism }) when is_list(MBox) -> new_command({ list_to_binary(MBox), Mechanism });
27
+new_command({ MBox, select }) -> new_command(MBox);
28
+new_command({ MBox, examine }) -> { <<"EXAMINE \"", MBox/binary, "\"">>, all_multiline_response };
29
+new_command(MBox) when is_list(MBox) -> new_command(list_to_binary(MBox));
30
+new_command(MBox) when is_binary(MBox) -> { <<"SELECT \"", MBox/binary, "\"">>, all_multiline_response }.
31
+
32
+%TODO: parse:
33
+% REQUIRED untagged responses: FLAGS, EXISTS, RECENT
34
+% REQUIRED OK untagged responses:  UNSEEN,  PERMANENTFLAGS, UIDNEXT, UIDVALIDITY
35
+process_line(<<"* OK [", Info/binary>>, Acc) ->
36
+    case binary:match(Info, <<"]">>) of
37
+        nomatch -> Acc;
38
+        { ClosingBracket, _ } -> process_ok_response(binary:part(Info, 0, ClosingBracket), Acc)
39
+    end;
40
+process_line(<<"* FLAGS ", FlagString/binary>>, Acc) ->
41
+    Flags = eimap_utils:parse_flags(FlagString),
42
+    [{ flags, Flags }|Acc];
43
+process_line(<<"* ", Info/binary>>, Acc) ->
44
+    case binary:split(Info, <<" ">>) of
45
+        [ Value, Key ] -> [{ eimap_utils:binary_to_atom(Key), binary_to_integer(Value) }|Acc];
46
+        _ ->Acc
47
+    end;
48
+process_line(_Data, Acc) -> Acc.
49
+
50
+process_tagged_line(Data, Acc) ->
51
+    case binary:match(Data, <<"[READ-WRITE]">>) of
52
+        nomatch -> [{ writeable, false }|Acc];
53
+        _ -> [{ writeable, true}|Acc]
54
+    end.
55
+
56
+formulate_response(Response, Acc) -> eimap_command:formulate_response(Response, Acc).
57
+
58
+%PRIVATE
59
+process_ok_response(<<"PERMANENTFLAGS ", FlagString/binary>>, Acc) -> [{ permanent_flags, eimap_utils:parse_flags(FlagString) }|Acc];
60
+process_ok_response(<<"UIDVALIDITY ", String/binary>>, Acc) -> [{ uid_validity, binary_to_integer(String) }|Acc];
61
+process_ok_response(<<"UIDNEXT ", String/binary>>, Acc) -> [{ uid_next, binary_to_integer(String) }|Acc];
62
+process_ok_response(<<"HIGHESTMODSEQ ", String/binary>>, Acc) -> [{ highest_mod_seq, binary_to_integer(String) }|Acc];
63
+process_ok_response(<<"URLMECH ", String/binary>>, Acc) -> [{ url_mech, eimap_utils:binary_to_atom(String) }|Acc]; %TODO: this is very ugly
64
+process_ok_response(<<"ANNOTATIONS ", String/binary>>, Acc) -> [{ annotations, binary_to_integer(String) }|Acc];
65
+process_ok_response(String, Acc) -> lager:warning("eimap_command_select_folder: Unknown untagged OK response ~s~n", [String]), Acc.
66
+
67
erlang-eimap-0.1.5.tar.gz/src/eimap.app.src -> erlang-eimap-0.2.4.tar.gz/src/eimap.app.src Changed
10
 
1
@@ -2,7 +2,7 @@
2
 {application, eimap,
3
  [
4
   { description, "IMAP client implementation" },
5
-  { vsn, "0.1.5" },
6
+  { vsn, "0.2.4" },
7
   { registered, [] },
8
   { applications, [
9
                    kernel,
10
erlang-eimap-0.1.5.tar.gz/src/eimap.erl -> erlang-eimap-0.2.4.tar.gz/src/eimap.erl Changed
487
 
1
@@ -17,7 +17,6 @@
2
 
3
 -module(eimap).
4
 -behaviour(gen_fsm).
5
--include("eimap.hrl").
6
 
7
 %% API
8
 -export([start_link/1,
9
@@ -33,12 +32,13 @@
10
          capabilities/3,
11
          login/5, logout/3,
12
          compress/1,
13
-         get_server_metadata/4,
14
+         get_server_metadata/4, get_server_metadata/6,
15
          get_folder_status/5,
16
-         get_folder_metadata/5,
17
+         get_folder_metadata/5, get_folder_metadata/7,
18
          get_folder_annotations/4,
19
-         get_message_headers_and_body/5,
20
-         get_path_tokens/3]).
21
+         peek_message_headers_and_body/5,
22
+         get_path_tokens/3,
23
+         noop/3]).
24
 
25
 %% gen_fsm callbacks
26
 -export([disconnected/2, idle/2, passthrough/2, wait_response/2, startingtls/2]).
27
@@ -47,26 +47,16 @@
28
 %% state record definition
29
 -record(state, { host, port, tls, tls_state = false, socket, server_id = <<>>,
30
                  command_serial = 1, command_queue = queue:new(),
31
-                 current_command, current_mbox, parse_state,
32
+                 current_command, current_mbox,
33
                  passthrough = false, passthrough_recv, passthrough_send_buffer = <<>>,
34
-                 inflator, deflator}).
35
--record(command, { tag, mbox, message, from, response_token, parse_fun }).
36
+                 inflator, deflator, process_command_queue_guard = false }).
37
+-record(command, { tag, mbox, message, from, response_type, response_token, parse_state }).
38
 
39
 -define(SSL_UPGRADE_TIMEOUT, 5000).
40
 -define(TCP_CONNECT_TIMEOUT, 5000).
41
 
42
--export([test/0]).
43
-test() ->
44
-    ServerConfig = #eimap_server_config{ host = "192.168.56.101", port = 143, tls = false },
45
-    { ok, Conn } = start_link(ServerConfig),
46
-    login(Conn, self(), undefined, "doe", "doe"),
47
-    get_folder_metadata(Conn, self(), undefined, "*", ["/shared/vendor/kolab/folder-type"]),
48
-    logout(Conn, self(), undefined),
49
-    connect(Conn).
50
-
51
-
52
 %% public API
53
-start_link(ServerConfig) when is_record(ServerConfig, eimap_server_config) -> gen_fsm:start_link(?MODULE, ServerConfig, []).
54
+start_link(Options) when is_list(Options) -> gen_fsm:start_link(?MODULE, Options, []).
55
 
56
 start_passthrough(PID, Receiver) when is_pid(Receiver)  -> gen_fsm:send_all_state_event(PID, { start_passthrough, Receiver } ).
57
 stop_passthrough(PID) -> gen_fsm:send_all_state_event(PID, stop_passthrough).
58
@@ -75,93 +65,80 @@
59
 connect(PID, From, ResponseToken) -> gen_fsm:send_all_state_event(PID, { connect, From, ResponseToken }).
60
 disconnect(PID) -> gen_fsm:send_all_state_event(PID, disconnect).
61
 
62
-compress(PID) when is_pid(PID) ->
63
-    Command = #command{ message = eimap_command_compress:new(ok),
64
-                        from = PID, response_token = compress,
65
-                        parse_fun = fun eimap_command_compress:parse/2 },
66
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
67
+-spec compress(EImap :: pid()) -> ok.
68
+compress(EImap) when is_pid(EImap) ->
69
+    send_command_to_queue(EImap, EImap, compress, eimap_command_compress, ok).
70
 
71
-starttls(PID, From, ResponseToken) when is_pid(PID) ->
72
-    Command = #command{ message = eimap_command_starttls:new(ok),
73
-                        from = From, response_token = ResponseToken,
74
-                        parse_fun = fun eimap_command_starttls:parse/2 },
75
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
76
+-spec starttls(EImap :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
77
+starttls(EImap, From, ResponseToken) when is_pid(EImap) ->
78
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_starttls, ok).
79
 
80
-capabilities(PID, From, ResponseToken) when is_pid(PID) ->
81
-    Command = #command{ message = eimap_command_capability:new(noparams),
82
-                        from = From, response_token = ResponseToken,
83
-                        parse_fun = fun eimap_command_capability:parse/2 },
84
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
85
+-spec capabilities(EImap :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
86
+capabilities(EImap, From, ResponseToken) when is_pid(EImap) ->
87
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_capability, ok).
88
 
89
--spec login(PID :: pid(), From :: pid(), ResponseToken :: any(), User :: list() | binary(), Pass :: list() | binary()) -> ok.
90
-login(PID, From, ResponseToken, User, Pass) ->
91
-    Command = #command{ message = eimap_command_login:new({ User, Pass }),
92
-                        from = From, response_token = ResponseToken,
93
-                        parse_fun = fun eimap_command_login:parse/2 },
94
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
95
+-spec login(EImap :: pid(), From :: pid(), ResponseToken :: any(), User :: list() | binary(), Pass :: list() | binary()) -> ok.
96
+login(EImap, From, ResponseToken, User, Pass) ->
97
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_login, { User, Pass }).
98
 
99
--spec logout(PID :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
100
-logout(PID, From, ResponseToken) ->
101
-    Command = #command{ message = eimap_command_logout:new(ok),
102
-                        from = From, response_token = ResponseToken,
103
-                        parse_fun = fun eimap_command_logout:parse/2 },
104
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
105
+-spec logout(EImap :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
106
+logout(EImap, From, ResponseToken) ->
107
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_logout, ok).
108
 
109
 -type status_property() :: messages | recent | uidnext | uidvalidity | unseen.
110
 -type status_properties() :: [status_property()].
111
--spec get_folder_status(PID :: pid(), From :: pid(), ResponseToken :: any(), Folder :: list() | binary(), Properties:: status_properties()) -> ok.
112
-get_folder_status(PID, From, ResponseToken, Folder, Properties) ->
113
-    Command = #command{ message = eimap_command_status:new({ Folder, Properties }),
114
-                        from = From, response_token = ResponseToken,
115
-                        parse_fun = fun eimap_command_status:parse/2 },
116
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
117
+-spec get_folder_status(EImap :: pid(), From :: pid(), ResponseToken :: any(), Folder :: list() | binary(), Properties:: status_properties()) -> ok.
118
+get_folder_status(EImap, From, ResponseToken, Folder, Properties) ->
119
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_status, { Folder, Properties }).
120
 
121
--spec get_folder_metadata(PID :: pid(), From :: pid(), ResponseToken :: any(), Folder :: list() | binary(), Properties:: [list() | binary()]) -> ok.
122
-get_folder_metadata(PID, From, ResponseToken, Folder, Properties) ->
123
-    Command = #command{ message = eimap_command_getmetadata:new({ Folder, Properties}),
124
-                        from = From, response_token = ResponseToken,
125
-                        parse_fun = fun eimap_command_getmetadata:parse/2 },
126
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
127
+-spec get_folder_metadata(EImap :: pid(), From :: pid(), ResponseToken :: any(), Folder :: list() | binary(), Properties:: [list() | binary()]) -> ok.
128
+get_folder_metadata(EImap, From, ResponseToken, Folder, Properties) ->
129
+    get_folder_metadata(EImap, From, ResponseToken, Folder, Properties, infinity, nomax).
130
 
131
--spec get_server_metadata(PID :: pid(), From :: pid(), ResponseToken :: any(), Properties:: [list() | binary()]) -> ok.
132
-get_server_metadata(PID, From, ResponseToken, Properties) ->
133
-    Command = #command{ message = eimap_command_getmetadata:new({ <<>>, Properties}),
134
-                        from = From, response_token = ResponseToken,
135
-                        parse_fun = fun eimap_command_getmetadata:parse/2 },
136
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
137
+-spec get_folder_metadata(EImap :: pid(), From :: pid(), ResponseToken :: any(), Folder :: list() | binary(), Properties:: [list() | binary()], Depth :: infinity | integer(), MaxSize :: nomax | integer()) -> ok.
138
+get_folder_metadata(EImap, From, ResponseToken, Folder, Properties, Depth, MaxSize) ->
139
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_getmetadata, { Folder, Properties, Depth, MaxSize }).
140
 
141
-get_folder_annotations(PID, From, ResponseToken, Folder) when is_list(Folder) ->
142
-    get_folder_annotations(PID, From, ResponseToken, list_to_binary(Folder));
143
-get_folder_annotations(PID, From, ResponseToken, Folder) when is_binary(Folder) ->
144
-    Command = #command{ message = eimap_command_annotation:new(Folder),
145
-                        from = From, response_token = ResponseToken,
146
-                        parse_fun = fun eimap_command_annotation:parse/2 },
147
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
148
+-spec get_server_metadata(EImap :: pid(), From :: pid(), ResponseToken :: any(), Properties:: [list() | binary()]) -> ok.
149
+get_server_metadata(EImap, From, ResponseToken, Properties) ->
150
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_getmetadata, { <<>>, Properties, infinity, nomax }).
151
 
152
-get_message_headers_and_body(PID, From, ResponseToken, Folder, MessageID) ->
153
-    %%lager:info("SELECT_DEBUG: peeking message ~p ~p", [Folder, MessageID]),
154
-    Command = #command{ mbox = Folder, message = eimap_command_peek_message:new(MessageID),
155
-                        from = From, response_token = ResponseToken,
156
-                        parse_fun = fun eimap_command_peek_message:parse/2 },
157
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
158
+-spec get_server_metadata(EImap :: pid(), From :: pid(), ResponseToken :: any(), Properties:: [list() | binary()], Depth :: infinity | integer(), MaxSize :: nomax | integer()) -> ok.
159
+get_server_metadata(EImap, From, ResponseToken, Properties, Depth, MaxSize) ->
160
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_getmetadata, { <<>>, Properties, Depth, MaxSize }).
161
+
162
+-spec get_folder_annotations(EImap :: pid(), From :: pid(), ResponseToken :: any(), Folder :: [list() | binary()]) -> ok.
163
+get_folder_annotations(EImap, From, ResponseToken, Folder) ->
164
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_annotation, Folder).
165
+
166
+-spec peek_message_headers_and_body(EImap :: pid(), From :: pid(), ResponseToken :: any(), Folder :: [list() | binary()], MessageID :: [integer() | binary()]) -> ok.
167
+peek_message_headers_and_body(EImap, From, ResponseToken, Folder, MessageID) ->
168
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_peek_message, MessageID, Folder).
169
+
170
+-spec get_path_tokens(EImap :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
171
+get_path_tokens(EImap, From, ResponseToken) ->
172
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_namespace, ok).
173
+
174
+-spec noop(EImap :: pid(), From :: pid(), ResponseToken :: any()) -> ok.
175
+noop(EImap, From, ResponseToken) ->
176
+    send_command_to_queue(EImap, From, ResponseToken, eimap_command_noop, ok).
177
 
178
-get_path_tokens(PID, From, ResponseToken) ->
179
-    Command = #command{ message = eimap_command_namespace:new([]),
180
-                        from = From, response_token = ResponseToken,
181
-                        parse_fun = fun eimap_command_namespace:parse/2 },
182
-    gen_fsm:send_all_state_event(PID, { ready_command, Command }).
183
 
184
 %% gen_server API
185
-init(#eimap_server_config{ host = Host, port = Port, tls = TLS }) ->
186
+init(Options) ->
187
+    Host = proplists:get_value(host, Options, "127.0.0.1"),
188
+    Port = proplists:get_value(port, Options, 143),
189
+    TLS = proplists:get_value(tls, Options, false),
190
     State = #state { host = Host, port = Port, tls  = TLS },
191
     { ok, disconnected, State }.
192
 
193
 disconnected({ connect, Receiver, ResponseToken }, #state{ command_queue = CommandQueue, host = Host, port = Port, tls = TLS, socket = undefined } = State) ->
194
     %lager:debug("CONNECTING! ~p ~p", [Receiver, ResponseToken]),
195
     { {ok, Socket}, TlsState, SendCapabilitiesTo, NewCommandQueue } = create_socket(Host, Port, TLS, Receiver, ResponseToken, CommandQueue),
196
-    Command = #command{ message = eimap_command_capability:new([]),
197
+    { Message, ResponseType } = eimap_command_capability:new_command(parse_serverid),
198
+    Command = #command{ message = Message, response_type = ResponseType,
199
                         from = SendCapabilitiesTo, response_token = { connected, Receiver, ResponseToken },
200
-                        parse_fun = fun eimap_command_capability:parse/2 },
201
+                        parse_state = eimap_command_capability },
202
     { next_state, wait_response, State#state { socket = Socket, tls_state = TlsState, current_command = Command, command_queue = NewCommandQueue } };
203
 disconnected(Command, State) when is_record(Command, command) ->
204
     { next_state, disconnected, enque_command(Command, State) }.
205
@@ -182,19 +159,19 @@
206
     Receiver ! { imap_server_response, Data },
207
     { next_state, passthrough, State };
208
 passthrough(Command, State) when is_record(Command, command) ->
209
-    gen_fsm:send_event(self(), process_command_queue),
210
-    { next_state, idle, enque_command(Command, State) }.
211
+    NewState = ensure_process_command_queue(State),
212
+    { next_state, idle, enque_command(Command, NewState) }.
213
 
214
 idle(process_command_queue, #state{ command_queue = Queue } = State) ->
215
+    UnguardedState = State#state{ process_command_queue_guard = false },
216
     case queue:out(Queue) of
217
        { { value, Command }, ModifiedQueue } when is_record(Command, command) ->
218
             %lager:info("Clearing queue of ~p", [Command]),
219
-            NewState = send_command(Command, State#state{ command_queue = ModifiedQueue }),
220
+            NewState = send_command(Command, UnguardedState#state{ command_queue = ModifiedQueue }),
221
             { next_state, wait_response, NewState };
222
-       { empty, ModifiedQueue } ->
223
+       { empty, _ModifiedQueue } ->
224
             NextState = next_state_after_emptied_queue(State),
225
-            %lager:info("Queue was empty moving on to ... ~p", [NextState]),
226
-            { next_state, NextState, State#state{ command_queue = ModifiedQueue } }
227
+            { next_state, NextState, UnguardedState }
228
     end;
229
 idle({ data, _Data }, State) ->
230
     %%lager:info("Idling, server sent: ~p", [_Data]),
231
@@ -204,7 +181,8 @@
232
     NewState = send_command(Command, State),
233
     { next_state, wait_response, NewState };
234
 idle(_Event, State) ->
235
-    { next_state, idle, State }.
236
+    { next_state, idle, ensure_process_command_queue(State) }.
237
+
238
 
239
 next_state_after_emptied_queue(#state{ passthrough = true }) ->
240
     gen_fsm:send_event(self(), flush_passthrough_buffer),
241
@@ -212,19 +190,22 @@
242
 next_state_after_emptied_queue(_State) ->
243
     idle.
244
 
245
+ensure_process_command_queue(State) ->
246
+    case State#state.process_command_queue_guard of
247
+        true ->
248
+            State;
249
+        _ ->
250
+            gen_fsm:send_event(self(), process_command_queue),
251
+            State#state{ process_command_queue_guard = true }
252
+    end.
253
 
254
 %%TODO a variant that checks "#command{ from = undefined }" to avoid parsing responses which will go undelivered?
255
 wait_response(Command, State) when is_record(Command, command) ->
256
     { next_state, wait_response, enque_command(Command, State) };
257
-wait_response({ data, _Data }, #state{ current_command = #command{ parse_fun = undefined } } = State) ->
258
-    gen_fsm:send_event(self(), process_command_queue),
259
-    { next_state, idle, State };
260
-wait_response({ data, Data }, #state{ current_command = #command{ parse_fun = Fun, tag = Tag } } = State) when is_function(Fun, 2) ->
261
-    Response = Fun(Data, Tag),
262
-    %%lager:info("Response from parser was ~p ~p, size of queue ~p", [More, Response, queue:len(State#state.command_queue)]),
263
-    next_command_after_response(Response, State);
264
-wait_response({ data, Data }, #state{ parse_state = ParseState, current_command = #command{ parse_fun = Fun, tag = Tag } } = State) when is_function(Fun, 3) ->
265
-    Response = Fun(Data, Tag, ParseState),
266
+wait_response({ data, _Data }, #state{ current_command = #command{ parse_state = undefined } } = State) ->
267
+    { next_state, idle, ensure_process_command_queue(State) };
268
+wait_response({ data, Data }, #state{ current_command = #command{ response_type = ResponseType, parse_state = CommandState , tag = Tag } } = State) ->
269
+    Response = eimap_command:parse_response(ResponseType, Data, Tag, CommandState),
270
     %%lager:info("Response from parser was ~p ~p, size of queue ~p", [More, Response, queue:len(State#state.command_queue)]),
271
     next_command_after_response(Response, State);
272
 wait_response(process_command_queue, State) ->
273
@@ -235,8 +216,8 @@
274
     { next_state, startingtls, State#state{ passthrough_send_buffer = <<Buffer/binary, Data>> } };
275
 startingtls(Command, State) when is_record(Command, command) ->
276
     { next_state, startingtls, enque_command(Command, State) };
277
-startingtls({ data, Data }, #state{ current_command = #command{ parse_fun = Fun, tag = Tag } } = State) when is_function(Fun, 2) ->
278
-    Response = Fun(Data, Tag),
279
+startingtls({ data, Data }, #state{ current_command = #command{ response_type = ResponseType, parse_state = CommandState, tag = Tag } } = State) ->
280
+    Response = eimap_command:parse_response(ResponseType, Data, Tag, CommandState),
281
     %%lager:info("Response from parser was ~p ~p, size of queue ~p", [More, Response, queue:len(State#state.command_queue)]),
282
     next_command_after_response(Response, State).
283
 
284
@@ -249,27 +230,26 @@
285
 handle_event(disconnect, _StateName, State) ->
286
     close_socket(State),
287
     { next_state, disconnected, reset_state(State) };
288
+handle_event({ ready_command, Command }, StateName, State) when is_record(Command, command) ->
289
+    ?MODULE:StateName(Command, State);
290
 handle_event({ start_passthrough, Receiver }, StateName, State) ->
291
     { next_state, StateName, State#state{ passthrough = true, passthrough_recv = Receiver } };
292
 handle_event(stop_passthrough, StateName, State) ->
293
-    NextState = case StateName of
294
-                    passthrough ->
295
-                        gen_fsm:send_event(self(), process_command_queue),
296
-                        idle;
297
-                    State -> State
298
+    { NextStateName, NewState } = case StateName of
299
+                    passthrough -> { idle, ensure_process_command_queue(State) };
300
+                    StateName -> { StateName, State }
301
                 end,
302
-    { next_state, NextState, State#state{ passthrough = false } };
303
-handle_event({ ready_command, Command }, StateName, State) when is_record(Command, command) ->
304
-    ?MODULE:StateName(Command, State);
305
+    { next_state, NextStateName, NewState#state{ passthrough = false } };
306
 handle_event({ passthrough, Data }, passthrough, #state{ passthrough = true } = State) ->
307
     ?MODULE:passthrough({ passthrough, Data }, State);
308
 handle_event({ passthrough, Data }, StateName, #state{ passthrough = true, passthrough_send_buffer = Buffer } = State) ->
309
     NewBuffer = <<Buffer/binary, Data/binary>>,
310
+    NewState =
311
     case StateName of
312
-        idle -> gen_fsm:send_event(self(), process_command_queue);
313
-        _ -> ok
314
+        idle -> ensure_process_command_queue(State);
315
+        _ -> State
316
     end,
317
-    { next_state, StateName, State#state{ passthrough_send_buffer = NewBuffer } };
318
+    { next_state, StateName, NewState#state{ passthrough_send_buffer = NewBuffer } };
319
 handle_event(_Event, StateName, State) -> { next_state, StateName, State}.
320
 
321
 handle_sync_event(_Event, _From, StateName, State) -> { next_state, StateName, State}.
322
@@ -287,13 +267,13 @@
323
     %lager:info("Received from server plaintext: ~s", [Data]),
324
     ?MODULE:StateName({ data, Data }, State);
325
 handle_info({ ssl_closed, Socket }, _StateName, #state{ socket = Socket, host = Host, port = Port } = State) ->
326
-    lager:info("~p Disconnected from ~p:~p .\n", [self(), Host, Port]),
327
+    lager:debug("~p Disconnected from ~p:~p .\n", [self(), Host, Port]),
328
     { stop, normal, State };
329
 handle_info({ ssl_error, Socket, _Reason }, _StateName, #state{ socket = Socket, host = Host, port = Port } = State) ->
330
     lager:info("~p Disconnected due to socket error from ~p:~p .\n", [self(), Host, Port]),
331
     { stop, normal, State };
332
 handle_info({ tcp_closed, Socket }, _StateName, #state{ socket = Socket, host = Host, port = Port } = State) ->
333
-    lager:info("~p Disconnected from ~p:~p .\n", [self(), Host, Port]),
334
+    lager:debug("~p Disconnected from ~p:~p .\n", [self(), Host, Port]),
335
     { stop, normal, State };
336
 handle_info({ tcp_error, Socket, _Reason }, _StateName, #state{ socket = Socket, host = Host, port = Port } = State) ->
337
     lager:info("~p Disconnected due to socket error from ~p:~p .\n", [self(), Host, Port]),
338
@@ -313,10 +293,15 @@
339
             %lager:debug("Connected, capabilities are: ~s; ServerID is ~s", [Capabilities, ServerID]),
340
             send_hello_string(Capabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver)
341
     end,
342
-    { next_state, idle, State#state{ parse_state = none, server_id = ServerID } };
343
+    { next_state, idle, State#state{ current_command = undefined, server_id = ServerID } };
344
 handle_info({ { posttls_capabilities, Receiver, ResponseToken }, Capabilities }, _StateName, #state{ server_id = ServerID, passthrough = Passthrough, passthrough_recv = PassthroughReceiver } = State) ->
345
-    send_hello_string(Capabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver),
346
-    { next_state, idle, State#state{ parse_state = none } };
347
+    OurCapabilities =
348
+        case binary:match(Capabilities, <<"STARTTLS">>) of
349
+            nomatch -> <<Capabilities/binary, " STARTTLS">>;
350
+            _ -> Capabilities
351
+        end,
352
+    send_hello_string(OurCapabilities, ServerID, Receiver, ResponseToken, Passthrough, PassthroughReceiver),
353
+    { next_state, idle, State#state{ current_command = none } };
354
 handle_info({ { selected, MBox }, ok }, StateName, State) ->
355
     %%lager:info("~p Selected mbox ~p", [self(), MBox]),
356
     { next_state, StateName, State#state{ current_mbox = MBox } };
357
@@ -336,13 +321,23 @@
358
 code_change(_OldVsn, Statename, State, _Extra) -> { ok, Statename, State }.
359
 
360
 %% private API
361
+send_command_to_queue(EImap, From, ResponseToken, Module, Args) ->
362
+    send_command_to_queue(EImap, From, ResponseToken, Module, Args, undefined).
363
+send_command_to_queue(EImap, From, ResponseToken, Module, Args, Folder) ->
364
+    { Message, ResponseType } = Module:new_command(Args),
365
+    Command = #command{ mbox = Folder, message = Message, response_type = ResponseType,
366
+                        from = From, response_token = ResponseToken,
367
+                        parse_state = Module },
368
+    gen_fsm:send_all_state_event(EImap, { ready_command, Command }).
369
+
370
 send_hello_string(Capabilities, ServerId, Receiver, ResponseToken, Passthrough, PassthroughReceiver) ->
371
-    Message = <<"* OK [CAPABILITY ", Capabilities/binary, "] ", ServerId/binary, "\r\n">>,
372
-    notify_of_response(Message, Receiver, ResponseToken),
373
-    passthrough_capabilities(Message, Passthrough, PassthroughReceiver).
374
+    notify_of_response([{ capabilities, Capabilities }, { server_id, ServerId } ], Receiver, ResponseToken),
375
+    passthrough_capabilities(Capabilities, ServerId, Passthrough, PassthroughReceiver).
376
 
377
-passthrough_capabilities(Response, true, Receiver) -> Receiver ! { imap_server_response, Response };
378
-passthrough_capabilities(_Response, _Passthrough, _Receiver) -> ok.
379
+passthrough_capabilities(Capabilities, ServerId, true, Receiver) ->
380
+    Message = <<"* OK [CAPABILITY ", Capabilities/binary, "] ", ServerId/binary, "\r\n">>,
381
+    Receiver ! { imap_server_response, Message };
382
+passthrough_capabilities(_Capabilities, _ServerId, _Passthrough, _Receiver) -> ok.
383
 
384
 notify_of_response(none, _Command) -> ok;
385
 notify_of_response(Response, #command { from = From, response_token = Token }) -> notify_of_response(Response, From, Token);
386
@@ -356,28 +351,26 @@
387
 notify_of_mbox_failure_during_filter(Command, true) -> notify_of_response({ error, mailboxnotfound }, Command), false;
388
 notify_of_mbox_failure_during_filter(_Command, false) -> true.
389
 
390
-next_command_after_response({ more, Fun, ParseState }, State) when is_function(Fun, 3) ->
391
-    { next_state, wait_response, State#state{ parse_state = ParseState, current_command = State#state.current_command#command{ parse_fun = Fun } } };
392
 next_command_after_response({ more, ParseState }, State) ->
393
-    { next_state, wait_response, State#state{ parse_state = ParseState } };
394
+    { next_state, wait_response, State#state{ current_command = State#state.current_command#command{ parse_state = ParseState } } };
395
 next_command_after_response({ error, _ } = ErrorResponse, State) ->
396
     notify_of_response(ErrorResponse, State#state.current_command),
397
-    gen_fsm:send_event(self(), process_command_queue),
398
-    { next_state, idle, State#state{ parse_state = none } };
399
+    NewState = ensure_process_command_queue(State),
400
+    { next_state, idle, NewState#state{ current_command = undefined } };
401
 next_command_after_response({ fini, Response }, State) ->
402
     %lager:info("Notifying with ~p", [State#state.current_command]),
403
     notify_of_response(Response, State#state.current_command),
404
-    gen_fsm:send_event(self(), process_command_queue),
405
-    { next_state, idle, State#state{ parse_state = none } };
406
+    NewState = ensure_process_command_queue(State),
407
+    { next_state, idle, NewState#state{ current_command = undefined } };
408
 next_command_after_response(starttls, State) ->
409
     { TLSState, Socket } = upgrade_socket(State),
410
     %lager:info("~p Upgraded the socket ...", [self()]),
411
-    gen_fsm:send_event(self(), process_command_queue),
412
-    { next_state, idle, State#state{ parse_state = none, socket = Socket, tls_state = TLSState } };
413
+    NewState = ensure_process_command_queue(State),
414
+    { next_state, idle, NewState#state{ current_command = undefined, socket = Socket, tls_state = TLSState } };
415
 next_command_after_response(compression_active, State) ->
416
     { Inflator, Deflator } = eimap_utils:new_imap_compressors(),
417
-    gen_fsm:send_event(self(), process_command_queue),
418
-    { next_state, idle, State#state{ inflator = Inflator, deflator = Deflator } };
419
+    NewState = ensure_process_command_queue(State),
420
+    { next_state, idle, NewState#state{ current_command = undefined, inflator = Inflator, deflator = Deflator } };
421
 next_command_after_response({ close_socket, Response }, State) ->
422
     notify_of_response(Response, State#state.current_command),
423
     { stop, normal, State }.
424
@@ -393,12 +386,15 @@
425
     %lager:debug("Setting up the tls creation with ultimate end point of ~p ~p", [Receiver, ResponseToken]),
426
     % we do an implicit TLS by adding a starttls command and then a capability command so we can
427
     % pretend to the user that the socket just magically opened up like this.
428
-    TlsCommand = #command{ message = eimap_command_starttls:new(noparams),
429
+    %TODO: some duplicated code here; would be nice to clean this up a bit?
430
+    { TlsMessage, TlsResponseType } = eimap_command_starttls:new_command(ok),
431
+    TlsCommand = #command{ message = TlsMessage, response_type = TlsResponseType,
432
                            from = self(), response_token = undefined,
433
-                           parse_fun = fun eimap_command_starttls:parse/2 },
434
-    CapabilitiesCommand = #command{ message = eimap_command_capability:new(noparams),
435
+                           parse_state = eimap_command_starttls },
436
+    { CapMessage, CapResponseType } = eimap_command_capability:new_command(ok),
437
+    CapabilitiesCommand = #command{ message = CapMessage, response_type = CapResponseType,
438
                                     from = self(), response_token = { posttls_capabilities, Receiver, ResponseToken },
439
-                                    parse_fun = fun eimap_command_capability:parse/2 },
440
+                                    parse_state = eimap_command_capability },
441
     % note the use of queue:in_r to _prepend_ the commands so they get run first even if the user
442
     % has pre-connection queued up commands
443
     NewCommandQueue = queue:in_r(TlsCommand, queue:in_r(CapabilitiesCommand, CommandQueue)),
444
@@ -444,28 +440,30 @@
445
 send_command(Command, State) ->
446
     send_command(fun gen_tcp:send/2, Command, State).
447
 
448
-send_command(Fun, #command{ mbox = undefined } = Command, State) ->
449
+send_command(SocketFun, #command{ mbox = undefined } = Command, State) ->
450
     %%lager:info("~p SELECT_DEBUG issuing command without mbox: ~p", [self(), Command#command.message]),
451
-    send_command_now(Fun, Command, State);
452
-send_command(Fun, #command{ mbox = MBox } = Command, #state{ current_mbox = CurrentMbox } = State) ->
453
+    send_command_now(SocketFun, Command, State);
454
+send_command(SocketFun, #command{ mbox = MBox } = Command, #state{ current_mbox = CurrentMbox } = State) ->
455
     %%lager:info("~p SELECT_DEBUG issuing command with mbox ~p (current: ~p, equal -> ~p): ~p", [self(), MBox, CurrentMbox, (MBox =:= CurrentMbox), Command#command.message]),
456
-    send_command_or_select_mbox(Fun, Command, State, MBox, MBox =:= CurrentMbox).
457
+    send_command_or_select_mbox(SocketFun, Command, State, MBox, MBox =:= CurrentMbox).
458
 
459
-send_command_or_select_mbox(Fun, Command, State, _MBox, true) ->
460
-    send_command_now(Fun, Command, State);
461
-send_command_or_select_mbox(Fun, DelayedCommand, State, MBox, false) ->
462
+send_command_or_select_mbox(SocketFun, Command, State, _MBox, true) ->
463
+    send_command_now(SocketFun, Command, State);
464
+send_command_or_select_mbox(SocketFun, DelayedCommand, State, MBox, false) ->
465
     NextState = reenque_command(DelayedCommand, State),
466
-    SelectMessage = eimap_command_examine:new(MBox),
467
-    SelectCommand = #command{ message = SelectMessage, parse_fun = fun eimap_command_examine:parse/2,
468
+    %TODO: this really should be SELECT rather than EXAMINE
469
+    { SelectMessage, ResponseType } = eimap_command_switch_folder:new_command(MBox),
470
+    SelectCommand = #command{ message = SelectMessage, response_type = ResponseType,
471
+                              parse_state = eimap_command_switch_folder,
472
                               from = self(), response_token = { selected, MBox } },
473
     %%lager:info("~p SELECT_DEBUG: Doing a select first ~p", [self(), SelectMessage]),
474
-    send_command_now(Fun, SelectCommand, NextState).
475
+    send_command_now(SocketFun, SelectCommand, NextState).
476
 
477
-send_command_now(Fun, #command{ message = Message } = Command, #state{ command_serial = Serial, socket = Socket } = State) ->
478
+send_command_now(SocketFun, #command{ message = Message } = Command, #state{ command_serial = Serial, socket = Socket } = State) ->
479
     Tag = list_to_binary(io_lib:format("EG~*..0B", [tag_field_width(Serial), Serial])),
480
     Data = <<Tag/binary, " ", Message/binary, "\r\n">>,
481
     %lager:info("Sending command via ~p: ~s", [Fun, Data]),
482
-    Fun(Socket, deflated(Data, State)),
483
+    SocketFun(Socket, deflated(Data, State)),
484
     State#state{ command_serial = Serial + 1, current_command = Command#command{ tag = Tag } }.
485
 
486
 enque_command(Command, State) ->
487
erlang-eimap-0.1.5.tar.gz/src/eimap_command.erl -> erlang-eimap-0.2.4.tar.gz/src/eimap_command.erl Changed
83
 
1
@@ -1,13 +1,78 @@
2
 -module(eimap_command).
3
 
4
+-export([
5
+         parse_response/4,
6
+         formulate_response/2,
7
+         process_status_line/2
8
+        ]).
9
+
10
 -type more_tuple() :: { more, ParseContinuation :: parse_continuation(), State :: term }.
11
 -type finished_tuple() :: { fini, Results :: term }.
12
 -type error_tuple() :: { error, Reason :: binary() }.
13
 -type parse_continuation() :: fun((Data :: binary(), Tag :: binary(), State :: term()) ->
14
                                    more_tuple() | finished_tuple() | error_tuple()).
15
 
16
--callback new(Args :: any()) -> binary().
17
--callback parse(Data :: binary(), Tag :: binary()) ->
18
-    more_tuple() | finished_tuple() | error_tuple() | starttls.
19
+-callback new_command(Args :: any()) -> { binary(), single_line_response | multiline_reponse | blob_response }.
20
+% TODO:bring back when we can depend on OTP 18 which introduced optional_callback
21
+% also define parse/2 (for blob_response), formulate_response/2
22
+%-callback process_line(Data :: binary(), Acc :: any()) ->
23
+%    more_tuple() | finished_tuple() | error_tuple() | starttls.
24
+
25
+parse_response(multiline_response, Data, Tag, ParseState) -> multiline_parse(false, Data, Tag, ParseState);
26
+parse_response(all_multiline_response, Data, Tag, ParseState) -> multiline_parse(parse_tagged, Data, Tag, ParseState);
27
+parse_response(single_line_response, Data, Tag, Module) -> Module:formulate_response(Data, Tag);
28
+parse_response(blob_response, Data, Tag, { Continuation, ParseState }) -> Continuation(Data, Tag, ParseState);
29
+parse_response(blob_response, Data, Tag, Module) ->
30
+    case Module:parse(Data, Tag) of
31
+        { more, Continuation, ParseState } -> { more, { Continuation, ParseState } };
32
+        Response -> Response
33
+    end.
34
+
35
+multiline_parse(ParseTaggedLine, Data, Tag, { LastPartialLine, Acc, Module }) ->
36
+    FullBuffer = <<LastPartialLine/binary, Data/binary>>,
37
+    { FullLinesBuffer, NewLastPartialLine } = eimap_utils:only_full_lines(FullBuffer),
38
+    Lines = binary:split(FullLinesBuffer, <<"\r\n">>, [global]),
39
+    process_lines(ParseTaggedLine, Tag, NewLastPartialLine, Lines, Acc, Module);
40
+multiline_parse(ParseTaggedLine, Data, Tag, Module) ->
41
+    multiline_parse(ParseTaggedLine, Data, Tag, { <<>>, [], Module }).
42
+
43
+process_lines(_ParseTaggedLine, _Tag, LastPartialLine, [], Acc, Module) -> { more, { LastPartialLine, Acc, Module } };
44
+process_lines(ParseTaggedLine, Tag, LastPartialLine, [Line|MoreLines], Acc, Module) ->
45
+    { FirstLine, ContinuationBytes } = eimap_utils:num_literal_continuation_bytes(Line),
46
+    process_line(ContinuationBytes, ParseTaggedLine, eimap_utils:is_tagged_response(FirstLine, Tag), Tag, LastPartialLine, FirstLine, MoreLines, Acc, Module).
47
+
48
+
49
+process_line(ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, [<<>>|MoreLines], Acc, Module) ->
50
+    %% skip empty lines
51
+    process_line(ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, MoreLines, Acc, Module);
52
+process_line(0, ParseTaggedLine, tagged, Tag, _LastPartialLine, Line, _MoreLines, Acc, Module) ->
53
+    Checked = eimap_utils:check_response_for_failure(Line, Tag),
54
+    Module:formulate_response(Checked, parse_tagged(Checked, ParseTaggedLine, Line, Acc, Module));
55
+process_line(0, ParseTaggedLine, untagged, Tag, LastPartialLine, Line, MoreLines, Acc, Module) ->
56
+    %io:format("Calling it here with ~p~n~n...", [Line]),
57
+    process_lines(ParseTaggedLine, Tag, LastPartialLine, MoreLines, Module:process_line(Line, Acc), Module);
58
+process_line(ContinuationBytes, ParseTaggedLine, _IsTagged, Tag, LastPartialLine, Line, [], Acc, Module) ->
59
+    %% the line was continued, but there is no more lines ... so this line is now our last partial line. more must be on its way
60
+    io:format("Missing lines!~p~n~n", [Acc]),
61
+    BytesAsBinary = integer_to_binary(ContinuationBytes),
62
+    process_lines(ParseTaggedLine, Tag, <<Line/binary, ${, BytesAsBinary/binary, $}, LastPartialLine/binary>>, [], Acc, Module);
63
+process_line(_ContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, Line, [NextLine|MoreLines], Acc, Module) ->
64
+    { StrippedNextLine, NextContinuationBytes } = eimap_utils:num_literal_continuation_bytes(NextLine),
65
+    io:format("Connected up the next line: ~p ~i~n", [StrippedNextLine, NextContinuationBytes]),
66
+    FullLine = <<Line/binary, StrippedNextLine/binary>>,
67
+    process_line(NextContinuationBytes, ParseTaggedLine, IsTagged, Tag, LastPartialLine, FullLine, MoreLines, Acc, Module).
68
+
69
+formulate_response(ok, Data) -> { fini, Data };
70
+formulate_response({ _, Reason }, _Data) -> { error, Reason }.
71
+
72
+parse_tagged(_, false, _Line, Acc, _Module) -> Acc; % we are not passing the tagged line forward (no content, e.g)
73
+parse_tagged(ok, _, Line, Acc, Module) -> Module:process_tagged_line(Line, Acc); % success, so pass it forward
74
+parse_tagged(_Checked, _ParsedTaggedLine, _Line, Acc, _Module) -> Acc. % error, don't bother passing it forward
75
+
76
+-spec process_status_line(Line :: binary(), Acc :: list()) -> NewAcc :: list().
77
+process_status_line(<<"* ", Rest/binary>>, Acc) -> process_matched_status_line(binary:split(Rest, <<" ">>, [global]), Acc);
78
+process_status_line(_, Acc) -> Acc.
79
 
80
+process_matched_status_line([Number, Key|_], Acc) -> [{ eimap_utils:binary_to_atom(Key), binary_to_integer(Number) }|Acc];
81
+process_matched_status_line(_, Acc) -> Acc.
82
 
83
erlang-eimap-0.1.5.tar.gz/src/eimap_uidset.erl -> erlang-eimap-0.2.4.tar.gz/src/eimap_uidset.erl Changed
64
 
1
@@ -16,12 +16,20 @@
2
 %% along with this program.  If not, see <http://www.gnu.org/licenses/>.
3
 
4
 -module(eimap_uidset).
5
--export([parse/1, next_uid/1]).
6
+-export([uid_list_to_binary/1, parse/1, next_uid/1]).
7
+
8
+% for internal use only
9
+-export([add_uid_as_binary_to_list/2, stitch_bin_list/2]).
10
 
11
 %% tokens have the form: integer | { integer, integer }
12
 -record(uidset, { tokens = [], current = none }).
13
 
14
-%% currently do not support parsing regular (list) strings, but could be added
15
+uid_list_to_binary(UidList) when is_list(UidList) ->
16
+    ListOfBins = lists:foldl(fun ?MODULE:add_uid_as_binary_to_list/2, [], UidList),
17
+    lists:foldl(fun ?MODULE:stitch_bin_list/2, <<>>, ListOfBins);
18
+uid_list_to_binary(_UidList) -> <<>>.
19
+
20
+parse(UidSet) when is_list(UidSet) -> parse(list_to_binary(UidSet));
21
 parse(<<>>)  -> badarg;
22
 parse(UidSet) when is_binary(UidSet) ->
23
     Components = binary:split(UidSet, <<",">>, [global]),
24
@@ -31,6 +39,27 @@
25
         _:_ -> badarg
26
     end.
27
 
28
+next_uid(#uidset{ tokens = Tokens, current = Current }) -> next_uid(Tokens, Current).
29
+next_uid([], _Current) -> { none, #uidset {} };
30
+next_uid([{ First, Second }|_] = FullList, Current) -> next_in_range(FullList, First, Second, Current);
31
+next_uid([Uid|UidSet], _Current) -> { Uid, #uidset{ tokens = UidSet } }.
32
+
33
+% PRIVATE API
34
+add_uid_as_binary_to_list(Uid, Acc) when is_integer(Uid), Uid >= 0 -> [integer_to_binary(Uid)|Acc];
35
+add_uid_as_binary_to_list({ StartUid, EndUid }, Acc) when is_integer(StartUid), is_integer(EndUid) ->
36
+    case add_uid_range_as_binary_to_list(StartUid, EndUid) of
37
+        error -> Acc;
38
+        { Start, End } -> [<<Start/binary, ":", End/binary>>|Acc]
39
+    end;
40
+add_uid_as_binary_to_list(_, Acc) -> Acc.
41
+
42
+add_uid_range_as_binary_to_list(Start, End) when Start < 0; End < 0 -> error;
43
+add_uid_range_as_binary_to_list(Start, End) when Start < End -> { integer_to_binary(Start), integer_to_binary(End) };
44
+add_uid_range_as_binary_to_list(Start, End) -> { integer_to_binary(End), integer_to_binary(Start) }.
45
+
46
+stitch_bin_list(Uid, <<>>) -> Uid;
47
+stitch_bin_list(Uid, Acc) -> <<Uid/binary, ",", Acc/binary>>.
48
+
49
 add_component(Uid, Acc) when is_integer(Uid), Uid >= 0 -> [Uid|Acc];
50
 add_component([First, Second], Acc) -> add_component(binary_to_integer(First), binary_to_integer(Second), Acc);
51
 add_component([<<"">>], Acc) -> Acc;
52
@@ -43,11 +72,6 @@
53
 add_range(First, Second, Acc) when First == Second -> [First|Acc];
54
 add_range(First, Second, Acc) -> [{ First, Second }|Acc].
55
 
56
-next_uid(#uidset{ tokens = Tokens, current = Current }) -> next_uid(Tokens, Current).
57
-next_uid([], _Current) -> { none, #uidset {} };
58
-next_uid([{ First, Second }|_] = FullList, Current) -> next_in_range(FullList, First, Second, Current);
59
-next_uid([Uid|UidSet], _Current) -> { Uid, #uidset{ tokens = UidSet } }.
60
-
61
 next_in_range(Tokens, First, _Second, none) -> { First, #uidset{ tokens = Tokens, current = First } };
62
 next_in_range(Tokens, First, Second, Current) when First < Second, Current < Second -> Next = Current + 1, { Next, #uidset{ tokens = Tokens, current = Next } };
63
 next_in_range(Tokens, First, Second, Current) when First > Second, Current > Second -> Next = Current - 1, { Next, #uidset{ tokens = Tokens, current = Next } };
64
erlang-eimap-0.1.5.tar.gz/src/eimap_utils.erl -> erlang-eimap-0.2.4.tar.gz/src/eimap_utils.erl Changed
118
 
1
@@ -19,15 +19,18 @@
2
 -export([
3
          extract_path_from_uri/3, extract_uidset_from_uri/1,
4
          split_command_into_components/1, is_tagged_response/2, remove_tag_from_response/3,
5
-         header_name/1,
6
+         header_name/1, parse_flags/1,
7
          check_response_for_failure/2,
8
          ensure_binary/1,
9
          new_imap_compressors/0,
10
-         only_full_lines/1
11
+         only_full_lines/1,
12
+         binary_to_atom/1,
13
+         num_literal_continuation_bytes/1
14
         ]).
15
 
16
 %% Translate the folder name in to a fully qualified folder path such as it
17
 %% would be used by a cyrus administrator.
18
+-spec extract_path_from_uri(SharedPrefix :: binary(), HierarchyDelim :: binary, URI :: binary()) -> Path :: binary() | bad_uri.
19
 extract_path_from_uri(SharedPrefix, HierarchyDelim, URI) when is_binary(URI) ->
20
     extract_path_from_uri(SharedPrefix, HierarchyDelim, binary_to_list(URI));
21
 extract_path_from_uri(SharedPrefix, HierarchyDelim, URI) when is_list(URI) ->
22
@@ -39,6 +42,7 @@
23
         Error -> Error
24
     end.
25
 
26
+-spec extract_uidset_from_uri(URI :: binary()) -> UIDSet:: binary().
27
 extract_uidset_from_uri(URI) when is_binary(URI) ->
28
     { TagStart, TagEnd } = binary:match(URI, <<";UID=">>),
29
     UIDStart = TagStart + TagEnd + 1,
30
@@ -48,11 +52,23 @@
31
         { Semicolon, _ } -> binary:part(URI, UIDStart - 1, Semicolon - UIDStart + 1)
32
     end.
33
 
34
+-spec header_name(mailbox_uid | groupware_uid | groupware_uid) -> binary(); (any()) -> unknown.
35
 header_name(mailbox_uid) -> <<"/vendor/cmu/cyrus-imapd/uniqueid">>;
36
 header_name(groupware_type) -> <<"X-Kolab-Type">>;
37
 header_name(groupware_uid) -> <<"Subject">>;
38
 header_name(_) -> unknown.
39
 
40
+-spec parse_flags(FlagString :: binary() | list()) -> Flags :: [binary()].
41
+parse_flags(String) when is_list(String) -> parse_flags(list_to_binary(String));
42
+parse_flags(<<"(", Parened/binary>>) ->
43
+    case binary:match(Parened, <<")">>) of
44
+        nomatch -> [];
45
+        { ClosingParens, _ } -> parse_flags(binary_part(Parened, 0, ClosingParens))
46
+    end;
47
+parse_flags(<<>>) -> [];
48
+parse_flags(FlagString) when is_binary(FlagString) ->
49
+    binary:split(FlagString, <<" ">>, [global]).
50
+
51
 -spec check_response_for_failure(Data :: binary(), Tag :: undefined | binary()) -> ok | { error, Reason :: binary() }.
52
 check_response_for_failure(Data, undefined) when is_binary(Data) ->
53
     check_response_for_failure(Data, <<>>);
54
@@ -68,13 +84,49 @@
55
 split_command_into_components(Buffer) when is_binary(Buffer) ->
56
     split_command(Buffer).
57
 
58
--spec is_tagged_response(Buffer :: binary(), Tag :: binary()) -> true | false.
59
+-spec is_tagged_response(Buffer :: binary(), Tag :: binary()) -> tagged | untagged.
60
 is_tagged_response(Buffer, Tag) ->
61
     TagSize = size(Tag) + 1, % The extra char is a space
62
     BufferSize = size(Buffer),
63
-    case (TagSize =< BufferSize) of
64
-        true -> <<Tag/binary, " ">> =:= binary:part(Buffer, 0, TagSize);
65
-        false -> false
66
+    case
67
+        case (TagSize =< BufferSize) of
68
+            true -> <<Tag/binary, " ">> =:= binary:part(Buffer, 0, TagSize);
69
+            _ -> false
70
+        end of
71
+
72
+        true -> tagged;
73
+        _ -> untagged
74
+    end.
75
+
76
+-spec num_literal_continuation_bytes(Buffer :: binary()) -> { BufferSansContinuation :: binary(), NumberBytes :: integer() }.
77
+num_literal_continuation_bytes(Buffer) when size(Buffer) < 4 ->
78
+    { Buffer, 0 };
79
+num_literal_continuation_bytes(Buffer) ->
80
+    case binary:last(Buffer) =:= $} of
81
+        true -> number_of_bytes_in_continuation(Buffer);
82
+        false -> { Buffer, 0 }
83
+    end.
84
+
85
+number_of_bytes_in_continuation(Buffer) ->
86
+    BufferSize = size(Buffer),
87
+    OpenBracePos = find_continuation_open_brace(Buffer, BufferSize - 3),
88
+    confirm_continuation(Buffer, OpenBracePos).
89
+
90
+find_continuation_open_brace(_Buffer, 0) -> -1;
91
+find_continuation_open_brace(Buffer, Pos) ->
92
+    case binary:at(Buffer, Pos) of
93
+        ${ -> Pos;
94
+        _ -> find_continuation_open_brace(Buffer, Pos - 1)
95
+    end.
96
+
97
+confirm_continuation(Buffer, -1) ->
98
+    { Buffer, 0 };
99
+confirm_continuation(Buffer, OpenBracePos) ->
100
+    BufferSize = size(Buffer),
101
+    try binary_to_integer(binary:part(Buffer, OpenBracePos + 1, BufferSize - OpenBracePos - 2)) of
102
+        Result -> { binary:part(Buffer, 0, OpenBracePos), Result }
103
+    catch
104
+        _:_ -> { Buffer, 0 }
105
     end.
106
 
107
 -spec remove_tag_from_response(Buffer :: binary(), Tag :: undefine | binary(), Check :: check | trust) -> Command :: binary().
108
@@ -187,6 +239,8 @@
109
 
110
 only_full_lines(Buffer, BufferLength, $\n, Pos) when Pos =:= BufferLength -> { Buffer, <<>> };
111
 only_full_lines(Buffer, BufferLength, $\n, Pos) -> { binary:part(Buffer, 0, Pos + 1), binary:part(Buffer, Pos + 1, BufferLength - Pos - 1) };
112
-only_full_lines(Buffer, BufferLength, _, 0) -> { <<>>, Buffer };
113
+only_full_lines(Buffer, _BufferLength, _, 0) -> { <<>>, Buffer };
114
 only_full_lines(Buffer, BufferLength, _, Pos) -> only_full_lines(Buffer, BufferLength, binary:at(Buffer, Pos - 1), Pos - 1).
115
 
116
+-spec binary_to_atom(Value :: binary()) -> ValueAsAtom :: atom().
117
+binary_to_atom(Value) -> list_to_atom(string:to_lower(binary_to_list(Value))).
118
erlang-eimap-0.1.5.tar.gz/test/eimap_command_annotation_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_annotation_tests.erl Changed
40
 
1
@@ -26,7 +26,7 @@
2
         %% { tag, server_response, expected_parsed_results }
3
         {
4
             <<"EG0002">>,
5
-            <<"* ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/color\" (\"value.shared\" \"32CD32\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/folder-type\" (\"value.shared\" \"event\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/x-toltec/test\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/horde/share-params\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/h-share-attr-desc\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/uniqueid\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/pxfb-readable-for\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/incidences-for\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/folder-test\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/displayname\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/activesync\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/uniqueid\" (\"value.shared\" \"b1a5bb95-2bd3-4628-a0d2-49e9bd10735e\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/squat\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/size\" (\"value.shared\" \"5726\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/sieve\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/sharedseen\" (\"value.shared\" \"false\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/pop3showafter\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/pop3newuidl\" (\"value.shared\" \"true\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/partition\" (\"value.shared\" \"default\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/news2mail\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/lastupdate\" (\"value.shared\" \"20-Mar-2015 05:17:51 +0100\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/lastpop\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/expire\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/duplicatedeliver\" (\"value.shared\" \"false\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/thread\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/specialuse\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/sort\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/comment\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/checkperiod\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/check\" (\"value.shared\" NIL)\r\n    EG0002 OK Completed">>,
6
+            <<"* ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/color\" (\"value.shared\" \"32CD32\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/folder-type\" (\"value.shared\" \"event\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/x-toltec/test\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/horde/share-params\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/h-share-attr-desc\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/uniqueid\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/pxfb-readable-for\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/incidences-for\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/folder-test\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/displayname\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/kolab/activesync\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/uniqueid\" (\"value.shared\" \"b1a5bb95-2bd3-4628-a0d2-49e9bd10735e\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/squat\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/size\" (\"value.shared\" \"5726\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/sieve\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/sharedseen\" (\"value.shared\" \"false\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/pop3showafter\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/pop3newuidl\" (\"value.shared\" \"true\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/partition\" (\"value.shared\" \"default\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/news2mail\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/lastupdate\" (\"value.shared\" \"20-Mar-2015 05:17:51 +0100\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/lastpop\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/expire\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/vendor/cmu/cyrus-imapd/duplicatedeliver\" (\"value.shared\" \"false\")\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/thread\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/specialuse\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/sort\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/comment\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/checkperiod\" (\"value.shared\" NIL)\r\n    * ANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"/check\" (\"value.shared\" NIL)\r\nEG0002 OK Completed\r\n">>,
7
             { fini, [
8
                 {<<"/vendor/cmu/cyrus-imapd/duplicatedeliver">>, false},
9
                 {<<"/vendor/cmu/cyrus-imapd/lastupdate">>, <<"20-Mar-2015 05:17:51 +0100">>},
10
@@ -42,7 +42,7 @@
11
         },
12
         {
13
             <<"EG0002">>,
14
-            <<"* ANNOTATION user/john.doe/Sent@example.org \"/vendor/x-toltec/test\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/horde/share-params\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/h-share-attr-desc\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/uniqueid\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/pxfb-readable-for\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/incidences-for\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/folder-type\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/folder-test\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/displayname\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/color\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/activesync\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/uniqueid\" (\"value.shared\" \"237357ec-7610-422e-9e55-0bae83caf58a\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/squat\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/size\" (\"value.shared\" \"401\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/sieve\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/sharedseen\" (\"value.shared\" \"false\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/pop3showafter\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/pop3newuidl\" (\"value.shared\" \"true\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/partition\" (\"value.shared\" \"default\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/news2mail\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/lastupdate\" (\"value.shared\" \"23-Mar-2015 15:19:29 +0100\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/lastpop\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/expire\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/duplicatedeliver\" (\"value.shared\" \"false\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/thread\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/specialuse\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/sort\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/comment\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/checkperiod\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/check\" (\"value.shared\" NIL)\r\n         EG0002 OK Completed">>,
15
+            <<"* ANNOTATION user/john.doe/Sent@example.org \"/vendor/x-toltec/test\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/horde/share-params\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/h-share-attr-desc\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/uniqueid\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/pxfb-readable-for\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/incidences-for\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/folder-type\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/folder-test\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/displayname\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/color\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/kolab/activesync\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/uniqueid\" (\"value.shared\" \"237357ec-7610-422e-9e55-0bae83caf58a\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/squat\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/size\" (\"value.shared\" \"401\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/sieve\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/sharedseen\" (\"value.shared\" \"false\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/pop3showafter\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/pop3newuidl\" (\"value.shared\" \"true\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/partition\" (\"value.shared\" \"default\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/news2mail\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/lastupdate\" (\"value.shared\" \"23-Mar-2015 15:19:29 +0100\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/lastpop\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/expire\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/vendor/cmu/cyrus-imapd/duplicatedeliver\" (\"value.shared\" \"false\")\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/thread\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/specialuse\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/sort\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/comment\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/checkperiod\" (\"value.shared\" NIL)\r\n         * ANNOTATION user/john.doe/Sent@example.org \"/check\" (\"value.shared\" NIL)\r\nEG0002 OK Completed\r\n">>,
16
             { fini, [
17
                 {<<"/vendor/cmu/cyrus-imapd/duplicatedeliver">>,false},
18
                 {<<"/vendor/cmu/cyrus-imapd/lastupdate">>,<<"23-Mar-2015 15:19:29 +0100">>},
19
@@ -55,13 +55,17 @@
20
             }
21
         }
22
     ],
23
-    lists:foldl(fun({ Tag, ServerData, Expected }, Acc) -> [?_assertEqual(Expected, eimap_command_annotation:parse(ServerData, Tag))|Acc] end, [], Data).
24
+    { _Command, ResponseType } = eimap_command_annotation:new_command(<<"/my/folder">>),
25
+    lists:foldl(fun({ Tag, ServerData, Expected }, Acc) -> [?_assertEqual(Expected, eimap_command:parse_response(ResponseType, ServerData, Tag, eimap_command_annotation))|Acc] end, [], Data).
26
 
27
 new_test_() ->
28
     Data =
29
     [
30
         %% mailbox, command
31
-        { <<"user/john.doe/Calendar/Personal Calendar@example.org">>, <<"GETANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"*\" \"value.shared\"">> }
32
+        {
33
+          <<"user/john.doe/Calendar/Personal Calendar@example.org">>,
34
+          { <<"GETANNOTATION \"user/john.doe/Calendar/Personal Calendar@example.org\" \"*\" \"value.shared\"">>, multiline_response }
35
+        }
36
     ],
37
-    lists:foldl(fun({ Mailbox, Command }, Acc) -> [?_assertEqual(Command, eimap_command_annotation:new(Mailbox))|Acc] end, [], Data).
38
+    lists:foldl(fun({ Mailbox, Command }, Acc) -> [?_assertEqual(Command, eimap_command_annotation:new_command(Mailbox))|Acc] end, [], Data).
39
 
40
erlang-eimap-0.1.5.tar.gz/test/eimap_command_capability_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_capability_tests.erl Changed
57
 
1
@@ -23,40 +23,48 @@
2
     [
3
         % { Binary Response, Binary Tag, Parsed Results }
4
         {
5
+          parse_serverid,
6
           <<"* OK [CAPABILITY IMAP4rev1 LITERAL+ ID ENABLE STARTTLS LOGINDISABLED] acme.com Cyrus IMAP 2.5.5.5-Kolab-2.5.5-5.1.el6.kolab_14 server ready\r\n">>,
7
           <<>>,
8
           { fini, { <<"IMAP4rev1 LITERAL+ ID ENABLE STARTTLS LOGINDISABLED">>, <<"acme.com Cyrus IMAP 2.5.5.5-Kolab-2.5.5-5.1.el6.kolab_14 server ready">> } }
9
         },
10
         {
11
+          ok,
12
           <<"* CAPABILITY IMAP4rev1 LITERAL+ ID ENABLE STARTTLS AUTH=PLAIN AUTH=LOGIN SASL-IR\r\nabcd OK CAPABILITY COMPLETED\r\n">>,
13
           <<"abcd">>,
14
-          { fini, <<"IMAP4rev1 LITERAL+ ID ENABLE STARTTLS AUTH=PLAIN AUTH=LOGIN SASL-IR">> }
15
+          { fini, [<<"IMAP4rev1 LITERAL+ ID ENABLE STARTTLS AUTH=PLAIN AUTH=LOGIN SASL-IR">>] }
16
         },
17
         {
18
+          ok,
19
           <<"other stuff\r\n">>,
20
           <<"abcd">>,
21
-          <<"other stuff\r\n">>
22
+          { more, { <<>>, [], eimap_command_capability } }
23
         },
24
         {
25
+          ok,
26
           <<"abcd BAD Uh uh uh\r\n">>,
27
           <<"abcd">>,
28
           { error, <<"Uh uh uh">> }
29
         },
30
         {
31
+          ok,
32
           <<"abcd NO Uh uh uh\r\n">>,
33
           <<"abcd">>,
34
           { error, <<"Uh uh uh">> }
35
         }
36
     ],
37
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_capability:parse(Response, Tag))|Acc] end, [], Data).
38
+    lists:foldl(fun({ InitArgs, Response, Tag, Parsed }, Acc) ->
39
+                        { _Command, ResponseType } = eimap_command_capability:new_command(InitArgs),
40
+                        [?_assertEqual(Parsed, eimap_command:parse_response(ResponseType, Response, Tag, eimap_command_capability))|Acc] end, [], Data).
41
 
42
 new_test_() ->
43
     Data =
44
     [
45
         % input, output
46
-        { <<>>, <<"CAPABILITY">> },
47
-        { true, <<"CAPABILITY">> },
48
-        { [], <<"CAPABILITY">> }
49
+        { parse_serverid, { <<"CAPABILITY">>, single_line_response } },
50
+        { <<>>, { <<"CAPABILITY">>, multiline_response } },
51
+        { true, { <<"CAPABILITY">>, multiline_response } },
52
+        { [], { <<"CAPABILITY">>, multiline_response } }
53
     ],
54
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_capability:new(Params))|Acc] end, [], Data).
55
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_capability:new_command(Params))|Acc] end, [], Data).
56
 
57
erlang-eimap-0.1.5.tar.gz/test/eimap_command_compress_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_compress_tests.erl Changed
25
 
1
@@ -38,15 +38,18 @@
2
           { error, <<"Uh uh uh">> }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_compress:parse(Response, Tag))|Acc] end, [], Data).
6
+    InitArgs = ok,
7
+    { _Command, ResponseType } = eimap_command_compress:new_command(InitArgs),
8
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(ResponseType, Response, Tag, eimap_command_compress))|Acc] end, [], Data).
9
 
10
 new_test_() ->
11
     Data =
12
     [
13
         % input, output
14
-        { <<>>, <<"COMPRESS DEFLATE">> },
15
-        { true, <<"COMPRESS DEFLATE">> },
16
-        { [], <<"COMPRESS DEFLATE">> }
17
+        { ok, { <<"COMPRESS DEFLATE">>, single_line_response } },
18
+        { <<>>, { <<"COMPRESS DEFLATE">>, single_line_response } },
19
+        { true, { <<"COMPRESS DEFLATE">>, single_line_response } },
20
+        { [], { <<"COMPRESS DEFLATE">>, single_line_response } }
21
     ],
22
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_compress:new(Params))|Acc] end, [], Data).
23
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_compress:new_command(Params))|Acc] end, [], Data).
24
 
25
erlang-eimap-0.1.5.tar.gz/test/eimap_command_getmetadata_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_getmetadata_tests.erl Changed
31
 
1
@@ -57,17 +57,22 @@
2
           { error, <<"Uh uh uh">> }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_getmetadata:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) ->
7
+                        [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_getmetadata))|Acc] end, [], Data).
8
 
9
 new_test_() ->
10
     Data =
11
     [
12
         % input, output
13
-        { { <<>> }, <<"GETMETADATA (DEPTH infinity) \"\"">> },
14
-        { { <<>>, [<<"/shared/comment">>, "/private/comment"] }, <<"GETMETADATA (DEPTH infinity) \"\" (/shared/comment /private/comment)">> },
15
-        { { <<"/my/folder">>, [<<"/shared/comment">>, "/private/comment"] }, <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">> },
16
-        { { "/my/folder", [<<"/shared/comment">>, "/private/comment"] }, <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">> },
17
-        { { <<"/my/folder">> }, <<"GETMETADATA (DEPTH infinity) \"/my/folder\"">> }
18
+        { { <<>> }, { <<"GETMETADATA (DEPTH infinity) \"\"">>, multiline_response } },
19
+        { { <<>>, [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"\" (/shared/comment /private/comment)">>, multiline_response } },
20
+        { { <<"/my/folder">>, [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">>, multiline_response } },
21
+        { { "/my/folder", [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">>, multiline_response } },
22
+        { { <<"/my/folder">> }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\"">>, multiline_response } },
23
+        { { "/my/folder", [], 10, 100 }, { <<"GETMETADATA (DEPTH 10 MAXSIZE 100) \"/my/folder\"">>, multiline_response } },
24
+        { { <<"/my/folder">>, [], 10, 100 }, { <<"GETMETADATA (DEPTH 10 MAXSIZE 100) \"/my/folder\"">>, multiline_response } },
25
+        { { <<"/my/folder">>, [], 10, none}, { <<"GETMETADATA (DEPTH 10) \"/my/folder\"">>, multiline_response } },
26
+        { { <<"/my/folder">>, [], none, 100 }, { <<"GETMETADATA (MAXSIZE 100) \"/my/folder\"">>, multiline_response } }
27
     ],
28
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_getmetadata:new(Params))|Acc] end, [], Data).
29
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_getmetadata:new_command(Params))|Acc] end, [], Data).
30
 
31
erlang-eimap-0.1.5.tar.gz/test/eimap_command_login_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_login_tests.erl Changed
20
 
1
@@ -38,14 +38,14 @@
2
           { error, <<"Uh uh uh">> }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_login:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_login))|Acc] end, [], Data).
7
 
8
 new_test_() ->
9
     Data =
10
     [
11
         % input, output
12
-        { { <<"aseigo">>, <<"oohyeah">> }, <<"LOGIN aseigo oohyeah">> },
13
-        { { "aseigo", "oohyeah" }, <<"LOGIN aseigo oohyeah">> }
14
+        { { <<"aseigo">>, <<"oohyeah">> }, { <<"LOGIN aseigo oohyeah">>, multiline_response } },
15
+        { { "aseigo", "oohyeah" }, { <<"LOGIN aseigo oohyeah">>, multiline_response } }
16
     ],
17
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_login:new(Params))|Acc] end, [], Data).
18
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_login:new_command(Params))|Acc] end, [], Data).
19
 
20
erlang-eimap-0.1.5.tar.gz/test/eimap_command_logout_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_logout_tests.erl Changed
22
 
1
@@ -38,15 +38,15 @@
2
           { close_socket, { error, <<"Uh uh uh">> } }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_logout:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_logout))|Acc] end, [], Data).
7
 
8
 new_test_() ->
9
     Data =
10
     [
11
         % input, output
12
-        { <<>>, <<"LOGOUT">> },
13
-        { true, <<"LOGOUT">> },
14
-        { [], <<"LOGOUT">> }
15
+        { <<>>, { <<"LOGOUT">>, multiline_response } },
16
+        { true, { <<"LOGOUT">>, multiline_response } },
17
+        { [], { <<"LOGOUT">>, multiline_response } }
18
     ],
19
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_logout:new(Params))|Acc] end, [], Data).
20
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_logout:new_command(Params))|Acc] end, [], Data).
21
 
22
erlang-eimap-0.1.5.tar.gz/test/eimap_command_namespace_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_namespace_tests.erl Changed
22
 
1
@@ -58,15 +58,15 @@
2
           { error, <<"Uh uh uh">> }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_namespace:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_namespace))|Acc] end, [], Data).
7
 
8
 new_test_() ->
9
     Data =
10
     [
11
         % input, output
12
-        { <<>>, <<"NAMESPACE">> },
13
-        { true, <<"NAMESPACE">> },
14
-        { [], <<"NAMESPACE">> }
15
+        { <<>>, { <<"NAMESPACE">>, multiline_response } },
16
+        { true, { <<"NAMESPACE">>, multiline_response } },
17
+        { [], { <<"NAMESPACE">>, multiline_response } }
18
     ],
19
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_namespace:new(Params))|Acc] end, [], Data).
20
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_namespace:new_command(Params))|Acc] end, [], Data).
21
 
22
erlang-eimap-0.2.4.tar.gz/test/eimap_command_noop_tests.erl Added
71
 
1
@@ -0,0 +1,69 @@
2
+%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
+%%
4
+%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
+%%
6
+%% This program is free software: you can redistribute it and/or modify
7
+%% it under the terms of the GNU General Public License as published by
8
+%% the Free Software Foundation, either version 3 of the License, or
9
+%% (at your option) any later version.
10
+%%
11
+%% This program is distributed in the hope that it will be useful,
12
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+%% GNU General Public License for more details.
15
+%%
16
+%% You should have received a copy of the GNU General Public License
17
+%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+-module(eimap_command_noop_tests).
20
+-include_lib("eunit/include/eunit.hrl").
21
+
22
+parse_test_() ->
23
+    Data =
24
+    [
25
+        % { Binary Response, Binary Tag, Parsed Results }
26
+        {
27
+          ok,
28
+          <<"* 100 EXISTS\r\n* 22 EXPUNGE\r\n* 14 FETCH (FLAGS (\\\\Answered \\\\Flagged \\\\Draft \\\\Deleted \\\\Seen))\r\n* 3 Recent\r\nabcd OK Completed\r\n">>,
29
+          <<"abcd">>,
30
+          { fini,
31
+            [
32
+             { recent, 3 },
33
+             { fetch, 14 },
34
+             { expunge, 22 },
35
+             { exists, 100 }
36
+            ]
37
+          }
38
+        },
39
+        {
40
+          ok,
41
+          <<"abcd OK Completed\r\n">>,
42
+          <<"abcd">>,
43
+          { fini, [] }
44
+        },
45
+        {
46
+          ok,
47
+          <<"abcd BAD Uh uh uh\r\n">>,
48
+          <<"abcd">>,
49
+          { error, <<"Uh uh uh">> }
50
+        },
51
+        {
52
+          ok,
53
+          <<"abcd NO Uh uh uh\r\n">>,
54
+          <<"abcd">>,
55
+          { error, <<"Uh uh uh">> }
56
+        }
57
+    ],
58
+    lists:foldl(fun({ InitArgs, ServerResponse, Tag, Parsed }, Acc) ->
59
+                        { _Command, ResponseType } = eimap_command_noop:new_command(InitArgs),
60
+                        [?_assertEqual(Parsed, eimap_command:parse_response(ResponseType, ServerResponse, Tag, eimap_command_noop))|Acc] end, [], Data).
61
+
62
+new_test_() ->
63
+    Data =
64
+    [
65
+        % input, output
66
+        { ok, { <<"NOOP">>, multiline_response } },
67
+        { <<>>, { <<"NOOP">>, multiline_response } }
68
+    ],
69
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_noop:new_command(Params))|Acc] end, [], Data).
70
+
71
erlang-eimap-0.1.5.tar.gz/test/eimap_command_starttls_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_starttls_tests.erl Changed
22
 
1
@@ -38,15 +38,15 @@
2
           { error, <<"Uh uh uh">> }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_starttls:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(single_line_response, Response, Tag, eimap_command_starttls))|Acc] end, [], Data).
7
 
8
 new_test_() ->
9
     Data =
10
     [
11
         % input, output
12
-        { <<>>, <<"STARTTLS">> },
13
-        { true, <<"STARTTLS">> },
14
-        { [], <<"STARTTLS">> }
15
+        { <<>>, { <<"STARTTLS">>, single_line_response } },
16
+        { true, { <<"STARTTLS">>, single_line_response } },
17
+        { [], { <<"STARTTLS">>, single_line_response } }
18
     ],
19
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_starttls:new(Params))|Acc] end, [], Data).
20
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_starttls:new_command(Params))|Acc] end, [], Data).
21
 
22
erlang-eimap-0.1.5.tar.gz/test/eimap_command_status_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_command_status_tests.erl Changed
40
 
1
@@ -38,23 +38,28 @@
2
           { fini, [{ uidvalidity, 44292 }, { recent, 1 }] }
3
         }
4
     ],
5
-    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command_status:parse(Response, Tag))|Acc] end, [], Data).
6
+    lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_status))|Acc] end, [], Data).
7
 
8
 new_test_() ->
9
     Data =
10
     [
11
         % input, output
12
-        { { "INBOX", [] }, <<"STATUS INBOX (MESSAGES)">> },
13
-        { { <<"INBOX">>, [] }, <<"STATUS INBOX (MESSAGES)">> },
14
-        { { <<>>, [messages] }, <<"STATUS INBOX (MESSAGES)">> },
15
-        { { <<"">>, [messages] }, <<"STATUS INBOX (MESSAGES)">> },
16
+        { { "INBOX", [] },
17
+          { <<"STATUS INBOX (MESSAGES)">>, multiline_response } },
18
+        { { <<"INBOX">>, [] },
19
+          { <<"STATUS INBOX (MESSAGES)">>, multiline_response } },
20
+        { { <<>>, [messages] },
21
+          { <<"STATUS INBOX (MESSAGES)">>, multiline_response } },
22
+        { { <<"">>, [messages] },
23
+          { <<"STATUS INBOX (MESSAGES)">>, multiline_response } },
24
         { { <<"/my/folder">>, [messages, recent, uidnext, uidvalidity, unseen] },
25
-           <<"STATUS /my/folder (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)">> },
26
+          { <<"STATUS /my/folder (MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)">>, multiline_response } },
27
         { { <<"/my/folder">>, [uidnext, recent, uidvalidity, unseen] },
28
-           <<"STATUS /my/folder (UIDNEXT RECENT UIDVALIDITY UNSEEN)">> },
29
+          { <<"STATUS /my/folder (UIDNEXT RECENT UIDVALIDITY UNSEEN)">>, multiline_response } },
30
         { { <<"/my/folder">>, [garbage, unseen] },
31
-           <<"STATUS /my/folder (UNSEEN)">> },
32
-        { { <<"/my/folder">>, [garbage] }, <<"STATUS /my/folder (MESSAGES)">> }
33
+          { <<"STATUS /my/folder (UNSEEN)">>, multiline_response } },
34
+        { { <<"/my/folder">>, [garbage] },
35
+          { <<"STATUS /my/folder (MESSAGES)">>, multiline_response } }
36
     ],
37
-    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_status:new(Params))|Acc] end, [], Data).
38
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_status:new_command(Params))|Acc] end, [], Data).
39
 
40
erlang-eimap-0.2.4.tar.gz/test/eimap_command_switch_folder_tests.erl Added
74
 
1
@@ -0,0 +1,72 @@
2
+%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
+%%
4
+%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
+%%
6
+%% This program is free software: you can redistribute it and/or modify
7
+%% it under the terms of the GNU General Public License as published by
8
+%% the Free Software Foundation, either version 3 of the License, or
9
+%% (at your option) any later version.
10
+%%
11
+%% This program is distributed in the hope that it will be useful,
12
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+%% GNU General Public License for more details.
15
+%%
16
+%% You should have received a copy of the GNU General Public License
17
+%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+-module(eimap_command_switch_folder_tests).
20
+-include_lib("eunit/include/eunit.hrl").
21
+
22
+parse_test_() ->
23
+    Data =
24
+    [
25
+        % { Binary Response, Binary Tag, Parsed Results }
26
+        {
27
+          <<"Trash">>,
28
+          <<"* 0 EXISTS\r\n* 0 RECENT\r\n* FLAGS (\\\\Answered \\\\Flagged \\\\Draft \\\\Deleted \\\\Seen)\r\n* OK [PERMANENTFLAGS ()] Ok\r\n* OK [UIDVALIDITY 1447439786] Ok\r\n* OK [UIDNEXT 1] Ok\r\n* OK [HIGHESTMODSEQ 1] Ok\r\n* OK [URLMECH INTERNAL] Ok>\r\n* OK [ANNOTATIONS 65536] Ok\r\nabcd OK [READ-WRITE] SELECT completed\r\n">>,
29
+          <<"abcd">>,
30
+          { fini,
31
+            [
32
+             { writeable, true },
33
+             { annotations, 65536 },
34
+             { url_mech, internal },
35
+             { highest_mod_seq, 1 },
36
+             { uid_next, 1 },
37
+             { uid_validity, 1447439786 },
38
+             { permanent_flags, [] },
39
+             { flags, [<<"\\\\Answered">>, <<"\\\\Flagged">>, <<"\\\\Draft">>, <<"\\\\Deleted">>, <<"\\\\Seen">>] },
40
+             { recent, 0 },
41
+             { exists, 0 }
42
+            ]
43
+          }
44
+        },
45
+        {
46
+          <<"Nope">>,
47
+          <<"abcd BAD Uh uh uh\r\n">>,
48
+          <<"abcd">>,
49
+          { error, <<"Uh uh uh">> }
50
+        },
51
+        {
52
+          <<"ALsoWrong">>,
53
+          <<"abcd NO Uh uh uh\r\n">>,
54
+          <<"abcd">>,
55
+          { error, <<"Uh uh uh">> }
56
+        }
57
+    ],
58
+    lists:foldl(fun({ InitArgs, ServerResponse, Tag, Parsed }, Acc) ->
59
+                        { _Command, ResponseType } = eimap_command_switch_folder:new_command(InitArgs),
60
+                        [?_assertEqual(Parsed, eimap_command:parse_response(ResponseType, ServerResponse, Tag, eimap_command_switch_folder))|Acc] end, [], Data).
61
+
62
+new_test_() ->
63
+    Data =
64
+    [
65
+        % input, output
66
+        { "Trash", { <<"SELECT \"Trash\"">>, all_multiline_response } },
67
+        { <<"Trash">>, { <<"SELECT \"Trash\"">>, all_multiline_response } },
68
+        { { <<"Trash">>, select }, { <<"SELECT \"Trash\"">>, all_multiline_response } },
69
+        { { <<"Trash">>, examine }, { <<"EXAMINE \"Trash\"">>, all_multiline_response } },
70
+        { { "Trash", examine }, { <<"EXAMINE \"Trash\"">>, all_multiline_response } }
71
+    ],
72
+    lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_switch_folder:new_command(Params))|Acc] end, [], Data).
73
+
74
erlang-eimap-0.2.4.tar.gz/test/eimap_command_tests.erl Added
67
 
1
@@ -0,0 +1,65 @@
2
+%% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
3
+%%
4
+%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
5
+%%
6
+%% This program is free software: you can redistribute it and/or modify
7
+%% it under the terms of the GNU General Public License as published by
8
+%% the Free Software Foundation, either version 3 of the License, or
9
+%% (at your option) any later version.
10
+%%
11
+%% This program is distributed in the hope that it will be useful,
12
+%% but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+%% GNU General Public License for more details.
15
+%%
16
+%% You should have received a copy of the GNU General Public License
17
+%% along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+-module(eimap_command_tests).
20
+-include_lib("eunit/include/eunit.hrl").
21
+-export([process_line/2, formulate_response/2]).
22
+
23
+% c("test/eimap_command_tests.erl"). eunit:test(eimap_command).
24
+
25
+process_line(Data, Acc) -> [{ marked, Data } | Acc].
26
+formulate_response(Result, Data) -> eimap_command:formulate_response(Result, lists:reverse(Data)).
27
+
28
+
29
+literal_continuations_test_() ->
30
+    Data =
31
+    [
32
+        % input, output
33
+        % { Binary Response, Binary Tag, Parsed Results }
34
+        {
35
+          <<"* STATUS 1 (MESSAGES 231 {14}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>,
36
+          <<"abcd">>,
37
+          { fini, [ { marked, <<"* STATUS 1 (MESSAGES 231 UIDNEXT 44292)">> } ] }
38
+        },
39
+        {
40
+          <<"* STATUS 1a (MESSAGES 231 {14}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation">>,
41
+          <<"abcd">>,
42
+          { more, { <<"abcd OK Begin TLS negotiation">>, [ { marked, <<"* STATUS 1a (MESSAGES 231 UIDNEXT 44292)">> } ], ?MODULE } }
43
+        },
44
+        {
45
+          <<"* STATUS 2 (MESSAGES 231 {14}\r\n">>,
46
+          <<"abcd">>,
47
+          { more, { <<"* STATUS 2 (MESSAGES 231 {14}">>, [], ?MODULE } }
48
+        },
49
+        {
50
+          <<"* STATUS 3 (MESSAGES 231 {h14}\r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>,
51
+          <<"abcd">>,
52
+          { fini, [ { marked, <<"* STATUS 3 (MESSAGES 231 {h14}">> }, { marked, <<"UIDNEXT 44292)">> } ] }
53
+        },
54
+        {
55
+          <<"* STATUS 4 (MESSAGES 231 \r\nUIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>,
56
+          <<"abcd">>,
57
+          { fini, [ { marked, <<"* STATUS 4 (MESSAGES 231 ">> }, { marked, <<"UIDNEXT 44292)">> } ] }
58
+        },
59
+        {
60
+          <<"* STATUS 5 (MESSAGES 231 UIDNEXT 44292)\r\nabcd OK Begin TLS negotiation now\r\n">>,
61
+          <<"abcd">>,
62
+          { fini, [ { marked, <<"* STATUS 5 (MESSAGES 231 UIDNEXT 44292)">> } ] }
63
+        }
64
+    ],
65
+    lists:foldl(fun({ Binary, Tag, Result }, Acc) -> [?_assertEqual(Result, eimap_command:parse_response(multiline_response, Binary, Tag, ?MODULE))|Acc] end, [], Data).
66
+
67
erlang-eimap-0.1.5.tar.gz/test/eimap_uidset_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_uidset_tests.erl Changed
27
 
1
@@ -20,6 +20,25 @@
2
 
3
 % c("test/eimap_uidset_tests.erl"). eunit:test(eimap_uidset).
4
 
5
+new_test_() ->
6
+    Data =
7
+    [
8
+        % input, output
9
+        { [4], <<"4">> },
10
+        { [1, 2, 3, 4], <<"1,2,3,4">> },
11
+        { [-1, 1, 2, 3, 4], <<"1,2,3,4">> },
12
+        { [1, {100, 10}, 2, 3, 4], <<"1,10:100,2,3,4">> },
13
+        { [1, {10, 100}, 2, 3, 4], <<"1,10:100,2,3,4">> },
14
+        { [1, {-10, 100}, 2, 3, 4], <<"1,2,3,4">> },
15
+        { [1, {-100, 10}, 2, 3, 4], <<"1,2,3,4">> },
16
+        { [1, {-10, 100}, 2, 3, 4], <<"1,2,3,4">> },
17
+        { [1, {"alpha", 100}, <<"random_binary">>, some_atom, 2, 3, 4], <<"1,2,3,4">> },
18
+        { <<"1,2,3,4">>, <<>> },
19
+        { other, <<>> },
20
+        { [], <<>> }
21
+    ],
22
+    lists:foldl(fun({ List, Binary }, Acc) -> [?_assertEqual(Binary, eimap_uidset:uid_list_to_binary(List))|Acc] end, [], Data).
23
+
24
 single_value_test_() ->
25
     [
26
         ?_assertEqual([1], iterate_uidset(eimap_uidset:parse(<<"1,">>))),
27
erlang-eimap-0.1.5.tar.gz/test/eimap_utils_tests.erl -> erlang-eimap-0.2.4.tar.gz/test/eimap_utils_tests.erl Changed
56
 
1
@@ -79,6 +79,7 @@
2
     Data =
3
     [
4
         { { <<>>, <<>>, <<>> }, <<>> },
5
+        { { <<".">>, <<"LIST">>, <<"\"\" \"*\"">> }, <<". LIST \"\" \"*\"">> },
6
         { { <<"1">>, <<"STARTTLS">>, <<>> }, <<"1 STARTTLS">> },
7
         { { <<"1">>, <<"STARTTLS">>, <<>> }, <<"1 STARTTLS\r\n">> },
8
         { { <<"3">>, <<"ID">>, <<"(\"name\" \"Thunderbird\" \"version\" \"38.3.0\")">> }, <<"3 ID (\"name\" \"Thunderbird\" \"version\" \"38.3.0\")">> }
9
@@ -104,10 +105,10 @@
10
     Tag = <<"abcd">>,
11
     Data =
12
     [
13
-       { <<Tag/binary, " Indeed\r\n">>, true },
14
-       { <<Tag/binary, " Indeed">>, true },
15
-       { <<"one">>, false },
16
-       { <<"* Yeah baby">>, false }
17
+       { <<Tag/binary, " Indeed\r\n">>, tagged },
18
+       { <<Tag/binary, " Indeed">>, tagged },
19
+       { <<"one">>, untagged },
20
+       { <<"* Yeah baby">>, untagged }
21
     ],
22
     lists:foldl(fun({ Input, Output}, Acc) -> [?_assertEqual(Output, eimap_utils:is_tagged_response(Input, Tag)) | Acc] end, [], Data).
23
 
24
@@ -167,3 +168,31 @@
25
     ],
26
     lists:foldl(fun({ Input, Output}, Acc) -> [?_assertEqual(Output, eimap_utils:only_full_lines(Input)) | Acc] end, [], Data).
27
 
28
+parse_flags_test_() ->
29
+    Data =
30
+    [
31
+        { <<"()">>, [] },
32
+        { <<>>, [] },
33
+        { <<"\\\\Answered \\\\Flagged \\\\Draft \\\\Deleted \\\\Seen">>, [<<"\\\\Answered">>, <<"\\\\Flagged">>, <<"\\\\Draft">>, <<"\\\\Deleted">>, <<"\\\\Seen">> ] },
34
+        { <<"(\\\\Answered \\\\Flagged \\\\Draft \\\\Deleted \\\\Seen)">>, [<<"\\\\Answered">>, <<"\\\\Flagged">>, <<"\\\\Draft">>, <<"\\\\Deleted">>, <<"\\\\Seen">> ] },
35
+        { "(\\\\Answered \\\\Flagged \\\\Draft \\\\Deleted \\\\Seen)", [<<"\\\\Answered">>, <<"\\\\Flagged">>, <<"\\\\Draft">>, <<"\\\\Deleted">>, <<"\\\\Seen">> ] }
36
+    ],
37
+    lists:foldl(fun({ Input, Output}, Acc) -> [?_assertEqual(Output, eimap_utils:parse_flags(Input)) | Acc] end, [], Data).
38
+
39
+num_literal_continuation_bytes_test_() ->
40
+    Data =
41
+    [
42
+        { <<"abcd">>, { <<"abcd">>, 0 } },
43
+        { <<"abcd{5}">>, { <<"abcd">>, 5 } },
44
+        { <<"abcd{100}">>, { <<"abcd">>, 100 } },
45
+        { <<"123abcd{100}">>, { <<"123abcd">>, 100  } },
46
+        { <<"ab{123abcd{100}">>, { <<"ab{123abcd">>, 100  } },
47
+        { <<"ab{123abcd{1{00}">>, { <<"ab{123abcd{1">>, 0 } },
48
+        { <<"abcd{aa0}">>, { <<"abcd{aa0}">>, 0 } },
49
+        { <<"abcd{10aa0}">>, { <<"abcd{10aa0}">>, 0 } },
50
+        { <<"abcd100}">>, { <<"abcd100}">>, 0 } },
51
+        { <<"abcd100}">>, { <<"abcd100}">>, 0 } }
52
+    ],
53
+    lists:foldl(fun({ Input, Output}, Acc) -> [?_assertEqual(Output, eimap_utils:num_literal_continuation_bytes(Input)) | Acc] end, [], Data).
54
+
55
+
56
erlang-eimap.dsc Added
16
 
1
@@ -0,0 +1,14 @@
2
+Format: 1.0
3
+Source: erlang-eimap
4
+Binary: erlang-eimap
5
+Architecture: any
6
+Version: 0.2.4-1
7
+Maintainer: Christoph Erhardt <kolab@sicherha.de>
8
+Homepage: https://git.kolab.org/diffusion/EI/eimap.git
9
+Standards-Version: 3.9.6
10
+Build-Depends: debhelper (>= 9), erlang-goldrush (>= 0.1.7), erlang-lager (>= 2.2.0), rebar (>= 2.5.1)
11
+Package-List:
12
+ erlang-eimap deb devel extra
13
+Files:
14
+ 00000000000000000000000000000000 0 erlang-eimap-0.2.4.tar.gz
15
+ 00000000000000000000000000000000 0 debian.tar.gz
16
repack.sh Deleted
10
 
1
@@ -1,8 +0,0 @@
2
-#!/bin/bash
3
-
4
-tar zxvf eimap-*.*.*.tar.gz
5
-target=$(ls -1d eimap-*.*.*/ | xargs -n 1 basename | sed -e 's/eimap/erlang-eimap/g')
6
-rm -rf eimap-*.*.*.tar.gz
7
-rm -rf ${target}
8
-mv eimap-*.*.* ${target}
9
-tar czvf ${target}.tar.gz ${target}
10