feat: h5ai 0.30.0 with PHP 8.4.26 security patch for PHP2Shell
This commit is contained in:
Executable
+110
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+91
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+65
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Executable
+89
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user