XMPP Client : basic message send to XMPP server

git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5912 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
2020-04-14 11:45:56 +00:00
parent b2d343afad
commit ac218caaea
2 changed files with 296 additions and 0 deletions

55
Tests/xmppclientTest.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
/** 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);
}
}

241
xmppclient.php Normal file
View File

@@ -0,0 +1,241 @@
<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
*/
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 = "<?xml version='1.0' encoding='UTF-8'?".">".
"<stream:stream version='1.0' ".
"xmlns:stream='http://etherx.jabber.org/streams' ".
"xmlns='jabber:client' ".
"to='$domain' from='$client'>";
$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.*?".">(.*)<\/starttls>#", $xml, $tls);
if (count ($tls))
{
$this->send ("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
$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.*>(.*)<\/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 ("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' ".
"mechanism='$method'>$val</auth>");
$xml = $this->read ();
preg_match ("#<success #", $xml, $match);
preg_match ("#<text>(.+)</text>#", $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 ("<iq id='netjabber-$this->id' type='set'>".
"<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>".
"<resource>".__CLASS__."</resource></bind></iq>");
$xml = $this->read ();
preg_match ("#<jid>(.+)</jid>#", $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 ("</stream:stream>");
$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 ("<iq id='disco-$this->id' type='get'>".
"<query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
$xml = $this->read ();
preg_match_all ("#<feature .*var='(\S+)'/>#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 ("<message from='$this->jid' to='$recipient' ".
"id='message-$this->id' type='chat'>".
"<body>$message</body><subject>$subject</subject>".
"<nick xmlns='http://jabber.org/protocol/nick'>Ishmael</nick>".
"</message>");
$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");
}
// }}}
}