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.2.0
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: * @var array|string $rawData
55: * @return array|string
56: */
57: public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null)
58: {
59: $strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes;
60: if ($strip) {
61: return self::_stripSlashes($rawData);
62: } else {
63: return $rawData;
64: }
65: }
66:
67: /**
68: * Strip slashes from string or array
69: * @param array|string $rawData
70: * @return array|string
71: */
72: protected static function _stripSlashes($rawData)
73: {
74: return is_array($rawData) ? array_map(array('self', '_stripSlashes'), $rawData) : stripslashes($rawData);
75: }
76:
77: /**
78: * Encrypt data
79: *
80: * This method will encrypt data using a given key, vector, and cipher.
81: * By default, this will encrypt data using the RIJNDAEL/AES 256 bit cipher. You
82: * may override the default cipher and cipher mode by passing your own desired
83: * cipher and cipher mode as the final key-value array argument.
84: *
85: * @param string $data The unencrypted data
86: * @param string $key The encryption key
87: * @param string $iv The encryption initialization vector
88: * @param array $settings Optional key-value array with custom algorithm and mode
89: * @return string
90: */
91: public static function encrypt($data, $key, $iv, $settings = array())
92: {
93: if ($data === '' || !extension_loaded('mcrypt')) {
94: return $data;
95: }
96:
97: //Merge settings with defaults
98: $settings = array_merge(array(
99: 'algorithm' => MCRYPT_RIJNDAEL_256,
100: 'mode' => MCRYPT_MODE_CBC
101: ), $settings);
102:
103: //Get module
104: $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
105:
106: //Validate IV
107: $ivSize = mcrypt_enc_get_iv_size($module);
108: if (strlen($iv) > $ivSize) {
109: $iv = substr($iv, 0, $ivSize);
110: }
111:
112: //Validate key
113: $keySize = mcrypt_enc_get_key_size($module);
114: if (strlen($key) > $keySize) {
115: $key = substr($key, 0, $keySize);
116: }
117:
118: //Encrypt value
119: mcrypt_generic_init($module, $key, $iv);
120: $res = @mcrypt_generic($module, $data);
121: mcrypt_generic_deinit($module);
122:
123: return $res;
124: }
125:
126: /**
127: * Decrypt data
128: *
129: * This method will decrypt data using a given key, vector, and cipher.
130: * By default, this will decrypt data using the RIJNDAEL/AES 256 bit cipher. You
131: * may override the default cipher and cipher mode by passing your own desired
132: * cipher and cipher mode as the final key-value array argument.
133: *
134: * @param string $data The encrypted data
135: * @param string $key The encryption key
136: * @param string $iv The encryption initialization vector
137: * @param array $settings Optional key-value array with custom algorithm and mode
138: * @return string
139: */
140: public static function decrypt($data, $key, $iv, $settings = array())
141: {
142: if ($data === '' || !extension_loaded('mcrypt')) {
143: return $data;
144: }
145:
146: //Merge settings with defaults
147: $settings = array_merge(array(
148: 'algorithm' => MCRYPT_RIJNDAEL_256,
149: 'mode' => MCRYPT_MODE_CBC
150: ), $settings);
151:
152: //Get module
153: $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], '');
154:
155: //Validate IV
156: $ivSize = mcrypt_enc_get_iv_size($module);
157: if (strlen($iv) > $ivSize) {
158: $iv = substr($iv, 0, $ivSize);
159: }
160:
161: //Validate key
162: $keySize = mcrypt_enc_get_key_size($module);
163: if (strlen($key) > $keySize) {
164: $key = substr($key, 0, $keySize);
165: }
166:
167: //Decrypt value
168: mcrypt_generic_init($module, $key, $iv);
169: $decryptedData = @mdecrypt_generic($module, $data);
170: $res = str_replace("\x0", '', $decryptedData);
171: mcrypt_generic_deinit($module);
172:
173: return $res;
174: }
175:
176: /**
177: * Encode secure cookie value
178: *
179: * This method will create the secure value of an HTTP cookie. The
180: * cookie value is encrypted and hashed so that its value is
181: * secure and checked for integrity when read in subsequent requests.
182: *
183: * @param string $value The unsecure HTTP cookie value
184: * @param int $expires The UNIX timestamp at which this cookie will expire
185: * @param string $secret The secret key used to hash the cookie value
186: * @param int $algorithm The algorithm to use for encryption
187: * @param int $mode The algorithm mode to use for encryption
188: * @param string
189: */
190: public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode)
191: {
192: $key = hash_hmac('sha1', $expires, $secret);
193: $iv = self::get_iv($expires, $secret);
194: $secureString = base64_encode(self::encrypt($value, $key, $iv, array(
195: 'algorithm' => $algorithm,
196: 'mode' => $mode
197: )));
198: $verificationString = hash_hmac('sha1', $expires . $value, $key);
199:
200: return implode('|', array($expires, $secureString, $verificationString));
201: }
202:
203: /**
204: * Decode secure cookie value
205: *
206: * This method will decode the secure value of an HTTP cookie. The
207: * cookie value is encrypted and hashed so that its value is
208: * secure and checked for integrity when read in subsequent requests.
209: *
210: * @param string $value The secure HTTP cookie value
211: * @param int $expires The UNIX timestamp at which this cookie will expire
212: * @param string $secret The secret key used to hash the cookie value
213: * @param int $algorithm The algorithm to use for encryption
214: * @param int $mode The algorithm mode to use for encryption
215: * @param string
216: */
217: public static function decodeSecureCookie($value, $secret, $algorithm, $mode)
218: {
219: if ($value) {
220: $value = explode('|', $value);
221: if (count($value) === 3 && ((int) $value[0] === 0 || (int) $value[0] > time())) {
222: $key = hash_hmac('sha1', $value[0], $secret);
223: $iv = self::get_iv($value[0], $secret);
224: $data = self::decrypt(base64_decode($value[1]), $key, $iv, array(
225: 'algorithm' => $algorithm,
226: 'mode' => $mode
227: ));
228: $verificationString = hash_hmac('sha1', $value[0] . $data, $key);
229: if ($verificationString === $value[2]) {
230: return $data;
231: }
232: }
233: }
234:
235: return false;
236: }
237:
238: /**
239: * Set HTTP cookie header
240: *
241: * This method will construct and set the HTTP `Set-Cookie` header. Slim
242: * uses this method instead of PHP's native `setcookie` method. This allows
243: * more control of the HTTP header irrespective of the native implementation's
244: * dependency on PHP versions.
245: *
246: * This method accepts the Slim_Http_Headers object by reference as its
247: * first argument; this method directly modifies this object instead of
248: * returning a value.
249: *
250: * @param array $header
251: * @param string $name
252: * @param string $value
253: */
254: public static function setCookieHeader(&$header, $name, $value)
255: {
256: //Build cookie header
257: if (is_array($value)) {
258: $domain = '';
259: $path = '';
260: $expires = '';
261: $secure = '';
262: $httponly = '';
263: if (isset($value['domain']) && $value['domain']) {
264: $domain = '; domain=' . $value['domain'];
265: }
266: if (isset($value['path']) && $value['path']) {
267: $path = '; path=' . $value['path'];
268: }
269: if (isset($value['expires'])) {
270: if (is_string($value['expires'])) {
271: $timestamp = strtotime($value['expires']);
272: } else {
273: $timestamp = (int) $value['expires'];
274: }
275: if ($timestamp !== 0) {
276: $expires = '; expires=' . gmdate('D, d-M-Y H:i:s e', $timestamp);
277: }
278: }
279: if (isset($value['secure']) && $value['secure']) {
280: $secure = '; secure';
281: }
282: if (isset($value['httponly']) && $value['httponly']) {
283: $httponly = '; HttpOnly';
284: }
285: $cookie = sprintf('%s=%s%s', urlencode($name), urlencode((string) $value['value']), $domain . $path . $expires . $secure . $httponly);
286: } else {
287: $cookie = sprintf('%s=%s', urlencode($name), urlencode((string) $value));
288: }
289:
290: //Set cookie header
291: if (!isset($header['Set-Cookie']) || $header['Set-Cookie'] === '') {
292: $header['Set-Cookie'] = $cookie;
293: } else {
294: $header['Set-Cookie'] = implode("\n", array($header['Set-Cookie'], $cookie));
295: }
296: }
297:
298: /**
299: * Delete HTTP cookie header
300: *
301: * This method will construct and set the HTTP `Set-Cookie` header to invalidate
302: * a client-side HTTP cookie. If a cookie with the same name (and, optionally, domain)
303: * is already set in the HTTP response, it will also be removed. Slim uses this method
304: * instead of PHP's native `setcookie` method. This allows more control of the HTTP header
305: * irrespective of PHP's native implementation's dependency on PHP versions.
306: *
307: * This method accepts the Slim_Http_Headers object by reference as its
308: * first argument; this method directly modifies this object instead of
309: * returning a value.
310: *
311: * @param array $header
312: * @param string $name
313: * @param string $value
314: */
315: public static function deleteCookieHeader(&$header, $name, $value = array())
316: {
317: //Remove affected cookies from current response header
318: $cookiesOld = array();
319: $cookiesNew = array();
320: if (isset($header['Set-Cookie'])) {
321: $cookiesOld = explode("\n", $header['Set-Cookie']);
322: }
323: foreach ($cookiesOld as $c) {
324: if (isset($value['domain']) && $value['domain']) {
325: $regex = sprintf('@%s=.*domain=%s@', urlencode($name), preg_quote($value['domain']));
326: } else {
327: $regex = sprintf('@%s=@', urlencode($name));
328: }
329: if (preg_match($regex, $c) === 0) {
330: $cookiesNew[] = $c;
331: }
332: }
333: if ($cookiesNew) {
334: $header['Set-Cookie'] = implode("\n", $cookiesNew);
335: } else {
336: unset($header['Set-Cookie']);
337: }
338:
339: //Set invalidating cookie to clear client-side cookie
340: self::setCookieHeader($header, $name, array_merge(array('value' => '', 'path' => null, 'domain' => null, 'expires' => time() - 100), $value));
341: }
342:
343: /**
344: * Parse cookie header
345: *
346: * This method will parse the HTTP requst's `Cookie` header
347: * and extract cookies into an associative array.
348: *
349: * @param string
350: * @return array
351: */
352: public static function parseCookieHeader($header)
353: {
354: $cookies = array();
355: $header = rtrim($header, "\r\n");
356: $headerPieces = preg_split('@\s*[;,]\s*@', $header);
357: foreach ($headerPieces as $c) {
358: $cParts = explode('=', $c);
359: if (count($cParts) === 2) {
360: $key = urldecode($cParts[0]);
361: $value = urldecode($cParts[1]);
362: if (!isset($cookies[$key])) {
363: $cookies[$key] = $value;
364: }
365: }
366: }
367:
368: return $cookies;
369: }
370:
371: /**
372: * Generate a random IV
373: *
374: * This method will generate a non-predictable IV for use with
375: * the cookie encryption
376: *
377: * @param int $expires The UNIX timestamp at which this cookie will expire
378: * @param string $secret The secret key used to hash the cookie value
379: * @return binary string with length 40
380: */
381: private static function get_iv($expires, $secret)
382: {
383: $data1 = hash_hmac('sha1', 'a'.$expires.'b', $secret);
384: $data2 = hash_hmac('sha1', 'z'.$expires.'y', $secret);
385:
386: return pack("h*", $data1.$data2);
387: }
388:
389: }
390: