true, 'chmod' => true, 'duplicate' => true, 'extract' => true, 'mkdir' => true, 'mkfile' => true, 'paste' => true, 'put' => true, 'rename' => true, 'resize' => true, 'rm' => true, 'upload' => true ); /** * Constructor * * @param $elFinder * @param bool $debug * * @author Dmitry (dio) Levashov */ public function __construct($elFinder, $debug = false) { $this->elFinder = $elFinder; $this->reqMethod = strtoupper($_SERVER["REQUEST_METHOD"]); if ($debug) { self::$contentType = 'Content-Type: text/plain; charset=utf-8'; } } /** * Determine whether the command requires CSRF validation * * @param string $cmd * * @return bool */ protected function csrfProtectedCommand($cmd) { return isset(self::$csrfProtectedCmds[$cmd]); } /** * Determine whether current request should issue CSRF token * * @param string $cmd * @param array $src * * @return bool */ protected function shouldIssueCsrfToken($cmd, array $src) { return ($cmd === 'open' && !empty($src['init'])); } /** * Determine whether current request should refresh CSRF token TTL * * @param string $cmd * @param array $src * * @return bool */ protected function shouldRefreshCsrfToken($cmd, array $src) { return ( ($cmd === 'info' && !empty($src['reload'])) || $cmd === 'open' ); } /** * Generate or reuse current CSRF token * * @return string * @throws Exception */ protected function issueCsrfToken() { $session = $this->elFinder->getSession(); $now = time(); $tokenData = $session->get(self::$csrfSessionKey, array()); if (!is_array($tokenData)) { $tokenData = array(); } if (empty($tokenData['token']) || empty($tokenData['expires']) || (int)$tokenData['expires'] <= $now) { $tokenData = array( 'token' => $this->generateCsrfToken(), 'expires' => $now + self::$csrfTokenTtl ); $session->set(self::$csrfSessionKey, $tokenData); } return $tokenData['token']; } /** * Generate a random CSRF token string with old PHP compatibility * * @return string * @throws Exception */ protected function generateCsrfToken() { if (function_exists('random_bytes')) { return bin2hex(random_bytes(32)); } if (function_exists('openssl_random_pseudo_bytes')) { return bin2hex(openssl_random_pseudo_bytes(32)); } return sha1(uniqid(mt_rand(), true) . microtime(true)); } /** * Get HTTP request header value * * @param string $name * * @return string */ protected function getRequestHeader($name) { $serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $name)); if (isset($_SERVER[$serverKey])) { return trim($_SERVER[$serverKey]); } else { if (function_exists('getallheaders')) { $headers = getallheaders(); if (isset($headers[$name])) { return trim($headers[$name]); } } return ''; } } /** * Validate CSRF token header for protected commands * * @return bool */ protected function validateCsrfToken() { $session = $this->elFinder->getSession(); $tokenData = $session->get(self::$csrfSessionKey, array()); $headerToken = $this->getRequestHeader(self::$csrfHeaderName); $now = time(); if (!is_array($tokenData) || empty($tokenData['token']) || empty($tokenData['expires'])) { return false; } if ((int)$tokenData['expires'] <= $now) { $session->remove(self::$csrfSessionKey); return false; } if ($headerToken === '') { return false; } if (function_exists('hash_equals')) { return hash_equals($tokenData['token'], $headerToken); } return ($tokenData['token'] === $headerToken); } /** * Refresh current CSRF token TTL if header matches the active token * * @return bool */ protected function refreshCsrfTokenTtl() { $session = $this->elFinder->getSession(); $tokenData = $session->get(self::$csrfSessionKey, array()); $headerToken = $this->getRequestHeader(self::$csrfHeaderName); $now = time(); $isValid = false; if (!is_array($tokenData) || empty($tokenData['token']) || empty($tokenData['expires']) || $headerToken === '') { return false; } if ((int)$tokenData['expires'] <= $now) { $session->remove(self::$csrfSessionKey); return false; } if (function_exists('hash_equals')) { $isValid = hash_equals($tokenData['token'], $headerToken); } else { $isValid = ($tokenData['token'] === $headerToken); } if (!$isValid) { return false; } $tokenData['expires'] = $now + self::$csrfTokenTtl; $session->set(self::$csrfSessionKey, $tokenData); return true; } /** * Output CSRF validation error response * * @return void * @throws elFinderAbortException */ protected function outputCsrfError() { $this->output(array( 'error' => $this->elFinder->error(elFinder::ERROR_PERM_DENIED, 'Invalid request. Please reload.'), 'csrfReload' => true, 'header' => array( 'HTTP/1.1 403 Forbidden', self::$contentType ) )); } /** * Execute elFinder command and output result * * @return void * @throws Exception * @author Dmitry (dio) Levashov */ public function run() { $isPost = $this->reqMethod === 'POST'; $src = $isPost ? array_merge($_GET, $_POST) : $_GET; $maxInputVars = (!$src || isset($src['targets'])) ? ini_get('max_input_vars') : null; if ((!$src || $maxInputVars) && $rawPostData = file_get_contents('php://input')) { // for max_input_vars and supports IE XDomainRequest() $parts = explode('&', $rawPostData); if (!$src || $maxInputVars < count($parts)) { $src = array(); foreach ($parts as $part) { list($key, $value) = array_pad(explode('=', $part), 2, ''); $key = rawurldecode($key); if (preg_match('/^(.+?)\[([^\[\]]*)\]$/', $key, $m)) { $key = $m[1]; $idx = $m[2]; if (!isset($src[$key])) { $src[$key] = array(); } if ($idx) { $src[$key][$idx] = rawurldecode($value); } else { $src[$key][] = rawurldecode($value); } } else { $src[$key] = rawurldecode($value); } } $_POST = $this->input_filter($src); $_REQUEST = $this->input_filter(array_merge_recursive($src, $_REQUEST)); } } if (isset($src['targets']) && $this->elFinder->maxTargets && count($src['targets']) > $this->elFinder->maxTargets) { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_MAX_TARGTES))); } $cmd = isset($src['cmd']) ? $src['cmd'] : ''; $args = array(); if (!function_exists('json_encode')) { $error = $this->elFinder->error(elFinder::ERROR_CONF, elFinder::ERROR_CONF_NO_JSON); $this->output(array('error' => '{"error":["' . implode('","', $error) . '"]}', 'raw' => true)); } if (!$this->elFinder->loaded()) { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_CONF, elFinder::ERROR_CONF_NO_VOL), 'debug' => $this->elFinder->mountErrors)); } // telepat_mode: on if (!$cmd && $isPost) { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_UPLOAD, elFinder::ERROR_UPLOAD_TOTAL_SIZE), 'header' => 'Content-Type: text/html')); } // telepat_mode: off if (!$this->elFinder->commandExists($cmd)) { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_UNKNOWN_CMD))); } if ($this->csrfProtectedCommand($cmd)) { if (!$this->validateCsrfToken()) { $this->outputCsrfError(); } $this->refreshCsrfTokenTtl(); } // collect required arguments to exec command $hasFiles = false; foreach ($this->elFinder->commandArgsList($cmd) as $name => $req) { if ($name === 'FILES') { if (isset($_FILES)) { $hasFiles = true; } elseif ($req) { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_INV_PARAMS, $cmd))); } } else { $arg = isset($src[$name]) ? $src[$name] : ''; if (!is_array($arg) && $req !== '') { $arg = trim($arg); } if ($req && $arg === '') { $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_INV_PARAMS, $cmd))); } $args[$name] = $arg; } } $args['debug'] = isset($src['debug']) ? !!$src['debug'] : false; $args = $this->input_filter($args); if ($hasFiles) { $args['FILES'] = $_FILES; } try { $result = $this->elFinder->exec($cmd, $args); if (is_array($result) && !isset($result['error'])) { if ($this->shouldIssueCsrfToken($cmd, $src)) { $result[self::$csrfResponseKey] = $this->issueCsrfToken(); } else if ($this->shouldRefreshCsrfToken($cmd, $src)) { $this->refreshCsrfTokenTtl(); } } $this->output($result); } catch (elFinderAbortException $e) { // connection aborted // unlock session data for multiple access $this->elFinder->getSession()->close(); // HTTP response code header('HTTP/1.0 204 No Content'); // clear output buffer while (ob_get_level() && ob_end_clean()) { } exit(); } } /** * Sets the header. * * @param array|string $value HTTP header(s) */ public function setHeader($value) { $this->header = $value; } /** * Output json * * @param array data to output * * @return void * @throws elFinderAbortException * @author Dmitry (dio) Levashov */ protected function output(array $data) { // unlock session data for multiple access $this->elFinder->getSession()->close(); // client disconnect should abort ignore_user_abort(false); if ($this->header) { self::sendHeader($this->header); } if (isset($data['pointer'])) { // set time limit to 0 elFinder::extendTimeLimit(0); // send optional header if (!empty($data['header'])) { self::sendHeader($data['header']); } // clear output buffer while (ob_get_level() && ob_end_clean()) { } $toEnd = true; $fp = $data['pointer']; $sendData = !($this->reqMethod === 'HEAD' || !empty($data['info']['xsendfile'])); $psize = null; if (($this->reqMethod === 'GET' || !$sendData) && (elFinder::isSeekableStream($fp) || elFinder::isSeekableUrl($fp)) && (array_search('Accept-Ranges: none', headers_list()) === false)) { header('Accept-Ranges: bytes'); if (!empty($_SERVER['HTTP_RANGE'])) { $size = $data['info']['size']; $end = $size - 1; if (preg_match('/bytes=(\d*)-(\d*)(,?)/i', $_SERVER['HTTP_RANGE'], $matches)) { if (empty($matches[3])) { if (empty($matches[1]) && $matches[1] !== '0') { $start = $size - $matches[2]; } else { $start = intval($matches[1]); if (!empty($matches[2])) { $end = intval($matches[2]); if ($end >= $size) { $end = $size - 1; } $toEnd = ($end == ($size - 1)); } } $psize = $end - $start + 1; header('HTTP/1.1 206 Partial Content'); header('Content-Length: ' . $psize); header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size); // Apache mod_xsendfile dose not support range request if (isset($data['info']['xsendfile']) && strtolower($data['info']['xsendfile']) === 'x-sendfile') { if (function_exists('header_remove')) { header_remove($data['info']['xsendfile']); } else { header($data['info']['xsendfile'] . ':'); } unset($data['info']['xsendfile']); if ($this->reqMethod !== 'HEAD') { $sendData = true; } } $sendData && !elFinder::isSeekableUrl($fp) && fseek($fp, $start); } } } if ($sendData && is_null($psize)) { elFinder::rewind($fp); } } else { header('Accept-Ranges: none'); if (isset($data['info']) && !$data['info']['size']) { if (function_exists('header_remove')) { header_remove('Content-Length'); } else { header('Content-Length:'); } } } if ($sendData) { if ($toEnd || elFinder::isSeekableUrl($fp)) { // PHP < 5.6 has a bug of fpassthru // see https://bugs.php.net/bug.php?id=66736 if (version_compare(PHP_VERSION, '5.6', '<')) { file_put_contents('php://output', $fp); } else { fpassthru($fp); } } else { $out = fopen('php://output', 'wb'); stream_copy_to_stream($fp, $out, $psize); fclose($out); } } if (!empty($data['volume'])) { $data['volume']->close($fp, $data['info']['hash']); } else { fclose($fp); } exit(); } else { self::outputJson($data); exit(0); } } /** * Remove null & stripslashes applies on "magic_quotes_gpc" * * @param mixed $args * * @return mixed * @author Naoki Sawada */ protected function input_filter($args) { static $magic_quotes_gpc = NULL; if ($magic_quotes_gpc === NULL) $magic_quotes_gpc = (version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc()); if (is_array($args)) { return array_map(array(& $this, 'input_filter'), $args); } $res = str_replace("\0", '', $args); $magic_quotes_gpc && ($res = stripslashes($res)); return $res; } /** * Send HTTP header * * @param string|array $header optional header */ protected static function sendHeader($header = null) { if ($header) { if (is_array($header)) { foreach ($header as $h) { header($h); } } else { header($header); } } } /** * Output JSON * * @param array $data */ public static function outputJson($data) { // send header $header = isset($data['header']) ? $data['header'] : self::$contentType; self::sendHeader($header); unset($data['header']); if (!empty($data['raw']) && isset($data['error'])) { $out = $data['error']; } else { if (isset($data['debug']) && isset($data['debug']['backendErrors'])) { $data['debug']['backendErrors'] = array_merge($data['debug']['backendErrors'], elFinder::$phpErrors); } $out = json_encode($data); } // clear output buffer while (ob_get_level() && ob_end_clean()) { } header('Content-Length: ' . strlen($out)); echo $out; flush(); } }// END class