feat: h5ai 0.30.0 with PHP 8.4.26 security patch for PHP2Shell
This commit is contained in:
Executable
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
class Bootstrap {
|
||||
private static $autopaths = ['core', 'ext'];
|
||||
|
||||
public static function run() {
|
||||
spl_autoload_register(['Bootstrap', 'autoload']);
|
||||
putenv('LANG=en_US.UTF-8');
|
||||
setlocale(LC_CTYPE, 'en_US.UTF-8');
|
||||
date_default_timezone_set(@date_default_timezone_get());
|
||||
session_start();
|
||||
|
||||
$session = new Session($_SESSION);
|
||||
$request = new Request($_REQUEST, file_get_contents('php://input'));
|
||||
$setup = new Setup($request->query_boolean('refresh', false));
|
||||
$context = new Context($session, $request, $setup);
|
||||
|
||||
if ($context->is_api_request()) {
|
||||
(new Api($context))->apply();
|
||||
} elseif ($context->is_info_request()) {
|
||||
$public_href = $setup->get('PUBLIC_HREF');
|
||||
$x_head_tags = $context->get_x_head_html();
|
||||
$fallback_mode = false;
|
||||
require __DIR__ . '/pages/info.php';
|
||||
} else {
|
||||
$public_href = $setup->get('PUBLIC_HREF');
|
||||
$x_head_tags = $context->get_x_head_html();
|
||||
$fallback_mode = $context->is_fallback_mode();
|
||||
$fallback_html = (new Fallback($context))->get_html();
|
||||
require __DIR__ . '/pages/index.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function autoload($class_name) {
|
||||
$filename = 'class-' . strtolower($class_name) . '.php';
|
||||
|
||||
foreach (Bootstrap::$autopaths as $path) {
|
||||
$file = __DIR__ . '/' . $path . '/' . $filename;
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
class Archive {
|
||||
const NULL_BYTE = "\0";
|
||||
|
||||
private static $SEGMENT_SIZE = 16777216; // 1024 * 1024 * 16 = 16MiB
|
||||
private static $TAR_PASSTHRU_CMD = 'cd [ROOTDIR] && tar --no-recursion -c -- [DIRS] [FILES]';
|
||||
private static $ZIP_PASSTHRU_CMD = 'cd [ROOTDIR] && zip - -- [FILES]';
|
||||
|
||||
private $context;
|
||||
private $base_path;
|
||||
private $dirs;
|
||||
private $files;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function output($type, $base_href, $hrefs) {
|
||||
$this->base_path = $this->context->to_path($base_href);
|
||||
if (!$this->context->is_managed_path($this->base_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->dirs = [];
|
||||
$this->files = [];
|
||||
|
||||
$this->add_hrefs($hrefs);
|
||||
|
||||
if (count($this->dirs) === 0 && count($this->files) === 0) {
|
||||
if ($type === 'php-tar') {
|
||||
$this->add_dir($this->base_path, '/');
|
||||
} else {
|
||||
$this->add_dir($this->base_path, '.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($type === 'php-tar') {
|
||||
return $this->php_tar($this->dirs, $this->files);
|
||||
} elseif ($type === 'shell-tar') {
|
||||
return $this->shell_cmd(Archive::$TAR_PASSTHRU_CMD);
|
||||
} elseif ($type === 'shell-zip') {
|
||||
return $this->shell_cmd(Archive::$ZIP_PASSTHRU_CMD);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function shell_cmd($cmd) {
|
||||
$cmd = str_replace('[ROOTDIR]', escapeshellarg($this->base_path), $cmd);
|
||||
$cmd = str_replace('[DIRS]', count($this->dirs) ? implode(' ', array_map('escapeshellarg', $this->dirs)) : '', $cmd);
|
||||
$cmd = str_replace('[FILES]', count($this->files) ? implode(' ', array_map('escapeshellarg', $this->files)) : '', $cmd);
|
||||
try {
|
||||
Util::passthru_cmd($cmd);
|
||||
} catch (Exeption $err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function php_tar($dirs, $files) {
|
||||
$filesizes = [];
|
||||
$total_size = 512 * count($dirs);
|
||||
foreach (array_keys($files) as $real_file) {
|
||||
$size = filesize($real_file);
|
||||
|
||||
$filesizes[$real_file] = $size;
|
||||
$total_size += 512 + $size;
|
||||
if ($size % 512 != 0) {
|
||||
$total_size += 512 - ($size % 512);
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Length: ' . $total_size);
|
||||
|
||||
foreach ($dirs as $real_dir => $archived_dir) {
|
||||
echo $this->php_tar_header($archived_dir, 0, @filemtime($real_dir . DIRECTORY_SEPARATOR . '.'), 5);
|
||||
}
|
||||
|
||||
foreach ($files as $real_file => $archived_file) {
|
||||
$size = $filesizes[$real_file];
|
||||
|
||||
echo $this->php_tar_header($archived_file, $size, @filemtime($real_file), 0);
|
||||
$this->print_file($real_file);
|
||||
|
||||
if ($size % 512 != 0) {
|
||||
echo str_repeat(Archive::NULL_BYTE, 512 - ($size % 512));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function php_tar_header($filename, $size, $mtime, $type) {
|
||||
$name = substr(basename($filename), -99);
|
||||
$prefix = substr(Util::normalize_path(dirname($filename)), -154);
|
||||
if ($prefix === '.') {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$header =
|
||||
str_pad($name, 100, Archive::NULL_BYTE) // filename [100]
|
||||
. '0000755' . Archive::NULL_BYTE // file mode [8]
|
||||
. '0000000' . Archive::NULL_BYTE // uid [8]
|
||||
. '0000000' . Archive::NULL_BYTE // gid [8]
|
||||
. str_pad(decoct($size), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file size [12]
|
||||
. str_pad(decoct($mtime), 11, '0', STR_PAD_LEFT) . Archive::NULL_BYTE // file modification time [12]
|
||||
. ' ' // checksum [8]
|
||||
. str_pad($type, 1) // file type [1]
|
||||
. str_repeat(Archive::NULL_BYTE, 100) // linkname [100]
|
||||
. 'ustar' . Archive::NULL_BYTE // magic [6]
|
||||
. '00' // version [2]
|
||||
. str_repeat(Archive::NULL_BYTE, 80) // uname, gname, defmajor, devminor [32 + 32 + 8 + 8]
|
||||
. str_pad($prefix, 155, Archive::NULL_BYTE) // filename [155]
|
||||
. str_repeat(Archive::NULL_BYTE, 12); // fill [12]
|
||||
assert(strlen($header) === 512);
|
||||
|
||||
// checksum
|
||||
$checksum = array_sum(array_map('ord', str_split($header)));
|
||||
$checksum = str_pad(decoct($checksum), 6, '0', STR_PAD_LEFT) . Archive::NULL_BYTE . ' ';
|
||||
$header = substr_replace($header, $checksum, 148, 8);
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
private function print_file($file) {
|
||||
// Send file content in segments to not hit PHP's memory limit (default: 128M)
|
||||
if ($fd = fopen($file, 'rb')) {
|
||||
while (!feof($fd)) {
|
||||
print fread($fd, Archive::$SEGMENT_SIZE);
|
||||
@ob_flush();
|
||||
@flush();
|
||||
}
|
||||
fclose($fd);
|
||||
}
|
||||
}
|
||||
|
||||
private function add_hrefs($hrefs) {
|
||||
if (!is_array($hrefs)) {
|
||||
$hrefs = array($hrefs);
|
||||
}
|
||||
|
||||
foreach ($hrefs as $href) {
|
||||
if (trim($href) === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$href = Util::normalize_path($href, false);
|
||||
$d = dirname($href);
|
||||
$n = basename($href);
|
||||
|
||||
if ($this->context->is_managed_href($d) && !$this->context->is_hidden($n)) {
|
||||
|
||||
$real_file = $this->context->to_path($href);
|
||||
$archived_file = preg_replace('!^' . preg_quote(Util::normalize_path($this->base_path, true)) . '!', '', $real_file);
|
||||
|
||||
if (is_dir($real_file)) {
|
||||
$this->add_dir($real_file, $archived_file);
|
||||
} else {
|
||||
$this->add_file($real_file, $archived_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_file($real_file, $archived_file) {
|
||||
if (is_readable($real_file)) {
|
||||
$this->files[$real_file] = $archived_file;
|
||||
}
|
||||
}
|
||||
|
||||
private function add_dir($real_dir, $archived_dir) {
|
||||
if ($this->context->is_managed_path($real_dir)) {
|
||||
$this->dirs[$real_dir] = $archived_dir;
|
||||
|
||||
$files = $this->context->read_dir($real_dir);
|
||||
foreach ($files as $file) {
|
||||
$real_file = $real_dir . '/' . $file;
|
||||
$archived_file = $archived_dir . '/' . $file;
|
||||
|
||||
if (is_dir($real_file)) {
|
||||
$this->add_dir($real_file, $archived_file);
|
||||
} else {
|
||||
$this->add_file($real_file, $archived_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
class Custom {
|
||||
private static $EXTENSIONS = ['html', 'md'];
|
||||
private $context;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
private function read_custom_file($path, $name, &$content, &$type) {
|
||||
$file_prefix = $this->context->get_setup()->get('FILE_PREFIX');
|
||||
|
||||
foreach (Custom::$EXTENSIONS as $ext) {
|
||||
$file = $path . '/' . $file_prefix . '.' . $name . '.' . $ext;
|
||||
if (is_readable($file)) {
|
||||
$content = file_get_contents($file);
|
||||
$type = $ext;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_customizations($href) {
|
||||
if (!$this->context->query_option('custom.enabled', false)) {
|
||||
return [
|
||||
'header' => ['content' => null, 'type' => null],
|
||||
'footer' => ['content' => null, 'type' => null]
|
||||
];
|
||||
}
|
||||
|
||||
$root_path = $this->context->get_setup()->get('FILE_PREFIX');
|
||||
$path = $this->context->to_path($href);
|
||||
|
||||
$header = null;
|
||||
$header_type = null;
|
||||
$footer = null;
|
||||
$footer_type = null;
|
||||
|
||||
$this->read_custom_file($path, 'header', $header, $header_type);
|
||||
$this->read_custom_file($path, 'footer', $footer, $footer_type);
|
||||
|
||||
while ($header === null || $footer === null) {
|
||||
if ($header === null) {
|
||||
$this->read_custom_file($path, 'headers', $header, $header_type);
|
||||
}
|
||||
if ($footer === null) {
|
||||
$this->read_custom_file($path, 'footers', $footer, $footer_type);
|
||||
}
|
||||
if ($path === $root_path) {
|
||||
break;
|
||||
}
|
||||
$parent_path = Util::normalize_path(dirname($path));
|
||||
if ($parent_path === $path) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Stop once we reach the root
|
||||
if (
|
||||
$this->context->query_option('custom.stopSearchingAtRoot', true) &&
|
||||
$path === $this->context->get_setup()->get('ROOT_PATH')
|
||||
) {
|
||||
break;
|
||||
}
|
||||
$path = $parent_path;
|
||||
}
|
||||
|
||||
return [
|
||||
'header' => ['content' => $header, 'type' => $header_type],
|
||||
'footer' => ['content' => $footer, 'type' => $footer_type]
|
||||
];
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class Search {
|
||||
private $context;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
public function get_paths($root, $pattern = null, $ignorecase = false) {
|
||||
$paths = [];
|
||||
if ($pattern && $this->context->is_managed_path($root)) {
|
||||
$re = Util::wrap_pattern($pattern);
|
||||
if ($ignorecase) {
|
||||
$re .= 'i';
|
||||
}
|
||||
$names = $this->context->read_dir($root);
|
||||
foreach ($names as $name) {
|
||||
$path = $root . '/' . $name;
|
||||
if (preg_match($re, @basename($path))) {
|
||||
$paths[] = $path;
|
||||
}
|
||||
if (@is_dir($path)) {
|
||||
$paths = array_merge($paths, $this->get_paths($path, $pattern, $ignorecase));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
|
||||
public function get_items($href, $pattern = null, $ignorecase = false) {
|
||||
$cache = [];
|
||||
$root = $this->context->to_path($href);
|
||||
$paths = $this->get_paths($root, $pattern, $ignorecase);
|
||||
$items = array_map(function ($path) {
|
||||
return Item::get($this->context, $path, $cache)->to_json_object();
|
||||
}, $paths);
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
Executable
+253
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
class Thumb {
|
||||
private static $FFMPEG_CMDV = ['ffmpeg', '-ss', '0:00:10', '-i', '[SRC]', '-an', '-vframes', '1', '[DEST]'];
|
||||
private static $AVCONV_CMDV = ['avconv', '-ss', '0:00:10', '-i', '[SRC]', '-an', '-vframes', '1', '[DEST]'];
|
||||
private static $CONVERT_CMDV = ['convert', '-density', '200', '-quality', '100', '-strip', '[SRC][0]', '[DEST]'];
|
||||
private static $GM_CONVERT_CMDV = ['gm', 'convert', '-density', '200', '-quality', '100', '[SRC][0]', '[DEST]'];
|
||||
private static $THUMB_CACHE = 'thumbs';
|
||||
|
||||
private $context;
|
||||
private $setup;
|
||||
private $thumbs_path;
|
||||
private $thumbs_href;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->context = $context;
|
||||
$this->setup = $context->get_setup();
|
||||
$this->thumbs_path = $this->setup->get('CACHE_PUB_PATH') . '/' . Thumb::$THUMB_CACHE;
|
||||
$this->thumbs_href = $this->setup->get('CACHE_PUB_HREF') . Thumb::$THUMB_CACHE;
|
||||
|
||||
if (!is_dir($this->thumbs_path)) {
|
||||
@mkdir($this->thumbs_path, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function thumb($type, $source_href, $width, $height) {
|
||||
$source_path = $this->context->to_path($source_href);
|
||||
if (!file_exists($source_path) || Util::starts_with($source_path, $this->setup->get('CACHE_PUB_PATH'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$capture_path = $source_path;
|
||||
if ($type === 'img') {
|
||||
$capture_path = $source_path;
|
||||
} elseif ($type === 'mov') {
|
||||
if ($this->setup->get('HAS_CMD_AVCONV')) {
|
||||
$capture_path = $this->capture(Thumb::$AVCONV_CMDV, $source_path);
|
||||
} elseif ($this->setup->get('HAS_CMD_FFMPEG')) {
|
||||
$capture_path = $this->capture(Thumb::$FFMPEG_CMDV, $source_path);
|
||||
}
|
||||
} elseif ($type === 'doc') {
|
||||
if ($this->setup->get('HAS_CMD_CONVERT')) {
|
||||
$capture_path = $this->capture(Thumb::$CONVERT_CMDV, $source_path);
|
||||
} elseif ($this->setup->get('HAS_CMD_GM')) {
|
||||
$capture_path = $this->capture(Thumb::$GM_CONVERT_CMDV, $source_path);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->thumb_href($capture_path, $width, $height);
|
||||
}
|
||||
|
||||
private function thumb_href($source_path, $width, $height) {
|
||||
if (!file_exists($source_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = 'thumb-' . sha1($source_path) . '-' . $width . 'x' . $height . '.jpg';
|
||||
$thumb_path = $this->thumbs_path . '/' . $name;
|
||||
$thumb_href = $this->thumbs_href . '/' . $name;
|
||||
|
||||
if (!file_exists($thumb_path) || filemtime($source_path) >= filemtime($thumb_path)) {
|
||||
$image = new Image();
|
||||
|
||||
$et = false;
|
||||
if ($this->setup->get('HAS_PHP_EXIF') && $this->context->query_option('thumbnails.exif', false) === true && $height != 0) {
|
||||
$et = @exif_thumbnail($source_path);
|
||||
}
|
||||
if($et !== false) {
|
||||
file_put_contents($thumb_path, $et);
|
||||
$image->set_source($thumb_path);
|
||||
$image->normalize_exif_orientation($source_path);
|
||||
} else {
|
||||
$image->set_source($source_path);
|
||||
}
|
||||
|
||||
$image->thumb($width, $height);
|
||||
$image->save_dest_jpeg($thumb_path, 80);
|
||||
}
|
||||
|
||||
return file_exists($thumb_path) ? $thumb_href : null;
|
||||
}
|
||||
|
||||
private function capture($cmdv, $source_path) {
|
||||
if (!file_exists($source_path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$capture_path = $this->thumbs_path . '/capture-' . sha1($source_path) . '.jpg';
|
||||
|
||||
if (!file_exists($capture_path) || filemtime($source_path) >= filemtime($capture_path)) {
|
||||
foreach ($cmdv as &$arg) {
|
||||
$arg = str_replace('[SRC]', $source_path, $arg);
|
||||
$arg = str_replace('[DEST]', $capture_path, $arg);
|
||||
}
|
||||
|
||||
Util::exec_cmdv($cmdv);
|
||||
}
|
||||
|
||||
return file_exists($capture_path) ? $capture_path : null;
|
||||
}
|
||||
}
|
||||
|
||||
class Image {
|
||||
private $source_file;
|
||||
private $source;
|
||||
private $width;
|
||||
private $height;
|
||||
private $type;
|
||||
private $dest;
|
||||
|
||||
public function __construct($filename = null) {
|
||||
$this->source_file = null;
|
||||
$this->source = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
|
||||
$this->dest = null;
|
||||
|
||||
$this->set_source($filename);
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->release_source();
|
||||
$this->release_dest();
|
||||
}
|
||||
|
||||
public function set_source($filename) {
|
||||
$this->release_source();
|
||||
$this->release_dest();
|
||||
|
||||
if (is_null($filename)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source_file = $filename;
|
||||
|
||||
list($this->width, $this->height, $this->type) = @getimagesize($this->source_file);
|
||||
|
||||
if (!$this->width || !$this->height) {
|
||||
$this->source_file = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source = imagecreatefromstring(file_get_contents($this->source_file));
|
||||
}
|
||||
|
||||
public function save_dest_jpeg($filename, $quality = 80) {
|
||||
if (!is_null($this->dest)) {
|
||||
@imagejpeg($this->dest, $filename, $quality);
|
||||
@chmod($filename, 0775);
|
||||
}
|
||||
}
|
||||
|
||||
public function release_dest() {
|
||||
if (!is_null($this->dest)) {
|
||||
@imagedestroy($this->dest);
|
||||
$this->dest = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function release_source() {
|
||||
if (!is_null($this->source)) {
|
||||
@imagedestroy($this->source);
|
||||
$this->source_file = null;
|
||||
$this->source = null;
|
||||
$this->width = null;
|
||||
$this->height = null;
|
||||
$this->type = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function thumb($width, $height) {
|
||||
if (is_null($this->source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$src_r = 1.0 * $this->width / $this->height;
|
||||
|
||||
if ($height == 0) {
|
||||
if ($src_r >= 1) {
|
||||
$height = 1.0 * $width / $src_r;
|
||||
} else {
|
||||
$height = $width;
|
||||
$width = 1.0 * $height * $src_r;
|
||||
}
|
||||
if ($width > $this->width) {
|
||||
$width = $this->width;
|
||||
$height = $this->height;
|
||||
}
|
||||
}
|
||||
|
||||
$ratio = 1.0 * $width / $height;
|
||||
|
||||
if ($src_r <= $ratio) {
|
||||
$src_w = $this->width;
|
||||
$src_h = $src_w / $ratio;
|
||||
$src_x = 0;
|
||||
} else {
|
||||
$src_h = $this->height;
|
||||
$src_w = $src_h * $ratio;
|
||||
$src_x = 0.5 * ($this->width - $src_w);
|
||||
}
|
||||
|
||||
$width = intval($width);
|
||||
$height = intval($height);
|
||||
$src_x = intval($src_x);
|
||||
$src_w = intval($src_w);
|
||||
$src_h = intval($src_h);
|
||||
|
||||
$this->dest = imagecreatetruecolor($width, $height);
|
||||
$icol = imagecolorallocate($this->dest, 255, 255, 255);
|
||||
imagefill($this->dest, 0, 0, $icol);
|
||||
imagecopyresampled($this->dest, $this->source, 0, 0, $src_x, 0, $width, $height, $src_w, $src_h);
|
||||
}
|
||||
|
||||
public function rotate($angle) {
|
||||
if (is_null($this->source) || ($angle !== 90 && $angle !== 180 && $angle !== 270)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->source = imagerotate($this->source, $angle, 0);
|
||||
if ( $angle === 90 || $angle === 270 ) {
|
||||
list($this->width, $this->height) = [$this->height, $this->width];
|
||||
}
|
||||
}
|
||||
|
||||
public function normalize_exif_orientation($exif_source_file = null) {
|
||||
if (is_null($this->source) || !function_exists('exif_read_data')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($exif_source_file === null) {
|
||||
$exif_source_file = $this->source_file;
|
||||
}
|
||||
|
||||
$exif = exif_read_data($exif_source_file);
|
||||
switch (@$exif['Orientation']) {
|
||||
case 3:
|
||||
$this->rotate(180);
|
||||
break;
|
||||
case 6:
|
||||
$this->rotate(270);
|
||||
break;
|
||||
case 8:
|
||||
$this->rotate(90);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
<?php header('Content-type: text/html;charset=utf-8'); ?><!DOCTYPE html><html class="no-js" lang="en"><head><meta charset="utf-8"><meta http-equiv="x-ua-compatible" content="ie=edge"><title>index - powered by h5ai v0.30.0 (https://larsjung.de/h5ai/)</title><meta name="description" content="index - powered by h5ai v0.30.0 (https://larsjung.de/h5ai/)"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="shortcut icon" href="<?= $public_href; ?>images/favicon/favicon-16-32.ico"><link rel="apple-touch-icon-precomposed" type="image/png" href="<?= $public_href; ?>images/favicon/favicon-152.png"><link rel="stylesheet" href="<?= $public_href; ?>css/styles.css"><?php if (!$fallback_mode) { ?><script src="<?= $public_href; ?>js/scripts.js" data-module="index"></script><?php } ?>
|
||||
<?= $x_head_tags; ?></head><body class="index" id="root"><div id="fallback-hints"><?php if (!$fallback_mode) { ?><span class="noJsMsg">Works best with JavaScript enabled!</span><span class="noBrowserMsg">Works best in <a href="http://browsehappy.com">modern browsers</a>!</span><?php } ?><span class="backlink"><a href="https://larsjung.de/h5ai/" title="h5ai v0.30.0 - Modern HTTP web server index.">powered by h5ai</a></span></div><div id="fallback"><?= $fallback_html; ?></div></body></html><!-- h5ai v0.30.0 - https://larsjung.de/h5ai/ -->
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
<?php header('Content-type: text/html;charset=utf-8'); ?><!DOCTYPE html><html class="no-js" lang="en"><head><meta charset="utf-8"><meta http-equiv="x-ua-compatible" content="ie=edge"><title>h5ai info page - v0.30.0</title><meta name="description" content="h5ai info page - v0.30.0"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="shortcut icon" href="<?= $public_href; ?>images/favicon/favicon-16-32.ico"><link rel="apple-touch-icon-precomposed" type="image/png" href="<?= $public_href; ?>images/favicon/favicon-152.png"><link rel="stylesheet" href="<?= $public_href; ?>css/styles.css"><?php if (!$fallback_mode) { ?><script src="<?= $public_href; ?>js/scripts.js" data-module="info"></script><?php } ?>
|
||||
<?= $x_head_tags; ?></head><body class="info" id="root"><div id="fallback-hints"><?php if (!$fallback_mode) { ?><span class="noJsMsg">Works best with JavaScript enabled!</span><span class="noBrowserMsg">Works best in <a href="http://browsehappy.com">modern browsers</a>!</span><?php } ?><span class="backlink"><a href="https://larsjung.de/h5ai/" title="h5ai v0.30.0 - Modern HTTP web server index.">powered by h5ai</a></span></div><div id="content"><h1 id="header"><a href="https://larsjung.de/h5ai/">h5ai</a></h1></div></body></html><!-- h5ai v0.30.0 - https://larsjung.de/h5ai/ -->
|
||||
Reference in New Issue
Block a user