PluginProbe
File Manager Pro – Filester / 2.1.3
File Manager Pro – Filester v2.1.3
2.1.3 2.1.2 2.1.1 trunk 1.6.1 1.7.6 1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9 2.0 2.0.1 2.0.2 2.1.0
filester / includes / File_manager / lib / php / elFinderConnector.class.php
elFinderConnector.class.php
643 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Default elFinder connector
5 *
6 * @author Dmitry (dio) Levashov
7 **/
8 class elFinderConnector
9 {
10 /**
11 * elFinder instance
12 *
13 * @var elFinder
14 **/
15 protected $elFinder;
16
17 /**
18 * Options
19 *
20 * @var array
21 **/
22 protected $options = array();
23
24 /**
25 * Must be use output($data) $data['header']
26 *
27 * @var string
28 * @deprecated
29 **/
30 protected $header = '';
31
32 /**
33 * HTTP request method
34 *
35 * @var string
36 */
37 protected $reqMethod = '';
38
39 /**
40 * Content type of output JSON
41 *
42 * @var string
43 */
44 protected static $contentType = 'Content-Type: application/json; charset=utf-8';
45
46 /**
47 * CSRF token header name
48 *
49 * @var string
50 */
51 protected static $csrfHeaderName = 'X-elFinder-CSRF';
52
53 /**
54 * JSON response key for CSRF token
55 *
56 * @var string
57 */
58 protected static $csrfResponseKey = 'csrf';
59
60 /**
61 * Session key for CSRF token data
62 *
63 * @var string
64 */
65 protected static $csrfSessionKey = 'elfinder.csrf';
66
67 /**
68 * Default CSRF token TTL seconds
69 *
70 * @var int
71 */
72 protected static $csrfTokenTtl = 900;
73
74 /**
75 * Commands that require CSRF header validation
76 *
77 * @var array
78 */
79 protected static $csrfProtectedCmds = array(
80 'archive' => true,
81 'chmod' => true,
82 'duplicate' => true,
83 'extract' => true,
84 'mkdir' => true,
85 'mkfile' => true,
86 'netmount' => true,
87 'paste' => true,
88 'put' => true,
89 'rename' => true,
90 'resize' => true,
91 'rm' => true,
92 'upload' => true
93 );
94
95 /**
96 * Constructor
97 *
98 * @param $elFinder
99 * @param bool $debug
100 *
101 * @author Dmitry (dio) Levashov
102 */
103 public function __construct($elFinder, $debug = false)
104 {
105
106 $this->elFinder = $elFinder;
107 $this->reqMethod = strtoupper($_SERVER["REQUEST_METHOD"]);
108 if ($debug) {
109 self::$contentType = 'Content-Type: text/plain; charset=utf-8';
110 }
111 }
112
113 /**
114 * Determine whether the command requires CSRF validation
115 *
116 * @param string $cmd
117 *
118 * @return bool
119 */
120 protected function csrfProtectedCommand($cmd)
121 {
122 return isset(self::$csrfProtectedCmds[$cmd]);
123 }
124
125 /**
126 * Determine whether current request should issue CSRF token
127 *
128 * @param string $cmd
129 * @param array $src
130 *
131 * @return bool
132 */
133 protected function shouldIssueCsrfToken($cmd, array $src)
134 {
135 return ($cmd === 'open' && !empty($src['init']));
136 }
137
138 /**
139 * Determine whether current request should refresh CSRF token TTL
140 *
141 * @param string $cmd
142 * @param array $src
143 *
144 * @return bool
145 */
146 protected function shouldRefreshCsrfToken($cmd, array $src)
147 {
148 return (
149 ($cmd === 'info' && !empty($src['reload']))
150 || $cmd === 'open'
151 );
152 }
153
154 /**
155 * Generate or reuse current CSRF token
156 *
157 * @return string
158 * @throws Exception
159 */
160 protected function issueCsrfToken()
161 {
162 $session = $this->elFinder->getSession();
163 $now = time();
164 $tokenData = $session->get(self::$csrfSessionKey, array());
165
166 if (!is_array($tokenData)) {
167 $tokenData = array();
168 }
169
170 if (empty($tokenData['token']) || empty($tokenData['expires']) || (int)$tokenData['expires'] <= $now) {
171 $tokenData = array(
172 'token' => $this->generateCsrfToken(),
173 'expires' => $now + self::$csrfTokenTtl
174 );
175 $session->set(self::$csrfSessionKey, $tokenData);
176 }
177
178 return $tokenData['token'];
179 }
180
181 /**
182 * Generate a random CSRF token string with old PHP compatibility
183 *
184 * @return string
185 * @throws Exception
186 */
187 protected function generateCsrfToken()
188 {
189 if (function_exists('random_bytes')) {
190 return bin2hex(random_bytes(32));
191 }
192
193 if (function_exists('openssl_random_pseudo_bytes')) {
194 return bin2hex(openssl_random_pseudo_bytes(32));
195 }
196
197 return sha1(uniqid(mt_rand(), true) . microtime(true));
198 }
199
200 /**
201 * Get HTTP request header value
202 *
203 * @param string $name
204 *
205 * @return string
206 */
207 protected function getRequestHeader($name)
208 {
209 $serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $name));
210 if (isset($_SERVER[$serverKey])) {
211 return trim($_SERVER[$serverKey]);
212 } else {
213 if (function_exists('getallheaders')) {
214 $headers = getallheaders();
215 if (isset($headers[$name])) {
216 return trim($headers[$name]);
217 }
218 }
219 return '';
220 }
221 }
222
223 /**
224 * Validate CSRF token header for protected commands
225 *
226 * @return bool
227 */
228 protected function validateCsrfToken()
229 {
230 $session = $this->elFinder->getSession();
231 $tokenData = $session->get(self::$csrfSessionKey, array());
232 $headerToken = $this->getRequestHeader(self::$csrfHeaderName);
233 $now = time();
234
235 if (!is_array($tokenData) || empty($tokenData['token']) || empty($tokenData['expires'])) {
236 return false;
237 }
238
239 if ((int)$tokenData['expires'] <= $now) {
240 $session->remove(self::$csrfSessionKey);
241 return false;
242 }
243
244 if ($headerToken === '') {
245 return false;
246 }
247
248 if (function_exists('hash_equals')) {
249 return hash_equals($tokenData['token'], $headerToken);
250 }
251
252 return ($tokenData['token'] === $headerToken);
253 }
254
255 /**
256 * Refresh current CSRF token TTL if header matches the active token
257 *
258 * @return bool
259 */
260 protected function refreshCsrfTokenTtl()
261 {
262 $session = $this->elFinder->getSession();
263 $tokenData = $session->get(self::$csrfSessionKey, array());
264 $headerToken = $this->getRequestHeader(self::$csrfHeaderName);
265 $now = time();
266 $isValid = false;
267
268 if (!is_array($tokenData) || empty($tokenData['token']) || empty($tokenData['expires']) || $headerToken === '') {
269 return false;
270 }
271
272 if ((int)$tokenData['expires'] <= $now) {
273 $session->remove(self::$csrfSessionKey);
274 return false;
275 }
276
277 if (function_exists('hash_equals')) {
278 $isValid = hash_equals($tokenData['token'], $headerToken);
279 } else {
280 $isValid = ($tokenData['token'] === $headerToken);
281 }
282
283 if (!$isValid) {
284 return false;
285 }
286
287 $tokenData['expires'] = $now + self::$csrfTokenTtl;
288 $session->set(self::$csrfSessionKey, $tokenData);
289
290 return true;
291 }
292
293 /**
294 * Output CSRF validation error response
295 *
296 * @return void
297 * @throws elFinderAbortException
298 */
299 protected function outputCsrfError()
300 {
301 $this->output(array(
302 'error' => $this->elFinder->error(elFinder::ERROR_PERM_DENIED, 'Invalid request. Please reload.'),
303 'csrfReload' => true,
304 'header' => array(
305 'HTTP/1.1 403 Forbidden',
306 self::$contentType
307 )
308 ));
309 }
310
311 /**
312 * Execute elFinder command and output result
313 *
314 * @return void
315 * @throws Exception
316 * @author Dmitry (dio) Levashov
317 */
318 public function run()
319 {
320 $isPost = $this->reqMethod === 'POST';
321 $src = $isPost ? array_merge($_GET, $_POST) : $_GET;
322 $maxInputVars = (!$src || isset($src['targets'])) ? ini_get('max_input_vars') : null;
323 if ((!$src || $maxInputVars) && $rawPostData = file_get_contents('php://input')) {
324 // for max_input_vars and supports IE XDomainRequest()
325 $parts = explode('&', $rawPostData);
326 if (!$src || $maxInputVars < count($parts)) {
327 $src = array();
328 foreach ($parts as $part) {
329 list($key, $value) = array_pad(explode('=', $part), 2, '');
330 $key = rawurldecode($key);
331 if (preg_match('/^(.+?)\[([^\[\]]*)\]$/', $key, $m)) {
332 $key = $m[1];
333 $idx = $m[2];
334 if (!isset($src[$key])) {
335 $src[$key] = array();
336 }
337 if ($idx) {
338 $src[$key][$idx] = rawurldecode($value);
339 } else {
340 $src[$key][] = rawurldecode($value);
341 }
342 } else {
343 $src[$key] = rawurldecode($value);
344 }
345 }
346 $_POST = $this->input_filter($src);
347 $_REQUEST = $this->input_filter(array_merge_recursive($src, $_REQUEST));
348 }
349 }
350
351 if (isset($src['targets']) && $this->elFinder->maxTargets && count($src['targets']) > $this->elFinder->maxTargets) {
352 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_MAX_TARGTES)));
353 }
354
355 $cmd = isset($src['cmd']) ? $src['cmd'] : '';
356 $args = array();
357
358 if (!function_exists('json_encode')) {
359 $error = $this->elFinder->error(elFinder::ERROR_CONF, elFinder::ERROR_CONF_NO_JSON);
360 $this->output(array('error' => '{"error":["' . implode('","', $error) . '"]}', 'raw' => true));
361 }
362
363 if (!$this->elFinder->loaded()) {
364 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_CONF, elFinder::ERROR_CONF_NO_VOL), 'debug' => $this->elFinder->mountErrors));
365 }
366
367 // telepat_mode: on
368 if (!$cmd && $isPost) {
369 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_UPLOAD, elFinder::ERROR_UPLOAD_TOTAL_SIZE), 'header' => 'Content-Type: text/html'));
370 }
371 // telepat_mode: off
372
373 if (!$this->elFinder->commandExists($cmd)) {
374 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_UNKNOWN_CMD)));
375 }
376
377 if ($this->csrfProtectedCommand($cmd)) {
378 if (!$this->validateCsrfToken()) {
379 $this->outputCsrfError();
380 }
381 $this->refreshCsrfTokenTtl();
382 }
383
384 // collect required arguments to exec command
385 $hasFiles = false;
386 foreach ($this->elFinder->commandArgsList($cmd) as $name => $req) {
387 if ($name === 'FILES') {
388 if (isset($_FILES)) {
389 $hasFiles = true;
390 } elseif ($req) {
391 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_INV_PARAMS, $cmd)));
392 }
393 } else {
394 $arg = isset($src[$name]) ? $src[$name] : '';
395
396 if (!is_array($arg) && $req !== '') {
397 $arg = trim($arg);
398 }
399 if ($req && $arg === '') {
400 $this->output(array('error' => $this->elFinder->error(elFinder::ERROR_INV_PARAMS, $cmd)));
401 }
402 $args[$name] = $arg;
403 }
404 }
405
406 $args['debug'] = isset($src['debug']) ? !!$src['debug'] : false;
407
408 $args = $this->input_filter($args);
409 if ($hasFiles) {
410 $args['FILES'] = $_FILES;
411 }
412
413 try {
414 $result = $this->elFinder->exec($cmd, $args);
415 if (is_array($result) && !isset($result['error'])) {
416 if ($this->shouldIssueCsrfToken($cmd, $src)) {
417 $result[self::$csrfResponseKey] = $this->issueCsrfToken();
418 } else if ($this->shouldRefreshCsrfToken($cmd, $src)) {
419 $this->refreshCsrfTokenTtl();
420 }
421 }
422 $this->output($result);
423 } catch (elFinderAbortException $e) {
424 // connection aborted
425 // unlock session data for multiple access
426 $this->elFinder->getSession()->close();
427 // HTTP response code
428 header('HTTP/1.0 204 No Content');
429 // clear output buffer
430 while (ob_get_level() && ob_end_clean()) {
431 }
432 exit();
433 }
434 }
435
436 /**
437 * Sets the header.
438 *
439 * @param array|string $value HTTP header(s)
440 */
441 public function setHeader($value)
442 {
443 $this->header = $value;
444 }
445
446 /**
447 * Output json
448 *
449 * @param array data to output
450 *
451 * @return void
452 * @throws elFinderAbortException
453 * @author Dmitry (dio) Levashov
454 */
455 protected function output(array $data)
456 {
457 // unlock session data for multiple access
458 $this->elFinder->getSession()->close();
459 // client disconnect should abort
460 ignore_user_abort(false);
461
462 if ($this->header) {
463 self::sendHeader($this->header);
464 }
465
466 if (isset($data['pointer'])) {
467 // set time limit to 0
468 elFinder::extendTimeLimit(0);
469
470 // send optional header
471 if (!empty($data['header'])) {
472 self::sendHeader($data['header']);
473 }
474
475 // clear output buffer
476 while (ob_get_level() && ob_end_clean()) {
477 }
478
479 $toEnd = true;
480 $fp = $data['pointer'];
481 $sendData = !($this->reqMethod === 'HEAD' || !empty($data['info']['xsendfile']));
482 $psize = null;
483 if (($this->reqMethod === 'GET' || !$sendData)
484 && (elFinder::isSeekableStream($fp) || elFinder::isSeekableUrl($fp))
485 && (array_search('Accept-Ranges: none', headers_list()) === false)) {
486 header('Accept-Ranges: bytes');
487 if (!empty($_SERVER['HTTP_RANGE'])) {
488 $size = $data['info']['size'];
489 $end = $size - 1;
490 if (preg_match('/bytes=(\d*)-(\d*)(,?)/i', $_SERVER['HTTP_RANGE'], $matches)) {
491 if (empty($matches[3])) {
492 if (empty($matches[1]) && $matches[1] !== '0') {
493 $start = $size - $matches[2];
494 } else {
495 $start = intval($matches[1]);
496 if (!empty($matches[2])) {
497 $end = intval($matches[2]);
498 if ($end >= $size) {
499 $end = $size - 1;
500 }
501 $toEnd = ($end == ($size - 1));
502 }
503 }
504 $psize = $end - $start + 1;
505
506 header('HTTP/1.1 206 Partial Content');
507 header('Content-Length: ' . $psize);
508 header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
509
510 // Apache mod_xsendfile dose not support range request
511 if (isset($data['info']['xsendfile']) && strtolower($data['info']['xsendfile']) === 'x-sendfile') {
512 if (function_exists('header_remove')) {
513 header_remove($data['info']['xsendfile']);
514 } else {
515 header($data['info']['xsendfile'] . ':');
516 }
517 unset($data['info']['xsendfile']);
518 if ($this->reqMethod !== 'HEAD') {
519 $sendData = true;
520 }
521 }
522
523 $sendData && !elFinder::isSeekableUrl($fp) && fseek($fp, $start);
524 }
525 }
526 }
527 if ($sendData && is_null($psize)) {
528 elFinder::rewind($fp);
529 }
530 } else {
531 header('Accept-Ranges: none');
532 if (isset($data['info']) && !$data['info']['size']) {
533 if (function_exists('header_remove')) {
534 header_remove('Content-Length');
535 } else {
536 header('Content-Length:');
537 }
538 }
539 }
540
541 if ($sendData) {
542 if ($toEnd || elFinder::isSeekableUrl($fp)) {
543 // PHP < 5.6 has a bug of fpassthru
544 // see https://bugs.php.net/bug.php?id=66736
545 if (version_compare(PHP_VERSION, '5.6', '<')) {
546 file_put_contents('php://output', $fp);
547 } else {
548 fpassthru($fp);
549 }
550 } else {
551 $out = fopen('php://output', 'wb');
552 stream_copy_to_stream($fp, $out, $psize);
553 fclose($out);
554 }
555 }
556
557 if (!empty($data['volume'])) {
558 $data['volume']->close($fp, $data['info']['hash']);
559 } else {
560 fclose($fp);
561 }
562 exit();
563 } else {
564 self::outputJson($data);
565 exit(0);
566 }
567 }
568
569 /**
570 * Remove null & stripslashes applies on "magic_quotes_gpc"
571 *
572 * @param mixed $args
573 *
574 * @return mixed
575 * @author Naoki Sawada
576 */
577 protected function input_filter($args)
578 {
579 static $magic_quotes_gpc = NULL;
580
581 if ($magic_quotes_gpc === NULL)
582 $magic_quotes_gpc = (version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc());
583
584 if (is_array($args)) {
585 return array_map(array(& $this, 'input_filter'), $args);
586 }
587 $res = str_replace("\0", '', $args);
588 $magic_quotes_gpc && ($res = stripslashes($res));
589 return $res;
590 }
591
592 /**
593 * Send HTTP header
594 *
595 * @param string|array $header optional header
596 */
597 protected static function sendHeader($header = null)
598 {
599 if ($header) {
600 if (is_array($header)) {
601 foreach ($header as $h) {
602 header($h);
603 }
604 } else {
605 header($header);
606 }
607 }
608 }
609
610 /**
611 * Output JSON
612 *
613 * @param array $data
614 */
615 public static function outputJson($data)
616 {
617 // send header
618 $header = isset($data['header']) ? $data['header'] : self::$contentType;
619 self::sendHeader($header);
620
621 unset($data['header']);
622
623 if (!empty($data['raw']) && isset($data['error'])) {
624 $out = $data['error'];
625 } else {
626 if (isset($data['debug']) && isset($data['debug']['backendErrors'])) {
627 $data['debug']['backendErrors'] = array_merge($data['debug']['backendErrors'], elFinder::$phpErrors);
628 }
629 $out = json_encode($data);
630 }
631
632 // clear output buffer
633 while (ob_get_level() && ob_end_clean()) {
634 }
635
636 header('Content-Length: ' . strlen($out));
637
638 echo $out;
639
640 flush();
641 }
642 }// END class
643