File indexing completed on 2024-06-23 05:55:01

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Amf
0017  * @subpackage Parse_Amf3
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /** Zend_Amf_Constants */
0024 // require_once 'Zend/Amf/Constants.php';
0025 
0026 
0027 /** Zend_Amf_Parse_Serializer */
0028 // require_once 'Zend/Amf/Parse/Serializer.php';
0029 
0030 /** Zend_Amf_Parse_TypeLoader */
0031 // require_once 'Zend/Amf/Parse/TypeLoader.php';
0032 
0033 /**
0034  * Detect PHP object type and convert it to a corresponding AMF3 object type
0035  *
0036  * @package    Zend_Amf
0037  * @subpackage Parse_Amf3
0038  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0039  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0040  */
0041 class Zend_Amf_Parse_Amf3_Serializer extends Zend_Amf_Parse_Serializer
0042 {
0043     /**
0044      * A constant empty string
0045      * @var string
0046      */
0047     protected $_strEmpty = '';
0048 
0049     /**
0050      * An array of reference objects per amf body
0051      * @var array
0052      */
0053     protected $_referenceObjects = array();
0054 
0055     /**
0056      * An array of reference strings per amf body
0057      * @var array
0058      */
0059     protected $_referenceStrings = array();
0060 
0061     /**
0062      * An array of reference class definitions, indexed by classname
0063      * @var array
0064      */
0065     protected $_referenceDefinitions = array();
0066 
0067     /**
0068      * Serialize PHP types to AMF3 and write to stream
0069      *
0070      * Checks to see if the type was declared and then either
0071      * auto negotiates the type or use the user defined markerType to
0072      * serialize the data from php back to AMF3
0073      *
0074      * @param  mixed $data
0075      * @param  int $markerType
0076      * @param  mixed $dataByVal
0077      * @return void
0078      */
0079     public function writeTypeMarker(&$data, $markerType = null, $dataByVal = false)
0080     {
0081         // Workaround for PHP5 with E_STRICT enabled complaining about "Only
0082         // variables should be passed by reference"
0083         if ((null === $data) && ($dataByVal !== false)) {
0084             $data = &$dataByVal;
0085         }
0086         if (null !== $markerType) {
0087             // Write the Type Marker to denote the following action script data type
0088             $this->_stream->writeByte($markerType);
0089 
0090             switch ($markerType) {
0091                 case Zend_Amf_Constants::AMF3_NULL:
0092                     break;
0093                 case Zend_Amf_Constants::AMF3_BOOLEAN_FALSE:
0094                     break;
0095                 case Zend_Amf_Constants::AMF3_BOOLEAN_TRUE:
0096                     break;
0097                 case Zend_Amf_Constants::AMF3_INTEGER:
0098                     $this->writeInteger($data);
0099                     break;
0100                 case Zend_Amf_Constants::AMF3_NUMBER:
0101                     $this->_stream->writeDouble($data);
0102                     break;
0103                 case Zend_Amf_Constants::AMF3_STRING:
0104                     $this->writeString($data);
0105                     break;
0106                 case Zend_Amf_Constants::AMF3_DATE:
0107                     $this->writeDate($data);
0108                     break;
0109                 case Zend_Amf_Constants::AMF3_ARRAY:
0110                     $this->writeArray($data);
0111                     break;
0112                 case Zend_Amf_Constants::AMF3_OBJECT:
0113                     $this->writeObject($data);
0114                     break;
0115                 case Zend_Amf_Constants::AMF3_BYTEARRAY:
0116                     $this->writeByteArray($data);
0117                     break;
0118                 case Zend_Amf_Constants::AMF3_XMLSTRING;
0119                     $this->writeXml($data);
0120                     break;
0121                 default:
0122                     // require_once 'Zend/Amf/Exception.php';
0123                     throw new Zend_Amf_Exception('Unknown Type Marker: ' . $markerType);
0124             }
0125         } else {
0126             // Detect Type Marker
0127             if (is_resource($data)) {
0128                 $data = Zend_Amf_Parse_TypeLoader::handleResource($data);
0129             }
0130             switch (true) {
0131                 case (null === $data):
0132                     $markerType = Zend_Amf_Constants::AMF3_NULL;
0133                     break;
0134                 case (is_bool($data)):
0135                     if ($data){
0136                         $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_TRUE;
0137                     } else {
0138                         $markerType = Zend_Amf_Constants::AMF3_BOOLEAN_FALSE;
0139                     }
0140                     break;
0141                 case (is_int($data)):
0142                     if (($data > 0xFFFFFFF) || ($data < -268435456)) {
0143                         $markerType = Zend_Amf_Constants::AMF3_NUMBER;
0144                     } else {
0145                         $markerType = Zend_Amf_Constants::AMF3_INTEGER;
0146                     }
0147                     break;
0148                 case (is_float($data)):
0149                     $markerType = Zend_Amf_Constants::AMF3_NUMBER;
0150                     break;
0151                 case (is_string($data)):
0152                     $markerType = Zend_Amf_Constants::AMF3_STRING;
0153                     break;
0154                 case (is_array($data)):
0155                     $markerType = Zend_Amf_Constants::AMF3_ARRAY;
0156                     break;
0157                 case (is_object($data)):
0158                     // Handle object types.
0159                     if (($data instanceof DateTime) || ($data instanceof Zend_Date)) {
0160                         $markerType = Zend_Amf_Constants::AMF3_DATE;
0161                     } else if ($data instanceof Zend_Amf_Value_ByteArray) {
0162                         $markerType = Zend_Amf_Constants::AMF3_BYTEARRAY;
0163                     } else if (($data instanceof DOMDocument) || ($data instanceof SimpleXMLElement)) {
0164                         $markerType = Zend_Amf_Constants::AMF3_XMLSTRING;
0165                     } else {
0166                         $markerType = Zend_Amf_Constants::AMF3_OBJECT;
0167                     }
0168                     break;
0169                 default:
0170                     // require_once 'Zend/Amf/Exception.php';
0171                     throw new Zend_Amf_Exception('Unsupported data type: ' . gettype($data));
0172             }
0173             $this->writeTypeMarker($data, $markerType);
0174         }
0175     }
0176 
0177     /**
0178      * Write an AMF3 integer
0179      *
0180      * @param int|float $data
0181      * @return Zend_Amf_Parse_Amf3_Serializer
0182      */
0183     public function writeInteger($int)
0184     {
0185         if (($int & 0xffffff80) == 0) {
0186             $this->_stream->writeByte($int & 0x7f);
0187             return $this;
0188         }
0189 
0190         if (($int & 0xffffc000) == 0 ) {
0191             $this->_stream->writeByte(($int >> 7 ) | 0x80);
0192             $this->_stream->writeByte($int & 0x7f);
0193             return $this;
0194         }
0195 
0196         if (($int & 0xffe00000) == 0) {
0197             $this->_stream->writeByte(($int >> 14 ) | 0x80);
0198             $this->_stream->writeByte(($int >> 7 ) | 0x80);
0199             $this->_stream->writeByte($int & 0x7f);
0200             return $this;
0201         }
0202 
0203         $this->_stream->writeByte(($int >> 22 ) | 0x80);
0204         $this->_stream->writeByte(($int >> 15 ) | 0x80);
0205         $this->_stream->writeByte(($int >> 8 ) | 0x80);
0206         $this->_stream->writeByte($int & 0xff);
0207         return $this;
0208     }
0209 
0210     /**
0211      * Send string to output stream, without trying to reference it.
0212      * The string is prepended with strlen($string) << 1 | 0x01
0213      *
0214      * @param  string $string
0215      * @return Zend_Amf_Parse_Amf3_Serializer
0216      */
0217     protected function writeBinaryString(&$string){
0218         $ref = ($this->_mbStringFunctionsOverloaded ? mb_strlen($string, '8bit') : strlen($string)) << 1 | 0x01;
0219         $this->writeInteger($ref);
0220         $this->_stream->writeBytes($string);
0221 
0222         return $this;
0223     }
0224 
0225     /**
0226      * Send string to output stream
0227      *
0228      * @param  string $string
0229      * @return Zend_Amf_Parse_Amf3_Serializer
0230      */
0231     public function writeString(&$string)
0232     {
0233         $len = $this->_mbStringFunctionsOverloaded ? mb_strlen($string, '8bit') : strlen($string);
0234         if(!$len){
0235             $this->writeInteger(0x01);
0236             return $this;
0237         }
0238 
0239         $ref = array_key_exists($string, $this->_referenceStrings) 
0240              ? $this->_referenceStrings[$string] 
0241              : false;
0242         if ($ref === false){
0243             $this->_referenceStrings[$string] = count($this->_referenceStrings);
0244             $this->writeBinaryString($string);
0245         } else {
0246             $ref <<= 1;
0247             $this->writeInteger($ref);
0248         }
0249 
0250         return $this;
0251     }
0252 
0253     /**
0254      * Send ByteArray to output stream
0255      *
0256      * @param  string|Zend_Amf_Value_ByteArray  $data
0257      * @return Zend_Amf_Parse_Amf3_Serializer
0258      */
0259     public function writeByteArray(&$data)
0260     {
0261         if ($this->writeObjectReference($data)) {
0262             return $this;
0263         }
0264 
0265         if (is_string($data)) {
0266             //nothing to do
0267         } else if ($data instanceof Zend_Amf_Value_ByteArray) {
0268             $data = $data->getData();
0269         } else {
0270             // require_once 'Zend/Amf/Exception.php';
0271             throw new Zend_Amf_Exception('Invalid ByteArray specified; must be a string or Zend_Amf_Value_ByteArray');
0272         }
0273 
0274         $this->writeBinaryString($data);
0275 
0276         return $this;
0277     }
0278 
0279     /**
0280      * Send xml to output stream
0281      *
0282      * @param  DOMDocument|SimpleXMLElement  $xml
0283      * @return Zend_Amf_Parse_Amf3_Serializer
0284      */
0285     public function writeXml($xml)
0286     {
0287         if ($this->writeObjectReference($xml)) {
0288             return $this;
0289         }
0290 
0291         if(is_string($xml)) {
0292             //nothing to do
0293         } else if ($xml instanceof DOMDocument) {
0294             $xml = $xml->saveXml();
0295         } else if ($xml instanceof SimpleXMLElement) {
0296             $xml = $xml->asXML();
0297         } else {
0298             // require_once 'Zend/Amf/Exception.php';
0299             throw new Zend_Amf_Exception('Invalid xml specified; must be a DOMDocument or SimpleXMLElement');
0300         }
0301 
0302         $this->writeBinaryString($xml);
0303 
0304         return $this;
0305     }
0306 
0307     /**
0308      * Convert DateTime/Zend_Date to AMF date
0309      *
0310      * @param  DateTime|Zend_Date $date
0311      * @return Zend_Amf_Parse_Amf3_Serializer
0312      */
0313     public function writeDate($date)
0314     {
0315         if ($this->writeObjectReference($date)) {
0316             return $this;
0317         }
0318 
0319         if ($date instanceof DateTime) {
0320             $dateString = $date->format('U') * 1000;
0321         } elseif ($date instanceof Zend_Date) {
0322             $dateString = $date->toString('U') * 1000;
0323         } else {
0324             // require_once 'Zend/Amf/Exception.php';
0325             throw new Zend_Amf_Exception('Invalid date specified; must be a string DateTime or Zend_Date object');
0326         }
0327 
0328         $this->writeInteger(0x01);
0329         // write time to stream minus milliseconds
0330         $this->_stream->writeDouble($dateString);
0331         return $this;
0332     }
0333 
0334     /**
0335      * Write a PHP array back to the amf output stream
0336      *
0337      * @param array $array
0338      * @return Zend_Amf_Parse_Amf3_Serializer
0339      */
0340     public function writeArray(&$array)
0341     {
0342         // arrays aren't reference here but still counted
0343         $this->_referenceObjects[] = $array;
0344 
0345         // have to seperate mixed from numberic keys.
0346         $numeric = array();
0347         $string  = array();
0348         foreach ($array as $key => &$value) {
0349             if (is_int($key)) {
0350                 $numeric[] = $value;
0351             } else {
0352                 $string[$key] = $value;
0353             }
0354         }
0355 
0356         // write the preamble id of the array
0357         $length = count($numeric);
0358         $id     = ($length << 1) | 0x01;
0359         $this->writeInteger($id);
0360 
0361         //Write the mixed type array to the output stream
0362         foreach($string as $key => &$value) {
0363             $this->writeString($key)
0364                  ->writeTypeMarker($value);
0365         }
0366         $this->writeString($this->_strEmpty);
0367 
0368         // Write the numeric array to ouput stream
0369         foreach($numeric as &$value) {
0370             $this->writeTypeMarker($value);
0371         }
0372         return $this;
0373     }
0374 
0375     /**
0376      * Check if the given object is in the reference table, write the reference if it exists,
0377      * otherwise add the object to the reference table
0378      *
0379      * @param mixed $object object reference to check for reference
0380      * @param mixed $objectByVal object to check for reference
0381      * @return Boolean true, if the reference was written, false otherwise
0382      */
0383     protected function writeObjectReference(&$object, $objectByVal = false)
0384     {
0385         // Workaround for PHP5 with E_STRICT enabled complaining about "Only
0386         // variables should be passed by reference"
0387         if ((null === $object) && ($objectByVal !== false)) {
0388             $object = &$objectByVal;
0389         }
0390 
0391         $hash = spl_object_hash($object);
0392         $ref = array_key_exists($hash, $this->_referenceObjects) 
0393              ? $this->_referenceObjects[$hash] 
0394              : false;
0395 
0396         // quickly handle object references
0397         if ($ref !== false){
0398             $ref <<= 1;
0399             $this->writeInteger($ref);
0400             return true;
0401         }
0402         $this->_referenceObjects[$hash] = count($this->_referenceObjects);
0403         return false;
0404     }
0405 
0406     /**
0407      * Write object to ouput stream
0408      *
0409      * @param  mixed $data
0410      * @return Zend_Amf_Parse_Amf3_Serializer
0411      */
0412     public function writeObject($object)
0413     {
0414         if($this->writeObjectReference($object)){
0415             return $this;
0416         }
0417 
0418         $className = '';
0419 
0420         //Check to see if the object is a typed object and we need to change
0421         switch (true) {
0422              // the return class mapped name back to actionscript class name.
0423             case ($className = Zend_Amf_Parse_TypeLoader::getMappedClassName(get_class($object))):
0424                 break;
0425 
0426             // Check to see if the user has defined an explicit Action Script type.
0427             case isset($object->_explicitType):
0428                 $className = $object->_explicitType;
0429                 break;
0430 
0431             // Check if user has defined a method for accessing the Action Script type
0432             case method_exists($object, 'getASClassName'):
0433                 $className = $object->getASClassName();
0434                 break;
0435 
0436             // No return class name is set make it a generic object
0437             case ($object instanceof stdClass):
0438                 $className = '';
0439                 break;
0440 
0441              // By default, use object's class name
0442             default:
0443                 $className = get_class($object);
0444                 break;
0445         }
0446 
0447         $writeTraits = true;
0448 
0449         //check to see, if we have a corresponding definition
0450         if(array_key_exists($className, $this->_referenceDefinitions)){
0451             $traitsInfo    = $this->_referenceDefinitions[$className]['id'];
0452             $encoding      = $this->_referenceDefinitions[$className]['encoding'];
0453             $propertyNames = $this->_referenceDefinitions[$className]['propertyNames'];
0454 
0455             $traitsInfo = ($traitsInfo << 2) | 0x01;
0456 
0457             $writeTraits = false;
0458         } else {
0459             $propertyNames = array();
0460 
0461             if($className == ''){
0462                 //if there is no className, we interpret the class as dynamic without any sealed members
0463                 $encoding = Zend_Amf_Constants::ET_DYNAMIC;
0464             } else {
0465                 $encoding = Zend_Amf_Constants::ET_PROPLIST;
0466 
0467                 foreach($object as $key => $value) {
0468                     if( $key[0] != "_") {
0469                         $propertyNames[] = $key;
0470                     }
0471                 }
0472             }
0473 
0474             $this->_referenceDefinitions[$className] = array(
0475                         'id'            => count($this->_referenceDefinitions),
0476                         'encoding'      => $encoding,
0477                         'propertyNames' => $propertyNames,
0478                     );
0479 
0480             $traitsInfo = Zend_Amf_Constants::AMF3_OBJECT_ENCODING;
0481             $traitsInfo |= $encoding << 2;
0482             $traitsInfo |= (count($propertyNames) << 4);
0483         }
0484 
0485         $this->writeInteger($traitsInfo);
0486 
0487         if($writeTraits){
0488             $this->writeString($className);
0489             foreach ($propertyNames as $value) {
0490                 $this->writeString($value);
0491             }
0492         }
0493 
0494         try {
0495             switch($encoding) {
0496                 case Zend_Amf_Constants::ET_PROPLIST:
0497                     //Write the sealed values to the output stream.
0498                     foreach ($propertyNames as $key) {
0499                         $this->writeTypeMarker($object->$key);
0500                     }
0501                     break;
0502                 case Zend_Amf_Constants::ET_DYNAMIC:
0503                     //Write the sealed values to the output stream.
0504                     foreach ($propertyNames as $key) {
0505                         $this->writeTypeMarker($object->$key);
0506                     }
0507 
0508                     //Write remaining properties
0509                     foreach($object as $key => $value){
0510                         if(!in_array($key,$propertyNames) && $key[0] != "_"){
0511                             $this->writeString($key);
0512                             $this->writeTypeMarker($value);
0513                         }
0514                     }
0515 
0516                     //Write an empty string to end the dynamic part
0517                     $this->writeString($this->_strEmpty);
0518                     break;
0519                 case Zend_Amf_Constants::ET_EXTERNAL:
0520                     // require_once 'Zend/Amf/Exception.php';
0521                     throw new Zend_Amf_Exception('External Object Encoding not implemented');
0522                     break;
0523                 default:
0524                     // require_once 'Zend/Amf/Exception.php';
0525                     throw new Zend_Amf_Exception('Unknown Object Encoding type: ' . $encoding);
0526             }
0527         } catch (Exception $e) {
0528             // require_once 'Zend/Amf/Exception.php';
0529             throw new Zend_Amf_Exception('Unable to writeObject output: ' . $e->getMessage(), 0, $e);
0530         }
0531 
0532         return $this;
0533     }
0534 }