diff --git a/src/commands/eimap_command_getmetadata.erl b/src/commands/eimap_command_getmetadata.erl index 3a3e64a..2d5d93e 100644 --- a/src/commands/eimap_command_getmetadata.erl +++ b/src/commands/eimap_command_getmetadata.erl @@ -1,113 +1,115 @@ %% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com) %% %% Aaron Seigo (Kolab Systems) %% %% This program is free software: you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation, either version 3 of the License, or %% (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program. If not, see . -module(eimap_command_getmetadata). -behavior(eimap_command). -export([new_command/1, process_line/2, formulate_response/2]). %% http://tools.ietf.org/html/rfc2342 %% Public API new_command({ Folder }) -> new_command({ Folder, [] }); new_command({ Folder, Attributes }) when is_list(Folder) -> new_command({ list_to_binary(Folder), Attributes }); new_command({ Folder, Attributes }) -> new_command({ Folder, Attributes, infinity, nomax }); + +new_command({ Folder, Attributes, Depth, MaxSize }) when is_list(Folder) -> new_command({ list_to_binary(Folder), Attributes, Depth, MaxSize }); new_command({ Folder, Attributes, Depth, MaxSize }) -> AttributesString = format_attributes(Attributes, <<>>), DepthString = depth_param(Depth), MaxSizeString = maxsize_param(MaxSize), Command = metadata_command(DepthString, MaxSizeString, Folder, AttributesString), { Command, multiline_response }. process_line(<<"* METADATA ", Details/binary>>, Acc) -> Results = parse_folder(Details), [Results|Acc]; process_line(_Line, Acc) -> Acc. formulate_response(Result, Data) -> eimap_command:formulate_response(Result, Data). %% Private API depth_param(infinity) -> <<"DEPTH infinity">>; depth_param(Depth) when is_integer(Depth) -> Bin = integer_to_binary(Depth), <<"DEPTH ", Bin/binary>>; depth_param(_) -> <<>>. maxsize_param(Size) when is_integer(Size) -> Bin = integer_to_binary(Size), <<"MAXSIZE ", Bin/binary>>; maxsize_param(_) -> <<>>. metadata_command(<<>>, <<>>, Folder, Attributes) -> <<"GETMETADATA \"", Folder/binary, "\"", Attributes/binary>>; metadata_command(Depth, <<>>, Folder, Attributes) -> <<"GETMETADATA (", Depth/binary, ") \"", Folder/binary, "\"", Attributes/binary>>; metadata_command(<<>>, MaxSize, Folder, Attributes) -> <<"GETMETADATA (", MaxSize/binary, ") \"", Folder/binary, "\"", Attributes/binary>>; metadata_command(Depth, MaxSize, Folder, Attributes) -> <<"GETMETADATA (", Depth/binary, " ", MaxSize/binary, ") \"", Folder/binary, "\"", Attributes/binary>>. format_attributes([], <<>>) -> <<>>; format_attributes([], String) -> <<" (", String/binary, ")">>; format_attributes([Attribute|Attributes], String) -> AttrBin = case is_list(Attribute) of true -> list_to_binary(Attribute); false -> Attribute end, case String of <<>> -> format_attributes(Attributes, AttrBin); _ -> format_attributes(Attributes, <>) end; format_attributes(_, _String) -> <<>>. parse_folder(<<"\"", Rest/binary>>) -> { Folder, RemainingBuffer } = until_closing_quote(Rest), Properties = parse_properties(RemainingBuffer), { Folder, Properties }; parse_folder(Buffer) -> { FolderEnd, _ } = binary:match(Buffer, <<" ">>), Folder = binary:part(Buffer, 0, FolderEnd), Properties = parse_properties(binary:part(Buffer, FolderEnd, size(Buffer) - FolderEnd)), { Folder, Properties }. parse_properties(Buffer) -> { Start, _ } = binary:match(Buffer, <<"(">>), { End, _ } = binary:match(Buffer, <<")">>), Properties = binary:part(Buffer, Start + 1, End - Start - 1), next_property(Properties, []). next_property(<<>>, Acc) -> Acc; next_property(Buffer, Acc) -> { KeyEnd, _ } = binary:match(Buffer, <<" ">>), Key = binary:part(Buffer, 0, KeyEnd), { Value, RemainingBuffer } = case next_value(binary:part(Buffer, KeyEnd + 1, size(Buffer) - KeyEnd - 1)) of { <<"NIL">>, RBuffer } -> { null, RBuffer }; Rv -> Rv end, next_property(RemainingBuffer, [{ Key, Value }|Acc]). next_value(<<"\"", Rest/binary>>) -> until_closing_quote(Rest); next_value(Buffer) -> case binary:match(Buffer, <<" ">>) of nomatch -> { Buffer, <<>> }; { ValueEnd , _ } -> { binary:part(Buffer, 0, ValueEnd - 1), binary:part(Buffer, ValueEnd, size(Buffer) - ValueEnd) } end. until_closing_quote(Buffer) -> until_closing_quote(Buffer, 0, binary:at(Buffer, 0), 0, <<>>). until_closing_quote(Buffer, Start, $\\, Pos, Acc) -> Escaped = binary:at(Buffer, Pos + 1 ), until_closing_quote(Buffer, Start, binary:at(Buffer, Pos + 2), Pos + 2, <>); until_closing_quote(Buffer, Start, $", Pos, Acc) -> { Acc, binary:part(Buffer, Start + Pos + 1, size(Buffer) - Start - Pos - 1) }; until_closing_quote(Buffer, _Start, Char, Pos, Acc) when Pos =:= size(Buffer) - 1 -> { <>, <<>> }; until_closing_quote(Buffer, Start, Char, Pos, Acc) -> until_closing_quote(Buffer, Start, binary:at(Buffer, Pos + 1), Pos + 1, <>). diff --git a/test/eimap_command_getmetadata_tests.erl b/test/eimap_command_getmetadata_tests.erl index deba42e..8c482d2 100644 --- a/test/eimap_command_getmetadata_tests.erl +++ b/test/eimap_command_getmetadata_tests.erl @@ -1,77 +1,78 @@ %% Copyright 2014 Kolab Systems AG (http://www.kolabsys.com) %% %% Aaron Seigo (Kolab Systems) %% %% This program is free software: you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation, either version 3 of the License, or %% (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program. If not, see . -module(eimap_command_getmetadata_tests). -include_lib("eunit/include/eunit.hrl"). parse_test_() -> Data = [ % { Binary Response, Binary Tag, Parsed Results } { <<"* METADATA Tasks (/shared/vendor/kolab/folder-type \"task\")\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, { fini, [ { <<"Tasks">>, [ { <<"/shared/vendor/kolab/folder-type">>, <<"task">> } ] } ] } }, { <<"* METADATA \"Tasks Tasks\" (/shared/vendor/kolab/folder-type \"task\")\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, { fini, [ { <<"Tasks Tasks">>, [ { <<"/shared/vendor/kolab/folder-type">>, <<"task">> } ] } ] } }, { <<"* METADATA Tasks (/shared/vendor/kolab/folder-type \"task \\\"sigh\\\"\")\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, { fini, [ { <<"Tasks">>, [ { <<"/shared/vendor/kolab/folder-type">>, <<"task \"sigh\"">> } ] } ] } }, { <<"* METADATA Tasks (/shared/vendor/kolab/folder-type \"task \\\"sigh\\\"\")\r\n* METADATA Archive (/shared/vendor/kolab/folder-type NIL)\r\nabcd OK Begin TLS negotiation now\r\n">>, <<"abcd">>, { fini, [ { <<"Archive">>, [ {<<"/shared/vendor/kolab/folder-type">>, null } ] }, { <<"Tasks">>, [ { <<"/shared/vendor/kolab/folder-type">>, <<"task \"sigh\"">> } ] } ] } }, { <<"abcd BAD Uh uh uh\r\n">>, <<"abcd">>, { error, <<"Uh uh uh">> } }, { <<"abcd NO Uh uh uh\r\n">>, <<"abcd">>, { error, <<"Uh uh uh">> } } ], lists:foldl(fun({ Response, Tag, Parsed }, Acc) -> [?_assertEqual(Parsed, eimap_command:parse_response(multiline_response, Response, Tag, eimap_command_getmetadata))|Acc] end, [], Data). new_test_() -> Data = [ % input, output { { <<>> }, { <<"GETMETADATA (DEPTH infinity) \"\"">>, multiline_response } }, { { <<>>, [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"\" (/shared/comment /private/comment)">>, multiline_response } }, { { <<"/my/folder">>, [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">>, multiline_response } }, { { "/my/folder", [<<"/shared/comment">>, "/private/comment"] }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\" (/shared/comment /private/comment)">>, multiline_response } }, { { <<"/my/folder">> }, { <<"GETMETADATA (DEPTH infinity) \"/my/folder\"">>, multiline_response } }, + { { "/my/folder", [], 10, 100 }, { <<"GETMETADATA (DEPTH 10 MAXSIZE 100) \"/my/folder\"">>, multiline_response } }, { { <<"/my/folder">>, [], 10, 100 }, { <<"GETMETADATA (DEPTH 10 MAXSIZE 100) \"/my/folder\"">>, multiline_response } }, { { <<"/my/folder">>, [], 10, none}, { <<"GETMETADATA (DEPTH 10) \"/my/folder\"">>, multiline_response } }, { { <<"/my/folder">>, [], none, 100 }, { <<"GETMETADATA (MAXSIZE 100) \"/my/folder\"">>, multiline_response } } ], lists:foldl(fun({ Params, Command }, Acc) -> [?_assertEqual(Command, eimap_command_getmetadata:new_command(Params))|Acc] end, [], Data).