Erebot  latest
A modular IRC bot for PHP 7.0+
LineIO.php
1 <?php
2 /*
3  This file is part of Erebot, a modular IRC bot written in PHP.
4 
5  Copyright © 2010 François Poirotte
6 
7  Erebot is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Erebot is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Erebot. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Erebot;
22 
31 class LineIO
32 {
34  const EOL_WIN = "\r\n";
35 
37  const EOL_OLD_MAC = "\r";
38 
40  const EOL_UNIX = "\n";
41 
43  const EOL_NEW_MAC = "\n";
44 
46  const EOL_ANY = null;
47 
49  protected $eol;
50 
52  protected $socket;
53 
55  protected $sndQueue;
56 
58  protected $rcvQueue;
59 
61  protected $incomingData;
62 
85  public function __construct($eol, $socket = null)
86  {
87  $this->setSocket($socket);
88  $this->setEOL($eol);
89  }
90 
97  public function setSocket($socket)
98  {
99  $this->socket = $socket;
100  $this->incomingData = "";
101  $this->rcvQueue = array();
102  $this->sndQueue = array();
103  }
104 
113  public function getSocket()
114  {
115  return $this->socket;
116  }
117 
130  public function setEOL($eol)
131  {
132  $eols = array(
133  self::EOL_WIN,
134  self::EOL_OLD_MAC,
135  self::EOL_UNIX,
136  self::EOL_ANY,
137  );
138  if (!in_array($eol, $eols)) {
139  throw new \Erebot\InvalidValueException('Invalid EOL mode');
140  }
141  if ($eol === self::EOL_ANY) {
142  $eol = array("\r\n", "\r", "\n");
143  } else {
144  $eol = array($eol);
145  }
146 
147  $this->eol = $eol;
148  }
149 
157  public function getEOL()
158  {
159  return $this->eol;
160  }
161 
175  protected function getLine()
176  {
177  $pos = false;
178  foreach ($this->eol as $eol) {
179  $pos = strpos($this->incomingData, $eol);
180  if ($pos !== false) {
181  break;
182  }
183  }
184  if ($pos === false) {
185  return false;
186  }
187 
188  $len = strlen($eol);
189  $line = \Erebot\Utils::toUTF8(substr($this->incomingData, 0, $pos));
190  $this->incomingData = substr($this->incomingData, $pos + $len);
191  $this->rcvQueue[] = $line;
192 
193  $logger = \Plop\Plop::getInstance();
194  $logger->debug(
195  '%(line)s',
196  array('line' => addcslashes($line, "\000..\037"))
197  );
198  return true;
199  }
200 
216  public function read()
217  {
218  if ($this->socket === null) {
219  return false;
220  }
221 
222  if (feof($this->socket)) {
223  return false;
224  }
225 
226  $received = fread($this->socket, 4096);
227  if ($received === false) {
228  return false;
229  }
230  $this->incomingData .= $received;
231 
232  // Workaround for issue #8.
233  $metadata = stream_get_meta_data($this->socket);
234  if ($metadata['stream_type'] == 'tcp_socket/ssl' && !feof($this->socket)) {
235  $blocking = (int) $metadata['blocked'];
236  stream_set_blocking($this->socket, 0);
237  $received = fread($this->socket, 4096);
238  stream_set_blocking($this->socket, $blocking);
239 
240  if ($received !== false) {
241  $this->incomingData .= $received;
242  }
243  }
244 
245  // Read all messages currently in the input buffer.
246  while ($this->getLine()) {
247  ; // Nothing more to do.
248  }
249  return true;
250  }
251 
262  public function pop()
263  {
264  if (count($this->rcvQueue)) {
265  return array_shift($this->rcvQueue);
266  }
267  return null;
268  }
269 
279  public function push($line)
280  {
281  if ($this->socket === null) {
282  throw new \Erebot\IllegalActionException('Uninitialized socket');
283  }
284 
285  if (strcspn($line, "\r\n") != strlen($line)) {
286  throw new \Erebot\InvalidValueException(
287  'Line contains forbidden characters'
288  );
289  }
290  $this->sndQueue[] = $line;
291  }
292 
306  public function write()
307  {
308  if (!count($this->sndQueue)) {
309  return false;
310  }
311 
312  $line = array_shift($this->sndQueue);
313  $logger = \Plop\Plop::getInstance();
314 
315  // Make sure we send the whole line,
316  // with a trailing CR LF sequence.
317  $eol = $this->eol[count($this->eol) - 1];
318  $line .= $eol;
319  $len = strlen($line);
320  for ($written = 0; $written < $len; $written += $fwrite) {
321  $fwrite = @fwrite($this->socket, substr($line, $written));
322  if ($fwrite === false) {
323  return false;
324  }
325  }
326  $line = substr($line, 0, -strlen($eol));
327  $logger->debug(
328  '%(line)s',
329  array('line' => addcslashes($line, "\000..\037"))
330  );
331  return $written;
332  }
333 
344  public function inReadQueue()
345  {
346  return count($this->rcvQueue);
347  }
348 
359  public function inWriteQueue()
360  {
361  return count($this->sndQueue);
362  }
363 }
$rcvQueue
A FIFO queue for incoming messages.
Definition: LineIO.php:58
setEOL($eol)
Definition: LineIO.php:130
$sndQueue
A FIFO queue for outgoing messages.
Definition: LineIO.php:55
setSocket($socket)
Definition: LineIO.php:97
$eol
Line-ending mode in use.
Definition: LineIO.php:49
push($line)
Definition: LineIO.php:279
A class that provides a line-by-line reader.
Definition: LineIO.php:31
$socket
The underlying socket, represented as a stream.
Definition: LineIO.php:52
__construct($eol, $socket=null)
Definition: LineIO.php:85
$incomingData
A raw buffer for incoming data.
Definition: LineIO.php:61