Permalink
Browse files

Initial commit of the Net_IRC class

# Alpha state


git-svn-id: http://svn.php.net/repository/pear/packages/Net_IRC/trunk@79719 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information...
tvvcox committed Apr 24, 2002
1 parent c8210f2 commit 84867c39bc78256e05e0d999c5c256148f49d0e8
Showing with 702 additions and 0 deletions.
  1. +377 −0 IRC.php
  2. +109 −0 examples/chan_copy.php
  3. +128 −0 examples/client.php
  4. +31 −0 examples/stats.php
  5. +41 −0 package.xml
  6. +16 −0 tests/parse_msg.php
View
377 IRC.php
@@ -0,0 +1,377 @@
+<?php
+// /* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 4 |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 2002 The PHP Group |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 2.02 of the PHP license, |
+// | that is bundled with this package in the file LICENSE, and is |
+// | available at through the world-wide-web at |
+// | http://www.php.net/license/2_02.txt. |
+// | If you did not receive a copy of the PHP license and are unable to |
+// | obtain it through the world-wide-web, please send a note to |
+// | license@php.net so we can mail you a copy immediately. |
+// +----------------------------------------------------------------------+
+// | Author: Tomas V.V.Cox <cox@idecnet.com> |
+// | |
+// +----------------------------------------------------------------------+
+//
+// $Id$
+
+/**
+* Class for handling the client side of the IRC protocol (RFC 1459)
+*
+* @author Tomas V.V.Cox <cox@idecnet.com>
+*/
+class Net_IRC
+{
+ var $options = array();
+ /**
+ * logging error types
+ * 0 fatal
+ * 1 warning
+ * 2 notice
+ * 3 informative
+ * 4 debug
+ * 5 debug++
+ * @var array $log_types
+ * @see Net_IRC::logTypes
+ */
+ var $log_types = array(0, 1, 2, 3, 4);
+ var $buffer = array();
+ var $stats = array();
+
+ function connect($options)
+ {
+ // XXX Check options
+ if (isset($options['log_types'])) {
+ $this->logTypes($options['log_types']);
+ }
+ $this->log(3, "connecting to {$options['server']}:{$options['port']}");
+ $sd = fsockopen($options['server'], $options['port'],
+ $errno, $errstr, 5);
+ if (!$sd) {
+ $this->log(0, "could not connect $errstr ($errno)");
+ return false;
+ }
+ $this->log(3, "connected");
+ $this->initStats();
+ $this->socket = $sd;
+ $this->command('USER '.
+ $options['identd'] . ' '.
+ $options['host'] . ' '.
+ $options['server'] . ' '.
+ ':' . $options['realname']);
+ $this->command('NICK ' . $options['nick']);
+ $this->loopRead('MOTD', false);
+ $this->callback('CONNECT', false);
+ socket_set_blocking($sd, false);
+ $this->options = $options;
+ return true;
+ }
+
+ function disconnect()
+ {
+ $this->command('QUIT');
+ fclose($this->socket);
+ $this->socket = null;
+ }
+
+ /**
+ * @param int $code Search the value for a code
+ * @param string $value Search the code for a value
+ */
+ function getEvent($code = null, $handler = null)
+ {
+ static $events;
+ if (empty($events)) {
+ $events = array(
+ 376 => 'MOTD',
+ 422 => 'MOTD',
+ 366 => 'NAMES',
+ 318 => 'WHOIS'
+ );
+ }
+ if ($code) {
+ return isset($events[$code]) ? $events[$code] : $code;
+ }
+ foreach ($events as $k => $e) {
+ if ($handler == $e) {
+ return $events[$k];
+ }
+ }
+ return false;
+ }
+
+ function command($command)
+ {
+ $this->write(trim($command));
+ }
+
+ function write($command)
+ {
+ if (feof($this->socket)) {
+ $this->log(0, 'Write Disconnected');
+ $this->callback('DISCONNECT', false);
+ return false;
+ }
+
+ if ($command && !fputs($this->socket, $command . "\r\n")) {
+ $this->log(1, "could not write to socket");
+ return false;
+ } else {
+ $this->log(4, "<- $command");
+ }
+ return true;
+ }
+
+ // XXX rename $once -> $block
+ function read($once = false)
+ {
+ if (feof($this->socket)) {
+ $this->log(0, 'Read Disconnected');
+ $this->callback('DISCONNECT', false);
+ return false;
+ }
+ do {
+ $receive = rtrim(fgets($this->socket, 1024));
+ // XXX Only update stats for example each 2 seconds
+ $this->updateStats();
+ if (!$receive && $once) {
+ break;
+ }
+ if (!$receive && !$once) {
+ usleep(500000); // Half second is enough interactive
+ continue;
+ }
+ if ($receive) {
+ $this->log(4, "-> $receive");
+ // XXX Return direclty the parsed response
+ $result = $this->parseResponse($receive);
+ $this->updateStats($result[0], $result[1]);
+ if ($result[0] == 'PING') {
+ $this->callback('ping', $result[1]);
+ $receive = false;
+ }
+ if ($result[0] == 'ERROR') {
+ $this->callback('error', $result[1]);
+ }
+ }
+ } while (!$receive);
+ return $receive;
+ }
+
+ // XXX Clean-up the params & rename $once -> $block
+ function loopRead($only_this_event = null, $continue = true, $once = false)
+ {
+ while ($response = $this->read($once)) {
+ $result = $this->parseResponse($response);
+ $event = $result[0];
+ if (is_numeric($event)) {
+ $event = $this->getEvent($event);
+ }
+ if (!$only_this_event || ($only_this_event == $event)) {
+ $this->callback($event, $result[1]);
+ if (!$continue) {
+ break;
+ }
+ }
+ }
+ }
+
+ function parseResponse($response)
+ {
+ /*
+ <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
+ $origin!$orighost $command $target $params
+ */
+ $message = explode(' ', $response, 2);
+ if ($message[0]{0} == ':') {
+ // parse prefix
+ $prefix = substr($message[0], 1);
+ if (strpos($prefix, '!') !== false) {
+ list($origin, $orighost) = explode('!', $prefix, 2);
+ } else {
+ $origin = $prefix;
+ $orighost = null;
+ }
+ list($command, $rest) = explode(' ', $message[1], 2);
+ // foo :bar
+ if (strpos($rest, ' :') !== false) {
+ list($target, $params) = explode(' :', $rest, 2);
+ // :bar
+ } elseif ($rest{0} == ':') {
+ $target = substr($rest, 1);
+ $params = null;
+ // foo
+ } else {
+ $target = $rest;
+ $params = null;
+ }
+ } else {
+ $origin = null;
+ $orighost = null;
+ $command = $message[0];
+ $target = null;
+ $params = substr($message[1], 1);
+ }
+ return array($command, array($origin, $orighost, $target, $params));
+ }
+
+ function callback($command, $params = array())
+ {
+ $method = "event_$command";
+ if (method_exists($this, $method)) {
+ $this->log(5, "Calling callback $method");
+ return call_user_func_array(array(&$this, $method), $params);
+ }
+ if ($params) {
+ $this->log(4, "Method $method not provided, calling fallback");
+ return call_user_func_array(array(&$this, 'fallback'), $params);
+ }
+ }
+
+ // XXX Introduce stats levels (none, normal, full)
+ function updateStats($event = null, $args = array())
+ {
+ if ($event) {
+ $this->stats['rx_idle'] = 0;
+
+ if (isset($this->stats['events'][$event])) {
+ $item = &$this->stats['events'][$event];
+ $this->log(5, "Updating event $event");
+ $this->updateEventStats($item, $args);
+ } else {
+ $this->stats['events'][$event] = array();
+ $this->stats['events'][$event]['times'] = 1;
+ $this->stats['events'][$event]['interval'] = 1;
+ $this->stats['events'][$event]['last'] = time();
+ }
+ } else {
+ $this->stats['rx_idle'] += time() - $this->stats['last_updated'];
+ $this->log(6, "Updating idle time to: " . $this->stats['rx_idle']);
+ }
+ $this->stats['last_updated'] = time();
+ }
+
+ // XXX This should be enhanced to be able to track the different
+ // kinds of flood attacks (maybe in a different class)
+ function updateEventStats(&$event, $args = array())
+ {
+ $event['times'] += 1;
+ // XXX make a configurable param
+ $int = 60;
+ if ((time() - $event['last']) < $int) {
+ $event['interval'] += 1;
+ } else {
+ $event['interval'] = 0;
+ }
+ $this->log(5, "event interval: " . $event['interval']);
+ array_pop($args);
+ foreach ($args as $k => $v) {
+ if ($v) {
+ $event[$k][$v] = isset($event[$k][$v]) ? $event[$k][$v] + 1 : 1;
+ // To avoid stats flooding we only track the last 30 different ones
+ if (count($event[$k]) > 30) {
+ $this->log(5, "Dropping key ($k) param");
+ array_shift($event[$k]);
+ }
+ }
+ }
+ $event['last'] = time();
+ }
+
+ function initStats()
+ {
+ $this->stats['rx_idle'] = 0;
+ $this->stats['last_updated'] = time();
+ $this->stats['events'] = array();
+ $this->stats['started'] = time();
+ }
+
+ function getStats($label = null)
+ {
+ $this->stats['running'] = time() - $this->stats['started'];
+ if ($label) {
+ return isset($this->stats[$label]) ? $this->stats[$label] : false;
+ }
+ return $this->stats;
+ }
+
+ function log($level, $message)
+ {
+ if (in_array($level, $this->log_types)) {
+ print date('H:i:s') . " " . trim($message) . "\n"; flush();
+ }
+ }
+
+ /**
+ * Sets which type of messages will be logged
+ * @param mixed $codes int one code or array multiple codes
+ */
+ function logTypes($codes = array())
+ {
+ settype($codes, 'array');
+ $this->log_types = $codes;
+ }
+
+ function getOption($option)
+ {
+ return isset($this->options[$option]) ? $this->options[$option] : null;
+ }
+
+ /**
+ * Method to feed the clas with external information we want to
+ * access from inside (for avoiding the "global" uglyness)
+ *
+ */
+ function setExtra($extra)
+ {
+ $this->extra = $extra;
+ }
+
+ function getExtra($label = null)
+ {
+ if ($label) {
+ return isset($this->extra[$label]) ? $this->extra[$label] : null;
+ }
+ return $this->extra;
+ }
+
+}
+
+/**
+* Basic class for handling the callbacks (events). This class should
+* be extended by the user
+*/
+class Net_IRC_Event extends Net_IRC
+{
+ function event_error($origin, $orighost, $target, $params)
+ {
+ $this->log(0, "Error ocurred ($origin, $orighost, $target, $params)");
+ // XXX add error handling
+ }
+
+ function event_ping($origin, $orighost, $target, $params)
+ {
+ $this->command("PONG :$params");
+ }
+
+ function fallback($origin, $orighost, $target, $params)
+ {
+ $this->buffer[] = array($origin, $target, $params);
+ // Only store the last 25 lines
+ if (count($this->buffer) > 25) {
+ array_shift($this->buffer);
+ }
+ }
+
+ function &getBuffer()
+ {
+ $buff = $this->buffer;
+ $this->buffer = array();
+ return $buff;
+ }
+}
+?>
Oops, something went wrong.

0 comments on commit 84867c3

Please sign in to comment.