├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── lib ├── Auth ├── SASL.php └── SASL │ ├── Anonymous.php │ ├── Common.php │ ├── CramMD5.php │ ├── DigestMD5.php │ ├── Exception.php │ ├── External.php │ ├── Login.php │ └── Plain.php ├── FirePHP ├── FirePHP.class.php ├── LICENSE └── fb.php └── XmppPrebind.php /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .svn 3 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | =========== 3 | Copyright (c) 2011 Amiado Group AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 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 IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | XMPP Prebind for PHP 2 | ==================== 3 | 4 | This class is for [prebinding](http://metajack.im/2009/12/14/fastest-xmpp-sessions-with-http-prebinding/) a XMPP Session with PHP. 5 | 6 | Usage 7 | ===== 8 | * Clone the repo 9 | * In your file where you want to do the prebinding: 10 | 11 | ```php 12 | /** 13 | * Comment here for explanation of the options. 14 | * 15 | * Create a new XMPP Object with the required params 16 | * 17 | * @param string $jabberHost Jabber Server Host 18 | * @param string $boshUri Full URI to the http-bind 19 | * @param string $resource Resource identifier 20 | * @param bool $useSsl Use SSL (not working yet, TODO) 21 | * @param bool $debug Enable debug 22 | */ 23 | $xmppPrebind = new XmppPrebind('your-jabber-host.tld', 'http://your-jabber-host/http-bind/', 'Your XMPP Clients resource name', false, false); 24 | $xmppPrebind->connect($username, $password); 25 | $xmppPrebind->auth(); 26 | $sessionInfo = $xmppPrebind->getSessionInfo(); // array containing sid, rid and jid 27 | ``` 28 | 29 | * If you use [Candy](http://amiadogroup.github.com/candy), change the `Candy.Core.Connect()` line to the following: 30 | 31 | ```javascript 32 | Candy.Core.attach('', '', ''); 33 | ``` 34 | 35 | * You should now have a working prebinding with PHP 36 | 37 | Debugging 38 | ========= 39 | If something doesn't work, you can enable Debug. Debug output is logged to [FirePHP](http://www.firephp.org/), so you have to install that first. 40 | 41 | Other Languages 42 | =============== 43 | There exist other projects for other languages to support a prebind. Go googling :) 44 | 45 | Be aware 46 | ======== 47 | This class is in no way feature complete. There may also be bugs. I'd appreciate it if you contribute or submit bug reports. 48 | 49 | Thanks. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "piotr-cz/xmpp-prebind-php", 3 | "description": "This class is for prebinding a XMPP Session with PHP.", 4 | "version": "0.1.0", 5 | "type": "library", 6 | "keywords": ["xmpp", "php", "prebind"], 7 | "homepage": "http://jolicode.com", 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Michael Weibel" 12 | }, 13 | { 14 | "name": "piotr", 15 | "email": "hello@piotr.cz" 16 | }, 17 | { 18 | "name": "Alex Knol", 19 | "email": "alex@cobrowser.net" 20 | } 21 | ], 22 | "autoload": { 23 | "classmap": ["lib/"] 24 | }, 25 | "require": { 26 | "php": ">=5.3", 27 | "lib-libxml": "*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/Auth/SASL.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Client implementation of various SASL mechanisms 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.1 43 | * @package Auth_SASL 44 | */ 45 | require_once(dirname(__FILE__) . '/SASL/Exception.php'); 46 | 47 | class Auth_SASL 48 | { 49 | /** 50 | * Factory class. Returns an object of the request 51 | * type. 52 | * 53 | * @param string $type One of: Anonymous 54 | * Plain 55 | * CramMD5 56 | * DigestMD5 57 | * Types are not case sensitive 58 | */ 59 | public static function factory($type) 60 | { 61 | switch (strtolower($type)) { 62 | case 'anonymous': 63 | $filename = 'SASL/Anonymous.php'; 64 | $classname = 'Auth_SASL_Anonymous'; 65 | break; 66 | 67 | case 'login': 68 | $filename = 'SASL/Login.php'; 69 | $classname = 'Auth_SASL_Login'; 70 | break; 71 | 72 | case 'plain': 73 | $filename = 'SASL/Plain.php'; 74 | $classname = 'Auth_SASL_Plain'; 75 | break; 76 | 77 | case 'external': 78 | $filename = 'SASL/External.php'; 79 | $classname = 'Auth_SASL_External'; 80 | break; 81 | 82 | case 'cram-md5': 83 | $filename = 'SASL/CramMD5.php'; 84 | $classname = 'Auth_SASL_CramMD5'; 85 | break; 86 | 87 | case 'digest-md5': 88 | $filename = 'SASL/DigestMD5.php'; 89 | $classname = 'Auth_SASL_DigestMD5'; 90 | break; 91 | 92 | default: 93 | throw new Auth_SASL_Exception('Invalid SASL mechanism type ("' . $type .'")'); 94 | break; 95 | } 96 | 97 | require_once(dirname(__FILE__) . '/' . $filename); 98 | $obj = new $classname(); 99 | return $obj; 100 | } 101 | } 102 | 103 | ?> 104 | -------------------------------------------------------------------------------- /lib/Auth/SASL/Anonymous.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Implmentation of ANONYMOUS SASL mechanism 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.1 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Common.php'); 47 | 48 | class Auth_SASL_Anonymous extends Auth_SASL_Common 49 | { 50 | /** 51 | * Not much to do here except return the token supplied. 52 | * No encoding, hashing or encryption takes place for this 53 | * mechanism, simply one of: 54 | * o An email address 55 | * o An opaque string not containing "@" that can be interpreted 56 | * by the sysadmin 57 | * o Nothing 58 | * 59 | * We could have some logic here for the second option, but this 60 | * would by no means create something interpretable. 61 | * 62 | * @param string $token Optional email address or string to provide 63 | * as trace information. 64 | * @return string The unaltered input token 65 | */ 66 | public function getResponse($token = '') 67 | { 68 | return $token; 69 | } 70 | } 71 | ?> -------------------------------------------------------------------------------- /lib/Auth/SASL/Common.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Common functionality to SASL mechanisms 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.1 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Exception.php'); 47 | 48 | class Auth_SASL_Common 49 | { 50 | /** 51 | * Function which implements HMAC MD5 digest 52 | * 53 | * @param string $key The secret key 54 | * @param string $data The data to protect 55 | * @return string The HMAC MD5 digest 56 | */ 57 | protected function HMAC_MD5($key, $data) 58 | { 59 | if (strlen($key) > 64) { 60 | $key = pack('H32', md5($key)); 61 | } 62 | 63 | if (strlen($key) < 64) { 64 | $key = str_pad($key, 64, chr(0)); 65 | } 66 | 67 | $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64); 68 | $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64); 69 | 70 | $inner = pack('H32', md5($k_ipad . $data)); 71 | $digest = md5($k_opad . $inner); 72 | 73 | return $digest; 74 | } 75 | } 76 | ?> 77 | -------------------------------------------------------------------------------- /lib/Auth/SASL/CramMD5.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Implmentation of CRAM-MD5 SASL mechanism 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.1 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Common.php'); 47 | 48 | class Auth_SASL_CramMD5 extends Auth_SASL_Common 49 | { 50 | /** 51 | * Implements the CRAM-MD5 SASL mechanism 52 | * This DOES NOT base64 encode the return value, 53 | * you will need to do that yourself. 54 | * 55 | * @param string $user Username 56 | * @param string $pass Password 57 | * @param string $challenge The challenge supplied by the server. 58 | * this should be already base64_decoded. 59 | * 60 | * @return string The string to pass back to the server, of the form 61 | * " ". This is NOT base64_encoded. 62 | */ 63 | public function getResponse($user, $pass, $challenge) 64 | { 65 | return $user . ' ' . $this->HMAC_MD5($pass, $challenge); 66 | } 67 | } 68 | ?> -------------------------------------------------------------------------------- /lib/Auth/SASL/DigestMD5.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Implmentation of DIGEST-MD5 SASL mechanism 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.1 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Common.php'); 47 | 48 | class Auth_SASL_DigestMD5 extends Auth_SASL_Common 49 | { 50 | /** 51 | * Provides the (main) client response for DIGEST-MD5 52 | * requires a few extra parameters than the other 53 | * mechanisms, which are unavoidable. 54 | * 55 | * @param string $authcid Authentication id (username) 56 | * @param string $pass Password 57 | * @param string $challenge The digest challenge sent by the server 58 | * @param string $hostname The hostname of the machine you're connecting to 59 | * @param string $service The servicename (eg. imap, pop, acap etc) 60 | * @param string $authzid Authorization id (username to proxy as) 61 | * @return string The digest response (NOT base64 encoded) 62 | * @access public 63 | */ 64 | public function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '') 65 | { 66 | $challenge = $this->parseChallenge($challenge); 67 | $authzid_string = ''; 68 | if ($authzid != '') { 69 | $authzid_string = ',authzid="' . $authzid . '"'; 70 | } 71 | 72 | if (!empty($challenge)) { 73 | $cnonce = $this->getCnonce(); 74 | $digest_uri = sprintf('%s/%s', $service, $hostname); 75 | $response_value = $this->getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid); 76 | 77 | if ($challenge['realm']) { 78 | return sprintf('username="%s",realm="%s"' . $authzid_string . 79 | ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']); 80 | } else { 81 | return sprintf('username="%s"' . $authzid_string . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']); 82 | } 83 | } else { 84 | throw new Auth_SASL_Exception('Invalid digest challenge'); 85 | } 86 | } 87 | 88 | /** 89 | * Parses and verifies the digest challenge* 90 | * 91 | * @param string $challenge The digest challenge 92 | * @return array The parsed challenge as an assoc 93 | * array in the form "directive => value". 94 | * @access private 95 | */ 96 | private function parseChallenge($challenge) 97 | { 98 | $tokens = array(); 99 | while (preg_match('/^([a-z-]+)=("[^"]+(? 198 | -------------------------------------------------------------------------------- /lib/Auth/SASL/Exception.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | 31 | /** 32 | * Auth SASL Exception 33 | * 34 | * @author Michael Weibel 35 | * @package Auth_SASL 36 | */ 37 | class Auth_SASL_Exception extends Exception {} -------------------------------------------------------------------------------- /lib/Auth/SASL/External.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Implmentation of EXTERNAL SASL mechanism 38 | * 39 | * @author Christoph Schulz 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0.4 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Common.php'); 47 | 48 | class Auth_SASL_External extends Auth_SASL_Common 49 | { 50 | /** 51 | * Returns EXTERNAL response 52 | * 53 | * @param string $authcid Authentication id (username) 54 | * @param string $pass Password 55 | * @param string $authzid Autorization id 56 | * @return string EXTERNAL Response 57 | */ 58 | public function getResponse($authcid, $pass, $authzid = '') 59 | { 60 | return $authzid; 61 | } 62 | } 63 | ?> 64 | -------------------------------------------------------------------------------- /lib/Auth/SASL/Login.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * This is technically not a SASL mechanism, however 38 | * it's used by Net_Sieve, Net_Cyrus and potentially 39 | * other protocols , so here is a good place to abstract 40 | * it. 41 | * 42 | * @author Richard Heyes 43 | * @author Michael Weibel (made it work for PHP5) 44 | * @access public 45 | * @version 1.0 46 | * @package Auth_SASL 47 | */ 48 | 49 | require_once(dirname(__FILE__) . '/Common.php'); 50 | 51 | class Auth_SASL_Login extends Auth_SASL_Common 52 | { 53 | /** 54 | * Pseudo SASL LOGIN mechanism 55 | * 56 | * @param string $user Username 57 | * @param string $pass Password 58 | * @return string LOGIN string 59 | */ 60 | public function getResponse($user, $pass) 61 | { 62 | return sprintf('LOGIN %s %s', $user, $pass); 63 | } 64 | } 65 | ?> -------------------------------------------------------------------------------- /lib/Auth/SASL/Plain.php: -------------------------------------------------------------------------------- 1 | | 33 | // +-----------------------------------------------------------------------+ 34 | // 35 | 36 | /** 37 | * Implmentation of PLAIN SASL mechanism 38 | * 39 | * @author Richard Heyes 40 | * @author Michael Weibel (made it work for PHP5) 41 | * @access public 42 | * @version 1.0 43 | * @package Auth_SASL 44 | */ 45 | 46 | require_once(dirname(__FILE__) . '/Common.php'); 47 | 48 | class Auth_SASL_Plain extends Auth_SASL_Common 49 | { 50 | /** 51 | * Returns PLAIN response 52 | * 53 | * @param string $authcid Authentication id (username) 54 | * @param string $pass Password 55 | * @param string $authzid Autorization id 56 | * @return string PLAIN Response 57 | */ 58 | public function getResponse($authcid, $pass, $authzid = '') 59 | { 60 | return $authzid . chr(0) . $authcid . chr(0) . $pass; 61 | } 62 | } 63 | ?> 64 | -------------------------------------------------------------------------------- /lib/FirePHP/FirePHP.class.php: -------------------------------------------------------------------------------- 1 | 41 | * @license http://www.opensource.org/licenses/bsd-license.php 42 | * @package FirePHPCore 43 | */ 44 | 45 | /** 46 | * @see http://code.google.com/p/firephp/issues/detail?id=112 47 | */ 48 | if (!defined('E_STRICT')) { 49 | define('E_STRICT', 2048); 50 | } 51 | if (!defined('E_RECOVERABLE_ERROR')) { 52 | define('E_RECOVERABLE_ERROR', 4096); 53 | } 54 | if (!defined('E_DEPRECATED')) { 55 | define('E_DEPRECATED', 8192); 56 | } 57 | if (!defined('E_USER_DEPRECATED')) { 58 | define('E_USER_DEPRECATED', 16384); 59 | } 60 | 61 | /** 62 | * Sends the given data to the FirePHP Firefox Extension. 63 | * The data can be displayed in the Firebug Console or in the 64 | * "Server" request tab. 65 | * 66 | * For more information see: http://www.firephp.org/ 67 | * 68 | * @copyright Copyright (C) 2007-2009 Christoph Dorn 69 | * @author Christoph Dorn 70 | * @license http://www.opensource.org/licenses/bsd-license.php 71 | * @package FirePHPCore 72 | */ 73 | class FirePHP { 74 | 75 | /** 76 | * FirePHP version 77 | * 78 | * @var string 79 | */ 80 | const VERSION = '0.3'; // @pinf replace '0.3' with '%%package.version%%' 81 | 82 | /** 83 | * Firebug LOG level 84 | * 85 | * Logs a message to firebug console. 86 | * 87 | * @var string 88 | */ 89 | const LOG = 'LOG'; 90 | 91 | /** 92 | * Firebug INFO level 93 | * 94 | * Logs a message to firebug console and displays an info icon before the message. 95 | * 96 | * @var string 97 | */ 98 | const INFO = 'INFO'; 99 | 100 | /** 101 | * Firebug WARN level 102 | * 103 | * Logs a message to firebug console, displays an warning icon before the message and colors the line turquoise. 104 | * 105 | * @var string 106 | */ 107 | const WARN = 'WARN'; 108 | 109 | /** 110 | * Firebug ERROR level 111 | * 112 | * Logs a message to firebug console, displays an error icon before the message and colors the line yellow. Also increments the firebug error count. 113 | * 114 | * @var string 115 | */ 116 | const ERROR = 'ERROR'; 117 | 118 | /** 119 | * Dumps a variable to firebug's server panel 120 | * 121 | * @var string 122 | */ 123 | const DUMP = 'DUMP'; 124 | 125 | /** 126 | * Displays a stack trace in firebug console 127 | * 128 | * @var string 129 | */ 130 | const TRACE = 'TRACE'; 131 | 132 | /** 133 | * Displays an exception in firebug console 134 | * 135 | * Increments the firebug error count. 136 | * 137 | * @var string 138 | */ 139 | const EXCEPTION = 'EXCEPTION'; 140 | 141 | /** 142 | * Displays an table in firebug console 143 | * 144 | * @var string 145 | */ 146 | const TABLE = 'TABLE'; 147 | 148 | /** 149 | * Starts a group in firebug console 150 | * 151 | * @var string 152 | */ 153 | const GROUP_START = 'GROUP_START'; 154 | 155 | /** 156 | * Ends a group in firebug console 157 | * 158 | * @var string 159 | */ 160 | const GROUP_END = 'GROUP_END'; 161 | 162 | /** 163 | * Singleton instance of FirePHP 164 | * 165 | * @var FirePHP 166 | */ 167 | protected static $instance = null; 168 | 169 | /** 170 | * Flag whether we are logging from within the exception handler 171 | * 172 | * @var boolean 173 | */ 174 | protected $inExceptionHandler = false; 175 | 176 | /** 177 | * Flag whether to throw PHP errors that have been converted to ErrorExceptions 178 | * 179 | * @var boolean 180 | */ 181 | protected $throwErrorExceptions = true; 182 | 183 | /** 184 | * Flag whether to convert PHP assertion errors to Exceptions 185 | * 186 | * @var boolean 187 | */ 188 | protected $convertAssertionErrorsToExceptions = true; 189 | 190 | /** 191 | * Flag whether to throw PHP assertion errors that have been converted to Exceptions 192 | * 193 | * @var boolean 194 | */ 195 | protected $throwAssertionExceptions = false; 196 | 197 | /** 198 | * Wildfire protocol message index 199 | * 200 | * @var int 201 | */ 202 | protected $messageIndex = 1; 203 | 204 | /** 205 | * Options for the library 206 | * 207 | * @var array 208 | */ 209 | protected $options = array('maxDepth' => 10, 210 | 'maxObjectDepth' => 5, 211 | 'maxArrayDepth' => 5, 212 | 'useNativeJsonEncode' => true, 213 | 'includeLineNumbers' => true); 214 | 215 | /** 216 | * Filters used to exclude object members when encoding 217 | * 218 | * @var array 219 | */ 220 | protected $objectFilters = array( 221 | 'firephp' => array('objectStack', 'instance', 'json_objectStack'), 222 | 'firephp_test_class' => array('objectStack', 'instance', 'json_objectStack') 223 | ); 224 | 225 | /** 226 | * A stack of objects used to detect recursion during object encoding 227 | * 228 | * @var object 229 | */ 230 | protected $objectStack = array(); 231 | 232 | /** 233 | * Flag to enable/disable logging 234 | * 235 | * @var boolean 236 | */ 237 | protected $enabled = true; 238 | 239 | /** 240 | * The insight console to log to if applicable 241 | * 242 | * @var object 243 | */ 244 | protected $logToInsightConsole = null; 245 | 246 | /** 247 | * When the object gets serialized only include specific object members. 248 | * 249 | * @return array 250 | */ 251 | public function __sleep() 252 | { 253 | return array('options','objectFilters','enabled'); 254 | } 255 | 256 | /** 257 | * Gets singleton instance of FirePHP 258 | * 259 | * @param boolean $AutoCreate 260 | * @return FirePHP 261 | */ 262 | public static function getInstance($AutoCreate = false) 263 | { 264 | if ($AutoCreate===true && !self::$instance) { 265 | self::init(); 266 | } 267 | return self::$instance; 268 | } 269 | 270 | /** 271 | * Creates FirePHP object and stores it for singleton access 272 | * 273 | * @return FirePHP 274 | */ 275 | public static function init() 276 | { 277 | return self::setInstance(new self()); 278 | } 279 | 280 | /** 281 | * Set the instance of the FirePHP singleton 282 | * 283 | * @param FirePHP $instance The FirePHP object instance 284 | * @return FirePHP 285 | */ 286 | public static function setInstance($instance) 287 | { 288 | return self::$instance = $instance; 289 | } 290 | 291 | /** 292 | * Set an Insight console to direct all logging calls to 293 | * 294 | * @param object $console The console object to log to 295 | * @return void 296 | */ 297 | public function setLogToInsightConsole($console) 298 | { 299 | if(is_string($console)) { 300 | if(get_class($this)!='FirePHP_Insight' && !is_subclass_of($this, 'FirePHP_Insight')) { 301 | throw new Exception('FirePHP instance not an instance or subclass of FirePHP_Insight!'); 302 | } 303 | $this->logToInsightConsole = $this->to('request')->console($console); 304 | } else { 305 | $this->logToInsightConsole = $console; 306 | } 307 | } 308 | 309 | /** 310 | * Enable and disable logging to Firebug 311 | * 312 | * @param boolean $Enabled TRUE to enable, FALSE to disable 313 | * @return void 314 | */ 315 | public function setEnabled($Enabled) 316 | { 317 | $this->enabled = $Enabled; 318 | } 319 | 320 | /** 321 | * Check if logging is enabled 322 | * 323 | * @return boolean TRUE if enabled 324 | */ 325 | public function getEnabled() 326 | { 327 | return $this->enabled; 328 | } 329 | 330 | /** 331 | * Specify a filter to be used when encoding an object 332 | * 333 | * Filters are used to exclude object members. 334 | * 335 | * @param string $Class The class name of the object 336 | * @param array $Filter An array of members to exclude 337 | * @return void 338 | */ 339 | public function setObjectFilter($Class, $Filter) 340 | { 341 | $this->objectFilters[strtolower($Class)] = $Filter; 342 | } 343 | 344 | /** 345 | * Set some options for the library 346 | * 347 | * Options: 348 | * - maxDepth: The maximum depth to traverse (default: 10) 349 | * - maxObjectDepth: The maximum depth to traverse objects (default: 5) 350 | * - maxArrayDepth: The maximum depth to traverse arrays (default: 5) 351 | * - useNativeJsonEncode: If true will use json_encode() (default: true) 352 | * - includeLineNumbers: If true will include line numbers and filenames (default: true) 353 | * 354 | * @param array $Options The options to be set 355 | * @return void 356 | */ 357 | public function setOptions($Options) 358 | { 359 | $this->options = array_merge($this->options,$Options); 360 | } 361 | 362 | /** 363 | * Get options from the library 364 | * 365 | * @return array The currently set options 366 | */ 367 | public function getOptions() 368 | { 369 | return $this->options; 370 | } 371 | 372 | /** 373 | * Set an option for the library 374 | * 375 | * @param string $Name 376 | * @param mixed $Value 377 | * @throws Exception 378 | * @return void 379 | */ 380 | public function setOption($Name, $Value) 381 | { 382 | if (!isset($this->options[$Name])) { 383 | throw $this->newException('Unknown option: ' . $Name); 384 | } 385 | $this->options[$Name] = $Value; 386 | } 387 | 388 | /** 389 | * Get an option from the library 390 | * 391 | * @param string $Name 392 | * @throws Exception 393 | * @return mixed 394 | */ 395 | public function getOption($Name) 396 | { 397 | if (!isset($this->options[$Name])) { 398 | throw $this->newException('Unknown option: ' . $Name); 399 | } 400 | return $this->options[$Name]; 401 | } 402 | 403 | /** 404 | * Register FirePHP as your error handler 405 | * 406 | * Will throw exceptions for each php error. 407 | * 408 | * @return mixed Returns a string containing the previously defined error handler (if any) 409 | */ 410 | public function registerErrorHandler($throwErrorExceptions = false) 411 | { 412 | //NOTE: The following errors will not be caught by this error handler: 413 | // E_ERROR, E_PARSE, E_CORE_ERROR, 414 | // E_CORE_WARNING, E_COMPILE_ERROR, 415 | // E_COMPILE_WARNING, E_STRICT 416 | 417 | $this->throwErrorExceptions = $throwErrorExceptions; 418 | 419 | return set_error_handler(array($this,'errorHandler')); 420 | } 421 | 422 | /** 423 | * FirePHP's error handler 424 | * 425 | * Throws exception for each php error that will occur. 426 | * 427 | * @param int $errno 428 | * @param string $errstr 429 | * @param string $errfile 430 | * @param int $errline 431 | * @param array $errcontext 432 | */ 433 | public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) 434 | { 435 | // Don't throw exception if error reporting is switched off 436 | if (error_reporting() == 0) { 437 | return; 438 | } 439 | // Only throw exceptions for errors we are asking for 440 | if (error_reporting() & $errno) { 441 | 442 | $exception = new ErrorException($errstr, 0, $errno, $errfile, $errline); 443 | if ($this->throwErrorExceptions) { 444 | throw $exception; 445 | } else { 446 | $this->fb($exception); 447 | } 448 | } 449 | } 450 | 451 | /** 452 | * Register FirePHP as your exception handler 453 | * 454 | * @return mixed Returns the name of the previously defined exception handler, 455 | * or NULL on error. 456 | * If no previous handler was defined, NULL is also returned. 457 | */ 458 | public function registerExceptionHandler() 459 | { 460 | return set_exception_handler(array($this,'exceptionHandler')); 461 | } 462 | 463 | /** 464 | * FirePHP's exception handler 465 | * 466 | * Logs all exceptions to your firebug console and then stops the script. 467 | * 468 | * @param Exception $Exception 469 | * @throws Exception 470 | */ 471 | function exceptionHandler($Exception) 472 | { 473 | 474 | $this->inExceptionHandler = true; 475 | 476 | header('HTTP/1.1 500 Internal Server Error'); 477 | 478 | try { 479 | $this->fb($Exception); 480 | } catch (Exception $e) { 481 | echo 'We had an exception: ' . $e; 482 | } 483 | $this->inExceptionHandler = false; 484 | } 485 | 486 | /** 487 | * Register FirePHP driver as your assert callback 488 | * 489 | * @param boolean $convertAssertionErrorsToExceptions 490 | * @param boolean $throwAssertionExceptions 491 | * @return mixed Returns the original setting or FALSE on errors 492 | */ 493 | public function registerAssertionHandler($convertAssertionErrorsToExceptions = true, $throwAssertionExceptions = false) 494 | { 495 | $this->convertAssertionErrorsToExceptions = $convertAssertionErrorsToExceptions; 496 | $this->throwAssertionExceptions = $throwAssertionExceptions; 497 | 498 | if ($throwAssertionExceptions && !$convertAssertionErrorsToExceptions) { 499 | throw $this->newException('Cannot throw assertion exceptions as assertion errors are not being converted to exceptions!'); 500 | } 501 | 502 | return assert_options(ASSERT_CALLBACK, array($this, 'assertionHandler')); 503 | } 504 | 505 | /** 506 | * FirePHP's assertion handler 507 | * 508 | * Logs all assertions to your firebug console and then stops the script. 509 | * 510 | * @param string $file File source of assertion 511 | * @param int $line Line source of assertion 512 | * @param mixed $code Assertion code 513 | */ 514 | public function assertionHandler($file, $line, $code) 515 | { 516 | if ($this->convertAssertionErrorsToExceptions) { 517 | 518 | $exception = new ErrorException('Assertion Failed - Code[ '.$code.' ]', 0, null, $file, $line); 519 | 520 | if ($this->throwAssertionExceptions) { 521 | throw $exception; 522 | } else { 523 | $this->fb($exception); 524 | } 525 | 526 | } else { 527 | $this->fb($code, 'Assertion Failed', FirePHP::ERROR, array('File'=>$file,'Line'=>$line)); 528 | } 529 | } 530 | 531 | /** 532 | * Start a group for following messages. 533 | * 534 | * Options: 535 | * Collapsed: [true|false] 536 | * Color: [#RRGGBB|ColorName] 537 | * 538 | * @param string $Name 539 | * @param array $Options OPTIONAL Instructions on how to log the group 540 | * @return true 541 | * @throws Exception 542 | */ 543 | public function group($Name, $Options = null) 544 | { 545 | 546 | if (!$Name) { 547 | throw $this->newException('You must specify a label for the group!'); 548 | } 549 | 550 | if ($Options) { 551 | if (!is_array($Options)) { 552 | throw $this->newException('Options must be defined as an array!'); 553 | } 554 | if (array_key_exists('Collapsed', $Options)) { 555 | $Options['Collapsed'] = ($Options['Collapsed'])?'true':'false'; 556 | } 557 | } 558 | 559 | return $this->fb(null, $Name, FirePHP::GROUP_START, $Options); 560 | } 561 | 562 | /** 563 | * Ends a group you have started before 564 | * 565 | * @return true 566 | * @throws Exception 567 | */ 568 | public function groupEnd() 569 | { 570 | return $this->fb(null, null, FirePHP::GROUP_END); 571 | } 572 | 573 | /** 574 | * Log object with label to firebug console 575 | * 576 | * @see FirePHP::LOG 577 | * @param mixes $Object 578 | * @param string $Label 579 | * @return true 580 | * @throws Exception 581 | */ 582 | public function log($Object, $Label = null, $Options = array()) 583 | { 584 | return $this->fb($Object, $Label, FirePHP::LOG, $Options); 585 | } 586 | 587 | /** 588 | * Log object with label to firebug console 589 | * 590 | * @see FirePHP::INFO 591 | * @param mixes $Object 592 | * @param string $Label 593 | * @return true 594 | * @throws Exception 595 | */ 596 | public function info($Object, $Label = null, $Options = array()) 597 | { 598 | return $this->fb($Object, $Label, FirePHP::INFO, $Options); 599 | } 600 | 601 | /** 602 | * Log object with label to firebug console 603 | * 604 | * @see FirePHP::WARN 605 | * @param mixes $Object 606 | * @param string $Label 607 | * @return true 608 | * @throws Exception 609 | */ 610 | public function warn($Object, $Label = null, $Options = array()) 611 | { 612 | return $this->fb($Object, $Label, FirePHP::WARN, $Options); 613 | } 614 | 615 | /** 616 | * Log object with label to firebug console 617 | * 618 | * @see FirePHP::ERROR 619 | * @param mixes $Object 620 | * @param string $Label 621 | * @return true 622 | * @throws Exception 623 | */ 624 | public function error($Object, $Label = null, $Options = array()) 625 | { 626 | return $this->fb($Object, $Label, FirePHP::ERROR, $Options); 627 | } 628 | 629 | /** 630 | * Dumps key and variable to firebug server panel 631 | * 632 | * @see FirePHP::DUMP 633 | * @param string $Key 634 | * @param mixed $Variable 635 | * @return true 636 | * @throws Exception 637 | */ 638 | public function dump($Key, $Variable, $Options = array()) 639 | { 640 | if (!is_string($Key)) { 641 | throw $this->newException('Key passed to dump() is not a string'); 642 | } 643 | if (strlen($Key)>100) { 644 | throw $this->newException('Key passed to dump() is longer than 100 characters'); 645 | } 646 | if (!preg_match_all('/^[a-zA-Z0-9-_\.:]*$/', $Key, $m)) { 647 | throw $this->newException('Key passed to dump() contains invalid characters [a-zA-Z0-9-_\.:]'); 648 | } 649 | return $this->fb($Variable, $Key, FirePHP::DUMP, $Options); 650 | } 651 | 652 | /** 653 | * Log a trace in the firebug console 654 | * 655 | * @see FirePHP::TRACE 656 | * @param string $Label 657 | * @return true 658 | * @throws Exception 659 | */ 660 | public function trace($Label) 661 | { 662 | return $this->fb($Label, FirePHP::TRACE); 663 | } 664 | 665 | /** 666 | * Log a table in the firebug console 667 | * 668 | * @see FirePHP::TABLE 669 | * @param string $Label 670 | * @param string $Table 671 | * @return true 672 | * @throws Exception 673 | */ 674 | public function table($Label, $Table, $Options = array()) 675 | { 676 | return $this->fb($Table, $Label, FirePHP::TABLE, $Options); 677 | } 678 | 679 | /** 680 | * Insight API wrapper 681 | * 682 | * @see Insight_Helper::to() 683 | */ 684 | public static function to() 685 | { 686 | $instance = self::getInstance(); 687 | if (!method_exists($instance, "_to")) { 688 | throw new Exception("FirePHP::to() implementation not loaded"); 689 | } 690 | $args = func_get_args(); 691 | return call_user_func_array(array($instance, '_to'), $args); 692 | } 693 | 694 | /** 695 | * Insight API wrapper 696 | * 697 | * @see Insight_Helper::plugin() 698 | */ 699 | public static function plugin() 700 | { 701 | $instance = self::getInstance(); 702 | if (!method_exists($instance, "_plugin")) { 703 | throw new Exception("FirePHP::plugin() implementation not loaded"); 704 | } 705 | $args = func_get_args(); 706 | return call_user_func_array(array($instance, '_plugin'), $args); 707 | } 708 | 709 | /** 710 | * Check if FirePHP is installed on client 711 | * 712 | * @return boolean 713 | */ 714 | public function detectClientExtension() 715 | { 716 | // Check if FirePHP is installed on client via User-Agent header 717 | if (@preg_match_all('/\sFirePHP\/([\.\d]*)\s?/si',$this->getUserAgent(),$m) && 718 | version_compare($m[1][0],'0.0.6','>=')) { 719 | return true; 720 | } else 721 | // Check if FirePHP is installed on client via X-FirePHP-Version header 722 | if (@preg_match_all('/^([\.\d]*)$/si',$this->getRequestHeader("X-FirePHP-Version"),$m) && 723 | version_compare($m[1][0],'0.0.6','>=')) { 724 | return true; 725 | } 726 | return false; 727 | } 728 | 729 | /** 730 | * Log varible to Firebug 731 | * 732 | * @see http://www.firephp.org/Wiki/Reference/Fb 733 | * @param mixed $Object The variable to be logged 734 | * @return true Return TRUE if message was added to headers, FALSE otherwise 735 | * @throws Exception 736 | */ 737 | public function fb($Object) 738 | { 739 | if($this instanceof FirePHP_Insight && method_exists($this, '_logUpgradeClientMessage')) { 740 | if(!FirePHP_Insight::$upgradeClientMessageLogged) { // avoid infinite recursion as _logUpgradeClientMessage() logs a message 741 | $this->_logUpgradeClientMessage(); 742 | } 743 | } 744 | 745 | static $insightGroupStack = array(); 746 | 747 | if (!$this->getEnabled()) { 748 | return false; 749 | } 750 | 751 | if ($this->headersSent($filename, $linenum)) { 752 | // If we are logging from within the exception handler we cannot throw another exception 753 | if ($this->inExceptionHandler) { 754 | // Simply echo the error out to the page 755 | echo '
FirePHP ERROR: Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.
'; 756 | } else { 757 | throw $this->newException('Headers already sent in '.$filename.' on line '.$linenum.'. Cannot send log data to FirePHP. You must have Output Buffering enabled via ob_start() or output_buffering ini directive.'); 758 | } 759 | } 760 | 761 | $Type = null; 762 | $Label = null; 763 | $Options = array(); 764 | 765 | if (func_num_args()==1) { 766 | } else 767 | if (func_num_args()==2) { 768 | switch(func_get_arg(1)) { 769 | case self::LOG: 770 | case self::INFO: 771 | case self::WARN: 772 | case self::ERROR: 773 | case self::DUMP: 774 | case self::TRACE: 775 | case self::EXCEPTION: 776 | case self::TABLE: 777 | case self::GROUP_START: 778 | case self::GROUP_END: 779 | $Type = func_get_arg(1); 780 | break; 781 | default: 782 | $Label = func_get_arg(1); 783 | break; 784 | } 785 | } else 786 | if (func_num_args()==3) { 787 | $Type = func_get_arg(2); 788 | $Label = func_get_arg(1); 789 | } else 790 | if (func_num_args()==4) { 791 | $Type = func_get_arg(2); 792 | $Label = func_get_arg(1); 793 | $Options = func_get_arg(3); 794 | } else { 795 | throw $this->newException('Wrong number of arguments to fb() function!'); 796 | } 797 | 798 | if($this->logToInsightConsole!==null && (get_class($this)=='FirePHP_Insight' || is_subclass_of($this, 'FirePHP_Insight'))) { 799 | $msg = $this->logToInsightConsole; 800 | if ($Object instanceof Exception) { 801 | $Type = self::EXCEPTION; 802 | } 803 | if($Label && $Type!=self::TABLE && $Type!=self::GROUP_START) { 804 | $msg = $msg->label($Label); 805 | } 806 | switch($Type) { 807 | case self::DUMP: 808 | case self::LOG: 809 | return $msg->log($Object); 810 | case self::INFO: 811 | return $msg->info($Object); 812 | case self::WARN: 813 | return $msg->warn($Object); 814 | case self::ERROR: 815 | return $msg->error($Object); 816 | case self::TRACE: 817 | return $msg->trace($Object); 818 | case self::EXCEPTION: 819 | return $this->plugin('engine')->handleException($Object, $msg); 820 | case self::TABLE: 821 | if (isset($Object[0]) && !is_string($Object[0]) && $Label) { 822 | $Object = array($Label, $Object); 823 | } 824 | return $msg->table($Object[0], array_slice($Object[1],1), $Object[1][0]); 825 | case self::GROUP_START: 826 | $insightGroupStack[] = $msg->group(md5($Label))->open(); 827 | return $msg->log($Label); 828 | case self::GROUP_END: 829 | if(count($insightGroupStack)==0) { 830 | throw new Error('Too many groupEnd() as opposed to group() calls!'); 831 | } 832 | $group = array_pop($insightGroupStack); 833 | return $group->close(); 834 | default: 835 | return $msg->log($Object); 836 | } 837 | } 838 | 839 | if (!$this->detectClientExtension()) { 840 | return false; 841 | } 842 | 843 | $meta = array(); 844 | $skipFinalObjectEncode = false; 845 | 846 | if ($Object instanceof Exception) { 847 | 848 | $meta['file'] = $this->_escapeTraceFile($Object->getFile()); 849 | $meta['line'] = $Object->getLine(); 850 | 851 | $trace = $Object->getTrace(); 852 | if ($Object instanceof ErrorException 853 | && isset($trace[0]['function']) 854 | && $trace[0]['function']=='errorHandler' 855 | && isset($trace[0]['class']) 856 | && $trace[0]['class']=='FirePHP') { 857 | 858 | $severity = false; 859 | switch($Object->getSeverity()) { 860 | case E_WARNING: $severity = 'E_WARNING'; break; 861 | case E_NOTICE: $severity = 'E_NOTICE'; break; 862 | case E_USER_ERROR: $severity = 'E_USER_ERROR'; break; 863 | case E_USER_WARNING: $severity = 'E_USER_WARNING'; break; 864 | case E_USER_NOTICE: $severity = 'E_USER_NOTICE'; break; 865 | case E_STRICT: $severity = 'E_STRICT'; break; 866 | case E_RECOVERABLE_ERROR: $severity = 'E_RECOVERABLE_ERROR'; break; 867 | case E_DEPRECATED: $severity = 'E_DEPRECATED'; break; 868 | case E_USER_DEPRECATED: $severity = 'E_USER_DEPRECATED'; break; 869 | } 870 | 871 | $Object = array('Class'=>get_class($Object), 872 | 'Message'=>$severity.': '.$Object->getMessage(), 873 | 'File'=>$this->_escapeTraceFile($Object->getFile()), 874 | 'Line'=>$Object->getLine(), 875 | 'Type'=>'trigger', 876 | 'Trace'=>$this->_escapeTrace(array_splice($trace,2))); 877 | $skipFinalObjectEncode = true; 878 | } else { 879 | $Object = array('Class'=>get_class($Object), 880 | 'Message'=>$Object->getMessage(), 881 | 'File'=>$this->_escapeTraceFile($Object->getFile()), 882 | 'Line'=>$Object->getLine(), 883 | 'Type'=>'throw', 884 | 'Trace'=>$this->_escapeTrace($trace)); 885 | $skipFinalObjectEncode = true; 886 | } 887 | $Type = self::EXCEPTION; 888 | 889 | } else 890 | if ($Type==self::TRACE) { 891 | 892 | $trace = debug_backtrace(); 893 | if (!$trace) return false; 894 | for( $i=0 ; $i_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' 901 | || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { 902 | /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ 903 | } else 904 | if (isset($trace[$i]['class']) 905 | && isset($trace[$i+1]['file']) 906 | && $trace[$i]['class']=='FirePHP' 907 | && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { 908 | /* Skip fb() */ 909 | } else 910 | if ($trace[$i]['function']=='fb' 911 | || $trace[$i]['function']=='trace' 912 | || $trace[$i]['function']=='send') { 913 | 914 | $Object = array('Class'=>isset($trace[$i]['class'])?$trace[$i]['class']:'', 915 | 'Type'=>isset($trace[$i]['type'])?$trace[$i]['type']:'', 916 | 'Function'=>isset($trace[$i]['function'])?$trace[$i]['function']:'', 917 | 'Message'=>$trace[$i]['args'][0], 918 | 'File'=>isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):'', 919 | 'Line'=>isset($trace[$i]['line'])?$trace[$i]['line']:'', 920 | 'Args'=>isset($trace[$i]['args'])?$this->encodeObject($trace[$i]['args']):'', 921 | 'Trace'=>$this->_escapeTrace(array_splice($trace,$i+1))); 922 | 923 | $skipFinalObjectEncode = true; 924 | $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; 925 | $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; 926 | break; 927 | } 928 | } 929 | 930 | } else 931 | if ($Type==self::TABLE) { 932 | 933 | if (isset($Object[0]) && is_string($Object[0])) { 934 | $Object[1] = $this->encodeTable($Object[1]); 935 | } else { 936 | $Object = $this->encodeTable($Object); 937 | } 938 | 939 | $skipFinalObjectEncode = true; 940 | 941 | } else 942 | if ($Type==self::GROUP_START) { 943 | 944 | if (!$Label) { 945 | throw $this->newException('You must specify a label for the group!'); 946 | } 947 | 948 | } else { 949 | if ($Type===null) { 950 | $Type = self::LOG; 951 | } 952 | } 953 | 954 | if ($this->options['includeLineNumbers']) { 955 | if (!isset($meta['file']) || !isset($meta['line'])) { 956 | 957 | $trace = debug_backtrace(); 958 | for( $i=0 ; $trace && $i_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php' 965 | || substr($this->_standardizePath($trace[$i]['file']),-29,29)=='FirePHPCore/FirePHP.class.php')) { 966 | /* Skip - FB::trace(), FB::send(), $firephp->trace(), $firephp->fb() */ 967 | } else 968 | if (isset($trace[$i]['class']) 969 | && isset($trace[$i+1]['file']) 970 | && $trace[$i]['class']=='FirePHP' 971 | && substr($this->_standardizePath($trace[$i+1]['file']),-18,18)=='FirePHPCore/fb.php') { 972 | /* Skip fb() */ 973 | } else 974 | if (isset($trace[$i]['file']) 975 | && substr($this->_standardizePath($trace[$i]['file']),-18,18)=='FirePHPCore/fb.php') { 976 | /* Skip FB::fb() */ 977 | } else { 978 | $meta['file'] = isset($trace[$i]['file'])?$this->_escapeTraceFile($trace[$i]['file']):''; 979 | $meta['line'] = isset($trace[$i]['line'])?$trace[$i]['line']:''; 980 | break; 981 | } 982 | } 983 | } 984 | } else { 985 | unset($meta['file']); 986 | unset($meta['line']); 987 | } 988 | 989 | $this->setHeader('X-Wf-Protocol-1','http://meta.wildfirehq.org/Protocol/JsonStream/0.2'); 990 | $this->setHeader('X-Wf-1-Plugin-1','http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/'.self::VERSION); 991 | 992 | $structure_index = 1; 993 | if ($Type==self::DUMP) { 994 | $structure_index = 2; 995 | $this->setHeader('X-Wf-1-Structure-2','http://meta.firephp.org/Wildfire/Structure/FirePHP/Dump/0.1'); 996 | } else { 997 | $this->setHeader('X-Wf-1-Structure-1','http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'); 998 | } 999 | 1000 | if ($Type==self::DUMP) { 1001 | $msg = '{"'.$Label.'":'.$this->jsonEncode($Object, $skipFinalObjectEncode).'}'; 1002 | } else { 1003 | $msg_meta = $Options; 1004 | $msg_meta['Type'] = $Type; 1005 | if ($Label!==null) { 1006 | $msg_meta['Label'] = $Label; 1007 | } 1008 | if (isset($meta['file']) && !isset($msg_meta['File'])) { 1009 | $msg_meta['File'] = $meta['file']; 1010 | } 1011 | if (isset($meta['line']) && !isset($msg_meta['Line'])) { 1012 | $msg_meta['Line'] = $meta['line']; 1013 | } 1014 | $msg = '['.$this->jsonEncode($msg_meta).','.$this->jsonEncode($Object, $skipFinalObjectEncode).']'; 1015 | } 1016 | 1017 | $parts = explode("\n",chunk_split($msg, 5000, "\n")); 1018 | 1019 | for( $i=0 ; $i2) { 1025 | // Message needs to be split into multiple parts 1026 | $this->setHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, 1027 | (($i==0)?strlen($msg):'') 1028 | . '|' . $part . '|' 1029 | . (($isetHeader('X-Wf-1-'.$structure_index.'-'.'1-'.$this->messageIndex, 1032 | strlen($part) . '|' . $part . '|'); 1033 | } 1034 | 1035 | $this->messageIndex++; 1036 | 1037 | if ($this->messageIndex > 99999) { 1038 | throw $this->newException('Maximum number (99,999) of messages reached!'); 1039 | } 1040 | } 1041 | } 1042 | 1043 | $this->setHeader('X-Wf-1-Index',$this->messageIndex-1); 1044 | 1045 | return true; 1046 | } 1047 | 1048 | /** 1049 | * Standardizes path for windows systems. 1050 | * 1051 | * @param string $Path 1052 | * @return string 1053 | */ 1054 | protected function _standardizePath($Path) 1055 | { 1056 | return preg_replace('/\\\\+/','/',$Path); 1057 | } 1058 | 1059 | /** 1060 | * Escape trace path for windows systems 1061 | * 1062 | * @param array $Trace 1063 | * @return array 1064 | */ 1065 | protected function _escapeTrace($Trace) 1066 | { 1067 | if (!$Trace) return $Trace; 1068 | for( $i=0 ; $i_escapeTraceFile($Trace[$i]['file']); 1071 | } 1072 | if (isset($Trace[$i]['args'])) { 1073 | $Trace[$i]['args'] = $this->encodeObject($Trace[$i]['args']); 1074 | } 1075 | } 1076 | return $Trace; 1077 | } 1078 | 1079 | /** 1080 | * Escape file information of trace for windows systems 1081 | * 1082 | * @param string $File 1083 | * @return string 1084 | */ 1085 | protected function _escapeTraceFile($File) 1086 | { 1087 | /* Check if we have a windows filepath */ 1088 | if (strpos($File,'\\')) { 1089 | /* First strip down to single \ */ 1090 | 1091 | $file = preg_replace('/\\\\+/','\\',$File); 1092 | 1093 | return $file; 1094 | } 1095 | return $File; 1096 | } 1097 | 1098 | /** 1099 | * Check if headers have already been sent 1100 | * 1101 | * @param string $Filename 1102 | * @param integer $Linenum 1103 | */ 1104 | protected function headersSent(&$Filename, &$Linenum) 1105 | { 1106 | return headers_sent($Filename, $Linenum); 1107 | } 1108 | 1109 | /** 1110 | * Send header 1111 | * 1112 | * @param string $Name 1113 | * @param string $Value 1114 | */ 1115 | protected function setHeader($Name, $Value) 1116 | { 1117 | return header($Name.': '.$Value); 1118 | } 1119 | 1120 | /** 1121 | * Get user agent 1122 | * 1123 | * @return string|false 1124 | */ 1125 | protected function getUserAgent() 1126 | { 1127 | if (!isset($_SERVER['HTTP_USER_AGENT'])) return false; 1128 | return $_SERVER['HTTP_USER_AGENT']; 1129 | } 1130 | 1131 | /** 1132 | * Get all request headers 1133 | * 1134 | * @return array 1135 | */ 1136 | public static function getAllRequestHeaders() { 1137 | static $_cached_headers = false; 1138 | if($_cached_headers!==false) { 1139 | return $_cached_headers; 1140 | } 1141 | $headers = array(); 1142 | if(function_exists('getallheaders')) { 1143 | foreach( getallheaders() as $name => $value ) { 1144 | $headers[strtolower($name)] = $value; 1145 | } 1146 | } else { 1147 | foreach($_SERVER as $name => $value) { 1148 | if(substr($name, 0, 5) == 'HTTP_') { 1149 | $headers[strtolower(str_replace(' ', '-', str_replace('_', ' ', substr($name, 5))))] = $value; 1150 | } 1151 | } 1152 | } 1153 | return $_cached_headers = $headers; 1154 | } 1155 | 1156 | /** 1157 | * Get a request header 1158 | * 1159 | * @return string|false 1160 | */ 1161 | protected function getRequestHeader($Name) 1162 | { 1163 | $headers = self::getAllRequestHeaders(); 1164 | if (isset($headers[strtolower($Name)])) { 1165 | return $headers[strtolower($Name)]; 1166 | } 1167 | return false; 1168 | } 1169 | 1170 | /** 1171 | * Returns a new exception 1172 | * 1173 | * @param string $Message 1174 | * @return Exception 1175 | */ 1176 | protected function newException($Message) 1177 | { 1178 | return new Exception($Message); 1179 | } 1180 | 1181 | /** 1182 | * Encode an object into a JSON string 1183 | * 1184 | * Uses PHP's jeson_encode() if available 1185 | * 1186 | * @param object $Object The object to be encoded 1187 | * @return string The JSON string 1188 | */ 1189 | public function jsonEncode($Object, $skipObjectEncode = false) 1190 | { 1191 | if (!$skipObjectEncode) { 1192 | $Object = $this->encodeObject($Object); 1193 | } 1194 | 1195 | if (function_exists('json_encode') 1196 | && $this->options['useNativeJsonEncode']!=false) { 1197 | 1198 | return json_encode($Object); 1199 | } else { 1200 | return $this->json_encode($Object); 1201 | } 1202 | } 1203 | 1204 | /** 1205 | * Encodes a table by encoding each row and column with encodeObject() 1206 | * 1207 | * @param array $Table The table to be encoded 1208 | * @return array 1209 | */ 1210 | protected function encodeTable($Table) 1211 | { 1212 | 1213 | if (!$Table) return $Table; 1214 | 1215 | $new_table = array(); 1216 | foreach($Table as $row) { 1217 | 1218 | if (is_array($row)) { 1219 | $new_row = array(); 1220 | 1221 | foreach($row as $item) { 1222 | $new_row[] = $this->encodeObject($item); 1223 | } 1224 | 1225 | $new_table[] = $new_row; 1226 | } 1227 | } 1228 | 1229 | return $new_table; 1230 | } 1231 | 1232 | /** 1233 | * Encodes an object including members with 1234 | * protected and private visibility 1235 | * 1236 | * @param Object $Object The object to be encoded 1237 | * @param int $Depth The current traversal depth 1238 | * @return array All members of the object 1239 | */ 1240 | protected function encodeObject($Object, $ObjectDepth = 1, $ArrayDepth = 1, $MaxDepth = 1) 1241 | { 1242 | if ($MaxDepth > $this->options['maxDepth']) { 1243 | return '** Max Depth ('.$this->options['maxDepth'].') **'; 1244 | } 1245 | 1246 | $return = array(); 1247 | 1248 | if (is_resource($Object)) { 1249 | 1250 | return '** '.(string)$Object.' **'; 1251 | 1252 | } else 1253 | if (is_object($Object)) { 1254 | 1255 | if ($ObjectDepth > $this->options['maxObjectDepth']) { 1256 | return '** Max Object Depth ('.$this->options['maxObjectDepth'].') **'; 1257 | } 1258 | 1259 | foreach ($this->objectStack as $refVal) { 1260 | if ($refVal === $Object) { 1261 | return '** Recursion ('.get_class($Object).') **'; 1262 | } 1263 | } 1264 | array_push($this->objectStack, $Object); 1265 | 1266 | $return['__className'] = $class = get_class($Object); 1267 | $class_lower = strtolower($class); 1268 | 1269 | $reflectionClass = new ReflectionClass($class); 1270 | $properties = array(); 1271 | foreach( $reflectionClass->getProperties() as $property) { 1272 | $properties[$property->getName()] = $property; 1273 | } 1274 | 1275 | $members = (array)$Object; 1276 | 1277 | foreach( $properties as $plain_name => $property ) { 1278 | 1279 | $name = $raw_name = $plain_name; 1280 | if ($property->isStatic()) { 1281 | $name = 'static:'.$name; 1282 | } 1283 | if ($property->isPublic()) { 1284 | $name = 'public:'.$name; 1285 | } else 1286 | if ($property->isPrivate()) { 1287 | $name = 'private:'.$name; 1288 | $raw_name = "\0".$class."\0".$raw_name; 1289 | } else 1290 | if ($property->isProtected()) { 1291 | $name = 'protected:'.$name; 1292 | $raw_name = "\0".'*'."\0".$raw_name; 1293 | } 1294 | 1295 | if (!(isset($this->objectFilters[$class_lower]) 1296 | && is_array($this->objectFilters[$class_lower]) 1297 | && in_array($plain_name,$this->objectFilters[$class_lower]))) { 1298 | 1299 | if (array_key_exists($raw_name,$members) 1300 | && !$property->isStatic()) { 1301 | 1302 | $return[$name] = $this->encodeObject($members[$raw_name], $ObjectDepth + 1, 1, $MaxDepth + 1); 1303 | 1304 | } else { 1305 | if (method_exists($property,'setAccessible')) { 1306 | $property->setAccessible(true); 1307 | $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1); 1308 | } else 1309 | if ($property->isPublic()) { 1310 | $return[$name] = $this->encodeObject($property->getValue($Object), $ObjectDepth + 1, 1, $MaxDepth + 1); 1311 | } else { 1312 | $return[$name] = '** Need PHP 5.3 to get value **'; 1313 | } 1314 | } 1315 | } else { 1316 | $return[$name] = '** Excluded by Filter **'; 1317 | } 1318 | } 1319 | 1320 | // Include all members that are not defined in the class 1321 | // but exist in the object 1322 | foreach( $members as $raw_name => $value ) { 1323 | 1324 | $name = $raw_name; 1325 | 1326 | if ($name{0} == "\0") { 1327 | $parts = explode("\0", $name); 1328 | $name = $parts[2]; 1329 | } 1330 | 1331 | $plain_name = $name; 1332 | 1333 | if (!isset($properties[$name])) { 1334 | $name = 'undeclared:'.$name; 1335 | 1336 | if (!(isset($this->objectFilters[$class_lower]) 1337 | && is_array($this->objectFilters[$class_lower]) 1338 | && in_array($plain_name,$this->objectFilters[$class_lower]))) { 1339 | 1340 | $return[$name] = $this->encodeObject($value, $ObjectDepth + 1, 1, $MaxDepth + 1); 1341 | } else { 1342 | $return[$name] = '** Excluded by Filter **'; 1343 | } 1344 | } 1345 | } 1346 | 1347 | array_pop($this->objectStack); 1348 | 1349 | } elseif (is_array($Object)) { 1350 | 1351 | if ($ArrayDepth > $this->options['maxArrayDepth']) { 1352 | return '** Max Array Depth ('.$this->options['maxArrayDepth'].') **'; 1353 | } 1354 | 1355 | foreach ($Object as $key => $val) { 1356 | 1357 | // Encoding the $GLOBALS PHP array causes an infinite loop 1358 | // if the recursion is not reset here as it contains 1359 | // a reference to itself. This is the only way I have come up 1360 | // with to stop infinite recursion in this case. 1361 | if ($key=='GLOBALS' 1362 | && is_array($val) 1363 | && array_key_exists('GLOBALS',$val)) { 1364 | $val['GLOBALS'] = '** Recursion (GLOBALS) **'; 1365 | } 1366 | 1367 | $return[$key] = $this->encodeObject($val, 1, $ArrayDepth + 1, $MaxDepth + 1); 1368 | } 1369 | } else { 1370 | if (self::is_utf8($Object)) { 1371 | return $Object; 1372 | } else { 1373 | return utf8_encode($Object); 1374 | } 1375 | } 1376 | return $return; 1377 | } 1378 | 1379 | /** 1380 | * Returns true if $string is valid UTF-8 and false otherwise. 1381 | * 1382 | * @param mixed $str String to be tested 1383 | * @return boolean 1384 | */ 1385 | protected static function is_utf8($str) 1386 | { 1387 | if(function_exists('mb_detect_encoding')) { 1388 | return (mb_detect_encoding($str) == 'UTF-8'); 1389 | } 1390 | $c=0; $b=0; 1391 | $bits=0; 1392 | $len=strlen($str); 1393 | for($i=0; $i<$len; $i++){ 1394 | $c=ord($str[$i]); 1395 | if ($c > 128){ 1396 | if (($c >= 254)) return false; 1397 | elseif ($c >= 252) $bits=6; 1398 | elseif ($c >= 248) $bits=5; 1399 | elseif ($c >= 240) $bits=4; 1400 | elseif ($c >= 224) $bits=3; 1401 | elseif ($c >= 192) $bits=2; 1402 | else return false; 1403 | if (($i+$bits) > $len) return false; 1404 | while($bits > 1){ 1405 | $i++; 1406 | $b=ord($str[$i]); 1407 | if ($b < 128 || $b > 191) return false; 1408 | $bits--; 1409 | } 1410 | } 1411 | } 1412 | return true; 1413 | } 1414 | 1415 | /** 1416 | * Converts to and from JSON format. 1417 | * 1418 | * JSON (JavaScript Object Notation) is a lightweight data-interchange 1419 | * format. It is easy for humans to read and write. It is easy for machines 1420 | * to parse and generate. It is based on a subset of the JavaScript 1421 | * Programming Language, Standard ECMA-262 3rd Edition - December 1999. 1422 | * This feature can also be found in Python. JSON is a text format that is 1423 | * completely language independent but uses conventions that are familiar 1424 | * to programmers of the C-family of languages, including C, C++, C#, Java, 1425 | * JavaScript, Perl, TCL, and many others. These properties make JSON an 1426 | * ideal data-interchange language. 1427 | * 1428 | * This package provides a simple encoder and decoder for JSON notation. It 1429 | * is intended for use with client-side Javascript applications that make 1430 | * use of HTTPRequest to perform server communication functions - data can 1431 | * be encoded into JSON notation for use in a client-side javascript, or 1432 | * decoded from incoming Javascript requests. JSON format is native to 1433 | * Javascript, and can be directly eval()'ed with no further parsing 1434 | * overhead 1435 | * 1436 | * All strings should be in ASCII or UTF-8 format! 1437 | * 1438 | * LICENSE: Redistribution and use in source and binary forms, with or 1439 | * without modification, are permitted provided that the following 1440 | * conditions are met: Redistributions of source code must retain the 1441 | * above copyright notice, this list of conditions and the following 1442 | * disclaimer. Redistributions in binary form must reproduce the above 1443 | * copyright notice, this list of conditions and the following disclaimer 1444 | * in the documentation and/or other materials provided with the 1445 | * distribution. 1446 | * 1447 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED 1448 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 1449 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 1450 | * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 1451 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 1452 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 1453 | * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 1454 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 1455 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 1456 | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 1457 | * DAMAGE. 1458 | * 1459 | * @category 1460 | * @package Services_JSON 1461 | * @author Michal Migurski 1462 | * @author Matt Knapp 1463 | * @author Brett Stimmerman 1464 | * @author Christoph Dorn 1465 | * @copyright 2005 Michal Migurski 1466 | * @version CVS: $Id: FirePHP.class.php 51 2011-03-31 06:59:05Z michael $ 1467 | * @license http://www.opensource.org/licenses/bsd-license.php 1468 | * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 1469 | */ 1470 | 1471 | 1472 | /** 1473 | * Keep a list of objects as we descend into the array so we can detect recursion. 1474 | */ 1475 | private $json_objectStack = array(); 1476 | 1477 | 1478 | /** 1479 | * convert a string from one UTF-8 char to one UTF-16 char 1480 | * 1481 | * Normally should be handled by mb_convert_encoding, but 1482 | * provides a slower PHP-only method for installations 1483 | * that lack the multibye string extension. 1484 | * 1485 | * @param string $utf8 UTF-8 character 1486 | * @return string UTF-16 character 1487 | * @access private 1488 | */ 1489 | private function json_utf82utf16($utf8) 1490 | { 1491 | // oh please oh please oh please oh please oh please 1492 | if (function_exists('mb_convert_encoding')) { 1493 | return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); 1494 | } 1495 | 1496 | switch(strlen($utf8)) { 1497 | case 1: 1498 | // this case should never be reached, because we are in ASCII range 1499 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1500 | return $utf8; 1501 | 1502 | case 2: 1503 | // return a UTF-16 character from a 2-byte UTF-8 char 1504 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1505 | return chr(0x07 & (ord($utf8{0}) >> 2)) 1506 | . chr((0xC0 & (ord($utf8{0}) << 6)) 1507 | | (0x3F & ord($utf8{1}))); 1508 | 1509 | case 3: 1510 | // return a UTF-16 character from a 3-byte UTF-8 char 1511 | // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1512 | return chr((0xF0 & (ord($utf8{0}) << 4)) 1513 | | (0x0F & (ord($utf8{1}) >> 2))) 1514 | . chr((0xC0 & (ord($utf8{1}) << 6)) 1515 | | (0x7F & ord($utf8{2}))); 1516 | } 1517 | 1518 | // ignoring UTF-32 for now, sorry 1519 | return ''; 1520 | } 1521 | 1522 | /** 1523 | * encodes an arbitrary variable into JSON format 1524 | * 1525 | * @param mixed $var any number, boolean, string, array, or object to be encoded. 1526 | * see argument 1 to Services_JSON() above for array-parsing behavior. 1527 | * if var is a strng, note that encode() always expects it 1528 | * to be in ASCII or UTF-8 format! 1529 | * 1530 | * @return mixed JSON string representation of input var or an error if a problem occurs 1531 | * @access public 1532 | */ 1533 | private function json_encode($var) 1534 | { 1535 | 1536 | if (is_object($var)) { 1537 | if (in_array($var,$this->json_objectStack)) { 1538 | return '"** Recursion **"'; 1539 | } 1540 | } 1541 | 1542 | switch (gettype($var)) { 1543 | case 'boolean': 1544 | return $var ? 'true' : 'false'; 1545 | 1546 | case 'NULL': 1547 | return 'null'; 1548 | 1549 | case 'integer': 1550 | return (int) $var; 1551 | 1552 | case 'double': 1553 | case 'float': 1554 | return (float) $var; 1555 | 1556 | case 'string': 1557 | // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT 1558 | $ascii = ''; 1559 | $strlen_var = strlen($var); 1560 | 1561 | /* 1562 | * Iterate over every character in the string, 1563 | * escaping with a slash or encoding to UTF-8 where necessary 1564 | */ 1565 | for ($c = 0; $c < $strlen_var; ++$c) { 1566 | 1567 | $ord_var_c = ord($var{$c}); 1568 | 1569 | switch (true) { 1570 | case $ord_var_c == 0x08: 1571 | $ascii .= '\b'; 1572 | break; 1573 | case $ord_var_c == 0x09: 1574 | $ascii .= '\t'; 1575 | break; 1576 | case $ord_var_c == 0x0A: 1577 | $ascii .= '\n'; 1578 | break; 1579 | case $ord_var_c == 0x0C: 1580 | $ascii .= '\f'; 1581 | break; 1582 | case $ord_var_c == 0x0D: 1583 | $ascii .= '\r'; 1584 | break; 1585 | 1586 | case $ord_var_c == 0x22: 1587 | case $ord_var_c == 0x2F: 1588 | case $ord_var_c == 0x5C: 1589 | // double quote, slash, slosh 1590 | $ascii .= '\\'.$var{$c}; 1591 | break; 1592 | 1593 | case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): 1594 | // characters U-00000000 - U-0000007F (same as ASCII) 1595 | $ascii .= $var{$c}; 1596 | break; 1597 | 1598 | case (($ord_var_c & 0xE0) == 0xC0): 1599 | // characters U-00000080 - U-000007FF, mask 110XXXXX 1600 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1601 | $char = pack('C*', $ord_var_c, ord($var{$c + 1})); 1602 | $c += 1; 1603 | $utf16 = $this->json_utf82utf16($char); 1604 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 1605 | break; 1606 | 1607 | case (($ord_var_c & 0xF0) == 0xE0): 1608 | // characters U-00000800 - U-0000FFFF, mask 1110XXXX 1609 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1610 | $char = pack('C*', $ord_var_c, 1611 | ord($var{$c + 1}), 1612 | ord($var{$c + 2})); 1613 | $c += 2; 1614 | $utf16 = $this->json_utf82utf16($char); 1615 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 1616 | break; 1617 | 1618 | case (($ord_var_c & 0xF8) == 0xF0): 1619 | // characters U-00010000 - U-001FFFFF, mask 11110XXX 1620 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1621 | $char = pack('C*', $ord_var_c, 1622 | ord($var{$c + 1}), 1623 | ord($var{$c + 2}), 1624 | ord($var{$c + 3})); 1625 | $c += 3; 1626 | $utf16 = $this->json_utf82utf16($char); 1627 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 1628 | break; 1629 | 1630 | case (($ord_var_c & 0xFC) == 0xF8): 1631 | // characters U-00200000 - U-03FFFFFF, mask 111110XX 1632 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1633 | $char = pack('C*', $ord_var_c, 1634 | ord($var{$c + 1}), 1635 | ord($var{$c + 2}), 1636 | ord($var{$c + 3}), 1637 | ord($var{$c + 4})); 1638 | $c += 4; 1639 | $utf16 = $this->json_utf82utf16($char); 1640 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 1641 | break; 1642 | 1643 | case (($ord_var_c & 0xFE) == 0xFC): 1644 | // characters U-04000000 - U-7FFFFFFF, mask 1111110X 1645 | // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 1646 | $char = pack('C*', $ord_var_c, 1647 | ord($var{$c + 1}), 1648 | ord($var{$c + 2}), 1649 | ord($var{$c + 3}), 1650 | ord($var{$c + 4}), 1651 | ord($var{$c + 5})); 1652 | $c += 5; 1653 | $utf16 = $this->json_utf82utf16($char); 1654 | $ascii .= sprintf('\u%04s', bin2hex($utf16)); 1655 | break; 1656 | } 1657 | } 1658 | 1659 | return '"'.$ascii.'"'; 1660 | 1661 | case 'array': 1662 | /* 1663 | * As per JSON spec if any array key is not an integer 1664 | * we must treat the the whole array as an object. We 1665 | * also try to catch a sparsely populated associative 1666 | * array with numeric keys here because some JS engines 1667 | * will create an array with empty indexes up to 1668 | * max_index which can cause memory issues and because 1669 | * the keys, which may be relevant, will be remapped 1670 | * otherwise. 1671 | * 1672 | * As per the ECMA and JSON specification an object may 1673 | * have any string as a property. Unfortunately due to 1674 | * a hole in the ECMA specification if the key is a 1675 | * ECMA reserved word or starts with a digit the 1676 | * parameter is only accessible using ECMAScript's 1677 | * bracket notation. 1678 | */ 1679 | 1680 | // treat as a JSON object 1681 | if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { 1682 | 1683 | $this->json_objectStack[] = $var; 1684 | 1685 | $properties = array_map(array($this, 'json_name_value'), 1686 | array_keys($var), 1687 | array_values($var)); 1688 | 1689 | array_pop($this->json_objectStack); 1690 | 1691 | foreach($properties as $property) { 1692 | if ($property instanceof Exception) { 1693 | return $property; 1694 | } 1695 | } 1696 | 1697 | return '{' . join(',', $properties) . '}'; 1698 | } 1699 | 1700 | $this->json_objectStack[] = $var; 1701 | 1702 | // treat it like a regular array 1703 | $elements = array_map(array($this, 'json_encode'), $var); 1704 | 1705 | array_pop($this->json_objectStack); 1706 | 1707 | foreach($elements as $element) { 1708 | if ($element instanceof Exception) { 1709 | return $element; 1710 | } 1711 | } 1712 | 1713 | return '[' . join(',', $elements) . ']'; 1714 | 1715 | case 'object': 1716 | $vars = self::encodeObject($var); 1717 | 1718 | $this->json_objectStack[] = $var; 1719 | 1720 | $properties = array_map(array($this, 'json_name_value'), 1721 | array_keys($vars), 1722 | array_values($vars)); 1723 | 1724 | array_pop($this->json_objectStack); 1725 | 1726 | foreach($properties as $property) { 1727 | if ($property instanceof Exception) { 1728 | return $property; 1729 | } 1730 | } 1731 | 1732 | return '{' . join(',', $properties) . '}'; 1733 | 1734 | default: 1735 | return null; 1736 | } 1737 | } 1738 | 1739 | /** 1740 | * array-walking function for use in generating JSON-formatted name-value pairs 1741 | * 1742 | * @param string $name name of key to use 1743 | * @param mixed $value reference to an array element to be encoded 1744 | * 1745 | * @return string JSON-formatted name-value pair, like '"name":value' 1746 | * @access private 1747 | */ 1748 | private function json_name_value($name, $value) 1749 | { 1750 | // Encoding the $GLOBALS PHP array causes an infinite loop 1751 | // if the recursion is not reset here as it contains 1752 | // a reference to itself. This is the only way I have come up 1753 | // with to stop infinite recursion in this case. 1754 | if ($name=='GLOBALS' 1755 | && is_array($value) 1756 | && array_key_exists('GLOBALS',$value)) { 1757 | $value['GLOBALS'] = '** Recursion **'; 1758 | } 1759 | 1760 | $encoded_value = $this->json_encode($value); 1761 | 1762 | if ($encoded_value instanceof Exception) { 1763 | return $encoded_value; 1764 | } 1765 | 1766 | return $this->json_encode(strval($name)) . ':' . $encoded_value; 1767 | } 1768 | 1769 | /** 1770 | * @deprecated 1771 | */ 1772 | public function setProcessorUrl($URL) 1773 | { 1774 | trigger_error("The FirePHP::setProcessorUrl() method is no longer supported", E_USER_DEPRECATED); 1775 | } 1776 | 1777 | /** 1778 | * @deprecated 1779 | */ 1780 | public function setRendererUrl($URL) 1781 | { 1782 | trigger_error("The FirePHP::setRendererUrl() method is no longer supported", E_USER_DEPRECATED); 1783 | } 1784 | } 1785 | -------------------------------------------------------------------------------- /lib/FirePHP/LICENSE: -------------------------------------------------------------------------------- 1 | Software License Agreement (New BSD License) 2 | 3 | Copyright (c) 2006-2009, Christoph Dorn 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Christoph Dorn nor the names of its 17 | contributors may be used to endorse or promote products derived from this 18 | software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /lib/FirePHP/fb.php: -------------------------------------------------------------------------------- 1 | 41 | * @license http://www.opensource.org/licenses/bsd-license.php 42 | * @package FirePHPCore 43 | */ 44 | 45 | if(!class_exists('FirePHP')) { 46 | require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'FirePHP.class.php'; 47 | } 48 | 49 | /** 50 | * Sends the given data to the FirePHP Firefox Extension. 51 | * The data can be displayed in the Firebug Console or in the 52 | * "Server" request tab. 53 | * 54 | * @see http://www.firephp.org/Wiki/Reference/Fb 55 | * @param mixed $Object 56 | * @return true 57 | * @throws Exception 58 | */ 59 | function fb() 60 | { 61 | $instance = FirePHP::getInstance(true); 62 | 63 | $args = func_get_args(); 64 | return call_user_func_array(array($instance,'fb'),$args); 65 | } 66 | 67 | 68 | class FB 69 | { 70 | /** 71 | * Enable and disable logging to Firebug 72 | * 73 | * @see FirePHP->setEnabled() 74 | * @param boolean $Enabled TRUE to enable, FALSE to disable 75 | * @return void 76 | */ 77 | public static function setEnabled($Enabled) 78 | { 79 | $instance = FirePHP::getInstance(true); 80 | $instance->setEnabled($Enabled); 81 | } 82 | 83 | /** 84 | * Check if logging is enabled 85 | * 86 | * @see FirePHP->getEnabled() 87 | * @return boolean TRUE if enabled 88 | */ 89 | public static function getEnabled() 90 | { 91 | $instance = FirePHP::getInstance(true); 92 | return $instance->getEnabled(); 93 | } 94 | 95 | /** 96 | * Specify a filter to be used when encoding an object 97 | * 98 | * Filters are used to exclude object members. 99 | * 100 | * @see FirePHP->setObjectFilter() 101 | * @param string $Class The class name of the object 102 | * @param array $Filter An array or members to exclude 103 | * @return void 104 | */ 105 | public static function setObjectFilter($Class, $Filter) 106 | { 107 | $instance = FirePHP::getInstance(true); 108 | $instance->setObjectFilter($Class, $Filter); 109 | } 110 | 111 | /** 112 | * Set some options for the library 113 | * 114 | * @see FirePHP->setOptions() 115 | * @param array $Options The options to be set 116 | * @return void 117 | */ 118 | public static function setOptions($Options) 119 | { 120 | $instance = FirePHP::getInstance(true); 121 | $instance->setOptions($Options); 122 | } 123 | 124 | /** 125 | * Get options for the library 126 | * 127 | * @see FirePHP->getOptions() 128 | * @return array The options 129 | */ 130 | public static function getOptions() 131 | { 132 | $instance = FirePHP::getInstance(true); 133 | return $instance->getOptions(); 134 | } 135 | 136 | /** 137 | * Log object to firebug 138 | * 139 | * @see http://www.firephp.org/Wiki/Reference/Fb 140 | * @param mixed $Object 141 | * @return true 142 | * @throws Exception 143 | */ 144 | public static function send() 145 | { 146 | $instance = FirePHP::getInstance(true); 147 | $args = func_get_args(); 148 | return call_user_func_array(array($instance,'fb'),$args); 149 | } 150 | 151 | /** 152 | * Start a group for following messages 153 | * 154 | * Options: 155 | * Collapsed: [true|false] 156 | * Color: [#RRGGBB|ColorName] 157 | * 158 | * @param string $Name 159 | * @param array $Options OPTIONAL Instructions on how to log the group 160 | * @return true 161 | */ 162 | public static function group($Name, $Options=null) 163 | { 164 | $instance = FirePHP::getInstance(true); 165 | return $instance->group($Name, $Options); 166 | } 167 | 168 | /** 169 | * Ends a group you have started before 170 | * 171 | * @return true 172 | * @throws Exception 173 | */ 174 | public static function groupEnd() 175 | { 176 | return self::send(null, null, FirePHP::GROUP_END); 177 | } 178 | 179 | /** 180 | * Log object with label to firebug console 181 | * 182 | * @see FirePHP::LOG 183 | * @param mixes $Object 184 | * @param string $Label 185 | * @return true 186 | * @throws Exception 187 | */ 188 | public static function log($Object, $Label=null) 189 | { 190 | return self::send($Object, $Label, FirePHP::LOG); 191 | } 192 | 193 | /** 194 | * Log object with label to firebug console 195 | * 196 | * @see FirePHP::INFO 197 | * @param mixes $Object 198 | * @param string $Label 199 | * @return true 200 | * @throws Exception 201 | */ 202 | public static function info($Object, $Label=null) 203 | { 204 | return self::send($Object, $Label, FirePHP::INFO); 205 | } 206 | 207 | /** 208 | * Log object with label to firebug console 209 | * 210 | * @see FirePHP::WARN 211 | * @param mixes $Object 212 | * @param string $Label 213 | * @return true 214 | * @throws Exception 215 | */ 216 | public static function warn($Object, $Label=null) 217 | { 218 | return self::send($Object, $Label, FirePHP::WARN); 219 | } 220 | 221 | /** 222 | * Log object with label to firebug console 223 | * 224 | * @see FirePHP::ERROR 225 | * @param mixes $Object 226 | * @param string $Label 227 | * @return true 228 | * @throws Exception 229 | */ 230 | public static function error($Object, $Label=null) 231 | { 232 | return self::send($Object, $Label, FirePHP::ERROR); 233 | } 234 | 235 | /** 236 | * Dumps key and variable to firebug server panel 237 | * 238 | * @see FirePHP::DUMP 239 | * @param string $Key 240 | * @param mixed $Variable 241 | * @return true 242 | * @throws Exception 243 | */ 244 | public static function dump($Key, $Variable) 245 | { 246 | return self::send($Variable, $Key, FirePHP::DUMP); 247 | } 248 | 249 | /** 250 | * Log a trace in the firebug console 251 | * 252 | * @see FirePHP::TRACE 253 | * @param string $Label 254 | * @return true 255 | * @throws Exception 256 | */ 257 | public static function trace($Label) 258 | { 259 | return self::send($Label, FirePHP::TRACE); 260 | } 261 | 262 | /** 263 | * Log a table in the firebug console 264 | * 265 | * @see FirePHP::TABLE 266 | * @param string $Label 267 | * @param string $Table 268 | * @return true 269 | * @throws Exception 270 | */ 271 | public static function table($Label, $Table) 272 | { 273 | return self::send($Table, $Label, FirePHP::TABLE); 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /lib/XmppPrebind.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | /** 10 | * FirePHP for debugging 11 | */ 12 | include 'FirePHP/fb.php'; 13 | 14 | /** 15 | * PEAR Auth_SASL 16 | */ 17 | require 'Auth/SASL.php'; 18 | 19 | /** 20 | * XMPP Library for connecting to jabber server & receiving sid and rid 21 | */ 22 | class XmppPrebind { 23 | 24 | const XMLNS_BODY = 'http://jabber.org/protocol/httpbind'; 25 | const XMLNS_BOSH = 'urn:xmpp:xbosh'; 26 | const XMLNS_CLIENT = 'jabber:client'; 27 | const XMLNS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'; 28 | const XMLNS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'; 29 | const XMLNS_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'; 30 | const XMLNS_VCARD = 'vcard-temp'; 31 | 32 | const XML_LANG = 'en'; 33 | const CONTENT_TYPE = 'text/xml charset=utf-8'; 34 | 35 | const ENCRYPTION_PLAIN = 'PLAIN'; 36 | const ENCRYPTION_DIGEST_MD5 = 'DIGEST-MD5'; 37 | const ENCRYPTION_CRAM_MD5 = 'CRAM-MD5'; 38 | 39 | const SERVICE_NAME = 'xmpp'; 40 | 41 | protected $jabberHost = ''; 42 | protected $boshUri = ''; 43 | protected $resource = ''; 44 | 45 | protected $debug = false; 46 | /** 47 | * FirePHP Instance 48 | * 49 | * @var FirePHP 50 | */ 51 | protected $firePhp = null; 52 | 53 | protected $useGzip = false; 54 | protected $useSsl = false; 55 | protected $encryption = self::ENCRYPTION_PLAIN; 56 | 57 | protected $jid = ''; 58 | protected $password = ''; 59 | 60 | protected $rid = ''; 61 | protected $sid = ''; 62 | 63 | protected $doSession = false; 64 | protected $doBind = false; 65 | 66 | protected $mechanisms = array(); 67 | 68 | // the Bosh attributes for use in a client using this prebound session 69 | protected $wait; 70 | protected $requests; 71 | protected $ver; 72 | protected $polling; 73 | protected $inactivity; 74 | protected $hold; 75 | protected $to; 76 | protected $ack; 77 | protected $accept; 78 | protected $maxpause; 79 | 80 | /** 81 | * Session creation response 82 | * 83 | * @var DOMDocument 84 | */ 85 | public $response; 86 | 87 | /** 88 | * Create a new XmppPrebind Object with the required params 89 | * 90 | * @param string $jabberHost Jabber Server Host 91 | * @param string $boshUri Full URI to the http-bind 92 | * @param string $resource Resource identifier 93 | * @param bool $useSsl Use SSL (not working yet, TODO) 94 | * @param bool $debug Enable debug 95 | */ 96 | public function __construct($jabberHost, $boshUri, $resource, $useSsl = false, $debug = false) { 97 | $this->jabberHost = $jabberHost; 98 | $this->boshUri = $boshUri; 99 | $this->resource = $resource; 100 | 101 | $this->useSsl = $useSsl; 102 | 103 | $this->debug = $debug; 104 | if ($this->debug === true) { 105 | $this->firePhp = FirePHP::getInstance(true); 106 | $this->firePhp->setEnabled(true); 107 | } 108 | 109 | /* TODO: Not working 110 | if (function_exists('gzinflate')) { 111 | $this->useGzip = true; 112 | }*/ 113 | 114 | /* 115 | * The client MUST generate a large, random, positive integer for the initial 'rid' (see Security Considerations) 116 | * and then increment that value by one for each subsequent request. The client MUST take care to choose an 117 | * initial 'rid' that will never be incremented above 9007199254740991 [21] within the session. 118 | * In practice, a session would have to be extraordinarily long (or involve the exchange of an extraordinary 119 | * number of packets) to exceed the defined limit. 120 | * 121 | * @link http://xmpp.org/extensions/xep-0124.html#rids 122 | */ 123 | if (function_exists('mt_rand')) { 124 | $this->rid = mt_rand(1000000000, 10000000000); 125 | } else { 126 | $this->rid = rand(1000000000, 10000000000); 127 | } 128 | } 129 | 130 | /** 131 | * connect to the jabber server with the supplied username & password 132 | * 133 | * @param string $username Username without jabber host 134 | * @param string $password Password 135 | * @param string $route Route 136 | */ 137 | public function connect($username, $password, $route = false) { 138 | $this->jid = $username . '@' . $this->jabberHost; 139 | 140 | if($this->resource) { 141 | $this->jid .= '/' . $this->resource; 142 | } 143 | 144 | $this->password = $password; 145 | 146 | $response = $this->sendInitialConnection($route); 147 | if(empty($response)) { 148 | throw new XmppPrebindConnectionException("No response from server."); 149 | } 150 | 151 | $body = self::getBodyFromXml($response); 152 | if ( empty( $body ) ) 153 | throw new XmppPrebindConnectionException("No body could be found in response from server."); 154 | $this->sid = $body->getAttribute('sid'); 155 | 156 | // set the Bosh Attributes 157 | $this->wait = $body->getAttribute('wait'); 158 | $this->requests = $body->getAttribute('requests'); 159 | $this->ver = $body->getAttribute('ver'); 160 | $this->polling = $body->getAttribute('polling'); 161 | $this->inactivity = $body->getAttribute('inactivity'); 162 | $this->hold = $body->getAttribute('hold'); 163 | $this->to = $body->getAttribute('to'); 164 | $this->accept = $body->getAttribute('accept'); 165 | $this->maxpause = $body->getAttribute('maxpause'); 166 | 167 | $this->debug($this->sid, 'sid'); 168 | 169 | if(empty($body->firstChild) || empty($body->firstChild->firstChild)) { 170 | throw new XmppPrebindConnectionException("Child not found in response from server."); 171 | } 172 | $mechanisms = $body->getElementsByTagName('mechanism'); 173 | 174 | foreach ($mechanisms as $value) { 175 | $this->mechanisms[] = $value->nodeValue; 176 | } 177 | 178 | if (in_array(self::ENCRYPTION_DIGEST_MD5, $this->mechanisms)) { 179 | $this->encryption = self::ENCRYPTION_DIGEST_MD5; 180 | } elseif (in_array(self::ENCRYPTION_CRAM_MD5, $this->mechanisms)) { 181 | $this->encryption = self::ENCRYPTION_CRAM_MD5; 182 | } elseif (in_array(self::ENCRYPTION_PLAIN, $this->mechanisms)) { 183 | $this->encryption = self::ENCRYPTION_PLAIN; 184 | } else { 185 | throw new XmppPrebindConnectionException("No encryption supported by the server is supported by this library."); 186 | } 187 | 188 | $this->debug($this->encryption, 'encryption used'); 189 | 190 | // Assign session creation response 191 | $this->response = $body; 192 | } 193 | 194 | /** 195 | * Try to authenticate 196 | * 197 | * @throws XmppPrebindException if invalid login 198 | * @return bool 199 | */ 200 | public function auth() { 201 | $auth = Auth_SASL::factory($this->encryption); 202 | 203 | switch ($this->encryption) { 204 | case self::ENCRYPTION_PLAIN: 205 | $authXml = $this->buildPlainAuth($auth); 206 | break; 207 | case self::ENCRYPTION_DIGEST_MD5: 208 | $authXml = $this->sendChallengeAndBuildDigestMd5Auth($auth); 209 | break; 210 | case self::ENCRYPTION_CRAM_MD5: 211 | $authXml = $this->sendChallengeAndBuildCramMd5Auth($auth); 212 | break; 213 | } 214 | $response = $this->send($authXml); 215 | 216 | $body = self::getBodyFromXml($response); 217 | 218 | if (!$body->hasChildNodes() || $body->firstChild->nodeName !== 'success') { 219 | throw new XmppPrebindException("Invalid login"); 220 | } 221 | 222 | $this->sendRestart(); 223 | $this->sendBindIfRequired(); 224 | $this->sendSessionIfRequired(); 225 | 226 | return true; 227 | } 228 | 229 | /** 230 | * Get BOSH parameters to properly setup the BOSH client 231 | * 232 | * @return array 233 | */ 234 | public function getBoshInfo() 235 | { 236 | return array( 237 | 'wait' => $this->wait, 238 | 'requests' => $this->requests, 239 | 'ver' => $this->ver, 240 | 'polling' => $this->polling, 241 | 'inactivity' => $this->inactivity, 242 | 'hold' => $this->hold, 243 | 'to' => $this->to, 244 | 'ack' => $this->ack, 245 | 'accept' => $this->accept, 246 | 'maxpause' => $this->maxpause, 247 | ); 248 | } 249 | 250 | /** 251 | * Get jid, sid and rid for attaching 252 | * 253 | * @return array 254 | */ 255 | public function getSessionInfo() { 256 | return array('jid' => $this->jid, 'sid' => $this->sid, 'rid' => $this->rid); 257 | } 258 | 259 | /** 260 | * Debug if debug enabled 261 | * 262 | * @param string $msg 263 | * @param string $label 264 | */ 265 | protected function debug($msg, $label = null) { 266 | if ($this->firePhp) { 267 | $this->firePhp->log($msg, $label); 268 | } 269 | } 270 | 271 | /** 272 | * Send xmpp restart message after successful auth 273 | * 274 | * @return string Response 275 | */ 276 | protected function sendRestart() { 277 | $domDocument = $this->buildBody(); 278 | $body = self::getBodyFromDomDocument($domDocument); 279 | $body->appendChild(self::getNewTextAttribute($domDocument, 'to', $this->jabberHost)); 280 | $body->appendChild(self::getNewTextAttribute($domDocument, 'xmlns:xmpp', self::XMLNS_BOSH)); 281 | $body->appendChild(self::getNewTextAttribute($domDocument, 'xmpp:restart', 'true')); 282 | 283 | $restartResponse = $this->send($domDocument->saveXML()); 284 | 285 | $restartBody = self::getBodyFromXml($restartResponse); 286 | foreach ($restartBody->childNodes as $bodyChildNodes) { 287 | if ($bodyChildNodes->nodeName === 'stream:features') { 288 | foreach ($bodyChildNodes->childNodes as $streamFeatures) { 289 | if ($streamFeatures->nodeName === 'bind') { 290 | $this->doBind = true; 291 | } elseif ($streamFeatures->nodeName === 'session') { 292 | $this->doSession = true; 293 | } 294 | } 295 | } 296 | } 297 | 298 | return $restartResponse; 299 | } 300 | 301 | /** 302 | * Send xmpp bind message after restart 303 | * 304 | * @return string Response 305 | */ 306 | protected function sendBindIfRequired() { 307 | if ($this->doBind) { 308 | $domDocument = $this->buildBody(); 309 | $body = self::getBodyFromDomDocument($domDocument); 310 | 311 | $iq = $domDocument->createElement('iq'); 312 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_CLIENT)); 313 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'type', 'set')); 314 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'id', 'bind_' . rand())); 315 | 316 | $bind = $domDocument->createElement('bind'); 317 | $bind->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_BIND)); 318 | 319 | $resource = $domDocument->createElement('resource'); 320 | $resource->appendChild($domDocument->createTextNode($this->resource)); 321 | 322 | $bind->appendChild($resource); 323 | $iq->appendChild($bind); 324 | $body->appendChild($iq); 325 | 326 | return $this->send($domDocument->saveXML()); 327 | } 328 | return false; 329 | } 330 | 331 | /** 332 | * Send session if there's a session node in the restart response (within stream:features) 333 | */ 334 | protected function sendSessionIfRequired() { 335 | if ($this->doSession) { 336 | $domDocument = $this->buildBody(); 337 | $body = self::getBodyFromDomDocument($domDocument); 338 | 339 | $iq = $domDocument->createElement('iq'); 340 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_CLIENT)); 341 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'type', 'set')); 342 | $iq->appendChild(self::getNewTextAttribute($domDocument, 'id', 'session_auth_' . rand())); 343 | 344 | $session = $domDocument->createElement('session'); 345 | $session->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SESSION)); 346 | 347 | $iq->appendChild($session); 348 | $body->appendChild($iq); 349 | 350 | return $this->send($domDocument->saveXML()); 351 | } 352 | return false; 353 | } 354 | 355 | /** 356 | * Send initial connection string 357 | * 358 | * @param string $route 359 | * @return string Response 360 | */ 361 | protected function sendInitialConnection($route = false) { 362 | $domDocument = $this->buildBody(); 363 | $body = self::getBodyFromDomDocument($domDocument); 364 | 365 | $waitTime = 60; 366 | 367 | $body->appendChild(self::getNewTextAttribute($domDocument, 'hold', '1')); 368 | $body->appendChild(self::getNewTextAttribute($domDocument, 'to', $this->jabberHost)); 369 | $body->appendChild(self::getNewTextAttribute($domDocument, 'xmlns:xmpp', self::XMLNS_BOSH)); 370 | $body->appendChild(self::getNewTextAttribute($domDocument, 'xmpp:version', '1.0')); 371 | $body->appendChild(self::getNewTextAttribute($domDocument, 'wait', $waitTime)); 372 | 373 | if ($route) 374 | { 375 | $body->appendChild(self::getNewTextAttribute($domDocument, 'route', $route)); 376 | } 377 | 378 | return $this->send($domDocument->saveXML()); 379 | } 380 | 381 | /** 382 | * Send challenge request 383 | * 384 | * @return string Challenge 385 | */ 386 | protected function sendChallenge() { 387 | $domDocument = $this->buildBody(); 388 | $body = self::getBodyFromDomDocument($domDocument); 389 | 390 | $auth = $domDocument->createElement('auth'); 391 | $auth->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL)); 392 | $auth->appendChild(self::getNewTextAttribute($domDocument, 'mechanism', $this->encryption)); 393 | $body->appendChild($auth); 394 | 395 | $response = $this->send($domDocument->saveXML()); 396 | 397 | $body = $this->getBodyFromXml($response); 398 | $challenge = base64_decode($body->firstChild->nodeValue); 399 | 400 | return $challenge; 401 | } 402 | 403 | /** 404 | * Build PLAIN auth string 405 | * 406 | * @param Auth_SASL_Common $auth 407 | * @return string Auth XML to send 408 | */ 409 | protected function buildPlainAuth(Auth_SASL_Common $auth) { 410 | $authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, self::getBareJidFromJid($this->jid)); 411 | $authString = base64_encode($authString); 412 | $this->debug($authString, 'PLAIN Auth String'); 413 | 414 | $domDocument = $this->buildBody(); 415 | $body = self::getBodyFromDomDocument($domDocument); 416 | 417 | $auth = $domDocument->createElement('auth'); 418 | $auth->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL)); 419 | $auth->appendChild(self::getNewTextAttribute($domDocument, 'mechanism', $this->encryption)); 420 | $auth->appendChild($domDocument->createTextNode($authString)); 421 | $body->appendChild($auth); 422 | 423 | return $domDocument->saveXML(); 424 | } 425 | 426 | /** 427 | * Send challenge request and build DIGEST-MD5 auth string 428 | * 429 | * @param Auth_SASL_Common $auth 430 | * @return string Auth XML to send 431 | */ 432 | protected function sendChallengeAndBuildDigestMd5Auth(Auth_SASL_Common $auth) { 433 | $challenge = $this->sendChallenge(); 434 | 435 | $authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, $challenge, $this->jabberHost, self::SERVICE_NAME); 436 | $this->debug($authString, 'DIGEST-MD5 Auth String'); 437 | 438 | $authString = base64_encode($authString); 439 | 440 | $domDocument = $this->buildBody(); 441 | $body = self::getBodyFromDomDocument($domDocument); 442 | 443 | $response = $domDocument->createElement('response'); 444 | $response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL)); 445 | $response->appendChild($domDocument->createTextNode($authString)); 446 | 447 | $body->appendChild($response); 448 | 449 | 450 | $challengeResponse = $this->send($domDocument->saveXML()); 451 | 452 | return $this->replyToChallengeResponse($challengeResponse); 453 | } 454 | 455 | /** 456 | * Send challenge request and build CRAM-MD5 auth string 457 | * 458 | * @param Auth_SASL_Common $auth 459 | * @return string Auth XML to send 460 | */ 461 | protected function sendChallengeAndBuildCramMd5Auth(Auth_SASL_Common $auth) { 462 | $challenge = $this->sendChallenge(); 463 | 464 | $authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, $challenge); 465 | $this->debug($authString, 'CRAM-MD5 Auth String'); 466 | 467 | $authString = base64_encode($authString); 468 | 469 | $domDocument = $this->buildBody(); 470 | $body = self::getBodyFromDomDocument($domDocument); 471 | 472 | $response = $domDocument->createElement('response'); 473 | $response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL)); 474 | $response->appendChild($domDocument->createTextNode($authString)); 475 | 476 | $body->appendChild($response); 477 | 478 | $challengeResponse = $this->send($domDocument->saveXML()); 479 | 480 | return $this->replyToChallengeResponse($challengeResponse); 481 | } 482 | 483 | /** 484 | * CRAM-MD5 and DIGEST-MD5 reply with an additional challenge response which must be replied to. 485 | * After this additional reply, the server should reply with "success". 486 | */ 487 | protected function replyToChallengeResponse($challengeResponse) { 488 | $body = self::getBodyFromXml($challengeResponse); 489 | $challenge = base64_decode((string)$body->firstChild->nodeValue); 490 | if (strpos($challenge, 'rspauth') === false) { 491 | throw new XmppPrebindConnectionException('Invalid challenge response received'); 492 | } 493 | 494 | $domDocument = $this->buildBody(); 495 | $body = self::getBodyFromDomDocument($domDocument); 496 | $response = $domDocument->createElement('response'); 497 | $response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL)); 498 | 499 | $body->appendChild($response); 500 | 501 | return $domDocument->saveXML(); 502 | } 503 | 504 | /** 505 | * Send XML via CURL 506 | * 507 | * @param string $xml 508 | * @return string Response 509 | */ 510 | protected function send($xml) { 511 | $ch = curl_init($this->boshUri); 512 | curl_setopt($ch, CURLOPT_HEADER, 0); 513 | curl_setopt($ch, CURLOPT_POST, 1); 514 | curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); 515 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 516 | 517 | $header = array('Content-Type: ' . self::CONTENT_TYPE); 518 | if ($this->useGzip) { 519 | $header[] = 'Accept-Encoding: gzip, deflate'; 520 | } 521 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 522 | 523 | curl_setopt($ch, CURLOPT_VERBOSE, 0); 524 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 525 | 526 | $response = curl_exec($ch); 527 | 528 | // Check if curl failed to get response 529 | if ($response === false) { 530 | throw new XmppPrebindConnectionException("Cannot connect to service"); 531 | } 532 | 533 | curl_close($ch); 534 | 535 | if ($this->useGzip) { 536 | $response = self::compatibleGzInflate($response); 537 | } 538 | 539 | $this->debug($xml, 'SENT'); 540 | $this->debug($response, 'RECV:'); 541 | 542 | return $response; 543 | } 544 | 545 | /** 546 | * Fix gzdecompress/gzinflate data error warning. 547 | * 548 | * @link http://www.mydigitallife.info/2010/01/17/workaround-to-fix-php-warning-gzuncompress-or-gzinflate-data-error-in-wordpress-http-php/ 549 | * 550 | * @param string $gzData 551 | * @return string|bool 552 | */ 553 | public static function compatibleGzInflate($gzData) { 554 | if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) { 555 | $i = 10; 556 | $flg = ord( substr($gzData, 3, 1) ); 557 | if ( $flg > 0 ) { 558 | if ( $flg & 4 ) { 559 | list($xlen) = unpack('v', substr($gzData, $i, 2) ); 560 | $i = $i + 2 + $xlen; 561 | } 562 | if ( $flg & 8 ) 563 | $i = strpos($gzData, "\0", $i) + 1; 564 | if ( $flg & 16 ) 565 | $i = strpos($gzData, "\0", $i) + 1; 566 | if ( $flg & 2 ) 567 | $i = $i + 2; 568 | } 569 | return gzinflate( substr($gzData, $i, -8) ); 570 | } else { 571 | return false; 572 | } 573 | } 574 | 575 | /** 576 | * Build DOMDocument with standard xmpp body child node. 577 | * 578 | * @return DOMDocument 579 | */ 580 | protected function buildBody() { 581 | $xml = new DOMDocument('1.0', 'UTF-8'); 582 | 583 | $body = $xml->createElement('body'); 584 | $xml->appendChild($body); 585 | 586 | $body->appendChild(self::getNewTextAttribute($xml, 'xmlns', self::XMLNS_BODY)); 587 | $body->appendChild(self::getNewTextAttribute($xml, 'content', self::CONTENT_TYPE)); 588 | $body->appendChild(self::getNewTextAttribute($xml, 'rid', $this->getAndIncrementRid())); 589 | $body->appendChild(self::getNewTextAttribute($xml, 'xml:lang', self::XML_LANG)); 590 | 591 | if ($this->sid != '') { 592 | $body->appendChild(self::getNewTextAttribute($xml, 'sid', $this->sid)); 593 | } 594 | 595 | return $xml; 596 | } 597 | 598 | /** 599 | * Get jid in form of username@jabberHost 600 | * 601 | * @param string $jid Jid in form username@jabberHost/Resource 602 | * @return string JID 603 | */ 604 | public static function getBareJidFromJid($jid) { 605 | if ($jid == '') { 606 | return ''; 607 | } 608 | $splittedJid = explode('/', $jid, 1); 609 | return $splittedJid[0]; 610 | } 611 | 612 | /** 613 | * Get node (username) from jid 614 | * 615 | * @param string $jid 616 | * @return string Node 617 | */ 618 | public static function getNodeFromJid($jid) { 619 | $atPos = strpos($jid, '@'); 620 | if ($atPos === false) { 621 | return ''; 622 | } 623 | return substr($jid, 0, $atPos); 624 | } 625 | 626 | /** 627 | * Append new attribute to existing DOMDocument. 628 | * 629 | * @param DOMDocument $domDocument 630 | * @param string $attributeName 631 | * @param string $value 632 | * @return DOMNode 633 | */ 634 | protected static function getNewTextAttribute($domDocument, $attributeName, $value) { 635 | $attribute = $domDocument->createAttribute($attributeName); 636 | $attribute->appendChild($domDocument->createTextNode($value)); 637 | 638 | return $attribute; 639 | } 640 | 641 | /** 642 | * Get body node from DOMDocument 643 | * 644 | * @param DOMDocument $domDocument 645 | * @return DOMNode 646 | */ 647 | protected static function getBodyFromDomDocument($domDocument) { 648 | $body = $domDocument->getElementsByTagName('body'); 649 | return $body->item(0); 650 | } 651 | 652 | /** 653 | * Parse XML and return DOMNode of the body 654 | * 655 | * @uses XmppPrebind::getBodyFromDomDocument() 656 | * @param string $xml 657 | * @return DOMNode 658 | */ 659 | protected static function getBodyFromXml($xml) { 660 | $domDocument = new DOMDocument(); 661 | $domDocument->loadXml($xml); 662 | 663 | return self::getBodyFromDomDocument($domDocument); 664 | } 665 | 666 | /** 667 | * Get the rid and increment it by one. 668 | * Required by RFC 669 | * 670 | * @return int 671 | */ 672 | protected function getAndIncrementRid() { 673 | return $this->rid++; 674 | } 675 | } 676 | 677 | /** 678 | * Standard XmppPrebind Exception 679 | */ 680 | class XmppPrebindException extends Exception{} 681 | 682 | class XmppPrebindConnectionException extends XmppPrebindException{} 683 | --------------------------------------------------------------------------------