540 lines
12 KiB
PHP
Executable File
540 lines
12 KiB
PHP
Executable File
<?php
|
|
/* KE Software Open Source Licence
|
|
**
|
|
** Notice: Copyright (c) 2011-2013 KE SOFTWARE PTY LTD (ACN 006 213 298)
|
|
** (the "Owner"). All rights reserved.
|
|
**
|
|
** Licence: Permission is hereby granted, free of charge, to any person
|
|
** obtaining a copy of this software and associated documentation files
|
|
** (the "Software"), to deal with the Software without restriction,
|
|
** including without limitation the rights to use, copy, modify, merge,
|
|
** publish, distribute, sublicense, and/or sell copies of the Software,
|
|
** and to permit persons to whom the Software is furnished to do so,
|
|
** subject to the following conditions.
|
|
**
|
|
** Conditions: The Software is licensed on condition that:
|
|
**
|
|
** (1) Redistributions of source code must retain the above Notice,
|
|
** these Conditions and the following Limitations.
|
|
**
|
|
** (2) Redistributions in binary form must reproduce the above Notice,
|
|
** these Conditions and the following Limitations in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
**
|
|
** (3) Neither the names of the Owner, nor the names of its contributors
|
|
** may be used to endorse or promote products derived from this
|
|
** Software without specific prior written permission.
|
|
**
|
|
** Limitations: Any person exercising any of the permissions in the
|
|
** relevant licence will be taken to have accepted the following as
|
|
** legally binding terms severally with the Owner and any other
|
|
** copyright owners (collectively "Participants"):
|
|
**
|
|
** TO THE EXTENT PERMITTED BY LAW, THE SOFTWARE IS PROVIDED "AS IS",
|
|
** WITHOUT ANY REPRESENTATION, WARRANTY OR CONDITION OF ANY KIND, EXPRESS
|
|
** OR IMPLIED, INCLUDING (WITHOUT LIMITATION) AS TO MERCHANTABILITY,
|
|
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. TO THE EXTENT
|
|
** PERMITTED BY LAW, IN NO EVENT SHALL ANY PARTICIPANT BE LIABLE FOR ANY
|
|
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
** SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
|
|
**
|
|
** WHERE BY LAW A LIABILITY (ON ANY BASIS) OF ANY PARTICIPANT IN RELATION
|
|
** TO THE SOFTWARE CANNOT BE EXCLUDED, THEN TO THE EXTENT PERMITTED BY
|
|
** LAW THAT LIABILITY IS LIMITED AT THE OPTION OF THE PARTICIPANT TO THE
|
|
** REPLACEMENT, REPAIR OR RESUPPLY OF THE RELEVANT GOODS OR SERVICES
|
|
** (INCLUDING BUT NOT LIMITED TO SOFTWARE) OR THE PAYMENT OF THE COST OF SAME.
|
|
*/
|
|
require_once __DIR__ . '/IMu.php';
|
|
require_once IMu::$api . '/Exception.php';
|
|
require_once IMu::$api . '/Trace.php';
|
|
|
|
class IMuStream
|
|
{
|
|
/* Static Properties */
|
|
public static function
|
|
getBlockSize()
|
|
{
|
|
return self::$_blockSize;
|
|
}
|
|
|
|
public static function
|
|
setBlockSize(int $size)
|
|
{
|
|
self::$_blockSize = $size;
|
|
}
|
|
|
|
/* Constructor */
|
|
public function
|
|
__construct($socket)
|
|
{
|
|
$this->_socket = $socket;
|
|
|
|
$this->_next = '';
|
|
$this->_token = null;
|
|
$this->_string = null;
|
|
$this->_file = null;
|
|
|
|
$this->_buffer = '';
|
|
$this->_length = 0;
|
|
}
|
|
|
|
/* Methods */
|
|
public function
|
|
get()
|
|
{
|
|
$what = null;
|
|
try
|
|
{
|
|
$this->getNext();
|
|
$this->getToken();
|
|
$what = $this->getValue();
|
|
}
|
|
catch (IMuException $e)
|
|
{
|
|
throw $e;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new IMuException("StreamGet", $e->getMessage());
|
|
}
|
|
return $what;
|
|
}
|
|
|
|
public function
|
|
put($what)
|
|
{
|
|
try
|
|
{
|
|
$this->putValue($what, 0);
|
|
$this->putLine();
|
|
$this->putFlush();
|
|
}
|
|
catch (IMuException $e)
|
|
{
|
|
throw $e;
|
|
}
|
|
catch (Exception $e)
|
|
{
|
|
throw new IMuException("StreamPut", $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private static $_blockSize = 8192;
|
|
|
|
private $_socket;
|
|
|
|
private $_next;
|
|
private $_token;
|
|
private $_string;
|
|
private $_file;
|
|
|
|
private $_buffer;
|
|
private $_length;
|
|
|
|
private function
|
|
getValue()
|
|
{
|
|
if ($this->_token == 'end')
|
|
return null;
|
|
if ($this->_token == 'string')
|
|
return $this->_string;
|
|
if ($this->_token == 'number')
|
|
return $this->_string + 0;
|
|
if ($this->_token == '{')
|
|
{
|
|
$array = array();
|
|
$this->getToken();
|
|
while ($this->_token != '}')
|
|
{
|
|
if ($this->_token == 'string')
|
|
$name = $this->_string;
|
|
else if ($this->_token == 'identifier')
|
|
// Extension - allow simple identifiers
|
|
$name = $this->_string;
|
|
else
|
|
throw new IMuException('StreamSyntaxName', $this->_token);
|
|
|
|
$this->getToken();
|
|
if ($this->_token != ':')
|
|
throw new IMuException('StreamSyntaxColon', $this->_token);
|
|
|
|
$this->getToken();
|
|
$array[$name] = $this->getValue();
|
|
|
|
$this->getToken();
|
|
if ($this->_token == ',')
|
|
$this->getToken();
|
|
}
|
|
return $array;
|
|
}
|
|
if ($this->_token == '[')
|
|
{
|
|
$array = array();
|
|
$this->getToken();
|
|
while ($this->_token != ']')
|
|
{
|
|
$array[] = $this->getValue();
|
|
|
|
$this->getToken();
|
|
if ($this->_token == ',')
|
|
$this->getToken();
|
|
}
|
|
return $array;
|
|
}
|
|
if ($this->_token == 'true')
|
|
return true;
|
|
if ($this->_token == 'false')
|
|
return false;
|
|
if ($this->_token == 'null')
|
|
return null;
|
|
if ($this->_token == 'binary')
|
|
return $this->_file;
|
|
|
|
throw new IMuException('StreamSyntaxToken', $this->_token);
|
|
}
|
|
|
|
private function
|
|
getToken()
|
|
{
|
|
while (ctype_space($this->_next))
|
|
$this->getNext();
|
|
$this->_string = '';
|
|
$this->_file = null;
|
|
if ($this->_next == '"')
|
|
{
|
|
$this->_token = 'string';
|
|
$this->getNext();
|
|
while ($this->_next != '"')
|
|
{
|
|
if ($this->_next == '\\')
|
|
{
|
|
$this->getNext();
|
|
if ($this->_next == 'b')
|
|
$this->_next = "\b";
|
|
else if ($this->_next == 'f')
|
|
$this->_next = "\f";
|
|
else if ($this->_next == 'n')
|
|
$this->_next = "\n";
|
|
else if ($this->_next == 'r')
|
|
$this->_next = "\r";
|
|
else if ($this->_next == 't')
|
|
$this->_next = "\t";
|
|
else if ($this->_next == 'u')
|
|
{
|
|
$this->getNext();
|
|
$str = "";
|
|
for ($i = 0; $i < 4; $i++)
|
|
{
|
|
if (! ctype_xdigit($this->_next))
|
|
break;
|
|
$str .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
if ($str == '')
|
|
throw new IMuException('StreamSyntaxUnicode');
|
|
$this->_next = chr($str);
|
|
}
|
|
}
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
$this->getNext();
|
|
}
|
|
else if (ctype_digit($this->_next) || $this->_next == '-')
|
|
{
|
|
$this->_token = 'number';
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
while (ctype_digit($this->_next))
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
if ($this->_next == '.')
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
while (ctype_digit($this->_next))
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
}
|
|
if ($this->_next == 'e' || $this->_next == 'E')
|
|
{
|
|
$this->_string .= 'e';
|
|
$this->getNext();
|
|
if ($this->_next == '+')
|
|
{
|
|
$this->_string .= '+';
|
|
$this->getNext();
|
|
}
|
|
else if ($this->_next == '-')
|
|
{
|
|
$this->_string .= '-';
|
|
$this->getNext();
|
|
}
|
|
while (ctype_digit($this->_next))
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
}
|
|
}
|
|
else if (ctype_alpha($this->_next) || $this->_next == '_')
|
|
{
|
|
$this->_token = 'identifier';
|
|
while (ctype_alnum($this->_next) || $this->_next == '_')
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
$lower = strtolower($this->_string);
|
|
if ($lower == 'false')
|
|
$this->_token = 'false';
|
|
else if ($lower == 'null')
|
|
$this->_token = 'null';
|
|
else if ($lower == 'true')
|
|
$this->_token = 'true';
|
|
}
|
|
else if ($this->_next == '*')
|
|
{
|
|
// Extension - allow embedded binary data
|
|
$this->_token = 'binary';
|
|
$this->getNext();
|
|
while (ctype_digit($this->_next))
|
|
{
|
|
$this->_string .= $this->_next;
|
|
$this->getNext();
|
|
}
|
|
if ($this->_string == '')
|
|
throw new IMuException('StreamSyntaxBinary');
|
|
$size = $this->_string + 0;
|
|
while ($this->_next != "\n")
|
|
$this->getNext();
|
|
|
|
// Save data into a temporary file
|
|
$temp = tmpfile();
|
|
if ($temp === false)
|
|
throw new IMuException('StreamTempFile', sys_get_temp_dir());
|
|
$left = $size;
|
|
while ($left > 0)
|
|
{
|
|
$read = self::$_blockSize;
|
|
if ($read > $left)
|
|
$read = $left;
|
|
$data = fread($this->_socket, $read);
|
|
if ($data === false)
|
|
throw new IMuException('StreamInput');
|
|
$done = strlen($data);
|
|
if ($done == 0)
|
|
throw new IMuException('StreamEOF', 'binary');
|
|
fwrite($temp, $data);
|
|
$left -= $done;
|
|
}
|
|
fseek($temp, 0, SEEK_SET);
|
|
$this->_file = $temp;
|
|
|
|
$this->getNext();
|
|
}
|
|
else
|
|
{
|
|
$this->_token = $this->_next;
|
|
$this->getNext();
|
|
}
|
|
}
|
|
|
|
private function
|
|
getNext()
|
|
{
|
|
$c = fgetc($this->_socket);
|
|
if ($c === false)
|
|
throw new IMuException('StreamEOF', 'character');
|
|
$this->_next = $c;
|
|
return $this->_next;
|
|
}
|
|
|
|
private function
|
|
putValue($what, $indent)
|
|
{
|
|
$type = gettype($what);
|
|
if ($type == 'NULL')
|
|
$this->putData('null');
|
|
else if ($type == 'string')
|
|
$this->putString($what);
|
|
else if ($type == 'integer')
|
|
$this->putData(sprintf('%d', $what));
|
|
else if ($type == 'double')
|
|
$this->putData(sprintf('%g', $what));
|
|
else if ($type == 'object')
|
|
$this->putObject(get_object_vars($what), $indent);
|
|
else if ($type == 'array')
|
|
{
|
|
/* A bit magical.
|
|
**
|
|
** If the array is empty treat it as an array rather than
|
|
** a JSON object. Also, if the keys of the array are exactly
|
|
** from 0 to count - 1 then put a JSON array otherwise put a
|
|
** JSON object.
|
|
*/
|
|
if (empty($what))
|
|
$this->putArray($what, $indent);
|
|
else if (array_keys($what) === range(0, count($what) - 1))
|
|
$this->putArray($what, $indent);
|
|
else
|
|
$this->putObject($what, $indent);
|
|
}
|
|
else if ($type == 'boolean')
|
|
$this->putData($what ? 'true' : 'false');
|
|
else if ($type == 'resource')
|
|
$this->putResource($what);
|
|
else
|
|
throw new IMuException('StreamType', $type);
|
|
}
|
|
|
|
private function
|
|
putString($what)
|
|
{
|
|
$this->putData('"');
|
|
$what = preg_replace('/\\\\/', '\\\\\\\\', $what);
|
|
$what = preg_replace('/"/', '\\"', $what);
|
|
$this->putData($what);
|
|
$this->putData('"');
|
|
}
|
|
|
|
private function
|
|
putObject($what, $indent)
|
|
{
|
|
$this->putData('{');
|
|
$this->putLine();
|
|
$count = count($what);
|
|
$i = 0;
|
|
foreach ($what as $name => $what)
|
|
{
|
|
$this->putIndent($indent + 1);
|
|
$this->putString($name);
|
|
$this->putData(' : ');
|
|
$this->putValue($what, $indent + 1);
|
|
if ($i < $count - 1)
|
|
$this->putData(',');
|
|
$this->putLine();
|
|
$i++;
|
|
}
|
|
$this->putIndent($indent);
|
|
$this->putData('}');
|
|
}
|
|
|
|
private function
|
|
putArray($what, $indent)
|
|
{
|
|
$this->putData('[');
|
|
$this->putLine();
|
|
$count = count($what);
|
|
$i = 0;
|
|
foreach ($what as $what)
|
|
{
|
|
$this->putIndent($indent + 1);
|
|
$this->putValue($what, $indent + 1);
|
|
if ($i < $count - 1)
|
|
$this->putData(',');
|
|
$this->putLine();
|
|
$i++;
|
|
}
|
|
$this->putIndent($indent);
|
|
$this->putData(']');
|
|
}
|
|
|
|
private function
|
|
putResource($what)
|
|
{
|
|
if (fseek($what, 0, SEEK_END) < 0)
|
|
throw new IMuException('StreamFileSeek');
|
|
$size = ftell($what);
|
|
if (fseek($what, 0, SEEK_SET) < 0)
|
|
throw new IMuException('StreamFileSeek');
|
|
|
|
$this->putData('*');
|
|
$this->putData($size);
|
|
$this->putLine();
|
|
|
|
$left = $size;
|
|
while ($left > 0)
|
|
{
|
|
$need = self::$_blockSize;
|
|
if ($need > $left)
|
|
$need = $left;
|
|
$data = fread($what, $need);
|
|
if ($data === false)
|
|
throw new IMuException('StreamFileRead');
|
|
$done = strlen($data);
|
|
if ($done == 0)
|
|
break;
|
|
$this->putData($data);
|
|
$left -= $done;
|
|
}
|
|
if ($left > 0)
|
|
{
|
|
/* The file did not contain enough bytes
|
|
** so the output is padded with nulls
|
|
*/
|
|
while ($left > 0)
|
|
{
|
|
$need = self::$blockSize;
|
|
if ($need > $left)
|
|
$need = $left;
|
|
$data = str_repeat(chr(0), $need);
|
|
$this->putData($data);
|
|
$left -= $need;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function
|
|
putIndent($indent)
|
|
{
|
|
$string = '';
|
|
for ($i = 0; $i < $indent; $i++)
|
|
$string .= "\t";
|
|
$this->putData($string);
|
|
}
|
|
|
|
private function
|
|
putLine()
|
|
{
|
|
$this->putData("\r\n");
|
|
}
|
|
|
|
private function
|
|
putData($data)
|
|
{
|
|
$this->_buffer .= $data;
|
|
$this->_length += strlen($data);
|
|
if ($this->_length >= self::$_blockSize)
|
|
$this->putFlush();
|
|
}
|
|
|
|
private function
|
|
putFlush()
|
|
{
|
|
if ($this->_length > 0)
|
|
{
|
|
while ($this->_length > 0)
|
|
{
|
|
$wrote = fwrite($this->_socket, $this->_buffer);
|
|
if ($wrote === false)
|
|
throw new IMuException('StreamWriteError');
|
|
if ($wrote == 0)
|
|
throw new IMuException('StreamWriteError');
|
|
$this->_buffer = substr($this->_buffer, $wrote);
|
|
$this->_length -= $wrote;
|
|
}
|
|
fflush($this->_socket);
|
|
$this->_buffer = '';
|
|
$this->_length = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
?>
|