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 = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" }; 302 local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
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;