~singpolyma/biboumi

f0e07beacb1ca14822d5ad52a7b4462f15ee47cc — Florent Le Coz 8 years ago 649ebaf
Forgot to remove some XMPP files that are now in louloulibs instead
7 files changed, 0 insertions(+), 862 deletions(-)

M CMakeLists.txt
D src/xmpp/jid.cpp
D src/xmpp/jid.hpp
D src/xmpp/xmpp_parser.cpp
D src/xmpp/xmpp_parser.hpp
D src/xmpp/xmpp_stanza.cpp
D src/xmpp/xmpp_stanza.hpp
M CMakeLists.txt => CMakeLists.txt +0 -3
@@ 76,9 76,6 @@ include_directories(${ICONV_INCLUDE_DIRS})
include_directories(${LIBUUID_INCLUDE_DIRS})

# If they are found in louloulibs CMakeLists.txt, we inherite these values
if(LIBIDN_FOUND)
  include_directories(${LIBIDN_INCLUDE_DIRS})
endif()
if(SYSTEMD_FOUND)
  include_directories(${SYSTEMD_INCLUDE_DIRS})
endif()

D src/xmpp/jid.cpp => src/xmpp/jid.cpp +0 -94
@@ 1,94 0,0 @@
#include <xmpp/jid.hpp>
#include <cstring>
#include <map>

#include <louloulibs.h>
#ifdef LIBIDN_FOUND
 #include <stringprep.h>
#endif

#include <logger/logger.hpp>

Jid::Jid(const std::string& jid)
{
  std::string::size_type slash = jid.find('/');
  if (slash != std::string::npos)
    {
      this->resource = jid.substr(slash + 1);
    }

  std::string::size_type at = jid.find('@');
  if (at != std::string::npos && at < slash)
    {
      this->local = jid.substr(0, at);
      at++;
    }
  else
    at = 0;

  this->domain = jid.substr(at, slash - at);
}

#include <iostream>

static constexpr size_t max_jid_part_len = 1023;

std::string jidprep(const std::string& original)
{
#ifdef LIBIDN_FOUND
  using CacheType = std::map<std::string, std::string>;
  static CacheType cache;
  std::pair<CacheType::iterator, bool> cached = cache.insert({original, {}});
  if (std::get<1>(cached) == false)
    { // Insertion failed: the result is already in the cache, return it
      return std::get<0>(cached)->second;
    }

  const std::string error_msg("Failed to convert " + original + " into a valid JID:");
  Jid jid(original);

  char local[max_jid_part_len] = {};
  memcpy(local, jid.local.data(), jid.local.size());
  Stringprep_rc rc = static_cast<Stringprep_rc>(::stringprep(local, max_jid_part_len,
                     static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_nodeprep));
  if (rc != STRINGPREP_OK)
  {
    log_error(error_msg + stringprep_strerror(rc));
    return "";
  }

  char domain[max_jid_part_len] = {};
  memcpy(domain, jid.domain.data(), jid.domain.size());
  rc = static_cast<Stringprep_rc>(::stringprep(domain, max_jid_part_len,
       static_cast<Stringprep_profile_flags>(0), stringprep_nameprep));
  if (rc != STRINGPREP_OK)
  {
    log_error(error_msg + stringprep_strerror(rc));
    return "";
  }

  // If there is no resource, stop here
  if (jid.resource.empty())
    {
      std::get<0>(cached)->second = std::string(local) + "@" + domain;
      return std::get<0>(cached)->second;
    }

  // Otherwise, also process the resource part
  char resource[max_jid_part_len] = {};
  memcpy(resource, jid.resource.data(), jid.resource.size());
  rc = static_cast<Stringprep_rc>(::stringprep(resource, max_jid_part_len,
       static_cast<Stringprep_profile_flags>(0), stringprep_xmpp_resourceprep));
  if (rc != STRINGPREP_OK)
    {
      log_error(error_msg + stringprep_strerror(rc));
      return "";
    }
  std::get<0>(cached)->second = std::string(local) + "@" + domain + "/" + resource;
  return std::get<0>(cached)->second;

#else
  (void)original;
  return "";
#endif
}

D src/xmpp/jid.hpp => src/xmpp/jid.hpp +0 -36
@@ 1,36 0,0 @@
#ifndef JID_INCLUDED
# define JID_INCLUDED

#include <string>

/**
 * Parse a JID into its different subart
 */
class Jid
{
public:
  explicit Jid(const std::string& jid);

  std::string domain;
  std::string local;
  std::string resource;

private:
  Jid(const Jid&) = delete;
  Jid(Jid&&) = delete;
  Jid& operator=(const Jid&) = delete;
  Jid& operator=(Jid&&) = delete;
};

/**
 * Prepare the given UTF-8 string according to the XMPP node stringprep
 * identifier profile.  This is used to send properly-formed JID to the XMPP
 * server.
 *
 * If the stringprep library is not found, we return an empty string.  When
 * this function is used, the result must always be checked for an empty
 * value, and if this is the case it must not be used as a JID.
 */
std::string jidprep(const std::string& original);

#endif // JID_INCLUDED

D src/xmpp/xmpp_parser.cpp => src/xmpp/xmpp_parser.cpp +0 -169
@@ 1,169 0,0 @@
#include <xmpp/xmpp_parser.hpp>
#include <xmpp/xmpp_stanza.hpp>

#include <logger/logger.hpp>

/**
 * Expat handlers. Called by the Expat library, never by ourself.
 * They just forward the call to the XmppParser corresponding methods.
 */

static void start_element_handler(void* user_data, const XML_Char* name, const XML_Char** atts)
{
  static_cast<XmppParser*>(user_data)->start_element(name, atts);
}

static void end_element_handler(void* user_data, const XML_Char* name)
{
  static_cast<XmppParser*>(user_data)->end_element(name);
}

static void character_data_handler(void *user_data, const XML_Char *s, int len)
{
  static_cast<XmppParser*>(user_data)->char_data(s, len);
}

/**
 * XmppParser class
 */

XmppParser::XmppParser():
  level(0),
  current_node(nullptr)
{
  this->init_xml_parser();
}

void XmppParser::init_xml_parser()
{
  // Create the expat parser
  this->parser = XML_ParserCreateNS("UTF-8", ':');
  XML_SetUserData(this->parser, static_cast<void*>(this));

  // Install Expat handlers
  XML_SetElementHandler(this->parser, &start_element_handler, &end_element_handler);
  XML_SetCharacterDataHandler(this->parser, &character_data_handler);
}

XmppParser::~XmppParser()
{
  if (this->current_node)
    delete this->current_node;
  XML_ParserFree(this->parser);
}

int XmppParser::feed(const char* data, const int len, const bool is_final)
{
  int res = XML_Parse(this->parser, data, len, is_final);
  if (res == XML_STATUS_ERROR &&
      (XML_GetErrorCode(this->parser) != XML_ERROR_FINISHED))
    log_error("Xml_Parse encountered an error: " <<
              XML_ErrorString(XML_GetErrorCode(this->parser)))
  return res;
}

int XmppParser::parse(const int len, const bool is_final)
{
  int res = XML_ParseBuffer(this->parser, len, is_final);
  if (res == XML_STATUS_ERROR)
    log_error("Xml_Parsebuffer encountered an error: " <<
              XML_ErrorString(XML_GetErrorCode(this->parser)));
  return res;
}

void XmppParser::reset()
{
  XML_ParserFree(this->parser);
  this->init_xml_parser();
  if (this->current_node)
    delete this->current_node;
  this->current_node = nullptr;
  this->level = 0;
}

void* XmppParser::get_buffer(const size_t size) const
{
  return XML_GetBuffer(this->parser, static_cast<int>(size));
}

void XmppParser::start_element(const XML_Char* name, const XML_Char** attribute)
{
  level++;

  XmlNode* new_node = new XmlNode(name, this->current_node);
  if (this->current_node)
    this->current_node->add_child(new_node);
  this->current_node = new_node;
  for (size_t i = 0; attribute[i]; i += 2)
    this->current_node->set_attribute(attribute[i], attribute[i+1]);
  if (this->level == 1)
    this->stream_open_event(*this->current_node);
}

void XmppParser::end_element(const XML_Char* name)
{
  (void)name;
  level--;
  this->current_node->close();
  if (level == 1)
    {
      this->stanza_event(*this->current_node);
    }
  if (level == 0)
    {
      this->stream_close_event(*this->current_node);
      delete this->current_node;
      this->current_node = nullptr;
    }
  else
    this->current_node = this->current_node->get_parent();
  if (level == 1)
    this->current_node->delete_all_children();
}

void XmppParser::char_data(const XML_Char* data, int len)
{
  if (this->current_node->has_children())
    this->current_node->get_last_child()->add_to_tail(std::string(data, len));
  else
    this->current_node->add_to_inner(std::string(data, len));
}

void XmppParser::stanza_event(const Stanza& stanza) const
{
  for (const auto& callback: this->stanza_callbacks)
    {
      try {
        callback(stanza);
      } catch (const std::exception& e) {
        log_debug("Unhandled exception: " << e.what());
      }
    }
}

void XmppParser::stream_open_event(const XmlNode& node) const
{
  for (const auto& callback: this->stream_open_callbacks)
    callback(node);
}

void XmppParser::stream_close_event(const XmlNode& node) const
{
  for (const auto& callback: this->stream_close_callbacks)
    callback(node);
}

void XmppParser::add_stanza_callback(std::function<void(const Stanza&)>&& callback)
{
  this->stanza_callbacks.emplace_back(std::move(callback));
}

void XmppParser::add_stream_open_callback(std::function<void(const XmlNode&)>&& callback)
{
  this->stream_open_callbacks.emplace_back(std::move(callback));
}

void XmppParser::add_stream_close_callback(std::function<void(const XmlNode&)>&& callback)
{
  this->stream_close_callbacks.emplace_back(std::move(callback));
}

D src/xmpp/xmpp_parser.hpp => src/xmpp/xmpp_parser.hpp +0 -126
@@ 1,126 0,0 @@
#ifndef XMPP_PARSER_INCLUDED
# define XMPP_PARSER_INCLUDED

#include <xmpp/xmpp_stanza.hpp>

#include <functional>

#include <expat.h>

/**
 * A SAX XML parser that builds XML nodes and spawns events when a complete
 * stanza is received (an element of level 2), or when the document is
 * opened/closed (an element of level 1)
 *
 * After a stanza_event has been spawned, we delete the whole stanza. This
 * means that even with a very long document (in XMPP the document is
 * potentially infinite), the memory is never exhausted as long as each
 * stanza is reasonnably short.
 *
 * The element names generated by expat contain the namespace of the
 * element, a colon (':') and then the actual name of the element.  To get
 * an element "x" with a namespace of "http://jabber.org/protocol/muc", you
 * just look for an XmlNode named "http://jabber.org/protocol/muc:x"
 *
 * TODO: enforce the size-limit for the stanza (limit the number of childs
 * it can contain). For example forbid the parser going further than level
 * 20 (arbitrary number here), and each XML node to have more than 15 childs
 * (arbitrary number again).
 */
class XmppParser
{
public:
  explicit XmppParser();
  ~XmppParser();

public:
  /**
   * Init the XML parser and install the callbacks
   */
  void init_xml_parser();
  /**
   * Feed the parser with some XML data
   */
  int feed(const char* data, const int len, const bool is_final);
  /**
   * Parse the data placed in the parser buffer
   */
  int parse(const int size, const bool is_final);
  /**
   * Reset the parser, so it can be used from scratch afterward
   */
  void reset();
  /**
   * Get a buffer provided by the xml parser.
   */
  void* get_buffer(const size_t size) const;
  /**
   * Add one callback for the various events that this parser can spawn.
   */
  void add_stanza_callback(std::function<void(const Stanza&)>&& callback);
  void add_stream_open_callback(std::function<void(const XmlNode&)>&& callback);
  void add_stream_close_callback(std::function<void(const XmlNode&)>&& callback);

  /**
   * Called when a new XML element has been opened. We instanciate a new
   * XmlNode and set it as our current node. The parent of this new node is
   * the previous "current" node. We have all the element's attributes in
   * this event.
   *
   * We spawn a stream_event with this node if this is a level-1 element.
   */
  void start_element(const XML_Char* name, const XML_Char** attribute);
  /**
   * Called when an XML element has been closed. We close the current_node,
   * set our current_node as the parent of the current_node, and if that was
   * a level-2 element we spawn a stanza_event with this node.
   *
   * And we then delete the stanza (and everything under it, its children,
   * attribute, etc).
   */
  void end_element(const XML_Char* name);
  /**
   * Some inner or tail data has been parsed
   */
  void char_data(const XML_Char* data, int len);
  /**
   * Calls all the stanza_callbacks one by one.
   */
  void stanza_event(const Stanza& stanza) const;
  /**
   * Calls all the stream_open_callbacks one by one. Note: the passed node is not
   * closed yet.
   */
  void stream_open_event(const XmlNode& node) const;
  /**
   * Calls all the stream_close_callbacks one by one.
   */
  void stream_close_event(const XmlNode& node) const;

private:
  /**
   * Expat structure.
   */
  XML_Parser parser;
  /**
   * The current depth in the XML document
   */
  size_t level;
  /**
   * The deepest XML node opened but not yet closed (to which we are adding
   * new children, inner or tail)
   */
  XmlNode* current_node;
  /**
   * A list of callbacks to be called on an *_event, receiving the
   * concerned Stanza/XmlNode.
   */
  std::vector<std::function<void(const Stanza&)>> stanza_callbacks;
  std::vector<std::function<void(const XmlNode&)>> stream_open_callbacks;
  std::vector<std::function<void(const XmlNode&)>> stream_close_callbacks;

  XmppParser(const XmppParser&) = delete;
  XmppParser& operator=(const XmppParser&) = delete;
};

#endif // XMPP_PARSER_INCLUDED

D src/xmpp/xmpp_stanza.cpp => src/xmpp/xmpp_stanza.cpp +0 -274
@@ 1,274 0,0 @@
#include <xmpp/xmpp_stanza.hpp>

#include <utils/encoding.hpp>
#include <utils/split.hpp>

#include <stdexcept>
#include <iostream>

#include <string.h>

std::string xml_escape(const std::string& data)
{
  std::string res;
  res.reserve(data.size());
  for (size_t pos = 0; pos != data.size(); ++pos)
    {
      switch(data[pos])
        {
        case '&':
          res += "&amp;";
          break;
        case '<':
          res += "&lt;";
          break;
        case '>':
          res += "&gt;";
          break;
        case '\"':
          res += "&quot;";
          break;
        case '\'':
          res += "&apos;";
          break;
        default:
          res += data[pos];
          break;
        }
    }
  return res;
}

std::string xml_unescape(const std::string& data)
{
  std::string res;
  res.reserve(data.size());
  const char* str = data.c_str();
  while (str && *str && static_cast<size_t>(str - data.c_str()) < data.size())
    {
      if (*str == '&')
        {
          if (strncmp(str+1, "amp;", 4) == 0)
            {
              res += "&";
              str += 4;
            }
          else if (strncmp(str+1, "lt;", 3) == 0)
            {
              res += "<";
              str += 3;
            }
          else if (strncmp(str+1, "gt;", 3) == 0)
            {
              res += ">";
              str += 3;
            }
          else if (strncmp(str+1, "quot;", 5) == 0)
            {
              res += "\"";
              str += 5;
            }
          else if (strncmp(str+1, "apos;", 5) == 0)
            {
              res += "'";
              str += 5;
            }
          else
            res += "&";
        }
      else
        res += *str;
      str++;
    }
  return res;
}

XmlNode::XmlNode(const std::string& name, XmlNode* parent):
  parent(parent),
  closed(false)
{
  // split the namespace and the name
  auto n = name.rfind(":");
  if (n == std::string::npos)
    this->name = name;
  else
    {
      this->name = name.substr(n+1);
      this->attributes["xmlns"] = name.substr(0, n);
    }
}

XmlNode::XmlNode(const std::string& name):
  XmlNode(name, nullptr)
{
}

XmlNode::~XmlNode()
{
  this->delete_all_children();
}

void XmlNode::delete_all_children()
{
  for (auto& child: this->children)
    {
      delete child;
    }
  this->children.clear();
}

void XmlNode::set_attribute(const std::string& name, const std::string& value)
{
  this->attributes[name] = value;
}

void XmlNode::set_tail(const std::string& data)
{
  this->tail = xml_escape(data);
}

void XmlNode::add_to_tail(const std::string& data)
{
  this->tail += xml_escape(data);
}

void XmlNode::set_inner(const std::string& data)
{
  this->inner = xml_escape(data);
}

void XmlNode::add_to_inner(const std::string& data)
{
  this->inner += xml_escape(data);
}

std::string XmlNode::get_inner() const
{
  return xml_unescape(this->inner);
}

std::string XmlNode::get_tail() const
{
  return xml_unescape(this->tail);
}

XmlNode* XmlNode::get_child(const std::string& name, const std::string& xmlns) const
{
  for (auto& child: this->children)
    {
      if (child->name == name && child->get_tag("xmlns") == xmlns)
        return child;
    }
  return nullptr;
}

std::vector<XmlNode*> XmlNode::get_children(const std::string& name, const std::string& xmlns) const
{
  std::vector<XmlNode*> res;
  for (auto& child: this->children)
    {
      if (child->name == name && child->get_tag("xmlns") == xmlns)
        res.push_back(child);
    }
  return res;
}

XmlNode* XmlNode::add_child(XmlNode* child)
{
  child->parent = this;
  this->children.push_back(child);
  return child;
}

XmlNode* XmlNode::add_child(XmlNode&& child)
{
  XmlNode* new_node = new XmlNode(std::move(child));
  return this->add_child(new_node);
}

XmlNode* XmlNode::get_last_child() const
{
  return this->children.back();
}

void XmlNode::close()
{
  if (this->closed)
    throw std::runtime_error("Closing an already closed XmlNode");
  this->closed = true;
}

XmlNode* XmlNode::get_parent() const
{
  return this->parent;
}

void XmlNode::set_name(const std::string& name)
{
  this->name = name;
}

const std::string XmlNode::get_name() const
{
  return this->name;
}

std::string XmlNode::to_string() const
{
  std::string res("<");
  res += this->name;
  for (const auto& it: this->attributes)
    res += " " + it.first + "='" + sanitize(it.second) + "'";
  if (this->closed && !this->has_children() && this->inner.empty())
    res += "/>";
  else
    {
      res += ">" + sanitize(this->inner);
      for (const auto& child: this->children)
        res += child->to_string();
      if (this->closed)
        {
          res += "</" + this->get_name() + ">";
        }
    }
  res += sanitize(this->tail);
  return res;
}

bool XmlNode::has_children() const
{
  return !this->children.empty();
}

const std::string XmlNode::get_tag(const std::string& name) const
{
  try
    {
      const auto& value = this->attributes.at(name);
      return value;
    }
  catch (const std::out_of_range& e)
    {
      return "";
    }
}

bool XmlNode::del_tag(const std::string& name)
{
  if (this->attributes.erase(name) != 0)
    return true;
  return false;
}

std::string& XmlNode::operator[](const std::string& name)
{
  return this->attributes[name];
}

std::string sanitize(const std::string& data)
{
  if (utils::is_valid_utf8(data.data()))
    return xml_escape(utils::remove_invalid_xml_chars(data));
  else
    return xml_escape(utils::remove_invalid_xml_chars(utils::convert_to_utf8(data, "ISO-8859-1")));
}

D src/xmpp/xmpp_stanza.hpp => src/xmpp/xmpp_stanza.hpp +0 -160
@@ 1,160 0,0 @@
#ifndef XMPP_STANZA_INCLUDED
# define XMPP_STANZA_INCLUDED

#include <unordered_map>
#include <string>
#include <vector>

std::string xml_escape(const std::string& data);
std::string xml_unescape(const std::string& data);
std::string sanitize(const std::string& data);

/**
 * Represent an XML node. It has
 * - A parent XML node (in the case of the first-level nodes, the parent is
     nullptr)
 * - zero, one or more children XML nodes
 * - A name
 * - A map of attributes
 * - inner data (text inside the node)
 * - tail data (text just after the node)
 */
class XmlNode
{
public:
  explicit XmlNode(const std::string& name, XmlNode* parent);
  explicit XmlNode(const std::string& name);
  XmlNode(XmlNode&& node):
    name(std::move(node.name)),
    parent(node.parent),
    closed(node.closed),
    attributes(std::move(node.attributes)),
    children(std::move(node.children)),
    inner(std::move(node.inner)),
    tail(std::move(node.tail))
  {
    node.parent = nullptr;
  }
  /**
   * The copy constructor do not copy the parent attribute. The children
   * nodes are all copied recursively.
   */
  XmlNode(const XmlNode& node):
    name(node.name),
    parent(nullptr),
    closed(node.closed),
    attributes(node.attributes),
    children{},
    inner(node.inner),
    tail(node.tail)
  {
    for (XmlNode* child: node.children)
      {
        XmlNode* child_copy = new XmlNode(*child);
        this->add_child(child_copy);
      }
  }

  ~XmlNode();

  void delete_all_children();
  void set_attribute(const std::string& name, const std::string& value);
  /**
   * Set the content of the tail, that is the text just after this node
   */
  void set_tail(const std::string& data);
  /**
   * Append the given data to the content of the tail. This exists because
   * the expat library may provide the complete text of an element in more
   * than one call
   */
  void add_to_tail(const std::string& data);
  /**
   * Set the content of the inner, that is the text inside this node.
   */
  void set_inner(const std::string& data);
  /**
   * Append the given data to the content of the inner. For the reason
   * described in add_to_tail comment.
   */
  void add_to_inner(const std::string& data);
  /**
   * Get the content of inner
   */
  std::string get_inner() const;
  /**
   * Get the content of the tail
   */
  std::string get_tail() const;
  /**
   * Get a pointer to the first child element with that name and that xml namespace
   */
  XmlNode* get_child(const std::string& name, const std::string& xmlns) const;
  /**
   * Get a vector of all the children that have that name and that xml namespace.
   */
  std::vector<XmlNode*> get_children(const std::string& name, const std::string& xmlns) const;
  /**
   * Add a node child to this node. Assign this node to the child’s parent.
   * Returns a pointer to the newly added child.
   */
  XmlNode* add_child(XmlNode* child);
  XmlNode* add_child(XmlNode&& child);
  /**
   * Returns the last of the children. If the node doesn't have any child,
   * the behaviour is undefined. The user should make sure this is the case
   * by calling has_children() for example.
   */
  XmlNode* get_last_child() const;
  /**
   * Mark this node as closed, nothing else
   */
  void close();
  XmlNode* get_parent() const;
  void set_name(const std::string& name);
  const std::string get_name() const;
  /**
   * Serialize the stanza into a string
   */
  std::string to_string() const;
  /**
   * Whether or not this node has at least one child (if not, this is a leaf
   * node)
   */
  bool has_children() const;
  /**
   * Gets the value for the given attribute, returns an empty string if the
   * node as no such attribute.
   */
  const std::string get_tag(const std::string& name) const;
  /**
   * Remove the attribute of the node. Does nothing if that attribute is not
   * present. Returns true if the tag was removed, false if it was absent.
   */
  bool del_tag(const std::string& name);
  /**
   * Use this to set an attribute's value, like node["id"] = "12";
   */
  std::string& operator[](const std::string& name);

private:
  std::string name;
  XmlNode* parent;
  bool closed;
  std::unordered_map<std::string, std::string> attributes;
  std::vector<XmlNode*> children;
  std::string inner;
  std::string tail;

  XmlNode& operator=(const XmlNode&) = delete;
  XmlNode& operator=(XmlNode&&) = delete;
};

/**
 * An XMPP stanza is just an XML node of level 2 in the XMPP document (the
 * level 1 ones are the <stream::stream/>, and the ones above 2 are just the
 * content of the stanzas)
 */
typedef XmlNode Stanza;

#endif // XMPP_STANZA_INCLUDED