1: <?php
2: /**
3: * The Horde_Util:: class provides generally useful methods.
4: *
5: * Copyright 1999-2012 Horde LLC (http://www.horde.org/)
6: *
7: * See the enclosed file COPYING for license information (LGPL). If you
8: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
9: *
10: * @author Chuck Hagenbuch <chuck@horde.org>
11: * @author Jon Parise <jon@horde.org>
12: * @category Horde
13: * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
14: * @package Util
15: */
16: class Horde_Util
17: {
18: /**
19: * A list of random patterns to use for overwriting purposes.
20: * See http://www.cs.auckland.ac.nz/~pgut001/pubs/secure_del.html.
21: * We save the random overwrites for efficiency reasons.
22: *
23: * @var array
24: */
25: static public $patterns = array(
26: "\x55", "\xaa", "\x92\x49\x24", "\x49\x24\x92", "\x24\x92\x49",
27: "\x00", "\x11", "\x22", "\x33", "\x44", "\x55", "\x66", "\x77",
28: "\x88", "\x99", "\xaa", "\xbb", "\xcc", "\xdd", "\xee", "\xff",
29: "\x92\x49\x24", "\x49\x24\x92", "\x24\x92\x49", "\x6d\xb6\xdb",
30: "\xb6\xdb\x6d", "\xdb\x6d\xb6"
31: );
32:
33: /**
34: * Temp directory locations.
35: *
36: * @var array
37: */
38: static public $tmpLocations = array(
39: '/tmp/', '/var/tmp/', 'c:\WUTemp\\', 'c:\temp\\', 'c:\windows\temp\\',
40: 'c:\winnt\temp\\'
41: );
42:
43: /**
44: * Are magic quotes in use?
45: *
46: * @var boolean
47: */
48: static protected $_magicquotes = null;
49:
50: /**
51: * TODO
52: *
53: * @var array
54: */
55: static protected $_shutdowndata = array(
56: 'dirs' => array(),
57: 'files' => array(),
58: 'secure' => array()
59: );
60:
61: /**
62: * Has the shutdown method been registered?
63: *
64: * @var boolean
65: */
66: static protected $_shutdownreg = false;
67:
68: /**
69: * Cache for extensionExists().
70: *
71: * @var array
72: */
73: static protected $_cache = array();
74:
75: /**
76: * Returns an object's clone.
77: *
78: * @param object &$obj The object to clone.
79: *
80: * @return object The cloned object.
81: */
82: static public function &cloneObject(&$obj)
83: {
84: if (!is_object($obj)) {
85: $bt = debug_backtrace();
86: if (isset($bt[1])) {
87: $caller = $bt[1]['function'];
88: if (isset($bt[1]['class'])) {
89: $caller = $bt[1]['class'].$bt[1]['type'].$caller;
90: }
91: } else {
92: $caller = 'main';
93: }
94:
95: $caller .= ' on line ' . $bt[0]['line'] . ' of ' . $bt[0]['file'];
96:
97: $ret = $obj;
98: return $ret;
99: }
100:
101: $ret = clone($obj);
102: return $ret;
103: }
104:
105: /**
106: * Checks to see if a value has been set by the script and not by GET,
107: * POST, or cookie input. The value being checked MUST be in the global
108: * scope.
109: *
110: * @param string $varname The variable name to check.
111: * @param mixed $default Default value if the variable isn't present
112: * or was specified by the user. Defaults to null.
113: *
114: * @return mixed $default if the var is in user input or not present,
115: * the variable value otherwise.
116: */
117: static public function nonInputVar($varname, $default = null)
118: {
119: return (isset($_GET[$varname]) || isset($_POST[$varname]) || isset($_COOKIE[$varname]))
120: ? $default
121: : (isset($GLOBALS[$varname]) ? $GLOBALS[$varname] : $default);
122: }
123:
124: /**
125: * Adds a name=value pair to the end of an URL, taking care of whether
126: * there are existing parameters and whether to use ?, & or & as the
127: * glue. All data will be urlencoded.
128: *
129: * @deprecated
130: *
131: * @param Horde_Url|string $url The URL to modify.
132: * @param mixed $parameter Either the name value -or- an array of
133: * name/value pairs.
134: * @param string $value If specified, the value part ($parameter
135: * is then assumed to just be the parameter
136: * name).
137: * @param boolean $encode Encode the argument separator?
138: *
139: * @return Horde_Url The modified URL.
140: */
141: static public function addParameter($url, $parameter, $value = null,
142: $encode = true)
143: {
144: if (empty($parameter)) {
145: return $url;
146: }
147:
148: if ($url instanceof Horde_Url) {
149: $url = $url->copy()->add($parameter, $value);
150: if (is_null($url->raw)) {
151: $url->setRaw(!$encode);
152: }
153: return $url;
154: }
155:
156: $url = new Horde_Url($url);
157: if (is_null($url->raw) && count($url->parameters)) {
158: $url->setRaw(!$encode);
159: }
160: return $url->add($parameter, $value);
161: }
162:
163: /**
164: * Removes name=value pairs from a URL.
165: *
166: * @deprecated
167: *
168: * @param Horde_Url|string $url The URL to modify.
169: * @param mixed $remove Either a single parameter to remove or an
170: * array of parameters to remove.
171: *
172: * @return Horde_Url The modified URL.
173: */
174: static public function removeParameter($url, $remove)
175: {
176: if ($url instanceof Horde_Url) {
177: return $url->copy()->remove($remove);
178: }
179:
180: $horde_url = new Horde_Url($url);
181: return $horde_url->remove($remove);
182: }
183:
184: /**
185: * Returns a hidden form input containing the session name and id.
186: *
187: * @param boolean $append_session 0 = only if needed, 1 = always.
188: *
189: * @return string The hidden form input, if needed/requested.
190: */
191: static public function formInput($append_session = 0)
192: {
193: return (($append_session == 1) || !isset($_COOKIE[session_name()]))
194: ? '<input type="hidden" name="' . htmlspecialchars(session_name()) . '" value="' . htmlspecialchars(session_id()) . "\" />\n"
195: : '';
196: }
197:
198: /**
199: * Prints a hidden form input containing the session name and id.
200: *
201: * @param boolean $append_session 0 = only if needed, 1 = always.
202: */
203: static public function pformInput($append_session = 0)
204: {
205: echo self::formInput($append_session);
206: }
207:
208: /**
209: * If magic_quotes_gpc is in use, run stripslashes() on $var.
210: *
211: * @param mixed $var The string, or an array of strings, to un-quote.
212: *
213: * @return mixed $var, minus any magic quotes.
214: */
215: static public function dispelMagicQuotes($var)
216: {
217: if (is_null(self::$_magicquotes)) {
218: self::$_magicquotes = get_magic_quotes_gpc();
219: }
220:
221: if (self::$_magicquotes) {
222: $var = is_array($var)
223: ? array_map(array(__CLASS__, 'dispelMagicQuotes'), $var)
224: : stripslashes($var);
225: }
226:
227: return $var;
228: }
229:
230: /**
231: * Gets a form variable from GET or POST data, stripped of magic quotes if
232: * necessary. If the variable is somehow set in both the GET data and the
233: * POST data, the value from the POST data will be returned and the GET
234: * value will be ignored.
235: *
236: * @param string $var The name of the form variable to look for.
237: * @param string $default The value to return if the variable is not
238: * there.
239: *
240: * @return string The cleaned form variable, or $default.
241: */
242: static public function getFormData($var, $default = null)
243: {
244: return (($val = self::getPost($var)) !== null)
245: ? $val
246: : self::getGet($var, $default);
247: }
248:
249: /**
250: * Gets a form variable from GET data, stripped of magic quotes if
251: * necessary. This function will NOT return a POST variable.
252: *
253: * @param string $var The name of the form variable to look for.
254: * @param string $default The value to return if the variable is not
255: * there.
256: *
257: * @return string The cleaned form variable, or $default.
258: */
259: static public function getGet($var, $default = null)
260: {
261: return (isset($_GET[$var]))
262: ? self::dispelMagicQuotes($_GET[$var])
263: : $default;
264: }
265:
266: /**
267: * Gets a form variable from POST data, stripped of magic quotes if
268: * necessary. This function will NOT return a GET variable.
269: *
270: * @param string $var The name of the form variable to look for.
271: * @param string $default The value to return if the variable is not
272: * there.
273: *
274: * @return string The cleaned form variable, or $default.
275: */
276: static public function getPost($var, $default = null)
277: {
278: return (isset($_POST[$var]))
279: ? self::dispelMagicQuotes($_POST[$var])
280: : $default;
281: }
282:
283: /**
284: * Determines the location of the system temporary directory.
285: *
286: * @return string A directory name which can be used for temp files.
287: * Returns false if one could not be found.
288: */
289: static public function getTempDir()
290: {
291: $tmp = false;
292:
293: // Try sys_get_temp_dir() - only available in PHP 5.2.1+.
294: if (function_exists('sys_get_temp_dir')) {
295: $tmp = sys_get_temp_dir();
296: }
297:
298: // First, try PHP's upload_tmp_dir directive.
299: if (!$tmp) {
300: $tmp = ini_get('upload_tmp_dir');
301:
302: // Otherwise, try to determine the TMPDIR environment
303: // variable.
304: if (!$tmp) {
305: $tmp = getenv('TMPDIR');
306:
307: // If we still cannot determine a value, then cycle through a
308: // list of preset possibilities.
309: if (!$tmp) {
310: foreach (self::$tmpLocations as $tmp_check) {
311: if (@is_dir($tmp_check)) {
312: return $tmp_check;
313: }
314: }
315: }
316: }
317: }
318:
319: return $tmp ? $tmp : false;
320: }
321:
322: /**
323: * Creates a temporary filename for the lifetime of the script, and
324: * (optionally) registers it to be deleted at request shutdown.
325: *
326: * @param string $prefix Prefix to make the temporary name more
327: * recognizable.
328: * @param boolean $delete Delete the file at the end of the request?
329: * @param string $dir Directory to create the temporary file in.
330: * @param boolean $secure If deleting the file, should we securely delete
331: * the file by overwriting it with random data?
332: *
333: * @return string Returns the full path-name to the temporary file.
334: * Returns false if a temp file could not be created.
335: */
336: static public function getTempFile($prefix = '', $delete = true, $dir = '',
337: $secure = false)
338: {
339: $tempDir = (empty($dir) || !is_dir($dir))
340: ? self::getTempDir()
341: : $dir;
342:
343: $tempFile = tempnam($tempDir, $prefix);
344:
345: // If the file was created, then register it for deletion and return.
346: if (empty($tempFile)) {
347: return false;
348: }
349:
350: if ($delete) {
351: self::deleteAtShutdown($tempFile, true, $secure);
352: }
353:
354: return $tempFile;
355: }
356:
357: /**
358: * Creates a temporary filename with a specific extension for the lifetime
359: * of the script, and (optionally) registers it to be deleted at request
360: * shutdown.
361: *
362: * @param string $extension The file extension to use.
363: * @param string $prefix Prefix to make the temporary name more
364: * recognizable.
365: * @param boolean $delete Delete the file at the end of the request?
366: * @param string $dir Directory to create the temporary file in.
367: * @param boolean $secure If deleting file, should we securely delete
368: * the file by overwriting it with random data?
369: *
370: * @return string Returns the full path-name to the temporary file.
371: * Returns false if a temporary file could not be created.
372: */
373: static public function getTempFileWithExtension($extension = '.tmp',
374: $prefix = '',
375: $delete = true, $dir = '',
376: $secure = false)
377: {
378: $tempDir = (empty($dir) || !is_dir($dir))
379: ? self::getTempDir()
380: : $dir;
381:
382: if (empty($tempDir)) {
383: return false;
384: }
385:
386: $windows = substr(PHP_OS, 0, 3) == 'WIN';
387: $tries = 1;
388: do {
389: // Get a known, unique temporary file name.
390: $sysFileName = tempnam($tempDir, $prefix);
391: if ($sysFileName === false) {
392: return false;
393: }
394:
395: // tack on the extension
396: $tmpFileName = $sysFileName . $extension;
397: if ($sysFileName == $tmpFileName) {
398: return $sysFileName;
399: }
400:
401: // Move or point the created temporary file to the full filename
402: // with extension. These calls fail if the new name already
403: // exists.
404: $fileCreated = ($windows ? @rename($sysFileName, $tmpFileName) : @link($sysFileName, $tmpFileName));
405: if ($fileCreated) {
406: if (!$windows) {
407: unlink($sysFileName);
408: }
409:
410: if ($delete) {
411: self::deleteAtShutdown($tmpFileName, true, $secure);
412: }
413:
414: return $tmpFileName;
415: }
416:
417: unlink($sysFileName);
418: } while (++$tries <= 5);
419:
420: return false;
421: }
422:
423: /**
424: * Creates a temporary directory in the system's temporary directory.
425: *
426: * @param boolean $delete Delete the temporary directory at the end of
427: * the request?
428: * @param string $temp_dir Use this temporary directory as the directory
429: * where the temporary directory will be created.
430: *
431: * @return string The pathname to the new temporary directory.
432: * Returns false if directory not created.
433: */
434: static public function createTempDir($delete = true, $temp_dir = null)
435: {
436: if (is_null($temp_dir)) {
437: $temp_dir = self::getTempDir();
438: }
439:
440: if (empty($temp_dir)) {
441: return false;
442: }
443:
444: /* Get the first 8 characters of a random string to use as a temporary
445: directory name. */
446: do {
447: $new_dir = $temp_dir . '/' . substr(base_convert(uniqid(mt_rand()), 10, 36), 0, 8);
448: } while (file_exists($new_dir));
449:
450: $old_umask = umask(0000);
451: if (!mkdir($new_dir, 0700)) {
452: $new_dir = false;
453: } elseif ($delete) {
454: self::deleteAtShutdown($new_dir);
455: }
456: umask($old_umask);
457:
458: return $new_dir;
459: }
460:
461: /**
462: * Wrapper around fgetcsv().
463: *
464: * Empty lines will be skipped. If the 'length' parameter is provided, all
465: * rows are filled up with empty strings up to this length, or stripped
466: * down to this length.
467: *
468: * @param resource $file A file pointer.
469: * @param array $params Optional parameters. Possible values:
470: * - 'separator': The field delimiter.
471: * - 'quote': The quote character.
472: * - 'escape': The escape character.
473: * - 'length': The expected number of fields.
474: *
475: * @return array|boolean A row from the CSV file or false on error or end
476: * of file.
477: */
478: static public function getCsv($file, $params = array())
479: {
480: $params += array('separator' => ',', 'quote' => '"', 'escape' => '\\');
481:
482: // Detect Mac line endings.
483: $old = ini_get('auto_detect_line_endings');
484: ini_set('auto_detect_line_endings', 1);
485:
486: do {
487: // fgetcsv() throws a warning if the quote character is empty.
488: if (!strlen($params['quote']) && $params['escape'] != '\\') {
489: $params['quote'] = '"';
490: }
491: if (!strlen($params['quote'])) {
492: $row = fgetcsv($file, 0, $params['separator']);
493: } elseif (version_compare(PHP_VERSION, '5.3.0', '<')) {
494: $row = fgetcsv($file, 0, $params['separator'], $params['quote']);
495: } else {
496: $row = fgetcsv($file, 0, $params['separator'], $params['quote'], $params['escape']);
497: }
498: } while ($row && $row[0] === null);
499:
500: ini_set('auto_detect_line_endings', $old);
501:
502: if ($row) {
503: if (strlen($params['quote']) && strlen($params['escape'])) {
504: $row = array_map(create_function('$a', 'return str_replace(\'' . str_replace('\'', '\\\'', $params['escape'] . $params['quote']) . '\', \'' . str_replace('\'', '\\\'', $params['quote']) . '\', $a);'), $row);
505: } else {
506: $row = array_map('trim', $row);
507: }
508: if (!empty($params['length'])) {
509: $length = count($row);
510: if ($length < $params['length']) {
511: $row += array_fill($length, $params['length'] - $length, '');
512: } elseif ($length > $params['length']) {
513: array_splice($row, $params['length']);
514: }
515: }
516: }
517:
518: return $row;
519: }
520:
521: /**
522: * Returns the canonical path of the string. Like PHP's built-in
523: * realpath() except the directory need not exist on the local server.
524: *
525: * Algorithim loosely based on code from the Perl File::Spec::Unix module
526: * (version 1.5).
527: *
528: * @param string $path A file path.
529: *
530: * @return string The canonicalized file path.
531: */
532: static public function realPath($path)
533: {
534: /* Standardize on UNIX directory separators. */
535: if (!strncasecmp(PHP_OS, 'WIN', 3)) {
536: $path = str_replace('\\', '/', $path);
537: }
538:
539: /* xx////xx -> xx/xx
540: * xx/././xx -> xx/xx */
541: $path = preg_replace(array("|/+|", "@(/\.)+(/|\Z(?!\n))@"), array('/', '/'), $path);
542:
543: /* ./xx -> xx */
544: if ($path != './') {
545: $path = preg_replace("|^(\./)+|", '', $path);
546: }
547:
548: /* /../../xx -> xx */
549: $path = preg_replace("|^/(\.\./?)+|", '/', $path);
550:
551: /* xx/ -> xx */
552: if ($path != '/') {
553: $path = preg_replace("|/\Z(?!\n)|", '', $path);
554: }
555:
556: /* /xx/.. -> / */
557: while (strpos($path, '/..') !== false) {
558: $path = preg_replace("|/[^/]+/\.\.|", '', $path);
559: }
560:
561: return empty($path) ? '/' : $path;
562: }
563:
564: /**
565: * Removes given elements at request shutdown.
566: *
567: * If called with a filename will delete that file at request shutdown; if
568: * called with a directory will remove that directory and all files in that
569: * directory at request shutdown.
570: *
571: * If called with no arguments, return all elements to be deleted (this
572: * should only be done by Horde_Util::_deleteAtShutdown()).
573: *
574: * The first time it is called, it initializes the array and registers
575: * Horde_Util::_deleteAtShutdown() as a shutdown function - no need to do
576: * so manually.
577: *
578: * The second parameter allows the unregistering of previously registered
579: * elements.
580: *
581: * @param string $filename The filename to be deleted at the end of the
582: * request.
583: * @param boolean $register If true, then register the element for
584: * deletion, otherwise, unregister it.
585: * @param boolean $secure If deleting file, should we securely delete
586: * the file?
587: */
588: static public function deleteAtShutdown($filename, $register = true,
589: $secure = false)
590: {
591: /* Initialization of variables and shutdown functions. */
592: if (!self::$_shutdownreg) {
593: register_shutdown_function(array(__CLASS__, 'shutdown'));
594: self::$_shutdownreg = true;
595: }
596:
597: $ptr = &self::$_shutdowndata;
598: if ($register) {
599: if (@is_dir($filename)) {
600: $ptr['dirs'][$filename] = true;
601: } else {
602: $ptr['files'][$filename] = true;
603: }
604:
605: if ($secure) {
606: $ptr['secure'][$filename] = true;
607: }
608: } else {
609: unset($ptr['dirs'][$filename], $ptr['files'][$filename], $ptr['secure'][$filename]);
610: }
611: }
612:
613: /**
614: * Deletes registered files at request shutdown.
615: *
616: * This function should never be called manually; it is registered as a
617: * shutdown function by Horde_Util::deleteAtShutdown() and called
618: * automatically at the end of the request.
619: *
620: * Contains code from gpg_functions.php.
621: * Copyright 2002-2003 Braverock Ventures
622: */
623: static public function shutdown()
624: {
625: $ptr = &self::$_shutdowndata;
626:
627: foreach ($ptr['files'] as $file => $val) {
628: /* Delete files */
629: if ($val && file_exists($file)) {
630: /* Should we securely delete the file by overwriting the data
631: with a random string? */
632: if (isset($ptr['secure'][$file])) {
633: $filesize = filesize($file);
634: $fp = fopen($file, 'r+');
635: foreach (self::$patterns as $pattern) {
636: $pattern = substr(str_repeat($pattern, floor($filesize / strlen($pattern)) + 1), 0, $filesize);
637: fwrite($fp, $pattern);
638: fseek($fp, 0);
639: }
640: fclose($fp);
641: }
642: @unlink($file);
643: }
644: }
645:
646: foreach ($ptr['dirs'] as $dir => $val) {
647: /* Delete directories */
648: if ($val && file_exists($dir)) {
649: /* Make sure directory is empty. */
650: $dir_class = dir($dir);
651: while (false !== ($entry = $dir_class->read())) {
652: if ($entry != '.' && $entry != '..') {
653: @unlink($dir . '/' . $entry);
654: }
655: }
656: $dir_class->close();
657: @rmdir($dir);
658: }
659: }
660: }
661:
662: /**
663: * Caches the result of extension_loaded() calls.
664: *
665: * @param string $ext The extension name.
666: *
667: * @return boolean Is the extension loaded?
668: */
669: static public function extensionExists($ext)
670: {
671: if (!isset(self::$_cache[$ext])) {
672: self::$_cache[$ext] = extension_loaded($ext);
673: }
674:
675: return self::$_cache[$ext];
676: }
677:
678: /**
679: * Tries to load a PHP extension, behaving correctly for all operating
680: * systems.
681: *
682: * @param string $ext The extension to load.
683: *
684: * @return boolean True if the extension is now loaded, false if not.
685: * True can mean that the extension was already loaded,
686: * OR was loaded dynamically.
687: */
688: static public function loadExtension($ext)
689: {
690: /* If $ext is already loaded, our work is done. */
691: if (self::extensionExists($ext)) {
692: return true;
693: }
694:
695: /* See if we can call dl() at all, by the current ini settings.
696: * dl() has been removed in some PHP 5.3 SAPIs. */
697: if ((ini_get('enable_dl') != 1) ||
698: (ini_get('safe_mode') == 1) ||
699: !function_exists('dl')) {
700: return false;
701: }
702:
703: if (!strncasecmp(PHP_OS, 'WIN', 3)) {
704: $suffix = 'dll';
705: } else {
706: switch (PHP_OS) {
707: case 'HP-UX':
708: $suffix = 'sl';
709: break;
710:
711: case 'AIX':
712: $suffix = 'a';
713: break;
714:
715: case 'OSX':
716: $suffix = 'bundle';
717: break;
718:
719: default:
720: $suffix = 'so';
721: }
722: }
723:
724: return dl($ext . '.' . $suffix) || dl('php_' . $ext . '.' . $suffix);
725: }
726:
727: /**
728: * Utility function to obtain PATH_INFO information.
729: *
730: * @return string The PATH_INFO string.
731: */
732: static public function getPathInfo()
733: {
734: if (isset($_SERVER['PATH_INFO']) &&
735: (strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') === false)) {
736: return $_SERVER['PATH_INFO'];
737: } elseif (isset($_SERVER['REQUEST_URI']) &&
738: isset($_SERVER['SCRIPT_NAME'])) {
739: $search = Horde_String::common($_SERVER['SCRIPT_NAME'], $_SERVER['REQUEST_URI']);
740: if (substr($search, -1) == '/') {
741: $search = substr($search, 0, -1);
742: }
743: $search = array($search);
744: if (!empty($_SERVER['QUERY_STRING'])) {
745: // We can't use QUERY_STRING directly because URL rewriting
746: // might add more parameters to the query string than those
747: // from the request URI.
748: $url = parse_url($_SERVER['REQUEST_URI']);
749: if (!empty($url['query'])) {
750: $search[] = '?' . $url['query'];
751: }
752: }
753: $path = str_replace($search, '', $_SERVER['REQUEST_URI']);
754: if ($path == '/') {
755: $path = '';
756: }
757: return $path;
758: }
759:
760: return '';
761: }
762:
763: }
764: