Util.php (15795B)
1 <?php 2 /** 3 * Slim - a micro PHP 5 framework 4 * 5 * @author Josh Lockhart <info@slimframework.com> 6 * @copyright 2011 Josh Lockhart 7 * @link http://www.slimframework.com 8 * @license http://www.slimframework.com/license 9 * @version 2.4.2 10 * @package Slim 11 * 12 * MIT LICENSE 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining 15 * a copy of this software and associated documentation files (the 16 * "Software"), to deal in the Software without restriction, including 17 * without limitation the rights to use, copy, modify, merge, publish, 18 * distribute, sublicense, and/or sell copies of the Software, and to 19 * permit persons to whom the Software is furnished to do so, subject to 20 * the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be 23 * included in all copies or substantial portions of the Software. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 29 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 30 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 31 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 */ 33 namespace Slim\Http; 34 35 /** 36 * Slim HTTP Utilities 37 * 38 * This class provides useful methods for handling HTTP requests. 39 * 40 * @package Slim 41 * @author Josh Lockhart 42 * @since 1.0.0 43 */ 44 class Util 45 { 46 /** 47 * Strip slashes from string or array 48 * 49 * This method strips slashes from its input. By default, this method will only 50 * strip slashes from its input if magic quotes are enabled. Otherwise, you may 51 * override the magic quotes setting with either TRUE or FALSE as the send argument 52 * to force this method to strip or not strip slashes from its input. 53 * 54 * @param array|string $rawData 55 * @param bool $overrideStripSlashes 56 * @return array|string 57 */ 58 public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null) 59 { 60 $strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes; 61 if ($strip) { 62 return self::stripSlashes($rawData); 63 } else { 64 return $rawData; 65 } 66 } 67 68 /** 69 * Strip slashes from string or array 70 * @param array|string $rawData 71 * @return array|string 72 */ 73 protected static function stripSlashes($rawData) 74 { 75 return is_array($rawData) ? array_map(array('self', 'stripSlashes'), $rawData) : stripslashes($rawData); 76 } 77 78 /** 79 * Encrypt data 80 * 81 * This method will encrypt data using a given key, vector, and cipher. 82 * By default, this will encrypt data using the RIJNDAEL/AES 256 bit cipher. You 83 * may override the default cipher and cipher mode by passing your own desired 84 * cipher and cipher mode as the final key-value array argument. 85 * 86 * @param string $data The unencrypted data 87 * @param string $key The encryption key 88 * @param string $iv The encryption initialization vector 89 * @param array $settings Optional key-value array with custom algorithm and mode 90 * @return string 91 */ 92 public static function encrypt($data, $key, $iv, $settings = array()) 93 { 94 if ($data === '' || !extension_loaded('mcrypt')) { 95 return $data; 96 } 97 98 //Merge settings with defaults 99 $defaults = array( 100 'algorithm' => MCRYPT_RIJNDAEL_256, 101 'mode' => MCRYPT_MODE_CBC 102 ); 103 $settings = array_merge($defaults, $settings); 104 105 //Get module 106 $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); 107 108 //Validate IV 109 $ivSize = mcrypt_enc_get_iv_size($module); 110 if (strlen($iv) > $ivSize) { 111 $iv = substr($iv, 0, $ivSize); 112 } 113 114 //Validate key 115 $keySize = mcrypt_enc_get_key_size($module); 116 if (strlen($key) > $keySize) { 117 $key = substr($key, 0, $keySize); 118 } 119 120 //Encrypt value 121 mcrypt_generic_init($module, $key, $iv); 122 $res = @mcrypt_generic($module, $data); 123 mcrypt_generic_deinit($module); 124 125 return $res; 126 } 127 128 /** 129 * Decrypt data 130 * 131 * This method will decrypt data using a given key, vector, and cipher. 132 * By default, this will decrypt data using the RIJNDAEL/AES 256 bit cipher. You 133 * may override the default cipher and cipher mode by passing your own desired 134 * cipher and cipher mode as the final key-value array argument. 135 * 136 * @param string $data The encrypted data 137 * @param string $key The encryption key 138 * @param string $iv The encryption initialization vector 139 * @param array $settings Optional key-value array with custom algorithm and mode 140 * @return string 141 */ 142 public static function decrypt($data, $key, $iv, $settings = array()) 143 { 144 if ($data === '' || !extension_loaded('mcrypt')) { 145 return $data; 146 } 147 148 //Merge settings with defaults 149 $defaults = array( 150 'algorithm' => MCRYPT_RIJNDAEL_256, 151 'mode' => MCRYPT_MODE_CBC 152 ); 153 $settings = array_merge($defaults, $settings); 154 155 //Get module 156 $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); 157 158 //Validate IV 159 $ivSize = mcrypt_enc_get_iv_size($module); 160 if (strlen($iv) > $ivSize) { 161 $iv = substr($iv, 0, $ivSize); 162 } 163 164 //Validate key 165 $keySize = mcrypt_enc_get_key_size($module); 166 if (strlen($key) > $keySize) { 167 $key = substr($key, 0, $keySize); 168 } 169 170 //Decrypt value 171 mcrypt_generic_init($module, $key, $iv); 172 $decryptedData = @mdecrypt_generic($module, $data); 173 $res = rtrim($decryptedData, "\0"); 174 mcrypt_generic_deinit($module); 175 176 return $res; 177 } 178 179 /** 180 * Serialize Response cookies into raw HTTP header 181 * @param \Slim\Http\Headers $headers The Response headers 182 * @param \Slim\Http\Cookies $cookies The Response cookies 183 * @param array $config The Slim app settings 184 */ 185 public static function serializeCookies(\Slim\Http\Headers &$headers, \Slim\Http\Cookies $cookies, array $config) 186 { 187 if ($config['cookies.encrypt']) { 188 foreach ($cookies as $name => $settings) { 189 if (is_string($settings['expires'])) { 190 $expires = strtotime($settings['expires']); 191 } else { 192 $expires = (int) $settings['expires']; 193 } 194 195 $settings['value'] = static::encodeSecureCookie( 196 $settings['value'], 197 $expires, 198 $config['cookies.secret_key'], 199 $config['cookies.cipher'], 200 $config['cookies.cipher_mode'] 201 ); 202 static::setCookieHeader($headers, $name, $settings); 203 } 204 } else { 205 foreach ($cookies as $name => $settings) { 206 static::setCookieHeader($headers, $name, $settings); 207 } 208 } 209 } 210 211 /** 212 * Encode secure cookie value 213 * 214 * This method will create the secure value of an HTTP cookie. The 215 * cookie value is encrypted and hashed so that its value is 216 * secure and checked for integrity when read in subsequent requests. 217 * 218 * @param string $value The insecure HTTP cookie value 219 * @param int $expires The UNIX timestamp at which this cookie will expire 220 * @param string $secret The secret key used to hash the cookie value 221 * @param int $algorithm The algorithm to use for encryption 222 * @param int $mode The algorithm mode to use for encryption 223 * @return string 224 */ 225 public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode) 226 { 227 $key = hash_hmac('sha1', (string) $expires, $secret); 228 $iv = self::getIv($expires, $secret); 229 $secureString = base64_encode( 230 self::encrypt( 231 $value, 232 $key, 233 $iv, 234 array( 235 'algorithm' => $algorithm, 236 'mode' => $mode 237 ) 238 ) 239 ); 240 $verificationString = hash_hmac('sha1', $expires . $value, $key); 241 242 return implode('|', array($expires, $secureString, $verificationString)); 243 } 244 245 /** 246 * Decode secure cookie value 247 * 248 * This method will decode the secure value of an HTTP cookie. The 249 * cookie value is encrypted and hashed so that its value is 250 * secure and checked for integrity when read in subsequent requests. 251 * 252 * @param string $value The secure HTTP cookie value 253 * @param string $secret The secret key used to hash the cookie value 254 * @param int $algorithm The algorithm to use for encryption 255 * @param int $mode The algorithm mode to use for encryption 256 * @return bool|string 257 */ 258 public static function decodeSecureCookie($value, $secret, $algorithm, $mode) 259 { 260 if ($value) { 261 $value = explode('|', $value); 262 if (count($value) === 3 && ((int) $value[0] === 0 || (int) $value[0] > time())) { 263 $key = hash_hmac('sha1', $value[0], $secret); 264 $iv = self::getIv($value[0], $secret); 265 $data = self::decrypt( 266 base64_decode($value[1]), 267 $key, 268 $iv, 269 array( 270 'algorithm' => $algorithm, 271 'mode' => $mode 272 ) 273 ); 274 $verificationString = hash_hmac('sha1', $value[0] . $data, $key); 275 if ($verificationString === $value[2]) { 276 return $data; 277 } 278 } 279 } 280 281 return false; 282 } 283 284 /** 285 * Set HTTP cookie header 286 * 287 * This method will construct and set the HTTP `Set-Cookie` header. Slim 288 * uses this method instead of PHP's native `setcookie` method. This allows 289 * more control of the HTTP header irrespective of the native implementation's 290 * dependency on PHP versions. 291 * 292 * This method accepts the Slim_Http_Headers object by reference as its 293 * first argument; this method directly modifies this object instead of 294 * returning a value. 295 * 296 * @param array $header 297 * @param string $name 298 * @param string $value 299 */ 300 public static function setCookieHeader(&$header, $name, $value) 301 { 302 //Build cookie header 303 if (is_array($value)) { 304 $domain = ''; 305 $path = ''; 306 $expires = ''; 307 $secure = ''; 308 $httponly = ''; 309 if (isset($value['domain']) && $value['domain']) { 310 $domain = '; domain=' . $value['domain']; 311 } 312 if (isset($value['path']) && $value['path']) { 313 $path = '; path=' . $value['path']; 314 } 315 if (isset($value['expires'])) { 316 if (is_string($value['expires'])) { 317 $timestamp = strtotime($value['expires']); 318 } else { 319 $timestamp = (int) $value['expires']; 320 } 321 if ($timestamp !== 0) { 322 $expires = '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp); 323 } 324 } 325 if (isset($value['secure']) && $value['secure']) { 326 $secure = '; secure'; 327 } 328 if (isset($value['httponly']) && $value['httponly']) { 329 $httponly = '; HttpOnly'; 330 } 331 $cookie = sprintf('%s=%s%s', urlencode($name), urlencode((string) $value['value']), $domain . $path . $expires . $secure . $httponly); 332 } else { 333 $cookie = sprintf('%s=%s', urlencode($name), urlencode((string) $value)); 334 } 335 336 //Set cookie header 337 if (!isset($header['Set-Cookie']) || $header['Set-Cookie'] === '') { 338 $header['Set-Cookie'] = $cookie; 339 } else { 340 $header['Set-Cookie'] = implode("\n", array($header['Set-Cookie'], $cookie)); 341 } 342 } 343 344 /** 345 * Delete HTTP cookie header 346 * 347 * This method will construct and set the HTTP `Set-Cookie` header to invalidate 348 * a client-side HTTP cookie. If a cookie with the same name (and, optionally, domain) 349 * is already set in the HTTP response, it will also be removed. Slim uses this method 350 * instead of PHP's native `setcookie` method. This allows more control of the HTTP header 351 * irrespective of PHP's native implementation's dependency on PHP versions. 352 * 353 * This method accepts the Slim_Http_Headers object by reference as its 354 * first argument; this method directly modifies this object instead of 355 * returning a value. 356 * 357 * @param array $header 358 * @param string $name 359 * @param array $value 360 */ 361 public static function deleteCookieHeader(&$header, $name, $value = array()) 362 { 363 //Remove affected cookies from current response header 364 $cookiesOld = array(); 365 $cookiesNew = array(); 366 if (isset($header['Set-Cookie'])) { 367 $cookiesOld = explode("\n", $header['Set-Cookie']); 368 } 369 foreach ($cookiesOld as $c) { 370 if (isset($value['domain']) && $value['domain']) { 371 $regex = sprintf('@%s=.*domain=%s@', urlencode($name), preg_quote($value['domain'])); 372 } else { 373 $regex = sprintf('@%s=@', urlencode($name)); 374 } 375 if (preg_match($regex, $c) === 0) { 376 $cookiesNew[] = $c; 377 } 378 } 379 if ($cookiesNew) { 380 $header['Set-Cookie'] = implode("\n", $cookiesNew); 381 } else { 382 unset($header['Set-Cookie']); 383 } 384 385 //Set invalidating cookie to clear client-side cookie 386 self::setCookieHeader($header, $name, array_merge(array('value' => '', 'path' => null, 'domain' => null, 'expires' => time() - 100), $value)); 387 } 388 389 /** 390 * Parse cookie header 391 * 392 * This method will parse the HTTP request's `Cookie` header 393 * and extract cookies into an associative array. 394 * 395 * @param string 396 * @return array 397 */ 398 public static function parseCookieHeader($header) 399 { 400 $cookies = array(); 401 $header = rtrim($header, "\r\n"); 402 $headerPieces = preg_split('@\s*[;,]\s*@', $header); 403 foreach ($headerPieces as $c) { 404 $cParts = explode('=', $c, 2); 405 if (count($cParts) === 2) { 406 $key = urldecode($cParts[0]); 407 $value = urldecode($cParts[1]); 408 if (!isset($cookies[$key])) { 409 $cookies[$key] = $value; 410 } 411 } 412 } 413 414 return $cookies; 415 } 416 417 /** 418 * Generate a random IV 419 * 420 * This method will generate a non-predictable IV for use with 421 * the cookie encryption 422 * 423 * @param int $expires The UNIX timestamp at which this cookie will expire 424 * @param string $secret The secret key used to hash the cookie value 425 * @return string Hash 426 */ 427 private static function getIv($expires, $secret) 428 { 429 $data1 = hash_hmac('sha1', 'a'.$expires.'b', $secret); 430 $data2 = hash_hmac('sha1', 'z'.$expires.'y', $secret); 431 432 return pack("h*", $data1.$data2); 433 } 434 }