<?php
/*
 * Script: Tooling.php
 *   MooTools FileManager - Backend for the FileManager Script - Support Code
 *
 * Authors:
 *  - Christoph Pojer (http://cpojer.net) (author)
 *  - James Ehly (http://www.devtrench.com)
 *  - Fabian Vogelsteller (http://frozeman.de)
 *  - Ger Hobbelt (http://hebbut.net)
 *
 * License:
 *   MIT-style license.
 *
 * Copyright:
 *   Copyright (c) 2011 [Christoph Pojer](http://cpojer.net)
 */




if (!function_exists('safe_glob'))
{
	/**#@+
	 * Extra GLOB constant for safe_glob()
	 */
	if (!defined('GLOB_NODIR'))       define('GLOB_NODIR',256);
	if (!defined('GLOB_PATH'))        define('GLOB_PATH',512);
	if (!defined('GLOB_NODOTS'))      define('GLOB_NODOTS',1024);
	if (!defined('GLOB_RECURSE'))     define('GLOB_RECURSE',2048);
	if (!defined('GLOB_NOHIDDEN'))    define('GLOB_NOHIDDEN',4096);
	/**#@-*/


	/**
	 * A safe empowered glob().
	 *
	 * Function glob() is prohibited on some server (probably in safe mode)
	 * (Message "Warning: glob() has been disabled for security reasons in
	 * (script) on line (line)") for security reasons as stated on:
	 * http://seclists.org/fulldisclosure/2005/Sep/0001.html
	 *
	 * safe_glob() intends to replace glob() using readdir() & fnmatch() instead.
	 * Supported flags: GLOB_MARK, GLOB_NOSORT, GLOB_ONLYDIR
	 * Additional flags: GLOB_NODIR, GLOB_PATH, GLOB_NODOTS, GLOB_RECURSE, GLOB_NOHIDDEN
	 * (not original glob() flags)
	 *
	 * @author BigueNique AT yahoo DOT ca
	 * @updates
	 * - 080324 Added support for additional flags: GLOB_NODIR, GLOB_PATH,
	 *   GLOB_NODOTS, GLOB_RECURSE
	 * - [i_a] Added support for GLOB_NOHIDDEN, split output in directories and files subarrays
	 */
	function safe_glob($pattern, $flags = 0)
	{
		$split = explode('/', strtr($pattern, '\\', '/'));
		$mask = array_pop($split);
		$path = implode('/', $split);
		if (($dir = @opendir($path)) !== false)
		{
			$dirs = array();
			$files = array();
			while(($file = readdir($dir)) !== false)
			{
				// HACK/TWEAK: PHP5 and below are completely b0rked when it comes to international filenames   :-(
				//             --> do not show such files/directories in the list as they won't be accessible anyway!
				//
				// The regex charset is limited even within the ASCII range, due to    http://en.wikipedia.org/wiki/Filename#Comparison%5Fof%5Ffile%5Fname%5Flimitations
				// Although the filtered characters here are _possible_ on UNIX file systems, they're severely frowned upon.
				if (preg_match('/[^ -)+-.0-;=@-\[\]-{}~]/', $file))  // filesystem-illegal characters are not part of the set:   * > < ? / \ |
				{
					// simply do NOT list anything that we cannot cope with.
					// That includes clearly inaccessible files (and paths) with non-ASCII characters:
					// PHP5 and below are a real mess when it comes to handling Unicode filesystems
					// (see the php.net site too: readdir / glob / etc. user comments and the official
					// notice that PHP will support filesystem UTF-8/Unicode only when PHP6 is released.
					//
					// Big, fat bummer!
					continue;
				}
				//$temp = unpack("H*",$file);
				//echo 'hexdump of filename = ' . $temp[1] . ' for filename = ' . $file . "<br>\n";

				$filepath = $path . '/' . $file;
				$isdir = is_dir($filepath);

				// Recurse subdirectories (GLOB_RECURSE); speedup: no need to sort the intermediate results
				if (($flags & GLOB_RECURSE) && $isdir && !($file == '.' || $file == '..'))
				{
					$subsect = safe_glob($filepath . '/' . $mask, $flags | GLOB_NOSORT);
					if (is_array($subsect))
					{
						if (!($flags & GLOB_PATH))
						{
							$dirs = array_merge($dirs, array_prepend($subject['dirs'], $file . '/'));
							$files = array_merge($files, array_prepend($subject['files'], $file . '/'));
						}
					}
				}
				// Match file mask
				if (fnmatch($mask, $file))
				{
					if ( ( (!($flags & GLOB_ONLYDIR)) || $isdir )
					  && ( (!($flags & GLOB_NODIR)) || !$isdir )
					  && ( (!($flags & GLOB_NODOTS)) || !($file == '.' || $file == '..') )
					  && ( (!($flags & GLOB_NOHIDDEN)) || ($file[0] != '.' || $file == '..')) )
					{
						if ($isdir)
						{
							$dirs[] = ($flags & GLOB_PATH ? $path . '/' : '') . $file . (($flags & GLOB_MARK) ? '/' : '');
						}
						else
						{
							$files[] = ($flags & GLOB_PATH ? $path . '/' : '') . $file;
						}
					}
				}
			}
			closedir($dir);
			if (!($flags & GLOB_NOSORT))
			{
				sort($dirs);
				sort($files);
			}
			return array('dirs' => $dirs, 'files' => $files);
		}
		else
		{
			return false;
		}
	}
}




// derived from http://nl.php.net/manual/en/function.http-build-query.php#90438
if (!function_exists('http_build_query_ex'))
{
	if (!defined('PHP_QUERY_RFC1738')) define('PHP_QUERY_RFC1738', 1); // encoding is performed per RFC 1738 and the application/x-www-form-urlencoded media type, which implies that spaces are encoded as plus (+) signs.
	if (!defined('PHP_QUERY_RFC3986')) define('PHP_QUERY_RFC3986', 2); // encoding is performed according to  RFC 3986, and spaces will be percent encoded (%20).

	function http_build_query_ex($data, $prefix = '', $sep = '', $key = '', $enc_type = PHP_QUERY_RFC1738)
	{
		$ret = array();
		if (!is_array($data) && !is_object($data))
		{
			if ($enc_type == PHP_QUERY_RFC1738)
			{
				$ret[] = urlencode($data);
			}
			else
			{
				$ret[] = rawurlencode($data);
			}
		}
		else
		{
			if (!empty($prefix))
			{
				if ($enc_type == PHP_QUERY_RFC1738)
				{
					$prefix = urlencode($prefix);
				}
				else
				{
					$prefix = rawurlencode($prefix);
				}
			}
			foreach ($data as $k => $v)
			{
				if (is_int($k))
				{
					$k = $prefix . $k;
				}
				else if ($enc_type == PHP_QUERY_RFC1738)
				{
					$k = urlencode($k);
				}
				else
				{
					$k = rawurlencode($k);
				}
				if (!empty($key) || $key === 0)
				{
					$k = $key . '[' . $k . ']';
				}
				if (is_array($v) || is_object($v))
				{
					$ret[] = http_build_query_ex($v, '', $sep, $k, $enc_type);
				}
				else
				{
					if ($enc_type == PHP_QUERY_RFC1738)
					{
						$v = urlencode($v);
					}
					else
					{
						$v = rawurlencode($v);
					}
					$ret[] = $k . '=' . $v;
				}
			}
		}
		if (empty($sep)) $sep = ini_get('arg_separator.output');
		return implode($sep, $ret);
	}
}



/**
 * Determine how the PHP interpreter was invoked: cli/cgi/fastcgi/server,
 * where 'server' implies PHP is part of a webserver in the form of a 'module' (e.g. mod_php5) or similar.
 *
 * This information is used, for example, to decide the correct way to send the 'respose header code':
 * see send_response_status_header().
 */
if (!function_exists('get_interpreter_invocation_mode'))
{
	function get_interpreter_invocation_mode()
	{
		global $_ENV;
		global $_SERVER;

		/*
		 * see
		 *
		 * http://nl2.php.net/manual/en/function.php-sapi-name.php
		 * http://stackoverflow.com/questions/190759/can-php-detect-if-its-run-from-a-cron-job-or-from-the-command-line
		 */
		$mode = "server";
		$name = php_sapi_name();
		if (preg_match("/fcgi/", $name) == 1)
		{
			$mode = "fastcgi";
		}
		else if (preg_match("/cli/", $name) == 1)
		{
			$mode = "cli";
		}
		else if (preg_match("/cgi/", $name) == 1)
		{
			$mode = "cgi";
		}

		/*
		 * check whether POSIX functions have been compiled/enabled; xampp on Win32/64 doesn't have the buggers! :-(
		 */
		if (function_exists('posix_isatty'))
		{
			if (posix_isatty(STDOUT))
			{
				/* even when seemingly run as cgi/fastcgi, a valid stdout TTY implies an interactive commandline run */
				$mode = 'cli';
			}
		}

		if (!empty($_ENV['TERM']) && empty($_SERVER['REMOTE_ADDR']))
		{
			/* even when seemingly run as cgi/fastcgi, a valid stdout TTY implies an interactive commandline run */
			$mode = 'cli';
		}

		return $mode;
	}
}






/**
 * Return the HTTP response code string for the given response code
 */
if (!function_exists('get_response_code_string'))
{
	function get_response_code_string($response_code)
	{
		$response_code = intval($response_code);
		switch ($response_code)
		{
		case 100:   return "RFC2616 Section 10.1.1: Continue";
		case 101:   return "RFC2616 Section 10.1.2: Switching Protocols";
		case 200:   return "RFC2616 Section 10.2.1: OK";
		case 201:   return "RFC2616 Section 10.2.2: Created";
		case 202:   return "RFC2616 Section 10.2.3: Accepted";
		case 203:   return "RFC2616 Section 10.2.4: Non-Authoritative Information";
		case 204:   return "RFC2616 Section 10.2.5: No Content";
		case 205:   return "RFC2616 Section 10.2.6: Reset Content";
		case 206:   return "RFC2616 Section 10.2.7: Partial Content";
		case 300:   return "RFC2616 Section 10.3.1: Multiple Choices";
		case 301:   return "RFC2616 Section 10.3.2: Moved Permanently";
		case 302:   return "RFC2616 Section 10.3.3: Found";
		case 303:   return "RFC2616 Section 10.3.4: See Other";
		case 304:   return "RFC2616 Section 10.3.5: Not Modified";
		case 305:   return "RFC2616 Section 10.3.6: Use Proxy";
		case 307:   return "RFC2616 Section 10.3.8: Temporary Redirect";
		case 400:   return "RFC2616 Section 10.4.1: Bad Request";
		case 401:   return "RFC2616 Section 10.4.2: Unauthorized";
		case 402:   return "RFC2616 Section 10.4.3: Payment Required";
		case 403:   return "RFC2616 Section 10.4.4: Forbidden";
		case 404:   return "RFC2616 Section 10.4.5: Not Found";
		case 405:   return "RFC2616 Section 10.4.6: Method Not Allowed";
		case 406:   return "RFC2616 Section 10.4.7: Not Acceptable";
		case 407:   return "RFC2616 Section 10.4.8: Proxy Authentication Required";
		case 408:   return "RFC2616 Section 10.4.9: Request Time-out";
		case 409:   return "RFC2616 Section 10.4.10: Conflict";
		case 410:   return "RFC2616 Section 10.4.11: Gone";
		case 411:   return "RFC2616 Section 10.4.12: Length Required";
		case 412:   return "RFC2616 Section 10.4.13: Precondition Failed";
		case 413:   return "RFC2616 Section 10.4.14: Request Entity Too Large";
		case 414:   return "RFC2616 Section 10.4.15: Request-URI Too Large";
		case 415:   return "RFC2616 Section 10.4.16: Unsupported Media Type";
		case 416:   return "RFC2616 Section 10.4.17: Requested range not satisfiable";
		case 417:   return "RFC2616 Section 10.4.18: Expectation Failed";
		case 500:   return "RFC2616 Section 10.5.1: Internal Server Error";
		case 501:   return "RFC2616 Section 10.5.2: Not Implemented";
		case 502:   return "RFC2616 Section 10.5.3: Bad Gateway";
		case 503:   return "RFC2616 Section 10.5.4: Service Unavailable";
		case 504:   return "RFC2616 Section 10.5.5: Gateway Time-out";
		case 505:   return "RFC2616 Section 10.5.6: HTTP Version not supported";
	/*
		case 102:   return "Processing";  // http://www.askapache.com/htaccess/apache-status-code-headers-errordocument.html#m0-askapache3
		case 207:   return "Multi-Status";
		case 418:   return "I'm a teapot";
		case 419:   return "unused";
		case 420:   return "unused";
		case 421:   return "unused";
		case 422:   return "Unproccessable entity";
		case 423:   return "Locked";
		case 424:   return "Failed Dependency";
		case 425:   return "Node code";
		case 426:   return "Upgrade Required";
		case 506:   return "Variant Also Negotiates";
		case 507:   return "Insufficient Storage";
		case 508:   return "unused";
		case 509:   return "unused";
		case 510:   return "Not Extended";
	*/
		default:   return rtrim("Unknown Response Code " . $response_code);
		}
	}
}



/**
 * Performs the correct way of transmitting the response status code header: PHP header() must be invoked in different ways
 * dependent on the way the PHP interpreter has been invoked.
 *
 * See also:
 *
 *   http://nl2.php.net/manual/en/function.header.php
 */
if (!function_exists('send_response_status_header'))
{
	function send_response_status_header($response_code)
	{
		$mode = get_interpreter_invocation_mode();
		switch ($mode)
		{
		default:
		case 'fcgi':
			header('Status: ' . $response_code, true, $response_code);
			break;

		case 'server':
			header('HTTP/1.0 ' . $response_code . ' ' . get_response_code_string($response_code), true, $response_code);
			break;
		}
	}
}






/*
 * http://www.php.net/manual/en/function.image-type-to-extension.php#77354
 * -->
 * http://www.php.net/manual/en/function.image-type-to-extension.php#79688
 */
if (!function_exists('image_type_to_extension'))
{
	function image_type_to_extension($type, $dot = true)
	{
		$e = array(1 => 'gif', 'jpeg', 'png', 'swf', 'psd', 'bmp', 'tiff', 'tiff', 'jpc', 'jp2', 'jpf', 'jb2', 'swc', 'aiff', 'wbmp', 'xbm');

		// We are expecting an integer.
		$t = (int)$type;
		if (!$t)
		{
			trigger_error('invalid IMAGETYPE_XXX(' . $type . ') passed to image_type_to_extension()', E_USER_NOTICE);
			return null;
		}
		if (!isset($e[$t]))
		{
			trigger_error('unidentified IMAGETYPE_XXX(' . $type . ') passed to image_type_to_extension()', E_USER_NOTICE);
			return null;
		}

		return ($dot ? '.' : '') . $e[$t];
	}
}


if (!function_exists('image_type_to_mime_type'))
{
	function image_type_to_mime_type($type)
	{
		$m = array(1 => 'image/gif', 'image/jpeg', 'image/png',
			'application/x-shockwave-flash', 'image/psd', 'image/bmp',
			'image/tiff', 'image/tiff', 'application/octet-stream',
			'image/jp2', 'application/octet-stream', 'application/octet-stream',
			'application/x-shockwave-flash', 'image/iff', 'image/vnd.wap.wbmp', 'image/xbm');

		// We are expecting an integer.
		$t = (int)$type;
		if (!$t)
		{
			trigger_error('invalid IMAGETYPE_XXX(' . $type . ') passed to image_type_to_mime_type()', E_USER_NOTICE);
			return null;
		}
		if (!isset($m[$t]))
		{
			trigger_error('unidentified IMAGETYPE_XXX(' . $type . ') passed to image_type_to_mime_type()', E_USER_NOTICE);
			return null;
		}
		return $m[$t];
	}
}






