1 local array = require "util.array"; |
1 local array = require "util.array"; |
2 local jid = require "util.jid"; |
2 local jid = require "util.jid"; |
3 local json = require "util.json"; |
3 local json = require "util.json"; |
4 local st = require "util.stanza"; |
4 local st = require "util.stanza"; |
5 local xml = require "util.xml"; |
5 local xml = require "util.xml"; |
6 |
6 local map = require "util.datamapper"; |
|
7 |
|
8 local schema do |
|
9 local f = assert(module:load_resource("res/schema-xmpp.json")); |
|
10 schema = json.decode(f:read("*a")) |
|
11 f:close(); |
|
12 -- Copy common properties to all stanza kinds |
|
13 if schema._common then |
|
14 for key, prop in pairs(schema._common) do |
|
15 for _, copyto in pairs(schema.properties) do |
|
16 copyto.properties[key] = prop; |
|
17 end |
|
18 end |
|
19 schema._common = nil; |
|
20 end |
|
21 end |
|
22 |
|
23 -- Some mappings that are still hard to do in a nice way with util.datamapper |
7 local field_mappings; -- in scope for "func" mappings |
24 local field_mappings; -- in scope for "func" mappings |
8 field_mappings = { |
25 field_mappings = { |
9 -- top level stanza attributes |
|
10 -- needed here to mark them as known fields |
|
11 kind = "attr", |
|
12 type = "attr", |
|
13 to = "attr", |
|
14 from = "attr", |
|
15 id = "attr", |
|
16 lang = "attr", |
|
17 |
|
18 -- basic message |
|
19 body = "text_tag", |
|
20 subject = "text_tag", |
|
21 thread = "text_tag", |
|
22 |
|
23 -- basic presence |
|
24 show = "text_tag", |
|
25 status = "text_tag", |
|
26 priority = "text_tag", |
|
27 |
|
28 state = { type = "name", xmlns = "http://jabber.org/protocol/chatstates" }, |
|
29 nick = { type = "text_tag", xmlns = "http://jabber.org/protocol/nick", tagname = "nick" }, |
|
30 delay = { type = "attr", xmlns = "urn:xmpp:delay", tagname = "delay", attr = "stamp" }, |
|
31 replace = { type = "attr", xmlns = "urn:xmpp:message-correct:0", tagname = "replace", attr = "id" }, |
|
32 |
|
33 -- XEP-0045 MUC |
|
34 -- TODO history, password, ??? |
|
35 join = { type = "bool_tag", xmlns = "http://jabber.org/protocol/muc", tagname = "x" }, |
|
36 |
|
37 -- XEP-0071 |
26 -- XEP-0071 |
38 html = { |
27 html = { |
39 type = "func", xmlns = "http://jabber.org/protocol/xhtml-im", tagname = "html", |
28 type = "func", xmlns = "http://jabber.org/protocol/xhtml-im", tagname = "html", |
40 st2json = function (s) --> json string |
29 st2json = function (s) --> json string |
41 return (tostring(s:get_child("body", "http://www.w3.org/1999/xhtml")):gsub(" xmlns='[^']*'", "", 1)); |
30 return (tostring(s:get_child("body", "http://www.w3.org/1999/xhtml")):gsub(" xmlns='[^']*'", "", 1)); |
43 json2st = function (s) --> xml |
32 json2st = function (s) --> xml |
44 if type(s) == "string" then |
33 if type(s) == "string" then |
45 return assert(xml.parse("<x:html xmlns:x='http://jabber.org/protocol/xhtml-im' xmlns='http://www.w3.org/1999/xhtml'>" .. s .. "</x:html>")); |
34 return assert(xml.parse("<x:html xmlns:x='http://jabber.org/protocol/xhtml-im' xmlns='http://www.w3.org/1999/xhtml'>" .. s .. "</x:html>")); |
46 end |
35 end |
47 end; |
36 end; |
48 }; |
|
49 |
|
50 -- XEP-0199: XMPP Ping |
|
51 ping = { type = "bool_tag", xmlns = "urn:xmpp:ping", tagname = "ping" }, |
|
52 |
|
53 -- XEP-0092: Software Version |
|
54 version = { type = "func", xmlns = "jabber:iq:version", tagname = "query", |
|
55 st2json = function (s) |
|
56 return { |
|
57 name = s:get_child_text("name"); |
|
58 version = s:get_child_text("version"); |
|
59 os = s:get_child_text("os"); |
|
60 } |
|
61 end, |
|
62 json2st = function (s) |
|
63 local v = st.stanza("query", { xmlns = "jabber:iq:version" }); |
|
64 if type(s) == "table" then |
|
65 v:text_tag("name", s.name); |
|
66 v:text_tag("version", s.version); |
|
67 if s.os then |
|
68 v:text_tag("os", s.os); |
|
69 end |
|
70 end |
|
71 return v; |
|
72 end |
|
73 }; |
37 }; |
74 |
38 |
75 -- XEP-0030 |
39 -- XEP-0030 |
76 disco = { |
40 disco = { |
77 type = "func", xmlns = "http://jabber.org/protocol/disco#info", tagname = "query", |
41 type = "func", xmlns = "http://jabber.org/protocol/disco#info", tagname = "query", |
226 -- else .. missing required attribute |
190 -- else .. missing required attribute |
227 end; |
191 end; |
228 }; |
192 }; |
229 |
193 |
230 -- XEP-0066: Out of Band Data |
194 -- XEP-0066: Out of Band Data |
|
195 -- TODO Replace by oob.url in datamapper schema |
231 oob_url = { type = "func", xmlns = "jabber:x:oob", tagname = "x", |
196 oob_url = { type = "func", xmlns = "jabber:x:oob", tagname = "x", |
232 -- XXX namespace depends on whether it's in an iq or message stanza |
197 -- XXX namespace depends on whether it's in an iq or message stanza |
233 st2json = function (s) |
198 st2json = function (s) |
234 return s:get_child_text("url"); |
199 return s:get_child_text("url"); |
235 end; |
200 end; |
414 |
379 |
415 }; |
380 }; |
416 |
381 |
417 local byxmlname = {}; |
382 local byxmlname = {}; |
418 for k, spec in pairs(field_mappings) do |
383 for k, spec in pairs(field_mappings) do |
|
384 for _, replace in pairs(schema.properties) do |
|
385 replace.properties[k] = nil |
|
386 end |
|
387 |
419 if type(spec) == "table" then |
388 if type(spec) == "table" then |
420 spec.key = k; |
389 spec.key = k; |
421 if spec.xmlns and spec.tagname then |
390 if spec.xmlns and spec.tagname then |
422 byxmlname["{" .. spec.xmlns .. "}" .. spec.tagname] = spec; |
391 byxmlname["{" .. spec.xmlns .. "}" .. spec.tagname] = spec; |
423 elseif spec.type == "name" then |
392 elseif spec.type == "name" then |
459 subscribe = "presence", unsubscribe = "presence", |
428 subscribe = "presence", unsubscribe = "presence", |
460 subscribed = "presence", unsubscribed = "presence", |
429 subscribed = "presence", unsubscribed = "presence", |
461 } |
430 } |
462 |
431 |
463 local function st2json(s) |
432 local function st2json(s) |
464 local t = { |
433 local t = map.parse(schema.properties[s.name], s); |
465 kind = s.name, |
434 |
466 type = s.attr.type, |
|
467 to = s.attr.to, |
|
468 from = s.attr.from, |
|
469 id = s.attr.id, |
|
470 lang = s.attr["xml:lang"], |
|
471 }; |
|
472 if s.name == "presence" and not s.attr.type then |
435 if s.name == "presence" and not s.attr.type then |
473 t.type = "available"; |
436 t.type = "available"; |
474 end |
437 end |
475 |
438 |
476 if t.to then |
439 if t.to then |
499 local mapping = byxmlname[prefix .. tag.name]; |
462 local mapping = byxmlname[prefix .. tag.name]; |
500 if not mapping then |
463 if not mapping then |
501 mapping = byxmlname[prefix]; |
464 mapping = byxmlname[prefix]; |
502 end |
465 end |
503 |
466 |
504 if not mapping then -- luacheck: ignore 542 |
467 if mapping and mapping.type == "func" and mapping.st2json then |
505 -- pass |
|
506 elseif mapping.type == "text_tag" then |
|
507 t[mapping.key] = tag:get_text(); |
|
508 elseif mapping.type == "name" then |
|
509 t[mapping.key] = tag.name; |
|
510 elseif mapping.type == "attr" then |
|
511 t[mapping.key] = tag.attr[mapping.attr]; |
|
512 elseif mapping.type == "bool_tag" then |
|
513 t[mapping.key] = true; |
|
514 elseif mapping.type == "func" and mapping.st2json then |
|
515 t[mapping.key] = mapping.st2json(tag); |
468 t[mapping.key] = mapping.st2json(tag); |
516 end |
469 end |
517 end |
470 end |
518 |
471 |
519 return t; |
472 return t; |
545 break |
498 break |
546 end |
499 end |
547 end |
500 end |
548 end |
501 end |
549 |
502 |
550 if t_type == "available" then |
503 if kind == "presence" and t_type == "available" then |
551 t_type = nil; |
504 t_type = nil; |
552 end |
505 elseif kind == "iq" and not t_type then |
553 |
506 t_type = "get"; |
554 local s = st.stanza(kind or "message", { |
507 end |
555 type = t_type; |
508 |
556 to = str(t.to) and jid.prep(t.to); |
509 local s = map.unparse(schema.properties[kind or "message"], t); |
557 from = str(t.to) and jid.prep(t.from); |
510 |
558 id = str(t.id), |
511 s.attr.type = t_type; |
559 ["xml:lang"] = str(t.lang), |
512 s.attr.to = str(t.to) and jid.prep(t.to); |
560 }); |
513 s.attr.from = str(t.to) and jid.prep(t.from); |
561 |
|
562 if t.to and not s.attr.to then |
|
563 return nil, "invalid-jid-to"; |
|
564 end |
|
565 if t.from and not s.attr.from then |
|
566 return nil, "invalid-jid-from"; |
|
567 end |
|
568 if kind == "iq" and not s.attr.type then |
|
569 s.attr.type = "get"; |
|
570 end |
|
571 |
514 |
572 if type(t.error) == "table" then |
515 if type(t.error) == "table" then |
573 return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text), str(t.error.by)); |
516 return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text), str(t.error.by)); |
574 elseif t.type == "error" then |
517 elseif t.type == "error" then |
575 s:text_tag("error", t.body, { code = t.error_code and tostring(t.error_code) }); |
518 s:text_tag("error", t.body, { code = t.error_code and tostring(t.error_code) }); |
576 return s; |
519 return s; |
577 end |
520 end |
578 |
521 |
579 for k, v in pairs(t) do |
522 for k, v in pairs(t) do |
580 local mapping = field_mappings[k]; |
523 local mapping = field_mappings[k]; |
581 if mapping then |
524 if mapping and mapping.type == "func" and mapping.json2st then |
582 if mapping == "text_tag" then |
|
583 s:text_tag(k, v); |
|
584 elseif mapping == "attr" then -- luacheck: ignore 542 |
|
585 -- handled already |
|
586 elseif mapping.type == "text_tag" then |
|
587 s:text_tag(mapping.tagname or k, v, mapping.xmlns and { xmlns = mapping.xmlns }); |
|
588 elseif mapping.type == "name" then |
|
589 s:tag(v, { xmlns = mapping.xmlns }):up(); |
|
590 elseif mapping.type == "attr" then |
|
591 s:tag(mapping.tagname or k, { xmlns = mapping.xmlns, [mapping.attr or k] = v }):up(); |
|
592 elseif mapping.type == "bool_tag" then |
|
593 s:tag(mapping.tagname or k, { xmlns = mapping.xmlns }):up(); |
|
594 elseif mapping.type == "func" and mapping.json2st then |
|
595 s:add_child(mapping.json2st(v)):up(); |
525 s:add_child(mapping.json2st(v)):up(); |
596 end |
526 end |
597 else |
|
598 return nil, "unknown-field"; |
|
599 end |
|
600 end |
527 end |
601 |
528 |
602 s:reset(); |
529 s:reset(); |
603 |
530 |
604 return s; |
531 return s; |