From b2f536ec8b40adca1e7c619507116155d6a8497a Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Mon, 13 Jan 2020 03:36:52 +0300 Subject: [PATCH] Use SQL ESCAPE statement only with MSSQL and SQLite, improve compatibility with CockroachDB (#3074) --- include/ejabberd_sql.hrl | 3 ++- src/ejabberd_auth_sql.erl | 8 ++++---- src/ejabberd_sql.erl | 32 ++++++++++++++++++++++++-------- src/ejabberd_sql_pt.erl | 16 ++++++++++++++++ src/node_flat_sql.erl | 12 ++++++------ src/node_pep_sql.erl | 8 ++++---- src/nodetree_tree_sql.erl | 4 ++-- 7 files changed, 58 insertions(+), 25 deletions(-) diff --git a/include/ejabberd_sql.hrl b/include/ejabberd_sql.hrl index 83d70c982..9d297d212 100644 --- a/include/ejabberd_sql.hrl +++ b/include/ejabberd_sql.hrl @@ -38,4 +38,5 @@ -record(sql_escape, {string :: fun((binary()) -> binary()), integer :: fun((integer()) -> binary()), boolean :: fun((boolean()) -> binary()), - in_array_string :: fun((binary()) -> binary())}). + in_array_string :: fun((binary()) -> binary()), + like_escape :: fun(() -> binary())}). diff --git a/src/ejabberd_auth_sql.erl b/src/ejabberd_auth_sql.erl index ed833ca0f..dd020d534 100644 --- a/src/ejabberd_auth_sql.erl +++ b/src/ejabberd_auth_sql.erl @@ -205,12 +205,12 @@ list_users(LServer, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}]) when is_binary(Prefix) and is_integer(Limit) and is_integer(Offset) -> - SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), + SPrefix = ejabberd_sql:escape_like_arg(Prefix), SPrefix2 = <>, ejabberd_sql:sql_query( LServer, ?SQL("select @(username)s from users " - "where username like %(SPrefix2)s escape '^' and %(LServer)H " + "where username like %(SPrefix2)s %ESCAPE and %(LServer)H " "order by username " "limit %(Limit)d offset %(Offset)d")). @@ -235,12 +235,12 @@ users_number(LServer) -> users_number(LServer, [{prefix, Prefix}]) when is_binary(Prefix) -> - SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), + SPrefix = ejabberd_sql:escape_like_arg(Prefix), SPrefix2 = <>, ejabberd_sql:sql_query( LServer, ?SQL("select @(count(*))d from users " - "where username like %(SPrefix2)s escape '^' and %(LServer)H")); + "where username like %(SPrefix2)s %ESCAPE and %(LServer)H")); users_number(LServer, []) -> users_number(LServer). diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 6c7507e90..308c33020 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -227,6 +227,8 @@ escape_like_arg(S) when is_binary(S) -> escape_like_arg($%) -> <<"\\%">>; escape_like_arg($_) -> <<"\\_">>; escape_like_arg($\\) -> <<"\\\\">>; +escape_like_arg($[) -> <<"\\[">>; % For MSSQL +escape_like_arg($]) -> <<"\\]">>; escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <>. escape_like_arg_circumflex(S) when is_binary(S) -> @@ -718,7 +720,8 @@ generic_escape() -> boolean = fun(true) -> <<"1">>; (false) -> <<"0">> end, - in_array_string = fun(X) -> <<"'", (escape(X))/binary, "'">> end + in_array_string = fun(X) -> <<"'", (escape(X))/binary, "'">> end, + like_escape = fun() -> <<"">> end }. pgsql_sql_query(SQLQuery) -> @@ -736,7 +739,8 @@ pgsql_escape() -> boolean = fun(true) -> <<"1">>; (false) -> <<"0">> end, - in_array_string = fun(X) -> <<"E'", (escape(X))/binary, "'">> end + in_array_string = fun(X) -> <<"E'", (escape(X))/binary, "'">> end, + like_escape = fun() -> <<"">> end }. sqlite_sql_query(SQLQuery) -> @@ -754,7 +758,8 @@ sqlite_escape() -> boolean = fun(true) -> <<"1">>; (false) -> <<"0">> end, - in_array_string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end + in_array_string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end, + like_escape = fun() -> <<"ESCAPE '\\'">> end }. standard_escape(S) -> @@ -767,9 +772,18 @@ mssql_sql_query(SQLQuery) -> sqlite_sql_query(SQLQuery). pgsql_prepare(SQLQuery, State) -> - Escape = #sql_escape{_ = fun(X) -> X end}, - N = length((SQLQuery#sql_query.args)(Escape)), - Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)], + Escape = #sql_escape{_ = fun(_) -> arg end, + like_escape = fun() -> escape end}, + {RArgs, _} = + lists:foldl( + fun(arg, {Acc, I}) -> + {[<<$$, (integer_to_binary(I))/binary>> | Acc], I + 1}; + (escape, {Acc, I}) -> + {[<<"">> | Acc], I} + end, {[], 1}, (SQLQuery#sql_query.args)(Escape)), + Args = lists:reverse(RArgs), + %N = length((SQLQuery#sql_query.args)(Escape)), + %Args = [<<$$, (integer_to_binary(I))/binary>> || I <- lists:seq(1, N)], Query = (SQLQuery#sql_query.format_query)(Args), pgsql:prepare(State#state.db_ref, SQLQuery#sql_query.hash, Query). @@ -779,13 +793,15 @@ pgsql_execute_escape() -> boolean = fun(true) -> "1"; (false) -> "0" end, - in_array_string = fun(X) -> <<"\"", (escape(X))/binary, "\"">> end + in_array_string = fun(X) -> <<"\"", (escape(X))/binary, "\"">> end, + like_escape = fun() -> ignore end }. pgsql_execute_sql_query(SQLQuery, State) -> Args = (SQLQuery#sql_query.args)(pgsql_execute_escape()), + Args2 = lists:filter(fun(ignore) -> false; (_) -> true end, Args), ExecuteRes = - pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args), + pgsql:execute(State#state.db_ref, SQLQuery#sql_query.hash, Args2), % {T, ExecuteRes} = % timer:tc(pgsql, execute, [State#state.db_ref, SQLQuery#sql_query.hash, Args]), % io:format("T ~ts ~p~n", [SQLQuery#sql_query.hash, T]), diff --git a/src/ejabberd_sql_pt.erl b/src/ejabberd_sql_pt.erl index c868ef8f3..5ac027f9b 100644 --- a/src/ejabberd_sql_pt.erl +++ b/src/ejabberd_sql_pt.erl @@ -354,6 +354,22 @@ parse1([$%, $( | S], Acc, State) -> used_vars = [Name | State2#state.used_vars]} end, parse1(S1, [], State4); +parse1("%ESCAPE" ++ S, Acc, State) -> + State1 = append_string(lists:reverse(Acc), State), + Convert = + erl_syntax:application( + erl_syntax:record_access( + erl_syntax:variable(?ESCAPE_VAR), + erl_syntax:atom(?ESCAPE_RECORD), + erl_syntax:atom(like_escape)), + []), + Var = State1#state.param_pos, + State2 = + State1#state{'query' = [{var, Var} | State1#state.'query'], + args = [Convert | State1#state.args], + params = [Var | State1#state.params], + param_pos = State1#state.param_pos + 1}, + parse1(S, [], State2); parse1([C | S], Acc, State) -> parse1(S, [C | Acc], State). diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index c4cc293d0..38feed821 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -393,7 +393,7 @@ get_entity_subscriptions(Host, Owner) -> ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " "from pubsub_state i, pubsub_node n " "where i.nodeid = n.nodeid and " - "(jid=%(GJ)s or jid like %(GJLike)s escape '^') and host=%(H)s"); + "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s"); _ -> SJ = encode_jid(SubKey), ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " @@ -436,7 +436,7 @@ get_entity_subscriptions_for_send_last(Host, Owner) -> "from pubsub_state i, pubsub_node n, pubsub_node_option o " "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and " "name='send_last_published_item' and val='on_sub_and_presence' and " - "(jid=%(GJ)s or jid like %(GJLike)s escape '^') and host=%(H)s"); + "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host=%(H)s"); _ -> SJ = encode_jid(SubKey), ?SQL("select @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " @@ -869,7 +869,7 @@ itemids(Nidx, {_U, _S, _R} = JID) -> ejabberd_sql:sql_query_t( ?SQL("select @(itemid)s from pubsub_item where " "nodeid=%(Nidx)d and (publisher=%(SJID)s" - " or publisher like %(SJIDLike)s escape '^') " + " or publisher like %(SJIDLike)s %ESCAPE) " "order by modification desc")) of {selected, RItems} -> @@ -968,16 +968,16 @@ encode_jid(JID) -> -spec encode_jid_like(JID :: ljid()) -> binary(). encode_jid_like(JID) -> - ejabberd_sql:escape_like_arg_circumflex(jid:encode(JID)). + ejabberd_sql:escape_like_arg(jid:encode(JID)). -spec encode_host(Host :: host()) -> binary(). encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID); encode_host(Host) -> Host. -spec encode_host_like(Host :: host()) -> binary(). -encode_host_like({_U, _S, _R} = LJID) -> ejabberd_sql:escape(encode_jid_like(LJID)); +encode_host_like({_U, _S, _R} = LJID) -> encode_jid_like(LJID); encode_host_like(Host) -> - ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Host)). + ejabberd_sql:escape_like_arg(Host). -spec encode_affiliation(Arg :: atom()) -> binary(). encode_affiliation(owner) -> <<"o">>; diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index 58a15cc16..3d1458084 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -125,13 +125,13 @@ get_entity_subscriptions(_Host, Owner) -> ?SQL("select @(host)s, @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " "from pubsub_state i, pubsub_node n " "where i.nodeid = n.nodeid and " - "(jid=%(GJ)s or jid like %(GJLike)s escape '^') and host like %(HLike)s escape '^'"); + "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host like %(HLike)s %ESCAPE"); _ -> SJ = node_flat_sql:encode_jid(SubKey), ?SQL("select @(host)s, @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " "from pubsub_state i, pubsub_node n " "where i.nodeid = n.nodeid and " - "jid in (%(SJ)s,%(GJ)s) and host like %(HLike)s escape '^'") + "jid in (%(SJ)s,%(GJ)s) and host like %(HLike)s %ESCAPE") end, {result, case ejabberd_sql:sql_query_t(Query) of @@ -162,14 +162,14 @@ get_entity_subscriptions_for_send_last(_Host, Owner) -> "from pubsub_state i, pubsub_node n, pubsub_node_option o " "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and " "name='send_last_published_item' and val='on_sub_and_presence' and " - "(jid=%(GJ)s or jid like %(GJLike)s escape '^') and host like %(HLike)s escape '^'"); + "(jid=%(GJ)s or jid like %(GJLike)s %ESCAPE) and host like %(HLike)s %ESCAPE"); _ -> SJ = node_flat_sql:encode_jid(SubKey), ?SQL("select @(host)s, @(node)s, @(plugin)s, @(i.nodeid)d, @(jid)s, @(subscriptions)s " "from pubsub_state i, pubsub_node n, pubsub_node_option o " "where i.nodeid = n.nodeid and n.nodeid = o.nodeid and " "name='send_last_published_item' and val='on_sub_and_presence' and " - "jid in (%(SJ)s,%(GJ)s) and host like %(HLike)s escape '^'") + "jid in (%(SJ)s,%(GJ)s) and host like %(HLike)s %ESCAPE") end, {result, case ejabberd_sql:sql_query_t(Query) of diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl index 2cd208be1..a9e022091 100644 --- a/src/nodetree_tree_sql.erl +++ b/src/nodetree_tree_sql.erl @@ -220,12 +220,12 @@ get_subnodes_tree(Host, Node) -> Rec -> Type = Rec#pubsub_node.type, H = node_flat_sql:encode_host(Host), - N = <<(ejabberd_sql:escape_like_arg_circumflex(Node))/binary, "/%">>, + N = <<(ejabberd_sql:escape_like_arg(Node))/binary, "/%">>, Sub = case catch ejabberd_sql:sql_query_t( ?SQL("select @(node)s, @(parent)s, @(plugin)s, @(nodeid)d from pubsub_node " "where host=%(H)s and plugin=%(Type)s and" - " (parent=%(Node)s or parent like %(N)s escape '^')")) + " (parent=%(Node)s or parent like %(N)s %ESCAPE)")) of {selected, RItems} -> [raw_to_node(Host, Item) || Item <- RItems];