feat: h5ai 0.30.0 with PHP 8.4.26 security patch for PHP2Shell

This commit is contained in:
Patrick Asmus
2026-10-21 21:45:49 +02:00
parent 3568d43858
commit e6536444ad
133 changed files with 4363 additions and 29 deletions
+110
View File
@@ -0,0 +1,110 @@
<?php
class Api {
private $context;
private $request;
private $setup;
public function __construct($context) {
$this->context = $context;
$this->request = $context->get_request();
$this->setup = $context->get_setup();
}
public function apply() {
$action = $this->request->query('action');
$supported = ['download', 'get', 'login', 'logout'];
Util::json_fail(Util::ERR_UNSUPPORTED, 'unsupported action', !in_array($action, $supported));
$methodname = 'on_' . $action;
$this->$methodname();
}
private function on_download() {
Util::json_fail(Util::ERR_DISABLED, 'download disabled', !$this->context->query_option('download.enabled', false));
$as = $this->request->query('as');
$type = $this->request->query('type');
$base_href = $this->request->query('baseHref');
$hrefs = $this->request->query('hrefs', '');
$archive = new Archive($this->context);
set_time_limit(0);
session_write_close();
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $as . '"');
header('Connection: close');
$ok = $archive->output($type, $base_href, $hrefs);
Util::json_fail(Util::ERR_FAILED, 'packaging failed', !$ok);
exit;
}
private function on_get() {
$response = [];
foreach (['langs', 'options', 'types'] as $name) {
if ($this->request->query_boolean($name, false)) {
$methodname = 'get_' . $name;
$response[$name] = $this->context->$methodname();
}
}
if ($this->request->query_boolean('setup', false)) {
$response['setup'] = $this->setup->to_jsono($this->context->is_admin());
}
if ($this->request->query_boolean('theme', false)) {
$theme = new Theme($this->context);
$response['theme'] = $theme->get_icons();
}
if ($this->request->query('items', false)) {
$href = $this->request->query('items.href');
$what = $this->request->query_numeric('items.what');
$response['items'] = $this->context->get_items($href, $what);
}
if ($this->request->query('custom', false)) {
Util::json_fail(Util::ERR_DISABLED, 'custom disabled', !$this->context->query_option('custom.enabled', false));
$href = $this->request->query('custom');
$custom = new Custom($this->context);
$response['custom'] = $custom->get_customizations($href);
}
if ($this->request->query('l10n', false)) {
Util::json_fail(Util::ERR_DISABLED, 'l10n disabled', !$this->context->query_option('l10n.enabled', false));
$iso_codes = $this->request->query_array('l10n');
$iso_codes = array_filter($iso_codes);
$response['l10n'] = $this->context->get_l10n($iso_codes);
}
if ($this->request->query('search', false)) {
Util::json_fail(Util::ERR_DISABLED, 'search disabled', !$this->context->query_option('search.enabled', false));
$href = $this->request->query('search.href');
$pattern = $this->request->query('search.pattern');
$ignorecase = $this->request->query_boolean('search.ignorecase', false);
$search = new Search($this->context);
$response['search'] = $search->get_items($href, $pattern, $ignorecase);
}
if ($this->request->query('thumbs', false)) {
Util::json_fail(Util::ERR_DISABLED, 'thumbnails disabled', !$this->context->query_option('thumbnails.enabled', false));
Util::json_fail(Util::ERR_UNSUPPORTED, 'thumbnails not supported', !$this->setup->get('HAS_PHP_JPEG'));
$thumbs = $this->request->query_array('thumbs');
$response['thumbs'] = $this->context->get_thumbs($thumbs);
}
Util::json_exit($response);
}
private function on_login() {
$pass = $this->request->query('pass');
Util::json_exit(['asAdmin' => $this->context->login_admin($pass)]);
}
private function on_logout() {
Util::json_exit(['asAdmin' => $this->context->logout_admin()]);
}
}
+304
View File
@@ -0,0 +1,304 @@
<?php
class Context {
private static $DEFAULT_PASSHASH = 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e';
private static $AS_ADMIN_SESSION_KEY = 'AS_ADMIN';
private static $L10N_ISO_CODES = array(
'af', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'et', 'fi', 'fr', 'he',
'hi', 'hr', 'hu', 'id', 'it', 'ja','ko', 'lv', 'nb', 'nl', 'pl',
'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk',
'zh-cn', 'zh-tw'
);
private $session;
private $request;
private $setup;
private $options;
private $passhash;
public function __construct($session, $request, $setup) {
$this->session = $session;
$this->request = $request;
$this->setup = $setup;
$this->options = Json::load($this->setup->get('CONF_PATH') . '/options.json');
$this->passhash = $this->query_option('passhash', '');
$this->options['hasCustomPasshash'] = strcasecmp($this->passhash, Context::$DEFAULT_PASSHASH) !== 0;
unset($this->options['passhash']);
}
public function get_session() {
return $this->session;
}
public function get_request() {
return $this->request;
}
public function get_setup() {
return $this->setup;
}
public function get_options() {
return $this->options;
}
public function query_option($keypath = '', $default = null) {
return Util::array_query($this->options, $keypath, $default);
}
public function get_types() {
return Json::load($this->setup->get('CONF_PATH') . '/types.json');
}
public function login_admin($pass) {
$this->session->set(Context::$AS_ADMIN_SESSION_KEY, strcasecmp(hash('sha512', $pass), $this->passhash) === 0);
return $this->session->get(Context::$AS_ADMIN_SESSION_KEY);
}
public function logout_admin() {
$this->session->set(Context::$AS_ADMIN_SESSION_KEY, false);
return $this->session->get(Context::$AS_ADMIN_SESSION_KEY);
}
public function is_admin() {
return $this->session->get(Context::$AS_ADMIN_SESSION_KEY);
}
public function is_api_request() {
return strtolower($this->setup->get('REQUEST_METHOD')) === 'post';
}
public function is_info_request() {
return Util::starts_with($this->setup->get('REQUEST_HREF') . '/', $this->setup->get('PUBLIC_HREF'));
}
public function is_text_browser() {
return preg_match('/curl|links|lynx|w3m/i', $this->setup->get('HTTP_USER_AGENT')) === 1;
}
public function is_fallback_mode() {
return $this->query_option('view.fallbackMode', false) || $this->is_text_browser();
}
public function to_href($path, $trailing_slash = true) {
$rel_path = substr($path, strlen($this->setup->get('ROOT_PATH')));
$parts = explode('/', $rel_path);
$encoded_parts = [];
foreach ($parts as $part) {
if ($part != '') {
$encoded_parts[] = rawurlencode($part);
}
}
return Util::normalize_path($this->setup->get('ROOT_HREF') . implode('/', $encoded_parts), $trailing_slash);
}
public function to_path($href) {
$rel_href = substr($href, strlen($this->setup->get('ROOT_HREF')));
return Util::normalize_path($this->setup->get('ROOT_PATH') . '/' . rawurldecode($rel_href));
}
public function is_hidden($name) {
// always hide
if ($name === '.' || $name === '..') {
return true;
}
foreach ($this->query_option('view.hidden', []) as $re) {
$re = Util::wrap_pattern($re);
if (preg_match($re, $name)) {
return true;
}
}
return false;
}
public function read_dir($path) {
$names = [];
if (is_dir($path)) {
foreach (scandir($path) as $name) {
if (
$this->is_hidden($name)
|| $this->is_hidden($this->to_href($path) . $name)
|| (!is_readable($path . '/' . $name) && $this->query_option('view.hideIf403', false))
) {
continue;
}
$names[] = $name;
}
}
return $names;
}
public function is_managed_href($href) {
return $this->is_managed_path($this->to_path($href));
}
public function is_managed_path($path) {
if (!is_dir($path) || strpos($path, '../') !== false || strpos($path, '/..') !== false || $path === '..') {
return false;
}
if (strpos($path, $this->setup->get('PUBLIC_PATH')) === 0) {
return false;
}
if (strpos($path, $this->setup->get('PRIVATE_PATH')) === 0) {
return false;
}
foreach ($this->query_option('view.unmanaged', []) as $name) {
if (file_exists($path . '/' . $name)) {
return false;
}
}
while ($path !== $this->setup->get('ROOT_PATH')) {
if (@is_dir($path . '/_h5ai/private/conf')) {
return false;
}
$parent_path = Util::normalize_path(dirname($path));
if ($parent_path === $path) {
return false;
}
$path = $parent_path;
}
return true;
}
public function get_current_path() {
$current_href = Util::normalize_path($this->setup->get('REQUEST_HREF'), true);
$current_path = $this->to_path($current_href);
if (!is_dir($current_path)) {
$current_path = Util::normalize_path(dirname($current_path), false);
}
return $current_path;
}
public function get_items($href, $what) {
if (!$this->is_managed_href($href)) {
return [];
}
$cache = [];
$folder = Item::get($this, $this->to_path($href), $cache);
// add content of subfolders
if ($what >= 2 && $folder !== null) {
foreach ($folder->get_content($cache) as $item) {
$item->get_content($cache);
}
$folder = $folder->get_parent($cache);
}
// add content of this folder and all parent folders
while ($what >= 1 && $folder !== null) {
$folder->get_content($cache);
$folder = $folder->get_parent($cache);
}
uasort($cache, ['Item', 'cmp']);
$result = [];
foreach ($cache as $p => $item) {
$result[] = $item->to_json_object();
}
return $result;
}
public function get_langs() {
$langs = [];
$l10n_path = $this->setup->get('CONF_PATH') . '/l10n';
if (is_dir($l10n_path)) {
if ($dir = opendir($l10n_path)) {
while (($file = readdir($dir)) !== false) {
if (Util::ends_with($file, '.json')) {
$translations = Json::load($l10n_path . '/' . $file);
$langs[basename($file, '.json')] = $translations['lang'];
}
}
closedir($dir);
}
}
ksort($langs);
return $langs;
}
public function get_l10n($iso_codes) {
$results = [];
foreach ($iso_codes as $iso_code) {
if (!in_array($iso_code, Context::$L10N_ISO_CODES)) {
continue;
}
$file = $this->setup->get('CONF_PATH') . '/l10n/' . $iso_code . '.json';
$results[$iso_code] = Json::load($file);
$results[$iso_code]['isoCode'] = $iso_code;
}
return $results;
}
public function get_thumbs($requests) {
$hrefs = [];
foreach ($requests as $req) {
$thumb = new Thumb($this);
$hrefs[] = $thumb->thumb($req['type'], $req['href'], $req['width'], $req['height']);
}
return $hrefs;
}
private function prefix_x_head_href($href) {
if (preg_match('@^(https?://|/)@i', $href)) {
return $href;
}
return $this->setup->get('PUBLIC_HREF') . 'ext/' . $href;
}
private function get_fonts_html() {
$fonts = $this->query_option('view.fonts', []);
$fonts_mono = $this->query_option('view.fontsMono', []);
$html = '<style class="x-head">';
if (sizeof($fonts) > 0) {
$html .= '#root,input,select{font-family:"' . implode('","', $fonts) . '"!important}';
}
if (sizeof($fonts_mono) > 0) {
$html .= 'pre,code{font-family:"' . implode('","', $fonts_mono) . '"!important}';
}
$html .= '</style>';
return $html;
}
public function get_x_head_html() {
$scripts = $this->query_option('resources.scripts', []);
$styles = $this->query_option('resources.styles', []);
$html = '';
foreach ($styles as $href) {
$html .= '<link rel="stylesheet" href="' . $this->prefix_x_head_href($href) . '" class="x-head">';
}
foreach ($scripts as $href) {
$html .= '<script src="' . $this->prefix_x_head_href($href) . '" class="x-head"></script>';
}
$html .= $this->get_fonts_html();
return $html;
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
class Fallback {
private $context;
public function __construct($context) {
$this->context = $context;
}
public function get_html($path = null) {
if (!$path) {
$path = $this->context->get_current_path();
}
$fallback_images_href = $this->context->get_setup()->get('PUBLIC_HREF') . 'images/fallback/';
$cache = [];
$folder = Item::get($this->context, $path, $cache);
$items = $folder->get_content($cache);
uasort($items, ['Item', 'cmp']);
$html = '<table>';
$html .= '<tr>';
$html .= '<th class="fb-i"></th>';
$html .= '<th class="fb-n"><span>Name</span></th>';
$html .= '<th class="fb-d"><span>Last modified</span></th>';
$html .= '<th class="fb-s"><span>Size</span></th>';
$html .= '</tr>';
if ($folder->get_parent($cache)) {
$html .= '<tr>';
$html .= '<td class="fb-i"><img src="' . $fallback_images_href . 'folder-parent.png" alt="folder-parent"/></td>';
$html .= '<td class="fb-n"><a href="..">Parent Directory</a></td>';
$html .= '<td class="fb-d"></td>';
$html .= '<td class="fb-s"></td>';
$html .= '</tr>';
}
foreach ($items as $item) {
$type = $item->is_folder ? 'folder' : 'file';
$html .= '<tr>';
$html .= '<td class="fb-i"><img src="' . $fallback_images_href . $type . '.png" alt="' . $type . '"/></td>';
$html .= '<td class="fb-n"><a href="' . $item->href . '">' . basename($item->path) . '</a></td>';
$html .= '<td class="fb-d">' . date('Y-m-d H:i', $item->date) . '</td>';
$html .= '<td class="fb-s">' . ($item->size !== null ? intval($item->size / 1000) . ' KB' : '' ) . '</td>';
$html .= '</tr>';
}
$html .= '</table>';
return $html;
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
class Filesize {
private static $cache = [];
public static function getSize($path, $withFoldersize, $withDu) {
$fs = new Filesize();
return $fs->size($path, $withFoldersize, $withDu);
}
public static function getCachedSize($path, $withFoldersize, $withDu) {
if (array_key_exists($path, Filesize::$cache)) {
return Filesize::$cache[$path];
}
$size = Filesize::getSize($path, $withFoldersize, $withDu);
Filesize::$cache[$path] = $size;
return $size;
}
private function __construct() {}
private function read_dir($path) {
$paths = [];
if (is_dir($path)) {
foreach (scandir($path) as $name) {
if ($name !== '.' && $name !== '..') {
$paths[] = $path . '/' . $name;
}
}
}
return $paths;
}
private function php_filesize($path, $recursive = false) {
// if (PHP_INT_SIZE < 8) {
// }
$size = @filesize($path);
if (!is_dir($path) || !$recursive) {
return $size;
}
foreach ($this->read_dir($path) as $p) {
$size += $this->php_filesize($p, true);
}
return $size;
}
private function exec($cmdv) {
$cmd = implode(' ', array_map('escapeshellarg', $cmdv));
$lines = [];
$rc = null;
exec($cmd, $lines, $rc);
return $lines;
}
private function exec_du_all($paths) {
$cmdv = array_merge(['du', '-sbL'], $paths);
$lines = $this->exec($cmdv);
$sizes = [];
foreach ($lines as $line) {
$parts = preg_split('/[\s]+/', $line, 2);
$size = intval($parts[0], 10);
$path = $parts[1];
$sizes[$path] = $size;
}
return $sizes;
}
private function exec_du($path) {
$sizes = $this->exec_du_all([$path]);
return $sizes[$path];
}
private function size($path, $withFoldersize = false, $withDu = false) {
if (is_file($path)) {
return $this->php_filesize($path);
}
if (is_dir($path) && $withFoldersize) {
if ($withDu) {
return $this->exec_du($path);
}
return $this->php_filesize($path, true);
}
return null;
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
class Item {
public static function cmp($item1, $item2) {
if ($item1->is_folder && !$item2->is_folder) {
return -1;
}
if (!$item1->is_folder && $item2->is_folder) {
return 1;
}
return strcasecmp($item1->path, $item2->path);
}
public static function get($context, $path, &$cache) {
if (!Util::starts_with($path, $context->get_setup()->get('ROOT_PATH'))) {
return null;
}
if (is_array($cache) && array_key_exists($path, $cache)) {
return $cache[$path];
}
$item = new Item($context, $path);
if (is_array($cache)) {
$cache[$path] = $item;
}
return $item;
}
public $context;
public $path;
public $href;
public $date;
public $size;
public $is_folder;
public $is_content_fetched;
private function __construct($context, $path) {
$this->context = $context;
$this->path = Util::normalize_path($path, false);
$this->is_folder = is_dir($this->path);
$this->href = $context->to_href($this->path, $this->is_folder);
$this->date = @filemtime($this->path);
$this->size = Util::filesize($context, $this->path);
$this->is_content_fetched = false;
}
public function to_json_object() {
$obj = [
'href' => $this->href,
'time' => $this->date * 1000, // seconds (PHP) to milliseconds (JavaScript)
'size' => $this->size
];
if ($this->is_folder) {
$obj['managed'] = $this->context->is_managed_href($this->href);
$obj['fetched'] = $this->is_content_fetched;
}
return $obj;
}
public function get_parent(&$cache) {
$parent_path = Util::normalize_path(dirname($this->path), false);
if ($parent_path !== $this->path && Util::starts_with($parent_path, $this->context->get_setup()->get('ROOT_PATH'))) {
return Item::get($this->context, $parent_path, $cache);
}
return null;
}
public function get_content(&$cache) {
$items = [];
if (!$this->context->is_managed_href($this->href)) {
return $items;
}
$files = $this->context->read_dir($this->path);
foreach ($files as $file) {
$item = Item::get($this->context, $this->path . '/' . $file, $cache);
$items[$item->path] = $item;
}
$this->is_content_fetched = true;
return $items;
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
class Json {
const SINGLE = 1;
const MULTI = 2;
public static function load($path) {
if (!is_readable($path)) {
return [];
}
$json = file_get_contents($path);
return Json::decode($json);
}
public static function save($path, $obj) {
$json = json_encode($obj);
return file_put_contents($path, $json) !== false;
}
private static function decode($json) {
$json = Json::strip($json);
return json_decode($json, true);
}
private static function strip($commented_json) {
$insideString = false;
$insideComment = false;
$json = '';
for ($i = 0, $len = strlen($commented_json); $i < $len; $i += 1) {
$char = $commented_json[$i];
$charchar = $char . @$commented_json[$i + 1];
$prevChar = @$commented_json[$i - 1];
if (!$insideComment && $char === '"' && $prevChar !== "\\") {
$insideString = !$insideString;
}
if ($insideString) {
$json .= $char;
} elseif (!$insideComment && $charchar === '//') {
$insideComment = Json::SINGLE;
$i += 1;
} elseif (!$insideComment && $charchar === '/*') {
$insideComment = Json::MULTI;
$i += 1;
} elseif (!$insideComment) {
$json .= $char;
} elseif ($insideComment === Json::SINGLE && $charchar === "\r\n") {
$insideComment = false;
$json .= $charchar;
$i += 1;
} elseif ($insideComment === Json::SINGLE && $char === "\n") {
$insideComment = false;
$json .= $char;
} elseif ($insideComment === Json::MULTI && $charchar === '*/') {
$insideComment = false;
$i += 1;
}
}
return $json;
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
class Logger {
private static $start;
private static $prev;
private static function time() {
return microtime(true) * 1000; // sec * 1000 = ms
}
public static function init() {
self::$start = self::time();
self::$prev = self::$start;
register_shutdown_function(function () { Logger::log('shutdown'); });
Logger::log('--------------------------------');
}
public static function log($message=null, $obj=null) {
$now = self::time();
$message = number_format($now - self::$start, 3) . ' ' . number_format($now - self::$prev, 3) . ' ' . $message;
@error_log($message . ' ' . var_export($obj, true));
self::$prev = $now;
}
}
Logger::init();
+38
View File
@@ -0,0 +1,38 @@
<?php
class Request {
private $params;
public function __construct($params, $body) {
$data = json_decode($body, true);
$this->params = $data !== null ? $data : $params;
}
public function query($keypath = '', $default = Util::NO_DEFAULT) {
$value = Util::array_query($this->params, $keypath, Util::NO_DEFAULT);
if ($value === Util::NO_DEFAULT) {
Util::json_fail(Util::ERR_MISSING_PARAM, 'parameter \'' . $keypath . '\' is missing', $default === Util::NO_DEFAULT);
return $default;
}
return $value;
}
public function query_boolean($keypath = '', $default = Util::NO_DEFAULT) {
$value = $this->query($keypath, $default);
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
public function query_numeric($keypath = '', $default = Util::NO_DEFAULT) {
$value = $this->query($keypath, $default);
Util::json_fail(Util::ERR_ILLIGAL_PARAM, 'parameter \'' . $keypath . '\' is not numeric', !is_numeric($value));
return intval($value, 10);
}
public function query_array($keypath = '', $default = Util::NO_DEFAULT) {
$value = $this->query($keypath, $default);
Util::json_fail(Util::ERR_ILLIGAL_PARAM, 'parameter \'' . $keypath . '\' is no array', !is_array($value));
return $value;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
class Session {
private static $KEY_PREFIX = '__H5AI__';
private $store;
public function __construct(&$store) {
$this->store = &$store;
}
public function set($key, $value) {
$key = Session::$KEY_PREFIX . $key;
$this->store[$key] = $value;
}
public function get($key, $default = null) {
$key = Session::$KEY_PREFIX . $key;
return array_key_exists($key, $this->store) ? $this->store[$key] : $default;
}
}
+188
View File
@@ -0,0 +1,188 @@
<?php
class Setup {
private $store;
private $refresh;
public function __construct($refresh = false) {
$this->store = [];
$this->refresh = $refresh;
$this->add_globals_and_envs();
$this->add_php_checks();
$this->add_app_metadata();
$this->add_server_metadata_and_check();
$this->add_paths();
$this->add_sys_cmd_checks();
}
private function set($key, $value) {
if (array_key_exists($key, $this->store)) {
Logger::log('setup key already taken', [
'key' => $key,
'value' => $value,
'found' => $this->store[$key]
]);
exit;
}
if (!is_string($value) && !is_bool($value)) {
Logger::log('setup value neither string nor boolean', [
'key' => $key,
'value' => $value
]);
exit;
}
$this->store[$key] = $value;
}
public function get($key) {
if (!array_key_exists($key, $this->store)) {
Logger::log('setup key not found', ['key' => $key]);
exit;
}
return $this->store[$key];
}
private function add_globals_and_envs() {
$this->set('PHP_VERSION', PHP_VERSION);
$this->set('MIN_PHP_VERSION', MIN_PHP_VERSION);
$this->set('PHP_ARCH', (PHP_INT_SIZE * 8) . '-bit');
$this->set('REQUEST_METHOD', $_SERVER['REQUEST_METHOD']);
$this->set('REQUEST_HREF', parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
$this->set('SCRIPT_NAME', $_SERVER['SCRIPT_NAME']);
$this->set('SERVER_SOFTWARE', $_SERVER['SERVER_SOFTWARE']);
$this->set('HTTP_USER_AGENT', $_SERVER['HTTP_USER_AGENT']);
}
private function add_php_checks() {
$this->set('HAS_PHP_EXIF', function_exists('exif_thumbnail'));
$has_php_jpeg = false;
if (function_exists('gd_info')) {
$infos = gd_info();
$has_php_jpeg = array_key_exists('JPEG Support', $infos) && $infos['JPEG Support'];
}
$this->set('HAS_PHP_JPEG', $has_php_jpeg);
}
private function add_app_metadata() {
$this->set('NAME', 'h5ai');
$this->set('VERSION', H5AI_VERSION);
$this->set('FILE_PREFIX', '_h5ai');
}
private function add_server_metadata_and_check() {
$server_software = $this->get('SERVER_SOFTWARE');
$server_name = null;
$server_version = null;
if ($server_software && preg_match('#^(.*?)(?:/(.*?))?(?: |$)#', strtolower($server_software), $matches)) {
$server_name = $matches[1];
$server_version = count($matches) > 2 ? $matches[2] : '';
}
$this->set('SERVER_NAME', $server_name);
$this->set('SERVER_VERSION', $server_version);
$this->set('HAS_SERVER', in_array($server_name, ['apache', 'lighttpd', 'nginx', 'cherokee']));
}
private function add_paths() {
$script_name = $this->get('SCRIPT_NAME');
if ($this->get('SERVER_NAME') === 'lighttpd') {
$script_name = preg_replace('#^.*?//#', '/', $script_name);
}
$this->set('H5AI_HREF', Util::normalize_path(dirname(dirname($script_name)), true));
$this->set('H5AI_PATH', Util::normalize_path(dirname(dirname(dirname(dirname(__FILE__)))), false));
$this->set('ROOT_HREF', Util::normalize_path(dirname($this->get('H5AI_HREF')), true));
$this->set('ROOT_PATH', '/h5ai');
$this->set('PUBLIC_HREF', Util::normalize_path($this->get('H5AI_HREF') . '/public/', true));
$this->set('PUBLIC_PATH', Util::normalize_path($this->get('H5AI_PATH') . '/public/', false));
$this->set('INDEX_HREF', Util::normalize_path($this->get('PUBLIC_HREF') . '/index.php', false));
$this->set('CACHE_PUB_HREF', Util::normalize_path($this->get('PUBLIC_HREF') . '/cache', true));
$this->set('CACHE_PUB_PATH', Util::normalize_path($this->get('PUBLIC_PATH') . '/cache', false));
$this->set('HAS_WRITABLE_CACHE_PUB', @is_writable($this->get('CACHE_PUB_PATH')));
$this->set('PRIVATE_PATH', Util::normalize_path($this->get('H5AI_PATH') . '/private', false));
$this->set('CONF_PATH', Util::normalize_path($this->get('PRIVATE_PATH') . '/conf', false));
$this->set('CACHE_PRV_PATH', Util::normalize_path($this->get('PRIVATE_PATH') . '/cache', false));
$this->set('HAS_WRITABLE_CACHE_PRV', @is_writable($this->get('CACHE_PRV_PATH')));
}
private function add_sys_cmd_checks() {
$cmds_cache_path = Util::normalize_path($this->get('CACHE_PRV_PATH') . '/cmds.json', false);
$cmds = Json::load($cmds_cache_path);
if (sizeof($cmds) === 0 || $this->refresh) {
$cmds['command'] = Util::exec_0('command -v command');
$cmds['which'] = Util::exec_0('which which') || Util::exec_0('which which.exe');
$cmds['where'] = Util::exec_0('where where.exe');
$cmd = false;
if ($cmds['command']) {
$cmd = 'command -v';
} elseif ($cmds['which']) {
$cmd = 'which';
} elseif ($cmds['where']) {
$cmd = 'where';
}
foreach (['avconv', 'convert', 'du', 'ffmpeg', 'gm', 'tar', 'zip'] as $c) {
$cmds[$c] = ($cmd !== false) && (Util::exec_0($cmd . ' ' . $c) || Util::exec_0($cmd . ' ' . $c . '.exe'));
}
Json::save($cmds_cache_path, $cmds);
}
foreach ($cmds as $c => $has) {
$this->set('HAS_CMD_' . strtoupper($c), $has);
}
}
public function to_jsono($as_admin = false) {
$keys = [
'PUBLIC_HREF',
'ROOT_HREF'
];
if ($as_admin) {
$keys = array_merge($keys, [
'VERSION',
'PHP_VERSION',
'MIN_PHP_VERSION',
'PHP_ARCH',
'HAS_PHP_EXIF',
'HAS_PHP_JPEG',
'SERVER_NAME',
'SERVER_VERSION',
'HAS_SERVER',
'INDEX_HREF',
'HAS_WRITABLE_CACHE_PUB',
'HAS_WRITABLE_CACHE_PRV',
'HAS_CMD_AVCONV',
'HAS_CMD_CONVERT',
'HAS_CMD_DU',
'HAS_CMD_FFMPEG',
'HAS_CMD_GM',
'HAS_CMD_TAR',
'HAS_CMD_ZIP'
]);
}
$jsono = ['AS_ADMIN' => $as_admin];
foreach ($keys as $key) {
$jsono[$key] = $this->get($key);
}
return $jsono;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
class Theme {
private static $EXTENSIONS = ['svg', 'png', 'jpg'];
private $context;
public function __construct($context) {
$this->context = $context;
}
public function get_icons() {
$public_path = $this->context->get_setup()->get('PUBLIC_PATH');
$theme = $this->context->query_option('view.theme', '-NONE-');
$theme_path = $public_path . '/images/themes/' . $theme;
$icons = [];
if (is_dir($theme_path)) {
if ($dir = opendir($theme_path)) {
while (($name = readdir($dir)) !== false) {
$path_parts = pathinfo($name);
if (in_array(@$path_parts['extension'], Theme::$EXTENSIONS)) {
$icons[$path_parts['filename']] = $theme . '/' . $name;
}
}
closedir($dir);
}
}
return $icons;
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
class Util {
const ERR_MISSING_PARAM = 'ERR_MISSING_PARAM';
const ERR_ILLIGAL_PARAM = 'ERR_ILLIGAL_PARAM';
const ERR_FAILED = 'ERR_FAILED';
const ERR_DISABLED = 'ERR_DISABLED';
const ERR_UNSUPPORTED = 'ERR_UNSUPPORTED';
const NO_DEFAULT = 'NO_*@+#?!_DEFAULT';
const RE_DELIMITER = '@';
public static function normalize_path($path, $trailing_slash = false) {
$path = preg_replace('#[\\\\/]+#', '/', $path);
return preg_match('#^(\w:)?/$#', $path) ? $path : (rtrim($path, '/') . ($trailing_slash ? '/' : ''));
}
public static function json_exit($obj = []) {
header('Content-type: application/json;charset=utf-8');
echo json_encode($obj);
exit;
}
public static function json_fail($err, $msg = '', $cond = true) {
if ($cond) {
Util::json_exit(['err' => $err, 'msg' => $msg]);
}
}
public static function array_query($array, $keypath = '', $default = Util::NO_DEFAULT) {
$value = $array;
$keys = array_filter(explode('.', $keypath));
foreach ($keys as $key) {
if (!is_array($value) || !array_key_exists($key, $value)) {
return $default;
}
$value = $value[$key];
}
return $value;
}
public static function starts_with($sequence, $head) {
return substr($sequence, 0, strlen($head)) === $head;
}
public static function ends_with($sequence, $tail) {
$len = strlen($tail);
return $len === 0 ? true : substr($sequence, -$len) === $tail;
}
public static function wrap_pattern($pattern) {
return Util::RE_DELIMITER . str_replace(Util::RE_DELIMITER, '\\' . Util::RE_DELIMITER, $pattern) . Util::RE_DELIMITER;
}
public static function passthru_cmd($cmd) {
$rc = null;
passthru($cmd, $rc);
return $rc;
}
public static function exec_cmdv($cmdv) {
if (!is_array($cmdv)) {
$cmdv = func_get_args();
}
$cmd = implode(' ', array_map('escapeshellarg', $cmdv));
$lines = [];
$rc = null;
exec($cmd, $lines, $rc);
return implode("\n", $lines);
}
public static function exec_0($cmd) {
$lines = [];
$rc = null;
try {
@exec($cmd, $lines, $rc);
return $rc === 0;
} catch (Exception $e) {}
return false;
}
public static function filesize($context, $path) {
$withFoldersize = $context->query_option('foldersize.enabled', false);
$withDu = $context->get_setup()->get('HAS_CMD_DU') && $context->query_option('foldersize.type', null) === 'shell-du';
return Filesize::getCachedSize($path, $withFoldersize, $withDu);
}
}