From e6c25f229a9ae95fe7665cc80a50e8ac79c9f456 Mon Sep 17 00:00:00 2001 From: bohwaz Date: Thu, 27 Nov 2025 19:27:29 +0000 Subject: [PATCH 1/5] Upgrade dependencies FossilOrigin-Name: 593379b27b4979bde3a6ef92ac05ad38dd60758a23d0c4f512deecc6c28bddaf --- Makefile | 3 + index.php | 907 ++++++++++++++++++++++++++++++++++++++--------------- make.php | 2 + server.php | 26 +- 4 files changed, 685 insertions(+), 253 deletions(-) diff --git a/Makefile b/Makefile index 94b87fa..d884e92 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,9 @@ KD2FW_URL := https://fossil.kd2.org/kd2fw/doc/trunk/src/lib/KD2/ deps: @-mkdir -p lib/KD2/WebDAV + @-mkdir -p lib/KD2/HTTP + wget -O lib/KD2/HTTP/Server.php '${KD2FW_URL}HTTP/Server.php' + wget -O lib/KD2/WebDAV/Exception.php '${KD2FW_URL}WebDAV/Exception.php' wget -O lib/KD2/WebDAV/Server.php '${KD2FW_URL}WebDAV/Server.php' wget -O lib/KD2/WebDAV/AbstractStorage.php '${KD2FW_URL}WebDAV/AbstractStorage.php' wget -O webdav.js https://raw.githubusercontent.com/kd2org/webdav-manager.js/main/webdav.js diff --git a/index.php b/index.php index fb6e1b0..cf9b139 100644 --- a/index.php +++ b/index.php @@ -1,10 +1,242 @@ $size) { + throw new \LogicException('End range cannot be satisfied', 416); + } + } + elseif ($allow_gzip + && isset($_SERVER['HTTP_ACCEPT_ENCODING']) + && false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') + && isset($size, $name) + // Don't compress if size is larger than 8 MiB + && $size < 8*1024*1024 + // Don't compress already compressed content + && !preg_match('/\.(?:cbz|cbr|cb7|mp4|m4a|zip|docx|xlsx|pptx|ods|odt|odp|7z|gz|bz2|lzma|lz|xz|apk|dmg|jar|rar|webm|ogg|mp3|ogm|flac|ogv|mkv|avi)$/i', $name)) { + $gzip = true; + header('Content-Encoding: gzip', true); + } + + // Try to avoid common issues with output buffering and stuff + if (function_exists('apache_setenv')) { + @apache_setenv('no-gzip', 1); + } + + @ini_set('zlib.output_compression', 'Off'); + + // Clean buffer, just in case + if (@ob_get_length()) { + @ob_clean(); + } + + if (isset($content)) { + $length = strlen($content); + + if ($start || $end) { + if (null !== $end && $end > $length) { + header('Content-Range: bytes */' . $length, true); + throw new \LogicException('End range cannot be satisfied', 416); + } + + if ($start === null) { + $start = $length - $end; + $end = $start + $end; + } + elseif ($end === null) { + $end = $length; + } + + http_response_code(206); + header(sprintf('Content-Range: bytes %s-%s/%s', $start, $end - 1, $length)); + $content = substr($content, $start, $end - $start); + $length = $end - $start; + } + + if ($gzip) { + $content = gzencode($content, 9); + $length = strlen($content); + } + + if (!$disable_length) { + header('Content-Length: ' . $length, true); + header('Accept-Ranges: bytes'); + } + + echo $content; + return; + } + + if (isset($path)) { + $resource = fopen($path, 'rb'); + } + + $seek = fseek($resource, 0, SEEK_END); + + if ($seek === 0) { + $length = ftell($resource); + fseek($resource, 0, SEEK_SET); + } + + http_response_code(200); + + if (($start || $end) && $seek === 0) { + if (null !== $end && $end > $length) { + header('Content-Range: bytes */' . $length, true); + throw new \LogicException('End range cannot be satisfied', 416); + } + + if ($start === null) { + $start = $length - $end; + $end = $start + $end; + } + elseif ($end === null) { + $end = $length; + } + + fseek($resource, $start, SEEK_SET); + + http_response_code(206); + header(sprintf('Content-Range: bytes %s-%s/%s', $start, $end - 1, $length), true); + + $length = $end - $start; + $end -= $start; + } + elseif (null === $length && isset($path)) { + $end = $length = filesize($path); + } + + if ($gzip) { + $gzip = deflate_init(ZLIB_ENCODING_GZIP); + + $fp = fopen('php://temp', 'wb'); + + while (!feof($resource)) { + fwrite($fp, deflate_add($gzip, fread($resource, 8192), ZLIB_NO_FLUSH)); + } + + fwrite($fp, deflate_add($gzip, '', ZLIB_FINISH)); + $length = ftell($fp); + rewind($fp); + unset($resource); + + $resource = $fp; + unset($fp); + } + + if (null !== $length && !$disable_length) { + header('Content-Length: ' . $length, true); + header('Accept-Ranges: bytes'); + } + + $block_size = 8192*4; + + while (!feof($resource) && ($end === null || $end > 0)) { + $l = $end !== null ? min($block_size, $end) : $block_size; + + echo fread($resource, $l); + flush(); + + if (null !== $end) { + $end -= $block_size; + } + } + } + } + +} + namespace KD2\WebDAV { class Exception extends \RuntimeException {} + + + use KD2\HTTP\Server as HTTP_Server; + class Server { // List of basic DAV properties that you should return if $requested_properties is NULL @@ -25,8 +257,6 @@ namespace KD2\WebDAV 'DAV::quota-available-bytes', ]; - const PROP_NAMESPACE_MICROSOFT = 'urn:schemas-microsoft-com:'; - const MODIFICATION_TIME_PROPERTIES = [ 'DAV::lastmodified', 'DAV::creationdate', @@ -44,6 +274,8 @@ namespace KD2\WebDAV const SHARED_LOCK = 'shared'; const EXCLUSIVE_LOCK = 'exclusive'; + const DATE_RFC7231 = "D, d M Y H:i:s \\G\\M\\T"; + protected bool $enable_gzip = true; protected string $base_uri; @@ -54,6 +286,24 @@ namespace KD2\WebDAV protected AbstractStorage $storage; + protected array $headers; + + public function __construct() + { + $this->headers = apache_request_headers(); + $this->headers = array_change_key_case($this->headers, \CASE_LOWER); + } + + public function getHeader(string $name): ?string + { + return $this->headers[strtolower($name)] ?? null; + } + + public function setHeader(string $name, string $value): void + { + $this->headers[strtolower($name)] = $value; + } + public function setStorage(AbstractStorage $storage) { $this->storage = $storage; @@ -183,12 +433,16 @@ namespace KD2\WebDAV public function http_put(string $uri): ?string { - if (!empty($_SERVER['HTTP_CONTENT_TYPE']) && !strncmp($_SERVER['HTTP_CONTENT_TYPE'], 'multipart/', 10)) { + $content_type = $this->getHeader('Content-Type'); + + if ($content_type && !strncmp($content_type, 'multipart/', 10)) { throw new Exception('Multipart PUT requests are not supported', 501); } - if (!empty($_SERVER['HTTP_CONTENT_ENCODING'])) { - if (false !== strpos($_SERVER['HTTP_CONTENT_ENCODING'], 'gzip')) { + $content_encoding = $this->getHeader('Content-Encoding'); + + if ($content_encoding) { + if (false !== strpos($content_encoding, 'gzip')) { // Might be supported later? throw new Exception('Content Encoding is not supported', 501); } @@ -197,12 +451,12 @@ namespace KD2\WebDAV } } - if (!empty($_SERVER['HTTP_CONTENT_RANGE'])) { + if ($this->getHeader('Content-Range')) { throw new Exception('Content Range is not supported', 501); } // See SabreDAV CorePlugin for reason why OS/X Finder is buggy - if (isset($_SERVER['HTTP_X_EXPECTED_ENTITY_LENGTH'])) { + if ($this->getHeader('X-Expected-Entity-Length')) { throw new Exception('This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.', 403); } @@ -211,14 +465,14 @@ namespace KD2\WebDAV // Support for checksum matching // https://dcache.org/old/manuals/UserGuide-6.0/webdav.shtml#checksums - if (!empty($_SERVER['HTTP_CONTENT_MD5'])) { - $hash = bin2hex(base64_decode($_SERVER['HTTP_CONTENT_MD5'])); + if ($hash = $this->getHeader('Content-MD5')) { + $hash = bin2hex(base64_decode($hash)); $hash_algo = 'MD5'; } // Support for ownCloud/NextCloud checksum // https://github.com/owncloud-archive/documentation/issues/2964 - elseif (!empty($_SERVER['HTTP_OC_CHECKSUM']) - && preg_match('/MD5:[a-f0-9]{32}|SHA1:[a-f0-9]{40}/', $_SERVER['HTTP_OC_CHECKSUM'], $match)) { + elseif (($checksum = $this->getHeader('OC-Checksum')) + && preg_match('/MD5:[a-f0-9]{32}|SHA1:[a-f0-9]{40}/', $checksum, $match)) { $hash_algo = strtok($match[0], ':'); $hash = strtok(''); } @@ -227,8 +481,8 @@ namespace KD2\WebDAV $this->checkLock($uri); - if (!empty($_SERVER['HTTP_IF_MATCH'])) { - $etag = trim($_SERVER['HTTP_IF_MATCH'], '" '); + if ($match = $this->getHeader('If-Match')) { + $etag = trim($match, '" '); $prop = $this->storage->propfind($uri, ['DAV::getetag'], 0); if (!empty($prop['DAV::getetag']) && $prop['DAV::getetag'] != $etag) { @@ -236,9 +490,19 @@ namespace KD2\WebDAV } } + if ($date = $this->getHeader('If-Unmodified-Since')) { + $date = \DateTime::createFromFormat(self::DATE_RFC7231, $date); + $prop = $this->storage->propfind($uri, ['DAV::getlastmodified'], 0); + if ($date && $prop && $prop instanceof \DateTimeInterface) { + if ($date != $prop) { + throw new Exception('File was modified since "If-Unmodified-Since" condition', 412); + } + } + } + // Specific to NextCloud/ownCloud, to allow setting file mtime // This expects a UNIX timestamp - $mtime = (int)($_SERVER['HTTP_X_OC_MTIME'] ?? 0) ?: null; + $mtime = intval($this->getHeader('X-OC-MTime')) ?: null; $this->extendExecutionTime(); @@ -246,7 +510,7 @@ namespace KD2\WebDAV // mod_fcgid <= 2.3.9 doesn't handle chunked transfer encoding for PUT requests // see https://github.com/kd2org/picodav/issues/6 - if (strstr($_SERVER['HTTP_TRANSFER_ENCODING'] ?? '', 'chunked') && PHP_SAPI == 'fpm-fcgi') { + if (strstr($this->getHeader('Transfer-Encoding') ?? '', 'chunked') && PHP_SAPI == 'fpm-fcgi') { // We can't seek here // see https://github.com/php/php-src/issues/9441 $l = strlen(fread($stream, 1)); @@ -307,7 +571,7 @@ namespace KD2\WebDAV if (isset($props['DAV::getlastmodified']) && $props['DAV::getlastmodified'] instanceof \DateTimeInterface) { - header(sprintf('Last-Modified: %s', $props['DAV::getlastmodified']->format(\DATE_RFC7231))); + header(sprintf('Last-Modified: %s', $props['DAV::getlastmodified']->format(self::DATE_RFC7231))); } if (!empty($props['DAV::getetag'])) { @@ -346,7 +610,6 @@ namespace KD2\WebDAV $uri = $this->_prefix($uri); $is_collection = !empty($props['DAV::resourcetype']) && $props['DAV::resourcetype'] == 'collection'; - $out = ''; if ($is_collection) { $list = $this->storage->list($uri, self::BASIC_PROPERTIES); @@ -377,163 +640,29 @@ namespace KD2\WebDAV return null; } - if (!isset($file['content']) && !isset($file['resource']) && !isset($file['path'])) { - throw new \RuntimeException('Invalid file array returned by ::get(): ' . print_r($file, true)); + try { + HTTP_Server::serveFile( + $file['content'] ?? null, + $file['path'] ?? null, + $file['resource'] ?? null, + [ + 'gzip' => $this->enable_gzip, + 'ranges' => true, + 'xsendfile' => false, + 'name' => $uri, + 'size' => $props['DAV::getcontentlength'], + ] + ); } - - $this->extendExecutionTime(); - - $length = $start = $end = null; - $gzip = false; - - if (isset($_SERVER['HTTP_RANGE']) - && preg_match('/^bytes=(\d*)-(\d*)$/i', $_SERVER['HTTP_RANGE'], $match) - && $match[1] . $match[2] !== '') { - $start = $match[1] === '' ? null : (int) $match[1]; - $end = $match[2] === '' ? null : (int) $match[2]; - - if (null !== $start && $start < 0) { - throw new Exception('Start range cannot be satisfied', 416); - } - - if (isset($props['DAV::getcontentlength']) && $start > $props['DAV::getcontentlength']) { - throw new Exception('End range cannot be satisfied', 416); - } - - $this->log('HTTP Range requested: %s-%s', $start, $end); + catch (\LogicException $e) { + throw new Exception($e->getMessage(), $e->getCode()); } - elseif ($this->enable_gzip - && isset($_SERVER['HTTP_ACCEPT_ENCODING']) - && false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') - && isset($props['DAV::getcontentlength']) - // Don't compress if size is larger than 8 MiB - && $props['DAV::getcontentlength'] < 8*1024*1024 - // Don't compress already compressed content - && !preg_match('/\.(?:cbz|cbr|cb7|mp4|m4a|zip|docx|xlsx|pptx|ods|odt|odp|7z|gz|bz2|lzma|lz|xz|apk|dmg|jar|rar|webm|ogg|mp3|ogm|flac|ogv|mkv|avi)$/i', $uri)) { - $gzip = true; - header('Content-Encoding: gzip', true); - } - - // Try to avoid common issues with output buffering and stuff - if (function_exists('apache_setenv')) { - @apache_setenv('no-gzip', 1); - } - - @ini_set('zlib.output_compression', 'Off'); - - if (@ob_get_length()) { - @ob_clean(); - } - - if (isset($file['content'])) { - $length = strlen($file['content']); - - if ($start || $end) { - if (null !== $end && $end > $length) { - header('Content-Range: bytes */' . $length, true); - throw new Exception('End range cannot be satisfied', 416); - } - - if ($start === null) { - $start = $length - $end; - $end = $start + $end; - } - elseif ($end === null) { - $end = $length; - } - - http_response_code(206); - header(sprintf('Content-Range: bytes %s-%s/%s', $start, $end - 1, $length)); - $file['content'] = substr($file['content'], $start, $end - $start); - $length = $end - $start; - } - - if ($gzip) { - $file['content'] = gzencode($file['content'], 9); - $length = strlen($file['content']); - } - - header('Content-Length: ' . $length, true); - echo $file['content']; - return null; - } - - if (isset($file['path'])) { - $file['resource'] = fopen($file['path'], 'rb'); - } - - $seek = fseek($file['resource'], 0, SEEK_END); - - if ($seek === 0) { - $length = ftell($file['resource']); - fseek($file['resource'], 0, SEEK_SET); - } - - if (($start || $end) && $seek === 0) { - if (null !== $end && $end > $length) { - header('Content-Range: bytes */' . $length, true); - throw new Exception('End range cannot be satisfied', 416); - } - - if ($start === null) { - $start = $length - $end; - $end = $start + $end; - } - elseif ($end === null) { - $end = $length; - } - - fseek($file['resource'], $start, SEEK_SET); - - http_response_code(206); - header(sprintf('Content-Range: bytes %s-%s/%s', $start, $end - 1, $length), true); - - $length = $end - $start; - $end -= $start; - } - elseif (null === $length && isset($file['path'])) { - $end = $length = filesize($file['path']); - } - - if ($gzip) { - $this->log('Using gzip output compression'); - $gzip = deflate_init(ZLIB_ENCODING_GZIP); - - $fp = fopen('php://temp', 'wb'); - - while (!feof($file['resource'])) { - fwrite($fp, deflate_add($gzip, fread($file['resource'], 8192), ZLIB_NO_FLUSH)); - } - - fwrite($fp, deflate_add($gzip, '', ZLIB_FINISH)); - $length = ftell($fp); - rewind($fp); - fclose($file['resource']); - - $file['resource'] = $fp; - unset($fp); - } - - if (null !== $length) { - $this->log('Length: %s', $length); - header('Content-Length: ' . $length, true); - } - - $block_size = 8192*4; - - while (!feof($file['resource']) && ($end === null || $end > 0)) { - $l = $end !== null ? min($block_size, $end) : $block_size; - - echo fread($file['resource'], $l); - flush(); - - if (null !== $end) { - $end -= $block_size; + finally { + if (isset($file['resource'])) { + fclose($file['resource']); } } - fclose($file['resource']); - return null; } @@ -687,7 +816,7 @@ namespace KD2\WebDAV throw new Exception('Invalid XML', 400); } - $this->log('Requested depth: %s', $depth); + $this->log('<= Requested depth: %s', $depth); // We don't really care about having a correct XML string, // but we can get better WebDAV compliance if we do @@ -727,7 +856,7 @@ namespace KD2\WebDAV $properties = $properties ?? $this->storage->propfind($path, $requested_keys, 0); if (!$properties) { - $this->log('!!! Cannot find "%s"', $path); + $this->log('!! Cannot find "%s"', $path); continue; } @@ -807,8 +936,8 @@ namespace KD2\WebDAV } $pos = strrpos($name, ':'); - $ns = substr($name, 0, strrpos($name, ':')); - $tag_name = substr($name, strrpos($name, ':') + 1); + $ns = substr($name, 0, $pos); + $tag_name = substr($name, $pos + 1); $alias = $root_namespaces[$ns] ?? null; $attributes = ''; @@ -837,7 +966,7 @@ namespace KD2\WebDAV // Change value to GMT $value = clone $value; $value->setTimezone(new \DateTimeZone('GMT')); - $value = $value->format(DATE_RFC7231); + $value = $value->format(self::DATE_RFC7231); } elseif (is_array($value)) { $attributes = $value['attributes'] ?? ''; @@ -874,8 +1003,8 @@ namespace KD2\WebDAV foreach ($missing_properties as $name) { $pos = strrpos($name, ':'); - $ns = substr($name, 0, strrpos($name, ':')); - $name = substr($name, strrpos($name, ':') + 1); + $ns = substr($name, 0, $pos); + $name = substr($name, $pos + 1); $alias = $root_namespaces[$ns] ?? null; // NULL namespace, see Litmus FAQ for propnullns @@ -909,7 +1038,7 @@ namespace KD2\WebDAV $xml = @simplexml_load_string($body); if (false === $xml) { - throw new WebDAV_Exception('Invalid XML', 400); + throw new Exception('Invalid XML', 400); } $_ns = null; @@ -1003,11 +1132,11 @@ namespace KD2\WebDAV $ts = '@' . $ts; } - $set_time = new \DateTime($value['content']); + $set_time = new \DateTime($ts); $set_time_name = $name; } - $prefix .= sprintf(">\n\n %s\n", htmlspecialchars($url, ENT_XML1)); + $prefix .= sprintf(">\n\n %s\n", htmlspecialchars($uri, ENT_XML1)); // http_response_code doesn't know the 207 status code header('HTTP/1.1 207 Multi-Status', true); @@ -1023,7 +1152,7 @@ namespace KD2\WebDAV $return = $this->storage->proppatch($uri, $properties); - if ($set_time && $this->touch($uri, $set_time)) { + if ($set_time && $this->storage->touch($uri, $set_time)) { $return[$set_time_name] = 200; } @@ -1072,7 +1201,6 @@ namespace KD2\WebDAV throw new Exception('Invalid If header', 400); } - $info = null; $ns = 'D'; $scope = self::EXCLUSIVE_LOCK; @@ -1086,7 +1214,7 @@ namespace KD2\WebDAV throw new Exception('Cannot acquire another lock, resource is locked for exclusive use', 423); } - if ($locked_scope && $token = $this->getLockToken()) { + if ($locked_scope) { $token = $this->getLockToken(); if (!$token) { @@ -1254,6 +1382,19 @@ namespace KD2\WebDAV } } + public function validateURI(string $uri): string + { + $uri = preg_replace('!/{2,}!', '/', $uri); + $uri = str_replace('\\', '/', $uri); + + // Protect against path traversal + if (preg_match('!(?:^|/)\.\.(?:$|/)!', $uri)) { + throw new Exception(sprintf('Invalid URI: "%s"', $uri), 403); + } + + return $uri; + } + protected function getURI(string $source): string { $uri = parse_url($source, PHP_URL_PATH); @@ -1269,11 +1410,7 @@ namespace KD2\WebDAV throw new Exception(sprintf('Invalid URI, "%s" is outside of scope "%s"', $uri, $this->base_uri), 400); } - $uri = preg_replace('!/{2,}!', '/', $uri); - - if (false !== strpos($uri, '..')) { - throw new Exception(sprintf('Invalid URI: "%s"', $uri), 403); - } + $uri = $this->validateURI($uri); $uri = substr($uri, strlen($this->base_uri)); $uri = $this->_prefix($uri); @@ -1297,7 +1434,7 @@ namespace KD2\WebDAV $uri = substr($uri, strlen($this->base_uri)); } else { - $this->log('<= %s is not a managed URL (%s)', $uri, $this->base_uri); + $this->log('=> %s is not a managed URL (%s)', $uri, $this->base_uri); return false; } @@ -1327,9 +1464,7 @@ namespace KD2\WebDAV $this->log('<= %s /%s', $method, $uri); try { - if (false !== strpos($uri, '..')) { - throw new Exception(sprintf('Invalid URI: "%s"', $uri), 403); - } + $uri = $this->validateURI($uri); // Call 'http_method' class method $method = 'http_' . strtolower($method); @@ -1386,8 +1521,13 @@ namespace KD2\WebDAV abstract class AbstractStorage { + const PUT_IGNORE_PATTERN = '!^~|~$|^~.*tmp$|^Thumbs\.db$|^desktop\.ini$|\.unison$|^My Saved Places' + . '|^\.(lock\.|_|DS_Store|DocumentRevisions|directory|Trash|Temp|fseventsd|apdisk|synkron|sync|symform|fuse|nfs)!i'; + abstract public function get(string $uri): ?array; + abstract public function fetch(string $uri): ?string; + abstract public function exists(string $uri): bool; abstract public function propfind(string $uri, ?array $requested_properties, int $depth): ?array; @@ -1395,6 +1535,13 @@ namespace KD2\WebDAV public function proppatch(string $uri, array $properties): array { // By default, properties are not saved + $out = []; + + foreach ($properties as $key => $value) { + $out[$key] = 200; + } + + return $out; } abstract public function put(string $uri, $pointer, ?string $hash_algo, ?string $hash): bool; @@ -1409,7 +1556,7 @@ namespace KD2\WebDAV abstract public function list(string $uri, array $properties): iterable; - abstract public function touch(string $uri, \DateTimeInterface $timestamp): bool; + abstract public function touch(string $uri, \DateTimeInterface $datetime): bool; public function lock(string $uri, string $token, string $scope): void { @@ -1482,10 +1629,28 @@ namespace PicoDAV return true; } - static protected function glob(string $path, string $pattern = '', int $flags = 0): array + static protected function glob(string $path, int $flags = 0): array { - $path = preg_replace('/[\*\?\[\]]/', '\\\\$0', $path); - return glob($path . $pattern, $flags); + if (file_exists($path) && !is_dir($path)) { + return [$path]; + } + + $dir = dir($path); + $out = []; + + while ($file = $dir->read()) { + if ($file === '.' || $file === '..') { + continue; + } + + if (($flags & GLOB_ONLYDIR) && !is_dir($path . '/' . $file)) { + continue; + } + + $out[] = $path . '/' . $file; + } + + return $out; } public function canRead(string $uri): bool @@ -1577,12 +1742,12 @@ namespace PicoDAV //throw new WebDAV_Exception('Access forbidden', 403); } - $dirs = self::glob($this->path . $uri, '/*', \GLOB_ONLYDIR); + $dirs = self::glob($this->path . $uri, \GLOB_ONLYDIR); $dirs = array_map('basename', $dirs); $dirs = array_filter($dirs, fn($a) => $this->canRead(ltrim($uri . '/' . $a, '/') . '/')); natcasesort($dirs); - $files = self::glob($this->path . $uri, '/*'); + $files = self::glob($this->path . $uri); $files = array_map('basename', $files); $files = array_diff($files, $dirs); @@ -1612,6 +1777,23 @@ namespace PicoDAV return ['path' => $path]; } + public function fetch(string $uri): ?string + { + $r = $this->get($uri); + + if (!$r) { + return null; + } + + $r = file_get_contents($r['path']); + + if ($r === false) { + return null; + } + + return $r; + } + public function exists(string $uri): bool { return file_exists($this->path . $uri); @@ -1634,7 +1816,7 @@ namespace PicoDAV return is_dir($target) ? 'collection' : ''; case 'DAV::getlastmodified': $mtime = filemtime($target); - + if (!$mtime) { return null; } @@ -1790,7 +1972,7 @@ namespace PicoDAV } if (is_dir($target)) { - foreach (self::glob($target, '/*') as $file) { + foreach (self::glob($target) as $file) { $this->delete(substr($file, strlen($this->path))); } @@ -1851,8 +2033,6 @@ namespace PicoDAV } else { $method($source, $target); - - $this->getResourceProperties($uri)->move($destination); } return $overwritten; @@ -1980,6 +2160,14 @@ namespace { if (!empty($_SERVER['SERVER_SOFTWARE']) && stristr($_SERVER['SERVER_SOFTWARE'], 'apache') && !file_exists($self_dir . '/.htaccess')) { file_put_contents($self_dir . '/.htaccess', str_replace('index.php', basename($self), 'DirectoryIndex disabled + + = 2.4.13> + # Force Apache to pass the Authorization header to PHP + # See https://github.com/kd2org/picodav/issues/14 + CGIPassAuth On + + + RedirectMatch 404 \\.picodav\\.ini RewriteEngine On @@ -2014,12 +2202,12 @@ RewriteRule ^.*$ /index.php [END] $fp = fopen(__FILE__, 'r'); if ($relative_uri == '.webdav/webdav.js') { - fseek($fp, 55024, SEEK_SET); - echo fread($fp, 27891); + fseek($fp, 58874, SEEK_SET); + echo fread($fp, 31826); } else { - fseek($fp, 55024 + 27891, SEEK_SET); - echo fread($fp, 7004); + fseek($fp, 58874 + 31826, SEEK_SET); + echo fread($fp, 7988); } fclose($fp); @@ -2143,16 +2331,33 @@ const WebDAVNavigator = (url, options) => { + %table%
`; const create_buttons = ` - - - `; + + + `; - const dir_row_tpl = `%icon%%name%%modified%
`; - const file_row_tpl = `%icon%%name%%size_bytes%%modified%
${_('Download')}
`; + const dir_row_tpl = ` + %icon% + %name% + %modified% +
+ `; + + const file_row_tpl = ` + %thumb% + %name% + %size_bytes% + %modified% +
${_('Download')}
+ `; + + const icon_tpl = `%icon%`; + const root_url = url.replace(/(?`; const propfind_tpl = '<'+ `?xml version="1.0" encoding="UTF-8"?> @@ -2164,7 +2369,7 @@ const WebDAVNavigator = (url, options) => { const wopi_propfind_tpl = '<' + `?xml version="1.0" encoding="UTF-8"?> - + `; @@ -2181,21 +2386,27 @@ const WebDAVNavigator = (url, options) => { }).then(str => new window.DOMParser().parseFromString(str, "text/xml")); }; + const reqHandler = (r, c) => { + if (!r.ok) { + return r.text().then(t => { + var message; + if (a = t.match(/<((?:\w+:)?message)>(.*)<\/\1>/)) { + message = "\n" + a[2]; + } + + throw new Error(r.status + ' ' + r.statusText + message); + }); + } + window.setTimeout(c, 200); + return r; + }; + const reqAndReload = (method, url, body, headers) => { animateLoading(); - req(method, url, body, headers).then(r => { + req(method, url, body, headers).then(r => reqHandler(r, () => { stopLoading(); - if (!r.ok) { - return r.text().then(t => { - var message; - if (a = t.match(/<((?:\w+:)?message)>(.*)<\/\1>/)) { - message = "\n" + a[2]; - } - - throw new Error(r.status + ' ' + r.statusText + message); }); - } reloadListing(); - }).catch(e => { + })).catch(e => { console.error(e); alert(e); }); @@ -2242,6 +2453,32 @@ const WebDAVNavigator = (url, options) => { return p; }; + const uploadFiles = (files) => { + animateLoading(); + + (async () => { + for (var i = 0; i < files.length; i++) { + var f = files[i]; + await reqOrError('PUT', current_url + encodeURIComponent(f.name), f); + } + + window.setTimeout(() => { + stopLoading(); + reloadListing(); + }, 500); + })(); + }; + + const reqOrError = (method, url, body) => { + return req(method, url, body).then(reqHandler).catch(e => { + console.error(e); + alert(e); + stopLoading(); + reloadListing(); + throw e; + }); + } + const get_url = async (url) => { var progress = (e) => { var p = $('progress'); @@ -2322,7 +2559,7 @@ const WebDAVNavigator = (url, options) => { const wopi_open = async (document_url, wopi_url) => { var properties = await reqXML('PROPFIND', document_url, wopi_propfind_tpl, {'Depth': '0'}); - var src = (a = properties.querySelector('file-url')) ? a.textContent : null; + var src = (a = properties.querySelector('wopi-url')) ? a.textContent : null; var token = (a = properties.querySelector('token')) ? a.textContent : null; var token_ttl = (a = properties.querySelector('token-ttl')) ? a.textContent : +(new Date(Date.now() + 3600 * 1000)); @@ -2543,7 +2780,8 @@ const WebDAVNavigator = (url, options) => { var root_permissions = null; xml.querySelectorAll('response').forEach((node) => { - var item_uri = normalizeURL(node.querySelector('href').textContent); + var path = node.querySelector('href').textContent; + var item_uri = normalizeURL(path); var props = null; node.querySelectorAll('propstat').forEach((propstat) => { @@ -2574,6 +2812,7 @@ const WebDAVNavigator = (url, options) => { items[index].push({ 'uri': item_uri, + 'path': item_uri.substring(base_url.length), 'name': name, 'size': !is_dir && (prop = node.querySelector('getcontentlength')) ? parseInt(prop.textContent, 10) : null, 'mime': !is_dir && (prop = node.querySelector('getcontenttype')) ? prop.textContent : null, @@ -2630,6 +2869,14 @@ const WebDAVNavigator = (url, options) => { item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : ''); item.modified = item.modified !== null ? formatDate(item.modified) : null; item.name = html(item.name); + + if (item.mime && item.mime.match(/^image\//) && options.nc_thumbnails) { + item.thumb = template(image_thumb_tpl, item); + } + else { + item.thumb = template(icon_tpl, item); + } + table += template(row, item); }); @@ -2651,6 +2898,31 @@ const WebDAVNavigator = (url, options) => { $('.download_all').onclick = download_all; } + $('.deleted_selected').onclick = () => { + var l = document.querySelectorAll('input[name=delete]:checked'); + + if (!l.length) { + alert(_('No file is selected')); + return; + } + + openDialog(delete_dialog); + document.forms[0].onsubmit = () => { + animateLoading(); + + for (var i = 0; i < l.length; i++) { + reqOrError('DELETE', l[i].value); + } + + // Don't reload too fast + window.setTimeout(() => { + stopLoading(); + reloadListing(); + }, 500); + }; + + }; + if (!root_permissions || root_permissions.indexOf('C') != -1 || root_permissions.indexOf('K') != -1) { $('.upload').insertAdjacentHTML('afterbegin', create_buttons); @@ -2692,12 +2964,7 @@ const WebDAVNavigator = (url, options) => { fi.onchange = () => { if (!fi.files.length) return; - var body = new Blob(fi.files); - var name = fi.files[0].name; - - name = encodeURIComponent(name); - - return reqAndReload('PUT', current_url + name, body); + uploadFiles(fi.files); }; } @@ -2787,7 +3054,7 @@ const WebDAVNavigator = (url, options) => { openDialog('
', false); $('dialog').className = 'preview'; req('GET', file_url).then(r => r.text()).then(t => { - $('.md_preview').innerHTML = microdown.parse(html(t)); + $('.md_preview').innerHTML = microdown.parse(t); }); return false; } @@ -2820,16 +3087,97 @@ const WebDAVNavigator = (url, options) => { $$('.edit').onclick = (e) => { req('GET', file_url).then((r) => r.text().then((t) => { let md = file_url.match(/\.md$/); - openDialog(md ? markdown_dialog : edit_dialog); + var tpl = dialog_tpl.replace(/%b/, ''); + $('body').classList.add('dialog'); + $('body').insertAdjacentHTML('beforeend', tpl.replace(/%s/, md ? markdown_dialog : edit_dialog)); + + var tb = $('.close'); + tb.className = 'toolbar'; + tb.innerHTML = ` + + + `; + var txt = $('textarea[name=edit]'); txt.value = t; + var saved_status = $('.toolbar .status'); + var close_btn = $('.toolbar .close'); + var save_btn = $('.toolbar .save'); + var autosave = $('.toolbar .autosave'); + + var c = localStorage.getItem('autosave') ?? options.autosave; + autosave.checked = c == 1 || c === true; + autosave.onchange = () => { + localStorage.setItem('autosave', autosave.checked ? 1 : 0); + }; + + var preventClose = (e) => { + if (txt.value == t) { + return; + } + + e.preventDefault(); + e.returnValue = ''; + return true; + }; + + var close = () => { + if (txt.value !== t) { + if (!confirm(_('Your changes have not been saved. Do you want to cancel WITHOUT saving?'))) { + return; + } + } + + window.removeEventListener('beforeunload', preventClose, {capture: true}); + closeDialog(); + }; + + var save = () => { + reqOrError('PUT', file_url, txt.value); + t = txt.value; + updateSaveStatus(); + }; + + var updateSaveStatus = () => { + saved_status.innerHTML = txt.value !== t ? '⚠️ ' + _('Modified') : '✔️ ' + _('Saved'); + }; + + save_btn.onclick = () => { save(); close(); }; + close_btn.onclick = close; + + // Prevent close of tab if content has changed and is not saved + window.addEventListener('beforeunload', preventClose, { capture: true }); + + txt.onkeydown = (e) => { + if (e.ctrlKey && e.key == 's') { + save(); + e.preventDefault(); + return false; + } + else if (e.key === 'Escape') { + close(); + e.preventDefault(); + return false; + } + }; + + txt.onkeyup = (e) => { + updateSaveStatus(); + }; + + window.setInterval(() => { + if (autosave.checked && t != txt.value) { + save(); + } + }, 10000); + // Markdown editor if (md) { let pre = $('#md'); txt.oninput = () => { - pre.innerHTML = microdown.parse(html(txt.value)); + pre.innerHTML = microdown.parse(txt.value); }; txt.oninput(); @@ -2883,6 +3231,7 @@ const WebDAVNavigator = (url, options) => { var wopi_mimes = {}, wopi_extensions = {}; const wopi_discovery_url = options.wopi_discovery_url || null; + options.autosave = options.autosave || false; document.querySelector('html').innerHTML = html_tpl; @@ -2961,25 +3310,14 @@ const WebDAVNavigator = (url, options) => { if (!files.length) return; - animateLoading(); - - (async () => { - for (var i = 0; i < files.length; i++) { - var f = files[i] - await req('PUT', current_url + encodeURIComponent(f.name), f); - } - - window.setTimeout(() => { - stopLoading(); - reloadListing(); - }, 500); - })(); + uploadFiles(files); }); }; if (url = document.querySelector('html').getAttribute('data-webdav-url')) { WebDAVNavigator(url, { 'wopi_discovery_url': document.querySelector('html').getAttribute('data-wopi-discovery-url'), + 'nc_thumbnails': document.querySelector('html').getAttribute('data-nc-thumbnails') ? true : false }); } :root { @@ -3030,9 +3368,48 @@ th { } td.thumb { - width: 5%; + width: 3.6em; + padding: 0; + text-align: center; + position: relative; } +td.thumb img { + width: 3.6em; + height: 3.6em; + display: block; +} + +td.thumb .icon { + margin: .5em; +} + +td.thumb input, td.thumb label span::before { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: 0; + padding: 0; + opacity: 0; + display: block; + cursor: pointer; + user-select: none; +} + +td.thumb input:checked + span::before { + content: "✔️"; + font-size: 3em; + opacity: 1; + color: #fff; + text-shadow: 0px 0px 5px #000, 0px 0px 10px #000; + display: flex; + align-items: center; + justify-content: center; +} + + td.buttons { text-align: right; width: 20em; @@ -3050,7 +3427,7 @@ table tr:nth-child(even) { .icon { width: 2.6em; height: 2.6em; - display: block; + display: inline-block; border-radius: .2em; background:var(--g3-color); overflow: hidden; @@ -3099,7 +3476,7 @@ table tr:nth-child(even) { text-align: right; } -input[type=button], input[type=submit], .btn { +input[type=button], input[type=submit], .btn, a.btn { font-size: 1.2em; padding: .3em .5em; margin: .2em .3em; @@ -3108,7 +3485,7 @@ input[type=button], input[type=submit], .btn { border-radius: .2em; cursor: pointer; text-decoration: none; - color: var(--fg-color) !important; + color: var(--fg-color); font-family: inherit; } @@ -3143,10 +3520,34 @@ input[type=button]:hover, input[type=submit]:hover, .btn:hover { margin: 0; } -.close input { +input.close { font-size: .8em; } +.toolbar { + margin: 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.toolbar label { + padding: .5em; + background: var(--bg-color); + cursor: pointer; + border-radius: .5em; +} + +.toolbar .save { + background: DarkSlateBlue; + color: #fff; +} + +.toolbar .status { + width: 10em; + display: block; +} + input[type=submit] { float: right; } @@ -3196,6 +3597,10 @@ dialog form div { height: 82vh; } +#md img, #md video, #md iframe, #md embed, #md object { + max-width: 100%; +} + dialog.preview { height: calc(100%); width: calc(100%); diff --git a/make.php b/make.php index 8bb818f..07a6295 100644 --- a/make.php +++ b/make.php @@ -15,6 +15,8 @@ function clean_php_source(string $file): string $php = file_get_contents('server.php'); $php = strtr($php, [ + '//__KD2\HTTP\Server__' => clean_php_source('lib/KD2/HTTP/Server.php'), + '//__KD2\WebDAV\Exception__' => clean_php_source('lib/KD2/WebDAV/Exception.php'), '//__KD2\WebDAV\Server__' => clean_php_source('lib/KD2/WebDAV/Server.php'), '//__KD2\WebDAV\AbstractStorage__' => clean_php_source('lib/KD2/WebDAV/AbstractStorage.php'), '/*__HTACCESS__*/' => var_export(file_get_contents('.htaccess'), true), diff --git a/server.php b/server.php index 44e149c..d821788 100644 --- a/server.php +++ b/server.php @@ -1,7 +1,14 @@ $path]; } + public function fetch(string $uri): ?string + { + $r = $this->get($uri); + + if (!$r) { + return null; + } + + $r = file_get_contents($r['path']); + + if ($r === false) { + return null; + } + + return $r; + } + public function exists(string $uri): bool { return file_exists($this->path . $uri); @@ -446,8 +470,6 @@ namespace PicoDAV } else { $method($source, $target); - - $this->getResourceProperties($uri)->move($destination); } return $overwritten; From ef9f5f3235e293cb5945839195a8baf77bce4d1b Mon Sep 17 00:00:00 2001 From: bohwaz Date: Thu, 11 Dec 2025 02:33:57 +0000 Subject: [PATCH 2/5] Update webdav manager UI FossilOrigin-Name: 065c89bc3641a6c5fdb464fa5a68322c6ebac4ece4258389d12b46bfffd19f33 --- index.php | 642 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 434 insertions(+), 208 deletions(-) diff --git a/index.php b/index.php index cf9b139..7f8da1c 100644 --- a/index.php +++ b/index.php @@ -942,11 +942,12 @@ namespace KD2\WebDAV $alias = $root_namespaces[$ns] ?? null; $attributes = ''; - // The ownCloud Android app doesn't like formatted dates, it makes it crash. + // The ownCloud/OpenCloud Android app doesn't like formatted dates, it makes it crash. // so force it to have a timestamp + // see https://github.com/opencloud-eu/android/issues/74 if ($name == 'DAV::creationdate' && ($value instanceof \DateTimeInterface) - && false !== stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'owncloud')) { + && false !== preg_match('/owncloud|opencloud/', $_SERVER['HTTP_USER_AGENT'] ?? '')) { $value = $value->getTimestamp(); } // ownCloud app crashes if mimetype is provided for a directory @@ -2202,12 +2203,12 @@ RewriteRule ^.*$ /index.php [END] $fp = fopen(__FILE__, 'r'); if ($relative_uri == '.webdav/webdav.js') { - fseek($fp, 58874, SEEK_SET); - echo fread($fp, 31826); + fseek($fp, 58962, SEEK_SET); + echo fread($fp, 33638); } else { - fseek($fp, 58874 + 31826, SEEK_SET); - echo fread($fp, 7988); + fseek($fp, 58962 + 33638, SEEK_SET); + echo fread($fp, 16729); } fclose($fp); @@ -2298,61 +2299,69 @@ const WebDAVNavigator = (url, options) => { const microdown=function(){function l(n,e,r){return"<"+n+(r?" "+Object.keys(r).map(function(n){return r[n]?n+'="'+(a(r[n])||"")+'"':""}).join(" "):"")+">"+e+""}function c(n,e){return e=n.match(/^[+-]/m)?"ul":"ol",n?"<"+e+">"+n.replace(/(?:[+-]|\d+\.) +(.*)\n?(([ \t].*\n?)*)/g,function(n,e,r){return"
  • "+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"
  • ";var t})+"":""}function e(r,t,u,c){return function(n,e){return n=n.replace(t,u),l(r,c?c(n):n)}}function t(n,u){return f(n,[//g,"\x3c!--$1--\x3e",/^("""|```)(.*)\n((.*\n)*?)\1/gm,function(n,e,r,t){return'"""'===e?l("div",p(t,u),{class:r}):u&&u.preCode?l("pre",l("code",a(t),{class:r})):l("pre",a(t),{class:r})},/(^>.*\n?)+/gm,e("blockquote",/^> ?(.*)$/gm,"$1",r),/((^|\n)\|.+)+/g,e("table",/^.*(\n\|---.*?)?$/gm,function(n,t){return e("tr",/\|(-?)([^|]*)\1(\|$)?/gm,function(n,e,r){return l(e||t?"th":"td",g(r))})(n.slice(0,n.length-(t||"").length))}),o,c,/#\[([^\]]+?)]/g,'',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"
    "],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").replace(//g,">"):""},o=/(?:(^|\n)([+-]|\d+\.) +(.*(\n[ \t]+.*)*))+/g,g=function c(n,i){var o=[];return n=(n||"").trim().replace(/`([^`]*)`/g,function(n,e){return"\\"+o.push(l("code",a(e)))}).replace(/[!&]?\[([!&]?\[.*?\)|[^\]]*?)]\((.*?)( .*?)?\)|(\w+:\/\/[$\-.+!*'()/,\w]+)/g,function(n,e,r,t,u){return u?i?n:"\\"+o.push(l("a",u,{href:u})):"&"==n[0]?(e=e.match(/^(.+),(.+),([^ \]]+)( ?.+?)?$/),"\\"+o.push(l("iframe","",{width:e[1],height:e[2],frameborder:e[3],class:e[4],src:r,title:t}))):"\\"+o.push("!"==n[0]?l("img","",{src:r,alt:e,title:t}):l("a",c(e,1),{href:r,title:t}))}),n=function r(n){return n.replace(/\\(\d+)/g,function(n,e){return r(o[Number.parseInt(e)-1])})}(i?n:r(n))},r=function t(n){return f(n,[/([*_]{1,3})((.|\n)+?)\1/g,function(n,e,r){return e=e.length,r=t(r),1"],t)},f=function(n,e,r,t){for(var u,c=0;c typeof lang_strings != 'undefined' && key in lang_strings ? lang_strings[key] : key; - const rename_button = ``; - const delete_button = ``; + const rename_button = ``; + const delete_button = ``; - const edit_button = ``; + const edit_button = ``; const mkdir_dialog = ``; const mkfile_dialog = ``; const rename_dialog = ``; const paste_upload_dialog = `

    Upload this file?

    `; const edit_dialog = ``; - const markdown_dialog = `
    `; + const markdown_dialog = `
    `; const delete_dialog = `

    ${_('Confirm delete?')}

    `; - const wopi_dialog = ``; const dialog_tpl = `

    %s
    %b
    `; const html_tpl = ` - Files +
    `; - const body_tpl = `

    %title%

    -
    - - - + const body_tpl = ` +
    +
    + + +
    - %table%
    `; + + + + + + + + + + + %table% +
    `; - const create_buttons = ` + const create_buttons = ` - - `; + + `; - const dir_row_tpl = ` - %icon% - %name% - %modified% + const dir_row_tpl = ` + + %thumb% %name% + %modified%
    `; - const file_row_tpl = ` - %thumb% - %name% + const file_row_tpl = ` + + %thumb% %name% %size_bytes% - %modified% - + %modified% + `; const icon_tpl = `%icon%`; @@ -2646,14 +2655,18 @@ const WebDAVNavigator = (url, options) => { window.onbeforeunload = null; }; - const download_all = async () => { + const download_selected = async () => { + var items = document.querySelectorAll('tbody input[type=checkbox]:checked'); for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (item.is_dir) { - continue; + var input = items[i]; + var row = input.parentNode.parentNode; + + // Skip directories + if (!row.dataset.mime) { + return; } - await download(item.name, item.size, item.uri) + await download(row.dataset.name, row.dataset.size, row.querySelector('th a').href); } }; @@ -2713,7 +2726,7 @@ const WebDAVNavigator = (url, options) => { return _('Yesterday, %s').replace(/%s/, date.toLocaleTimeString()); } - return date.toLocaleString(); + return date.toLocaleString([], {year: 'numeric', month: 'numeric', day: 'numeric'}); }; const openListing = (uri, push) => { @@ -2775,7 +2788,7 @@ const WebDAVNavigator = (url, options) => { const buildListing = (uri, xml) => { uri = normalizeURL(uri); - items = [[], []]; + items = []; var title = null; var root_permissions = null; @@ -2808,9 +2821,9 @@ const WebDAVNavigator = (url, options) => { } var is_dir = node.querySelector('resourcetype collection') ? true : false; - var index = sort_order == 'name' && is_dir ? 0 : 1; + var index = is_dir ? 0 : 1; - items[index].push({ + items.push({ 'uri': item_uri, 'path': item_uri.substring(base_url.length), 'name': name, @@ -2822,39 +2835,35 @@ const WebDAVNavigator = (url, options) => { }); }); - if (sort_order == 'name') { - items[0].sort((a, b) => a.name.localeCompare(b.name)); - } - - items[1].sort((a, b) => { - if (sort_order == 'date') { - return b.modified - a.modified; + items.sort((a, b) => { + if (sort_order === 'date') { + return a.modified - b.modified; } - else if (sort_order == 'size') { - return b.size - a.size; + else if (sort_order === 'size') { + return a.size - b.size; } else { return a.name.localeCompare(b.name); } }); - if (sort_order == 'name') { + if (sort_order !== 'date') { // Sort with directories first - items = items[0].concat(items[1]); - } - else { - items = items[1]; + items.sort((a, b) => b.is_dir - a.is_dir); } + if (sort_order_desc) { + items = items.reverse(); + } var table = ''; var parent = uri.replace(/\/+$/, '').split('/').slice(0, -1).join('/') + '/'; if (parent.length >= base_url.length) { - table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'icon': '↲'}); + table += template(dir_row_tpl, {'name': _('Back'), 'uri': parent, 'class': 'parent', 'thumb': template(icon_tpl, {})}); } else { - title = 'My files'; + title = _('My files'); } items.forEach(item => { @@ -2866,7 +2875,17 @@ const WebDAVNavigator = (url, options) => { var row = item.is_dir ? dir_row_tpl : file_row_tpl; item.size_bytes = item.size !== null ? formatBytes(item.size).replace(/ /g, ' ') : null; - item.icon = item.is_dir ? '📁' : (item.uri.indexOf('.') > 0 ? item.uri.replace(/^.*\.(\w+)$/, '$1').toUpperCase() : ''); + + if (!item.is_dir && (pos = item.uri.lastIndexOf('.'))) { + var ext = item.uri.substr(pos+1).toUpperCase(); + + if (ext.length > 4) { + ext = ''; + } + } + + item.icon = ext ?? ''; + item.class = item.is_dir ? 'dir' : 'file'; item.modified = item.modified !== null ? formatDate(item.modified) : null; item.name = html(item.name); @@ -2883,22 +2902,40 @@ const WebDAVNavigator = (url, options) => { document.title = title; document.querySelector('main').innerHTML = template(body_tpl, {'title': html(document.title), 'base_url': base_url, 'table': table}); - var select = $('.sortorder'); - select.value = sort_order; - select.onchange = () => { - sort_order = select.value; - window.localStorage.setItem('sort_order', sort_order); + var parent_check = document.querySelector('tbody tr.parent .check'); + + if (parent_check) { + parent_check.innerHTML = ''; + } + + var column = document.querySelector('thead td[data-sort="' + sort_order + '"]').className += ' selected ' + (sort_order_desc ? 'desc' : 'asc'); + + document.querySelectorAll('thead td[data-sort] button').forEach(elm => elm.onclick = (e) => { + var new_sort_order = e.target.parentNode.dataset.sort; + + if (sort_order == new_sort_order) { + sort_order_desc = !sort_order_desc; + } + + sort_order = new_sort_order; + + window.localStorage.setItem('sort_order', new_sort_order); + window.localStorage.setItem('sort_order_desc', sort_order_desc ? '1' : '0'); reloadListing(); + }); + + document.querySelector('thead td.check input').onchange = (e) => { + document.querySelectorAll('tbody td.check input').forEach(i => i.checked = e.target.checked); }; if (!items.length) { - $('.download_all').disabled = true; + $('div.buttons .download').disabled = true; } else { - $('.download_all').onclick = download_all; + $('div.buttons .download').onclick = download_selected; } - $('.deleted_selected').onclick = () => { + $('div.buttons .delete').onclick = () => { var l = document.querySelectorAll('input[name=delete]:checked'); if (!l.length) { @@ -2924,7 +2961,7 @@ const WebDAVNavigator = (url, options) => { }; if (!root_permissions || root_permissions.indexOf('C') != -1 || root_permissions.indexOf('K') != -1) { - $('.upload').insertAdjacentHTML('afterbegin', create_buttons); + $('.buttons').insertAdjacentHTML('beforeend', create_buttons); $('.mkdir').onclick = () => { openDialog(mkdir_dialog); @@ -2959,7 +2996,7 @@ const WebDAVNavigator = (url, options) => { var fi = $('input[type=file]'); - $('.uploadfile').onclick = () => fi.click(); + $('.upload').onclick = () => fi.click(); fi.onchange = () => { if (!fi.files.length) return; @@ -2968,10 +3005,10 @@ const WebDAVNavigator = (url, options) => { }; } - Array.from($('table').rows).forEach((tr) => { + document.querySelectorAll('table tbody tr').forEach(tr => { var $$ = (a) => tr.querySelector(a); var file_url = $$('a').href; - var file_name = $$('a').innerText; + var file_name = tr.dataset.name; var dir = $$('[colspan]'); var mime = !dir ? tr.getAttribute('data-mime') : 'dir'; var buttons = $$('td.buttons div'); @@ -3045,10 +3082,20 @@ const WebDAVNavigator = (url, options) => { } var view_url, edit_url; + var allow_preview = false; + + if (mime.match(PREVIEW_TYPES) + || file_name.match(PREVIEW_EXTENSIONS)) { + allow_preview = true; + } // Don't preview PDF in mobile - if (mime.match(PREVIEW_TYPES) - && !(mime == 'application/pdf' && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/))) { + if ((mime == 'application/pdf' || file_name.match(/\.pdf/i)) + && window.navigator.userAgent.match(/Mobi|Tablet|Android|iPad|iPhone/)) { + allow_preview = false; + } + + if (allow_preview) { $$('a').onclick = () => { if (file_url.match(/\.md$/)) { openDialog('
    ', false); @@ -3076,7 +3123,7 @@ const WebDAVNavigator = (url, options) => { else if (user && password && !dir) { $$('a').onclick = () => { download(file_name, size, file_url); return false; }; } - else { + else if (!dir) { $$('a').download = file_name; } @@ -3174,7 +3221,7 @@ const WebDAVNavigator = (url, options) => { // Markdown editor if (md) { - let pre = $('#md'); + let pre = $('.md_preview'); txt.oninput = () => { pre.innerHTML = microdown.parse(txt.value); @@ -3228,6 +3275,7 @@ const WebDAVNavigator = (url, options) => { var evt, paste_upload, popstate_evt, temp_object_url; var sort_order = window.localStorage.getItem('sort_order') || 'name'; + var sort_order_desc = !!parseInt(window.localStorage.getItem('sort_order_desc'), 10); var wopi_mimes = {}, wopi_extensions = {}; const wopi_discovery_url = options.wopi_discovery_url || null; @@ -3306,7 +3354,9 @@ const WebDAVNavigator = (url, options) => { document.body.classList.remove('dragging'); dragcounter = 0; - const files = [...e.dataTransfer.items].map(item => item.getAsFile()); + var files = [...e.dataTransfer.items].map(item => item.getAsFile()); + + files = files.filter(f => f !== null); if (!files.length) return; @@ -3323,24 +3373,29 @@ if (url = document.querySelector('html').getAttribute('data-webdav-url')) { :root { --bg-color: #fff; --fg-color: #000; - --g1-color: #eee; - --g2-color: #ccc; - --g3-color: #999; - --link-color: blue; - --visited-color: purple; - --active-color: darkred; + --g1-color: #f5f7f7; + --g2-color: #ccd8d6; + --g3-color: #7c9696; + --link-color: #2e3c3a; + --visited-color: #2e3c3a; + --active-color: #c47508; +} + +* { + margin: 0; + padding: 0; } body { text-align: center; - font-size: 1.1em; - font-family: Arial, Helvetica, sans-serif; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; background: var(--bg-color); color: var(--fg-color); } a:link { color: var(--link-color); + text-decoration: none; } a:visited { @@ -3349,70 +3404,126 @@ a:visited { a:hover { color: var(--active-color); + text-decoration: underline; } table { - margin: 2em auto; + margin: 0 auto; border-collapse: collapse; - width: 90%; + width: 100%; } -th, td { - padding: .5em; +thead td { + height: 2em; + vertical-align: bottom; +} + +thead button { + width: 100%; + border: none; + text-align: inherit; + font: inherit; + background: none; + cursor: pointer; + font-size: .9em; + color: #999; + padding: .4em; +} + +thead .selected button::after { + content: "🠇"; + margin-left: .5em; +} + +thead .selected.desc button::after { + content: "🠅"; +} + +td.name, tbody th, td.date { text-align: left; - border: 2px solid var(--g2-color); } -th { +tbody tr:has(.check input:checked) { + background: #fdefdc; +} + +tbody th, tbody td { + padding: 0 .5em; + text-align: left; + border-top: 1px solid var(--g2-color); + border-bottom: 1px solid var(--g2-color); + vertical-align: middle; +} + +tbody th { word-break: break-all; + font-weight: normal; } -td.thumb { - width: 3.6em; - padding: 0; - text-align: center; +tbody th a { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 1em; +} + +tbody th img { + width: 2em; + height: 2em; + border-radius: .2em; +} + +tbody th .icon { + width: 2em; + height: 2em; + border-radius: .2em; + overflow: hidden; + background: no-repeat center center; + background-size: 100%; position: relative; } -td.thumb img { - width: 3.6em; - height: 3.6em; - display: block; -} - -td.thumb .icon { - margin: .5em; -} - -td.thumb input, td.thumb label span::before { +th .icon b { + font-size: 7pt; + font-weight: normal; + text-decoration: none; + display: inline-block; + background: #eee; + border-radius: .3em; + padding: 0 .2em; + color: #2e3c3a; position: absolute; - top: 0; - left: 0; - right: 0; bottom: 0; - margin: 0; - padding: 0; - opacity: 0; - display: block; + right: 0; + border: 1px solid #2e3c3a; +} + +td.check { + width: 1.5em; + text-align: center; +} + +td.check input { cursor: pointer; - user-select: none; + width: 1.5em; + height: 1.5em; + border: 2px solid #999; + border-radius: .2em; + background: none; + appearance: none; + outline: 0; } -td.thumb input:checked + span::before { - content: "✔️"; - font-size: 3em; - opacity: 1; - color: #fff; - text-shadow: 0px 0px 5px #000, 0px 0px 10px #000; - display: flex; - align-items: center; - justify-content: center; +td.check input:checked { + box-shadow: 0px 0px 5px darkorange; + background: no-repeat center center; + background-image: url('data:image/svg+xml;utf8,'); + background-size: contain; } - td.buttons { text-align: right; - width: 20em; + width: 5em; } td.buttons div { @@ -3420,60 +3531,65 @@ td.buttons div { flex-direction: row-reverse; } -table tr:nth-child(even) { +tbody tr:nth-child(even) { background: var(--g1-color); } -.icon { - width: 2.6em; - height: 2.6em; - display: inline-block; - border-radius: .2em; - background:var(--g3-color); - overflow: hidden; - color: var(--bg-color); - text-align: center; +tbody th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon b { - font-weight: normal; - display: inline-block; - transform: rotate(-30deg); - line-height: 2.6rem; +th .icon.JPEG, th .icon.PNG, th .icon.JPG, th .icon.GIF, th .icon.SVG, th .icon.WEBP { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.JPEG, .icon.PNG, .icon.JPG, .icon.GIF, .icon.SVG, .icon.WEBP { - background: #966; +th .icon.TXT, th .icon.MD { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.TXT, .icon.MD { - background: var(--fg-color); +th .icon.MP4, th .icon.MKV, th .icon.MP3, th .icon.M4A, th .icon.WAV, th .icon.FLAC, th .icon.OGG, th .icon.OGV, th .icon.AAC, th .icon.WEBM { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.MP4, .icon.MKV, .icon.MP3, .icon.M4A, .icon.WAV, .icon.FLAC, .icon.OGG, .icon.OGV, .icon.AAC, .icon.WEBM { - background: #669; +th .icon.document { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.document { - background: #696; +th .icon.document b { + position: relative; + background: none; + border: none; + color: #fff; + width: 100%; + height: 100%; + padding: 0; + display: inline-flex; + justify-content: center; + align-items: center; } -.icon.PDF { - background: #969; +.dir th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.dir { - background: var(--g2-color); - color: var(--fg-color); +.parent th .icon { + background-image: url('data:image/svg+xml;utf8,'); } -.icon.dir b { - font-size: 2em; - transform: none; +.dir th .icon b, .parent th .icon b { + display: none; } -.size { +td.size { text-align: right; + font-size: .9em; + color: #666; +} + +td.date { + font-size: .9em; + color: #666; + min-width: 9em; } input[type=button], input[type=submit], .btn, a.btn { @@ -3511,7 +3627,7 @@ input:focus, textarea:focus { input[type=button]:hover, input[type=submit]:hover, .btn:hover { color: var(--active-color); text-decoration: underline; - background: var(--bg-color); + background-color: var(--bg-color); box-shadow: 0px 0px 5px var(--fg-color); } @@ -3571,8 +3687,31 @@ dialog form div { text-align: center; } -.upload { - margin: 1em 0; +div.buttons { + display: flex; + justify-content: flex-end; + margin: .5em; + margin-bottom: 0; +} + +div.buttons .selected { + margin-right: auto; + display: none; +} + +body:has(tbody .check input:checked) div.buttons .selected { + display: block; +} + +div.buttons input { + overflow: visible; + text-indent: 0; + width: auto; + height: auto; + background-position: .2em center; + padding: .3em .5em; + padding-left: 2em; + background-size: 1.3em; } #mdp div, #mdp textarea { @@ -3584,7 +3723,7 @@ dialog form div { margin: 0; } -#md { +#mdp .md_preview { overflow: hidden; overflow-x: auto; } @@ -3594,10 +3733,10 @@ dialog form div { grid-template-columns: 1fr 1fr; grid-gap: .2em; background: var(--g1-color); - height: 82vh; + height: 90vh; } -#md img, #md video, #md iframe, #md embed, #md object { +.md_preview img, .md_preview video, .md_preview iframe, .md_preview embed, .md_preview object { max-width: 100%; } @@ -3614,7 +3753,7 @@ dialog.preview { overflow: hidden; } -iframe, .md_preview { +iframe, dialog.preview .md_preview { overflow: auto; position: absolute; top: 0; @@ -3655,11 +3794,21 @@ iframe, iframe body, .md_preview { max-height: 100%; } -.md_preview { +dialog.preview .md_preview { width: calc(100vw - 2em); - height: calc(100vh - 2em); + height: calc(100vh - 4em); padding: 1em; text-align: left; + padding-bottom: 2em; +} + +.md_preview ul, .md_preview ol, .md_preview p, .md_preview blockquote, .md_preview pre, +.md_preview h1, .md_preview h2, .md_preview h3, .md_preview h4, .md_preview h5, .md_preview h6 { + margin: 1rem 0; +} + +.md_preview ul, .md_preview ol, .md_preview blockquote { + margin-left: 2em; } .preview .close { @@ -3748,6 +3897,19 @@ input[name=rename], input[name=paste_name] { dialog { transition: all .3s; + margin: auto; + padding: 1em; +} + +dialog:has(#mdp) { + margin: 0; + padding: .5em; + border-radius: none; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100vh; } progress { @@ -3755,6 +3917,45 @@ progress { width: 90%; } +input.icon, .buttons a[download] { + background: #e0e6e6 no-repeat center center; + background-size: contain; + width: 1.8em; + height: 1.8em; + padding: 0; + overflow: hidden; + text-indent: 100em; + display: inline-block; +} + +input.delete { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.rename { + background-image: url('data:image/svg+xml;utf8,'); +} + +.buttons a[download], input.download { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.edit { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.mkdir { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.mkfile { + background-image: url('data:image/svg+xml;utf8,'); +} + +input.upload { + background-image: url('data:image/svg+xml;utf8,'); +} + @keyframes spin { to { transform: rotate(360deg); } } @media screen and (max-width: 800px) { @@ -3767,74 +3968,99 @@ progress { body { margin: 0; - font-size: 1em; + } + + div.buttons { + flex-wrap: wrap; + justify-content: center; + flex-direction: row; + font-size: .9em; + } + + div.buttons .selected { + margin: 0; + position: fixed; + top: 0; + left: 0; + right: 0; + padding: 1em; + background: var(--bg-color); + box-shadow: 0px 0px 2px 5px var(--g3-color); } table { - margin: 2em 0; width: 100%; display: flex; flex-direction: column; } - table tr { - display: block; - border-top: 5px solid var(--bg-color); + table tbody tr { + display: grid; + grid-template-columns: 3rem 1fr .3fr; + grid-template-areas: + "name name check" + "size date buttons"; padding: 0; - padding-left: 2em; - position: relative; - text-align: left; - min-height: 2.5em; } - table td, table th { + table tbody td, table tbody th { border: none; - display: inline-block; + display: block; padding: .2em .5em; + width: unset; } - table td.buttons { - display: block; - width: auto; - text-align: left; + table tbody th { + grid-area: name; } - td.buttons div { - display: inline-block; - } - - table td.thumb { - padding: 0; - width: 2em; - position: absolute; - left: 0; - top: 0; - bottom: 0; + table tbody td.check { + grid-area: check; + text-align: right; + width: unset; } - table th { - display: block; + table tbody td.buttons { + grid-area: buttons; + width: unset; } - .icon { - font-size: 12px; - height: 100%; - border-radius: 0; + table tbody td.date { + grid-area: date; + display: inline-block; + min-width: unset; } - .icon:not(.dir) b { - line-height: 3em; - display: block; - transform: translateX(-50%) translateY(-50%) rotate(-90deg); - font-size: 2em; - height: 3em; - position: absolute; - top: 50%; - left: 50%; + table tbody td.size { + grid-area: size; + font-size: .8em; + width: 3rem; + overflow: hidden; + text-align: center; } table th a { font-size: 1.2em; + gap: .5em; + } + + table thead tr { + display: block; + text-align: left; + } + + table thead tr td { + display: inline-block; + vertical-align: top; + } + + table thead td.check { + float: right; + margin-right: .4em; + } + + table thead button { + font-size: 1rem; } input[name=rename], input[name=paste_name] { From 24084273735e080d89f210d6eceae2acb9fe72b9 Mon Sep 17 00:00:00 2001 From: bohwaz Date: Thu, 11 Dec 2025 02:35:35 +0000 Subject: [PATCH 3/5] Add last changes FossilOrigin-Name: 671720c174bd166616acc7b0fb76ef27ad22a06868cd235512864cdbf4ec06b7 --- index.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/index.php b/index.php index 7f8da1c..6f44439 100644 --- a/index.php +++ b/index.php @@ -2204,11 +2204,11 @@ RewriteRule ^.*$ /index.php [END] if ($relative_uri == '.webdav/webdav.js') { fseek($fp, 58962, SEEK_SET); - echo fread($fp, 33638); + echo fread($fp, 33651); } else { - fseek($fp, 58962 + 33638, SEEK_SET); - echo fread($fp, 16729); + fseek($fp, 58962 + 33651, SEEK_SET); + echo fread($fp, 16767); } fclose($fp); @@ -2299,7 +2299,7 @@ const WebDAVNavigator = (url, options) => { const microdown=function(){function l(n,e,r){return"<"+n+(r?" "+Object.keys(r).map(function(n){return r[n]?n+'="'+(a(r[n])||"")+'"':""}).join(" "):"")+">"+e+""}function c(n,e){return e=n.match(/^[+-]/m)?"ul":"ol",n?"<"+e+">"+n.replace(/(?:[+-]|\d+\.) +(.*)\n?(([ \t].*\n?)*)/g,function(n,e,r){return"
  • "+g(e+"\n"+(t=r||"").replace(new RegExp("^"+(t.match(/^\s+/)||"")[0],"gm"),"").replace(o,c))+"
  • ";var t})+"":""}function e(r,t,u,c){return function(n,e){return n=n.replace(t,u),l(r,c?c(n):n)}}function t(n,u){return f(n,[//g,"\x3c!--$1--\x3e",/^("""|```)(.*)\n((.*\n)*?)\1/gm,function(n,e,r,t){return'"""'===e?l("div",p(t,u),{class:r}):u&&u.preCode?l("pre",l("code",a(t),{class:r})):l("pre",a(t),{class:r})},/(^>.*\n?)+/gm,e("blockquote",/^> ?(.*)$/gm,"$1",r),/((^|\n)\|.+)+/g,e("table",/^.*(\n\|---.*?)?$/gm,function(n,t){return e("tr",/\|(-?)([^|]*)\1(\|$)?/gm,function(n,e,r){return l(e||t?"th":"td",g(r))})(n.slice(0,n.length-(t||"").length))}),o,c,/#\[([^\]]+?)]/g,'',/^(#+) +(.*)(?:$)/gm,function(n,e,r){return l("h"+e.length,g(r))},/^(===+|---+)(?=\s*$)/gm,"
    "],p,u)}var i=this,a=function(n){return n?n.replace(/"/g,""").replace(//g,">"):""},o=/(?:(^|\n)([+-]|\d+\.) +(.*(\n[ \t]+.*)*))+/g,g=function c(n,i){var o=[];return n=(n||"").trim().replace(/`([^`]*)`/g,function(n,e){return"\\"+o.push(l("code",a(e)))}).replace(/[!&]?\[([!&]?\[.*?\)|[^\]]*?)]\((.*?)( .*?)?\)|(\w+:\/\/[$\-.+!*'()/,\w]+)/g,function(n,e,r,t,u){return u?i?n:"\\"+o.push(l("a",u,{href:u})):"&"==n[0]?(e=e.match(/^(.+),(.+),([^ \]]+)( ?.+?)?$/),"\\"+o.push(l("iframe","",{width:e[1],height:e[2],frameborder:e[3],class:e[4],src:r,title:t}))):"\\"+o.push("!"==n[0]?l("img","",{src:r,alt:e,title:t}):l("a",c(e,1),{href:r,title:t}))}),n=function r(n){return n.replace(/\\(\d+)/g,function(n,e){return r(o[Number.parseInt(e)-1])})}(i?n:r(n))},r=function t(n){return f(n,[/([*_]{1,3})((.|\n)+?)\1/g,function(n,e,r){return e=e.length,r=t(r),1"],t)},f=function(n,e,r,t){for(var u,c=0;c typeof lang_strings != 'undefined' && key in lang_strings ? lang_strings[key] : key; @@ -2884,7 +2884,7 @@ const WebDAVNavigator = (url, options) => { } } - item.icon = ext ?? ''; + item.icon = ext || ''; item.class = item.is_dir ? 'dir' : 'file'; item.modified = item.modified !== null ? formatDate(item.modified) : null; item.name = html(item.name); @@ -3498,6 +3498,10 @@ th .icon b { border: 1px solid #2e3c3a; } +th .icon b:empty { + display: none; +} + td.check { width: 1.5em; text-align: center; From a4405fe6c4260355e0879cdb828010afefd3196e Mon Sep 17 00:00:00 2001 From: bohwaz Date: Thu, 11 Dec 2025 02:38:43 +0000 Subject: [PATCH 4/5] Update screenshot FossilOrigin-Name: f87c59b6c4b25b538b11b44f2f225da74a0397e20f7a7c21ad0f2708a53bd27b --- README.md | 2 +- scr.webp | Bin 0 -> 61122 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 scr.webp diff --git a/README.md b/README.md index 252ba01..3d9771a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ If you drop the [`index.php`](https://fossil.kd2.org/picodav/doc/trunk/index.php) file in a directory of your web-server, it will make the contents of this directory available via WebDAV, and will also provide a nice web UI to manage the files, using [WebDAV Manager.js](https://fossil.kd2.org/webdav-manager/). -![Web UI screenshot](https://raw.githubusercontent.com/kd2org/webdav-manager.js/main/scr_desktop.png) +![Web UI screenshot](scr.webp) * Single-file WebDAV server! Only 85 KB! * No database! diff --git a/scr.webp b/scr.webp new file mode 100644 index 0000000000000000000000000000000000000000..3770a69a286f376ee2c385f5d6141e346126c1c6 GIT binary patch literal 61122 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 XLYZ1c4LS%?gT(5Hys*}@P}Fb$+5GrO literal 0 HcmV?d00001 From 7b63ba2606aa2dbb025a6b33598776652954e91d Mon Sep 17 00:00:00 2001 From: bohwaz Date: Thu, 11 Dec 2025 02:39:28 +0000 Subject: [PATCH 5/5] Update size FossilOrigin-Name: 614905965c3791fef7097d0696a011b6d942df51987e6233665921bbdae44220 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d9771a..30bc7f0 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ If you drop the [`index.php`](https://fossil.kd2.org/picodav/doc/trunk/index.php ![Web UI screenshot](scr.webp) -* Single-file WebDAV server! Only 85 KB! +* Single-file WebDAV server! Only 110 KB! * No database! * Very fast and lightweight! * Compatible with tons of apps!