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; |