Viewing file: DIME.php (21.82 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
//
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-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. |
// +----------------------------------------------------------------------+
// | Authors: Shane Caraveo <shane@caraveo.com> |
// | Ralf Hofmann <ralf.hofmann@verdisoft.com> |
// +----------------------------------------------------------------------+
//
// $Id: DIME.php,v 1.5 2002/09/29 01:55:16 shane Exp $
//
require_once 'PEAR.php';
/**
*
* DIME Encoding/Decoding
*
* What is it?
* This class enables you to manipulate and build
* a DIME encapsulated message.
*
* http://www.ietf.org/internet-drafts/draft-nielsen-dime-02.txt
*
* 09/18/02 Ralf - A huge number of changes to be compliant
* with the DIME Specification Release 17 June 2002
*
* TODO: lots of stuff needs to be tested.
* Definitily have to go through DIME spec and
* make things work right, most importantly, sec 3.3
* make examples, document
*
* see test/dime_mesage_test.php for example of usage
*
* @author Shane Caraveo <shane@caraveo.com>,
* Ralf Hofmann <ralf.hofmann@verdisoft.com>
* @version $Revision: 1.5 $
* @package Net_DIME
*/
define('NET_DIME_TYPE_UNCHANGED',0x00);
define('NET_DIME_TYPE_MEDIA',0x01);
define('NET_DIME_TYPE_URI',0x02);
define('NET_DIME_TYPE_UNKNOWN',0x03);
define('NET_DIME_TYPE_NONE',0x04);
define('NET_DIME_VERSION',0x0001);
define('NET_DIME_RECORD_HEADER',12);
define('NET_DIME_FLAGS', 0);
define('NET_DIME_OPTS_LEN', 1);
define('NET_DIME_ID_LEN', 2);
define('NET_DIME_TYPE_LEN', 3);
define('NET_DIME_DATA_LEN', 4);
define('NET_DIME_OPTS', 5);
define('NET_DIME_ID', 6);
define('NET_DIME_TYPE', 7);
define('NET_DIME_DATA', 8);
class Net_DIME_Record extends PEAR
{
// these are used to hold the padded length
var $OPTS_LENGTH = 0;
var $ID_LENGTH = 0;
var $TYPE_LENGTH = 0;
var $DATA_LENGTH = 0;
var $_haveOpts = FALSE;
var $_haveID = FALSE;
var $_haveType = FALSE;
var $_haveData = FALSE;
var $debug = FALSE;
var $padstr = "\0";
/**
* Elements
* [NET_DIME_FLAGS], 16 bits: VERSION:MB:ME:CF:TYPE_T
* [NET_DIME_OPTS_LEN], 16 bits: OPTIONS_LENGTH
* [NET_DIME_ID_LEN], 16 bits: ID_LENGTH
* [NET_DIME_TYPE_LEN], 16 bits: TYPE_LENGTH
* [NET_DIME_DATA_LEN], 32 bits: DATA_LENGTH
* [NET_DIME_OPTS] : OPTIONS
* [NET_DIME_ID] : ID
* [NET_DIME_TYPE] : TYPE
* [NET_DIME_DATA] : DATA
*/
var $Elements = array(NET_DIME_FLAGS => 0, NET_DIME_OPTS_LEN => 0,
NET_DIME_ID_LEN => 0, NET_DIME_TYPE_LEN => 0,
NET_DIME_DATA_LEN => 0,
NET_DIME_OPTS => '',
NET_DIME_ID => '',
NET_DIME_TYPE => '',
NET_DIME_DATA => '');
function Net_DIME_Record($debug = FALSE)
{
$this->debug = $debug;
if ($debug) $this->padstr = '*';
}
function setMB()
{
$this->Elements[NET_DIME_FLAGS] |= 0x0400;
}
function setME()
{
$this->Elements[NET_DIME_FLAGS] |= 0x0200;
}
function setCF()
{
$this->Elements[NET_DIME_FLAGS] |= 0x0100;
}
function isChunk()
{
return $this->Elements[NET_DIME_FLAGS] & 0x0100;
}
function isEnd()
{
return $this->Elements[NET_DIME_FLAGS] & 0x0200;
}
function isStart()
{
return $this->Elements[NET_DIME_FLAGS] & 0x0400;
}
function getID()
{
return $this->Elements[NET_DIME_ID];
}
function getType()
{
return $this->Elements[NET_DIME_TYPE];
}
function getData()
{
return $this->Elements[NET_DIME_DATA];
}
function getDataLength()
{
return $this->Elements[NET_DIME_DATA_LEN];
}
function setType($typestring, $type=NET_DIME_TYPE_UNKNOWN)
{
$typelen = strlen($typestring) & 0xFFFF;
$type = $type << 4;
$this->Elements[NET_DIME_FLAGS] = ($this->Elements[NET_DIME_FLAGS] & 0xFF0F) | $type;
$this->Elements[NET_DIME_TYPE_LEN] = $typelen;
$this->TYPE_LENGTH = $this->_getPadLength($typelen);
$this->Elements[NET_DIME_TYPE] = $typestring;
}
function generateID()
{
$id = md5(time());
$this->setID($id);
return $id;
}
function setID($id)
{
$idlen = strlen($id) & 0xFFFF;
$this->Elements[NET_DIME_ID_LEN] = $idlen;
$this->ID_LENGTH = $this->_getPadLength($idlen);
$this->Elements[NET_DIME_ID] = $id;
}
function setData($data, $size=0)
{
$datalen = $size?$size:strlen($data);
$this->Elements[NET_DIME_DATA_LEN] = $datalen;
$this->DATA_LENGTH = $this->_getPadLength($datalen);
$this->Elements[NET_DIME_DATA] = $data;
}
function encode()
{
// insert version
$this->Elements[NET_DIME_FLAGS] = ($this->Elements[NET_DIME_FLAGS] & 0x07FF) | (NET_DIME_VERSION << 11);
// the real dime encoding
$format = '%c%c%c%c%c%c%c%c%c%c%c%c'.
'%'.$this->OPTS_LENGTH.'s'.
'%'.$this->ID_LENGTH.'s'.
'%'.$this->TYPE_LENGTH.'s'.
'%'.$this->DATA_LENGTH.'s';
return sprintf($format,
($this->Elements[NET_DIME_FLAGS]&0x0000FF00)>>8,
($this->Elements[NET_DIME_FLAGS]&0x000000FF),
($this->Elements[NET_DIME_OPTS_LEN]&0x0000FF00)>>8,
($this->Elements[NET_DIME_OPTS_LEN]&0x000000FF),
($this->Elements[NET_DIME_ID_LEN]&0x0000FF00)>>8,
($this->Elements[NET_DIME_ID_LEN]&0x000000FF),
($this->Elements[NET_DIME_TYPE_LEN]&0x0000FF00)>>8,
($this->Elements[NET_DIME_TYPE_LEN]&0x000000FF),
($this->Elements[NET_DIME_DATA_LEN]&0xFF000000)>>24,
($this->Elements[NET_DIME_DATA_LEN]&0x00FF0000)>>16,
($this->Elements[NET_DIME_DATA_LEN]&0x0000FF00)>>8,
($this->Elements[NET_DIME_DATA_LEN]&0x000000FF),
str_pad($this->Elements[NET_DIME_OPTS], $this->OPTS_LENGTH, $this->padstr),
str_pad($this->Elements[NET_DIME_ID], $this->ID_LENGTH, $this->padstr),
str_pad($this->Elements[NET_DIME_TYPE], $this->TYPE_LENGTH, $this->padstr),
str_pad($this->Elements[NET_DIME_DATA], $this->DATA_LENGTH, $this->padstr));
}
function _getPadLength($len)
{
$pad = 0;
if ($len) {
$pad = $len % 4;
if ($pad) $pad = 4 - $pad;
}
return $len + $pad;
}
function decode(&$data)
{
// REAL DIME decoding
$this->Elements[NET_DIME_FLAGS] = (hexdec(bin2hex($data[0]))<<8) + hexdec(bin2hex($data[1]));
$this->Elements[NET_DIME_OPTS_LEN] = (hexdec(bin2hex($data[2]))<<8) + hexdec(bin2hex($data[3]));
$this->Elements[NET_DIME_ID_LEN] = (hexdec(bin2hex($data[4]))<<8) + hexdec(bin2hex($data[5]));
$this->Elements[NET_DIME_TYPE_LEN] = (hexdec(bin2hex($data[6]))<<8) + hexdec(bin2hex($data[7]));
$this->Elements[NET_DIME_DATA_LEN] = (hexdec(bin2hex($data[8]))<<24) +
(hexdec(bin2hex($data[9]))<<16) +
(hexdec(bin2hex($data[10]))<<8) +
hexdec(bin2hex($data[11]));
$p = 12;
$version = (($this->Elements[NET_DIME_FLAGS]>>11) & 0x001F);
if ($version == NET_DIME_VERSION)
{
$this->OPTS_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_OPTS_LEN]);
$this->ID_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_ID_LEN]);
$this->TYPE_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_TYPE_LEN]);
$this->DATA_LENGTH = $this->_getPadLength($this->Elements[NET_DIME_DATA_LEN]);
$datalen = strlen($data);
$this->Elements[NET_DIME_OPTS] = substr($data,$p,$this->Elements[NET_DIME_OPTS_LEN]);
$this->_haveOpts = (strlen($this->Elements[NET_DIME_OPTS]) == $this->Elements[NET_DIME_OPTS_LEN]);
if ($this->_haveOpts) {
$p += $this->OPTS_LENGTH;
$this->Elements[NET_DIME_ID] = substr($data,$p,$this->Elements[NET_DIME_ID_LEN]);
$this->_haveID = (strlen($this->Elements[NET_DIME_ID]) == $this->Elements[NET_DIME_ID_LEN]);
if ($this->_haveID) {
$p += $this->ID_LENGTH;
$this->Elements[NET_DIME_TYPE] = substr($data,$p,$this->Elements[NET_DIME_TYPE_LEN]);
$this->_haveType = (strlen($this->Elements[NET_DIME_TYPE]) == $this->Elements[NET_DIME_TYPE_LEN]);
if ($this->_haveType) {
$p += $this->TYPE_LENGTH;
$this->Elements[NET_DIME_DATA] = substr($data,$p,$this->Elements[NET_DIME_DATA_LEN]);
$this->_haveData = (strlen($this->Elements[NET_DIME_DATA]) == $this->Elements[NET_DIME_DATA_LEN]);
if ($this->_haveData) {
$p += $this->DATA_LENGTH;
} else {
$p += strlen($this->Elements[NET_DIME_DATA]);
}
} else {
$p += strlen($this->Elements[NET_DIME_TYPE]);
}
} else {
$p += strlen($this->Elements[NET_DIME_ID]);
}
} else {
$p += strlen($this->Elements[NET_DIME_OPTS]);
}
}
return substr($data, $p);
}
function addData(&$data)
{
$datalen = strlen($data);
$p = 0;
if (!$this->_haveOpts) {
$have = strlen($this->Elements[NET_DIME_OPTS]);
$this->Elements[NET_DIME_OPTS] .= substr($data,$p,$this->Elements[NET_DIME_OPTS_LEN]-$have);
$this->_haveOpts = (strlen($this->Elements[NET_DIME_OPTS]) == $this->Elements[DIME_OTPS_LEN]);
if (!$this->_haveOpts) return NULL;
$p += $this->OPTS_LENGTH-$have;
}
if (!$this->_haveID) {
$have = strlen($this->Elements[NET_DIME_ID]);
$this->Elements[NET_DIME_ID] .= substr($data,$p,$this->Elements[NET_DIME_ID_LEN]-$have);
$this->_haveID = (strlen($this->Elements[NET_DIME_ID]) == $this->Elements[NET_DIME_ID_LEN]);
if (!$this->_haveID) return NULL;
$p += $this->ID_LENGTH-$have;
}
if (!$this->_haveType && $p < $datalen) {
$have = strlen($this->Elements[NET_DIME_TYPE]);
$this->Elements[NET_DIME_TYPE] .= substr($data,$p,$this->Elements[NET_DIME_TYPE_LEN]-$have);
$this->_haveType = (strlen($this->Elements[NET_DIME_TYPE]) == $this->Elements[NET_DIME_TYPE_LEN]);
if (!$this->_haveType) return NULL;
$p += $this->TYPE_LENGTH-$have;
}
if (!$this->_haveData && $p < $datalen) {
$have = strlen($this->Elements[NET_DIME_DATA]);
$this->Elements[NET_DIME_DATA] .= substr($data,$p,$this->Elements[NET_DIME_DATA_LEN]-$have);
$this->_haveData = (strlen($this->Elements[NET_DIME_DATA]) == $this->Elements[NET_DIME_DATA_LEN]);
if (!$this->_haveData) return NULL;
$p += $this->DATA_LENGTH-$have;
}
return substr($data,$p);
}
}
class Net_DIME_Message extends PEAR
{
var $record_size = 4096;
#var $records =array();
var $parts = array();
var $currentPart = -1;
var $stream = NULL;
var $_currentRecord;
var $_proc = array();
var $type;
var $typestr;
var $mb = 1;
var $me = 0;
var $cf = 0;
var $id = NULL;
var $debug = FALSE;
/**
* constructor
*
* this currently takes a file pointer as provided
* by fopen
*
* TODO: integrate with the php streams stuff
*/
function Net_DIME_Message($stream=NULL, $record_size = 4096, $debug = FALSE)
{
$this->stream = $stream;
$this->record_size = $record_size;
$this->debug = $debug;
}
function _makeRecord(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$record = new Net_DIME_Record($this->debug);
if ($this->mb) {
$record->setMB();
// all subsequent records are not message begin!
$this->mb = 0;
}
if ($this->me) $record->setME();
if ($this->cf) $record->setCF();
$record->setData($data);
$record->setType($typestr,$type);
if ($id) $record->setID($id);
#if ($this->debug) {
# print str_replace('\0','*',$record->encode());
#}
return $record->encode();
}
function startChunk(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$this->me = 0;
$this->cf = 1;
$this->type = $type;
$this->typestr = $typestr;
if ($id) {
$this->id = $id;
} else {
$this->id = md5(time());
}
return $this->_makeRecord($data, $this->typestr, $this->id, $this->type);
}
function doChunk(&$data)
{
$this->me = 0;
$this->cf = 1;
return $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_UNCHANGED);
}
function endChunk()
{
$this->cf = 0;
$data = NULL;
$rec = $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_UNCHANGED);
$this->id = 0;
$this->cf = 0;
$this->id = 0;
$this->type = NET_DIME_TYPE_UNKNOWN;
$this->typestr = NULL;
return $rec;
}
function endMessage()
{
$this->me = 1;
$data = NULL;
$rec = $this->_makeRecord($data, NULL, NULL, NET_DIME_TYPE_NONE);
$this->me = 0;
$this->mb = 1;
$this->id = 0;
return $rec;
}
/**
* sendRecord
*
* given a chunk of data, it creates DIME records
* and writes them to the stream
*
*/
function sendData(&$data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$len = strlen($data);
if ($len > $this->record_size) {
$chunk = substr($data, 0, $this->record_size);
$p = $this->record_size;
$rec = $this->startChunk($chunk,$typestr,$id,$type);
fwrite($this->stream, $rec);
while ($p < $len) {
$chunk = substr($data, $p, $this->record_size);
$p += $this->record_size;
$rec = $this->doChunk($chunk);
fwrite($this->stream, $rec);
}
$rec = $this->endChunk();
fwrite($this->stream, $rec);
return;
}
$rec = $this->_makeRecord($data, $typestr,$id,$type);
fwrite($this->stream, $rec);
}
function sendEndMessage()
{
$rec = $this->endMessage();
fwrite($this->stream, $rec);
}
/**
* sendFile
*
* given a filename, it reads the file,
* creates records and writes them to the stream
*
*/
function sendFile($filename, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$f = fopen($filename, "rb");
if ($f) {
if ($data = fread($f, $this->record_size)) {
$this->startChunk($data,$typestr,$id,$type);
}
while ($data = fread($f, $this->record_size)) {
$this->doChunk($data,$typestr,$id,$type);
}
$this->endChunk();
fclose($f);
}
}
/**
* encodeData
*
* given data, encode it in DIME
*
*/
function encodeData($data, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$len = strlen($data);
$resp = '';
if ($len > $this->record_size) {
$chunk = substr($data, 0, $this->record_size);
$p = $this->record_size;
$resp .= $this->startChunk($chunk,$typestr,$id,$type);
while ($p < $len) {
$chunk = substr($data, $p, $this->record_size);
$p += $this->record_size;
$resp .= $this->doChunk($chunk);
}
$resp .= $this->endChunk();
} else {
$resp .= $this->_makeRecord($data, $typestr,$id,$type);
}
return $resp;
}
/**
* sendFile
*
* given a filename, it reads the file,
* creates records and writes them to the stream
*
*/
function encodeFile($filename, $typestr='', $id=NULL, $type=NET_DIME_TYPE_UNKNOWN)
{
$f = fopen($filename, "rb");
if ($f) {
if ($data = fread($f, $this->record_size)) {
$resp = $this->startChunk($data,$typestr,$id,$type);
}
while ($data = fread($f, $this->record_size)) {
$resp = $this->doChunk($data,$typestr,$id,$type);
}
$resp = $this->endChunk();
fclose($f);
}
return $resp;
}
/**
* _processData
*
* creates Net_DIME_Records from provided data
*
*/
function _processData(&$data)
{
$leftover = NULL;
if (!$this->_currentRecord) {
$this->_currentRecord = new Net_DIME_Record($this->debug);
$data = $this->_currentRecord->decode($data);
} else {
$data = $this->_currentRecord->addData($data);
}
if ($this->_currentRecord->_haveData) {
if (count($this->parts)==0 && !$this->_currentRecord->isStart()) {
// raise an error!
return PEAR::raiseError('First Message is not a DIME begin record!');
}
if ($this->_currentRecord->isEnd() && $this->_currentRecord->getDataLength()==0) {
return NULL;
}
if ($this->currentPart < 0 && !$this->_currentRecord->isChunk()) {
$this->parts[] = array();
$this->currentPart = count($this->parts)-1;
$this->parts[$this->currentPart]['id'] = $this->_currentRecord->getID();
$this->parts[$this->currentPart]['type'] = $this->_currentRecord->getType();
$this->parts[$this->currentPart]['data'] = $this->_currentRecord->getData();
$this->currentPart = -1;
} else {
if ($this->currentPart < 0) {
$this->parts[] = array();
$this->currentPart = count($this->parts)-1;
$this->parts[$this->currentPart]['id'] = $this->_currentRecord->getID();
$this->parts[$this->currentPart]['type'] = $this->_currentRecord->getType();
$this->parts[$this->currentPart]['data'] = $this->_currentRecord->getData();
} else {
$this->parts[$this->currentPart]['data'] .= $this->_currentRecord->getData();
if (!$this->_currentRecord->isChunk()) {
// we reached the end of the chunk
$this->currentPart = -1;
}
}
}
#$this->records[] = $this->_currentRecord;
if (!$this->_currentRecord->isEnd()) $this->_currentRecord = NULL;
}
return NULL;
}
/**
* decodeData
*
* decodes a DIME encrypted string of data
*
*/
function decodeData(&$data) {
while (strlen($data) >= NET_DIME_RECORD_HEADER) {
$err = $this->_processData($data);
if (PEAR::isError($err)) {
return $err;
}
}
}
/**
* read
*
* reads the stream and creates
* an array of records
*
* it can accept the start of a previously read buffer
* this is usefull in situations where you need to read
* headers before discovering that the data is DIME encoded
* such as in the case of reading an HTTP response.
*/
function read($buf=NULL)
{
while ($data = fread($this->stream, 8192)) {
if ($buf) {
$data = $buf.$data;
$buf = NULL;
}
if ($this->debug)
echo "read: ".strlen($data)." bytes\n";
$err = $this->decodeData($data);
if (PEAR::isError($err)) {
return $err;
}
// store any leftover data to be used again
// should be < NET_DIME_RECORD_HEADER bytes
$buf = $data;
}
if (!$this->_currentRecord || !$this->_currentRecord->isEnd()) {
return PEAR::raiseError('reached stream end without end record');
}
return NULL;
}
}
?>
|