diff --git a/Tests/xmppclientTest.php b/Tests/xmppclientTest.php
new file mode 100644
index 0000000..5942407
--- /dev/null
+++ b/Tests/xmppclientTest.php
@@ -0,0 +1,55 @@
+
+ */
+
+/** Test the domframework xmppclient part */
+class xmppclientTest extends PHPUnit_Framework_TestCase
+{
+ public function test_connection_BADNAME ()
+ {
+ $this->expectException ("Exception");
+ $xmppclient = new xmppclient ();
+ $res = $xmppclient->connect ("NOTFOUND.fournier38.fr", 5222, "", "");
+ }
+
+ public function test_connection_authenticate_1 ()
+ {
+ $xmppclient = new xmppclient ();
+ $res = $xmppclient->connect ("xmpp.fournier38.fr", 5222,
+ "testxmpp@xmpp.fournier38.fr", "LSqmBXDUZWxk");
+ $this->assertSame (is_object ($res), true);
+ }
+
+ public function test_connection_disco_1 ()
+ {
+ $xmppclient = new xmppclient ();
+ $xmppclient->connect ("xmpp.fournier38.fr", 5222,
+ "testxmpp@xmpp.fournier38.fr", "LSqmBXDUZWxk");
+ $res = $xmppclient->discoveryService ();
+ $this->assertSame (is_array ($res) && count ($res) > 5, true);
+ }
+
+ public function test_connection_sendMessage_1 ()
+ {
+ $xmppclient = new xmppclient ();
+ $xmppclient->connect ("xmpp.fournier38.fr", 5222,
+ "testxmpp@xmpp.fournier38.fr", "LSqmBXDUZWxk");
+ $res = $xmppclient->sendMessagePrivate ("dominique@fournier38.fr",
+ "DFW TEST XMPP : test_connection_sendMessage_1");
+ $this->assertSame (is_object ($res), true);
+ }
+
+ public function test_connection_sendMessage_2 ()
+ {
+ $xmppclient = new xmppclient ();
+ $xmppclient->connect ("xmpp.fournier38.fr", 5222,
+ "testxmpp@xmpp.fournier38.fr", "LSqmBXDUZWxk");
+ $res = $xmppclient->sendMessagePrivate ("dominique@fournier38.fr",
+ "DFW TEST XMPP : test_connection_sendMessage_2 : MESSAGE 1");
+ $res = $xmppclient->sendMessagePrivate ("dominique@fournier38.fr",
+ "DFW TEST XMPP : test_connection_sendMessage_2 : MESSAGE 2");
+ $this->assertSame (is_object ($res), true);
+ }
+}
diff --git a/xmppclient.php b/xmppclient.php
new file mode 100644
index 0000000..3600cc9
--- /dev/null
+++ b/xmppclient.php
@@ -0,0 +1,241 @@
+
+ */
+
+require_once ("domframework/tcpclient.php");
+
+/** This class allow to send XMPP messages to a server.
+ * If the server supports it, crypt the connection by StartTLS before auth
+ */
+class xmppclient
+{
+ // CLASS CONSTANT //
+ // {{{
+ /** One send command each $RATE µs maximum
+ */
+ const RATE = 30000;
+ // }}}
+
+ // PROPERTIES //
+ // {{{
+ /** The connection socket when connected
+ */
+ private $sock;
+
+ /** The XML stream exchange string
+ */
+ private $xmlStream;
+
+ /** The debug state
+ */
+ private $debug = false;
+
+ /* The object identifier increment
+ */
+ private $id = 0;
+
+ /* The JID provided by the user
+ */
+ private $jid = "";
+
+ /* Ratelimiter of send commands to server. Store the last timestamp, in
+ * microseconds.
+ */
+ private $sendLastTime = 0;
+ // }}}
+
+ // PUBLIC METHODS //
+ /** Connect to XMPP server $server, on port $port (5222 by default)
+ * @param string $server The XMPP server
+ * @param integer $port The port to use (5222 by default)
+ * @param string $login The login to use
+ * @param string $password The password to use
+ * @param boolean|null $debug Debug the socket on stderr
+ * @return $this
+ */
+ public function connect ($server, $port, $login, $password,
+ $debug = false)
+ // {{{
+ {
+ // To have a really one microsecond precision in microtime function
+ ini_set ("precision", 16);
+ $this->debug = $debug;
+ $this->sock = new tcpclient ($server, $port);
+ $this->sock->readMode ("binary");
+ $client = gethostname ();
+ @list ($user, $domain) = explode ("@", $login, 2);
+ if ($domain === null)
+ $domain = $server;
+ $this->xmlStream = "".
+ "";
+ $this->sock->connect ();
+ $this->debug ("Info: Connected to '$server:$port'");
+ $this->send ($this->xmlStream);
+ $xml = $this->read ();
+ // Check if we can/must go in StartTLS
+ preg_match ("#(.*)<\/starttls>#", $xml, $tls);
+ if (count ($tls))
+ {
+ $this->send ("");
+ $xml = $this->read ();
+ // Can not use the tcpclient part : the certificate is provided by domain,
+ // not by the XMPP server globally
+ $options = array ("peer_name" => $domain);
+ $this->sock->cryptoEnable (true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
+ $options);
+ $this->debug ("Info: Crypto enabled");
+ }
+ $this->send ($this->xmlStream);
+ $xml = $this->read ();
+ preg_match ("#(.*)<\/mechanism>#", $xml, $auths);
+ if (! key_exists (1, $auths))
+ return null;
+ $method = strtoupper ($auths[1]);
+ if ($method === "PLAIN")
+ {
+ $val = base64_encode ($login.chr(0).$user.chr(0).$password);
+ }
+ else
+ throw new \Exception (sprintf (dgettext ("domframework",
+ "XMPP Client : unknown authentication method requested : %s"),
+ $method));
+ $this->send ("$val");
+ $xml = $this->read ();
+ preg_match ("#(.+)#", $xml, $error);
+ if (count ($match) === 0)
+ throw new \Exception (sprintf (dgettext ("domframework",
+ "XMPP Client : Authentication rejected for user '%s' : %s"),
+ $login, $error[1]), 401);
+ $this->debug ("Info: User '$login' authenticated");
+ $this->send ($this->xmlStream);
+ $xml = $this->read ();
+ // $xml : Here we can have the session.
+ $this->send ("".
+ "".
+ "".__CLASS__."");
+ $xml = $this->read ();
+ preg_match ("#(.+)#", $xml, $jids);
+ if (! key_exists (1, $jids))
+ throw new \Exception (dgettext ("domframework",
+ "XMPP Client : can not get the JID from the server"), 500);
+ $this->debug ("Info: Get JID => ".$jids[1]);
+ $this->jid = $jids[1];
+ $this->sock->timeout (5);
+ return $this;
+ }
+ // }}}
+
+ /** Disconnect from the XMPP server
+ */
+ public function disconnect ()
+ // {{{
+ {
+ if ($this->sock && $this->sock->getSock ())
+ {
+ $this->send ("");
+ $this->sock->disconnect ();
+ }
+ }
+ // }}}
+
+ /** Call the Discovery Service to find the available services on the server
+ * @return array The services available
+ */
+ public function discoveryService ()
+ // {{{
+ {
+ $this->send ("".
+ "");
+ $xml = $this->read ();
+ preg_match_all ("##U", $xml, $features);
+ if (! key_exists (1, $features))
+ throw new \Exception (dgettext ("domframework",
+ "XMPP Client : Can not get the discovery service result from server"),
+ 500);
+ $this->id++;
+ return $features[1];
+ }
+ // }}}
+
+ /** Send a direct message to a recipient. The message subject can be omitted,
+ * as it is not displayed on the clients
+ * @param string $recipient The recipient of the message
+ * @param string $message The message to send
+ * @param string|null $subject The message subject
+ * @return $this
+ */
+ public function sendMessagePrivate ($recipient, $message, $subject = null)
+ // {{{
+ {
+ $this->send ("id' type='chat'>".
+ "$message$subject".
+ "Ishmael".
+ "");
+ $this->id++;
+ return $this;
+ }
+ // }}}
+
+ /** In case of destruction, try to disconnect the server properly
+ */
+ public function __destruct ()
+ // {{{
+ {
+ $this->disconnect ();
+ }
+ // }}}
+
+ /** Each debug message is sent to stderr if $debug is set
+ * @param string $msg The message to display
+ */
+ public function debug ($msg)
+ // {{{
+ {
+ if ($this->debug === false)
+ return;
+ file_put_contents ("php://stderr", $msg."\n");
+ }
+ // }}}
+
+ // PRIVATE METHODS //
+ /** Read fron socket
+ * @return the read value
+ */
+ private function read ()
+ // {{{
+ {
+ $xml = $this->sock->read (4096);
+ $this->debug ("Read: $xml");
+ return $xml;
+ }
+ // }}}
+
+ /** Send the data on socket, add the carriage return
+ * @param $msg The message to send
+ */
+ private function send ($msg)
+ // {{{
+ {
+ $start = microtime (true);
+ $wait = intval (($start - $this->sendLastTime) * 100000);
+ if ($wait > self::RATE)
+ $this->debug ("Send: $msg");
+ else
+ {
+ $sleep = intval (self::RATE - $wait);
+ $this->debug ("Send: $msg");
+ usleep ($sleep);
+ }
+ $this->sendLastTime = microtime (true);
+ return $this->sock->send ($msg."\n");
+ }
+ // }}}
+}