diff --git a/src/ejabberd_sql.erl b/src/ejabberd_sql.erl index 09bd31973..f503a749f 100644 --- a/src/ejabberd_sql.erl +++ b/src/ejabberd_sql.erl @@ -43,6 +43,7 @@ escape/1, escape_like/1, escape_like_arg/1, + escape_like_arg_circumflex/1, to_bool/1, sqlite_db/1, sqlite_file/1, @@ -200,6 +201,7 @@ escape_like(S) when is_binary(S) -> << <<(escape_like(C))/binary>> || <> <= S >>; escape_like($%) -> <<"\\%">>; escape_like($_) -> <<"\\_">>; +escape_like($\\) -> <<"\\\\\\\\">>; escape_like(C) when is_integer(C), C >= 0, C =< 255 -> sql_queries:escape(C). escape_like_arg(S) when is_binary(S) -> @@ -209,6 +211,13 @@ escape_like_arg($_) -> <<"\\_">>; escape_like_arg($\\) -> <<"\\\\">>; escape_like_arg(C) when is_integer(C), C >= 0, C =< 255 -> <>. +escape_like_arg_circumflex(S) when is_binary(S) -> + << <<(escape_like_arg_circumflex(C))/binary>> || <> <= S >>; +escape_like_arg_circumflex($%) -> <<"^%">>; +escape_like_arg_circumflex($_) -> <<"^_">>; +escape_like_arg_circumflex($^) -> <<"^^">>; +escape_like_arg_circumflex(C) when is_integer(C), C >= 0, C =< 255 -> <>. + to_bool(<<"t">>) -> true; to_bool(<<"true">>) -> true; to_bool(<<"1">>) -> true; @@ -508,7 +517,7 @@ sql_query_internal(#sql_query{} = Query) -> odbc -> generic_sql_query(Query); mssql -> - generic_sql_query(Query); + mssql_sql_query(Query); pgsql -> Key = {?PREPARE_KEY, Query#sql_query.hash}, case get(Key) of @@ -534,7 +543,7 @@ sql_query_internal(#sql_query{} = Query) -> mysql -> generic_sql_query(Query); sqlite -> - generic_sql_query(Query) + sqlite_sql_query(Query) end catch Class:Reason -> @@ -623,6 +632,32 @@ generic_escape() -> end }. +sqlite_sql_query(SQLQuery) -> + sql_query_format_res( + sql_query_internal(sqlite_sql_query_format(SQLQuery)), + SQLQuery). + +sqlite_sql_query_format(SQLQuery) -> + Args = (SQLQuery#sql_query.args)(sqlite_escape()), + (SQLQuery#sql_query.format_query)(Args). + +sqlite_escape() -> + #sql_escape{string = fun(X) -> <<"'", (standard_escape(X))/binary, "'">> end, + integer = fun(X) -> integer_to_binary(X) end, + boolean = fun(true) -> <<"1">>; + (false) -> <<"0">> + end + }. + +standard_escape(S) -> + << <<(case Char of + $' -> << "''" >>; + _ -> << Char >> + end)/binary>> || <> <= S >>. + +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)), diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl index 6b8e90333..b8234bf9c 100644 --- a/src/mod_vcard_sql.erl +++ b/src/mod_vcard_sql.erl @@ -227,9 +227,11 @@ make_val(Match, Field, Val) -> Condition = case str:suffix(<<"*">>, Val) of true -> Val1 = str:substr(Val, 1, byte_size(Val) - 1), - SVal = <<(ejabberd_sql:escape_like(Val1))/binary, + SVal = <<(ejabberd_sql:escape( + ejabberd_sql:escape_like_arg_circumflex( + Val1)))/binary, "%">>, - [Field, <<" LIKE '">>, SVal, <<"'">>]; + [Field, <<" LIKE '">>, SVal, <<"' ESCAPE '^'">>]; _ -> SVal = ejabberd_sql:escape(Val), [Field, <<" = '">>, SVal, <<"'">>] diff --git a/src/node_flat_sql.erl b/src/node_flat_sql.erl index 5baabcfcf..ea3aa7d3c 100644 --- a/src/node_flat_sql.erl +++ b/src/node_flat_sql.erl @@ -51,9 +51,11 @@ get_entity_subscriptions_for_send_last/2, get_last_items/3]). -export([decode_jid/1, encode_jid/1, + encode_jid_like/1, decode_affiliation/1, decode_subscriptions/1, encode_affiliation/1, encode_subscriptions/1, - encode_host/1]). + encode_host/1, + encode_host_like/1]). init(_Host, _ServerHost, _Opts) -> %%pubsub_subscription_sql:init(), @@ -350,11 +352,13 @@ get_entity_subscriptions(Host, Owner) -> H = encode_host(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), + GJLike = encode_jid_like(GenKey), Query = case SubKey of GenKey -> [<<"select node, type, i.nodeid, jid, subscriptions " "from pubsub_state i, pubsub_node n " - "where i.nodeid = n.nodeid and jid like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; + "where i.nodeid = n.nodeid and jid like '">>, GJLike, + <<"%' escape '^' and host='">>, H, <<"';">>]; _ -> [<<"select node, type, i.nodeid, jid, subscriptions " "from pubsub_state i, pubsub_node n " @@ -399,12 +403,14 @@ get_entity_subscriptions_for_send_last(Host, Owner) -> H = encode_host(Host), SJ = encode_jid(SubKey), GJ = encode_jid(GenKey), + GJLike = encode_jid_like(GenKey), Query = case SubKey of GenKey -> [<<"select node, type, i.nodeid, jid, subscriptions " "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 like '">>, GJ, <<"%' and host='">>, H, <<"';">>]; + "and val='on_sub_and_presence' and jid like '">>, GJLike, + <<"%' escape '^' and host='">>, H, <<"';">>]; _ -> [<<"select node, type, i.nodeid, jid, subscriptions " "from pubsub_state i, pubsub_node n, pubsub_node_option o " @@ -568,8 +574,9 @@ get_states(Nidx) -> [<<"jid">>, <<"affiliation">>, <<"subscriptions">>], RItems} -> {result, lists:map(fun ([SJID, Aff, Subs]) -> - #pubsub_state{stateid = {decode_jid(SJID), Nidx}, - items = itemids(Nidx, SJID), + JID = decode_jid(SJID), + #pubsub_state{stateid = {JID, Nidx}, + items = itemids(Nidx, JID), affiliation = decode_affiliation(Aff), subscriptions = decode_subscriptions(Subs)} end, @@ -899,13 +906,12 @@ first_in_list(Pred, [H | T]) -> _ -> first_in_list(Pred, T) end. -itemids(Nidx, {U, S, R}) -> - itemids(Nidx, encode_jid({U, S, R})); -itemids(Nidx, SJID) -> +itemids(Nidx, {_U, _S, _R} = JID) -> + SJID = encode_jid_like(JID), case catch ejabberd_sql:sql_query_t([<<"select itemid from pubsub_item where " "nodeid='">>, Nidx, <<"' and publisher like '">>, SJID, - <<"%' order by modification desc;">>]) + <<"%' escape '^' order by modification desc;">>]) of {selected, [<<"itemid">>], RItems} -> [ItemId || [ItemId] <- RItems]; @@ -1011,6 +1017,10 @@ decode_subscriptions(Subscriptions) -> encode_jid(JID) -> ejabberd_sql:escape(jid:to_string(JID)). +-spec(encode_jid_like/1 :: (JID :: ljid()) -> binary()). +encode_jid_like(JID) -> + ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(jid:to_string(JID))). + -spec(encode_host/1 :: ( Host :: host()) -> binary() @@ -1018,6 +1028,14 @@ encode_jid(JID) -> encode_host({_U, _S, _R} = LJID) -> encode_jid(LJID); encode_host(Host) -> ejabberd_sql:escape(Host). +-spec(encode_host_like/1 :: + ( Host :: host()) + -> binary() + ). +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)). + -spec(encode_affiliation/1 :: ( Arg :: atom()) -> binary() diff --git a/src/node_pep_sql.erl b/src/node_pep_sql.erl index c5b31d15f..1f2c13d5c 100644 --- a/src/node_pep_sql.erl +++ b/src/node_pep_sql.erl @@ -114,20 +114,21 @@ set_affiliation(Nidx, Owner, Affiliation) -> get_entity_subscriptions(_Host, Owner) -> SubKey = jid:tolower(Owner), GenKey = jid:remove_resource(SubKey), - Host = node_flat_sql:encode_host(element(2, SubKey)), + HostLike = node_flat_sql:encode_host_like(element(2, SubKey)), SJ = node_flat_sql:encode_jid(SubKey), GJ = node_flat_sql:encode_jid(GenKey), + GJLike = node_flat_sql:encode_jid_like(GenKey), Query = case SubKey of GenKey -> [<<"select host, node, type, i.nodeid, jid, " "subscriptions from pubsub_state i, pubsub_node n " "where i.nodeid = n.nodeid and jid " - "like '">>, GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + "like '">>, GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>]; _ -> [<<"select host, node, type, i.nodeid, jid, " "subscriptions from pubsub_state i, pubsub_node n " "where i.nodeid = n.nodeid and jid " - "in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] + "in ('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>] end, Reply = case catch ejabberd_sql:sql_query_t(Query) of {selected, @@ -149,9 +150,10 @@ get_entity_subscriptions(_Host, Owner) -> get_entity_subscriptions_for_send_last(_Host, Owner) -> SubKey = jid:tolower(Owner), GenKey = jid:remove_resource(SubKey), - Host = node_flat_sql:encode_host(element(2, SubKey)), + HostLike = node_flat_sql:encode_host_like(element(2, SubKey)), SJ = node_flat_sql:encode_jid(SubKey), GJ = node_flat_sql:encode_jid(GenKey), + GJLike = node_flat_sql:encode_jid_like(GenKey), Query = case SubKey of GenKey -> [<<"select host, node, type, i.nodeid, jid, " @@ -159,14 +161,14 @@ get_entity_subscriptions_for_send_last(_Host, Owner) -> "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 like '">>, - GJ, <<"%' and host like '%@">>, Host, <<"';">>]; + GJLike, <<"%' escape '^' and host like '%@">>, HostLike, <<"' escape '^';">>]; _ -> [<<"select host, node, type, i.nodeid, jid, " "subscriptions 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, <<"', '">>, GJ, <<"') and host like '%@">>, Host, <<"';">>] + "('">>, SJ, <<"', '">>, GJ, <<"') and host like '%@">>, HostLike, <<"' escape '^';">>] end, Reply = case catch ejabberd_sql:sql_query_t(Query) of {selected, diff --git a/src/nodetree_tree_sql.erl b/src/nodetree_tree_sql.erl index b56543395..17ae91b52 100644 --- a/src/nodetree_tree_sql.erl +++ b/src/nodetree_tree_sql.erl @@ -196,11 +196,11 @@ get_subnodes_tree(Host, Node, _From) -> get_subnodes_tree(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), + N = ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Node)), case catch ejabberd_sql:sql_query_t([<<"select node, parent, type, nodeid from " "pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%';">>]) + H, <<"' and node like '">>, N, <<"%' escape '^';">>]) of {selected, [<<"node">>, <<"parent">>, <<"type">>, <<"nodeid">>], RItems} -> @@ -256,10 +256,10 @@ create_node(Host, Node, Type, Owner, Options, Parents) -> delete_node(Host, Node) -> H = node_flat_sql:encode_host(Host), - N = ejabberd_sql:escape(Node), + N = ejabberd_sql:escape(ejabberd_sql:escape_like_arg_circumflex(Node)), Removed = get_subnodes_tree(Host, Node), catch ejabberd_sql:sql_query_t([<<"delete from pubsub_node where host='">>, - H, <<"' and node like '">>, N, <<"%';">>]), + H, <<"' and node like '">>, N, <<"%' escape '^';">>]), Removed. %% helpers diff --git a/src/sql_queries.erl b/src/sql_queries.erl index e1374a817..121117574 100644 --- a/src/sql_queries.erl +++ b/src/sql_queries.erl @@ -231,12 +231,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(Prefix), + SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), SPrefix2 = <>, ejabberd_sql:sql_query( LServer, ?SQL("select @(username)s from users " - "where username like %(SPrefix2)s " + "where username like %(SPrefix2)s escape '^' " "order by username " "limit %(Limit)d offset %(Offset)d")). @@ -264,12 +264,12 @@ users_number(LServer) -> users_number(LServer, [{prefix, Prefix}]) when is_binary(Prefix) -> - SPrefix = ejabberd_sql:escape_like_arg(Prefix), + SPrefix = ejabberd_sql:escape_like_arg_circumflex(Prefix), SPrefix2 = <>, ejabberd_sql:sql_query( LServer, ?SQL("select @(count(*))d from users " - "where username like %(SPrefix2)s")); + "where username like %(SPrefix2)s escape '^'")); users_number(LServer, []) -> users_number(LServer). diff --git a/test/suite.erl b/test/suite.erl index f37b51ea6..fb8b6ae2f 100644 --- a/test/suite.erl +++ b/test/suite.erl @@ -66,7 +66,7 @@ init_config(Config) -> {resource, <<"resource">>}, {master_resource, <<"master_resource">>}, {slave_resource, <<"slave_resource">>}, - {password, <<"password">>}, + {password, <<"password!@#$%^&*()'\"`~<>+-/;:_=[]{}|\\">>} {backends, get_config_backends()} |Config].