PageRenderTime 25ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/xep227toprosody.lua

http://github.com/bjc/prosody
Lua | 269 lines | 177 code | 36 blank | 56 comment | 40 complexity | 0ace78576aa0d440fc9d28cdc3fe751a MD5 | raw file
  1. #!/usr/bin/env lua
  2. -- Prosody IM
  3. -- Copyright (C) 2008-2009 Matthew Wild
  4. -- Copyright (C) 2008-2009 Waqas Hussain
  5. -- Copyright (C) 2010 Stefan Gehn
  6. --
  7. -- This project is MIT/X11 licensed. Please see the
  8. -- COPYING file in the source package for more information.
  9. --
  10. -- FIXME: XEP-0227 supports XInclude but luaexpat does not
  11. --
  12. -- XEP-227 elements and their current level of support:
  13. -- Hosts : supported
  14. -- Users : supported
  15. -- Rosters : supported, needs testing
  16. -- Offline Messages : supported, needs testing
  17. -- Private XML Storage : supported, needs testing
  18. -- vCards : supported, needs testing
  19. -- Privacy Lists: UNSUPPORTED
  20. -- http://xmpp.org/extensions/xep-0227.html#privacy-lists
  21. -- mod_privacy uses dm.load(username, host, "privacy"); and stores stanzas 1:1
  22. -- Incoming Subscription Requests : supported
  23. package.path = package.path..";../?.lua";
  24. package.cpath = package.cpath..";../?.so"; -- needed for util.pposix used in datamanager
  25. local my_name = arg[0];
  26. if my_name:match("[/\\]") then
  27. package.path = package.path..";"..my_name:gsub("[^/\\]+$", "../?.lua");
  28. package.cpath = package.cpath..";"..my_name:gsub("[^/\\]+$", "../?.so");
  29. end
  30. -- ugly workaround for getting datamanager to work outside of prosody :(
  31. prosody = { };
  32. prosody.platform = "unknown";
  33. if os.getenv("WINDIR") then
  34. prosody.platform = "windows";
  35. elseif package.config:sub(1,1) == "/" then
  36. prosody.platform = "posix";
  37. end
  38. local lxp = require "lxp";
  39. local st = require "util.stanza";
  40. local xmppstream = require "util.xmppstream";
  41. local new_xmpp_handlers = xmppstream.new_sax_handlers;
  42. local dm = require "util.datamanager"
  43. dm.set_data_path("data");
  44. local ns_separator = xmppstream.ns_separator;
  45. local ns_pattern = xmppstream.ns_pattern;
  46. local xmlns_xep227 = "http://www.xmpp.org/extensions/xep-0227.html#ns";
  47. -----------------------------------------------------------------------
  48. function store_vcard(username, host, stanza)
  49. -- create or update vCard for username@host
  50. local ret, err = dm.store(username, host, "vcard", st.preserialize(stanza));
  51. print("["..(err or "success").."] stored vCard: "..username.."@"..host);
  52. end
  53. function store_password(username, host, password)
  54. -- create or update account for username@host
  55. local ret, err = dm.store(username, host, "accounts", {password = password});
  56. print("["..(err or "success").."] stored account: "..username.."@"..host.." = "..password);
  57. end
  58. function store_roster(username, host, roster_items)
  59. -- fetch current roster-table for username@host if he already has one
  60. local roster = dm.load(username, host, "roster") or {};
  61. -- merge imported roster-items with loaded roster
  62. for item_tag in roster_items:childtags("item") do
  63. -- jid for this roster-item
  64. local item_jid = item_tag.attr.jid
  65. -- validate item stanzas
  66. if (item_jid ~= "") then
  67. -- prepare roster item
  68. -- TODO: is the subscription attribute optional?
  69. local item = {subscription = item_tag.attr.subscription, groups = {}};
  70. -- optional: give roster item a real name
  71. if item_tag.attr.name then
  72. item.name = item_tag.attr.name;
  73. end
  74. -- optional: iterate over group stanzas inside item stanza
  75. for group_tag in item_tag:childtags("group") do
  76. local group_name = group_tag:get_text();
  77. if (group_name ~= "") then
  78. item.groups[group_name] = true;
  79. else
  80. print("[error] invalid group stanza: "..group_tag:pretty_print());
  81. end
  82. end
  83. -- store item in roster
  84. roster[item_jid] = item;
  85. print("[success] roster entry: " ..username.."@"..host.." - "..item_jid);
  86. else
  87. print("[error] invalid roster stanza: " ..item_tag:pretty_print());
  88. end
  89. end
  90. -- store merged roster-table
  91. local ret, err = dm.store(username, host, "roster", roster);
  92. print("["..(err or "success").."] stored roster: " ..username.."@"..host);
  93. end
  94. function store_private(username, host, private_items)
  95. local private = dm.load(username, host, "private") or {};
  96. for _, ch in ipairs(private_items.tags) do
  97. --print("private :"..ch:pretty_print());
  98. private[ch.name..":"..ch.attr.xmlns] = st.preserialize(ch);
  99. print("[success] private item: " ..username.."@"..host.." - "..ch.name);
  100. end
  101. local ret, err = dm.store(username, host, "private", private);
  102. print("["..(err or "success").."] stored private: " ..username.."@"..host);
  103. end
  104. function store_offline_messages(username, host, offline_messages)
  105. -- TODO: maybe use list_load(), append and list_store() instead
  106. -- of constantly reopening the file with list_append()?
  107. for ch in offline_messages:childtags("message", "jabber:client") do
  108. --print("message :"..ch:pretty_print());
  109. local ret, err = dm.list_append(username, host, "offline", st.preserialize(ch));
  110. print("["..(err or "success").."] stored offline message: " ..username.."@"..host.." - "..ch.attr.from);
  111. end
  112. end
  113. function store_subscription_request(username, host, presence_stanza)
  114. local from_bare = presence_stanza.attr.from;
  115. -- fetch current roster-table for username@host if he already has one
  116. local roster = dm.load(username, host, "roster") or {};
  117. local item = roster[from_bare];
  118. if item and (item.subscription == "from" or item.subscription == "both") then
  119. return; -- already subscribed, do nothing
  120. end
  121. -- add to table of pending subscriptions
  122. if not roster.pending then roster.pending = {}; end
  123. roster.pending[from_bare] = true;
  124. -- store updated roster-table
  125. local ret, err = dm.store(username, host, "roster", roster);
  126. print("["..(err or "success").."] stored subscription request: " ..username.."@"..host.." - "..from_bare);
  127. end
  128. -----------------------------------------------------------------------
  129. local curr_host = "";
  130. local user_name = "";
  131. local cb = {
  132. stream_tag = "user",
  133. stream_ns = xmlns_xep227,
  134. };
  135. function cb.streamopened(session, attr)
  136. session.notopen = false;
  137. user_name = attr.name;
  138. store_password(user_name, curr_host, attr.password);
  139. end
  140. function cb.streamclosed(session)
  141. session.notopen = true;
  142. user_name = "";
  143. end
  144. function cb.handlestanza(session, stanza)
  145. --print("Parsed stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
  146. if (stanza.name == "vCard") and (stanza.attr.xmlns == "vcard-temp") then
  147. store_vcard(user_name, curr_host, stanza);
  148. elseif (stanza.name == "query") then
  149. if (stanza.attr.xmlns == "jabber:iq:roster") then
  150. store_roster(user_name, curr_host, stanza);
  151. elseif (stanza.attr.xmlns == "jabber:iq:private") then
  152. store_private(user_name, curr_host, stanza);
  153. end
  154. elseif (stanza.name == "offline-messages") then
  155. store_offline_messages(user_name, curr_host, stanza);
  156. elseif (stanza.name == "presence") and (stanza.attr.xmlns == "jabber:client") then
  157. store_subscription_request(user_name, curr_host, stanza);
  158. else
  159. print("UNHANDLED stanza "..stanza.name.." xmlns: "..(stanza.attr.xmlns or ""));
  160. end
  161. end
  162. local user_handlers = new_xmpp_handlers({ notopen = true }, cb);
  163. -----------------------------------------------------------------------
  164. local lxp_handlers = {
  165. --count = 0
  166. };
  167. -- TODO: error handling for invalid opening elements if curr_host is empty
  168. function lxp_handlers.StartElement(parser, elementname, attributes)
  169. local curr_ns, name = elementname:match(ns_pattern);
  170. if name == "" then
  171. curr_ns, name = "", curr_ns;
  172. end
  173. --io.write("+ ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
  174. --count = count + 1;
  175. if curr_host ~= "" then
  176. -- forward to xmlhandlers
  177. user_handlers.StartElement(parser, elementname, attributes);
  178. elseif (curr_ns == xmlns_xep227) and (name == "host") then
  179. curr_host = attributes["jid"]; -- start of host element
  180. print("Begin parsing host "..curr_host);
  181. elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
  182. io.stderr:write("Unhandled XML element: ", name, "\n");
  183. os.exit(1);
  184. end
  185. end
  186. -- TODO: error handling for invalid closing elements if host is empty
  187. function lxp_handlers.EndElement(parser, elementname)
  188. local curr_ns, name = elementname:match(ns_pattern);
  189. if name == "" then
  190. curr_ns, name = "", curr_ns;
  191. end
  192. --count = count - 1;
  193. --io.write("- ", string.rep(" ", count), name, " (", curr_ns, ")", "\n")
  194. if curr_host ~= "" then
  195. if (curr_ns == xmlns_xep227) and (name == "host") then
  196. print("End parsing host "..curr_host);
  197. curr_host = "" -- end of host element
  198. else
  199. -- forward to xmlhandlers
  200. user_handlers.EndElement(parser, elementname);
  201. end
  202. elseif (curr_ns ~= xmlns_xep227) or (name ~= "server-data") then
  203. io.stderr:write("Unhandled XML element: ", name, "\n");
  204. os.exit(1);
  205. end
  206. end
  207. function lxp_handlers.CharacterData(parser, string)
  208. if curr_host ~= "" then
  209. -- forward to xmlhandlers
  210. user_handlers.CharacterData(parser, string);
  211. end
  212. end
  213. -----------------------------------------------------------------------
  214. local arg = ...;
  215. local help = "/? -? ? /h -h /help -help --help";
  216. if not arg or help:find(arg, 1, true) then
  217. print([[XEP-227 importer for Prosody
  218. Usage: xep227toprosody.lua filename.xml
  219. ]]);
  220. os.exit(1);
  221. end
  222. local file = io.open(arg);
  223. if not file then
  224. io.stderr:write("Could not open file: ", arg, "\n");
  225. os.exit(0);
  226. end
  227. local parser = lxp.new(lxp_handlers, ns_separator);
  228. for l in file:lines() do
  229. parser:parse(l);
  230. end
  231. parser:parse();
  232. parser:close();
  233. file:close();