~singpolyma/biboumi

5a5bb7f63222189ea0dcfbd387d5e34458ccefe5 — louiz’ 5 years ago f512c9c
Introduce a XmlSubNode class that automatically adds itself into its parent
M louloulibs/xmpp/adhoc_command.cpp => louloulibs/xmpp/adhoc_command.cpp +11 -21
@@ 18,30 18,24 @@ bool AdhocCommand::is_admin_only() const

void PingStep1(XmppComponent&, AdhocSession&, XmlNode& command_node)
{
  XmlNode note("note");
  XmlSubNode note(command_node, "note");
  note["type"] = "info";
  note.set_inner("Pong");
  command_node.add_child(std::move(note));
}

void HelloStep1(XmppComponent&, AdhocSession&, XmlNode& command_node)
{
  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Configure your name.");
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Please provide your name.");
  x.add_child(std::move(instructions));
  XmlNode name_field("field");
  XmlSubNode name_field(x, "field");
  name_field["var"] = "name";
  name_field["type"] = "text-single";
  name_field["label"] = "Your name";
  XmlNode required("required");
  name_field.add_child(std::move(required));
  x.add_child(std::move(name_field));
  command_node.add_child(std::move(x));
  XmlSubNode required(name_field, "required");
}

void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)


@@ 60,21 54,18 @@ void HelloStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)
        {
          if (const XmlNode* value = name_field->get_child("value", "jabber:x:data"))
            {
              XmlNode note("note");
              command_node.delete_all_children();
              XmlSubNode note(command_node, "note");
              note["type"] = "info";
              note.set_inner("Hello "s + value->get_inner() + "!"s);
              command_node.delete_all_children();
              command_node.add_child(std::move(note));
              return;
            }
        }
    }
  command_node.delete_all_children();
  XmlNode error(ADHOC_NS":error");
  XmlSubNode error(command_node, ADHOC_NS":error");
  error["type"] = "modify";
  XmlNode condition(STANZA_NS":bad-request");
  error.add_child(std::move(condition));
  command_node.add_child(std::move(error));
  XmlSubNode condition(error, STANZA_NS":bad-request");
  session.terminate();
}



@@ 82,8 73,7 @@ void Reload(XmppComponent&, AdhocSession&, XmlNode& command_node)
{
  ::reload_process();
  command_node.delete_all_children();
  XmlNode note("note");
  XmlSubNode note(command_node, "note");
  note["type"] = "info";
  note.set_inner("Configuration reloaded.");
  command_node.add_child(std::move(note));
}

M louloulibs/xmpp/adhoc_commands_handler.cpp => louloulibs/xmpp/adhoc_commands_handler.cpp +10 -20
@@ 36,20 36,16 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
  auto command_it = this->commands.find(node);
  if (command_it == this->commands.end())
    {
      XmlNode error(ADHOC_NS":error");
      XmlSubNode error(command_node, ADHOC_NS":error");
      error["type"] = "cancel";
      XmlNode condition(STANZA_NS":item-not-found");
      error.add_child(std::move(condition));
      command_node.add_child(std::move(error));
      XmlSubNode condition(error, STANZA_NS":item-not-found");
    }
  else if (command_it->second.is_admin_only() &&
           Config::get("admin", "") != jid.local + "@" + jid.domain)
    {
      XmlNode error(ADHOC_NS":error");
      XmlSubNode error(command_node, ADHOC_NS":error");
      error["type"] = "cancel";
      XmlNode condition(STANZA_NS":forbidden");
      error.add_child(std::move(condition));
      command_node.add_child(std::move(error));
      XmlSubNode condition(error, STANZA_NS":forbidden");
    }
  else
    {


@@ 68,11 64,9 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
      auto session_it = this->sessions.find(std::make_pair(sessionid, executor_jid));
      if (session_it == this->sessions.end())
        {
          XmlNode error(ADHOC_NS":error");
          XmlSubNode error(command_node, ADHOC_NS":error");
          error["type"] = "modify";
          XmlNode condition(STANZA_NS":bad-request");
          error.add_child(std::move(condition));
          command_node.add_child(std::move(error));
          XmlSubNode condition(error, STANZA_NS":bad-request");
        }
      else if (action == "execute" || action == "next" || action == "complete")
        {


@@ 90,10 84,8 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
          else
            {
              command_node["status"] = "executing";
              XmlNode actions("actions");
              XmlNode next("next");
              actions.add_child(std::move(next));
              command_node.add_child(std::move(actions));
              XmlSubNode actions(command_node, "actions");
              XmlSubNode next(actions, "next");
            }
        }
      else if (action == "cancel")


@@ 104,11 96,9 @@ XmlNode AdhocCommandsHandler::handle_request(const std::string& executor_jid, co
        }
      else                      // unsupported action
        {
          XmlNode error(ADHOC_NS":error");
          XmlSubNode error(command_node, ADHOC_NS":error");
          error["type"] = "modify";
          XmlNode condition(STANZA_NS":bad-request");
          error.add_child(std::move(condition));
          command_node.add_child(std::move(error));
          XmlSubNode condition(error, STANZA_NS":bad-request");
        }
    }
  return command_node;

M louloulibs/xmpp/xmpp_component.cpp => louloulibs/xmpp/xmpp_component.cpp +267 -272
@@ 172,12 172,13 @@ void XmppComponent::on_stanza(const Stanza& stanza)

void XmppComponent::send_stream_error(const std::string& name, const std::string& explanation)
{
  XmlNode node("stream:error");
  XmlNode error(name);
  error["xmlns"] = STREAM_NS;
  if (!explanation.empty())
    error.set_inner(explanation);
  node.add_child(std::move(error));
  Stanza node("stream:error");
  {
    XmlSubNode error(node, name);
    error["xmlns"] = STREAM_NS;
    if (!explanation.empty())
      error.set_inner(explanation);
  }
  this->send_stanza(node);
}



@@ 187,31 188,34 @@ void XmppComponent::send_stanza_error(const std::string& kind, const std::string
                                      const bool fulljid)
{
  Stanza node(kind);
  if (!to.empty())
    node["to"] = to;
  if (!from.empty())
  {
    if (!to.empty())
      node["to"] = to;
    if (!from.empty())
      {
        if (fulljid)
          node["from"] = from;
        else
          node["from"] = from + "@" + this->served_hostname;
      }
    if (!id.empty())
      node["id"] = id;
    node["type"] = "error";
    {
      if (fulljid)
        node["from"] = from;
      else
        node["from"] = from + "@" + this->served_hostname;
      XmlSubNode error(node, "error");
      error["type"] = error_type;
      {
        XmlSubNode inner_error(error, defined_condition);
        inner_error["xmlns"] = STANZA_NS;
      }
      if (!text.empty())
        {
          XmlSubNode text_node(error, "text");
          text_node["xmlns"] = STANZA_NS;
          text_node.set_inner(text);
        }
    }
  if (!id.empty())
    node["id"] = id;
  node["type"] = "error";
  XmlNode error("error");
  error["type"] = error_type;
  XmlNode inner_error(defined_condition);
  inner_error["xmlns"] = STANZA_NS;
  error.add_child(std::move(inner_error));
  if (!text.empty())
    {
      XmlNode text_node("text");
      text_node["xmlns"] = STANZA_NS;
      text_node.set_inner(text);
      error.add_child(std::move(text_node));
    }
  node.add_child(std::move(error));
  }
  this->send_stanza(node);
}



@@ 264,38 268,33 @@ void* XmppComponent::get_receive_buffer(const size_t size) const
void XmppComponent::send_message(const std::string& from, Xmpp::body&& body, const std::string& to,
                                 const std::string& type, const bool fulljid, const bool nocopy)
{
  XmlNode node("message");
  node["to"] = to;
  if (fulljid)
    node["from"] = from;
  else
    node["from"] = from + "@" + this->served_hostname;
  if (!type.empty())
    node["type"] = type;
  XmlNode body_node("body");
  body_node.set_inner(std::get<0>(body));
  node.add_child(std::move(body_node));
  if (std::get<1>(body))
    {
      XmlNode html("html");
      html["xmlns"] = XHTMLIM_NS;
      // Pass the ownership of the pointer to this xmlnode
      html.add_child(std::move(std::get<1>(body)));
      node.add_child(std::move(html));
    }

  if (nocopy)
    {
      XmlNode private_node("private");
      private_node["xmlns"] = "urn:xmpp:carbons:2";
      node.add_child(std::move(private_node));

      XmlNode nocopy("no-copy");
      nocopy["xmlns"] = "urn:xmpp:hints";
      node.add_child(std::move(nocopy));
    }

  this->send_stanza(node);
  Stanza message("message");
  {
    message["to"] = to;
    if (fulljid)
      message["from"] = from;
    else
      message["from"] = from + "@" + this->served_hostname;
    if (!type.empty())
      message["type"] = type;
    XmlSubNode body_node(message, "body");
    body_node.set_inner(std::get<0>(body));
    if (std::get<1>(body))
      {
        XmlSubNode html(message, "html");
        html["xmlns"] = XHTMLIM_NS;
        // Pass the ownership of the pointer to this xmlnode
        html.add_child(std::move(std::get<1>(body)));
      }
    if (nocopy)
      {
        XmlSubNode private_node(message, "private");
        private_node["xmlns"] = "urn:xmpp:carbons:2";
        XmlSubNode nocopy(message, "no-copy");
        nocopy["xmlns"] = "urn:xmpp:hints";
      }
  }
  this->send_stanza(message);
}

void XmppComponent::send_user_join(const std::string& from,


@@ 306,34 305,33 @@ void XmppComponent::send_user_join(const std::string& from,
                                   const std::string& to,
                                   const bool self)
{
  XmlNode node("presence");
  node["to"] = to;
  node["from"] = from + "@" + this->served_hostname + "/" + nick;

  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;

  XmlNode item("item");
  if (!affiliation.empty())
    item["affiliation"] = affiliation;
  if (!role.empty())
    item["role"] = role;
  if (!realjid.empty())
    {
      const std::string preped_jid = jidprep(realjid);
      if (!preped_jid.empty())
        item["jid"] = preped_jid;
    }
  x.add_child(std::move(item));

  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      x.add_child(std::move(status));
    }
  node.add_child(std::move(x));
  this->send_stanza(node);
  Stanza presence("presence");
  {
    presence["to"] = to;
    presence["from"] = from + "@" + this->served_hostname + "/" + nick;

    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;

    XmlSubNode item(x, "item");
    if (!affiliation.empty())
      item["affiliation"] = affiliation;
    if (!role.empty())
      item["role"] = role;
    if (!realjid.empty())
      {
        const std::string preped_jid = jidprep(realjid);
        if (!preped_jid.empty())
          item["jid"] = preped_jid;
      }

    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
  this->send_stanza(presence);
}

void XmppComponent::send_invalid_room_error(const std::string& muc_name,


@@ 341,44 339,43 @@ void XmppComponent::send_invalid_room_error(const std::string& muc_name,
                                            const std::string& to)
{
  Stanza presence("presence");
  if (!muc_name.empty())
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  else
    presence["from"] = this->served_hostname;
  presence["to"] = to;
  presence["type"] = "error";
  XmlNode x("x");
  x["xmlns"] = MUC_NS;
  presence.add_child(std::move(x));
  XmlNode error("error");
  error["by"] = muc_name + "@" + this->served_hostname;
  error["type"] = "cancel";
  XmlNode item_not_found("item-not-found");
  item_not_found["xmlns"] = STANZA_NS;
  error.add_child(std::move(item_not_found));
  XmlNode text("text");
  text["xmlns"] = STANZA_NS;
  text["xml:lang"] = "en";
  text.set_inner(muc_name +
                 " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" +
                 this->served_hostname);
  error.add_child(std::move(text));
  presence.add_child(std::move(error));
  {
    if (!muc_name.empty ())
      presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
    else
      presence["from"] = this->served_hostname;
    presence["to"] = to;
    presence["type"] = "error";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_NS;
    XmlSubNode error(presence, "error");
    error["by"] = muc_name + "@" + this->served_hostname;
    error["type"] = "cancel";
    XmlSubNode item_not_found(error, "item-not-found");
    item_not_found["xmlns"] = STANZA_NS;
    XmlSubNode text(error, "text");
    text["xmlns"] = STANZA_NS;
    text["xml:lang"] = "en";
    text.set_inner(muc_name +
                   " is not a valid IRC channel name. A correct room jid is of the form: #<chan>%<server>@" +
                   this->served_hostname);
  }
  this->send_stanza(presence);
}

void XmppComponent::send_topic(const std::string& from, Xmpp::body&& topic, const std::string& to, const std::string& who)
{
  XmlNode message("message");
  message["to"] = to;
  if (who.empty())
    message["from"] = from + "@" + this->served_hostname;
  else
    message["from"] = from + "@" + this->served_hostname + "/" + who;
  message["type"] = "groupchat";
  XmlNode subject("subject");
  subject.set_inner(std::get<0>(topic));
  message.add_child(std::move(subject));
  Stanza message("message");
  {
    message["to"] = to;
    if (who.empty())
      message["from"] = from + "@" + this->served_hostname;
    else
      message["from"] = from + "@" + this->served_hostname + "/" + who;
    message["type"] = "groupchat";
    XmlSubNode subject(message, "subject");
    subject.set_inner(std::get<0>(topic));
  }
  this->send_stanza(message);
}



@@ 391,16 388,18 @@ void XmppComponent::send_muc_message(const std::string& muc_name, const std::str
  else // Message from the room itself
    message["from"] = muc_name + "@" + this->served_hostname;
  message["type"] = "groupchat";
  XmlNode body("body");
  body.set_inner(std::get<0>(xmpp_body));
  message.add_child(std::move(body));

  {
    XmlSubNode body(message, "body");
    body.set_inner(std::get<0>(xmpp_body));
  }

  if (std::get<1>(xmpp_body))
    {
      XmlNode html("html");
      XmlSubNode html(message, "html");
      html["xmlns"] = XHTMLIM_NS;
      // Pass the ownership of the pointer to this xmlnode
      html.add_child(std::move(std::get<1>(xmpp_body)));
      message.add_child(std::move(html));
    }
  this->send_stanza(message);
}


@@ 415,41 414,41 @@ void XmppComponent::send_history_message(const std::string& muc_name, const std:
    message["from"] = muc_name + "@" + this->served_hostname;
  message["type"] = "groupchat";

  XmlNode body("body");
  body.set_inner(body_txt);
  message.add_child(std::move(body));

  XmlNode delay("delay");
  delay["xmlns"] = DELAY_NS;
  delay["from"] = muc_name + "@" + this->served_hostname;
  delay["stamp"] = utils::to_string(timestamp);
  {
    XmlSubNode body(message, "body");
    body.set_inner(body_txt);
  }
  {
    XmlSubNode delay(message, "delay");
    delay["xmlns"] = DELAY_NS;
    delay["from"] = muc_name + "@" + this->served_hostname;
    delay["stamp"] = utils::to_string(timestamp);
  }

  message.add_child(std::move(delay));
  this->send_stanza(message);
}

void XmppComponent::send_muc_leave(const std::string& muc_name, std::string&& nick, Xmpp::body&& message, const std::string& jid_to, const bool self)
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
  presence["type"] = "unavailable";
  const std::string message_str = std::get<0>(message);
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      x.add_child(std::move(status));
    }
  presence.add_child(std::move(x));
  if (!message_str.empty())
    {
      XmlNode status("status");
      status.set_inner(message_str);
      presence.add_child(std::move(status));
    }
  {
    presence["to"] = jid_to;
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nick;
    presence["type"] = "unavailable";
    const std::string message_str = std::get<0>(message);
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
    if (!message_str.empty())
      {
        XmlSubNode status(presence, "status");
        status.set_inner(message_str);
      }
  }
  this->send_stanza(presence);
}



@@ 462,24 461,22 @@ void XmppComponent::send_nick_change(const std::string& muc_name,
                                     const bool self)
{
  Stanza presence("presence");
  presence["to"] = jid_to;
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
  presence["type"] = "unavailable";
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["nick"] = new_nick;
  x.add_child(std::move(item));
  XmlNode status("status");
  status["code"] = "303";
  x.add_child(std::move(status));
  if (self)
    {
      XmlNode status2("status");
      status2["code"] = "110";
      x.add_child(std::move(status2));
    }
  presence.add_child(std::move(x));
  {
    presence["to"] = jid_to;
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + old_nick;
    presence["type"] = "unavailable";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["nick"] = new_nick;
    XmlSubNode status(x, "status");
    status["code"] = "303";
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
  this->send_stanza(presence);

  this->send_user_join(muc_name, new_nick, "", affiliation, role, jid_to, self);


@@ 489,32 486,28 @@ void XmppComponent::kick_user(const std::string& muc_name, const std::string& ta
                              const std::string& author, const std::string& jid_to, const bool self)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
  presence["to"] = jid_to;
  presence["type"] = "unavailable";
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["affiliation"] = "none";
  item["role"] = "none";
  XmlNode actor("actor");
  actor["nick"] = author;
  actor["jid"] = author; // backward compatibility with old clients
  item.add_child(std::move(actor));
  XmlNode reason("reason");
  reason.set_inner(txt);
  item.add_child(std::move(reason));
  x.add_child(std::move(item));
  XmlNode status("status");
  status["code"] = "307";
  x.add_child(std::move(status));
  if (self)
    {
      XmlNode status("status");
      status["code"] = "110";
      x.add_child(std::move(status));
    }
  presence.add_child(std::move(x));
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
    presence["to"] = jid_to;
    presence["type"] = "unavailable";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["affiliation"] = "none";
    item["role"] = "none";
    XmlSubNode actor(item, "actor");
    actor["nick"] = author;
    actor["jid"] = author; // backward compatibility with old clients
    XmlSubNode reason(item, "reason");
    reason.set_inner(txt);
    XmlSubNode status(x, "status");
    status["code"] = "307";
    if (self)
      {
        XmlSubNode status(x, "status");
        status["code"] = "110";
      }
  }
  this->send_stanza(presence);
}



@@ 527,28 520,26 @@ void XmppComponent::send_presence_error(const std::string& muc_name,
                                        const std::string& text)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
  presence["to"] = jid_to;
  presence["type"] = "error";
  XmlNode x("x");
  x["xmlns"] = MUC_NS;
  presence.add_child(std::move(x));
  XmlNode error("error");
  error["by"] = muc_name + "@" + this->served_hostname;
  error["type"] = type;
  if (!text.empty())
    {
      XmlNode text_node("text");
      text_node["xmlns"] = STANZA_NS;
      text_node.set_inner(text);
      error.add_child(std::move(text_node));
    }
  if (!error_code.empty())
    error["code"] = error_code;
  XmlNode subnode(condition);
  subnode["xmlns"] = STANZA_NS;
  error.add_child(std::move(subnode));
  presence.add_child(std::move(error));
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + nickname;
    presence["to"] = jid_to;
    presence["type"] = "error";
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_NS;
    XmlSubNode error(presence, "error");
    error["by"] = muc_name + "@" + this->served_hostname;
    error["type"] = type;
    if (!text.empty())
      {
        XmlSubNode text_node(error, "text");
        text_node["xmlns"] = STANZA_NS;
        text_node.set_inner(text);
      }
    if (!error_code.empty())
      error["code"] = error_code;
    XmlSubNode subnode(error, condition);
    subnode["xmlns"] = STANZA_NS;
  }
  this->send_stanza(presence);
}



@@ 559,15 550,15 @@ void XmppComponent::send_affiliation_role_change(const std::string& muc_name,
                                                 const std::string& jid_to)
{
  Stanza presence("presence");
  presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
  presence["to"] = jid_to;
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode item("item");
  item["affiliation"] = affiliation;
  item["role"] = role;
  x.add_child(std::move(item));
  presence.add_child(std::move(x));
  {
    presence["from"] = muc_name + "@" + this->served_hostname + "/" + target;
    presence["to"] = jid_to;
    XmlSubNode x(presence, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode item(x, "item");
    item["affiliation"] = affiliation;
    item["role"] = role;
  }
  this->send_stanza(presence);
}



@@ 579,27 570,30 @@ void XmppComponent::send_version(const std::string& id, const std::string& jid_t
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = jid_from;
  XmlNode query("query");
  query["xmlns"] = VERSION_NS;
  if (version.empty())
    {
      XmlNode name("name");
      name.set_inner("biboumi");
      query.add_child(std::move(name));
      XmlNode version("version");
      version.set_inner(SOFTWARE_VERSION);
      query.add_child(std::move(version));
      XmlNode os("os");
      os.set_inner(SYSTEM_NAME);
      query.add_child(std::move(os));
  {
    XmlSubNode query(iq, "query");
    query["xmlns"] = VERSION_NS;
    if (version.empty())
      {
        {
          XmlSubNode name(query, "name");
          name.set_inner("biboumi");
        }
        {
          XmlSubNode version(query, "version");
          version.set_inner(SOFTWARE_VERSION);
        }
        {
          XmlSubNode os(query, "os");
          os.set_inner(SYSTEM_NAME);
        }
    }
  else
    else
    {
      XmlNode name("name");
      XmlSubNode name(query, "name");
      name.set_inner(version);
      query.add_child(std::move(name));
    }
  iq.add_child(std::move(query));
  }
  this->send_stanza(iq);
}



@@ 608,24 602,24 @@ void XmppComponent::send_adhoc_commands_list(const std::string& id, const std::s
                                             const bool with_admin_only, const AdhocCommandsHandler& adhoc_handler)
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = requester_jid;
  iq["from"] = from_jid;
  XmlNode query("query");
  query["xmlns"] = DISCO_ITEMS_NS;
  query["node"] = ADHOC_NS;
  for (const auto& kv: adhoc_handler.get_commands())
    {
      if (kv.second.is_admin_only() && !with_admin_only)
        continue;
      XmlNode item("item");
      item["jid"] = from_jid;
      item["node"] = kv.first;
      item["name"] = kv.second.name;
      query.add_child(std::move(item));
    }
  iq.add_child(std::move(query));
  {
    iq["type"] = "result";
    iq["id"] = id;
    iq["to"] = requester_jid;
    iq["from"] = from_jid;
    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_ITEMS_NS;
    query["node"] = ADHOC_NS;
    for (const auto &kv: adhoc_handler.get_commands())
      {
        if (kv.second.is_admin_only() && !with_admin_only)
          continue;
        XmlSubNode item(query, "item");
        item["jid"] = from_jid;
        item["node"] = kv.first;
        item["name"] = kv.second.name;
      }
  }
  this->send_stanza(iq);
}



@@ 633,13 627,14 @@ void XmppComponent::send_iq_version_request(const std::string& from,
                                            const std::string& jid_to)
{
  Stanza iq("iq");
  iq["type"] = "get";
  iq["id"] = "version_"s + XmppComponent::next_id();
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = jid_to;
  XmlNode query("query");
  query["xmlns"] = VERSION_NS;
  iq.add_child(std::move(query));
  {
    iq["type"] = "get";
    iq["id"] = "version_"s + XmppComponent::next_id();
    iq["from"] = from + "@" + this->served_hostname;
    iq["to"] = jid_to;
    XmlSubNode query(iq, "query");
    query["xmlns"] = VERSION_NS;
  }
  this->send_stanza(iq);
}


M louloulibs/xmpp/xmpp_stanza.hpp => louloulibs/xmpp/xmpp_stanza.hpp +14 -0
@@ 143,4 143,18 @@ std::ostream& operator<<(std::ostream& os, const XmlNode& node);
 */
using Stanza = XmlNode;

class XmlSubNode: public XmlNode
{
public:
    XmlSubNode(XmlNode& parent_ref, const std::string& name):
            XmlNode(name),
            parent_to_add(parent_ref)
    {}

    ~XmlSubNode()
    {
      this->parent_to_add.add_child(std::move(*this));
    }
private:
    XmlNode& parent_to_add;
};
\ No newline at end of file

M src/xmpp/biboumi_adhoc_commands.cpp => src/xmpp/biboumi_adhoc_commands.cpp +87 -166
@@ 26,40 26,31 @@ void DisconnectUserStep1(XmppComponent& xmpp_component, AdhocSession&, XmlNode& 
{
  auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);

  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Disconnect a user from the gateway");
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Choose a user JID and a quit message");
  x.add_child(std::move(instructions));
  XmlNode jids_field("field");
  XmlSubNode jids_field(x, "field");
  jids_field["var"] = "jids";
  jids_field["type"] = "list-multi";
  jids_field["label"] = "The JIDs to disconnect";
  XmlNode required("required");
  jids_field.add_child(std::move(required));
  XmlSubNode required(jids_field, "required");
  for (Bridge* bridge: biboumi_component.get_bridges())
    {
      XmlNode option("option");
      XmlSubNode option(jids_field, "option");
      option["label"] = bridge->get_jid();
      XmlNode value("value");
      XmlSubNode value(option, "value");
      value.set_inner(bridge->get_jid());
      option.add_child(std::move(value));
      jids_field.add_child(std::move(option));
    }
  x.add_child(std::move(jids_field));

  XmlNode message_field("field");
  XmlSubNode message_field(x, "field");
  message_field["var"] = "quit-message";
  message_field["type"] = "text-single";
  message_field["label"] = "Quit message";
  XmlNode message_value("value");
  XmlSubNode message_value(message_field, "value");
  message_value.set_inner("Disconnected by admin");
  message_field.add_child(std::move(message_value));
  x.add_child(std::move(message_field));
  command_node.add_child(std::move(x));
}

void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)


@@ 98,7 89,7 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X
            }
          command_node.delete_all_children();

          XmlNode note("note");
          XmlSubNode note(command_node, "note");
          note["type"] = "info";
          if (num == 0)
            note.set_inner("No user were disconnected.");


@@ 106,15 97,12 @@ void DisconnectUserStep2(XmppComponent& xmpp_component, AdhocSession& session, X
            note.set_inner("1 user has been disconnected.");
          else
            note.set_inner(std::to_string(num) + " users have been disconnected.");
          command_node.add_child(std::move(note));
          return;
        }
    }
  XmlNode error(ADHOC_NS":error");
  XmlSubNode error(command_node, ADHOC_NS":error");
  error["type"] = "modify";
  XmlNode condition(STANZA_NS":bad-request");
  error.add_child(std::move(condition));
  command_node.add_child(std::move(error));
  XmlSubNode condition(error, STANZA_NS":bad-request");
  session.terminate();
}



@@ 127,43 115,38 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman

  auto options = Database::get_global_options(owner.bare());

  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Configure some global default settings.");
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Edit the form, to configure your global settings for the component.");
  x.add_child(std::move(instructions));

  XmlNode required("required");

  XmlNode max_histo_length("field");
  XmlSubNode max_histo_length(x, "field");
  max_histo_length["var"] = "max_history_length";
  max_histo_length["type"] = "text-single";
  max_histo_length["label"] = "Max history length";
  max_histo_length["desc"] = "The maximum number of lines in the history that the server sends when joining a channel";

  XmlNode value("value");
  value.set_inner(std::to_string(options.maxHistoryLength.value()));
  max_histo_length.add_child(std::move(value));
  x.add_child(std::move(max_histo_length));
  {
    XmlSubNode value(max_histo_length, "value");
    value.set_inner(std::to_string(options.maxHistoryLength.value()));
  }

  XmlNode record_history("field");
  XmlSubNode record_history(x, "field");
  record_history["var"] = "record_history";
  record_history["type"] = "boolean";
  record_history["label"] = "Record history";
  record_history["desc"] = "Whether to save the messages into the database, or not";

  value.set_name("value");
  if (options.recordHistory.value())
    value.set_inner("true");
  else
    value.set_inner("false");
  record_history.add_child(std::move(value));
  x.add_child(std::move(record_history));

  command_node.add_child(std::move(x));
  {
    XmlSubNode value(record_history, "value");
    value.set_name("value");
    if (options.recordHistory.value())
      value.set_inner("true");
    else
      value.set_inner("false");
  }
}

void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)


@@ 195,17 178,14 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session, 
      options.update();

      command_node.delete_all_children();
      XmlNode note("note");
      XmlSubNode note(command_node, "note");
      note["type"] = "info";
      note.set_inner("Configuration successfully applied.");
      command_node.add_child(std::move(note));
      return;
    }
  XmlNode error(ADHOC_NS":error");
  XmlSubNode error(command_node, ADHOC_NS":error");
  error["type"] = "modify";
  XmlNode condition(STANZA_NS":bad-request");
  error.add_child(std::move(condition));
  command_node.add_child(std::move(error));
  XmlSubNode condition(error, STANZA_NS":bad-request");
  session.terminate();
}



@@ 219,18 199,16 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
  auto options = Database::get_irc_server_options(owner.local + "@" + owner.domain,
                                                  server_domain);

  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Configure the IRC server "s + server_domain);
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Edit the form, to configure the settings of the IRC server "s + server_domain);
  x.add_child(std::move(instructions));

  XmlNode required("required");

  XmlNode ports("field");
  XmlSubNode ports(x, "field");
  ports["var"] = "ports";
  ports["type"] = "text-multi";
  ports["label"] = "Ports";


@@ 238,15 216,13 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
  auto vals = utils::split(options.ports.value(), ';', false);
  for (const auto& val: vals)
    {
      XmlNode ports_value("value");
      XmlSubNode ports_value(ports, "value");
      ports_value.set_inner(val);
      ports.add_child(std::move(ports_value));
    }
  ports.add_child(required);
  x.add_child(std::move(ports));

#ifdef BOTAN_FOUND
  XmlNode tls_ports("field");
  XmlSubNode tls_ports(x, "field");
  tls_ports["var"] = "tls_ports";
  tls_ports["type"] = "text-multi";
  tls_ports["label"] = "TLS ports";


@@ 254,126 230,105 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
  vals = utils::split(options.tlsPorts.value(), ';', false);
  for (const auto& val: vals)
    {
      XmlNode tls_ports_value("value");
      XmlSubNode tls_ports_value(tls_ports, "value");
      tls_ports_value.set_inner(val);
      tls_ports.add_child(std::move(tls_ports_value));
    }
  tls_ports.add_child(required);
  x.add_child(std::move(tls_ports));

  XmlNode verify_cert("field");
  XmlSubNode verify_cert(x, "field");
  verify_cert["var"] = "verify_cert";
  verify_cert["type"] = "boolean";
  verify_cert["label"] = "Verify certificate";
  verify_cert["desc"] = "Whether or not to abort the connection if the server’s TLS certificate is invalid";
  XmlNode verify_cert_value("value");
  XmlSubNode verify_cert_value(verify_cert, "value");
  if (options.verifyCert.value())
    verify_cert_value.set_inner("true");
  else
    verify_cert_value.set_inner("false");
  verify_cert.add_child(std::move(verify_cert_value));
  x.add_child(std::move(verify_cert));

  XmlNode fingerprint("field");
  XmlSubNode fingerprint(x, "field");
  fingerprint["var"] = "fingerprint";
  fingerprint["type"] = "text-single";
  fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust.";
  if (!options.trustedFingerprint.value().empty())
    {
      XmlNode fingerprint_value("value");
      XmlSubNode fingerprint_value(fingerprint, "value");
      fingerprint_value.set_inner(options.trustedFingerprint.value());
      fingerprint.add_child(std::move(fingerprint_value));
    }
  fingerprint.add_child(required);
  x.add_child(std::move(fingerprint));
#endif

  XmlNode pass("field");
  XmlSubNode pass(x, "field");
  pass["var"] = "pass";
  pass["type"] = "text-private";
  pass["label"] = "Server password (to be used in a PASS command when connecting)";
  if (!options.pass.value().empty())
    {
      XmlNode pass_value("value");
      XmlSubNode pass_value(pass, "value");
      pass_value.set_inner(options.pass.value());
      pass.add_child(std::move(pass_value));
    }
  pass.add_child(required);
  x.add_child(std::move(pass));

  XmlNode after_cnt_cmd("field");
  XmlSubNode after_cnt_cmd(x, "field");
  after_cnt_cmd["var"] = "after_connect_command";
  after_cnt_cmd["type"] = "text-single";
  after_cnt_cmd["desc"] = "Custom IRC command sent after the connection is established with the server.";
  after_cnt_cmd["label"] = "After-connection IRC command";
  if (!options.afterConnectionCommand.value().empty())
    {
      XmlNode after_cnt_cmd_value("value");
      XmlSubNode after_cnt_cmd_value(after_cnt_cmd, "value");
      after_cnt_cmd_value.set_inner(options.afterConnectionCommand.value());
      after_cnt_cmd.add_child(std::move(after_cnt_cmd_value));
    }
  after_cnt_cmd.add_child(required);
  x.add_child(std::move(after_cnt_cmd));

  if (Config::get("realname_customization", "true") == "true")
    {
      XmlNode username("field");
      XmlSubNode username(x, "field");
      username["var"] = "username";
      username["type"] = "text-single";
      username["label"] = "Username";
      if (!options.username.value().empty())
        {
          XmlNode username_value("value");
          XmlSubNode username_value(username, "value");
          username_value.set_inner(options.username.value());
          username.add_child(std::move(username_value));
        }
      username.add_child(required);
      x.add_child(std::move(username));

      XmlNode realname("field");
      XmlSubNode realname(x, "field");
      realname["var"] = "realname";
      realname["type"] = "text-single";
      realname["label"] = "Realname";
      if (!options.realname.value().empty())
        {
          XmlNode realname_value("value");
          XmlSubNode realname_value(realname, "value");
          realname_value.set_inner(options.realname.value());
          realname.add_child(std::move(realname_value));
        }
      realname.add_child(required);
      x.add_child(std::move(realname));
    }

  XmlNode encoding_out("field");
  XmlSubNode encoding_out(x, "field");
  encoding_out["var"] = "encoding_out";
  encoding_out["type"] = "text-single";
  encoding_out["desc"] = "The encoding used when sending messages to the IRC server.";
  encoding_out["label"] = "Out encoding";
  if (!options.encodingOut.value().empty())
    {
      XmlNode encoding_out_value("value");
      XmlSubNode encoding_out_value(encoding_out, "value");
      encoding_out_value.set_inner(options.encodingOut.value());
      encoding_out.add_child(std::move(encoding_out_value));
    }
  encoding_out.add_child(required);
  x.add_child(std::move(encoding_out));

  XmlNode encoding_in("field");
  XmlSubNode encoding_in(x, "field");
  encoding_in["var"] = "encoding_in";
  encoding_in["type"] = "text-single";
  encoding_in["desc"] = "The encoding used to decode message received from the IRC server.";
  encoding_in["label"] = "In encoding";
  if (!options.encodingIn.value().empty())
    {
      XmlNode encoding_in_value("value");
      XmlSubNode encoding_in_value(encoding_in, "value");
      encoding_in_value.set_inner(options.encodingIn.value());
      encoding_in.add_child(std::move(encoding_in_value));
    }
  encoding_in.add_child(required);
  x.add_child(std::move(encoding_in));


  command_node.add_child(std::move(x));
}

void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)


@@ 458,17 413,14 @@ void ConfigureIrcServerStep2(XmppComponent&, AdhocSession& session, XmlNode& com
      options.update();

      command_node.delete_all_children();
      XmlNode note("note");
      XmlSubNode note(command_node, "note");
      note["type"] = "info";
      note.set_inner("Configuration successfully applied.");
      command_node.add_child(std::move(note));
      return;
    }
  XmlNode error(ADHOC_NS":error");
  XmlSubNode error(command_node, ADHOC_NS":error");
  error["type"] = "modify";
  XmlNode condition(STANZA_NS":bad-request");
  error.add_child(std::move(condition));
  command_node.add_child(std::move(error));
  XmlSubNode condition(error, STANZA_NS":bad-request");
  session.terminate();
}



@@ 480,46 432,38 @@ void ConfigureIrcChannelStep1(XmppComponent&, AdhocSession& session, XmlNode& co
  auto options = Database::get_irc_channel_options_with_server_default(owner.local + "@" + owner.domain,
                                                                       iid.get_server(), iid.get_local());

  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Configure the IRC channel "s + iid.get_local() + " on server "s + iid.get_server());
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Edit the form, to configure the settings of the IRC channel "s + iid.get_local());
  x.add_child(std::move(instructions));

  XmlNode required("required");

  XmlNode encoding_out("field");
  XmlSubNode encoding_out(x, "field");
  encoding_out["var"] = "encoding_out";
  encoding_out["type"] = "text-single";
  encoding_out["desc"] = "The encoding used when sending messages to the IRC server. Defaults to the server's “out encoding” if unset for the channel";
  encoding_out["label"] = "Out encoding";
  if (!options.encodingOut.value().empty())
    {
      XmlNode encoding_out_value("value");
      XmlSubNode encoding_out_value(encoding_out, "value");
      encoding_out_value.set_inner(options.encodingOut.value());
      encoding_out.add_child(std::move(encoding_out_value));
    }
  encoding_out.add_child(required);
  x.add_child(std::move(encoding_out));

  XmlNode encoding_in("field");
  XmlSubNode encoding_in(x, "field");
  encoding_in["var"] = "encoding_in";
  encoding_in["type"] = "text-single";
  encoding_in["desc"] = "The encoding used to decode message received from the IRC server. Defaults to the server's “in encoding” if unset for the channel";
  encoding_in["label"] = "In encoding";
  if (!options.encodingIn.value().empty())
    {
      XmlNode encoding_in_value("value");
      XmlSubNode encoding_in_value(encoding_in, "value");
      encoding_in_value.set_inner(options.encodingIn.value());
      encoding_in.add_child(std::move(encoding_in_value));
    }
  encoding_in.add_child(required);
  x.add_child(std::move(encoding_in));

  command_node.add_child(std::move(x));
}

void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& command_node)


@@ 548,17 492,14 @@ void ConfigureIrcChannelStep2(XmppComponent&, AdhocSession& session, XmlNode& co
      options.update();

      command_node.delete_all_children();
      XmlNode note("note");
      XmlSubNode note(command_node, "note");
      note["type"] = "info";
      note.set_inner("Configuration successfully applied.");
      command_node.add_child(std::move(note));
      return;
    }
  XmlNode error(ADHOC_NS":error");
  XmlSubNode error(command_node, ADHOC_NS":error");
  error["type"] = "modify";
  XmlNode condition(STANZA_NS":bad-request");
  error.add_child(std::move(condition));
  command_node.add_child(std::move(error));
  XmlSubNode condition(error, STANZA_NS":bad-request");
  session.terminate();
}
#endif  // USE_DATABASE


@@ 576,31 517,24 @@ void DisconnectUserFromServerStep1(XmppComponent& xmpp_component, AdhocSession& 
    { // Send a form to select the user to disconnect
      auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);

      XmlNode x("jabber:x:data:x");
      XmlSubNode x(command_node, "jabber:x:data:x");
      x["type"] = "form";
      XmlNode title("title");
      XmlSubNode title(x, "title");
      title.set_inner("Disconnect a user from selected IRC servers");
      x.add_child(std::move(title));
      XmlNode instructions("instructions");
      XmlSubNode instructions(x, "instructions");
      instructions.set_inner("Choose a user JID");
      x.add_child(std::move(instructions));
      XmlNode jids_field("field");
      XmlSubNode jids_field(x, "field");
      jids_field["var"] = "jid";
      jids_field["type"] = "list-single";
      jids_field["label"] = "The JID to disconnect";
      XmlNode required("required");
      jids_field.add_child(std::move(required));
      XmlSubNode required(jids_field, "required");
      for (Bridge* bridge: biboumi_component.get_bridges())
        {
          XmlNode option("option");
          XmlSubNode option(jids_field, "option");
          option["label"] = bridge->get_jid();
          XmlNode value("value");
          XmlSubNode value(option, "value");
          value.set_inner(bridge->get_jid());
          option.add_child(std::move(value));
          jids_field.add_child(std::move(option));
        }
      x.add_child(std::move(jids_field));
      command_node.add_child(std::move(x));
    }
}



@@ 628,53 562,42 @@ void DisconnectUserFromServerStep2(XmppComponent& xmpp_component, AdhocSession& 
  command_node.delete_all_children();
  auto& biboumi_component = static_cast<BiboumiComponent&>(xmpp_component);

  XmlNode x("jabber:x:data:x");
  XmlSubNode x(command_node, "jabber:x:data:x");
  x["type"] = "form";
  XmlNode title("title");
  XmlSubNode title(x, "title");
  title.set_inner("Disconnect a user from selected IRC servers");
  x.add_child(std::move(title));
  XmlNode instructions("instructions");
  XmlSubNode instructions(x, "instructions");
  instructions.set_inner("Choose one or more servers to disconnect this JID from");
  x.add_child(std::move(instructions));
  XmlNode jids_field("field");
  XmlSubNode jids_field(x, "field");
  jids_field["var"] = "irc-servers";
  jids_field["type"] = "list-multi";
  jids_field["label"] = "The servers to disconnect from";
  XmlNode required("required");
  jids_field.add_child(std::move(required));
  XmlSubNode required(jids_field, "required");
  Bridge* bridge = biboumi_component.find_user_bridge(jid_to_disconnect);

  if (!bridge || bridge->get_irc_clients().empty())
    {
      XmlNode note("note");
      XmlSubNode note(command_node, "note");
      note["type"] = "info";
      note.set_inner("User "s + jid_to_disconnect + " is not connected to any IRC server.");
      command_node.add_child(std::move(note));
      session.terminate();
      return ;
    }

  for (const auto& pair: bridge->get_irc_clients())
    {
      XmlNode option("option");
      XmlSubNode option(jids_field, "option");
      option["label"] = pair.first;
      XmlNode value("value");
      XmlSubNode value(option, "value");
      value.set_inner(pair.first);
      option.add_child(std::move(value));
      jids_field.add_child(std::move(option));
    }
  x.add_child(std::move(jids_field));

  XmlNode message_field("field");
  XmlSubNode message_field(x, "field");
  message_field["var"] = "quit-message";
  message_field["type"] = "text-single";
  message_field["label"] = "Quit message";
  XmlNode message_value("value");
  XmlSubNode message_value(message_field, "value");
  message_value.set_inner("Killed by admin");
  message_field.add_child(std::move(message_value));
  x.add_child(std::move(message_field));

  command_node.add_child(std::move(x));
}

void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& session, XmlNode& command_node)


@@ 719,14 642,13 @@ void DisconnectUserFromServerStep3(XmppComponent& xmpp_component, AdhocSession& 
        }
    }
  command_node.delete_all_children();
  XmlNode note("note");
  XmlSubNode note(command_node, "note");
  note["type"] = "info";
  std::string msg = jid_to_disconnect + " was disconnected from " + std::to_string(number) + " IRC server";
  if (number > 1)
    msg += "s";
  msg += ".";
  note.set_inner(msg);
  command_node.add_child(std::move(note));
}

void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, XmlNode& command_node)


@@ 742,10 664,9 @@ void GetIrcConnectionInfoStep1(XmppComponent& component, AdhocSession& session, 
  utils::ScopeGuard sg([&message, &command_node]()
                       {
                         command_node.delete_all_children();
                         XmlNode note("note");
                         XmlSubNode note(command_node, "note");
                         note["type"] = "info";
                         note.set_inner(message);
                         command_node.add_child(std::move(note));
                       });

  Bridge* bridge = biboumi_component.get_user_bridge(owner.bare());

M src/xmpp/biboumi_component.cpp => src/xmpp/biboumi_component.cpp +107 -120
@@ 625,39 625,33 @@ bool BiboumiComponent::handle_mam_request(const Stanza& stanza)
void BiboumiComponent::send_archived_message(const db::MucLogLine& log_line, const std::string& from, const std::string& to,
                                             const std::string& queryid)
{
    Stanza message("message");
  Stanza message("message");
  {
    message["from"] = from;
    message["to"] = to;

    XmlNode result("result");
    XmlSubNode result(message, "result");
    result["xmlns"] = MAM_NS;
    if (!queryid.empty())
      result["queryid"] = queryid;
    result["id"] = log_line.uuid.value();

    XmlNode forwarded("forwarded");
    XmlSubNode forwarded(result, "forwarded");
    forwarded["xmlns"] = FORWARD_NS;

    XmlNode delay("delay");
    XmlSubNode delay(forwarded, "delay");
    delay["xmlns"] = DELAY_NS;
    delay["stamp"] = utils::to_string(log_line.date.value().timeStamp());

    forwarded.add_child(std::move(delay));

    XmlNode submessage("message");
    XmlSubNode submessage(forwarded, "message");
    submessage["xmlns"] = CLIENT_NS;
    submessage["from"] = from + "/" + log_line.nick.value();
    submessage["type"] = "groupchat";

    XmlNode body("body");
    XmlSubNode body(submessage, "body");
    body.set_inner(log_line.body.value());
    submessage.add_child(std::move(body));

    forwarded.add_child(std::move(submessage));
    result.add_child(std::move(forwarded));
    message.add_child(std::move(result));

    this->send_stanza(message);
  }
  this->send_stanza(message);
}

#endif


@@ 699,24 693,23 @@ std::vector<Bridge*> BiboumiComponent::get_bridges() const
void BiboumiComponent::send_self_disco_info(const std::string& id, const std::string& jid_to)
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = this->served_hostname;
  XmlNode query("query");
  query["xmlns"] = DISCO_INFO_NS;
  XmlNode identity("identity");
  identity["category"] = "conference";
  identity["type"] = "irc";
  identity["name"] = "Biboumi XMPP-IRC gateway";
  query.add_child(std::move(identity));
  for (const char* ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      query.add_child(std::move(feature));
    }
  iq.add_child(std::move(query));
  {
    iq["type"] = "result";
    iq["id"] = id;
    iq["to"] = jid_to;
    iq["from"] = this->served_hostname;
    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_INFO_NS;
    XmlSubNode identity(query, "identity");
    identity["category"] = "conference";
    identity["type"] = "irc";
    identity["name"] = "Biboumi XMPP-IRC gateway";
    for (const char *ns: {DISCO_INFO_NS, MUC_NS, ADHOC_NS, PING_NS, MAM_NS, VERSION_NS})
      {
        XmlSubNode feature(query, "feature");
        feature["var"] = ns;
      }
  }
  this->send_stanza(iq);
}



@@ 724,44 717,42 @@ void BiboumiComponent::send_irc_server_disco_info(const std::string& id, const s
{
  Jid from(jid_from);
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["to"] = jid_to;
  iq["from"] = jid_from;
  XmlNode query("query");
  query["xmlns"] = DISCO_INFO_NS;
  XmlNode identity("identity");
  identity["category"] = "conference";
  identity["type"] = "irc";
  identity["name"] = "IRC server "s + from.local + " over Biboumi";
  query.add_child(std::move(identity));
  for (const char* ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS})
    {
      XmlNode feature("feature");
      feature["var"] = ns;
      query.add_child(std::move(feature));
    }
  iq.add_child(std::move(query));
  {
    iq["type"] = "result";
    iq["id"] = id;
    iq["to"] = jid_to;
    iq["from"] = jid_from;
    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_INFO_NS;
    XmlSubNode identity(query, "identity");
    identity["category"] = "conference";
    identity["type"] = "irc";
    identity["name"] = "IRC server "s + from.local + " over Biboumi";
    for (const char *ns: {DISCO_INFO_NS, ADHOC_NS, PING_NS, VERSION_NS})
      {
        XmlSubNode feature(query, "feature");
        feature["var"] = ns;
      }
  }
  this->send_stanza(iq);
}

void BiboumiComponent::send_irc_channel_muc_traffic_info(const std::string id, const std::string& jid_from, const std::string& jid_to)
{
  Stanza iq("iq");
  iq["type"] = "result";
  iq["id"] = id;
  iq["from"] = jid_from;
  iq["to"] = jid_to;

  XmlNode query("query");
  query["xmlns"] = DISCO_INFO_NS;
  query["node"] = MUC_TRAFFIC_NS;
  // We drop all “special” traffic (like xhtml-im, chatstates, etc), so
  // don’t include any <feature/>
  iq.add_child(std::move(query));

  {
    iq["type"] = "result";
    iq["id"] = id;
    iq["from"] = jid_from;
    iq["to"] = jid_to;

    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_INFO_NS;
    query["node"] = MUC_TRAFFIC_NS;
    // We drop all “special” traffic (like xhtml-im, chatstates, etc), so
    // don’t include any <feature/>
  }
  this->send_stanza(iq);

}

void BiboumiComponent::send_ping_request(const std::string& from,


@@ 769,13 760,14 @@ void BiboumiComponent::send_ping_request(const std::string& from,
                                         const std::string& id)
{
  Stanza iq("iq");
  iq["type"] = "get";
  iq["id"] = id;
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = jid_to;
  XmlNode ping("ping");
  ping["xmlns"] = PING_NS;
  iq.add_child(std::move(ping));
  {
    iq["type"] = "get";
    iq["id"] = id;
    iq["from"] = from + "@" + this->served_hostname;
    iq["to"] = jid_to;
    XmlSubNode ping(iq, "ping");
    ping["xmlns"] = PING_NS;
  }
  this->send_stanza(iq);

  auto result_cb = [from, id](Bridge* bridge, const Stanza& stanza)


@@ 799,48 791,43 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std
                                                const ResultSetInfo& rs_info)
{
  Stanza iq("iq");
  iq["from"] = from + "@" + this->served_hostname;
  iq["to"] = to_jid;
  iq["id"] = id;
  iq["type"] = "result";
  XmlNode query("query");
  query["xmlns"] = DISCO_ITEMS_NS;
  {
    iq["from"] = from + "@" + this->served_hostname;
    iq["to"] = to_jid;
    iq["id"] = id;
    iq["type"] = "result";
    XmlSubNode query(iq, "query");
    query["xmlns"] = DISCO_ITEMS_NS;

    for (auto it = begin; it != end; ++it)
    {
      XmlNode item("item");
      {
        XmlSubNode item(query, "item");
        item["jid"] = it->channel + "@" + this->served_hostname;
      query.add_child(std::move(item));
    }

  if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty()))
    {
      XmlNode set_node("set");
      set_node["xmlns"] = RSM_NS;
      }

      if (begin != channel_list.channels.cend())
        {
          XmlNode first_node("first");
          first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin));
          first_node.set_inner(begin->channel + "@" + this->served_hostname);
          set_node.add_child(std::move(first_node));
        }
      if (end != channel_list.channels.cbegin())
        {
          XmlNode last_node("last");
          last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname);
          set_node.add_child(std::move(last_node));
        }
      if (channel_list.complete)
        {
          XmlNode count_node("count");
          count_node.set_inner(std::to_string(channel_list.channels.size()));
          set_node.add_child(std::move(count_node));
        }
      query.add_child(std::move(set_node));
    }
    if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty()))
      {
        XmlSubNode set_node(query, "set");
        set_node["xmlns"] = RSM_NS;

  iq.add_child(std::move(query));
        if (begin != channel_list.channels.cend())
          {
            XmlSubNode first_node(set_node, "first");
            first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin));
            first_node.set_inner(begin->channel + "@" + this->served_hostname);
          }
        if (end != channel_list.channels.cbegin())
          {
            XmlSubNode last_node(set_node, "last");
            last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname);
          }
        if (channel_list.complete)
          {
            XmlSubNode count_node(set_node, "count");
            count_node.set_inner(std::to_string(channel_list.channels.size()));
          }
      }
  }
  this->send_stanza(iq);
}



@@ 849,17 836,17 @@ void BiboumiComponent::send_invitation(const std::string& room_target,
                                       const std::string& author_nick)
{
  Stanza message("message");
  message["from"] = room_target + "@" + this->served_hostname;
  message["to"] = jid_to;
  XmlNode x("x");
  x["xmlns"] = MUC_USER_NS;
  XmlNode invite("invite");
  if (author_nick.empty())
    invite["from"] = room_target + "@" + this->served_hostname;
  else
    invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick;
  x.add_child(std::move(invite));
  message.add_child(std::move(x));
  {
    message["from"] = room_target + "@" + this->served_hostname;
    message["to"] = jid_to;
    XmlSubNode x(message, "x");
    x["xmlns"] = MUC_USER_NS;
    XmlSubNode invite(x, "invite");
    if (author_nick.empty())
      invite["from"] = room_target + "@" + this->served_hostname;
    else
      invite["from"] = room_target + "@" + this->served_hostname + "/" + author_nick;
  }
  this->send_stanza(message);
}



@@ 875,7 862,7 @@ void BiboumiComponent::accept_subscription(const std::string& from, const std::s

void BiboumiComponent::ask_subscription(const std::string& from, const std::string& to)
{
 Stanza presence("presence");
  Stanza presence("presence");
  presence["from"] = from;
  presence["to"] = to;
  presence["id"] = this->next_id();

M tests/xmpp.cpp => tests/xmpp.cpp +17 -0
@@ 52,3 52,20 @@ TEST_CASE("handshake_digest")
  const auto res = get_handshake_digest("id1234", "S4CR3T");
  CHECK(res == "c92901b5d376ad56269914da0cce3aab976847df");
}

TEST_CASE("substanzas")
{
  Stanza a("a");
  {
    XmlSubNode b(a, "b");
    {
      CHECK(!a.has_children());
      XmlSubNode c(b, "c");
      XmlSubNode d(b, "d");
      CHECK(!c.has_children());
      CHECK(!d.has_children());
    }
    CHECK(b.has_children());
  }
  CHECK(a.has_children());
}
\ No newline at end of file