%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
%%% Implemented queue for bind() requests to prevent pending binds.
+%%% Implemented extensibleMatch/2 function.
%%% Modified by Christophe Romain <christophe.romain@process-one.net>
%%% Improve error case handling
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
- approxMatch/2,search/2,substrings/2,present/1,
+ approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2,
'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
mod_replace/2, add/3, delete/2, modify_dn/5, bind/3]).
-export([get_status/1]).
{substrings,#'SubstringFilter'{type = Type,
substrings = Ss}}.
+%%%
+%%% extensibleMatch filter.
+%%% FIXME: Describe the purpose of this filter.
+%%%
+%%% Value ::= string( <attribute> )
+%%% Opts ::= listof( {matchingRule, Str} | {type, Str} | {dnAttributes, true} )
+%%%
+%%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]).
+%%%
+extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) ->
+ MRA = #'MatchingRuleAssertion'{matchValue=Value},
+ {extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
+
+extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) ->
+ extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule});
+extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) ->
+ extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc});
+extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) ->
+ extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true});
+extensibleMatch_opts([_ | Opts], MRA) ->
+ extensibleMatch_opts(Opts, MRA);
+extensibleMatch_opts([], MRA) ->
+ MRA.
get_handle(Pid) when is_pid(Pid) -> Pid;
get_handle(Atom) when is_atom(Atom) -> Atom;
v_filter({approxMatch,AV}) -> {approxMatch,AV};
v_filter({present,A}) -> {present,A};
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
+v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') ->
+ {extensibleMatch, S};
v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
v_modifications(Mods) ->
%%% Purpose: Converts String Representation of
%%% LDAP Search Filter (RFC 2254)
%%% to eldap's representation of filter
-%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
+%%% Author: Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------
-
-module(eldap_filter).
-%%%======================
-%%% Export functions
-%%%======================
+%% TODO: remove this when new regexp module will be used
+-compile({nowarn_deprecated_function, {regexp, sub, 3}}).
--export([parse/1,
- parse/2,
- do_sub/2
- ]).
+-export([parse/1, parse/2, do_sub/2]).
-%%%-------------------------------------------------------------------------
+%%====================================================================
+%% API
+%%====================================================================
+%%%-------------------------------------------------------------------
%%% Arity: parse/1
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
%%% {error, bad_filter}
%%% to eldap's representation of filter.
%%%
%%% Example:
-%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
+%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
%%%
-%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
-%%% {present,"mail"}]}}
-%%%-------------------------------------------------------------------------
-parse(RFC2254_Filter) ->
- parse(RFC2254_Filter, []).
+%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
+%%% {present,"mail"}]}}
+%%%-------------------------------------------------------------------
+parse(L) when is_list(L) ->
+ parse(L, []).
-%%%-------------------------------------------------------------------------
+%%%-------------------------------------------------------------------
%%% Arity: parse/2
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
%%% {ok, EldapFilter} |
%%% {equalityMatch,{'AttributeValueAssertion',
%%% "jid",
%%% "xramtsov@gmail.com"}}]}}
-%%%--------------------------------------------------------------------------
-parse(RFC2254_Filter, ListOfSubValues) ->
- case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
- [EldapFilter] when is_tuple(EldapFilter) ->
- {ok, EldapFilter};
- {regexp, Error} ->
- {error, Error};
- _ ->
- {error, bad_filter}
- end.
-
-%%%==========================
-%%% Internal functions
-%%%==========================
-
-%%%----------------------
-%%% split/1,4
-%%%----------------------
-split(Filter) ->
- split(Filter, 0, [], []).
-
-split([], _, _, Result) ->
- Result;
-
-split([H|T], Num, Rest, Result) ->
- NewNum = case H of
- $( -> Num + 1;
- $) -> Num - 1;
- _ -> Num
- end,
- if
- NewNum == 0 ->
- X = Rest++[H],
- LenX = length(X),
- if
- LenX > 2 ->
- split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
- true ->
- split(T, 0, Rest, Result)
- end;
- true ->
- split(T, NewNum, Rest++[H], Result)
+%%%-------------------------------------------------------------------
+parse(L, SList) when is_list(L), is_list(SList) ->
+ case catch eldap_filter_yecc:parse(scan(L, SList)) of
+ {error, {_, _, Msg}} ->
+ {error, Msg};
+ {ok, Result} ->
+ {ok, Result};
+ {regexp, Err} ->
+ {error, Err}
end.
-%%%-----------------------
-%%% parse_filter/1
-%%%-----------------------
-parse_filter(Filter) ->
- case Filter of
- [$! | T] ->
- {'not', parse_filter(T)};
- [$| | T] ->
- {'or', parse_filter(T)};
- [$& | T] ->
- {'and', parse_filter(T)};
- [$( | _] ->
- parse_filter(split(Filter));
- [List | _] when is_list(List) ->
- [parse_filter(X) || X <- Filter];
- _ ->
- Filter
- end.
-
-%%%--------------------
-%%% convert_filter/2
-%%%--------------------
-convert_filter({'not', [Val | _]}, Replace) ->
- eldap:'not'(convert_filter(Val, Replace));
-
-convert_filter({'or', Vals}, Replace) ->
- eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
-
-convert_filter({'and', Vals}, Replace) ->
- eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
-
-convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
- parse_attr(Filter, Replace);
-
-convert_filter(Filter, Replace) when is_list(Filter) ->
- [convert_filter(X, Replace) || X <- Filter].
-
-%%%-----------------
-%%% parse_attr/2,3
-%%%-----------------
-parse_attr(Attr, ListOfSubValues) ->
- {Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
- parse_attr(Action, {Name, Value}, ListOfSubValues).
-
-parse_attr(approx, {Name, Value}, ListOfSubValues) ->
- NewValue = do_sub(Value, ListOfSubValues),
- eldap:approxMatch(Name, NewValue);
-
-parse_attr(greater, {Name, Value}, ListOfSubValues) ->
- NewValue = do_sub(Value, ListOfSubValues),
- eldap:greaterOrEqual(Name, NewValue);
-
-parse_attr(less, {Name, Value}, ListOfSubValues) ->
- NewValue = do_sub(Value, ListOfSubValues),
- eldap:lessOrEqual(Name, NewValue);
-
-parse_attr(equal, {Name, Value}, ListOfSubValues) ->
- {ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
- Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
- [Head | Tail] when Tail /= [] ->
- {Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
- R ->
- R
- end,
- case Pattern of
- [V] ->
- eldap:equalityMatch(Name, V);
- {[], [], []} ->
- eldap:present(Name);
- {"", Any, ""} ->
- eldap:substrings(Name, [{any, X} || X<-Any]);
- {H, Any, ""} ->
- eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
- {"", Any, T} ->
- eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
- {H, Any, T} ->
- eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
- end;
-
-parse_attr(_, _, _) ->
- false.
-
-%%%--------------------
-%%% do_sub/2,3
-%%%--------------------
+%%====================================================================
+%% Internal functions
+%%====================================================================
+-define(do_scan(L), scan(Rest, [], [{L, 1} | check(Buf, S) ++ Result], L, S)).
+
+scan(L, SList) ->
+ scan(L, "", [], undefined, SList).
+
+scan("=*)" ++ Rest, Buf, Result, '(', S) ->
+ scan(Rest, [], [{')', 1}, {'=*', 1} | check(Buf, S) ++ Result], ')', S);
+scan(":dn" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':dn');
+scan(":=" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':=');
+scan(":=" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':=');
+scan(":=" ++ Rest, Buf, Result, ':', S) -> ?do_scan(':=');
+scan("~=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('~=');
+scan(">=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('>=');
+scan("<=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('<=');
+scan("=" ++ Rest, Buf, Result, '(', S) -> ?do_scan('=');
+scan(":" ++ Rest, Buf, Result, '(', S) -> ?do_scan(':');
+scan(":" ++ Rest, Buf, Result, ':dn', S) -> ?do_scan(':');
+scan("&" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('&');
+scan("|" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('|');
+scan("!" ++ Rest, Buf, Result, '(', S) when Buf=="" -> ?do_scan('!');
+scan("*" ++ Rest, Buf, Result, '*', S) -> ?do_scan('*');
+scan("*" ++ Rest, Buf, Result, '=', S) -> ?do_scan('*');
+scan("(" ++ Rest, Buf, Result, _, S) -> ?do_scan('(');
+scan(")" ++ Rest, Buf, Result, _, S) -> ?do_scan(')');
+scan([Letter | Rest], Buf, Result, PreviosAtom, S) ->
+ scan(Rest, [Letter|Buf], Result, PreviosAtom, S);
+scan([], Buf, Result, _, S) ->
+ lists:reverse(check(Buf, S) ++ Result).
+
+check([], _) ->
+ [];
+check(Buf, S) ->
+ [{str, 1, do_sub(lists:reverse(Buf), S)}].
-define(MAX_RECURSION, 100).
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
do_sub(NewS, {RegExp, New}, Iter+1);
{ok, _, _} when Iter > ?MAX_RECURSION ->
- throw({regexp, max_substitute_recursion});
+ erlang:error(max_substitute_recursion);
_ ->
- throw({regexp, bad_regexp})
+ erlang:error(bad_regexp)
end;
do_sub(S, {_, _, N}, _) when N<1 ->
{ok, NewS, _} ->
NewS;
_ ->
- throw({regexp, bad_regexp})
+ erlang:error(bad_regexp)
end.
-remove_extra_asterisks(String) ->
- {Res, _} = lists:foldl(
- fun(X, {Acc, Last}) ->
- case X of
- $* when Last==$* ->
- {Acc, X};
- _ ->
- {Acc ++ [X], X}
- end
- end,
- {"", ""}, String),
- Res.
-
replace_amps(String) ->
- lists:foldl(
- fun(X, Acc) ->
- if
- X == $& ->
- Acc ++ "\\&";
- true ->
- Acc ++ [X]
- end
- end,
- "", String).
-
-split_attribute(String) ->
- split_attribute(String, "", $0).
-
-split_attribute([], _, _) ->
- {error, "", ""};
-
-split_attribute([H|Tail], Acc, Last) ->
- case H of
- $= when Last==$> ->
- {greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
- $= when Last==$< ->
- {less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
- $= when Last==$~ ->
- {approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
- $= when Last==$: ->
- {equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
- $= ->
- {equal, Acc, Tail};
- _ ->
- split_attribute(Tail, Acc++[H], H)
- end.
+ lists:map(
+ fun($&) -> "\\&";
+ (Chr) -> Chr
+ end, String).
--- /dev/null
+Nonterminals
+filter filtercomp filterlist item
+simple present substring extensible
+initial any final matchingrule xattr
+attr value.
+
+Terminals str
+'(' ')' '&' '|' '!' '=' '~=' '>=' '<=' '=*' '*' ':dn' ':' ':='.
+
+Rootsymbol filter.
+
+filter -> '(' filtercomp ')': '$2'.
+filtercomp -> '&' filterlist: 'and'('$2').
+filtercomp -> '|' filterlist: 'or'('$2').
+filtercomp -> '!' filter: 'not'('$2').
+filtercomp -> item: '$1'.
+filterlist -> filter: '$1'.
+filterlist -> filter filterlist: flatten(['$1', '$2']).
+
+item -> simple: '$1'.
+item -> present: '$1'.
+item -> substring: '$1'.
+item -> extensible: '$1'.
+
+simple -> attr '=' value: equal('$1', '$3').
+simple -> attr '~=' value: approx('$1', '$3').
+simple -> attr '>=' value: greater('$1', '$3').
+simple -> attr '<=' value: less('$1', '$3').
+
+present -> attr '=*': present('$1').
+
+substring -> attr '=' initial '*' any: substrings('$1', ['$3', '$5']).
+substring -> attr '=' '*' any final: substrings('$1', ['$4', '$5']).
+substring -> attr '=' initial '*' any final: substrings('$1', ['$3', '$5', '$6']).
+substring -> attr '=' '*' any: substrings('$1', ['$4']).
+any -> any value '*': 'any'('$1', '$2').
+any -> '$empty': [].
+initial -> value: initial('$1').
+final -> value: final('$1').
+
+extensible -> xattr ':dn' ':' matchingrule ':=' value: extensible('$6', ['$1', '$4']).
+extensible -> xattr ':' matchingrule ':=' value: extensible('$5', ['$1', '$3']).
+extensible -> xattr ':dn' ':=' value: extensible('$4', ['$1']).
+extensible -> xattr ':=' value: extensible('$3', ['$1']).
+extensible -> ':dn' ':' matchingrule ':=' value: extensible('$5', ['$3']).
+extensible -> ':' matchingrule ':=' value: extensible('$4', ['$2']).
+xattr -> value: xattr('$1').
+matchingrule -> value: matchingrule('$1').
+
+attr -> str: value_of('$1').
+value -> str: value_of('$1').
+
+Erlang code.
+
+'and'(Value) -> eldap:'and'(Value).
+'or'(Value) -> eldap:'or'(Value).
+'not'(Value) -> eldap:'not'(Value).
+equal(Desc, Value) -> eldap:equalityMatch(Desc, Value).
+approx(Desc, Value) -> eldap:approxMatch(Desc, Value).
+greater(Desc, Value) -> eldap:greaterOrEqual(Desc, Value).
+less(Desc, Value) -> eldap:lessOrEqual(Desc, Value).
+present(Value) -> eldap:present(Value).
+extensible(Value, Opts) -> eldap:extensibleMatch(Value, Opts).
+substrings(Desc, ValueList) -> eldap:substrings(Desc, flatten(ValueList)).
+initial(Value) -> {initial, Value}.
+final(Value) -> {final, Value}.
+'any'(Token, Value) -> [Token, {any, Value}].
+xattr(Value) -> {type, Value}.
+matchingrule(Value) -> {matchingRule, Value}.
+value_of(Token) -> element(3, Token).
+flatten(List) -> lists:flatten(List).