Software /
code /
prosody
Comparison
util/stanza.lua @ 11120:b2331f3dfeea
Merge 0.11->trunk
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Wed, 30 Sep 2020 09:50:33 +0100 |
parent | 11088:1f84d0e4d0c4 |
child | 11206:f051394762ff |
comparison
equal
deleted
inserted
replaced
11119:68df52bf08d5 | 11120:b2331f3dfeea |
---|---|
96 function stanza_mt:query(xmlns) | 96 function stanza_mt:query(xmlns) |
97 return self:tag("query", { xmlns = xmlns }); | 97 return self:tag("query", { xmlns = xmlns }); |
98 end | 98 end |
99 | 99 |
100 function stanza_mt:body(text, attr) | 100 function stanza_mt:body(text, attr) |
101 return self:tag("body", attr):text(text); | 101 return self:text_tag("body", text, attr); |
102 end | 102 end |
103 | 103 |
104 function stanza_mt:text_tag(name, text, attr, namespaces) | 104 function stanza_mt:text_tag(name, text, attr, namespaces) |
105 return self:tag(name, attr, namespaces):text(text):up(); | 105 return self:tag(name, attr, namespaces):text(text):up(); |
106 end | 106 end |
268 end | 268 end |
269 self = self:get_child(name, xmlns); | 269 self = self:get_child(name, xmlns); |
270 until not self | 270 until not self |
271 end | 271 end |
272 | 272 |
273 local function _clone(stanza, only_top) | |
274 local attr, tags = {}, {}; | |
275 for k,v in pairs(stanza.attr) do attr[k] = v; end | |
276 local old_namespaces, namespaces = stanza.namespaces; | |
277 if old_namespaces then | |
278 namespaces = {}; | |
279 for k,v in pairs(old_namespaces) do namespaces[k] = v; end | |
280 end | |
281 local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags }; | |
282 if not only_top then | |
283 for i=1,#stanza do | |
284 local child = stanza[i]; | |
285 if child.name then | |
286 child = _clone(child); | |
287 t_insert(tags, child); | |
288 end | |
289 t_insert(new, child); | |
290 end | |
291 end | |
292 return setmetatable(new, stanza_mt); | |
293 end | |
294 | |
295 local function clone(stanza, only_top) | |
296 if not is_stanza(stanza) then | |
297 error("bad argument to clone: expected stanza, got "..type(stanza)); | |
298 end | |
299 return _clone(stanza, only_top); | |
300 end | |
273 | 301 |
274 local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; | 302 local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" }; |
275 local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end | 303 local function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end |
276 | 304 |
277 local function _dostring(t, buf, self, _xml_escape, parentns) | 305 local function _dostring(t, buf, self, _xml_escape, parentns) |
308 _dostring(t, buf, _dostring, xml_escape, nil); | 336 _dostring(t, buf, _dostring, xml_escape, nil); |
309 return t_concat(buf); | 337 return t_concat(buf); |
310 end | 338 end |
311 | 339 |
312 function stanza_mt.top_tag(t) | 340 function stanza_mt.top_tag(t) |
313 local attr_string = ""; | 341 local top_tag_clone = clone(t, true); |
314 if t.attr then | 342 return tostring(top_tag_clone):sub(1,-3)..">"; |
315 for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end | |
316 end | |
317 return s_format("<%s%s>", t.name, attr_string); | |
318 end | 343 end |
319 | 344 |
320 function stanza_mt.get_text(t) | 345 function stanza_mt.get_text(t) |
321 if #t.tags == 0 then | 346 if #t.tags == 0 then |
322 return t_concat(t); | 347 return t_concat(t); |
323 end | 348 end |
324 end | 349 end |
325 | 350 |
326 function stanza_mt.get_error(stanza) | 351 function stanza_mt.get_error(stanza) |
327 local error_type, condition, text; | 352 local error_type, condition, text, extra_tag; |
328 | 353 |
329 local error_tag = stanza:get_child("error"); | 354 local error_tag = stanza:get_child("error"); |
330 if not error_tag then | 355 if not error_tag then |
331 return nil, nil, nil; | 356 return nil, nil, nil, nil; |
332 end | 357 end |
333 error_type = error_tag.attr.type; | 358 error_type = error_tag.attr.type; |
334 | 359 |
335 for _, child in ipairs(error_tag.tags) do | 360 for _, child in ipairs(error_tag.tags) do |
336 if child.attr.xmlns == xmlns_stanzas then | 361 if child.attr.xmlns == xmlns_stanzas then |
337 if not text and child.name == "text" then | 362 if not text and child.name == "text" then |
338 text = child:get_text(); | 363 text = child:get_text(); |
339 elseif not condition then | 364 elseif not condition then |
340 condition = child.name; | 365 condition = child.name; |
341 end | 366 end |
342 if condition and text then | 367 else |
343 break; | 368 extra_tag = child; |
344 end | 369 end |
345 end | 370 if condition and text and extra_tag then |
346 end | 371 break; |
347 return error_type, condition or "undefined-condition", text; | 372 end |
373 end | |
374 return error_type, condition or "undefined-condition", text, extra_tag; | |
348 end | 375 end |
349 | 376 |
350 local function preserialize(stanza) | 377 local function preserialize(stanza) |
351 local s = { name = stanza.name, attr = stanza.attr }; | 378 local s = { name = stanza.name, attr = stanza.attr }; |
352 for _, child in ipairs(stanza) do | 379 for _, child in ipairs(stanza) do |
386 end | 413 end |
387 return stanza; | 414 return stanza; |
388 end | 415 end |
389 end | 416 end |
390 | 417 |
391 local function _clone(stanza) | |
392 local attr, tags = {}, {}; | |
393 for k,v in pairs(stanza.attr) do attr[k] = v; end | |
394 local old_namespaces, namespaces = stanza.namespaces; | |
395 if old_namespaces then | |
396 namespaces = {}; | |
397 for k,v in pairs(old_namespaces) do namespaces[k] = v; end | |
398 end | |
399 local new = { name = stanza.name, attr = attr, namespaces = namespaces, tags = tags }; | |
400 for i=1,#stanza do | |
401 local child = stanza[i]; | |
402 if child.name then | |
403 child = _clone(child); | |
404 t_insert(tags, child); | |
405 end | |
406 t_insert(new, child); | |
407 end | |
408 return setmetatable(new, stanza_mt); | |
409 end | |
410 | |
411 local function clone(stanza) | |
412 if not is_stanza(stanza) then | |
413 error("bad argument to clone: expected stanza, got "..type(stanza)); | |
414 end | |
415 return _clone(stanza); | |
416 end | |
417 | |
418 local function message(attr, body) | 418 local function message(attr, body) |
419 if not body then | 419 if not body then |
420 return new_stanza("message", attr); | 420 return new_stanza("message", attr); |
421 else | 421 else |
422 return new_stanza("message", attr):tag("body"):text(body):up(); | 422 return new_stanza("message", attr):text_tag("body", body); |
423 end | 423 end |
424 end | 424 end |
425 local function iq(attr) | 425 local function iq(attr) |
426 if not (attr and attr.id) then | 426 if not attr then |
427 error("iq stanzas require id and type attributes"); | |
428 end | |
429 if not attr.id then | |
427 error("iq stanzas require an id attribute"); | 430 error("iq stanzas require an id attribute"); |
428 end | 431 end |
432 if not attr.type then | |
433 error("iq stanzas require a type attribute"); | |
434 end | |
429 return new_stanza("iq", attr); | 435 return new_stanza("iq", attr); |
430 end | 436 end |
431 | 437 |
432 local function reply(orig) | 438 local function reply(orig) |
439 if not is_stanza(orig) then | |
440 error("bad argument to reply: expected stanza, got "..type(orig)); | |
441 end | |
433 return new_stanza(orig.name, | 442 return new_stanza(orig.name, |
434 orig.attr and { | 443 { |
435 to = orig.attr.from, | 444 to = orig.attr.from, |
436 from = orig.attr.to, | 445 from = orig.attr.to, |
437 id = orig.attr.id, | 446 id = orig.attr.id, |
438 type = ((orig.name == "iq" and "result") or orig.attr.type) | 447 type = ((orig.name == "iq" and "result") or orig.attr.type) |
439 }); | 448 }); |
440 end | 449 end |
441 | 450 |
442 local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; | 451 local xmpp_stanzas_attr = { xmlns = xmlns_stanzas }; |
443 local function error_reply(orig, error_type, condition, error_message) | 452 local function error_reply(orig, error_type, condition, error_message, error_by) |
453 if not is_stanza(orig) then | |
454 error("bad argument to error_reply: expected stanza, got "..type(orig)); | |
455 elseif orig.attr.type == "error" then | |
456 error("bad argument to error_reply: got stanza of type error which must not be replied to"); | |
457 end | |
444 local t = reply(orig); | 458 local t = reply(orig); |
445 t.attr.type = "error"; | 459 t.attr.type = "error"; |
446 t:tag("error", {type = error_type}) --COMPAT: Some day xmlns:stanzas goes here | 460 local extra; |
447 :tag(condition, xmpp_stanzas_attr):up(); | 461 if type(error_type) == "table" then -- an util.error or similar object |
448 if error_message then t:tag("text", xmpp_stanzas_attr):text(error_message):up(); end | 462 if type(error_type.extra) == "table" then |
463 extra = error_type.extra; | |
464 end | |
465 if type(error_type.context) == "table" and type(error_type.context.by) == "string" then error_by = error_type.context.by; end | |
466 error_type, condition, error_message = error_type.type, error_type.condition, error_type.text; | |
467 end | |
468 if t.attr.from == error_by then | |
469 error_by = nil; | |
470 end | |
471 t:tag("error", {type = error_type, by = error_by}) --COMPAT: Some day xmlns:stanzas goes here | |
472 :tag(condition, xmpp_stanzas_attr); | |
473 if extra and condition == "gone" and type(extra.uri) == "string" then | |
474 t:text(extra.uri); | |
475 end | |
476 t:up(); | |
477 if error_message then t:text_tag("text", error_message, xmpp_stanzas_attr); end | |
478 if extra and is_stanza(extra.tag) then | |
479 t:add_child(extra.tag); | |
480 elseif extra and extra.namespace and extra.condition then | |
481 t:tag(extra.condition, { xmlns = extra.namespace }):up(); | |
482 end | |
449 return t; -- stanza ready for adding app-specific errors | 483 return t; -- stanza ready for adding app-specific errors |
450 end | 484 end |
451 | 485 |
452 local function presence(attr) | 486 local function presence(attr) |
453 return new_stanza("presence", attr); | 487 return new_stanza("presence", attr); |
489 end | 523 end |
490 else | 524 else |
491 -- Sorry, fresh out of colours for you guys ;) | 525 -- Sorry, fresh out of colours for you guys ;) |
492 stanza_mt.pretty_print = stanza_mt.__tostring; | 526 stanza_mt.pretty_print = stanza_mt.__tostring; |
493 stanza_mt.pretty_top_tag = stanza_mt.top_tag; | 527 stanza_mt.pretty_top_tag = stanza_mt.top_tag; |
528 end | |
529 | |
530 function stanza_mt.indent(t, level, indent) | |
531 if #t == 0 or (#t == 1 and type(t[1]) == "string") then | |
532 -- Empty nodes wouldn't have any indentation | |
533 -- Text-only nodes are preserved as to not alter the text content | |
534 -- Optimization: Skip clone of these since we don't alter them | |
535 return t; | |
536 end | |
537 | |
538 indent = indent or "\t"; | |
539 level = level or 1; | |
540 local tag = clone(t, true); | |
541 | |
542 for child in t:children() do | |
543 if type(child) == "string" then | |
544 -- Already indented text would look weird but let's ignore that for now. | |
545 if child:find("%S") then | |
546 tag:text("\n" .. indent:rep(level)); | |
547 tag:text(child); | |
548 end | |
549 elseif is_stanza(child) then | |
550 tag:text("\n" .. indent:rep(level)); | |
551 tag:add_direct_child(child:indent(level+1, indent)); | |
552 end | |
553 end | |
554 -- before the closing tag | |
555 tag:text("\n" .. indent:rep((level-1))); | |
556 | |
557 return tag; | |
494 end | 558 end |
495 | 559 |
496 return { | 560 return { |
497 stanza_mt = stanza_mt; | 561 stanza_mt = stanza_mt; |
498 stanza = new_stanza; | 562 stanza = new_stanza; |