From 9a0b951855c649a2aeb262389d7479f42067d86f Mon Sep 17 00:00:00 2001
From: Evgeniy Khramtsov <ekhramtsov@process-one.net>
Date: Thu, 30 Oct 2014 23:54:02 +0300
Subject: [PATCH] Add tests for mod_vcard_xupdate

---
 test/ejabberd_SUITE.erl               | 68 +++++++++++++++++++---
 test/ejabberd_SUITE_data/ejabberd.yml |  8 +++
 tools/xmpp_codec.erl                  | 84 +++++++++++++++++++++++++++
 tools/xmpp_codec.hrl                  |  2 +
 tools/xmpp_codec.spec                 | 12 ++++
 5 files changed, 165 insertions(+), 9 deletions(-)

diff --git a/test/ejabberd_SUITE.erl b/test/ejabberd_SUITE.erl
index 471265c59..bb39795ea 100644
--- a/test/ejabberd_SUITE.erl
+++ b/test/ejabberd_SUITE.erl
@@ -210,6 +210,8 @@ db_tests(riak) ->
       [muc_master, muc_slave]},
      {test_announce, [sequence],
       [announce_master, announce_slave]},
+     {test_vcard_xupdate, [parallel],
+      [vcard_xupdate_master, vcard_xupdate_slave]},
      {test_roster_remove, [parallel],
       [roster_remove_master,
        roster_remove_slave]}];
@@ -243,6 +245,8 @@ db_tests(mnesia) ->
       [muc_master, muc_slave]},
      {test_announce, [sequence],
       [announce_master, announce_slave]},
+     {test_vcard_xupdate, [parallel],
+      [vcard_xupdate_master, vcard_xupdate_slave]},
      {test_roster_remove, [parallel],
       [roster_remove_master,
        roster_remove_slave]}];
@@ -273,6 +277,8 @@ db_tests(_) ->
       [muc_master, muc_slave]},
      {test_announce, [sequence],
       [announce_master, announce_slave]},
+     {test_vcard_xupdate, [parallel],
+      [vcard_xupdate_master, vcard_xupdate_slave]},
      {test_roster_remove, [parallel],
       [roster_remove_master,
        roster_remove_slave]}].
@@ -722,6 +728,42 @@ vcard_get(Config) ->
         send_recv(Config, #iq{type = get, sub_els = [#vcard{}]}),
     disconnect(Config).
 
+vcard_xupdate_master(Config) ->
+    Img = <<137, "PNG\r\n", 26, $\n>>,
+    ImgHash = p1_sha:sha(Img),
+    MyJID = my_jid(Config),
+    Peer = ?config(slave, Config),
+    wait_for_slave(Config),
+    send(Config, #presence{}),
+    ?recv2(#presence{from = MyJID, type = undefined},
+           #presence{from = Peer, type = undefined}),
+    VCard = #vcard{photo = #vcard_photo{type = <<"image/png">>, binval = Img}},
+    I1 = send(Config, #iq{type = set, sub_els = [VCard]}),
+    ?recv2(#iq{type = result, sub_els = [], id = I1},
+	   #presence{from = MyJID, type = undefined,
+		     sub_els = [#vcard_xupdate{photo = ImgHash}]}),
+    I2 = send(Config, #iq{type = set, sub_els = [#vcard{}]}),
+    ?recv3(#iq{type = result, sub_els = [], id = I2},
+	   #presence{from = MyJID, type = undefined,
+		     sub_els = [#vcard_xupdate{photo = undefined}]},
+	   #presence{from = Peer, type = unavailable}),
+    disconnect(Config).
+
+vcard_xupdate_slave(Config) ->
+    Img = <<137, "PNG\r\n", 26, $\n>>,
+    ImgHash = p1_sha:sha(Img),
+    MyJID = my_jid(Config),
+    Peer = ?config(master, Config),
+    send(Config, #presence{}),
+    #presence{from = MyJID, type = undefined} = recv(),
+    wait_for_master(Config),
+    #presence{from = Peer, type = undefined} = recv(),
+    #presence{from = Peer, type = undefined,
+	      sub_els = [#vcard_xupdate{photo = ImgHash}]} = recv(),
+    #presence{from = Peer, type = undefined,
+	      sub_els = [#vcard_xupdate{photo = undefined}]} = recv(),
+    disconnect(Config).
+
 stats(Config) ->
     #iq{type = result, sub_els = [#stats{stat = Stats}]} =
         send_recv(Config, #iq{type = get, sub_els = [#stats{}],
@@ -1018,7 +1060,8 @@ muc_master(Config) ->
     %% As this is the newly created room, we receive only the 2nd stanza.
     #presence{
           from = MyNickJID,
-          sub_els = [#muc_user{
+          sub_els = [#vcard_xupdate{},
+		     #muc_user{
                         status_codes = Codes,
                         items = [#muc_item{role = moderator,
                                            jid = MyJID,
@@ -1087,7 +1130,8 @@ muc_master(Config) ->
 				      [#muc_invite{to = PeerJID}]}]}),
     %% Peer is joining
     #presence{from = PeerNickJID,
-	      sub_els = [#muc_user{
+	      sub_els = [#vcard_xupdate{},
+			 #muc_user{
 			    items = [#muc_item{role = visitor,
 					       jid = PeerJID,
 					       affiliation = none}]}]} = recv(),
@@ -1120,7 +1164,8 @@ muc_master(Config) ->
 					    fields = ReplyVoiceReqFs}]}),
     %% Peer is becoming a participant
     #presence{from = PeerNickJID,
-	      sub_els = [#muc_user{
+	      sub_els = [#vcard_xupdate{},
+			 #muc_user{
 			    items = [#muc_item{role = participant,
 					       jid = PeerJID,
 					       affiliation = none}]}]} = recv(),
@@ -1138,7 +1183,8 @@ muc_master(Config) ->
 					     affiliation = member}]}]}),
     %% Peer became a member
     #presence{from = PeerNickJID,
-	      sub_els = [#muc_user{
+	      sub_els = [#vcard_xupdate{},
+			 #muc_user{
 			    items = [#muc_item{affiliation = member,
 					       jid = PeerJID,
 					       role = participant}]}]} = recv(),
@@ -1155,7 +1201,7 @@ muc_master(Config) ->
 						   role = none}]}]}),
     %% Got notification the peer is kicked
     %% 307 -> Inform user that he or she has been kicked from the room
-    #presence{from = PeerNickJID,
+    #presence{from = PeerNickJID, type = unavailable,
 	      sub_els = [#muc_user{
 			    status_codes = [307],
 			    items = [#muc_item{affiliation = member,
@@ -1213,14 +1259,16 @@ muc_slave(Config) ->
     %% First presence is from the participant, i.e. from the peer
     #presence{
        from = PeerNickJID,
-       sub_els = [#muc_user{
+       sub_els = [#vcard_xupdate{},
+		  #muc_user{
 		     status_codes = [],
 		     items = [#muc_item{role = moderator,
 					affiliation = owner}]}]} = recv(),
     %% The next is the self-presence (code 110 means it)
     #presence{
        from = MyNickJID,
-       sub_els = [#muc_user{
+       sub_els = [#vcard_xupdate{},
+		  #muc_user{
 		     status_codes = [110],
 		     items = [#muc_item{role = visitor,
 					affiliation = none}]}]} = recv(),
@@ -1251,7 +1299,8 @@ muc_slave(Config) ->
     send(Config, #message{to = Room, sub_els = [VoiceReq]}),
     %% Becoming a participant
     #presence{from = MyNickJID,
-	      sub_els = [#muc_user{
+	      sub_els = [#vcard_xupdate{},
+			 #muc_user{
 			    items = [#muc_item{role = participant,
 					       affiliation = none}]}]} = recv(),
     %% Sending private message to the peer
@@ -1259,7 +1308,8 @@ muc_slave(Config) ->
 			  body = [#text{data = Subject}]}),
     %% Becoming a member
     #presence{from = MyNickJID,
-	      sub_els = [#muc_user{
+	      sub_els = [#vcard_xupdate{},
+			 #muc_user{
 			    items = [#muc_item{role = participant,
 					       affiliation = member}]}]} = recv(),
     %% Retrieving a member list
diff --git a/test/ejabberd_SUITE_data/ejabberd.yml b/test/ejabberd_SUITE_data/ejabberd.yml
index 597ba5be7..9bd8a8b0a 100644
--- a/test/ejabberd_SUITE_data/ejabberd.yml
+++ b/test/ejabberd_SUITE_data/ejabberd.yml
@@ -40,6 +40,8 @@ host_config:
         db_type: odbc
       mod_vcard: 
         db_type: odbc
+      mod_vcard_xupdate:
+        db_type: odbc
       mod_adhoc: []
       mod_configure: []
       mod_disco: []
@@ -94,6 +96,8 @@ Welcome to this XMPP server."
         db_type: odbc
       mod_vcard: 
         db_type: odbc
+      mod_vcard_xupdate:
+        db_type: odbc
       mod_adhoc: []
       mod_configure: []
       mod_disco: []
@@ -141,6 +145,8 @@ Welcome to this XMPP server."
         db_type: internal
       mod_vcard: 
         db_type: internal
+      mod_vcard_xupdate:
+        db_type: internal
       mod_carboncopy:
         db_type: internal
       mod_client_state:
@@ -185,6 +191,8 @@ Welcome to this XMPP server."
         db_type: riak
       mod_vcard: 
         db_type: riak
+      mod_vcard_xupdate:
+        db_type: riak
       mod_adhoc: []
       mod_configure: []
       mod_disco: []
diff --git a/tools/xmpp_codec.erl b/tools/xmpp_codec.erl
index 58749af28..1d5cd88e0 100644
--- a/tools/xmpp_codec.erl
+++ b/tools/xmpp_codec.erl
@@ -287,6 +287,12 @@ decode({xmlel, _name, _attrs, _} = _el, Opts) ->
       {<<"required">>, <<"jabber:x:data">>} ->
 	  decode_xdata_field_required(<<"jabber:x:data">>,
 				      IgnoreEls, _el);
+      {<<"x">>, <<"vcard-temp:x:update">>} ->
+	  decode_vcard_xupdate(<<"vcard-temp:x:update">>,
+			       IgnoreEls, _el);
+      {<<"photo">>, <<"vcard-temp:x:update">>} ->
+	  decode_vcard_xupdate_photo(<<"vcard-temp:x:update">>,
+				     IgnoreEls, _el);
       {<<"vCard">>, <<"vcard-temp">>} ->
 	  decode_vcard(<<"vcard-temp">>, IgnoreEls, _el);
       {<<"CLASS">>, <<"vcard-temp">>} ->
@@ -1182,6 +1188,8 @@ is_known_tag({xmlel, _name, _attrs, _} = _el) ->
       {<<"value">>, <<"jabber:x:data">>} -> true;
       {<<"desc">>, <<"jabber:x:data">>} -> true;
       {<<"required">>, <<"jabber:x:data">>} -> true;
+      {<<"x">>, <<"vcard-temp:x:update">>} -> true;
+      {<<"photo">>, <<"vcard-temp:x:update">>} -> true;
       {<<"vCard">>, <<"vcard-temp">>} -> true;
       {<<"CLASS">>, <<"vcard-temp">>} -> true;
       {<<"CATEGORIES">>, <<"vcard-temp">>} -> true;
@@ -1841,6 +1849,9 @@ encode({vcard, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
 	_, _, _, _, _, _, _, _, _, _, _, _, _, _, _} =
 	   Vcard) ->
     encode_vcard(Vcard, [{<<"xmlns">>, <<"vcard-temp">>}]);
+encode({vcard_xupdate, _} = X) ->
+    encode_vcard_xupdate(X,
+			 [{<<"xmlns">>, <<"vcard-temp:x:update">>}]);
 encode({xdata_field, _, _, _, _, _, _, _} = Field) ->
     encode_xdata_field(Field,
 		       [{<<"xmlns">>, <<"jabber:x:data">>}]);
@@ -2126,6 +2137,7 @@ get_ns({vcard_key, _, _}) -> <<"vcard-temp">>;
 get_ns({vcard, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
 	_, _, _, _, _, _, _, _, _, _, _, _, _, _, _}) ->
     <<"vcard-temp">>;
+get_ns({vcard_xupdate, _}) -> <<"vcard-temp:x:update">>;
 get_ns({xdata_field, _, _, _, _, _, _, _}) ->
     <<"jabber:x:data">>;
 get_ns({xdata, _, _, _, _, _, _}) ->
@@ -2339,6 +2351,7 @@ pp(vcard, 29) ->
      email, jabberid, mailer, tz, geo, title, role, logo,
      org, categories, note, prodid, rev, sort_string, sound,
      uid, url, class, key, desc];
+pp(vcard_xupdate, 1) -> [photo];
 pp(xdata_field, 7) ->
     [label, type, var, required, desc, values, options];
 pp(xdata, 6) ->
@@ -7515,6 +7528,77 @@ encode_xdata_field_required(true, _xmlns_attrs) ->
     _attrs = _xmlns_attrs,
     {xmlel, <<"required">>, _attrs, _els}.
 
+decode_vcard_xupdate(__TopXMLNS, __IgnoreEls,
+		     {xmlel, <<"x">>, _attrs, _els}) ->
+    Photo = decode_vcard_xupdate_els(__TopXMLNS,
+				     __IgnoreEls, _els, undefined),
+    {vcard_xupdate, Photo}.
+
+decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, [],
+			 Photo) ->
+    Photo;
+decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls,
+			 [{xmlel, <<"photo">>, _attrs, _} = _el | _els],
+			 Photo) ->
+    _xmlns = get_attr(<<"xmlns">>, _attrs),
+    if _xmlns == <<>>; _xmlns == __TopXMLNS ->
+	   decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
+				    decode_vcard_xupdate_photo(__TopXMLNS,
+							       __IgnoreEls,
+							       _el));
+       true ->
+	   decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
+				    Photo)
+    end;
+decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls,
+			 [_ | _els], Photo) ->
+    decode_vcard_xupdate_els(__TopXMLNS, __IgnoreEls, _els,
+			     Photo).
+
+encode_vcard_xupdate({vcard_xupdate, Photo},
+		     _xmlns_attrs) ->
+    _els =
+	lists:reverse('encode_vcard_xupdate_$photo'(Photo, [])),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"x">>, _attrs, _els}.
+
+'encode_vcard_xupdate_$photo'(undefined, _acc) -> _acc;
+'encode_vcard_xupdate_$photo'(Photo, _acc) ->
+    [encode_vcard_xupdate_photo(Photo, []) | _acc].
+
+decode_vcard_xupdate_photo(__TopXMLNS, __IgnoreEls,
+			   {xmlel, <<"photo">>, _attrs, _els}) ->
+    Cdata = decode_vcard_xupdate_photo_els(__TopXMLNS,
+					   __IgnoreEls, _els, <<>>),
+    Cdata.
+
+decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
+			       [], Cdata) ->
+    decode_vcard_xupdate_photo_cdata(__TopXMLNS, Cdata);
+decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
+			       [{xmlcdata, _data} | _els], Cdata) ->
+    decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
+				   _els, <<Cdata/binary, _data/binary>>);
+decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
+			       [_ | _els], Cdata) ->
+    decode_vcard_xupdate_photo_els(__TopXMLNS, __IgnoreEls,
+				   _els, Cdata).
+
+encode_vcard_xupdate_photo(Cdata, _xmlns_attrs) ->
+    _els = encode_vcard_xupdate_photo_cdata(Cdata, []),
+    _attrs = _xmlns_attrs,
+    {xmlel, <<"photo">>, _attrs, _els}.
+
+decode_vcard_xupdate_photo_cdata(__TopXMLNS, <<>>) ->
+    undefined;
+decode_vcard_xupdate_photo_cdata(__TopXMLNS, _val) ->
+    _val.
+
+encode_vcard_xupdate_photo_cdata(undefined, _acc) ->
+    _acc;
+encode_vcard_xupdate_photo_cdata(_val, _acc) ->
+    [{xmlcdata, _val} | _acc].
+
 decode_vcard(__TopXMLNS, __IgnoreEls,
 	     {xmlel, <<"vCard">>, _attrs, _els}) ->
     {Mailer, Adr, Class, Categories, Desc, Uid, Prodid,
diff --git a/tools/xmpp_codec.hrl b/tools/xmpp_codec.hrl
index c421aa1d6..4098a7fd6 100644
--- a/tools/xmpp_codec.hrl
+++ b/tools/xmpp_codec.hrl
@@ -252,6 +252,8 @@
                    status_codes = [] :: [pos_integer()],
                    password :: binary()}).
 
+-record(vcard_xupdate, {photo :: binary()}).
+
 -record(carbons_disable, {}).
 
 -record(bytestreams, {hosts = [] :: [#streamhost{}],
diff --git a/tools/xmpp_codec.spec b/tools/xmpp_codec.spec
index f8ed72af9..61f438cbe 100644
--- a/tools/xmpp_codec.spec
+++ b/tools/xmpp_codec.spec
@@ -1486,6 +1486,18 @@
                         label = '$categories'},
                    #ref{name = vcard_CLASS, min = 0, max = 1, label = '$class'}]}).
 
+-xml(vcard_xupdate_photo,
+     #elem{name = <<"photo">>,
+	   xmlns = <<"vcard-temp:x:update">>,
+	   result = '$cdata'}).
+
+-xml(vcard_xupdate,
+     #elem{name = <<"x">>,
+	   xmlns = <<"vcard-temp:x:update">>,
+	   result = {vcard_xupdate, '$photo'},
+	   refs = [#ref{name = vcard_xupdate_photo, min = 0, max = 1,
+			label = '$photo'}]}).
+
 -xml(xdata_field_required,
      #elem{name = <<"required">>,
            xmlns = <<"jabber:x:data">>,
-- 
2.40.0